feat(cli): install one orbit skill as a wrapper or the full tree

What does this MR do and why?

orbit setup now installs a single skill named orbit. By default it is a thin wrapper that needs no network at install time. --skill full writes the skill files themselves, for agents like Duo CLI that can't run a command when they load a skill. GitLab Duo gets skill installation too, and Claude Code no longer asks before the wrapper runs.

Relates to #1383. This supersedes its "install next to orbit-cli" part and doesn't close it.

Testing

mise lint:code, mise test:fast, mise test:cli, and the orbit-cli tests pass. New unit tests cover wrapper, full, backup of a foreign skill, shared-skill uninstall, kind persistence, legacy migration and the allow rules. New integration tests run setup --skill full against a mock instance (200, 401, 403, 429, 503, unreachable, no credentials). mise lint:docs reports one broken-link error, in a template file this MR doesn't touch.

Performance Analysis

No runtime paths change. setup --skill full makes one skills request. So does a setup that names or detects Duo, or that targets a directory already installed as full, when credentials resolve.

  • This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
Agent context: long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered
  • Install location is <skills dir>/orbit/ in both modes. The manifest name: is rewritten to orbit at install time (rename_manifest in crates/orbit-cli/src/skill.rs); the repo dirs stay skills/orbit-cli and skills/orbit-wrapper. The wrapper is embedded with a second rust-embed folder.
  • Network access lives in main.rs through setup::skill_request, because setup::install is sync and runs inside the async runtime. The instance's full tree is fetched up front only when --skill full is passed or a selected agent needs it (skipped on --dry-run or --skip skill) and passed in Options.skill. Which kind each directory gets is decided at install time: --skill wins, then an agent with skills.requires_full (Duo), then the kind recorded for that directory, then the wrapper. A recorded full also triggers the fetch, so a rerun keeps the instance's content. An agent picked in the interactive picker that needs the full tree gets the embedded one when nothing was fetched. skill::full_tree reuses the same resolution as skills get (resolved_view): credentials, ETag cache, compose, and degrade on 401/403/429/5xx/network errors. Any error from the resolution falls back to the embedded tree with a warning.
  • Ownership record .orbit-managed is a JSON file beside each installed directory (.agents/skills/.orbit-managed, and the same next to the Claude copy fallback), not inside it. It stores the kind and the agents using the directory, so the tree stays what glab skills install writes and survives glab skills update. A tree that already matches is adopted. An unmarked orbit/ with other content is renamed to orbit.orbit-backup (numbered if taken) with its SKILL.md renamed to SKILL.md.orbit-backup so agents don't list it, and a foreign symlink is just replaced. Uninstall removes only the selected agents from the record and deletes the directory once none are left, otherwise it reports kept for <agents>. It reports kept for a directory with no record.
  • Migration: setup and uninstall clean up a legacy orbit-cli directory whose manifest still says name: orbit-cli (unedited files go, edited ones stay, a differing SKILL.md is renamed to a backup), a legacy Claude link when it is a symlink whose target is named orbit-cli, and the real copy an earlier release wrote in .claude/skills/orbit-cli where symlinks fail. Anything else is left alone. Uninstall agent detection also counts a legacy orbit-cli/SKILL.md.
  • Item 7 decision: the wrapper uses the binary that installed it. At install time the embedded wrapper's glab orbit skills get orbit is replaced with <launcher> skills get orbit (orbit for the standalone binary, glab orbit when GITLAB_ORBIT_DISTRIBUTION=glab), the same launcher the hooks and MCP entry use. That keeps the source skills/orbit-wrapper/SKILL.md unchanged for the npx skills install and the server drift test, and orbit skills get orbit works without glab since !2757 (merged). The frontmatter compatibility line still mentions glab; the text only changes in the commands.
  • Item 8: skills.permissions in the agent spec (schema updated) lists allow rules merged into permissions.allow. Claude gets Bash({{orbit}} skills get orbit) and Bash({{orbit}} skills get orbit *). Rules are removed on uninstall, and the settings file is deleted if that leaves it empty. The settings backup is skipped when hooks already wrote the file in the same run.
  • config/setup/agents/duo.yaml gets the same skills: dir as codex.yaml, plus requires_full: true (new schema field), so Duo gets the full tree.
  • Docs: ORBIT_DATA_DIR is covered by one added sentence in the Storage section of docs/source/local/access/cli.md: orbit config also reads $ORBIT_DATA_DIR/settings.json. The graph layout text is main's revision-database text. No ORBIT_DB. Prompt versions for setup and uninstall are bumped. A sentence in the setup section says glab skills update orbit replaces the same directory, so rerun orbit setup.
  • Setup and uninstall also replace a stale Claude link or copy, keep user files in an unmarked legacy orbit-cli, and remove allow rules for both launchers.
  • Not done: docs-locale/ is generated and untouched. !2775 (closed) (docs rebuild, still draft) turns docs/source/local/access/cli.md and docs/source/ai_coding_agents.md into redirects, so whichever merges second must carry this MR's text to the new pages. In docs/source/agents/_index.md that means: the skill row (name orbit, --skill wrapper|full, Duo included), the line saying the orbit-cli skill covers only the local graph, the manual Claude allow rule (setup now adds it) and removal by rm (use orbit uninstall), and the quickstart's glab skills install --global orbit after glab orbit setup, which contradicts this MR because setup already installs orbit, so the glab command refuses or, with --force, overwrites it. In cli.md at its new path: marker-based uninstall, legacy orbit-cli migration, and ORBIT_DATA_DIR covering settings.json.
  • The labels come from #1383; milestone 19.6 is the current release.
  • Rebased onto main after !2757 (merged) was squash-merged; the branch now carries only this MR's commits.
Edited by Dmitry Gruzd

Merge request reports

Loading
Loading