Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/skills-codex-global-and-discovery.md
Original file line number Diff line number Diff line change
@@ -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".
2 changes: 1 addition & 1 deletion packages/sawala/skills/sawala-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -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"
---
Expand Down
10 changes: 10 additions & 0 deletions packages/sawala/src/lib/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand All @@ -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
Expand Down
22 changes: 21 additions & 1 deletion packages/sawala/test/skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
Loading