Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
80f3128
feat: carry MCP servers, linked home scripts, memories and external t…
Fafoooo Sep 2, 2026
d619c33
fix: address PR #8 review findings and Windows CI
Fafoooo Sep 2, 2026
545e031
fix: match ~/.claude.json project keys by normalized path on Windows
Fafoooo Sep 2, 2026
f57d9b9
fix: refuse claude/files writes through non-skill symlinks on pull
Fafoooo Sep 2, 2026
d459ef7
test: ignore unrelated CLI-missing warning in symlink gate test
Fafoooo Sep 2, 2026
711c2f7
test: create dir-typed symlinks so Windows/Node 18 can mkdir through …
Fafoooo Sep 2, 2026
7c0357e
fix: record every origin's claim in shared/home-index.json and write …
Fafoooo Sep 2, 2026
b4491f8
fix: apply captured file modes explicitly after write (umask-independ…
Fafoooo Sep 4, 2026
c60b6d3
chore: bump vitest to 3.2.x (clears GHSA-5xrq-8626-4rwp and the vite/…
Fafoooo Sep 4, 2026
916e013
fix: warn when per-project MCP servers are skipped on a fresh machine
Fafoooo Sep 4, 2026
1a5ff95
fix: stop a partial push from hiding a tool's backup, and from commit…
Fafoooo Sep 4, 2026
1680321
fix: contain restore write targets, honor local policy, align dry-run
Fafoooo Sep 4, 2026
9901b44
fix: prefer a tool's own instructions on pull, report skipped project…
Fafoooo Sep 4, 2026
04b422d
test: point os.homedir() at the temp dir on Windows too
Fafoooo Sep 4, 2026
3f31c8c
fix: harden restore previews and shared skill writes
Fafoooo Sep 12, 2026
d7df024
fix: reuse stored credentials for private pull previews
Fafoooo Sep 12, 2026
ee2a1b9
fix: preserve linked skills and report failed installers
Fafoooo Sep 12, 2026
e452a3c
fix: preserve Codex MCP environment references
Fafoooo Sep 12, 2026
f3d1e14
fix: avoid exposing repository credentials in restore logs
Fafoooo Sep 12, 2026
c65f602
feat: automate npm releases from main starting at 1.0.0
Fafoooo Sep 12, 2026
5e97efe
docs: align security support policy with stable releases
Fafoooo Sep 12, 2026
5a961dc
chore: keep the 1.0.0 bump with the existing release workflow
Fafoooo Sep 12, 2026
e518c3d
test: isolate restore policy checks from host CLI probes
Fafoooo Sep 12, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Thumbs.db
.idea/
.vscode/

# Agent tooling
.serena/
.claude/

# Env / local
.env
.env.local
Expand Down
67 changes: 56 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ memories — into a private Git repo you own.
[![License](https://img.shields.io/badge/license-AGPL--3.0--only-blue.svg)](./LICENSE)
 ![Node](https://img.shields.io/badge/node-%E2%89%A518-43853d?logo=node.js&logoColor=white)
 ![Platforms](https://img.shields.io/badge/platform-Linux%20%C2%B7%20macOS%20%C2%B7%20Windows-555)
 ![Status](https://img.shields.io/badge/status-v0.1-orange)
 ![Status](https://img.shields.io/badge/status-v1.0.0-green)

[Quick start](#quick-start) · [Commands](#commands) · [How sign-in works](#how-sign-in-works) · [Secrets](#secrets)
[Quick start](#quick-start) · [Commands](#commands) · [How sign-in works](#how-sign-in-works) · [What still needs you after a pull](#what-still-needs-you-after-a-pull) · [Secrets](#secrets)

</div>

Expand Down Expand Up @@ -51,18 +51,28 @@ arbella update # later: update arbella itself
## What it does

```text
push ~/.claude · ~/.codex · Cursor ──▶ strip · template ──▶ private repo
pull private repo ──▶ install · place ──▶ tools + Cursor User data
push ~/.claude · ~/.codex · Cursor · $HOME ──▶ strip · template ──▶ private repo
pull private repo ──▶ install · place ──▶ tools + Cursor User data + $HOME
```

`push` reads the parts of your setup that matter, strips anything secret, swaps machine-specific paths for placeholders, and pushes the result to your repo. `pull` does the reverse: installs the CLIs you don't have, drops files back with this machine's paths, reinstalls plugins and skills, and wires shared Claude/Codex instructions back into `CLAUDE.md` and `AGENTS.md`.
`push` reads the parts of your setup that matter, strips anything secret, swaps machine-specific paths for placeholders, and pushes the result to your repo. `pull` does the reverse: installs the CLIs you don't have, drops files back with this machine's paths, merges MCP servers into `~/.claude.json`, writes shared home files back under `$HOME`, reinstalls plugins, skills, and external tools, and wires shared Claude/Codex instructions back into `CLAUDE.md` and `AGENTS.md`.

Beyond the obvious config files, Arbella also carries:

| It also carries | Where it comes from | Notes |
| --- | --- | --- |
| MCP servers | `~/.claude.json` → `mcpServers` | Never the whole file — only the server definitions, sanitized and templated; env values are redacted unless `includeSecrets` is on |
| The rest of `~/.claude` | `rules/`, `scripts/`, `output-styles/`, `keybindings.json`, `mcp-configs/`, `AGENTS.md` | The pieces your hooks and `CLAUDE.md` actually reference |
| Shared home files | `shared/home/` | Scripts your hooks, statusLine, or MCP commands point at outside any tool's home — picked up automatically (one hop, files only, 1 MiB cap) — plus anything you list in `extraPaths`, and the claude-mem companion `~/.claude-mem/settings.json` |
| Claude memories | `claude/memories/` | Per-project memory, only when `includeMemories` is on |
| External tools | manifest `externalTools` | Binaries behind an MCP/hook command that brew, `uv tool`, or pipx installed — reinstalled best-effort on pull |

Two things it deliberately won't do:

- **Never commits secrets.** API keys, OAuth tokens, `auth.json`, `.credentials.json` — all excluded, no exceptions. You sign back in after a pull, or carry them yourself with [`arbella secrets`](#arbella-secrets).
- **Doesn't copy what it can reinstall.** Plugins and registry skills are saved as a list and pulled fresh, so the repo stays small and never goes stale.
- **Excludes secrets by default.** Whole credential files — `auth.json`, `.credentials.json`, and the like — sit on a hard denylist and are never read into the repo, no exceptions. Inline secret-shaped values (API keys, OAuth tokens) found in files that *are* captured are redacted by default; [`includeSecrets`](#arbella-init) is the explicit opt-in that carries them verbatim into your (private) repo instead. You sign back in after a pull, or carry credentials yourself with [`arbella secrets`](#arbella-secrets).
- **Reinstalls plugins from their sources.** Plugins are saved as a list and pulled fresh. Local and shared skills include their contents, preserving local edits and avoiding guesses about their original source.

Supported today: **Claude Code**, **Codex**, **Cursor** (IDE + the `cursor-agent` CLI), **opencode**, **GitHub Copilot CLI**, **Kilo Code**, and **Google Antigravity**. Cursor support covers global MCP config, user settings, keybindings, snippets, local skills, skills.sh symlinks, the `cursor-agent` CLI config + global commands, 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). Antigravity spans three roots — its VS Code-style User settings, the `~/.antigravity` extension list, and the shared `~/.gemini` agent config (global rules, MCP servers, skills) — while its Google OAuth tokens never leave your machine.
Supported today: **Claude Code**, **Codex**, **Cursor** (IDE + the `cursor-agent` CLI), **opencode**, **GitHub Copilot CLI**, **Kilo Code**, and **Google Antigravity**. Claude Code coverage also reaches into `~/.claude.json` for MCP servers, per-project memories, and any linked scripts living under `$HOME`. Cursor support covers global MCP config, user settings, keybindings, snippets, local skills, shared skill contents with their symlinks, the `cursor-agent` CLI config + global commands, 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). Antigravity spans three roots — its VS Code-style User settings, the `~/.antigravity` extension list, and the shared `~/.gemini` agent config (global rules, MCP servers, skills) — while its Google OAuth tokens never leave your machine.

## Commands

Expand Down Expand Up @@ -100,6 +110,7 @@ The one-time wiring. Pick a host (GitHub, GitLab, or a plain Git URL); if the re
```sh
arbella init
arbella init --provider github --repo you/your-setup --auto-push daily -y
arbella init --extra-paths ~/.agents/hooks,~/.agents/memory -y
```

Saves your preferences:
Expand All @@ -108,6 +119,7 @@ Saves your preferences:
- who wins a conflict — your machine or the repo
- auto-push cadence — off, once per session, or daily
- whether secrets are allowed into the repo (off by default), and whether to include memories (also off)
- extra `$HOME` paths to carry along (`--extra-paths`, comma-separated, or the interactive prompt) — Arbella pre-fills the prompt with suggestions from the scripts your hooks and MCP servers already point at. Suggestions are never accepted on your behalf: `-y` keeps whatever is already configured, so pass `--extra-paths` to carry them non-interactively

### `arbella push`

Expand All @@ -129,11 +141,18 @@ arbella pull https://github.com/you/your-setup
arbella pull <url> --dry-run
```

`pull --dry-run` does not alter your tool setup or persistent backup clone. It
plans from an existing local clone without refreshing it; when there is no clone
yet, it makes a temporary download for the preview and removes it afterwards.

- First copies your current `~/.claude` and `~/.codex` to a timestamped safety folder, so a pull can't quietly wreck what's already there.
- Installs any missing CLIs — reaching for `sudo` only when the global npm folder actually needs root.
- Writes files with this machine's paths, then reinstalls your plugins and skills from the manifest.
- Merges MCP servers into `~/.claude.json` — a safety copy is taken first; the file is read in full, only the `mcpServers` keys (user- and project-scope) are changed, every other key (auth, telemetry) is carried over unchanged, and the whole file is rewritten atomically so it's never left half-written.
- Writes shared home files (linked scripts, `extraPaths`) back under `$HOME` — an existing file is snapshotted first and never overwritten while your machine is the source of truth.
- Reinstalls external tools behind MCP/hook commands via brew, `uv tool`, or pipx, best-effort.
- Deploys your shared instructions to `CLAUDE.md` and `AGENTS.md`.
- Reminds you to sign back in at the end, since no credentials came along for the ride.
- Reminds you to sign back in for credential stores that never travel. With the default secret setting, it also lists inline values that were redacted and need to be supplied again.

### `arbella status`

Expand All @@ -144,7 +163,7 @@ arbella status
arbella status --json # pipe it somewhere
```

New and modified files, plugin drift, the secrets it would skip. Writes nothing, installs nothing.
New and modified files, plugin drift, MCP server and external-tool drift, and the secrets it would skip. Files it doesn't recognize print as `not backed up (unknown to arbella)` — add them via `extraPaths` or open an issue. Writes nothing, installs nothing.

### `arbella update`

Expand Down Expand Up @@ -188,12 +207,25 @@ Short version: **gh and glab are preferred, not required.** They're the smoothes

For the device flow, point Arbella at your own registered OAuth app with `ARBELLA_GITHUB_CLIENT_ID` (or `ARBELLA_GITLAB_CLIENT_ID`). There's no sensible default to ship — the client ID is public but tied to your account — so with none set, Arbella skips the device flow and asks for a token instead. SSH and `file://` remotes are left alone, so those keep working however your Git already has them.

## What still needs you after a pull

Arbella restores everything it safely can. A few things are yours to finish:

- **Credentials.** Credential stores never travel, so sign back in when Arbella tells you to, or carry them yourself with [`arbella secrets`](#arbella-secrets). By default, inline API keys and OAuth tokens are redacted too; `includeSecrets` is the explicit private-repo opt-in that carries those inline values.
- **Redacted MCP env values.** If an MCP server's config held a secret-shaped value, push replaced it with `{{REDACTED}}`. Pull prints one line per key it couldn't restore, e.g. `claude: MCP server serena needs env SERENA_TOKEN re-supplied (redacted on backup)`.
- **Per-project MCP servers for a project you haven't cloned yet.** A project's MCP servers are only registered when its directory exists on this machine; if it doesn't, pull skips them and warns you which directory was skipped. Clone the project, then run `arbella pull` again.
- **External tools Arbella couldn't classify or install.** brew/`uv tool`/pipx tools are reinstalled best-effort; anything unknown, or that failed, is listed by name and by what uses it, so you can install it yourself.
- **`~/.claude.json` is only merged, key by key.** Pull touches `mcpServers` (and per-project `mcpServers`) and nothing else in that file — your `oauthAccount`, telemetry, and everything else stay exactly as they were.
- **The repo is trusted.** A pull writes the hooks, MCP commands, and install lists your repo describes, and runs `brew` / `uv tool` / `pipx` / `npm` for the tools it lists — exactly like the machine you pushed from. Keep the repo private, and run `arbella pull <url> --dry-run` first on anything you did not push yourself: the plan prints every command and target path.

## Secrets

This is the part I most wanted to get right.

- Whole credential files (`auth.json`, `.credentials.json`, and the like) sit on a hard denylist and are never read into the repo.
- Everything that *does* get committed runs through a sanitizer that redacts token-shaped values.
- `~/.claude.json` itself is still never stored — only its `mcpServers` sub-objects are lifted out, sanitized, and templated; the rest of that file (OAuth account, telemetry) never leaves your machine.
- Shared home files go through the same sanitizer, plus a home-specific denylist — `.env`, `*.pem`, `*.key`, `id_rsa*`, `.netrc`, `.npmrc`, `.git-credentials`, and friends are never captured even when something points at them.
- Any token Arbella holds for itself lives in one local file with `0600` permissions — not in the repo, not in a logged command, not baked into a Git remote.
- A test fails the build if a credential can reach the repo. This isn't on the honor system.

Expand All @@ -209,7 +241,20 @@ The Arbella was the flagship of the 1630 Winthrop Fleet — a few hundred settle

## Status

v0.1 — early, but it builds clean, the full test suite passes, and I use it daily. Issues and pull requests welcome.
v1.0.0 — builds clean, the full test suite passes, and I use it daily. Issues and pull requests welcome.

## Releases

For **1.0.0**, merge the version bump to `main`, then publish a GitHub release with
tag **v1.0.0** targeting that commit. `publish.yml` checks that the tag matches
`package.json`, runs the tests, and publishes the package to npm automatically.
For later releases, update `package.json` and `package-lock.json` and create the
matching GitHub release when ready.

Publishing uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
The `arbella` package's npm Settings must have a GitHub Actions trusted publisher with
owner **Fafoooo**, repository **arbella**, and workflow filename **publish.yml**
(no environment name). This avoids maintaining an npm token in GitHub secrets.

## License

Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ carrying a secret into a Git repo**. Security reports are taken seriously.

## Supported versions

Arbella is pre-1.0 and ships from `main`. Only the latest published `0.1.x`
release on npm receives security fixes.
Arbella ships from `main`. Only the latest published release on npm receives
security fixes.

| Version | Supported |
| --- | --- |
| latest `0.1.x` | ✅ |
| latest published release | ✅ |
| older | ❌ |

## Reporting a vulnerability
Expand Down
Loading
Loading