diff --git a/AGENTS.md b/AGENTS.md index b9ae299..bc0de99 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,7 +75,7 @@ Host support is data-first. - Put paths and aliases in `spec/hosts.json` whenever possible. - Do not add host-specific branching unless the generic resolver cannot express the host. -- `projectSkillsDirs` and `userSkillsDirs` are ordered; the first path is the canonical install target. +- `projectSkillsDirs` and `userSkillsDirs` are ordered; reuse the first existing compatible path, or fall back to the first canonical path. - Project paths must be relative. User paths must start with `~/`. - A host may be project-only or user-only. - Multiple selected hosts may resolve to the same target directory; copy once and report all matching hosts. diff --git a/Makefile b/Makefile index 59a40c6..d74871b 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ GO_FILES := $(shell find $(GO_DIR) $(GO_COBRA_DIR) $(EXAMPLE_GO_DIR) -name '*.go # ── Quality ────────────────────────────────────────────────────────────────── -.PHONY: check test test-ts test-go test-go-cobra test-rust test-python fmt fmt-ts fmt-go fmt-rust fmt-python +.PHONY: check test test-ts test-go test-go-cobra test-go-release test-rust test-python fmt fmt-ts fmt-go fmt-rust fmt-python check: ## Full parity gate node scripts/check.mjs @@ -31,10 +31,13 @@ test-ts: ## Run TypeScript tests pnpm --dir $(TS_DIR) test test-go: ## Run Go SDK tests - cd $(GO_DIR) && go test ./... + cd $(GO_DIR) && GOWORK=off go test ./... test-go-cobra: ## Run Go Cobra adapter tests - cd $(GO_COBRA_DIR) && go test ./... + sh scripts/check-go-cobra.sh + +test-go-release: ## Verify packaged Go modules from an external consumer + sh scripts/check-go-release.sh test-rust: ## Run Rust SDK tests cargo test --manifest-path $(RUST_DIR)/Cargo.toml @@ -62,11 +65,13 @@ fmt-python: ## Lint and format Python code .PHONY: generate generate-check -generate: ## Refresh generated host constants +generate: ## Refresh generated host constants and Go test fixtures node scripts/sync-hosts.mjs + node scripts/sync-go-testdata.mjs -generate-check: ## Verify generated host constants +generate-check: ## Verify generated host constants and Go test fixtures node scripts/sync-hosts.mjs --check + node scripts/sync-go-testdata.mjs --check # ── Examples ───────────────────────────────────────────────────────────────── diff --git a/docs/API.md b/docs/API.md index 2727443..4f39102 100644 --- a/docs/API.md +++ b/docs/API.md @@ -415,8 +415,8 @@ In TTY mode, zero detected hosts prompts from all supported hosts. One detected Selector semantics: -- `scope: "user"` installs into the first `userSkillsDirs` path for each host. -- `scope: "project"` installs into the first `projectSkillsDirs` path for each host. +- `scope: "user"` reuses the first existing `userSkillsDirs` path, or creates the first canonical path when none exist. +- `scope: "project"` reuses the first existing `projectSkillsDirs` path, or creates the first canonical path when none exist. - `agents: "auto"` uses host detection. - `agents: "*"` selects every host adapter. - explicit agents select canonical host ids or aliases. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index a67d090..6489819 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -55,6 +55,16 @@ Do not tag the release branch. Do not publish packages by hand during the normal The release workflow publishes npm, PyPI, and crates.io packages, creates the `go/vX.Y.Z` and `go-cobra/vX.Y.Z` tags, creates the GitHub Release, and runs the public install smoke check. +The Go modules share the same release version. No workspace or local replacement +is committed. Source checks copy the modules to a temporary directory and add a +one-off replacement there so the Cobra adapter can test unreleased core APIs. +Release checks disable workspace resolution and verify both module archives from +a temporary consumer: + +```bash +make test-go-release +``` + ## First npm Release npm trusted publishing is configured in the npm package settings. For the first package version, the package settings may not exist yet. diff --git a/docs/host-adapter-contract.md b/docs/host-adapter-contract.md index 99d50dc..f991bba 100644 --- a/docs/host-adapter-contract.md +++ b/docs/host-adapter-contract.md @@ -8,7 +8,7 @@ Each host entry describes where a local Agent Skill can be installed and how the `projectSkillsDirs` and `userSkillsDirs` are ordered. -The first path is the canonical install target for that host. Later paths are compatible discovery roots that the host also scans. SDKs should install to the first path unless a caller explicitly requests another supported path. +The first path is the canonical install target for that host. Later paths are compatible roots that the host also scans. SDKs reuse the first supported path that already exists, preserving an established installation location. If none exist, SDKs create the first canonical path. Project paths must be relative paths. User paths must be home-relative paths beginning with `~/`. All adapter paths use `/` separators and non-empty segments; `..`, backslashes, colons, and NUL bytes are invalid. @@ -27,6 +27,8 @@ Aliases are for ecosystem compatibility only. SDK result objects should return t Detection should check path existence. Entries may be home-relative paths such as `~/.codex` or project-relative paths such as `.replit`. +SDKs check every non-generic detection path for a host. Every `detect` entry must be evidence that the specific host is present; a compatibility root that merely belongs to another host belongs in `projectSkillsDirs` or `userSkillsDirs`, not in `detect`. Shared roots and files such as `~/.agents` and `package.json` do not identify a specific host by themselves and must not cause that host to be auto-selected. + Detection must not run host binaries, start editors, mutate configuration, or require network access. Explicit host selection should still resolve install targets even when detection paths are absent. diff --git a/go-cobra/go.mod b/go-cobra/go.mod index e5e8257..32c7a5c 100644 --- a/go-cobra/go.mod +++ b/go-cobra/go.mod @@ -11,5 +11,3 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect ) - -replace github.com/lathe-cli/kitup/go => ../go diff --git a/go-cobra/go.sum b/go-cobra/go.sum index ffae55e..abfc725 100644 --- a/go-cobra/go.sum +++ b/go-cobra/go.sum @@ -1,6 +1,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/lathe-cli/kitup/go v0.1.3 h1:7eEW8mDr5MbXFaTwr2dlnH8ebg+3Kmhj2/j4d9YfQNQ= +github.com/lathe-cli/kitup/go v0.1.3/go.mod h1:dZgJDmFRKjaFBZyaP1qlzOB9IEafnf1/ai4KX4hMA4c= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= diff --git a/go-cobra/skill_test.go b/go-cobra/skill_test.go index 15a1639..4096048 100644 --- a/go-cobra/skill_test.go +++ b/go-cobra/skill_test.go @@ -6,16 +6,25 @@ import ( "path/filepath" "strings" "testing" + "testing/fstest" kitup "github.com/lathe-cli/kitup/go" ) +func testBundle() kitup.SkillBundle { + return kitup.FSBundle(fstest.MapFS{ + "basic/SKILL.md": { + Data: []byte("---\nname: basic\ndescription: Basic skill.\n---\n"), + }, + }, "basic") +} + func TestSkillCommandInstallsWithCoreFlags(t *testing.T) { home := t.TempDir() var out bytes.Buffer cmd := NewSkillCommand(Options{ AppID: "example-cli", - Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")), + Bundle: testBundle(), Home: home, Out: &out, }) @@ -36,7 +45,7 @@ func TestInstallCommandPromptsForScopeBeforeInstall(t *testing.T) { var out bytes.Buffer cmd := NewSkillCommand(Options{ AppID: "example-cli", - Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")), + Bundle: testBundle(), Home: home, CWD: workspace, StdinTTY: true, @@ -71,7 +80,7 @@ func TestInstallCommandForceOverwritesUnmanaged(t *testing.T) { var out bytes.Buffer cmd := NewSkillCommand(Options{ AppID: "example-cli", - Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")), + Bundle: testBundle(), Home: home, Out: &out, }) @@ -89,7 +98,7 @@ func TestInstallCommandForceOverwritesUnmanaged(t *testing.T) { func TestInstallCommandReturnsCoreFlagError(t *testing.T) { cmd := NewInstallCommand(Options{ AppID: "example-cli", - Bundle: kitup.DirectoryBundle(filepath.Join("..", "testdata", "skills", "basic")), + Bundle: testBundle(), Home: t.TempDir(), }) cmd.SetArgs([]string{"--scope", "bad"}) diff --git a/go/hosts_gen.go b/go/hosts_gen.go index 521b4e6..eb2ba14 100644 --- a/go/hosts_gen.go +++ b/go/hosts_gen.go @@ -2,4 +2,4 @@ package kitup -const defaultHostsSpecJSON = "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\",\"~/.agents\",\"~/.claude\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\",\"~/.agents\",\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\",\"~/.agents\",\"~/.claude\",\"~/.codex\",\"~/.cursor\",\"~/.gemini\",\"~/.copilot\",\"~/.factory\",\"~/.github\",\"~/.opencode\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}" +const defaultHostsSpecJSON = "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}" diff --git a/go/kitup.go b/go/kitup.go index 89ccd9e..9e90461 100644 --- a/go/kitup.go +++ b/go/kitup.go @@ -504,11 +504,11 @@ func DetectHosts(opts BaseOptions, scope Scope) ([]Host, error) { home, cwd := defaults(opts) detected := []Host{} for _, host := range hosts { - if len(host.Detect) == 0 || isGenericDetectPath(host.Detect[0]) { - continue - } - if exists(expandHostPath(host.Detect[0], home, cwd)) { - detected = append(detected, host) + for _, detectPath := range host.Detect { + if !isGenericDetectPath(detectPath) && exists(expandHostPath(detectPath, home, cwd)) { + detected = append(detected, host) + break + } } } if scope == "" { @@ -1687,7 +1687,7 @@ func skipName(name string) bool { } func isGenericDetectPath(path string) bool { - return path == "~/.agents" || path == "~/.agents/skills" || path == "~/.config/agents" + return path == "~/.agents" || path == "~/.agents/skills" || path == "~/.config/agents" || path == "package.json" } func exists(path string) bool { diff --git a/go/kitup_test.go b/go/kitup_test.go index 23bb6c3..cdda86a 100644 --- a/go/kitup_test.go +++ b/go/kitup_test.go @@ -28,7 +28,7 @@ type goldenCase struct { func TestGoldenCases(t *testing.T) { var file goldenFile - readJSON(t, "../testdata/cases/bundled-skill-install.json", &file) + readJSON(t, "testdata/cases/bundled-skill-install.json", &file) for _, tc := range file.Cases { t.Run(tc.ID, func(t *testing.T) { root := t.TempDir() @@ -145,6 +145,9 @@ func runCase(t *testing.T, tc goldenCase, home, workspace string) { must(t, err) equal(t, hostIDs(hosts), expected) } + if tc.Operation == "detect" { + return + } report, err := runReportCase(t, tc, opts, base) if throws, ok := tc.Expected["throws"].(bool); ok && throws { if err == nil { @@ -530,7 +533,7 @@ func repoPathFromCase(path string) string { if filepath.IsAbs(path) { return path } - return filepath.Join("..", path) + return path } func hostIDs(hosts []Host) []string { diff --git a/go/spec/hosts.json b/go/spec/hosts.json new file mode 100644 index 0000000..0343079 --- /dev/null +++ b/go/spec/hosts.json @@ -0,0 +1,1094 @@ +{ + "$schema": "./hosts.schema.json", + "schemaVersion": 1, + "hosts": [ + { + "id": "adal", + "displayName": "AdaL", + "projectSkillsDirs": [ + ".adal/skills" + ], + "userSkillsDirs": [ + "~/.adal/skills" + ], + "detect": [ + "~/.adal" + ], + "status": "community" + }, + { + "id": "aider-desk", + "displayName": "AiderDesk", + "projectSkillsDirs": [ + ".aider-desk/skills" + ], + "userSkillsDirs": [ + "~/.aider-desk/skills" + ], + "detect": [ + "~/.aider-desk" + ], + "status": "community" + }, + { + "id": "amp", + "displayName": "Amp", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.config/agents/skills" + ], + "detect": [ + "~/.config/amp", + "~/.config/agents" + ], + "status": "community" + }, + { + "id": "antigravity", + "displayName": "Antigravity", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.gemini/antigravity/skills" + ], + "detect": [ + "~/.gemini/antigravity" + ], + "status": "community" + }, + { + "id": "antigravity-cli", + "displayName": "Antigravity CLI", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.gemini/antigravity-cli/skills" + ], + "detect": [ + "~/.gemini/antigravity-cli" + ], + "status": "community" + }, + { + "id": "astrbot", + "displayName": "AstrBot", + "projectSkillsDirs": [ + "data/skills" + ], + "userSkillsDirs": [ + "~/.astrbot/data/skills" + ], + "detect": [ + "~/.astrbot", + "data/skills", + "~/.astrbot/data" + ], + "status": "community" + }, + { + "id": "augment", + "displayName": "Augment", + "projectSkillsDirs": [ + ".augment/skills" + ], + "userSkillsDirs": [ + "~/.augment/skills" + ], + "detect": [ + "~/.augment" + ], + "status": "community" + }, + { + "id": "autohand-code", + "displayName": "Autohand Code CLI", + "projectSkillsDirs": [ + ".autohand/skills" + ], + "userSkillsDirs": [ + "~/.autohand/skills" + ], + "detect": [ + "~/.autohand" + ], + "status": "community" + }, + { + "id": "bob", + "displayName": "IBM Bob", + "projectSkillsDirs": [ + ".bob/skills" + ], + "userSkillsDirs": [ + "~/.bob/skills" + ], + "detect": [ + "~/.bob" + ], + "status": "community" + }, + { + "id": "claude-code", + "displayName": "Claude Code", + "projectSkillsDirs": [ + ".claude/skills" + ], + "userSkillsDirs": [ + "~/.claude/skills" + ], + "detect": [ + "~/.claude" + ], + "status": "verified" + }, + { + "id": "cline", + "displayName": "Cline", + "projectSkillsDirs": [ + ".agents/skills", + ".cline/skills", + ".clinerules/skills", + ".claude/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills", + "~/.cline/skills" + ], + "detect": [ + "~/.cline", + "~/.agents" + ], + "status": "documented" + }, + { + "id": "codearts-agent", + "displayName": "CodeArts Agent", + "projectSkillsDirs": [ + ".codeartsdoer/skills" + ], + "userSkillsDirs": [ + "~/.codeartsdoer/skills" + ], + "detect": [ + "~/.codeartsdoer" + ], + "status": "community" + }, + { + "id": "codebuddy", + "displayName": "CodeBuddy", + "projectSkillsDirs": [ + ".codebuddy/skills" + ], + "userSkillsDirs": [ + "~/.codebuddy/skills" + ], + "detect": [ + "~/.codebuddy", + ".codebuddy" + ], + "status": "community" + }, + { + "id": "codemaker", + "displayName": "Codemaker", + "projectSkillsDirs": [ + ".codemaker/skills" + ], + "userSkillsDirs": [ + "~/.codemaker/skills" + ], + "detect": [ + "~/.codemaker" + ], + "status": "community" + }, + { + "id": "codestudio", + "displayName": "Code Studio", + "projectSkillsDirs": [ + ".codestudio/skills" + ], + "userSkillsDirs": [ + "~/.codestudio/skills" + ], + "detect": [ + "~/.codestudio" + ], + "status": "community" + }, + { + "id": "codex", + "displayName": "Codex", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills", + "~/.codex/skills" + ], + "detect": [ + "~/.codex", + "~/.agents/skills", + "~/.agents" + ], + "status": "verified", + "notes": [ + "Keep both ~/.agents/skills and ~/.codex/skills for compatibility." + ] + }, + { + "id": "command-code", + "displayName": "Command Code", + "projectSkillsDirs": [ + ".commandcode/skills" + ], + "userSkillsDirs": [ + "~/.commandcode/skills" + ], + "detect": [ + "~/.commandcode" + ], + "status": "community" + }, + { + "id": "continue", + "displayName": "Continue", + "projectSkillsDirs": [ + ".continue/skills" + ], + "userSkillsDirs": [ + "~/.continue/skills" + ], + "detect": [ + "~/.continue", + ".continue" + ], + "status": "community" + }, + { + "id": "cortex", + "displayName": "Cortex Code", + "projectSkillsDirs": [ + ".cortex/skills" + ], + "userSkillsDirs": [ + "~/.snowflake/cortex/skills" + ], + "detect": [ + "~/.snowflake/cortex" + ], + "status": "community" + }, + { + "id": "crush", + "displayName": "Crush", + "projectSkillsDirs": [ + ".crush/skills" + ], + "userSkillsDirs": [ + "~/.config/crush/skills" + ], + "detect": [ + "~/.config/crush" + ], + "status": "community" + }, + { + "id": "cursor", + "displayName": "Cursor", + "projectSkillsDirs": [ + ".agents/skills", + ".cursor/skills" + ], + "userSkillsDirs": [ + "~/.cursor/skills", + "~/.agents/skills" + ], + "detect": [ + "~/.cursor", + "~/.agents" + ], + "status": "documented" + }, + { + "id": "deepagents", + "displayName": "Deep Agents", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.deepagents/agent/skills" + ], + "detect": [ + "~/.deepagents", + "~/.deepagents/agent" + ], + "status": "community" + }, + { + "id": "devin", + "displayName": "Devin for Terminal", + "projectSkillsDirs": [ + ".devin/skills" + ], + "userSkillsDirs": [ + "~/.config/devin/skills" + ], + "detect": [ + "~/.config/devin" + ], + "status": "community" + }, + { + "id": "dexto", + "displayName": "Dexto", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills" + ], + "detect": [ + "~/.dexto", + "~/.agents" + ], + "status": "community" + }, + { + "id": "droid", + "displayName": "Droid", + "projectSkillsDirs": [ + ".factory/skills" + ], + "userSkillsDirs": [ + "~/.factory/skills" + ], + "detect": [ + "~/.factory" + ], + "status": "community" + }, + { + "id": "eve", + "displayName": "Eve", + "projectSkillsDirs": [ + "agent/skills" + ], + "userSkillsDirs": [], + "detect": [ + "agent", + "package.json" + ], + "status": "community", + "notes": [ + "Project-only host; userSkillsDirs is intentionally empty.", + "Detect from Eve project shape; no global skill directory." + ] + }, + { + "id": "firebender", + "displayName": "Firebender", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.firebender/skills" + ], + "detect": [ + "~/.firebender" + ], + "status": "community" + }, + { + "id": "forgecode", + "displayName": "ForgeCode", + "projectSkillsDirs": [ + ".forge/skills" + ], + "userSkillsDirs": [ + "~/.forge/skills" + ], + "detect": [ + "~/.forge" + ], + "status": "community" + }, + { + "id": "gemini-cli", + "displayName": "Gemini CLI", + "projectSkillsDirs": [ + ".agents/skills", + ".gemini/skills" + ], + "userSkillsDirs": [ + "~/.gemini/skills", + "~/.agents/skills" + ], + "detect": [ + "~/.gemini", + "~/.agents" + ], + "status": "documented" + }, + { + "id": "github-copilot", + "displayName": "GitHub Copilot", + "projectSkillsDirs": [ + ".agents/skills", + ".github/skills", + ".claude/skills" + ], + "userSkillsDirs": [ + "~/.copilot/skills", + "~/.agents/skills", + "~/.claude/skills" + ], + "detect": [ + "~/.copilot" + ], + "status": "documented" + }, + { + "id": "goose", + "displayName": "Goose", + "projectSkillsDirs": [ + ".goose/skills" + ], + "userSkillsDirs": [ + "~/.config/goose/skills" + ], + "detect": [ + "~/.config/goose" + ], + "status": "community" + }, + { + "id": "hermes-agent", + "displayName": "Hermes Agent", + "projectSkillsDirs": [ + ".hermes/skills" + ], + "userSkillsDirs": [ + "~/.hermes/skills" + ], + "detect": [ + "~/.hermes" + ], + "status": "community" + }, + { + "id": "iflow-cli", + "displayName": "iFlow CLI", + "projectSkillsDirs": [ + ".iflow/skills" + ], + "userSkillsDirs": [ + "~/.iflow/skills" + ], + "detect": [ + "~/.iflow" + ], + "status": "community" + }, + { + "id": "inference-sh", + "displayName": "inference.sh", + "projectSkillsDirs": [ + ".inferencesh/skills" + ], + "userSkillsDirs": [ + "~/.inferencesh/skills" + ], + "detect": [ + "~/.inferencesh" + ], + "status": "community" + }, + { + "id": "jazz", + "displayName": "Jazz", + "projectSkillsDirs": [ + ".jazz/skills" + ], + "userSkillsDirs": [ + "~/.jazz/skills" + ], + "detect": [ + "~/.jazz", + ".jazz" + ], + "status": "community" + }, + { + "id": "junie", + "displayName": "Junie", + "projectSkillsDirs": [ + ".junie/skills" + ], + "userSkillsDirs": [ + "~/.junie/skills" + ], + "detect": [ + "~/.junie" + ], + "status": "community" + }, + { + "id": "kilo", + "displayName": "Kilo Code", + "projectSkillsDirs": [ + ".kilocode/skills" + ], + "userSkillsDirs": [ + "~/.kilocode/skills" + ], + "detect": [ + "~/.kilocode" + ], + "status": "community" + }, + { + "id": "kimi-cli", + "displayName": "Kimi Code CLI", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.config/agents/skills", + "~/.agents/skills" + ], + "detect": [ + "~/.config/agents", + "~/.kimi-code", + "~/.kimi", + "~/.agents" + ], + "status": "community", + "notes": [ + "kimi-code-cli is an alias for the same Kimi Code CLI path family." + ], + "aliases": [ + "kimi-code-cli" + ] + }, + { + "id": "kiro-cli", + "displayName": "Kiro CLI", + "projectSkillsDirs": [ + ".kiro/skills" + ], + "userSkillsDirs": [ + "~/.kiro/skills" + ], + "detect": [ + "~/.kiro" + ], + "status": "community" + }, + { + "id": "kode", + "displayName": "Kode", + "projectSkillsDirs": [ + ".kode/skills" + ], + "userSkillsDirs": [ + "~/.kode/skills" + ], + "detect": [ + "~/.kode" + ], + "status": "community" + }, + { + "id": "lingma", + "displayName": "Lingma", + "projectSkillsDirs": [ + ".lingma/skills" + ], + "userSkillsDirs": [ + "~/.lingma/skills" + ], + "detect": [ + "~/.lingma" + ], + "status": "community" + }, + { + "id": "loaf", + "displayName": "Loaf", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills" + ], + "detect": [ + "~/.loaf", + "~/.agents" + ], + "status": "community" + }, + { + "id": "mcpjam", + "displayName": "MCPJam", + "projectSkillsDirs": [ + ".mcpjam/skills" + ], + "userSkillsDirs": [ + "~/.mcpjam/skills" + ], + "detect": [ + "~/.mcpjam" + ], + "status": "community" + }, + { + "id": "mistral-vibe", + "displayName": "Mistral Vibe", + "projectSkillsDirs": [ + ".vibe/skills" + ], + "userSkillsDirs": [ + "~/.vibe/skills" + ], + "detect": [ + "~/.vibe" + ], + "status": "community" + }, + { + "id": "moxby", + "displayName": "Moxby", + "projectSkillsDirs": [ + ".moxby/skills" + ], + "userSkillsDirs": [ + "~/.moxby/skills" + ], + "detect": [ + "~/.moxby" + ], + "status": "community" + }, + { + "id": "mux", + "displayName": "Mux", + "projectSkillsDirs": [ + ".mux/skills" + ], + "userSkillsDirs": [ + "~/.mux/skills" + ], + "detect": [ + "~/.mux" + ], + "status": "community" + }, + { + "id": "neovate", + "displayName": "Neovate", + "projectSkillsDirs": [ + ".neovate/skills" + ], + "userSkillsDirs": [ + "~/.neovate/skills" + ], + "detect": [ + "~/.neovate" + ], + "status": "community" + }, + { + "id": "ona", + "displayName": "Ona", + "projectSkillsDirs": [ + ".ona/skills" + ], + "userSkillsDirs": [ + "~/.ona/skills" + ], + "detect": [ + "~/.ona" + ], + "status": "community" + }, + { + "id": "openclaw", + "displayName": "OpenClaw", + "projectSkillsDirs": [ + "skills" + ], + "userSkillsDirs": [ + "~/.openclaw/skills" + ], + "detect": [ + "~/.openclaw", + "~/.clawdbot", + "~/.moltbot" + ], + "status": "community" + }, + { + "id": "opencode", + "displayName": "OpenCode", + "projectSkillsDirs": [ + ".agents/skills", + ".opencode/skills", + ".claude/skills" + ], + "userSkillsDirs": [ + "~/.config/opencode/skills", + "~/.agents/skills", + "~/.claude/skills" + ], + "detect": [ + "~/.config/opencode" + ], + "status": "verified" + }, + { + "id": "openhands", + "displayName": "OpenHands", + "projectSkillsDirs": [ + ".openhands/skills" + ], + "userSkillsDirs": [ + "~/.openhands/skills" + ], + "detect": [ + "~/.openhands" + ], + "status": "community" + }, + { + "id": "pi", + "displayName": "Pi", + "projectSkillsDirs": [ + ".pi/skills" + ], + "userSkillsDirs": [ + "~/.pi/agent/skills" + ], + "detect": [ + "~/.pi/agent" + ], + "status": "community" + }, + { + "id": "pochi", + "displayName": "Pochi", + "projectSkillsDirs": [ + ".pochi/skills" + ], + "userSkillsDirs": [ + "~/.pochi/skills" + ], + "detect": [ + "~/.pochi" + ], + "status": "community" + }, + { + "id": "promptscript", + "displayName": "PromptScript", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [], + "detect": [ + ".promptscript", + "promptscript.yaml" + ], + "status": "community", + "notes": [ + "Project-only host; userSkillsDirs is intentionally empty." + ] + }, + { + "id": "qoder", + "displayName": "Qoder", + "projectSkillsDirs": [ + ".qoder/skills" + ], + "userSkillsDirs": [ + "~/.qoder/skills" + ], + "detect": [ + "~/.qoder" + ], + "status": "community" + }, + { + "id": "qoder-cn", + "displayName": "Qoder CN", + "projectSkillsDirs": [ + ".qoder/skills" + ], + "userSkillsDirs": [ + "~/.qoder-cn/skills" + ], + "detect": [ + "~/.qoder-cn" + ], + "status": "community" + }, + { + "id": "qwen-code", + "displayName": "Qwen Code", + "projectSkillsDirs": [ + ".qwen/skills" + ], + "userSkillsDirs": [ + "~/.qwen/skills" + ], + "detect": [ + "~/.qwen" + ], + "status": "community" + }, + { + "id": "reasonix", + "displayName": "Reasonix", + "projectSkillsDirs": [ + ".reasonix/skills" + ], + "userSkillsDirs": [ + "~/.reasonix/skills" + ], + "detect": [ + "~/.reasonix" + ], + "status": "community" + }, + { + "id": "replit", + "displayName": "Replit", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.config/agents/skills" + ], + "detect": [ + ".replit", + "~/.config/agents" + ], + "status": "community" + }, + { + "id": "roo", + "displayName": "Roo Code", + "aliases": [ + "roo-code" + ], + "projectSkillsDirs": [ + ".roo/skills", + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.roo/skills", + "~/.agents/skills" + ], + "detect": [ + "~/.roo", + "~/.agents" + ], + "status": "documented" + }, + { + "id": "rovodev", + "displayName": "Rovo Dev", + "projectSkillsDirs": [ + ".rovodev/skills" + ], + "userSkillsDirs": [ + "~/.rovodev/skills" + ], + "detect": [ + "~/.rovodev" + ], + "status": "community" + }, + { + "id": "tabnine-cli", + "displayName": "Tabnine CLI", + "projectSkillsDirs": [ + ".tabnine/agent/skills" + ], + "userSkillsDirs": [ + "~/.tabnine/agent/skills" + ], + "detect": [ + "~/.tabnine", + "~/.tabnine/agent" + ], + "status": "community" + }, + { + "id": "terramind", + "displayName": "Terramind", + "projectSkillsDirs": [ + ".terramind/skills" + ], + "userSkillsDirs": [ + "~/.terramind/skills" + ], + "detect": [ + "~/.terramind" + ], + "status": "community" + }, + { + "id": "tinycloud", + "displayName": "Tinycloud", + "projectSkillsDirs": [ + ".tinycloud/skills" + ], + "userSkillsDirs": [ + "~/.tinycloud/skills" + ], + "detect": [ + "~/.tinycloud" + ], + "status": "community" + }, + { + "id": "trae", + "displayName": "Trae", + "projectSkillsDirs": [ + ".trae/skills" + ], + "userSkillsDirs": [ + "~/.trae/skills" + ], + "detect": [ + "~/.trae" + ], + "status": "community" + }, + { + "id": "trae-cn", + "displayName": "Trae CN", + "projectSkillsDirs": [ + ".trae/skills" + ], + "userSkillsDirs": [ + "~/.trae-cn/skills" + ], + "detect": [ + "~/.trae-cn" + ], + "status": "community" + }, + { + "id": "universal", + "displayName": "Universal", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills", + "~/.config/agents/skills" + ], + "detect": [ + "~/.agents", + "~/.config/agents" + ], + "status": "community" + }, + { + "id": "warp", + "displayName": "Warp", + "projectSkillsDirs": [ + ".agents/skills", + ".warp/skills", + ".claude/skills", + ".codex/skills", + ".cursor/skills", + ".gemini/skills", + ".copilot/skills", + ".factory/skills", + ".github/skills", + ".opencode/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills", + "~/.warp/skills", + "~/.claude/skills", + "~/.codex/skills", + "~/.cursor/skills", + "~/.gemini/skills", + "~/.copilot/skills", + "~/.factory/skills", + "~/.github/skills", + "~/.opencode/skills" + ], + "detect": [ + "~/.warp" + ], + "status": "documented" + }, + { + "id": "windsurf", + "displayName": "Windsurf", + "projectSkillsDirs": [ + ".windsurf/skills" + ], + "userSkillsDirs": [ + "~/.codeium/windsurf/skills" + ], + "detect": [ + "~/.codeium/windsurf" + ], + "status": "community" + }, + { + "id": "zed", + "displayName": "Zed", + "projectSkillsDirs": [ + ".agents/skills" + ], + "userSkillsDirs": [ + "~/.agents/skills" + ], + "detect": [ + "~/.config/zed", + "~/.agents" + ], + "status": "community" + }, + { + "id": "zencoder", + "displayName": "Zencoder", + "projectSkillsDirs": [ + ".zencoder/skills" + ], + "userSkillsDirs": [ + "~/.zencoder/skills" + ], + "detect": [ + "~/.zencoder" + ], + "status": "community" + }, + { + "id": "zenflow", + "displayName": "Zenflow", + "projectSkillsDirs": [ + ".zencoder/skills" + ], + "userSkillsDirs": [ + "~/.zencoder/skills" + ], + "detect": [ + "~/.zencoder" + ], + "status": "community" + } + ] +} diff --git a/go/testdata/cases/bundled-skill-install.json b/go/testdata/cases/bundled-skill-install.json new file mode 100644 index 0000000..0cfac14 --- /dev/null +++ b/go/testdata/cases/bundled-skill-install.json @@ -0,0 +1,3158 @@ +{ + "$schema": "../cases.schema.json", + "schemaVersion": 1, + "cases": [ + { + "id": "all-supported-hosts-load", + "operation": "resolve-hosts", + "description": "Loads the full host adapter baseline from spec/hosts.json.", + "options": { + "agents": "*" + }, + "given": { + "hostsFile": "spec/hosts.json" + }, + "expected": { + "count": 72, + "hostIds": [ + "adal", + "aider-desk", + "amp", + "antigravity", + "antigravity-cli", + "astrbot", + "augment", + "autohand-code", + "bob", + "claude-code", + "cline", + "codearts-agent", + "codebuddy", + "codemaker", + "codestudio", + "codex", + "command-code", + "continue", + "cortex", + "crush", + "cursor", + "deepagents", + "devin", + "dexto", + "droid", + "eve", + "firebender", + "forgecode", + "gemini-cli", + "github-copilot", + "goose", + "hermes-agent", + "iflow-cli", + "inference-sh", + "jazz", + "junie", + "kilo", + "kimi-cli", + "kiro-cli", + "kode", + "lingma", + "loaf", + "mcpjam", + "mistral-vibe", + "moxby", + "mux", + "neovate", + "ona", + "openclaw", + "opencode", + "openhands", + "pi", + "pochi", + "promptscript", + "qoder", + "qoder-cn", + "qwen-code", + "reasonix", + "replit", + "roo", + "rovodev", + "tabnine-cli", + "terramind", + "tinycloud", + "trae", + "trae-cn", + "universal", + "warp", + "windsurf", + "zed", + "zencoder", + "zenflow" + ] + } + }, + { + "id": "alias-resolution", + "operation": "resolve-hosts", + "description": "Resolves legacy or ecosystem-compatible host aliases to canonical adapter ids.", + "options": { + "agents": [ + "roo-code" + ] + }, + "given": { + "hostsFile": "spec/hosts.json" + }, + "expected": { + "resolvedHostIds": [ + "roo" + ] + } + }, + { + "id": "kimi-alias-resolution", + "operation": "resolve-hosts", + "description": "Resolves the kimi-code-cli alias to the canonical kimi-cli adapter id.", + "options": { + "agents": [ + "kimi-code-cli" + ] + }, + "given": { + "hostsFile": "spec/hosts.json" + }, + "expected": { + "resolvedHostIds": [ + "kimi-cli" + ] + } + }, + { + "id": "unknown-host-id", + "operation": "resolve-hosts", + "description": "Reports a structured error when an explicit host id or alias is unknown.", + "options": { + "agents": [ + "missing-agent" + ] + }, + "given": { + "hostsFile": "spec/hosts.json" + }, + "expected": { + "resolvedHostIds": [], + "errors": [ + { + "agent": "missing-agent", + "reason": "unknown-host" + } + ] + } + }, + { + "id": "parse-install-flags-defaults", + "operation": "parse-install-flags", + "description": "Maps missing install flags to the user default while recording that scope was not explicit.", + "options": {}, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "parsed": { + "scope": "user", + "scopeSet": false, + "agentKind": "auto", + "agentIds": [], + "yes": false, + "dryRun": false, + "force": false, + "errors": [] + } + } + }, + { + "id": "parse-install-flags-explicit", + "operation": "parse-install-flags", + "description": "Maps repeated and comma-separated agent flags to a deduplicated explicit selector.", + "options": { + "scope": "project", + "agents": [ + "codex,claude-code", + "codex" + ], + "yes": true, + "dryRun": true, + "force": true + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "parsed": { + "scope": "project", + "scopeSet": true, + "agentKind": "explicit", + "agentIds": [ + "codex", + "claude-code" + ], + "yes": true, + "dryRun": true, + "force": true, + "errors": [] + } + } + }, + { + "id": "parse-install-flags-star", + "operation": "parse-install-flags", + "description": "Maps --agent '*' to the all-host selector.", + "options": { + "agents": [ + "*" + ] + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "parsed": { + "scope": "user", + "scopeSet": false, + "agentKind": "*", + "agentIds": [], + "yes": false, + "dryRun": false, + "force": false, + "errors": [] + } + } + }, + { + "id": "parse-install-flags-errors", + "operation": "parse-install-flags", + "description": "Reports invalid scope and mixed star agent selector flags.", + "options": { + "scope": "global", + "agents": [ + "*", + "codex" + ] + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "parsed": { + "scope": "user", + "scopeSet": true, + "agentKind": "*", + "agentIds": [], + "yes": false, + "dryRun": false, + "force": false, + "errors": [ + { + "flag": "scope", + "reason": "invalid-scope", + "value": "global" + }, + { + "flag": "agent", + "reason": "agent-star-must-be-alone", + "value": "*,codex" + } + ] + } + } + }, + { + "id": "shared-target-deduplication-many-hosts", + "operation": "install", + "description": "Copies once when many supported agents share the .agents/skills project target.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "amp", + "antigravity", + "antigravity-cli", + "cline", + "codex", + "cursor", + "deepagents", + "dexto", + "firebender", + "gemini-cli", + "github-copilot", + "kimi-cli", + "loaf", + "opencode", + "promptscript", + "replit", + "universal", + "warp", + "zed" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostIds": [ + "amp", + "antigravity", + "antigravity-cli", + "cline", + "codex", + "cursor", + "deepagents", + "dexto", + "firebender", + "gemini-cli", + "github-copilot", + "kimi-cli", + "loaf", + "opencode", + "promptscript", + "replit", + "universal", + "warp", + "zed" + ], + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "writeCountByTargetDir": { + "$WORKSPACE/.agents/skills/basic": 1 + } + } + }, + { + "id": "user-scope-install", + "operation": "install", + "description": "Installs a bundled skill into the canonical user scope for an explicit host.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.agents/skills/basic/references/guide.md", + "$HOME/.agents/skills/basic/scripts/helper.sh", + "$HOME/.agents/skills/basic/assets/template.json", + "$HOME/.agents/skills/basic/.kitup.json" + ], + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + }, + "hash": "from-skill-bundle-dir" + } + } + }, + { + "id": "codex-user-scope-reuses-existing-compatible-dir", + "operation": "install", + "description": "Reuses the existing Codex-compatible user directory when the canonical directory does not exist.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.codex/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.codex/skills/basic/SKILL.md", + "$HOME/.codex/skills/basic/.kitup.json" + ], + "filesAbsent": [ + "$HOME/.agents/skills/basic" + ] + } + }, + { + "id": "codex-user-scope-prefers-first-user-dir", + "operation": "install", + "description": "Prefers the first canonical Codex user path when both compatible directories already exist.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills", + "$HOME/.codex/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.agents/skills/basic/.kitup.json" + ], + "filesAbsent": [ + "$HOME/.codex/skills/basic" + ] + } + }, + { + "id": "project-scope-install", + "operation": "install", + "description": "Installs a bundled skill into the canonical project scope for an explicit host.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$WORKSPACE/.agents/skills/basic/SKILL.md", + "$WORKSPACE/.agents/skills/basic/.kitup.json" + ], + "fileModes": { + "$WORKSPACE/.agents/skills/basic": "755" + } + } + }, + { + "id": "project-scope-plan", + "operation": "plan", + "description": "Reports the project-scope install target without writing skill files.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesAbsent": [ + "$WORKSPACE/.agents/skills/basic" + ] + } + }, + { + "id": "project-only-host-project-scope-install", + "operation": "install", + "description": "Installs into a project-only host when project scope is selected explicitly.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "eve" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "eve", + "skillName": "basic", + "targetDir": "$WORKSPACE/agent/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$WORKSPACE/agent/skills/basic/SKILL.md", + "$WORKSPACE/agent/skills/basic/.kitup.json" + ] + } + }, + { + "id": "project-only-host-user-scope-error", + "operation": "install", + "description": "Reports an unsupported-scope error when a project-only host is selected for user scope.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "eve" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [ + { + "hostId": "eve", + "skillName": "basic", + "scope": "user", + "reason": "unsupported-scope" + } + ] + } + } + }, + { + "id": "explicit-host-selection", + "operation": "install", + "description": "Installs only into the explicitly selected host targets.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex", + "claude-code" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills", + "$HOME/.claude/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + }, + { + "hostId": "claude-code", + "skillName": "basic", + "targetDir": "$HOME/.claude/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "auto-host-detection", + "operation": "install", + "description": "Uses detection paths to select installed hosts for automatic installation.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": "auto", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude", + "$HOME/.agents/skills", + "$HOME/.claude/skills" + ], + "files": {} + }, + "expected": { + "detectedHosts": [ + "codex", + "claude-code" + ], + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + }, + { + "hostId": "claude-code", + "skillName": "basic", + "targetDir": "$HOME/.claude/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "auto-host-detection-empty", + "operation": "install", + "description": "Returns an empty report when automatic host detection finds no supported hosts.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": "auto", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "detectedHosts": [], + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "auto-host-detection-secondary-specific-path", + "operation": "detect", + "description": "Detects a host when a non-primary specific detection path exists.", + "options": { + "scope": "user", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.clawdbot" + ], + "files": {} + }, + "expected": { + "detectedHosts": [ + "openclaw" + ] + } + }, + { + "id": "auto-host-detection-generic-path-only", + "operation": "detect", + "description": "Does not infer a specific host from a shared compatibility root alone.", + "options": { + "scope": "user", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents" + ], + "files": { + "$WORKSPACE/package.json": "{}\n" + } + }, + "expected": { + "detectedHosts": [] + } + }, + { + "id": "shared-target-deduplication", + "operation": "install", + "description": "Copies once when multiple selected hosts resolve to the same canonical target directory.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex", + "warp", + "gemini-cli" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostIds": [ + "codex", + "warp", + "gemini-cli" + ], + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "writeCountByTargetDir": { + "$HOME/.agents/skills/basic": 1 + } + } + }, + { + "id": "unchanged-noop", + "operation": "install", + "description": "Skips a kitup-owned target when its metadata hash matches the bundled skill.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "copySkillBundleTo": "$HOME/.agents/skills/basic", + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + }, + "hash": "from-skill-bundle-dir" + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unchanged" + } + ], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "workflow-unchanged-silent", + "operation": "run-install-workflow", + "description": "Does not render a user-facing summary or confirmation when every selected target is unchanged.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "stdinTTY": true, + "yes": false, + "input": "y\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "copySkillBundleTo": "$HOME/.agents/skills/basic", + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + }, + "hash": "from-skill-bundle-dir" + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": true, + "code": "ok", + "message": "" + }, + "output": "", + "report": { + "installed": [], + "updated": [], + "skipped": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unchanged" + } + ], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "unchanged-repairs-script-mode", + "operation": "install", + "description": "Repairs script executable mode drift even when the stored content hash is unchanged.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "copySkillBundleTo": "$HOME/.agents/skills/basic", + "fileModes": { + "$HOME/.agents/skills/basic/scripts/helper.sh": "644" + }, + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + }, + "hash": "from-skill-bundle-dir" + } + }, + "expected": { + "report": { + "installed": [], + "updated": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "fileModes": { + "$HOME/.agents/skills/basic/scripts/helper.sh": "755" + } + } + }, + { + "id": "workflow-conflict-exit", + "operation": "run-install-workflow", + "description": "Returns a conflict exit classification when the plan has conflicts but no writes.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "stdinTTY": true, + "yes": false, + "input": "y\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Unmanaged fixture.\n---\n\n# Basic\n" + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": false, + "code": "conflict", + "message": "Installation has conflicts." + }, + "output": "", + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + } + } + }, + { + "id": "workflow-conflict-blocks-writes", + "operation": "run-install-workflow", + "description": "Does not write any target when the plan mixes writable targets with conflicts.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex", + "claude-code" + ], + "stdinTTY": true, + "yes": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Unmanaged fixture.\n---\n\n# Basic\n" + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": false, + "code": "conflict", + "message": "Installation has conflicts." + }, + "output": "", + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesAbsent": [ + "$HOME/.claude/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-dry-run-conflict-renders-plan", + "operation": "run-install-workflow", + "description": "Dry-run renders writable plan items even when another target conflicts.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex", + "claude-code" + ], + "stdinTTY": true, + "yes": true, + "dryRun": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Unmanaged fixture.\n---\n\n# Basic\n" + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": true + }, + "exit": { + "ok": false, + "code": "conflict", + "message": "Installation has conflicts." + }, + "outputContains": [ + " - basic -> ", + "(claude-code)" + ], + "report": { + "installed": [ + { + "hostId": "claude-code", + "skillName": "basic", + "targetDir": "$HOME/.claude/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesAbsent": [ + "$HOME/.claude/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-force-overwrites-conflicts", + "operation": "run-install-workflow", + "description": "Force turns unmanaged and different-owner targets into explicit updates.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex", + "claude-code" + ], + "stdinTTY": true, + "yes": true, + "force": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic", + "$HOME/.claude/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Unmanaged fixture.\n---\n\n# Basic\n", + "$HOME/.claude/skills/basic/SKILL.md": "---\nname: basic\ndescription: Other owner fixture.\n---\n\n# Basic\n", + "$HOME/.claude/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "other-cli", + "skillName": "basic", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": true, + "code": "ok", + "message": "" + }, + "outputContains": [ + "(codex)", + "(claude-code)" + ], + "report": { + "installed": [], + "updated": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + }, + { + "hostId": "claude-code", + "skillName": "basic", + "targetDir": "$HOME/.claude/skills/basic" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.claude/skills/basic/SKILL.md" + ], + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "hash": "from-skill-bundle-dir", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + } + } + } + }, + { + "id": "changed-update", + "operation": "update", + "description": "Replaces a kitup-owned target when its metadata hash differs from the bundled skill.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Old fixture.\n---\n\n# Old\n", + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/references/guide.md", + "$HOME/.agents/skills/basic/assets/template.json" + ] + } + }, + { + "id": "unmanaged-conflict", + "operation": "install", + "description": "Refuses to overwrite a target directory that has no kitup metadata.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/SKILL.md": "---\nname: basic\ndescription: Existing unmanaged skill.\n---\n" + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + } + } + }, + { + "id": "install-incomplete-metadata-conflict", + "operation": "install", + "description": "Treats incomplete .kitup.json as unmanaged and refuses overwrite.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "appId": "example-cli" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "install-skill-name-mismatch-conflict", + "operation": "install", + "description": "Treats metadata for a different skill name as unmanaged and refuses overwrite.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "other", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "update-skill-name-mismatch-with-force", + "operation": "update", + "description": "Force replaces metadata for a different skill name and records the requested owner.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "force": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "other", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md" + ], + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "hash": "from-skill-bundle-dir", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + } + } + } + }, + { + "id": "different-owner-conflict", + "operation": "install", + "description": "Refuses to overwrite a kitup-managed target owned by another app.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "other-cli", + "skillName": "basic", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "owner-mismatch" + } + ], + "errors": [] + } + } + }, + { + "id": "uninstall-owned-skill", + "operation": "uninstall", + "description": "Removes only a kitup-owned skill target for the same app id.", + "options": { + "appId": "example-cli", + "skillName": "basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "copySkillBundleTo": "$HOME/.agents/skills/basic", + "metadata": { + "path": "$HOME/.agents/skills/basic/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled" + }, + "hash": "from-skill-bundle-dir" + } + }, + "expected": { + "report": { + "removed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesAbsent": [ + "$HOME/.agents/skills/basic" + ] + } + }, + { + "id": "uninstall-owner-mismatch", + "operation": "uninstall", + "description": "Refuses to remove a skill target owned by another app id.", + "options": { + "appId": "example-cli", + "skillName": "basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "other-cli", + "skillName": "basic", + "source": "bundled", + "hash": "sha256:old" + } + } + }, + "expected": { + "report": { + "removed": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "owner-mismatch" + } + ], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "missing-skill-md", + "operation": "validate", + "description": "Rejects a skill directory without SKILL.md.", + "options": { + "skillBundleDir": "testdata/skills/missing-skill-md", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "valid": false, + "errorCode": "missing-skill-md" + } + }, + { + "id": "invalid-frontmatter", + "operation": "validate", + "description": "Rejects SKILL.md frontmatter that violates the Agent Skills naming and description rules.", + "options": { + "skillBundleDir": "testdata/skills/invalid-frontmatter", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "valid": false, + "errorCode": "invalid-frontmatter" + } + }, + { + "id": "nested-resources-copied", + "operation": "install", + "description": "Copies nested references, scripts, and assets with the bundled skill.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.agents/skills/basic/references/guide.md", + "$HOME/.agents/skills/basic/scripts/helper.sh", + "$HOME/.agents/skills/basic/assets/template.json" + ] + } + }, + { + "id": "embedded-skill-source", + "operation": "install", + "description": "Installs a bundled skill from an embedded directory tree source without materializing a directory source first.", + "options": { + "appId": "example-cli", + "skillFiles": [ + { + "path": "SKILL.md", + "contents": "---\nname: embedded\ndescription: Embedded whole skill tree fixture.\n---\n\n# Embedded\n" + }, + { + "path": "references/guide.md", + "contents": "# Guide\n" + }, + { + "path": "assets/template.json", + "contents": "{\"ok\":true}\n" + }, + { + "path": "scripts/helper.sh", + "contents": "#!/usr/bin/env sh\necho embedded\n" + }, + { + "path": "scripts/disabled.sh", + "contents": "#!/usr/bin/env sh\necho disabled\n", + "mode": 420 + }, + { + "path": ".kitup.json", + "contents": "{\"ignored\":true}\n" + }, + { + "path": ".DS_Store", + "contents": "ignored" + } + ], + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "embedded", + "targetDir": "$HOME/.agents/skills/embedded" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/embedded/SKILL.md", + "$HOME/.agents/skills/embedded/references/guide.md", + "$HOME/.agents/skills/embedded/assets/template.json", + "$HOME/.agents/skills/embedded/scripts/helper.sh", + "$HOME/.agents/skills/embedded/.kitup.json" + ], + "filesAbsent": [ + "$HOME/.agents/skills/embedded/.DS_Store" + ], + "fileModes": { + "$HOME/.agents/skills/embedded/scripts/helper.sh": "755", + "$HOME/.agents/skills/embedded/scripts/disabled.sh": "644" + }, + "metadata": { + "path": "$HOME/.agents/skills/embedded/.kitup.json", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "embedded", + "source": "bundled" + }, + "hash": "from-skill-files" + } + } + }, + { + "id": "workflow-explicit-agent", + "operation": "resolve-install-selection", + "description": "Explicit agents select canonical hosts without detection.", + "options": { + "scope": "user", + "agents": [ + "codex" + ], + "stdinTTY": false, + "yes": false, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "selection": { + "action": "install", + "selectedHostIds": [ + "codex" + ], + "candidateHostIds": [], + "detectedHostIds": [], + "needsConfirmation": false, + "errors": [] + } + } + }, + { + "id": "workflow-agent-star", + "operation": "resolve-install-selection", + "description": "Agent star explicitly selects every supported host.", + "options": { + "scope": "user", + "agents": "*", + "stdinTTY": false, + "yes": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "selection": { + "action": "install", + "selectedCount": 72, + "candidateHostIds": [], + "detectedHostIds": [], + "needsConfirmation": false, + "errors": [] + } + } + }, + { + "id": "workflow-scope-prompt-before-agent", + "operation": "run-install-workflow", + "description": "TTY workflow prompts for scope before planning an explicit agent install when scope was not provided.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "agents": [ + "codex" + ], + "stdinTTY": true, + "promptScope": true, + "input": "project\ny\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "workflow": { + "scope": "project", + "canceled": false, + "dryRun": false + }, + "outputContains": [ + "Select install scope:", + "Scope (user/project) [user]: ", + " - basic -> ", + "(codex)" + ], + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$WORKSPACE/.agents/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-scope-non-tty-error", + "operation": "run-install-workflow", + "description": "Non-TTY workflow refuses to choose scope implicitly when scope was not provided.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "agents": [ + "codex" + ], + "stdinTTY": false, + "promptScope": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "workflow": { + "scope": "", + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": false, + "code": "selection-error", + "message": "Agent selection failed." + }, + "output": "kitup: scope-selection-required\n", + "filesAbsent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$WORKSPACE/.agents/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-scope-yes-default", + "operation": "run-install-workflow", + "description": "Yes mode uses the configured default scope when scope was not provided.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "agents": [ + "codex" + ], + "stdinTTY": false, + "promptScope": true, + "defaultScope": "project", + "yes": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "workflow": { + "scope": "project", + "canceled": false, + "dryRun": false + }, + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$WORKSPACE/.agents/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-shared-target-renders-host-rows", + "operation": "run-install-workflow", + "description": "Shared target install still renders one user-facing row per selected host.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "codex", + "cursor", + "github-copilot", + "opencode" + ], + "stdinTTY": true, + "input": "y\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "workflow": { + "scope": "project", + "canceled": false, + "dryRun": false + }, + "outputContains": [ + "(codex)", + "(cursor)", + "(github-copilot)", + "(opencode)", + "Proceed? [y/N]" + ], + "report": { + "installed": [ + { + "hostIds": [ + "codex", + "cursor", + "github-copilot", + "opencode" + ], + "skillName": "basic", + "targetDir": "$WORKSPACE/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$WORKSPACE/.agents/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-zero-detected-tty-prompts", + "operation": "resolve-install-selection", + "description": "TTY with no detected hosts asks the user to choose from supported hosts.", + "options": { + "scope": "user", + "stdinTTY": true, + "yes": false, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "selection": { + "action": "select-agents", + "selectedHostIds": [], + "candidateCount": 72, + "detectedHostIds": [], + "needsConfirmation": true, + "errors": [] + } + } + }, + { + "id": "workflow-one-detected-auto-selects", + "operation": "resolve-install-selection", + "description": "TTY with one detected host can select it before summary confirmation.", + "options": { + "scope": "user", + "stdinTTY": true, + "yes": false, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {} + }, + "expected": { + "selection": { + "action": "install", + "selectedHostIds": [ + "codex" + ], + "candidateHostIds": [], + "detectedHostIds": [ + "codex" + ], + "needsConfirmation": true, + "errors": [] + } + } + }, + { + "id": "workflow-many-detected-tty-prompts", + "operation": "resolve-install-selection", + "description": "TTY with multiple detected hosts enters agent multi-selection instead of auto-installing all.", + "options": { + "scope": "user", + "stdinTTY": true, + "yes": false, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude" + ], + "files": {} + }, + "expected": { + "selection": { + "action": "select-agents", + "selectedHostIds": [], + "candidateHostIds": [ + "codex", + "claude-code" + ], + "detectedHostIds": [ + "codex", + "claude-code" + ], + "needsConfirmation": true, + "errors": [] + } + } + }, + { + "id": "workflow-many-detected-enter-cancels", + "operation": "run-install-workflow", + "description": "TTY multi-select with an empty selection cancels instead of installing every detected host.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "stdinTTY": true, + "yes": false, + "input": "\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude" + ], + "files": {} + }, + "expected": { + "workflow": { + "canceled": true, + "dryRun": false + }, + "exit": { + "ok": false, + "code": "canceled", + "message": "Installation canceled." + }, + "outputContains": [ + "Select agents:" + ], + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesAbsent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.claude/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-many-detected-select-one-confirms", + "operation": "run-install-workflow", + "description": "TTY multi-select installs only the selected host after summary confirmation.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "stdinTTY": true, + "yes": false, + "input": "1\ny\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude" + ], + "files": {} + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": true, + "code": "ok", + "message": "" + }, + "outputContains": [ + "Select agents:", + "(codex)", + "Proceed? [y/N]" + ], + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.agents/skills/basic/assets/template.json" + ], + "filesAbsent": [ + "$HOME/.claude/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-one-detected-confirms-installs", + "operation": "run-install-workflow", + "description": "TTY with one detected host still renders a summary and waits for confirmation before writing.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "stdinTTY": true, + "yes": false, + "input": "y\n", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {} + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": true, + "code": "ok", + "message": "" + }, + "outputContains": [ + "(codex)", + "Proceed? [y/N]" + ], + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-yes-batch-installs-detected", + "operation": "run-install-workflow", + "description": "Yes mode skips prompts and installs every detected host explicitly selected by policy.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "stdinTTY": false, + "yes": true, + "input": "", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude" + ], + "files": {} + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": false + }, + "exit": { + "ok": true, + "code": "ok", + "message": "" + }, + "outputContains": [ + "(codex)" + ], + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic" + }, + { + "hostId": "claude-code", + "skillName": "basic", + "targetDir": "$HOME/.claude/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/SKILL.md", + "$HOME/.claude/skills/basic/SKILL.md" + ] + } + }, + { + "id": "workflow-many-detected-yes-installs", + "operation": "resolve-install-selection", + "description": "Yes mode can accept detected hosts non-interactively.", + "options": { + "scope": "user", + "stdinTTY": false, + "yes": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex", + "$HOME/.claude" + ], + "files": {} + }, + "expected": { + "selection": { + "action": "install", + "selectedHostIds": [ + "codex", + "claude-code" + ], + "candidateHostIds": [], + "detectedHostIds": [ + "codex", + "claude-code" + ], + "needsConfirmation": false, + "errors": [] + } + } + }, + { + "id": "workflow-non-tty-no-agent-error", + "operation": "resolve-install-selection", + "description": "Non-TTY without explicit agent or yes cannot enter interactive selection.", + "options": { + "scope": "user", + "stdinTTY": false, + "yes": false, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {} + }, + "expected": { + "selection": { + "action": "error", + "selectedHostIds": [], + "candidateHostIds": [], + "detectedHostIds": [ + "codex" + ], + "needsConfirmation": false, + "errors": [ + { + "reason": "agent-selection-required" + } + ] + } + } + }, + { + "id": "workflow-zero-detected-yes-error", + "operation": "resolve-install-selection", + "description": "Yes mode does not turn zero detection into all-host installation.", + "options": { + "scope": "user", + "stdinTTY": false, + "yes": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": {} + }, + "expected": { + "selection": { + "action": "error", + "selectedHostIds": [], + "candidateHostIds": [], + "detectedHostIds": [], + "needsConfirmation": false, + "errors": [ + { + "reason": "no-detected-hosts" + } + ] + } + } + }, + { + "id": "github-bundle-install", + "operation": "install", + "description": "Installs a public GitHub bundle with provenance metadata while keeping the user-facing install flow unchanged.", + "options": { + "appId": "example-cli", + "githubBundle": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-basic", + "ref": "main" + }, + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {}, + "github": { + "owner": "acme", + "repo": "mycli-skills", + "ref": "main", + "commit": "abc123", + "treeSha": "tree123", + "files": { + "skills/github-basic/SKILL.md": "---\nname: github-basic\ndescription: GitHub sourced skill.\n---\n", + "skills/github-basic/references/guide.md": "GitHub guide.\n", + "other/SKILL.md": "---\nname: ignored\ndescription: Ignored skill.\n---\n" + } + } + }, + "expected": { + "filesPresent": [ + "$HOME/.agents/skills/github-basic/SKILL.md", + "$HOME/.agents/skills/github-basic/references/guide.md" + ], + "filesAbsent": [ + "$HOME/.agents/skills/github-basic/other/SKILL.md" + ], + "metadata": { + "path": "$HOME/.agents/skills/github-basic/.kitup.json", + "hash": "from-github-bundle", + "fields": { + "appId": "example-cli", + "skillName": "github-basic", + "source": "github", + "sourceId": "github:acme/mycli-skills/skills/github-basic", + "version": "main", + "provenance": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-basic", + "ref": "main", + "resolvedCommit": "abc123" + } + } + } + } + }, + { + "id": "github-bundle-mode-only-update-refreshes-metadata", + "operation": "install", + "description": "Refreshes GitHub provenance metadata when an unchanged content hash only needs mode repair.", + "options": { + "appId": "example-cli", + "githubBundle": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-mode", + "ref": "main" + }, + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": { + "$HOME/.agents/skills/github-mode/SKILL.md": "---\nname: github-mode\ndescription: GitHub mode skill.\n---\n", + "$HOME/.agents/skills/github-mode/scripts/helper.sh": "#!/bin/sh\n" + }, + "fileModes": { + "$HOME/.agents/skills/github-mode/scripts/helper.sh": "644" + }, + "metadata": { + "path": "$HOME/.agents/skills/github-mode/.kitup.json", + "hash": "from-github-bundle", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "github-mode", + "source": "github", + "sourceId": "github:acme/mycli-skills/skills/github-mode", + "version": "old", + "provenance": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-mode", + "ref": "old", + "resolvedCommit": "old123" + } + } + }, + "github": { + "owner": "acme", + "repo": "mycli-skills", + "ref": "main", + "commit": "new123", + "treeSha": "tree-mode", + "files": { + "skills/github-mode/SKILL.md": "---\nname: github-mode\ndescription: GitHub mode skill.\n---\n", + "skills/github-mode/scripts/helper.sh": "#!/bin/sh\n" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [ + { + "hostId": "codex", + "skillName": "github-mode", + "targetDir": "$HOME/.agents/skills/github-mode" + } + ], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "fileModes": { + "$HOME/.agents/skills/github-mode/scripts/helper.sh": "755" + }, + "metadata": { + "path": "$HOME/.agents/skills/github-mode/.kitup.json", + "hash": "from-github-bundle", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "github-mode", + "source": "github", + "sourceId": "github:acme/mycli-skills/skills/github-mode", + "version": "main", + "provenance": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-mode", + "ref": "main", + "resolvedCommit": "new123" + } + } + } + } + }, + { + "id": "github-bundle-dry-run", + "operation": "run-install-workflow", + "description": "Plans a GitHub bundle install without writing files in dry-run mode.", + "options": { + "appId": "example-cli", + "githubBundle": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-basic", + "ref": "main" + }, + "scope": "user", + "agents": [ + "codex" + ], + "yes": true, + "dryRun": true, + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {}, + "github": { + "owner": "acme", + "repo": "mycli-skills", + "ref": "main", + "commit": "abc123", + "treeSha": "tree123", + "files": { + "skills/github-basic/SKILL.md": "---\nname: github-basic\ndescription: GitHub sourced skill.\n---\n" + } + } + }, + "expected": { + "workflow": { + "canceled": false, + "dryRun": true + }, + "filesAbsent": [ + "$HOME/.agents/skills/github-basic/SKILL.md", + "$HOME/.agents/skills/github-basic/.kitup.json" + ] + } + }, + { + "id": "github-bundle-resolve-failure", + "operation": "install", + "description": "Reports a structured error when the configured GitHub bundle path has no files.", + "options": { + "appId": "example-cli", + "githubBundle": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/missing", + "ref": "main" + }, + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {}, + "github": { + "owner": "acme", + "repo": "mycli-skills", + "ref": "main", + "commit": "abc123", + "treeSha": "tree123", + "files": { + "skills/github-basic/SKILL.md": "---\nname: github-basic\ndescription: GitHub sourced skill.\n---\n" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [ + { + "reason": "bundle-resolve-failed" + } + ] + }, + "filesAbsent": [ + "$HOME/.agents/skills/github-basic/SKILL.md" + ] + } + }, + { + "id": "github-bundle-unchanged", + "operation": "install", + "description": "Skips a GitHub bundle install when kitup ownership and content hash are unchanged.", + "options": { + "appId": "example-cli", + "githubBundle": { + "owner": "acme", + "repo": "mycli-skills", + "path": "skills/github-basic", + "ref": "main" + }, + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex" + ], + "files": {}, + "metadata": { + "path": "$HOME/.agents/skills/github-basic/.kitup.json", + "hash": "from-github-bundle", + "fields": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "github-basic", + "source": "github" + } + }, + "github": { + "owner": "acme", + "repo": "mycli-skills", + "ref": "main", + "commit": "abc123", + "treeSha": "tree123", + "files": { + "skills/github-basic/SKILL.md": "---\nname: github-basic\ndescription: GitHub sourced skill.\n---\n" + } + } + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [ + { + "hostId": "codex", + "skillName": "github-basic", + "targetDir": "$HOME/.agents/skills/github-basic", + "reason": "unchanged" + } + ], + "conflicts": [], + "errors": [] + } + } + }, + { + "id": "uninstall-rejects-path-traversal-skill-name", + "operation": "uninstall", + "description": "Refuses skill names that escape the install root and never deletes a sibling owned install.", + "options": { + "appId": "example-cli", + "skillName": "../victim", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/victim" + ], + "files": { + "$HOME/.agents/victim/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "victim", + "source": "bundled", + "hash": "sha256:abc" + } + } + }, + "expected": { + "report": { + "removed": [], + "skipped": [], + "conflicts": [], + "errors": [ + { + "skillName": "../victim", + "reason": "invalid-skill-name" + } + ] + }, + "filesPresent": [ + "$HOME/.agents/victim/.kitup.json" + ] + } + }, + { + "id": "uninstall-incomplete-metadata-conflict", + "operation": "uninstall", + "description": "Treats incomplete .kitup.json as unmanaged and refuses deletion.", + "options": { + "appId": "example-cli", + "skillName": "basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "appId": "example-cli" + } + } + }, + "expected": { + "report": { + "removed": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "uninstall-skill-name-mismatch-conflict", + "operation": "uninstall", + "description": "Refuses deletion when metadata skillName does not exactly match the requested skill.", + "options": { + "appId": "example-cli", + "skillName": "basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "other", + "source": "bundled", + "hash": "sha256:abc" + } + } + }, + "expected": { + "report": { + "removed": [], + "skipped": [], + "conflicts": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.agents/skills/basic", + "reason": "unmanaged" + } + ], + "errors": [] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "hosts-file-rejects-escaped-project-path", + "operation": "install", + "description": "Rejects a custom hosts file whose project path escapes the workspace root.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "evil" + ], + "hostsFile": "$WORKSPACE/hosts.json", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": { + "$WORKSPACE/hosts.json": { + "schemaVersion": 1, + "hosts": [ + { + "id": "evil", + "displayName": "Evil", + "projectSkillsDirs": [ + "../outside" + ], + "userSkillsDirs": [], + "detect": [ + ".agents" + ], + "status": "community" + } + ] + } + } + }, + "expected": { + "throws": true, + "filesAbsent": [ + "$WORKSPACE/../outside/basic", + "$HOME/../outside/basic" + ] + } + }, + { + "id": "hosts-file-rejects-windows-project-path", + "operation": "install", + "description": "Rejects a custom hosts file whose Windows-style project path escapes the workspace root.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "project", + "agents": [ + "evil" + ], + "hostsFile": "$WORKSPACE/hosts.json", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": { + "$WORKSPACE/hosts.json": { + "schemaVersion": 1, + "hosts": [ + { + "id": "evil", + "displayName": "Evil", + "projectSkillsDirs": [ + "..\\outside" + ], + "userSkillsDirs": [], + "detect": [ + ".agents" + ], + "status": "community" + } + ] + } + } + }, + "expected": { + "throws": true, + "filesAbsent": [ + "$WORKSPACE/../outside/basic" + ] + } + }, + { + "id": "hosts-file-rejects-windows-home-path", + "operation": "install", + "description": "Rejects a custom hosts file whose home path contains a Windows volume path.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "evil" + ], + "hostsFile": "$WORKSPACE/hosts.json", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [], + "files": { + "$WORKSPACE/hosts.json": { + "schemaVersion": 1, + "hosts": [ + { + "id": "evil", + "displayName": "Evil", + "projectSkillsDirs": [], + "userSkillsDirs": [ + "~/C:/outside" + ], + "detect": [ + "~/.agents" + ], + "status": "community" + } + ] + } + } + }, + "expected": { + "throws": true, + "filesAbsent": [ + "$HOME/C:/outside/basic" + ] + } + }, + { + "id": "install-rejects-empty-app-id", + "operation": "install", + "description": "Rejects an empty owner id before writing install metadata.", + "options": { + "appId": "", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [ + { + "reason": "invalid-app-id" + } + ] + }, + "filesAbsent": [ + "$HOME/.agents/skills/basic" + ] + } + }, + { + "id": "uninstall-rejects-empty-app-id", + "operation": "uninstall", + "description": "Rejects an empty owner id before considering a managed install for deletion.", + "options": { + "appId": "", + "skillName": "basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills/basic" + ], + "files": { + "$HOME/.agents/skills/basic/.kitup.json": { + "schemaVersion": 1, + "appId": "example-cli", + "skillName": "basic", + "source": "bundled", + "hash": "sha256:abc" + } + } + }, + "expected": { + "report": { + "removed": [], + "skipped": [], + "conflicts": [], + "errors": [ + { + "reason": "invalid-app-id" + } + ] + }, + "filesPresent": [ + "$HOME/.agents/skills/basic/.kitup.json" + ] + } + }, + { + "id": "initial-install-copy-failure-is-atomic", + "operation": "install", + "description": "Leaves no target behind when a new install fails while copying its bundle.", + "options": { + "appId": "example-cli", + "skillFiles": [ + { + "path": "SKILL.md", + "contents": "---\nname: atomic-failure\ndescription: Atomic initial install fixture.\n---\n" + }, + { + "path": "a", + "contents": "file\n" + }, + { + "path": "a/b", + "contents": "nested\n" + } + ], + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents/skills" + ], + "files": {} + }, + "expected": { + "throws": true, + "filesAbsent": [ + "$HOME/.agents/skills/atomic-failure" + ] + } + } + ] +} diff --git a/go/testdata/skills/basic/SKILL.md b/go/testdata/skills/basic/SKILL.md new file mode 100644 index 0000000..0a45681 --- /dev/null +++ b/go/testdata/skills/basic/SKILL.md @@ -0,0 +1,12 @@ +--- +name: basic +description: Validate kitup installation behavior. Use when testing bundled skill copy, metadata, and nested resource handling. +--- + +# Basic Skill + +Use `references/guide.md` for the expected behavior. + +Use `assets/template.json` as fixture data when a test needs a bundled resource. + +Run `scripts/helper.sh` only when an embedding test explicitly asks for script behavior. diff --git a/go/testdata/skills/basic/assets/template.json b/go/testdata/skills/basic/assets/template.json new file mode 100644 index 0000000..8961d7f --- /dev/null +++ b/go/testdata/skills/basic/assets/template.json @@ -0,0 +1,4 @@ +{ + "name": "basic", + "purpose": "fixture" +} diff --git a/go/testdata/skills/basic/references/guide.md b/go/testdata/skills/basic/references/guide.md new file mode 100644 index 0000000..2949a95 --- /dev/null +++ b/go/testdata/skills/basic/references/guide.md @@ -0,0 +1,5 @@ +# Basic Fixture Guide + +This fixture represents a small valid Agent Skill with nested resources. + +Installers must copy this file with the rest of the skill directory. diff --git a/go/testdata/skills/basic/scripts/helper.sh b/go/testdata/skills/basic/scripts/helper.sh new file mode 100755 index 0000000..352e354 --- /dev/null +++ b/go/testdata/skills/basic/scripts/helper.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +printf '%s\n' "basic helper" diff --git a/go/testdata/skills/invalid-frontmatter/SKILL.md b/go/testdata/skills/invalid-frontmatter/SKILL.md new file mode 100644 index 0000000..da2e3d5 --- /dev/null +++ b/go/testdata/skills/invalid-frontmatter/SKILL.md @@ -0,0 +1,8 @@ +--- +name: Invalid Name +description: +--- + +# Invalid Frontmatter + +This fixture intentionally violates the Agent Skills frontmatter rules. diff --git a/go/testdata/skills/missing-skill-md/README.md b/go/testdata/skills/missing-skill-md/README.md new file mode 100644 index 0000000..f717a71 --- /dev/null +++ b/go/testdata/skills/missing-skill-md/README.md @@ -0,0 +1,3 @@ +# Missing SKILL Fixture + +This directory intentionally has no `SKILL.md`. diff --git a/python/src/kitup/_hosts_generated.py b/python/src/kitup/_hosts_generated.py index 71b68c1..788188d 100644 --- a/python/src/kitup/_hosts_generated.py +++ b/python/src/kitup/_hosts_generated.py @@ -1,3 +1,3 @@ # Code generated from spec/hosts.json. DO NOT EDIT. -DEFAULT_HOSTS_SPEC_JSON = "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\",\"~/.agents\",\"~/.claude\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\",\"~/.agents\",\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\",\"~/.agents\",\"~/.claude\",\"~/.codex\",\"~/.cursor\",\"~/.gemini\",\"~/.copilot\",\"~/.factory\",\"~/.github\",\"~/.opencode\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}" +DEFAULT_HOSTS_SPEC_JSON = "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}" diff --git a/python/src/kitup/hosts.py b/python/src/kitup/hosts.py index 046b697..87cc66c 100644 --- a/python/src/kitup/hosts.py +++ b/python/src/kitup/hosts.py @@ -4,7 +4,12 @@ from ._hosts_generated import DEFAULT_HOSTS_SPEC_JSON from .types import BaseOptions, Host, HostSpec, KitupError, Scope -_GENERIC_DETECT_PATHS = {"~/.agents", "~/.agents/skills", "~/.config/agents"} +_GENERIC_DETECT_PATHS = { + "~/.agents", + "~/.agents/skills", + "~/.config/agents", + "package.json", +} def load_host_spec(hosts_file: str | None = None) -> HostSpec: @@ -99,7 +104,7 @@ def detect_hosts(options: BaseOptions, scope: Scope | None = None) -> list[Host] cwd = Path(options.cwd) if options.cwd else Path.cwd() detected: list[Host] = [] for host in spec.hosts: - if _primary_detect_path_exists(host, home=home, cwd=cwd): + if _specific_detect_path_exists(host, home=home, cwd=cwd): detected.append(host) if scope is not None: @@ -121,13 +126,12 @@ def _canonical_scope_path( return _expand_host_path(paths[0], home=home, cwd=cwd) -def _primary_detect_path_exists(host: Host, *, home: Path, cwd: Path) -> bool: - if not host.detect: - return False - path = host.detect[0] - if path in _GENERIC_DETECT_PATHS: - return False - return _expand_host_path(path, home=home, cwd=cwd).exists() +def _specific_detect_path_exists(host: Host, *, home: Path, cwd: Path) -> bool: + return any( + path not in _GENERIC_DETECT_PATHS + and _expand_host_path(path, home=home, cwd=cwd).exists() + for path in host.detect + ) def _expand_host_path(path: str, *, home: Path, cwd: Path) -> Path: diff --git a/python/tests/golden_test.py b/python/tests/golden_test.py index 13460ef..ae08c4e 100644 --- a/python/tests/golden_test.py +++ b/python/tests/golden_test.py @@ -138,6 +138,8 @@ def run_case(case, home: Path, workspace: Path) -> None: case["options"]["scope"], ) assert [host.id for host in hosts] == case["expected"]["detectedHosts"] + if operation == "detect": + return if case["expected"].get("throws"): try: diff --git a/python/tests/test_hosts.py b/python/tests/test_hosts.py index 3323ec4..9ea54e1 100644 --- a/python/tests/test_hosts.py +++ b/python/tests/test_hosts.py @@ -85,7 +85,7 @@ def test_detect_hosts_skips_generic_detect_paths_and_sorts_by_scope_path(tmp_pat assert [host.id for host in hosts] == ["codex", "claude-code"] -def test_detect_hosts_does_not_scan_past_primary_generic_detect_path(tmp_path): +def test_detect_hosts_scans_past_primary_generic_detect_path(tmp_path): home = tmp_path / "home" workspace = tmp_path / "workspace" home.mkdir() @@ -100,4 +100,4 @@ def test_detect_hosts_does_not_scan_past_primary_generic_detect_path(tmp_path): scope="user", ) - assert "kimi-cli" not in [host.id for host in hosts] + assert [host.id for host in hosts] == ["kimi-cli"] diff --git a/rust/src/hosts_generated.rs b/rust/src/hosts_generated.rs index a75dd27..ddb4d1b 100644 --- a/rust/src/hosts_generated.rs +++ b/rust/src/hosts_generated.rs @@ -1,3 +1,3 @@ // Code generated from spec/hosts.json. DO NOT EDIT. -pub(crate) const DEFAULT_HOSTS_SPEC_JSON: &str = r#"{"$schema":"./hosts.schema.json","schemaVersion":1,"hosts":[{"id":"adal","displayName":"AdaL","projectSkillsDirs":[".adal/skills"],"userSkillsDirs":["~/.adal/skills"],"detect":["~/.adal"],"status":"community"},{"id":"aider-desk","displayName":"AiderDesk","projectSkillsDirs":[".aider-desk/skills"],"userSkillsDirs":["~/.aider-desk/skills"],"detect":["~/.aider-desk"],"status":"community"},{"id":"amp","displayName":"Amp","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills"],"detect":["~/.config/amp","~/.config/agents"],"status":"community"},{"id":"antigravity","displayName":"Antigravity","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.gemini/antigravity/skills"],"detect":["~/.gemini/antigravity"],"status":"community"},{"id":"antigravity-cli","displayName":"Antigravity CLI","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.gemini/antigravity-cli/skills"],"detect":["~/.gemini/antigravity-cli"],"status":"community"},{"id":"astrbot","displayName":"AstrBot","projectSkillsDirs":["data/skills"],"userSkillsDirs":["~/.astrbot/data/skills"],"detect":["~/.astrbot","data/skills","~/.astrbot/data"],"status":"community"},{"id":"augment","displayName":"Augment","projectSkillsDirs":[".augment/skills"],"userSkillsDirs":["~/.augment/skills"],"detect":["~/.augment"],"status":"community"},{"id":"autohand-code","displayName":"Autohand Code CLI","projectSkillsDirs":[".autohand/skills"],"userSkillsDirs":["~/.autohand/skills"],"detect":["~/.autohand"],"status":"community"},{"id":"bob","displayName":"IBM Bob","projectSkillsDirs":[".bob/skills"],"userSkillsDirs":["~/.bob/skills"],"detect":["~/.bob"],"status":"community"},{"id":"claude-code","displayName":"Claude Code","projectSkillsDirs":[".claude/skills"],"userSkillsDirs":["~/.claude/skills"],"detect":["~/.claude"],"status":"verified"},{"id":"cline","displayName":"Cline","projectSkillsDirs":[".agents/skills",".cline/skills",".clinerules/skills",".claude/skills"],"userSkillsDirs":["~/.agents/skills","~/.cline/skills"],"detect":["~/.cline","~/.agents"],"status":"documented"},{"id":"codearts-agent","displayName":"CodeArts Agent","projectSkillsDirs":[".codeartsdoer/skills"],"userSkillsDirs":["~/.codeartsdoer/skills"],"detect":["~/.codeartsdoer"],"status":"community"},{"id":"codebuddy","displayName":"CodeBuddy","projectSkillsDirs":[".codebuddy/skills"],"userSkillsDirs":["~/.codebuddy/skills"],"detect":["~/.codebuddy",".codebuddy"],"status":"community"},{"id":"codemaker","displayName":"Codemaker","projectSkillsDirs":[".codemaker/skills"],"userSkillsDirs":["~/.codemaker/skills"],"detect":["~/.codemaker"],"status":"community"},{"id":"codestudio","displayName":"Code Studio","projectSkillsDirs":[".codestudio/skills"],"userSkillsDirs":["~/.codestudio/skills"],"detect":["~/.codestudio"],"status":"community"},{"id":"codex","displayName":"Codex","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills","~/.codex/skills"],"detect":["~/.codex","~/.agents/skills","~/.agents"],"status":"verified","notes":["Keep both ~/.agents/skills and ~/.codex/skills for compatibility."]},{"id":"command-code","displayName":"Command Code","projectSkillsDirs":[".commandcode/skills"],"userSkillsDirs":["~/.commandcode/skills"],"detect":["~/.commandcode"],"status":"community"},{"id":"continue","displayName":"Continue","projectSkillsDirs":[".continue/skills"],"userSkillsDirs":["~/.continue/skills"],"detect":["~/.continue",".continue"],"status":"community"},{"id":"cortex","displayName":"Cortex Code","projectSkillsDirs":[".cortex/skills"],"userSkillsDirs":["~/.snowflake/cortex/skills"],"detect":["~/.snowflake/cortex"],"status":"community"},{"id":"crush","displayName":"Crush","projectSkillsDirs":[".crush/skills"],"userSkillsDirs":["~/.config/crush/skills"],"detect":["~/.config/crush"],"status":"community"},{"id":"cursor","displayName":"Cursor","projectSkillsDirs":[".agents/skills",".cursor/skills"],"userSkillsDirs":["~/.cursor/skills","~/.agents/skills"],"detect":["~/.cursor","~/.agents"],"status":"documented"},{"id":"deepagents","displayName":"Deep Agents","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.deepagents/agent/skills"],"detect":["~/.deepagents","~/.deepagents/agent"],"status":"community"},{"id":"devin","displayName":"Devin for Terminal","projectSkillsDirs":[".devin/skills"],"userSkillsDirs":["~/.config/devin/skills"],"detect":["~/.config/devin"],"status":"community"},{"id":"dexto","displayName":"Dexto","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.dexto","~/.agents"],"status":"community"},{"id":"droid","displayName":"Droid","projectSkillsDirs":[".factory/skills"],"userSkillsDirs":["~/.factory/skills"],"detect":["~/.factory"],"status":"community"},{"id":"eve","displayName":"Eve","projectSkillsDirs":["agent/skills"],"userSkillsDirs":[],"detect":["agent","package.json"],"status":"community","notes":["Project-only host; userSkillsDirs is intentionally empty.","Detect from Eve project shape; no global skill directory."]},{"id":"firebender","displayName":"Firebender","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.firebender/skills"],"detect":["~/.firebender"],"status":"community"},{"id":"forgecode","displayName":"ForgeCode","projectSkillsDirs":[".forge/skills"],"userSkillsDirs":["~/.forge/skills"],"detect":["~/.forge"],"status":"community"},{"id":"gemini-cli","displayName":"Gemini CLI","projectSkillsDirs":[".agents/skills",".gemini/skills"],"userSkillsDirs":["~/.gemini/skills","~/.agents/skills"],"detect":["~/.gemini","~/.agents"],"status":"documented"},{"id":"github-copilot","displayName":"GitHub Copilot","projectSkillsDirs":[".agents/skills",".github/skills",".claude/skills"],"userSkillsDirs":["~/.copilot/skills","~/.agents/skills","~/.claude/skills"],"detect":["~/.copilot","~/.agents","~/.claude"],"status":"documented"},{"id":"goose","displayName":"Goose","projectSkillsDirs":[".goose/skills"],"userSkillsDirs":["~/.config/goose/skills"],"detect":["~/.config/goose"],"status":"community"},{"id":"hermes-agent","displayName":"Hermes Agent","projectSkillsDirs":[".hermes/skills"],"userSkillsDirs":["~/.hermes/skills"],"detect":["~/.hermes"],"status":"community"},{"id":"iflow-cli","displayName":"iFlow CLI","projectSkillsDirs":[".iflow/skills"],"userSkillsDirs":["~/.iflow/skills"],"detect":["~/.iflow"],"status":"community"},{"id":"inference-sh","displayName":"inference.sh","projectSkillsDirs":[".inferencesh/skills"],"userSkillsDirs":["~/.inferencesh/skills"],"detect":["~/.inferencesh"],"status":"community"},{"id":"jazz","displayName":"Jazz","projectSkillsDirs":[".jazz/skills"],"userSkillsDirs":["~/.jazz/skills"],"detect":["~/.jazz",".jazz"],"status":"community"},{"id":"junie","displayName":"Junie","projectSkillsDirs":[".junie/skills"],"userSkillsDirs":["~/.junie/skills"],"detect":["~/.junie"],"status":"community"},{"id":"kilo","displayName":"Kilo Code","projectSkillsDirs":[".kilocode/skills"],"userSkillsDirs":["~/.kilocode/skills"],"detect":["~/.kilocode"],"status":"community"},{"id":"kimi-cli","displayName":"Kimi Code CLI","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills","~/.agents/skills"],"detect":["~/.config/agents","~/.kimi-code","~/.kimi","~/.agents"],"status":"community","notes":["kimi-code-cli is an alias for the same Kimi Code CLI path family."],"aliases":["kimi-code-cli"]},{"id":"kiro-cli","displayName":"Kiro CLI","projectSkillsDirs":[".kiro/skills"],"userSkillsDirs":["~/.kiro/skills"],"detect":["~/.kiro"],"status":"community"},{"id":"kode","displayName":"Kode","projectSkillsDirs":[".kode/skills"],"userSkillsDirs":["~/.kode/skills"],"detect":["~/.kode"],"status":"community"},{"id":"lingma","displayName":"Lingma","projectSkillsDirs":[".lingma/skills"],"userSkillsDirs":["~/.lingma/skills"],"detect":["~/.lingma"],"status":"community"},{"id":"loaf","displayName":"Loaf","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.loaf","~/.agents"],"status":"community"},{"id":"mcpjam","displayName":"MCPJam","projectSkillsDirs":[".mcpjam/skills"],"userSkillsDirs":["~/.mcpjam/skills"],"detect":["~/.mcpjam"],"status":"community"},{"id":"mistral-vibe","displayName":"Mistral Vibe","projectSkillsDirs":[".vibe/skills"],"userSkillsDirs":["~/.vibe/skills"],"detect":["~/.vibe"],"status":"community"},{"id":"moxby","displayName":"Moxby","projectSkillsDirs":[".moxby/skills"],"userSkillsDirs":["~/.moxby/skills"],"detect":["~/.moxby"],"status":"community"},{"id":"mux","displayName":"Mux","projectSkillsDirs":[".mux/skills"],"userSkillsDirs":["~/.mux/skills"],"detect":["~/.mux"],"status":"community"},{"id":"neovate","displayName":"Neovate","projectSkillsDirs":[".neovate/skills"],"userSkillsDirs":["~/.neovate/skills"],"detect":["~/.neovate"],"status":"community"},{"id":"ona","displayName":"Ona","projectSkillsDirs":[".ona/skills"],"userSkillsDirs":["~/.ona/skills"],"detect":["~/.ona"],"status":"community"},{"id":"openclaw","displayName":"OpenClaw","projectSkillsDirs":["skills"],"userSkillsDirs":["~/.openclaw/skills"],"detect":["~/.openclaw","~/.clawdbot","~/.moltbot"],"status":"community"},{"id":"opencode","displayName":"OpenCode","projectSkillsDirs":[".agents/skills",".opencode/skills",".claude/skills"],"userSkillsDirs":["~/.config/opencode/skills","~/.agents/skills","~/.claude/skills"],"detect":["~/.config/opencode","~/.agents","~/.claude"],"status":"verified"},{"id":"openhands","displayName":"OpenHands","projectSkillsDirs":[".openhands/skills"],"userSkillsDirs":["~/.openhands/skills"],"detect":["~/.openhands"],"status":"community"},{"id":"pi","displayName":"Pi","projectSkillsDirs":[".pi/skills"],"userSkillsDirs":["~/.pi/agent/skills"],"detect":["~/.pi/agent"],"status":"community"},{"id":"pochi","displayName":"Pochi","projectSkillsDirs":[".pochi/skills"],"userSkillsDirs":["~/.pochi/skills"],"detect":["~/.pochi"],"status":"community"},{"id":"promptscript","displayName":"PromptScript","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":[],"detect":[".promptscript","promptscript.yaml"],"status":"community","notes":["Project-only host; userSkillsDirs is intentionally empty."]},{"id":"qoder","displayName":"Qoder","projectSkillsDirs":[".qoder/skills"],"userSkillsDirs":["~/.qoder/skills"],"detect":["~/.qoder"],"status":"community"},{"id":"qoder-cn","displayName":"Qoder CN","projectSkillsDirs":[".qoder/skills"],"userSkillsDirs":["~/.qoder-cn/skills"],"detect":["~/.qoder-cn"],"status":"community"},{"id":"qwen-code","displayName":"Qwen Code","projectSkillsDirs":[".qwen/skills"],"userSkillsDirs":["~/.qwen/skills"],"detect":["~/.qwen"],"status":"community"},{"id":"reasonix","displayName":"Reasonix","projectSkillsDirs":[".reasonix/skills"],"userSkillsDirs":["~/.reasonix/skills"],"detect":["~/.reasonix"],"status":"community"},{"id":"replit","displayName":"Replit","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills"],"detect":[".replit","~/.config/agents"],"status":"community"},{"id":"roo","displayName":"Roo Code","aliases":["roo-code"],"projectSkillsDirs":[".roo/skills",".agents/skills"],"userSkillsDirs":["~/.roo/skills","~/.agents/skills"],"detect":["~/.roo","~/.agents"],"status":"documented"},{"id":"rovodev","displayName":"Rovo Dev","projectSkillsDirs":[".rovodev/skills"],"userSkillsDirs":["~/.rovodev/skills"],"detect":["~/.rovodev"],"status":"community"},{"id":"tabnine-cli","displayName":"Tabnine CLI","projectSkillsDirs":[".tabnine/agent/skills"],"userSkillsDirs":["~/.tabnine/agent/skills"],"detect":["~/.tabnine","~/.tabnine/agent"],"status":"community"},{"id":"terramind","displayName":"Terramind","projectSkillsDirs":[".terramind/skills"],"userSkillsDirs":["~/.terramind/skills"],"detect":["~/.terramind"],"status":"community"},{"id":"tinycloud","displayName":"Tinycloud","projectSkillsDirs":[".tinycloud/skills"],"userSkillsDirs":["~/.tinycloud/skills"],"detect":["~/.tinycloud"],"status":"community"},{"id":"trae","displayName":"Trae","projectSkillsDirs":[".trae/skills"],"userSkillsDirs":["~/.trae/skills"],"detect":["~/.trae"],"status":"community"},{"id":"trae-cn","displayName":"Trae CN","projectSkillsDirs":[".trae/skills"],"userSkillsDirs":["~/.trae-cn/skills"],"detect":["~/.trae-cn"],"status":"community"},{"id":"universal","displayName":"Universal","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills","~/.config/agents/skills"],"detect":["~/.agents","~/.config/agents"],"status":"community"},{"id":"warp","displayName":"Warp","projectSkillsDirs":[".agents/skills",".warp/skills",".claude/skills",".codex/skills",".cursor/skills",".gemini/skills",".copilot/skills",".factory/skills",".github/skills",".opencode/skills"],"userSkillsDirs":["~/.agents/skills","~/.warp/skills","~/.claude/skills","~/.codex/skills","~/.cursor/skills","~/.gemini/skills","~/.copilot/skills","~/.factory/skills","~/.github/skills","~/.opencode/skills"],"detect":["~/.warp","~/.agents","~/.claude","~/.codex","~/.cursor","~/.gemini","~/.copilot","~/.factory","~/.github","~/.opencode"],"status":"documented"},{"id":"windsurf","displayName":"Windsurf","projectSkillsDirs":[".windsurf/skills"],"userSkillsDirs":["~/.codeium/windsurf/skills"],"detect":["~/.codeium/windsurf"],"status":"community"},{"id":"zed","displayName":"Zed","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.config/zed","~/.agents"],"status":"community"},{"id":"zencoder","displayName":"Zencoder","projectSkillsDirs":[".zencoder/skills"],"userSkillsDirs":["~/.zencoder/skills"],"detect":["~/.zencoder"],"status":"community"},{"id":"zenflow","displayName":"Zenflow","projectSkillsDirs":[".zencoder/skills"],"userSkillsDirs":["~/.zencoder/skills"],"detect":["~/.zencoder"],"status":"community"}]}"#; +pub(crate) const DEFAULT_HOSTS_SPEC_JSON: &str = r#"{"$schema":"./hosts.schema.json","schemaVersion":1,"hosts":[{"id":"adal","displayName":"AdaL","projectSkillsDirs":[".adal/skills"],"userSkillsDirs":["~/.adal/skills"],"detect":["~/.adal"],"status":"community"},{"id":"aider-desk","displayName":"AiderDesk","projectSkillsDirs":[".aider-desk/skills"],"userSkillsDirs":["~/.aider-desk/skills"],"detect":["~/.aider-desk"],"status":"community"},{"id":"amp","displayName":"Amp","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills"],"detect":["~/.config/amp","~/.config/agents"],"status":"community"},{"id":"antigravity","displayName":"Antigravity","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.gemini/antigravity/skills"],"detect":["~/.gemini/antigravity"],"status":"community"},{"id":"antigravity-cli","displayName":"Antigravity CLI","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.gemini/antigravity-cli/skills"],"detect":["~/.gemini/antigravity-cli"],"status":"community"},{"id":"astrbot","displayName":"AstrBot","projectSkillsDirs":["data/skills"],"userSkillsDirs":["~/.astrbot/data/skills"],"detect":["~/.astrbot","data/skills","~/.astrbot/data"],"status":"community"},{"id":"augment","displayName":"Augment","projectSkillsDirs":[".augment/skills"],"userSkillsDirs":["~/.augment/skills"],"detect":["~/.augment"],"status":"community"},{"id":"autohand-code","displayName":"Autohand Code CLI","projectSkillsDirs":[".autohand/skills"],"userSkillsDirs":["~/.autohand/skills"],"detect":["~/.autohand"],"status":"community"},{"id":"bob","displayName":"IBM Bob","projectSkillsDirs":[".bob/skills"],"userSkillsDirs":["~/.bob/skills"],"detect":["~/.bob"],"status":"community"},{"id":"claude-code","displayName":"Claude Code","projectSkillsDirs":[".claude/skills"],"userSkillsDirs":["~/.claude/skills"],"detect":["~/.claude"],"status":"verified"},{"id":"cline","displayName":"Cline","projectSkillsDirs":[".agents/skills",".cline/skills",".clinerules/skills",".claude/skills"],"userSkillsDirs":["~/.agents/skills","~/.cline/skills"],"detect":["~/.cline","~/.agents"],"status":"documented"},{"id":"codearts-agent","displayName":"CodeArts Agent","projectSkillsDirs":[".codeartsdoer/skills"],"userSkillsDirs":["~/.codeartsdoer/skills"],"detect":["~/.codeartsdoer"],"status":"community"},{"id":"codebuddy","displayName":"CodeBuddy","projectSkillsDirs":[".codebuddy/skills"],"userSkillsDirs":["~/.codebuddy/skills"],"detect":["~/.codebuddy",".codebuddy"],"status":"community"},{"id":"codemaker","displayName":"Codemaker","projectSkillsDirs":[".codemaker/skills"],"userSkillsDirs":["~/.codemaker/skills"],"detect":["~/.codemaker"],"status":"community"},{"id":"codestudio","displayName":"Code Studio","projectSkillsDirs":[".codestudio/skills"],"userSkillsDirs":["~/.codestudio/skills"],"detect":["~/.codestudio"],"status":"community"},{"id":"codex","displayName":"Codex","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills","~/.codex/skills"],"detect":["~/.codex","~/.agents/skills","~/.agents"],"status":"verified","notes":["Keep both ~/.agents/skills and ~/.codex/skills for compatibility."]},{"id":"command-code","displayName":"Command Code","projectSkillsDirs":[".commandcode/skills"],"userSkillsDirs":["~/.commandcode/skills"],"detect":["~/.commandcode"],"status":"community"},{"id":"continue","displayName":"Continue","projectSkillsDirs":[".continue/skills"],"userSkillsDirs":["~/.continue/skills"],"detect":["~/.continue",".continue"],"status":"community"},{"id":"cortex","displayName":"Cortex Code","projectSkillsDirs":[".cortex/skills"],"userSkillsDirs":["~/.snowflake/cortex/skills"],"detect":["~/.snowflake/cortex"],"status":"community"},{"id":"crush","displayName":"Crush","projectSkillsDirs":[".crush/skills"],"userSkillsDirs":["~/.config/crush/skills"],"detect":["~/.config/crush"],"status":"community"},{"id":"cursor","displayName":"Cursor","projectSkillsDirs":[".agents/skills",".cursor/skills"],"userSkillsDirs":["~/.cursor/skills","~/.agents/skills"],"detect":["~/.cursor","~/.agents"],"status":"documented"},{"id":"deepagents","displayName":"Deep Agents","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.deepagents/agent/skills"],"detect":["~/.deepagents","~/.deepagents/agent"],"status":"community"},{"id":"devin","displayName":"Devin for Terminal","projectSkillsDirs":[".devin/skills"],"userSkillsDirs":["~/.config/devin/skills"],"detect":["~/.config/devin"],"status":"community"},{"id":"dexto","displayName":"Dexto","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.dexto","~/.agents"],"status":"community"},{"id":"droid","displayName":"Droid","projectSkillsDirs":[".factory/skills"],"userSkillsDirs":["~/.factory/skills"],"detect":["~/.factory"],"status":"community"},{"id":"eve","displayName":"Eve","projectSkillsDirs":["agent/skills"],"userSkillsDirs":[],"detect":["agent","package.json"],"status":"community","notes":["Project-only host; userSkillsDirs is intentionally empty.","Detect from Eve project shape; no global skill directory."]},{"id":"firebender","displayName":"Firebender","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.firebender/skills"],"detect":["~/.firebender"],"status":"community"},{"id":"forgecode","displayName":"ForgeCode","projectSkillsDirs":[".forge/skills"],"userSkillsDirs":["~/.forge/skills"],"detect":["~/.forge"],"status":"community"},{"id":"gemini-cli","displayName":"Gemini CLI","projectSkillsDirs":[".agents/skills",".gemini/skills"],"userSkillsDirs":["~/.gemini/skills","~/.agents/skills"],"detect":["~/.gemini","~/.agents"],"status":"documented"},{"id":"github-copilot","displayName":"GitHub Copilot","projectSkillsDirs":[".agents/skills",".github/skills",".claude/skills"],"userSkillsDirs":["~/.copilot/skills","~/.agents/skills","~/.claude/skills"],"detect":["~/.copilot"],"status":"documented"},{"id":"goose","displayName":"Goose","projectSkillsDirs":[".goose/skills"],"userSkillsDirs":["~/.config/goose/skills"],"detect":["~/.config/goose"],"status":"community"},{"id":"hermes-agent","displayName":"Hermes Agent","projectSkillsDirs":[".hermes/skills"],"userSkillsDirs":["~/.hermes/skills"],"detect":["~/.hermes"],"status":"community"},{"id":"iflow-cli","displayName":"iFlow CLI","projectSkillsDirs":[".iflow/skills"],"userSkillsDirs":["~/.iflow/skills"],"detect":["~/.iflow"],"status":"community"},{"id":"inference-sh","displayName":"inference.sh","projectSkillsDirs":[".inferencesh/skills"],"userSkillsDirs":["~/.inferencesh/skills"],"detect":["~/.inferencesh"],"status":"community"},{"id":"jazz","displayName":"Jazz","projectSkillsDirs":[".jazz/skills"],"userSkillsDirs":["~/.jazz/skills"],"detect":["~/.jazz",".jazz"],"status":"community"},{"id":"junie","displayName":"Junie","projectSkillsDirs":[".junie/skills"],"userSkillsDirs":["~/.junie/skills"],"detect":["~/.junie"],"status":"community"},{"id":"kilo","displayName":"Kilo Code","projectSkillsDirs":[".kilocode/skills"],"userSkillsDirs":["~/.kilocode/skills"],"detect":["~/.kilocode"],"status":"community"},{"id":"kimi-cli","displayName":"Kimi Code CLI","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills","~/.agents/skills"],"detect":["~/.config/agents","~/.kimi-code","~/.kimi","~/.agents"],"status":"community","notes":["kimi-code-cli is an alias for the same Kimi Code CLI path family."],"aliases":["kimi-code-cli"]},{"id":"kiro-cli","displayName":"Kiro CLI","projectSkillsDirs":[".kiro/skills"],"userSkillsDirs":["~/.kiro/skills"],"detect":["~/.kiro"],"status":"community"},{"id":"kode","displayName":"Kode","projectSkillsDirs":[".kode/skills"],"userSkillsDirs":["~/.kode/skills"],"detect":["~/.kode"],"status":"community"},{"id":"lingma","displayName":"Lingma","projectSkillsDirs":[".lingma/skills"],"userSkillsDirs":["~/.lingma/skills"],"detect":["~/.lingma"],"status":"community"},{"id":"loaf","displayName":"Loaf","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.loaf","~/.agents"],"status":"community"},{"id":"mcpjam","displayName":"MCPJam","projectSkillsDirs":[".mcpjam/skills"],"userSkillsDirs":["~/.mcpjam/skills"],"detect":["~/.mcpjam"],"status":"community"},{"id":"mistral-vibe","displayName":"Mistral Vibe","projectSkillsDirs":[".vibe/skills"],"userSkillsDirs":["~/.vibe/skills"],"detect":["~/.vibe"],"status":"community"},{"id":"moxby","displayName":"Moxby","projectSkillsDirs":[".moxby/skills"],"userSkillsDirs":["~/.moxby/skills"],"detect":["~/.moxby"],"status":"community"},{"id":"mux","displayName":"Mux","projectSkillsDirs":[".mux/skills"],"userSkillsDirs":["~/.mux/skills"],"detect":["~/.mux"],"status":"community"},{"id":"neovate","displayName":"Neovate","projectSkillsDirs":[".neovate/skills"],"userSkillsDirs":["~/.neovate/skills"],"detect":["~/.neovate"],"status":"community"},{"id":"ona","displayName":"Ona","projectSkillsDirs":[".ona/skills"],"userSkillsDirs":["~/.ona/skills"],"detect":["~/.ona"],"status":"community"},{"id":"openclaw","displayName":"OpenClaw","projectSkillsDirs":["skills"],"userSkillsDirs":["~/.openclaw/skills"],"detect":["~/.openclaw","~/.clawdbot","~/.moltbot"],"status":"community"},{"id":"opencode","displayName":"OpenCode","projectSkillsDirs":[".agents/skills",".opencode/skills",".claude/skills"],"userSkillsDirs":["~/.config/opencode/skills","~/.agents/skills","~/.claude/skills"],"detect":["~/.config/opencode"],"status":"verified"},{"id":"openhands","displayName":"OpenHands","projectSkillsDirs":[".openhands/skills"],"userSkillsDirs":["~/.openhands/skills"],"detect":["~/.openhands"],"status":"community"},{"id":"pi","displayName":"Pi","projectSkillsDirs":[".pi/skills"],"userSkillsDirs":["~/.pi/agent/skills"],"detect":["~/.pi/agent"],"status":"community"},{"id":"pochi","displayName":"Pochi","projectSkillsDirs":[".pochi/skills"],"userSkillsDirs":["~/.pochi/skills"],"detect":["~/.pochi"],"status":"community"},{"id":"promptscript","displayName":"PromptScript","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":[],"detect":[".promptscript","promptscript.yaml"],"status":"community","notes":["Project-only host; userSkillsDirs is intentionally empty."]},{"id":"qoder","displayName":"Qoder","projectSkillsDirs":[".qoder/skills"],"userSkillsDirs":["~/.qoder/skills"],"detect":["~/.qoder"],"status":"community"},{"id":"qoder-cn","displayName":"Qoder CN","projectSkillsDirs":[".qoder/skills"],"userSkillsDirs":["~/.qoder-cn/skills"],"detect":["~/.qoder-cn"],"status":"community"},{"id":"qwen-code","displayName":"Qwen Code","projectSkillsDirs":[".qwen/skills"],"userSkillsDirs":["~/.qwen/skills"],"detect":["~/.qwen"],"status":"community"},{"id":"reasonix","displayName":"Reasonix","projectSkillsDirs":[".reasonix/skills"],"userSkillsDirs":["~/.reasonix/skills"],"detect":["~/.reasonix"],"status":"community"},{"id":"replit","displayName":"Replit","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.config/agents/skills"],"detect":[".replit","~/.config/agents"],"status":"community"},{"id":"roo","displayName":"Roo Code","aliases":["roo-code"],"projectSkillsDirs":[".roo/skills",".agents/skills"],"userSkillsDirs":["~/.roo/skills","~/.agents/skills"],"detect":["~/.roo","~/.agents"],"status":"documented"},{"id":"rovodev","displayName":"Rovo Dev","projectSkillsDirs":[".rovodev/skills"],"userSkillsDirs":["~/.rovodev/skills"],"detect":["~/.rovodev"],"status":"community"},{"id":"tabnine-cli","displayName":"Tabnine CLI","projectSkillsDirs":[".tabnine/agent/skills"],"userSkillsDirs":["~/.tabnine/agent/skills"],"detect":["~/.tabnine","~/.tabnine/agent"],"status":"community"},{"id":"terramind","displayName":"Terramind","projectSkillsDirs":[".terramind/skills"],"userSkillsDirs":["~/.terramind/skills"],"detect":["~/.terramind"],"status":"community"},{"id":"tinycloud","displayName":"Tinycloud","projectSkillsDirs":[".tinycloud/skills"],"userSkillsDirs":["~/.tinycloud/skills"],"detect":["~/.tinycloud"],"status":"community"},{"id":"trae","displayName":"Trae","projectSkillsDirs":[".trae/skills"],"userSkillsDirs":["~/.trae/skills"],"detect":["~/.trae"],"status":"community"},{"id":"trae-cn","displayName":"Trae CN","projectSkillsDirs":[".trae/skills"],"userSkillsDirs":["~/.trae-cn/skills"],"detect":["~/.trae-cn"],"status":"community"},{"id":"universal","displayName":"Universal","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills","~/.config/agents/skills"],"detect":["~/.agents","~/.config/agents"],"status":"community"},{"id":"warp","displayName":"Warp","projectSkillsDirs":[".agents/skills",".warp/skills",".claude/skills",".codex/skills",".cursor/skills",".gemini/skills",".copilot/skills",".factory/skills",".github/skills",".opencode/skills"],"userSkillsDirs":["~/.agents/skills","~/.warp/skills","~/.claude/skills","~/.codex/skills","~/.cursor/skills","~/.gemini/skills","~/.copilot/skills","~/.factory/skills","~/.github/skills","~/.opencode/skills"],"detect":["~/.warp"],"status":"documented"},{"id":"windsurf","displayName":"Windsurf","projectSkillsDirs":[".windsurf/skills"],"userSkillsDirs":["~/.codeium/windsurf/skills"],"detect":["~/.codeium/windsurf"],"status":"community"},{"id":"zed","displayName":"Zed","projectSkillsDirs":[".agents/skills"],"userSkillsDirs":["~/.agents/skills"],"detect":["~/.config/zed","~/.agents"],"status":"community"},{"id":"zencoder","displayName":"Zencoder","projectSkillsDirs":[".zencoder/skills"],"userSkillsDirs":["~/.zencoder/skills"],"detect":["~/.zencoder"],"status":"community"},{"id":"zenflow","displayName":"Zenflow","projectSkillsDirs":[".zencoder/skills"],"userSkillsDirs":["~/.zencoder/skills"],"detect":["~/.zencoder"],"status":"community"}]}"#; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7465e7c..a45c14f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -551,10 +551,10 @@ pub fn detect_hosts(options: &BaseOptions, scope: Option) -> io::Result bool { } fn is_generic_detect_path(path: &str) -> bool { - path == "~/.agents" || path == "~/.agents/skills" || path == "~/.config/agents" + path == "~/.agents" + || path == "~/.agents/skills" + || path == "~/.config/agents" + || path == "package.json" } fn scope_text(scope: Scope) -> &'static str { diff --git a/rust/tests/golden.rs b/rust/tests/golden.rs index 6783ce8..0d3a909 100644 --- a/rust/tests/golden.rs +++ b/rust/tests/golden.rs @@ -272,6 +272,9 @@ fn run_case(case: &GoldenCase, home: &Path, workspace: &Path) { detect_hosts(&base, Some(scope(options["scope"].as_str().unwrap()))).unwrap(); assert_json_eq(&json!(host_ids(&hosts)), expected.clone()); } + if case.operation == "detect" { + return; + } let result = run_report_case(case, options, base); if case.expected.get("throws").and_then(Value::as_bool) == Some(true) { assert!(result.is_err(), "expected operation to throw"); diff --git a/scripts/check-go-cobra.sh b/scripts/check-go-cobra.sh new file mode 100644 index 0000000..4a1f0a5 --- /dev/null +++ b/scripts/check-go-cobra.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT INT TERM + +mkdir -p "$tmp/modules" +tar -C "$root" -cf "$tmp/go.tar" go +tar -C "$root" -cf "$tmp/go-cobra.tar" go-cobra +tar -C "$tmp/modules" -xf "$tmp/go.tar" +tar -C "$tmp/modules" -xf "$tmp/go-cobra.tar" + +cd "$tmp/modules/go-cobra" +go mod edit "-replace=github.com/lathe-cli/kitup/go=$tmp/modules/go" +GOWORK=off go test ./... + +echo "ok: Go Cobra adapter passes against the local core module" diff --git a/scripts/check-go-release.sh b/scripts/check-go-release.sh new file mode 100644 index 0000000..1240269 --- /dev/null +++ b/scripts/check-go-release.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -eu + +root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +version="$(node -p 'require(process.argv[1]).version' "$root/ts/package.json")" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT INT TERM + +mkdir -p "$tmp/modules" +tar -C "$root" -cf "$tmp/go.tar" go +tar -C "$root" -cf "$tmp/go-cobra.tar" go-cobra +tar -C "$tmp/modules" -xf "$tmp/go.tar" +tar -C "$tmp/modules" -xf "$tmp/go-cobra.tar" + +( + cd "$tmp/modules/go" + GOWORK=off go test ./... +) +( + cd "$tmp/modules/go-cobra" + GOWORK=off go test ./... +) + +consumer="$tmp/consumer" +mkdir -p "$consumer" +cd "$consumer" +go mod init kitup-release-consumer >/dev/null +go mod edit \ + "-require=github.com/lathe-cli/kitup/go@v$version" \ + "-require=github.com/lathe-cli/kitup/go-cobra@v$version" \ + "-replace=github.com/lathe-cli/kitup/go=$tmp/modules/go" \ + "-replace=github.com/lathe-cli/kitup/go-cobra=$tmp/modules/go-cobra" + +cat > main.go <<'GO' +package main + +import ( + "fmt" + + kitup "github.com/lathe-cli/kitup/go" + kitupcobra "github.com/lathe-cli/kitup/go-cobra" +) + +func main() { + cmd := kitupcobra.NewSkillCommand(kitupcobra.Options{}) + if cmd.Use != kitup.InstallUX.SkillUse { + panic(fmt.Sprintf("expected %s, got %s", kitup.InstallUX.SkillUse, cmd.Use)) + } +} +GO + +GOWORK=off go mod tidy +GOWORK=off go list -m all >/dev/null +GOWORK=off go test ./... +GOWORK=off go build . + +test "$(GOWORK=off go list -m -f '{{.Version}}' github.com/lathe-cli/kitup/go)" = "v$version" +test "$(GOWORK=off go list -m -f '{{.Version}}' github.com/lathe-cli/kitup/go-cobra)" = "v$version" + +echo "ok: packaged Go modules are self-contained at v$version" diff --git a/scripts/check.mjs b/scripts/check.mjs index c67bbff..75b6879 100755 --- a/scripts/check.mjs +++ b/scripts/check.mjs @@ -157,12 +157,15 @@ function validateCases(cases, hosts) { "parse-install-flags-errors", "shared-target-deduplication-many-hosts", "user-scope-install", + "codex-user-scope-reuses-existing-compatible-dir", "codex-user-scope-prefers-first-user-dir", "project-scope-install", "project-scope-plan", "project-only-host-project-scope-install", "project-only-host-user-scope-error", "auto-host-detection", + "auto-host-detection-secondary-specific-path", + "auto-host-detection-generic-path-only", "auto-host-detection-empty", "unchanged-noop", "unchanged-repairs-script-mode", @@ -331,6 +334,13 @@ for (const [group, name, command, args, cwd, env] of [ ["scripts/sync-hosts.mjs", "--check"], rootPath, ], + [ + "source", + "go-test-fixtures", + "node", + ["scripts/sync-go-testdata.mjs", "--check"], + rootPath, + ], [ "typescript", "typescript-format", @@ -356,14 +366,10 @@ for (const [group, name, command, args, cwd, env] of [ "go", ["test", "-count=1", "./..."], new URL("../go/", import.meta.url), + { GOWORK: "off" }, ], - [ - "go", - "go-cobra", - "go", - ["test", "./..."], - new URL("../go-cobra/", import.meta.url), - ], + ["go", "go-cobra", "sh", ["scripts/check-go-cobra.sh"], rootPath], + ["go", "go-release", "sh", ["scripts/check-go-release.sh"], rootPath], ["rust", "rust", "cargo", ["test"], new URL("../rust/", import.meta.url)], [ "rust", diff --git a/scripts/sync-go-testdata.mjs b/scripts/sync-go-testdata.mjs new file mode 100644 index 0000000..c5879e0 --- /dev/null +++ b/scripts/sync-go-testdata.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node +import { cpSync, mkdirSync, readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +const root = new URL("../", import.meta.url); +const check = process.argv.slice(2).includes("--check"); +const files = [ + "spec/hosts.json", + "testdata/cases/bundled-skill-install.json", + "testdata/skills/basic/SKILL.md", + "testdata/skills/basic/assets/template.json", + "testdata/skills/basic/references/guide.md", + "testdata/skills/basic/scripts/helper.sh", + "testdata/skills/invalid-frontmatter/SKILL.md", + "testdata/skills/missing-skill-md/README.md", +]; + +for (const sourcePath of files) { + const targetPath = `go/${sourcePath}`; + const source = new URL(sourcePath, root); + const target = new URL(targetPath, root); + if (check) { + let targetContents; + try { + targetContents = readFileSync(target); + } catch { + fail(`missing generated Go test fixture: ${targetPath}`); + } + if (!readFileSync(source).equals(targetContents)) { + fail(`stale generated Go test fixture: ${targetPath}`); + } + continue; + } + mkdirSync(new URL("./", target), { recursive: true }); + cpSync(source, target); +} + +console.log( + check + ? `ok: ${files.length} Go test fixtures are current` + : `updated ${files.length} Go test fixtures`, +); + +function fail(message) { + console.error(message); + process.exit(1); +} diff --git a/spec/hosts.json b/spec/hosts.json index d1af3c3..0343079 100644 --- a/spec/hosts.json +++ b/spec/hosts.json @@ -449,9 +449,7 @@ "~/.claude/skills" ], "detect": [ - "~/.copilot", - "~/.agents", - "~/.claude" + "~/.copilot" ], "status": "documented" }, @@ -749,9 +747,7 @@ "~/.claude/skills" ], "detect": [ - "~/.config/opencode", - "~/.agents", - "~/.claude" + "~/.config/opencode" ], "status": "verified" }, @@ -1033,16 +1029,7 @@ "~/.opencode/skills" ], "detect": [ - "~/.warp", - "~/.agents", - "~/.claude", - "~/.codex", - "~/.cursor", - "~/.gemini", - "~/.copilot", - "~/.factory", - "~/.github", - "~/.opencode" + "~/.warp" ], "status": "documented" }, diff --git a/testdata/cases/bundled-skill-install.json b/testdata/cases/bundled-skill-install.json index fcc5407..0cfac14 100644 --- a/testdata/cases/bundled-skill-install.json +++ b/testdata/cases/bundled-skill-install.json @@ -397,10 +397,53 @@ } } }, + { + "id": "codex-user-scope-reuses-existing-compatible-dir", + "operation": "install", + "description": "Reuses the existing Codex-compatible user directory when the canonical directory does not exist.", + "options": { + "appId": "example-cli", + "skillBundleDir": "testdata/skills/basic", + "scope": "user", + "agents": [ + "codex" + ], + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.codex/skills" + ], + "files": {} + }, + "expected": { + "report": { + "installed": [ + { + "hostId": "codex", + "skillName": "basic", + "targetDir": "$HOME/.codex/skills/basic" + } + ], + "updated": [], + "skipped": [], + "conflicts": [], + "errors": [] + }, + "filesPresent": [ + "$HOME/.codex/skills/basic/SKILL.md", + "$HOME/.codex/skills/basic/.kitup.json" + ], + "filesAbsent": [ + "$HOME/.agents/skills/basic" + ] + } + }, { "id": "codex-user-scope-prefers-first-user-dir", "operation": "install", - "description": "Installs Codex user-scope skills into the first canonical user path when multiple user paths are valid.", + "description": "Prefers the first canonical Codex user path when both compatible directories already exist.", "options": { "appId": "example-cli", "skillBundleDir": "testdata/skills/basic", @@ -708,6 +751,48 @@ } } }, + { + "id": "auto-host-detection-secondary-specific-path", + "operation": "detect", + "description": "Detects a host when a non-primary specific detection path exists.", + "options": { + "scope": "user", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.clawdbot" + ], + "files": {} + }, + "expected": { + "detectedHosts": [ + "openclaw" + ] + } + }, + { + "id": "auto-host-detection-generic-path-only", + "operation": "detect", + "description": "Does not infer a specific host from a shared compatibility root alone.", + "options": { + "scope": "user", + "home": "$HOME", + "cwd": "$WORKSPACE" + }, + "given": { + "dirs": [ + "$HOME/.agents" + ], + "files": { + "$WORKSPACE/package.json": "{}\n" + } + }, + "expected": { + "detectedHosts": [] + } + }, { "id": "shared-target-deduplication", "operation": "install", diff --git a/ts/src/hosts.generated.ts b/ts/src/hosts.generated.ts index 419abb5..ad04c1e 100644 --- a/ts/src/hosts.generated.ts +++ b/ts/src/hosts.generated.ts @@ -2,4 +2,4 @@ // prettier-ignore export const defaultHostsSpecJson = - "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\",\"~/.agents\",\"~/.claude\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\",\"~/.agents\",\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\",\"~/.agents\",\"~/.claude\",\"~/.codex\",\"~/.cursor\",\"~/.gemini\",\"~/.copilot\",\"~/.factory\",\"~/.github\",\"~/.opencode\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}"; + "{\"$schema\":\"./hosts.schema.json\",\"schemaVersion\":1,\"hosts\":[{\"id\":\"adal\",\"displayName\":\"AdaL\",\"projectSkillsDirs\":[\".adal/skills\"],\"userSkillsDirs\":[\"~/.adal/skills\"],\"detect\":[\"~/.adal\"],\"status\":\"community\"},{\"id\":\"aider-desk\",\"displayName\":\"AiderDesk\",\"projectSkillsDirs\":[\".aider-desk/skills\"],\"userSkillsDirs\":[\"~/.aider-desk/skills\"],\"detect\":[\"~/.aider-desk\"],\"status\":\"community\"},{\"id\":\"amp\",\"displayName\":\"Amp\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\"~/.config/amp\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"antigravity\",\"displayName\":\"Antigravity\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity/skills\"],\"detect\":[\"~/.gemini/antigravity\"],\"status\":\"community\"},{\"id\":\"antigravity-cli\",\"displayName\":\"Antigravity CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.gemini/antigravity-cli/skills\"],\"detect\":[\"~/.gemini/antigravity-cli\"],\"status\":\"community\"},{\"id\":\"astrbot\",\"displayName\":\"AstrBot\",\"projectSkillsDirs\":[\"data/skills\"],\"userSkillsDirs\":[\"~/.astrbot/data/skills\"],\"detect\":[\"~/.astrbot\",\"data/skills\",\"~/.astrbot/data\"],\"status\":\"community\"},{\"id\":\"augment\",\"displayName\":\"Augment\",\"projectSkillsDirs\":[\".augment/skills\"],\"userSkillsDirs\":[\"~/.augment/skills\"],\"detect\":[\"~/.augment\"],\"status\":\"community\"},{\"id\":\"autohand-code\",\"displayName\":\"Autohand Code CLI\",\"projectSkillsDirs\":[\".autohand/skills\"],\"userSkillsDirs\":[\"~/.autohand/skills\"],\"detect\":[\"~/.autohand\"],\"status\":\"community\"},{\"id\":\"bob\",\"displayName\":\"IBM Bob\",\"projectSkillsDirs\":[\".bob/skills\"],\"userSkillsDirs\":[\"~/.bob/skills\"],\"detect\":[\"~/.bob\"],\"status\":\"community\"},{\"id\":\"claude-code\",\"displayName\":\"Claude Code\",\"projectSkillsDirs\":[\".claude/skills\"],\"userSkillsDirs\":[\"~/.claude/skills\"],\"detect\":[\"~/.claude\"],\"status\":\"verified\"},{\"id\":\"cline\",\"displayName\":\"Cline\",\"projectSkillsDirs\":[\".agents/skills\",\".cline/skills\",\".clinerules/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.cline/skills\"],\"detect\":[\"~/.cline\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"codearts-agent\",\"displayName\":\"CodeArts Agent\",\"projectSkillsDirs\":[\".codeartsdoer/skills\"],\"userSkillsDirs\":[\"~/.codeartsdoer/skills\"],\"detect\":[\"~/.codeartsdoer\"],\"status\":\"community\"},{\"id\":\"codebuddy\",\"displayName\":\"CodeBuddy\",\"projectSkillsDirs\":[\".codebuddy/skills\"],\"userSkillsDirs\":[\"~/.codebuddy/skills\"],\"detect\":[\"~/.codebuddy\",\".codebuddy\"],\"status\":\"community\"},{\"id\":\"codemaker\",\"displayName\":\"Codemaker\",\"projectSkillsDirs\":[\".codemaker/skills\"],\"userSkillsDirs\":[\"~/.codemaker/skills\"],\"detect\":[\"~/.codemaker\"],\"status\":\"community\"},{\"id\":\"codestudio\",\"displayName\":\"Code Studio\",\"projectSkillsDirs\":[\".codestudio/skills\"],\"userSkillsDirs\":[\"~/.codestudio/skills\"],\"detect\":[\"~/.codestudio\"],\"status\":\"community\"},{\"id\":\"codex\",\"displayName\":\"Codex\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.codex/skills\"],\"detect\":[\"~/.codex\",\"~/.agents/skills\",\"~/.agents\"],\"status\":\"verified\",\"notes\":[\"Keep both ~/.agents/skills and ~/.codex/skills for compatibility.\"]},{\"id\":\"command-code\",\"displayName\":\"Command Code\",\"projectSkillsDirs\":[\".commandcode/skills\"],\"userSkillsDirs\":[\"~/.commandcode/skills\"],\"detect\":[\"~/.commandcode\"],\"status\":\"community\"},{\"id\":\"continue\",\"displayName\":\"Continue\",\"projectSkillsDirs\":[\".continue/skills\"],\"userSkillsDirs\":[\"~/.continue/skills\"],\"detect\":[\"~/.continue\",\".continue\"],\"status\":\"community\"},{\"id\":\"cortex\",\"displayName\":\"Cortex Code\",\"projectSkillsDirs\":[\".cortex/skills\"],\"userSkillsDirs\":[\"~/.snowflake/cortex/skills\"],\"detect\":[\"~/.snowflake/cortex\"],\"status\":\"community\"},{\"id\":\"crush\",\"displayName\":\"Crush\",\"projectSkillsDirs\":[\".crush/skills\"],\"userSkillsDirs\":[\"~/.config/crush/skills\"],\"detect\":[\"~/.config/crush\"],\"status\":\"community\"},{\"id\":\"cursor\",\"displayName\":\"Cursor\",\"projectSkillsDirs\":[\".agents/skills\",\".cursor/skills\"],\"userSkillsDirs\":[\"~/.cursor/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.cursor\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"deepagents\",\"displayName\":\"Deep Agents\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.deepagents/agent/skills\"],\"detect\":[\"~/.deepagents\",\"~/.deepagents/agent\"],\"status\":\"community\"},{\"id\":\"devin\",\"displayName\":\"Devin for Terminal\",\"projectSkillsDirs\":[\".devin/skills\"],\"userSkillsDirs\":[\"~/.config/devin/skills\"],\"detect\":[\"~/.config/devin\"],\"status\":\"community\"},{\"id\":\"dexto\",\"displayName\":\"Dexto\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.dexto\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"droid\",\"displayName\":\"Droid\",\"projectSkillsDirs\":[\".factory/skills\"],\"userSkillsDirs\":[\"~/.factory/skills\"],\"detect\":[\"~/.factory\"],\"status\":\"community\"},{\"id\":\"eve\",\"displayName\":\"Eve\",\"projectSkillsDirs\":[\"agent/skills\"],\"userSkillsDirs\":[],\"detect\":[\"agent\",\"package.json\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\",\"Detect from Eve project shape; no global skill directory.\"]},{\"id\":\"firebender\",\"displayName\":\"Firebender\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.firebender/skills\"],\"detect\":[\"~/.firebender\"],\"status\":\"community\"},{\"id\":\"forgecode\",\"displayName\":\"ForgeCode\",\"projectSkillsDirs\":[\".forge/skills\"],\"userSkillsDirs\":[\"~/.forge/skills\"],\"detect\":[\"~/.forge\"],\"status\":\"community\"},{\"id\":\"gemini-cli\",\"displayName\":\"Gemini CLI\",\"projectSkillsDirs\":[\".agents/skills\",\".gemini/skills\"],\"userSkillsDirs\":[\"~/.gemini/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.gemini\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"github-copilot\",\"displayName\":\"GitHub Copilot\",\"projectSkillsDirs\":[\".agents/skills\",\".github/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.copilot/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.copilot\"],\"status\":\"documented\"},{\"id\":\"goose\",\"displayName\":\"Goose\",\"projectSkillsDirs\":[\".goose/skills\"],\"userSkillsDirs\":[\"~/.config/goose/skills\"],\"detect\":[\"~/.config/goose\"],\"status\":\"community\"},{\"id\":\"hermes-agent\",\"displayName\":\"Hermes Agent\",\"projectSkillsDirs\":[\".hermes/skills\"],\"userSkillsDirs\":[\"~/.hermes/skills\"],\"detect\":[\"~/.hermes\"],\"status\":\"community\"},{\"id\":\"iflow-cli\",\"displayName\":\"iFlow CLI\",\"projectSkillsDirs\":[\".iflow/skills\"],\"userSkillsDirs\":[\"~/.iflow/skills\"],\"detect\":[\"~/.iflow\"],\"status\":\"community\"},{\"id\":\"inference-sh\",\"displayName\":\"inference.sh\",\"projectSkillsDirs\":[\".inferencesh/skills\"],\"userSkillsDirs\":[\"~/.inferencesh/skills\"],\"detect\":[\"~/.inferencesh\"],\"status\":\"community\"},{\"id\":\"jazz\",\"displayName\":\"Jazz\",\"projectSkillsDirs\":[\".jazz/skills\"],\"userSkillsDirs\":[\"~/.jazz/skills\"],\"detect\":[\"~/.jazz\",\".jazz\"],\"status\":\"community\"},{\"id\":\"junie\",\"displayName\":\"Junie\",\"projectSkillsDirs\":[\".junie/skills\"],\"userSkillsDirs\":[\"~/.junie/skills\"],\"detect\":[\"~/.junie\"],\"status\":\"community\"},{\"id\":\"kilo\",\"displayName\":\"Kilo Code\",\"projectSkillsDirs\":[\".kilocode/skills\"],\"userSkillsDirs\":[\"~/.kilocode/skills\"],\"detect\":[\"~/.kilocode\"],\"status\":\"community\"},{\"id\":\"kimi-cli\",\"displayName\":\"Kimi Code CLI\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.config/agents\",\"~/.kimi-code\",\"~/.kimi\",\"~/.agents\"],\"status\":\"community\",\"notes\":[\"kimi-code-cli is an alias for the same Kimi Code CLI path family.\"],\"aliases\":[\"kimi-code-cli\"]},{\"id\":\"kiro-cli\",\"displayName\":\"Kiro CLI\",\"projectSkillsDirs\":[\".kiro/skills\"],\"userSkillsDirs\":[\"~/.kiro/skills\"],\"detect\":[\"~/.kiro\"],\"status\":\"community\"},{\"id\":\"kode\",\"displayName\":\"Kode\",\"projectSkillsDirs\":[\".kode/skills\"],\"userSkillsDirs\":[\"~/.kode/skills\"],\"detect\":[\"~/.kode\"],\"status\":\"community\"},{\"id\":\"lingma\",\"displayName\":\"Lingma\",\"projectSkillsDirs\":[\".lingma/skills\"],\"userSkillsDirs\":[\"~/.lingma/skills\"],\"detect\":[\"~/.lingma\"],\"status\":\"community\"},{\"id\":\"loaf\",\"displayName\":\"Loaf\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.loaf\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"mcpjam\",\"displayName\":\"MCPJam\",\"projectSkillsDirs\":[\".mcpjam/skills\"],\"userSkillsDirs\":[\"~/.mcpjam/skills\"],\"detect\":[\"~/.mcpjam\"],\"status\":\"community\"},{\"id\":\"mistral-vibe\",\"displayName\":\"Mistral Vibe\",\"projectSkillsDirs\":[\".vibe/skills\"],\"userSkillsDirs\":[\"~/.vibe/skills\"],\"detect\":[\"~/.vibe\"],\"status\":\"community\"},{\"id\":\"moxby\",\"displayName\":\"Moxby\",\"projectSkillsDirs\":[\".moxby/skills\"],\"userSkillsDirs\":[\"~/.moxby/skills\"],\"detect\":[\"~/.moxby\"],\"status\":\"community\"},{\"id\":\"mux\",\"displayName\":\"Mux\",\"projectSkillsDirs\":[\".mux/skills\"],\"userSkillsDirs\":[\"~/.mux/skills\"],\"detect\":[\"~/.mux\"],\"status\":\"community\"},{\"id\":\"neovate\",\"displayName\":\"Neovate\",\"projectSkillsDirs\":[\".neovate/skills\"],\"userSkillsDirs\":[\"~/.neovate/skills\"],\"detect\":[\"~/.neovate\"],\"status\":\"community\"},{\"id\":\"ona\",\"displayName\":\"Ona\",\"projectSkillsDirs\":[\".ona/skills\"],\"userSkillsDirs\":[\"~/.ona/skills\"],\"detect\":[\"~/.ona\"],\"status\":\"community\"},{\"id\":\"openclaw\",\"displayName\":\"OpenClaw\",\"projectSkillsDirs\":[\"skills\"],\"userSkillsDirs\":[\"~/.openclaw/skills\"],\"detect\":[\"~/.openclaw\",\"~/.clawdbot\",\"~/.moltbot\"],\"status\":\"community\"},{\"id\":\"opencode\",\"displayName\":\"OpenCode\",\"projectSkillsDirs\":[\".agents/skills\",\".opencode/skills\",\".claude/skills\"],\"userSkillsDirs\":[\"~/.config/opencode/skills\",\"~/.agents/skills\",\"~/.claude/skills\"],\"detect\":[\"~/.config/opencode\"],\"status\":\"verified\"},{\"id\":\"openhands\",\"displayName\":\"OpenHands\",\"projectSkillsDirs\":[\".openhands/skills\"],\"userSkillsDirs\":[\"~/.openhands/skills\"],\"detect\":[\"~/.openhands\"],\"status\":\"community\"},{\"id\":\"pi\",\"displayName\":\"Pi\",\"projectSkillsDirs\":[\".pi/skills\"],\"userSkillsDirs\":[\"~/.pi/agent/skills\"],\"detect\":[\"~/.pi/agent\"],\"status\":\"community\"},{\"id\":\"pochi\",\"displayName\":\"Pochi\",\"projectSkillsDirs\":[\".pochi/skills\"],\"userSkillsDirs\":[\"~/.pochi/skills\"],\"detect\":[\"~/.pochi\"],\"status\":\"community\"},{\"id\":\"promptscript\",\"displayName\":\"PromptScript\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[],\"detect\":[\".promptscript\",\"promptscript.yaml\"],\"status\":\"community\",\"notes\":[\"Project-only host; userSkillsDirs is intentionally empty.\"]},{\"id\":\"qoder\",\"displayName\":\"Qoder\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder/skills\"],\"detect\":[\"~/.qoder\"],\"status\":\"community\"},{\"id\":\"qoder-cn\",\"displayName\":\"Qoder CN\",\"projectSkillsDirs\":[\".qoder/skills\"],\"userSkillsDirs\":[\"~/.qoder-cn/skills\"],\"detect\":[\"~/.qoder-cn\"],\"status\":\"community\"},{\"id\":\"qwen-code\",\"displayName\":\"Qwen Code\",\"projectSkillsDirs\":[\".qwen/skills\"],\"userSkillsDirs\":[\"~/.qwen/skills\"],\"detect\":[\"~/.qwen\"],\"status\":\"community\"},{\"id\":\"reasonix\",\"displayName\":\"Reasonix\",\"projectSkillsDirs\":[\".reasonix/skills\"],\"userSkillsDirs\":[\"~/.reasonix/skills\"],\"detect\":[\"~/.reasonix\"],\"status\":\"community\"},{\"id\":\"replit\",\"displayName\":\"Replit\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.config/agents/skills\"],\"detect\":[\".replit\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"roo\",\"displayName\":\"Roo Code\",\"aliases\":[\"roo-code\"],\"projectSkillsDirs\":[\".roo/skills\",\".agents/skills\"],\"userSkillsDirs\":[\"~/.roo/skills\",\"~/.agents/skills\"],\"detect\":[\"~/.roo\",\"~/.agents\"],\"status\":\"documented\"},{\"id\":\"rovodev\",\"displayName\":\"Rovo Dev\",\"projectSkillsDirs\":[\".rovodev/skills\"],\"userSkillsDirs\":[\"~/.rovodev/skills\"],\"detect\":[\"~/.rovodev\"],\"status\":\"community\"},{\"id\":\"tabnine-cli\",\"displayName\":\"Tabnine CLI\",\"projectSkillsDirs\":[\".tabnine/agent/skills\"],\"userSkillsDirs\":[\"~/.tabnine/agent/skills\"],\"detect\":[\"~/.tabnine\",\"~/.tabnine/agent\"],\"status\":\"community\"},{\"id\":\"terramind\",\"displayName\":\"Terramind\",\"projectSkillsDirs\":[\".terramind/skills\"],\"userSkillsDirs\":[\"~/.terramind/skills\"],\"detect\":[\"~/.terramind\"],\"status\":\"community\"},{\"id\":\"tinycloud\",\"displayName\":\"Tinycloud\",\"projectSkillsDirs\":[\".tinycloud/skills\"],\"userSkillsDirs\":[\"~/.tinycloud/skills\"],\"detect\":[\"~/.tinycloud\"],\"status\":\"community\"},{\"id\":\"trae\",\"displayName\":\"Trae\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae/skills\"],\"detect\":[\"~/.trae\"],\"status\":\"community\"},{\"id\":\"trae-cn\",\"displayName\":\"Trae CN\",\"projectSkillsDirs\":[\".trae/skills\"],\"userSkillsDirs\":[\"~/.trae-cn/skills\"],\"detect\":[\"~/.trae-cn\"],\"status\":\"community\"},{\"id\":\"universal\",\"displayName\":\"Universal\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.config/agents/skills\"],\"detect\":[\"~/.agents\",\"~/.config/agents\"],\"status\":\"community\"},{\"id\":\"warp\",\"displayName\":\"Warp\",\"projectSkillsDirs\":[\".agents/skills\",\".warp/skills\",\".claude/skills\",\".codex/skills\",\".cursor/skills\",\".gemini/skills\",\".copilot/skills\",\".factory/skills\",\".github/skills\",\".opencode/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\",\"~/.warp/skills\",\"~/.claude/skills\",\"~/.codex/skills\",\"~/.cursor/skills\",\"~/.gemini/skills\",\"~/.copilot/skills\",\"~/.factory/skills\",\"~/.github/skills\",\"~/.opencode/skills\"],\"detect\":[\"~/.warp\"],\"status\":\"documented\"},{\"id\":\"windsurf\",\"displayName\":\"Windsurf\",\"projectSkillsDirs\":[\".windsurf/skills\"],\"userSkillsDirs\":[\"~/.codeium/windsurf/skills\"],\"detect\":[\"~/.codeium/windsurf\"],\"status\":\"community\"},{\"id\":\"zed\",\"displayName\":\"Zed\",\"projectSkillsDirs\":[\".agents/skills\"],\"userSkillsDirs\":[\"~/.agents/skills\"],\"detect\":[\"~/.config/zed\",\"~/.agents\"],\"status\":\"community\"},{\"id\":\"zencoder\",\"displayName\":\"Zencoder\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"},{\"id\":\"zenflow\",\"displayName\":\"Zenflow\",\"projectSkillsDirs\":[\".zencoder/skills\"],\"userSkillsDirs\":[\"~/.zencoder/skills\"],\"detect\":[\"~/.zencoder\"],\"status\":\"community\"}]}"; diff --git a/ts/src/index.ts b/ts/src/index.ts index 0c74fb5..48347ab 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -488,13 +488,14 @@ export async function detectHosts( const detected: Host[] = []; for (const host of spec.hosts) { - const detectPath = host.detect[0]; - if ( - detectPath && - !isGenericDetectPath(detectPath) && - (await exists(expandHostPath(detectPath, home, cwd))) - ) { - detected.push(host); + for (const detectPath of host.detect) { + if ( + !isGenericDetectPath(detectPath) && + (await exists(expandHostPath(detectPath, home, cwd))) + ) { + detected.push(host); + break; + } } } @@ -1690,7 +1691,8 @@ function isGenericDetectPath(path: string) { return ( path === "~/.agents" || path === "~/.agents/skills" || - path === "~/.config/agents" + path === "~/.config/agents" || + path === "package.json" ); } diff --git a/ts/test/golden.test.ts b/ts/test/golden.test.ts index 2ad482d..350aaa2 100644 --- a/ts/test/golden.test.ts +++ b/ts/test/golden.test.ts @@ -221,6 +221,7 @@ async function runCase(testCase: any, home: string, workspace: string) { testCase.expected.detectedHosts, ); } + if (testCase.operation === "detect") return; const reportPromise = testCase.operation === "uninstall"