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.
Related Issues
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 manifestname:is rewritten toorbitat install time (rename_manifestincrates/orbit-cli/src/skill.rs); the repo dirs stayskills/orbit-cliandskills/orbit-wrapper. The wrapper is embedded with a second rust-embed folder. - Network access lives in
main.rsthroughsetup::skill_request, becausesetup::installis sync and runs inside the async runtime. The instance's full tree is fetched up front only when--skill fullis passed or a selected agent needs it (skipped on--dry-runor--skip skill) and passed inOptions.skill. Which kind each directory gets is decided at install time:--skillwins, then an agent withskills.requires_full(Duo), then the kind recorded for that directory, then the wrapper. A recordedfullalso 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_treereuses the same resolution asskills 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-managedis 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 whatglab skills installwrites and survivesglab skills update. A tree that already matches is adopted. An unmarkedorbit/with other content is renamed toorbit.orbit-backup(numbered if taken) with itsSKILL.mdrenamed toSKILL.md.orbit-backupso 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 reportskept for <agents>. It reportskeptfor a directory with no record. - Migration: setup and uninstall clean up a legacy
orbit-clidirectory whose manifest still saysname: orbit-cli(unedited files go, edited ones stay, a differingSKILL.mdis renamed to a backup), a legacy Claude link when it is a symlink whose target is namedorbit-cli, and the real copy an earlier release wrote in.claude/skills/orbit-cliwhere symlinks fail. Anything else is left alone. Uninstall agent detection also counts a legacyorbit-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 orbitis replaced with<launcher> skills get orbit(orbitfor the standalone binary,glab orbitwhenGITLAB_ORBIT_DISTRIBUTION=glab), the same launcher the hooks and MCP entry use. That keeps the sourceskills/orbit-wrapper/SKILL.mdunchanged for thenpx skillsinstall and the server drift test, andorbit skills get orbitworks without glab since !2757 (merged). The frontmattercompatibilityline still mentions glab; the text only changes in the commands. - Item 8:
skills.permissionsin the agent spec (schema updated) lists allow rules merged intopermissions.allow. Claude getsBash({{orbit}} skills get orbit)andBash({{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.yamlgets the sameskills:dir ascodex.yaml, plusrequires_full: true(new schema field), so Duo gets the full tree.- Docs:
ORBIT_DATA_DIRis covered by one added sentence in the Storage section ofdocs/source/local/access/cli.md:orbit configalso reads$ORBIT_DATA_DIR/settings.json. The graph layout text is main's revision-database text. NoORBIT_DB. Prompt versions forsetupanduninstallare bumped. A sentence in the setup section saysglab skills update orbitreplaces the same directory, so rerunorbit 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) turnsdocs/source/local/access/cli.mdanddocs/source/ai_coding_agents.mdinto redirects, so whichever merges second must carry this MR's text to the new pages. Indocs/source/agents/_index.mdthat means: the skill row (nameorbit,--skill wrapper|full, Duo included), the line saying theorbit-cliskill covers only the local graph, the manual Claude allow rule (setup now adds it) and removal byrm(useorbit uninstall), and the quickstart'sglab skills install --global orbitafterglab orbit setup, which contradicts this MR because setup already installsorbit, so the glab command refuses or, with--force, overwrites it. Incli.mdat its new path: marker-based uninstall, legacyorbit-climigration, andORBIT_DATA_DIRcoveringsettings.json. - The labels come from #1383; milestone 19.6 is the current release.
- Rebased onto
mainafter !2757 (merged) was squash-merged; the branch now carries only this MR's commits.