diff --git a/README.md b/README.md index 25906294..54f9fd8a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ # CLI to install and manage agent skills -**agent-skill-manager** (`asm`) is a scriptable CLI built for AI agents and automation — install, search, audit, and organize skills across Claude Code, Codex, Cursor, and 16 more tools. Every command supports `--json` and `--yes` for non-interactive use. An optional TUI (`asm`) is available for local browsing. +**agent-skill-manager** (`asm`) is a scriptable CLI built for AI agents and automation — install, search, audit, and organize skills across Claude Code, Codex, Cursor, and 18 more tools. Every command supports `--json` and `--yes` for non-interactive use. An optional TUI (`asm`) is available for local browsing. [**Get Started**](#getting-started) · [**Browse 4,300+ skills**](https://luongnv.com/asm/#/skills) · [**Full docs**](#documentation) @@ -25,7 +25,7 @@ | Pain | Without `asm` | With `asm` | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| Scattered installs | Same skill copied into `~/.claude/skills/`, `~/.codex/skills/`, `~/.cursor/rules/` — different versions, no single view | One `asm list` across all 19 providers and scopes | +| Scattered installs | Same skill copied into `~/.claude/skills/`, `~/.codex/skills/`, `~/.cursor/rules/` — different versions, no single view | One `asm list` across all 21 providers and scopes | | No inventory | `ls` through hidden dirs; no idea what is installed, duplicated, or outdated | `asm search`, `asm inspect`, `asm stats`, `asm audit` | | Invisible context cost | Every installed skill's description is resident in the agent's prompt on every message, whether or not it ever fires | `asm stats --tokens`, `asm audit residency` | | Install just to read once | Installing is the only way to get a skill in front of an agent — and it stays resident forever afterwards | `asm get ` prints the body and installs nothing | @@ -38,7 +38,7 @@ | | | | ------------------- | ------------------------------------------------------------------------------ | | **Catalog** | 4,672 skills from 58 repos — [browse online](https://luongnv.com/asm/#/skills) | -| **Providers** | 19 agents (Claude, Codex, Cursor, Windsurf, Copilot, …) | +| **Providers** | 21 providers (Agents, Claude Code, Pi, OpenCode, Codex, …) | | **Agent-ready CLI** | `--json`, `--yes`, `--machine` on list, search, install, audit, eval | | **Security** | Pre-install scan for shell exec, network access, credential exposure | | **License** | MIT — no accounts, no telemetry | @@ -281,7 +281,7 @@ MIT licensed. No accounts or paywalls. Yes. Commands return structured JSON (`--json`), accept non-interactive flags (`--yes`, `--machine`), and map to discrete actions an agent can chain — list inventory, search catalog, install, audit, uninstall. **Which agents are supported?** -19 providers: Claude Code, Codex, OpenClaw, Cursor, Windsurf, Cline, Roo Code, Continue, GitHub Copilot, Aider, OpenCode, Zed, Augment, Amp, Gemini CLI, Google Antigravity, Pi, Hermes, and a generic Agents provider. Disable any via `asm config edit`. +21 providers: a generic Agents provider (supported by most harnesses except Claude Code), Claude Code, Pi, OpenCode, Codex, Oh My Pi, Grok CLI, Hermes, OpenClaw, Cursor, GitHub Copilot, Windsurf, Google Antigravity, Gemini CLI, Cline, Roo Code, Continue, Aider, Zed, Augment, and Amp. Disable any via `asm config edit`. **What about the TUI?** Run `asm` with no args for an optional local browser. The CLI is the primary interface for scripts, CI, and agent tool calls. @@ -509,29 +509,31 @@ asm install github:anthropics/skills --all
Supported agent tools -19 built-in providers, all enabled by default. Disable via `asm config edit`. - -| Tool | Global Path | Project Path | -| ------------------ | --------------------------------- | ----------------------- | -| Claude Code | `~/.claude/skills/` | `.claude/skills/` | -| Codex | `~/.codex/skills/` | `.codex/skills/` | -| OpenClaw | `~/.openclaw/skills/` | `.openclaw/skills/` | -| Agents (generic) | `~/.agents/skills/` | `.agents/skills/` | -| Cursor | `~/.cursor/rules/` | `.cursor/rules/` | -| Windsurf | `~/.windsurf/rules/` | `.windsurf/rules/` | -| Cline | `~/Documents/Cline/Rules/` | `.clinerules/` | -| Roo Code | `~/.roo/rules/` | `.roo/rules/` | -| Continue | `~/.continue/rules/` | `.continue/rules/` | -| GitHub Copilot | `~/.github/instructions/` | `.github/instructions/` | -| Aider | `~/.aider/skills/` | `.aider/skills/` | -| OpenCode | `~/.config/opencode/skills/` | `.opencode/skills/` | -| Zed | `~/.config/zed/prompt_overrides/` | `.zed/rules/` | -| Augment | `~/.augment/rules/` | `.augment/rules/` | -| Amp | `~/.amp/skills/` | `.amp/skills/` | -| Gemini CLI | `~/.gemini/skills/` | `.gemini/skills/` | -| Google Antigravity | `~/.antigravity/skills/` | `.antigravity/skills/` | -| Pi | `~/.pi/skills/` | `.pi/skills/` | -| Hermes | `~/.hermes/skills/` | `.hermes/skills/` | +21 built-in providers, all enabled by default. Disable via `asm config edit`. + +| Tool | Global Path | Project Path | +| ------------------------------------------ | --------------------------------- | ----------------------- | +| Agents (most harnesses except Claude Code) | `~/.agents/skills/` | `.agents/skills/` | +| Claude Code | `~/.claude/skills/` | `.claude/skills/` | +| Pi | `~/.pi/skills/` | `.pi/skills/` | +| OpenCode | `~/.config/opencode/skills/` | `.opencode/skills/` | +| Codex | `~/.codex/skills/` | `.codex/skills/` | +| Oh My Pi | `~/.omp/agent/skills/` | `.omp/skills/` | +| Grok CLI | `~/.grok/skills/` | `.grok/skills/` | +| Hermes | `~/.hermes/skills/` | `.hermes/skills/` | +| OpenClaw | `~/.openclaw/skills/` | `.openclaw/skills/` | +| Cursor | `~/.cursor/rules/` | `.cursor/rules/` | +| GitHub Copilot | `~/.github/instructions/` | `.github/instructions/` | +| Windsurf | `~/.windsurf/rules/` | `.windsurf/rules/` | +| Google Antigravity | `~/.antigravity/skills/` | `.antigravity/skills/` | +| Gemini CLI | `~/.gemini/skills/` | `.gemini/skills/` | +| Cline | `~/Documents/Cline/Rules/` | `.clinerules/` | +| Roo Code | `~/.roo/rules/` | `.roo/rules/` | +| Continue | `~/.continue/rules/` | `.continue/rules/` | +| Aider | `~/.aider/skills/` | `.aider/skills/` | +| Zed | `~/.config/zed/prompt_overrides/` | `.zed/rules/` | +| Augment | `~/.augment/rules/` | `.augment/rules/` | +| Amp | `~/.amp/skills/` | `.amp/skills/` | Add custom providers in config. @@ -964,7 +966,7 @@ On first run, config is created at `~/.config/agent-skill-manager/config.json`: } ``` -- All 19 providers are enabled by default +- All 21 providers are enabled by default - Set `"enabled": false` to skip a provider you don't use - Add arbitrary directories via `customPaths` - Manage via `asm config show|path|reset|edit` or press `c` in the TUI diff --git a/src/config.test.ts b/src/config.test.ts index 3ef94d4b..ef0ce007 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -24,22 +24,24 @@ describe("getDefaultConfig", () => { expect(config.version).toBe(1); }); - it("returns 19 default providers", () => { + it("returns 21 default providers", () => { const config = getDefaultConfig(); - expect(config.providers).toHaveLength(19); + expect(config.providers).toHaveLength(21); }); - it("includes all 19 default providers in priority order", () => { + it("includes all 21 default providers in priority order", () => { const config = getDefaultConfig(); const names = config.providers.map((p) => p.name); expect(names).toEqual([ + "agents", "claude", - "codex", - "opencode", "pi", + "opencode", + "codex", + "omp", + "grok", "hermes", "openclaw", - "agents", "cursor", "copilot", "windsurf", @@ -55,9 +57,9 @@ describe("getDefaultConfig", () => { ]); }); - it("all 19 providers are enabled by default", () => { + it("all 21 providers are enabled by default", () => { const config = getDefaultConfig(); - expect(config.providers).toHaveLength(19); + expect(config.providers).toHaveLength(21); expect(config.providers.every((p) => p.enabled)).toBe(true); }); @@ -76,7 +78,7 @@ describe("getDefaultConfig", () => { const a = getDefaultConfig(); const b = getDefaultConfig(); a.providers[0].name = "mutated"; - expect(b.providers[0].name).toBe("claude"); + expect(b.providers[0].name).toBe("agents"); }); }); @@ -202,7 +204,7 @@ describe("config backup on corruption", () => { // Should return defaults expect(config.version).toBe(1); - expect(config.providers).toHaveLength(19); + expect(config.providers).toHaveLength(21); // Should have created backup const backup = await readFile(backupPath, "utf-8"); @@ -446,13 +448,68 @@ describe("mergeWithDefaults priority-order insertion", () => { const config = await loadConfig(); const piIdx = config.providers.findIndex((p) => p.name === "pi"); + const claudeIdx = config.providers.findIndex((p) => p.name === "claude"); const opencodeIdx = config.providers.findIndex( (p) => p.name === "opencode", ); - const hermesIdx = config.providers.findIndex((p) => p.name === "hermes"); - expect(piIdx).toBeGreaterThan(opencodeIdx); - expect(piIdx).toBeLessThan(hermesIdx); + // Post-#617 canonical slot: pi sits between claude and opencode. + expect(piIdx).toBeGreaterThan(claudeIdx); + expect(piIdx).toBeLessThan(opencodeIdx); + }); + + it("canonicalizes a saved config that predates the #617 reorder", async () => { + await mkdir(dirname(configPath), { recursive: true }); + // Pre-#617 saved order: claude first, agents seventh, no omp/grok. + const legacyNames = [ + "claude", + "codex", + "opencode", + "pi", + "hermes", + "openclaw", + "agents", + "cursor", + "copilot", + "windsurf", + "antigravity", + "gemini", + "cline", + "roocode", + "continue", + "aider", + "zed", + "augment", + "amp", + ]; + const partial = { + version: 1, + providers: legacyNames.map((name) => ({ + name, + label: name, + global: `~/.${name}/skills`, + project: `.${name}/skills`, + enabled: true, + })), + preferences: { defaultScope: "both", defaultSort: "name" }, + }; + await writeFile(configPath, JSON.stringify(partial), "utf-8"); + + const config = await loadConfig(); + const names = config.providers.map((p) => p.name); + + expect(names.slice(0, 7)).toEqual([ + "agents", + "claude", + "pi", + "opencode", + "codex", + "omp", + "grok", + ]); + expect(names).toHaveLength(21); + // Nothing the user had is dropped by the reorder. + for (const name of legacyNames) expect(names).toContain(name); }); it("preserves user-added custom providers in place when adding new defaults", async () => { diff --git a/src/config.ts b/src/config.ts index bcfb0b52..7badc1ce 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,7 +24,14 @@ export function getConfigDir(): string { } const DEFAULT_PROVIDERS: ProviderConfig[] = [ - // ── Priority providers (ordered by user preference) ── + // ── Priority providers (ordered by user preference, #617) ── + { + name: "agents", + label: "Agents", + global: "~/.agents/skills", + project: ".agents/skills", + enabled: true, + }, { name: "claude", label: "Claude Code", @@ -33,10 +40,10 @@ const DEFAULT_PROVIDERS: ProviderConfig[] = [ enabled: true, }, { - name: "codex", - label: "Codex", - global: "~/.codex/skills", - project: ".codex/skills", + name: "pi", + label: "Pi", + global: "~/.pi/skills", + project: ".pi/skills", enabled: true, }, { @@ -47,12 +54,27 @@ const DEFAULT_PROVIDERS: ProviderConfig[] = [ enabled: true, }, { - name: "pi", - label: "Pi", - global: "~/.pi/skills", - project: ".pi/skills", + name: "codex", + label: "Codex", + global: "~/.codex/skills", + project: ".codex/skills", + enabled: true, + }, + { + name: "omp", + label: "Oh My Pi", + global: "~/.omp/agent/skills", + project: ".omp/skills", enabled: true, }, + { + name: "grok", + label: "Grok CLI", + global: "~/.grok/skills", + project: ".grok/skills", + enabled: true, + }, + // ── Additional providers ── { name: "hermes", label: "Hermes", @@ -67,14 +89,6 @@ const DEFAULT_PROVIDERS: ProviderConfig[] = [ project: ".openclaw/skills", enabled: true, }, - // ── Additional providers ── - { - name: "agents", - label: "Agents", - global: "~/.agents/skills", - project: ".agents/skills", - enabled: true, - }, { name: "cursor", label: "Cursor", @@ -264,6 +278,28 @@ function mergeWithDefaults(config: Partial): AppConfig { existingNames.add(defaultProvider.name); } + // Canonicalize order so DEFAULT_PROVIDERS ordering wins for built-ins (#617). + // Reordering the defaults must reach users who already have a saved config, + // not just fresh installs. A user-added provider sorts just after the + // built-in that preceded it at this point, which is after the new defaults + // the insertion loop above already slotted in ahead of it. + const defaultRank = new Map( + defaults.providers.map((p, i) => [p.name, i] as const), + ); + const sortKey = new Map(); + let lastRank = -1; + for (const p of providers) { + const rank = defaultRank.get(p.name); + if (rank === undefined) { + sortKey.set(p, lastRank + 0.5); + } else { + lastRank = rank; + sortKey.set(p, rank); + } + } + // Stable sort keeps consecutive user-added providers in their saved order. + providers.sort((a, b) => sortKey.get(a)! - sortKey.get(b)!); + return { version: config.version ?? defaults.version, providers, diff --git a/src/installer-link.ts b/src/installer-link.ts index 82306ea4..f86549d7 100644 --- a/src/installer-link.ts +++ b/src/installer-link.ts @@ -211,8 +211,17 @@ export async function resolveProvider( const pickerItems = config.providers.map((p) => ({ label: `${p.label} (${p.name})`, - hint: p.global, - checked: savedSet ? savedSet.has(p.name) : p.name === "agents", + // The Claude Code exception leads so it survives hint truncation on + // narrow terminals (#617) — a 15–25 col hint still names the exception. + hint: + p.name === "agents" + ? `except Claude Code; most harnesses — ${p.global}` + : p.global, + // First-time setup pre-checks Agents + Claude Code (#617), but never a + // provider the user has disabled — a pre-checked row installs on Enter. + checked: savedSet + ? savedSet.has(p.name) + : p.enabled && (p.name === "agents" || p.name === "claude"), })); const selectedIndices = await checkboxPicker({ items: pickerItems }); diff --git a/src/installer.test.ts b/src/installer.test.ts index f200aeb6..582bdf36 100644 --- a/src/installer.test.ts +++ b/src/installer.test.ts @@ -940,7 +940,7 @@ describe("resolveProvider", () => { ); }); - test("interactive picker: no saved tools defaults agents to checked", async () => { + test("interactive picker: no saved tools defaults agents + claude to checked", async () => { const agents: ProviderConfig = { name: "agents", label: "Agents", @@ -961,9 +961,18 @@ describe("resolveProvider", () => { await resolveProvider(config, null, true); expect(capturedItems).toHaveLength(3); - expect((capturedItems[0] as { checked: boolean }).checked).toBe(false); + // claude + agents pre-checked on first-time setup (#617); codex is not. + expect((capturedItems[0] as { checked: boolean }).checked).toBe(true); expect((capturedItems[1] as { checked: boolean }).checked).toBe(true); expect((capturedItems[2] as { checked: boolean }).checked).toBe(false); + // The agents row leads with the Claude Code exception so it survives + // hint truncation on a narrow terminal (#617). + expect((capturedItems[1] as { hint: string }).hint).toBe( + "except Claude Code; most harnesses — ~/.agents/skills", + ); + expect((capturedItems[0] as { hint: string }).hint).toBe( + "~/.claude/skills", + ); }); test("interactive picker: saved tools override default checked state", async () => { @@ -1026,7 +1035,7 @@ describe("resolveProvider", () => { expect(savedNames).toEqual(["claude", "agents"]); }); - test("interactive picker: empty saved tools array falls back to agents default", async () => { + test("interactive picker: empty saved tools array falls back to agents + claude default", async () => { let capturedItems: unknown[] = []; const agents: ProviderConfig = { name: "agents", @@ -1050,19 +1059,19 @@ describe("resolveProvider", () => { preferences: { defaultScope: "both", defaultSort: "name", - selectedTools: [], // empty array — should fall back to agents default + selectedTools: [], // empty array — falls back to the first-run default }, }; await resolveProvider(config, null, true); expect(capturedItems).toHaveLength(3); - // Falls back to agents default - expect((capturedItems[0] as { checked: boolean }).checked).toBe(false); + // Falls back to the agents + claude first-run default (#617) + expect((capturedItems[0] as { checked: boolean }).checked).toBe(true); expect((capturedItems[1] as { checked: boolean }).checked).toBe(true); expect((capturedItems[2] as { checked: boolean }).checked).toBe(false); }); - test("interactive picker: all items default to deselected when no agents and no saved", async () => { + test("interactive picker: only claude is pre-checked when no agents and no saved", async () => { let capturedItems: unknown[] = []; const pickerFn = vi.fn((opts: { items: unknown[] }) => { capturedItems = opts.items; @@ -1076,7 +1085,8 @@ describe("resolveProvider", () => { await resolveProvider(config, null, true); expect(capturedItems).toHaveLength(3); - expect((capturedItems[0] as { checked: boolean }).checked).toBe(false); + // No agents provider present, so only claude is pre-checked (#617). + expect((capturedItems[0] as { checked: boolean }).checked).toBe(true); expect((capturedItems[1] as { checked: boolean }).checked).toBe(false); expect((capturedItems[2] as { checked: boolean }).checked).toBe(false); }); @@ -1099,6 +1109,8 @@ describe("resolveProvider", () => { const result = await resolveProvider(config, null, true); expect(result.provider.name).toBe("claude"); expect(capturedItems).toHaveLength(2); + // Disabled providers still show, but the first-run pre-check never + // ticks one the user turned off (#617). expect((capturedItems[0] as { checked: boolean }).checked).toBe(false); expect((capturedItems[1] as { checked: boolean }).checked).toBe(false); }); diff --git a/website-src/src/__tests__/docs-page.test.jsx b/website-src/src/__tests__/docs-page.test.jsx index e5267894..ecd0b7e9 100644 --- a/website-src/src/__tests__/docs-page.test.jsx +++ b/website-src/src/__tests__/docs-page.test.jsx @@ -118,4 +118,22 @@ describe("DocsPage", () => { expect(Element.prototype.scrollIntoView).toHaveBeenCalled(); expect(window.location.hash).toMatch(/s=eval/); }); + + it("lists providers in picker order with the Agents harness note", () => { + renderDocs(); + const section = document.getElementById("tools"); + const names = [...section.querySelectorAll("tbody tr td:first-child")].map( + (td) => td.textContent, + ); + expect(names.slice(0, 7)).toEqual([ + "Agents — most harnesses except Claude Code", + "Claude Code", + "Pi", + "OpenCode", + "Codex", + "Oh My Pi", + "Grok CLI", + ]); + expect(names).toHaveLength(21); + }); }); diff --git a/website-src/src/pages/DocsPage.jsx b/website-src/src/pages/DocsPage.jsx index 34892e64..91e1610a 100644 --- a/website-src/src/pages/DocsPage.jsx +++ b/website-src/src/pages/DocsPage.jsx @@ -796,7 +796,7 @@ metadata:

Supported Agent Tools

- asm ships with 19 built-in providers, all enabled by + asm ships with 21 built-in providers, all enabled by default:

@@ -808,28 +808,61 @@ metadata: + + + + + + + - + + - - - - - -
@@ -890,7 +897,7 @@ metadata:

Config file is created at{" "} ~/.config/agent-skill-manager/config.json on first run - with 19 default providers. + with 21 default providers.

@@ -1727,10 +1734,13 @@ function FieldRow({ name, req, desc }) { ); } -function ToolRow({ tool, g, p }) { +function ToolRow({ tool, g, p, note }) { return ( - + diff --git a/website-src/src/pages/LandingPage.jsx b/website-src/src/pages/LandingPage.jsx index 26660c97..494892cc 100644 --- a/website-src/src/pages/LandingPage.jsx +++ b/website-src/src/pages/LandingPage.jsx @@ -10,7 +10,7 @@ import { prefersReducedMotion } from "../lib/motion.js"; const REPO_URL = "https://github.com/luongnv89/asm"; const NPM_CMD = "npm install -g agent-skill-manager"; -const PROVIDER_COUNT = 19; +const PROVIDER_COUNT = 21; /** * Marketing landing page (route `/`). The catalog lives at `/skills`. @@ -376,7 +376,7 @@ function Solution() { { icon: "05", head: "Works with every major agent", - body: "19 providers built in: Claude Code, Codex, Cursor, Windsurf, Cline, Roo, Continue, Copilot, Aider, Zed, Gemini CLI, and more. Add custom ones in seconds.", + body: "21 providers built in: Agents, Claude Code, Pi, OpenCode, Codex, Oh My Pi, Grok CLI, Cursor, Windsurf, Cline, Gemini CLI, and more. Add custom ones in seconds.", }, { icon: "06",
{tool} + {tool} + {note ? ` — ${note}` : null} + {g}