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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/host-adapter-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Uninstall removes every valid kitup-owned copy for the requested `appId` and ski

`aliases` are accepted input names for one host adapter.

Canonical ids use lowercase kebab-case. Aliases may also contain dots when preserving an external ecosystem identifier.

Aliases are for ecosystem compatibility only. SDK result objects should return the canonical `id`, not the alias, unless the caller needs an echo of the original selector.

## Detection
Expand All @@ -29,6 +31,8 @@ Aliases are for ecosystem compatibility only. SDK result objects should return t

Detection checks path existence across every `detect` entry: a host is detected when any of its non-generic entries exists. Entries may be home-relative paths such as `~/.codex` or project-relative paths such as `.replit`.

`detect` may be empty when no host-specific path-only evidence exists. Such hosts remain available through explicit selection. Do not add generic placeholder paths to make the array non-empty.

Every `detect` entry must be evidence that this specific host is present:

- Generic shared roots (`~/.agents`, `~/.agents/skills`, `~/.config/agents`, `.agents`, `.agents/skills`, `package.json`) never count as evidence and are ignored by detection.
Expand Down
2 changes: 1 addition & 1 deletion go/hosts_gen.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion python/src/kitup/_hosts_generated.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion python/tests/test_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def test_load_host_spec_uses_baked_default_when_no_override():
spec = load_host_spec()
assert spec.schema_version == 1
assert len(spec.hosts) == 72
assert len(spec.hosts) == 78
assert spec.hosts[0].id == "adal"


Expand Down
2 changes: 1 addition & 1 deletion rust/src/hosts_generated.rs

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions scripts/check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function validateHosts(spec, schema) {
assert(Array.isArray(spec.hosts), "hosts must be an array");

const idPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
const aliasPattern = /^[a-z0-9]+([.-][a-z0-9]+)*$/;
const projectPattern = new RegExp(schema.$defs.projectPath.pattern);
const homePattern = new RegExp(schema.$defs.homePath.pattern);
for (const path of [
Expand Down Expand Up @@ -81,6 +82,7 @@ function validateHosts(spec, schema) {
for (const host of spec.hosts) {
assert(idPattern.test(host.id), `bad host id: ${host.id}`);
assert(!ids.has(host.id), `duplicate host id: ${host.id}`);
assert(!aliases.has(host.id), `host id conflicts with alias: ${host.id}`);
ids.add(host.id);
assert(host.displayName, `missing displayName: ${host.id}`);

Expand All @@ -107,10 +109,7 @@ function validateHosts(spec, schema) {
assert(homePattern.test(path), `bad user path for ${host.id}: ${path}`);
}

assert(
Array.isArray(host.detect) && host.detect.length > 0,
`missing detect paths: ${host.id}`,
);
assert(Array.isArray(host.detect), `detect must be an array: ${host.id}`);
const installDirs = new Set([
...host.projectSkillsDirs,
...host.userSkillsDirs,
Expand All @@ -132,7 +131,7 @@ function validateHosts(spec, schema) {
);

for (const alias of host.aliases || []) {
assert(idPattern.test(alias), `bad alias for ${host.id}: ${alias}`);
assert(aliasPattern.test(alias), `bad alias for ${host.id}: ${alias}`);
assert(!ids.has(alias), `alias conflicts with host id: ${alias}`);
assert(!aliases.has(alias), `duplicate alias: ${alias}`);
aliases.add(alias);
Expand Down Expand Up @@ -190,6 +189,7 @@ function validateCases(cases, hosts) {

for (const id of [
"alias-resolution",
"antigravity-dotted-alias-resolution",
"kimi-alias-resolution",
"unknown-host-id",
"parse-install-flags-defaults",
Expand Down
10 changes: 5 additions & 5 deletions scripts/smoke-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ smoke_npm() {
cd "$dir"
npm init -y >/dev/null
npm install "@kitup/sdk@$version" >/dev/null
node --input-type=module -e 'import { loadHostSpec } from "@kitup/sdk"; const spec = await loadHostSpec(); if (spec.hosts.length !== 72) throw new Error(`expected 72 hosts, got ${spec.hosts.length}`); console.log(`npm ok: ${spec.hosts.length}`);'
node --input-type=module -e 'import { loadHostSpec } from "@kitup/sdk"; const spec = await loadHostSpec(); if (spec.hosts.length !== 78) throw new Error(`expected 78 hosts, got ${spec.hosts.length}`); console.log(`npm ok: ${spec.hosts.length}`);'
}

smoke_rust() {
Expand All @@ -35,7 +35,7 @@ smoke_rust() {
cat > src/main.rs <<'RS'
fn main() {
let hosts = kitup::load_host_spec(None).expect("load host spec");
assert_eq!(hosts.len(), 72);
assert_eq!(hosts.len(), 78);
println!("rust ok: {}", hosts.len());
}
RS
Expand All @@ -61,8 +61,8 @@ func main() {
if err != nil {
panic(err)
}
if len(hosts) != 72 {
panic(fmt.Sprintf("expected 72 hosts, got %d", len(hosts)))
if len(hosts) != 78 {
panic(fmt.Sprintf("expected 78 hosts, got %d", len(hosts)))
}
fmt.Printf("go ok: %d\n", len(hosts))
}
Expand Down Expand Up @@ -104,7 +104,7 @@ smoke_python() {
from kitup import load_host_spec

spec = load_host_spec()
assert len(spec.hosts) == 72
assert len(spec.hosts) == 78
print(f"python ok: {len(spec.hosts)}")
PY
}
Expand Down
95 changes: 88 additions & 7 deletions spec/hosts.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@
],
"status": "community"
},
{
"id": "antigravity2-0",
"displayName": "Antigravity 2.0",
"aliases": [
"antigravity2.0"
],
"projectSkillsDirs": [
".agents/skills"
],
"userSkillsDirs": [
"~/.gemini/config/skills"
],
"detect": [],
"status": "documented"
},
{
"id": "astrbot",
"displayName": "AstrBot",
Expand Down Expand Up @@ -379,9 +394,7 @@
"agent/skills"
],
"userSkillsDirs": [],
"detect": [
"package.json"
],
"detect": [],
"status": "community",
"notes": [
"Project-only host; userSkillsDirs is intentionally empty.",
Expand Down Expand Up @@ -466,6 +479,20 @@
],
"status": "community"
},
{
"id": "grok",
"displayName": "Grok Build",
"projectSkillsDirs": [
".grok/skills"
],
"userSkillsDirs": [
"~/.grok/skills"
],
"detect": [
"~/.grok"
],
"status": "community"
},
{
"id": "hermes-agent",
"displayName": "Hermes Agent",
Expand Down Expand Up @@ -551,6 +578,20 @@
],
"status": "community"
},
{
"id": "kimchi",
"displayName": "Kimchi",
"projectSkillsDirs": [
".kimchi/skills"
],
"userSkillsDirs": [
"~/.config/kimchi/harness/skills"
],
"detect": [
"~/.config/kimchi"
],
"status": "community"
},
{
"id": "kimi-cli",
"displayName": "Kimi Code CLI",
Expand Down Expand Up @@ -646,6 +687,20 @@
],
"status": "community"
},
{
"id": "minimax-code",
"displayName": "MiniMax Code",
"projectSkillsDirs": [
".minimax/skills"
],
"userSkillsDirs": [
"~/.minimax/skills"
],
"detect": [
"~/.minimax"
],
"status": "community"
},
{
"id": "mistral-vibe",
"displayName": "Mistral Vibe",
Expand Down Expand Up @@ -793,6 +848,21 @@
],
"status": "community"
},
{
"id": "posit-assistant",
"displayName": "Posit Assistant",
"projectSkillsDirs": [
".posit/assistant/skills"
],
"userSkillsDirs": [
"~/.posit/assistant/skills"
],
"detect": [
"~/.posit/assistant",
"~/.positai"
],
"status": "community"
},
{
"id": "promptscript",
"displayName": "PromptScript",
Expand Down Expand Up @@ -995,10 +1065,7 @@
"~/.agents/skills",
"~/.config/agents/skills"
],
"detect": [
"~/.agents",
"~/.config/agents"
],
"detect": [],
"status": "community"
},
{
Expand Down Expand Up @@ -1063,6 +1130,20 @@
],
"status": "community"
},
{
"id": "zcode",
"displayName": "ZCode",
"projectSkillsDirs": [
".zcode/skills"
],
"userSkillsDirs": [
"~/.zcode/skills"
],
"detect": [
"~/.zcode"
],
"status": "community"
},
{
"id": "zencoder",
"displayName": "Zencoder",
Expand Down
3 changes: 1 addition & 2 deletions spec/hosts.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
"pattern": "^[a-z0-9]+([.-][a-z0-9]+)*$"
},
"uniqueItems": true
},
Expand All @@ -73,7 +73,6 @@
},
"detect": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/detectPath"
},
Expand Down
30 changes: 27 additions & 3 deletions testdata/cases/bundled-skill-install.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
"hostsFile": "spec/hosts.json"
},
"expected": {
"count": 72,
"count": 78,
"hostIds": [
"adal",
"aider-desk",
"amp",
"antigravity",
"antigravity-cli",
"antigravity2-0",
"astrbot",
"augment",
"autohand-code",
Expand All @@ -46,18 +47,21 @@
"gemini-cli",
"github-copilot",
"goose",
"grok",
"hermes-agent",
"iflow-cli",
"inference-sh",
"jazz",
"junie",
"kilo",
"kimchi",
"kimi-cli",
"kiro-cli",
"kode",
"lingma",
"loaf",
"mcpjam",
"minimax-code",
"mistral-vibe",
"moxby",
"mux",
Expand All @@ -68,6 +72,7 @@
"openhands",
"pi",
"pochi",
"posit-assistant",
"promptscript",
"qoder",
"qoder-cn",
Expand All @@ -85,6 +90,7 @@
"warp",
"windsurf",
"zed",
"zcode",
"zencoder",
"zenflow"
]
Expand All @@ -108,6 +114,24 @@
]
}
},
{
"id": "antigravity-dotted-alias-resolution",
"operation": "resolve-hosts",
"description": "Resolves the gh skill dotted Antigravity identifier to the canonical kitup adapter id.",
"options": {
"agents": [
"antigravity2.0"
]
},
"given": {
"hostsFile": "spec/hosts.json"
},
"expected": {
"resolvedHostIds": [
"antigravity2-0"
]
}
},
{
"id": "kimi-alias-resolution",
"operation": "resolve-hosts",
Expand Down Expand Up @@ -2139,7 +2163,7 @@
"expected": {
"selection": {
"action": "install",
"selectedCount": 72,
"selectedCount": 78,
"candidateHostIds": [],
"detectedHostIds": [],
"needsConfirmation": false,
Expand Down Expand Up @@ -2357,7 +2381,7 @@
"selection": {
"action": "select-agents",
"selectedHostIds": [],
"candidateCount": 72,
"candidateCount": 78,
"detectedHostIds": [],
"needsConfirmation": true,
"errors": []
Expand Down
Loading
Loading