From 55c423f27421604625258455ed4edc60f65cb7c4 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Sat, 1 Aug 2026 13:43:08 -0600 Subject: [PATCH 1/5] Add: Grok host skill placement Teach the managed installer the repository-local Grok skill path so install, status, update, and uninstall track .grok/skills the same way as Codex, Claude, and Cursor. --- .github/workflows/ci.yml | 2 +- README.md | 9 +++++---- bin/corvid-skills | 12 +++++++----- tests/installer.sh | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad24491..fbcd9ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: assert catalog["skills"] == sorted(catalog["skills"]) PY - for host in codex claude cursor; do + for host in codex claude cursor grok; do test_repo="$test_root/$host" mkdir -p "$test_repo" git -C "$test_repo" init -q diff --git a/README.md b/README.md index f8a933f..1144e46 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,12 @@ for these hosts: | Codex | `.codex/skills/` | Yes | | Claude | `.claude/skills/` | Yes | | Cursor | `.cursor/skills/` | Yes | +| Grok | `.grok/skills/` | Yes | | Other agents | Agent-defined | No; use the agent's documented skill path. | Host placement does not translate private context or product-specific assumptions into a skill. All catalog content and examples must remain usable from public CorvidLabs sources. -Fledge lifecycle tracking applies to the three automatic placements; a custom agent path remains +Fledge lifecycle tracking applies to the four automatic placements; a custom agent path remains managed by that agent or by the user. ## Manage installed skills @@ -108,9 +109,9 @@ fledge skills update --all --dry-run fledge skills update --all ``` -Use `--host codex`, `--host claude`, or `--host cursor` to select one placement when a skill -is installed for multiple hosts. `--host auto` is available for install only when exactly one -supported host directory already exists. +Use `--host codex`, `--host claude`, `--host cursor`, or `--host grok` to select one placement +when a skill is installed for multiple hosts. `--host auto` is available for install only when +exactly one supported host directory already exists. ## Spec Sync placement diff --git a/bin/corvid-skills b/bin/corvid-skills index 2bdfb7f..f33f802 100755 --- a/bin/corvid-skills +++ b/bin/corvid-skills @@ -11,10 +11,10 @@ Manage shared CorvidLabs agent skills in one repository. Usage: fledge skills list [--json] - fledge skills install [--repo ] [--host ] [--link] [--dry-run] + fledge skills install [--repo ] [--host ] [--link] [--dry-run] fledge skills status [--repo ] [--json] - fledge skills update |--all [--repo ] [--host ] [--dry-run] - fledge skills uninstall [--repo ] [--host ] [--dry-run] + fledge skills update |--all [--repo ] [--host ] [--dry-run] + fledge skills uninstall [--repo ] [--host ] [--dry-run] Install copies a selected skill into a repository-local agent directory and records the exact source revision and content digest in .corvid-skills.json. @@ -38,6 +38,7 @@ host_directory() { codex) printf '.codex/skills' ;; claude) printf '.claude/skills' ;; cursor) printf '.cursor/skills' ;; + grok) printf '.grok/skills' ;; *) fail "unsupported host: $1" ;; esac } @@ -102,7 +103,7 @@ resolve_host() { local candidates=() local host - for host in codex claude cursor; do + for host in codex claude cursor grok; do [[ -d "$repo/$(host_directory "$host")" ]] && candidates+=("$host") done if [[ ${#candidates[@]} -eq 1 ]]; then @@ -110,7 +111,7 @@ resolve_host() { return fi if [[ ${#candidates[@]} -eq 0 ]]; then - fail "cannot infer an agent host; pass --host codex, --host claude, or --host cursor" + fail "cannot infer an agent host; pass --host codex, --host claude, --host cursor, or --host grok" fi fail "multiple agent hosts are present (${candidates[*]}); pass --host explicitly" } @@ -235,6 +236,7 @@ expected_destination() { codex) printf '.codex/skills/%s' "$skill" ;; claude) printf '.claude/skills/%s' "$skill" ;; cursor) printf '.cursor/skills/%s' "$skill" ;; + grok) printf '.grok/skills/%s' "$skill" ;; *) return 1 ;; esac } diff --git a/tests/installer.sh b/tests/installer.sh index 8e0b862..2a6dc9d 100755 --- a/tests/installer.sh +++ b/tests/installer.sh @@ -256,4 +256,41 @@ test -L "$link_update_repo/.claude/skills/augur" "$lifecycle_installer" status --repo "$link_update_repo" | grep -q $'^augur\t.*\tcurrent$' "$lifecycle_installer" uninstall augur --repo "$link_update_repo" test ! -e "$link_update_repo/.claude/skills/augur" + +grok_repo="$test_dir/grok-repo" +mkdir -p "$grok_repo" +git -C "$grok_repo" init -q +"$installer" install agent-coordination --repo "$grok_repo" --host grok +test -f "$grok_repo/.grok/skills/agent-coordination/SKILL.md" +"$installer" status --repo "$grok_repo" | grep -q $'^agent-coordination\tgrok\t.grok/skills/agent-coordination\t.*\tcopy\tcurrent$' +python3 - "$grok_repo/.corvid-skills.json" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as source: + manifest = json.load(source) +assert len(manifest["installs"]) == 1 +assert manifest["installs"][0]["host"] == "grok" +assert manifest["installs"][0]["destination"] == ".grok/skills/agent-coordination" +PY +"$installer" uninstall agent-coordination --repo "$grok_repo" --dry-run +test -d "$grok_repo/.grok/skills/agent-coordination" +"$installer" uninstall agent-coordination --repo "$grok_repo" +test ! -e "$grok_repo/.grok/skills/agent-coordination" +python3 - "$grok_repo/.corvid-skills.json" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as source: + manifest = json.load(source) +assert manifest["installs"] == [] +PY + +grok_auto_repo="$test_dir/grok-auto-repo" +mkdir -p "$grok_auto_repo/.grok/skills" +git -C "$grok_auto_repo" init -q +"$installer" install let --repo "$grok_auto_repo" --host auto +test -f "$grok_auto_repo/.grok/skills/let/SKILL.md" +"$installer" status --repo "$grok_auto_repo" | grep -q $'^let\tgrok\t.grok/skills/let\t.*\tcurrent$' + echo "installer tests passed" From 609ae58db2d26afba51cbfaff63c88fe2d0579f3 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Sat, 1 Aug 2026 13:44:31 -0600 Subject: [PATCH 2/5] Add Gemini host integration --- .github/workflows/ci.yml | 4 ++-- README.md | 2 +- bin/corvid-skills | 8 +++++--- tests/installer.sh | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aeceaa9..fababc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,10 +59,10 @@ jobs: fledge skills list | grep -qx 'agent-coordination' fledge skills list | grep -qx 'spec-sync' - fledge skills install agent-coordination --repo "$test_repo" --host codex + fledge skills install agent-coordination --repo "$test_repo" --host gemini fledge skills install spec-sync --repo "$test_repo" --host codex fledge skills status --repo "$test_repo" | grep -q '^agent-coordination' fledge skills status --repo "$test_repo" | grep -q '^spec-sync' - test -f "$test_repo/.codex/skills/agent-coordination/SKILL.md" + test -f "$test_repo/.gemini/skills/agent-coordination/SKILL.md" test -f "$test_repo/.codex/skills/spec-sync/SKILL.md" diff --git a/README.md b/README.md index e1e2b30..b7718c7 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ bin/corvid-skills install agent-coordination --repo /path/to/project --host code bin/corvid-skills install spec-sync --repo /path/to/project --host claude ``` -Supported hosts are `codex`, `claude`, and `cursor`. Installations copy the selected +Supported hosts are `codex`, `claude`, `cursor`, and `gemini`. Installations copy the selected skill and record its source revision, destination, install mode, and content digest in `.corvid-skills.json`. Existing skill directories are never overwritten. diff --git a/bin/corvid-skills b/bin/corvid-skills index 5ebcaa1..6183115 100755 --- a/bin/corvid-skills +++ b/bin/corvid-skills @@ -11,7 +11,7 @@ Manage shared CorvidLabs agent skills in one repository. Usage: fledge skills list - fledge skills install [--repo ] [--host ] [--link] [--dry-run] + fledge skills install [--repo ] [--host ] [--link] [--dry-run] fledge skills status [--repo ] Install copies a selected skill into a repository-local agent directory and @@ -34,6 +34,7 @@ host_directory() { codex) printf '.codex/skills' ;; claude) printf '.claude/skills' ;; cursor) printf '.cursor/skills' ;; + gemini) printf '.gemini/skills' ;; *) fail "unsupported host: $1" ;; esac } @@ -79,7 +80,7 @@ resolve_host() { local candidates=() local host - for host in codex claude cursor; do + for host in codex claude cursor gemini; do [[ -d "$repo/$(host_directory "$host")" ]] && candidates+=("$host") done if [[ ${#candidates[@]} -eq 1 ]]; then @@ -87,7 +88,7 @@ resolve_host() { return fi if [[ ${#candidates[@]} -eq 0 ]]; then - fail "cannot infer an agent host; pass --host codex, --host claude, or --host cursor" + fail "cannot infer an agent host; pass --host codex, --host claude, --host cursor, or --host gemini" fi fail "multiple agent hosts are present (${candidates[*]}); pass --host explicitly" } @@ -249,6 +250,7 @@ status_skills() { codex) expected_destination_rel=".codex/skills/$skill" ;; claude) expected_destination_rel=".claude/skills/$skill" ;; cursor) expected_destination_rel=".cursor/skills/$skill" ;; + gemini) expected_destination_rel=".gemini/skills/$skill" ;; *) safe_manifest="false" ;; esac [[ "$skill" =~ ^[a-z0-9][a-z0-9-]*$ ]] || safe_manifest="false" diff --git a/tests/installer.sh b/tests/installer.sh index 9c44fd7..d04a04b 100755 --- a/tests/installer.sh +++ b/tests/installer.sh @@ -146,4 +146,18 @@ grep -q $'^spec-sync\t.*\tcurrent$' <<< "$root_status" nested_status="$("$installer" status --repo "$test_dir/project/subdirectory")" grep -q $'^spec-sync\t.*\tcurrent$' <<< "$nested_status" "$installer" status --repo "$test_dir" | grep -q '^agent-coordination' +gemini_repo="$test_dir/gemini-repo" +mkdir -p "$gemini_repo/.gemini/skills" +git -C "$gemini_repo" init -q +"$installer" install agent-coordination --repo "$gemini_repo" --host auto +test -f "$gemini_repo/.gemini/skills/agent-coordination/SKILL.md" +"$installer" status --repo "$gemini_repo" | grep -q $'^agent-coordination\tgemini\t.gemini/skills/agent-coordination\t.*\tcopy\tcurrent$' + +gemini_link_repo="$test_dir/gemini-link-repo" +mkdir -p "$gemini_link_repo" +git -C "$gemini_link_repo" init -q +"$installer" install spec-sync --repo "$gemini_link_repo" --host gemini --link +test -L "$gemini_link_repo/.gemini/skills/spec-sync" +"$installer" status --repo "$gemini_link_repo" | grep -q $'^spec-sync\tgemini\t.gemini/skills/spec-sync\t.*\tlink\tcurrent$' + echo "installer tests passed" From 6002fb0814c0bfaac189c636598c5801203b9610 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Sat, 1 Aug 2026 13:49:35 -0600 Subject: [PATCH 3/5] Add: prove Grok placement with Let find skills Extend installer tests and CI dogfood so managed .grok/skills installs are discovered by fledge let find skills for host grok, then disappear after uninstall. --- .github/workflows/ci.yml | 59 +++++++++++++++++++++++- tests/installer.sh | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbcd9ff..8631036 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: [main] pull_request: - branches: [main] workflow_dispatch: permissions: @@ -93,3 +92,61 @@ jobs: fledge skills uninstall agent-coordination --repo "$test_repo" --host "$host" test ! -e "$test_repo/.$host/skills/agent-coordination" done + + - name: Prove Grok placement with Let find skills + shell: bash + run: | + fledge plugins install CorvidLabs/let --yes --non-interactive + test_repo="$(mktemp -d)/grok-let" + mkdir -p "$test_repo" + git -C "$test_repo" init -q + fledge skills install agent-coordination --repo "$test_repo" --host grok + fledge skills install fledge-workflows --repo "$test_repo" --host grok + fledge let find skills --scope project --host grok \ + --repo "$test_repo" --cwd "$test_repo" --json \ + > /tmp/let-find-skills-grok.json + python3 - "$test_repo" <<'PY' + import json + import sys + from pathlib import Path + + repo = Path(sys.argv[1]).resolve() + payload = json.loads(Path("/tmp/let-find-skills-grok.json").read_text(encoding="utf-8")) + assert payload.get("ok") is True, payload + project = [] + for item in payload.get("data", {}).get("items", []): + if item.get("scope") != "project" or item.get("host") != "grok": + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) + names = {item["name"] for item in project} + assert names >= {"agent-coordination", "fledge-workflows"}, names + for item in project: + expected = repo / ".grok" / "skills" / item["name"] / "SKILL.md" + assert Path(item["path"]).resolve() == expected.resolve() + print("let find skills proof ok:", sorted(names)) + PY + fledge let find skills --scope project --host grok \ + --repo "$test_repo" --cwd "$test_repo" \ + --query agent-coordination --json \ + > /tmp/let-find-query.json + python3 - <<'PY' + import json + from pathlib import Path + + payload = json.loads(Path("/tmp/let-find-query.json").read_text(encoding="utf-8")) + assert payload.get("ok") is True, payload + project = [ + item for item in payload.get("data", {}).get("items", []) + if item.get("scope") == "project" + and item.get("host") == "grok" + and item.get("name") == "agent-coordination" + ] + assert len(project) == 1, project + assert project[0]["path"].endswith(".grok/skills/agent-coordination/SKILL.md") + print("let find query proof ok") + PY diff --git a/tests/installer.sh b/tests/installer.sh index 2a6dc9d..529c677 100755 --- a/tests/installer.sh +++ b/tests/installer.sh @@ -293,4 +293,100 @@ git -C "$grok_auto_repo" init -q test -f "$grok_auto_repo/.grok/skills/let/SKILL.md" "$installer" status --repo "$grok_auto_repo" | grep -q $'^let\tgrok\t.grok/skills/let\t.*\tcurrent$' +# Prove Grok project placement is visible to Let find skills when Let is available. +# Let 0.2 may still federate user-global skills; only project-scope rows are asserted. +if command -v fledge >/dev/null 2>&1 && fledge let version >/dev/null 2>&1; then + grok_let_repo="$test_dir/grok-let-repo" + mkdir -p "$grok_let_repo" + git -C "$grok_let_repo" init -q + "$installer" install agent-coordination --repo "$grok_let_repo" --host grok + "$installer" install fledge-workflows --repo "$grok_let_repo" --host grok + fledge let find skills --scope project --host grok \ + --repo "$grok_let_repo" --cwd "$grok_let_repo" --json \ + > "$test_dir/let-find-skills-grok.json" + python3 - "$test_dir/let-find-skills-grok.json" "$grok_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +items = payload.get("data", {}).get("items", []) +project = [] +for item in items: + if item.get("scope") != "project" or item.get("host") != "grok": + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +names = {item["name"] for item in project} +assert names >= {"agent-coordination", "fledge-workflows"}, names +for item in project: + expected = repo / ".grok" / "skills" / item["name"] / "SKILL.md" + assert Path(item["path"]).resolve() == expected.resolve(), (item["path"], expected) +PY + fledge let find skills --scope project --host grok \ + --repo "$grok_let_repo" --cwd "$grok_let_repo" \ + --query agent-coordination --json \ + > "$test_dir/let-find-query-agent-coordination.json" + python3 - "$test_dir/let-find-query-agent-coordination.json" "$grok_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +project = [] +for item in payload.get("data", {}).get("items", []): + if ( + item.get("scope") != "project" + or item.get("host") != "grok" + or item.get("name") != "agent-coordination" + ): + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +assert len(project) == 1, project +assert project[0]["path"].endswith(".grok/skills/agent-coordination/SKILL.md") +PY + "$installer" uninstall agent-coordination --repo "$grok_let_repo" --host grok + fledge let find skills --scope project --host grok \ + --repo "$grok_let_repo" --cwd "$grok_let_repo" \ + --query agent-coordination --json \ + > "$test_dir/let-find-after-uninstall.json" + python3 - "$test_dir/let-find-after-uninstall.json" "$grok_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +project = [] +for item in payload.get("data", {}).get("items", []): + if ( + item.get("scope") != "project" + or item.get("host") != "grok" + or item.get("name") != "agent-coordination" + ): + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +assert project == [], project +PY +fi + echo "installer tests passed" From 444a6c03958997a53b72a4b445c3c82a4b4d2860 Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Sat, 1 Aug 2026 13:50:33 -0600 Subject: [PATCH 4/5] Fix: install Bun before Let dogfood in CI The CorvidLabs/let plugin build hook requires Bun; install it so the Grok placement proof can run fledge let find skills on ubuntu-latest. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8631036..cfa8038 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,12 @@ jobs: - name: Prove Grok placement with Let find skills shell: bash run: | + # Let’s fledge plugin build hook requires Bun. + curl -fsSL https://bun.sh/install | bash + export BUN_INSTALL="${HOME}/.bun" + export PATH="${BUN_INSTALL}/bin:${PATH}" + command -v bun + bun --version fledge plugins install CorvidLabs/let --yes --non-interactive test_repo="$(mktemp -d)/grok-let" mkdir -p "$test_repo" From 2117fbb7715fd1da2b311c835161e9507319dace Mon Sep 17 00:00:00 2001 From: 0xLeif Date: Sat, 1 Aug 2026 14:15:29 -0600 Subject: [PATCH 5/5] Add OpenAI provider integration - Recognize openai as a managed host with .openai/skills/ destination - Update installer usage, status, and auto-resolution for openai - Document OpenAI host in README compatibility table - Add installer tests covering openai install, status, uninstall, auto host, and Let skill discovery - Include openai in CI dogfood host loop --- .github/workflows/ci.yml | 2 +- README.md | 9 +-- bin/corvid-skills | 12 ++-- tests/installer.sh | 136 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfa8038..71423f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: assert catalog["skills"] == sorted(catalog["skills"]) PY - for host in codex claude cursor grok; do + for host in codex claude cursor grok openai; do test_repo="$test_root/$host" mkdir -p "$test_repo" git -C "$test_repo" init -q diff --git a/README.md b/README.md index 1144e46..94aa9e3 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,12 @@ for these hosts: | Claude | `.claude/skills/` | Yes | | Cursor | `.cursor/skills/` | Yes | | Grok | `.grok/skills/` | Yes | +| OpenAI | `.openai/skills/` | Yes | | Other agents | Agent-defined | No; use the agent's documented skill path. | Host placement does not translate private context or product-specific assumptions into a skill. All catalog content and examples must remain usable from public CorvidLabs sources. -Fledge lifecycle tracking applies to the four automatic placements; a custom agent path remains +Fledge lifecycle tracking applies to the five automatic placements; a custom agent path remains managed by that agent or by the user. ## Manage installed skills @@ -109,9 +110,9 @@ fledge skills update --all --dry-run fledge skills update --all ``` -Use `--host codex`, `--host claude`, `--host cursor`, or `--host grok` to select one placement -when a skill is installed for multiple hosts. `--host auto` is available for install only when -exactly one supported host directory already exists. +Use `--host codex`, `--host claude`, `--host cursor`, `--host grok`, or `--host openai` to select +one placement when a skill is installed for multiple hosts. `--host auto` is available for +install only when exactly one supported host directory already exists. ## Spec Sync placement diff --git a/bin/corvid-skills b/bin/corvid-skills index f33f802..49ec4b2 100755 --- a/bin/corvid-skills +++ b/bin/corvid-skills @@ -11,10 +11,10 @@ Manage shared CorvidLabs agent skills in one repository. Usage: fledge skills list [--json] - fledge skills install [--repo ] [--host ] [--link] [--dry-run] + fledge skills install [--repo ] [--host ] [--link] [--dry-run] fledge skills status [--repo ] [--json] - fledge skills update |--all [--repo ] [--host ] [--dry-run] - fledge skills uninstall [--repo ] [--host ] [--dry-run] + fledge skills update |--all [--repo ] [--host ] [--dry-run] + fledge skills uninstall [--repo ] [--host ] [--dry-run] Install copies a selected skill into a repository-local agent directory and records the exact source revision and content digest in .corvid-skills.json. @@ -39,6 +39,7 @@ host_directory() { claude) printf '.claude/skills' ;; cursor) printf '.cursor/skills' ;; grok) printf '.grok/skills' ;; + openai) printf '.openai/skills' ;; *) fail "unsupported host: $1" ;; esac } @@ -103,7 +104,7 @@ resolve_host() { local candidates=() local host - for host in codex claude cursor grok; do + for host in codex claude cursor grok openai; do [[ -d "$repo/$(host_directory "$host")" ]] && candidates+=("$host") done if [[ ${#candidates[@]} -eq 1 ]]; then @@ -111,7 +112,7 @@ resolve_host() { return fi if [[ ${#candidates[@]} -eq 0 ]]; then - fail "cannot infer an agent host; pass --host codex, --host claude, --host cursor, or --host grok" + fail "cannot infer an agent host; pass --host codex, --host claude, --host cursor, --host grok, or --host openai" fi fail "multiple agent hosts are present (${candidates[*]}); pass --host explicitly" } @@ -237,6 +238,7 @@ expected_destination() { claude) printf '.claude/skills/%s' "$skill" ;; cursor) printf '.cursor/skills/%s' "$skill" ;; grok) printf '.grok/skills/%s' "$skill" ;; + openai) printf '.openai/skills/%s' "$skill" ;; *) return 1 ;; esac } diff --git a/tests/installer.sh b/tests/installer.sh index 529c677..4907c13 100755 --- a/tests/installer.sh +++ b/tests/installer.sh @@ -389,4 +389,140 @@ assert project == [], project PY fi +openai_repo="$test_dir/openai-repo" +mkdir -p "$openai_repo" +git -C "$openai_repo" init -q +"$installer" install agent-coordination --repo "$openai_repo" --host openai +test -f "$openai_repo/.openai/skills/agent-coordination/SKILL.md" +"$installer" status --repo "$openai_repo" | grep -q $'^agent-coordination\topenai\t.openai/skills/agent-coordination\t.*\tcopy\tcurrent$' +python3 - "$openai_repo/.corvid-skills.json" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as source: + manifest = json.load(source) +assert len(manifest["installs"]) == 1 +assert manifest["installs"][0]["host"] == "openai" +assert manifest["installs"][0]["destination"] == ".openai/skills/agent-coordination" +PY +"$installer" uninstall agent-coordination --repo "$openai_repo" --dry-run +test -d "$openai_repo/.openai/skills/agent-coordination" +"$installer" uninstall agent-coordination --repo "$openai_repo" +test ! -e "$openai_repo/.openai/skills/agent-coordination" +python3 - "$openai_repo/.corvid-skills.json" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as source: + manifest = json.load(source) +assert manifest["installs"] == [] +PY + +openai_auto_repo="$test_dir/openai-auto-repo" +mkdir -p "$openai_auto_repo/.openai/skills" +git -C "$openai_auto_repo" init -q +"$installer" install let --repo "$openai_auto_repo" --host auto +test -f "$openai_auto_repo/.openai/skills/let/SKILL.md" +"$installer" status --repo "$openai_auto_repo" | grep -q $'^let\topenai\t.openai/skills/let\t.*\tcurrent$' + +# Prove OpenAI project placement is visible to Let find skills when Let supports it. +# This requires a Let version that recognizes .openai/skills roots (CorvidLabs/let#6). +if command -v fledge >/dev/null 2>&1 && fledge let version >/dev/null 2>&1; then + if fledge let find skills --scope project --host openai \ + --repo "$openai_repo" --cwd "$openai_repo" --json >/dev/null 2>&1; then + openai_let_repo="$test_dir/openai-let-repo" + mkdir -p "$openai_let_repo" + git -C "$openai_let_repo" init -q + "$installer" install agent-coordination --repo "$openai_let_repo" --host openai + "$installer" install fledge-workflows --repo "$openai_let_repo" --host openai + fledge let find skills --scope project --host openai \ + --repo "$openai_let_repo" --cwd "$openai_let_repo" --json \ + > "$test_dir/let-find-skills-openai.json" + python3 - "$test_dir/let-find-skills-openai.json" "$openai_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +project = [] +for item in payload.get("data", {}).get("items", []): + if item.get("scope") != "project" or item.get("host") != "openai": + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +names = {item["name"] for item in project} +assert names >= {"agent-coordination", "fledge-workflows"}, names +for item in project: + expected = repo / ".openai" / "skills" / item["name"] / "SKILL.md" + assert Path(item["path"]).resolve() == expected.resolve(), (item["path"], expected) +PY + fledge let find skills --scope project --host openai \ + --repo "$openai_let_repo" --cwd "$openai_let_repo" \ + --query agent-coordination --json \ + > "$test_dir/let-find-query-agent-coordination-openai.json" + python3 - "$test_dir/let-find-query-agent-coordination-openai.json" "$openai_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +project = [] +for item in payload.get("data", {}).get("items", []): + if ( + item.get("scope") != "project" + or item.get("host") != "openai" + or item.get("name") != "agent-coordination" + ): + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +assert len(project) == 1, project +assert project[0]["path"].endswith(".openai/skills/agent-coordination/SKILL.md") +PY + "$installer" uninstall agent-coordination --repo "$openai_let_repo" --host openai + fledge let find skills --scope project --host openai \ + --repo "$openai_let_repo" --cwd "$openai_let_repo" \ + --query agent-coordination --json \ + > "$test_dir/let-find-after-uninstall-openai.json" + python3 - "$test_dir/let-find-after-uninstall-openai.json" "$openai_let_repo" <<'PY' +import json +import sys +from pathlib import Path + +payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +repo = Path(sys.argv[2]).resolve() +assert payload.get("ok") is True, payload +project = [] +for item in payload.get("data", {}).get("items", []): + if ( + item.get("scope") != "project" + or item.get("host") != "openai" + or item.get("name") != "agent-coordination" + ): + continue + path = Path(item.get("path", "")).resolve() + try: + path.relative_to(repo) + except ValueError: + continue + project.append(item) +assert project == [], project +PY + else + echo "skipping OpenAI Let proof: installed Let does not recognize .openai/skills" + fi +fi + echo "installer tests passed"