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
48 changes: 48 additions & 0 deletions .github/workflows/skill-convention-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: skill-convention-review

# Quarterly reminder to re-check the four per-platform Skill-install conventions
# hard-coded in nsc/cli/skill_commands.py `_RESOLVERS`. Opens a tracking issue so
# a maintainer re-reads each canonical source and updates code+docs on drift.

on:
schedule:
# 09:00 UTC on the 1st of Feb/May/Aug/Nov.
- cron: "0 9 1 2,5,8,11 *"
workflow_dispatch:

permissions:
contents: read
issues: write

jobs:
open-tracking-issue:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Open quarterly convention re-check issue
run: |
gh issue create \
--repo "${{ github.repository }}" \
--title "Quarterly: re-check Skill install conventions" \
--body "$(cat <<'EOF'
Quarterly re-check of the four per-platform Skill-install conventions
hard-coded in `nsc/cli/skill_commands.py` `_RESOLVERS`. Re-read each
canonical source below and confirm the documented user-scoped path is
still current.

- [ ] **claude-code** — https://code.claude.com/docs/en/skills
- [ ] **codex** — https://developers.openai.com/codex/skills
- [ ] **gemini** — https://geminicli.com/docs/cli/skills/
- [ ] **copilot** — https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills

If any convention drifted, update all of:

- `nsc/cli/skill_commands.py` (the relevant `_resolve_*` resolver)
- `docs/guides/using-with-ai-agents.md` (per-target path table)
- `skills/netbox-super-cli/SKILL.md` (if it lists per-target paths)
- `CHANGELOG.md` (note the change under the unreleased section)

Close this issue once all four are confirmed (and any drift is shipped).
EOF
)"
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,17 @@ mode — plus a round of state-directory, workflow, and supply-chain hardening.
(github-actions ecosystem, weekly).
- **E2E CI matrix over NetBox v4.5 and v4.6** ([#4]). The end-to-end job now
runs against both NetBox lines (`fail-fast: false`, per-version labels).
- **`nsc skill install` now supports Gemini and Copilot CLI** ([#7]). Their
user-scoped Skill paths (`~/.gemini/skills/`, `~/.copilot/skills/`) are now
confirmed and documented, so `--target gemini` / `--target copilot` resolve to
a real install path instead of printing manual instructions. A quarterly CI
job opens a tracking issue to re-check the four per-platform conventions.

[#6]: https://github.com/thomaschristory/netbox-super-cli/issues/6
[#3]: https://github.com/thomaschristory/netbox-super-cli/issues/3
[#2]: https://github.com/thomaschristory/netbox-super-cli/issues/2
[#5]: https://github.com/thomaschristory/netbox-super-cli/issues/5
[#7]: https://github.com/thomaschristory/netbox-super-cli/issues/7
[#90]: https://github.com/thomaschristory/netbox-super-cli/issues/90
[#9]: https://github.com/thomaschristory/netbox-super-cli/issues/9
[#12]: https://github.com/thomaschristory/netbox-super-cli/issues/12
Expand Down
19 changes: 9 additions & 10 deletions docs/guides/using-with-ai-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,15 @@ the resolved destination path.
|--------------|------------|----------------------------------------------------------------|
| claude-code | confirmed | `~/.claude/skills/netbox-super-cli/SKILL.md` |
| codex | confirmed | `~/.agents/skills/netbox-super-cli/SKILL.md` |
| gemini | manual | (no programmatic install; prints manual instructions) |
| copilot | manual | (no programmatic install; prints manual instructions) |

For targets marked `manual`, `nsc skill install --target <t>` prints
actionable instructions (exit 0) instead of writing a guessed path.
The bundled Skill content is at `skills/netbox-super-cli/SKILL.md` in
this repo and inside the installed wheel; for `manual` targets, paste
its content into your tool's project-scoped configuration (e.g.
`GEMINI.md` for Gemini CLI, `.github/copilot-instructions.md` for
GitHub Copilot CLI).
| gemini | confirmed | `~/.gemini/skills/netbox-super-cli/SKILL.md` |
| copilot | confirmed | `~/.copilot/skills/netbox-super-cli/SKILL.md` |

Gemini CLI and GitHub Copilot CLI also accept the agent-neutral
`~/.agents/skills/` interop alias (the same prefix Codex uses), so a
single export to `~/.agents/skills/` can be shared across all three;
`nsc skill install` writes to each tool's primary path above. The
bundled Skill content is at `skills/netbox-super-cli/SKILL.md` in this
repo and inside the installed wheel.

## Agent prompts that work well

Expand Down
25 changes: 11 additions & 14 deletions nsc/cli/skill_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,28 +64,25 @@ def _resolve_codex() -> _Resolution:


def _resolve_gemini() -> _Resolution:
# Per geminicli.com/docs/cli/skills/ (confirmed 2026-06-25): Gemini CLI
# loads user-scoped skills from $HOME/.gemini/skills/<name>/SKILL.md. It
# also accepts the agent-neutral ~/.agents/skills/ interop alias.
return _Resolution(
target=_Target.GEMINI,
path=None,
manual_instructions=(
"Gemini CLI does not document a programmatic Skill install path "
"as of this nsc release. To use the bundled Skill: paste its "
"content into a project-scoped GEMINI.md or your Gemini system "
"prompt."
),
path=_home() / ".gemini" / "skills" / BUNDLE_NAME / "SKILL.md",
manual_instructions=None,
)


def _resolve_copilot() -> _Resolution:
# Per docs.github.com/.../copilot-cli/customize-copilot/add-skills (confirmed
# 2026-06-25): GitHub Copilot CLI loads user-scoped skills from
# $HOME/.copilot/skills/<name>/SKILL.md. It also accepts the agent-neutral
# ~/.agents/skills/ interop alias.
return _Resolution(
target=_Target.COPILOT,
path=None,
manual_instructions=(
"GitHub Copilot CLI does not document a stable user-scoped Skill "
"install path as of this nsc release. To use the bundled Skill: "
"paste its content into .github/copilot-instructions.md or your "
"team's Copilot configuration."
),
path=_home() / ".copilot" / "skills" / BUNDLE_NAME / "SKILL.md",
manual_instructions=None,
)


Expand Down
38 changes: 30 additions & 8 deletions tests/cli/test_skill_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,34 @@ def test_install_apply_codex_writes_to_agents_skills(home: Path) -> None:
assert dest.exists()


def test_install_dry_run_gemini_prints_manual_instructions(home: Path) -> None:
def test_install_apply_gemini_writes_to_gemini_skills(home: Path) -> None:
"""Gemini CLI loads from $HOME/.gemini/skills/ (confirmed 2026-06-25)."""
runner = CliRunner()
result = runner.invoke(app, ["skill", "install", "--target", "gemini", "--apply"])

assert result.exit_code == 0, result.output
dest = home / ".gemini" / "skills" / "netbox-super-cli" / "SKILL.md"
assert dest.exists()


def test_install_apply_copilot_writes_to_copilot_skills(home: Path) -> None:
"""Copilot CLI loads from $HOME/.copilot/skills/ (confirmed 2026-06-25)."""
runner = CliRunner()
result = runner.invoke(app, ["skill", "install", "--target", "copilot", "--apply"])

assert result.exit_code == 0, result.output
dest = home / ".copilot" / "skills" / "netbox-super-cli" / "SKILL.md"
assert dest.exists()


def test_install_dry_run_gemini_prints_would_write(home: Path) -> None:
runner = CliRunner()
result = runner.invoke(app, ["skill", "install", "--target", "gemini"])

assert result.exit_code == 0, result.output
assert "GEMINI.md" in result.output or "Gemini" in result.output
assert "would write to" not in result.output
assert "would write to" in result.output
assert ".gemini/skills/netbox-super-cli/SKILL.md" in result.output
assert not (home / ".gemini" / "skills" / "netbox-super-cli" / "SKILL.md").exists()


def test_install_apply_overwrites_existing_file(home: Path) -> None:
Expand Down Expand Up @@ -96,15 +117,16 @@ def test_install_json_output_apply(home: Path) -> None:
assert Path(payload["destination"]).exists()


def test_install_json_output_manual_target(home: Path) -> None:
@pytest.mark.parametrize("target", ["gemini", "copilot"])
def test_install_json_output_resolves_path(home: Path, target: str) -> None:
runner = CliRunner()
result = runner.invoke(app, ["skill", "install", "--target", "copilot", "--output", "json"])
result = runner.invoke(app, ["skill", "install", "--target", target, "--output", "json"])

assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload["manual"] is True
assert payload["destination"] is None
assert "instructions" in payload
assert payload["manual"] is False
assert payload["destination"].endswith("skills/netbox-super-cli/SKILL.md")
assert "instructions" not in payload


@pytest.mark.parametrize("target", ["claude-code", "codex", "gemini", "copilot"])
Expand Down
Loading