feat: add opencode, GitHub Copilot CLI, and Kilo Code adapters#5
Conversation
Extends arbella to back up and restore three more terminal AI CLIs alongside Claude Code, Codex, and Cursor. All three are "config-dir" CLIs: their portable setup is a single config directory of mostly-text files (a JSON/JSONC config plus markdown agents/commands and MCP config). Their capture/restore logic is identical apart from which paths to freeze, so it lives once in a shared engine (src/adapters/shared/configDir.ts); each adapter only declares its paths and wires detect/install. - opencode: ~/.config/opencode (XDG) — opencode.json/jsonc, agents/, commands/ - copilot: ~/.copilot (or $COPILOT_HOME) — config.json, mcp-config.json, agents/ - kilo: ~/.config/kilo (XDG) — kilo.jsonc, agents/, rules/ (CLI only) Per-tool denylists exclude plugin-install artifacts (node_modules, package.json, lockfiles) and the reinstallable skills/ tree, which are regenerated from each config on first run, plus machine-local session/log state. MCP env API keys are redacted by the existing structural sanitizer. Wires the new tools through every ToolId touch-point: types, config + manifest schemas, registry, platform paths/install, denylist, and the init/setup/backup/status/restore/secrets commands (incl. re-auth reminders). Adds unit tests for the config-dir engine round-trip and the platform wiring.
📝 WalkthroughWalkthroughAdds three new AI CLI tool adapters — opencode, GitHub Copilot CLI, and Kilo Code — introducing a shared config-dir capture/restore engine, per-tool paths/denylist/secrets modules, platform OS/install wiring, adapter registry entries, command dispatch updates across backup/restore/status/init/secrets/setup, and corresponding unit tests and README updates. ChangesNew tool adapter support
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Adapter
participant captureConfigDir
participant walkFrozen
participant captureFile
participant Sanitizer
participant Templater
Adapter->>captureConfigDir: ConfigDirSpec + CaptureContext
captureConfigDir->>walkFrozen: frozenPaths entries + denylist
walkFrozen->>captureFile: regular file path
captureFile->>Sanitizer: redact secrets
Sanitizer-->>captureFile: SecretRefs
captureFile->>Templater: toTemplate(content, vars)
Templater-->>captureFile: templated content
captureFile-->>walkFrozen: CapturedFile
walkFrozen-->>captureConfigDir: files + symlinks
captureConfigDir-->>Adapter: CaptureResult
sequenceDiagram
participant Adapter
participant restoreConfigDir
participant Templater
participant FileSystem
Adapter->>restoreConfigDir: RestoreContext + RestoreData + spec
alt dryRun
restoreConfigDir-->>Adapter: planned actions (logged)
else real restore
restoreConfigDir->>Templater: fromTemplate(content, vars)
Templater-->>restoreConfigDir: rehydrated content
restoreConfigDir->>FileSystem: write file/symlink (best-effort)
FileSystem-->>restoreConfigDir: success or warning
restoreConfigDir-->>Adapter: void
end
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c939cd7ef9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The Windows CI runner restores paths with native backslashes, so the hard-coded forward-slash assertion failed there. Normalize both sides to POSIX separators before comparing. Product behavior is unchanged — paths correctly rehydrate to the target machine's native separators.
There was a problem hiding this comment.
Pull request overview
Adds three new “config-dir CLI” adapters (opencode, GitHub Copilot CLI, Kilo Code) to Arbella’s backup/restore system by introducing a shared capture/restore engine for tools whose portable state is a single config directory.
Changes:
- Introduces a shared
configDircapture/restore engine and wires new adapters (detect/install/capture/restore) for opencode, copilot, and kilo. - Extends platform/path resolution, dependency install metadata, schemas, denylists, and CLI commands to recognize and handle the new tools end-to-end.
- Adds unit tests covering home-dir resolution, per-tool install commands/denylists, and config-dir capture/restore behavior (including redaction + placeholders).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/new-tool-platform.test.ts | New unit tests for new tools’ platform path resolution, binary names, install commands, and denylists. |
| test/unit/configdir-adapters.test.ts | New hermetic round-trip tests for config-dir capture/restore engine (redaction, denylist, templating, missing homes). |
| src/types.ts | Extends ToolId and canonical TOOL_IDS ordering to include opencode/copilot/kilo. |
| src/platform/os.ts | Adds XDG config-child resolver, new tool home-dir mapping, npm install commands, and CLI binary names. |
| src/platform/install.ts | Registers new dependencies and install specs for opencode/copilot/kilo via npm. |
| src/core/secrets/index.ts | Adds placeholder entries for new tools to document that no portable credential files are bundled. |
| src/core/sanitizer/denylist.ts | Adds per-tool denylists for opencode/copilot/kilo (plugin artifacts, skills, local state). |
| src/core/manifest/schema.ts | Updates manifest schema tool-id enum to include new tool IDs. |
| src/core/config/schema.ts | Updates config schema tool-id enum to include new tool IDs. |
| src/commands/status.ts | Wires new adapters into status capture flow. |
| src/commands/setup.ts | Updates setup command description to mention new supported AI CLIs. |
| src/commands/secrets.ts | Updates tool label mapping to include new tool IDs for secret reporting. |
| src/commands/restore.ts | Registers new tools in restore wiring and adds post-restore re-auth reminders. |
| src/commands/init.ts | Updates display-name mapping and tool-id parsing/guarding to include new tools. |
| src/commands/backup.ts | Wires new adapters into backup capture flow and generated labels. |
| src/adapters/shared/configDir.ts | New shared capture/restore implementation for config-dir CLIs (freeze paths, denylist, symlinks, redaction, templating). |
| src/adapters/registry.ts | Registers new adapters in the registry for detection and iteration. |
| src/adapters/opencode/paths.ts | New opencode tool-home + frozen-path declarations. |
| src/adapters/opencode/index.ts | New opencode adapter implementation delegating to shared config-dir engine. |
| src/adapters/kilo/paths.ts | New kilo tool-home + frozen-path declarations. |
| src/adapters/kilo/index.ts | New kilo adapter implementation delegating to shared config-dir engine. |
| src/adapters/copilot/paths.ts | New copilot tool-home + frozen-path declarations. |
| src/adapters/copilot/index.ts | New copilot adapter implementation delegating to shared config-dir engine. |
| README.md | Updates supported-tools documentation to include opencode, GitHub Copilot CLI, and Kilo Code and describe config-dir capture behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/commands/setup.ts (1)
93-105: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
--depsexample list not updated for the new tools.The command description now claims setup handles
opencode, copilot, kilo, but the--depsoption's example string (Line 104) still only listsgit,gh,glab,claude,codex,cursor, which is misleading for users trying to target the new tools via--deps.📝 Suggested fix
.option( "--deps <list>", - "comma/space-separated dependency ids to install (git,gh,glab,claude,codex,cursor)", + "comma/space-separated dependency ids to install (git,gh,glab,claude,codex,cursor,opencode,copilot,kilo)", )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/setup.ts` around lines 93 - 105, The `setup` command’s `--deps` help text is stale and does not match the tools now supported by the description. Update the option string in `setup.ts` so the example dependency list referenced by the `--deps` flag includes the newly supported IDs like `opencode`, `copilot`, and `kilo`, keeping it aligned with the command description and the dependency selection logic in `setup`.
🧹 Nitpick comments (4)
README.md (1)
64-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winClarify Kilo Code's scope excludes VS Code extension storage.
Per the PR objectives, Kilo Code support is scoped to the CLI config only, not VS Code extension storage — but this line doesn't call that out, and readers using Kilo Code as a VS Code extension may expect its extension state to be backed up too.
📝 Suggested clarification
-Supported today: **Claude Code**, **Codex**, **Cursor**, **opencode**, **GitHub Copilot CLI**, and **Kilo Code**. Cursor support covers global MCP config, user settings, keybindings, snippets, local skills, skills.sh symlinks, and extension IDs; runtime state and credentials stay out. opencode, Copilot CLI, and Kilo are config-dir CLIs — Arbella captures each one's config dir (the JSON/JSONC config, custom agents/commands, MCP config), strips token-shaped values, and skips machine-local state, plugin install artifacts, and reinstallable skills (regenerated from the config on first run). +Supported today: **Claude Code**, **Codex**, **Cursor**, **opencode**, **GitHub Copilot CLI**, and **Kilo Code**. Cursor support covers global MCP config, user settings, keybindings, snippets, local skills, skills.sh symlinks, and extension IDs; runtime state and credentials stay out. opencode, Copilot CLI, and Kilo are config-dir CLIs — Arbella captures each one's config dir (the JSON/JSONC config, custom agents/commands, MCP config), strips token-shaped values, and skips machine-local state, plugin install artifacts, and reinstallable skills (regenerated from the config on first run). Kilo Code support is limited to the CLI's own config directory, not the VS Code extension's storage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` at line 64, Clarify the Kilo Code support scope in the README summary so it is explicit that only the CLI config-dir content is captured and VS Code extension storage is not included. Update the supported-today sentence near the Kilo Code mention to distinguish Kilo Code CLI config handling from any extension-state or storage paths, keeping the wording consistent with the existing scope notes used for opencode and Copilot CLI.src/commands/init.ts (1)
710-725: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider deriving from
TOOL_IDSinstead of hardcoded literals.Both
displayNameandisToolIdmanually enumerate everyToolIdmember. This duplicates the source-of-truth list (TOOL_IDSinsrc/types.ts), so a future tool addition risks being wired into one function but forgotten in the other (e.g.isToolIdsilently rejecting a valid new tool passed via--tools).♻️ Suggested refactor for `isToolId`
-function isToolId(value: string): value is ToolId { - return ( - value === "claude" || - value === "codex" || - value === "cursor" || - value === "opencode" || - value === "copilot" || - value === "kilo" - ); -} +function isToolId(value: string): value is ToolId { + return (TOOL_IDS as readonly string[]).includes(value); +}Also applies to: 743-752
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/init.ts` around lines 710 - 725, Both displayName and isToolId are manually duplicating the ToolId source of truth, which can get out of sync with TOOL_IDS. Refactor the ToolId handling in src/commands/init.ts so these functions derive from TOOL_IDS in src/types.ts instead of hardcoded switch/union checks, keeping displayName and validation consistent for any new tool IDs.test/unit/configdir-adapters.test.ts (1)
190-208: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo restore round-trip coverage for copilot/kilo.
Only capture-side "graceful absence" is tested for copilot/kilo; the restore round-trip (secret redaction survives restore, placeholder expansion) is only exercised for opencode. Since all three share the same
configDirengine, this is lower risk, but adding a similar restore assertion for at least one of copilot/kilo would strengthen confidence that the shared engine wiring per-adapter is correct end-to-end.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/configdir-adapters.test.ts` around lines 190 - 208, Add restore round-trip coverage for at least one of the config-dir adapters besides opencode, since only capture-side graceful absence is currently tested for captureCopilot and captureKilo. Extend the config-dir adapter test suite with a restore assertion that verifies secret redaction and placeholder expansion survive a full capture/restore cycle for copilot or kilo, using the existing shared configDir engine helpers and the adapter entry points to keep the test end-to-end.src/adapters/kilo/index.ts (1)
45-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a static import instead of dynamic
import()insidedetect().This same pattern (
await import("../../utils/fs.js")insidedetect()) is repeated verbatim across the opencode, copilot, and kilo adapters. A static top-level import would be simpler and avoid repeated module resolution on everydetect()call, unless there's a specific reason (e.g. avoiding a circular import) for the lazy load.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adapters/kilo/index.ts` around lines 45 - 48, The detect() function in the kilo adapter uses a repeated dynamic import for utils/fs.js, which should be replaced with a static top-level import unless lazy loading is required to avoid a circular dependency. Update detect() to reference the already imported fs helper directly, and apply the same import pattern consistently across the opencode, copilot, and kilo adapter detect() implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adapters/shared/configDir.ts`:
- Around line 104-148: The early return in captureFile bypasses sanitizeFile for
binary-detected content, so make the looksBinary(bytes) path visible when
ctx.includeSecrets is false. Update captureFile to emit a warning through
warnings before pushing the base64 CapturedFile, using the existing tool and rel
identifiers so false-positive binary captures are observable instead of silently
skipping secret redaction.
In `@src/platform/os.ts`:
- Around line 65-79: The xdgConfigChild helper is routing Windows to %APPDATA%
instead of the expected ~/.config/<tool> path, which causes
toolHomeDir("opencode") and toolHomeDir("kilo") to resolve incorrectly. Update
xdgConfigChild in os.ts so it uses the same XDG-style home config location on
Windows as on other platforms, while keeping the existing fallback behavior for
XDG_CONFIG_HOME and homeDir(); use detectOS(), xdgConfigChild, and toolHomeDir
as the key references when locating the change.
---
Outside diff comments:
In `@src/commands/setup.ts`:
- Around line 93-105: The `setup` command’s `--deps` help text is stale and does
not match the tools now supported by the description. Update the option string
in `setup.ts` so the example dependency list referenced by the `--deps` flag
includes the newly supported IDs like `opencode`, `copilot`, and `kilo`, keeping
it aligned with the command description and the dependency selection logic in
`setup`.
---
Nitpick comments:
In `@README.md`:
- Line 64: Clarify the Kilo Code support scope in the README summary so it is
explicit that only the CLI config-dir content is captured and VS Code extension
storage is not included. Update the supported-today sentence near the Kilo Code
mention to distinguish Kilo Code CLI config handling from any extension-state or
storage paths, keeping the wording consistent with the existing scope notes used
for opencode and Copilot CLI.
In `@src/adapters/kilo/index.ts`:
- Around line 45-48: The detect() function in the kilo adapter uses a repeated
dynamic import for utils/fs.js, which should be replaced with a static top-level
import unless lazy loading is required to avoid a circular dependency. Update
detect() to reference the already imported fs helper directly, and apply the
same import pattern consistently across the opencode, copilot, and kilo adapter
detect() implementations.
In `@src/commands/init.ts`:
- Around line 710-725: Both displayName and isToolId are manually duplicating
the ToolId source of truth, which can get out of sync with TOOL_IDS. Refactor
the ToolId handling in src/commands/init.ts so these functions derive from
TOOL_IDS in src/types.ts instead of hardcoded switch/union checks, keeping
displayName and validation consistent for any new tool IDs.
In `@test/unit/configdir-adapters.test.ts`:
- Around line 190-208: Add restore round-trip coverage for at least one of the
config-dir adapters besides opencode, since only capture-side graceful absence
is currently tested for captureCopilot and captureKilo. Extend the config-dir
adapter test suite with a restore assertion that verifies secret redaction and
placeholder expansion survive a full capture/restore cycle for copilot or kilo,
using the existing shared configDir engine helpers and the adapter entry points
to keep the test end-to-end.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 77ac0c86-a6ce-451a-b3f1-21a3dca902e6
📒 Files selected for processing (24)
README.mdsrc/adapters/copilot/index.tssrc/adapters/copilot/paths.tssrc/adapters/kilo/index.tssrc/adapters/kilo/paths.tssrc/adapters/opencode/index.tssrc/adapters/opencode/paths.tssrc/adapters/registry.tssrc/adapters/shared/configDir.tssrc/commands/backup.tssrc/commands/init.tssrc/commands/restore.tssrc/commands/secrets.tssrc/commands/setup.tssrc/commands/status.tssrc/core/config/schema.tssrc/core/manifest/schema.tssrc/core/sanitizer/denylist.tssrc/core/secrets/index.tssrc/platform/install.tssrc/platform/os.tssrc/types.tstest/unit/configdir-adapters.test.tstest/unit/new-tool-platform.test.ts
What
Adds backup/restore support for three more terminal AI coding CLIs alongside Claude Code, Codex, and Cursor:
~/.config/opencode(XDG)opencode.json/.jsonc,agents/,commands/~/.copilot(or$COPILOT_HOME)config.json,mcp-config.json,agents/~/.config/kilo(XDG)kilo.jsonc,agents/,rules/(CLI only)How
All three are config-dir CLIs: their portable setup is a single config directory of mostly-text files. The capture/restore logic is identical apart from which paths to freeze, so it lives once in a shared engine (
src/adapters/shared/configDir.ts); each adapter just declares its paths and wiresdetect/installCli.node_modules/,package.json, lockfiles) and the reinstallableskills/tree — both regenerated from each tool's config on first run — plus machine-local session/log/state (e.g. Copilot'ssession-state/,command-history-state.json).ToolIdtouch-point:types, config + manifest schemas, registry,platform/os(paths + install + binary name), denylist, and theinit/setup/backup/status/restore/secretscommands (including per-tool re-auth reminders).Tests
test/unit/configdir-adapters.test.ts— capture/restore round-trip into a fresh home: secret redaction, denylist exclusion,{{HOME}}/{{TOOL_HOME}}templating, graceful absence.test/unit/new-tool-platform.test.ts— home-dir resolution (XDG +COPILOT_HOME), CLI binary names, npm install commands, per-tool denylists.Manual verification (WSL)
Verified end-to-end on WSL (Ubuntu) against the live config dirs and a real git remote:
detectAll()finds all installed tools; capture of live~/.config/opencodefroze 78 files with 1 MCP secret redacted, Copilot excluded its session state, Kilo handled empty gracefully.push→pullround-trip into a fresh$HOME: files restored with paths rehydrated, the MCP secret stayed{{REDACTED}}, R14 safety backup ran, re-auth reminders printed.🤖 Cross-platform: all paths go through
platform/os.ts(XDG on Linux/macOS,%APPDATA%on Windows); WSL is treated as Linux.Summary by CodeRabbit