From a0de4958a0147e844553fdfae508dbf5eb55552a Mon Sep 17 00:00:00 2001 From: Sutisna Date: Tue, 4 Aug 2026 13:45:52 +0200 Subject: [PATCH] fix(sawala): cover both Codex global skill dirs; make Formulir/Berkasna discoverable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --target codex --global wrote only ~/.agents/skills. Codex's docs name that path but installs in the wild use ~/.codex/skills, and we could not verify which is live — so write both. A wrong single choice installs into a directory nothing reads, which fails silently. Project-level is unchanged. sawala-cli absorbed Formulir and Berkasna but its description named them only as products, so task vocabulary ('list my files', 'read the form submissions') matched no skill. Adds that vocabulary to the description. --- .../skills-codex-global-and-discovery.md | 7 ++++++ packages/sawala/skills/sawala-cli/SKILL.md | 2 +- packages/sawala/src/lib/skills.ts | 10 +++++++++ packages/sawala/test/skills.test.ts | 22 ++++++++++++++++++- 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .changeset/skills-codex-global-and-discovery.md 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'])