diff --git a/.changeset/skills-codex-global-and-discovery.md b/.changeset/skills-codex-global-and-discovery.md new file mode 100644 index 0000000..2317318 --- /dev/null +++ b/.changeset/skills-codex-global-and-discovery.md @@ -0,0 +1,7 @@ +--- +"@sawala/cli": patch +--- + +`sawala skills install --target codex --global` now writes to both `~/.agents/skills` and `~/.codex/skills`. Codex's documentation names the first while real installs use the second, and rather than pick one and risk a global install landing where nothing reads it, both are written. Project-level installs are unchanged — `.agents/skills` is well attested there. + +The `sawala-cli` skill's description now also mentions listing uploaded files, assets, and media, and reading forms and submissions. It absorbs the Formulir and Berkasna surfaces, but previously named them only as products — so "list my files" or "read the form submissions" matched nothing unless the user happened to say "Berkasna" or "Formulir". diff --git a/packages/sawala/skills/sawala-cli/SKILL.md b/packages/sawala/skills/sawala-cli/SKILL.md index e9ccd57..818b089 100644 --- a/packages/sawala/skills/sawala-cli/SKILL.md +++ b/packages/sawala/skills/sawala-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: sawala-cli -description: Orientation for driving Sawala Cloud from an agent — the `sawala` and `kodena` CLIs and the `@sawala/mcp` / `@sawala/kodena-mcp` MCP servers. Use when a task mentions Sawala, Kodena, Kontena, Datana, Ajena, Formulir, Berkasna, Sebar, Tugasna, or Akuna; when deciding whether to shell out or call an MCP tool; or when a Sawala command fails with an auth, org, or project error. +description: Orientation for driving Sawala Cloud from an agent — the `sawala` and `kodena` CLIs and the `@sawala/mcp` / `@sawala/kodena-mcp` MCP servers. Use when a task mentions Sawala, Kodena, Kontena, Datana, Ajena, Formulir, Berkasna, Sebar, Tugasna, or Akuna; when listing or fetching uploaded files, assets, or media; when reading forms or form submissions; when deciding whether to shell out or call an MCP tool; or when a Sawala command fails with an auth, org, or project error. metadata: sawala-cli-version: "0.13.0" --- diff --git a/packages/sawala/src/lib/skills.ts b/packages/sawala/src/lib/skills.ts index 1e9e211..28c56be 100644 --- a/packages/sawala/src/lib/skills.ts +++ b/packages/sawala/src/lib/skills.ts @@ -119,8 +119,17 @@ export function resolveTargetDirs(opts: { for (const t of targets) { switch (t) { case 'agents': + dirs.add(join(base, '.agents', 'skills')) + break case 'codex': dirs.add(join(base, '.agents', 'skills')) + // Sources disagree on where Codex reads *personal* skills: its own docs + // say ~/.agents/skills, while installs in the wild put them in + // ~/.codex/skills ($CODEX_HOME/skills). We could not verify which is + // live. Writing both is cheap and cannot fail silently; picking one and + // being wrong means --global installs into a directory nothing reads. + // Project-level is not in doubt — .agents/skills is well attested. + if (isGlobal) dirs.add(join(home, '.codex', 'skills')) break case 'claude': dirs.add(join(base, '.claude', 'skills')) @@ -133,6 +142,7 @@ export function resolveTargetDirs(opts: { dirs.add(join(base, '.agents', 'skills')) dirs.add(join(base, '.claude', 'skills')) dirs.add(isGlobal ? join(home, '.copilot', 'skills') : join(cwd, '.github', 'skills')) + if (isGlobal) dirs.add(join(home, '.codex', 'skills')) break default: { const bad: never = t diff --git a/packages/sawala/test/skills.test.ts b/packages/sawala/test/skills.test.ts index db7bc34..e8bae83 100644 --- a/packages/sawala/test/skills.test.ts +++ b/packages/sawala/test/skills.test.ts @@ -43,11 +43,31 @@ describe('resolveTargetDirs', () => { const cwd = '/repo' const home = '/home/dev' - it('maps agents and codex to the same .agents/skills', () => { + it('maps agents and codex to the same .agents/skills at project level', () => { expect(resolveTargetDirs({ targets: ['agents'], cwd, home })).toEqual(['/repo/.agents/skills']) expect(resolveTargetDirs({ targets: ['codex'], cwd, home })).toEqual(['/repo/.agents/skills']) }) + // Sources disagree on Codex's personal skills directory (~/.agents/skills per + // its docs, ~/.codex/skills in the wild) and we could not verify which is + // live. Writing both is the safe choice: picking one and being wrong means a + // --global install lands where nothing reads it. + it('writes both candidate Codex directories under --global', () => { + expect(resolveTargetDirs({ targets: ['codex'], global: true, cwd, home })).toEqual([ + '/home/dev/.agents/skills', + '/home/dev/.codex/skills', + ]) + }) + + it('includes the Codex home directory in a global `all`, but not a project `all`', () => { + expect(resolveTargetDirs({ targets: ['all'], global: true, cwd, home })).toContain( + '/home/dev/.codex/skills', + ) + expect(resolveTargetDirs({ targets: ['all'], cwd, home })).not.toContain( + '/repo/.codex/skills', + ) + }) + it('maps claude and copilot to their own project directories', () => { expect(resolveTargetDirs({ targets: ['claude'], cwd, home })).toEqual(['/repo/.claude/skills']) expect(resolveTargetDirs({ targets: ['copilot'], cwd, home })).toEqual(['/repo/.github/skills'])