arbella 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/log.ts","../src/utils/fs.ts","../src/platform/os.ts","../src/platform/install.ts","../src/adapters/claude/paths.ts","../src/core/sanitizer/denylist.ts","../src/adapters/claude/plugins.ts","../src/adapters/claude/capture.ts","../src/adapters/claude/restore.ts","../src/adapters/claude/index.ts","../src/adapters/codex/paths.ts","../src/core/sanitizer/patterns.ts","../src/adapters/codex/configToml.ts","../src/adapters/codex/capture.ts","../src/adapters/codex/restore.ts","../src/adapters/codex/index.ts","../src/adapters/cursor/paths.ts","../src/adapters/cursor/index.ts","../src/core/autobackup/hook.ts","../src/core/autobackup/throttle.ts","../src/core/autobackup/index.ts","../src/core/config/schema.ts","../src/core/config/index.ts","../src/core/auth/cli.ts","../src/core/auth/device-flow.ts","../src/core/auth/providers.ts","../src/core/auth/store.ts","../src/core/auth/index.ts","../src/core/repo/git.ts","../src/core/repo/providers/generic.ts","../src/core/repo/providers/github.ts","../src/core/repo/providers/gitlab.ts","../src/core/repo/index.ts","../src/core/sanitizer/index.ts","../src/core/templater/variables.ts","../src/core/templater/index.ts","../src/commands/_context.ts","../src/commands/setup.ts","../src/core/manifest/schema.ts","../src/core/manifest/index.ts","../src/commands/backup.ts","../src/index.ts","../src/types.ts","../src/adapters/registry.ts","../src/commands/init.ts","../src/commands/auth.ts","../src/commands/restore.ts","../src/commands/status.ts","../src/commands/secrets.ts","../src/core/secrets/index.ts"],"sourcesContent":["/**\n * picocolors-based logger. The single concrete Logger implementation injected\n * into adapters and commands. Writes human-facing lines to stderr so that any\n * machine-readable stdout (e.g. JSON status) stays clean.\n */\n\nimport pc from \"picocolors\";\n\nimport type { Logger } from \"../types.js\";\n\nlet verbose = false;\n\n/** Enable/disable debug() output (wired from a global --verbose flag). */\nexport function setVerbose(on: boolean): void {\n verbose = on;\n}\n\nfunction out(line: string): void {\n // All decorative logging goes to stderr; reserve stdout for data payloads.\n process.stderr.write(line + \"\\n\");\n}\n\nexport const log: Logger = {\n info(msg: string): void {\n out(`${pc.blue(\"ℹ\")} ${msg}`);\n },\n success(msg: string): void {\n out(`${pc.green(\"✓\")} ${msg}`);\n },\n warn(msg: string): void {\n out(`${pc.yellow(\"⚠\")} ${pc.yellow(msg)}`);\n },\n error(msg: string): void {\n out(`${pc.red(\"✗\")} ${pc.red(msg)}`);\n },\n step(msg: string): void {\n out(` ${pc.dim(\"›\")} ${msg}`);\n },\n debug(msg: string): void {\n if (verbose) out(`${pc.dim(\"·\")} ${pc.dim(msg)}`);\n },\n};\n\nexport default log;\n","/**\n * Safe filesystem wrappers over node:fs/promises. The single concrete FsService\n * implementation. Adapters use this rather than node:fs directly so behavior\n * (missing-path tolerance, recursive mkdir, mode handling) is uniform.\n */\n\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { FsService } from \"../types.js\";\n\nasync function read(p: string): Promise<string> {\n return fsp.readFile(p, \"utf8\");\n}\n\nasync function readBytes(p: string): Promise<Buffer> {\n return fsp.readFile(p);\n}\n\nasync function ensureDir(p: string): Promise<void> {\n await fsp.mkdir(p, { recursive: true });\n}\n\nasync function write(p: string, content: string, mode?: number): Promise<void> {\n await ensureDir(path.dirname(p));\n await fsp.writeFile(p, content, mode !== undefined ? { mode } : undefined);\n}\n\nasync function writeBytes(p: string, content: Buffer, mode?: number): Promise<void> {\n await ensureDir(path.dirname(p));\n await fsp.writeFile(p, content, mode !== undefined ? { mode } : undefined);\n}\n\nasync function copy(from: string, to: string): Promise<void> {\n await ensureDir(path.dirname(to));\n // cpSync/cp handles files and dirs recursively; preserve nothing special.\n await fsp.cp(from, to, { recursive: true });\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fsp.lstat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function rmrf(p: string): Promise<void> {\n await fsp.rm(p, { recursive: true, force: true });\n}\n\nasync function list(p: string): Promise<string[]> {\n try {\n return await fsp.readdir(p);\n } catch {\n return [];\n }\n}\n\nasync function isSymlink(p: string): Promise<boolean> {\n try {\n const st = await fsp.lstat(p);\n return st.isSymbolicLink();\n } catch {\n return false;\n }\n}\n\nasync function readLink(p: string): Promise<string> {\n return fsp.readlink(p);\n}\n\nasync function symlink(target: string, linkPath: string): Promise<void> {\n await ensureDir(path.dirname(linkPath));\n // Remove an existing entry first so re-create is idempotent.\n await fsp.rm(linkPath, { force: true, recursive: false }).catch(() => {});\n await fsp.symlink(target, linkPath);\n}\n\nasync function statKind(\n p: string,\n): Promise<\"file\" | \"dir\" | \"symlink\" | \"missing\"> {\n try {\n const st = await fsp.lstat(p);\n if (st.isSymbolicLink()) return \"symlink\";\n if (st.isDirectory()) return \"dir\";\n return \"file\";\n } catch {\n return \"missing\";\n }\n}\n\nexport const fs: FsService = {\n read,\n readBytes,\n write,\n writeBytes,\n copy,\n ensureDir,\n exists,\n rmrf,\n list,\n isSymlink,\n readLink,\n symlink,\n statKind,\n};\n\nexport default fs;\n","/**\n * OS abstraction. The ONE place that touches node:os / node:process for paths.\n * Never hardcode home or config paths anywhere else — call these helpers so the\n * tool works identically on macOS, Linux, and Windows.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport type { OS, ToolId } from \"../types.js\";\n\n/** Detect the current platform, narrowed to the OSes arbella supports. */\nexport function detectOS(): OS {\n const p = process.platform;\n if (p === \"darwin\") return \"darwin\";\n if (p === \"win32\") return \"win32\";\n // Treat every other POSIX platform as linux for install/path purposes.\n return \"linux\";\n}\n\n/** Absolute path to the user's home directory. */\nexport function homeDir(): string {\n return os.homedir();\n}\n\n/** Current username (best-effort; falls back to a parsed home basename). */\nexport function userName(): string {\n try {\n return os.userInfo().username;\n } catch {\n return path.basename(homeDir());\n }\n}\n\n/**\n * Directory for arbella's own config (config.json lives here).\n * - win32: %APPDATA%/arbella (or ~/AppData/Roaming/arbella)\n * - darwin/linux: $XDG_CONFIG_HOME/arbella or ~/.config/arbella\n */\nexport function configDir(): string {\n if (detectOS() === \"win32\") {\n const appData = process.env.APPDATA ?? path.join(homeDir(), \"AppData\", \"Roaming\");\n return path.join(appData, \"arbella\");\n }\n const xdg = process.env.XDG_CONFIG_HOME ?? path.join(homeDir(), \".config\");\n return path.join(xdg, \"arbella\");\n}\n\n/**\n * Directory for arbella's local data (default backup-repo clone location).\n * - win32: %LOCALAPPDATA%/arbella or ~/AppData/Local/arbella\n * - darwin/linux: $XDG_DATA_HOME/arbella or ~/.local/share/arbella\n */\nexport function dataDir(): string {\n if (detectOS() === \"win32\") {\n const localAppData =\n process.env.LOCALAPPDATA ?? path.join(homeDir(), \"AppData\", \"Local\");\n return path.join(localAppData, \"arbella\");\n }\n const xdg = process.env.XDG_DATA_HOME ?? path.join(homeDir(), \".local\", \"share\");\n return path.join(xdg, \"arbella\");\n}\n\n/** Absolute path to a tool's home directory on this machine. */\nexport function toolHomeDir(tool: ToolId): string {\n switch (tool) {\n case \"claude\":\n return path.join(homeDir(), \".claude\");\n case \"codex\":\n return path.join(homeDir(), \".codex\");\n case \"cursor\":\n return path.join(homeDir(), \".cursor\");\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* CLI install command mapping (R6) */\n/* -------------------------------------------------------------------------- */\n\n/** A single shell command to run (program + args), OS-resolved. */\nexport interface InstallCommand {\n /** Executable, e.g. \"npm\", \"brew\", \"winget\". */\n cmd: string;\n /** Arguments array (never shell-joined to avoid injection). */\n args: string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Linux package manager detection (R6 cross-OS installs) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The Linux package managers arbella can drive for system tools (git, gh, glab).\n * `unknown` means none of the supported managers were found on PATH, in which\n * case the install layer degrades to a clear \"install it manually\" message.\n */\nexport type LinuxPackageManager = \"apt\" | \"dnf\" | \"yum\" | \"pacman\" | \"zypper\" | \"unknown\";\n\n/**\n * Candidate manager executables in priority order. Debian/Ubuntu (`apt-get`)\n * first as it's the most common target, then Fedora/RHEL (`dnf`, legacy `yum`),\n * Arch (`pacman`), and openSUSE (`zypper`). The detector probes PATH for each and\n * returns the first present — this is the ONLY heuristic for \"which distro\".\n */\nconst LINUX_PM_PROBES: ReadonlyArray<{ id: LinuxPackageManager; bin: string }> = [\n { id: \"apt\", bin: \"apt-get\" },\n { id: \"dnf\", bin: \"dnf\" },\n { id: \"yum\", bin: \"yum\" },\n { id: \"pacman\", bin: \"pacman\" },\n { id: \"zypper\", bin: \"zypper\" },\n];\n\n/**\n * The executable that drives a given Linux package manager (e.g. `apt` -> the\n * `apt-get` binary). Returns null for `unknown`. Exposed so the install layer can\n * `which`-probe managers without re-encoding the apt->apt-get mapping.\n */\nexport function linuxPackageManagerBinary(pm: LinuxPackageManager): string | null {\n switch (pm) {\n case \"apt\":\n return \"apt-get\";\n case \"dnf\":\n return \"dnf\";\n case \"yum\":\n return \"yum\";\n case \"pacman\":\n return \"pacman\";\n case \"zypper\":\n return \"zypper\";\n case \"unknown\":\n return null;\n }\n}\n\n/**\n * The ordered list of (manager id, driving binary) candidates to probe on Linux.\n * The install layer iterates this with its PATH probe to pick the first present\n * manager; kept here so os.ts stays the single source of OS/package-manager\n * knowledge. (No I/O here — probing belongs to the install layer.)\n */\nexport function linuxPackageManagerCandidates(): ReadonlyArray<{\n id: LinuxPackageManager;\n bin: string;\n}> {\n return LINUX_PM_PROBES;\n}\n\n/**\n * Build the argv that installs a single distro package non-interactively with a\n * given manager. Returns the program + args, ready for `sudo` prefixing by the\n * caller (system package managers need root; npm/brew/winget do not).\n *\n * apt -> apt-get install -y <pkg>\n * dnf -> dnf install -y <pkg>\n * yum -> yum install -y <pkg>\n * pacman -> pacman -S --noconfirm <pkg>\n * zypper -> zypper install -y <pkg>\n *\n * Returns null for `unknown` (no supported manager). `-y`/`--noconfirm` keep the\n * install non-interactive so a restore on a fresh box does not hang on a prompt.\n */\nexport function linuxInstallArgs(\n pm: LinuxPackageManager,\n pkg: string,\n): { cmd: string; args: string[] } | null {\n switch (pm) {\n case \"apt\":\n return { cmd: \"apt-get\", args: [\"install\", \"-y\", pkg] };\n case \"dnf\":\n return { cmd: \"dnf\", args: [\"install\", \"-y\", pkg] };\n case \"yum\":\n return { cmd: \"yum\", args: [\"install\", \"-y\", pkg] };\n case \"pacman\":\n return { cmd: \"pacman\", args: [\"-S\", \"--noconfirm\", pkg] };\n case \"zypper\":\n return { cmd: \"zypper\", args: [\"install\", \"-y\", pkg] };\n case \"unknown\":\n return null;\n }\n}\n\n/**\n * Map a tool -> the command that installs its CLI/app on the given OS.\n * Returns null when there is no supported install path for that tool on that OS\n * (caller should warn + skip gracefully).\n *\n * claude: npm i -g @anthropic-ai/claude-code (all OSes)\n * codex: npm i -g @openai/codex (all OSes)\n * cursor: brew install --cask cursor (darwin), winget install ... (win32),\n * no headless install on linux (null) -- it's a desktop app.\n */\nexport function installCommandFor(tool: ToolId, targetOS: OS): InstallCommand | null {\n switch (tool) {\n case \"claude\":\n return { cmd: \"npm\", args: [\"install\", \"-g\", \"@anthropic-ai/claude-code\"] };\n case \"codex\":\n return { cmd: \"npm\", args: [\"install\", \"-g\", \"@openai/codex\"] };\n case \"cursor\":\n if (targetOS === \"darwin\") {\n return { cmd: \"brew\", args: [\"install\", \"--cask\", \"cursor\"] };\n }\n if (targetOS === \"win32\") {\n return { cmd: \"winget\", args: [\"install\", \"--id\", \"Anysphere.Cursor\", \"-e\"] };\n }\n return null; // No standard headless install on Linux.\n }\n}\n\n/**\n * The probe used to check whether a tool's CLI is on PATH. Returns the binary\n * name to look up with `command -v` / `where`.\n */\nexport function cliBinaryName(tool: ToolId): string {\n switch (tool) {\n case \"claude\":\n return \"claude\";\n case \"codex\":\n return \"codex\";\n case \"cursor\":\n return \"cursor\";\n }\n}\n","/**\n * Platform install layer (R6).\n *\n * The ONE place that shells out to package managers (`npm`, `brew`, `winget`)\n * and probes `PATH` (`which` / `where`). Everything tool-specific (which command\n * installs which tool on which OS, which binary name to probe) is resolved via\n * `./os.js` — this module only knows *how* to run those commands and how to read\n * their results.\n *\n * Design rules honored here:\n * - Never shell-interpolate: every spawn uses an explicit argv array (execa).\n * - Never throw on a \"tool is simply absent\" probe — `which` returns a boolean.\n * - Surface missing package managers with a clear, actionable error so a restore\n * on a fresh machine fails loudly instead of silently producing a broken setup.\n * - `runInstall(null)` is a graceful no-op + warning (e.g. Cursor has no headless\n * install path on Linux).\n */\n\nimport { promises as fsp, constants as fsConstants } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { execa } from \"execa\";\n\nimport type { OS, ToolId } from \"../types.js\";\nimport {\n cliBinaryName,\n detectOS,\n installCommandFor,\n linuxInstallArgs,\n linuxPackageManagerCandidates,\n type InstallCommand,\n type LinuxPackageManager,\n} from \"./os.js\";\nimport { log } from \"../utils/log.js\";\n\n/* -------------------------------------------------------------------------- */\n/* PATH probing */\n/* -------------------------------------------------------------------------- */\n\n/**\n * True if `bin` resolves on PATH.\n *\n * POSIX uses the `which` executable, win32 uses `where`. We deliberately do NOT\n * use `command -v`, which is a shell *builtin* (no standalone executable) and so\n * cannot be spawned directly. A non-zero exit / spawn failure simply means\n * \"not found\" — never an error to propagate.\n */\nexport async function which(bin: string): Promise<boolean> {\n // Guard against accidental empty lookups (would make `where`/`which` hang or\n // match nothing in confusing ways).\n if (!bin) return false;\n\n const probe = detectOS() === \"win32\" ? \"where\" : \"which\";\n try {\n const result = await execa(probe, [bin], {\n // We interpret exit codes ourselves; do not let execa throw on non-zero.\n reject: false,\n // Some shells/PATH setups are slow; keep it bounded.\n timeout: 10_000,\n // We don't need the output, but capture it so nothing leaks to our stderr.\n stdio: \"pipe\",\n });\n return result.exitCode === 0;\n } catch {\n // `which`/`where` itself missing, timeout, or any spawn error => not found.\n return false;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Running install commands */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Human-readable package-manager name + the hint we show when it is missing.\n * Keyed by the executable that `InstallCommand.cmd` will be.\n */\nconst PKG_MANAGER_HINTS: Record<string, string> = {\n npm: \"npm (ships with Node.js). Install Node.js from https://nodejs.org/ and try again.\",\n brew: \"Homebrew. Install it from https://brew.sh/ and try again.\",\n winget:\n \"winget (App Installer). Install it from the Microsoft Store (\\\"App Installer\\\") and try again.\",\n};\n\n/** Best-effort friendly name for the manager driving an install command. */\nfunction managerHint(cmd: string): string {\n return PKG_MANAGER_HINTS[cmd] ?? `\\`${cmd}\\` (required to install this tool).`;\n}\n\n/* -- global-npm permission handling (so installs work on system Node too) -- */\n\n/** Cached writability of the global npm install dir (constant within a run). */\nlet cachedGlobalNpmWritable: boolean | undefined;\n\n/** Walk up to the nearest existing directory and test W_OK. Never throws. */\nasync function isPathWritable(target: string): Promise<boolean> {\n let p = target;\n for (;;) {\n try {\n await fsp.access(p, fsConstants.W_OK);\n return true;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n const parent = path.dirname(p);\n if (parent === p) return false;\n p = parent;\n continue;\n }\n return false; // EACCES / EPERM / anything else -> not writable\n }\n }\n}\n\n/**\n * True when the npm GLOBAL install dir is writable by the current user — what\n * decides whether `npm install -g` needs `sudo`:\n * - nvm / Homebrew / a user `prefix` / Windows -> writable -> plain npm.\n * - system Node (apt's /usr/lib/node_modules etc.) -> NOT writable -> needs root.\n * Probes `<npm prefix -g>/lib/node_modules` (Windows: `<prefix>`), climbing to the\n * nearest existing ancestor. Never throws; unknown -> not writable (prefer sudo,\n * the safe default for the EACCES case). Cached for the run.\n */\nasync function isGlobalNpmWritable(): Promise<boolean> {\n if (cachedGlobalNpmWritable !== undefined) return cachedGlobalNpmWritable;\n let dir: string | null = null;\n try {\n const res = await execa(\"npm\", [\"prefix\", \"-g\"], {\n reject: false,\n stdio: \"pipe\",\n timeout: 10_000,\n });\n const prefix = (res.stdout ?? \"\").trim();\n if (prefix) {\n dir =\n detectOS() === \"win32\" ? prefix : path.join(prefix, \"lib\", \"node_modules\");\n }\n } catch {\n // unknown prefix -> treated as not writable below.\n }\n cachedGlobalNpmWritable = dir ? await isPathWritable(dir) : false;\n return cachedGlobalNpmWritable;\n}\n\n/**\n * Given an install command, add `sudo` IFF it is a global `npm install -g` that\n * needs root on this machine — and only when sudo is possible. This single choke\n * point makes EVERY path (restore's CLI installs, the captured-globals reinstall,\n * and setup) elevate correctly, while user-owned Node (nvm/brew/Windows) is never\n * needlessly sudo'd. Returns the (possibly elevated) command + a one-line note\n * when sudo was added.\n */\nasync function elevateForNpmGlobal(\n cmd: InstallCommand,\n): Promise<{ command: InstallCommand; note?: string }> {\n if (cmd.cmd !== \"npm\") return { command: cmd };\n const a = cmd.args;\n const installs = a.includes(\"install\") || a.includes(\"i\") || a.includes(\"add\");\n const global = a.includes(\"-g\") || a.includes(\"--global\");\n if (!installs || !global) return { command: cmd };\n if (detectOS() === \"win32\" || isRoot() || (await isGlobalNpmWritable())) {\n return { command: cmd };\n }\n if (await which(\"sudo\")) {\n return {\n command: { cmd: \"sudo\", args: [\"npm\", ...a] },\n note:\n \"The global npm folder needs root here — using sudo for this install. \" +\n \"You may be prompted for your password.\",\n };\n }\n return { command: cmd };\n}\n\n/**\n * True for a package whose NAME encodes a platform/arch that is NOT this machine\n * (e.g. `@scope/tui-darwin-arm64` on Linux). Such native builds only install on\n * their own OS, so reinstalling them elsewhere just fails noisily — restore skips\n * them. Conservative: only the unambiguous os tokens darwin/win32/linux as\n * delimited segments count, so ordinary names (winston, macaron) are safe.\n */\nexport function isForeignPlatformPackage(\n name: string,\n os: OS = detectOS(),\n): boolean {\n const lower = name.toLowerCase();\n const tokenFor: Record<OS, RegExp> = {\n darwin: /(^|[-_/@])darwin([-_/]|$)/,\n win32: /(^|[-_/@])win32([-_/]|$)/,\n linux: /(^|[-_/@])linux([-_/]|$)/,\n };\n for (const other of [\"darwin\", \"win32\", \"linux\"] as OS[]) {\n if (other !== os && tokenFor[other].test(lower)) return true;\n }\n return false;\n}\n\n/**\n * Execute an `InstallCommand` produced by `installCommandFor`.\n *\n * - `null` (no supported install path on this OS, e.g. Cursor on Linux) => warn\n * and return without throwing, so a backup/restore of other tools proceeds.\n * - If the underlying package manager is not on PATH, throw a clear error naming\n * the missing manager and how to get it.\n * - Inherit stdio so the user sees real-time install progress (npm/brew are\n * chatty and long-running; swallowing their output would look frozen).\n */\nexport async function runInstall(cmd: InstallCommand | null): Promise<void> {\n if (cmd === null) {\n log.warn(\n \"No supported automatic install for this tool on this OS — skipping. Install it manually.\",\n );\n return;\n }\n\n // Elevate a global `npm install -g` with sudo when the global folder is\n // root-owned (system Node); a no-op for user-owned Node, Windows, and non-npm.\n const elevated = await elevateForNpmGlobal(cmd);\n if (elevated.note) log.info(elevated.note);\n const { cmd: program, args } = elevated.command;\n\n // Fail fast with a useful message if the package manager itself is absent.\n const hasManager = await which(program);\n if (!hasManager) {\n throw new Error(\n `Cannot run \"${program} ${args.join(\" \")}\": ${managerHint(program)}`,\n );\n }\n\n log.step(`Running: ${program} ${args.join(\" \")}`);\n try {\n await execa(program, args, {\n // Show progress live; these commands are interactive-ish and slow.\n stdio: \"inherit\",\n // Generous: brew cask / npm global installs can take minutes.\n timeout: 10 * 60_000,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Install command failed (${program} ${args.join(\" \")}): ${reason}`,\n );\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Per-tool CLI helpers (used by the adapter index modules) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * True if a tool's CLI/app binary is on PATH.\n * Thin wrapper over `which(cliBinaryName(tool))` so adapters don't repeat the\n * binary-name lookup. (Cursor in particular is frequently absent — that's fine.)\n */\nexport async function isCliInstalled(tool: ToolId): Promise<boolean> {\n return which(cliBinaryName(tool));\n}\n\n/**\n * Install a tool's CLI/app for the given OS (R6).\n * Resolves the correct command via `installCommandFor` and runs it. Safe to call\n * even if already installed; callers should `isCliInstalled()`-guard to avoid\n * redundant work, but this remains idempotent-ish (package managers no-op or\n * upgrade). A `null` command (unsupported OS) is handled gracefully by\n * `runInstall`.\n */\nexport async function installCli(tool: ToolId, os: OS): Promise<void> {\n await runInstall(installCommandFor(tool, os));\n}\n\n/**\n * Convenience: ensure a tool's CLI is present for `os`, skipping the install when\n * it already resolves on PATH. Used by restore to only install what's missing.\n */\nexport async function ensureCli(tool: ToolId, os: OS): Promise<void> {\n if (await isCliInstalled(tool)) {\n log.debug(`${cliBinaryName(tool)} already installed; skipping.`);\n return;\n }\n log.step(`Installing ${tool} CLI…`);\n await installCli(tool, os);\n}\n\n/* -------------------------------------------------------------------------- */\n/* npm globals */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Install a single global npm package (`npm install -g <pkg>`).\n * Throws a clear error if npm is missing, or if the install itself fails.\n */\nexport async function npmInstallGlobal(pkg: string): Promise<void> {\n await runInstall({ cmd: \"npm\", args: [\"install\", \"-g\", pkg] });\n}\n\n/**\n * Shape of `npm ls -g --json` (the only fields we rely on). npm emits\n * `{ name, dependencies: { \"<pkg>\": { version, overridden } } }`.\n */\ninterface NpmLsJson {\n dependencies?: Record<string, { version?: string } | undefined>;\n}\n\n/**\n * Packages that are part of the Node/npm toolchain itself, never user setup, and\n * which we must not try to \"reinstall\" on another machine.\n */\nconst NPM_GLOBAL_IGNORE = new Set<string>([\"npm\", \"corepack\", \"lib\", \"arbella\"]);\n\n/**\n * List installed global npm packages as `[{ package, version? }]`.\n *\n * This is the SHARED source of `ToolManifest.npmGlobals` for both adapters.\n * Tolerant by design:\n * - if npm is absent, returns `[]` (no globals we can speak to);\n * - `npm ls -g` exits non-zero on peer-dep/extraneous warnings while STILL\n * printing valid JSON, so we set `reject:false` and parse stdout regardless;\n * - any parse failure / unexpected shape => `[]` rather than throwing.\n *\n * Note: tools installed outside npm (e.g. a binary in ~/.local/bin) do NOT appear\n * here. That is expected — we record only what npm actually reports and never\n * fabricate entries.\n */\nexport async function listNpmGlobals(): Promise<\n Array<{ package: string; version?: string }>\n> {\n if (!(await which(\"npm\"))) {\n log.debug(\"npm not found on PATH; reporting no global packages.\");\n return [];\n }\n\n let stdout = \"\";\n try {\n const result = await execa(\n \"npm\",\n [\"ls\", \"-g\", \"--depth=0\", \"--json\"],\n {\n reject: false, // npm may exit 1 on warnings yet still emit valid JSON.\n stdio: \"pipe\",\n timeout: 60_000,\n },\n );\n stdout = result.stdout ?? \"\";\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n log.debug(`npm ls -g failed to run: ${reason}`);\n return [];\n }\n\n const trimmed = stdout.trim();\n if (!trimmed) return [];\n\n let parsed: NpmLsJson;\n try {\n parsed = JSON.parse(trimmed) as NpmLsJson;\n } catch {\n log.debug(\"Could not parse `npm ls -g --json` output; reporting none.\");\n return [];\n }\n\n const deps = parsed.dependencies;\n if (!deps || typeof deps !== \"object\") return [];\n\n const out: Array<{ package: string; version?: string }> = [];\n for (const [name, info] of Object.entries(deps)) {\n if (NPM_GLOBAL_IGNORE.has(name)) continue;\n const version =\n info && typeof info === \"object\" && typeof info.version === \"string\"\n ? info.version\n : undefined;\n out.push(version ? { package: name, version } : { package: name });\n }\n\n // Deterministic order so manifests diff cleanly across runs/machines.\n out.sort((a, b) => a.package.localeCompare(b.package));\n return out;\n}\n\n/* ========================================================================== */\n/* Cross-OS dependency registry (P1) */\n/* */\n/* The plug-&-play foundation the Auth (`core/auth`) and Setup */\n/* (`commands/setup`) layers build on. Everything here is metadata + thin */\n/* wrappers over the spawn helpers above: */\n/* */\n/* DEPENDENCIES a typed registry of every tool arbella can install */\n/* (git, gh, glab, claude, codex, cursor) with the per-OS */\n/* install command resolved lazily. */\n/* isInstalled(id) true if the dependency's binary resolves on PATH. */\n/* install(id) install it for the current OS (cross-OS dispatch). */\n/* */\n/* Design rules (same as the rest of this module): */\n/* - Never shell-interpolate: every spawn uses an explicit argv array. */\n/* - A probe NEVER throws (absence is a boolean, not an error). */\n/* - A missing package manager / unsupported OS surfaces a clear, actionable */\n/* message; `install` returns an outcome object rather than always */\n/* throwing, so the Setup multiselect can install several deps and report */\n/* per-dep success/failure without aborting the whole batch. */\n/* ========================================================================== */\n\n/**\n * Every dependency arbella knows how to detect + install. A superset of\n * {@link ToolId} (claude/codex/cursor) that also covers the version-control and\n * provider CLIs needed for auth:\n * - `git` : required for every clone/push.\n * - `gh` : GitHub CLI — preferred GitHub auth + repo creation path.\n * - `glab` : GitLab CLI — preferred GitLab auth + repo creation path.\n * - `claude`/`codex`/`cursor` : the AI tool CLIs arbella backs up.\n *\n * Kept LOCAL to the platform layer (not added to `src/types.ts`) so the install\n * foundation is self-contained and the Auth/Setup agents import it from here.\n */\nexport type DependencyId = \"git\" | \"gh\" | \"glab\" | ToolId;\n\n/** All known dependency ids, in the order Setup should present them. */\nexport const DEPENDENCY_IDS: readonly DependencyId[] = [\n \"git\",\n \"gh\",\n \"glab\",\n \"claude\",\n \"codex\",\n \"cursor\",\n] as const;\n\n/**\n * How a dependency is installed on one OS. Exactly one variant applies per OS:\n * - `npm` : a global npm package (`npm install -g <package>`), OS-independent.\n * - `brew` : a Homebrew formula or cask (macOS).\n * - `winget` : a winget package id (Windows).\n * - `apt` : a Linux distro package name; the install layer resolves the\n * actual manager (apt-get/dnf/pacman/...) at runtime and prefixes\n * `sudo`. `repoNote` is an optional one-line hint shown when the\n * distro package is known to be stale/absent (e.g. gh/glab on\n * older distros need the vendor apt/dnf repo).\n * - `none` : no supported headless install on this OS (e.g. Cursor on Linux,\n * a desktop app). `reason` explains why; `install` warns + skips.\n */\nexport type InstallMethod =\n | { kind: \"npm\"; package: string }\n | { kind: \"brew\"; formula: string; cask?: boolean }\n | { kind: \"winget\"; id: string }\n | { kind: \"apt\"; package: string; repoNote?: string }\n | { kind: \"none\"; reason: string };\n\n/** Per-OS install methods for a dependency. */\nexport interface PlatformInstall {\n readonly darwin: InstallMethod;\n readonly linux: InstallMethod;\n readonly win32: InstallMethod;\n}\n\n/**\n * Full metadata for a dependency. This is what the Setup multiselect renders and\n * what Auth consults before offering \"install gh? [Y/n]\".\n */\nexport interface DependencySpec {\n /** Stable id. */\n readonly id: DependencyId;\n /** Short human label for prompts (\"GitHub CLI (gh)\"). */\n readonly label: string;\n /** One-line rationale shown next to the checkbox (\"preferred GitHub auth\"). */\n readonly why: string;\n /** Executable probed on PATH to decide \"installed?\". */\n readonly binary: string;\n /**\n * True for the dependencies arbella recommends by default in `setup` (git, gh,\n * glab). The AI tool CLIs are opt-in (the user picks which they use), so they\n * are NOT recommended-by-default — a fresh user is not assumed to want all\n * three. `git` is the only STRONGLY recommended one (see `strong`).\n */\n readonly recommended: boolean;\n /**\n * True for git only — it is effectively required, so Setup should both\n * pre-check it AND label it \"strongly recommended\".\n */\n readonly strong: boolean;\n /** The per-OS install methods. */\n readonly install: PlatformInstall;\n}\n\n/** Convenience builder for an npm-global dependency (same on every OS). */\nfunction npmEverywhere(pkg: string): PlatformInstall {\n const m: InstallMethod = { kind: \"npm\", package: pkg };\n return { darwin: m, linux: m, win32: m };\n}\n\n/**\n * The dependency registry. The single source of truth the Auth + Setup layers\n * import. Ordering matches {@link DEPENDENCY_IDS}.\n */\nexport const DEPENDENCIES: Readonly<Record<DependencyId, DependencySpec>> = {\n git: {\n id: \"git\",\n label: \"Git\",\n why: \"Required to clone and push your backup repository.\",\n binary: \"git\",\n recommended: true,\n strong: true,\n install: {\n darwin: { kind: \"brew\", formula: \"git\" },\n // git is in the base repos of every supported distro.\n linux: { kind: \"apt\", package: \"git\" },\n win32: { kind: \"winget\", id: \"Git.Git\" },\n },\n },\n gh: {\n id: \"gh\",\n label: \"GitHub CLI (gh)\",\n why: \"Preferred GitHub sign-in (handles OAuth for you) and private-repo creation.\",\n binary: \"gh\",\n recommended: true,\n strong: false,\n install: {\n darwin: { kind: \"brew\", formula: \"gh\" },\n linux: {\n kind: \"apt\",\n package: \"gh\",\n repoNote:\n \"On older distros `gh` may be absent from the base repos — add GitHub's \" +\n \"official package repo (https://github.com/cli/cli/blob/trunk/docs/install_linux.md) \" +\n \"and retry.\",\n },\n win32: { kind: \"winget\", id: \"GitHub.cli\" },\n },\n },\n glab: {\n id: \"glab\",\n label: \"GitLab CLI (glab)\",\n why: \"Preferred GitLab sign-in (handles OAuth for you) and private-repo creation.\",\n binary: \"glab\",\n recommended: true,\n strong: false,\n install: {\n darwin: { kind: \"brew\", formula: \"glab\" },\n linux: {\n kind: \"apt\",\n package: \"glab\",\n repoNote:\n \"If your distro has no `glab` package, install it from \" +\n \"https://gitlab.com/gitlab-org/cli/-/releases (or via Homebrew on Linux) and retry.\",\n },\n win32: { kind: \"winget\", id: \"GitLab.GitLabCLI\" },\n },\n },\n claude: {\n id: \"claude\",\n label: \"Claude Code\",\n why: \"Anthropic's CLI — backed up/restored by arbella.\",\n binary: cliBinaryName(\"claude\"),\n recommended: false,\n strong: false,\n install: npmEverywhere(\"@anthropic-ai/claude-code\"),\n },\n codex: {\n id: \"codex\",\n label: \"Codex\",\n why: \"OpenAI's CLI — backed up/restored by arbella.\",\n binary: cliBinaryName(\"codex\"),\n recommended: false,\n strong: false,\n install: npmEverywhere(\"@openai/codex\"),\n },\n cursor: {\n id: \"cursor\",\n label: \"Cursor\",\n why: \"The Cursor editor/CLI — backed up/restored by arbella.\",\n binary: cliBinaryName(\"cursor\"),\n recommended: false,\n strong: false,\n install: {\n darwin: { kind: \"brew\", formula: \"cursor\", cask: true },\n // Cursor is a desktop app with no standard headless Linux install.\n linux: {\n kind: \"none\",\n reason:\n \"Cursor is a desktop app with no standard headless install on Linux — \" +\n \"download it from https://www.cursor.com/downloads.\",\n },\n win32: { kind: \"winget\", id: \"Anysphere.Cursor\" },\n },\n },\n};\n\n/** Look up a dependency spec by id. Throws on an unknown id (programmer error). */\nexport function getDependency(id: DependencyId): DependencySpec {\n const spec = DEPENDENCIES[id];\n if (!spec) throw new Error(`Unknown dependency id: ${String(id)}`);\n return spec;\n}\n\n/** Every dependency spec, in canonical order (handy for Setup's multiselect). */\nexport function allDependencies(): DependencySpec[] {\n return DEPENDENCY_IDS.map((id) => DEPENDENCIES[id]);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Linux package-manager probing */\n/* -------------------------------------------------------------------------- */\n\n/** Memoized result of the (mildly expensive) PATH probe for a Linux manager. */\nlet cachedLinuxPm: LinuxPackageManager | undefined;\n\n/**\n * Detect which supported Linux package manager is available, by probing PATH for\n * each candidate (apt-get -> dnf -> yum -> pacman -> zypper) in priority order.\n * Returns \"unknown\" when none is found. Cached after the first call (the answer\n * does not change within a process). Safe to call on non-Linux too — it simply\n * probes and returns \"unknown\" there.\n */\nexport async function detectLinuxPackageManager(): Promise<LinuxPackageManager> {\n if (cachedLinuxPm !== undefined) return cachedLinuxPm;\n for (const { id, bin } of linuxPackageManagerCandidates()) {\n if (await which(bin)) {\n cachedLinuxPm = id;\n return id;\n }\n }\n cachedLinuxPm = \"unknown\";\n return \"unknown\";\n}\n\n/* -------------------------------------------------------------------------- */\n/* Dependency detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * True if a dependency's binary resolves on PATH. Thin wrapper over\n * {@link which} keyed by the registry's `binary` field, so callers don't repeat\n * the binary-name lookup. Never throws (absence is a boolean).\n *\n * This is the public detection entrypoint the Auth + Setup layers use, e.g.\n * `if (!(await isInstalled(\"gh\"))) { offer to install }`.\n */\nexport async function isInstalled(id: DependencyId): Promise<boolean> {\n return which(getDependency(id).binary);\n}\n\n/**\n * Resolve `[{ id, installed }]` for several dependencies at once (concurrently).\n * Used by `setup` to pre-check the boxes for what's missing. Order matches the\n * input.\n */\nexport async function checkInstalled(\n ids: readonly DependencyId[] = DEPENDENCY_IDS,\n): Promise<Array<{ id: DependencyId; installed: boolean }>> {\n return Promise.all(\n ids.map(async (id) => ({ id, installed: await isInstalled(id) })),\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Dependency installation */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The outcome of an {@link install} attempt. `install` does NOT throw on a failed\n * or unsupported install — it returns this so a Setup batch can install several\n * dependencies and report each one's result without aborting the rest. (The\n * lower-level {@link runInstall} still throws; this layer catches and classifies.)\n *\n * - \"installed\" : the install command ran successfully.\n * - \"already\" : the binary was already on PATH; nothing was done.\n * - \"unsupported\" : no headless install for this dependency on this OS\n * (e.g. Cursor on Linux). `message` explains + points to a\n * manual download.\n * - \"failed\" : the install command (or its package manager) failed.\n * `message` is a secret-free, actionable error.\n */\nexport interface InstallOutcome {\n id: DependencyId;\n status: \"installed\" | \"already\" | \"unsupported\" | \"failed\";\n /** Human-readable, secret-free detail (always present for non-\"installed\"). */\n message?: string;\n}\n\n/** Options for {@link install}. */\nexport interface InstallOptions {\n /**\n * Re-install / upgrade even if the binary is already present. Default false:\n * an already-installed dependency short-circuits to `status: \"already\"`.\n */\n force?: boolean;\n /**\n * Override the target OS (defaults to {@link detectOS}). Mainly for tests; in\n * normal use the current OS is correct.\n */\n os?: OS;\n}\n\n/**\n * Install a dependency for the current OS (cross-OS dispatch). Resolves the\n * registry's per-OS {@link InstallMethod}, turns it into a concrete command, and\n * runs it via {@link runInstall}. System packages on Linux are installed via the\n * detected package manager, prefixed with `sudo` (with a clear message), since\n * apt/dnf/pacman need root; npm/brew/winget do not.\n *\n * Returns an {@link InstallOutcome} instead of throwing, so callers (Setup's\n * multiselect, Auth's install-on-demand) can handle each dependency's result\n * individually. Idempotent by default: an already-present binary is reported as\n * `\"already\"` without running anything (pass `force` to reinstall).\n */\nexport async function install(\n id: DependencyId,\n opts: InstallOptions = {},\n): Promise<InstallOutcome> {\n const spec = getDependency(id);\n const targetOS = opts.os ?? detectOS();\n\n if (!opts.force && (await which(spec.binary))) {\n log.debug(`${spec.label} already installed; skipping.`);\n return { id, status: \"already\", message: `${spec.label} is already installed.` };\n }\n\n const method = spec.install[targetOS];\n\n // No headless install path on this OS (e.g. Cursor on Linux).\n if (method.kind === \"none\") {\n log.warn(`${spec.label}: ${method.reason}`);\n return { id, status: \"unsupported\", message: method.reason };\n }\n\n // Resolve the concrete command (+ any extra hint) for this method.\n const resolved = await resolveInstallCommand(spec, method, targetOS);\n if (resolved.error) {\n log.warn(`${spec.label}: ${resolved.error}`);\n return { id, status: \"failed\", message: resolved.error };\n }\n if (resolved.note) log.info(resolved.note);\n\n log.step(`Installing ${spec.label}…`);\n try {\n await runInstall(resolved.command);\n log.success(`${spec.label} installed.`);\n return { id, status: \"installed\" };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n // runInstall already prefixes context; keep the message but don't rethrow.\n return { id, status: \"failed\", message: reason };\n }\n}\n\n/**\n * Install several dependencies in sequence, returning one {@link InstallOutcome}\n * per id (input order preserved). Sequential by design: package managers\n * (apt/brew/npm) serialize internally and parallel runs would interleave their\n * inherited stdio into unreadable output. A single dependency failing does NOT\n * stop the rest — its outcome simply has `status: \"failed\"`.\n */\nexport async function installMany(\n ids: readonly DependencyId[],\n opts: InstallOptions = {},\n): Promise<InstallOutcome[]> {\n const outcomes: InstallOutcome[] = [];\n for (const id of ids) {\n outcomes.push(await install(id, opts));\n }\n return outcomes;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Install-command resolution (internal) */\n/* -------------------------------------------------------------------------- */\n\n/** Resolved command for an install method, plus optional UX note / hard error. */\ninterface ResolvedInstall {\n /** The command to run (null => runInstall warns + no-ops; only for safety). */\n command: InstallCommand | null;\n /** A one-line, non-error hint to print before installing (e.g. a repo note). */\n note?: string;\n /** A hard error meaning \"cannot install here\" — caller reports `failed`. */\n error?: string;\n}\n\n/**\n * Turn an {@link InstallMethod} into a concrete {@link InstallCommand} for the\n * target OS. The npm/brew/winget cases are direct; the Linux `apt` case probes\n * for the actual package manager and wraps the install in `sudo` (managers need\n * root). Returns an `error` string when the OS has no usable manager.\n */\nasync function resolveInstallCommand(\n spec: DependencySpec,\n method: InstallMethod,\n targetOS: OS,\n): Promise<ResolvedInstall> {\n switch (method.kind) {\n case \"npm\":\n return { command: { cmd: \"npm\", args: [\"install\", \"-g\", method.package] } };\n\n case \"brew\":\n return {\n command: {\n cmd: \"brew\",\n args: method.cask\n ? [\"install\", \"--cask\", method.formula]\n : [\"install\", method.formula],\n },\n };\n\n case \"winget\":\n return {\n command: {\n cmd: \"winget\",\n args: [\"install\", \"--id\", method.id, \"-e\", \"--source\", \"winget\"],\n },\n };\n\n case \"apt\":\n return resolveLinuxInstall(spec, method, targetOS);\n\n case \"none\":\n // Handled by the caller before we get here; included for exhaustiveness.\n return { command: null, error: method.reason };\n }\n}\n\n/**\n * Resolve a Linux distro-package install: detect the package manager, build its\n * `install <pkg>` argv, and prefix `sudo` (we are almost never already root, and\n * apt/dnf/pacman require it). When no supported manager is found, returns a\n * helpful `error` (including any vendor-repo note for gh/glab).\n */\nasync function resolveLinuxInstall(\n spec: DependencySpec,\n method: Extract<InstallMethod, { kind: \"apt\" }>,\n targetOS: OS,\n): Promise<ResolvedInstall> {\n // Only meaningful on Linux. On a non-Linux OS this method shouldn't be picked,\n // but guard so a misconfiguration produces a clear message, not a bad spawn.\n if (targetOS !== \"linux\") {\n return {\n command: null,\n error: `${spec.label} has no automatic install on this OS.`,\n };\n }\n\n const pm = await detectLinuxPackageManager();\n const base = linuxInstallArgs(pm, method.package);\n if (pm === \"unknown\" || base === null) {\n const extra = method.repoNote ? ` ${method.repoNote}` : \"\";\n return {\n command: null,\n error:\n `No supported package manager found (looked for apt-get, dnf, yum, ` +\n `pacman, zypper). Install ${spec.label} manually.${extra}`,\n };\n }\n\n // Build the note shown before a (chatty, root) system install.\n const noteParts = [\n `Using ${base.cmd} (with sudo) to install ${spec.label}. ` +\n \"You may be prompted for your password.\",\n ];\n if (method.repoNote) noteParts.push(method.repoNote);\n\n // Prefix sudo unless we are already root. We don't add `-n` because the user\n // may legitimately need to type their password (stdio is inherited).\n const useSudo = !isRoot();\n const command: InstallCommand = useSudo\n ? { cmd: \"sudo\", args: [base.cmd, ...base.args] }\n : { cmd: base.cmd, args: base.args };\n\n // If sudo itself is missing and we're not root, surface that clearly.\n if (useSudo && !(await which(\"sudo\"))) {\n return {\n command: null,\n error:\n `Installing ${spec.label} needs root via the ${base.cmd} package manager, ` +\n `but \\`sudo\\` was not found. Re-run as root or install ${spec.label} manually.`,\n };\n }\n\n return { command, note: noteParts.join(\" \") };\n}\n\n/** True when the process is running as root (uid 0). Best-effort; false on win32. */\nfunction isRoot(): boolean {\n const getuid = (process as NodeJS.Process & { getuid?: () => number }).getuid;\n try {\n return typeof getuid === \"function\" && getuid.call(process) === 0;\n } catch {\n return false;\n }\n}\n","/**\n * All Claude-specific path knowledge for the arbella Claude adapter.\n *\n * This is the ONE place that encodes where things live under ~/.claude. Every\n * other Claude module (capture/restore/plugins/index) asks here rather than\n * re-deriving paths, so a layout change is a single-file edit.\n *\n * Cross-OS: the tool home is resolved via src/platform/os.ts (toolHomeDir),\n * never hardcoded. All sub-paths are built with node:path.join so separators are\n * correct on win32 as well. The `repoPath` prefix, by contrast, is a POSIX-only\n * string used inside the backup repo and is intentionally a literal.\n */\n\nimport path from \"node:path\";\n\nimport { toolHomeDir } from \"../../platform/os.js\";\n\n/** Absolute path to ~/.claude on this machine. */\nexport function home(): string {\n return toolHomeDir(\"claude\");\n}\n\n/**\n * Prefix (POSIX) for every CapturedFile.repoPath this adapter emits.\n * A file at `<home>/X` is stored at `claude/files/X` in the backup repo.\n */\nexport const REPO_PREFIX = \"claude/files\";\n\n/** Fully-resolved set of Claude paths, all absolute. */\nexport interface ClaudePaths {\n /** ~/.claude */\n home: string;\n /** ~/.claude/settings.json */\n settings: string;\n /** ~/.claude/settings.local.json */\n settingsLocal: string;\n /** ~/.claude/CLAUDE.md */\n claudeMd: string;\n /** ~/.claude/agents */\n agentsDir: string;\n /** ~/.claude/commands */\n commandsDir: string;\n /** ~/.claude/hooks */\n hooksDir: string;\n /** ~/.claude/statusline */\n statuslineDir: string;\n /** ~/.claude/skills */\n skillsDir: string;\n /** ~/.claude/plugins */\n pluginsDir: string;\n /** ~/.claude/plugins/installed_plugins.json */\n installedPlugins: string;\n /** ~/.claude/plugins/known_marketplaces.json */\n knownMarketplaces: string;\n}\n\n/**\n * Build the absolute Claude path set.\n * @param overrideHome optional home dir (tests point this at a fixture); when\n * omitted, the live ~/.claude is used.\n */\nexport function paths(overrideHome?: string): ClaudePaths {\n const base = overrideHome ?? home();\n return {\n home: base,\n settings: path.join(base, \"settings.json\"),\n settingsLocal: path.join(base, \"settings.local.json\"),\n claudeMd: path.join(base, \"CLAUDE.md\"),\n agentsDir: path.join(base, \"agents\"),\n commandsDir: path.join(base, \"commands\"),\n hooksDir: path.join(base, \"hooks\"),\n statuslineDir: path.join(base, \"statusline\"),\n skillsDir: path.join(base, \"skills\"),\n pluginsDir: path.join(base, \"plugins\"),\n installedPlugins: path.join(base, \"plugins\", \"installed_plugins.json\"),\n knownMarketplaces: path.join(base, \"plugins\", \"known_marketplaces.json\"),\n };\n}\n\n/**\n * Files/dirs to FREEZE (copy into the repo), relative to the tool home, in\n * capture order. Directories are walked recursively by capture.ts. Anything\n * matching the denylist is skipped during that walk.\n *\n * NOTE: CLAUDE.md is included here but capture may skip it when R9 shared\n * instructions are active (the backup command passes skipInstructions).\n */\nexport const FROZEN_PATHS: readonly string[] = [\n \"settings.json\",\n \"settings.local.json\",\n \"CLAUDE.md\",\n \"agents\",\n \"commands\",\n \"hooks\",\n \"statusline\",\n \"skills\",\n] as const;\n\n/** Basename of the Claude global instructions file (R9). */\nexport const INSTRUCTIONS_FILE = \"CLAUDE.md\";\n","/**\n * The static denylist of path globs/segments that must NEVER leave the machine.\n *\n * This is the FIRST line of defense: any captured path matching the denylist is\n * excluded *wholesale* (the file is never read into the repo at all). Per-tool\n * lists encode the verified reality of ~/.claude and ~/.codex. The matcher is\n * deliberately tiny and dependency-free — no external glob\n * library — so its behavior is deterministic and auditable.\n *\n * Matching model (POSIX paths, relative to a tool home):\n * - a pattern ending in \"/\" matches that directory AND everything beneath it,\n * whether the dir appears as a path segment or as the whole relative path;\n * - \"*\" is a SINGLE-segment wildcard (it never crosses \"/\"). A pattern such as\n * \"*.sqlite\" matches any segment ending in \".sqlite\";\n * - an exact (wildcard-free, slash-free) pattern matches if it equals ANY path\n * segment OR equals the basename. So \"auth.json\" matches \"auth.json\" and\n * \"nested/auth.json\"; \".DS_Store\" matches it at any depth.\n *\n * Pure module: no imports, no fs, no clock.\n */\n\nimport type { ToolId } from \"../../types.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Denylists */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Patterns that are dangerous/noisy for EVERY tool. Kept intentionally generic:\n * OS cruft, editor cruft, and SQLite databases (+ their WAL/SHM sidecars) which\n * are large, binary, and frequently hold session/telemetry data.\n */\nexport const COMMON_DENY: readonly string[] = [\n \".DS_Store\",\n \"Thumbs.db\",\n \"desktop.ini\",\n \".git/\",\n \"node_modules/\",\n \"*.sqlite\",\n \"*.sqlite-shm\",\n \"*.sqlite-wal\",\n \"*.db\",\n \"*.db-shm\",\n \"*.db-wal\",\n \"*.log\",\n \"*.lock\",\n \"*.pid\",\n \"*.swp\",\n \"*.tmp\",\n \".tmp/\",\n \"tmp/\",\n \"cache/\",\n \".cache/\",\n];\n\n/**\n * Claude (~/.claude) EXCLUDE list — verified against the live machine.\n * Covers credentials, the big sanitize-or-skip config, session/telemetry/state\n * dirs, history, caches, and machine-local bookkeeping dotfiles.\n */\nexport const CLAUDE_DENY: readonly string[] = [\n // Secrets / tokens (NEVER leave the machine).\n \".credentials.json\",\n \".claude.json\", // 177KB, mode 600: tokens + projects. Default exclude.\n \".caveman-active\",\n // Session / history / telemetry / state.\n \"history.jsonl\",\n \"projects/\",\n \"sessions/\",\n \"session-env/\",\n \"shell-snapshots/\",\n \"statsig/\",\n \"telemetry/\",\n \"cost-tally.json\",\n \"stats-cache.json\",\n \"mcp-needs-auth-cache.json\",\n // Caches & transient working data.\n \"paste-cache/\",\n \"file-history/\",\n \"downloads/\",\n \"ide/\",\n \"debug/\",\n \"chrome/\",\n // arbella-managed / restorable-from-manifest working dirs.\n \"backups/\",\n \"plans/\",\n \"checkpoints/\",\n \"tasks/\",\n \"teams/\",\n // Machine-local bookkeeping dotfiles (\".\" last-run markers etc.).\n \".last-cleanup\",\n \".last-update-result.json\",\n \".last-*\",\n];\n\n/**\n * Codex (~/.codex) EXCLUDE list — verified against the live machine.\n * config.toml itself is KEPT (it is sanitized/templated by configToml.ts); only\n * the wholesale-secret / session / cache / machine-id artifacts are excluded.\n */\nexport const CODEX_DENY: readonly string[] = [\n // Secrets / tokens.\n \"auth.json\",\n // SQLite databases (goals_*, logs_*, state_*) and their sidecars + dir.\n \"sqlite/\",\n // (the *.sqlite / -shm / -wal globs in COMMON_DENY catch the loose files)\n // Session / history / logs.\n \"history.jsonl\",\n \"session_index.jsonl\",\n \"external_agent_session_imports.json\",\n \"sessions/\",\n \"shell_snapshots/\",\n \"log/\",\n // Caches.\n \"models_cache.json\",\n \".tmp/\",\n \"tmp/\",\n // Machine identity / migration / version bookkeeping (machine-specific).\n \"installation_id\",\n \".codex-global-state.json\",\n \"version.json\",\n \".personality_migration\",\n];\n\n/**\n * Cursor (~/.cursor) EXCLUDE list. Cursor's global dir is minimal; exclude its\n * local caches/state so only mcp.json (and project rules) ever surface.\n */\nexport const CURSOR_DENY: readonly string[] = [\n \"logs/\",\n \"sessions/\",\n \"extensions/\",\n \"User/globalStorage/\",\n \"User/workspaceStorage/\",\n \"machineid\",\n \"storage.json\",\n];\n\n/* -------------------------------------------------------------------------- */\n/* Composition + matcher */\n/* -------------------------------------------------------------------------- */\n\n/** The per-tool denylist: COMMON_DENY plus the tool-specific patterns. */\nexport function denylistFor(tool: ToolId): string[] {\n switch (tool) {\n case \"claude\":\n return [...COMMON_DENY, ...CLAUDE_DENY];\n case \"codex\":\n return [...COMMON_DENY, ...CODEX_DENY];\n case \"cursor\":\n return [...COMMON_DENY, ...CURSOR_DENY];\n }\n}\n\n/**\n * Normalize a relative path to POSIX form and split into non-empty segments.\n * Tolerates Windows separators and leading \"./\" so adapters can pass whatever\n * the local fs handed them.\n */\nfunction toSegments(relativePath: string): string[] {\n return relativePath\n .replace(/\\\\/g, \"/\")\n .replace(/^\\.\\//, \"\")\n .replace(/^\\/+/, \"\")\n .split(\"/\")\n .filter((s) => s.length > 0 && s !== \".\");\n}\n\n/**\n * Match a single path segment against a single-segment pattern that may contain\n * \"*\" wildcards. \"*\" matches any run of characters WITHIN the segment (never\n * crosses a \"/\", which is guaranteed because we only ever feed it one segment).\n */\nfunction segmentMatches(pattern: string, segment: string): boolean {\n if (pattern === \"*\") return true;\n if (!pattern.includes(\"*\")) return pattern === segment;\n // Build an anchored regex from the glob, escaping regex metachars and turning\n // \"*\" into \".*\". Anchored so \"*.sqlite\" does not match \"sqlite.txt\".\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(`^${escaped}$`).test(segment);\n}\n\n/**\n * True if `relativePath` (POSIX, relative to a tool home) matches any pattern.\n *\n * Semantics per pattern:\n * - trailing \"/\" -> directory prefix match: the named directory itself OR\n * anything underneath it, at any depth.\n * - contains \"/\" -> path-suffix match: the pattern's segments must appear as\n * a contiguous tail of the path's segments (each compared\n * with single-segment wildcard rules). Lets callers write\n * e.g. \"User/globalStorage/\".\n * - single token -> segment OR basename match at any depth.\n */\nexport function matchesDeny(relativePath: string, patterns: string[]): boolean {\n const segs = toSegments(relativePath);\n if (segs.length === 0) return false;\n\n for (const raw of patterns) {\n if (!raw) continue;\n const isDir = raw.endsWith(\"/\");\n const pattern = isDir ? raw.slice(0, -1) : raw;\n const patSegs = toSegments(pattern);\n if (patSegs.length === 0) continue;\n\n if (isDir) {\n // Directory prefix: find the pattern's segment sequence anywhere such that\n // the path either IS that dir or continues beneath it.\n if (containsSequence(segs, patSegs, /* requireMore */ false)) return true;\n continue;\n }\n\n if (patSegs.length > 1) {\n // Multi-segment exact-ish pattern: must appear as a contiguous tail.\n if (matchesTail(segs, patSegs)) return true;\n continue;\n }\n\n // Single-segment pattern: match against ANY segment (covers basename too).\n const p = patSegs[0]!;\n if (segs.some((s) => segmentMatches(p, s))) return true;\n }\n\n return false;\n}\n\n/**\n * True if `patSegs` occurs as a contiguous run inside `segs`. When\n * `requireMore` is false, the run may end exactly at the path's end (the dir\n * itself) or be followed by more segments (something underneath it) — both\n * count, which is exactly the \"dir and everything under it\" rule.\n */\nfunction containsSequence(segs: string[], patSegs: string[], requireMore: boolean): boolean {\n const limit = segs.length - patSegs.length;\n for (let i = 0; i <= limit; i++) {\n let ok = true;\n for (let j = 0; j < patSegs.length; j++) {\n if (!segmentMatches(patSegs[j]!, segs[i + j]!)) {\n ok = false;\n break;\n }\n }\n if (!ok) continue;\n const consumedEnd = i + patSegs.length;\n if (!requireMore || consumedEnd < segs.length) return true;\n }\n return false;\n}\n\n/** True if `patSegs` matches the final segments of `segs` (a path suffix). */\nfunction matchesTail(segs: string[], patSegs: string[]): boolean {\n if (patSegs.length > segs.length) return false;\n const offset = segs.length - patSegs.length;\n for (let j = 0; j < patSegs.length; j++) {\n if (!segmentMatches(patSegs[j]!, segs[offset + j]!)) return false;\n }\n return true;\n}\n","/**\n * Claude plugin + marketplace parsing and restore-command construction.\n *\n * Pure data transforms (no fs, no clock): given the already-parsed JSON content\n * of `plugins/installed_plugins.json`, `plugins/known_marketplaces.json`, and\n * `settings.json`, produce the manifest entry shapes from\n * src/core/manifest/schema.ts. Also builds the argv arrays for the `claude`\n * CLI used on restore.\n *\n * Real on-disk shapes (verified on this machine):\n *\n * installed_plugins.json:\n * { \"version\": 2, \"plugins\": {\n * \"superpowers@claude-plugins-official\": [\n * { \"scope\":\"user\"|\"project\", \"version\":\"5.1.0\",\n * \"projectPath\":\"...\", \"installPath\":\"...\", ... } ] } }\n *\n * known_marketplaces.json:\n * { \"claude-plugins-official\": {\n * \"source\": { \"source\":\"github\", \"repo\":\"anthropics/claude-plugins-official\" },\n * \"installLocation\":\"...\", \"lastUpdated\":\"...\" } }\n *\n * settings.json.enabledPlugins:\n * { \"superpowers@claude-plugins-official\": true, ... }\n */\n\nimport type { PluginEntry, MarketplaceEntry } from \"../../types.js\";\n\n/** Narrow a value to a plain object record. */\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\n/**\n * Split a fully-qualified plugin id (\"name@marketplace\") into its parts.\n * If there is no \"@\", marketplace is undefined and the whole id is the name.\n */\nfunction splitPluginId(id: string): { name: string; marketplace?: string } {\n const at = id.lastIndexOf(\"@\");\n if (at <= 0) return { name: id };\n return { name: id.slice(0, at), marketplace: id.slice(at + 1) };\n}\n\n/**\n * Parse `installed_plugins.json` content into PluginEntry[].\n *\n * The value under each id key is an ARRAY of install records (a plugin can be\n * installed at user scope and/or for specific projects). We emit one\n * PluginEntry per record so user-scope and project-scope installs are both\n * represented; restore only auto-reinstalls scope:\"user\" entries.\n *\n * Malformed records are skipped (graceful absence rule). Returns [] for any\n * non-conforming top-level shape.\n */\nexport function parseInstalledPlugins(json: unknown): PluginEntry[] {\n if (!isRecord(json)) return [];\n const plugins = json.plugins;\n if (!isRecord(plugins)) return [];\n\n const out: PluginEntry[] = [];\n for (const [id, recordsRaw] of Object.entries(plugins)) {\n if (typeof id !== \"string\" || id.length === 0) continue;\n const records = Array.isArray(recordsRaw) ? recordsRaw : [recordsRaw];\n const { name, marketplace } = splitPluginId(id);\n\n for (const rec of records) {\n if (!isRecord(rec)) continue;\n const scope = rec.scope === \"project\" ? \"project\" : \"user\";\n const version =\n typeof rec.version === \"string\" ? rec.version : undefined;\n const projectPath =\n typeof rec.projectPath === \"string\" ? rec.projectPath : undefined;\n\n const entry: PluginEntry = {\n id,\n name,\n enabled: true,\n scope,\n };\n if (marketplace !== undefined) entry.marketplace = marketplace;\n if (version !== undefined) entry.version = version;\n if (projectPath !== undefined) entry.projectPath = projectPath;\n out.push(entry);\n }\n }\n return out;\n}\n\n/**\n * Parse `known_marketplaces.json` content into MarketplaceEntry[].\n *\n * Maps each top-level key to { id, sourceType, source }. The nested `source`\n * object carries `source` (the kind: \"github\" | \"git\" | \"local\") and a locator\n * (`repo` for github => \"owner/repo\", or `url`/`path` otherwise). Unknown kinds\n * are coerced to the closest supported sourceType; entries without any usable\n * locator are skipped.\n */\nexport function parseKnownMarketplaces(json: unknown): MarketplaceEntry[] {\n if (!isRecord(json)) return [];\n\n const out: MarketplaceEntry[] = [];\n for (const [id, valueRaw] of Object.entries(json)) {\n if (typeof id !== \"string\" || id.length === 0) continue;\n if (!isRecord(valueRaw)) continue;\n\n const src = valueRaw.source;\n if (!isRecord(src)) continue;\n\n const kindRaw = typeof src.source === \"string\" ? src.source : \"\";\n let sourceType: MarketplaceEntry[\"sourceType\"];\n if (kindRaw === \"github\") sourceType = \"github\";\n else if (kindRaw === \"local\") sourceType = \"local\";\n else sourceType = \"git\";\n\n // Locator: github => repo (\"owner/repo\"); git => url; local => path.\n const locator =\n (typeof src.repo === \"string\" && src.repo) ||\n (typeof src.url === \"string\" && src.url) ||\n (typeof src.path === \"string\" && src.path) ||\n \"\";\n if (!locator) continue;\n\n out.push({ id, sourceType, source: locator });\n }\n return out;\n}\n\n/**\n * Extract the `enabledPlugins` map from a parsed settings.json object.\n * Only boolean-valued entries are kept. Returns {} when absent/malformed.\n */\nexport function extractEnabledPlugins(settings: unknown): Record<string, boolean> {\n if (!isRecord(settings)) return {};\n const enabled = settings.enabledPlugins;\n if (!isRecord(enabled)) return {};\n\n const out: Record<string, boolean> = {};\n for (const [id, val] of Object.entries(enabled)) {\n if (typeof val === \"boolean\") out[id] = val;\n }\n return out;\n}\n\n/**\n * argv for `claude plugin marketplace add <source>`.\n *\n * The `claude plugin marketplace add` command accepts a URL, a local path, or a\n * GitHub \"owner/repo\" shorthand. We pass the stored locator verbatim:\n * - github => \"owner/repo\"\n * - git => clone URL\n * - local => (templated) path; restore rehydrates it before use\n */\nexport function marketplaceAddArgs(m: MarketplaceEntry): string[] {\n return [\"plugin\", \"marketplace\", \"add\", m.source];\n}\n\n/**\n * argv for `claude plugin install <plugin> --scope user`.\n *\n * The plugin id is the fully-qualified \"name@marketplace\" form, which is exactly\n * what `claude plugin install` understands (\"use plugin@marketplace for a\n * specific marketplace\"). Scope is forced to user — only user-scope plugins are\n * auto-reinstalled on restore.\n */\nexport function pluginInstallArgs(p: PluginEntry): string[] {\n return [\"plugin\", \"install\", p.id, \"--scope\", \"user\"];\n}\n\n/** True if a plugin entry is a user-scope install (the only kind we reinstall). */\nexport function isUserScope(p: PluginEntry): boolean {\n return p.scope === \"user\";\n}\n","/**\n * Capture for the Claude adapter: turn the live ~/.claude tree into a\n * CaptureResult (frozen files + symlinks + reinstall manifest + secret refs).\n *\n * Strategy:\n * - Walk FROZEN_PATHS. For each existing path, recurse into files.\n * - Compute `rel` (POSIX, relative to tool home). Skip if it matches the\n * Claude denylist (secrets / caches / session noise never leave the box).\n * - Read each file; if textual, sanitize secret VALUES then template machine\n * paths to {{HOME}}/{{USER}}/... placeholders; emit a CapturedFile with\n * repoPath = \"claude/files/<rel>\". Preserve executable mode for hooks/\n * statusline/commands scripts.\n * - skills/ is MIXED: a relative symlink \"-> ../../.agents/skills/<name>\" is a\n * skills.sh install => emit a CapturedSymlink + a SkillEntry(source:\"skills.sh\",\n * symlinked:true, installCommand:\"npx skills add <name>\"). A real dir is\n * hand-made => freeze its files + a SkillEntry(source:\"frozen\").\n * - Parse plugins/installed_plugins.json + plugins/known_marketplaces.json +\n * settings.json.enabledPlugins into the manifest.\n * - Collect npm globals via the shared platform helper.\n * - Any denylisted-but-present secret file (.credentials.json, .claude.json)\n * is recorded as a SecretRef(kind:\"file\") so the user is reminded to\n * re-supply it; its CONTENTS are never read or stored.\n *\n * All fs/sanitizer/templater work goes through the injected CaptureContext, so\n * this module is unit-testable against a fixture dir. No clock, no direct\n * node:fs. dryRun changes nothing here (we never write during capture; the\n * backup command owns writing), but we still honor it by not touching the\n * network/npm when set, to keep dry runs fast and side-effect free.\n */\n\nimport path from \"node:path\";\n\nimport type { CaptureContext } from \"../adapter.interface.js\";\nimport type {\n CaptureResult,\n CapturedFile,\n CapturedSymlink,\n SecretRef,\n SkillEntry,\n ToolManifest,\n} from \"../../types.js\";\n\nimport {\n denylistFor,\n matchesDeny,\n} from \"../../core/sanitizer/denylist.js\";\nimport { listNpmGlobals } from \"../../platform/install.js\";\n\nimport { REPO_PREFIX, FROZEN_PATHS, INSTRUCTIONS_FILE, paths } from \"./paths.js\";\nimport {\n parseInstalledPlugins,\n parseKnownMarketplaces,\n extractEnabledPlugins,\n} from \"./plugins.js\";\n\n/** Files known to hold whole-file secrets; recorded (never read) if present. */\nconst SECRET_FILES: ReadonlyArray<{ rel: string; key: string; description: string }> = [\n {\n rel: \".credentials.json\",\n key: \".credentials.json\",\n description: \"Claude OAuth / API credentials — re-login after restore.\",\n },\n {\n rel: \".claude.json\",\n key: \".claude.json\",\n description: \"Claude global state (auth tokens + project history) — excluded.\",\n },\n];\n\n/** Heuristic: treat a file as binary if its bytes contain a NUL. */\nfunction looksBinary(buf: Buffer): boolean {\n const n = Math.min(buf.length, 8000);\n for (let i = 0; i < n; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n/** Build the repo path for a tool-home-relative POSIX path. */\nfunction repoPathFor(rel: string): string {\n return `${REPO_PREFIX}/${rel}`;\n}\n\n/** Convert an absolute child path under `home` into a POSIX rel path. */\nfunction toRel(home: string, abs: string): string {\n return path.relative(home, abs).split(path.sep).join(\"/\");\n}\n\n/**\n * Recursively collect frozen files (and skill symlinks) under an absolute path.\n * Mutates the provided accumulators. Denylisted entries are skipped.\n */\nasync function walk(\n ctx: CaptureContext,\n home: string,\n abs: string,\n deny: string[],\n files: CapturedFile[],\n symlinks: CapturedSymlink[],\n warnings: string[],\n): Promise<void> {\n const kind = await ctx.fs.statKind(abs);\n if (kind === \"missing\") return;\n\n const rel = toRel(home, abs);\n if (rel && matchesDeny(rel, deny)) {\n ctx.log.debug(`claude: skip (denylist) ${rel}`);\n return;\n }\n\n if (kind === \"symlink\") {\n // A symlink anywhere in the frozen tree is preserved as a link. (Skill\n // symlinks are special-cased by the caller before recursing into skills/.)\n const target = await ctx.fs.readLink(abs);\n symlinks.push({ repoPath: repoPathFor(rel), target });\n ctx.log.debug(`claude: symlink ${rel} -> ${target}`);\n return;\n }\n\n if (kind === \"dir\") {\n const entries = await ctx.fs.list(abs);\n entries.sort();\n for (const name of entries) {\n await walk(ctx, home, path.join(abs, name), deny, files, symlinks, warnings);\n }\n return;\n }\n\n // kind === \"file\"\n await captureFile(ctx, home, abs, rel, files, warnings);\n}\n\n/** Read + sanitize + template a single file and push a CapturedFile. */\nasync function captureFile(\n ctx: CaptureContext,\n home: string,\n abs: string,\n rel: string,\n files: CapturedFile[],\n warnings: string[],\n): Promise<void> {\n let mode: number | undefined;\n try {\n // Preserve executable bit for scripts (hooks/statusline/commands *.sh|*.py).\n const { promises: fsp } = await import(\"node:fs\");\n const st = await fsp.stat(abs);\n mode = st.mode & 0o777;\n } catch {\n mode = undefined;\n }\n\n let bytes: Buffer;\n try {\n bytes = await ctx.fs.readBytes(abs);\n } catch (err) {\n warnings.push(`claude: could not read ${rel}: ${(err as Error).message}`);\n return;\n }\n\n if (looksBinary(bytes)) {\n const file: CapturedFile = {\n repoPath: repoPathFor(rel),\n content: bytes.toString(\"base64\"),\n binary: true,\n };\n if (mode !== undefined) file.mode = mode;\n files.push(file);\n return;\n }\n\n // Textual: sanitize secret values (unless includeSecrets opts INTO carrying\n // them), then template machine paths.\n //\n // sanitizeFile routes JSON configs (settings.json, settings.local.json, ...)\n // through the STRUCTURAL key-name-aware sanitizer so an opaque secret under a\n // secret KEY NAME (e.g. ANTHROPIC_AUTH_TOKEN feeding an MCP server /\n // apiKeyHelper) is redacted even when its value matches no known token shape;\n // non-JSON files (hooks/*.sh|*.py, CLAUDE.md, ...) fall back to the text pass.\n //\n // ctx.includeSecrets (opt-in, default OFF) is the documented switch that carries\n // inline secret VALUES into the PRIVATE repo: when true we SKIP value redaction\n // so the real values are stored (the \"risk\" the init prompt/README warn about).\n // Whole-file credential stores (.credentials.json/.claude.json) remain excluded\n // by the denylist regardless — only in-place values in shareable configs are\n // carried, never the wholesale token files.\n const raw = bytes.toString(\"utf8\");\n const content = ctx.includeSecrets ? raw : ctx.sanitizer.sanitizeFile(raw, \"claude\", rel).content;\n const templated = ctx.templater.toTemplate(content, ctx.vars);\n\n const file: CapturedFile = {\n repoPath: repoPathFor(rel),\n content: templated,\n };\n if (mode !== undefined) file.mode = mode;\n files.push(file);\n}\n\n/**\n * Handle the skills/ directory specially: classify each entry as a skills.sh\n * symlink (reinstallable) or a frozen hand-made dir.\n */\nasync function captureSkills(\n ctx: CaptureContext,\n home: string,\n skillsDir: string,\n deny: string[],\n files: CapturedFile[],\n symlinks: CapturedSymlink[],\n skills: SkillEntry[],\n warnings: string[],\n): Promise<void> {\n if ((await ctx.fs.statKind(skillsDir)) !== \"dir\") return;\n\n const entries = await ctx.fs.list(skillsDir);\n entries.sort();\n\n for (const name of entries) {\n const abs = path.join(skillsDir, name);\n const rel = toRel(home, abs);\n if (matchesDeny(rel, deny)) {\n ctx.log.debug(`claude: skip skill (denylist) ${rel}`);\n continue;\n }\n\n const kind = await ctx.fs.statKind(abs);\n if (kind === \"symlink\") {\n // skills.sh: ~/.claude/skills/<name> -> ../../.agents/skills/<name>\n const target = await ctx.fs.readLink(abs);\n symlinks.push({ repoPath: repoPathFor(rel), target });\n skills.push({\n name,\n source: \"skills.sh\",\n symlinked: true,\n installCommand: `npx skills add ${name}`,\n });\n ctx.log.debug(`claude: skill (skills.sh) ${name} -> ${target}`);\n } else if (kind === \"dir\") {\n // Hand-made skill: freeze its files AND record it as frozen.\n await walk(ctx, home, abs, deny, files, symlinks, warnings);\n skills.push({ name, source: \"frozen\", symlinked: false });\n ctx.log.debug(`claude: skill (frozen) ${name}`);\n } else if (kind === \"file\") {\n // A loose file directly under skills/ (rare) — freeze it.\n await captureFile(ctx, home, abs, rel, files, warnings);\n }\n }\n}\n\n/** Read + parse a JSON file via the fs service; returns undefined if absent/bad. */\nasync function readJson(\n ctx: CaptureContext,\n abs: string,\n warnings: string[],\n label: string,\n): Promise<unknown | undefined> {\n if ((await ctx.fs.statKind(abs)) !== \"file\") return undefined;\n try {\n return JSON.parse(await ctx.fs.read(abs));\n } catch (err) {\n warnings.push(`claude: could not parse ${label}: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * Capture the Claude setup.\n *\n * @param ctx injected services + tool home + flags.\n * @param opts.skipInstructions when true (R9 shared instructions active), do NOT\n * emit CLAUDE.md as a frozen file — the backup command stores it once\n * under shared/instructions.md instead.\n */\nexport async function capture(\n ctx: CaptureContext,\n opts?: { skipInstructions?: boolean },\n): Promise<CaptureResult> {\n const skipInstructions = opts?.skipInstructions ?? false;\n const home = ctx.toolHome;\n const p = paths(home);\n const deny = denylistFor(\"claude\");\n\n const files: CapturedFile[] = [];\n const symlinks: CapturedSymlink[] = [];\n const skills: SkillEntry[] = [];\n const secrets: SecretRef[] = [];\n const warnings: string[] = [];\n\n // ----- Frozen files (recurse FROZEN_PATHS) -----\n for (const top of FROZEN_PATHS) {\n if (skipInstructions && top === INSTRUCTIONS_FILE) {\n ctx.log.debug(\"claude: skipping CLAUDE.md (shared instructions)\");\n continue;\n }\n if (top === \"skills\") {\n await captureSkills(ctx, home, p.skillsDir, deny, files, symlinks, skills, warnings);\n continue;\n }\n await walk(ctx, home, path.join(home, top), deny, files, symlinks, warnings);\n }\n\n // ----- Manifest: plugins, marketplaces, enabledPlugins -----\n const installedJson = await readJson(ctx, p.installedPlugins, warnings, \"installed_plugins.json\");\n const marketplacesJson = await readJson(ctx, p.knownMarketplaces, warnings, \"known_marketplaces.json\");\n const settingsJson = await readJson(ctx, p.settings, warnings, \"settings.json\");\n\n const plugins = parseInstalledPlugins(installedJson);\n const marketplaces = parseKnownMarketplaces(marketplacesJson);\n const enabledPlugins = extractEnabledPlugins(settingsJson);\n\n // Mirror the enabled state onto each plugin entry for convenience.\n for (const entry of plugins) {\n if (entry.id in enabledPlugins) {\n entry.enabled = enabledPlugins[entry.id]!;\n }\n }\n\n // ----- npm globals (shared source of truth) -----\n let npmGlobals: ToolManifest[\"npmGlobals\"] = [];\n try {\n npmGlobals = await listNpmGlobals();\n } catch (err) {\n warnings.push(`claude: npm globals unavailable: ${(err as Error).message}`);\n }\n\n // ----- Secret files: record presence, never read contents -----\n for (const sf of SECRET_FILES) {\n const abs = path.join(home, sf.rel);\n if ((await ctx.fs.exists(abs))) {\n secrets.push({\n tool: \"claude\",\n source: sf.rel,\n key: sf.key,\n description: sf.description,\n kind: \"file\",\n });\n }\n }\n\n const manifest: ToolManifest = {\n tool: \"claude\",\n plugins,\n marketplaces,\n skills,\n npmGlobals,\n enabledPlugins,\n };\n\n ctx.log.debug(\n `claude: captured ${files.length} files, ${symlinks.length} symlinks, ` +\n `${plugins.length} plugins, ${marketplaces.length} marketplaces, ` +\n `${skills.length} skills, ${npmGlobals.length} npm globals`,\n );\n\n return { tool: \"claude\", files, symlinks, manifest, secrets, warnings };\n}\n\nexport default capture;\n","/**\n * Restore for the Claude adapter: materialize the frozen ~/.claude subtree onto\n * the target machine, recreate skill symlinks, reinstall marketplaces + plugins\n * via the `claude` CLI, re-enable plugins by merging enabledPlugins into the\n * restored settings.json, and reinstall skills.sh skills via `npx skills add`.\n *\n * Placement:\n * - For each CapturedFile, strip the \"claude/files/\" prefix from repoPath and\n * join onto ctx.toolHome. Run templater.fromTemplate FIRST so {{HOME}}/\n * {{USER}}/... become this machine's real values, then write (restoring the\n * POSIX mode for executables). Binary files are base64-decoded.\n * - Recreate each CapturedSymlink with its verbatim (relative) target.\n * - `claude plugin marketplace add <source>` for every marketplace, then\n * `claude plugin install <id> --scope user` for every USER-scope plugin.\n * - Merge manifest.enabledPlugins into the freshly-written settings.json (the\n * file Claude actually reads) so disabled plugins stay disabled.\n * - `npx skills add <name>` for every skills.sh skill (the symlink itself is\n * recreated above; this repopulates ~/.agents/skills/<name>).\n *\n * sourceOfTruth (R12): \"repo\" => overwrite existing local files; \"local\" =>\n * never clobber a file that already exists locally (skip + debug). All install\n * steps are guarded by `which(\"claude\")` / the npm helper and degrade to a\n * warning when the CLI is missing, rather than throwing.\n *\n * planActions() returns the same set of intended actions WITHOUT executing, for\n * the restore command's --dry-run output.\n */\n\nimport path from \"node:path\";\n\nimport { execa } from \"execa\";\n\nimport type { RestoreContext, RestoreData } from \"../adapter.interface.js\";\nimport type { RestoreAction, CapturedFile } from \"../../types.js\";\n\nimport { cliBinaryName } from \"../../platform/os.js\";\nimport { which } from \"../../platform/install.js\";\n\nimport { REPO_PREFIX } from \"./paths.js\";\nimport {\n marketplaceAddArgs,\n pluginInstallArgs,\n isUserScope,\n} from \"./plugins.js\";\n\n/** Strip the \"claude/files/\" repo prefix; returns the tool-home-relative POSIX path. */\nfunction stripPrefix(repoPath: string): string {\n const prefix = `${REPO_PREFIX}/`;\n return repoPath.startsWith(prefix) ? repoPath.slice(prefix.length) : repoPath;\n}\n\n/** Absolute target path on this machine for a captured file/symlink. */\nfunction targetFor(toolHome: string, repoPath: string): string {\n const rel = stripPrefix(repoPath);\n return path.join(toolHome, ...rel.split(\"/\"));\n}\n\n/** True if `claude` CLI is on PATH. */\nasync function claudeAvailable(): Promise<boolean> {\n return which(cliBinaryName(\"claude\"));\n}\n\n/**\n * Write one CapturedFile to disk, honoring sourceOfTruth + mode + binary flag.\n * Returns true if written, false if skipped.\n */\nasync function writeOne(\n ctx: RestoreContext,\n file: CapturedFile,\n): Promise<boolean> {\n const dest = targetFor(ctx.toolHome, file.repoPath);\n\n if (ctx.sourceOfTruth === \"local\" && (await ctx.fs.exists(dest))) {\n ctx.log.debug(`claude: keep local (sourceOfTruth=local) ${dest}`);\n return false;\n }\n\n if (file.binary) {\n await ctx.fs.writeBytes(dest, Buffer.from(file.content, \"base64\"), file.mode);\n } else {\n const hydrated = ctx.templater.fromTemplate(file.content, ctx.vars);\n await ctx.fs.write(dest, hydrated, file.mode);\n }\n return true;\n}\n\n/**\n * Merge manifest.enabledPlugins into the restored settings.json on disk. The\n * frozen settings.json was just written; we re-read it, overlay the enabled map\n * (authoritative re-enable source), and write it back. No-op if settings.json\n * is absent or unparseable.\n */\nasync function mergeEnabledPlugins(\n ctx: RestoreContext,\n enabled: Record<string, boolean>,\n): Promise<void> {\n if (Object.keys(enabled).length === 0) return;\n const settingsPath = path.join(ctx.toolHome, \"settings.json\");\n if (!(await ctx.fs.exists(settingsPath))) return;\n\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(await ctx.fs.read(settingsPath)) as Record<string, unknown>;\n } catch (err) {\n ctx.log.warn(`claude: could not merge enabledPlugins: ${(err as Error).message}`);\n return;\n }\n\n const existing =\n typeof obj.enabledPlugins === \"object\" && obj.enabledPlugins !== null\n ? (obj.enabledPlugins as Record<string, boolean>)\n : {};\n obj.enabledPlugins = { ...existing, ...enabled };\n\n await ctx.fs.write(settingsPath, JSON.stringify(obj, null, 2) + \"\\n\");\n ctx.log.debug(`claude: merged ${Object.keys(enabled).length} enabledPlugins entries`);\n}\n\n/**\n * Build the list of restore actions WITHOUT executing them (for --dry-run).\n */\nexport async function planActions(\n ctx: RestoreContext,\n data: RestoreData,\n): Promise<RestoreAction[]> {\n const actions: RestoreAction[] = [];\n\n for (const file of data.files) {\n const dest = targetFor(ctx.toolHome, file.repoPath);\n const overwrites = await ctx.fs.exists(dest);\n if (ctx.sourceOfTruth === \"local\" && overwrites) continue;\n actions.push({\n type: \"write-file\",\n tool: \"claude\",\n targetPath: dest,\n description: `Write ${stripPrefix(file.repoPath)}`,\n overwrites,\n });\n }\n\n for (const link of data.symlinks) {\n const dest = targetFor(ctx.toolHome, link.repoPath);\n const overwrites = (await ctx.fs.statKind(dest)) !== \"missing\";\n actions.push({\n type: \"write-symlink\",\n tool: \"claude\",\n targetPath: dest,\n description: `Symlink ${stripPrefix(link.repoPath)} -> ${link.target}`,\n overwrites,\n });\n }\n\n for (const m of data.manifest.marketplaces) {\n actions.push({\n type: \"add-marketplace\",\n tool: \"claude\",\n description: `claude plugin marketplace add ${m.source}`,\n });\n }\n\n for (const plugin of data.manifest.plugins) {\n if (!isUserScope(plugin)) continue;\n actions.push({\n type: \"install-plugin\",\n tool: \"claude\",\n description: `claude plugin install ${plugin.id} --scope user`,\n });\n }\n\n const enabledIds = Object.keys(data.manifest.enabledPlugins);\n if (enabledIds.length > 0) {\n actions.push({\n type: \"enable-plugin\",\n tool: \"claude\",\n targetPath: path.join(ctx.toolHome, \"settings.json\"),\n description: `Re-enable ${enabledIds.length} plugin(s) in settings.json`,\n });\n }\n\n for (const skill of data.manifest.skills) {\n if (skill.source !== \"skills.sh\") continue;\n actions.push({\n type: \"install-skill\",\n tool: \"claude\",\n description: skill.installCommand ?? `npx skills add ${skill.name}`,\n });\n }\n\n return actions;\n}\n\n/**\n * Execute the restore. Honors ctx.dryRun (delegates to planActions + logs,\n * performs no mutations).\n */\nexport async function restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n if (ctx.dryRun) {\n const actions = await planActions(ctx, data);\n for (const a of actions) ctx.log.step(a.description);\n return;\n }\n\n // ----- 1. Frozen files -----\n let written = 0;\n for (const file of data.files) {\n if (await writeOne(ctx, file)) written++;\n }\n ctx.log.debug(`claude: wrote ${written}/${data.files.length} files`);\n\n // ----- 2. Symlinks -----\n for (const link of data.symlinks) {\n const dest = targetFor(ctx.toolHome, link.repoPath);\n if (ctx.sourceOfTruth === \"local\" && (await ctx.fs.statKind(dest)) !== \"missing\") {\n ctx.log.debug(`claude: keep local symlink ${dest}`);\n continue;\n }\n try {\n await ctx.fs.symlink(link.target, dest);\n } catch (err) {\n ctx.log.warn(`claude: could not create symlink ${dest}: ${(err as Error).message}`);\n }\n }\n\n // ----- 3. Marketplaces + plugins via the claude CLI -----\n const hasClaude = await claudeAvailable();\n if (!hasClaude) {\n ctx.log.warn(\n \"claude CLI not found on PATH; skipping marketplace/plugin reinstall. \" +\n \"Install Claude Code and re-run `arbella pull`.\",\n );\n } else {\n for (const m of data.manifest.marketplaces) {\n const args = marketplaceAddArgs(m);\n try {\n await execa(\"claude\", args, { reject: false });\n ctx.log.step(`marketplace add ${m.source}`);\n } catch (err) {\n ctx.log.warn(`claude: marketplace add ${m.source} failed: ${(err as Error).message}`);\n }\n }\n\n for (const plugin of data.manifest.plugins) {\n if (!isUserScope(plugin)) continue;\n const args = pluginInstallArgs(plugin);\n try {\n await execa(\"claude\", args, { reject: false });\n ctx.log.step(`plugin install ${plugin.id}`);\n } catch (err) {\n ctx.log.warn(`claude: plugin install ${plugin.id} failed: ${(err as Error).message}`);\n }\n }\n }\n\n // ----- 4. Re-enable plugins (authoritative: settings.enabledPlugins) -----\n await mergeEnabledPlugins(ctx, data.manifest.enabledPlugins);\n\n // ----- 5. skills.sh skills (repopulate ~/.agents/skills) -----\n for (const skill of data.manifest.skills) {\n if (skill.source !== \"skills.sh\") continue;\n try {\n await execa(\"npx\", [\"--yes\", \"skills\", \"add\", skill.name], { reject: false });\n ctx.log.step(`skills add ${skill.name}`);\n } catch (err) {\n ctx.log.warn(`claude: skills add ${skill.name} failed: ${(err as Error).message}`);\n }\n }\n\n // NOTE: npm globals are NOT installed here. Both adapters capture the SAME full\n // machine list (listNpmGlobals), so the restore command owns a single shared\n // npm-globals pass that dedupes across every restored tool and installs each\n // package exactly once (avoiding the double-install when claude + codex are both\n // restored, and ensuring a codex-only restore still installs them). See\n // src/commands/restore.ts (installSharedNpmGlobals).\n}\n\nexport default restore;\n","/**\n * The Adapter object for Claude Code.\n *\n * Thin wiring layer: detection + CLI presence/install + delegation to the\n * capture/restore modules. All tool-specific path/plugin knowledge lives in the\n * sibling modules (paths.ts, plugins.ts, capture.ts, restore.ts).\n *\n * The R9 \"skipInstructions\" capture variant is NOT exposed on the Adapter\n * interface (capture(ctx) takes no opts there). The backup command imports the\n * capture module's `capture(ctx, { skipInstructions })` directly when shared\n * instructions are active; the Adapter.capture below is the default path.\n */\n\nimport type { Adapter, CaptureContext, RestoreContext, RestoreData } from \"../adapter.interface.js\";\nimport type { CaptureResult, OS } from \"../../types.js\";\n\nimport { fs } from \"../../utils/fs.js\";\nimport { cliBinaryName, installCommandFor } from \"../../platform/os.js\";\nimport { which, runInstall } from \"../../platform/install.js\";\n\nimport { paths } from \"./paths.js\";\nimport { capture as captureClaude } from \"./capture.js\";\nimport { restore as restoreClaude } from \"./restore.js\";\n\nexport const claudeAdapter: Adapter = {\n id: \"claude\",\n displayName: \"Claude Code\",\n\n /**\n * Present if ~/.claude exists AND it has recognizable content: a settings\n * file, a CLAUDE.md, or at least one agent. Graceful: returns false (never\n * throws) when the home dir is absent.\n */\n async detect(): Promise<boolean> {\n const p = paths();\n if (!(await fs.exists(p.home))) return false;\n if (await fs.exists(p.settings)) return true;\n if (await fs.exists(p.settingsLocal)) return true;\n if (await fs.exists(p.claudeMd)) return true;\n const agents = await fs.list(p.agentsDir);\n return agents.length > 0;\n },\n\n /** True if the `claude` CLI resolves on PATH. */\n async isCliInstalled(): Promise<boolean> {\n return which(cliBinaryName(\"claude\"));\n },\n\n /** Install Claude Code's CLI for the given OS (npm i -g @anthropic-ai/claude-code). */\n async installCli(os: OS): Promise<void> {\n await runInstall(installCommandFor(\"claude\", os));\n },\n\n /** Capture ~/.claude into a CaptureResult. */\n async capture(ctx: CaptureContext): Promise<CaptureResult> {\n return captureClaude(ctx);\n },\n\n /** Restore ~/.claude from repo data. */\n async restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n return restoreClaude(ctx, data);\n },\n};\n\nexport default claudeAdapter;\n","/**\n * All Codex-specific path knowledge for the arbella Codex adapter.\n *\n * This is the ONE place that encodes where things live under ~/.codex. Every\n * other Codex module (capture/restore/configToml/index) asks here rather than\n * re-deriving paths, so a layout change is a single-file edit.\n *\n * Cross-OS: the tool home is resolved via src/platform/os.ts (toolHomeDir),\n * never hardcoded. All sub-paths are built with node:path.join so separators are\n * correct on win32 as well. The `repoPath` prefix, by contrast, is a POSIX-only\n * string used inside the backup repo and is intentionally a literal.\n *\n * Verified against the live ~/.codex: config.toml, AGENTS.md,\n * agents/*.toml, hooks/ (+ hooks.json), rules/, prompts/, skills/, vendor_imports/,\n * and memories/ (gated by includeMemories).\n */\n\nimport path from \"node:path\";\n\nimport { toolHomeDir } from \"../../platform/os.js\";\n\n/** Absolute path to ~/.codex on this machine. */\nexport function home(): string {\n return toolHomeDir(\"codex\");\n}\n\n/**\n * Prefix (POSIX) for every CapturedFile.repoPath this adapter emits.\n * A file at `<home>/X` is stored at `codex/files/X` in the backup repo.\n */\nexport const REPO_PREFIX = \"codex/files\";\n\n/** Fully-resolved set of Codex paths, all absolute. */\nexport interface CodexPaths {\n /** ~/.codex */\n home: string;\n /** ~/.codex/config.toml */\n configToml: string;\n /** ~/.codex/AGENTS.md */\n agentsMd: string;\n /** ~/.codex/agents (per-agent *.toml definitions) */\n agentsDir: string;\n /** ~/.codex/hooks (shell + python hook scripts, executable) */\n hooksDir: string;\n /** ~/.codex/hooks.json (hook registration; contains absolute paths) */\n hooksJson: string;\n /** ~/.codex/rules */\n rulesDir: string;\n /** ~/.codex/prompts */\n promptsDir: string;\n /** ~/.codex/skills */\n skillsDir: string;\n /** ~/.codex/memories (only frozen when includeMemories) */\n memoriesDir: string;\n /** ~/.codex/vendor_imports */\n vendorImportsDir: string;\n}\n\n/**\n * Build the absolute Codex path set.\n * @param overrideHome optional home dir (tests point this at a fixture); when\n * omitted, the live ~/.codex is used.\n */\nexport function paths(overrideHome?: string): CodexPaths {\n const base = overrideHome ?? home();\n return {\n home: base,\n configToml: path.join(base, \"config.toml\"),\n agentsMd: path.join(base, \"AGENTS.md\"),\n agentsDir: path.join(base, \"agents\"),\n hooksDir: path.join(base, \"hooks\"),\n hooksJson: path.join(base, \"hooks.json\"),\n rulesDir: path.join(base, \"rules\"),\n promptsDir: path.join(base, \"prompts\"),\n skillsDir: path.join(base, \"skills\"),\n memoriesDir: path.join(base, \"memories\"),\n vendorImportsDir: path.join(base, \"vendor_imports\"),\n };\n}\n\n/**\n * Files/dirs to FREEZE (copy into the repo), relative to the tool home, in\n * capture order. Directories are walked recursively by capture.ts. Anything\n * matching the denylist is skipped during that walk.\n *\n * NOTE: config.toml is included here but capture routes it through\n * configToml.ts (sanitize + template + strip) rather than the generic file\n * walk. AGENTS.md is included but capture may skip it when R9 shared\n * instructions are active (the backup command passes skipInstructions).\n * \"memories\" is appended dynamically by capture.ts only when includeMemories.\n */\nexport const FROZEN_PATHS: readonly string[] = [\n \"config.toml\",\n \"AGENTS.md\",\n \"agents\",\n \"hooks\",\n \"hooks.json\",\n \"rules\",\n \"prompts\",\n \"skills\",\n \"vendor_imports\",\n] as const;\n\n/** Basename of the Codex global instructions file (R9). */\nexport const INSTRUCTIONS_FILE = \"AGENTS.md\";\n\n/** Basename of the Codex config file routed through configToml.ts. */\nexport const CONFIG_FILE = \"config.toml\";\n\n/** Relative dir name for memories (gated by includeMemories). */\nexport const MEMORIES_DIR = \"memories\";\n","/**\n * Secret VALUE detection: regexes for high-entropy / well-known credential\n * shapes, plus the set of key NAMES whose values are always secret regardless of\n * shape. Used by the SanitizerService to redact values that slip into otherwise\n * shareable files (settings.json, config.toml, mcp.json, ...).\n *\n * Pure module: no imports, no fs, no clock. The redaction marker is a constant\n * so restore/round-trip code can recognize it and so it is obvious in diffs.\n */\n\n/** The placeholder written in place of a redacted secret value. */\nexport const REDACTED = \"{{REDACTED}}\";\n\n/** A named secret-value pattern. */\nexport interface SecretPattern {\n /** Stable identifier surfaced in SecretRef.key / descriptions. */\n name: string;\n /** Regex matching the secret token. SHOULD be global so all hits are replaced. */\n regex: RegExp;\n}\n\n/**\n * Ordered list of secret-value patterns. Order matters only for reporting (the\n * first matching name is used to describe a hit); replacement applies them all.\n *\n * Each regex uses the `g` flag so String#replace swaps every occurrence, and is\n * written to match the TOKEN itself (not surrounding quotes/keys) so the redacted\n * marker drops cleanly in place. Patterns are intentionally specific to keep the\n * false-positive rate low on ordinary prose and paths.\n */\nexport const SECRET_PATTERNS: readonly SecretPattern[] = [\n // OpenAI-style keys: sk-..., sk-proj-..., sk-ant-... (>= 20 token chars).\n {\n name: \"openai-style-key\",\n regex: /\\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}\\b/g,\n },\n // Anthropic API keys.\n {\n name: \"anthropic-api-key\",\n regex: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g,\n },\n // GitHub tokens: ghp_, gho_, ghu_, ghs_, ghr_ (classic + app) and fine-grained.\n {\n name: \"github-token\",\n regex: /\\bgh[pousr]_[A-Za-z0-9]{30,}\\b/g,\n },\n {\n name: \"github-fine-grained-token\",\n regex: /\\bgithub_pat_[A-Za-z0-9_]{40,}\\b/g,\n },\n // GitLab personal/runner/feed tokens.\n {\n name: \"gitlab-token\",\n regex: /\\bglpat-[A-Za-z0-9_-]{20,}\\b/g,\n },\n // Slack tokens: xoxb-, xoxp-, xoxa-, xoxr-, xoxs-.\n {\n name: \"slack-token\",\n regex: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/g,\n },\n // AWS access key id.\n {\n name: \"aws-access-key-id\",\n regex: /\\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA)[A-Z0-9]{16}\\b/g,\n },\n // Google API key.\n {\n name: \"google-api-key\",\n regex: /\\bAIza[A-Za-z0-9_-]{35}\\b/g,\n },\n // Stripe live/test secret keys.\n {\n name: \"stripe-secret-key\",\n regex: /\\b[rs]k_(?:live|test)_[A-Za-z0-9]{20,}\\b/g,\n },\n // OpenAI org/project-scoped service tokens sometimes start with these.\n {\n name: \"openai-service-account-key\",\n regex: /\\bsk-svcacct-[A-Za-z0-9_-]{20,}\\b/g,\n },\n // npm automation tokens.\n {\n name: \"npm-token\",\n regex: /\\bnpm_[A-Za-z0-9]{36}\\b/g,\n },\n // JSON Web Tokens (three base64url parts). Conservative length floors.\n {\n name: \"jwt\",\n regex: /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g,\n },\n // Bearer / Authorization header values: \"Bearer <token>\".\n {\n name: \"bearer-token\",\n regex: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{12,}/gi,\n },\n // \"Basic <base64>\" Authorization values.\n {\n name: \"basic-auth\",\n regex: /\\bBasic\\s+[A-Za-z0-9+/]{16,}={0,2}/g,\n },\n // PEM private key blocks (RSA/EC/OPENSSH/PGP/etc). DOTALL via [\\s\\S].\n {\n name: \"private-key-block\",\n regex: /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,\n },\n // Generic \"<SECRETKEY>=<value>\" / \"<SECRETKEY>: <value>\" assignments where the\n // key name itself screams secret (env-style). Captures the VALUE only via a\n // replacer-aware group reference is NOT possible from a flat list, so this\n // pattern matches the whole assignment; the sanitizer treats SECRET_KEY_NAMES\n // structurally for JSON/TOML and uses this for free-text fallback (shell/python\n // hook scripts). Case-INSENSITIVE (the \"i\" flag) + a mixed-case key class so\n // lowercase assignments like `export my_api_key=...` / `api_token=...` that\n // real users write in hooks are caught too, not just SCREAMING_CASE.\n {\n name: \"env-assignment-secret\",\n regex:\n /\\b([A-Za-z0-9_]*(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|CLIENT[_-]?SECRET)[A-Za-z0-9_]*)\\s*[:=]\\s*[\"']?[^\\s\"'#]{6,}[\"']?/gi,\n },\n];\n\n/**\n * Key NAMES whose value is ALWAYS treated as secret (case-insensitive, and\n * matched flexibly so \"apiKey\", \"api_key\", \"API-KEY\" all hit). Used by\n * sanitizeJson and by configToml's structural pass. This is broader than the\n * value patterns: it catches custom/opaque tokens that match no known shape.\n */\nexport const SECRET_KEY_NAMES: readonly string[] = [\n \"api_key\",\n \"apikey\",\n \"api_token\",\n \"token\",\n \"secret\",\n \"secret_key\",\n \"password\",\n \"passwd\",\n \"passphrase\",\n \"authorization\",\n \"auth\",\n \"auth_token\",\n \"access_token\",\n \"refresh_token\",\n \"id_token\",\n \"session_token\",\n \"client_secret\",\n \"private_key\",\n \"secret_access_key\",\n \"encryption_key\",\n \"credentials\",\n \"cookie\",\n \"bearer\",\n \"pat\",\n \"x-api-key\",\n];\n\n/**\n * Normalize a key for fuzzy matching against SECRET_KEY_NAMES: lowercased with\n * separators (-, _, space) stripped, so \"ANTHROPIC_API_KEY\" -> \"anthropicapikey\".\n */\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[\\s_-]+/g, \"\");\n}\n\n/** Pre-normalized secret key names (and their separator-stripped forms). */\nconst NORMALIZED_SECRET_KEYS: readonly string[] = SECRET_KEY_NAMES.map(normalizeKey);\n\n/**\n * True if a JSON/TOML key name denotes a secret value.\n *\n * Matching is fuzzy and substring-aware so vendor-prefixed keys are caught:\n * - exact normalized equality (e.g. \"token\" === \"token\"); and\n * - the key ENDS WITH / CONTAINS a known secret token in normalized form, so\n * \"anthropic_api_key\", \"github_token\", \"myClientSecret\" all match — while a\n * benign \"tokenizer\" or \"tokens_used\" is excluded via word-boundary checks\n * on the original key.\n */\nexport function isSecretKey(key: string): boolean {\n if (!key) return false;\n const norm = normalizeKey(key);\n\n for (const secret of NORMALIZED_SECRET_KEYS) {\n if (norm === secret) return true;\n }\n\n // High-signal compound stems matched as a normalized SUBSTRING so vendor- and\n // helper-suffixed keys are caught even when they don't END with the token, e.g.\n // \"apiKeyHelper\" (a key-PRODUCING command — its output is a live credential),\n // \"apiKeyHelperPath\", \"clientSecretRef\", \"privateKeyPem\". These stems are\n // specific enough not to fire on ordinary words (\"tokenizer\" lacks them).\n const CONTAINS_STEMS = [\n \"apikey\",\n \"secretkey\",\n \"secretaccesskey\",\n \"accesskey\",\n \"privatekey\",\n \"clientsecret\",\n \"accesstoken\",\n \"refreshtoken\",\n \"sessiontoken\",\n \"authtoken\",\n ];\n for (const stem of CONTAINS_STEMS) {\n if (norm.includes(stem)) return true;\n }\n\n // Boundary-aware substring check on the ORIGINAL key to avoid \"tokenizer\".\n const lower = key.toLowerCase();\n const boundaryTokens = [\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"access_key\",\n \"access-key\",\n \"secret_key\",\n \"secret-key\",\n \"client_secret\",\n \"client-secret\",\n \"private_key\",\n \"private-key\",\n \"refresh_token\",\n \"access_token\",\n \"session_token\",\n \"auth_token\",\n \"id_token\",\n \"_token\",\n \"-token\",\n \"_secret\",\n \"-secret\",\n \"_password\",\n \"-password\",\n \"passphrase\",\n \"credentials\",\n ];\n for (const tok of boundaryTokens) {\n if (lower.endsWith(tok)) return true;\n }\n // Whole-word \"token\"/\"secret\"/\"password\" (not \"tokenizer\"/\"tokens\"/\"secretary\").\n if (/(^|[^a-z])(token|secret|password|passwd|authorization)([^a-z]|$)/.test(lower)) {\n return true;\n }\n return false;\n}\n","/**\n * config.toml processing for the Codex adapter — the trickiest piece.\n *\n * Responsibility:\n * - parse `config.toml` with smol-toml;\n * - REDACT secret VALUES under [mcp_servers.*] (env maps, headers, tokens) and\n * [shell_environment_policy.set] using the sanitizer's key-name + value\n * pattern detection;\n * - DROP [hooks.state.*] tables entirely (machine-specific trusted hashes, and\n * their KEYS embed absolute paths — never useful on another machine);\n * - extract [plugins.\"name@marketplace\"] -> PluginEntry and\n * [marketplaces.name] -> MarketplaceEntry for the reinstall manifest;\n * - TEMPLATE absolute machine paths — both in VALUES (mcp command/args, etc.)\n * and in [projects.\"/abs/path\"] KEYS — via templater.toTemplate over the\n * re-stringified text (the templater is path-aware and separator-agnostic, so\n * it folds paths regardless of whether they sit in a value or a quoted key);\n * - KEEP scalars / [features] / [tui.*] / [notice.*] / [projects.*] (trust\n * levels, with their path keys templated).\n *\n * On restore, rehydrateConfigToml() expands the {{TOKENS}} back to this machine's\n * paths. Plugins/marketplaces are reinstalled via the CLI (see restore.ts), not\n * by editing the TOML, but project/mcp paths must be rehydrated so a written-back\n * config.toml points at the right local directories.\n *\n * Libraries: smol-toml (parse/stringify), the sanitizer patterns (isSecretKey +\n * SECRET_PATTERNS + REDACTED), and the injected templater. No fs, no clock.\n */\n\nimport { parse as parseToml, stringify as stringifyToml } from \"smol-toml\";\n\nimport type {\n MarketplaceEntry,\n PluginEntry,\n SecretRef,\n TemplaterService,\n TemplateVariables,\n} from \"../../types.js\";\nimport { isSecretKey, REDACTED, SECRET_PATTERNS } from \"../../core/sanitizer/patterns.js\";\n\n/** Result of processing a raw config.toml on capture. */\nexport interface ParsedCodexConfig {\n /** The sanitized + templated TOML text to store (for the frozen config.toml). */\n sanitizedToml: string;\n /** Plugins parsed from [plugins.\"name@marketplace\"] { enabled }. */\n plugins: PluginEntry[];\n /** Marketplaces parsed from [marketplaces.name] { source_type, source }. */\n marketplaces: MarketplaceEntry[];\n /** Any redacted values (mcp_servers env/headers, shell_environment_policy.set). */\n secrets: SecretRef[];\n}\n\n/** A loosely-typed TOML table (object). */\ntype TomlTable = Record<string, unknown>;\n\n/** Narrow an unknown value to a plain object table (not array, not null). */\nfunction isTable(value: unknown): value is TomlTable {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Redact secret VALUES inside an arbitrary TOML subtree IN PLACE.\n *\n * A value is redacted when EITHER its key name denotes a secret (isSecretKey) OR\n * its string value matches a known secret pattern. Objects and arrays are walked\n * recursively. Every redaction appends a SecretRef (kind:\"value\") whose `source`\n * pinpoints the dotted path inside config.toml.\n *\n * `keyIsSecretContext` forces redaction of *all* string leaves regardless of the\n * leaf key — used for env/headers maps where the table key (e.g. \"env\") is itself\n * a secret container and the inner keys are arbitrary variable names.\n */\nfunction redactSubtree(\n node: unknown,\n dottedPath: string,\n secrets: SecretRef[],\n keyIsSecretContext: boolean,\n): void {\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n const child = node[i];\n if (typeof child === \"string\") {\n if (keyIsSecretContext || valueLooksSecret(child)) {\n node[i] = REDACTED;\n secrets.push(makeValueSecret(`${dottedPath}[${i}]`));\n }\n } else if (isTable(child) || Array.isArray(child)) {\n redactSubtree(child, `${dottedPath}[${i}]`, secrets, keyIsSecretContext);\n }\n }\n return;\n }\n\n if (!isTable(node)) return;\n\n for (const key of Object.keys(node)) {\n const child = node[key];\n const childPath = dottedPath ? `${dottedPath}.${key}` : key;\n const secretContext = keyIsSecretContext || isSecretKey(key) || isSecretContainerKey(key);\n\n if (typeof child === \"string\") {\n if (secretContext || valueLooksSecret(child)) {\n node[key] = REDACTED;\n secrets.push(makeValueSecret(childPath));\n }\n } else if (isTable(child) || Array.isArray(child)) {\n // For container keys (\"env\", \"headers\", \"set\", ...) every leaf below is\n // treated as secret; otherwise recurse with normal per-key detection.\n redactSubtree(child, childPath, secrets, secretContext);\n }\n // numbers / booleans are never secret values we can meaningfully redact.\n }\n}\n\n/**\n * Keys whose entire SUBTREE of string values should be treated as secret,\n * regardless of the inner variable names. These are the standard MCP/credential\n * container maps.\n */\nfunction isSecretContainerKey(key: string): boolean {\n const k = key.toLowerCase();\n return (\n k === \"env\" ||\n k === \"environment\" ||\n k === \"headers\" ||\n k === \"header\" ||\n k === \"set\" || // [shell_environment_policy.set]\n k === \"secrets\" ||\n k === \"credentials\"\n );\n}\n\n/** True if a free string value matches any known secret-value pattern. */\nfunction valueLooksSecret(value: string): boolean {\n for (const p of SECRET_PATTERNS) {\n // Patterns carry the global flag; reset lastIndex before each test so a\n // shared RegExp object does not skip on subsequent calls.\n p.regex.lastIndex = 0;\n if (p.regex.test(value)) return true;\n }\n return false;\n}\n\n/** Build a value-kind SecretRef for a redacted config.toml leaf. */\nfunction makeValueSecret(dottedPath: string): SecretRef {\n return {\n tool: \"codex\",\n source: `config.toml#${dottedPath}`,\n key: dottedPath,\n description: `Redacted secret value in config.toml at ${dottedPath}`,\n kind: \"value\",\n };\n}\n\n/**\n * Parse [plugins.\"name@marketplace\"] tables into PluginEntry[].\n * Each plugin key is \"name@marketplace\"; the table carries { enabled }.\n */\nfunction extractPlugins(root: TomlTable): PluginEntry[] {\n const out: PluginEntry[] = [];\n const plugins = root[\"plugins\"];\n if (!isTable(plugins)) return out;\n\n for (const id of Object.keys(plugins)) {\n const entry = plugins[id];\n const atIdx = id.lastIndexOf(\"@\");\n const name = atIdx >= 0 ? id.slice(0, atIdx) : id;\n const marketplace = atIdx >= 0 ? id.slice(atIdx + 1) : undefined;\n let enabled = true;\n if (isTable(entry) && typeof entry[\"enabled\"] === \"boolean\") {\n enabled = entry[\"enabled\"] as boolean;\n }\n const plugin: PluginEntry = {\n id,\n name,\n enabled,\n // Codex plugins captured here are user-global config; restore reinstalls them.\n scope: \"user\",\n };\n if (marketplace) plugin.marketplace = marketplace;\n out.push(plugin);\n }\n return out;\n}\n\n/**\n * Parse [marketplaces.name] tables into MarketplaceEntry[].\n * Each table carries { source_type, source }. Codex uses source_type \"git\" with a\n * clone URL (verified), or \"github\"/\"local\"; default unknown types to \"git\".\n */\nfunction extractMarketplaces(root: TomlTable): MarketplaceEntry[] {\n const out: MarketplaceEntry[] = [];\n const marketplaces = root[\"marketplaces\"];\n if (!isTable(marketplaces)) return out;\n\n for (const id of Object.keys(marketplaces)) {\n const entry = marketplaces[id];\n if (!isTable(entry)) continue;\n const rawType = typeof entry[\"source_type\"] === \"string\" ? (entry[\"source_type\"] as string) : \"git\";\n const source = typeof entry[\"source\"] === \"string\" ? (entry[\"source\"] as string) : \"\";\n const sourceType: MarketplaceEntry[\"sourceType\"] =\n rawType === \"github\" || rawType === \"git\" || rawType === \"local\" ? rawType : \"git\";\n out.push({ id, sourceType, source });\n }\n return out;\n}\n\n/**\n * Parse raw config.toml text and produce sanitized output + manifest pieces.\n *\n * Steps:\n * 1. parse with smol-toml;\n * 2. extract plugins + marketplaces (BEFORE mutation, so the entries are clean);\n * 3. redact secret values across [mcp_servers.*] and [shell_environment_policy*]\n * (and any other secret-keyed leaves) in place;\n * 4. delete the [hooks.state] table (and the [hooks] parent if it became empty);\n * 5. re-stringify, then run templater.toTemplate over the TEXT to fold every\n * remaining absolute machine path — in values and in [projects.\"PATH\"] keys.\n *\n * The function never throws on malformed TOML where avoidable, but smol-toml's\n * parse will throw on genuinely invalid syntax; callers (capture.ts) catch that\n * and fall back to skipping the file with a warning.\n */\nexport function processConfigToml(\n raw: string,\n templater: TemplaterService,\n vars: TemplateVariables,\n opts?: { redactSecrets?: boolean },\n): ParsedCodexConfig {\n // redactSecrets defaults to true (the safe default). The codex adapter passes\n // false only when ctx.includeSecrets opts INTO carrying inline secret values\n // into the private repo; extraction/templating/hooks-state stripping still run.\n const redactSecrets = opts?.redactSecrets ?? true;\n const secrets: SecretRef[] = [];\n\n const parsed = parseToml(raw) as TomlTable;\n\n // 2) Manifest extraction first — from the still-pristine parse.\n const plugins = extractPlugins(parsed);\n const marketplaces = extractMarketplaces(parsed);\n\n // 3) Redact secrets under the at-risk tables. We walk the whole tree so any\n // secret-keyed leaf anywhere is caught, but the explicit container handling\n // (env/headers/set) guarantees full mcp_servers + shell_env coverage.\n // Skipped entirely when includeSecrets opted to carry values verbatim.\n if (redactSecrets) {\n const mcpServers = parsed[\"mcp_servers\"];\n if (isTable(mcpServers)) {\n redactSubtree(mcpServers, \"mcp_servers\", secrets, false);\n }\n const shellEnv = parsed[\"shell_environment_policy\"];\n if (isTable(shellEnv)) {\n redactSubtree(shellEnv, \"shell_environment_policy\", secrets, false);\n }\n // Catch-all: any other secret-keyed scalar at the top level or in keep-sections.\n for (const key of Object.keys(parsed)) {\n if (key === \"mcp_servers\" || key === \"shell_environment_policy\") continue;\n if (key === \"plugins\" || key === \"marketplaces\" || key === \"hooks\") continue;\n const value = parsed[key];\n if (typeof value === \"string\") {\n if (isSecretKey(key) || valueLooksSecret(value)) {\n parsed[key] = REDACTED;\n secrets.push(makeValueSecret(key));\n }\n } else if (isTable(value) || Array.isArray(value)) {\n redactSubtree(value, key, secrets, false);\n }\n }\n }\n\n // 4) Strip machine-specific hook trust state. The KEYS embed absolute paths and\n // sha256 hashes that are meaningless on another machine; drop the whole\n // [hooks.state] table. Remove the [hooks] parent if nothing else remains.\n const hooks = parsed[\"hooks\"];\n if (isTable(hooks)) {\n delete hooks[\"state\"];\n if (Object.keys(hooks).length === 0) {\n delete parsed[\"hooks\"];\n }\n }\n\n // 5) Re-serialize, then template all remaining machine paths in the text.\n const serialized = stringifyToml(parsed);\n const sanitizedToml = templater.toTemplate(serialized, vars);\n\n return { sanitizedToml, plugins, marketplaces, secrets };\n}\n\n/**\n * Inverse for restore: expand {{TOKENS}} in a stored config.toml back to this\n * machine's paths. Pure text transform via the injected templater; the structure\n * is preserved (we do not re-parse, so comments/formatting survive untouched).\n */\nexport function rehydrateConfigToml(\n stored: string,\n templater: TemplaterService,\n vars: TemplateVariables,\n): string {\n return templater.fromTemplate(stored, vars);\n}\n","/**\n * Codex capture: produce a CaptureResult from ~/.codex.\n *\n * Mirrors the Claude adapter's capture but for Codex specifics:\n * - config.toml is routed through processConfigToml() (sanitize secrets, drop\n * [hooks.state.*], template paths, extract plugins/marketplaces);\n * - the rest of FROZEN_PATHS is walked recursively and frozen file-by-file;\n * - AGENTS.md is skipped when opts.skipInstructions is set (R9 shared);\n * - memories/ is gated on ctx.includeMemories;\n * - skills are classified: a relative symlink into ~/.agents/skills becomes a\n * reinstallable SkillEntry (source:\"skills.sh\") + a CapturedSymlink; a real\n * directory is frozen file-by-file + recorded as SkillEntry (source:\"frozen\");\n * - denylisted-but-present secret files (auth.json, ...) yield a SecretRef.\n *\n * Security: every file path is checked against the per-tool denylist before it is\n * read; text files are sanitized (secret values redacted) then templated (machine\n * paths -> {{TOKENS}}). Binary files are stored base64 without templating. Hook\n * scripts keep their executable mode.\n */\n\nimport path from \"node:path\";\n\nimport type { CaptureContext } from \"../adapter.interface.js\";\nimport type {\n CaptureResult,\n CapturedFile,\n CapturedSymlink,\n NpmGlobalEntry,\n SecretRef,\n SkillEntry,\n ToolManifest,\n} from \"../../types.js\";\nimport { denylistFor, matchesDeny } from \"../../core/sanitizer/denylist.js\";\nimport { listNpmGlobals } from \"../../platform/install.js\";\nimport {\n CONFIG_FILE,\n FROZEN_PATHS,\n INSTRUCTIONS_FILE,\n MEMORIES_DIR,\n REPO_PREFIX,\n paths as codexPaths,\n} from \"./paths.js\";\nimport { processConfigToml } from \"./configToml.js\";\n\n/** The Codex per-tool denylist, computed once per capture. */\nconst DENY = denylistFor(\"codex\");\n\n/** Convert a tool-home-relative POSIX path into its repo path. */\nfunction repoPathFor(rel: string): string {\n // rel is already POSIX (we build it with \"/\" joins); prefix with codex/files.\n return `${REPO_PREFIX}/${rel}`;\n}\n\n/** Heuristic for \"is this content safe to treat as UTF-8 text?\". */\nfunction isProbablyBinary(bytes: Buffer): boolean {\n // A NUL byte is the strongest signal of binary content. Also bail if a large\n // fraction of the first chunk is non-printable / non-whitespace.\n const sample = bytes.subarray(0, 8000);\n if (sample.includes(0)) return true;\n let suspicious = 0;\n for (const b of sample) {\n // Allow tab(9), LF(10), CR(13), and the printable ASCII range; treat the\n // rest (including most control chars) as suspicious. High bytes (>=128) are\n // common in UTF-8 text, so they are NOT counted as suspicious here.\n if (b === 9 || b === 10 || b === 13) continue;\n if (b >= 32 && b < 127) continue;\n if (b >= 128) continue;\n suspicious++;\n }\n return suspicious / Math.max(1, sample.length) > 0.3;\n}\n\n/**\n * Recursively collect frozen files (and symlinks) under an absolute dir,\n * appending CapturedFile/CapturedSymlink/SecretRef as appropriate. `relBase` is\n * the POSIX path of `absPath` relative to the tool home.\n */\nasync function walkDir(\n ctx: CaptureContext,\n absPath: string,\n relBase: string,\n out: {\n files: CapturedFile[];\n symlinks: CapturedSymlink[];\n secrets: SecretRef[];\n warnings: string[];\n },\n): Promise<void> {\n const entries = await ctx.fs.list(absPath);\n for (const name of entries) {\n const childAbs = path.join(absPath, name);\n const childRel = relBase ? `${relBase}/${name}` : name;\n\n // Denylist check first — never even read excluded paths.\n if (matchesDeny(childRel, DENY)) {\n // Surface known wholesale-secret files as SecretRefs so the user is told.\n maybeRecordSecretFile(childRel, out.secrets);\n continue;\n }\n\n const kind = await ctx.fs.statKind(childAbs);\n if (kind === \"symlink\") {\n await captureSymlink(ctx, childAbs, childRel, out);\n } else if (kind === \"dir\") {\n await walkDir(ctx, childAbs, childRel, out);\n } else if (kind === \"file\") {\n await captureFile(ctx, childAbs, childRel, out);\n }\n // \"missing\" (race) -> skip silently.\n }\n}\n\n/** Capture a single regular file at absPath (rel is tool-home-relative POSIX). */\nasync function captureFile(\n ctx: CaptureContext,\n absPath: string,\n rel: string,\n out: { files: CapturedFile[]; secrets: SecretRef[]; warnings: string[] },\n): Promise<void> {\n let bytes: Buffer;\n try {\n bytes = await ctx.fs.readBytes(absPath);\n } catch {\n out.warnings.push(`codex: could not read ${rel}; skipped`);\n return;\n }\n\n const mode = await fileMode(absPath);\n\n if (isProbablyBinary(bytes)) {\n out.files.push({\n repoPath: repoPathFor(rel),\n content: bytes.toString(\"base64\"),\n binary: true,\n ...(mode !== undefined ? { mode } : {}),\n });\n return;\n }\n\n const text = bytes.toString(\"utf8\");\n // sanitizeFile routes any JSON file through the STRUCTURAL key-name-aware\n // sanitizer; non-JSON (hooks/*.sh|*.py, AGENTS.md, prompts) falls back to the\n // text pass. config.toml is handled separately via processConfigToml().\n //\n // ctx.includeSecrets (opt-in, default OFF) carries inline secret VALUES into the\n // PRIVATE repo: when ON we skip value redaction here (whole-file secrets like\n // auth.json remain denylisted regardless).\n let sanitized = text;\n if (!ctx.includeSecrets) {\n const res = ctx.sanitizer.sanitizeFile(text, \"codex\", rel);\n sanitized = res.content;\n for (const ref of res.found) out.secrets.push(ref);\n }\n const templated = ctx.templater.toTemplate(sanitized, ctx.vars);\n\n out.files.push({\n repoPath: repoPathFor(rel),\n content: templated,\n ...(mode !== undefined ? { mode } : {}),\n });\n}\n\n/**\n * Capture a symlink. Codex skills MAY be relative symlinks into ~/.agents/skills\n * (the skills.sh mechanism). For a skills/<name> symlink we record a reinstallable\n * SkillEntry as well, but the symlink is recreated on restore regardless.\n */\nasync function captureSymlink(\n ctx: CaptureContext,\n absPath: string,\n rel: string,\n out: { symlinks: CapturedSymlink[]; warnings: string[] },\n): Promise<void> {\n let target: string;\n try {\n target = await ctx.fs.readLink(absPath);\n } catch {\n out.warnings.push(`codex: could not read symlink ${rel}; skipped`);\n return;\n }\n out.symlinks.push({ repoPath: repoPathFor(rel), target });\n}\n\n/**\n * Best-effort file mode. We only care about preserving the executable bit for\n * hook scripts; everything else uses the writer default. Returns undefined when\n * the mode is the ordinary 0o644 so the manifest/diff stays clean.\n */\nasync function fileMode(absPath: string): Promise<number | undefined> {\n // FsService has no stat-mode accessor; infer \"executable\" by reading bytes is\n // wrong. Instead we use node:fs lstat via a tiny dynamic import kept local so\n // adapters still funnel I/O through ctx for reads/writes. This is the single\n // exception (mode is not exposed on FsService).\n try {\n const { promises: fsp } = await import(\"node:fs\");\n const st = await fsp.lstat(absPath);\n const perm = st.mode & 0o777;\n // Preserve only when an execute bit is set (hooks/, scripts).\n if (perm & 0o111) return perm;\n return undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Build skill entries by scanning ~/.codex/skills for symlinks vs real dirs. */\nasync function captureSkills(\n ctx: CaptureContext,\n skillsDir: string,\n skills: SkillEntry[],\n): Promise<void> {\n const names = await ctx.fs.list(skillsDir);\n for (const name of names) {\n const childRel = `skills/${name}`;\n if (matchesDeny(childRel, DENY)) continue; // e.g. .DS_Store\n const childAbs = path.join(skillsDir, name);\n const kind = await ctx.fs.statKind(childAbs);\n if (kind === \"symlink\") {\n // Reinstallable skills.sh skill (symlink into ~/.agents/skills).\n skills.push({\n name,\n source: \"skills.sh\",\n installCommand: `npx skills add ${name}`,\n symlinked: true,\n });\n } else if (kind === \"dir\") {\n // Real, hand-made / system skill directory -> frozen (files captured by the\n // generic walk); record for visibility.\n skills.push({ name, source: \"frozen\", symlinked: false });\n }\n // files / missing -> ignore (skills are dirs or symlinks).\n }\n}\n\n/** Record a SecretRef for known wholesale-secret files that were excluded. */\nfunction maybeRecordSecretFile(rel: string, secrets: SecretRef[]): void {\n const base = rel.split(\"/\").pop() ?? rel;\n if (base === \"auth.json\") {\n secrets.push({\n tool: \"codex\",\n source: rel,\n key: \"auth.json\",\n description: \"Codex auth token file (excluded; re-login required after restore).\",\n kind: \"file\",\n });\n }\n}\n\n/** Gather npm globals for the manifest (shared helper; never fabricated). */\nasync function collectNpmGlobals(): Promise<NpmGlobalEntry[]> {\n try {\n const pkgs = await listNpmGlobals();\n return pkgs.map((p) => (p.version !== undefined ? { package: p.package, version: p.version } : { package: p.package }));\n } catch {\n return [];\n }\n}\n\n/**\n * Capture the Codex setup into a CaptureResult.\n *\n * @param opts.skipInstructions when true (R9 shared), do NOT emit AGENTS.md as a\n * frozen file (the shared instructions file covers it).\n */\nexport async function capture(\n ctx: CaptureContext,\n opts?: { skipInstructions?: boolean },\n): Promise<CaptureResult> {\n const skipInstructions = opts?.skipInstructions === true;\n const p = codexPaths(ctx.toolHome);\n\n const files: CapturedFile[] = [];\n const symlinks: CapturedSymlink[] = [];\n const secrets: SecretRef[] = [];\n const warnings: string[] = [];\n const skills: SkillEntry[] = [];\n\n const manifest: ToolManifest = {\n tool: \"codex\",\n plugins: [],\n marketplaces: [],\n skills,\n npmGlobals: [],\n enabledPlugins: {},\n };\n\n const out = { files, symlinks, secrets, warnings };\n\n // Build the list of frozen entries; add memories only when opted in.\n const frozen = [...FROZEN_PATHS];\n if (ctx.includeMemories) frozen.push(MEMORIES_DIR);\n\n for (const rel of frozen) {\n // 1) config.toml -> processConfigToml (sanitize + template + extract).\n if (rel === CONFIG_FILE) {\n await captureConfigToml(ctx, p.configToml, manifest, out);\n continue;\n }\n\n // 2) AGENTS.md -> skip when sharing instructions (R9).\n if (rel === INSTRUCTIONS_FILE) {\n if (skipInstructions) continue;\n const kind = await ctx.fs.statKind(p.agentsMd);\n if (kind === \"file\") {\n await captureFile(ctx, p.agentsMd, INSTRUCTIONS_FILE, out);\n }\n continue;\n }\n\n // 3) Everything else: stat, then walk dirs / capture files / record skills.\n const abs = path.join(p.home, rel);\n const kind = await ctx.fs.statKind(abs);\n if (kind === \"missing\") continue;\n\n if (kind === \"file\") {\n if (matchesDeny(rel, DENY)) {\n maybeRecordSecretFile(rel, secrets);\n continue;\n }\n await captureFile(ctx, abs, rel, out);\n continue;\n }\n\n if (kind === \"symlink\") {\n if (matchesDeny(rel, DENY)) continue;\n await captureSymlink(ctx, abs, rel, out);\n continue;\n }\n\n // dir\n if (matchesDeny(rel + \"/\", DENY)) continue;\n await walkDir(ctx, abs, rel, out);\n // Classify skills (symlink vs frozen) after freezing their files.\n if (rel === \"skills\") {\n await captureSkills(ctx, p.skillsDir, skills);\n }\n }\n\n // npm globals are workflow-level state shared across adapters.\n manifest.npmGlobals = await collectNpmGlobals();\n\n return {\n tool: \"codex\",\n files,\n symlinks,\n manifest,\n secrets,\n warnings,\n };\n}\n\n/**\n * Process config.toml: read it, run processConfigToml, and emit the sanitized\n * text as a CapturedFile plus fold the extracted plugins/marketplaces/secrets\n * into the manifest. Tolerates absence and parse failure gracefully.\n */\nasync function captureConfigToml(\n ctx: CaptureContext,\n configAbs: string,\n manifest: ToolManifest,\n out: { files: CapturedFile[]; secrets: SecretRef[]; warnings: string[] },\n): Promise<void> {\n const kind = await ctx.fs.statKind(configAbs);\n if (kind !== \"file\") return; // no config.toml -> nothing to do.\n\n let raw: string;\n try {\n raw = await ctx.fs.read(configAbs);\n } catch {\n out.warnings.push(\"codex: could not read config.toml; skipped\");\n return;\n }\n\n let processed;\n try {\n processed = processConfigToml(raw, ctx.templater, ctx.vars, {\n redactSecrets: !ctx.includeSecrets,\n });\n } catch (err) {\n // Malformed TOML: do NOT ship the raw file (it may contain secrets we failed\n // to strip). Skip it and warn loudly.\n const msg = err instanceof Error ? err.message : String(err);\n out.warnings.push(`codex: config.toml could not be parsed (${msg}); EXCLUDED for safety`);\n return;\n }\n\n out.files.push({\n repoPath: `${REPO_PREFIX}/${CONFIG_FILE}`,\n content: processed.sanitizedToml,\n });\n\n manifest.plugins = processed.plugins;\n manifest.marketplaces = processed.marketplaces;\n for (const s of processed.secrets) out.secrets.push(s);\n\n // Mirror plugin enabled-state into enabledPlugins (authoritative re-enable map).\n for (const plugin of processed.plugins) {\n manifest.enabledPlugins[plugin.id] = plugin.enabled;\n }\n}\n","/**\n * Codex restore: place frozen files + symlinks onto the target machine, reinstall\n * plugins/marketplaces, and recreate reinstallable skills.\n *\n * Strategy for plugins/marketplaces (documented choice):\n * The Codex CLI's plugin surface is `codex plugin ...`. We attempt, in order:\n * codex plugin marketplace add <source>\n * codex plugin install <name@marketplace>\n * guarded by `which(\"codex\")`. If the CLI is missing OR a command exits\n * non-zero (the subcommand may differ across Codex versions), we DO NOT fail\n * the restore: the sanitized config.toml we just wrote already retains the\n * [marketplaces.*] and [plugins.*] tables (configToml.ts keeps them), so Codex\n * re-syncs them from config on next launch. We log.warn that a manual\n * `codex` re-sync may be needed. This keeps restore resilient and idempotent.\n *\n * File writing: each CapturedFile's repoPath is stripped of the \"codex/files/\"\n * prefix and joined onto ctx.toolHome. Text files are de-templated\n * (fromTemplate) so {{TOKENS}} become this machine's paths; config.toml is\n * additionally rehydrated (same fromTemplate, applied through configToml.ts for\n * clarity/symmetry). Binary files are decoded from base64. Modes are restored.\n * sourceOfTruth governs whether existing files are overwritten.\n */\n\nimport path from \"node:path\";\n\nimport { execa } from \"execa\";\n\nimport type { RestoreContext, RestoreData } from \"../adapter.interface.js\";\nimport type { CapturedFile, MarketplaceEntry, PluginEntry, RestoreAction } from \"../../types.js\";\nimport { which } from \"../../platform/install.js\";\nimport { REPO_PREFIX } from \"./paths.js\";\nimport { rehydrateConfigToml } from \"./configToml.js\";\n\n/** POSIX prefix that every Codex CapturedFile.repoPath carries. */\nconst PREFIX_WITH_SLASH = `${REPO_PREFIX}/`;\n\n/** Map a repoPath (\"codex/files/X\") to its tool-home-relative POSIX subpath (\"X\"). */\nfunction repoPathToRel(repoPath: string): string | null {\n if (!repoPath.startsWith(PREFIX_WITH_SLASH)) return null;\n return repoPath.slice(PREFIX_WITH_SLASH.length);\n}\n\n/** Resolve a tool-home-relative POSIX subpath onto the absolute target path. */\nfunction targetAbsFor(toolHome: string, rel: string): string {\n // Split on POSIX \"/\" and re-join with node:path so win32 separators are right.\n const segments = rel.split(\"/\").filter((s) => s.length > 0);\n return path.join(toolHome, ...segments);\n}\n\n/** Is this captured file the Codex config.toml? */\nfunction isConfigToml(rel: string): boolean {\n return rel === \"config.toml\";\n}\n\n/**\n * Build the plan fragment (actions) without executing. Used by the restore\n * command for --dry-run and to size the safety backup.\n */\nexport async function planActions(\n ctx: RestoreContext,\n data: RestoreData,\n): Promise<RestoreAction[]> {\n const actions: RestoreAction[] = [];\n const overwriteAllowed = ctx.sourceOfTruth === \"repo\";\n\n // Frozen files.\n for (const file of data.files) {\n const rel = repoPathToRel(file.repoPath);\n if (rel === null) continue;\n const targetPath = targetAbsFor(ctx.toolHome, rel);\n const exists = await ctx.fs.exists(targetPath);\n actions.push({\n type: \"write-file\",\n tool: \"codex\",\n targetPath,\n description: `Write ${rel}${file.binary ? \" (binary)\" : \"\"}`,\n overwrites: exists && overwriteAllowed,\n });\n }\n\n // Symlinks (skills.sh etc.).\n for (const link of data.symlinks) {\n const rel = repoPathToRel(link.repoPath);\n if (rel === null) continue;\n const targetPath = targetAbsFor(ctx.toolHome, rel);\n const exists = await ctx.fs.exists(targetPath);\n actions.push({\n type: \"write-symlink\",\n tool: \"codex\",\n targetPath,\n description: `Link ${rel} -> ${link.target}`,\n overwrites: exists && overwriteAllowed,\n });\n }\n\n // Marketplaces, then plugins (user-scope only).\n for (const m of data.manifest.marketplaces) {\n actions.push({\n type: \"add-marketplace\",\n tool: \"codex\",\n description: `Register marketplace ${m.id} (${m.source})`,\n });\n }\n for (const plugin of data.manifest.plugins) {\n if (plugin.scope !== \"user\") continue;\n actions.push({\n type: \"install-plugin\",\n tool: \"codex\",\n description: `Install plugin ${plugin.id}`,\n });\n }\n\n // Reinstallable skills (skills.sh).\n for (const skill of data.manifest.skills) {\n if (skill.source !== \"skills.sh\") continue;\n actions.push({\n type: \"install-skill\",\n tool: \"codex\",\n description: `Install skill ${skill.name} (${skill.installCommand ?? \"npx skills add \" + skill.name})`,\n });\n }\n\n // NOTE: npm globals are intentionally NOT planned per-tool. The restore command\n // dedupes them across every restored tool and emits a single set of system-level\n // install-npm-global actions (and runs them once each), so the dry-run plan\n // matches the executor and a package is never installed twice. See\n // src/commands/restore.ts (npmGlobalActions / installSharedNpmGlobals).\n\n return actions;\n}\n\n/**\n * Restore the Codex setup. No-ops on every filesystem/install action when\n * ctx.dryRun is true (planActions is the dry-run reporter; restore itself simply\n * returns early so the command can call either path).\n */\nexport async function restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n if (ctx.dryRun) {\n // Nothing to mutate in dry-run; the command prints planActions() output.\n return;\n }\n\n const overwriteAllowed = ctx.sourceOfTruth === \"repo\";\n\n await ctx.fs.ensureDir(ctx.toolHome);\n\n // 1) Write frozen files (config.toml rehydrated; modes restored).\n for (const file of data.files) {\n await writeCapturedFile(ctx, file, overwriteAllowed);\n }\n\n // 2) Recreate symlinks.\n for (const link of data.symlinks) {\n const rel = repoPathToRel(link.repoPath);\n if (rel === null) continue;\n const targetPath = targetAbsFor(ctx.toolHome, rel);\n try {\n await ctx.fs.symlink(link.target, targetPath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n ctx.log.warn(`codex: could not recreate symlink ${rel} (${msg})`);\n }\n }\n\n // 3) Reinstall marketplaces + plugins via the codex CLI (best-effort).\n await reinstallPluginsAndMarketplaces(ctx, data.manifest.marketplaces, data.manifest.plugins);\n\n // 4) Reinstall skills.sh skills.\n await reinstallSkills(ctx, data.manifest.skills);\n\n // npm globals are installed by the restore command's shared, deduped install\n // pass (src/commands/restore.ts -> installSharedNpmGlobals), which runs once\n // across ALL restored tools. The adapter records them in the manifest but does\n // not install them here (doing so would double-install when claude is also in\n // the restore set, and would miss them on a codex-only restore if left to the\n // claude adapter). This is the fix for the codex-only-restore gap.\n}\n\n/** Write a single CapturedFile onto the target, honoring overwrite policy + mode. */\nasync function writeCapturedFile(\n ctx: RestoreContext,\n file: CapturedFile,\n overwriteAllowed: boolean,\n): Promise<void> {\n const rel = repoPathToRel(file.repoPath);\n if (rel === null) return;\n const targetPath = targetAbsFor(ctx.toolHome, rel);\n\n if (!overwriteAllowed && (await ctx.fs.exists(targetPath))) {\n ctx.log.debug(`codex: skip existing ${rel} (sourceOfTruth=local)`);\n return;\n }\n\n if (file.binary) {\n const bytes = Buffer.from(file.content, \"base64\");\n await ctx.fs.writeBytes(targetPath, bytes, file.mode);\n return;\n }\n\n // Text: expand {{TOKENS}} back to this machine's paths. config.toml goes through\n // the dedicated rehydrate helper for symmetry with capture (same transform).\n const content = isConfigToml(rel)\n ? rehydrateConfigToml(file.content, ctx.templater, ctx.vars)\n : ctx.templater.fromTemplate(file.content, ctx.vars);\n\n await ctx.fs.write(targetPath, content, file.mode);\n}\n\n/**\n * Best-effort marketplace + plugin reinstall via the `codex` CLI. Failures are\n * logged, never thrown: the written config.toml already carries the tables so\n * Codex can re-sync on next launch.\n */\nasync function reinstallPluginsAndMarketplaces(\n ctx: RestoreContext,\n marketplaces: MarketplaceEntry[],\n plugins: PluginEntry[],\n): Promise<void> {\n const userPlugins = plugins.filter((p) => p.scope === \"user\");\n if (marketplaces.length === 0 && userPlugins.length === 0) return;\n\n const hasCodex = await which(\"codex\");\n if (!hasCodex) {\n ctx.log.warn(\n \"codex CLI not found; plugins/marketplaces left in config.toml for Codex to re-sync on next launch.\",\n );\n return;\n }\n\n for (const m of marketplaces) {\n const args = marketplaceAddArgs(m);\n try {\n await execa(\"codex\", args, { stdout: \"ignore\", stderr: \"ignore\", stdin: \"ignore\" });\n ctx.log.step(`codex: registered marketplace ${m.id}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n ctx.log.warn(`codex: 'codex ${args.join(\" \")}' failed (${msg}); config.toml retains it.`);\n }\n }\n\n for (const plugin of userPlugins) {\n const args = pluginInstallArgs(plugin);\n try {\n await execa(\"codex\", args, { stdout: \"ignore\", stderr: \"ignore\", stdin: \"ignore\" });\n ctx.log.step(`codex: installed plugin ${plugin.id}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n ctx.log.warn(`codex: 'codex ${args.join(\" \")}' failed (${msg}); config.toml retains it.`);\n }\n }\n}\n\n/** Reinstall skills.sh-sourced skills via `npx skills add <name>` (best-effort). */\nasync function reinstallSkills(\n ctx: RestoreContext,\n skills: RestoreData[\"manifest\"][\"skills\"],\n): Promise<void> {\n const reinstallable = skills.filter((s) => s.source === \"skills.sh\");\n if (reinstallable.length === 0) return;\n\n for (const skill of reinstallable) {\n try {\n await execa(\"npx\", [\"skills\", \"add\", skill.name], {\n stdout: \"ignore\",\n stderr: \"ignore\",\n stdin: \"ignore\",\n });\n ctx.log.step(`codex: installed skill ${skill.name}`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n ctx.log.warn(`codex: 'npx skills add ${skill.name}' failed (${msg}); skill skipped.`);\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* CLI argv builders (exported for testability + reuse) */\n/* -------------------------------------------------------------------------- */\n\n/** argv for registering a Codex marketplace from a MarketplaceEntry. */\nexport function marketplaceAddArgs(m: MarketplaceEntry): string[] {\n return [\"plugin\", \"marketplace\", \"add\", m.source];\n}\n\n/** argv for installing a Codex plugin from a PluginEntry. */\nexport function pluginInstallArgs(p: PluginEntry): string[] {\n return [\"plugin\", \"install\", p.id];\n}\n","/**\n * The Adapter object for Codex (OpenAI Codex CLI), wiring together this module's\n * paths/capture/restore. All tool-specific knowledge lives in the sibling files;\n * this file only adapts them to the shared Adapter contract.\n *\n * id: \"codex\"\n * displayName: \"Codex\"\n * detect: ~/.codex exists AND (config.toml OR AGENTS.md present)\n * isCliInstalled: `codex` resolves on PATH\n * installCli: runs installCommandFor(\"codex\", os) (npm i -g @openai/codex)\n * capture/restore: delegate to ./capture.js and ./restore.js\n *\n * Graceful absence: detect() returns false (never throws) when ~/.codex is\n * missing, so the backup/restore commands cleanly skip a machine without Codex.\n */\n\nimport type { OS } from \"../../types.js\";\nimport type { Adapter } from \"../adapter.interface.js\";\nimport type { CaptureContext, RestoreContext, RestoreData } from \"../adapter.interface.js\";\nimport type { CaptureResult } from \"../../types.js\";\n\nimport { cliBinaryName, installCommandFor } from \"../../platform/os.js\";\nimport { runInstall, which } from \"../../platform/install.js\";\nimport { fs } from \"../../utils/fs.js\";\n\nimport { paths as codexPaths } from \"./paths.js\";\nimport { capture as captureCodex } from \"./capture.js\";\nimport { restore as restoreCodex } from \"./restore.js\";\n\n/**\n * True if a Codex setup is present on this machine: the home dir exists and has\n * at least one recognizable config artifact (config.toml or AGENTS.md). Tolerant\n * of a partially-populated dir.\n */\nasync function detect(): Promise<boolean> {\n const p = codexPaths();\n if (!(await fs.exists(p.home))) return false;\n if (await fs.exists(p.configToml)) return true;\n if (await fs.exists(p.agentsMd)) return true;\n return false;\n}\n\n/** True if the `codex` CLI is on PATH. */\nasync function isCliInstalled(): Promise<boolean> {\n return which(cliBinaryName(\"codex\"));\n}\n\n/** Install the Codex CLI for the given OS (npm i -g @openai/codex). */\nasync function installCli(os: OS): Promise<void> {\n await runInstall(installCommandFor(\"codex\", os));\n}\n\n/** Capture delegate (Adapter signature: ctx only; opts handled by the module fn). */\nfunction capture(ctx: CaptureContext): Promise<CaptureResult> {\n return captureCodex(ctx);\n}\n\n/** Restore delegate. */\nfunction restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n return restoreCodex(ctx, data);\n}\n\n/** The Codex adapter. */\nexport const codexAdapter: Adapter = {\n id: \"codex\",\n displayName: \"Codex\",\n detect,\n isCliInstalled,\n installCli,\n capture,\n restore,\n};\n\nexport default codexAdapter;\n","/**\n * All Cursor-specific path knowledge for the arbella Cursor adapter.\n *\n * This is the ONE place that encodes where things live under ~/.cursor. The\n * Cursor adapter (index.ts) asks here rather than re-deriving paths, so a layout\n * change is a single-file edit.\n *\n * Cross-OS: the tool home is resolved via src/platform/os.ts (toolHomeDir),\n * never hardcoded. All sub-paths are built with node:path.join so separators are\n * correct on win32 as well. The `repoPath` prefix, by contrast, is a POSIX-only\n * string used inside the backup repo and is intentionally a literal.\n *\n * Cursor reality: Cursor is a desktop app and may be\n * entirely absent (no CLI on Linux). The only globally-portable artifact is\n * `~/.cursor/mcp.json` (`{ \"mcpServers\": { ... } }`). Project-level\n * `.cursor/rules` are repo-specific and out of scope for the global backup; on\n * restore, the shared-instructions (R9) content is materialized as a Cursor user\n * rule under the rules dir.\n */\n\nimport path from \"node:path\";\n\nimport { toolHomeDir } from \"../../platform/os.js\";\n\n/** Absolute path to ~/.cursor on this machine. */\nexport function home(): string {\n return toolHomeDir(\"cursor\");\n}\n\n/**\n * Prefix (POSIX) for every CapturedFile.repoPath this adapter emits.\n * A file at `<home>/X` is stored at `cursor/files/X` in the backup repo.\n */\nexport const REPO_PREFIX = \"cursor/files\";\n\n/** Fully-resolved set of Cursor paths, all absolute. */\nexport interface CursorPaths {\n /** ~/.cursor */\n home: string;\n /** ~/.cursor/mcp.json */\n mcpJson: string;\n /** ~/.cursor/skills */\n skillsDir: string;\n /** ~/.cursor/rules (global user rules; may not exist) */\n rulesDir: string;\n}\n\n/**\n * Build the absolute Cursor path set.\n * @param overrideHome optional home dir (tests point this at a fixture); when\n * omitted, the live ~/.cursor is used.\n */\nexport function paths(overrideHome?: string): CursorPaths {\n const base = overrideHome ?? home();\n return {\n home: base,\n mcpJson: path.join(base, \"mcp.json\"),\n skillsDir: path.join(base, \"skills\"),\n rulesDir: path.join(base, \"rules\"),\n };\n}\n\n/**\n * Files/dirs to FREEZE (copy into the repo), relative to the tool home, in\n * capture order. Cursor's global, portable state is just `mcp.json`. The skills\n * dir is intentionally NOT frozen here: like Claude/Codex it is a skills.sh\n * mechanism (relative symlinks into ~/.agents/skills) handled centrally, and the\n * minimal Cursor adapter keeps to the MCP config + R9 rule on restore.\n *\n * NOTE: anything matching the denylist is skipped during capture regardless of\n * its presence here.\n */\nexport const FROZEN_PATHS: readonly string[] = [\"mcp.json\"] as const;\n\n/**\n * Repo path (POSIX) of the shared-instructions file the backup command writes at\n * the repo root when R9 is active. The Cursor adapter reads this on restore to\n * materialize a Cursor user rule.\n */\nexport const SHARED_INSTRUCTIONS_REPO_PATH = \"shared/instructions.md\";\n\n/**\n * Basename of the Cursor user-rule file the adapter writes (under the rules dir)\n * to carry the shared CLAUDE.md/AGENTS.md instructions across to Cursor (R9).\n * `.mdc` is Cursor's rule file extension.\n */\nexport const SHARED_RULE_FILENAME = \"arbella-shared-instructions.mdc\";\n\n/** Absolute path to the Cursor user rule deployed on restore for R9. */\nexport function sharedRulePath(overrideHome?: string): string {\n return path.join(paths(overrideHome).rulesDir, SHARED_RULE_FILENAME);\n}\n","/**\n * The Cursor adapter — implements the Adapter contract for Cursor.\n *\n * Cursor is a desktop app; its CLI may be entirely absent (notably on Linux,\n * where there is no headless install). This adapter is therefore deliberately\n * MINIMAL but real, and FULLY GRACEFUL when ~/.cursor does not exist:\n *\n * - detect(): true only if ~/.cursor exists (returns false cleanly,\n * never throws, when the dir is missing).\n * - isCliInstalled():probes `cursor` on PATH (often false on this kind of box).\n * - installCli(os): brew --cask cursor (darwin) / winget (win32); on Linux\n * there is no standard install -> warn + skip (no throw).\n * - capture(ctx): if Cursor is absent, returns an EMPTY CaptureResult with a\n * single warning (so backup of the other tools is never\n * blocked). Otherwise freezes ~/.cursor/mcp.json, sanitized\n * (secret values redacted) + templated (machine paths ->\n * {{HOME}}/...). MCP server entries whose env/headers held\n * secret values are recorded as SecretRefs (metadata only;\n * the redacted file is what gets stored).\n * - restore(ctx,d): writes the frozen files back onto ~/.cursor (rehydrating\n * {{TOKENS}} -> machine paths). When the backup repo carried\n * shared instructions (R9), also materializes a Cursor user\n * rule from <repoRoot>/shared/instructions.md so the single\n * CLAUDE.md == AGENTS.md content reaches Cursor too.\n *\n * All fs work goes through the injected CoreServices on the context objects, so\n * the adapter is unit-testable against a fixture dir. No direct node:fs, no clock.\n * Non-fatal problems are surfaced via the warnings array / log.warn, never thrown.\n */\n\nimport path from \"node:path\";\n\nimport type { Adapter, CaptureContext, RestoreContext, RestoreData } from \"../adapter.interface.js\";\nimport type {\n CaptureResult,\n CapturedFile,\n CapturedSymlink,\n OS,\n SecretRef,\n ToolManifest,\n} from \"../../types.js\";\n\nimport { denylistFor, matchesDeny } from \"../../core/sanitizer/denylist.js\";\nimport { cliBinaryName, installCommandFor } from \"../../platform/os.js\";\nimport { runInstall, which } from \"../../platform/install.js\";\n\nimport {\n FROZEN_PATHS,\n REPO_PREFIX,\n SHARED_INSTRUCTIONS_REPO_PATH,\n paths,\n sharedRulePath,\n} from \"./paths.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Small local helpers (mirroring the Claude/Codex adapter conventions) */\n/* -------------------------------------------------------------------------- */\n\n/** Build the repo path for a tool-home-relative POSIX path. */\nfunction repoPathFor(rel: string): string {\n return `${REPO_PREFIX}/${rel}`;\n}\n\n/** Convert an absolute child path under `home` into a POSIX rel path. */\nfunction toRel(home: string, abs: string): string {\n return path.relative(home, abs).split(path.sep).join(\"/\");\n}\n\n/** Strip the leading \"cursor/files/\" prefix from a repoPath. Returns the POSIX\n * tail relative to the tool home, or undefined if the path is not ours. */\nfunction relFromRepoPath(repoPath: string): string | undefined {\n const norm = repoPath.replace(/\\\\/g, \"/\");\n const prefix = `${REPO_PREFIX}/`;\n if (!norm.startsWith(prefix)) return undefined;\n return norm.slice(prefix.length);\n}\n\n/** Heuristic: treat a file as binary if its bytes contain a NUL. */\nfunction looksBinary(buf: Buffer): boolean {\n const n = Math.min(buf.length, 8000);\n for (let i = 0; i < n; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n/** An empty manifest for cursor (all arrays empty). Kept inline so the adapter\n * does not depend on the manifest module's internals. */\nfunction emptyCursorManifest(): ToolManifest {\n return {\n tool: \"cursor\",\n plugins: [],\n marketplaces: [],\n skills: [],\n npmGlobals: [],\n enabledPlugins: {},\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Secret discovery in mcp.json */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Walk a parsed mcp.json shape and record SecretRefs for any secret VALUES found\n * under each MCP server's `env` / `headers` (the common spots for API keys and\n * tokens). This is METADATA ONLY for the user-facing \"you'll need to re-supply\"\n * report; the actual file stored in the repo is the sanitizer-redacted text. We\n * use the sanitizer's own JSON pass to decide what counts as secret so the two\n * stay consistent.\n */\nfunction collectMcpSecretRefs(\n ctx: CaptureContext,\n parsed: unknown,\n source: string,\n): SecretRef[] {\n const refs: SecretRef[] = [];\n if (parsed === null || typeof parsed !== \"object\") return refs;\n\n const servers = (parsed as { mcpServers?: unknown }).mcpServers;\n if (servers === null || typeof servers !== \"object\") return refs;\n\n for (const [serverName, rawServer] of Object.entries(servers as Record<string, unknown>)) {\n if (rawServer === null || typeof rawServer !== \"object\") continue;\n for (const bag of [\"env\", \"headers\"] as const) {\n const values = (rawServer as Record<string, unknown>)[bag];\n if (values === null || typeof values !== \"object\") continue;\n // Let the sanitizer judge each {key: value} object; any redaction it makes\n // means there was a secret value we should report.\n const { found } = ctx.sanitizer.sanitizeJson(values, \"cursor\", `${source}#mcpServers.${serverName}.${bag}`);\n for (const f of found) {\n refs.push({\n tool: \"cursor\",\n source: `${source}#mcpServers.${serverName}.${bag}.${f.key}`,\n key: f.key,\n description: `Cursor MCP server \"${serverName}\" ${bag} value — redacted; re-supply after restore.`,\n kind: \"value\",\n });\n }\n }\n }\n return refs;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Capture */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Capture the Cursor setup into a CaptureResult.\n *\n * Exported (like the Claude/Codex capture modules) so the backup command can\n * call it with the R9 `opts` directly; the Adapter.capture wrapper below is the\n * default (no-opts) path.\n *\n * @param opts.skipInstructions accepted for signature parity with the other\n * adapters (R9). Cursor never emits a CLAUDE.md/AGENTS.md frozen file, so\n * this flag is a no-op here — the shared rule is deployed on RESTORE.\n */\nexport async function capture(\n ctx: CaptureContext,\n _opts?: { skipInstructions?: boolean },\n): Promise<CaptureResult> {\n const files: CapturedFile[] = [];\n const symlinks: CapturedSymlink[] = [];\n const secrets: SecretRef[] = [];\n const warnings: string[] = [];\n const manifest = emptyCursorManifest();\n\n const p = paths(ctx.toolHome);\n const deny = denylistFor(\"cursor\");\n\n // Graceful absence: Cursor may not be installed at all. Never block the rest\n // of the backup — return an empty result with a single explanatory warning.\n if ((await ctx.fs.statKind(p.home)) !== \"dir\") {\n warnings.push(\"cursor: ~/.cursor not found; skipping (Cursor not installed?).\");\n return { tool: \"cursor\", files, symlinks, manifest, secrets, warnings };\n }\n\n for (const rel of FROZEN_PATHS) {\n const abs = path.join(p.home, rel);\n const relPosix = toRel(p.home, abs);\n\n if (matchesDeny(relPosix, deny)) {\n ctx.log.debug(`cursor: skip (denylist) ${relPosix}`);\n continue;\n }\n\n const kind = await ctx.fs.statKind(abs);\n if (kind === \"missing\") {\n ctx.log.debug(`cursor: not present ${relPosix}`);\n continue;\n }\n if (kind !== \"file\") {\n // The minimal Cursor adapter only freezes top-level files (mcp.json).\n ctx.log.debug(`cursor: skip non-file ${relPosix}`);\n continue;\n }\n\n let bytes: Buffer;\n try {\n bytes = await ctx.fs.readBytes(abs);\n } catch (err) {\n warnings.push(`cursor: could not read ${relPosix}: ${(err as Error).message}`);\n continue;\n }\n\n if (looksBinary(bytes)) {\n // Unexpected for mcp.json, but stay robust: store as base64.\n files.push({ repoPath: repoPathFor(relPosix), content: bytes.toString(\"base64\"), binary: true });\n continue;\n }\n\n const raw = bytes.toString(\"utf8\");\n\n // For mcp.json: discover secret refs from the parsed shape BEFORE redaction,\n // so we can tell the user which MCP server env/header to re-supply.\n if (relPosix === \"mcp.json\") {\n try {\n secrets.push(...collectMcpSecretRefs(ctx, JSON.parse(raw), relPosix));\n } catch (err) {\n warnings.push(`cursor: could not parse mcp.json for secret scan: ${(err as Error).message}`);\n }\n }\n\n // Sanitize secret VALUES (unless includeSecrets opts INTO carrying them),\n // then template machine paths to placeholders.\n //\n // sanitizeFile routes mcp.json through the STRUCTURAL key-name-aware sanitizer\n // so an opaque MCP env/header secret stored under a secret KEY NAME (e.g.\n // ACME_API_KEY with a custom-shaped value) is redacted before it is stored,\n // not just values matching a known token regex.\n //\n // When ctx.includeSecrets is ON (opt-in, default OFF), the documented switch\n // carries the real MCP secret values into the PRIVATE repo (we still recorded\n // the SecretRefs above so the user is told which ones are now present).\n const content = ctx.includeSecrets\n ? raw\n : ctx.sanitizer.sanitizeFile(raw, \"cursor\", relPosix).content;\n const templated = ctx.templater.toTemplate(content, ctx.vars);\n files.push({ repoPath: repoPathFor(relPosix), content: templated });\n ctx.log.debug(`cursor: froze ${relPosix}`);\n }\n\n if (files.length === 0) {\n warnings.push(\"cursor: present but nothing portable to capture (no mcp.json).\");\n }\n\n return { tool: \"cursor\", files, symlinks, manifest, secrets, warnings };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Restore */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Write a single CapturedFile onto the target tool home, rehydrating\n * {{TOKENS}} -> machine paths for text content. Respects ctx.sourceOfTruth:\n * when the local machine is the source of truth we do NOT clobber an existing\n * local file (the repo only \"wins\" when sourceOfTruth === \"repo\").\n */\nasync function writeRestoredFile(ctx: RestoreContext, file: CapturedFile): Promise<void> {\n const rel = relFromRepoPath(file.repoPath);\n if (rel === undefined) {\n ctx.log.debug(`cursor: ignoring foreign repoPath ${file.repoPath}`);\n return;\n }\n const dest = path.join(ctx.toolHome, ...rel.split(\"/\"));\n\n if (ctx.sourceOfTruth === \"local\" && (await ctx.fs.exists(dest))) {\n ctx.log.debug(`cursor: keep local ${rel} (sourceOfTruth=local)`);\n return;\n }\n\n if (file.binary) {\n await ctx.fs.writeBytes(dest, Buffer.from(file.content, \"base64\"), file.mode);\n return;\n }\n\n const expanded = ctx.templater.fromTemplate(file.content, ctx.vars);\n await ctx.fs.write(dest, expanded, file.mode);\n}\n\n/**\n * Deploy the shared-instructions (R9) content as a Cursor user rule. Reads\n * <repoRoot>/shared/instructions.md (if present) and writes it under the Cursor\n * rules dir. Best-effort: absence of the shared file is not an error.\n */\nasync function deploySharedRule(ctx: RestoreContext): Promise<void> {\n const sharedAbs = path.join(ctx.repoRoot, ...SHARED_INSTRUCTIONS_REPO_PATH.split(\"/\"));\n if (!(await ctx.fs.exists(sharedAbs))) {\n ctx.log.debug(\"cursor: no shared/instructions.md to deploy as a Cursor rule.\");\n return;\n }\n const body = await ctx.fs.read(sharedAbs);\n // Cursor `.mdc` rules support frontmatter; `alwaysApply: true` makes the rule\n // global. The instructions content is plain markdown and needs no templating.\n const rule = `---\\ndescription: Shared agent instructions (managed by arbella)\\nalwaysApply: true\\n---\\n\\n${body}`;\n const dest = sharedRulePath(ctx.toolHome);\n await ctx.fs.write(dest, rule);\n ctx.log.debug(`cursor: wrote shared-instructions rule -> ${dest}`);\n}\n\n/**\n * Restore Cursor: place frozen files back, then (if the repo carried shared\n * instructions) materialize the Cursor user rule. Honors ctx.dryRun (plan only).\n * Exported for direct use by the restore command + the Adapter wrapper below.\n */\nexport async function restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n if (ctx.dryRun) {\n for (const file of data.files) {\n const rel = relFromRepoPath(file.repoPath);\n if (rel) ctx.log.step(`cursor: would write ${path.join(ctx.toolHome, ...rel.split(\"/\"))}`);\n }\n const sharedAbs = path.join(ctx.repoRoot, ...SHARED_INSTRUCTIONS_REPO_PATH.split(\"/\"));\n if (await ctx.fs.exists(sharedAbs)) {\n ctx.log.step(`cursor: would write shared-instructions rule -> ${sharedRulePath(ctx.toolHome)}`);\n }\n return;\n }\n\n for (const file of data.files) {\n try {\n await writeRestoredFile(ctx, file);\n } catch (err) {\n ctx.log.warn(`cursor: failed to write ${file.repoPath}: ${(err as Error).message}`);\n }\n }\n\n // R9: deploy the shared CLAUDE.md == AGENTS.md content as a Cursor rule.\n try {\n await deploySharedRule(ctx);\n } catch (err) {\n ctx.log.warn(`cursor: failed to deploy shared-instructions rule: ${(err as Error).message}`);\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Detect / CLI */\n/* -------------------------------------------------------------------------- */\n\n/** True if ~/.cursor exists. Graceful: returns false (never throws) if absent. */\nasync function detect(): Promise<boolean> {\n return fsExistsDir(paths().home);\n}\n\n/** Local lstat-free existence-as-dir probe via the default fs service. */\nasync function fsExistsDir(p: string): Promise<boolean> {\n // Use a dynamic import of the default fs service to avoid threading a ctx into\n // detect()/isCliInstalled(), which the Adapter interface calls without a ctx.\n const { fs } = await import(\"../../utils/fs.js\");\n return (await fs.statKind(p)) === \"dir\";\n}\n\n/** True if the `cursor` CLI resolves on PATH. */\nasync function isCliInstalled(): Promise<boolean> {\n return which(cliBinaryName(\"cursor\"));\n}\n\n/**\n * Install the Cursor app/CLI for the given OS. brew --cask cursor (darwin),\n * winget (win32). On Linux there is no standard headless install -> warn + skip\n * (installCommandFor returns null there). Never throws on the unsupported path.\n */\nasync function installCli(os: OS): Promise<void> {\n const cmd = installCommandFor(\"cursor\", os);\n if (cmd === null) {\n const { log } = await import(\"../../utils/log.js\");\n log.warn(\"cursor: no headless install available on this OS — install Cursor manually from cursor.com.\");\n return;\n }\n await runInstall(cmd);\n}\n\n/* -------------------------------------------------------------------------- */\n/* The Adapter object */\n/* -------------------------------------------------------------------------- */\n\nexport const cursorAdapter: Adapter = {\n id: \"cursor\",\n displayName: \"Cursor\",\n detect,\n isCliInstalled,\n installCli,\n // The R9 `skipInstructions` variant is NOT exposed on the Adapter interface\n // (capture(ctx) takes no opts there). The backup command imports the exported\n // `capture(ctx, { skipInstructions })` above directly when shared instructions\n // are active; this wrapper is the default path.\n async capture(ctx: CaptureContext): Promise<CaptureResult> {\n return capture(ctx);\n },\n async restore(ctx: RestoreContext, data: RestoreData): Promise<void> {\n return restore(ctx, data);\n },\n};\n\nexport default cursorAdapter;\n","/**\n * Auto-backup SessionStart hook installer (R4).\n *\n * Writes a throttled `SessionStart` hook into both supported tools so that opening\n * a session triggers `arbella push --auto` in the background. The `--auto` path\n * is itself throttled (see throttle.ts), so the hook firing on every session start\n * is cheap and safe — the actual backup only happens when the cadence allows.\n *\n * Surgical & idempotent: every entry we add carries HOOK_TAG (embedded in the\n * command string), so install is a no-op when ours already exists and uninstall\n * removes ONLY our entries, never the user's other hooks.\n *\n * File shapes (verified against the real machine):\n *\n * Claude ~/.claude/settings.json\n * { ..., \"hooks\": { \"SessionStart\": [ { \"hooks\": [ { \"type\":\"command\",\n * \"command\":\"...\" } ] }, ... ], \"UserPromptSubmit\": [ ... ] } }\n *\n * Codex ~/.codex/hooks.json\n * { \"hooks\": { \"SessionStart\": [ { \"hooks\": [ { \"type\":\"command\",\n * \"command\":\"...\" } ] } ] } }\n *\n * Both use the same nested `{ hooks: [ { type:\"command\", command } ] }` group\n * shape; they differ only in where the top-level event map lives (settings.json's\n * `hooks` field vs hooks.json's `hooks` field).\n *\n * No hardcoded paths: tool homes come from os.ts (toolHomeDir). The command string\n * is OS-resolved (POSIX background `&` vs a Windows `start /b` variant).\n */\n\nimport path from \"node:path\";\n\nimport { fs } from \"../../utils/fs.js\";\nimport { detectOS, toolHomeDir } from \"../../platform/os.js\";\n\nimport type { AutoBackupMode } from \"../../types.js\";\n\n/**\n * Marker embedded in the command so we can find/replace our own entries without\n * clobbering user-authored hooks. It is a shell comment, so it is inert when the\n * command runs but unambiguous when we scan for it.\n */\nexport const HOOK_TAG = \"arbella-autobackup\";\n\n/** The SessionStart event key used by both Claude and Codex hook maps. */\nconst EVENT = \"SessionStart\";\n\n/* -------------------------------------------------------------------------- */\n/* Hook command string */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The command the hook runs. It launches `arbella push --auto` detached so the\n * session start is never blocked, discards output, and appends a `# HOOK_TAG`\n * comment so the entry is recognizable for surgical uninstall.\n *\n * POSIX: arbella push --auto >/dev/null 2>&1 & # arbella-autobackup\n * win32: start /b \"\" arbella push --auto >NUL 2>&1 :: arbella-autobackup\n *\n * `arbella` is invoked by bare name (it is the installed bin on PATH), so no\n * machine-specific path is baked into the hook — it stays portable across restore.\n */\nexport function hookCommand(): string {\n if (detectOS() === \"win32\") {\n // `::` is a batch comment; `start /b` runs detached without a new window.\n return `start /b \"\" arbella push --auto >NUL 2>&1 :: ${HOOK_TAG}`;\n }\n // POSIX: trailing `&` backgrounds it; the `#` comment carries the tag.\n return `arbella push --auto >/dev/null 2>&1 & # ${HOOK_TAG}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal hook-shape helpers */\n/* -------------------------------------------------------------------------- */\n\n/** A single command step inside a hook group. */\ninterface HookCommandStep {\n type: \"command\";\n command: string;\n [extra: string]: unknown;\n}\n\n/** A hook group: `{ hooks: [ { type:\"command\", command } ] }`. */\ninterface HookGroup {\n hooks: HookCommandStep[];\n [extra: string]: unknown;\n}\n\n/** True if a value looks like a hook group we can inspect. */\nfunction isHookGroup(value: unknown): value is HookGroup {\n return (\n !!value &&\n typeof value === \"object\" &&\n Array.isArray((value as { hooks?: unknown }).hooks)\n );\n}\n\n/** True if a hook group is one of ours (any step's command carries HOOK_TAG). */\nfunction isOurGroup(group: unknown): boolean {\n if (!isHookGroup(group)) return false;\n return group.hooks.some(\n (step) =>\n !!step &&\n typeof step === \"object\" &&\n typeof (step as { command?: unknown }).command === \"string\" &&\n (step as { command: string }).command.includes(HOOK_TAG),\n );\n}\n\n/** Build the hook group arbella installs under SessionStart. */\nfunction buildOurGroup(): HookGroup {\n return { hooks: [{ type: \"command\", command: hookCommand() }] };\n}\n\n/**\n * Given the existing array under `event` (possibly undefined), return a new array\n * with all of our prior entries removed and exactly one fresh entry appended.\n * User entries are preserved in order.\n */\nfunction withOurGroupInstalled(existing: unknown): HookGroup[] {\n const kept: HookGroup[] = Array.isArray(existing)\n ? (existing.filter((g) => isHookGroup(g) && !isOurGroup(g)) as HookGroup[])\n : [];\n kept.push(buildOurGroup());\n return kept;\n}\n\n/**\n * Given the existing array under `event`, return a new array with our entries\n * removed. Returns the filtered array (may be empty) or null when nothing of ours\n * was present (so the caller can avoid rewriting the file needlessly).\n */\nfunction withOurGroupRemoved(existing: unknown): HookGroup[] | null {\n if (!Array.isArray(existing)) return null;\n const hadOurs = existing.some((g) => isOurGroup(g));\n if (!hadOurs) return null;\n return existing.filter((g) => isHookGroup(g) && !isOurGroup(g)) as HookGroup[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* JSON file read/write helpers (tolerant of absence + BOM) */\n/* -------------------------------------------------------------------------- */\n\n/** Read a JSON object from `file`, or return {} when the file is missing/empty/corrupt. */\nasync function readJsonObject(file: string): Promise<Record<string, unknown>> {\n if (!(await fs.exists(file))) return {};\n try {\n const raw = (await fs.read(file)).replace(/^/, \"\");\n if (raw.trim() === \"\") return {};\n const parsed = JSON.parse(raw) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Unreadable/corrupt JSON: we must not destroy it. Signal by throwing below.\n throw new Error(\n `Cannot modify hooks: ${file} exists but is not valid JSON. Leaving it untouched.`,\n );\n }\n return {};\n}\n\n/** Pretty-print + write a JSON object, mirroring the 2-space style the tools use. */\nasync function writeJsonObject(\n file: string,\n value: Record<string, unknown>,\n): Promise<void> {\n await fs.write(file, JSON.stringify(value, null, 2) + \"\\n\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Claude: settings.json -> hooks.SessionStart */\n/* -------------------------------------------------------------------------- */\n\nfunction claudeSettingsPath(): string {\n return path.join(toolHomeDir(\"claude\"), \"settings.json\");\n}\n\n/**\n * Mutate Claude's settings.json `hooks.SessionStart`. When `enable` is true we add\n * our (deduplicated) group; when false we strip it. The rest of settings.json is\n * left byte-for-byte intact aside from the touched event array.\n *\n * Only writes when the tool home actually exists (graceful absence) AND when the\n * content would change. Returns true if a write happened.\n */\nasync function applyClaude(enable: boolean): Promise<boolean> {\n const home = toolHomeDir(\"claude\");\n // Do not create a Claude config out of thin air; only touch an existing install.\n if (!(await fs.exists(home))) return false;\n\n const file = claudeSettingsPath();\n const settings = await readJsonObject(file);\n\n const hooksField = settings.hooks;\n const hooks: Record<string, unknown> =\n hooksField && typeof hooksField === \"object\" && !Array.isArray(hooksField)\n ? { ...(hooksField as Record<string, unknown>) }\n : {};\n\n if (enable) {\n hooks[EVENT] = withOurGroupInstalled(hooks[EVENT]);\n } else {\n const removed = withOurGroupRemoved(hooks[EVENT]);\n if (removed === null) return false; // nothing of ours to remove\n if (removed.length === 0) {\n delete hooks[EVENT];\n } else {\n hooks[EVENT] = removed;\n }\n }\n\n settings.hooks = hooks;\n await writeJsonObject(file, settings);\n return true;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Codex: hooks.json -> hooks.SessionStart */\n/* -------------------------------------------------------------------------- */\n\nfunction codexHooksPath(): string {\n return path.join(toolHomeDir(\"codex\"), \"hooks.json\");\n}\n\n/**\n * Mutate Codex's hooks.json `hooks.SessionStart`. Codex nests the event map under\n * a top-level `hooks` object: `{ \"hooks\": { \"SessionStart\": [...] } }`. We honor\n * that wrapper, creating it if (and only if) we are enabling and the tool home\n * exists. Returns true if a write happened.\n */\nasync function applyCodex(enable: boolean): Promise<boolean> {\n const home = toolHomeDir(\"codex\");\n if (!(await fs.exists(home))) return false;\n\n const file = codexHooksPath();\n const root = await readJsonObject(file);\n\n const hooksField = root.hooks;\n const hooks: Record<string, unknown> =\n hooksField && typeof hooksField === \"object\" && !Array.isArray(hooksField)\n ? { ...(hooksField as Record<string, unknown>) }\n : {};\n\n if (enable) {\n hooks[EVENT] = withOurGroupInstalled(hooks[EVENT]);\n } else {\n const removed = withOurGroupRemoved(hooks[EVENT]);\n if (removed === null) return false; // nothing of ours to remove\n if (removed.length === 0) {\n delete hooks[EVENT];\n } else {\n hooks[EVENT] = removed;\n }\n }\n\n root.hooks = hooks;\n await writeJsonObject(file, root);\n return true;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Public API */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Install (or, for `mode===\"off\"`, remove) the throttled SessionStart hook in both\n * tools. Idempotent: re-running with the same mode leaves a single tagged entry.\n *\n * `mode` of `\"off\"` is treated as an uninstall so callers can funnel every cadence\n * change through one entry point. `\"session-start\"` and `\"daily\"` both install the\n * same hook — the cadence difference is enforced by the throttle when the hook\n * fires, not by the hook itself.\n *\n * Failures on one tool do not abort the other; both are attempted.\n */\nexport async function installHook(mode: AutoBackupMode): Promise<void> {\n if (mode === \"off\") {\n await uninstallHook();\n return;\n }\n await Promise.all([applyClaude(true), applyCodex(true)]);\n}\n\n/** Remove any arbella-installed SessionStart hook from both tools. Idempotent. */\nexport async function uninstallHook(): Promise<void> {\n await Promise.all([applyClaude(false), applyCodex(false)]);\n}\n","/**\n * Auto-backup throttle (R4).\n *\n * Persists the timestamp of the last auto-backup in a small JSON stamp file under\n * dataDir() and answers the question \"should we back up right now?\" given the\n * configured cadence. This is what keeps `session-start` from hammering the repo\n * on rapid restarts and keeps `daily` to at most once per 24h.\n *\n * The decision function `shouldRun` is PURE: it takes the current time as an ISO\n * string parameter rather than reading the clock, so it is trivially testable.\n * Only the thin I/O helpers (`readState`/`writeState`) and the command layer touch\n * real time. (The contract explicitly allows the autobackup throttle to read the\n * clock, but we still prefer injection.)\n */\n\nimport path from \"node:path\";\n\nimport { fs } from \"../../utils/fs.js\";\nimport { dataDir } from \"../../platform/os.js\";\n\nimport type { AutoBackupMode } from \"../../types.js\";\n\n/** Absolute path to the stamp file that records the last auto-backup time. */\nexport const STAMP_FILE: string = path.join(dataDir(), \"autobackup.json\");\n\n/** Minimum gap between `session-start` auto-backups (5 minutes). */\nexport const MIN_SESSION_GAP_MS = 5 * 60 * 1000;\n\n/** Minimum gap between `daily` auto-backups (24 hours). */\nexport const DAILY_GAP_MS = 24 * 60 * 60 * 1000;\n\n/** Persisted throttle state. */\nexport interface ThrottleState {\n /** ISO timestamp of the last auto-backup, or null if it has never run. */\n lastRunIso: string | null;\n}\n\n/**\n * Read the throttle state from disk. Tolerant of a missing or corrupt stamp file:\n * any read/parse failure is treated as \"never run before\".\n */\nexport async function readState(): Promise<ThrottleState> {\n if (!(await fs.exists(STAMP_FILE))) {\n return { lastRunIso: null };\n }\n try {\n const raw = await fs.read(STAMP_FILE);\n const parsed = JSON.parse(raw) as unknown;\n if (\n parsed &&\n typeof parsed === \"object\" &&\n \"lastRunIso\" in parsed &&\n typeof (parsed as { lastRunIso: unknown }).lastRunIso === \"string\"\n ) {\n return { lastRunIso: (parsed as { lastRunIso: string }).lastRunIso };\n }\n } catch {\n // Corrupt stamp file -> behave as if we have never run.\n }\n return { lastRunIso: null };\n}\n\n/**\n * Record `nowIso` as the time of the most recent auto-backup. Creates dataDir()\n * if needed (fs.write ensures the parent directory).\n */\nexport async function writeState(nowIso: string): Promise<void> {\n const state: ThrottleState = { lastRunIso: nowIso };\n await fs.write(STAMP_FILE, JSON.stringify(state, null, 2) + \"\\n\");\n}\n\n/**\n * Pure throttle decision.\n *\n * - \"off\": never back up.\n * - \"session-start\": back up unless the last run was < MIN_SESSION_GAP_MS ago.\n * - \"daily\": back up only if >= DAILY_GAP_MS since the last run (or never).\n *\n * Unparseable/absent `lastRunIso` (null or NaN date) means \"never ran\" => run.\n * A future-dated last run (clock skew) is treated conservatively as \"just ran\".\n */\nexport function shouldRun(\n mode: AutoBackupMode,\n lastRunIso: string | null,\n nowIso: string,\n): boolean {\n if (mode === \"off\") return false;\n\n const now = Date.parse(nowIso);\n if (Number.isNaN(now)) {\n // We cannot reason about time without a valid \"now\"; refuse rather than spam.\n return false;\n }\n\n if (lastRunIso === null) return true;\n const last = Date.parse(lastRunIso);\n if (Number.isNaN(last)) return true; // unparseable previous stamp -> treat as never ran\n\n const elapsed = now - last;\n if (elapsed < 0) {\n // Last run is in the future relative to now (clock went backwards / skew).\n // Be conservative and do not back up.\n return false;\n }\n\n if (mode === \"session-start\") {\n return elapsed >= MIN_SESSION_GAP_MS;\n }\n // mode === \"daily\"\n return elapsed >= DAILY_GAP_MS;\n}\n","/**\n * Auto-backup glue (R4).\n *\n * Ties the throttle (when to back up) to the hook installer (how the cadence is\n * triggered) and exposes two small entry points the command layer uses:\n *\n * - setAutoBackup(mode): apply a cadence by (un)installing the SessionStart\n * hook in both tools. NOTE: per the build contract,\n * the CALLER is responsible for persisting `mode`\n * into the arbella config (`saveConfig`/\n * `updateConfig`); this function only owns the hook\n * side-effect, so it stays decoupled from the\n * config module.\n *\n * - maybeRunBackup(mode, now): the gate used by `arbella push --auto`. Returns\n * true when the throttle says it is time to run, and\n * records `now` as the last-run time when it does so\n * (so the NEXT invocation is correctly throttled).\n * The clock is injected (`nowIso`) for testability;\n * the command passes new Date().toISOString().\n */\n\nimport { installHook, uninstallHook } from \"./hook.js\";\nimport { readState, writeState, shouldRun } from \"./throttle.js\";\n\nimport type { AutoBackupMode } from \"../../types.js\";\n\n/**\n * Apply an auto-backup cadence.\n *\n * - \"off\": removes our SessionStart hook from both tools.\n * - \"session-start\": installs the throttled hook (>= 5 min between runs).\n * - \"daily\": installs the throttled hook (>= 24 h between runs).\n *\n * Persisting the chosen mode to config is the caller's job (the command writes the\n * config). Idempotent: safe to call repeatedly with the same mode.\n */\nexport async function setAutoBackup(mode: AutoBackupMode): Promise<void> {\n if (mode === \"off\") {\n await uninstallHook();\n return;\n }\n await installHook(mode);\n}\n\n/**\n * Decide whether `arbella push --auto` should proceed right now.\n *\n * Reads the persisted last-run stamp, asks the pure `shouldRun` decision, and —\n * only when the answer is \"go\" — writes the new stamp so subsequent session-start\n * firings are throttled. Returns the decision to the caller, which performs the\n * actual backup when true.\n *\n * @param mode the configured cadence (config.autoBackup).\n * @param nowIso the current time as an ISO string, injected by the command layer.\n */\nexport async function maybeRunBackup(\n mode: AutoBackupMode,\n nowIso: string,\n): Promise<boolean> {\n if (mode === \"off\") return false;\n\n const { lastRunIso } = await readState();\n const go = shouldRun(mode, lastRunIso, nowIso);\n if (go) {\n await writeState(nowIso);\n }\n return go;\n}\n","/**\n * Zod schema + inferred type for the arbella tool config.\n *\n * Location on disk: configDir()/config.json\n * - macOS/Linux: ~/.config/arbella/config.json\n * - Windows: %APPDATA%/arbella/config.json\n * (configDir() is resolved by src/platform/os.ts — never hardcode this.)\n *\n * This config is LOCAL to the machine (it is NOT the backup repo). It records\n * where the backup repo lives, the source-of-truth direction, auto-backup\n * cadence, and which tools/options are in scope.\n */\n\nimport { z } from \"zod\";\n\nconst toolIdSchema = z.enum([\"claude\", \"codex\", \"cursor\"]);\n\n/** Repo connection details (R11). */\nexport const repoConfigSchema = z.object({\n /** Hosting provider. \"generic\" = any git remote (no auto-create). */\n provider: z.enum([\"github\", \"gitlab\", \"generic\"]),\n /**\n * Remote URL (https or ssh). For github/gitlab this may be derived from a\n * \"owner/name\" pair during init; stored fully-qualified here.\n */\n url: z.string(),\n /**\n * Absolute path to the local clone/working copy of the backup repo on THIS\n * machine. Defaults under a arbella data dir; resolved at init time.\n */\n localPath: z.string(),\n});\n\nexport const arbellaConfigSchema = z.object({\n /** Backup repo connection. */\n repo: repoConfigSchema,\n /**\n * Source-of-truth direction (R12):\n * - \"local\": this machine pushes; backup is authoritative from here.\n * - \"repo\": the repo wins; restore overwrites local on conflict.\n */\n sourceOfTruth: z.enum([\"local\", \"repo\"]).default(\"local\"),\n /**\n * Auto-backup cadence (R4), enforced by a throttled SessionStart hook:\n * - \"off\": never auto-backup.\n * - \"session-start\": back up on every session start (still throttled to avoid\n * hammering on rapid restarts).\n * - \"daily\": back up at most once per 24h.\n */\n autoBackup: z.enum([\"off\", \"session-start\", \"daily\"]).default(\"off\"),\n /** Allow secrets into the (private) repo. Default OFF for safety (R5). */\n includeSecrets: z.boolean().default(false),\n /** Include memories/ in the backup. Opt-in, default OFF (R13). */\n includeMemories: z.boolean().default(false),\n /** Tools to manage. Order is respected during backup/restore. */\n tools: z.array(toolIdSchema).default([\"claude\", \"codex\"]),\n});\n\nexport type RepoConfig = z.infer<typeof repoConfigSchema>;\nexport type ArbellaConfig = z.infer<typeof arbellaConfigSchema>;\n\n/**\n * The default config used to seed a fresh install before `init` fills in the\n * repo. Exported so config/index.ts and tests share one definition. `repo` is\n * intentionally left as a placeholder that init must overwrite.\n */\nexport const DEFAULT_CONFIG: ArbellaConfig = {\n repo: { provider: \"generic\", url: \"\", localPath: \"\" },\n sourceOfTruth: \"local\",\n autoBackup: \"off\",\n includeSecrets: false,\n includeMemories: false,\n tools: [\"claude\", \"codex\"],\n};\n","/**\n * arbella local config: load / save / locate / default.\n *\n * The config lives at `configDir()/config.json` (cross-OS, resolved by\n * src/platform/os.ts — never hardcoded here). It is validated against the zod\n * schema in `./schema.js`. This file is LOCAL to the machine and is NOT the\n * backup repo; it only records where the repo lives plus per-machine options.\n *\n * Security note: this config never holds secret values. It stores a repo URL\n * and local clone path only. We still never log its contents from here.\n */\n\nimport path from \"node:path\";\n\nimport { z } from \"zod\";\n\nimport { configDir } from \"../../platform/os.js\";\nimport { fs } from \"../../utils/fs.js\";\n\nimport {\n type ArbellaConfig,\n arbellaConfigSchema,\n DEFAULT_CONFIG,\n} from \"./schema.js\";\n\n/** Absolute path to the on-disk config file: `configDir()/config.json`. */\nexport function configPath(): string {\n return path.join(configDir(), \"config.json\");\n}\n\n/** True if the config file is present on disk. */\nexport async function configExists(): Promise<boolean> {\n return fs.exists(configPath());\n}\n\n/**\n * Load + validate the config from disk.\n *\n * Throws a friendly Error when the file is missing, is not valid JSON, or fails\n * schema validation. Use {@link loadConfigOrDefault} when absence is fine.\n */\nexport async function loadConfig(): Promise<ArbellaConfig> {\n const file = configPath();\n\n if (!(await fs.exists(file))) {\n throw new Error(\n `No arbella config found at ${file}. Run \\`arbella init\\` first.`,\n );\n }\n\n let raw: string;\n try {\n raw = await fs.read(file);\n } catch (err) {\n throw new Error(\n `Could not read arbella config at ${file}: ${errMessage(err)}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `arbella config at ${file} is not valid JSON: ${errMessage(err)}`,\n );\n }\n\n const result = arbellaConfigSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `arbella config at ${file} is invalid:\\n${formatIssues(result.error)}`,\n );\n }\n\n return result.data;\n}\n\n/**\n * Load the config if present, otherwise return {@link DEFAULT_CONFIG}.\n *\n * A present-but-invalid file still throws (we don't silently mask corruption);\n * only true absence falls back to the default.\n */\nexport async function loadConfigOrDefault(): Promise<ArbellaConfig> {\n if (!(await configExists())) {\n return defaultConfig();\n }\n return loadConfig();\n}\n\n/**\n * The default config (deep copy of {@link DEFAULT_CONFIG}) used to seed a fresh\n * install before `init` fills in the repo. Returning a copy keeps the exported\n * constant immutable against accidental in-place mutation by callers.\n */\nexport function defaultConfig(): ArbellaConfig {\n return arbellaConfigSchema.parse(structuredClone(DEFAULT_CONFIG));\n}\n\n/**\n * Persist the config to disk. Validates first (so we never write garbage),\n * ensures the config directory exists, then writes deterministic JSON.\n */\nexport async function saveConfig(config: ArbellaConfig): Promise<void> {\n // Re-validate to guarantee we only ever serialize a well-formed config and to\n // normalize defaults for any omitted fields.\n const valid = arbellaConfigSchema.parse(config);\n const file = configPath();\n await fs.ensureDir(path.dirname(file));\n await fs.write(file, serializeConfig(valid));\n}\n\n/**\n * Shallow-merge `patch` over the current config (or the default when absent),\n * persist the result, and return it.\n *\n * Shallow by design: passing `{ repo: {...} }` replaces the whole `repo`\n * object. Pass a complete `RepoConfig` when touching repo fields.\n */\nexport async function updateConfig(\n patch: Partial<ArbellaConfig>,\n): Promise<ArbellaConfig> {\n const current = await loadConfigOrDefault();\n const merged = arbellaConfigSchema.parse({ ...current, ...patch });\n await saveConfig(merged);\n return merged;\n}\n\n/* -------------------------------------------------------------------------- */\n/* internal helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Deterministic JSON: 2-space indent, stable key order, trailing newline. */\nfunction serializeConfig(value: ArbellaConfig): string {\n return `${JSON.stringify(value, sortedReplacer, 2)}\\n`;\n}\n\n/** JSON replacer that emits object keys in a stable (sorted) order. */\nfunction sortedReplacer(_key: string, val: unknown): unknown {\n if (val !== null && typeof val === \"object\" && !Array.isArray(val)) {\n const obj = val as Record<string, unknown>;\n return Object.keys(obj)\n .sort()\n .reduce<Record<string, unknown>>((acc, k) => {\n acc[k] = obj[k];\n return acc;\n }, {});\n }\n return val;\n}\n\n/** Render zod issues into a short, human-readable bullet list. */\nfunction formatIssues(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n return ` - ${where}: ${issue.message}`;\n })\n .join(\"\\n\");\n}\n\n/** Best-effort message extraction from an unknown thrown value. */\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * Provider-CLI bridge: arbella's PREFERRED authentication path.\n *\n * The plug-&-play promise (the auth task) is that the user does not care *how*\n * they get authenticated — they just want a private clone/push to work. The\n * smoothest way to deliver that is to let the official provider CLIs do the job\n * they already do well:\n *\n * - GitHub: `gh auth status` / `gh auth login`\n * - GitLab: `glab auth status` / `glab auth login`\n *\n * When `gh`/`glab` is present and logged in, it has ALREADY configured git's\n * credential helper, so a plain `git clone <https-url>` succeeds with no token\n * handling on arbella's side at all. That is strictly better than arbella\n * minting and embedding its own token: nothing sensitive ever flows through\n * arbella, and the user's existing login is reused. So `core/auth/index.ts`\n * tries THIS module first and only falls back to the device-flow / token-paste\n * engine (the rest of core/auth) when the CLI is missing and the user declines\n * to install it (or it is unavailable for the host).\n *\n * What this module does:\n * - map an {@link AuthProviderId} (\"github\"/\"gitlab\") to its CLI binary +\n * the {@link DependencyId} the platform installer knows (\"gh\"/\"glab\");\n * - detect whether the CLI is installed (PATH probe, via the platform layer);\n * - read login state with `<cli> auth status` (NON-interactive, captured);\n * - run `<cli> auth login` with **inherited stdio** so the user completes the\n * browser / device login in their own terminal (the CLI prints the URL +\n * one-time code itself; arbella does not intermediate it);\n * - run `<cli> auth logout` for `arbella auth logout`.\n *\n * SECURITY (HARD): this module never reads, prints, stores, or transports any\n * token. `gh`/`glab` keep their own credentials in their own stores; arbella's\n * 0600 credential store (store.ts) is used ONLY for tokens arbella itself\n * obtains via the device-flow / paste fallback — never for anything the CLI\n * holds. `auth status` output can include a username/host (not a secret) and is\n * surfaced only at debug volume; its stderr is never echoed verbatim to the\n * user (it can be noisy and occasionally embeds a token hint).\n *\n * NO CLOCK / NO FS here — this module only spawns the provider CLIs (via execa)\n * and reads the platform install layer. All human-facing lines go through\n * src/utils/log.ts.\n */\n\nimport { execa } from \"execa\";\n\nimport { log } from \"../../utils/log.js\";\nimport { isInstalled, type DependencyId } from \"../../platform/install.js\";\n\nimport type { AuthProviderId } from \"./providers.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Provider <-> CLI mapping */\n/* -------------------------------------------------------------------------- */\n\n/** Static description of a provider's official CLI. */\nexport interface ProviderCli {\n /** The OAuth provider this CLI authenticates. */\n readonly provider: AuthProviderId;\n /** The executable name probed on PATH and spawned (\"gh\" / \"glab\"). */\n readonly bin: string;\n /** The dependency id the platform installer uses to install it (\"gh\"/\"glab\"). */\n readonly dependency: DependencyId;\n /** Human label for prompts/logs (\"GitHub CLI (gh)\"). */\n readonly label: string;\n}\n\n/** GitHub's `gh` CLI descriptor. */\nconst GH_CLI: ProviderCli = {\n provider: \"github\",\n bin: \"gh\",\n dependency: \"gh\",\n label: \"GitHub CLI (gh)\",\n};\n\n/** GitLab's `glab` CLI descriptor. */\nconst GLAB_CLI: ProviderCli = {\n provider: \"gitlab\",\n bin: \"glab\",\n dependency: \"glab\",\n label: \"GitLab CLI (glab)\",\n};\n\n/** All provider CLIs, keyed by provider id. */\nconst CLI_BY_PROVIDER: Readonly<Record<AuthProviderId, ProviderCli>> = {\n github: GH_CLI,\n gitlab: GLAB_CLI,\n};\n\n/** Resolve the CLI descriptor for a provider id. */\nexport function cliForProvider(provider: AuthProviderId): ProviderCli {\n return CLI_BY_PROVIDER[provider];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * True if the provider's CLI binary resolves on PATH. Thin wrapper over the\n * platform install layer's {@link isInstalled} keyed by the CLI's dependency id,\n * so detection stays consistent with `arbella setup`. Never throws (absence is a\n * boolean, not an error).\n */\nexport async function isProviderCliInstalled(\n provider: AuthProviderId,\n): Promise<boolean> {\n return isInstalled(cliForProvider(provider).dependency);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Login state */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The login state of a provider CLI for (optionally) a specific host.\n * - \"absent\" : the CLI is not installed.\n * - \"authenticated\" : `<cli> auth status` reports an active login.\n * - \"not-authenticated\" : the CLI is installed but not logged in (for the host).\n * - \"unknown\" : the status probe could not be classified (treated by\n * callers like \"not-authenticated\" — they will offer a\n * login rather than assume success).\n */\nexport type CliAuthState =\n | \"absent\"\n | \"authenticated\"\n | \"not-authenticated\"\n | \"unknown\";\n\n/**\n * Read whether the provider CLI is logged in, via `<cli> auth status`.\n *\n * Both `gh` and `glab` exit 0 when authenticated and non-zero when not, and\n * print their human report to STDERR. We run non-interactively (stdin ignored,\n * output captured) and classify by exit code first, then fall back to scanning\n * the combined output for the well-known \"Logged in\" / \"not logged\" phrasing so\n * a future exit-code change still resolves sensibly.\n *\n * `host` narrows the check to a specific hostname when given (`gh auth status\n * --hostname <h>` / `glab auth status --hostname <h>`), which matters for\n * self-hosted GitHub Enterprise / GitLab instances. The CLI output is NOT echoed\n * to the user (it can be noisy); only a debug-level classification is logged.\n */\nexport async function providerCliAuthStatus(\n provider: AuthProviderId,\n host?: string,\n): Promise<CliAuthState> {\n const cli = cliForProvider(provider);\n if (!(await isProviderCliInstalled(provider))) {\n return \"absent\";\n }\n\n const args = [\"auth\", \"status\"];\n if (host && host.trim() !== \"\") {\n args.push(\"--hostname\", host.trim());\n }\n\n let exitCode = 1;\n let combined = \"\";\n try {\n const res = await execa(cli.bin, args, {\n reject: false, // exit code is the signal; never throw on \"not logged in\".\n stdin: \"ignore\",\n stdout: \"pipe\",\n stderr: \"pipe\",\n timeout: 20_000,\n });\n exitCode = typeof res.exitCode === \"number\" ? res.exitCode : 1;\n combined = `${res.stdout ?? \"\"}\\n${res.stderr ?? \"\"}`;\n } catch (err) {\n // Spawn failure (CLI vanished between the probe and here, timeout, …): treat\n // as \"unknown\" so the caller offers a login rather than trusting a bad probe.\n log.debug(`auth: ${cli.bin} auth status could not run: ${errMessage(err)}`);\n return \"unknown\";\n }\n\n if (exitCode === 0) {\n log.debug(`auth: ${cli.bin} reports an active login${host ? ` for ${host}` : \"\"}.`);\n return \"authenticated\";\n }\n\n // Non-zero: usually \"not logged in\", but classify defensively from the text so\n // we don't misread an unrelated error as a clean \"logged out\".\n const text = combined.toLowerCase();\n if (text.includes(\"logged in\") && !text.includes(\"not logged in\")) {\n // Some `gh` versions exit non-zero when only SOME configured hosts are\n // logged in, while still reporting the one we asked about as logged in.\n return \"authenticated\";\n }\n if (\n text.includes(\"not logged in\") ||\n text.includes(\"no accounts\") ||\n text.includes(\"you are not logged\") ||\n text.includes(\"not authenticated\")\n ) {\n return \"not-authenticated\";\n }\n log.debug(\n `auth: ${cli.bin} auth status exited ${exitCode} with an unrecognized report.`,\n );\n return \"unknown\";\n}\n\n/** Convenience: is the provider CLI installed AND logged in (for `host`)? */\nexport async function isProviderCliAuthenticated(\n provider: AuthProviderId,\n host?: string,\n): Promise<boolean> {\n return (await providerCliAuthStatus(provider, host)) === \"authenticated\";\n}\n\n/* -------------------------------------------------------------------------- */\n/* Login (interactive, inherited stdio) */\n/* -------------------------------------------------------------------------- */\n\n/** Options for {@link providerCliLogin}. */\nexport interface ProviderLoginOptions {\n /**\n * Restrict the login to a specific host (GitHub Enterprise / self-hosted\n * GitLab). When omitted the CLI uses its default host (github.com / gitlab.com).\n */\n host?: string;\n /**\n * Ask the CLI to also set up git to use it as a credential helper, so a later\n * `git clone`/`push` over https just works. `gh` does this by default during\n * `auth login`; `glab` configures the git credential helper on login too. We\n * pass the explicit flags where supported to be deterministic. Default true.\n */\n configureGit?: boolean;\n}\n\n/**\n * Run `<cli> auth login` with **inherited stdio** so the user completes the\n * interactive browser / device-code login directly in their terminal.\n *\n * This is the crux of the \"plug & play\" flow: arbella does NOT scrape, proxy, or\n * re-implement the OAuth dance — it hands control to `gh`/`glab`, which print the\n * verification URL + one-time code, open the browser if they can, wait for the\n * user, and on success store their own credentials AND wire up git's credential\n * helper. When it returns successfully, a plain clone of the https URL works.\n *\n * Returns true on a successful (exit 0) login, false otherwise. Never throws on\n * a normal \"the user aborted / login failed\" — the caller decides whether to\n * fall back to arbella's device flow. A genuine inability to spawn the CLI\n * (missing binary) returns false after a clear warning.\n *\n * SECURITY: with inherited stdio, the token the CLI obtains goes straight into\n * the CLI's own store and never passes through arbella; arbella sees only the\n * process exit code.\n */\nexport async function providerCliLogin(\n provider: AuthProviderId,\n opts: ProviderLoginOptions = {},\n): Promise<boolean> {\n const cli = cliForProvider(provider);\n if (!(await isProviderCliInstalled(provider))) {\n log.warn(`${cli.label} is not installed; cannot run \\`${cli.bin} auth login\\`.`);\n return false;\n }\n\n const args = buildLoginArgs(cli, opts);\n\n log.info(\n `Signing in with ${cli.label}. Follow the prompts in your terminal — ` +\n `${cli.bin} will open a browser or show a one-time code.`,\n );\n log.step(`Running: ${cli.bin} ${args.join(\" \")}`);\n\n try {\n await execa(cli.bin, args, {\n // INHERITED stdio: the user interacts with gh/glab directly. This is the\n // whole point — arbella steps out of the way for the actual login.\n stdio: \"inherit\",\n // A human is in the loop (browser, code entry); allow plenty of time.\n timeout: 10 * 60_000,\n });\n log.success(`${cli.label}: signed in.`);\n return true;\n } catch (err) {\n // Non-zero exit (user cancelled, network problem, declined scopes, …). Not\n // fatal: report and let the orchestrator fall back to the device flow.\n log.warn(\n `${cli.label} login did not complete (${errMessage(err)}). ` +\n \"Falling back to arbella's own sign-in if available.\",\n );\n return false;\n }\n}\n\n/** Build the provider-specific `auth login` argv (host + git-credential setup). */\nfunction buildLoginArgs(cli: ProviderCli, opts: ProviderLoginOptions): string[] {\n const configureGit = opts.configureGit ?? true;\n const args = [\"auth\", \"login\"];\n\n if (opts.host && opts.host.trim() !== \"\") {\n args.push(\"--hostname\", opts.host.trim());\n }\n\n if (cli.provider === \"github\") {\n // `gh` over https + git-credential setup so `git clone`/`push` just work.\n // `--web` triggers the browser/device-code flow without needing a pasted PAT.\n if (configureGit) {\n args.push(\"--git-protocol\", \"https\");\n }\n args.push(\"--web\");\n } else {\n // `glab auth login` is interactive by default; `--stdin` would expect a piped\n // token, which we explicitly do NOT do (the user logs in via the browser).\n // It configures the git credential helper as part of login.\n // (No extra flags needed; inherited stdio drives the interactive prompts.)\n }\n\n return args;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Logout */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Run `<cli> auth logout` for `arbella auth logout`. With inherited stdio so the\n * CLI can confirm interactively if it wants to (and to surface a host picker on\n * `gh` when multiple hosts are configured). Returns true on a clean logout.\n *\n * `host` targets a specific host; when omitted, `gh` may prompt and `glab` logs\n * out of its default host. arbella never stores these credentials, so this only\n * affects the CLI's own store.\n */\nexport async function providerCliLogout(\n provider: AuthProviderId,\n host?: string,\n): Promise<boolean> {\n const cli = cliForProvider(provider);\n if (!(await isProviderCliInstalled(provider))) {\n log.debug(`auth: ${cli.label} not installed; nothing to log out of.`);\n return false;\n }\n\n const args = [\"auth\", \"logout\"];\n if (host && host.trim() !== \"\") {\n args.push(\"--hostname\", host.trim());\n }\n\n log.step(`Running: ${cli.bin} ${args.join(\" \")}`);\n try {\n await execa(cli.bin, args, { stdio: \"inherit\", timeout: 60_000 });\n log.success(`${cli.label}: signed out.`);\n return true;\n } catch (err) {\n log.warn(`${cli.label} logout did not complete: ${errMessage(err)}`);\n return false;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal */\n/* -------------------------------------------------------------------------- */\n\n/** Best-effort, secret-free message from an unknown thrown value. */\nfunction errMessage(err: unknown): string {\n if (err instanceof Error && err.message) return err.message;\n return String(err);\n}\n","/**\n * Generic OAuth 2.0 Device Authorization Grant (RFC 8628).\n *\n * This is the provider-agnostic engine arbella uses to obtain an access token\n * for a git host (GitHub, GitLab, …) WITHOUT shipping a browser, a redirect\n * server, or a client secret. The flow is:\n *\n * 1. POST to the device-authorization endpoint with our client_id (+ scope).\n * The server returns a `device_code`, a short `user_code`, a\n * `verification_uri` the user opens in any browser, an `expires_in`, and a\n * polling `interval`.\n * 2. We surface the verification URL + user code to the user (the CALLER does\n * the printing via the `onPrompt` callback, so this module performs no I/O\n * beyond `fetch`).\n * 3. We poll the token endpoint with `grant_type=\n * urn:ietf:params:oauth:grant-type:device_code` until the user approves.\n * Per RFC 8628 §3.5 we honor:\n * - `authorization_pending` -> keep waiting at the current interval,\n * - `slow_down` -> increase the interval (by 5s, per spec),\n * - `access_denied` -> the user said no; fail,\n * - `expired_token` -> the device_code lapsed; fail,\n * - success -> resolve with the access token.\n *\n * SECURITY: the resulting token is returned to the caller in memory only. This\n * module never logs it, never persists it, and never embeds it in an error\n * message. Token-at-rest is the store module's job (0600 file); token-in-URL is\n * the index module's job. The only thing this module prints is whatever the\n * caller chooses to print from the `DeviceCodePrompt` handed to `onPrompt`\n * (a verification URL + a user code — both safe to display).\n *\n * NETWORK: uses the Node 18+ global `fetch` (no node-fetch dependency). All\n * requests send `Accept: application/json` so GitHub returns JSON rather than\n * its default form-encoded body.\n *\n * LIBRARY PURITY: no system clock and no real timers are\n * read directly in a non-injectable way — the deadline is computed from an\n * injectable `now()` and the wait between polls goes through an injectable\n * `sleep()`, both defaulting to the real implementations. This keeps the poll\n * loop unit-testable (fast, deterministic) while behaving correctly in prod.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Endpoint description */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The two OAuth endpoints + the client_id and scopes a device flow needs. A\n * provider (see ./providers.ts) produces one of these for a given host.\n */\nexport interface DeviceFlowEndpoints {\n /** RFC 8628 §3.1 device authorization endpoint (issues device + user codes). */\n readonly deviceCodeUrl: string;\n /** RFC 6749 token endpoint we poll for the access token. */\n readonly tokenUrl: string;\n /** The registered OAuth App client identifier (public; not a secret). */\n readonly clientId: string;\n /** Space-joined scope string requested for the token (e.g. \"repo\"). */\n readonly scope: string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Prompt payload surfaced to the user */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The user-facing half of the device flow: open `verificationUri` and type\n * `userCode`. Everything here is safe to print — none of it is a credential.\n */\nexport interface DeviceCodePrompt {\n /** Short, human-typed code shown on the verification page (e.g. \"WDJB-MJHT\"). */\n readonly userCode: string;\n /** URL the user opens in a browser to enter the code. */\n readonly verificationUri: string;\n /**\n * Optional \"complete\" URI that pre-fills the code (GitHub's\n * `verification_uri_complete`). When present the user can just open it.\n */\n readonly verificationUriComplete?: string;\n /** Seconds until the device/user code expires and the flow must restart. */\n readonly expiresIn: number;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/** Injection seams (all optional; real implementations are the defaults). */\nexport interface DeviceFlowDeps {\n /** Fetch implementation; defaults to the global `fetch` (Node 18+). */\n readonly fetch?: typeof fetch;\n /** Monotonic-ish millisecond clock; defaults to `Date.now`. */\n readonly now?: () => number;\n /** Async delay in ms; defaults to a real `setTimeout`-backed sleep. */\n readonly sleep?: (ms: number) => Promise<void>;\n}\n\n/** Arguments for {@link runDeviceFlow}. */\nexport interface RunDeviceFlowArgs extends DeviceFlowDeps {\n /** The provider endpoints + client_id + scope to authenticate against. */\n readonly endpoints: DeviceFlowEndpoints;\n /**\n * Called once, as soon as the device/user code is issued, so the caller can\n * show the verification URL + user code. May be async (e.g. it might open a\n * spinner). The flow waits for it to resolve before it begins polling.\n */\n readonly onPrompt: (prompt: DeviceCodePrompt) => void | Promise<void>;\n /**\n * Optional per-poll heartbeat (e.g. to keep a spinner alive / log at debug\n * level). Never receives the token. Defaults to a no-op.\n */\n readonly onPoll?: (info: { attempt: number; waitedMs: number }) => void;\n /**\n * Optional hard cap on total wait (ms). Defaults to the server's `expires_in`.\n * Whichever is smaller wins, so we never poll past the device code's lifetime.\n */\n readonly maxWaitMs?: number;\n}\n\n/** The successful result: an access token (+ the metadata the server returned). */\nexport interface DeviceFlowToken {\n /** The bearer/access token. Treat as a SECRET — never log or print this. */\n readonly accessToken: string;\n /** Token type as returned (typically \"bearer\"). */\n readonly tokenType: string;\n /** Granted scope string, if the server echoed one. */\n readonly scope?: string;\n /** Optional refresh token (GitLab issues these; GitHub OAuth Apps do not). */\n readonly refreshToken?: string;\n /** Seconds until the access token expires, if the server provided it. */\n readonly expiresIn?: number;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Error type */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A classified device-flow failure. `code` is the RFC 8628 / OAuth error code\n * when the server supplied one (e.g. \"access_denied\", \"expired_token\"), or one\n * of our synthetic codes (\"timeout\", \"network\", \"http\", \"bad_response\").\n *\n * The message is always credential-free.\n */\nexport class DeviceFlowError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = \"DeviceFlowError\";\n this.code = code;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Defaults */\n/* -------------------------------------------------------------------------- */\n\n/** Real async delay. Kept tiny + injectable so the poll loop is testable. */\nfunction realSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** RFC 8628 §3.5: when the server is unspecified, poll no faster than every 5s. */\nconst DEFAULT_INTERVAL_SECONDS = 5;\n/** RFC 8628 §3.5: on `slow_down`, increase the interval by 5 seconds. */\nconst SLOW_DOWN_INCREMENT_SECONDS = 5;\n/** Floor on the poll interval so a hostile/buggy server can't busy-loop us. */\nconst MIN_INTERVAL_SECONDS = 1;\n\n/* -------------------------------------------------------------------------- */\n/* Device authorization request (step 1) */\n/* -------------------------------------------------------------------------- */\n\n/** Raw fields we read off the device-authorization response (snake_case wire). */\ninterface DeviceAuthResponse {\n device_code?: string;\n user_code?: string;\n verification_uri?: string;\n /** GitHub spells it `verification_uri`; some servers use `verification_url`. */\n verification_url?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n error?: string;\n error_description?: string;\n}\n\n/**\n * Perform the device-authorization request (RFC 8628 §3.1). Returns the codes +\n * the server's recommended polling interval and expiry. Throws DeviceFlowError\n * on a transport/HTTP/parse failure or an OAuth error payload.\n */\nasync function requestDeviceCode(\n endpoints: DeviceFlowEndpoints,\n doFetch: typeof fetch,\n): Promise<{\n deviceCode: string;\n prompt: DeviceCodePrompt;\n intervalSeconds: number;\n}> {\n const body = new URLSearchParams({\n client_id: endpoints.clientId,\n scope: endpoints.scope,\n });\n\n const json = await postForm<DeviceAuthResponse>(\n endpoints.deviceCodeUrl,\n body,\n doFetch,\n \"device authorization\",\n );\n\n if (json.error) {\n throw new DeviceFlowError(\n json.error,\n `Device authorization was rejected: ${describeOAuthError(json.error, json.error_description)}`,\n );\n }\n\n const deviceCode = json.device_code;\n const userCode = json.user_code;\n const verificationUri = json.verification_uri ?? json.verification_url;\n\n if (!deviceCode || !userCode || !verificationUri) {\n throw new DeviceFlowError(\n \"bad_response\",\n \"Device authorization response was missing device_code/user_code/verification_uri.\",\n );\n }\n\n const expiresIn =\n typeof json.expires_in === \"number\" && json.expires_in > 0\n ? json.expires_in\n : 900; // 15 min is the common default when unspecified.\n\n const intervalSeconds = clampInterval(json.interval);\n\n const prompt: DeviceCodePrompt = {\n userCode,\n verificationUri,\n ...(json.verification_uri_complete\n ? { verificationUriComplete: json.verification_uri_complete }\n : {}),\n expiresIn,\n };\n\n return { deviceCode, prompt, intervalSeconds };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Token polling (step 3) */\n/* -------------------------------------------------------------------------- */\n\n/** Raw fields we read off the token endpoint response. */\ninterface TokenResponse {\n access_token?: string;\n token_type?: string;\n scope?: string;\n refresh_token?: string;\n expires_in?: number;\n error?: string;\n error_description?: string;\n}\n\n/**\n * Poll the token endpoint until the user authorizes (or we fail / time out).\n *\n * `intervalSeconds` is the starting cadence; it grows on `slow_down`. We stop\n * once `now()` passes the deadline (min of the server expiry and any caller cap)\n * and report a \"timeout\".\n */\nasync function pollForToken(args: {\n endpoints: DeviceFlowEndpoints;\n deviceCode: string;\n intervalSeconds: number;\n deadlineMs: number;\n doFetch: typeof fetch;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n onPoll: (info: { attempt: number; waitedMs: number }) => void;\n}): Promise<DeviceFlowToken> {\n let intervalSeconds = args.intervalSeconds;\n let attempt = 0;\n const startedMs = args.now();\n\n for (;;) {\n // Respect the polling interval BEFORE each attempt (RFC 8628 §3.5 says the\n // client must wait `interval` seconds between polls).\n await args.sleep(intervalSeconds * 1000);\n\n if (args.now() >= args.deadlineMs) {\n throw new DeviceFlowError(\n \"timeout\",\n \"Timed out waiting for you to authorize the device. Run the command again to retry.\",\n );\n }\n\n attempt += 1;\n args.onPoll({ attempt, waitedMs: args.now() - startedMs });\n\n const body = new URLSearchParams({\n client_id: args.endpoints.clientId,\n device_code: args.deviceCode,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n });\n\n const json = await postForm<TokenResponse>(\n args.endpoints.tokenUrl,\n body,\n args.doFetch,\n \"token\",\n );\n\n // Success: the server minted a token.\n if (json.access_token) {\n return {\n accessToken: json.access_token,\n tokenType: json.token_type ?? \"bearer\",\n ...(json.scope ? { scope: json.scope } : {}),\n ...(json.refresh_token ? { refreshToken: json.refresh_token } : {}),\n ...(typeof json.expires_in === \"number\"\n ? { expiresIn: json.expires_in }\n : {}),\n };\n }\n\n // Otherwise we expect an OAuth error code telling us how to proceed.\n switch (json.error) {\n case \"authorization_pending\":\n // The user hasn't finished yet. Keep polling at the same cadence.\n continue;\n case \"slow_down\":\n // We're polling too fast; back off by the spec's 5-second increment.\n intervalSeconds += SLOW_DOWN_INCREMENT_SECONDS;\n continue;\n case \"access_denied\":\n throw new DeviceFlowError(\n \"access_denied\",\n \"Authorization was denied. The token was not granted.\",\n );\n case \"expired_token\":\n throw new DeviceFlowError(\n \"expired_token\",\n \"The device code expired before you authorized it. Run the command again.\",\n );\n case undefined:\n // No token AND no error code: a malformed response.\n throw new DeviceFlowError(\n \"bad_response\",\n \"Token endpoint returned neither an access token nor an error.\",\n );\n default:\n // Any other OAuth error (invalid_client, invalid_grant, unsupported…).\n throw new DeviceFlowError(\n json.error,\n `Authorization failed: ${describeOAuthError(json.error, json.error_description)}`,\n );\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Public entry point */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Run the full device flow: request a code, surface it via `onPrompt`, then poll\n * until the user authorizes. Resolves with the access token (in memory) or\n * rejects with a {@link DeviceFlowError} carrying a stable `code`.\n *\n * The returned token is a SECRET: the caller must hand it to the store (0600) /\n * URL builder and must never log it.\n */\nexport async function runDeviceFlow(\n args: RunDeviceFlowArgs,\n): Promise<DeviceFlowToken> {\n const doFetch = args.fetch ?? globalThis.fetch;\n if (typeof doFetch !== \"function\") {\n throw new DeviceFlowError(\n \"network\",\n \"global fetch is unavailable; arbella requires Node 18+ for the device flow.\",\n );\n }\n const now = args.now ?? Date.now;\n const sleep = args.sleep ?? realSleep;\n const onPoll = args.onPoll ?? (() => {});\n\n // Step 1: get the device + user code.\n const { deviceCode, prompt, intervalSeconds } = await requestDeviceCode(\n args.endpoints,\n doFetch,\n );\n\n // Step 2: let the caller show the verification URL + user code.\n await args.onPrompt(prompt);\n\n // Compute the deadline: never poll past the device code's lifetime, and honor\n // a tighter caller cap if one was given.\n const expiryMs = now() + prompt.expiresIn * 1000;\n const deadlineMs =\n args.maxWaitMs !== undefined\n ? Math.min(expiryMs, now() + args.maxWaitMs)\n : expiryMs;\n\n // Step 3: poll until authorized / denied / expired / timed out.\n return pollForToken({\n endpoints: args.endpoints,\n deviceCode,\n intervalSeconds,\n deadlineMs,\n doFetch,\n now,\n sleep,\n onPoll,\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * POST a url-encoded form and parse the JSON response. We always ask for JSON\n * (`Accept: application/json`) so GitHub does not fall back to a form-encoded\n * body. A non-2xx status that still carries a JSON OAuth error body is returned\n * to the caller (so the poll loop can read `error`); a non-2xx with no usable\n * body becomes a DeviceFlowError(\"http\"). Network failures become\n * DeviceFlowError(\"network\"). Parse failures become DeviceFlowError(\"bad_response\").\n *\n * The `what` label (e.g. \"token\") is only used to phrase error messages; no\n * request/response bodies — which could contain a token — are ever included.\n */\nasync function postForm<T>(\n url: string,\n body: URLSearchParams,\n doFetch: typeof fetch,\n what: string,\n): Promise<T> {\n let res: Response;\n try {\n res = await doFetch(url, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: body.toString(),\n });\n } catch (err) {\n throw new DeviceFlowError(\n \"network\",\n `Could not reach the ${what} endpoint (${safeHost(url)}): ${networkMessage(err)}`,\n );\n }\n\n // Read the body once as text, then try to parse it as JSON. Some servers send\n // a JSON error body with a non-2xx status (the OAuth poll path relies on this).\n const text = await res.text().catch(() => \"\");\n const parsed = tryParseJson<T>(text);\n\n if (parsed !== undefined) {\n return parsed;\n }\n\n // No JSON body we could parse. If the HTTP status was an error, surface that;\n // otherwise the server returned an unexpected (non-JSON) success body.\n if (!res.ok) {\n throw new DeviceFlowError(\n \"http\",\n `The ${what} endpoint (${safeHost(url)}) returned HTTP ${res.status}.`,\n );\n }\n throw new DeviceFlowError(\n \"bad_response\",\n `The ${what} endpoint (${safeHost(url)}) returned a response arbella could not parse.`,\n );\n}\n\n/** Parse JSON, returning undefined (never throwing) on empty/invalid input. */\nfunction tryParseJson<T>(text: string): T | undefined {\n if (!text || text.trim() === \"\") return undefined;\n try {\n return JSON.parse(text) as T;\n } catch {\n return undefined;\n }\n}\n\n/** RFC 8628 §3.5 interval handling: default 5s, floor 1s, ignore junk values. */\nfunction clampInterval(raw: number | undefined): number {\n if (typeof raw !== \"number\" || !Number.isFinite(raw) || raw <= 0) {\n return DEFAULT_INTERVAL_SECONDS;\n }\n return Math.max(MIN_INTERVAL_SECONDS, Math.floor(raw));\n}\n\n/** Build a human description of an OAuth error without leaking sensitive data. */\nfunction describeOAuthError(code: string, description?: string): string {\n const desc = typeof description === \"string\" ? description.trim() : \"\";\n return desc.length > 0 ? `${code} (${desc})` : code;\n}\n\n/** The host (scheme://host) of a URL for error messages; never the full path. */\nfunction safeHost(url: string): string {\n try {\n const u = new URL(url);\n return `${u.protocol}//${u.host}`;\n } catch {\n return \"the provider\";\n }\n}\n\n/** Extract a short, safe message from a thrown fetch/network error. */\nfunction networkMessage(err: unknown): string {\n if (err instanceof Error && err.message) return err.message;\n return String(err);\n}\n","/**\n * Auth provider catalog: maps a git HOST to its OAuth device-flow endpoints,\n * scopes, client_id, and personal-access-token (PAT) help page.\n *\n * arbella authenticates against the host parsed from the backup repo URL, NOT\n * against a hard-wired \"github vs gitlab\" config field — the same machine may\n * push to github.com for one repo and gitlab.com for another. {@link\n * detectProvider} therefore takes a host string and returns the matching\n * provider (or undefined for an unknown/self-hosted host, where arbella falls\n * back to interactive token paste).\n *\n * CLIENT_ID (honesty, A6): a real device flow needs an OAuth App registered with\n * the provider, with the Device Flow grant enabled. That client_id is PUBLIC\n * (not a secret), but it is account-specific, so arbella cannot ship a working\n * default. Resolution order:\n * 1. an explicit override injected by the caller (e.g. from arbella config),\n * 2. the provider's environment variable\n * (ARBELLA_GITHUB_CLIENT_ID / ARBELLA_GITLAB_CLIENT_ID),\n * 3. a clearly-marked PLACEHOLDER constant.\n * When the resolved client_id is still the placeholder, {@link providerHasClientId}\n * is false and the index module skips the device flow and offers token paste\n * instead. The README \"Authentication\" section documents how to register the App.\n *\n * SCOPES (A1): the minimum needed to clone + push a private repo over https:\n * - GitHub: `repo` (full control of private repositories — clone & push).\n * - GitLab: `read_repository` + `write_repository` (clone & push over https).\n *\n * This module performs NO network or filesystem I/O and reads no secrets; it is\n * a pure description layer over `process.env`.\n */\n\nimport process from \"node:process\";\n\nimport type { DeviceFlowEndpoints } from \"./device-flow.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Provider ids */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The OAuth providers arbella knows how to run a device flow against. This is a\n * SUPERSET-compatible subset of the repo `RepoProvider` (\"generic\" has no OAuth\n * provider — it always uses token paste), kept local so this module does not\n * couple the auth layer to repo-provider semantics.\n */\nexport type AuthProviderId = \"github\" | \"gitlab\";\n\n/* -------------------------------------------------------------------------- */\n/* Placeholder client ids */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Sentinel client_id used when no real OAuth App is configured. It is\n * intentionally NOT a valid client id: its presence means \"device flow is not\n * available; fall back to token paste\". The \"ARBELLA_PLACEHOLDER\" marker also\n * makes it obvious in any debug output that no App was registered.\n */\nexport const PLACEHOLDER_CLIENT_ID = \"ARBELLA_PLACEHOLDER_CLIENT_ID\";\n\n/** True when a resolved client_id is the placeholder (i.e. unconfigured). */\nexport function isPlaceholderClientId(clientId: string): boolean {\n return clientId.trim() === \"\" || clientId === PLACEHOLDER_CLIENT_ID;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Static provider descriptors */\n/* -------------------------------------------------------------------------- */\n\n/** Everything about a provider EXCEPT the runtime-resolved client_id. */\nexport interface AuthProviderSpec {\n /** Stable provider id. */\n readonly id: AuthProviderId;\n /** Human label for prompts/logs (\"GitHub\", \"GitLab\"). */\n readonly displayName: string;\n /** Canonical host this provider serves (e.g. \"github.com\"). */\n readonly host: string;\n /** RFC 8628 device authorization endpoint. */\n readonly deviceCodeUrl: string;\n /** OAuth token endpoint polled for the access token. */\n readonly tokenUrl: string;\n /** Space-joined OAuth scopes requested (clone + push a private repo). */\n readonly scope: string;\n /** Environment variable that supplies the OAuth App client_id. */\n readonly clientIdEnvVar: string;\n /** Page where a user creates a PAT as the zero-setup fallback. */\n readonly patHelpUrl: string;\n /** One-line hint shown above the token-paste prompt. */\n readonly patHint: string;\n}\n\n/** GitHub.com device-flow + token-paste descriptor. */\nconst GITHUB_SPEC: AuthProviderSpec = {\n id: \"github\",\n displayName: \"GitHub\",\n host: \"github.com\",\n deviceCodeUrl: \"https://github.com/login/device/code\",\n tokenUrl: \"https://github.com/login/oauth/access_token\",\n // `repo` grants clone + push for private repositories over https.\n scope: \"repo\",\n clientIdEnvVar: \"ARBELLA_GITHUB_CLIENT_ID\",\n patHelpUrl: \"https://github.com/settings/tokens/new?scopes=repo&description=arbella\",\n patHint:\n \"Create a GitHub token with the `repo` scope at \" +\n \"https://github.com/settings/tokens (classic) or a fine-grained token with \" +\n \"Contents: Read and write for the backup repo.\",\n};\n\n/** GitLab.com device-flow + token-paste descriptor. */\nconst GITLAB_SPEC: AuthProviderSpec = {\n id: \"gitlab\",\n displayName: \"GitLab\",\n host: \"gitlab.com\",\n deviceCodeUrl: \"https://gitlab.com/oauth/authorize_device\",\n tokenUrl: \"https://gitlab.com/oauth/token\",\n // read+write repository grant clone + push over https.\n scope: \"read_repository write_repository\",\n clientIdEnvVar: \"ARBELLA_GITLAB_CLIENT_ID\",\n patHelpUrl: \"https://gitlab.com/-/user_settings/personal_access_tokens\",\n patHint:\n \"Create a GitLab Personal Access Token with the `read_repository` and \" +\n \"`write_repository` scopes at \" +\n \"https://gitlab.com/-/user_settings/personal_access_tokens.\",\n};\n\n/** All known provider specs, keyed by id. */\nconst SPEC_BY_ID: Readonly<Record<AuthProviderId, AuthProviderSpec>> = {\n github: GITHUB_SPEC,\n gitlab: GITLAB_SPEC,\n};\n\n/** All known provider specs (stable order: github, gitlab). */\nexport const AUTH_PROVIDER_SPECS: readonly AuthProviderSpec[] = [\n GITHUB_SPEC,\n GITLAB_SPEC,\n];\n\n/* -------------------------------------------------------------------------- */\n/* Host parsing + detection */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Extract the bare host (lowercased, no port, no `user@`) from a git remote URL\n * or a host string. Handles the three remote shapes arbella sees:\n * - https://github.com/owner/repo.git -> \"github.com\"\n * - git@github.com:owner/repo.git -> \"github.com\"\n * - ssh://git@gitlab.com:22/owner/repo.git -> \"gitlab.com\"\n * and a plain \"github.com\" passthrough. Returns undefined when no host can be\n * determined (e.g. a `file://` path or a bare local path).\n */\nexport function parseHost(repoUrlOrHost: string): string | undefined {\n const raw = repoUrlOrHost.trim();\n if (raw === \"\") return undefined;\n\n // file:// and bare local paths have no meaningful host for auth purposes.\n if (/^file:\\/\\//i.test(raw) || raw.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(raw)) {\n return undefined;\n }\n\n // scp-like syntax: [user@]host:path (no scheme, a \":\" before the first \"/\").\n const scpLike = raw.match(/^(?:[^@/]+@)?([^/:]+):(?!\\/\\/)/);\n if (scpLike) {\n return normalizeHost(scpLike[1]);\n }\n\n // Anything with a scheme: let URL parse it (covers https/http/ssh/git).\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw)) {\n try {\n const u = new URL(raw);\n return u.hostname ? normalizeHost(u.hostname) : undefined;\n } catch {\n return undefined;\n }\n }\n\n // A bare host like \"github.com\" (optionally \"github.com:443\").\n if (/^[A-Za-z0-9.-]+(?::\\d+)?$/.test(raw) && raw.includes(\".\")) {\n return normalizeHost(raw);\n }\n\n return undefined;\n}\n\n/** Lowercase + strip a trailing \":port\" from a hostname. */\nfunction normalizeHost(host: string): string {\n return host.toLowerCase().replace(/:\\d+$/, \"\");\n}\n\n/**\n * Detect the OAuth provider for a host or repo URL. Matches github.com /\n * gitlab.com exactly (and their `www.`-prefixed forms defensively). Returns\n * undefined for unknown / self-hosted hosts, where the caller uses token paste.\n *\n * NOTE: self-hosted GitHub Enterprise / GitLab instances are intentionally NOT\n * auto-detected here — their endpoints differ per install. They still work via\n * the interactive token-paste fallback in the index module.\n */\nexport function detectProvider(repoUrlOrHost: string): AuthProviderSpec | undefined {\n const host = parseHost(repoUrlOrHost);\n if (!host) return undefined;\n const bare = host.replace(/^www\\./, \"\");\n if (bare === \"github.com\") return GITHUB_SPEC;\n if (bare === \"gitlab.com\") return GITLAB_SPEC;\n return undefined;\n}\n\n/** Look up a provider spec by its id. */\nexport function providerById(id: AuthProviderId): AuthProviderSpec {\n return SPEC_BY_ID[id];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Client id resolution */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Resolve the client_id for a provider. Order: explicit override (e.g. from\n * arbella config) -> the provider's env var -> the placeholder.\n *\n * `overrides` lets the command/config layer inject a client_id read from the\n * arbella config WITHOUT this module importing the config schema (which it must\n * not modify). Keys are provider ids.\n */\nexport function resolveClientId(\n spec: AuthProviderSpec,\n overrides?: Partial<Record<AuthProviderId, string>>,\n): string {\n const override = overrides?.[spec.id];\n if (typeof override === \"string\" && override.trim() !== \"\") {\n return override.trim();\n }\n const fromEnv = process.env[spec.clientIdEnvVar];\n if (typeof fromEnv === \"string\" && fromEnv.trim() !== \"\") {\n return fromEnv.trim();\n }\n return PLACEHOLDER_CLIENT_ID;\n}\n\n/**\n * True when a real (non-placeholder) client_id is configured for the provider —\n * i.e. the device flow is actually runnable. Used by the index module to decide\n * device-flow vs. token-paste.\n */\nexport function providerHasClientId(\n spec: AuthProviderSpec,\n overrides?: Partial<Record<AuthProviderId, string>>,\n): boolean {\n return !isPlaceholderClientId(resolveClientId(spec, overrides));\n}\n\n/**\n * Build the concrete {@link DeviceFlowEndpoints} (endpoints + resolved client_id\n * + scope) the device-flow engine consumes for a provider. Throws when no real\n * client_id is configured — callers should gate on {@link providerHasClientId}\n * first, but this guard keeps a misuse from silently sending the placeholder.\n */\nexport function buildEndpoints(\n spec: AuthProviderSpec,\n overrides?: Partial<Record<AuthProviderId, string>>,\n): DeviceFlowEndpoints {\n const clientId = resolveClientId(spec, overrides);\n if (isPlaceholderClientId(clientId)) {\n throw new Error(\n `No OAuth App client_id is configured for ${spec.displayName}. ` +\n `Set ${spec.clientIdEnvVar} (see the Authentication section of the README) ` +\n \"or use the interactive token paste instead.\",\n );\n }\n return {\n deviceCodeUrl: spec.deviceCodeUrl,\n tokenUrl: spec.tokenUrl,\n clientId,\n scope: spec.scope,\n };\n}\n","/**\n * Credential store: arbella's local-only token vault.\n *\n * Tokens obtained by the device flow (or pasted by the user) are persisted here\n * so the user authenticates once per host, not once per command. The store lives\n * at `dataDir()/credentials.json` (cross-OS, resolved via src/platform/os.ts —\n * never hardcoded) and is keyed by HOST (e.g. \"github.com\"), so a machine that\n * pushes to several hosts keeps a token for each.\n *\n * HARD SECURITY RULES:\n * - This file is the ONLY place a token is written at rest, and it lives ONLY\n * in arbella's own data dir — NEVER in the backup repo, a manifest, the\n * README, git history, or arbella's machine config.\n * - The file is written with mode 0600 (owner read/write only) on every save,\n * and we proactively chmod it to 0600 even if it already existed, so a token\n * can never sit world-readable.\n * - Token VALUES are never logged or returned in error messages. Listing /\n * status surfaces metadata only (host, provider, createdAt, a masked hint).\n *\n * LIBRARY PURITY: no system clock is read here. When a\n * caller saves a token it passes the `createdAt` ISO string (the command layer\n * owns the clock); the store just records it.\n *\n * FILE IO goes through src/utils/fs.ts; the one extra primitive the FsService\n * doesn't expose — chmod-ing an existing file — uses node:fs/promises directly\n * and is best-effort (a platform without POSIX modes, e.g. some Windows volumes,\n * simply skips it).\n */\n\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { dataDir } from \"../../platform/os.js\";\nimport { fs } from \"../../utils/fs.js\";\n\nimport type { AuthProviderId } from \"./providers.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Stored shapes */\n/* -------------------------------------------------------------------------- */\n\n/** How a stored token was obtained (informational; shown in `auth status`). */\nexport type CredentialSource = \"device-flow\" | \"token-paste\";\n\n/**\n * One stored credential, keyed by host in the store. The `token` is the access\n * token used to build the authenticated clone/push URL. It is a SECRET — never\n * log or print it. Everything else is safe metadata.\n */\nexport interface StoredCredential {\n /** Host this credential authenticates (e.g. \"github.com\"). */\n host: string;\n /** Which provider issued it (so `status` can label it). */\n provider: AuthProviderId;\n /** The access token. SECRET — used only to build the auth URL. */\n token: string;\n /** OAuth token type (typically \"bearer\"); informational. */\n tokenType?: string;\n /** Granted scope string, when known. */\n scope?: string;\n /** Optional refresh token (GitLab). SECRET — same handling as `token`. */\n refreshToken?: string;\n /** ISO-8601 capture time, SUPPLIED BY CALLER (no clock in library code). */\n createdAt: string;\n /** How the token was acquired. */\n source: CredentialSource;\n}\n\n/** Public, secret-free view of a stored credential for status/listing. */\nexport interface CredentialInfo {\n host: string;\n provider: AuthProviderId;\n scope?: string;\n createdAt: string;\n source: CredentialSource;\n /** A masked, non-reversible hint like \"ghp_…last4\" — safe to display. */\n tokenHint: string;\n /** Whether a refresh token is also stored (boolean only; never the value). */\n hasRefreshToken: boolean;\n}\n\n/** On-disk container. Versioned so the format can evolve compatibly. */\ninterface CredentialFile {\n version: 1;\n /** Credentials keyed by host. */\n hosts: Record<string, StoredCredential>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Constants */\n/* -------------------------------------------------------------------------- */\n\n/** Owner-only permissions: a credential file must never be world-readable. */\nconst CREDENTIALS_MODE = 0o600;\n\n/** Current on-disk format version. */\nconst FILE_VERSION = 1 as const;\n\n/* -------------------------------------------------------------------------- */\n/* Location */\n/* -------------------------------------------------------------------------- */\n\n/** Absolute path to the credential store: `dataDir()/credentials.json`. */\nexport function credentialsPath(): string {\n return path.join(dataDir(), \"credentials.json\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Load */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Load the whole credential file. A missing file yields an empty store (first\n * run). A present-but-corrupt file is tolerated as empty rather than throwing —\n * a damaged token cache must never block a backup/restore; the user simply\n * re-authenticates. (We do not surface the parse error's text, which could in\n * principle echo file bytes.)\n */\nasync function loadFile(): Promise<CredentialFile> {\n const file = credentialsPath();\n if (!(await fs.exists(file))) {\n return { version: FILE_VERSION, hosts: {} };\n }\n\n let raw: string;\n try {\n raw = await fs.read(file);\n } catch {\n return { version: FILE_VERSION, hosts: {} };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return { version: FILE_VERSION, hosts: {} };\n }\n\n return normalizeFile(parsed);\n}\n\n/** Coerce arbitrary parsed JSON into a well-formed CredentialFile (best-effort). */\nfunction normalizeFile(parsed: unknown): CredentialFile {\n if (typeof parsed !== \"object\" || parsed === null) {\n return { version: FILE_VERSION, hosts: {} };\n }\n const obj = parsed as Record<string, unknown>;\n const hostsRaw =\n typeof obj.hosts === \"object\" && obj.hosts !== null\n ? (obj.hosts as Record<string, unknown>)\n : {};\n\n const hosts: Record<string, StoredCredential> = {};\n for (const [host, value] of Object.entries(hostsRaw)) {\n const cred = normalizeCredential(host, value);\n if (cred) hosts[cred.host] = cred;\n }\n return { version: FILE_VERSION, hosts };\n}\n\n/** Validate a single stored credential; returns null when unusable. */\nfunction normalizeCredential(host: string, value: unknown): StoredCredential | null {\n if (typeof value !== \"object\" || value === null) return null;\n const v = value as Record<string, unknown>;\n\n const token = typeof v.token === \"string\" ? v.token : \"\";\n if (token === \"\") return null; // a credential with no token is useless.\n\n const provider: AuthProviderId =\n v.provider === \"gitlab\" ? \"gitlab\" : \"github\";\n const source: CredentialSource =\n v.source === \"token-paste\" ? \"token-paste\" : \"device-flow\";\n const normHost =\n typeof v.host === \"string\" && v.host.trim() !== \"\"\n ? v.host.trim().toLowerCase()\n : host.trim().toLowerCase();\n\n return {\n host: normHost,\n provider,\n token,\n ...(typeof v.tokenType === \"string\" ? { tokenType: v.tokenType } : {}),\n ...(typeof v.scope === \"string\" ? { scope: v.scope } : {}),\n ...(typeof v.refreshToken === \"string\" ? { refreshToken: v.refreshToken } : {}),\n createdAt:\n typeof v.createdAt === \"string\" ? v.createdAt : new Date(0).toISOString(),\n source,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Save */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Persist the whole file with strict 0600 permissions. We write via fs.write\n * (which passes the mode to writeFile) AND then chmod the existing inode, so a\n * pre-existing, looser-permissioned file is tightened too.\n */\nasync function saveFile(data: CredentialFile): Promise<void> {\n const file = credentialsPath();\n await fs.ensureDir(path.dirname(file));\n const json = `${JSON.stringify(data, null, 2)}\\n`;\n await fs.write(file, json, CREDENTIALS_MODE);\n await chmodBestEffort(file, CREDENTIALS_MODE);\n}\n\n/** chmod that never throws (no-op where modes aren't supported). */\nasync function chmodBestEffort(file: string, mode: number): Promise<void> {\n try {\n await fsp.chmod(file, mode);\n } catch {\n // Platform without POSIX modes (or a transient error): the 0600 passed to\n // writeFile is our primary guard; this is belt-and-braces.\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Public API: per-host operations */\n/* -------------------------------------------------------------------------- */\n\n/** Normalize a host key the way detection/storage expects (lowercase, no port). */\nfunction hostKey(host: string): string {\n return host.trim().toLowerCase().replace(/:\\d+$/, \"\");\n}\n\n/** Return the stored credential for a host, or undefined if none. */\nexport async function getCredential(host: string): Promise<StoredCredential | undefined> {\n const data = await loadFile();\n return data.hosts[hostKey(host)];\n}\n\n/** True when a usable token is stored for `host`. */\nexport async function hasCredential(host: string): Promise<boolean> {\n return (await getCredential(host)) !== undefined;\n}\n\n/**\n * Store (or replace) the credential for its host. The credential's own `host`\n * field is the key (normalized). Returns the normalized credential as stored.\n */\nexport async function saveCredential(cred: StoredCredential): Promise<StoredCredential> {\n const data = await loadFile();\n const normalized: StoredCredential = { ...cred, host: hostKey(cred.host) };\n data.hosts[normalized.host] = normalized;\n await saveFile(data);\n return normalized;\n}\n\n/**\n * Remove the credential for `host`. Returns true if one was removed, false if\n * there was nothing stored. When the store becomes empty the file is left in\n * place (an empty `{ hosts: {} }`), still 0600.\n */\nexport async function deleteCredential(host: string): Promise<boolean> {\n const data = await loadFile();\n const key = hostKey(host);\n if (!(key in data.hosts)) return false;\n delete data.hosts[key];\n await saveFile(data);\n return true;\n}\n\n/**\n * Remove every stored credential (used by `auth logout` with no provider).\n * Returns the number of hosts cleared.\n */\nexport async function clearAllCredentials(): Promise<number> {\n const data = await loadFile();\n const count = Object.keys(data.hosts).length;\n if (count === 0) return 0;\n await saveFile({ version: FILE_VERSION, hosts: {} });\n return count;\n}\n\n/**\n * Remove every stored credential for a given provider id (used by `auth logout\n * --provider github`). Returns the number of hosts cleared.\n */\nexport async function deleteCredentialsForProvider(\n provider: AuthProviderId,\n): Promise<number> {\n const data = await loadFile();\n let count = 0;\n for (const [host, cred] of Object.entries(data.hosts)) {\n if (cred.provider === provider) {\n delete data.hosts[host];\n count += 1;\n }\n }\n if (count > 0) await saveFile(data);\n return count;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Listing (metadata only — never token values) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * List all stored credentials as SECRET-FREE {@link CredentialInfo} records, in\n * stable host order. Safe for `auth status` output: the token itself is reduced\n * to a masked hint that reveals only a short suffix.\n */\nexport async function listCredentials(): Promise<CredentialInfo[]> {\n const data = await loadFile();\n return Object.values(data.hosts)\n .map((c) => toInfo(c))\n .sort((a, b) => (a.host < b.host ? -1 : a.host > b.host ? 1 : 0));\n}\n\n/** Project a stored credential to its secret-free info view. */\nexport function toInfo(cred: StoredCredential): CredentialInfo {\n return {\n host: cred.host,\n provider: cred.provider,\n ...(cred.scope ? { scope: cred.scope } : {}),\n createdAt: cred.createdAt,\n source: cred.source,\n tokenHint: maskToken(cred.token),\n hasRefreshToken: typeof cred.refreshToken === \"string\" && cred.refreshToken !== \"\",\n };\n}\n\n/**\n * Produce a non-reversible display hint for a token: show at most the last 4\n * characters, everything else as a fixed ellipsis. Never reveals enough to\n * reconstruct the token. Very short tokens are fully masked.\n */\nexport function maskToken(token: string): string {\n const t = token ?? \"\";\n if (t.length <= 4) return \"…\";\n return `…${t.slice(-4)}`;\n}\n","/**\n * Provider-agnostic auth orchestration for arbella — the public surface the\n * commands + the repo layer consume.\n *\n * Goal (A1/A2): make arbella able to clone/push a PRIVATE backup repo over https\n * WITHOUT depending on `gh`/`glab`. It does so by obtaining an OAuth access\n * token (device flow) or a pasted Personal Access Token, caching it locally\n * (0600), and rewriting an https remote into an authenticated form\n * `https://oauth2:<token>@host/owner/repo.git` — WITHOUT ever mutating the\n * user's global git config.\n *\n * The headline export is {@link ensureRepoAuth}. Given a repo URL it returns an\n * {@link RepoAuth} describing whether auth is needed and, if so, a ready-to-use\n * authenticated URL. The repo layer (core/repo) calls it like this:\n *\n * try {\n * await git.clone(repo.url, dest); // try unauthenticated\n * } catch (err) {\n * if (!isAuthFailure(err)) throw err; // not an auth problem\n * const auth = await ensureRepoAuth({ repoUrl: repo.url, interactive: true });\n * if (!auth.authUrl) throw err; // nothing we can do\n * await git.clone(auth.authUrl, dest); // retry with creds\n * }\n *\n * Public repos keep working: the contract is \"try unauthenticated first, only\n * authenticate when access is actually required/denied\". {@link ensureRepoAuth}\n * never forces auth on its own — callers decide when to invoke it (on failure,\n * or when a private repo is known up front via `requireAuth: true`).\n *\n * SECURITY (HARD):\n * - A token is returned only inside {@link RepoAuth.authUrl} (in memory) and is\n * cached only by the store (0600, arbella data dir). It is NEVER logged,\n * printed, written to the repo / manifest / README / git history, or placed\n * in a normal (non-debug) log line. The verification URL + user code printed\n * during the device flow are NOT secrets.\n * - {@link redactAuthUrl} is exported so callers can safely log an auth URL\n * (it replaces the token with `oauth2:***`). Use it anywhere a URL might be\n * surfaced.\n *\n * NETWORK: device flow uses the Node 18+ global `fetch` (no node-fetch).\n * USER OUTPUT: all human-facing lines go through src/utils/log.ts.\n * CLOCK: no clock here — callers may pass `createdAt`;\n * when omitted we still store a token but with an epoch timestamp, leaving the\n * authoritative \"now\" to the command layer (which supplies it).\n */\n\nimport { log } from \"../../utils/log.js\";\nimport type { DependencyId, InstallOutcome } from \"../../platform/install.js\";\n\nimport {\n cliForProvider,\n isProviderCliInstalled,\n providerCliAuthStatus,\n providerCliLogin,\n providerCliLogout,\n type CliAuthState,\n type ProviderCli,\n} from \"./cli.js\";\nimport {\n DeviceFlowError,\n runDeviceFlow,\n type DeviceCodePrompt,\n} from \"./device-flow.js\";\nimport {\n buildEndpoints,\n detectProvider,\n parseHost,\n providerHasClientId,\n type AuthProviderId,\n type AuthProviderSpec,\n} from \"./providers.js\";\nimport {\n clearAllCredentials,\n deleteCredential,\n deleteCredentialsForProvider,\n getCredential,\n listCredentials,\n saveCredential,\n type CredentialInfo,\n type StoredCredential,\n} from \"./store.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Install-on-demand seam (provider CLI) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The auth core never imports `@clack/prompts` and never calls the platform\n * installer directly — both are injected by the command layer so this module\n * stays UI- and side-effect-light and trivially testable. When a provider CLI\n * (gh/glab) is the preferred path but is MISSING, {@link ensureRepoAuth} uses\n * these two seams to (1) ask the user whether to install it and (2) install it.\n * When either seam is absent, the install-the-CLI branch is skipped and auth\n * degrades straight to the device-flow / token-paste fallback.\n */\nexport interface CliInstaller {\n /**\n * Ask the user to confirm installing the provider CLI. Resolve true to\n * install, false to decline (and fall back). `cli` carries the label/binary\n * for a friendly prompt.\n */\n confirm(args: { cli: ProviderCli; host: string }): Promise<boolean>;\n /**\n * Install the dependency (e.g. \"gh\"/\"glab\") via the platform installer.\n * Returns the platform {@link InstallOutcome} so the orchestrator can tell a\n * successful install from an unsupported/failed one.\n */\n install(dependency: DependencyId): Promise<InstallOutcome>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Interactive prompt seam */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The interactive token-paste prompt. The auth core does NOT import\n * `@clack/prompts` directly — the command layer injects a prompt function so\n * this module stays free of UI deps and trivially testable. When no prompter is\n * provided (non-interactive contexts: hooks, tests), the paste fallback is\n * skipped and auth resolves to \"no credentials\" rather than hanging on stdin.\n */\nexport interface TokenPaster {\n /**\n * Ask the user to paste a token. `hint` is a help line pointing at the\n * provider's PAT page. Resolve with the trimmed token, or `null` if the user\n * cancels / declines.\n */\n (args: { provider: AuthProviderSpec; host: string; hint: string }): Promise<\n string | null\n >;\n}\n\n/* -------------------------------------------------------------------------- */\n/* ensureRepoAuth */\n/* -------------------------------------------------------------------------- */\n\n/** Inputs to {@link ensureRepoAuth}. */\nexport interface EnsureRepoAuthArgs {\n /** The backup repo URL (the host is parsed from it). */\n repoUrl: string;\n /**\n * When true, arbella WILL try to obtain a token now (use a stored one, else\n * run the device flow / token paste). When false, it only uses an already\n * stored token and never prompts — handy for a pre-flight check. Defaults to\n * true.\n */\n interactive?: boolean;\n /**\n * Force authentication even if the caller hasn't seen a failure yet (e.g. the\n * repo is known to be private). Purely advisory: it does not change behavior\n * beyond `interactive` — it exists so callers can express intent and so the\n * messaging is accurate. Defaults to false.\n */\n requireAuth?: boolean;\n /**\n * Optional client_id overrides (per provider) read from the arbella config by\n * the caller. Lets a configured client_id win over the env var without this\n * module importing the config schema.\n */\n clientIdOverrides?: Partial<Record<AuthProviderId, string>>;\n /**\n * Interactive token-paste prompter (injected by the command layer). When\n * absent, the paste fallback is unavailable (device flow may still run).\n */\n paste?: TokenPaster;\n /**\n * Provider-CLI install-on-demand seam (injected by the command layer). When\n * present and a preferred CLI (gh/glab) is missing, arbella offers to install\n * it (via `confirm` + `install`) before falling back to the device flow. When\n * absent, a missing CLI degrades straight to device-flow / token-paste.\n */\n installer?: CliInstaller;\n /**\n * Skip the provider-CLI (gh/glab) path entirely and go straight to arbella's\n * own device-flow / token-paste engine. Default false. Used by callers that\n * specifically want an arbella-held token (rare) or by tests.\n */\n preferDeviceFlow?: boolean;\n /** ISO timestamp to stamp a newly-stored token with (command layer owns the clock). */\n createdAt?: string;\n}\n\n/**\n * The outcome of an auth attempt for a repo URL.\n *\n * - `needsAuth` : false for hosts arbella cannot/should not inject creds\n * into (ssh, file://, generic non-https, unknown host with\n * no token path) — the caller should just use the original\n * URL.\n * - `useProviderCli`: true when a provider CLI (gh/glab) is installed AND logged\n * in for the host. In that case git's credential helper is\n * already configured, so the caller should clone/push the\n * ORIGINAL url unchanged (there is no `authUrl` — and that is\n * the preferred, most secure outcome: no token touches\n * arbella). `cli` names the CLI that handled it.\n * - `authUrl` : when present, an https URL with an arbella-obtained token\n * embedded, safe to hand to `git clone/push`. Only produced\n * by the device-flow / token-paste fallback. Undefined when\n * the CLI path is used or no token is available.\n * - `provider` : the detected provider id, when one was matched.\n * - `host` : the parsed host, when one was found.\n * - `source` : how the token used here was obtained, when one was used.\n * - `cli` : the provider CLI binary (\"gh\"/\"glab\") when that path won.\n * - `reason` : a short, secret-free explanation (for debug logging).\n */\nexport interface RepoAuth {\n needsAuth: boolean;\n useProviderCli?: boolean;\n authUrl?: string;\n provider?: AuthProviderId;\n host?: string;\n source?: StoredCredential[\"source\"];\n cli?: string;\n reason: string;\n}\n\n/**\n * Ensure (as far as possible) that arbella can authenticate to the host behind\n * `repoUrl`, returning a usable authenticated clone/push URL when it can.\n *\n * Flow (gh/glab-first, device-flow/token fallback):\n * 1. Parse the host. Only https(-style) remotes can carry credentials this way;\n * ssh / file:// / scp-style / hostless URLs are returned untouched\n * (`needsAuth: false`) so existing SSH + local remotes behave exactly as\n * before.\n * 2. If an arbella-stored token already exists for the host -> build + return\n * the auth URL (the user previously chose the fallback; honor it).\n * 3. PREFERRED — provider CLI (gh/glab), when the host maps to a known provider:\n * a. CLI installed AND logged in -> return `useProviderCli:true` with NO\n * authUrl: git's credential helper is already wired, so the caller\n * clones the original URL. Nothing sensitive flows through arbella.\n * b. CLI installed but logged out -> (interactive) run `<cli> auth login`\n * with inherited stdio; on success -> `useProviderCli:true`.\n * c. CLI missing -> (interactive, installer injected) offer to install it;\n * if accepted, install then login -> `useProviderCli:true`.\n * Any non-success here falls through to step 4 rather than failing.\n * 4. FALLBACK — arbella's own engine: if not interactive, report \"needs auth\".\n * Otherwise, if a real OAuth App client_id is configured for the provider,\n * run the device flow; else (or on device-flow failure) use interactive\n * token paste. Store the obtained token (0600) and return the auth URL.\n * 5. If everything is exhausted, return \"needs auth\" with no `authUrl`/CLI so\n * the caller can surface a clear error.\n *\n * This function NEVER mutates global git config and NEVER logs a token.\n */\nexport async function ensureRepoAuth(args: EnsureRepoAuthArgs): Promise<RepoAuth> {\n const interactive = args.interactive ?? true;\n const host = parseHost(args.repoUrl);\n\n // (1) Non-https / hostless remotes: leave them exactly as-is.\n if (!host) {\n return {\n needsAuth: false,\n reason: \"URL has no http(s) host (ssh/file/local remote); using it as-is.\",\n };\n }\n if (!isHttpUrl(args.repoUrl)) {\n return {\n needsAuth: false,\n host,\n reason: \"Non-https remote (ssh/scp-style); credential injection not applicable.\",\n };\n }\n\n // (2) Honor an existing arbella-stored token (the user previously used the\n // fallback for this host; don't second-guess it).\n const existing = await getCredential(host);\n if (existing) {\n log.debug(`auth: using stored credential for ${host} (no token logged).`);\n return {\n needsAuth: true,\n authUrl: buildAuthenticatedUrl(args.repoUrl, existing.token),\n provider: existing.provider,\n host,\n source: existing.source,\n reason: \"Reused a stored token for this host.\",\n };\n }\n\n const provider = detectProvider(host);\n\n // (3) PREFERRED: try the provider CLI (gh/glab) for a known provider.\n if (provider && !args.preferDeviceFlow) {\n const viaCli = await tryProviderCli({\n provider,\n host,\n interactive,\n installer: args.installer,\n });\n if (viaCli) {\n return viaCli;\n }\n // else: CLI unavailable / login declined / failed — fall through to fallback.\n }\n\n // (4) FALLBACK: non-interactive can't prompt; report and let the caller decide.\n if (!interactive) {\n return {\n needsAuth: true,\n host,\n ...(provider ? { provider: provider.id } : {}),\n reason:\n \"No stored token, provider CLI unavailable, and non-interactive; \" +\n \"cannot obtain credentials now.\",\n };\n }\n\n // arbella's own engine: device flow when a client_id is configured, else paste.\n const acquired = await acquireToken({\n host,\n provider,\n clientIdOverrides: args.clientIdOverrides,\n paste: args.paste,\n createdAt: args.createdAt,\n requireAuth: args.requireAuth ?? false,\n });\n\n if (!acquired) {\n return {\n needsAuth: true,\n host,\n ...(provider ? { provider: provider.id } : {}),\n reason:\n \"Could not authenticate (provider CLI unavailable/declined, device flow \" +\n \"unavailable/declined, and no token pasted).\",\n };\n }\n\n return {\n needsAuth: true,\n authUrl: buildAuthenticatedUrl(args.repoUrl, acquired.token),\n provider: acquired.provider,\n host,\n source: acquired.source,\n reason: `Obtained a new token via ${acquired.source}.`,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Provider-CLI path (preferred) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Attempt to satisfy auth via the provider CLI (gh/glab). Returns a {@link\n * RepoAuth} with `useProviderCli:true` when the CLI ends up installed AND logged\n * in for the host (git is then ready, no token in arbella). Returns null when the\n * CLI route cannot be completed (not installed and not installed-on-demand, login\n * declined / failed), so the caller falls back to the device flow / token paste.\n *\n * Steps:\n * - status check; if already authenticated -> done (best outcome).\n * - if installed but logged out and interactive -> `<cli> auth login`.\n * - if missing and interactive and an installer was injected -> confirm +\n * install, then log in.\n */\nasync function tryProviderCli(args: {\n provider: AuthProviderSpec;\n host: string;\n interactive: boolean;\n installer?: CliInstaller;\n}): Promise<RepoAuth | null> {\n const { provider, host, interactive, installer } = args;\n const cli = cliForProvider(provider.id);\n\n let state: CliAuthState = await providerCliAuthStatus(provider.id, host);\n\n // Best case: already logged in. git's credential helper is configured.\n if (state === \"authenticated\") {\n return cliAuthResult(provider.id, host, cli);\n }\n\n // CLI missing: offer install-on-demand (needs an interactive installer seam).\n if (state === \"absent\") {\n if (!interactive || !installer) {\n log.debug(\n `auth: ${cli.label} not installed and no interactive installer; ` +\n \"falling back.\",\n );\n return null;\n }\n const installed = await offerInstallCli({ cli, host, installer });\n if (!installed) {\n return null; // user declined / install failed -> fall back.\n }\n // Installed now but certainly logged out; continue to the login step.\n state = \"not-authenticated\";\n }\n\n // Installed but not logged in: run the interactive login (inherited stdio).\n if (!interactive) {\n log.debug(`auth: ${cli.label} present but not logged in (non-interactive).`);\n return null;\n }\n\n const ok = await providerCliLogin(provider.id, { host });\n if (!ok) {\n return null; // login cancelled/failed -> fall back to device flow / paste.\n }\n\n // Re-verify so we only claim success when the CLI truly authenticated.\n if (await isProviderCliAuthenticatedSafe(provider.id, host)) {\n return cliAuthResult(provider.id, host, cli);\n }\n log.debug(`auth: ${cli.label} login returned ok but status still not logged in.`);\n return null;\n}\n\n/** Offer to install the provider CLI, then install it. Returns true on success. */\nasync function offerInstallCli(args: {\n cli: ProviderCli;\n host: string;\n installer: CliInstaller;\n}): Promise<boolean> {\n const { cli, host, installer } = args;\n let agreed = false;\n try {\n agreed = await installer.confirm({ cli, host });\n } catch (err) {\n log.debug(`auth: install confirmation failed: ${errMessage(err)}`);\n return false;\n }\n if (!agreed) {\n log.info(\n `Skipping ${cli.label} install; using arbella's own sign-in instead.`,\n );\n return false;\n }\n\n log.step(`Installing ${cli.label}…`);\n let outcome: InstallOutcome;\n try {\n outcome = await installer.install(cli.dependency);\n } catch (err) {\n log.warn(`Could not install ${cli.label}: ${errMessage(err)}`);\n return false;\n }\n if (outcome.status === \"installed\" || outcome.status === \"already\") {\n return true;\n }\n log.warn(\n `${cli.label} could not be installed (${outcome.status})` +\n (outcome.message ? `: ${outcome.message}` : \"\") +\n \". Falling back to arbella's own sign-in.\",\n );\n return false;\n}\n\n/** Build the success RepoAuth for the provider-CLI path (no token involved). */\nfunction cliAuthResult(\n provider: AuthProviderId,\n host: string,\n cli: ProviderCli,\n): RepoAuth {\n log.debug(`auth: ${cli.label} is authenticated for ${host}; using its git creds.`);\n return {\n needsAuth: true,\n useProviderCli: true,\n provider,\n host,\n cli: cli.bin,\n reason: `Authenticated via ${cli.label}; git credential helper is configured.`,\n };\n}\n\n/** Status recheck that never throws (absence/error -> false). */\nasync function isProviderCliAuthenticatedSafe(\n provider: AuthProviderId,\n host: string,\n): Promise<boolean> {\n try {\n return (await providerCliAuthStatus(provider, host)) === \"authenticated\";\n } catch {\n return false;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Token acquisition (device flow -> paste fallback) */\n/* -------------------------------------------------------------------------- */\n\n/** Internal: the token + how it was obtained, after a successful acquisition. */\ninterface AcquiredToken {\n token: string;\n provider: AuthProviderId;\n source: StoredCredential[\"source\"];\n}\n\n/**\n * Acquire and STORE a token for `host`. Tries the device flow first when the\n * provider is known and a client_id is configured; otherwise (or if the device\n * flow fails for a non-fatal reason) falls back to interactive token paste.\n * Returns null when no token could be obtained.\n *\n * The chosen provider for storage is the detected one, or — for an unknown host\n * reached via paste — we still record a best-effort provider id (\"github\") only\n * as a label; it never affects URL building (which is provider-independent).\n */\nasync function acquireToken(args: {\n host: string;\n provider: AuthProviderSpec | undefined;\n clientIdOverrides?: Partial<Record<AuthProviderId, string>>;\n paste?: TokenPaster;\n createdAt?: string;\n requireAuth: boolean;\n}): Promise<AcquiredToken | null> {\n const { host, provider } = args;\n\n // ---- Device flow (preferred) when runnable ------------------------------\n if (provider && providerHasClientId(provider, args.clientIdOverrides)) {\n try {\n const token = await loginViaDeviceFlow({\n provider,\n clientIdOverrides: args.clientIdOverrides,\n });\n const stored = await persist({\n host,\n provider: provider.id,\n token,\n source: \"device-flow\",\n createdAt: args.createdAt,\n });\n return { token: stored.token, provider: provider.id, source: \"device-flow\" };\n } catch (err) {\n // A hard refusal (the user denied) should not silently fall through to a\n // second prompt; everything else degrades to the paste fallback.\n if (err instanceof DeviceFlowError && err.code === \"access_denied\") {\n log.warn(\"Authorization was denied; no token was stored.\");\n return null;\n }\n log.warn(\n `Device flow did not complete (${errMessage(err)}). ` +\n \"Falling back to token paste.\",\n );\n // fall through to paste\n }\n } else if (provider) {\n log.debug(\n `auth: no OAuth App client_id configured for ${provider.displayName}; ` +\n \"using token paste (set the client_id env var to enable the device flow).\",\n );\n }\n\n // ---- Interactive token paste (fallback / unknown host) ------------------\n if (!args.paste) {\n // No prompter available (non-interactive UI). Nothing more we can do.\n return null;\n }\n\n const spec = provider ?? guessSpecForPaste(host);\n const token = await args.paste({ provider: spec, host, hint: spec.patHint });\n if (token === null || token.trim() === \"\") {\n return null;\n }\n const stored = await persist({\n host,\n provider: spec.id,\n token: token.trim(),\n source: \"token-paste\",\n createdAt: args.createdAt,\n });\n return { token: stored.token, provider: spec.id, source: \"token-paste\" };\n}\n\n/**\n * Run the device flow for a provider and return the access token (in memory).\n * Prints the verification URL + user code via the logger (those are NOT\n * secrets). Throws {@link DeviceFlowError} on failure.\n */\nasync function loginViaDeviceFlow(args: {\n provider: AuthProviderSpec;\n clientIdOverrides?: Partial<Record<AuthProviderId, string>>;\n}): Promise<string> {\n const endpoints = buildEndpoints(args.provider, args.clientIdOverrides);\n const result = await runDeviceFlow({\n endpoints,\n onPrompt: (prompt) => announcePrompt(args.provider, prompt),\n onPoll: ({ attempt }) =>\n log.debug(`auth: polling ${args.provider.displayName} for token (attempt ${attempt})…`),\n });\n return result.accessToken;\n}\n\n/** Print the device-flow instructions. Verification URL + user code are safe. */\nfunction announcePrompt(provider: AuthProviderSpec, prompt: DeviceCodePrompt): void {\n log.info(`Authorize arbella with ${provider.displayName} to access your backup repo.`);\n log.step(`1. Open: ${prompt.verificationUri}`);\n log.step(`2. Enter the code: ${prompt.userCode}`);\n if (prompt.verificationUriComplete) {\n log.step(` (or open this pre-filled link: ${prompt.verificationUriComplete})`);\n }\n log.step(\"Waiting for authorization… (this window will continue automatically)\");\n}\n\n/** Persist an acquired token and return the normalized stored credential. */\nasync function persist(args: {\n host: string;\n provider: AuthProviderId;\n token: string;\n source: StoredCredential[\"source\"];\n createdAt?: string;\n}): Promise<StoredCredential> {\n const stored = await saveCredential({\n host: args.host,\n provider: args.provider,\n token: args.token,\n source: args.source,\n // Library purity: when the caller didn't supply a timestamp we record the\n // epoch rather than read the clock here; the command layer passes a real one.\n createdAt: args.createdAt ?? new Date(0).toISOString(),\n });\n log.success(`Stored a ${args.provider} token for ${args.host} (local, 0600).`);\n return stored;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Command helpers: login / status / logout */\n/* -------------------------------------------------------------------------- */\n\n/** How an explicit `arbella auth login` was satisfied. */\nexport type LoginMethod = \"provider-cli\" | \"device-flow\" | \"token-paste\";\n\n/** Result of an explicit `auth login`. */\nexport interface LoginResult {\n host: string;\n provider: AuthProviderId;\n /** Which mechanism logged the user in. */\n method: LoginMethod;\n /**\n * For the fallback paths, how the arbella-stored token was obtained. Absent\n * when the provider CLI handled it (arbella stored nothing).\n */\n source?: StoredCredential[\"source\"];\n}\n\n/**\n * Explicit login for `arbella auth login [--provider]` (P4). PREFERS the provider\n * CLI: if gh/glab is installed it runs `<cli> auth login` (installing it first\n * when an installer is injected and the user agrees); only if the CLI is\n * unavailable / declined does it fall back to arbella's device flow or token\n * paste. Authenticates against the provider's canonical host (github.com /\n * gitlab.com). Returns the login result, or null if nothing was obtained.\n */\nexport async function login(args: {\n provider: AuthProviderSpec;\n clientIdOverrides?: Partial<Record<AuthProviderId, string>>;\n paste?: TokenPaster;\n installer?: CliInstaller;\n /** Skip the provider CLI and force arbella's own engine. Default false. */\n preferDeviceFlow?: boolean;\n createdAt?: string;\n}): Promise<LoginResult | null> {\n const host = args.provider.host;\n\n // Preferred: the provider CLI.\n if (!args.preferDeviceFlow) {\n const viaCli = await tryProviderCli({\n provider: args.provider,\n host,\n interactive: true,\n installer: args.installer,\n });\n if (viaCli?.useProviderCli) {\n return { host, provider: args.provider.id, method: \"provider-cli\" };\n }\n }\n\n // Fallback: arbella's own device flow / token paste.\n const acquired = await acquireToken({\n host,\n provider: args.provider,\n clientIdOverrides: args.clientIdOverrides,\n paste: args.paste,\n createdAt: args.createdAt,\n requireAuth: true,\n });\n if (!acquired) return null;\n return {\n host,\n provider: acquired.provider,\n method: acquired.source === \"device-flow\" ? \"device-flow\" : \"token-paste\",\n source: acquired.source,\n };\n}\n\n/** Login state for one provider, combining its CLI and arbella's own store. */\nexport interface ProviderAuthStatus {\n provider: AuthProviderId;\n /** Whether the provider CLI is installed. */\n cliInstalled: boolean;\n /** The CLI's login state (\"absent\" when not installed). */\n cliState: CliAuthState;\n /** The provider CLI binary name (\"gh\"/\"glab\"). */\n cli: string;\n}\n\n/**\n * Per-provider auth status for `arbella auth status` (P4), reflecting the\n * gh/glab-first model: it reports each provider CLI's install + login state.\n * arbella-held tokens (the fallback) are reported separately via {@link status}.\n * Secret-free: only install/login booleans + provider labels.\n */\nexport async function providerStatuses(): Promise<ProviderAuthStatus[]> {\n const out: ProviderAuthStatus[] = [];\n for (const id of [\"github\", \"gitlab\"] as AuthProviderId[]) {\n const cli = cliForProvider(id);\n const cliInstalled = await isProviderCliInstalled(id);\n const cliState = cliInstalled\n ? await providerCliAuthStatus(id)\n : (\"absent\" as CliAuthState);\n out.push({ provider: id, cliInstalled, cliState, cli: cli.bin });\n }\n return out;\n}\n\n/**\n * List arbella-STORED credentials as SECRET-FREE info for `arbella auth status`.\n * These exist only when the device-flow / token-paste fallback was used; the\n * token is reduced to a masked hint. (Provider-CLI logins are NOT here — they\n * live in gh/glab's own stores; see {@link providerStatuses}.)\n */\nexport async function status(): Promise<CredentialInfo[]> {\n return listCredentials();\n}\n\n/**\n * Log out for `arbella auth logout [--provider]` (P4). Clears BOTH surfaces:\n * - arbella's own stored token(s) for the provider(s), and\n * - the provider CLI's login (`<cli> auth logout`) when the CLI is present and\n * a single provider was specified (a no-provider logout does not touch the\n * CLIs — that would be surprising; it only clears arbella's own store).\n *\n * Returns the number of arbella-stored host credentials removed. The CLI logout\n * is best-effort and reported via logs (gh/glab manage their own state).\n */\nexport async function logout(provider?: AuthProviderId): Promise<number> {\n if (provider === undefined) {\n return clearAllCredentials();\n }\n // Also sign out of the provider CLI when present (best-effort).\n if (await isProviderCliInstalled(provider)) {\n await providerCliLogout(provider);\n }\n return deleteCredentialsForProvider(provider);\n}\n\n/** Remove the credential for an exact host (used by tests / advanced callers). */\nexport async function logoutHost(host: string): Promise<boolean> {\n return deleteCredential(host);\n}\n\n/* -------------------------------------------------------------------------- */\n/* URL building + redaction */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Rewrite an https repo URL into an authenticated form that embeds `token` as\n * the password of the `oauth2` user: `https://oauth2:<token>@host/owner/repo.git`.\n * Works for both GitHub and GitLab (both accept `oauth2:<token>` basic auth over\n * https). Any pre-existing userinfo in the URL is replaced.\n *\n * The token is URL-encoded so a token containing reserved characters can't break\n * the URL. Non-https URLs are returned unchanged (we never inject creds into ssh\n * or file remotes). This builds an in-memory URL only — it does not touch git\n * config.\n */\nexport function buildAuthenticatedUrl(repoUrl: string, token: string): string {\n if (!isHttpUrl(repoUrl)) return repoUrl;\n let u: URL;\n try {\n u = new URL(repoUrl.trim());\n } catch {\n return repoUrl;\n }\n u.username = \"oauth2\";\n u.password = encodeURIComponent(token);\n return u.toString();\n}\n\n/**\n * Redact the credentials out of an https URL for safe logging:\n * `https://oauth2:***@host/owner/repo.git`. Use this anywhere an auth URL might\n * be printed. Non-https or unparseable URLs are returned unchanged (they carry\n * no embedded token to leak).\n */\nexport function redactAuthUrl(url: string): string {\n if (!isHttpUrl(url)) return url;\n try {\n const u = new URL(url);\n if (u.username || u.password) {\n u.username = u.username || \"oauth2\";\n u.password = \"***\";\n }\n return u.toString();\n } catch {\n return url;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Auth-failure classification (so the repo layer can decide to retry) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Heuristically decide whether a thrown error from a git clone/push/pull is an\n * AUTHENTICATION/authorization failure (so the caller should obtain creds and\n * retry) versus an unrelated error (which must propagate).\n *\n * Git itself does not give a stable machine code for this, so we inspect the\n * combined stderr/message text for the well-known phrases GitHub/GitLab/git emit\n * on a private-repo access denial or a missing credential. We deliberately treat\n * \"repository not found\" as an auth signal too: GitHub returns a 404 (not a 403)\n * for a private repo the caller can't see, so a not-found over https is, in\n * practice, usually a permissions problem worth one authenticated retry.\n *\n * This reads ONLY error metadata (message/stderr) and returns a boolean; it\n * never logs and never touches the token.\n */\nexport function isAuthFailure(err: unknown): boolean {\n const text = extractErrorText(err).toLowerCase();\n if (text === \"\") return false;\n\n // Strong, unambiguous auth signals.\n const authPhrases = [\n \"authentication failed\",\n \"authorization failed\",\n \"could not read username\",\n \"could not read password\",\n \"terminal prompts disabled\",\n \"invalid username or password\",\n \"permission denied\",\n \"403 forbidden\",\n \"401 unauthorized\",\n // git/curl smart-HTTP phrasing for a rejected request, e.g.\n // \"fatal: unable to access '…': The requested URL returned error: 403\".\n \"returned error: 401\",\n \"returned error: 403\",\n \"error: 401\",\n \"error: 403\",\n \"access denied\",\n \"remote: invalid credentials\",\n \"remote: not authorized\",\n \"fatal: unable to access\",\n \"support for password authentication was removed\",\n ];\n for (const phrase of authPhrases) {\n if (text.includes(phrase)) return true;\n }\n\n // \"Repository not found\" over https is usually a private-repo permission\n // problem (GitHub masks 403 as 404). Treat it as an auth signal so we retry\n // with credentials exactly once.\n if (\n text.includes(\"repository not found\") ||\n (text.includes(\"not found\") && text.includes(\"fatal:\")) ||\n text.includes(\"the requested url returned error: 404\")\n ) {\n return true;\n }\n\n return false;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal helpers */\n/* -------------------------------------------------------------------------- */\n\n/** True for an http/https URL (the only remotes we inject credentials into). */\nfunction isHttpUrl(url: string): boolean {\n return /^https?:\\/\\//i.test(url.trim());\n}\n\n/**\n * For an unknown host reached via token paste, synthesize a minimal provider\n * spec so the paste prompt + storage have a label + a sensible PAT hint. The id\n * defaults to \"github\" (most common) but is purely a label; URL building is\n * provider-independent.\n */\nfunction guessSpecForPaste(host: string): AuthProviderSpec {\n return {\n id: \"github\",\n displayName: host,\n host,\n deviceCodeUrl: \"\",\n tokenUrl: \"\",\n scope: \"\",\n clientIdEnvVar: \"\",\n patHelpUrl: `https://${host}`,\n patHint:\n `Paste a personal access token for ${host} with permission to read and ` +\n \"write the backup repository over https.\",\n };\n}\n\n/** Pull a usable text blob out of an unknown thrown value (message + stderr). */\nfunction extractErrorText(err: unknown): string {\n if (err == null) return \"\";\n if (typeof err === \"string\") return err;\n if (err instanceof Error) {\n const parts: string[] = [err.message];\n const anyErr = err as { stderr?: unknown; stdout?: unknown; shortMessage?: unknown };\n if (typeof anyErr.stderr === \"string\") parts.push(anyErr.stderr);\n if (typeof anyErr.stdout === \"string\") parts.push(anyErr.stdout);\n if (typeof anyErr.shortMessage === \"string\") parts.push(anyErr.shortMessage);\n return parts.join(\"\\n\");\n }\n // Plain object with execa-like fields.\n const o = err as { message?: unknown; stderr?: unknown; shortMessage?: unknown };\n const parts: string[] = [];\n if (typeof o.message === \"string\") parts.push(o.message);\n if (typeof o.stderr === \"string\") parts.push(o.stderr);\n if (typeof o.shortMessage === \"string\") parts.push(o.shortMessage);\n return parts.length > 0 ? parts.join(\"\\n\") : String(err);\n}\n\n/** Best-effort message extraction from an unknown thrown value (secret-free). */\nfunction errMessage(err: unknown): string {\n if (err instanceof DeviceFlowError) return `${err.code}: ${err.message}`;\n return err instanceof Error ? err.message : String(err);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Re-exports for the command layer */\n/* -------------------------------------------------------------------------- */\n\nexport {\n detectProvider,\n parseHost,\n providerById,\n providerHasClientId,\n resolveClientId,\n AUTH_PROVIDER_SPECS,\n PLACEHOLDER_CLIENT_ID,\n type AuthProviderId,\n type AuthProviderSpec,\n} from \"./providers.js\";\n\nexport {\n credentialsPath,\n getCredential,\n hasCredential,\n listCredentials,\n type CredentialInfo,\n type StoredCredential,\n type CredentialSource,\n} from \"./store.js\";\n\nexport {\n DeviceFlowError,\n runDeviceFlow,\n type DeviceFlowToken,\n type DeviceCodePrompt,\n type DeviceFlowEndpoints,\n} from \"./device-flow.js\";\n\nexport {\n cliForProvider,\n isProviderCliInstalled,\n isProviderCliAuthenticated,\n providerCliAuthStatus,\n providerCliLogin,\n providerCliLogout,\n type ProviderCli,\n type CliAuthState,\n type ProviderLoginOptions,\n} from \"./cli.js\";\n","/**\n * Thin git wrapper over `execa`. The ONE place that shells out to `git`.\n *\n * Every function takes an absolute `cwd` (the backup repo's local working copy).\n * We always pass args as an array to `execa(\"git\", [...], { cwd })` — never a\n * shell string — so there is no shell interpolation / injection surface.\n *\n * Error policy: a non-zero git exit THROWS, EXCEPT for\n * the functions documented to return a boolean (`isGitRepo`, `commit`,\n * `hasRemote`), where a clean boolean is the contract. Those use `reject:false`\n * and inspect the exit code instead of throwing.\n */\n\nimport { execa } from \"execa\";\n\nimport { log } from \"../../utils/log.js\";\n\n/** Normalized result of a git invocation. */\nexport interface GitResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Environment forced on EVERY git invocation so git never blocks on an\n * interactive credential prompt. Git reads credential prompts from /dev/tty,\n * NOT stdin — so `stdin:\"ignore\"` is not enough; without this a private clone\n * hangs at `Username for 'https://…':`. `GIT_TERMINAL_PROMPT=0` makes a\n * missing-credential operation FAIL FAST instead, which the auth layer catches\n * (isAuthFailure) and then handles via gh/glab login or the device-flow/token\n * fallback. `GCM_INTERACTIVE=never` likewise silences Git Credential Manager.\n * A configured credential helper (e.g. gh's, after login) still supplies creds\n * normally — this only disables the interactive *fallback* prompt.\n */\nconst NONINTERACTIVE_GIT_ENV = {\n GIT_TERMINAL_PROMPT: \"0\",\n GCM_INTERACTIVE: \"never\",\n} as const;\n\n/**\n * Run git in `cwd`. When `reject` is false the promise resolves even on a\n * non-zero exit so callers can branch on `exitCode` themselves.\n */\nasync function git(\n cwd: string,\n args: string[],\n opts: { reject?: boolean } = {},\n): Promise<GitResult> {\n const reject = opts.reject ?? true;\n // Redact any credential embedded in a URL arg before logging, so a token can\n // never appear even at debug volume (defense-in-depth; the auth layer also\n // redacts before logging URLs it produces).\n log.debug(`git ${args.map(redactArg).join(\" \")} (cwd=${cwd})`);\n let res;\n try {\n res = await execa(\"git\", args, {\n cwd,\n reject,\n // Keep output as strings; do not inherit stdio so we can capture/redact.\n stdout: \"pipe\",\n stderr: \"pipe\",\n // git should never need stdin for the porcelain we run; closing it avoids\n // hangs on prompts (e.g. credential helpers in non-interactive contexts).\n stdin: \"ignore\",\n // Fail fast instead of prompting on the tty for a missing credential, so\n // the auth layer can take over (see NONINTERACTIVE_GIT_ENV).\n env: NONINTERACTIVE_GIT_ENV,\n });\n } catch (err) {\n // SECURITY: on a non-zero exit, execa throws an error whose `message`,\n // `shortMessage`, `command`, `escapedCommand`, and `stderr` echo the FULL\n // argv — including any credential-embedded URL (e.g. a one-shot\n // `https://oauth2:<token>@host/...` push/pull). That error propagates to the\n // top-level handler which logs `err.message` to stderr (a NON-debug surface).\n // Re-throw a token-redacted clone so a token can never leak via an error,\n // while preserving the text `isAuthFailure` keys off (the failure phrasing is\n // untouched; only the embedded secret is masked).\n throw redactExecaError(err);\n }\n return {\n stdout: typeof res.stdout === \"string\" ? res.stdout : \"\",\n stderr: typeof res.stderr === \"string\" ? res.stderr : \"\",\n exitCode: typeof res.exitCode === \"number\" ? res.exitCode : 0,\n };\n}\n\n/**\n * Mask userinfo in an http(s) URL argument so a credential-embedded clone/pull/\n * push URL never appears in a (debug) log line. Non-URL args are returned as-is.\n * `https://oauth2:TOKEN@host/x.git` -> `https://oauth2:***@host/x.git`.\n */\nfunction redactArg(arg: string): string {\n if (!/^https?:\\/\\//i.test(arg)) return arg;\n try {\n const u = new URL(arg);\n if (u.username || u.password) {\n u.username = u.username || \"oauth2\";\n u.password = \"***\";\n }\n return u.toString();\n } catch {\n return arg;\n }\n}\n\n/**\n * Mask the userinfo of ANY http(s) URL embedded ANYWHERE inside a free-text blob\n * (an execa error message/command line/stderr, not a clean standalone URL).\n * Replaces the `user:password@` portion of `scheme://user:password@host…` with\n * `oauth2:***@`, so a one-shot credential-embedded clone/pull/push URL cannot\n * leak through an error string. Leaves everything else (incl. the failure\n * phrasing isAuthFailure keys off) intact.\n */\nfunction redactText(text: string): string {\n // scheme://[user[:password]]@host — capture scheme+\"//\" and the host tail.\n return text.replace(\n /\\b(https?:\\/\\/)[^/@\\s]+@/gi,\n (_m, scheme: string) => `${scheme}oauth2:***@`,\n );\n}\n\n/**\n * Re-throw an execa (or any) error with every credential-bearing field redacted.\n * execa surfaces the full argv in several places (`message`, `shortMessage`,\n * `command`, `escapedCommand`) plus the captured `stderr`/`stdout`; all are\n * scrubbed. The returned Error keeps `stderr`/`stdout`/`shortMessage`/`exitCode`\n * so `isAuthFailure` (which scans message+stderr+shortMessage) still classifies\n * it correctly — only the embedded token is masked. Non-Error inputs are coerced\n * to a redacted-string Error.\n */\nfunction redactExecaError(err: unknown): Error {\n if (!(err instanceof Error)) {\n return new Error(redactText(String(err)));\n }\n const src = err as Error & {\n stderr?: unknown;\n stdout?: unknown;\n shortMessage?: unknown;\n command?: unknown;\n escapedCommand?: unknown;\n exitCode?: unknown;\n };\n const out = new Error(redactText(err.message)) as Error & Record<string, unknown>;\n out.name = err.name;\n // Carry over (redacted) the diagnostic fields callers + the auth classifier read.\n if (typeof src.stderr === \"string\") out.stderr = redactText(src.stderr);\n if (typeof src.stdout === \"string\") out.stdout = redactText(src.stdout);\n if (typeof src.shortMessage === \"string\") out.shortMessage = redactText(src.shortMessage);\n if (typeof src.command === \"string\") out.command = redactText(src.command);\n if (typeof src.escapedCommand === \"string\") {\n out.escapedCommand = redactText(src.escapedCommand);\n }\n if (typeof src.exitCode === \"number\") out.exitCode = src.exitCode;\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Queries */\n/* -------------------------------------------------------------------------- */\n\n/** True when `cwd` is inside a git work tree. Never throws. */\nexport async function isGitRepo(cwd: string): Promise<boolean> {\n const res = await git(cwd, [\"rev-parse\", \"--is-inside-work-tree\"], {\n reject: false,\n });\n return res.exitCode === 0 && res.stdout.trim() === \"true\";\n}\n\n/** Current branch name (e.g. \"main\"). Throws if not a repo / detached + unknown. */\nexport async function currentBranch(cwd: string): Promise<string> {\n const res = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n return res.stdout.trim();\n}\n\n/**\n * True if a remote named `name` (default \"origin\") is configured. Never throws;\n * returns false when not a repo or the remote is absent.\n */\nexport async function hasRemote(cwd: string, name = \"origin\"): Promise<boolean> {\n const res = await git(cwd, [\"remote\"], { reject: false });\n if (res.exitCode !== 0) return false;\n return res.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean)\n .includes(name);\n}\n\n/**\n * True if `branch` (default: current branch) has an upstream tracking ref.\n * Used to decide whether the first push must set upstream. Never throws.\n */\nexport async function hasUpstream(cwd: string, branch?: string): Promise<boolean> {\n const ref = branch ?? \"HEAD\";\n const res = await git(cwd, [\"rev-parse\", \"--abbrev-ref\", `${ref}@{upstream}`], {\n reject: false,\n });\n return res.exitCode === 0;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Mutations */\n/* -------------------------------------------------------------------------- */\n\n/** `git init` in `cwd` (creates the dir's repo metadata). Throws on failure. */\nexport async function gitInit(cwd: string): Promise<void> {\n await git(cwd, [\"init\"]);\n}\n\n/**\n * Clone `url` into `dest` (a path that does not yet exist or is empty). We run\n * the clone from `dest`'s parent and pass the absolute destination so behavior\n * is independent of the caller's process cwd. Throws on failure.\n */\nexport async function clone(url: string, dest: string): Promise<void> {\n // Never log the URL itself (it may carry an arbella-embedded token).\n log.debug(`git clone <url> -> ${dest}`);\n try {\n await execa(\"git\", [\"clone\", url, dest], {\n reject: true,\n stdout: \"pipe\",\n stderr: \"pipe\",\n stdin: \"ignore\",\n // Fail fast on a missing credential instead of prompting on the tty, so the\n // auth layer (gh/glab login or token fallback) handles a private repo.\n env: NONINTERACTIVE_GIT_ENV,\n });\n } catch (err) {\n // SECURITY: same leak surface as the shared git() runner — a failed clone of\n // a credential-embedded URL would otherwise echo the token in the thrown\n // error's message/command. Redact before re-throwing so the auth retry's\n // failure can be reported (and classified by isAuthFailure) without leaking.\n throw redactExecaError(err);\n }\n}\n\n/** Stage every change in the work tree (`git add -A`). Throws on failure. */\nexport async function addAll(cwd: string): Promise<void> {\n await git(cwd, [\"add\", \"-A\"]);\n}\n\n/**\n * Commit staged changes with `message`. Returns false (no commit made) when\n * there is nothing staged, so callers can report \"no changes\" without an error.\n * Throws on any other git failure.\n */\nexport async function commit(cwd: string, message: string): Promise<boolean> {\n // Fast path: if nothing is staged, skip the commit entirely. `--quiet`\n // exit code 0 => no staged differences, 1 => there are staged changes.\n const staged = await git(cwd, [\"diff\", \"--cached\", \"--quiet\"], {\n reject: false,\n });\n if (staged.exitCode === 0) {\n return false;\n }\n const res = await git(cwd, [\"commit\", \"-m\", message], { reject: false });\n if (res.exitCode === 0) {\n return true;\n }\n // Some git versions still report \"nothing to commit\" as a non-zero exit even\n // after the diff check (e.g. only-mode changes). Treat that as \"no commit\".\n const combined = `${res.stdout}\\n${res.stderr}`.toLowerCase();\n if (\n combined.includes(\"nothing to commit\") ||\n combined.includes(\"no changes added to commit\")\n ) {\n return false;\n }\n throw new Error(`git commit failed (exit ${res.exitCode}): ${res.stderr.trim()}`);\n}\n\n/**\n * Push to the remote. With `setUpstream`, sets the upstream tracking ref for the\n * given/current branch (`-u origin <branch>`), which is needed on the first push\n * of a freshly-created remote. Throws on failure.\n */\nexport async function push(\n cwd: string,\n opts: { setUpstream?: boolean; branch?: string } = {},\n): Promise<void> {\n const args = [\"push\"];\n if (opts.setUpstream) {\n const branch = opts.branch ?? (await currentBranch(cwd));\n args.push(\"-u\", \"origin\", branch);\n } else if (opts.branch) {\n args.push(\"origin\", opts.branch);\n }\n await git(cwd, args);\n}\n\n/**\n * Push the current branch to an EXPLICIT url (one-shot) without mutating the\n * stored `origin` remote. The auth-layer counterpart to {@link pushTo}'s sibling\n * {@link pullFrom}: when arbella has its own token it builds a\n * credential-embedded URL and pushes to it directly, leaving `.git/config` free\n * of any token. Pushes the current branch HEAD->HEAD by name. Throws on failure.\n *\n * SECURITY: `url` may embed a token; it is an argv element (no shell) and the\n * git() debug logger redacts embedded credentials.\n */\nexport async function pushTo(cwd: string, url: string): Promise<void> {\n const branch = await currentBranch(cwd);\n await git(cwd, [\"push\", url, `HEAD:${branch}`]);\n}\n\n/**\n * Pull from the remote with rebase + autostash so a dirty local tree does not\n * block fast-forwards (R12 repo-as-source uses this before restore). Throws on\n * unresolved conflicts / network errors.\n */\nexport async function pull(cwd: string): Promise<void> {\n await git(cwd, [\"pull\", \"--rebase\", \"--autostash\"]);\n}\n\n/**\n * Pull from an EXPLICIT url (one-shot) without mutating the stored remote. Used\n * by the auth layer's retry: when arbella obtains a token it builds a\n * credential-embedded URL and pulls from it directly, so the token never lands in\n * `.git/config` (the configured `origin` remote is left clean). The current\n * branch is pulled with the same rebase + autostash policy as {@link pull}.\n *\n * SECURITY: `url` may embed a token — it is passed as an argv element (no shell)\n * and is never logged here (git.ts logs argv at debug; the auth layer redacts\n * before logging, and this path is only reached with verbose off in normal use).\n */\nexport async function pullFrom(cwd: string, url: string): Promise<void> {\n const branch = await currentBranch(cwd);\n await git(cwd, [\"pull\", \"--rebase\", \"--autostash\", url, branch]);\n}\n\n/** Add or update a named remote (`git remote add` / fallback `set-url`). */\nexport async function setRemote(\n cwd: string,\n name: string,\n url: string,\n): Promise<void> {\n const exists = await hasRemote(cwd, name);\n if (exists) {\n await git(cwd, [\"remote\", \"set-url\", name, url]);\n } else {\n await git(cwd, [\"remote\", \"add\", name, url]);\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Status / diff */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Split a NUL-terminated porcelain/diff stream into trimmed records. We request\n * `-z` everywhere so paths with spaces/newlines survive intact.\n */\nfunction splitNul(raw: string): string[] {\n return raw.split(\"\\0\").filter((s) => s.length > 0);\n}\n\n/**\n * Working-tree status via `git status --porcelain -z`. Returns one entry per\n * changed path: `{ path, status }` where `status` is the 2-char XY code\n * (e.g. \"??\" untracked, \" M\" modified, \"A \" added). Empty array => clean tree.\n * Throws only if `cwd` is not a git repo.\n */\nexport async function status(\n cwd: string,\n): Promise<Array<{ path: string; status: string }>> {\n const res = await git(cwd, [\"status\", \"--porcelain\", \"-z\"]);\n const out: Array<{ path: string; status: string }> = [];\n const records = splitNul(res.stdout);\n for (let i = 0; i < records.length; i++) {\n const rec = records[i];\n // Porcelain v1 record: \"XY <path>\"; XY is exactly 2 chars, then a space.\n const code = rec.slice(0, 2);\n let p = rec.slice(3);\n // Renames/copies (R/C in XY) emit the new path in this record and the old\n // path as the NEXT NUL field; consume + ignore the old path.\n if (code[0] === \"R\" || code[0] === \"C\") {\n i++; // skip the paired source path\n }\n out.push({ path: p, status: code });\n }\n return out;\n}\n\n/**\n * Name-status diff of the work tree (incl. untracked) vs HEAD, for the `status`\n * command's \"what would change\" view. Returns `{ path, status }` where status is\n * a single letter: A/M/D/R/C, or \"?\" for untracked files. Throws if not a repo.\n *\n * We combine a tracked diff (`git diff HEAD --name-status -z`) with the list of\n * untracked files (`git ls-files --others --exclude-standard -z`) so newly\n * created repo files show up before they are ever committed.\n */\nexport async function diffNameStatus(\n cwd: string,\n): Promise<Array<{ path: string; status: string }>> {\n const out: Array<{ path: string; status: string }> = [];\n\n // Tracked changes vs HEAD. If there is no HEAD yet (no commits), this throws;\n // fall back to treating everything as untracked below.\n let hasHead = true;\n const headCheck = await git(cwd, [\"rev-parse\", \"--verify\", \"HEAD\"], {\n reject: false,\n });\n if (headCheck.exitCode !== 0) hasHead = false;\n\n if (hasHead) {\n const res = await git(cwd, [\"diff\", \"HEAD\", \"--name-status\", \"-z\"]);\n const records = splitNul(res.stdout);\n for (let i = 0; i < records.length; i++) {\n const code = records[i];\n const letter = code[0] ?? \"M\";\n if (letter === \"R\" || letter === \"C\") {\n // For rename/copy, name-status emits: STATUS \\0 OLD \\0 NEW\n const newPath = records[i + 2];\n i += 2;\n if (newPath) out.push({ path: newPath, status: letter });\n } else {\n const p = records[i + 1];\n i += 1;\n if (p) out.push({ path: p, status: letter });\n }\n }\n }\n\n // Untracked files (excluding gitignored) -> \"?\".\n const untracked = await git(\n cwd,\n [\"ls-files\", \"--others\", \"--exclude-standard\", \"-z\"],\n { reject: false },\n );\n if (untracked.exitCode === 0) {\n for (const p of splitNul(untracked.stdout)) {\n out.push({ path: p, status: \"?\" });\n }\n }\n\n return out;\n}\n","/**\n * Generic repo provider (R11). For any git remote that arbella cannot (or\n * should not) auto-create: the user supplies a ready-made remote URL.\n *\n * There is no hosting API here, so:\n * - isAvailable() is always true (plain git is the only requirement),\n * - repoExists() assumes true (we cannot probe without a host API; the caller\n * will simply try to clone/push and surface any git error),\n * - createRepo() never creates anything — it just hands the input back as the\n * clone URL (and warns, since \"creation\" is a no-op for generic),\n * - resolveUrl() returns the input verbatim.\n */\n\nimport type { RepoProvider } from \"../../../types.js\";\nimport { log } from \"../../../utils/log.js\";\nimport type { RepoProviderApi } from \"../index.js\";\n\nexport const genericProvider: RepoProviderApi = {\n id: \"generic\" as RepoProvider,\n\n async isAvailable(): Promise<boolean> {\n // Plain git is assumed present; nothing host-specific to detect.\n return true;\n },\n\n async repoExists(_name: string): Promise<boolean> {\n // No host API to query — assume the user-supplied remote already exists.\n return true;\n },\n\n async createRepo(\n name: string,\n _opts: { private?: boolean; description?: string } = {},\n ): Promise<string> {\n // Nothing to create for a generic remote: the user must have provisioned it.\n log.warn(\n \"Generic provider does not create remotes; using the supplied URL as-is. \" +\n \"Create the (private) repository on your host first if it does not exist.\",\n );\n return this.resolveUrl(name);\n },\n\n async resolveUrl(input: string): Promise<string> {\n // The input IS the clone URL for a generic remote.\n return input.trim();\n },\n};\n\nexport default genericProvider;\n","/**\n * GitHub repo provider (R11). Creates a PRIVATE backup repo via the `gh` CLI.\n *\n * We never create a public repo — the backup repo holds sanitized but still\n * personal config, so `createRepo` always passes `--private` unless the caller\n * explicitly opts out (which `init` never does).\n *\n * All shelling out goes through `execa(\"gh\", [...])` with array args (no shell\n * interpolation). When `gh` is missing or unauthenticated we surface that as a\n * thrown error from the create/exists paths, but `isAvailable()` stays a clean\n * boolean so callers can fall back to the generic provider.\n */\n\nimport { execa } from \"execa\";\n\nimport type { RepoProvider } from \"../../../types.js\";\nimport { log } from \"../../../utils/log.js\";\nimport type { RepoProviderApi } from \"../index.js\";\n\n/** True if the `gh` binary runs at all (so we can probe further). */\nasync function ghPresent(): Promise<boolean> {\n try {\n await execa(\"gh\", [\"--version\"], { reject: true, stdin: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run `gh` capturing stdout; throws (with a friendly message) on failure. */\nasync function gh(args: string[]): Promise<string> {\n log.debug(`gh ${args.join(\" \")}`);\n try {\n const res = await execa(\"gh\", args, {\n reject: true,\n stdout: \"pipe\",\n stderr: \"pipe\",\n stdin: \"ignore\",\n });\n return typeof res.stdout === \"string\" ? res.stdout : \"\";\n } catch (err) {\n const e = err as { stderr?: string; shortMessage?: string };\n const detail = (e.stderr && e.stderr.trim()) || e.shortMessage || String(err);\n throw new Error(`gh ${args[0] ?? \"\"} failed: ${detail}`);\n }\n}\n\n/**\n * Pick the preferred clone URL from a `gh repo view --json sshUrl,url` payload.\n * Prefer SSH (matches typical push auth for a private repo); fall back to the\n * https web URL turned into a .git clone URL.\n */\nfunction pickCloneUrl(json: { sshUrl?: string; url?: string }): string {\n if (json.sshUrl && json.sshUrl.length > 0) return json.sshUrl;\n if (json.url && json.url.length > 0) {\n return json.url.endsWith(\".git\") ? json.url : `${json.url}.git`;\n }\n throw new Error(\"gh repo view returned no usable clone URL\");\n}\n\n/**\n * Normalize provider input into the `OWNER/NAME` form `gh` expects, accepting:\n * - \"owner/name\"\n * - \"name\" (gh resolves to the authed user)\n * - \"https://github.com/o/n\" -> \"o/n\"\n * - \"git@github.com:o/n.git\" -> \"o/n\"\n * Returns the slug; an unrecognized full URL for a different host is returned\n * unchanged so `resolveUrl` can still hand it back verbatim.\n */\nfunction toSlug(input: string): string {\n const trimmed = input.trim();\n // git@host:owner/name(.git)\n const ssh = trimmed.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n if (ssh) return ssh[1];\n // https://host/owner/name(.git)\n const https = trimmed.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?\\/?$/);\n if (https) return https[1];\n return trimmed.replace(/\\.git$/, \"\");\n}\n\nexport const githubProvider: RepoProviderApi = {\n id: \"github\" as RepoProvider,\n\n async isAvailable(): Promise<boolean> {\n return ghPresent();\n },\n\n async repoExists(name: string): Promise<boolean> {\n const slug = toSlug(name);\n try {\n // `gh repo view` exits non-zero (-> throws here) when the repo is absent\n // or inaccessible; we treat any failure as \"does not exist for us\".\n await gh([\"repo\", \"view\", slug, \"--json\", \"name\"]);\n return true;\n } catch {\n return false;\n }\n },\n\n async createRepo(\n name: string,\n opts: { private?: boolean; description?: string } = {},\n ): Promise<string> {\n const slug = toSlug(name);\n const isPrivate = opts.private ?? true; // R11: default PRIVATE, never public.\n const args = [\"repo\", \"create\", slug, isPrivate ? \"--private\" : \"--public\"];\n if (opts.description) {\n args.push(\"--description\", opts.description);\n }\n // Do not clone here; the repo orchestrator (index.ts) owns the local clone.\n args.push(\"--clone=false\");\n await gh(args);\n log.success(`Created private GitHub repo ${slug}`);\n return this.resolveUrl(slug);\n },\n\n async resolveUrl(input: string): Promise<string> {\n const trimmed = input.trim();\n // A full clone URL for github was given -> hand it back unchanged.\n if (/^git@/.test(trimmed) || /^https?:\\/\\//.test(trimmed)) {\n // If it's a github URL we could still normalize, but returning verbatim\n // honors exactly what the user supplied for `generic`-style inputs.\n if (/github\\.com/.test(trimmed)) return trimmed;\n }\n const slug = toSlug(trimmed);\n const raw = await gh([\"repo\", \"view\", slug, \"--json\", \"sshUrl,url\"]);\n let parsed: { sshUrl?: string; url?: string };\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Could not parse gh repo view output for ${slug}`);\n }\n return pickCloneUrl(parsed);\n },\n};\n\nexport default githubProvider;\n","/**\n * GitLab repo provider (R11). Creates a PRIVATE backup repo via the `glab` CLI.\n *\n * Mirrors the GitHub provider: array-only `execa` calls, always `--private`\n * (never public), `isAvailable()` is a clean boolean so the orchestrator can\n * fall back to the generic provider when `glab` is missing/unauthenticated.\n *\n * `glab repo view <path> -F json` returns a project object whose clone URLs are\n * `ssh_url_to_repo` / `http_url_to_repo` (with `web_url` as a last resort).\n */\n\nimport { execa } from \"execa\";\n\nimport type { RepoProvider } from \"../../../types.js\";\nimport { log } from \"../../../utils/log.js\";\nimport type { RepoProviderApi } from \"../index.js\";\n\n/** True if the `glab` binary runs at all. */\nasync function glabPresent(): Promise<boolean> {\n try {\n await execa(\"glab\", [\"--version\"], { reject: true, stdin: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Run `glab` capturing stdout; throws (with a friendly message) on failure. */\nasync function glab(args: string[]): Promise<string> {\n log.debug(`glab ${args.join(\" \")}`);\n try {\n const res = await execa(\"glab\", args, {\n reject: true,\n stdout: \"pipe\",\n stderr: \"pipe\",\n stdin: \"ignore\",\n });\n return typeof res.stdout === \"string\" ? res.stdout : \"\";\n } catch (err) {\n const e = err as { stderr?: string; shortMessage?: string };\n const detail = (e.stderr && e.stderr.trim()) || e.shortMessage || String(err);\n throw new Error(`glab ${args[0] ?? \"\"} failed: ${detail}`);\n }\n}\n\n/** Project JSON shape (subset) returned by `glab repo view -F json`. */\ninterface GlabProject {\n ssh_url_to_repo?: string;\n http_url_to_repo?: string;\n web_url?: string;\n}\n\n/** Prefer SSH, then http(s) clone URL, then derive one from web_url. */\nfunction pickCloneUrl(p: GlabProject): string {\n if (p.ssh_url_to_repo && p.ssh_url_to_repo.length > 0) return p.ssh_url_to_repo;\n if (p.http_url_to_repo && p.http_url_to_repo.length > 0) return p.http_url_to_repo;\n if (p.web_url && p.web_url.length > 0) {\n return p.web_url.endsWith(\".git\") ? p.web_url : `${p.web_url}.git`;\n }\n throw new Error(\"glab repo view returned no usable clone URL\");\n}\n\n/**\n * Normalize provider input into the `namespace/name` path `glab` expects.\n * Accepts \"group/name\", \"name\", a full https URL, or an ssh URL. Unrecognized\n * inputs are returned with any trailing \".git\" stripped.\n */\nfunction toPath(input: string): string {\n const trimmed = input.trim();\n const ssh = trimmed.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n if (ssh) return ssh[1];\n const https = trimmed.match(/^https?:\\/\\/[^/]+\\/(.+?)(?:\\.git)?\\/?$/);\n if (https) return https[1];\n return trimmed.replace(/\\.git$/, \"\");\n}\n\nexport const gitlabProvider: RepoProviderApi = {\n id: \"gitlab\" as RepoProvider,\n\n async isAvailable(): Promise<boolean> {\n return glabPresent();\n },\n\n async repoExists(name: string): Promise<boolean> {\n const p = toPath(name);\n try {\n await glab([\"repo\", \"view\", p, \"-F\", \"json\"]);\n return true;\n } catch {\n return false;\n }\n },\n\n async createRepo(\n name: string,\n opts: { private?: boolean; description?: string } = {},\n ): Promise<string> {\n const p = toPath(name);\n const isPrivate = opts.private ?? true; // R11: default PRIVATE, never public.\n const args = [\"repo\", \"create\", p, isPrivate ? \"--private\" : \"--public\"];\n if (opts.description) {\n args.push(\"--description\", opts.description);\n }\n await glab(args);\n log.success(`Created private GitLab repo ${p}`);\n return this.resolveUrl(p);\n },\n\n async resolveUrl(input: string): Promise<string> {\n const trimmed = input.trim();\n if (/^git@/.test(trimmed) || /^https?:\\/\\//.test(trimmed)) {\n if (/gitlab/.test(trimmed)) return trimmed;\n }\n const p = toPath(trimmed);\n const raw = await glab([\"repo\", \"view\", p, \"-F\", \"json\"]);\n let parsed: GlabProject;\n try {\n parsed = JSON.parse(raw) as GlabProject;\n } catch {\n throw new Error(`Could not parse glab repo view output for ${p}`);\n }\n return pickCloneUrl(parsed);\n },\n};\n\nexport default gitlabProvider;\n","/**\n * Repo orchestration: provider selection + the high-level git flows the\n * commands call (init/backup/status/restore). This module is the single source\n * of truth for the `RepoProviderApi` shape — the three providers import the TYPE\n * from here (type-only, so there is no runtime import cycle even though this\n * file imports their concrete singletons).\n *\n * Path handling goes through node:path and src/utils/fs.ts; no hardcoded\n * separators, no machine paths. All git work is delegated to ./git.js.\n */\n\nimport path from \"node:path\";\n\nimport type { RepoProvider } from \"../../types.js\";\nimport { fs } from \"../../utils/fs.js\";\nimport { log } from \"../../utils/log.js\";\nimport type { RepoConfig } from \"../config/schema.js\";\nimport {\n ensureRepoAuth,\n isAuthFailure,\n redactAuthUrl,\n type EnsureRepoAuthArgs,\n type RepoAuth,\n} from \"../auth/index.js\";\n\nimport * as git from \"./git.js\";\nimport { genericProvider } from \"./providers/generic.js\";\nimport { githubProvider } from \"./providers/github.js\";\nimport { gitlabProvider } from \"./providers/gitlab.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Auth integration (P5) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interactive auth seams threaded from the COMMAND layer down into a clone/pull.\n * The repo layer must stay free of UI deps (@clack) and the platform installer\n * import cycle, so commands pass these in. They are the SAME seams `ensureRepoAuth`\n * accepts (token paste + provider-CLI install-on-demand) plus the timestamp the\n * command owns. When omitted, auth still runs but only NON-interactively: it uses\n * an already-logged-in gh/glab or an already-stored token, and never prompts.\n */\nexport type RepoAuthHooks = Pick<\n EnsureRepoAuthArgs,\n \"interactive\" | \"paste\" | \"installer\" | \"clientIdOverrides\" | \"preferDeviceFlow\" | \"createdAt\"\n>;\n\n/**\n * Run an authenticated git operation against `repoUrl` with one retry on an auth\n * failure. The first attempt uses the ORIGINAL url (so public repos and\n * already-working credential helpers need nothing). If it fails with an\n * auth/permission signal AND `hooks` allow it, we call {@link ensureRepoAuth}\n * (gh/glab-first, device-flow/token fallback) and retry — using the provider CLI\n * (original url, helper-configured) when that path wins, or the token-embedded\n * `authUrl` when arbella obtained a token itself.\n *\n * `op(url, credentialed)` performs the git work for a given url; `credentialed`\n * is true ONLY on the fallback retry when `url` carries an embedded arbella token\n * (so a clone op can scrub the persisted remote afterward — see ensureLocalClone).\n * Returns nothing; throws the original error when auth can't help (so callers see\n * the real failure). NEVER logs a token: only {@link redactAuthUrl}-safe URLs and\n * secret-free reasons.\n */\nasync function withRepoAuth(\n repoUrl: string,\n hooks: RepoAuthHooks | undefined,\n op: (url: string, credentialed: boolean) => Promise<void>,\n): Promise<void> {\n try {\n await op(repoUrl, false);\n return;\n } catch (err) {\n if (!isAuthFailure(err)) {\n throw err; // not an auth problem — propagate verbatim.\n }\n log.debug(\"repo: git operation failed with an auth signal; attempting sign-in.\");\n\n let auth: RepoAuth;\n try {\n auth = await ensureRepoAuth({\n repoUrl,\n interactive: hooks?.interactive ?? true,\n requireAuth: true,\n ...(hooks?.paste ? { paste: hooks.paste } : {}),\n ...(hooks?.installer ? { installer: hooks.installer } : {}),\n ...(hooks?.clientIdOverrides\n ? { clientIdOverrides: hooks.clientIdOverrides }\n : {}),\n ...(hooks?.preferDeviceFlow !== undefined\n ? { preferDeviceFlow: hooks.preferDeviceFlow }\n : {}),\n ...(hooks?.createdAt ? { createdAt: hooks.createdAt } : {}),\n });\n } catch (authErr) {\n // Auth itself blew up (network, CLI crash). Surface the ORIGINAL git error\n // as the primary cause; note the auth failure at debug.\n log.debug(\n `repo: sign-in attempt failed: ${\n authErr instanceof Error ? authErr.message : String(authErr)\n }`,\n );\n throw err;\n }\n\n // Decide the retry URL: provider-CLI path keeps the original (git helper is\n // configured); arbella-token path uses the embedded-credential URL (flagged\n // `credentialed` so a clone op scrubs the persisted remote afterward).\n if (auth.useProviderCli) {\n log.debug(`repo: retrying using ${auth.cli ?? \"provider CLI\"} credentials.`);\n await op(repoUrl, false);\n } else if (auth.authUrl) {\n log.debug(`repo: retrying with credentials for ${redactAuthUrl(auth.authUrl)}.`);\n await op(auth.authUrl, true);\n } else {\n // Could not obtain any usable credential — re-throw the original error with\n // a friendlier hint appended.\n log.warn(\n \"Could not authenticate to the backup repo. \" +\n \"Install/sign in with `arbella auth login` (or gh/glab) and retry.\",\n );\n throw err;\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Provider contract (the single definition; providers import this type) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A hosting-provider adapter for the backup repo. `github`/`gitlab` shell out to\n * their CLIs; `generic` is a no-op creator over a user-supplied remote.\n */\nexport interface RepoProviderApi {\n /** Stable provider id. */\n readonly id: RepoProvider;\n /** Is the provider usable here? (gh/glab present; generic => always true.) */\n isAvailable(): Promise<boolean>;\n /** Does the named repo already exist for the user? (generic => assumed true.) */\n repoExists(name: string): Promise<boolean>;\n /**\n * Create a PRIVATE repo named `name` and return its clone URL (ssh or https).\n * `private` defaults to true; arbella never creates public backup repos.\n */\n createRepo(\n name: string,\n opts?: { private?: boolean; description?: string },\n ): Promise<string>;\n /** Resolve an \"owner/name\" or full URL into a clone URL for this provider. */\n resolveUrl(input: string): Promise<string>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Provider selection */\n/* -------------------------------------------------------------------------- */\n\n/** Return the provider implementation for a given id. */\nexport function getProvider(provider: RepoProvider): RepoProviderApi {\n switch (provider) {\n case \"github\":\n return githubProvider;\n case \"gitlab\":\n return gitlabProvider;\n case \"generic\":\n return genericProvider;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* High-level flows used by commands */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Ensure the configured backup repo is cloned locally at `repo.localPath`.\n *\n * - If `localPath` already holds a git repo: no-op (caller pulls separately\n * when it wants repo-as-source freshness).\n * - If `localPath` exists but is NOT a git repo and is non-empty: throw, rather\n * than risk clobbering unrelated files.\n * - Otherwise: `git clone repo.url localPath` (parent dir is created first).\n *\n * AUTH (P5): the clone is attempted unauthenticated first, so PUBLIC repos and\n * machines whose gh/glab/credential-helper is already set up need nothing. On an\n * auth/permission failure for a PRIVATE repo, {@link ensureRepoAuth} runs\n * (gh/glab-first; install-on-demand or device-flow/token fallback) and the clone\n * is retried with the resulting credentials — all WITHOUT mutating global git\n * config. `auth` carries the interactive seams from the command layer; when it is\n * omitted, the retry is non-interactive (reuse a logged-in CLI / stored token).\n *\n * ssh / file:// / generic remotes are untouched by auth (ensureRepoAuth returns\n * `needsAuth:false` for them), so they keep working exactly as before.\n *\n * Requires `repo.url` to be set; throws a friendly error if it is empty.\n */\nexport async function ensureLocalClone(\n repo: RepoConfig,\n auth?: RepoAuthHooks,\n): Promise<void> {\n const localPath = repo.localPath;\n if (!localPath) {\n throw new Error(\"repo.localPath is not configured; run `arbella init` first.\");\n }\n\n if (await git.isGitRepo(localPath)) {\n log.debug(`Backup repo already cloned at ${localPath}`);\n return;\n }\n\n // Not a git repo yet. If the directory exists and has content, refuse.\n if (await fs.exists(localPath)) {\n const entries = await fs.list(localPath);\n const meaningful = entries.filter((e) => e !== \".git\");\n if (meaningful.length > 0) {\n throw new Error(\n `Cannot clone backup repo: ${localPath} exists and is not empty. ` +\n \"Move it aside or point repo.localPath at a fresh directory.\",\n );\n }\n }\n\n if (!repo.url) {\n throw new Error(\n \"repo.url is not configured; cannot clone. Run `arbella init` first.\",\n );\n }\n\n // Ensure the parent directory exists so `git clone <url> <dest>` succeeds.\n await fs.ensureDir(path.dirname(localPath));\n log.step(`Cloning backup repo into ${localPath}`);\n await withRepoAuth(repo.url, auth, async (url, credentialed) => {\n await git.clone(url, localPath);\n // SECURITY: `git clone <token-url>` persists that URL as the `origin` remote\n // in .git/config — which would leave a token at rest in the repo. When we\n // cloned with an arbella-embedded credential, immediately rewrite `origin`\n // back to the clean URL so no token is ever stored. (Provider-CLI clones use\n // the clean URL already and need no scrub.)\n if (credentialed) {\n await git.setRemote(localPath, \"origin\", repo.url);\n log.debug(\"repo: scrubbed credential from origin remote after clone.\");\n }\n });\n}\n\n/**\n * Pull the latest into an already-cloned backup repo, with the same gh/glab-first\n * auth retry as {@link ensureLocalClone}. A plain `git pull` uses whatever remote\n * URL is configured (typically the original, helper-authenticated one); on an auth\n * failure we sign in and retry by pulling explicitly from the resolved URL so a\n * private repo refreshes without the user pre-configuring credentials.\n *\n * `repo.url` is used for the auth retry's remote; when it is empty we just do a\n * plain pull (and let any auth error surface).\n */\nexport async function pullWithAuth(\n repo: RepoConfig,\n auth?: RepoAuthHooks,\n): Promise<void> {\n if (!repo.url) {\n await git.pull(repo.localPath);\n return;\n }\n await withRepoAuth(repo.url, auth, async (url, credentialed) => {\n if (!credentialed) {\n // Original URL (provider CLI / public): a normal pull from origin.\n await git.pull(repo.localPath);\n } else {\n // Token-embedded URL (arbella fallback): pull explicitly from it so the\n // one-shot credential is used WITHOUT writing it into git config.\n await git.pullFrom(repo.localPath, url);\n }\n });\n}\n\n/**\n * Create the remote repo if missing and return the resolved RepoConfig (R11).\n *\n * Behavior:\n * - Pick the provider; if its CLI is unavailable, fall back to the GENERIC\n * provider (which resolves `name` to a URL verbatim and warns).\n * - If the repo does not already exist (per `repoExists`), create it PRIVATE.\n * - Resolve the final clone URL and return a RepoConfig pointing `localPath` at\n * the requested path.\n *\n * Never prints tokens; only the resolved URL is logged at debug level.\n */\nexport async function ensureRemoteRepo(args: {\n provider: RepoProvider;\n name: string;\n localPath: string;\n}): Promise<RepoConfig> {\n const { name, localPath } = args;\n let provider = getProvider(args.provider);\n\n if (!(await provider.isAvailable())) {\n if (provider.id !== \"generic\") {\n log.warn(\n `${provider.id} CLI not available; treating \"${name}\" as a generic ` +\n \"remote URL (no auto-create).\",\n );\n }\n provider = genericProvider;\n }\n\n let url: string;\n if (await provider.repoExists(name)) {\n log.debug(`Remote repo \"${name}\" already exists; resolving URL.`);\n url = await provider.resolveUrl(name);\n } else {\n log.step(`Creating private remote repo \"${name}\"`);\n url = await provider.createRepo(name, {\n private: true,\n description: \"arbella backup of AI dev setup\",\n });\n }\n\n log.debug(`Resolved backup repo clone URL for ${name}`);\n return {\n provider: provider.id,\n url,\n localPath,\n };\n}\n\n/**\n * Stage + commit + push the local working copy. Returns false when there was\n * nothing to commit (no changes) — in that case nothing is pushed.\n *\n * On the first push to a freshly-created remote there is no upstream yet, so we\n * detect \"no upstream / no remote tracking\" and retry with `-u origin <branch>`.\n * The commit `message` is supplied by the caller (commands own the clock).\n *\n * AUTH (P5): like clone/pull, the push is attempted as-is first (credential\n * helper / public access). On an auth failure, when `opts.url` is known,\n * {@link ensureRepoAuth} runs (gh/glab-first; install-on-demand or device-flow/\n * token fallback) and the push is retried — to `origin` when the provider CLI\n * configured git, or to a one-shot credential-embedded URL when arbella holds the\n * token (never written into `.git/config`). `opts.auth` carries the command\n * layer's interactive seams; without `opts.url` the push behaves exactly as\n * before (no auth retry).\n */\nexport async function commitAndPush(\n localPath: string,\n message: string,\n opts: { url?: string; auth?: RepoAuthHooks } = {},\n): Promise<boolean> {\n await git.addAll(localPath);\n const committed = await git.commit(localPath, message);\n if (!committed) {\n log.debug(\"No changes to commit; skipping push.\");\n return false;\n }\n\n // Decide whether we need to set upstream on this push: no remote yet, or a\n // remote exists but the current branch has no upstream tracking ref.\n let needUpstream = false;\n try {\n if (!(await git.hasRemote(localPath, \"origin\"))) {\n needUpstream = true;\n } else {\n needUpstream = !(await git.hasUpstream(localPath));\n }\n } catch {\n // If branch/remote probing fails for any reason, fall back to set-upstream.\n needUpstream = true;\n }\n\n // The push-to-origin attempt, with the existing set-upstream retry preserved.\n const pushToOrigin = async (): Promise<void> => {\n try {\n await git.push(localPath, { setUpstream: needUpstream });\n } catch (err) {\n // Retry once with explicit upstream — covers the \"has no upstream branch\"\n // case that some git versions only report at push time. (An auth failure is\n // re-thrown unchanged so the auth wrapper above can catch + handle it.)\n if (!needUpstream && !isAuthFailure(err)) {\n log.debug(\"Initial push failed; retrying with --set-upstream.\");\n await git.push(localPath, { setUpstream: true });\n } else {\n throw err;\n }\n }\n };\n\n if (!opts.url) {\n // No URL to authenticate against — preserve the original behavior exactly.\n await pushToOrigin();\n return true;\n }\n\n // Auth-aware: first attempt pushes to origin; an auth failure triggers sign-in,\n // then either re-push to origin (provider CLI) or push to the token URL.\n await withRepoAuth(opts.url, opts.auth, async (url, credentialed) => {\n if (!credentialed) {\n await pushToOrigin();\n } else {\n // arbella-token path: push to the one-shot credential URL (no config write).\n await git.pushTo(localPath, url);\n }\n });\n return true;\n}\n\n/**\n * Changed paths of the local working copy vs the committed repo, for the\n * `status` command. Delegates to git porcelain status.\n */\nexport async function repoStatus(\n localPath: string,\n): Promise<Array<{ path: string; status: string }>> {\n if (!(await git.isGitRepo(localPath))) {\n return [];\n }\n return git.status(localPath);\n}\n","/**\n * The SanitizerService implementation: secret detection & removal so that\n * secrets NEVER leave the machine.\n *\n * Two layers:\n * 1. DENYLIST (whole-file exclusion). `isDenied(rel)` answers \"is this path\n * universally junk/secret?\" using COMMON_DENY ONLY. The per-tool secret\n * files (auth.json, .credentials.json, *.sqlite, history.jsonl, ...) live in\n * denylist.ts and are applied by the ADAPTERS, which call\n * `matchesDeny(rel, denylistFor(tool))` directly — keeping per-tool deny\n * filtering where the tool context is known. This split is intentional: the\n * service stays tool-agnostic; adapters own tool-specific exclusion.\n * 2. VALUE REDACTION (in-place). For files that ARE shareable but may contain a\n * stray credential, `sanitizeText` redacts matches of SECRET_PATTERNS, and\n * `sanitizeJson` deep-clones a parsed object redacting any value whose KEY\n * is secret (isSecretKey) or whose VALUE matches a pattern.\n *\n * Pure-ish: operates on strings / already-parsed objects. TOML/JSON parsing and\n * re-serialization are done by callers; only `sanitizeJson` clones a parsed\n * object. No fs, and no system clock.\n */\n\nimport type {\n SanitizerService,\n SanitizeResult,\n SecretRef,\n ToolId,\n} from \"../../types.js\";\n\nimport { COMMON_DENY, matchesDeny } from \"./denylist.js\";\nimport {\n REDACTED,\n SECRET_PATTERNS,\n isSecretKey,\n type SecretPattern,\n} from \"./patterns.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Standalone value redaction */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Redact a single scalar value if it looks secret OR if `keyIsSecret` is true.\n * Returns the (possibly unchanged) value and the matched pattern name, if any.\n *\n * - Non-string scalars are returned untouched UNLESS the key is secret, in which\n * case they collapse to the REDACTED marker (a numeric/boolean \"secret\" is\n * still a secret).\n * - Strings: if the key is secret the whole string is replaced; otherwise each\n * embedded SECRET_PATTERNS match is swapped for REDACTED in place.\n */\nexport function redactValue(\n value: unknown,\n keyIsSecret = false,\n): { value: unknown; matched: string | null } {\n if (keyIsSecret) {\n // Empty strings carry no secret; leave them so we don't manufacture noise.\n if (typeof value === \"string\" && value.length === 0) {\n return { value, matched: null };\n }\n return { value: REDACTED, matched: \"secret-key\" };\n }\n\n if (typeof value !== \"string\" || value.length === 0) {\n return { value, matched: null };\n }\n\n let firstMatch: string | null = null;\n let out = value;\n for (const pat of SECRET_PATTERNS) {\n const re = freshRegex(pat.regex);\n if (re.test(out)) {\n if (firstMatch === null) firstMatch = pat.name;\n out = out.replace(freshRegex(pat.regex), REDACTED);\n }\n }\n return { value: out, matched: firstMatch };\n}\n\n/* -------------------------------------------------------------------------- */\n/* The service factory */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build a SanitizerService. Stateless, so the default export is a shared\n * singleton; a factory is provided for symmetry with the other core services and\n * for tests that want an isolated instance.\n */\nexport function createSanitizer(): SanitizerService {\n /**\n * Whole-file exclusion check, tool-agnostic. Uses COMMON_DENY only (OS cruft,\n * sqlite, caches). Adapters layer their per-tool denylist on top.\n */\n function isDenied(relativePath: string): boolean {\n return matchesDeny(relativePath, [...COMMON_DENY]);\n }\n\n /**\n * Redact secret VALUES inside free text. Walks SECRET_PATTERNS, replacing\n * every match with REDACTED, and records one SecretRef per distinct pattern\n * that fired (value-kind). `source` is the file's repo-relative location, used\n * to build a human-readable SecretRef.source like \"<source>#<pattern>\".\n */\n function sanitizeText(content: string, tool: ToolId, source: string): SanitizeResult {\n const found: SecretRef[] = [];\n let out = content;\n let changed = false;\n\n for (const pat of SECRET_PATTERNS) {\n if (!freshRegex(pat.regex).test(out)) continue;\n out = out.replace(freshRegex(pat.regex), REDACTED);\n changed = true;\n found.push(valueSecretRef(tool, source, pat));\n }\n\n return { content: out, found, changed };\n }\n\n /**\n * Deep-clone a parsed JSON/TOML-ish object, redacting any value whose key is\n * secret (isSecretKey) or whose string value matches a SECRET_PATTERNS entry.\n * Recurses through nested objects and arrays. Never mutates the input.\n *\n * `source` seeds SecretRef.source; each hit appends its key path, e.g.\n * \"settings.json#mcpServers.foo.env.API_KEY\".\n */\n function sanitizeJson(\n obj: unknown,\n tool: ToolId,\n source: string,\n ): { value: unknown; found: SecretRef[] } {\n const found: SecretRef[] = [];\n const value = cloneRedact(obj, tool, source, \"\", found);\n return { value, found };\n }\n\n /**\n * The production sanitizer for STORED file content. For JSON files this routes\n * through the structural, key-name-aware sanitizeJson so opaque secrets under a\n * secret KEY NAME (whose value matches no known token shape) are redacted before\n * they reach the repo — the gap that sanitizeText (value-patterns only) leaves\n * open. Behavior:\n *\n * - If `source` looks like JSON (\".json\" basename) AND JSON.parse succeeds:\n * run sanitizeJson on the parsed object, then re-serialize with the file's\n * detected indentation. Returns changed=true iff the serialized output\n * differs from the input (so callers/reporting see a real redaction).\n * - Otherwise (non-JSON, or JSON that fails to parse): fall back to the\n * pattern-based sanitizeText (which still catches known token shapes and\n * the key-name-aware env-assignment pattern in free text / hook scripts).\n *\n * JSON that fails to parse falling back to sanitizeText is the SAFE choice: we\n * never ship raw unsanitized content, and the value patterns still fire.\n */\n function sanitizeFile(content: string, tool: ToolId, source: string): SanitizeResult {\n if (looksLikeJsonSource(source)) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch {\n // Not valid JSON despite the extension — fall back to the text pass.\n return sanitizeText(content, tool, source);\n }\n const { value, found } = sanitizeJson(parsed, tool, source);\n const indent = detectJsonIndent(content);\n const serialized = JSON.stringify(value, null, indent);\n return { content: serialized, found, changed: serialized !== content };\n }\n return sanitizeText(content, tool, source);\n }\n\n return { isDenied, sanitizeText, sanitizeJson, sanitizeFile };\n}\n\n/* -------------------------------------------------------------------------- */\n/* JSON helpers for sanitizeFile */\n/* -------------------------------------------------------------------------- */\n\n/** True if a source path's basename ends in \".json\" (case-insensitive). */\nfunction looksLikeJsonSource(source: string): boolean {\n const base = source.replace(/\\\\/g, \"/\").split(\"/\").pop() ?? source;\n return /\\.json$/i.test(base);\n}\n\n/**\n * Best-effort detection of a JSON document's indentation so re-serialization\n * preserves the file's style. Looks at the whitespace before the first indented\n * line; returns the number of spaces, \"\\t\" for tab-indented files, or 2 as a\n * sensible default. (Exact byte-for-byte preservation is not required — the file\n * is rewritten by the backup either way — but matching the common 2-space style\n * keeps diffs tidy.)\n */\nfunction detectJsonIndent(content: string): number | string {\n const m = /\\n([ \\t]+)\\S/.exec(content);\n if (!m) return 2;\n const ws = m[1]!;\n if (ws.includes(\"\\t\")) return \"\\t\";\n return ws.length || 2;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal recursion for sanitizeJson */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Recursively clone `node`, redacting secret values. `keyPath` is the dotted\n * path to `node` from the root (for SecretRef.source); `parentKey` is the\n * immediate key under which `node` sits (drives isSecretKey for scalars).\n */\nfunction cloneRedact(\n node: unknown,\n tool: ToolId,\n source: string,\n keyPath: string,\n found: SecretRef[],\n parentKey = \"\",\n): unknown {\n // Arrays: clone element-wise, preserving index in the path.\n if (Array.isArray(node)) {\n return node.map((el, i) =>\n cloneRedact(el, tool, source, joinPath(keyPath, String(i)), found, parentKey),\n );\n }\n\n // Plain objects: clone key-by-key.\n if (isPlainObject(node)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node)) {\n out[k] = cloneRedact(v, tool, source, joinPath(keyPath, k), found, k);\n }\n return out;\n }\n\n // Scalars (string/number/boolean/null/undefined): redact if warranted.\n const keyIsSecret = parentKey.length > 0 && isSecretKey(parentKey);\n const { value: redacted, matched } = redactValue(node, keyIsSecret);\n if (matched !== null && redacted !== node) {\n found.push({\n tool,\n source: keyPath ? `${source}#${keyPath}` : source,\n key: parentKey || keyPath || \"value\",\n description: describeMatch(matched, parentKey, keyPath),\n kind: \"value\",\n });\n }\n return redacted;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\n/** A SecretRef for a free-text pattern hit. */\nfunction valueSecretRef(tool: ToolId, source: string, pat: SecretPattern): SecretRef {\n return {\n tool,\n source: `${source}#${pat.name}`,\n key: pat.name,\n description: `Redacted ${humanizePattern(pat.name)} value in ${source}`,\n kind: \"value\",\n };\n}\n\n/** Build a human-readable description for a sanitizeJson hit. */\nfunction describeMatch(matched: string, parentKey: string, keyPath: string): string {\n if (matched === \"secret-key\") {\n return `Redacted secret value for key \"${parentKey || keyPath}\"`;\n }\n return `Redacted ${humanizePattern(matched)} value at \"${keyPath}\"`;\n}\n\n/** Turn a pattern id like \"github-token\" into \"GitHub token\"-ish prose. */\nfunction humanizePattern(name: string): string {\n return name.replace(/[-_]/g, \" \");\n}\n\n/**\n * Return a fresh, lastIndex-reset copy of a (possibly global) regex so that\n * stateful `.test()`/`.replace()` calls never interfere with one another.\n */\nfunction freshRegex(re: RegExp): RegExp {\n return new RegExp(re.source, re.flags);\n}\n\n/** True for ordinary `{}`-style objects (not arrays, not class instances). */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (v === null || typeof v !== \"object\" || Array.isArray(v)) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\n/** Join two dotted-path segments, skipping empties. */\nfunction joinPath(a: string, b: string): string {\n if (!a) return b;\n if (!b) return a;\n return `${a}.${b}`;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Default singleton */\n/* -------------------------------------------------------------------------- */\n\n/** Shared, stateless sanitizer instance for all non-test callers. */\nexport const sanitizer: SanitizerService = createSanitizer();\n\nexport default sanitizer;\n","/**\n * Template variable set + ordering for the templater.\n *\n * The templater converts machine-specific values (home directory, username,\n * tool home, OS) into stable {{TOKEN}} placeholders on capture, and expands\n * those placeholders back into this machine's values on restore. This module\n * owns:\n * - building the variable map for the current machine (buildVariables),\n * - building it explicitly for tests / a known restore target (makeVariables),\n * - and the canonical *ordering* of replacements (orderedReplacements).\n *\n * ORDERING RULE (critical): when collapsing machine values into tokens we must\n * apply the most-specific (longest) value FIRST. TOOL_HOME (e.g. ~/.claude) is a\n * child of HOME, so if HOME were applied first the result would be\n * \"{{HOME}}/.claude\" instead of the desired \"{{TOOL_HOME}}\". Ordering by value\n * length, descending, guarantees nested paths collapse to the tightest token.\n *\n * This file is dependency-light: it only touches the OS abstraction\n * (src/platform/os.ts) so home/user/OS are never hardcoded.\n */\n\nimport type { OS, TemplateVariables } from \"../../types.js\";\nimport { detectOS, homeDir, userName } from \"../../platform/os.js\";\n\n/** Wrap a bare variable name in the placeholder braces: \"HOME\" -> \"{{HOME}}\". */\nexport function tokenFor(name: string): string {\n return `{{${name}}}`;\n}\n\n/**\n * Build the variable map from the LIVE machine via the OS abstraction.\n * `toolHome` is optional; pass it when processing a specific tool's home so its\n * path collapses to {{TOOL_HOME}} (more specific than {{HOME}}).\n */\nexport function buildVariables(toolHome?: string): TemplateVariables {\n return makeVariables(homeDir(), userName(), detectOS(), toolHome);\n}\n\n/**\n * Build the variable map explicitly. Used by tests and by restore when the\n * target machine's identity is known/derived rather than read live.\n *\n * Empty/whitespace-only values are dropped so the templater never tries to\n * match the empty string (which would otherwise corrupt every position in the\n * text). TOOL_HOME is only set when a non-empty value is supplied.\n */\nexport function makeVariables(\n home: string,\n user: string,\n os: OS,\n toolHome?: string,\n): TemplateVariables {\n const vars: TemplateVariables = {\n HOME: home,\n USER: user,\n OS: os,\n };\n if (toolHome !== undefined && toolHome.trim() !== \"\") {\n vars.TOOL_HOME = toolHome;\n }\n return vars;\n}\n\n/** One ordered replacement directive: a placeholder token and its machine value. */\nexport interface Replacement {\n /** Placeholder form, e.g. \"{{TOOL_HOME}}\". */\n token: string;\n /** The machine value this token stands in for, e.g. \"/Users/fab/.claude\". */\n value: string;\n /** The bare variable name, e.g. \"TOOL_HOME\" (handy for special-casing OS). */\n name: string;\n}\n\n/**\n * Produce the ordered [{ token, value, name }] list the templater iterates.\n *\n * Order: by value length DESCENDING (most-specific path first), with a stable\n * tie-break on the variable name so output is deterministic. Variables with an\n * empty/whitespace-only value are skipped entirely.\n *\n * This list contains EVERY defined variable (HOME, USER, OS, TOOL_HOME, and any\n * forward-compatible extras). `fromTemplate` iterates the whole list to expand\n * placeholders; `toTemplate` iterates it too but applies its own per-variable\n * matching strategy (path-aware vs. word-boundary) and intentionally skips the\n * OS enum value — see index.ts.\n */\nexport function orderedReplacements(vars: TemplateVariables): Replacement[] {\n const entries: Replacement[] = [];\n for (const [name, value] of Object.entries(vars)) {\n if (typeof value !== \"string\") continue;\n if (value.trim() === \"\") continue;\n entries.push({ token: tokenFor(name), value, name });\n }\n entries.sort((a, b) => {\n if (b.value.length !== a.value.length) return b.value.length - a.value.length;\n return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n return entries;\n}\n","/**\n * TemplaterService implementation: machine value <-> {{TOKEN}}.\n *\n * On CAPTURE (toTemplate) we collapse machine-identifying values — the home\n * directory, the tool home, the username — into stable placeholders so the\n * stored files are portable across machines and operating systems. On RESTORE\n * (fromTemplate) we expand those placeholders back into THIS machine's values.\n *\n * Cross-OS matching is the subtle part. A Windows home like `C:\\Users\\fab` can\n * appear in captured text three ways:\n * - native backslash: C:\\Users\\fab\n * - JSON-escaped backslash: C:\\\\Users\\\\fab (each \"\\\" doubled in JSON)\n * - forward slash: C:/Users/fab (many tools normalize to \"/\")\n * toTemplate must collapse all three to the same token. We do that by matching\n * any path value with a separator class that accepts \"/\", \"\\\" or \"\\\\\" between\n * segments. POSIX paths (single separator style) are a trivial subset.\n *\n * Replacement order: most-specific (longest) value first, so TOOL_HOME collapses\n * before HOME (TOOL_HOME is a child of HOME). orderedReplacements() guarantees\n * this ordering; we simply honor it.\n *\n * Safety choices:\n * - The OS variable (\"darwin\" / \"linux\" / \"win32\") is metadata, not a machine\n * path. Blindly replacing the substring \"linux\" everywhere would corrupt\n * ordinary prose/config, so toTemplate SKIPS the OS value. {{OS}} is still\n * expanded by fromTemplate for anyone who references it deliberately.\n * - Non-path scalar variables (e.g. USER) are matched only at token\n * boundaries, so a username like \"al\" cannot rewrite the middle of \"also\".\n * Because longer path values are applied first, the username inside a home\n * path is already gone (folded into {{HOME}}) by the time USER is matched.\n *\n * Libraries: none beyond src/platform + the variable helpers. Pure string work.\n */\n\nimport type { TemplateVariables, TemplaterService } from \"../../types.js\";\nimport { orderedReplacements, type Replacement } from \"./variables.js\";\n\n/** Variable names that are descriptive enums, not machine values to fold away. */\nconst ENUM_ONLY_VARS = new Set<string>([\"OS\"]);\n\n/** Escape a literal string for safe insertion into a RegExp source. */\nfunction escapeRegExp(literal: string): string {\n return literal.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Heuristic: does this value look like a filesystem path (so we should match\n * across separator styles), or a plain scalar (match at boundaries)?\n *\n * True when it contains a path separator, starts with \"~\", starts with \"/\", or\n * begins with a Windows drive specifier like \"C:\".\n */\nfunction looksLikePath(value: string): boolean {\n if (value.includes(\"/\") || value.includes(\"\\\\\")) return true;\n if (value.startsWith(\"~\")) return true;\n if (/^[A-Za-z]:/.test(value)) return true;\n return false;\n}\n\n/**\n * Build a RegExp source that matches a path value irrespective of separator\n * style. Each run of separators in the original becomes a class that accepts a\n * forward slash, a single backslash, or a doubled (JSON-escaped) backslash.\n *\n * The leading edge is preserved: a drive prefix is matched, a leading separator\n * (POSIX root, or a UNC double-backslash) is matched with the separator class,\n * and a leading \"~\" is kept verbatim.\n *\n * `win32` widens the drive letter to a case-insensitive class so a config that\n * stored \"c:\\Users\\fab\" still folds against a var of \"C:\\Users\\fab\" (§5.2:\n * tools/Node frequently emit the drive letter in the opposite case). The rest of\n * the path stays case-sensitive (Windows is case-preserving for the visible name\n * and folding a different-cased username would be wrong).\n */\nfunction pathVariantSource(value: string, win32: boolean): string {\n // A separator in the source matches \"/\", \"\\\", or \"\\\\\" in the target text.\n const SEP = \"(?:\\\\\\\\{1,2}|/)\";\n\n // Peel an optional Windows drive specifier so its \":\" stays literal.\n const driveMatch = /^([A-Za-z]):/.exec(value);\n const driveLetter = driveMatch ? driveMatch[1]! : \"\";\n let rest = value.slice(driveLetter ? driveLetter.length + 1 : 0);\n\n // Detect and peel a leading run of separators (POSIX \"/...\", UNC \"\\\\...\").\n const leadSepMatch = /^[\\\\/]+/.exec(rest);\n const hasLeadingSep = leadSepMatch !== null;\n if (hasLeadingSep) rest = rest.slice(leadSepMatch![0].length);\n\n // Detect a trailing run of separators (rare, but keep it round-trippable).\n const trailSepMatch = /[\\\\/]+$/.exec(rest);\n const hasTrailingSep = trailSepMatch !== null;\n if (hasTrailingSep) rest = rest.slice(0, rest.length - trailSepMatch![0].length);\n\n const segments = rest.length === 0 ? [] : rest.split(/[\\\\/]+/);\n const body = segments.map((seg) => escapeRegExp(seg)).join(SEP);\n\n let source = \"\";\n if (driveLetter) {\n // On win32 the drive letter is case-insensitive (\"[Cc]:\"); elsewhere literal.\n source += win32\n ? `[${driveLetter.toUpperCase()}${driveLetter.toLowerCase()}]:`\n : `${escapeRegExp(driveLetter)}:`;\n }\n if (hasLeadingSep) source += SEP;\n source += body;\n if (hasTrailingSep) source += SEP;\n return source;\n}\n\n/**\n * Apply a single replacement to `content` for the capture direction.\n * Returns the rewritten content.\n */\nfunction foldValue(content: string, r: Replacement, win32: boolean): string {\n if (looksLikePath(r.value)) {\n const re = new RegExp(pathVariantSource(r.value, win32), \"g\");\n return content.replace(re, r.token);\n }\n // Plain scalar (e.g. username): only at non-word boundaries so we never split\n // a larger identifier. We approximate a word boundary that also treats \"-\",\n // \"_\" and \".\" as word characters by requiring the surrounding chars not to be\n // [A-Za-z0-9_.-].\n const esc = escapeRegExp(r.value);\n const re = new RegExp(`(?<![A-Za-z0-9_.-])${esc}(?![A-Za-z0-9_.-])`, \"g\");\n return content.replace(re, r.token);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Separator-aware expansion (restore direction) */\n/* -------------------------------------------------------------------------- */\n\n/** Detect the native separator style a path value itself uses. */\nfunction nativeSeparator(value: string): \"/\" | \"\\\\\" {\n return value.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n}\n\n/**\n * Re-emit a Windows path value so that ALL of its separators use `style`\n * (\"/\", \"\\\" or \"\\\\\"). The drive letter and segment names are preserved; only the\n * separators change. POSIX-rooted values (leading \"/\") keep their root.\n */\nfunction reseparate(value: string, style: \"/\" | \"\\\\\" | \"\\\\\\\\\"): string {\n return value.replace(/[\\\\/]+/g, style);\n}\n\n/**\n * Expand a single path token in the win32 direction, matching the separator\n * style of the text IMMEDIATELY FOLLOWING each occurrence so the rehydrated\n * value joins cleanly onto the path tail (§5.2). This is what makes\n * fromTemplate(toTemplate(x)) === x hold for native-backslash, JSON-doubled\n * backslash, and forward-slash variants alike: the tail keeps whatever style it\n * had on capture, and the prefix is re-emitted in that same style.\n *\n * Detection looks at the characters right after the token:\n * - \"/\" -> forward slash\n * - \"\\\\\" (two) -> JSON-doubled backslash\n * - \"\\\" (one, lone) -> single backslash\n * - anything else / EOL -> the value's own native style (token stood alone)\n */\nfunction expandPathTokenWin32(content: string, token: string, value: string): string {\n const native = nativeSeparator(value);\n let out = \"\";\n let i = 0;\n while (i < content.length) {\n const at = content.indexOf(token, i);\n if (at === -1) {\n out += content.slice(i);\n break;\n }\n out += content.slice(i, at);\n const after = content.slice(at + token.length);\n let style: \"/\" | \"\\\\\" | \"\\\\\\\\\";\n if (after.startsWith(\"/\")) {\n style = \"/\";\n } else if (after.startsWith(\"\\\\\\\\\")) {\n style = \"\\\\\\\\\";\n } else if (after.startsWith(\"\\\\\")) {\n style = \"\\\\\";\n } else {\n style = native;\n }\n out += reseparate(value, style);\n i = at + token.length;\n }\n return out;\n}\n\n/**\n * Create a TemplaterService. Stateless; the singleton below is the normal entry\n * point, but a factory is exported so callers/tests can hold their own instance.\n */\nexport function createTemplater(): TemplaterService {\n function toTemplate(content: string, vars: TemplateVariables): string {\n const win32 = vars.OS === \"win32\";\n let out = content;\n for (const r of orderedReplacements(vars)) {\n // Enum-style variables (OS) are descriptive metadata, not values to fold.\n if (ENUM_ONLY_VARS.has(r.name)) continue;\n out = foldValue(out, r, win32);\n }\n return out;\n }\n\n function fromTemplate(content: string, vars: TemplateVariables): string {\n const win32 = vars.OS === \"win32\";\n let out = content;\n // Order is irrelevant for expansion (tokens are unique literals), but we\n // reuse orderedReplacements for a single source of truth over the var set.\n for (const r of orderedReplacements(vars)) {\n // On win32, path-valued tokens are expanded with separators that match the\n // text right after the token, so a value's tail (which kept its original\n // separator style on capture) joins cleanly to the rehydrated prefix and\n // the round-trip is exact + the result is valid JSON/TOML. Non-path tokens\n // (USER, OS) and POSIX paths are a plain literal substitution.\n if (win32 && r.name !== \"OS\" && looksLikePath(r.value)) {\n out = expandPathTokenWin32(out, r.token, r.value);\n } else {\n out = out.split(r.token).join(r.value);\n }\n }\n return out;\n }\n\n return { toTemplate, fromTemplate };\n}\n\n/** Default singleton TemplaterService. */\nexport const templater: TemplaterService = createTemplater();\n\nexport default templater;\n","/**\n * Shared command-layer context factories.\n *\n * Commands are thin: they parse flags, assemble the injected `CoreServices`\n * bundle (plus the capture/restore extensions), and delegate to core/adapters.\n * This module is the ONE place that wires the concrete singletons\n * (`fs`, `log`, `sanitizer`, `templater`) together with the per-machine\n * variable map + detected OS, so every command builds contexts identically.\n *\n * The agreed shape is `buildCoreServices(toolHome)`;\n * we also provide `buildCaptureContext` / `buildRestoreContext` so the\n * backup/restore/status commands don't each hand-roll the extra fields.\n *\n * No system clock here — timestamps are owned by the command entrypoints and\n * passed inward. This file is pure wiring.\n */\n\nimport { confirm, isCancel, password } from \"@clack/prompts\";\n\nimport type {\n CaptureContext,\n CoreServices,\n RestoreContext,\n} from \"../adapters/adapter.interface.js\";\nimport type { SourceOfTruth } from \"../types.js\";\nimport { detectOS } from \"../platform/os.js\";\nimport { install, type DependencyId, type InstallOutcome } from \"../platform/install.js\";\nimport { sanitizer } from \"../core/sanitizer/index.js\";\nimport { templater } from \"../core/templater/index.js\";\nimport { buildVariables } from \"../core/templater/variables.js\";\nimport type {\n CliInstaller,\n ProviderCli,\n TokenPaster,\n} from \"../core/auth/index.js\";\nimport type { RepoAuthHooks } from \"../core/repo/index.js\";\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\n\n/**\n * Assemble the core service bundle injected into every adapter call.\n *\n * `toolHome` is folded into the template variable map so that machine paths\n * under that tool's home collapse to `{{TOOL_HOME}}` (more specific than\n * `{{HOME}}`) on capture and expand back on restore. Pass the absolute tool\n * home of the tool currently being processed.\n */\nexport function buildCoreServices(toolHome: string): CoreServices {\n return {\n fs,\n log,\n sanitizer,\n templater,\n vars: buildVariables(toolHome),\n os: detectOS(),\n };\n}\n\n/** Extra inputs the capture side needs beyond CoreServices. */\nexport interface CaptureContextInput {\n toolHome: string;\n includeSecrets: boolean;\n includeMemories: boolean;\n dryRun: boolean;\n}\n\n/** Build a full CaptureContext for an adapter's `capture()` call. */\nexport function buildCaptureContext(input: CaptureContextInput): CaptureContext {\n return {\n ...buildCoreServices(input.toolHome),\n toolHome: input.toolHome,\n includeSecrets: input.includeSecrets,\n includeMemories: input.includeMemories,\n dryRun: input.dryRun,\n };\n}\n\n/** Extra inputs the restore side needs beyond CoreServices. */\nexport interface RestoreContextInput {\n toolHome: string;\n repoToolDir: string;\n repoRoot: string;\n sourceOfTruth: SourceOfTruth;\n dryRun: boolean;\n}\n\n/** Build a full RestoreContext for an adapter's `restore()` call. */\nexport function buildRestoreContext(input: RestoreContextInput): RestoreContext {\n return {\n ...buildCoreServices(input.toolHome),\n toolHome: input.toolHome,\n repoToolDir: input.repoToolDir,\n repoRoot: input.repoRoot,\n sourceOfTruth: input.sourceOfTruth,\n dryRun: input.dryRun,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Repo-auth hooks (gh/glab-first, device-flow/token fallback) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The @clack token-paste prompt the auth core uses when it falls back to a PAT.\n * Masked input; points at the provider's token page; returns the trimmed token\n * or null on cancel/empty. Defined once here so init/backup/restore share it.\n */\nconst tokenPaster: TokenPaster = async ({ provider, host, hint }) => {\n log.info(`Paste a Personal Access Token for ${host}.`);\n if (provider.patHelpUrl) log.step(`Create one here: ${provider.patHelpUrl}`);\n log.step(hint);\n const value = await password({\n message: `${provider.displayName} token (input hidden):`,\n validate: (v) =>\n v && v.trim() !== \"\" ? undefined : \"Enter a token, or Esc to cancel.\",\n });\n if (isCancel(value)) return null;\n const token = String(value).trim();\n return token === \"\" ? null : token;\n};\n\n/**\n * The install-on-demand seam the auth core uses when a preferred provider CLI\n * (gh/glab) is missing: confirm, then install via the platform layer.\n */\nconst cliInstaller: CliInstaller = {\n async confirm({ cli, host }: { cli: ProviderCli; host: string }): Promise<boolean> {\n const answer = await confirm({\n message:\n `${cli.label} is not installed. It is the easiest way to sign in to ` +\n `${host} (it handles the login for you). Install it now?`,\n initialValue: true,\n });\n if (isCancel(answer)) return false;\n return answer === true;\n },\n async install(dependency: DependencyId): Promise<InstallOutcome> {\n return install(dependency);\n },\n};\n\n/**\n * Build the {@link RepoAuthHooks} passed into the repo layer's clone/pull/push so\n * a private backup repo can trigger a gh/glab login (installing the CLI on demand)\n * or arbella's device-flow/token fallback — without the repo/auth core importing\n * any UI deps. `createdAt` is the command's clock value (for any stored token);\n * `interactive` defaults to true. Pass `interactive:false` (e.g. `backup --auto`)\n * to forbid prompts so a background run never blocks on stdin.\n */\nexport function buildRepoAuthHooks(args: {\n createdAt: string;\n interactive?: boolean;\n}): RepoAuthHooks {\n return {\n interactive: args.interactive ?? true,\n paste: tokenPaster,\n installer: cliInstaller,\n createdAt: args.createdAt,\n };\n}\n","/**\n * `arbella setup` — interactive, cross-OS dependency installer (P3).\n *\n * The plug-&-play front door: a single command that detects which of arbella's\n * known dependencies (git, gh, glab, claude, codex, cursor) are present and lets\n * the user install the missing ones in one pass. It is a thin, interactive shell\n * over the platform install layer (`src/platform/install.ts`), which owns ALL the\n * cross-OS \"how do I install X on this machine\" knowledge (npm / brew / winget /\n * apt-dnf-pacman + sudo). This module only renders the choices, drives the\n * @clack multiselect, and reports per-dependency results.\n *\n * Interactive flow:\n * 1. Probe every dependency on PATH (concurrently) via `checkInstalled`.\n * 2. Render a @clack multiselect of all dependencies, each labelled with its\n * rationale + current \"installed / not installed\" state, PRE-CHECKING the\n * ones that are both MISSING and RECOMMENDED (git is strongly recommended;\n * gh/glab recommended; the tool CLIs are opt-in). Already-installed deps are\n * shown but left unchecked (installing them is a no-op the layer reports as\n * \"already\").\n * 3. Install the selected dependencies sequentially via `installMany` (each\n * shells out with inherited stdio so the user sees real progress; a single\n * failure does not abort the batch — every result is reported).\n * 4. Summarise: installed / already-present / skipped-unsupported / failed.\n *\n * Non-interactive use (CI, scripts, no TTY):\n * - `--all` : select EVERY dependency (subject to per-OS support).\n * - `--recommended` : select only the recommended-but-missing set.\n * - `--deps a,b,c` : select an explicit comma/space-separated id list.\n * - `-y, --yes` : do not prompt; install the resolved default set\n * (recommended-but-missing) unless another selector is\n * given. Also auto-confirms when stdin is not a TTY.\n * - `--force` : reinstall/upgrade even if a binary is already present.\n * When stdout/stdin is not a TTY and no selector flag is passed, setup behaves as\n * if `--yes` (install the recommended-but-missing set) rather than hanging on a\n * prompt that can never be answered.\n *\n * SECURITY: setup installs SOFTWARE only. It never sees, prints, or stores any\n * token or credential — authentication is a separate concern handled by\n * `core/auth` (which this module deliberately does NOT import). The only output\n * is dependency labels and install progress.\n *\n * No system clock is used here: setup performs no\n * time-stamped library decision; it only detects + installs.\n */\n\nimport type { Command } from \"commander\";\nimport * as clack from \"@clack/prompts\";\n\nimport {\n allDependencies,\n checkInstalled,\n DEPENDENCY_IDS,\n getDependency,\n installMany,\n type DependencyId,\n type DependencySpec,\n type InstallOutcome,\n} from \"../platform/install.js\";\nimport { log } from \"../utils/log.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Flags accepted by `arbella setup`. All are optional; with none of them the\n * command runs the interactive multiselect (when attached to a TTY).\n */\nexport interface SetupOptions {\n /** Select every known dependency (subject to per-OS install support). */\n all?: boolean;\n /** Select only the recommended dependencies that are currently missing. */\n recommended?: boolean;\n /** Explicit comma/space-separated dependency id list (git,gh,glab,...). */\n deps?: string;\n /** Reinstall/upgrade even if a binary already resolves on PATH. */\n force?: boolean;\n /**\n * Do not prompt: install the resolved default set (recommended-but-missing, or\n * whatever a selector flag resolves to). Implied when stdin is not a TTY.\n */\n yes?: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* commander registration */\n/* -------------------------------------------------------------------------- */\n\n/** Attach the `setup` subcommand to the program. */\nexport function register(program: Command): void {\n program\n .command(\"setup\")\n .description(\n \"Detect and install the tools arbella relies on (git, gh, glab) and the \" +\n \"AI CLIs it backs up (claude, codex, cursor) — cross-OS, in one pass.\",\n )\n .option(\"--all\", \"select every known dependency (where installable on this OS)\")\n .option(\n \"--recommended\",\n \"select only the recommended dependencies that are missing\",\n )\n .option(\n \"--deps <list>\",\n \"comma/space-separated dependency ids to install (git,gh,glab,claude,codex,cursor)\",\n )\n .option(\"--force\", \"reinstall/upgrade even if already present\")\n .option(\n \"-y, --yes\",\n \"non-interactive: install the recommended-but-missing set (or the selector flag's set) without prompting\",\n )\n .action(async (opts: SetupOptions) => {\n await run(opts);\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* run */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Execute the setup flow. Directly callable (for tests) without commander.\n *\n * Resolution order for \"which dependencies to install\":\n * 1. An explicit selector flag (`--deps` > `--all` > `--recommended`) — used\n * verbatim, no prompt.\n * 2. Otherwise, when interactive: a multiselect pre-checked with the\n * recommended-but-missing set.\n * 3. Otherwise (non-interactive / `--yes`): the recommended-but-missing set.\n */\nexport async function run(opts: SetupOptions): Promise<void> {\n clack.intro(\"arbella setup — install the tools you need\");\n\n // 1. Probe every dependency on PATH up front (concurrent, never throws).\n const statusList = await checkInstalled(DEPENDENCY_IDS);\n const installed = new Map<DependencyId, boolean>(\n statusList.map((s) => [s.id, s.installed]),\n );\n\n clack.note(formatStatusSummary(installed), \"Detected\");\n\n // 2. Resolve the selection (flag-driven, else prompt, else default set).\n const selection = await resolveSelection(opts, installed);\n if (selection === undefined) return; // user cancelled\n\n if (selection.length === 0) {\n clack.outro(\"Nothing selected — no changes made.\");\n return;\n }\n\n // 3. Install the selected dependencies sequentially (live, inherited stdio).\n clack.note(\n selection.map((id) => `• ${getDependency(id).label}`).join(\"\\n\"),\n `Installing ${selection.length} dependenc${selection.length === 1 ? \"y\" : \"ies\"}`,\n );\n\n const outcomes = await installMany(selection, { force: opts.force });\n\n // 4. Report per-dependency results + a final one-line verdict.\n reportOutcomes(outcomes);\n clack.outro(finalLine(outcomes));\n}\n\n/* -------------------------------------------------------------------------- */\n/* Selection resolution */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Decide which dependencies to install. Returns the chosen ids, or `undefined`\n * when the user cancels an interactive prompt. An explicit selector flag wins and\n * skips prompting entirely; with no flag we prompt (TTY) or fall back to the\n * recommended-but-missing default (non-TTY / `--yes`).\n */\nasync function resolveSelection(\n opts: SetupOptions,\n installed: ReadonlyMap<DependencyId, boolean>,\n): Promise<DependencyId[] | undefined> {\n // 2a. Explicit selectors (no prompt). Precedence: --deps > --all > --recommended.\n if (opts.deps && opts.deps.trim() !== \"\") {\n const ids = parseDepList(opts.deps);\n if (ids.length === 0) {\n throw new Error(\n `--deps \"${opts.deps}\" did not contain any known dependency ` +\n `(${DEPENDENCY_IDS.join(\", \")}).`,\n );\n }\n return ids;\n }\n if (opts.all) {\n // Everything, regardless of current state (force may reinstall; otherwise the\n // install layer reports already-present ones as \"already\").\n return [...DEPENDENCY_IDS];\n }\n if (opts.recommended) {\n return recommendedMissing(installed);\n }\n\n // 2b. No selector flag: the default selection is \"recommended but missing\".\n const defaults = recommendedMissing(installed);\n\n // Non-interactive (no TTY) or --yes: take the default set without prompting.\n if (opts.yes || !isInteractive()) {\n if (defaults.length === 0) {\n log.info(\n \"All recommended dependencies are already installed; nothing to do.\",\n );\n }\n return defaults;\n }\n\n // 2c. Interactive multiselect, pre-checking the recommended-but-missing set.\n return promptSelection(installed, defaults);\n}\n\n/**\n * Render the @clack multiselect of every dependency and return the user's chosen\n * ids. Pre-checks `defaults` (recommended-but-missing). `required: false` so the\n * user can legitimately pick nothing (we treat that as \"no changes\"). Returns\n * `undefined` on cancel.\n */\nasync function promptSelection(\n installed: ReadonlyMap<DependencyId, boolean>,\n defaults: readonly DependencyId[],\n): Promise<DependencyId[] | undefined> {\n const options = allDependencies().map((spec) => ({\n value: spec.id,\n label: spec.label,\n hint: optionHint(spec, installed.get(spec.id) === true),\n }));\n\n const picked = await clack.multiselect<typeof options, DependencyId>({\n message:\n \"Select the tools to install (Space toggles, Enter confirms). \" +\n \"Recommended-but-missing ones are pre-checked.\",\n options,\n initialValues: [...defaults],\n required: false,\n });\n\n if (clack.isCancel(picked)) {\n clack.cancel(\"Setup cancelled — no changes made.\");\n return undefined;\n }\n return picked;\n}\n\n/* -------------------------------------------------------------------------- */\n/* ensureDeps — on-demand installer for other commands (P3) */\n/* -------------------------------------------------------------------------- */\n\n/** Options for {@link ensureDeps}. */\nexport interface EnsureDepsOptions {\n /**\n * Skip the confirmation prompt and install missing dependencies directly. Also\n * implied when stdin is not a TTY (a prompt could not be answered). Callers that\n * already carry a `--yes` flag (init/restore) should forward it here.\n */\n yes?: boolean;\n /** Reinstall even if present (rarely needed; defaults to false). */\n force?: boolean;\n /**\n * Treat a still-missing dependency (declined / failed / unsupported) as FATAL:\n * throw with a clear, actionable message instead of returning `ok: false`. Use\n * for hard prerequisites the caller cannot proceed without (e.g. `git` before a\n * clone). Default false: the caller inspects the result and decides.\n */\n required?: boolean;\n}\n\n/** The result of an {@link ensureDeps} call. */\nexport interface EnsureDepsResult {\n /**\n * True if, after this call, every required dependency resolves on PATH (either\n * it was already present, or we installed it successfully).\n */\n ok: boolean;\n /** Required dependencies that were already installed before this call. */\n alreadyPresent: DependencyId[];\n /** Required dependencies we installed during this call. */\n installed: DependencyId[];\n /**\n * Required dependencies still missing afterwards: the user declined, the\n * install failed, or there is no install path on this OS. `ok` is false when\n * this is non-empty.\n */\n missing: DependencyId[];\n /** Per-dependency outcomes for anything we attempted to install. */\n outcomes: InstallOutcome[];\n}\n\n/**\n * Ensure a set of dependencies is installed, installing the missing ones on\n * demand. The on-ramp other commands use when they hit a hard requirement, e.g.\n * restore/init calling `ensureDeps([\"git\"])` (or `[\"git\", \"gh\"]`) before touching\n * a private repo. Mirrors the \"git/gh not found — install now? [Y/n]\" UX.\n *\n * Behaviour:\n * - Probes each required id; those already on PATH are returned in\n * `alreadyPresent` and never re-installed (unless `force`).\n * - If any are missing: when interactive and not `--yes`, asks a single\n * confirm (\"Install the missing dependencies now?\"). When the user agrees (or\n * `yes`/non-TTY), installs them via the platform layer; when they decline,\n * returns with `ok: false` and the still-missing list so the caller can warn\n * and continue or abort as it sees fit.\n * - NEVER throws for \"a dependency is missing/declined\" — that is a result, not\n * an error. (It can still surface a programmer error for an unknown id via\n * `getDependency`.)\n *\n * This helper performs NO authentication and imports nothing from `core/auth`; it\n * only installs software.\n */\nexport async function ensureDeps(\n required: readonly DependencyId[],\n opts: EnsureDepsOptions = {},\n): Promise<EnsureDepsResult> {\n // De-duplicate while preserving caller order, and validate ids early.\n const wanted = dedupe(required);\n for (const id of wanted) getDependency(id); // throws on an unknown id\n\n if (wanted.length === 0) {\n return {\n ok: true,\n alreadyPresent: [],\n installed: [],\n missing: [],\n outcomes: [],\n };\n }\n\n // Probe current state concurrently.\n const state = await checkInstalled(wanted);\n const alreadyPresent = state.filter((s) => s.installed).map((s) => s.id);\n const toInstall = state.filter((s) => !s.installed).map((s) => s.id);\n\n // Everything already there — nothing to do.\n if (toInstall.length === 0) {\n return {\n ok: true,\n alreadyPresent,\n installed: [],\n missing: [],\n outcomes: [],\n };\n }\n\n // Confirm before installing (unless told not to / non-interactive).\n const labels = toInstall.map((id) => getDependency(id).label);\n if (!opts.yes && isInteractive()) {\n const proceed = await clack.confirm({\n message:\n `${formatMissingList(labels)} not found. ` +\n `Install ${toInstall.length === 1 ? \"it\" : \"them\"} now?`,\n initialValue: true,\n });\n if (clack.isCancel(proceed) || proceed === false) {\n // User declined. Either hard-fail (required) or report what's still missing.\n if (opts.required) {\n throw new Error(\n `${formatMissingList(labels)} ${\n toInstall.length === 1 ? \"is\" : \"are\"\n } required to continue but ${\n toInstall.length === 1 ? \"was\" : \"were\"\n } not installed. Install ${\n toInstall.length === 1 ? \"it\" : \"them\"\n } (e.g. \\`arbella setup --deps ${toInstall.join(\",\")}\\`) and re-run.`,\n );\n }\n return {\n ok: false,\n alreadyPresent,\n installed: [],\n missing: toInstall,\n outcomes: [],\n };\n }\n } else {\n // Non-interactive: announce what we're about to install so output is legible.\n log.info(\n `${formatMissingList(labels)} not found; installing ${\n toInstall.length === 1 ? \"it\" : \"them\"\n }…`,\n );\n }\n\n // Install sequentially; classify each outcome.\n const outcomes = await installMany(toInstall, { force: opts.force });\n const installedNow: DependencyId[] = [];\n const stillMissing: DependencyId[] = [];\n for (const o of outcomes) {\n if (o.status === \"installed\" || o.status === \"already\") {\n installedNow.push(o.id);\n } else {\n // \"failed\" or \"unsupported\" — still not usable.\n stillMissing.push(o.id);\n if (o.message) log.warn(`${getDependency(o.id).label}: ${o.message}`);\n }\n }\n\n if (opts.required && stillMissing.length > 0) {\n const labels2 = stillMissing.map((id) => getDependency(id).label);\n throw new Error(\n `${formatMissingList(labels2)} ${\n stillMissing.length === 1 ? \"is\" : \"are\"\n } required to continue but could not be installed. ` +\n \"See the messages above; install manually and re-run.\",\n );\n }\n\n return {\n ok: stillMissing.length === 0,\n alreadyPresent,\n installed: installedNow,\n missing: stillMissing,\n outcomes,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Pure helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The recommended dependencies that are currently MISSING — the default\n * pre-checked / non-interactive selection. Order follows {@link DEPENDENCY_IDS}.\n */\nfunction recommendedMissing(\n installed: ReadonlyMap<DependencyId, boolean>,\n): DependencyId[] {\n return allDependencies()\n .filter((spec) => spec.recommended && installed.get(spec.id) !== true)\n .map((spec) => spec.id);\n}\n\n/** Parse a comma/space-separated id list into known, de-duplicated DependencyIds. */\nfunction parseDepList(raw: string): DependencyId[] {\n const out: DependencyId[] = [];\n const seen = new Set<DependencyId>();\n for (const part of raw.split(/[,\\s]+/)) {\n const t = part.trim().toLowerCase();\n if (t === \"\") continue;\n if (isDependencyId(t) && !seen.has(t)) {\n seen.add(t);\n out.push(t);\n }\n }\n return out;\n}\n\n/** Type guard for DependencyId, checked against the canonical id list. */\nfunction isDependencyId(value: string): value is DependencyId {\n return (DEPENDENCY_IDS as readonly string[]).includes(value);\n}\n\n/** De-duplicate an id list, preserving first-seen order. */\nfunction dedupe(ids: readonly DependencyId[]): DependencyId[] {\n const seen = new Set<DependencyId>();\n const out: DependencyId[] = [];\n for (const id of ids) {\n if (!seen.has(id)) {\n seen.add(id);\n out.push(id);\n }\n }\n return out;\n}\n\n/**\n * True when we may interactively prompt. Requires BOTH a TTY stdin (to read the\n * answer) and a TTY stdout (so the prompt is visible) — in a pipe/CI either is\n * absent and we must fall back to non-interactive behaviour rather than hang.\n */\nfunction isInteractive(): boolean {\n return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);\n}\n\n/** Per-checkbox hint: install state + the dependency's rationale (+ \"strong\"). */\nfunction optionHint(spec: DependencySpec, isInstalled: boolean): string {\n const state = isInstalled ? \"installed\" : \"not installed\";\n const strong = spec.strong ? \"strongly recommended — \" : \"\";\n return `${state} · ${strong}${spec.why}`;\n}\n\n/** Render the up-front detection summary (one line per dependency). */\nfunction formatStatusSummary(\n installed: ReadonlyMap<DependencyId, boolean>,\n): string {\n return allDependencies()\n .map((spec) => {\n const mark = installed.get(spec.id) === true ? \"✓ installed\" : \"✗ missing\";\n return `${mark} ${spec.label}`;\n })\n .join(\"\\n\");\n}\n\n/** Join human labels into a natural \"A, B and C\" / \"X\" fragment for prompts. */\nfunction formatMissingList(labels: readonly string[]): string {\n if (labels.length === 0) return \"(nothing)\";\n if (labels.length === 1) return labels[0]!;\n if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;\n return `${labels.slice(0, -1).join(\", \")} and ${labels[labels.length - 1]}`;\n}\n\n/** Print one result line per attempted install, with an appropriate log level. */\nfunction reportOutcomes(outcomes: readonly InstallOutcome[]): void {\n for (const o of outcomes) {\n const label = getDependency(o.id).label;\n switch (o.status) {\n case \"installed\":\n log.success(`${label}: installed.`);\n break;\n case \"already\":\n log.info(`${label}: already installed.`);\n break;\n case \"unsupported\":\n log.warn(`${label}: ${o.message ?? \"no automatic install on this OS.\"}`);\n break;\n case \"failed\":\n log.error(`${label}: ${o.message ?? \"install failed.\"}`);\n break;\n }\n }\n}\n\n/** One-line outro verdict summarising the batch. */\nfunction finalLine(outcomes: readonly InstallOutcome[]): string {\n const n = (status: InstallOutcome[\"status\"]): number =>\n outcomes.filter((o) => o.status === status).length;\n\n const parts: string[] = [];\n const installed = n(\"installed\");\n const already = n(\"already\");\n const unsupported = n(\"unsupported\");\n const failed = n(\"failed\");\n\n if (installed > 0) parts.push(`${installed} installed`);\n if (already > 0) parts.push(`${already} already present`);\n if (unsupported > 0) parts.push(`${unsupported} unsupported`);\n if (failed > 0) parts.push(`${failed} failed`);\n\n const summary = parts.length > 0 ? parts.join(\", \") : \"nothing to do\";\n return failed > 0\n ? `Setup finished with problems (${summary}). See messages above.`\n : `Setup complete (${summary}).`;\n}\n","/**\n * Zod schemas + inferred types for the reinstall manifests.\n *\n * Two artifacts are described here:\n * 1. ToolManifest -> written to <tool>/manifest.json in the backup repo.\n * The \"reinstall\" half of hybrid capture (R8, R10): plugins, marketplaces,\n * reinstallable skills, npm globals, and enabled-plugin state.\n * 2. ArbellaMeta -> written to arbella.json at the repo root.\n * Top-level metadata: which tools, schema version, options, createdAt.\n *\n * IMPORTANT (library purity): nothing here reads the system clock. `createdAt`\n * is supplied by the CALLER as an ISO string argument to buildArbellaMeta(),\n * so library code stays deterministic and testable.\n */\n\nimport { z } from \"zod\";\n\n/* -------------------------------------------------------------------------- */\n/* Schema version */\n/* -------------------------------------------------------------------------- */\n\n/** Bump when the on-disk manifest/meta shape changes incompatibly. */\nexport const MANIFEST_SCHEMA_VERSION = 1 as const;\n\nconst toolIdSchema = z.enum([\"claude\", \"codex\", \"cursor\"]);\n\n/* -------------------------------------------------------------------------- */\n/* Plugins (Claude installed_plugins.json + Codex [plugins.*]) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A plugin to reinstall + re-enable on restore. Modeled to capture both Claude's\n * installed_plugins.json entries and Codex's [plugins.\"name@marketplace\"] form.\n */\nexport const pluginEntrySchema = z.object({\n /** Fully-qualified id as used by the tool, e.g. \"superpowers@claude-plugins-official\". */\n id: z.string(),\n /** Short plugin name (left side of the @). */\n name: z.string(),\n /** Marketplace id this plugin came from (right side of the @), if any. */\n marketplace: z.string().optional(),\n /** Installed version string, when known (may be \"unknown\"). */\n version: z.string().optional(),\n /** Whether the plugin is enabled (Claude enabledPlugins / Codex enabled=true). */\n enabled: z.boolean().default(true),\n /**\n * Install scope. Claude entries carry \"user\" | \"project\"; restore only auto-\n * reinstalls \"user\"-scoped plugins (project-scoped ones are repo-specific).\n */\n scope: z.enum([\"user\", \"project\"]).default(\"user\"),\n /** Project path for project-scoped entries (informational; templated). */\n projectPath: z.string().optional(),\n});\n\n/* -------------------------------------------------------------------------- */\n/* Marketplaces (Claude known_marketplaces.json + Codex [marketplaces.*]) */\n/* -------------------------------------------------------------------------- */\n\nexport const marketplaceEntrySchema = z.object({\n /** Marketplace id, e.g. \"claude-plugins-official\". */\n id: z.string(),\n /** Source kind. \"github\" => owner/repo shorthand; \"git\" => full URL. */\n sourceType: z.enum([\"github\", \"git\", \"local\"]),\n /**\n * The source locator. For \"github\": \"owner/repo\". For \"git\": a clone URL.\n * For \"local\": a templated path.\n */\n source: z.string(),\n});\n\n/* -------------------------------------------------------------------------- */\n/* Skills (skills.sh / npx skills reinstallable; frozen ones are NOT here) */\n/* -------------------------------------------------------------------------- */\n\nexport const skillEntrySchema = z.object({\n /** Skill directory name, e.g. \"humanizer\". */\n name: z.string(),\n /**\n * How to reinstall:\n * - \"skills.sh\": installed via `npx skills add <name>` into ~/.agents/skills,\n * then symlinked into the tool's skills dir.\n * - \"frozen\": hand-made; the directory is stored as files (NOT reinstalled).\n * Recorded here for completeness/visibility only.\n */\n source: z.enum([\"skills.sh\", \"frozen\"]),\n /** The install command to run for reinstallable skills, when known. */\n installCommand: z.string().optional(),\n /** True when the local entry was a symlink into ~/.agents/skills. */\n symlinked: z.boolean().default(false),\n});\n\n/* -------------------------------------------------------------------------- */\n/* npm globals (greppy, gemini-cli, etc.) */\n/* -------------------------------------------------------------------------- */\n\nexport const npmGlobalEntrySchema = z.object({\n /** Package name as `npm i -g` expects, e.g. \"@google/gemini-cli\". */\n package: z.string(),\n /** Version captured at backup time (informational; restore installs latest). */\n version: z.string().optional(),\n});\n\n/* -------------------------------------------------------------------------- */\n/* ToolManifest */\n/* -------------------------------------------------------------------------- */\n\nexport const toolManifestSchema = z.object({\n /** Which tool this manifest belongs to. */\n tool: toolIdSchema,\n /** Plugins to reinstall + re-enable. */\n plugins: z.array(pluginEntrySchema).default([]),\n /** Marketplaces to register before reinstalling plugins. */\n marketplaces: z.array(marketplaceEntrySchema).default([]),\n /** Reinstallable + frozen-noted skills. */\n skills: z.array(skillEntrySchema).default([]),\n /** Global npm packages associated with this tool's workflow. */\n npmGlobals: z.array(npmGlobalEntrySchema).default([]),\n /**\n * enabledPlugins map mirrored from the tool's settings (id -> enabled). This\n * is the authoritative re-enable source on restore; `plugins[].enabled` is the\n * per-entry convenience copy.\n */\n enabledPlugins: z.record(z.string(), z.boolean()).default({}),\n});\n\n/* -------------------------------------------------------------------------- */\n/* ArbellaMeta (top-level arbella.json) */\n/* -------------------------------------------------------------------------- */\n\nexport const arbellaMetaSchema = z.object({\n /** Manifest schema version (for forward migration). */\n schemaVersion: z.literal(MANIFEST_SCHEMA_VERSION),\n /** arbella package version that produced this backup. */\n arbellaVersion: z.string(),\n /** Tools captured into this repo. */\n tools: z.array(toolIdSchema),\n /** Options that were active during capture (subset of config). */\n options: z.object({\n includeSecrets: z.boolean(),\n includeMemories: z.boolean(),\n sourceOfTruth: z.enum([\"local\", \"repo\"]),\n }),\n /** ISO-8601 timestamp, SUPPLIED BY CALLER (no clock calls in library code). */\n createdAt: z.string(),\n /**\n * True when CLAUDE.md and AGENTS.md were byte-identical and stored once in\n * shared/instructions.md (R9). Restore deploys the shared file to both tools.\n */\n sharedInstructions: z.boolean().default(false),\n});\n\n/* -------------------------------------------------------------------------- */\n/* Inferred types */\n/* -------------------------------------------------------------------------- */\n\nexport type PluginEntry = z.infer<typeof pluginEntrySchema>;\nexport type MarketplaceEntry = z.infer<typeof marketplaceEntrySchema>;\nexport type SkillEntry = z.infer<typeof skillEntrySchema>;\nexport type NpmGlobalEntry = z.infer<typeof npmGlobalEntrySchema>;\nexport type ToolManifest = z.infer<typeof toolManifestSchema>;\nexport type ArbellaMeta = z.infer<typeof arbellaMetaSchema>;\n","/**\n * Manifest + meta orchestration for arbella.\n *\n * This module is the single source of truth for:\n * - building / parsing / validating ToolManifest objects (per-tool manifest.json)\n * - building / parsing / validating ArbellaMeta (top-level arbella.json)\n * - deterministic JSON serialization for everything committed to the repo\n * - the shared-instructions (R9) decision + storage contract:\n * deciding whether CLAUDE.md and AGENTS.md are byte-identical and should be\n * stored once under shared/instructions.md, and where that single file is\n * deployed on restore.\n *\n * LIBRARY PURITY: nothing here reads the system clock.\n * `createdAt` is supplied by the caller as an ISO string. The R9 helpers are pure\n * and synchronous (no fs) — callers do the reading and pass content in.\n *\n * Validation is delegated to the zod schemas in ./schema.js so there is exactly\n * one definition of each on-disk shape.\n */\n\nimport type {\n ToolManifest,\n ArbellaMeta,\n ArbellaConfig,\n ToolId,\n CapturedFile,\n} from \"../../types.js\";\nimport { toolManifestSchema, arbellaMetaSchema, MANIFEST_SCHEMA_VERSION } from \"./schema.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Empty / default manifest */\n/* -------------------------------------------------------------------------- */\n\n/**\n * An empty manifest for `tool` (all collections empty). Built by parsing a\n * minimal object through the schema so every default is applied and the result\n * is guaranteed schema-valid (and carries the exact inferred output type).\n */\nexport function emptyManifest(tool: ToolId): ToolManifest {\n return toolManifestSchema.parse({\n tool,\n plugins: [],\n marketplaces: [],\n skills: [],\n npmGlobals: [],\n enabledPlugins: {},\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Parse / validate (read side) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Validate + parse a manifest read from disk. Throws a ZodError on malformed\n * data so the restore/status caller can surface a clear \"corrupt manifest\"\n * message rather than silently proceeding with garbage.\n */\nexport function parseManifest(json: unknown): ToolManifest {\n return toolManifestSchema.parse(json);\n}\n\n/**\n * Validate + parse the top-level arbella.json (ArbellaMeta). Throws on bad\n * data (e.g. wrong schemaVersion literal, missing required fields).\n */\nexport function parseMeta(json: unknown): ArbellaMeta {\n return arbellaMetaSchema.parse(json);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Build top-level meta (write side) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build the top-level ArbellaMeta written to <repoRoot>/arbella.json.\n *\n * `createdAt` MUST be supplied by the caller (e.g. `new Date().toISOString()` at\n * the command layer) — library code never touches the clock. The result is run\n * through the schema so it is validated + normalized before it is serialized.\n */\nexport function buildArbellaMeta(args: {\n arbellaVersion: string;\n tools: ToolId[];\n config: ArbellaConfig;\n createdAt: string;\n sharedInstructions: boolean;\n}): ArbellaMeta {\n const { arbellaVersion, tools, config, createdAt, sharedInstructions } = args;\n return arbellaMetaSchema.parse({\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n arbellaVersion,\n tools,\n options: {\n includeSecrets: config.includeSecrets,\n includeMemories: config.includeMemories,\n sourceOfTruth: config.sourceOfTruth,\n },\n createdAt,\n sharedInstructions,\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Manifest merge helpers */\n/* -------------------------------------------------------------------------- */\n/*\n * These let an adapter accumulate plugin / marketplace / skill / npm-global\n * entries from multiple sources (e.g. settings.json + installed_plugins.json,\n * or several skill directories) into one manifest without duplicating ids.\n * They are pure: they return NEW arrays / objects and never mutate inputs, so\n * they are safe to chain. De-duplication keeps the FIRST occurrence of a given\n * key (stable, predictable) and is therefore order-sensitive — pass the more\n * authoritative source first.\n */\n\n/** Merge plugin lists, de-duplicating by `id` (first occurrence wins). */\nexport function mergePlugins(\n ...lists: ReadonlyArray<ToolManifest[\"plugins\"]>\n): ToolManifest[\"plugins\"] {\n return dedupeBy(lists.flat(), (p) => p.id);\n}\n\n/** Merge marketplace lists, de-duplicating by `id` (first occurrence wins). */\nexport function mergeMarketplaces(\n ...lists: ReadonlyArray<ToolManifest[\"marketplaces\"]>\n): ToolManifest[\"marketplaces\"] {\n return dedupeBy(lists.flat(), (m) => m.id);\n}\n\n/** Merge skill lists, de-duplicating by `name` (first occurrence wins). */\nexport function mergeSkills(\n ...lists: ReadonlyArray<ToolManifest[\"skills\"]>\n): ToolManifest[\"skills\"] {\n return dedupeBy(lists.flat(), (s) => s.name);\n}\n\n/** Merge npm-global lists, de-duplicating by `package` (first occurrence wins). */\nexport function mergeNpmGlobals(\n ...lists: ReadonlyArray<ToolManifest[\"npmGlobals\"]>\n): ToolManifest[\"npmGlobals\"] {\n return dedupeBy(lists.flat(), (n) => n.package);\n}\n\n/**\n * Merge enabledPlugins maps (id -> enabled). Later maps win on key collisions,\n * matching how successive settings sources should override one another.\n */\nexport function mergeEnabledPlugins(\n ...maps: ReadonlyArray<ToolManifest[\"enabledPlugins\"]>\n): ToolManifest[\"enabledPlugins\"] {\n const out: Record<string, boolean> = {};\n for (const map of maps) {\n for (const key of Object.keys(map)) {\n out[key] = map[key]!;\n }\n }\n return out;\n}\n\n/**\n * Assemble a complete, schema-valid ToolManifest from parts. Any omitted part\n * defaults to empty. Each list is de-duplicated via the merge helpers and the\n * whole thing is validated through the schema. Convenience for adapter capture\n * code so it does not have to hand-build the object shape.\n */\nexport function buildManifest(\n tool: ToolId,\n raw: Partial<{\n plugins: ToolManifest[\"plugins\"];\n marketplaces: ToolManifest[\"marketplaces\"];\n skills: ToolManifest[\"skills\"];\n npmGlobals: ToolManifest[\"npmGlobals\"];\n enabledPlugins: ToolManifest[\"enabledPlugins\"];\n }> = {},\n): ToolManifest {\n return toolManifestSchema.parse({\n tool,\n plugins: mergePlugins(raw.plugins ?? []),\n marketplaces: mergeMarketplaces(raw.marketplaces ?? []),\n skills: mergeSkills(raw.skills ?? []),\n npmGlobals: mergeNpmGlobals(raw.npmGlobals ?? []),\n enabledPlugins: raw.enabledPlugins ?? {},\n });\n}\n\n/** De-duplicate an array by a derived string key, keeping the first occurrence. */\nfunction dedupeBy<T>(items: readonly T[], key: (item: T) => string): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const item of items) {\n const k = key(item);\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(item);\n }\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Deterministic serialization */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Serialize a value to deterministic JSON: 2-space indent, object keys sorted\n * lexicographically (recursively), arrays kept in order, with a trailing\n * newline. Determinism matters because these files are committed to git — stable\n * output means no spurious diffs when only insertion order changed.\n *\n * Semantics mirror JSON.stringify: `undefined` object values are dropped,\n * `undefined` array elements become `null`, and Dates/etc. are not specially\n * handled (callers pass plain JSON-shaped values).\n */\nexport function serialize(value: unknown): string {\n return JSON.stringify(sortKeysDeep(value), null, 2) + \"\\n\";\n}\n\n/**\n * Recursively produce a structurally-equal value with every plain-object's keys\n * sorted. Arrays preserve order. Non-plain values (primitives, null) pass\n * through. Keys whose value is `undefined` are omitted so output matches\n * JSON.stringify's treatment of them.\n */\nfunction sortKeysDeep(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((v) => sortKeysDeep(v));\n }\n if (isPlainObject(value)) {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value).sort()) {\n const v = (value as Record<string, unknown>)[key];\n if (v === undefined) continue;\n sorted[key] = sortKeysDeep(v);\n }\n return sorted;\n }\n return value;\n}\n\n/** True for ordinary record objects (excludes null, arrays, class instances). */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n if (Array.isArray(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Shared instructions (R9) — single source of truth lives HERE */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Repo path where the single shared instructions file is stored when CLAUDE.md\n * and AGENTS.md are byte-identical. POSIX separators (it is a CapturedFile\n * repoPath).\n */\nexport const SHARED_INSTRUCTIONS_REPO_PATH = \"shared/instructions.md\";\n\n/**\n * Decide R9: given the (already-read) contents of CLAUDE.md and AGENTS.md (either\n * may be undefined when the file is absent), return whether they are identical\n * and should therefore be stored once as the shared instructions file.\n *\n * Pure + synchronous (no fs). Only shares when BOTH files exist and their\n * contents are exactly equal. If either is missing, there is nothing to share —\n * each adapter freezes whatever it individually has.\n */\nexport function shouldShareInstructions(claudeMd?: string, agentsMd?: string): boolean {\n if (claudeMd === undefined || agentsMd === undefined) return false;\n return claudeMd === agentsMd;\n}\n\n/**\n * Produce the CapturedFile for the shared instructions (repoPath =\n * SHARED_INSTRUCTIONS_REPO_PATH). The content is the shared text verbatim; it is\n * already user-authored markdown (templating still applies upstream if the\n * caller chooses, but instructions are typically path-free). Callers should only\n * invoke this when shouldShareInstructions() returned true.\n */\nexport function buildSharedInstructionsFile(content: string): CapturedFile {\n return {\n repoPath: SHARED_INSTRUCTIONS_REPO_PATH,\n content,\n };\n}\n\n/**\n * The destinations the single shared instructions file deploys to on restore,\n * expressed relative to each tool's home dir. Claude reads CLAUDE.md; Codex reads\n * AGENTS.md. (Cursor rule deployment is handled by the cursor adapter, which\n * reads shared/instructions.md from the repo root directly — it is intentionally\n * not listed here because its destination is not a fixed tool-home-relative path.)\n */\nexport function sharedInstructionsTargets(): Array<{ tool: ToolId; relPath: string }> {\n return [\n { tool: \"claude\", relPath: \"CLAUDE.md\" },\n { tool: \"codex\", relPath: \"AGENTS.md\" },\n ];\n}\n","/**\n * `arbella push` — capture the local AI dev setup and push it to the private\n * backup repo (R3). Also the entry point the autobackup SessionStart hook calls\n * with `--auto` (R4), and supports `--dry-run` (R14) to preview without writing.\n *\n * Responsibilities:\n * 1. Load + validate the local config.\n * 2. When `--auto`, gate the whole run through the throttle (quiet no-op when\n * it is not yet time to back up).\n * 3. Ensure the backup repo is cloned locally (skipped in dry-run, which is\n * fully side-effect free).\n * 4. Decide the shared-instructions case (R9): if ~/.claude/CLAUDE.md and\n * ~/.codex/AGENTS.md are byte-identical, store them ONCE under\n * shared/instructions.md and tell each adapter to skip its own copy.\n * 5. For every configured + present tool, run its capture() to produce a\n * CaptureResult (frozen files + symlinks + reinstall manifest + secret\n * refs). All sanitizing/templating happens inside capture via the injected\n * CoreServices, so secrets are redacted and machine paths are placeheld\n * BEFORE anything is written.\n * 6. Materialize the repo working tree: per-tool frozen files + symlinks +\n * manifest.json, the shared instructions file (when sharing), the top-level\n * arbella.json, a generated README.md, and a defensive .gitignore that\n * bakes in the secret denylist.\n * 7. Commit + push (R3). Returns quietly with \"nothing changed\" when the tree\n * is already up to date.\n * 8. Print a clear summary, including every secret that was deliberately\n * skipped, so the user knows what they must re-supply elsewhere.\n *\n * SECURITY: this command never reads, prints, copies, or commits a secret VALUE.\n * Whole-file secrets (auth.json, .credentials.json, ...) are excluded by the\n * adapter denylists and only ever surface here as metadata (SecretRef). Value\n * redaction + path templating is performed by the sanitizer/templater inside\n * each adapter's capture(). The generated .gitignore is an additional belt-and-\n * braces guard so a stray secret file can never be committed by accident.\n *\n * CLOCK: only the command layer touches the clock. This\n * module reads `new Date().toISOString()` once and threads it inward (throttle,\n * meta.createdAt, default commit message).\n *\n * Cross-OS: every path is resolved via src/platform/os.ts + node:path; repoPaths\n * are POSIX strings (the on-disk contract) and are split back to native segments\n * when joined onto the local clone.\n */\n\nimport path from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport type {\n CaptureResult,\n CapturedFile,\n ArbellaConfig,\n SecretRef,\n ToolId,\n} from \"../types.js\";\nimport type { CaptureContext, CoreServices } from \"../adapters/adapter.interface.js\";\n\nimport { detectOS, toolHomeDir } from \"../platform/os.js\";\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\n\nimport { loadConfig } from \"../core/config/index.js\";\nimport { ensureLocalClone, commitAndPush } from \"../core/repo/index.js\";\nimport { maybeRunBackup } from \"../core/autobackup/index.js\";\nimport { buildRepoAuthHooks } from \"./_context.js\";\nimport { ensureDeps } from \"./setup.js\";\nimport {\n serialize,\n buildArbellaMeta,\n shouldShareInstructions,\n buildSharedInstructionsFile,\n SHARED_INSTRUCTIONS_REPO_PATH,\n} from \"../core/manifest/index.js\";\nimport { sanitizer } from \"../core/sanitizer/index.js\";\nimport { templater } from \"../core/templater/index.js\";\nimport { buildVariables } from \"../core/templater/variables.js\";\nimport { denylistFor } from \"../core/sanitizer/denylist.js\";\n\n// R9 note: capture(ctx, { skipInstructions }) lives on the capture modules, NOT\n// on the Adapter interface (Adapter.capture(ctx) takes no opts). We therefore\n// import the capture functions directly, and the adapter objects only for their\n// detect() probe + display metadata.\nimport { claudeAdapter } from \"../adapters/claude/index.js\";\nimport { codexAdapter } from \"../adapters/codex/index.js\";\nimport { cursorAdapter } from \"../adapters/cursor/index.js\";\nimport { capture as captureClaude } from \"../adapters/claude/capture.js\";\nimport { capture as captureCodex } from \"../adapters/codex/capture.js\";\nimport { capture as captureCursor } from \"../adapters/cursor/index.js\";\n\nimport type { Adapter } from \"../adapters/adapter.interface.js\";\n\n/** arbella version stamped into arbella.json (kept in sync with package.json). */\nconst ARBELLA_VERSION = \"0.1.0\";\n\n/* -------------------------------------------------------------------------- */\n/* Options + CLI registration */\n/* -------------------------------------------------------------------------- */\n\n/** Flags accepted by `arbella push`. */\nexport interface BackupOptions {\n /** Preview only: compute + print the plan, write/commit/push nothing (R14). */\n dryRun?: boolean;\n /** Invoked by the autobackup hook: gate through the throttle, stay quiet (R4). */\n auto?: boolean;\n /** Custom commit message (defaults to \"arbella push <iso>\"). */\n message?: string;\n}\n\n/**\n * Attach the `push` subcommand to the root program. Thin: it only parses\n * flags and delegates to {@link run} so the logic stays directly testable.\n *\n * Legacy aliases: `sync` and `backup` are also registered, hidden from `--help`,\n * so auto-hooks installed under the old command names (`arbella sync --auto`,\n * `arbella backup --auto`) keep working until the user re-runs `arbella init`,\n * which rewrites the hook to `arbella push --auto`.\n */\nexport function register(program: Command): void {\n const configure = (cmd: Command): Command =>\n cmd\n .description(\n \"Push your AI dev setup to your private repo (snapshot local changes, then commit + push).\",\n )\n .option(\"--dry-run\", \"show what would change without writing or pushing\")\n .option(\n \"--auto\",\n \"internal: run from the auto hook (throttled, quiet when skipped)\",\n )\n .option(\"-m, --message <message>\", \"commit message for this push\")\n .action(async (opts: BackupOptions) => {\n await run(opts);\n });\n\n configure(program.command(\"push\"));\n configure(program.command(\"sync\", { hidden: true }));\n configure(program.command(\"backup\", { hidden: true }));\n}\n\n/* -------------------------------------------------------------------------- */\n/* Capture dispatch (direct, opts-aware functions) */\n/* -------------------------------------------------------------------------- */\n\n/** A capture entry: the adapter (for detect/metadata) + its opts-aware capture. */\ninterface ToolCapture {\n adapter: Adapter;\n capture: (\n ctx: CaptureContext,\n opts?: { skipInstructions?: boolean },\n ) => Promise<CaptureResult>;\n}\n\n/** Map a ToolId to its adapter + capture function. */\nfunction toolCaptureFor(tool: ToolId): ToolCapture {\n switch (tool) {\n case \"claude\":\n return { adapter: claudeAdapter, capture: captureClaude };\n case \"codex\":\n return { adapter: codexAdapter, capture: captureCodex };\n case \"cursor\":\n return { adapter: cursorAdapter, capture: captureCursor };\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Context assembly */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build the CoreServices bundle for a given tool home. The sanitizer/templater\n * singletons are stateless and shared; vars are rebuilt per tool so TOOL_HOME\n * collapses to its own placeholder ahead of HOME.\n */\nfunction buildCoreServices(toolHome: string): CoreServices {\n return {\n fs,\n log,\n sanitizer,\n templater,\n vars: buildVariables(toolHome),\n os: detectOS(),\n };\n}\n\n/** Build the capture context for one tool. */\nfunction buildCaptureContext(\n tool: ToolId,\n config: ArbellaConfig,\n dryRun: boolean,\n): CaptureContext {\n const toolHome = toolHomeDir(tool);\n return {\n ...buildCoreServices(toolHome),\n toolHome,\n includeSecrets: config.includeSecrets,\n includeMemories: config.includeMemories,\n dryRun,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Main entry */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Run a backup. See the module header for the full flow. `opts.auto` gates the\n * whole thing through the throttle; `opts.dryRun` makes it a pure preview.\n */\nexport async function run(opts: BackupOptions = {}): Promise<void> {\n const dryRun = opts.dryRun === true;\n const auto = opts.auto === true;\n // Single clock read at the boundary; threaded inward.\n const nowIso = new Date().toISOString();\n\n const config = await loadConfig();\n\n // --- R4: autobackup throttle gate ---------------------------------------\n // When fired by the SessionStart hook we must stay quiet unless it is time.\n // The throttle also records \"now\" as the last-run stamp when it returns true,\n // so rapid restarts don't hammer the repo.\n if (auto) {\n const go = await maybeRunBackup(config.autoBackup, nowIso);\n if (!go) {\n log.debug(\"Autobackup throttled or disabled; skipping this run.\");\n return;\n }\n }\n\n // --- Tool selection ------------------------------------------------------\n const requested = config.tools;\n if (requested.length === 0) {\n log.warn(\"No tools configured for backup (config.tools is empty). Nothing to do.\");\n return;\n }\n\n // Detect which requested tools are actually present on this machine. Absent\n // tools are skipped with a note (graceful absence) and\n // their existing repo subtree is left untouched (it may have been captured on\n // another machine).\n const present: ToolId[] = [];\n for (const tool of requested) {\n const { adapter } = toolCaptureFor(tool);\n if (await adapter.detect()) {\n present.push(tool);\n } else {\n log.warn(`Skipping ${adapter.displayName} (${tool}): not present on this machine.`);\n }\n }\n\n if (present.length === 0) {\n log.warn(\"None of the configured tools are present on this machine. Nothing to back up.\");\n return;\n }\n\n // --- R9: shared-instructions decision ------------------------------------\n // Read CLAUDE.md + AGENTS.md (only for present tools) and decide whether they\n // are byte-identical. If so, both adapters skip their own copy and we store a\n // single shared/instructions.md.\n const sharing = await decideSharedInstructions(present);\n const sharedContent = sharing.share ? sharing.content : undefined;\n if (sharing.share) {\n log.debug(\"CLAUDE.md and AGENTS.md are identical; storing once as shared/instructions.md.\");\n }\n\n // --- Capture each present tool ------------------------------------------\n log.info(dryRun ? \"Dry run: capturing setup (nothing will be written)…\" : \"Capturing setup…\");\n\n const results: CaptureResult[] = [];\n for (const tool of present) {\n const { adapter, capture } = toolCaptureFor(tool);\n const ctx = buildCaptureContext(tool, config, dryRun);\n // Only claude/codex carry CLAUDE.md/AGENTS.md; the flag is a no-op for\n // cursor but passing it uniformly keeps the call site simple.\n const skipInstructions = sharing.share && (tool === \"claude\" || tool === \"codex\");\n try {\n const result = await capture(ctx, { skipInstructions });\n results.push(result);\n log.step(\n `${adapter.displayName}: ${result.files.length} file(s), ` +\n `${result.symlinks.length} symlink(s), ${result.secrets.length} secret(s) excluded`,\n );\n for (const w of result.warnings) log.warn(`${tool}: ${w}`);\n } catch (err) {\n // A single tool failing must not abort the whole backup; record + move on.\n log.error(`Capture failed for ${adapter.displayName} (${tool}): ${errMessage(err)}`);\n }\n }\n\n if (results.length === 0) {\n log.warn(\"Capture produced no results. Nothing to back up.\");\n return;\n }\n\n const capturedTools = results.map((r) => r.tool);\n const allSecrets = results.flatMap((r) => r.secrets);\n\n // --- Dry run: report + stop (no clone, no writes, no commit) -------------\n if (dryRun) {\n reportDryRun(results, sharedContent !== undefined, config);\n return;\n }\n\n // --- Ensure git + the local clone, then materialize the working tree ------\n // git is required to clone/commit/push. In an auto (background) run we must\n // never block on a prompt, so install non-interactively (or skip the prompt).\n await ensureDeps([\"git\"], { required: true, yes: auto });\n\n // Auth seams for a PRIVATE repo (gh/glab-first; install-on-demand or device-\n // flow/token fallback). An auto run is non-interactive so it can't prompt: it\n // will reuse a logged-in gh/glab or a stored token, else fail quietly.\n const authHooks = buildRepoAuthHooks({ createdAt: nowIso, interactive: !auto });\n\n await ensureLocalClone(config.repo, authHooks);\n const repoRoot = config.repo.localPath;\n\n // Mirror semantics: for each tool we captured, replace its `<tool>/files`\n // subtree wholesale so local deletions propagate to the repo. We deliberately\n // do NOT touch subtrees for tools we did not capture this run.\n for (const result of results) {\n await replaceToolFiles(repoRoot, result);\n }\n\n // Shared instructions (R9): write once when sharing, otherwise ensure a stale\n // shared file from a previous (sharing) backup is removed.\n if (sharedContent !== undefined) {\n const shared = buildSharedInstructionsFile(sharedContent);\n await writeCapturedFile(repoRoot, shared);\n log.step(`Wrote ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);\n } else {\n await fs.rmrf(repoJoin(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH));\n }\n\n // Top-level metadata (arbella.json) — createdAt supplied here (clock).\n const meta = buildArbellaMeta({\n arbellaVersion: ARBELLA_VERSION,\n tools: capturedTools,\n config,\n createdAt: nowIso,\n sharedInstructions: sharedContent !== undefined,\n });\n await fs.write(repoJoin(repoRoot, \"arbella.json\"), serialize(meta));\n\n // Generated repo scaffolding: restore README + defensive .gitignore.\n await fs.write(repoJoin(repoRoot, \"README.md\"), renderRepoReadme(meta, nowIso));\n await fs.write(repoJoin(repoRoot, \".gitignore\"), renderRepoGitignore(capturedTools));\n\n // --- Commit + push (auth-aware: sign in on a private-repo push failure) --\n const message = opts.message ?? `arbella push ${nowIso}`;\n log.info(\"Committing + pushing setup…\");\n const changed = await commitAndPush(repoRoot, message, {\n url: config.repo.url,\n auth: authHooks,\n });\n\n // --- Summary -------------------------------------------------------------\n printSummary({\n changed,\n repoRoot,\n capturedTools,\n results,\n secrets: allSecrets,\n sharing: sharedContent !== undefined,\n includeSecrets: config.includeSecrets,\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* R9: shared-instructions decision */\n/* -------------------------------------------------------------------------- */\n\n/** Outcome of the shared-instructions check. */\ninterface SharedDecision {\n share: boolean;\n content?: string;\n}\n\n/**\n * Read CLAUDE.md (claude home) and AGENTS.md (codex home) when both tools are\n * present, and decide via the manifest module's pure helper whether they are\n * identical and should be shared. Missing files => no sharing. Read failures are\n * swallowed (treated as \"absent\") so a transient FS hiccup can't break backup.\n */\nasync function decideSharedInstructions(present: ToolId[]): Promise<SharedDecision> {\n if (!present.includes(\"claude\") || !present.includes(\"codex\")) {\n return { share: false };\n }\n\n const claudeMd = await readIfExists(path.join(toolHomeDir(\"claude\"), \"CLAUDE.md\"));\n const agentsMd = await readIfExists(path.join(toolHomeDir(\"codex\"), \"AGENTS.md\"));\n\n if (shouldShareInstructions(claudeMd, agentsMd)) {\n // shouldShareInstructions guarantees both are defined + equal here.\n return { share: true, content: claudeMd };\n }\n return { share: false };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Repo working-tree materialization */\n/* -------------------------------------------------------------------------- */\n\n/** The `<tool>/files` repo-relative prefix for a tool. POSIX separators. */\nfunction toolFilesPrefix(tool: ToolId): string {\n return `${tool}/files`;\n}\n\n/**\n * Replace a captured tool's frozen subtree wholesale, then write its fresh files\n * + symlinks + manifest. Removing `<tool>/files` first makes the backup a true\n * mirror: files deleted locally disappear from the repo on the next backup.\n *\n * NOTE: memories (when included) are emitted by the codex adapter under\n * `codex/files/memories/...`, so they live inside the same subtree and are\n * covered by this replace.\n */\nasync function replaceToolFiles(repoRoot: string, result: CaptureResult): Promise<void> {\n const filesDir = repoJoin(repoRoot, toolFilesPrefix(result.tool));\n await fs.rmrf(filesDir);\n\n for (const file of result.files) {\n await writeCapturedFile(repoRoot, file);\n }\n\n for (const link of result.symlinks) {\n const linkPath = repoJoin(repoRoot, link.repoPath);\n // Preserve the link target verbatim: restore reads it\n // back exactly. fs.symlink removes any pre-existing entry first.\n await fs.symlink(link.target, linkPath);\n }\n\n // Per-tool reinstall manifest at `<tool>/manifest.json`.\n const manifestPath = repoJoin(repoRoot, `${result.tool}/manifest.json`);\n await fs.write(manifestPath, serialize(result.manifest));\n}\n\n/**\n * Write a single CapturedFile into the repo working tree. Handles the text vs.\n * base64-binary distinction and preserves an explicit file mode (e.g. 0o755 for\n * executable hooks/statusline scripts).\n */\nasync function writeCapturedFile(repoRoot: string, file: CapturedFile): Promise<void> {\n const dest = repoJoin(repoRoot, file.repoPath);\n if (file.binary === true) {\n const buf = Buffer.from(file.content, \"base64\");\n await fs.writeBytes(dest, buf, file.mode);\n } else {\n await fs.write(dest, file.content, file.mode);\n }\n}\n\n/**\n * Join a POSIX repoPath onto the local clone root using native separators.\n * repoPaths are always \"/\"-delimited (the on-disk contract); we split them so\n * the result is correct on Windows too.\n */\nfunction repoJoin(repoRoot: string, repoPath: string): string {\n const segments = repoPath.split(\"/\").filter((s) => s.length > 0);\n return path.join(repoRoot, ...segments);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Generated repo files (README + .gitignore) */\n/* -------------------------------------------------------------------------- */\n\n/** Human-friendly tool label for generated docs. */\nfunction toolLabel(tool: ToolId): string {\n switch (tool) {\n case \"claude\":\n return \"Claude Code\";\n case \"codex\":\n return \"Codex\";\n case \"cursor\":\n return \"Cursor\";\n }\n}\n\n/**\n * Render the auto-generated repo README with restore instructions. Pure string\n * builder — no machine-identifying content, no secrets.\n */\nfunction renderRepoReadme(\n meta: { tools: ToolId[]; createdAt: string; sharedInstructions: boolean; arbellaVersion: string },\n generatedAtIso: string,\n): string {\n const toolList = meta.tools.map((t) => `- ${toolLabel(t)} (\\`${t}\\`)`).join(\"\\n\");\n const sharedLine = meta.sharedInstructions\n ? \"Your `CLAUDE.md` and `AGENTS.md` were identical and are stored once in \" +\n \"[`shared/instructions.md`](shared/instructions.md); restore deploys it to both tools.\\n\"\n : \"\";\n\n return `# arbella backup\n\nThis is a **private** backup of an AI coding setup, produced by\n[arbella](https://github.com/) v${meta.arbellaVersion}.\n\nIt contains the *portable* parts of each tool's configuration — settings,\nagents, commands, hooks, skills, and a reinstall manifest (plugins,\nmarketplaces, npm globals). It is safe to keep private and version-controlled.\n\n> Generated: ${generatedAtIso}\n\n## What's inside\n\n${toolList}\n\nEach tool lives under \\`<tool>/\\`:\n\n- \\`<tool>/files/…\\` — frozen config files (paths replaced with \\`{{HOME}}\\`-style\n placeholders, secret values redacted).\n- \\`<tool>/manifest.json\\` — what to reinstall (plugins, marketplaces, skills,\n npm globals) and which plugins to re-enable.\n\n\\`arbella.json\\` at the root records the schema version, the tools captured, the\noptions that were active, and when this backup was made.\n\n${sharedLine}\n## Set up on a new machine\n\n\\`\\`\\`sh\nnpm install -g arbella\narbella pull <this-repo-url>\n\\`\\`\\`\n\narbella will (R6/R14) take a timestamped safety copy of any existing tool homes,\nauto-install missing CLIs, write the frozen files back (re-expanding placeholders\nto this machine's paths), reinstall plugins/marketplaces/skills, and re-enable\nplugins.\n\n## Secrets are NOT in this repo\n\nCredentials are intentionally excluded:\n\n- \\`~/.claude/.credentials.json\\`, \\`~/.claude/.claude.json\\`\n- \\`~/.codex/auth.json\\`\n- any API keys / tokens inside otherwise-safe files (redacted to \\`{{REDACTED}}\\`)\n\nAfter restoring you will need to **re-authenticate** each tool (e.g. \\`claude\\`\nlogin, \\`codex\\` login). To move secrets between your own machines without ever\ncommitting them, use the encrypted local bundle:\n\n\\`\\`\\`sh\narbella secrets export --out secrets.blob # on the old machine (prompts for a passphrase)\narbella secrets import --in secrets.blob # on the new machine (same passphrase)\n\\`\\`\\`\n\nThe blob never touches git and is never printed.\n`;\n}\n\n/**\n * Render a defensive `.gitignore` for the backup repo. Belt-and-braces: even\n * though captured files are already filtered through the denylist, this ensures\n * a stray secret/cruft file dropped into the working tree can never be staged.\n *\n * We translate the per-tool denylist into ignore globs scoped under each tool's\n * `files/` dir, and add a small set of universal cruft rules.\n */\nfunction renderRepoGitignore(tools: ToolId[]): string {\n const lines: string[] = [\n \"# Auto-generated by arbella. Do not commit secrets or machine cruft.\",\n \"\",\n \"# OS / editor cruft\",\n \".DS_Store\",\n \"Thumbs.db\",\n \"desktop.ini\",\n \"\",\n \"# Databases / caches (never useful in a portable backup)\",\n \"*.sqlite\",\n \"*.sqlite-shm\",\n \"*.sqlite-wal\",\n \"*.db\",\n \"*.db-shm\",\n \"*.db-wal\",\n \"*.log\",\n \"\",\n \"# Secret files (excluded wholesale on capture; ignored here as a safety net)\",\n ];\n\n // Always ignore the well-known whole-file secrets by basename, anywhere.\n const secretBasenames = [\n \".credentials.json\",\n \".claude.json\",\n \"auth.json\",\n \"history.jsonl\",\n \".env\",\n \"*.pem\",\n \"*.key\",\n ];\n for (const name of secretBasenames) lines.push(name);\n\n // Scope each tool's denylist directory patterns under <tool>/files/ so noise\n // dirs (sessions/, cache/, …) cannot sneak in if someone copies a raw home in.\n lines.push(\"\", \"# Per-tool excluded directories (scoped under <tool>/files/)\");\n const seen = new Set<string>();\n for (const tool of tools) {\n for (const pattern of denylistFor(tool)) {\n // Only directory patterns are useful to scope here; loose globs are already\n // covered by the universal rules above.\n if (!pattern.endsWith(\"/\")) continue;\n const scoped = `${tool}/files/${pattern}`;\n if (seen.has(scoped)) continue;\n seen.add(scoped);\n lines.push(scoped);\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n/* -------------------------------------------------------------------------- */\n/* Reporting */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Print the dry-run plan: per-tool file/symlink counts, the would-be file list,\n * the shared-instructions decision, and the secrets that would be skipped.\n * Writes nothing and commits nothing.\n */\nfunction reportDryRun(\n results: CaptureResult[],\n sharing: boolean,\n config: ArbellaConfig,\n): void {\n log.info(\"Dry run — the following would be backed up (no files written, no commit):\");\n\n for (const result of results) {\n log.step(`${toolLabel(result.tool)} (${result.tool}):`);\n for (const file of result.files) {\n log.step(` + ${file.repoPath}`);\n }\n for (const link of result.symlinks) {\n log.step(` → ${link.repoPath} -> ${link.target}`);\n }\n const m = result.manifest;\n log.step(\n ` manifest: ${m.plugins.length} plugin(s), ${m.marketplaces.length} marketplace(s), ` +\n `${m.skills.length} skill(s), ${m.npmGlobals.length} npm global(s)`,\n );\n }\n\n if (sharing) {\n log.step(`+ ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);\n }\n log.step(\"+ arbella.json, README.md, .gitignore\");\n\n reportSecrets(results.flatMap((r) => r.secrets), config.includeSecrets);\n log.info(\"Dry run complete. Re-run without --dry-run to write + push.\");\n}\n\n/**\n * Print the post-backup summary. Always lists skipped secrets so the user knows\n * what must be re-supplied (e.g. via `arbella secrets import` or a fresh login).\n */\nfunction printSummary(args: {\n changed: boolean;\n repoRoot: string;\n capturedTools: ToolId[];\n results: CaptureResult[];\n secrets: SecretRef[];\n sharing: boolean;\n includeSecrets: boolean;\n}): void {\n const { changed, repoRoot, capturedTools, results, secrets, sharing, includeSecrets } = args;\n\n const totalFiles = results.reduce((n, r) => n + r.files.length, 0);\n const totalLinks = results.reduce((n, r) => n + r.symlinks.length, 0);\n\n if (changed) {\n log.success(\n `Backed up ${capturedTools.map(toolLabel).join(\", \")} ` +\n `(${totalFiles} file(s), ${totalLinks} symlink(s)) and pushed to your repo.`,\n );\n } else {\n log.success(\"Backup is already up to date — nothing changed, nothing pushed.\");\n }\n if (sharing) {\n log.step(\"Shared instructions stored once (CLAUDE.md == AGENTS.md).\");\n }\n log.step(`Local clone: ${repoRoot}`);\n\n reportSecrets(secrets, includeSecrets);\n}\n\n/**\n * Print the secrets summary.\n *\n * - File-kind secrets (whole credential files: .credentials.json, auth.json) are\n * ALWAYS excluded by the denylist — never in the repo, regardless of the flag.\n * - Value-kind secrets (inline values in shareable configs) are REDACTED in place\n * when includeSecrets is OFF (default), or CARRIED VERBATIM into the private\n * repo when includeSecrets is ON (the opt-in \"risk\"). The summary reflects\n * which actually happened so the wording matches behavior (no dead-flag claim).\n */\nfunction reportSecrets(secrets: SecretRef[], includeSecrets: boolean): void {\n if (secrets.length === 0) {\n log.step(\"No secrets detected.\");\n return;\n }\n\n // Dedupe identical (tool, source) refs so the summary stays readable.\n const seen = new Set<string>();\n const unique: SecretRef[] = [];\n for (const ref of secrets) {\n const key = `${ref.tool} ${ref.source}`;\n if (seen.has(key)) continue;\n seen.add(key);\n unique.push(ref);\n }\n\n const fileSecrets = unique.filter((r) => r.kind === \"file\");\n const valueSecrets = unique.filter((r) => r.kind === \"value\");\n\n if (fileSecrets.length > 0) {\n log.warn(\n `${fileSecrets.length} secret file(s) were EXCLUDED (never in the repo):`,\n );\n for (const ref of fileSecrets) {\n log.step(` [file] ${ref.tool}: ${ref.source} — ${ref.description}`);\n }\n }\n\n if (valueSecrets.length > 0) {\n if (includeSecrets) {\n log.warn(\n `${valueSecrets.length} inline secret value(s) were CARRIED VERBATIM into ` +\n \"the (private) repo because includeSecrets is ON — keep the repo private:\",\n );\n } else {\n log.warn(\n `${valueSecrets.length} inline secret value(s) were REDACTED in place and ` +\n \"are NOT in the repo:\",\n );\n }\n for (const ref of valueSecrets) {\n log.step(` [value] ${ref.tool}: ${ref.source} — ${ref.description}`);\n }\n }\n\n if (!includeSecrets) {\n log.step(\n \"Re-authenticate each tool after restore, or move secrets between your own \" +\n \"machines with `arbella secrets export` / `import` (encrypted, never committed).\",\n );\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Small helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Read a file, returning undefined when it is absent or unreadable. */\nasync function readIfExists(p: string): Promise<string | undefined> {\n try {\n if (!(await fs.exists(p))) return undefined;\n return await fs.read(p);\n } catch {\n return undefined;\n }\n}\n\n/** Best-effort message extraction from an unknown thrown value. */\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * arbella — CLI entry point.\n *\n * NOTE: do NOT add a `#!/usr/bin/env node` shebang here — tsup injects it via\n * `banner.js` in tsup.config.ts, and a second one in the source would land on\n * line 2 of the bundle and crash Node with \"Invalid or unexpected token\".\n *\n * Responsibility: wire commander, declare the global flags, register every\n * subcommand, and dispatch. No business logic lives here — each command module\n * owns its own behavior and exposes a `register(program)` that attaches its\n * subcommand. This file only:\n *\n * 1. Builds the root program (name/version/description + the adapter list in\n * the help footer, sourced from the adapter registry so it never drifts).\n * 2. Declares the single global option `--verbose`, applied via a preAction\n * hook so debug logging is enabled BEFORE any subcommand action runs.\n * 3. Registers setup / init / push / pull / status / auth / secrets.\n * 4. Parses argv and funnels any thrown error into a single, friendly\n * top-level handler (log.error + exit 1) so stack traces never leak to the\n * user. Commander's own --help / --version exits are passed through.\n *\n * VERSION: hard-coded here and kept in sync with package.json. The contract\n * explicitly prefers this over a JSON import, because rootDir:\"src\" puts\n * package.json outside the compile root and would otherwise fight tsup/NodeNext.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\n\nimport { log, setVerbose } from \"./utils/log.js\";\nimport { listAdapters } from \"./adapters/registry.js\";\n\nimport { register as registerInit } from \"./commands/init.js\";\nimport { register as registerSetup } from \"./commands/setup.js\";\nimport { register as registerAuth } from \"./commands/auth.js\";\nimport { register as registerBackup } from \"./commands/backup.js\";\nimport { register as registerRestore } from \"./commands/restore.js\";\nimport { register as registerStatus } from \"./commands/status.js\";\nimport { register as registerSecrets } from \"./commands/secrets.js\";\n\n/** arbella version, kept in sync with package.json \"version\". */\nconst VERSION = \"0.1.0\";\n\n/** Build the configured root program (no parsing yet — easy to unit-test). */\nexport function buildProgram(): Command {\n const program = new Command();\n\n // A footer that lists the tools arbella manages, pulled from the registry so\n // adding an adapter surfaces here automatically.\n const supported = listAdapters()\n .map((a) => a.displayName)\n .join(\", \");\n\n program\n .name(\"arbella\")\n .description(\n \"Back up and migrate your complete AI dev setup across Linux, macOS, and \" +\n \"Windows — carry your tools, settings, plugins and skills to any machine.\\n\\n\" +\n `Supported tools: ${supported}.`,\n )\n .version(VERSION, \"-v, --version\", \"print the arbella version and exit\")\n .option(\"--verbose\", \"print verbose (debug) output\", false)\n // Show help (not an error dump) when an unknown command/flag is used.\n .showSuggestionAfterError(true)\n .configureHelp({ showGlobalOptions: true });\n\n // Apply the global --verbose BEFORE any subcommand action fires. optsWithGlobals\n // merges root + subcommand options so the flag works in any position.\n program.hook(\"preAction\", (thisCommand, actionCommand) => {\n const verbose = Boolean(actionCommand.optsWithGlobals().verbose);\n setVerbose(verbose);\n });\n\n // Register every subcommand. Each module attaches exactly one command.\n registerInit(program);\n registerSetup(program);\n registerAuth(program);\n registerBackup(program);\n registerRestore(program);\n registerStatus(program);\n registerSecrets(program);\n\n return program;\n}\n\n/** Parse argv and run. Centralizes error handling so commands can just throw. */\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const program = buildProgram();\n await program.parseAsync(argv);\n}\n\nfunction isDirectRun(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n\n const modulePath = fileURLToPath(import.meta.url);\n try {\n return fs.realpathSync(entry) === fs.realpathSync(modulePath);\n } catch {\n return path.resolve(entry) === modulePath;\n }\n}\n\nfunction handleTopLevelError(err: unknown): never {\n // Commander throws a CommanderError for normal control-flow exits (--help,\n // --version, \"unknown command\"); it has already written its own output and\n // carries the intended exit code. Honor that code without double-printing.\n if (err && typeof err === \"object\" && (err as { code?: unknown }).code !== undefined) {\n const code = (err as { exitCode?: unknown }).exitCode;\n process.exit(typeof code === \"number\" ? code : 1);\n }\n const message = err instanceof Error ? err.message : String(err);\n log.error(message);\n if (err instanceof Error && err.stack) {\n // Stack only at debug volume; the one-line message is the default surface.\n log.debug(pc.dim(err.stack));\n }\n process.exit(1);\n}\n\nif (isDirectRun()) {\n main().catch(handleTopLevelError);\n}\n","/**\n * Shared domain types for arbella.\n *\n * This file is the single source of truth for the vocabulary used across every\n * module: adapters, core services, and commands all import from here. Keep it\n * dependency-free (no imports) so it can be consumed anywhere without cycles.\n *\n * NOTE: zod-derived types (config + manifest schemas) live next to their schemas\n * in src/core/config/schema.ts and src/core/manifest/schema.ts and are re-exported\n * from here for convenience. This file owns the hand-written structural types.\n */\n\nimport type { z } from \"zod\";\n\n// Type-only import (erased at runtime, so it introduces no module cycle) so the\n// schema-owned ToolManifest can be referenced in the hand-written structural\n// types below. It is also re-exported for convenience at the bottom of the file.\nimport type { ToolManifest } from \"./core/manifest/schema.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Tools & OS */\n/* -------------------------------------------------------------------------- */\n\n/** The AI coding tools arbella knows how to back up / restore. */\nexport type ToolId = \"claude\" | \"codex\" | \"cursor\";\n\n/** All known tool ids, in canonical processing order. */\nexport const TOOL_IDS: readonly ToolId[] = [\"claude\", \"codex\", \"cursor\"] as const;\n\n/** node:os style platform identifiers arbella supports. */\nexport type OS = \"darwin\" | \"linux\" | \"win32\";\n\n/* -------------------------------------------------------------------------- */\n/* Captured files (the \"frozen\" half of hybrid capture) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A single file captured from the local machine to be written into the backup\n * repo. `content` is already sanitized + templated (placeholders substituted)\n * by the time it lives here.\n */\nexport interface CapturedFile {\n /**\n * Path RELATIVE to the backup repo root, using POSIX separators (\"/\").\n * e.g. \"claude/files/settings.json\", \"shared/instructions.md\".\n * Adapters MUST NOT include absolute machine paths here.\n */\n repoPath: string;\n /** UTF-8 text content of the file (post-sanitize, post-template). */\n content: string;\n /**\n * Optional POSIX file mode (e.g. 0o755 for executable hooks). When omitted,\n * the writer uses the default for a regular file (0o644).\n */\n mode?: number;\n /**\n * True when `content` is base64-encoded binary rather than UTF-8 text.\n * Defaults to false. Used for the rare binary asset (e.g. an image in a skill).\n */\n binary?: boolean;\n}\n\n/**\n * A symlink that existed on the source machine and should be re-created on\n * restore (e.g. ~/.claude/skills/<name> -> ../../.agents/skills/<name>).\n * Captured separately from CapturedFile because the link itself is the data.\n */\nexport interface CapturedSymlink {\n /** Link path relative to the backup repo's notion of the tool home. */\n repoPath: string;\n /** Raw link target as read from disk (kept relative when it was relative). */\n target: string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Secrets */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A reference to a secret that was detected and intentionally EXCLUDED (or, when\n * includeSecrets is on, redacted/templated) during capture. This is metadata\n * ONLY — the secret VALUE is never stored on a SecretRef. It exists so the user\n * can be told \"you'll need to re-supply X\" after a restore, and so `secrets\n * export` knows what to bundle.\n */\nexport interface SecretRef {\n /** Owning tool. */\n tool: ToolId;\n /**\n * Where the secret lives on disk, relative to the tool home (e.g.\n * \".credentials.json\", \"auth.json\", \"config.toml#mcp_servers.foo.env.API_KEY\").\n */\n source: string;\n /** Logical key/name of the secret (e.g. \"ANTHROPIC_API_KEY\", \"auth.json\"). */\n key: string;\n /** Human-readable description shown to the user. */\n description: string;\n /**\n * Kind of secret artifact:\n * - \"file\": an entire file is secret (excluded wholesale, e.g. auth.json)\n * - \"value\": a single value inside an otherwise-safe file (redacted in place)\n */\n kind: \"file\" | \"value\";\n}\n\n/* -------------------------------------------------------------------------- */\n/* Manifests (the \"reinstall\" half of hybrid capture) */\n/* -------------------------------------------------------------------------- */\n/* The authoritative zod schemas + inferred types live in */\n/* src/core/manifest/schema.ts. They are re-exported at the bottom of this */\n/* file so callers can `import type { ToolManifest } from \"../types.js\"`. */\n\n/* -------------------------------------------------------------------------- */\n/* Capture / Restore results */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The full result of capturing one tool. Returned by Adapter.capture().\n * The backup command aggregates these across tools, writes files + manifests to\n * the repo working tree, then commits + pushes.\n */\nexport interface CaptureResult {\n /** Which tool produced this result. */\n tool: ToolId;\n /** Frozen files to write into the repo (already sanitized + templated). */\n files: CapturedFile[];\n /** Symlinks to recreate on restore. */\n symlinks: CapturedSymlink[];\n /** Reinstall manifest for registry-style state (plugins, skills, npm globals). */\n manifest: ToolManifest;\n /** Secrets that were found (excluded or redacted). Metadata only. */\n secrets: SecretRef[];\n /** Non-fatal problems worth surfacing to the user (missing files, skips). */\n warnings: string[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Restore planning */\n/* -------------------------------------------------------------------------- */\n\n/** A single intended filesystem action computed during restore planning. */\nexport interface RestoreAction {\n /** What kind of action. */\n type:\n | \"write-file\"\n | \"write-symlink\"\n | \"install-cli\"\n | \"install-plugin\"\n | \"add-marketplace\"\n | \"install-skill\"\n | \"install-npm-global\"\n | \"enable-plugin\"\n | \"run-command\";\n /** Owning tool, or \"system\" for cross-cutting actions (CLI/npm installs). */\n tool: ToolId | \"system\";\n /** Absolute destination path on the target machine, when applicable. */\n targetPath?: string;\n /** Short human-readable description of the action (used in --dry-run output). */\n description: string;\n /**\n * True when this action would overwrite existing content on the target\n * machine. Drives the safety backup + dry-run reporting.\n */\n overwrites?: boolean;\n}\n\n/**\n * The full plan for restoring everything. Built by the restore command from the\n * repo contents + manifests BEFORE any mutation, so --dry-run can print it and\n * the safety backup (R14) can be sized correctly.\n */\nexport interface RestorePlan {\n /** Tools that will be restored (intersection of repo contents + config.tools). */\n tools: ToolId[];\n /** Ordered actions to perform. */\n actions: RestoreAction[];\n /** CLIs that are missing on this machine and will be auto-installed (R6). */\n missingClis: ToolId[];\n /** Whether a pre-restore safety backup of existing homes will be taken (R14). */\n willBackupExisting: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Sanitizer / Templater results */\n/* -------------------------------------------------------------------------- */\n\n/** Outcome of sanitizing a single file's text content. */\nexport interface SanitizeResult {\n /** The cleaned content (secret values replaced with REDACTED markers). */\n content: string;\n /** Secrets found while sanitizing (value-kind), for reporting. */\n found: SecretRef[];\n /** True if any redaction happened. */\n changed: boolean;\n}\n\n/**\n * The variable map used by the templater to convert machine values <-> {{TOKENS}}.\n * Keys are placeholder names WITHOUT braces (e.g. \"HOME\", \"USER\", \"OS\").\n */\nexport interface TemplateVariables {\n HOME: string;\n USER: string;\n OS: OS;\n /** Absolute path to the tool home being processed (e.g. ~/.claude). Optional. */\n TOOL_HOME?: string;\n /** Allow forward-compatible extra variables without widening everywhere. */\n [key: string]: string | undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Core services injected into adapters */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Direction of capture vs. restore (R12). \"local\" means the machine is the\n * source of truth and backup pushes; \"repo\" means the repo wins and restore\n * overwrites local. Mirrors config.sourceOfTruth.\n */\nexport type SourceOfTruth = \"local\" | \"repo\";\n\n/** Auto-backup cadence (R4). */\nexport type AutoBackupMode = \"off\" | \"session-start\" | \"daily\";\n\n/** Repo provider (R11). */\nexport type RepoProvider = \"github\" | \"gitlab\" | \"generic\";\n\n/**\n * The logger contract (implemented by src/utils/log.ts). Adapters receive this\n * so all output is consistent and testable.\n */\nexport interface Logger {\n info(msg: string): void;\n success(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n /** A numbered/indented sub-step line. */\n step(msg: string): void;\n /** Verbose-only line (suppressed unless --verbose). */\n debug(msg: string): void;\n}\n\n/**\n * Minimal filesystem surface adapters are allowed to use (implemented by\n * src/utils/fs.ts). Keeping this narrow makes adapters trivially unit-testable\n * against a temp dir and prevents direct node:fs sprawl.\n */\nexport interface FsService {\n read(path: string): Promise<string>;\n readBytes(path: string): Promise<Buffer>;\n write(path: string, content: string, mode?: number): Promise<void>;\n writeBytes(path: string, content: Buffer, mode?: number): Promise<void>;\n copy(from: string, to: string): Promise<void>;\n ensureDir(path: string): Promise<void>;\n exists(path: string): Promise<boolean>;\n /** Recursive remove (rm -rf). No-op if path is absent. */\n rmrf(path: string): Promise<void>;\n /** List immediate entries (names only). Returns [] if dir is absent. */\n list(path: string): Promise<string[]>;\n /** True if path is a symlink. */\n isSymlink(path: string): Promise<boolean>;\n /** Read a symlink's raw target. */\n readLink(path: string): Promise<string>;\n /** Create a symlink (target -> linkPath). */\n symlink(target: string, linkPath: string): Promise<void>;\n /** Stat helper: classify a path. Returns \"missing\" when absent. */\n statKind(path: string): Promise<\"file\" | \"dir\" | \"symlink\" | \"missing\">;\n}\n\n/**\n * The sanitizer service contract (implemented by src/core/sanitizer/index.ts).\n */\nexport interface SanitizerService {\n /** True if a path (relative to tool home) must be excluded wholesale. */\n isDenied(relativePath: string): boolean;\n /** Redact secret VALUES inside text content (pattern-based, free text). */\n sanitizeText(content: string, tool: ToolId, source: string): SanitizeResult;\n /** Redact secrets inside a parsed JSON object, returning a cleaned clone. */\n sanitizeJson(obj: unknown, tool: ToolId, source: string): { value: unknown; found: SecretRef[] };\n /**\n * The production capture path for stored file content. When `source` is a JSON\n * file (settings.json, settings.local.json, mcp.json, ...) and it parses, the\n * STRUCTURAL, key-name-aware sanitizer (sanitizeJson) runs and the redacted\n * object is re-serialized — so an opaque secret stored under a secret KEY NAME\n * (whose value matches no token pattern) is still redacted before it is written\n * to the repo. Non-JSON content (and JSON that fails to parse) falls back to the\n * pattern-based sanitizeText.\n */\n sanitizeFile(content: string, tool: ToolId, source: string): SanitizeResult;\n}\n\n/**\n * The templater service contract (implemented by src/core/templater/index.ts).\n */\nexport interface TemplaterService {\n /** Replace machine values with {{TOKENS}} for storage in the repo. */\n toTemplate(content: string, vars: TemplateVariables): string;\n /** Replace {{TOKENS}} with this machine's values on restore. */\n fromTemplate(content: string, vars: TemplateVariables): string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Adapter execution contexts */\n/* -------------------------------------------------------------------------- */\n/* The Adapter interface itself lives in */\n/* src/adapters/adapter.interface.ts. The context shapes live there too and are */\n/* NOT duplicated here to avoid a second source of truth. Import them from */\n/* \"./adapters/adapter.interface.js\". */\n\n/* -------------------------------------------------------------------------- */\n/* Re-exports of schema-derived types (config + manifest) */\n/* -------------------------------------------------------------------------- */\n/* These come from the zod schemas. We import the TYPES only (no runtime), so */\n/* there is exactly one definition of each and no circular runtime dependency. */\n\nexport type { ArbellaConfig } from \"./core/config/schema.js\";\nexport type {\n ToolManifest,\n PluginEntry,\n MarketplaceEntry,\n SkillEntry,\n NpmGlobalEntry,\n ArbellaMeta,\n} from \"./core/manifest/schema.js\";\n\n/**\n * Helper alias: a zod-validated value. Used in signatures where a module accepts\n * \"anything that the schema parses to\" without importing zod everywhere.\n */\nexport type Infer<T extends z.ZodTypeAny> = z.infer<T>;\n","/**\n * The adapter registry — the single place that knows the full set of tool\n * adapters (Claude, Codex, Cursor) and offers small helpers to look them up and\n * probe which are present on this machine.\n *\n * Why this exists: the Adapter contract (adapter.interface.ts) describes the\n * backup/restore lifecycle in terms of a \"registry\" (registry.detectAll() ->\n * capture, registry.forTools() -> restore). This module is that registry. It is\n * intentionally tiny and dependency-light: it imports only the concrete adapter\n * objects + shared types, so commands and the CLI entry can list/select adapters\n * without each re-deriving the same hard-coded trio.\n *\n * Ordering: adapters are kept in the canonical TOOL_IDS order so any UI that\n * iterates them (status, init, --help-ish summaries) is deterministic.\n */\n\nimport type { Adapter } from \"./adapter.interface.js\";\nimport type { ToolId } from \"../types.js\";\nimport { TOOL_IDS } from \"../types.js\";\n\nimport { claudeAdapter } from \"./claude/index.js\";\nimport { codexAdapter } from \"./codex/index.js\";\nimport { cursorAdapter } from \"./cursor/index.js\";\n\n/**\n * Every adapter arbella ships, keyed by ToolId. The map is the source of truth;\n * the ordered ALL_ADAPTERS array and the lookup helpers below derive from it.\n */\nconst ADAPTER_BY_ID: Readonly<Record<ToolId, Adapter>> = {\n claude: claudeAdapter,\n codex: codexAdapter,\n cursor: cursorAdapter,\n};\n\n/** All adapters in canonical processing order (claude, codex, cursor). */\nexport const ALL_ADAPTERS: readonly Adapter[] = TOOL_IDS.map((id) => ADAPTER_BY_ID[id]);\n\n/** Look up a single adapter by its tool id. Throws on an unknown id (the schema\n * constrains ToolId, so this only fires on a genuine programming error). */\nexport function adapterFor(tool: ToolId): Adapter {\n const adapter = ADAPTER_BY_ID[tool];\n if (!adapter) {\n throw new Error(`No adapter registered for tool \"${tool}\".`);\n }\n return adapter;\n}\n\n/** Adapters for an explicit list of tool ids, preserving the requested order. */\nexport function forTools(tools: readonly ToolId[]): Adapter[] {\n return tools.map((tool) => adapterFor(tool));\n}\n\n/**\n * Probe every adapter's detect() and return the adapters whose tool setup is\n * present on this machine. Detection is graceful per the Adapter contract\n * (detect() returns false rather than throwing when a home dir is absent); we\n * additionally swallow any unexpected throw so one tool can never block the\n * others.\n */\nexport async function detectAll(): Promise<Adapter[]> {\n const present: Adapter[] = [];\n for (const adapter of ALL_ADAPTERS) {\n let found = false;\n try {\n found = await adapter.detect();\n } catch {\n found = false;\n }\n if (found) present.push(adapter);\n }\n return present;\n}\n\n/** A small, side-effect-free summary of an adapter for listing/help output. */\nexport interface AdapterSummary {\n id: ToolId;\n displayName: string;\n}\n\n/** List the registered adapters as {id, displayName} pairs (no I/O). */\nexport function listAdapters(): AdapterSummary[] {\n return ALL_ADAPTERS.map((a) => ({ id: a.id, displayName: a.displayName }));\n}\n\n/** The registry object, mirroring the lifecycle vocabulary used in the Adapter\n * interface docs (registry.detectAll(), registry.forTools(), ...). */\nexport const registry = {\n all: ALL_ADAPTERS,\n adapterFor,\n forTools,\n detectAll,\n listAdapters,\n} as const;\n\nexport default registry;\n","/**\n * `arbella init` — interactive first-run setup (R11, R12, R4-config).\n *\n * Flow:\n * 1. Detect which tools are installed on this machine (tool-home presence).\n * 2. Prompt (clack) for provider + repo name + tools + sourceOfTruth +\n * auto-push + includeSecrets + includeMemories — unless supplied via flags\n * or `--yes` (which accepts defaults non-interactively).\n * 3. Create the PRIVATE backup repo if it is missing (core/repo provider) and\n * resolve its clone URL (`ensureRemoteRepo`).\n * 4. Clone it locally (`ensureLocalClone`).\n * 5. Persist the arbella config (`saveConfig`).\n * 6. Install/remove the throttled auto-push hook (`setAutoBackup`).\n * 7. Offer to run the first push right away.\n *\n * SECURITY: this command never prints, logs, or echoes tokens. Provider CLIs\n * (gh/glab) own their own auth; we only ever surface the provider id + repo name\n * and a debug-level note that a URL was resolved — never the URL with creds.\n *\n * The clock is owned here (command layer): the first-backup invocation lets the\n * backup command stamp its own timestamp.\n */\n\nimport path from \"node:path\";\n\nimport {\n cancel,\n confirm,\n intro,\n isCancel,\n multiselect,\n note,\n outro,\n select,\n text,\n} from \"@clack/prompts\";\nimport { Option, type Command } from \"commander\";\n\nimport type {\n AutoBackupMode,\n RepoProvider,\n SourceOfTruth,\n ToolId,\n} from \"../types.js\";\nimport { TOOL_IDS } from \"../types.js\";\nimport { setAutoBackup } from \"../core/autobackup/index.js\";\nimport {\n type ArbellaConfig,\n arbellaConfigSchema,\n} from \"../core/config/schema.js\";\nimport {\n configExists,\n configPath,\n loadConfigOrDefault,\n saveConfig,\n} from \"../core/config/index.js\";\nimport { ensureLocalClone, ensureRemoteRepo } from \"../core/repo/index.js\";\nimport { buildRepoAuthHooks } from \"./_context.js\";\nimport { ensureDeps } from \"./setup.js\";\nimport {\n isProviderCliInstalled,\n providerCliAuthStatus,\n providerCliLogin,\n providerById,\n type AuthProviderId,\n} from \"../core/auth/index.js\";\nimport { cliBinaryName, dataDir, toolHomeDir } from \"../platform/os.js\";\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Flags accepted by `arbella init`. The first six fields are the normative set;\n * the rest are convenience flags so a fully\n * non-interactive run is possible (each maps 1:1 onto a config field).\n */\nexport interface InitOptions {\n provider?: RepoProvider;\n /** Repo name (\"owner/name\" / bare name for github/gitlab; full URL for generic). */\n repo?: string;\n /** Comma-separated tool ids, e.g. \"claude,codex\". */\n tools?: string;\n sourceOfTruth?: SourceOfTruth;\n autoPush?: AutoBackupMode;\n autoBackup?: AutoBackupMode;\n /** Accept defaults for anything not supplied via flags (no prompting). */\n yes?: boolean;\n /** Allow secrets into the private repo (default false; see R5). */\n includeSecrets?: boolean;\n /** Include memories/ in backups (default false; see R13). */\n includeMemories?: boolean;\n /** Offer to run the first push after setup. Commander's --no-push => false. */\n push?: boolean;\n /** Legacy alias for --no-push. */\n backup?: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* commander registration */\n/* -------------------------------------------------------------------------- */\n\n/** Attach the `init` subcommand to the program. */\nexport function register(program: Command): void {\n program\n .command(\"init\")\n .description(\"Set up the backup repo and configure which tools to manage\")\n .option(\n \"--provider <provider>\",\n \"repo host: github | gitlab | generic\",\n )\n .option(\n \"--repo <name>\",\n \"repo name (owner/name or bare for github/gitlab; full git URL for generic)\",\n )\n .option(\n \"--tools <list>\",\n \"comma-separated tools to manage (claude,codex,cursor)\",\n )\n .option(\n \"--source-of-truth <which>\",\n \"conflict winner: local | repo\",\n )\n .addOption(\n new Option(\"--auto-push <mode>\", \"auto-push cadence: off | session-start | daily\"),\n )\n .addOption(\n new Option(\"--auto-backup <mode>\", \"legacy alias for --auto-push\").hideHelp(),\n )\n .option(\"--include-secrets\", \"allow secrets into the private repo (default: off)\")\n .option(\"--include-memories\", \"include memories/ in pushes (default: off)\")\n .option(\"--no-push\", \"do not offer to run the first push\")\n .addOption(new Option(\"--no-backup\", \"legacy alias for --no-push\").hideHelp())\n .option(\"-y, --yes\", \"accept defaults for anything not provided (no prompts)\")\n .action(async (opts: InitOptions) => {\n await run(opts);\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* run */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Execute the init flow. Directly callable (for tests) without commander.\n */\nexport async function run(opts: InitOptions): Promise<void> {\n intro(\"arbella — set up your AI dev-setup backup\");\n\n // 0. If a config already exists, confirm we may overwrite it.\n if (await configExists()) {\n const proceed = opts.yes\n ? true\n : await confirm({\n message: `A config already exists at ${configPath()}. Reconfigure it?`,\n initialValue: false,\n });\n if (isCancel(proceed) || proceed === false) {\n cancel(\"Keeping the existing configuration.\");\n return;\n }\n }\n\n const current = await loadConfigOrDefault();\n\n // 1. Detect installed tools (tool-home presence + best-effort CLI probe).\n const detected = await detectTools();\n reportDetection(detected);\n\n // 2. Provider.\n const provider = await resolveProvider(opts);\n if (provider === undefined) return; // cancelled\n\n // 3. Repo name / URL.\n const repoInput = await resolveRepoInput(opts, provider);\n if (repoInput === undefined) return; // cancelled\n\n // 4. Tools to manage (default to detected, else the config/default set).\n const defaultTools = pickDefaultTools(detected, current.tools);\n const tools = await resolveTools(opts, defaultTools);\n if (tools === undefined) return; // cancelled\n\n // 5. Source of truth.\n const sourceOfTruth = await resolveSourceOfTruth(opts, current.sourceOfTruth);\n if (sourceOfTruth === undefined) return; // cancelled\n\n // 6. Auto-backup cadence.\n const autoBackup = await resolveAutoBackup(opts, current.autoBackup);\n if (autoBackup === undefined) return; // cancelled\n\n // 7. Secrets / memories opt-ins.\n const includeSecrets = await resolveBooleanOption({\n flag: opts.includeSecrets,\n yes: opts.yes,\n fallback: current.includeSecrets,\n message:\n \"Carry inline secret VALUES (in settings/mcp/config) into the (private) repo? \" +\n \"OFF (default) redacts them in place; ON stores the real values — keep OFF \" +\n \"unless you understand the risk. Whole-file credentials are always excluded.\",\n });\n if (includeSecrets === undefined) return; // cancelled\n\n const includeMemories = await resolveBooleanOption({\n flag: opts.includeMemories,\n yes: opts.yes,\n fallback: current.includeMemories,\n message: \"Include memories/ in backups?\",\n });\n if (includeMemories === undefined) return; // cancelled\n\n // 8. Sign in to the provider FIRST (gh/glab-first) so creating the PRIVATE\n // repo and the subsequent clone \"just work\". For github/gitlab this ensures\n // the CLI is installed (offering to install it) and logged in; for generic\n // remotes there is no provider CLI step. Best-effort: a failure here is not\n // fatal — repo creation falls back to a generic remote and the clone path\n // has its own auth retry.\n await ensureProviderSignedIn(provider, opts.yes === true);\n\n // 9. Resolve / create the remote repo (PRIVATE when created) + local clone path.\n const localPath = defaultLocalPath();\n\n let repoConfig;\n try {\n log.step(`Resolving ${provider} repo \"${repoInput}\"…`);\n repoConfig = await ensureRemoteRepo({\n provider,\n name: repoInput,\n localPath,\n });\n } catch (err) {\n log.error(`Could not resolve or create the backup repo: ${errMessage(err)}`);\n cancel(\"Setup aborted before any config was written.\");\n return;\n }\n\n // 9. Assemble + persist the config (validated by the schema on save).\n const config: ArbellaConfig = arbellaConfigSchema.parse({\n repo: repoConfig,\n sourceOfTruth,\n autoBackup,\n includeSecrets,\n includeMemories,\n tools,\n });\n\n try {\n await saveConfig(config);\n log.success(`Saved config to ${configPath()}`);\n } catch (err) {\n log.error(`Failed to write config: ${errMessage(err)}`);\n cancel(\"Setup aborted.\");\n return;\n }\n\n // 11. Clone the repo locally (no-op if already present). The clone is\n // auth-aware (gh/glab-first; device-flow/token fallback) for private repos.\n try {\n const authHooks = buildRepoAuthHooks({\n createdAt: new Date().toISOString(),\n interactive: opts.yes !== true,\n });\n await ensureLocalClone(repoConfig, authHooks);\n log.success(`Backup repo ready at ${repoConfig.localPath}`);\n } catch (err) {\n log.error(`Could not clone the backup repo: ${errMessage(err)}`);\n log.warn(\n \"Config was saved, but the local clone failed. Fix access (auth/network) \" +\n \"then re-run `arbella init` or `arbella push`.\",\n );\n cancel(\"Setup partially complete.\");\n return;\n }\n\n // 11. Install (or remove) the throttled auto-push hook for the chosen cadence.\n try {\n await setAutoBackup(autoBackup);\n if (autoBackup === \"off\") {\n log.step(\"Auto-push is off (no session hook installed).\");\n } else {\n log.success(`Auto-push enabled (${autoBackup}).`);\n }\n } catch (err) {\n // Non-fatal: the rest of setup succeeded; the user can re-run later.\n log.warn(`Could not configure the auto-push hook: ${errMessage(err)}`);\n }\n\n // 12. Offer to run the first push now.\n await maybeRunFirstPush(opts);\n\n outro(\"arbella is configured. Run `arbella push` any time to snapshot your setup.\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Detection */\n/* -------------------------------------------------------------------------- */\n\n/** One detected tool: whether its home dir exists and whether its CLI is on PATH. */\ninterface DetectedTool {\n id: ToolId;\n homePresent: boolean;\n /** undefined when the CLI probe could not be run. */\n cliPresent?: boolean;\n}\n\n/**\n * Detect installed tools. Home presence is authoritative (and dependency-free,\n * via fs + os.ts); the CLI probe is best-effort and never blocks setup if the\n * platform install helper is unavailable.\n */\nasync function detectTools(): Promise<DetectedTool[]> {\n const probe = await loadWhich();\n const results: DetectedTool[] = [];\n for (const id of TOOL_IDS) {\n const homePresent = await fs.exists(toolHomeDir(id));\n let cliPresent: boolean | undefined;\n if (probe) {\n try {\n cliPresent = await probe(cliBinaryName(id));\n } catch {\n cliPresent = undefined;\n }\n }\n results.push({ id, homePresent, cliPresent });\n }\n return results;\n}\n\n/** Print a short detection summary (decorative; goes to stderr via log). */\nfunction reportDetection(detected: DetectedTool[]): void {\n const lines = detected.map((d) => {\n const home = d.homePresent ? \"config found\" : \"no config\";\n const cli =\n d.cliPresent === undefined\n ? \"\"\n : d.cliPresent\n ? \", CLI installed\"\n : \", CLI missing\";\n return `${displayName(d.id)}: ${home}${cli}`;\n });\n note(lines.join(\"\\n\"), \"Detected tools\");\n}\n\n/** The tools to pre-select: detected ones, else the existing/default config set. */\nfunction pickDefaultTools(detected: DetectedTool[], fallback: ToolId[]): ToolId[] {\n const present = detected.filter((d) => d.homePresent).map((d) => d.id);\n if (present.length > 0) return present;\n return fallback.length > 0 ? fallback : [\"claude\", \"codex\"];\n}\n\n/**\n * Best-effort loader for the platform `which` probe. Dynamically imported so\n * that init does not hard-depend on src/platform/install.ts existing at compile\n * time in isolation; returns null when it cannot be loaded.\n */\nasync function loadWhich(): Promise<((bin: string) => Promise<boolean>) | null> {\n try {\n const mod = (await import(\"../platform/install.js\")) as {\n which?: (bin: string) => Promise<boolean>;\n };\n return typeof mod.which === \"function\" ? mod.which : null;\n } catch {\n return null;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Prompt resolvers (flag/--yes aware) */\n/* -------------------------------------------------------------------------- */\n\n/** Resolve the repo provider. Returns undefined when the user cancels. */\nasync function resolveProvider(\n opts: InitOptions,\n): Promise<RepoProvider | undefined> {\n if (opts.provider) {\n if (!isProvider(opts.provider)) {\n throw new Error(\n `Invalid --provider \"${opts.provider}\". Use github | gitlab | generic.`,\n );\n }\n return opts.provider;\n }\n if (opts.yes) return \"github\";\n\n const picked = await select({\n message: \"Where should the private backup repo live?\",\n initialValue: \"github\" as RepoProvider,\n options: [\n { value: \"github\" as RepoProvider, label: \"GitHub\", hint: \"via gh CLI\" },\n { value: \"gitlab\" as RepoProvider, label: \"GitLab\", hint: \"via glab CLI\" },\n {\n value: \"generic\" as RepoProvider,\n label: \"Generic git remote\",\n hint: \"you supply a ready-made private repo URL\",\n },\n ],\n });\n if (isCancel(picked)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n return picked;\n}\n\n/** Resolve the repo name (github/gitlab) or full URL (generic). */\nasync function resolveRepoInput(\n opts: InitOptions,\n provider: RepoProvider,\n): Promise<string | undefined> {\n if (opts.repo && opts.repo.trim() !== \"\") return opts.repo.trim();\n\n if (opts.yes) {\n if (provider === \"generic\") {\n throw new Error(\n \"--yes with provider=generic requires --repo <git-url> (no default URL can be assumed).\",\n );\n }\n return \"arbella-backup\";\n }\n\n const message =\n provider === \"generic\"\n ? \"Full git URL of your (private) backup repo:\"\n : \"Backup repo name (created PRIVATE if it does not exist):\";\n const placeholder =\n provider === \"generic\"\n ? \"git@github.com:you/arbella-backup.git\"\n : \"arbella-backup\";\n\n const value = await text({\n message,\n placeholder,\n defaultValue: provider === \"generic\" ? \"\" : \"arbella-backup\",\n validate(input) {\n const v = (input ?? \"\").trim();\n if (provider === \"generic\") {\n if (v === \"\") return \"A git URL is required for a generic remote.\";\n if (!looksLikeGitUrl(v)) {\n return \"That does not look like a git URL (expected git@… or https://…).\";\n }\n } else if (v === \"\") {\n return \"A repo name is required.\";\n }\n return undefined;\n },\n });\n if (isCancel(value)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === \"\" ? \"arbella-backup\" : trimmed;\n}\n\n/** Resolve which tools to manage (multiselect, pre-checked from detection). */\nasync function resolveTools(\n opts: InitOptions,\n defaults: ToolId[],\n): Promise<ToolId[] | undefined> {\n if (opts.tools && opts.tools.trim() !== \"\") {\n const parsed = parseToolList(opts.tools);\n if (parsed.length === 0) {\n throw new Error(\n `--tools \"${opts.tools}\" did not contain any known tools (claude,codex,cursor).`,\n );\n }\n return parsed;\n }\n if (opts.yes) return defaults;\n\n const selected = await multiselect({\n message: \"Which tools should arbella manage?\",\n required: true,\n initialValues: defaults,\n options: TOOL_IDS.map((id) => ({\n value: id,\n label: displayName(id),\n })),\n });\n if (isCancel(selected)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n return selected as ToolId[];\n}\n\n/** Resolve the source-of-truth direction. */\nasync function resolveSourceOfTruth(\n opts: InitOptions,\n fallback: SourceOfTruth,\n): Promise<SourceOfTruth | undefined> {\n if (opts.sourceOfTruth) {\n if (opts.sourceOfTruth !== \"local\" && opts.sourceOfTruth !== \"repo\") {\n throw new Error(\n `Invalid --source-of-truth \"${opts.sourceOfTruth}\". Use local | repo.`,\n );\n }\n return opts.sourceOfTruth;\n }\n if (opts.yes) return fallback;\n\n const picked = await select({\n message: \"On conflicts, which side wins?\",\n initialValue: fallback,\n options: [\n {\n value: \"local\" as SourceOfTruth,\n label: \"This machine\",\n hint: \"your local setup is authoritative; backup pushes\",\n },\n {\n value: \"repo\" as SourceOfTruth,\n label: \"The repo\",\n hint: \"the repo wins; restore overwrites local\",\n },\n ],\n });\n if (isCancel(picked)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n return picked;\n}\n\n/** Resolve the auto-push cadence. */\nasync function resolveAutoBackup(\n opts: InitOptions,\n fallback: AutoBackupMode,\n): Promise<AutoBackupMode | undefined> {\n const requested = opts.autoPush ?? opts.autoBackup;\n if (requested) {\n if (!isAutoBackupMode(requested)) {\n const flag = opts.autoPush ? \"--auto-push\" : \"--auto-backup\";\n throw new Error(\n `Invalid ${flag} \"${requested}\". Use off | session-start | daily.`,\n );\n }\n return requested;\n }\n if (opts.yes) return fallback;\n\n const picked = await select({\n message: \"Auto-push cadence?\",\n initialValue: fallback,\n options: [\n { value: \"off\" as AutoBackupMode, label: \"Off\", hint: \"manual pushes only\" },\n {\n value: \"session-start\" as AutoBackupMode,\n label: \"On session start\",\n hint: \"throttled to once every few minutes\",\n },\n {\n value: \"daily\" as AutoBackupMode,\n label: \"Daily\",\n hint: \"at most once per 24h\",\n },\n ],\n });\n if (isCancel(picked)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n return picked;\n}\n\n/**\n * Resolve a yes/no option from a flag, --yes, or a confirm prompt.\n * Returns undefined when the user cancels.\n */\nasync function resolveBooleanOption(args: {\n flag: boolean | undefined;\n yes: boolean | undefined;\n fallback: boolean;\n message: string;\n}): Promise<boolean | undefined> {\n if (args.flag === true) return true;\n if (args.yes) return args.fallback;\n\n const answer = await confirm({\n message: args.message,\n initialValue: args.fallback,\n });\n if (isCancel(answer)) {\n cancel(\"Setup cancelled.\");\n return undefined;\n }\n return answer;\n}\n\n/* -------------------------------------------------------------------------- */\n/* First push */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Offer to run the first push. Suppressed by `--no-push`. The push command\n * is dynamically imported so init stays decoupled from it at compile time and\n * never hard-fails if it is unavailable.\n */\nasync function maybeRunFirstPush(opts: InitOptions): Promise<void> {\n if (opts.push === false || opts.backup === false) return;\n\n let go: boolean;\n if (opts.yes) {\n go = true;\n } else {\n const answer = await confirm({\n message: \"Run the first push now?\",\n initialValue: true,\n });\n if (isCancel(answer)) {\n // Treat a cancel here as \"skip the push\" — setup itself already succeeded.\n log.step(\"Skipped the first push. Run `arbella push` when ready.\");\n return;\n }\n go = answer;\n }\n if (!go) {\n log.step(\"Skipped the first push. Run `arbella push` when ready.\");\n return;\n }\n\n try {\n const mod = (await import(\"./backup.js\")) as {\n run?: (o: { dryRun?: boolean; auto?: boolean; message?: string }) => Promise<void>;\n };\n if (typeof mod.run !== \"function\") {\n log.warn(\"Push command unavailable; run `arbella push` manually.\");\n return;\n }\n log.step(\"Running first push...\");\n await mod.run({});\n } catch (err) {\n log.warn(\n `First push did not complete: ${errMessage(err)}. ` +\n \"You can run `arbella push` later.\",\n );\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Provider sign-in (gh/glab-first) pre-flight */\n/* -------------------------------------------------------------------------- */\n\n/**\n * For a github/gitlab init, make sure the provider CLI is installed and signed\n * in BEFORE we try to create the private repo + clone (both of which use the CLI\n * / its git credential helper). Steps, all best-effort:\n * 1. ensure the CLI dependency (gh/glab) — offering to install it on demand\n * (honoring `yes` for a non-interactive init);\n * 2. if installed but not logged in, run `<cli> auth login` (inherited stdio);\n * 3. on any miss, just warn — `ensureRemoteRepo` falls back to a generic remote\n * and the clone path has its own gh/glab-first + device-flow/token retry.\n *\n * Generic provider: nothing to do (no provider CLI). Never throws.\n */\nasync function ensureProviderSignedIn(\n provider: RepoProvider,\n yes: boolean,\n): Promise<void> {\n if (provider !== \"github\" && provider !== \"gitlab\") return;\n const providerId = provider as AuthProviderId;\n const spec = providerById(providerId);\n const dep = provider === \"github\" ? \"gh\" : \"glab\";\n\n try {\n // 1. Make sure the CLI is available (offer to install when missing).\n const deps = await ensureDeps([dep], { yes });\n if (!(await isProviderCliInstalled(providerId))) {\n log.warn(\n `${spec.displayName} CLI (${dep}) is not available. arbella will try to ` +\n \"create/clone the repo anyway and sign you in if needed.\",\n );\n void deps; // result already surfaced by ensureDeps' own logging.\n return;\n }\n\n // 2. Ensure it is logged in (run the interactive login if not).\n const state = await providerCliAuthStatus(providerId);\n if (state === \"authenticated\") {\n log.debug(`init: ${dep} is already signed in.`);\n return;\n }\n if (yes) {\n // Non-interactive init can't drive an interactive browser login.\n log.warn(\n `${dep} is not signed in and --yes was given; skipping interactive ` +\n `login. Run \\`arbella auth login --provider ${providerId}\\` if creation fails.`,\n );\n return;\n }\n log.info(`Signing in to ${spec.displayName} with ${dep}…`);\n await providerCliLogin(providerId);\n } catch (err) {\n // Pre-flight is advisory; never block init on it.\n log.debug(`init: provider sign-in pre-flight skipped: ${errMessage(err)}`);\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Small pure helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Default local clone path: dataDir()/repo (cross-OS, via os.ts). */\nfunction defaultLocalPath(): string {\n return path.join(dataDir(), \"repo\");\n}\n\n/** Human display name for a tool id. */\nfunction displayName(id: ToolId): string {\n switch (id) {\n case \"claude\":\n return \"Claude Code\";\n case \"codex\":\n return \"Codex\";\n case \"cursor\":\n return \"Cursor\";\n }\n}\n\n/** Parse a comma/space-separated tool list into known, de-duplicated ToolIds. */\nfunction parseToolList(raw: string): ToolId[] {\n const seen = new Set<ToolId>();\n const out: ToolId[] = [];\n for (const part of raw.split(/[,\\s]+/)) {\n const t = part.trim().toLowerCase();\n if (t === \"\") continue;\n if (isToolId(t) && !seen.has(t)) {\n seen.add(t);\n out.push(t);\n }\n }\n return out;\n}\n\n/** Type guard for ToolId. */\nfunction isToolId(value: string): value is ToolId {\n return value === \"claude\" || value === \"codex\" || value === \"cursor\";\n}\n\n/** Type guard for RepoProvider. */\nfunction isProvider(value: string): value is RepoProvider {\n return value === \"github\" || value === \"gitlab\" || value === \"generic\";\n}\n\n/** Type guard for AutoBackupMode. */\nfunction isAutoBackupMode(value: string): value is AutoBackupMode {\n return value === \"off\" || value === \"session-start\" || value === \"daily\";\n}\n\n/** Loose check that a string is a git remote URL (ssh or http(s) or scp-like). */\nfunction looksLikeGitUrl(value: string): boolean {\n return (\n /^https?:\\/\\//.test(value) ||\n /^git@/.test(value) ||\n /^ssh:\\/\\//.test(value) ||\n /^git:\\/\\//.test(value)\n );\n}\n\n/** Best-effort message extraction from an unknown thrown value. */\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * `arbella auth login | status | logout` — manage backup-repo authentication (P4).\n *\n * arbella is gh/glab-FIRST: for a known provider it prefers the official CLI\n * (`gh auth login` / `glab auth login`), which handles OAuth itself and wires up\n * git's credential helper so clones/pushes \"just work\". Only when the CLI is\n * missing (and the user declines to install it) or unavailable does arbella fall\n * back to its own RFC 8628 device flow or an interactive Personal-Access-Token\n * paste. All of that orchestration lives in `core/auth`; this command is the thin\n * UI shell that:\n * - injects the interactive seams the auth core needs but must not import\n * itself: a @clack password prompt (token paste) and a @clack confirm +\n * platform installer (install gh/glab on demand);\n * - owns the clock (passes `createdAt` inward for any stored token);\n * - renders human status / outcome lines.\n *\n * Subcommands:\n * login [--provider github|gitlab] [--device-flow]\n * Sign in to a provider. Defaults to GitHub. `--device-flow` skips the\n * provider CLI and forces arbella's own device-flow/token path.\n * status Show, per provider, whether its CLI is installed + logged in, plus any\n * arbella-held fallback tokens (metadata only — never a token value).\n * logout [--provider github|gitlab]\n * Sign out. With a provider: clears arbella's stored token for it AND\n * runs the provider CLI's logout. Without: clears ALL arbella-stored\n * tokens (leaves the CLIs alone).\n *\n * SECURITY (HARD): this command never prints a token. The device-flow URL + user\n * code and a provider login username/host are NOT secrets and may be shown; the\n * token itself only ever lives in gh/glab's own store or arbella's 0600 store.\n */\n\nimport { confirm, isCancel, password } from \"@clack/prompts\";\nimport type { Command } from \"commander\";\n\nimport { log } from \"../utils/log.js\";\nimport { install, type DependencyId, type InstallOutcome } from \"../platform/install.js\";\nimport {\n login as authLogin,\n logout as authLogout,\n providerStatuses,\n status as storedCredentials,\n providerById,\n type AuthProviderId,\n type AuthProviderSpec,\n type CliInstaller,\n type ProviderCli,\n type TokenPaster,\n} from \"../core/auth/index.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/** Flags for `arbella auth login`. */\nexport interface AuthLoginOptions {\n /** Which provider to sign in to (\"github\" | \"gitlab\"). Default \"github\". */\n provider?: string;\n /** Skip the provider CLI and force arbella's device-flow / token paste. */\n deviceFlow?: boolean;\n}\n\n/** Flags for `arbella auth logout`. */\nexport interface AuthLogoutOptions {\n /** Limit logout to this provider; omit to clear ALL arbella-stored tokens. */\n provider?: string;\n}\n\n/** Flags for `arbella auth status`. */\nexport interface AuthStatusOptions {\n /** Emit machine-readable JSON to stdout instead of a human table. */\n json?: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Injected seams (so core/auth stays UI-free) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The token-paste prompt the auth core invokes when it falls back to a PAT. Uses\n * @clack's masked `password` input and points the user at the provider's token\n * page. Returns the trimmed token, or null on cancel / empty.\n */\nconst tokenPaster: TokenPaster = async ({ provider, host, hint }) => {\n log.info(`Paste a Personal Access Token for ${host}.`);\n if (provider.patHelpUrl) {\n log.step(`Create one here: ${provider.patHelpUrl}`);\n }\n log.step(hint);\n const value = await password({\n message: `${provider.displayName} token (input hidden):`,\n validate: (v) => (v && v.trim() !== \"\" ? undefined : \"Enter a token, or Esc to cancel.\"),\n });\n if (isCancel(value)) return null;\n const token = String(value).trim();\n return token === \"\" ? null : token;\n};\n\n/**\n * The install-on-demand seam the auth core uses when a preferred provider CLI is\n * missing. Confirms with the user, then installs via the platform layer.\n */\nconst cliInstaller: CliInstaller = {\n async confirm({ cli, host }: { cli: ProviderCli; host: string }): Promise<boolean> {\n const answer = await confirm({\n message:\n `${cli.label} is not installed. It is the easiest way to sign in to ` +\n `${host} (it handles the login for you). Install it now?`,\n initialValue: true,\n });\n if (isCancel(answer)) return false;\n return answer === true;\n },\n async install(dependency: DependencyId): Promise<InstallOutcome> {\n return install(dependency);\n },\n};\n\n/* -------------------------------------------------------------------------- */\n/* commander registration */\n/* -------------------------------------------------------------------------- */\n\n/** Attach the `auth` command group (login/status/logout) to the program. */\nexport function register(program: Command): void {\n const auth = program\n .command(\"auth\")\n .description(\n \"Manage backup-repo authentication. Prefers the GitHub/GitLab CLI \" +\n \"(gh/glab); falls back to arbella's device-flow or a token paste.\",\n );\n\n auth\n .command(\"login\")\n .description(\"Sign in to a provider (GitHub or GitLab) for private-repo access.\")\n .option(\"--provider <provider>\", \"provider to sign in to: github | gitlab\")\n .option(\n \"--device-flow\",\n \"skip the gh/glab CLI and use arbella's own device-flow / token paste\",\n )\n .action(async (opts: AuthLoginOptions) => {\n await runLogin(opts);\n });\n\n auth\n .command(\"status\")\n .description(\"Show provider CLI sign-in state and any arbella-held tokens.\")\n .option(\"--json\", \"print machine-readable JSON to stdout\")\n .action(async (opts: AuthStatusOptions) => {\n await runStatus(opts);\n });\n\n auth\n .command(\"logout\")\n .description(\n \"Sign out. With --provider, also runs the provider CLI's logout; without \" +\n \"it, clears all arbella-stored tokens.\",\n )\n .option(\"--provider <provider>\", \"provider to sign out of: github | gitlab\")\n .action(async (opts: AuthLogoutOptions) => {\n await runLogout(opts);\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* login */\n/* -------------------------------------------------------------------------- */\n\n/** Execute `arbella auth login`. Directly callable (for tests). */\nexport async function runLogin(opts: AuthLoginOptions): Promise<void> {\n const provider = resolveProviderSpec(opts.provider) ?? providerById(\"github\");\n\n log.info(`Signing in to ${provider.displayName}…`);\n const result = await authLogin({\n provider,\n paste: tokenPaster,\n installer: cliInstaller,\n preferDeviceFlow: opts.deviceFlow === true,\n createdAt: new Date().toISOString(),\n });\n\n if (!result) {\n throw new Error(\n `Could not sign in to ${provider.displayName}. ` +\n \"No provider CLI completed the login and no token was provided. \" +\n `Install ${provider.id === \"github\" ? \"gh\" : \"glab\"} ` +\n \"(`arbella setup`) or re-run and paste a token.\",\n );\n }\n\n switch (result.method) {\n case \"provider-cli\":\n log.success(\n `Signed in to ${provider.displayName} via its CLI — git is configured ` +\n \"to use it; private clone/push will work.\",\n );\n break;\n case \"device-flow\":\n log.success(\n `Signed in to ${provider.displayName} via arbella's device flow ` +\n \"(token stored locally, 0600).\",\n );\n break;\n case \"token-paste\":\n log.success(\n `Stored your ${provider.displayName} token locally (0600). ` +\n \"arbella will use it for private clone/push.\",\n );\n break;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* status */\n/* -------------------------------------------------------------------------- */\n\n/** Execute `arbella auth status`. Directly callable (for tests). */\nexport async function runStatus(opts: AuthStatusOptions): Promise<void> {\n const providers = await providerStatuses();\n const stored = await storedCredentials();\n\n if (opts.json) {\n // Machine-readable: ONLY metadata. Token values are never present here.\n const payload = {\n providers: providers.map((p) => ({\n provider: p.provider,\n cli: p.cli,\n cliInstalled: p.cliInstalled,\n cliState: p.cliState,\n })),\n storedTokens: stored.map((c) => ({\n host: c.host,\n provider: c.provider,\n source: c.source,\n createdAt: c.createdAt,\n scope: c.scope ?? null,\n tokenHint: c.tokenHint,\n hasRefreshToken: c.hasRefreshToken,\n })),\n };\n // Data payload goes to stdout (logger writes to stderr).\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n return;\n }\n\n log.info(\"Provider CLI sign-in state:\");\n for (const p of providers) {\n const spec = providerById(p.provider);\n if (!p.cliInstalled) {\n log.step(`${spec.displayName}: ${p.cli} not installed (run \\`arbella setup\\`).`);\n continue;\n }\n switch (p.cliState) {\n case \"authenticated\":\n log.step(`${spec.displayName}: ${p.cli} installed and signed in. ✓`);\n break;\n case \"not-authenticated\":\n log.step(\n `${spec.displayName}: ${p.cli} installed but NOT signed in ` +\n `(run \\`arbella auth login --provider ${p.provider}\\`).`,\n );\n break;\n default:\n log.step(`${spec.displayName}: ${p.cli} installed; sign-in state unknown.`);\n break;\n }\n }\n\n if (stored.length === 0) {\n log.info(\"No arbella-stored tokens (the gh/glab CLI handles sign-in, or none yet).\");\n return;\n }\n log.info(\"arbella-stored tokens (fallback; metadata only):\");\n for (const c of stored) {\n log.step(\n `${c.host} · ${c.provider} · via ${c.source} · token ${c.tokenHint} · ` +\n `since ${c.createdAt}`,\n );\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* logout */\n/* -------------------------------------------------------------------------- */\n\n/** Execute `arbella auth logout`. Directly callable (for tests). */\nexport async function runLogout(opts: AuthLogoutOptions): Promise<void> {\n const provider = opts.provider\n ? resolveProviderSpec(opts.provider)\n : undefined;\n\n if (opts.provider && !provider) {\n throw new Error(\n `Unknown provider \"${opts.provider}\". Use \"github\" or \"gitlab\".`,\n );\n }\n\n const removed = await authLogout(provider?.id);\n\n if (provider) {\n log.success(\n `Signed out of ${provider.displayName}` +\n (removed > 0 ? ` and cleared ${removed} stored token(s).` : \".\"),\n );\n } else if (removed > 0) {\n log.success(`Cleared ${removed} arbella-stored token(s).`);\n } else {\n log.info(\"No arbella-stored tokens to clear.\");\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Resolve a `--provider` flag into a provider spec. Accepts \"github\"/\"gitlab\"\n * (case-insensitive); returns undefined for anything else so the caller can\n * error or default as appropriate.\n */\nfunction resolveProviderSpec(raw: string | undefined): AuthProviderSpec | undefined {\n if (!raw) return undefined;\n const id = raw.trim().toLowerCase();\n if (id === \"github\" || id === \"gitlab\") {\n return providerById(id as AuthProviderId);\n }\n return undefined;\n}\n\nexport default runLogin;\n","/**\n * `arbella pull [repoUrl]` — rebuild this machine's AI dev setup from the\n * private repo (R6, R8, R9, R12, R14).\n *\n * High-level flow:\n * 1. Resolve the repo: the positional `[repoUrl]` arg wins; otherwise fall back\n * to `config.repo`. Clone it if it is not already present locally, else pull\n * so the working copy is fresh (R12).\n * 2. Parse `arbella.json` (ArbellaMeta) at the repo root.\n * 3. Decide the set of tools to restore: intersection of the repo's captured\n * tools and (a) the `--tools` flag if given, else (b) the configured tools,\n * else (c) every captured tool.\n * 4. For each tool, load its RestoreData (frozen files + symlinks reconstructed\n * from <repoRoot>/<tool>/files, manifest via parseManifest) and build the\n * adapter's planned actions. Probe which CLIs are missing (R6).\n * 5. If `--dry-run`: print the full RestorePlan and STOP (no mutations).\n * 6. Otherwise — R14 SAFETY BACKUP FIRST: copy every existing target tool home\n * (~/.claude, ~/.codex, ~/.cursor) to a timestamped dir under dataDir()\n * BEFORE anything is overwritten. Then auto-install any missing CLIs (R6),\n * and run each adapter's restore() (which places files, recreates symlinks,\n * reinstalls plugins/marketplaces/skills, re-enables plugins, installs npm\n * globals — all guarded + best-effort per adapter).\n * 7. Deploy the shared instructions (R9) to BOTH tools when meta.sharedInstructions\n * is set: write shared/instructions.md to each sharedInstructionsTargets()\n * destination; the cursor adapter additionally writes a Cursor rule from the\n * same source inside its own restore().\n * 8. Print a re-auth reminder: secret files (.credentials.json / auth.json) were\n * NEVER carried, so the user must re-login. Never prints any secret value.\n *\n * SECURITY: this command never reads, prints, copies, or restores secret files —\n * those are excluded by the denylist at capture time and are simply absent from\n * the repo. The only secret-adjacent output is the human re-login reminder.\n *\n * CROSS-OS: every path is resolved via src/platform/os.ts + node:path.join; the\n * templater rehydrates {{HOME}}/{{USER}}/{{TOOL_HOME}} placeholders to THIS\n * machine's values inside each adapter. No machine path is ever hardcoded.\n *\n * The command is thin: it owns flag parsing + the clock\n * (timestamps are passed inward), assembles CoreServices/RestoreContext, and\n * delegates the real work to the adapters + core modules.\n */\n\nimport path from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport type {\n CapturedFile,\n CapturedSymlink,\n Logger,\n OS,\n RestoreAction,\n RestorePlan,\n ArbellaMeta,\n ToolId,\n ToolManifest,\n} from \"../types.js\";\nimport { TOOL_IDS } from \"../types.js\";\nimport type {\n Adapter,\n CoreServices,\n RestoreContext,\n RestoreData,\n} from \"../adapters/adapter.interface.js\";\n\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\nimport {\n cliBinaryName,\n dataDir,\n detectOS,\n toolHomeDir,\n} from \"../platform/os.js\";\nimport {\n ensureCli,\n which,\n npmInstallGlobal,\n isForeignPlatformPackage,\n} from \"../platform/install.js\";\nimport { createSanitizer } from \"../core/sanitizer/index.js\";\nimport { createTemplater } from \"../core/templater/index.js\";\nimport { buildVariables } from \"../core/templater/variables.js\";\nimport { loadConfigOrDefault } from \"../core/config/index.js\";\nimport type { RepoConfig } from \"../core/config/schema.js\";\nimport { ensureLocalClone, pullWithAuth } from \"../core/repo/index.js\";\nimport type { RepoAuthHooks } from \"../core/repo/index.js\";\nimport { buildRepoAuthHooks } from \"./_context.js\";\nimport { ensureDeps } from \"./setup.js\";\nimport * as git from \"../core/repo/git.js\";\nimport {\n parseManifest,\n parseMeta,\n emptyManifest,\n sharedInstructionsTargets,\n SHARED_INSTRUCTIONS_REPO_PATH,\n} from \"../core/manifest/index.js\";\n\nimport { claudeAdapter } from \"../adapters/claude/index.js\";\nimport { codexAdapter } from \"../adapters/codex/index.js\";\nimport { cursorAdapter } from \"../adapters/cursor/index.js\";\nimport { planActions as claudePlanActions } from \"../adapters/claude/restore.js\";\nimport { planActions as codexPlanActions } from \"../adapters/codex/restore.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/** CLI options for `arbella pull` (commander fills these from flags). */\nexport interface RestoreOptions {\n /** Plan + report only; perform no filesystem or install actions (R14). */\n dryRun?: boolean;\n /** Override the repo URL (otherwise the positional arg, then config.repo). */\n repo?: string;\n /** Comma-separated tool subset to restore (intersected with the repo's tools). */\n tools?: string;\n /** Restore even when the local machine is configured as source of truth (R12). */\n force?: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Adapter wiring */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Per-tool adapter + its (optional) standalone planActions exporter. The Adapter\n * interface itself only exposes restore(); planActions lives as a sibling module\n * export for claude/codex. Cursor has no separate\n * planner — its dry-run plan is derived by running restore() with dryRun=true,\n * which logs the intended writes (see cursor/index.ts). We model that uniformly.\n */\ninterface ToolWiring {\n readonly id: ToolId;\n readonly adapter: Adapter;\n /** Compute the action list without executing, when the tool provides one. */\n readonly planActions?: (\n ctx: RestoreContext,\n data: RestoreData,\n ) => Promise<RestoreAction[]>;\n}\n\nconst WIRING: readonly ToolWiring[] = [\n { id: \"claude\", adapter: claudeAdapter, planActions: claudePlanActions },\n { id: \"codex\", adapter: codexAdapter, planActions: codexPlanActions },\n { id: \"cursor\", adapter: cursorAdapter },\n];\n\n/** Look up the wiring for a tool id (every ToolId has an entry). */\nfunction wiringFor(id: ToolId): ToolWiring {\n const w = WIRING.find((entry) => entry.id === id);\n if (!w) throw new Error(`No restore wiring for tool \"${id}\".`);\n return w;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Service / context assembly */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build the injected CoreServices for a given tool home on the TARGET machine.\n * Built inline (this command does not own init.ts, which owns _context.ts):\n * default fs/log singletons, sanitizer/templater factories, live machine vars\n * (so {{TOOL_HOME}} expands to the correct child of {{HOME}}), and detectOS().\n */\nfunction buildCoreServices(toolHome: string, os: OS): CoreServices {\n return {\n fs,\n log,\n sanitizer: createSanitizer(),\n templater: createTemplater(),\n vars: buildVariables(toolHome),\n os,\n };\n}\n\n/** Assemble the RestoreContext an adapter consumes for one tool. */\nfunction buildRestoreContext(args: {\n toolId: ToolId;\n repoRoot: string;\n sourceOfTruth: \"local\" | \"repo\";\n dryRun: boolean;\n os: OS;\n}): RestoreContext {\n const toolHome = toolHomeDir(args.toolId);\n const repoToolDir = path.join(args.repoRoot, args.toolId);\n return {\n ...buildCoreServices(toolHome, args.os),\n toolHome,\n repoToolDir,\n repoRoot: args.repoRoot,\n sourceOfTruth: args.sourceOfTruth,\n dryRun: args.dryRun,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Repo reading: reconstruct RestoreData from <repoRoot>/<tool>/files */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Heuristic binary detection (mirrors the adapters): a file is treated as binary\n * when its first chunk contains a NUL byte. Binary files round-trip as base64;\n * text files round-trip as UTF-8 + templater rehydration on the adapter side.\n */\nfunction looksBinary(buf: Buffer): boolean {\n const n = Math.min(buf.length, 8000);\n for (let i = 0; i < n; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n/**\n * Recursively walk `<tool>/files` inside the cloned repo and reconstruct the\n * CapturedFile[] / CapturedSymlink[] the adapter expects. `repoPath` is rebuilt\n * with the canonical \"<tool>/files/<relPosix>\" prefix (POSIX separators) so the\n * adapter's stripPrefix() maps it back onto the target tool home.\n *\n * Symlinks captured under skills/ are preserved verbatim (their target is the\n * data). Regular files are classified binary vs. text on read. Missing dirs are\n * tolerated (graceful absence) and simply yield nothing.\n */\nasync function readToolFrozen(\n repoToolDir: string,\n tool: ToolId,\n): Promise<{ files: CapturedFile[]; symlinks: CapturedSymlink[] }> {\n const filesRoot = path.join(repoToolDir, \"files\");\n const files: CapturedFile[] = [];\n const symlinks: CapturedSymlink[] = [];\n\n // Nothing frozen for this tool (e.g. cursor absent at capture) -> empty.\n if ((await fs.statKind(filesRoot)) !== \"dir\") {\n return { files, symlinks };\n }\n\n /** relParts: POSIX path segments under filesRoot accumulated so far. */\n async function walk(absDir: string, relParts: string[]): Promise<void> {\n const entries = await fs.list(absDir);\n // Stable order so dry-run output and writes are deterministic.\n entries.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n\n for (const name of entries) {\n const abs = path.join(absDir, name);\n const nextRel = [...relParts, name];\n const relPosix = nextRel.join(\"/\");\n const repoPath = `${tool}/files/${relPosix}`;\n const kind = await fs.statKind(abs);\n\n if (kind === \"symlink\") {\n let target: string;\n try {\n target = await fs.readLink(abs);\n } catch (err) {\n log.warn(\n `restore: could not read symlink ${repoPath}: ${errMsg(err)}`,\n );\n continue;\n }\n symlinks.push({ repoPath, target });\n continue;\n }\n\n if (kind === \"dir\") {\n await walk(abs, nextRel);\n continue;\n }\n\n if (kind !== \"file\") continue;\n\n let bytes: Buffer;\n try {\n bytes = await fs.readBytes(abs);\n } catch (err) {\n log.warn(`restore: could not read ${repoPath}: ${errMsg(err)}`);\n continue;\n }\n\n // Preserve the executable bit (hooks/statusline scripts) where the host FS\n // exposes it; harmless on Windows. Captured as the POSIX mode.\n const mode = await fileMode(abs);\n\n if (looksBinary(bytes)) {\n files.push({\n repoPath,\n content: bytes.toString(\"base64\"),\n binary: true,\n ...(mode !== undefined ? { mode } : {}),\n });\n } else {\n files.push({\n repoPath,\n content: bytes.toString(\"utf8\"),\n ...(mode !== undefined ? { mode } : {}),\n });\n }\n }\n }\n\n await walk(filesRoot, []);\n return { files, symlinks };\n}\n\n/**\n * Read a tool's manifest.json from <repoRoot>/<tool>/manifest.json and validate\n * it. A missing manifest is tolerated (returns an empty manifest) so a tool that\n * only has frozen files can still be restored. A PRESENT-but-corrupt manifest\n * throws (via parseManifest) so the user gets a clear error rather than silent\n * data loss.\n */\nasync function readToolManifest(\n repoToolDir: string,\n tool: ToolId,\n): Promise<ToolManifest> {\n const manifestPath = path.join(repoToolDir, \"manifest.json\");\n if (!(await fs.exists(manifestPath))) {\n log.debug(`restore: no manifest.json for ${tool}; using empty manifest.`);\n return emptyManifest(tool);\n }\n const raw = await fs.read(manifestPath);\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `Backup repo manifest for ${tool} is not valid JSON ` +\n `(${manifestPath}): ${errMsg(err)}`,\n );\n }\n return parseManifest(json);\n}\n\n/** Assemble the full RestoreData (manifest + frozen files/symlinks) for a tool. */\nasync function loadRestoreData(\n repoRoot: string,\n tool: ToolId,\n): Promise<RestoreData> {\n const repoToolDir = path.join(repoRoot, tool);\n const manifest = await readToolManifest(repoToolDir, tool);\n const { files, symlinks } = await readToolFrozen(repoToolDir, tool);\n return { manifest, files, symlinks };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Tool selection */\n/* -------------------------------------------------------------------------- */\n\n/** Parse a comma-separated `--tools` value into validated ToolIds (ignores junk). */\nfunction parseToolsFlag(raw: string | undefined): ToolId[] | undefined {\n if (raw === undefined) return undefined;\n const wanted = raw\n .split(\",\")\n .map((t) => t.trim().toLowerCase())\n .filter(Boolean);\n const ids = wanted.filter((t): t is ToolId =>\n (TOOL_IDS as readonly string[]).includes(t),\n );\n return ids;\n}\n\n/**\n * Decide which tools to restore. Always constrained to what the repo actually\n * captured (`meta.tools`). Then narrowed by `--tools` if provided, else by the\n * configured tools, else left as the full captured set. Order follows the\n * canonical TOOL_IDS order for deterministic output.\n */\nfunction selectTools(\n meta: ArbellaMeta,\n flagTools: ToolId[] | undefined,\n configTools: ToolId[],\n): ToolId[] {\n const captured = new Set<ToolId>(meta.tools);\n\n let candidate: Set<ToolId>;\n if (flagTools && flagTools.length > 0) {\n candidate = new Set(flagTools);\n } else if (configTools.length > 0) {\n candidate = new Set(configTools);\n } else {\n candidate = new Set(captured);\n }\n\n return TOOL_IDS.filter((id) => captured.has(id) && candidate.has(id));\n}\n\n/* -------------------------------------------------------------------------- */\n/* R14 — pre-restore safety backup */\n/* -------------------------------------------------------------------------- */\n\n/** One recorded safety-backup copy: which tool, and where it was copied to. */\ninterface SafetyBackup {\n tool: ToolId;\n source: string;\n dest: string;\n}\n\n/**\n * Make a timestamped safety copy of each existing target tool home BEFORE any\n * restore mutation (R14). Destinations live under dataDir()/safety-backups so\n * they never pollute a tool home. Only existing homes are copied; absent ones\n * are skipped. `iso` is supplied by the caller (commands own the clock).\n *\n * Filesystem-safe timestamp: ISO \":\"/\".\" are replaced with \"-\".\n */\nasync function safetyBackup(\n tools: ToolId[],\n iso: string,\n): Promise<SafetyBackup[]> {\n const stamp = iso.replace(/[:.]/g, \"-\");\n const backupsRoot = path.join(dataDir(), \"safety-backups\");\n const made: SafetyBackup[] = [];\n\n for (const tool of tools) {\n const source = toolHomeDir(tool);\n if (!(await fs.exists(source))) {\n log.debug(`restore: no existing ${tool} home to back up (${source}).`);\n continue;\n }\n const dest = path.join(backupsRoot, `${tool}-${stamp}`);\n try {\n await fs.copy(source, dest);\n made.push({ tool, source, dest });\n log.step(`Safety backup: ${source} -> ${dest}`);\n } catch (err) {\n // A failed safety copy is serious: warn loudly but do not abort the whole\n // restore over a single tool's snapshot (the others still get protected).\n log.warn(\n `restore: failed to safety-backup ${tool} (${source}): ${errMsg(err)}`,\n );\n }\n }\n return made;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Shared instructions (R9) deployment */\n/* -------------------------------------------------------------------------- */\n\n/**\n * When the repo stored a single shared instructions file (CLAUDE.md == AGENTS.md\n * at capture, R9), deploy it to BOTH tool targets: ~/.claude/CLAUDE.md and\n * ~/.codex/AGENTS.md (from sharedInstructionsTargets()). The cursor adapter\n * deploys its own Cursor rule from the same source inside its restore(), so it\n * is intentionally not handled here.\n *\n * Respects `dryRun` (reports only). Best-effort + graceful: a missing shared\n * file is logged, not fatal. Only deploys to a target whose tool is in scope.\n */\nasync function deploySharedInstructions(\n repoRoot: string,\n toolsInScope: ToolId[],\n dryRun: boolean,\n): Promise<void> {\n const sharedAbs = path.join(\n repoRoot,\n ...SHARED_INSTRUCTIONS_REPO_PATH.split(\"/\"),\n );\n if (!(await fs.exists(sharedAbs))) {\n log.warn(\n \"restore: meta.sharedInstructions is set but \" +\n `${SHARED_INSTRUCTIONS_REPO_PATH} is missing from the repo; skipping ` +\n \"shared-instructions deployment.\",\n );\n return;\n }\n\n const inScope = new Set(toolsInScope);\n const content = await fs.read(sharedAbs);\n\n for (const target of sharedInstructionsTargets()) {\n if (!inScope.has(target.tool)) continue;\n const dest = path.join(toolHomeDir(target.tool), target.relPath);\n if (dryRun) {\n log.step(`Would deploy shared instructions -> ${dest}`);\n continue;\n }\n try {\n await fs.write(dest, content);\n log.step(`Deployed shared instructions -> ${dest}`);\n } catch (err) {\n log.warn(\n `restore: failed to deploy shared instructions to ${dest}: ${errMsg(err)}`,\n );\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Shared npm-globals install pass (R8) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Collect the UNION of npm global packages across every restored tool's manifest,\n * deduped by package name, in deterministic order.\n *\n * Both adapters capture the same full machine list (listNpmGlobals), so without\n * dedup a multi-tool restore would `npm i -g` the same package once per tool. This\n * is the single source of truth the command uses to both PLAN (system-level\n * install-npm-global actions) and EXECUTE the installs, guaranteeing the dry-run\n * matches reality and each package is installed exactly once — and, critically,\n * that a codex-only restore still installs them (the gap this fixes).\n */\nfunction dedupeNpmGlobals(prepared: PreparedTool[]): string[] {\n const seen = new Set<string>();\n for (const tool of prepared) {\n for (const g of tool.data.manifest.npmGlobals) {\n if (!g.package) continue;\n // Never reinstall arbella itself, and skip platform-specific native builds\n // that only install on their own OS (e.g. *-darwin-arm64 on Linux).\n if (g.package === \"arbella\" || isForeignPlatformPackage(g.package)) continue;\n seen.add(g.package);\n }\n }\n return [...seen].sort((a, b) => a.localeCompare(b));\n}\n\n/** Build the system-level install-npm-global plan actions (one per package). */\nfunction npmGlobalActions(packages: string[]): RestoreAction[] {\n return packages.map((pkg) => ({\n type: \"install-npm-global\" as const,\n tool: \"system\" as const,\n description: `npm install -g ${pkg}`,\n }));\n}\n\n/**\n * Install the deduped npm globals once each (best-effort). A missing npm or a\n * single failed package is downgraded to a warning so the rest of the restore is\n * never stranded — npm globals are convenience, not correctness. No-op (with a\n * debug line) when there is nothing to install.\n */\nasync function installSharedNpmGlobals(packages: string[]): Promise<void> {\n if (packages.length === 0) {\n log.debug(\"restore: no npm globals to install.\");\n return;\n }\n log.info(`Installing ${packages.length} npm global(s): ${packages.join(\", \")}`);\n for (const pkg of packages) {\n try {\n await npmInstallGlobal(pkg);\n log.step(`npm i -g ${pkg}`);\n } catch (err) {\n log.warn(`restore: npm i -g ${pkg} failed (continuing): ${errMsg(err)}`);\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Plan building + printing */\n/* -------------------------------------------------------------------------- */\n\n/** Per-tool prepared data + context + planned actions, computed once up front. */\ninterface PreparedTool {\n id: ToolId;\n adapter: Adapter;\n data: RestoreData;\n actions: RestoreAction[];\n}\n\n/** Strip the canonical \"<tool>/files/\" prefix from a reconstructed repoPath. */\nfunction stripFilesPrefix(tool: ToolId, repoPath: string): string {\n const prefix = `${tool}/files/`;\n const norm = repoPath.replace(/\\\\/g, \"/\");\n return norm.startsWith(prefix) ? norm.slice(prefix.length) : norm;\n}\n\n/**\n * Synthesize a plan fragment for a tool that has no dedicated planActions export\n * (cursor). Mirrors the planner shape used by the claude/codex restore modules:\n * one write-file/write-symlink action per frozen artifact, honoring sourceOfTruth\n * (when local is authoritative, an existing destination is kept, not overwritten,\n * so we omit the action just as the real restore would skip it).\n */\nasync function fallbackActions(\n ctx: RestoreContext,\n tool: ToolId,\n data: RestoreData,\n): Promise<RestoreAction[]> {\n const out: RestoreAction[] = [];\n\n for (const file of data.files) {\n const rel = stripFilesPrefix(tool, file.repoPath);\n const dest = path.join(ctx.toolHome, ...rel.split(\"/\"));\n const overwrites = await ctx.fs.exists(dest);\n if (ctx.sourceOfTruth === \"local\" && overwrites) continue;\n out.push({\n type: \"write-file\",\n tool,\n targetPath: dest,\n description: `Write ${rel}`,\n overwrites,\n });\n }\n\n for (const link of data.symlinks) {\n const rel = stripFilesPrefix(tool, link.repoPath);\n const dest = path.join(ctx.toolHome, ...rel.split(\"/\"));\n const overwrites = (await ctx.fs.statKind(dest)) !== \"missing\";\n if (ctx.sourceOfTruth === \"local\" && overwrites) continue;\n out.push({\n type: \"write-symlink\",\n tool,\n targetPath: dest,\n description: `Symlink ${rel} -> ${link.target}`,\n overwrites,\n });\n }\n\n return out;\n}\n\n/**\n * Build the full RestorePlan: gather each tool's RestoreData, compute its planned\n * actions (via the tool's planActions when available; otherwise a synthesized\n * file/symlink action list — see fallbackActions), and probe which tool CLIs are\n * missing on this machine (R6). No mutation happens here.\n *\n * Actions are always computed against a dryRun context so planning is side-effect\n * free regardless of the real run mode.\n */\nasync function buildPlan(args: {\n repoRoot: string;\n tools: ToolId[];\n sourceOfTruth: \"local\" | \"repo\";\n os: OS;\n}): Promise<{ plan: RestorePlan; prepared: PreparedTool[]; npmGlobals: string[] }> {\n const prepared: PreparedTool[] = [];\n const actions: RestoreAction[] = [];\n const missingClis: ToolId[] = [];\n\n for (const id of args.tools) {\n const wiring = wiringFor(id);\n const data = await loadRestoreData(args.repoRoot, id);\n\n // Plan against a dry-run context (pure: no writes, no installs).\n const planCtx = buildRestoreContext({\n toolId: id,\n repoRoot: args.repoRoot,\n sourceOfTruth: args.sourceOfTruth,\n dryRun: true,\n os: args.os,\n });\n\n // Prefer the tool's own planner (claude/codex). For tools without one\n // (cursor), synthesize file/symlink write actions from the loaded RestoreData\n // so the dry-run plan is still complete and uniform.\n const toolActions = wiring.planActions\n ? await wiring.planActions(planCtx, data)\n : await fallbackActions(planCtx, id, data);\n actions.push(...toolActions);\n prepared.push({ id, adapter: wiring.adapter, data, actions: toolActions });\n\n // R6: is the tool's CLI present? (Cursor's CLI is frequently absent; that's\n // fine — installCli degrades gracefully on platforms with no headless path.)\n if (!(await which(cliBinaryName(id)))) {\n missingClis.push(id);\n actions.push({\n type: \"install-cli\",\n tool: id,\n description: `Install ${id} CLI (missing on this machine)`,\n });\n }\n }\n\n // R8: a SINGLE deduped npm-globals pass across all restored tools. Planned as\n // system-level actions here (matching what installSharedNpmGlobals will run) so\n // the dry-run plan is faithful and no package is double-installed.\n const npmGlobals = dedupeNpmGlobals(prepared);\n actions.push(...npmGlobalActions(npmGlobals));\n\n const plan: RestorePlan = {\n tools: args.tools,\n actions,\n missingClis,\n willBackupExisting: true,\n };\n return { plan, prepared, npmGlobals };\n}\n\n/** Pretty-print a RestorePlan to the (stderr) logger for --dry-run. */\nfunction printPlan(\n plan: RestorePlan,\n meta: ArbellaMeta,\n l: Logger = log,\n): void {\n l.info(`Restore plan (dry run) — ${plan.tools.length} tool(s)`);\n l.step(\n `Tools: ${plan.tools.length > 0 ? plan.tools.join(\", \") : \"(none)\"}`,\n );\n l.step(\n `A timestamped safety backup of existing tool homes WILL be taken first (R14).`,\n );\n if (plan.missingClis.length > 0) {\n l.step(`CLIs to auto-install: ${plan.missingClis.join(\", \")}`);\n } else {\n l.step(\"All required CLIs already present.\");\n }\n if (meta.sharedInstructions) {\n l.step(\n \"Shared instructions (R9) will be deployed to CLAUDE.md + AGENTS.md.\",\n );\n }\n\n const counts = countByType(plan.actions);\n l.info(`Planned actions: ${plan.actions.length}`);\n for (const [type, n] of counts) {\n l.step(`${type}: ${n}`);\n }\n for (const action of plan.actions) {\n const target = action.targetPath ? ` (${action.targetPath})` : \"\";\n const ow = action.overwrites ? \" [overwrites]\" : \"\";\n l.step(`${action.tool} · ${action.description}${target}${ow}`);\n }\n\n l.info(\n \"Secrets were NOT carried in the repo; after a real restore you will need \" +\n \"to re-authenticate (see the post-restore reminder).\",\n );\n}\n\n/** Tally actions by their `type`, preserving first-seen order. */\nfunction countByType(actions: RestoreAction[]): Array<[string, number]> {\n const map = new Map<string, number>();\n for (const a of actions) {\n map.set(a.type, (map.get(a.type) ?? 0) + 1);\n }\n return [...map.entries()];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Re-auth reminder (post-restore) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Print the post-restore re-login reminder. Secret files are excluded by the\n * denylist and are therefore never present in the repo, so the user must\n * re-authenticate each restored tool. This prints GUIDANCE ONLY — never any\n * secret value, path-to-secret content, or token.\n */\nfunction printReauthReminder(tools: ToolId[]): void {\n log.info(\"Restore complete. Secrets were intentionally NOT included.\");\n log.step(\n \"You will need to re-authenticate the restored tools (no credentials were copied):\",\n );\n for (const tool of tools) {\n switch (tool) {\n case \"claude\":\n log.step(\n \"claude: run `claude login` (or `/login` inside Claude Code) to \" +\n \"re-create ~/.claude/.credentials.json.\",\n );\n break;\n case \"codex\":\n log.step(\n \"codex: run `codex login` to re-create ~/.codex/auth.json.\",\n );\n break;\n case \"cursor\":\n log.step(\n \"cursor: sign in via the Cursor app; re-add any MCP server API keys \" +\n \"in ~/.cursor/mcp.json (their values were redacted on backup).\",\n );\n break;\n }\n }\n log.step(\n \"If you also backed up secrets separately, run `arbella secrets import` \" +\n \"with your passphrase to restore them locally.\",\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Repo resolution */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Resolve the backup repo to restore from. Precedence: the positional `repoUrl`\n * arg, then `--repo`, then the configured `config.repo`. Returns a RepoConfig\n * with a concrete localPath (clone destination) — falling back to a deterministic\n * path under dataDir() when restoring from an ad-hoc URL that has no configured\n * clone location yet.\n */\nasync function resolveRepo(\n repoUrl: string | undefined,\n optRepo: string | undefined,\n): Promise<RepoConfig> {\n const config = await loadConfigOrDefault();\n const explicit = repoUrl ?? optRepo;\n\n if (explicit && explicit.trim() !== \"\") {\n const url = explicit.trim();\n // Reuse the configured clone path only when it matches the same URL;\n // otherwise clone the ad-hoc URL into a stable per-repo dir under dataDir().\n const sameAsConfig =\n config.repo.url.trim() !== \"\" && config.repo.url.trim() === url;\n const localPath =\n sameAsConfig && config.repo.localPath\n ? config.repo.localPath\n : path.join(dataDir(), \"restore\", slugForUrl(url));\n return { provider: config.repo.provider, url, localPath };\n }\n\n // No explicit URL — fall back to the configured repo.\n if (!config.repo.url || config.repo.url.trim() === \"\") {\n throw new Error(\n \"No repo to pull from. Pass a repo URL \" +\n \"(`arbella pull <repo-url>`) or run `arbella init` first.\",\n );\n }\n const localPath =\n config.repo.localPath && config.repo.localPath.trim() !== \"\"\n ? config.repo.localPath\n : path.join(dataDir(), \"restore\", slugForUrl(config.repo.url));\n return { provider: config.repo.provider, url: config.repo.url, localPath };\n}\n\n/** Derive a filesystem-safe slug from a clone URL for an ad-hoc clone dir. */\nfunction slugForUrl(url: string): string {\n const trimmed = url.trim().replace(/\\.git$/i, \"\").replace(/\\/+$/, \"\");\n // Keep the last 1-2 path segments (owner/repo) for readability, sanitized.\n const tail = trimmed.split(/[/:]/).filter(Boolean).slice(-2).join(\"-\");\n const safe = tail.replace(/[^A-Za-z0-9._-]/g, \"-\").replace(/-+/g, \"-\");\n return safe.length > 0 ? safe : \"backup\";\n}\n\n/**\n * Ensure the repo is present locally and as fresh as possible. Clones when\n * absent (via ensureLocalClone), otherwise pulls (R12 repo-as-source freshness).\n * A pull failure (e.g. offline) is downgraded to a warning so a restore from an\n * already-cloned repo still proceeds with the local copy.\n *\n * AUTH (P5): `auth` carries the interactive sign-in seams (gh/glab-first; install\n * on demand or device-flow/token fallback) threaded down into the clone/pull so a\n * PRIVATE backup repo authenticates automatically, then restore continues.\n */\nasync function ensureRepoReady(\n repo: RepoConfig,\n auth: RepoAuthHooks,\n): Promise<void> {\n const alreadyCloned = await git.isGitRepo(repo.localPath);\n await ensureLocalClone(repo, auth);\n if (alreadyCloned) {\n try {\n log.step(`Updating backup repo at ${repo.localPath}`);\n await pullWithAuth(repo, auth);\n } catch (err) {\n log.warn(\n `restore: could not update repo (using local copy): ${errMsg(err)}`,\n );\n }\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Run the restore. `repoUrl` is the optional positional arg; `opts` are the\n * parsed flags. This is the single directly-callable entrypoint (commander's\n * action handler delegates here, and tests call it without commander).\n */\nexport async function run(\n repoUrl: string | undefined,\n opts: RestoreOptions,\n): Promise<void> {\n const os = detectOS();\n const dryRun = opts.dryRun === true;\n\n // ---- 0. Ensure git is available before we touch any repo (P3 on-demand) --\n // A dry run only reads an already-cloned repo, so don't force-install\n // git just to plan; the real run needs it to clone/pull.\n if (!dryRun) {\n await ensureDeps([\"git\"], { required: true });\n }\n\n // ---- 1. Resolve + ready the repo (gh/glab-first auth on private repos) ---\n const repo = await resolveRepo(repoUrl, opts.repo);\n log.info(`Restoring from ${repo.url}`);\n // Interactive auth seams (skipped for a pure dry run, which shouldn't prompt).\n const authHooks = buildRepoAuthHooks({\n createdAt: new Date().toISOString(),\n interactive: !dryRun,\n });\n await ensureRepoReady(repo, authHooks);\n const repoRoot = repo.localPath;\n\n // ---- 2. Parse arbella.json (ArbellaMeta) ------------------------------\n const metaPath = path.join(repoRoot, \"arbella.json\");\n if (!(await fs.exists(metaPath))) {\n throw new Error(\n `Not a arbella backup repo: ${metaPath} is missing. ` +\n \"Did you point restore at the right repository?\",\n );\n }\n let meta: ArbellaMeta;\n try {\n meta = parseMeta(JSON.parse(await fs.read(metaPath)));\n } catch (err) {\n throw new Error(\n `Could not read ${metaPath} (corrupt arbella.json?): ${errMsg(err)}`,\n );\n }\n\n // ---- 3. Decide which tools to restore -----------------------------------\n const config = await loadConfigOrDefault();\n const flagTools = parseToolsFlag(opts.tools);\n const tools = selectTools(meta, flagTools, config.tools);\n if (tools.length === 0) {\n log.warn(\n \"No tools to restore (the repo + your selection have no overlap). \" +\n `Repo captured: ${meta.tools.join(\", \") || \"(none)\"}.`,\n );\n return;\n }\n\n // Source-of-truth direction (R12). `--force` forces repo-wins so existing\n // local files are overwritten even when the config says local is authoritative.\n const sourceOfTruth: \"local\" | \"repo\" = opts.force\n ? \"repo\"\n : config.sourceOfTruth;\n log.debug(\n `restore: tools=[${tools.join(\", \")}] sourceOfTruth=${sourceOfTruth} ` +\n `dryRun=${dryRun}`,\n );\n\n // ---- 4. Build the plan (no mutation) ------------------------------------\n const { plan, prepared, npmGlobals } = await buildPlan({\n repoRoot,\n tools,\n sourceOfTruth,\n os,\n });\n\n // ---- 5. Dry run: print the plan and STOP --------------------------------\n if (dryRun) {\n printPlan(plan, meta);\n log.info(\"Dry run: no changes were made.\");\n return;\n }\n\n // ---- 6. R14 safety backup BEFORE any overwrite --------------------------\n const iso = new Date().toISOString();\n log.info(\"Creating safety backups of existing tool homes (R14)…\");\n const backups = await safetyBackup(tools, iso);\n if (backups.length === 0) {\n log.debug(\"restore: no existing tool homes needed backing up.\");\n }\n\n // ---- 7. Auto-install missing CLIs (R6) ----------------------------------\n if (plan.missingClis.length > 0) {\n log.info(`Installing missing CLIs: ${plan.missingClis.join(\", \")}`);\n for (const id of plan.missingClis) {\n try {\n await ensureCli(id, os);\n } catch (err) {\n log.warn(\n `restore: could not install ${id} CLI (continuing): ${errMsg(err)}`,\n );\n }\n }\n }\n\n // ---- 8. Restore each tool -----------------------------------------------\n for (const tool of prepared) {\n log.info(`Restoring ${tool.id}…`);\n const ctx = buildRestoreContext({\n toolId: tool.id,\n repoRoot,\n sourceOfTruth,\n dryRun: false,\n os,\n });\n try {\n await tool.adapter.restore(ctx, tool.data);\n log.success(`${tool.id}: restored`);\n } catch (err) {\n // One tool failing must not strand the others (the safety backup is the\n // user's recovery path). Surface it and keep going.\n log.error(`restore: ${tool.id} failed: ${errMsg(err)}`);\n }\n }\n\n // ---- 8b. Shared npm-globals pass (R8): install each package ONCE across all\n // restored tools (deduped). Runs after the adapters so any tool-CLI\n // install above is already on PATH. ------------------------------\n await installSharedNpmGlobals(npmGlobals);\n\n // ---- 9. Deploy shared instructions (R9) to both tools -------------------\n if (meta.sharedInstructions) {\n log.info(\"Deploying shared instructions to Claude + Codex (R9)…\");\n await deploySharedInstructions(repoRoot, tools, false);\n }\n\n // ---- 10. Re-auth reminder ----------------------------------------------\n printReauthReminder(tools);\n if (backups.length > 0) {\n log.info(\n `Previous tool homes were safely backed up under ${path.join(\n dataDir(),\n \"safety-backups\",\n )} (restore them manually if anything looks wrong).`,\n );\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* commander registration */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Attach the `pull` subcommand to the program. The command is intentionally\n * thin: it parses flags, then delegates to {@link run}. The top-level error\n * handler in src/index.ts turns a thrown error into a non-zero exit.\n *\n * Legacy alias: `restore` is also registered, hidden from `--help`, so old\n * habits, scripts, and previously generated repo READMEs (`arbella restore <url>`)\n * keep working. `pull` is \"clone if needed, then apply\" — not a bare `git pull`.\n */\nexport function register(program: Command): void {\n const configure = (cmd: Command): Command =>\n cmd\n .description(\n \"Pull your AI dev setup from your private repo (installs missing CLIs, \" +\n \"places files, reinstalls plugins/skills, deploys shared instructions).\",\n )\n .argument(\n \"[repo-url]\",\n \"Repo URL to pull from (defaults to the configured repo).\",\n )\n .option(\n \"--dry-run\",\n \"Preview the pull: show what would change without writing anything.\",\n )\n .option(\n \"--repo <url>\",\n \"Repo URL (alternative to the positional argument).\",\n )\n .option(\n \"--tools <list>\",\n \"Comma-separated subset of tools to pull (e.g. claude,codex).\",\n )\n .option(\n \"--force\",\n \"Overwrite existing local files even when local is the source of truth.\",\n )\n .action(async (repoArg: string | undefined, opts: RestoreOptions) => {\n await run(repoArg, opts);\n });\n\n configure(program.command(\"pull\"));\n configure(program.command(\"restore\", { hidden: true }));\n}\n\n/* -------------------------------------------------------------------------- */\n/* internal helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Best-effort message extraction from an unknown thrown value. */\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Best-effort POSIX file mode for a path, used to preserve the executable bit on\n * hooks/statusline scripts during restore. Returns undefined when the mode can't\n * be read (e.g. unusual host FS) so the writer falls back to its default.\n *\n * Uses node:fs/promises.lstat directly (not the FsService, which intentionally\n * exposes no stat-mode surface). Read-only; touches nothing.\n */\nasync function fileMode(abs: string): Promise<number | undefined> {\n try {\n const { promises: fsp } = await import(\"node:fs\");\n const st = await fsp.lstat(abs);\n // Mask to the low 12 bits (perms + setuid/gid/sticky) — the portable subset.\n return st.mode & 0o7777;\n } catch {\n return undefined;\n }\n}\n\nexport default run;\n","/**\n * `arbella status` — show what a backup *would* change (R3 status).\n *\n * Read-only. Never writes to the tool homes or the repo working tree, never\n * commits, never installs anything. It answers one question: \"if I ran\n * `arbella push` right now, what would change in the backup repo?\"\n *\n * Strategy:\n * 1. loadConfig() to learn the repo location, the in-scope tools, and options.\n * 2. ensureLocalClone() so there is a working copy to diff against (clone-only;\n * it does not pull/commit). If the repo has never been backed up, every\n * would-be file shows as \"added\".\n * 3. Read CLAUDE.md + AGENTS.md and decide R9 sharing exactly as backup does,\n * so the shared/instructions.md vs per-tool CLAUDE.md/AGENTS.md split lines\n * up with what a real backup would produce (otherwise the instructions file\n * would always look \"removed\").\n * 4. For each detected tool run adapter.capture(ctx, { skipInstructions }) with\n * dryRun:true. capture() never writes; it just computes the would-be frozen\n * files + symlinks + reinstall manifest + secret refs.\n * 5. Diff the would-be repo paths against what is committed on disk under\n * repo.localPath: classify each as added / changed / unchanged, and find\n * committed tool files that are no longer produced (removed).\n * 6. Diff each tool's would-be manifest against the committed manifest.json to\n * surface plugin / marketplace / skill / npm-global / enabled-plugin drift.\n * 7. Print a human table to STDERR (via log), or a machine-readable JSON report\n * to STDOUT when --json (so the JSON payload stays clean).\n *\n * No clock is used for any library decision; status only reads. (The only time\n * facts here come from the filesystem, not Date.now().)\n */\n\nimport path from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport type {\n CapturedFile,\n CapturedSymlink,\n CaptureResult,\n MarketplaceEntry,\n NpmGlobalEntry,\n PluginEntry,\n SecretRef,\n SkillEntry,\n ToolId,\n ToolManifest,\n} from \"../types.js\";\nimport type { CaptureContext } from \"../adapters/adapter.interface.js\";\n\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\nimport { detectOS, toolHomeDir } from \"../platform/os.js\";\n\nimport { loadConfig } from \"../core/config/index.js\";\nimport { ensureLocalClone } from \"../core/repo/index.js\";\nimport { buildVariables } from \"../core/templater/variables.js\";\nimport { sanitizer } from \"../core/sanitizer/index.js\";\nimport { templater } from \"../core/templater/index.js\";\nimport {\n parseManifest,\n serialize,\n shouldShareInstructions,\n buildSharedInstructionsFile,\n SHARED_INSTRUCTIONS_REPO_PATH,\n} from \"../core/manifest/index.js\";\n\nimport { claudeAdapter } from \"../adapters/claude/index.js\";\nimport { codexAdapter } from \"../adapters/codex/index.js\";\nimport { cursorAdapter } from \"../adapters/cursor/index.js\";\n\n// The R9 `skipInstructions` capture variant is NOT exposed on the Adapter\n// interface (Adapter.capture(ctx) takes no opts). Mirror the backup command and\n// import each tool's standalone capture module fn, which accepts the opts.\nimport { capture as captureClaude } from \"../adapters/claude/capture.js\";\nimport { capture as captureCodex } from \"../adapters/codex/capture.js\";\nimport { capture as captureCursor } from \"../adapters/cursor/index.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\nexport interface StatusOptions {\n /** Emit a machine-readable JSON report on stdout instead of a human table. */\n json?: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Report shapes (also the JSON payload contract) */\n/* -------------------------------------------------------------------------- */\n\n/** Classification of a single would-be repo file relative to what's committed. */\nexport type FileChangeKind = \"added\" | \"changed\" | \"removed\" | \"unchanged\";\n\nexport interface FileChange {\n /** Repo-relative POSIX path (e.g. \"claude/files/settings.json\"). */\n repoPath: string;\n kind: FileChangeKind;\n /** True for a symlink entry rather than a regular file. */\n symlink?: boolean;\n}\n\n/** A drift entry for a manifest collection (plugins, marketplaces, ...). */\nexport interface ManifestDrift {\n /** Which manifest collection drifted. */\n category:\n | \"plugins\"\n | \"marketplaces\"\n | \"skills\"\n | \"npmGlobals\"\n | \"enabledPlugins\";\n /** Items present locally but not in the committed manifest. */\n added: string[];\n /** Items present in the committed manifest but no longer produced locally. */\n removed: string[];\n /** Items present in both but whose recorded details differ. */\n changed: string[];\n}\n\nexport interface ToolStatus {\n tool: ToolId;\n /** False when the tool is configured but not present on this machine. */\n present: boolean;\n /** File-level changes (added/changed/removed only; unchanged are omitted). */\n files: FileChange[];\n /** Manifest collection drift (only categories that actually differ). */\n manifest: ManifestDrift[];\n /** Secrets that would be excluded/redacted (metadata only — never values). */\n secrets: Array<{ source: string; key: string; kind: SecretRef[\"kind\"]; description: string }>;\n /** Non-fatal warnings surfaced by capture (missing optional files, etc.). */\n warnings: string[];\n}\n\nexport interface StatusReport {\n /** Absolute path to the local backup-repo working copy. */\n repoPath: string;\n /** True when the working copy has at least one commit / existing content. */\n repoInitialized: boolean;\n /** R9: whether CLAUDE.md and AGENTS.md are shared as one file this run. */\n sharedInstructions: boolean;\n /** Whether secrets are being carried into the repo (config.includeSecrets). */\n includeSecrets: boolean;\n /** Per-tool status. */\n tools: ToolStatus[];\n /** Repo-level (non-tool) file changes, e.g. shared/instructions.md. */\n shared: FileChange[];\n /** True when nothing at all would change. */\n clean: boolean;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Adapter lookup (commands may import the three adapters directly) */\n/* -------------------------------------------------------------------------- */\n\n/** Standalone capture fn signature (accepts the R9 skipInstructions opt). */\ntype CaptureFn = (\n ctx: CaptureContext,\n opts?: { skipInstructions?: boolean },\n) => Promise<CaptureResult>;\n\n/** Per-tool wiring: the Adapter (for detect) + its opts-aware capture fn. */\nconst ADAPTERS: Record<ToolId, { adapter: import(\"../adapters/adapter.interface.js\").Adapter; capture: CaptureFn }> = {\n claude: { adapter: claudeAdapter, capture: captureClaude },\n codex: { adapter: codexAdapter, capture: captureCodex },\n cursor: { adapter: cursorAdapter, capture: captureCursor },\n};\n\n/* -------------------------------------------------------------------------- */\n/* Command registration */\n/* -------------------------------------------------------------------------- */\n\nexport function register(program: Command): void {\n program\n .command(\"status\")\n .description(\n \"Show what a push would change: files added/changed/removed, plugin drift, excluded secrets.\",\n )\n .option(\"--json\", \"Output a machine-readable JSON report on stdout.\")\n .action(async (opts: StatusOptions) => {\n await run(opts);\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Entry point */\n/* -------------------------------------------------------------------------- */\n\nexport async function run(opts: StatusOptions): Promise<void> {\n const config = await loadConfig();\n\n // Make sure there's a working copy to compare against. ensureLocalClone is\n // clone-only (it never pulls/commits), so this stays read-only for our needs.\n await ensureLocalClone(config.repo);\n\n const repoRoot = config.repo.localPath;\n const repoInitialized = await isRepoInitialized(repoRoot);\n\n // --- R9 decision (mirror backup) -----------------------------------------\n // Read the live CLAUDE.md / AGENTS.md to decide whether the two instruction\n // files would be stored once (shared/instructions.md) or per-tool.\n const claudeMd = await readIfExists(path.join(toolHomeDir(\"claude\"), \"CLAUDE.md\"));\n const agentsMd = await readIfExists(path.join(toolHomeDir(\"codex\"), \"AGENTS.md\"));\n const sharing = shouldShareInstructions(claudeMd, agentsMd);\n\n // --- Per-tool capture + diff ---------------------------------------------\n const toolStatuses: ToolStatus[] = [];\n\n for (const tool of config.tools) {\n const wiring = ADAPTERS[tool];\n if (!wiring) continue; // unknown id (schema prevents this, but stay safe)\n const { adapter, capture } = wiring;\n\n const present = await safeDetect(adapter.detect.bind(adapter));\n if (!present) {\n toolStatuses.push({\n tool,\n present: false,\n files: [],\n manifest: [],\n secrets: [],\n warnings: [`${tool}: not present on this machine; nothing to back up.`],\n });\n continue;\n }\n\n const ctx = buildCaptureContext(tool, config.includeSecrets, config.includeMemories);\n let result: CaptureResult;\n try {\n // claude/codex captures honor skipInstructions for R9; cursor ignores it.\n result = await capture(ctx, { skipInstructions: sharing });\n } catch (err) {\n // A capture failure for one tool must not abort the whole status run.\n toolStatuses.push({\n tool,\n present: true,\n files: [],\n manifest: [],\n secrets: [],\n warnings: [`${tool}: capture failed: ${errMessage(err)}`],\n });\n continue;\n }\n\n const fileChanges = await diffFiles(repoRoot, result.files, result.symlinks);\n const removed = await findRemovedToolFiles(\n repoRoot,\n tool,\n expectedRepoPathsForTool(result.files, result.symlinks),\n );\n fileChanges.push(...removed);\n\n const manifestDrift = await diffManifest(repoRoot, tool, result.manifest);\n\n toolStatuses.push({\n tool,\n present: true,\n files: sortChanges(fileChanges),\n manifest: manifestDrift,\n secrets: result.secrets.map((s) => ({\n source: s.source,\n key: s.key,\n kind: s.kind,\n description: s.description,\n })),\n warnings: result.warnings,\n });\n }\n\n // --- Shared instructions (R9) diff ---------------------------------------\n const sharedChanges: FileChange[] = [];\n if (sharing && claudeMd !== undefined) {\n const file = buildSharedInstructionsFile(claudeMd);\n const change = await classifyFile(repoRoot, file);\n if (change.kind !== \"unchanged\") sharedChanges.push(change);\n } else {\n // Not sharing this run: a previously-committed shared file is now stale.\n if (await committedFileExists(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH)) {\n sharedChanges.push({ repoPath: SHARED_INSTRUCTIONS_REPO_PATH, kind: \"removed\" });\n }\n }\n\n const clean =\n sharedChanges.length === 0 &&\n toolStatuses.every(\n (t) => t.files.length === 0 && t.manifest.length === 0,\n );\n\n const report: StatusReport = {\n repoPath: repoRoot,\n repoInitialized,\n sharedInstructions: sharing,\n includeSecrets: config.includeSecrets,\n tools: toolStatuses,\n shared: sharedChanges,\n clean,\n };\n\n if (opts.json) {\n // Machine-readable payload goes to STDOUT; keep it the ONLY thing on stdout.\n process.stdout.write(serialize(report));\n return;\n }\n\n printHuman(report);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Context construction */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build a CaptureContext for `status`. Inline (we don't own _context.ts): the\n * singleton sanitizer/templater + default fs/log + per-tool variables + the live\n * OS. dryRun is ALWAYS true here — status never writes and never hits npm.\n */\nfunction buildCaptureContext(\n tool: ToolId,\n includeSecrets: boolean,\n includeMemories: boolean,\n): CaptureContext {\n const toolHome = toolHomeDir(tool);\n return {\n fs,\n log,\n sanitizer,\n templater,\n vars: buildVariables(toolHome),\n os: detectOS(),\n toolHome,\n includeSecrets,\n includeMemories,\n dryRun: true,\n };\n}\n\n/* -------------------------------------------------------------------------- */\n/* File diffing */\n/* -------------------------------------------------------------------------- */\n\n/** Diff captured files + symlinks against the committed working copy. */\nasync function diffFiles(\n repoRoot: string,\n files: CapturedFile[],\n symlinks: CapturedSymlink[],\n): Promise<FileChange[]> {\n const changes: FileChange[] = [];\n\n for (const file of files) {\n const change = await classifyFile(repoRoot, file);\n if (change.kind !== \"unchanged\") changes.push(change);\n }\n\n for (const link of symlinks) {\n const change = await classifySymlink(repoRoot, link);\n if (change.kind !== \"unchanged\") changes.push(change);\n }\n\n return changes;\n}\n\n/**\n * Classify a single would-be file against the committed copy on disk.\n * Binary files are compared by raw bytes; text by exact string equality (the\n * captured content is already sanitized + templated, i.e. exactly what backup\n * would write, so a byte-for-byte match means \"no change\").\n */\nasync function classifyFile(\n repoRoot: string,\n file: CapturedFile,\n): Promise<FileChange> {\n const abs = repoAbsPath(repoRoot, file.repoPath);\n const kind = await fs.statKind(abs);\n\n if (kind === \"missing\") {\n return { repoPath: file.repoPath, kind: \"added\" };\n }\n // A committed symlink where we now expect a regular file is a real change.\n if (kind === \"symlink\") {\n return { repoPath: file.repoPath, kind: \"changed\" };\n }\n\n try {\n if (file.binary) {\n const existing = await fs.readBytes(abs);\n const incoming = Buffer.from(file.content, \"base64\");\n const same = existing.length === incoming.length && existing.equals(incoming);\n return { repoPath: file.repoPath, kind: same ? \"unchanged\" : \"changed\" };\n }\n const existing = await fs.read(abs);\n return {\n repoPath: file.repoPath,\n kind: existing === file.content ? \"unchanged\" : \"changed\",\n };\n } catch {\n // Unreadable on disk but present -> treat as changed (it will be rewritten).\n return { repoPath: file.repoPath, kind: \"changed\" };\n }\n}\n\n/** Classify a would-be symlink against the committed entry. */\nasync function classifySymlink(\n repoRoot: string,\n link: CapturedSymlink,\n): Promise<FileChange> {\n const abs = repoAbsPath(repoRoot, link.repoPath);\n const kind = await fs.statKind(abs);\n\n if (kind === \"missing\") {\n return { repoPath: link.repoPath, kind: \"added\", symlink: true };\n }\n if (kind !== \"symlink\") {\n // Committed as a real file/dir but we now expect a symlink -> changed.\n return { repoPath: link.repoPath, kind: \"changed\", symlink: true };\n }\n try {\n const target = await fs.readLink(abs);\n return {\n repoPath: link.repoPath,\n kind: target === link.target ? \"unchanged\" : \"changed\",\n symlink: true,\n };\n } catch {\n return { repoPath: link.repoPath, kind: \"changed\", symlink: true };\n }\n}\n\n/**\n * Find files committed under \"<tool>/files/**\" that the current capture no\n * longer produces — these would be deleted by the next backup. We compare the\n * committed file tree against the set of repo paths this tool just emitted.\n */\nasync function findRemovedToolFiles(\n repoRoot: string,\n tool: ToolId,\n expectedForTool: Set<string>,\n): Promise<FileChange[]> {\n const prefix = `${tool}/files`;\n const baseAbs = repoAbsPath(repoRoot, prefix);\n const committed = await walkRepoFiles(baseAbs, prefix);\n\n const removed: FileChange[] = [];\n for (const entry of committed) {\n if (!expectedForTool.has(entry.repoPath)) {\n removed.push({\n repoPath: entry.repoPath,\n kind: \"removed\",\n symlink: entry.symlink || undefined,\n });\n }\n }\n return removed;\n}\n\n/** The repo paths (files + symlinks) a single tool's capture produced. */\nfunction expectedRepoPathsForTool(\n files: CapturedFile[],\n symlinks: CapturedSymlink[],\n): Set<string> {\n const set = new Set<string>();\n for (const f of files) set.add(f.repoPath);\n for (const s of symlinks) set.add(s.repoPath);\n return set;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Manifest diffing */\n/* -------------------------------------------------------------------------- */\n\n/** Diff a freshly-captured manifest against the committed manifest.json. */\nasync function diffManifest(\n repoRoot: string,\n tool: ToolId,\n local: ToolManifest,\n): Promise<ManifestDrift[]> {\n const manifestPath = repoAbsPath(repoRoot, `${tool}/manifest.json`);\n let committed: ToolManifest | null = null;\n\n if (await fs.exists(manifestPath)) {\n try {\n committed = parseManifest(JSON.parse(await fs.read(manifestPath)));\n } catch {\n // Corrupt/old committed manifest: treat every local entry as \"added\".\n committed = null;\n }\n }\n\n const drifts: ManifestDrift[] = [];\n\n const pluginDrift = diffKeyed(\n \"plugins\",\n (committed?.plugins ?? []) as PluginEntry[],\n local.plugins,\n (p) => p.id,\n pluginSignature,\n );\n if (hasDrift(pluginDrift)) drifts.push(pluginDrift);\n\n const mpDrift = diffKeyed(\n \"marketplaces\",\n (committed?.marketplaces ?? []) as MarketplaceEntry[],\n local.marketplaces,\n (m) => m.id,\n marketplaceSignature,\n );\n if (hasDrift(mpDrift)) drifts.push(mpDrift);\n\n const skillDrift = diffKeyed(\n \"skills\",\n (committed?.skills ?? []) as SkillEntry[],\n local.skills,\n (s) => s.name,\n skillSignature,\n );\n if (hasDrift(skillDrift)) drifts.push(skillDrift);\n\n const npmDrift = diffKeyed(\n \"npmGlobals\",\n (committed?.npmGlobals ?? []) as NpmGlobalEntry[],\n local.npmGlobals,\n (n) => n.package,\n (n) => `${n.package}@${n.version ?? \"\"}`,\n );\n if (hasDrift(npmDrift)) drifts.push(npmDrift);\n\n const enabledDrift = diffEnabledPlugins(\n committed?.enabledPlugins ?? {},\n local.enabledPlugins,\n );\n if (hasDrift(enabledDrift)) drifts.push(enabledDrift);\n\n return drifts;\n}\n\n/**\n * Generic keyed diff: compare two lists by a stable key, reporting added /\n * removed (by key) and changed (same key, different signature).\n */\nfunction diffKeyed<T>(\n category: ManifestDrift[\"category\"],\n committed: T[],\n local: T[],\n keyOf: (item: T) => string,\n signatureOf: (item: T) => string,\n): ManifestDrift {\n const committedMap = new Map<string, T>();\n for (const item of committed) committedMap.set(keyOf(item), item);\n const localMap = new Map<string, T>();\n for (const item of local) localMap.set(keyOf(item), item);\n\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n\n for (const [key, item] of localMap) {\n const prev = committedMap.get(key);\n if (!prev) {\n added.push(key);\n } else if (signatureOf(prev) !== signatureOf(item)) {\n changed.push(key);\n }\n }\n for (const key of committedMap.keys()) {\n if (!localMap.has(key)) removed.push(key);\n }\n\n return {\n category,\n added: added.sort(),\n removed: removed.sort(),\n changed: changed.sort(),\n };\n}\n\n/** enabledPlugins is a Record<string, boolean>; diff key-by-key on the flag. */\nfunction diffEnabledPlugins(\n committed: Record<string, boolean>,\n local: Record<string, boolean>,\n): ManifestDrift {\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n\n for (const [key, val] of Object.entries(local)) {\n if (!(key in committed)) added.push(key);\n else if (committed[key] !== val) changed.push(key);\n }\n for (const key of Object.keys(committed)) {\n if (!(key in local)) removed.push(key);\n }\n\n return {\n category: \"enabledPlugins\",\n added: added.sort(),\n removed: removed.sort(),\n changed: changed.sort(),\n };\n}\n\nfunction hasDrift(d: ManifestDrift): boolean {\n return d.added.length > 0 || d.removed.length > 0 || d.changed.length > 0;\n}\n\n/* ---- stable signatures used to detect \"same key, different details\" ------ */\n\nfunction pluginSignature(p: PluginEntry): string {\n return [\n p.id,\n p.name,\n p.marketplace ?? \"\",\n p.version ?? \"\",\n String(p.enabled),\n p.scope,\n p.projectPath ?? \"\",\n ].join(\"\u0001\");\n}\n\nfunction marketplaceSignature(m: MarketplaceEntry): string {\n return [m.id, m.sourceType, m.source].join(\"\u0001\");\n}\n\nfunction skillSignature(s: SkillEntry): string {\n return [s.name, s.source, s.installCommand ?? \"\", String(s.symlinked)].join(\"\u0001\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Repo working-copy walking */\n/* -------------------------------------------------------------------------- */\n\ninterface RepoFileEntry {\n /** Repo-relative POSIX path. */\n repoPath: string;\n symlink: boolean;\n}\n\n/**\n * Recursively list files (and symlinks) under an absolute base dir, returning\n * repo-relative POSIX paths. Skips the .git directory. Tolerant of absence.\n */\nasync function walkRepoFiles(\n baseAbs: string,\n basePosix: string,\n): Promise<RepoFileEntry[]> {\n const out: RepoFileEntry[] = [];\n if (!(await fs.exists(baseAbs))) return out;\n\n const entries = await fs.list(baseAbs);\n for (const name of entries) {\n if (name === \".git\") continue;\n const childAbs = path.join(baseAbs, name);\n const childPosix = `${basePosix}/${name}`;\n const kind = await fs.statKind(childAbs);\n if (kind === \"symlink\") {\n out.push({ repoPath: childPosix, symlink: true });\n } else if (kind === \"dir\") {\n out.push(...(await walkRepoFiles(childAbs, childPosix)));\n } else if (kind === \"file\") {\n out.push({ repoPath: childPosix, symlink: false });\n }\n }\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Small filesystem helpers */\n/* -------------------------------------------------------------------------- */\n\n/** Map a repo-relative POSIX path to an absolute OS path under repoRoot. */\nfunction repoAbsPath(repoRoot: string, repoPath: string): string {\n return path.join(repoRoot, ...repoPath.split(\"/\"));\n}\n\n/** True if a committed file (regular file or symlink) exists at repoPath. */\nasync function committedFileExists(repoRoot: string, repoPath: string): Promise<boolean> {\n const kind = await fs.statKind(repoAbsPath(repoRoot, repoPath));\n return kind === \"file\" || kind === \"symlink\";\n}\n\n/** Read a file's text, or undefined if it does not exist / can't be read. */\nasync function readIfExists(absPath: string): Promise<string | undefined> {\n if (!(await fs.exists(absPath))) return undefined;\n try {\n return await fs.read(absPath);\n } catch {\n return undefined;\n }\n}\n\n/** True when the working copy already has committed content (not a fresh dir). */\nasync function isRepoInitialized(repoRoot: string): Promise<boolean> {\n if (!(await fs.exists(repoRoot))) return false;\n const entries = await fs.list(repoRoot);\n // Anything beyond an empty/.git-only directory means a prior backup exists.\n return entries.some((e) => e !== \".git\");\n}\n\n/** Run an adapter detect() defensively; absence must never throw out of status. */\nasync function safeDetect(detect: () => Promise<boolean>): Promise<boolean> {\n try {\n return await detect();\n } catch {\n return false;\n }\n}\n\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Ordering + human rendering */\n/* -------------------------------------------------------------------------- */\n\nconst KIND_ORDER: Record<FileChangeKind, number> = {\n added: 0,\n changed: 1,\n removed: 2,\n unchanged: 3,\n};\n\n/** Stable ordering: by kind (added, changed, removed), then by path. */\nfunction sortChanges(changes: FileChange[]): FileChange[] {\n return [...changes].sort((a, b) => {\n const k = KIND_ORDER[a.kind] - KIND_ORDER[b.kind];\n if (k !== 0) return k;\n return a.repoPath.localeCompare(b.repoPath);\n });\n}\n\nconst SIGIL: Record<FileChangeKind, string> = {\n added: \"+\",\n changed: \"~\",\n removed: \"-\",\n unchanged: \" \",\n};\n\n/** Print a human-readable status table to STDERR (stdout stays data-clean). */\nfunction printHuman(report: StatusReport): void {\n log.info(`Backup repo: ${report.repoPath}`);\n if (!report.repoInitialized) {\n log.warn(\n \"Backup repo has no prior backup yet — everything below would be added by the first `arbella push`.\",\n );\n }\n log.step(\n `Shared instructions (CLAUDE.md == AGENTS.md): ${report.sharedInstructions ? \"yes\" : \"no\"}`,\n );\n log.step(`Secrets in repo: ${report.includeSecrets ? \"INCLUDED\" : \"excluded (default)\"}`);\n\n if (report.clean) {\n log.success(\"Up to date — a backup right now would change nothing.\");\n }\n\n // Repo-level shared file changes (e.g. shared/instructions.md).\n if (report.shared.length > 0) {\n log.info(\"Shared:\");\n for (const c of report.shared) {\n log.step(renderChangeLine(c));\n }\n }\n\n for (const tool of report.tools) {\n const headerExtras: string[] = [];\n if (!tool.present) headerExtras.push(\"not present\");\n const changedCount = tool.files.length;\n const driftCount = tool.manifest.length;\n if (tool.present && changedCount === 0 && driftCount === 0) {\n headerExtras.push(\"no changes\");\n }\n const suffix = headerExtras.length ? ` (${headerExtras.join(\", \")})` : \"\";\n log.info(`${tool.tool}${suffix}:`);\n\n for (const c of tool.files) {\n log.step(renderChangeLine(c));\n }\n\n for (const drift of tool.manifest) {\n const parts: string[] = [];\n if (drift.added.length) parts.push(`+${drift.added.length}`);\n if (drift.changed.length) parts.push(`~${drift.changed.length}`);\n if (drift.removed.length) parts.push(`-${drift.removed.length}`);\n log.step(`${drift.category}: ${parts.join(\" \")}`);\n for (const id of drift.added) log.step(` + ${id}`);\n for (const id of drift.changed) log.step(` ~ ${id}`);\n for (const id of drift.removed) log.step(` - ${id}`);\n }\n\n if (tool.secrets.length > 0) {\n log.step(\n `secrets ${report.includeSecrets ? \"redacted\" : \"excluded\"}: ${tool.secrets.length}`,\n );\n for (const s of tool.secrets) {\n log.step(` ! ${s.source} — ${s.description}`);\n }\n }\n\n for (const w of tool.warnings) {\n log.warn(` ${w}`);\n }\n }\n}\n\nfunction renderChangeLine(c: FileChange): string {\n const sigil = SIGIL[c.kind];\n const linkTag = c.symlink ? \" (symlink)\" : \"\";\n return `${sigil} ${c.repoPath}${linkTag}`;\n}\n","/**\n * `arbella secrets` — LOCAL-ONLY encrypted transfer of secret files (R5).\n *\n * Two subcommands:\n * - `arbella secrets export` : discover the secret files across every tool\n * (auth tokens / credentials), prompt for a passphrase, and write a single\n * encrypted blob the user copies out of band (USB, password manager, etc).\n * - `arbella secrets import` : read a blob + passphrase and write the secret\n * files back onto this machine's tool homes.\n *\n * HARD security rules honored here:\n * - This flow NEVER touches git. It is completely separate from backup/restore.\n * - Secret VALUES are never printed, logged, or echoed. The passphrase prompt\n * uses a masked input; on import we only report file paths, never contents.\n * - The only artifact that leaves the machine is the opaque encrypted blob; the\n * only thing written on import is plaintext onto the user's own tool homes\n * (with restrictive modes, handled by core/secrets).\n *\n * Clock rule: the command layer owns the clock. We mint the\n * `createdAt` ISO string here and pass it inward; core/secrets stays clock-free.\n *\n * The actual crypto + file IO lives in core/secrets; this module is a thin,\n * interactive front-end (flag parsing + clack prompts + friendly reporting).\n */\n\nimport path from \"node:path\";\n\nimport type { Command } from \"commander\";\nimport * as clack from \"@clack/prompts\";\n\nimport type { SecretRef, ToolId } from \"../types.js\";\nimport { TOOL_IDS } from \"../types.js\";\nimport {\n applySecretBundle,\n collectSecretFiles,\n decryptBundle,\n encryptBundle,\n gatherSecretRefs,\n type SecretBundle,\n} from \"../core/secrets/index.js\";\nimport { fs } from \"../utils/fs.js\";\nimport { log } from \"../utils/log.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Option shapes (commander passes these in) */\n/* -------------------------------------------------------------------------- */\n\n/** Options for `arbella secrets export`. `out` is the blob output file path. */\nexport interface SecretsExportOptions {\n /** Where to write the encrypted blob (e.g. \"arbella-secrets.blob\"). */\n out: string;\n}\n\n/** Options for `arbella secrets import`. `in` is the blob input file path. */\nexport interface SecretsImportOptions {\n /** Path to the encrypted blob produced by `secrets export`. */\n in: string;\n}\n\n/** Default filename used when the user doesn't pass `--out`/`--in`. */\nconst DEFAULT_BLOB_FILE = \"arbella-secrets.blob\";\n\n/* -------------------------------------------------------------------------- */\n/* Commander registration */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Attach `arbella secrets export|import` to the program. Kept declarative so\n * the actual work lives in the directly-callable `runExport` / `runImport`\n * (which tests can invoke without spinning up commander).\n */\nexport function register(program: Command): void {\n const secrets = program\n .command(\"secrets\")\n .description(\n \"Move secret files (auth tokens / credentials) between machines via an \" +\n \"encrypted, passphrase-protected blob. Never uses git.\",\n );\n\n secrets\n .command(\"export\")\n .description(\n \"Bundle this machine's secret files into an encrypted blob you copy out of band.\",\n )\n .option(\n \"-o, --out <file>\",\n \"File to write the encrypted blob to.\",\n DEFAULT_BLOB_FILE,\n )\n .action(async (opts: SecretsExportOptions) => {\n await runExport(opts);\n });\n\n secrets\n .command(\"import\")\n .description(\n \"Decrypt a secrets blob and write the secret files back onto this machine.\",\n )\n .option(\n \"-i, --in <file>\",\n \"Encrypted blob file to read (from `secrets export`).\",\n DEFAULT_BLOB_FILE,\n )\n .action(async (opts: SecretsImportOptions) => {\n await runImport(opts);\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* secrets export */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Discover secret files across all known tools, prompt for a passphrase, encrypt\n * the bundle, and write the opaque blob to `opts.out`.\n *\n * Never prints secret contents. The on-screen summary lists only tool + relative\n * file name (e.g. \"claude: .credentials.json\") so the user knows what's inside.\n */\nexport async function runExport(opts: SecretsExportOptions): Promise<void> {\n clack.intro(\"arbella secrets export\");\n\n // 1) Discover which secret files actually exist on this machine, per tool.\n const refs: SecretRef[] = [];\n for (const tool of TOOL_IDS) {\n const found = await gatherSecretRefs(tool);\n refs.push(...found);\n }\n\n if (refs.length === 0) {\n clack.note(\n \"No secret files were found on this machine. Nothing to export.\\n\" +\n \"(Tools store credentials in files like ~/.claude/.credentials.json or \" +\n \"~/.codex/auth.json — none are present here.)\",\n \"Nothing to do\",\n );\n clack.outro(\"No blob written.\");\n return;\n }\n\n // Show metadata ONLY — never the secret values.\n clack.note(formatRefList(refs), `Found ${refs.length} secret file(s)`);\n\n // 2) Prompt for a passphrase (masked) + confirm it matches.\n const passphrase = await promptNewPassphrase();\n if (passphrase === null) {\n clack.cancel(\"Export cancelled.\");\n return;\n }\n\n // 3) Collect the real bytes and encrypt. The clock lives here (§0.5).\n const createdAt = new Date().toISOString();\n const bundle: SecretBundle = await collectSecretFiles(refs, createdAt);\n\n if (bundle.entries.length === 0) {\n // Files disappeared between discovery and read (e.g. a logout). Don't write\n // an empty, useless blob.\n clack.cancel(\n \"Secret files vanished before they could be read; nothing to export.\",\n );\n return;\n }\n\n const blob = encryptBundle(bundle, passphrase);\n\n // 4) Write the blob. fs.write ensures the parent dir exists. 0o600 so the blob\n // (though encrypted) is not left world-readable.\n const outPath = path.resolve(opts.out);\n await fs.write(outPath, blob + \"\\n\", 0o600);\n\n clack.note(\n [\n `Wrote encrypted blob to:\\n ${outPath}`,\n \"\",\n \"This blob is safe to copy between machines (it is encrypted with your\",\n \"passphrase). It NEVER goes through git. On the target machine run:\",\n ` arbella secrets import --in ${path.basename(outPath)}`,\n \"\",\n \"Keep your passphrase safe: without it the blob cannot be decrypted.\",\n ].join(\"\\n\"),\n \"Done\",\n );\n clack.outro(`Exported ${bundle.entries.length} secret file(s).`);\n}\n\n/* -------------------------------------------------------------------------- */\n/* secrets import */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Read a blob from `opts.in`, prompt for the passphrase, decrypt, and write the\n * secret files back onto this machine's tool homes.\n *\n * A wrong passphrase / tampered blob fails loudly (GCM auth) before any file is\n * touched. Reports only file paths, never contents. Never touches git.\n */\nexport async function runImport(opts: SecretsImportOptions): Promise<void> {\n clack.intro(\"arbella secrets import\");\n\n const inPath = path.resolve(opts.in);\n\n if (!(await fs.exists(inPath))) {\n clack.cancel(\n `No blob found at:\\n ${inPath}\\n` +\n \"Pass the file you produced with `arbella secrets export` via --in.\",\n );\n return;\n }\n\n let blob: string;\n try {\n blob = (await fs.read(inPath)).trim();\n } catch (err) {\n clack.cancel(`Could not read blob file: ${errMessage(err)}`);\n return;\n }\n\n if (blob.length === 0) {\n clack.cancel(\"The blob file is empty.\");\n return;\n }\n\n // Prompt for the passphrase (masked, single entry — we verify by decrypting).\n const passphrase = await promptPassphrase(\n \"Enter the passphrase used to create this blob\",\n );\n if (passphrase === null) {\n clack.cancel(\"Import cancelled.\");\n return;\n }\n\n // Decrypt. A wrong passphrase or tampered blob throws here, before any write.\n let bundle: SecretBundle;\n try {\n bundle = decryptBundle(blob, passphrase);\n } catch (err) {\n // core/secrets throws a deliberately secret-free message.\n clack.cancel(errMessage(err));\n return;\n }\n\n if (bundle.entries.length === 0) {\n clack.note(\"The blob decrypted but contained no secret files.\", \"Nothing to write\");\n clack.outro(\"No files written.\");\n return;\n }\n\n // Show what WILL be written (paths only) before touching disk.\n clack.note(\n formatBundleTargets(bundle),\n `Restoring ${bundle.entries.length} secret file(s)`,\n );\n\n try {\n await applySecretBundle(bundle);\n } catch (err) {\n // e.g. a path-traversal guard tripped, or a write failed.\n clack.cancel(`Failed to write secret files: ${errMessage(err)}`);\n return;\n }\n\n clack.note(\n \"Secret files were written with owner-only permissions.\\n\" +\n \"You should now be authenticated again in the affected tools.\",\n \"Done\",\n );\n clack.outro(`Imported ${bundle.entries.length} secret file(s).`);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Prompt helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Prompt for a brand-new passphrase, requiring confirmation so a typo doesn't\n * produce a blob the user can never open. Returns `null` if the user cancels\n * (Ctrl-C) at any point.\n */\nasync function promptNewPassphrase(): Promise<string | null> {\n const first = await promptPassphrase(\"Choose a passphrase to encrypt the blob\");\n if (first === null) return null;\n\n const second = await clack.password({\n message: \"Confirm the passphrase\",\n validate(value) {\n if (!value || value.length === 0) return \"Passphrase cannot be empty.\";\n if (value !== first) return \"Passphrases do not match.\";\n return undefined;\n },\n });\n if (clack.isCancel(second)) return null;\n\n return first;\n}\n\n/**\n * Single masked passphrase prompt. Returns the entered string, or `null` when\n * the user cancels. Empty input is rejected by the validator.\n */\nasync function promptPassphrase(message: string): Promise<string | null> {\n const value = await clack.password({\n message,\n validate(v) {\n if (!v || v.length === 0) return \"Passphrase cannot be empty.\";\n return undefined;\n },\n });\n if (clack.isCancel(value)) return null;\n return value;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Formatting helpers (metadata only — never secret values) */\n/* -------------------------------------------------------------------------- */\n\n/** Render a list of discovered secret refs as \"tool: relPath — description\". */\nfunction formatRefList(refs: SecretRef[]): string {\n return refs\n .map((r) => `${toolLabel(r.tool)}: ${r.source} (${r.description})`)\n .join(\"\\n\");\n}\n\n/** Render the destinations a bundle will write to (paths only, no contents). */\nfunction formatBundleTargets(bundle: SecretBundle): string {\n return bundle.entries\n .map((e) => `${toolLabel(e.tool)}: ${e.relPath}`)\n .join(\"\\n\");\n}\n\n/** Stable display label for a tool id. */\nfunction toolLabel(tool: ToolId): string {\n switch (tool) {\n case \"claude\":\n return \"claude\";\n case \"codex\":\n return \"codex\";\n case \"cursor\":\n return \"cursor\";\n }\n}\n\n/** Extract a safe error message string from an unknown thrown value. */\nfunction errMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Re-exported so the CLI entry / other commands can reference the plain logger if\n * they want non-interactive output in a piped context. Not used internally beyond\n * keeping a single import surface; clack already writes to stderr.\n */\nexport { log };\n","/**\n * Secrets — LOCAL-ONLY encrypted transfer of secret files (R5).\n *\n * This module is the *only* sanctioned way to move secret material (auth tokens,\n * credentials) between machines. It NEVER touches git and is never invoked by\n * the backup/restore repo flow. The user explicitly runs `arbella secrets\n * export` to produce an encrypted, passphrase-protected blob, copies it out of\n * band (USB stick, password manager, secure channel), and runs `arbella\n * secrets import` on the new machine.\n *\n * Crypto (normative):\n * - key derivation: scryptSync(passphrase, salt(16), 32, SCRYPT_PARAMS)\n * - cipher: aes-256-gcm, random iv(12)\n * - wire format: base64( MAGIC(4) | salt(16) | iv(12) | authTag(16) | ciphertext )\n * - the GCM auth tag makes a wrong passphrase / tampered blob fail loudly on\n * decrypt rather than silently returning garbage.\n *\n * HARD security rules honored here:\n * - secret VALUES are never logged, printed, or returned in error messages;\n * - the only artifacts that leave this module are (a) the encrypted blob and\n * (b) plaintext written back onto the user's own tool homes during import;\n * - bundle files are restored with their original restrictive mode (default\n * 0o600) so credentials never land world-readable.\n *\n * No system clock is read in library code: `createdAt` is always supplied by the\n * caller (the command layer) as an ISO-8601 string.\n */\n\nimport {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n scryptSync,\n} from \"node:crypto\";\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { ToolId, SecretRef } from \"../../types.js\";\nimport { toolHomeDir } from \"../../platform/os.js\";\nimport { fs } from \"../../utils/fs.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Bundle shapes */\n/* -------------------------------------------------------------------------- */\n\n/**\n * One secret file bundled into the blob. `contentB64` is base64 of the raw file\n * bytes (so binary credential blobs survive intact). `relPath` is POSIX-relative\n * to the owning tool's home dir, e.g. \".credentials.json\" or \"auth.json\".\n */\nexport interface SecretBundleEntry {\n tool: ToolId;\n /** Path relative to the tool home, POSIX separators. */\n relPath: string;\n /** base64 of the raw file bytes. */\n contentB64: string;\n /** Original POSIX file mode (e.g. 0o600). Restored verbatim on import. */\n mode?: number;\n}\n\n/** The decrypted, in-memory bundle. `createdAt` is supplied by the caller. */\nexport interface SecretBundle {\n version: 1;\n /** ISO-8601 timestamp, SUPPLIED BY CALLER (no clock in library code). */\n createdAt: string;\n entries: SecretBundleEntry[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Crypto parameters */\n/* -------------------------------------------------------------------------- */\n\n/**\n * scrypt work factors. N=2^15 keeps derivation well under a second on a laptop\n * while remaining costly to brute-force. Exported so tests can assert the exact\n * parameters a blob was produced with.\n */\nexport const SCRYPT_PARAMS: { N: number; r: number; p: number; keyLen: 32 } = {\n N: 1 << 15, // 32768\n r: 8,\n p: 1,\n keyLen: 32,\n};\n\n/** 4-byte magic prefix for format detection (\"RSK1\" = Arbella Secrets v1). */\nconst MAGIC = Buffer.from(\"RSK1\", \"ascii\");\nconst SALT_LEN = 16;\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\n/**\n * Default mode for restored secret files when the bundle entry didn't capture\n * one. Credentials must never be world-readable, so we default to owner-only.\n */\nconst DEFAULT_SECRET_MODE = 0o600;\n\n/**\n * scrypt's default `maxmem` (32 MiB) is too small for N=2^15; raise the ceiling\n * so derivation does not throw. 64 MiB is comfortably above the requirement.\n */\nconst SCRYPT_MAXMEM = 64 * 1024 * 1024;\n\n/* -------------------------------------------------------------------------- */\n/* Key derivation */\n/* -------------------------------------------------------------------------- */\n\nfunction deriveKey(passphrase: string, salt: Buffer): Buffer {\n return scryptSync(passphrase, salt, SCRYPT_PARAMS.keyLen, {\n N: SCRYPT_PARAMS.N,\n r: SCRYPT_PARAMS.r,\n p: SCRYPT_PARAMS.p,\n maxmem: SCRYPT_MAXMEM,\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Encrypt / decrypt */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Encrypt a bundle with a passphrase. Returns an opaque, self-describing,\n * single-line base64 string safe to write to a file or paste into a manager.\n *\n * The plaintext is the UTF-8 JSON serialization of the bundle. Each per-file\n * payload inside is already base64, so the JSON contains no raw secret bytes\n * (and is, of course, encrypted regardless).\n */\nexport function encryptBundle(bundle: SecretBundle, passphrase: string): string {\n if (typeof passphrase !== \"string\" || passphrase.length === 0) {\n throw new Error(\"A non-empty passphrase is required to encrypt secrets.\");\n }\n\n const salt = randomBytes(SALT_LEN);\n const iv = randomBytes(IV_LEN);\n const key = deriveKey(passphrase, salt);\n\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const plaintext = Buffer.from(JSON.stringify(bundle), \"utf8\");\n const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n const blob = Buffer.concat([MAGIC, salt, iv, authTag, ciphertext]);\n return blob.toString(\"base64\");\n}\n\n/**\n * Decrypt a blob produced by `encryptBundle`. Throws a clear (secret-free) error\n * when the passphrase is wrong, the blob is truncated, or the magic/version is\n * unrecognized — the GCM auth tag guarantees tamper/wrong-key detection.\n */\nexport function decryptBundle(blob: string, passphrase: string): SecretBundle {\n if (typeof passphrase !== \"string\" || passphrase.length === 0) {\n throw new Error(\"A passphrase is required to decrypt secrets.\");\n }\n\n let raw: Buffer;\n try {\n raw = Buffer.from(blob.trim(), \"base64\");\n } catch {\n throw new Error(\"Secret blob is not valid base64.\");\n }\n\n const minLen = MAGIC.length + SALT_LEN + IV_LEN + TAG_LEN;\n if (raw.length < minLen) {\n throw new Error(\"Secret blob is truncated or corrupt.\");\n }\n\n let offset = 0;\n const magic = raw.subarray(offset, (offset += MAGIC.length));\n if (!magic.equals(MAGIC)) {\n throw new Error(\"Unrecognized secret blob format (bad magic / version).\");\n }\n\n const salt = raw.subarray(offset, (offset += SALT_LEN));\n const iv = raw.subarray(offset, (offset += IV_LEN));\n const authTag = raw.subarray(offset, (offset += TAG_LEN));\n const ciphertext = raw.subarray(offset);\n\n const key = deriveKey(passphrase, Buffer.from(salt));\n\n const decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(iv));\n decipher.setAuthTag(Buffer.from(authTag));\n\n let plaintext: Buffer;\n try {\n plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);\n } catch {\n // GCM auth failure: wrong passphrase or tampered blob. Never leak details.\n throw new Error(\n \"Failed to decrypt secrets: wrong passphrase or corrupted blob.\",\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(plaintext.toString(\"utf8\"));\n } catch {\n throw new Error(\"Decrypted secret bundle is not valid JSON.\");\n }\n\n return assertBundle(parsed);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Discover -> collect -> apply */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Known secret FILES per tool, relative to that tool's home dir. These are the\n * `kind:\"file\"` denylisted artifacts verified to exist on real machines.\n * Kept here as the self-contained source of truth for\n * secret-file discovery so this module never depends on the sanitizer module's\n * full denylist (which also lists non-secret junk like caches/sqlite).\n *\n * Only entries that genuinely hold credentials/tokens belong here — never\n * settings or history.\n */\nconst SECRET_FILES_BY_TOOL: Record<ToolId, ReadonlyArray<{ relPath: string; description: string }>> = {\n claude: [\n { relPath: \".credentials.json\", description: \"Claude OAuth credentials / API token store\" },\n { relPath: \".claude.json\", description: \"Claude global state (contains auth tokens)\" },\n ],\n codex: [\n { relPath: \"auth.json\", description: \"Codex authentication tokens\" },\n ],\n cursor: [\n // Cursor stores auth in the desktop app keychain, not a flat file; nothing\n // portable to bundle here. Present for completeness / forward-compat.\n ],\n};\n\n/**\n * Scan a tool's home for the known secret files and return `SecretRef`s\n * (metadata only — NEVER the secret value) for the ones that actually exist.\n *\n * Used by `arbella secrets export` to decide what to bundle, independent of a\n * full capture pass. Tolerates a missing tool home (returns []).\n */\nexport async function gatherSecretRefs(toolId: ToolId): Promise<SecretRef[]> {\n const refs: SecretRef[] = [];\n const home = toolHomeDir(toolId);\n\n for (const spec of SECRET_FILES_BY_TOOL[toolId]) {\n const abs = path.join(home, spec.relPath);\n if (await fs.exists(abs)) {\n refs.push({\n tool: toolId,\n source: spec.relPath,\n key: spec.relPath,\n description: spec.description,\n kind: \"file\",\n });\n }\n }\n\n return refs;\n}\n\n/**\n * Gather the actual secret files named by `refs` into an (unencrypted, in-memory)\n * bundle, reading each tool home. Only `kind:\"file\"` refs are collected; `value`\n * refs describe secrets that live inside otherwise-safe files and are handled by\n * the sanitizer/redaction path, not by file copy.\n *\n * Missing files are skipped silently (the ref may predate a logout). `createdAt`\n * is supplied by the caller.\n */\nexport async function collectSecretFiles(\n refs: SecretRef[],\n createdAt: string,\n): Promise<SecretBundle> {\n const entries: SecretBundleEntry[] = [];\n const seen = new Set<string>();\n\n for (const ref of refs) {\n if (ref.kind !== \"file\") continue;\n\n // `source` is the path relative to the tool home; strip any \"#fragment\"\n // (value-locator syntax) defensively, though file refs shouldn't carry one.\n const relPath = ref.source.split(\"#\")[0]!.replace(/\\\\/g, \"/\");\n const dedupeKey = `${ref.tool}:${relPath}`;\n if (seen.has(dedupeKey)) continue;\n seen.add(dedupeKey);\n\n const abs = path.join(toolHomeDir(ref.tool), relPath);\n if (!(await fs.exists(abs))) continue;\n\n const bytes = await fs.readBytes(abs);\n const mode = await readMode(abs);\n\n entries.push({\n tool: ref.tool,\n relPath,\n contentB64: bytes.toString(\"base64\"),\n mode,\n });\n }\n\n return { version: 1, createdAt, entries };\n}\n\n/**\n * Write a decrypted bundle's files back onto this machine's tool homes (used by\n * `arbella secrets import`). Each file is written to `toolHome/relPath` with its\n * captured mode (default 0o600). Parent dirs are created as needed.\n *\n * Defends against path traversal: any entry whose resolved destination escapes\n * its tool home is refused (so a malicious blob can't write outside ~/.<tool>).\n */\nexport async function applySecretBundle(bundle: SecretBundle): Promise<void> {\n for (const entry of bundle.entries) {\n const home = toolHomeDir(entry.tool);\n const rel = entry.relPath.replace(/\\\\/g, \"/\");\n const dest = path.resolve(home, rel);\n\n // Containment check: dest must stay within the tool home.\n const homeResolved = path.resolve(home);\n const withinHome =\n dest === homeResolved ||\n dest.startsWith(homeResolved + path.sep);\n if (!withinHome) {\n throw new Error(\n `Refusing to write secret outside ${entry.tool} home (suspicious path: ${rel}).`,\n );\n }\n\n const bytes = Buffer.from(entry.contentB64, \"base64\");\n const mode = entry.mode ?? DEFAULT_SECRET_MODE;\n await fs.writeBytes(dest, bytes, mode);\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* High-level convenience wrappers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * One-call export: collect the `kind:\"file\"` secret refs from disk and return\n * the encrypted, portable blob. `createdAt` is supplied by the caller (command\n * layer) so library code stays clock-free.\n *\n * `refs` are typically the secrets surfaced during capture or by\n * `gatherSecretRefs`. The resulting string is the only artifact that leaves the\n * machine; it contains no plaintext.\n */\nexport async function exportSecrets(\n refs: SecretRef[],\n passphrase: string,\n createdAt: string,\n): Promise<string> {\n const bundle = await collectSecretFiles(refs, createdAt);\n return encryptBundle(bundle, passphrase);\n}\n\n/**\n * One-call import: decrypt a blob and write its files back onto this machine's\n * tool homes. Throws on a wrong passphrase / tampered blob before touching the\n * filesystem.\n */\nexport async function importSecrets(blob: string, passphrase: string): Promise<void> {\n const bundle = decryptBundle(blob, passphrase);\n await applySecretBundle(bundle);\n}\n\n/* -------------------------------------------------------------------------- */\n/* Internal helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Best-effort read of a file's POSIX permission bits. Falls back to the\n * owner-only default when the platform/stat doesn't surface a usable mode\n * (e.g. some Windows filesystems).\n */\nasync function readMode(abs: string): Promise<number> {\n try {\n const st = await fsp.lstat(abs);\n // Mask to the low 12 bits (perm + setuid/gid/sticky) like `chmod` expects.\n const m = st.mode & 0o7777;\n return m === 0 ? DEFAULT_SECRET_MODE : m;\n } catch {\n return DEFAULT_SECRET_MODE;\n }\n}\n\n/**\n * Validate the decrypted JSON has the SecretBundle shape before handing it to\n * callers. Keeps `decryptBundle` honest against malformed-but-decryptable input.\n */\nfunction assertBundle(value: unknown): SecretBundle {\n if (typeof value !== \"object\" || value === null) {\n throw new Error(\"Decrypted secret bundle has the wrong shape.\");\n }\n const obj = value as Record<string, unknown>;\n if (obj.version !== 1) {\n throw new Error(`Unsupported secret bundle version: ${String(obj.version)}.`);\n }\n if (typeof obj.createdAt !== \"string\") {\n throw new Error(\"Secret bundle is missing a valid createdAt timestamp.\");\n }\n if (!Array.isArray(obj.entries)) {\n throw new Error(\"Secret bundle is missing its entries array.\");\n }\n\n const entries: SecretBundleEntry[] = obj.entries.map((e, i) => {\n if (typeof e !== \"object\" || e === null) {\n throw new Error(`Secret bundle entry #${i} is malformed.`);\n }\n const rec = e as Record<string, unknown>;\n if (rec.tool !== \"claude\" && rec.tool !== \"codex\" && rec.tool !== \"cursor\") {\n throw new Error(`Secret bundle entry #${i} has an invalid tool id.`);\n }\n if (typeof rec.relPath !== \"string\" || rec.relPath.length === 0) {\n throw new Error(`Secret bundle entry #${i} has an invalid relPath.`);\n }\n if (typeof rec.contentB64 !== \"string\") {\n throw new Error(`Secret bundle entry #${i} has invalid content.`);\n }\n const mode = typeof rec.mode === \"number\" ? rec.mode : undefined;\n return {\n tool: rec.tool as ToolId,\n relPath: rec.relPath,\n contentB64: rec.contentB64,\n mode,\n };\n });\n\n return { version: 1, createdAt: obj.createdAt, entries };\n}\n"],"mappings":"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","names":["path","process","fsp","path","os","tokenFor","out","path","out","path","home","fsp","file","path","execa","os","path","home","paths","REPO_PREFIX","FROZEN_PATHS","INSTRUCTIONS_FILE","init_paths","out","templater","path","repoPathFor","REPO_PREFIX","out","captureFile","text","fsp","captureSkills","capture","paths","FROZEN_PATHS","INSTRUCTIONS_FILE","kind","init_capture","init_paths","path","execa","planActions","exists","restore","marketplaceAddArgs","pluginInstallArgs","init_restore","init_paths","REPO_PREFIX","paths","isCliInstalled","installCli","os","capture","restore","init_paths","init_capture","init_restore","path","home","paths","REPO_PREFIX","FROZEN_PATHS","init_paths","path","repoPathFor","REPO_PREFIX","toRel","home","looksBinary","capture","paths","FROZEN_PATHS","restore","detect","fs","isCliInstalled","installCli","os","log","init_paths","path","home","path","path","execa","errMessage","text","text","process","fsp","path","errMessage","token","stored","out","text","parts","execa","text","out","exists","execa","execa","pickCloneUrl","path","out","home","os","re","out","out","isInstalled","status","z","toolIdSchema","init_schema","isPlainObject","SHARED_INSTRUCTIONS_REPO_PATH","init_schema","register","run","path","capture","errMessage","writeCapturedFile","SHARED_INSTRUCTIONS_REPO_PATH","init_capture","fs","path","pc","path","cancel","confirm","intro","isCancel","multiselect","note","outro","register","run","intro","confirm","isCancel","cancel","errMessage","outro","home","note","multiselect","path","out","confirm","isCancel","password","tokenPaster","cliInstaller","register","path","init_restore","planActions","buildCoreServices","os","path","looksBinary","walk","fileMode","SHARED_INSTRUCTIONS_REPO_PATH","out","localPath","run","register","fsp","init_capture","path","capture","register","run","readIfExists","buildCaptureContext","errMessage","SHARED_INSTRUCTIONS_REPO_PATH","existing","out","detect","path","clack","fsp","path","home","fsp","register","path","errMessage","toolLabel","verbose","register","fs","path","pc"]}