docks-kit 0.2.0 → 0.4.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.
- package/AGENTS.md +5 -3
- package/README.md +15 -7
- package/cli/docs/install.md +12 -12
- package/cli/docs/overview.md +7 -1
- package/cli/docs/platforms.md +8 -7
- package/cli/docs/sync-layers.md +15 -7
- package/cli/docs/toolchain.md +8 -2
- package/cli/src/engine-native/DESIGN.md +25 -14
- package/cli/src/engine-native/bun.ts +87 -0
- package/cli/src/engine-native/claudeRuntime.ts +132 -0
- package/cli/src/engine-native/claudeSync.ts +179 -115
- package/cli/src/engine-native/codexSync.ts +42 -36
- package/cli/src/engine-native/deps.ts +27 -10
- package/cli/src/engine-native/exec.ts +8 -23
- package/cli/src/engine-native/index.ts +8 -8
- package/cli/src/engine-native/models.ts +14 -16
- package/cli/src/engine-native/modes.ts +18 -9
- package/cli/src/engine-native/parseArgs.ts +1 -17
- package/cli/src/engine-native/powershell.ts +11 -0
- package/cli/src/engine-native/skillsSync.ts +11 -46
- package/cli/src/engine-native/toolchain.ts +6 -6
- package/cli/src/generated/sotPayload.ts +41 -0
- package/cli/src/kitHome.ts +15 -11
- package/cli/src/manifests.ts +17 -15
- package/cli/src/payload.ts +28 -0
- package/docks-kit +6 -6
- package/package.json +2 -3
- package/SoT/.agents/skills.txt +0 -14
- package/SoT/.claude/CLAUDE.md +0 -146
- package/SoT/.claude/fetch-usage.sh +0 -66
- package/SoT/.claude/hooks/notify.sh +0 -14
- package/SoT/.claude/mcp-servers.json +0 -10
- package/SoT/.claude/settings.json +0 -235
- package/SoT/.claude/statusline.sh +0 -175
- package/SoT/.codex/AGENTS.md +0 -75
- package/SoT/.codex/agents/.gitkeep +0 -1
- package/SoT/.codex/config.toml +0 -45
- package/SoT/.codex/plugins/marketplace.json +0 -50
- package/SoT/.codex/rules/docks.rules +0 -116
- package/SoT/models.json +0 -31
- package/SoT/toolchain.json +0 -27
- package/notification.mp3 +0 -0
package/AGENTS.md
CHANGED
|
@@ -19,10 +19,11 @@ Tool-specific instructions live alongside this file:
|
|
|
19
19
|
| `cli/` | Effect-TS CLI + bundled docs topics |
|
|
20
20
|
| `SoT/models.json` | Kit-verified model catalog |
|
|
21
21
|
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative) |
|
|
22
|
+
| `SoT/.claude/bin/` | Dependency-free Bun runtime programs for Claude's statusline, SessionStart, and Notification |
|
|
22
23
|
| `install.sh` | Global installer |
|
|
23
24
|
| `.github/workflows/release-cli.yml` | `cli-v*` release binaries + npm publish |
|
|
24
25
|
| `README.md` | Front door |
|
|
25
|
-
| `package.json` / `bun.lock` | npm package: `bin` = `cli/src/main.ts`; bundles `cli/`
|
|
26
|
+
| `package.json` / `bun.lock` | npm package: `bin` = `cli/src/main.ts`; bundles `cli/` with the generated in-memory SoT payload |
|
|
26
27
|
| `SoT/.agents/skills.txt` | Universal-skill manifest. One [agentskills.io](https://agentskills.io) slug per line; EngineNative runs `npx skills add` for each missing entry into `~/.agents/skills/`, where Codex et al. discover it natively and Claude Code follows a symlink at `~/.claude/skills/` |
|
|
27
28
|
| `notification.mp3` | Audio asset for Notification hooks (consumed by Claude Code today; tool-agnostic file) |
|
|
28
29
|
| `docs/plans/` | Multi-commit work-item plans (`active/` with status in frontmatter, plus `finished/` archive). Convention: `docs/plans/AGENTS.md` |
|
|
@@ -36,6 +37,7 @@ Codex SoT notes:
|
|
|
36
37
|
- `SoT/.codex/plugins/marketplace.json` deploys to Codex's personal marketplace path at `~/.agents/plugins/marketplace.json`; when the `codex` CLI is available, sync reruns `codex plugin add <plugin@marketplace>` for enabled SoT plugins so stale cached installs are refreshed.
|
|
37
38
|
- The `codex` CLI binary is upstream-owned, not kit-owned. The official standalone installer keeps package metadata under `$CODEX_HOME/packages/standalone` and places the `codex` symlink in `~/.local/bin` by default; sync only warns with a download-then-run installer command when the CLI is missing. Existing installs can self-update with `codex update`; npm and Homebrew remain upstream alternatives.
|
|
38
39
|
- `SoT/.codex/AGENTS.md` deliberately does not import `@RTK.md`: RTK's published Codex integration is prompt-file based rather than hook based, so importing it leaks implementation detail into agent-visible context. Use Codex hooks for RTK only after the kit installs a hook-backed Codex integration.
|
|
40
|
+
- Claude runtime settings are an authoring template with sentinels. `claudeRuntime.ts` materializes absolute Bun/script paths only after the shared `bun.ts` bootstrap is ready; `claudeSync.ts` writes all runtime assets before atomically committing settings, then prunes the legacy shell scripts and Stop hook. Native `rate_limits` is the sole quota source, so jq/curl/OAuth caches are not runtime dependencies. A missing Bun defers only this cutover and preserves legacy pointers/files.
|
|
39
41
|
|
|
40
42
|
For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT directory.
|
|
41
43
|
|
|
@@ -46,7 +48,7 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
46
48
|
- **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, and `agents`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync codex` for Codex-only config edits); targets can be combined with `--dry-run`, `--skip-rtk`, `--reconcile`, `--prune`, `--yes` (auto-accept toolchain prompts), and the deploy-time modifiers `--claude-compact-window=<tokens>` / `--claude-permissive` / `--claude-model=<m>` / `--codex-model=<m>` (see `CLAUDE.md` § Deploy-time modifiers).
|
|
47
49
|
- **Additive by default.** Keys present in deployed config but absent from SoT are preserved on default sync. This protects user-only additions, but means drift accumulates — neither flag-less reset can clean it up. The one exception is the Claude `removed` manifest (`claude::_removed_manifest`), a curated list of unambiguous kit-owned artifacts that `claude::sync_removals` force-prunes on every sync; see `CLAUDE.md` § Pruning stale artifacts.
|
|
48
50
|
- **`--reconcile` / `--prune` are the kit-owned reconcile flags.** Orthogonal — `--reconcile` reconciles the settings layer (SoT-declared keys/tables/arrays win; user-only keys and nested objects are preserved; permissions arrays are replaced wholesale by SoT). `--prune` uninstalls kit-managed installations not in the SoT (plugins, marketplaces, and `~/.agents/skills/*` entries tracked in `~/.agents/.kit-managed-skills`). Combine for a full reset to SoT's kit-managed scope. User-only additions outside the kit's scope (custom env vars, mcpServers, manually-installed skills, third-party plugins not declared in SoT) are always preserved. Each tool's per-tool file documents the specific paths and diff recipes.
|
|
49
|
-
- **SOLID-aligned modules.** `cli/src/engine-native/parseArgs.ts` owns flag parsing/validation. `toolchain.ts` owns the verified-version gate over `SoT/toolchain.json
|
|
51
|
+
- **SOLID-aligned modules.** `cli/src/engine-native/parseArgs.ts` owns flag parsing/validation. `toolchain.ts` owns the verified-version gate over `SoT/toolchain.json`; `bun.ts` owns the shared, memoized Bun bootstrap; `claudeRuntime.ts` owns Claude settings materialization. `claudeSync.ts`, `codexSync.ts`, and `skillsSync.ts` own tool-specific sync logic. `index.ts` is the thin orchestrator. The public CLI seam is `cli/src/engine.ts`.
|
|
50
52
|
- **Small, reviewable changes.** Bundled multi-concern PRs are harder to review and revert. Split an engine/CLI change and a per-tool config change unless the change requires atomicity.
|
|
51
53
|
- **Dry-run before destructive flags.** Always preview with `./docks-kit sync --dry-run` (or the relevant `diff <(jq -S …)` recipe in the per-tool file) before invoking `--reconcile` or `--prune`. User-added permissions / env vars / plugins absent from SoT will be discarded.
|
|
52
54
|
- **SoT prompt files are rules, not explanation.** `SoT/.claude/CLAUDE.md` and `SoT/.codex/AGENTS.md` are loaded into every agent session's prompt context — every line costs prompt tokens on every turn for every user. Restrict their content to rules, heuristics, and `<constraint>` blocks the agent must *act on* during a turn. Do NOT add inline source citations (`Source: …`, attributed quotes), "why this rule exists" preface text, version-watermarking trivia (e.g. "Distilled from X v2.0, captured 2025-11-07"), per-bug workarounds, or installation instructions. Provenance, motivation, and historical context belong in `CLAUDE.md` / `AGENTS.md` at the repo root (humans read once) or in commit messages — never in the SoT. For every line, apply the official test: would removing it cause the agent to make mistakes? If not, cut it — over-instruction degrades adherence on current frontier models.
|
|
@@ -75,7 +77,7 @@ This project ships **kit-mechanic skills** under `.claude/skills/` — narrowly-
|
|
|
75
77
|
When a kit-mechanic skill, its `references/`, or a wrapper agent (`.claude/agents/*.md` + its `.codex/agents/*.toml` twin) cites EngineNative internals, name the **module + exported/local function + semantic anchor** (e.g. `claudeSync.ts syncPlugins, pass 5 uninstall guard`) — never a raw `file:NNN` line number, which goes stale on every refactor. Keep exactly one coarse `metadata.source_files[].lines` range per skill file as the sole intentional line-number touchpoint.
|
|
76
78
|
</constraint>
|
|
77
79
|
|
|
78
|
-
**Universal-skill bootstrap.** `SoT/.agents/skills.txt` declares [agentskills.io](https://agentskills.io/specification) slugs the kit installs to `~/.agents/skills/` on every machine via `skillsSync.ts`. The bootstrap invokes `npx skills add <slug> -g -y -a claude-code codex` per missing skill — `<slug>` comes first because the CLI's `-a/--agent` flag is variadic and would otherwise swallow it. Naming **both** agents (`claude-code` + `codex`, the kit's support matrix) keeps the CLI in multi-agent mode: it writes the canonical `SKILL.md` to the universal `~/.agents/skills/<name>/` path — which Codex reads natively (per [OpenAI's Codex docs](https://developers.openai.com/codex/skills/), `$HOME/.agents/skills` is a user-level skill source) — and symlinks `~/.claude/skills/<name>` → it for Claude Code, which wants its own per-tool directory. A *single* `-a claude-code` would instead trigger a copy-direct shortcut (a real copy into `~/.claude/skills/`, no canonical path, Codex uncovered); `-a '*'` would over-reach into every AI tool the CLI can detect (~50). Add a new universal skill by appending one `<owner>/<repo>` line to `skills.txt` and re-running `./docks-kit sync` — idempotent: existing skills are skipped after checking `~/.agents/skills/<name>`. Skills that depend on a separate CLI binary get an explicit auto-install helper in `skillsSync.ts` (e.g. `syncAgentBrowserCli` runs `npm install -g agent-browser` + `agent-browser install --with-deps` on Linux; the `--with-deps` flag may prompt for sudo to install system libs). That helper also **self-upgrades** a present-but-stale binary: when `agent-browser`'s installed version is older than npm's `latest` it re-runs `npm install -g agent-browser` (the numeric-sort compare never downgrades a locally-newer pre-release, and skips silently when npm is absent/offline); the Chrome download is not repeated on upgrade. A second helper, `syncEffectSolutionsCli`, installs the optional `effect-solutions` Effect-docs CLI used by the `effect-kit` plugin
|
|
80
|
+
**Universal-skill bootstrap.** `SoT/.agents/skills.txt` declares [agentskills.io](https://agentskills.io/specification) slugs the kit installs to `~/.agents/skills/` on every machine via `skillsSync.ts`. The bootstrap invokes `npx skills add <slug> -g -y -a claude-code codex` per missing skill — `<slug>` comes first because the CLI's `-a/--agent` flag is variadic and would otherwise swallow it. Naming **both** agents (`claude-code` + `codex`, the kit's support matrix) keeps the CLI in multi-agent mode: it writes the canonical `SKILL.md` to the universal `~/.agents/skills/<name>/` path — which Codex reads natively (per [OpenAI's Codex docs](https://developers.openai.com/codex/skills/), `$HOME/.agents/skills` is a user-level skill source) — and symlinks `~/.claude/skills/<name>` → it for Claude Code, which wants its own per-tool directory. A *single* `-a claude-code` would instead trigger a copy-direct shortcut (a real copy into `~/.claude/skills/`, no canonical path, Codex uncovered); `-a '*'` would over-reach into every AI tool the CLI can detect (~50). Add a new universal skill by appending one `<owner>/<repo>` line to `skills.txt` and re-running `./docks-kit sync` — idempotent: existing skills are skipped after checking `~/.agents/skills/<name>`. Skills that depend on a separate CLI binary get an explicit auto-install helper in `skillsSync.ts` (e.g. `syncAgentBrowserCli` runs `npm install -g agent-browser` + `agent-browser install --with-deps` on Linux; the `--with-deps` flag may prompt for sudo to install system libs). That helper also **self-upgrades** a present-but-stale binary: when `agent-browser`'s installed version is older than npm's `latest` it re-runs `npm install -g agent-browser` (the numeric-sort compare never downgrades a locally-newer pre-release, and skips silently when npm is absent/offline); the Chrome download is not repeated on upgrade. A second helper, `syncEffectSolutionsCli`, installs the optional `effect-solutions` Effect-docs CLI used by the `effect-kit` plugin. It calls the shared `bun.ts` `bunBootstrap` when needed, then symlinks **both** `bun` and the CLI into `~/.local/bin`. Linking Bun too is mandatory — the CLI's `#!/usr/bin/env bun` shebang needs it on PATH at run time — and `~/.local/bin` is the only dir reliably on the *non-interactive* agent PATH, since `~/.bashrc`'s "if not interactive, return" guard means rc PATH edits never reach agent shells (the same PATH reason the official Codex standalone installer targets `~/.local/bin`).
|
|
79
81
|
|
|
80
82
|
## Plans
|
|
81
83
|
|
package/README.md
CHANGED
|
@@ -25,11 +25,13 @@ see `./docks-kit docs install`.
|
|
|
25
25
|
|
|
26
26
|
**No-Bun recovery path**:
|
|
27
27
|
|
|
28
|
-
Download the platform release binary from GitHub Releases and run it
|
|
29
|
-
|
|
28
|
+
Download the platform release binary from GitHub Releases and run it directly.
|
|
29
|
+
The executable carries the generated sync payload; no checkout or adjacent
|
|
30
|
+
`SoT/` directory is required.
|
|
30
31
|
|
|
31
|
-
Prerequisites for source/global installs: Bun
|
|
32
|
-
|
|
32
|
+
Prerequisites for source/global installs: Bun; Node/npm for npm-global tools.
|
|
33
|
+
`jq` is optional doctor/test tooling. `curl` is used only at requested POSIX
|
|
34
|
+
RTK/Bun download boundaries, not as a global sync prerequisite.
|
|
33
35
|
|
|
34
36
|
## CLI
|
|
35
37
|
|
|
@@ -82,17 +84,23 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
82
84
|
when possible). `docks-kit toolchain check` shows the full table.
|
|
83
85
|
- **Model catalog** — `SoT/models.json` is the research-verified source for
|
|
84
86
|
model validation, listings, and pickers.
|
|
87
|
+
- **Claude runtime** — sync materializes three dependency-free Bun `.mjs`
|
|
88
|
+
programs for statusline, SessionStart, and Notification. Quota display uses
|
|
89
|
+
Claude's native `rate_limits`; there is no OAuth fetch, shared usage cache,
|
|
90
|
+
or Stop hook. If Bun cannot be resolved or bootstrapped, sync preserves a
|
|
91
|
+
working legacy hook/statusline setup and reports that migration is deferred.
|
|
85
92
|
|
|
86
93
|
## Repository map
|
|
87
94
|
|
|
88
95
|
| Path | Purpose |
|
|
89
96
|
|------|---------|
|
|
90
|
-
| `SoT/.claude/` | Claude Code SoT (settings
|
|
97
|
+
| `SoT/.claude/` | Claude Code SoT (settings template, Bun runtime programs, CLAUDE.md) |
|
|
91
98
|
| `SoT/.codex/` | Codex SoT (config.toml, rules, AGENTS.md, marketplace) |
|
|
92
99
|
| `SoT/.agents/` | Universal-skill manifest |
|
|
93
100
|
| `SoT/models.json` | Kit-verified model catalog |
|
|
94
101
|
| `SoT/toolchain.json` | Verified-version floors |
|
|
95
102
|
| `cli/src/engine-native/` | EngineNative sync/model/toolchain implementation |
|
|
103
|
+
| `cli/src/generated/sotPayload.ts` | Generated in-memory payload used by standalone and npm installs |
|
|
96
104
|
| `cli/` | docks-kit CLI (Effect-TS on Bun) + bundled docs topics |
|
|
97
105
|
| `docks-kit` | Launcher (binary → bun-from-source) |
|
|
98
106
|
| `install.sh` | Global installer (Bun bootstrap + `bun add -g`) |
|
|
@@ -113,8 +121,8 @@ Details: `docks-kit docs platforms`.
|
|
|
113
121
|
|
|
114
122
|
Tagging `cli-v*` builds five standalone binaries (+ SHA256SUMS) and attaches
|
|
115
123
|
them to the GitHub release; npm publish runs when the `NPM_TOKEN` secret is
|
|
116
|
-
configured. Package bundles the CLI +
|
|
117
|
-
snapshots.
|
|
124
|
+
configured. Package bundles the CLI + generated payload, so npm releases are
|
|
125
|
+
versioned config snapshots without shipping the authoring `SoT/` tree.
|
|
118
126
|
|
|
119
127
|
## Deeper docs
|
|
120
128
|
|
package/cli/docs/install.md
CHANGED
|
@@ -21,10 +21,10 @@ docks-kit sync
|
|
|
21
21
|
bunx docks-kit status
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The npm package bundles the CLI and
|
|
25
|
-
snapshots
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
The npm package bundles the CLI and its generated in-memory payload — releases
|
|
25
|
+
are versioned config snapshots without publishing the authoring `SoT/` tree.
|
|
26
|
+
Kit-home resolution remains available for checkout/package update behavior and
|
|
27
|
+
display paths, but sync reads do not depend on it.
|
|
28
28
|
|
|
29
29
|
## 3. curl installer (Unix-only)
|
|
30
30
|
|
|
@@ -44,12 +44,10 @@ kit before. The installer bootstraps Bun when absent, runs
|
|
|
44
44
|
Two supported paths (CI-verified on windows-2025, native PowerShell):
|
|
45
45
|
|
|
46
46
|
- **Compiled binary** — `docks-kit-windows-x64.exe` release asset. No Bun,
|
|
47
|
-
no Git Bash: the exe embeds the runtime and
|
|
48
|
-
|
|
49
|
-
point `DOCKS_KIT_HOME` at one.
|
|
47
|
+
no Git Bash: the exe embeds the runtime and generated payload, and
|
|
48
|
+
EngineNative runs in-process from any working directory.
|
|
50
49
|
- **`bun add -g docks-kit`** — bun creates a working shim for the
|
|
51
|
-
`#!/usr/bin/env bun` bin;
|
|
52
|
-
SoT is used.
|
|
50
|
+
`#!/usr/bin/env bun` bin; the package carries the same generated payload.
|
|
53
51
|
|
|
54
52
|
`install.sh` is not a Windows path.
|
|
55
53
|
|
|
@@ -72,12 +70,14 @@ nudges when the checkout is stale (silent offline / detached / no git).
|
|
|
72
70
|
## No-Bun recovery
|
|
73
71
|
|
|
74
72
|
No Bun or constrained sandbox: download the platform release binary from GitHub
|
|
75
|
-
Releases and run it
|
|
76
|
-
|
|
73
|
+
Releases and run it directly. No checkout or `DOCKS_KIT_HOME` is required for
|
|
74
|
+
sync/config reads.
|
|
77
75
|
|
|
78
76
|
## Prerequisites
|
|
79
77
|
|
|
80
|
-
- jq and curl (sync preflight checks them for the deployed assets and installers)
|
|
81
78
|
- Bun for source/global installs; release binaries embed the runtime
|
|
82
79
|
- Node/npm for npm-global tools (agent-browser, LSP servers)
|
|
80
|
+
- jq is optional doctor/test tooling; sync has no jq runtime dependency
|
|
81
|
+
- curl is required only when a requested POSIX RTK/Bun bootstrap must download
|
|
82
|
+
an installer; an already-present Bun does not require it
|
|
83
83
|
- See `docks-kit toolchain check` for the full picture on this machine
|
package/cli/docs/overview.md
CHANGED
|
@@ -9,11 +9,12 @@ AI-assisted dev environment on every machine.
|
|
|
9
9
|
|
|
10
10
|
| Piece | Role |
|
|
11
11
|
|-------|------|
|
|
12
|
-
| `SoT/.claude/` | Claude Code config (settings
|
|
12
|
+
| `SoT/.claude/` | Claude Code config (settings template, Bun runtime programs, CLAUDE.md) |
|
|
13
13
|
| `SoT/.codex/` | Codex config (config.toml, rules, AGENTS.md, marketplace) |
|
|
14
14
|
| `SoT/.agents/` | Universal agent skills manifest (agentskills.io standard) |
|
|
15
15
|
| `SoT/models.json` | Kit-verified model catalog (see `docks-kit docs models`) |
|
|
16
16
|
| `SoT/toolchain.json` | Verified-version floors for external tools (see `docks-kit docs toolchain`) |
|
|
17
|
+
| `cli/src/generated/sotPayload.ts` | Deterministic generated payload embedded in standalone/npm execution |
|
|
17
18
|
| `cli/src/engine-native/` | EngineNative mutation logic for sync/model/toolchain |
|
|
18
19
|
| `cli/` | This CLI (Effect-TS on Bun) plus bundled docs |
|
|
19
20
|
| `docks-kit` | Launcher: compiled binary → bun-from-source, with Bun auto-install |
|
|
@@ -31,6 +32,11 @@ AI-assisted dev environment on every machine.
|
|
|
31
32
|
changes, and a later flag-less sync reverts them (`docks-kit docs modifiers`).
|
|
32
33
|
- **Engine/CLI split**: the CLI adds UX (typed flags, pickers, docs, JSON);
|
|
33
34
|
EngineNative owns mutation. No-Bun recovery is a platform release binary.
|
|
35
|
+
- **Authoring/runtime split**: changes begin in `SoT/`; build/prepack freshness
|
|
36
|
+
checks keep the generated in-memory payload byte-identical for every runtime.
|
|
37
|
+
- **Native Claude runtime**: three dependency-free Bun `.mjs` programs own the
|
|
38
|
+
statusline, SessionStart, and Notification. Quotas come only from native
|
|
39
|
+
`rate_limits`; missing Bun defers cutover without deleting legacy fallbacks.
|
|
34
40
|
|
|
35
41
|
## Where to go next
|
|
36
42
|
|
package/cli/docs/platforms.md
CHANGED
|
@@ -16,12 +16,10 @@ Supported entrypoints (both verified in CI on windows-2025 under native
|
|
|
16
16
|
PowerShell — see `.github/workflows/windows-entrypoints.yml`):
|
|
17
17
|
|
|
18
18
|
- **`docks-kit-windows-x64.exe`** (release asset) — the no-toolchain path.
|
|
19
|
-
No Bun, no Git Bash; the exe embeds the runtime and
|
|
20
|
-
in-process
|
|
21
|
-
set `DOCKS_KIT_HOME`.
|
|
19
|
+
No Bun, no Git Bash; the exe embeds the runtime and generated payload, and
|
|
20
|
+
EngineNative runs in-process from any working directory.
|
|
22
21
|
- **`bun add -g docks-kit`** — bun creates a working Windows shim for the
|
|
23
|
-
`#!/usr/bin/env bun` bin;
|
|
24
|
-
package's own bundled SoT.
|
|
22
|
+
`#!/usr/bin/env bun` bin; the package carries the same generated payload.
|
|
25
23
|
- `install.sh` is **Unix-only** and not a Windows path.
|
|
26
24
|
|
|
27
25
|
The managed tools all run natively on Windows: Claude Code (requires Git
|
|
@@ -35,8 +33,11 @@ CI coverage (all on the pinned windows-2025 label): EngineNative PowerShell
|
|
|
35
33
|
smoke with `HOME` unset — `%USERPROFILE%` path resolution, `.cmd` tool
|
|
36
34
|
spawning (npm), toolchain gate branches (`.github/workflows/parity.yml`,
|
|
37
35
|
`native-windows` job); the two entrypoints above (`windows-entrypoints.yml`).
|
|
38
|
-
Deployed
|
|
39
|
-
|
|
36
|
+
Deployed SessionStart/Notification hooks directly exec an absolute real
|
|
37
|
+
`bun.exe`. Claude still shell-evaluates the statusline, so its stored command is
|
|
38
|
+
an encoded PowerShell missing-file guard that behaves identically when the outer
|
|
39
|
+
shell is native PowerShell or Git Bash. CI executes both outer-shell paths and
|
|
40
|
+
pins output bytes/channels plus latency ceilings.
|
|
40
41
|
|
|
41
42
|
**Status: supported** — real-machine verified 2026-07-09 (Claude Code loads
|
|
42
43
|
the synced `%USERPROFILE%\.claude`; full sync, self-update, plugin passes,
|
package/cli/docs/sync-layers.md
CHANGED
|
@@ -5,23 +5,31 @@ no target means all three.
|
|
|
5
5
|
|
|
6
6
|
## claude (→ ~/.claude, ~/.claude.json, shell rc)
|
|
7
7
|
|
|
8
|
-
Order matters —
|
|
8
|
+
Order matters — runtime readiness and settings form one transaction:
|
|
9
9
|
|
|
10
10
|
1. **RTK** (toolchain-gated): install/upgrade, then `rtk init --global` on
|
|
11
11
|
first install. Runs FIRST because rtk init rewrites settings.json — the
|
|
12
12
|
later settings merge normalizes whatever it wrote.
|
|
13
|
-
2.
|
|
14
|
-
|
|
13
|
+
2. Resolve/bootstrap pinned Bun, materialize the sentinel settings template,
|
|
14
|
+
and prepare the merged settings bytes without mutation. If Bun remains
|
|
15
|
+
unavailable, omit only the new runtime pointers and preserve legacy ones.
|
|
16
|
+
3. When ready, write `bin/statusline.mjs`, `bin/session-start.mjs`,
|
|
17
|
+
`bin/notify.mjs`, and `notification.mp3`; deploy CLAUDE.md; atomically commit
|
|
18
|
+
settings; then prune the old shell assets and Stop hook.
|
|
19
|
+
4. **settings.json merge semantics** — additive: SoT keys win, permissions arrays are
|
|
15
20
|
unioned, user-only keys survive. `--reconcile` replaces permissions arrays
|
|
16
21
|
wholesale instead.
|
|
17
|
-
|
|
22
|
+
5. **Deploy-time modifiers** (`--claude-compact-window`, `--claude-permissive`,
|
|
18
23
|
`--claude-model`) — deployed file only.
|
|
19
|
-
|
|
24
|
+
6. ~/.claude.json (showTurnDuration, user-scoped MCP servers), connector env
|
|
20
25
|
export, removed-artifact pruning.
|
|
21
|
-
|
|
26
|
+
7. **Plugins** — seven idempotent passes via the `claude plugin` CLI
|
|
22
27
|
(marketplaces → install → update → [--prune: uninstall/remove] → re-assert
|
|
23
28
|
SoT enabled-state). Optional opt-ins via `--claude-plugin=<name>`.
|
|
24
|
-
|
|
29
|
+
8. LSP server binaries (npm globals).
|
|
30
|
+
|
|
31
|
+
The statusline reads Claude's native `rate_limits`. There is no OAuth request,
|
|
32
|
+
usage cache, jq/curl runtime dependency, or Stop fetch hook.
|
|
25
33
|
|
|
26
34
|
## codex (→ ~/.codex, ~/.agents/plugins)
|
|
27
35
|
|
package/cli/docs/toolchain.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Field | Meaning |
|
|
6
6
|
|-------|---------|
|
|
7
|
-
| `kind` | `
|
|
7
|
+
| `kind` | `check` (doctor visibility) / `managed` (kit installs + upgrades) / `pin` (no binary — a version pin for npx-invoked tools, e.g. `skills-cli`) |
|
|
8
8
|
| `policy` | `track` (upgrade toward latest, gated by `verified`) / `present` (install when missing, never upgrade) |
|
|
9
9
|
| `floor` | Minimum acceptable version (below → upgrade automatically) |
|
|
10
10
|
| `verified` | Last kit-tested version — the gate line |
|
|
@@ -31,10 +31,16 @@ now kit-approved" act.
|
|
|
31
31
|
settings rewrite is normalized by the merge that follows. Pinned installs
|
|
32
32
|
fetch the installer script from the version tag, not mutable master.
|
|
33
33
|
- **bun** — policy `present`: bootstrap only (pinned to `verified` via the
|
|
34
|
-
installer's
|
|
34
|
+
installer's version argument), never auto-upgraded. `bun.ts` owns one
|
|
35
|
+
per-engine-run memo shared by Claude runtime, effect-solutions, and direct
|
|
36
|
+
toolchain ensure. Windows resolves only a real absolute `bun.exe` for hooks.
|
|
35
37
|
- **effect-solutions**, **agent-browser** — policy `track`: self-upgrade
|
|
36
38
|
toward npm latest, gated by their `verified` pins.
|
|
37
39
|
|
|
40
|
+
jq and curl are `check` rows, not global prerequisites. jq is not consumed by
|
|
41
|
+
normal sync. curl is checked only at a requested POSIX RTK/Bun installer
|
|
42
|
+
download boundary; Windows Bun bootstrap uses PowerShell's native download.
|
|
43
|
+
|
|
38
44
|
## Supply-chain stance
|
|
39
45
|
|
|
40
46
|
Every kit-driven install is pinned to a `verified` version or gated by one —
|
|
@@ -27,15 +27,19 @@ explicit removed-engine diagnostic and exits 2 with the recovery tag message.
|
|
|
27
27
|
- **Prove-red stays red.** The golden suites compare live native output to a
|
|
28
28
|
mismatched golden under `--prove-red` and must exit non-zero after printing
|
|
29
29
|
`prove-red OK`.
|
|
30
|
-
- **Step ordering is load-bearing.** The Claude pipeline runs RTK
|
|
31
|
-
settings
|
|
32
|
-
after
|
|
30
|
+
- **Step ordering is load-bearing.** The Claude pipeline runs RTK, resolves Bun,
|
|
31
|
+
prepares materialized settings, writes runtime assets, commits settings, then
|
|
32
|
+
performs readiness-gated legacy cleanup. Modifiers run after the base commit,
|
|
33
|
+
removals before plugins, and LSP checks after plugin state.
|
|
33
34
|
- **External CLIs stay external.** `claude`, `codex`, `npx`, `npm`, `rtk`,
|
|
34
35
|
`bun`, `curl`, and platform package managers are spawned with argv arrays,
|
|
35
36
|
not shell command strings except where the external installer contract is a
|
|
36
37
|
shell script.
|
|
37
38
|
- **Backups precede mutation.** Deployed settings/config files write `.bak`
|
|
38
39
|
backups before replacement.
|
|
40
|
+
- **Runtime payload is in memory.** `SoT/` remains the reviewed authoring tree;
|
|
41
|
+
generated payload freshness gates build/package production, and EngineNative
|
|
42
|
+
never needs an adjacent runtime `SoT/` directory.
|
|
39
43
|
|
|
40
44
|
## Output Policy (log UX contract)
|
|
41
45
|
|
|
@@ -69,10 +73,12 @@ each such skip is an intentional behavior change named in its golden diff.
|
|
|
69
73
|
|
|
70
74
|
### Missing dependencies
|
|
71
75
|
|
|
72
|
-
Exactly one deduplicated warn per missing tool per run, uniform shape:
|
|
76
|
+
Exactly one deduplicated warn per requested missing tool per run, uniform shape:
|
|
73
77
|
`[warn] <tool> not installed — <platform-correct install command>`, sourced
|
|
74
|
-
from the dependency registry (`deps.ts`).
|
|
75
|
-
|
|
78
|
+
from the dependency registry (`deps.ts`). jq and curl are optional report rows:
|
|
79
|
+
jq has no runtime consumer, while curl warns only at a requested POSIX RTK/Bun
|
|
80
|
+
download boundary. A missing Bun defers Claude runtime migration without
|
|
81
|
+
deleting working legacy hooks or statusline files.
|
|
76
82
|
|
|
77
83
|
### Summary and next steps
|
|
78
84
|
|
|
@@ -111,14 +117,17 @@ active logger binding.
|
|
|
111
117
|
|
|
112
118
|
| Module | Owns |
|
|
113
119
|
|---|---|
|
|
114
|
-
| `parseArgs.ts` | engine usage, target selection, flag parsing, legacy rename hints,
|
|
120
|
+
| `parseArgs.ts` | engine usage, target selection, flag parsing, legacy rename hints, model flag validation |
|
|
115
121
|
| `index.ts` | sync orchestration, target dispatch, run summary and next-step blocks |
|
|
116
|
-
|
|
|
122
|
+
| `../payload.ts` | generated text/byte payload reads and presentation-only source labels |
|
|
123
|
+
| `claudeSync.ts` | Claude pipeline: RTK, prepared settings transaction, runtime assets, deploy-time modifiers, `~/.claude.json`, readiness-gated removed artifacts, plugins, optional plugins, LSP binaries |
|
|
124
|
+
| `bun.ts` | per-run memoized Bun resolution/bootstrap shared by Claude runtime, effect-solutions, and direct toolchain ensure |
|
|
125
|
+
| `claudeRuntime.ts` | sentinel validation, absolute runtime paths, no-cutover settings projection, and POSIX/encoded-PowerShell statusline commands |
|
|
117
126
|
| `settings.ts` | pure Claude settings merge/reconcile semantics and permission-array union |
|
|
118
127
|
| `claudeModel.ts` | deployed Claude model modifier and direct `model claude` write path |
|
|
119
128
|
| `codexSync.ts` | Codex pipeline: bubblewrap check, config merge, rules, AGENTS.md, personal marketplace, plugin refresh |
|
|
120
129
|
| `codexToml.ts` | line-based top-level TOML replacement and deployed Codex model modifier |
|
|
121
|
-
| `skillsSync.ts` | universal skill install/prune, Claude symlink healing, agent-browser/effect-solutions
|
|
130
|
+
| `skillsSync.ts` | universal skill install/prune, Claude symlink healing, agent-browser/effect-solutions callbacks, managed-skill snapshot |
|
|
122
131
|
| `toolchain.ts` | tool presence/version probes, verified-version gate, managed install/upgrade orchestration, report table |
|
|
123
132
|
| `modes.ts` | direct `model` and `toolchain` modes |
|
|
124
133
|
| `models.ts` | model catalog listing and validation |
|
|
@@ -138,8 +147,9 @@ active logger binding.
|
|
|
138
147
|
- Symlink creation falls back to copy where the platform or permissions require
|
|
139
148
|
it.
|
|
140
149
|
- Bubblewrap and shell-rc work are Linux/macOS only.
|
|
141
|
-
- Claude
|
|
142
|
-
|
|
150
|
+
- Claude command hooks directly exec the resolved absolute `bun.exe`; the
|
|
151
|
+
statusline stores an encoded PowerShell missing-file guard because Claude
|
|
152
|
+
shell-evaluates `statusLine.command` through PowerShell or Git Bash.
|
|
143
153
|
|
|
144
154
|
## Tests
|
|
145
155
|
|
|
@@ -149,8 +159,9 @@ active logger binding.
|
|
|
149
159
|
- `bun run golden:mutation` compares live native mutation snapshots, argv logs,
|
|
150
160
|
output, and TOML invariants to `cli/test/goldens/mutation.json`.
|
|
151
161
|
- `.github/workflows/parity.yml` is now the golden-regression workflow: Linux
|
|
152
|
-
runs unit + golden + prove-red
|
|
153
|
-
|
|
162
|
+
runs unit + golden + prove-red plus the exact materialized POSIX runtime
|
|
163
|
+
commands; the `native-windows` job executes the same stored statusline command
|
|
164
|
+
through PowerShell and Git Bash plus direct Bun hooks.
|
|
154
165
|
- `.github/workflows/windows-entrypoints.yml` verifies the release binary and
|
|
155
166
|
`bun add -g` entrypoints on Windows.
|
|
156
167
|
|
|
@@ -158,5 +169,5 @@ active logger binding.
|
|
|
158
169
|
|
|
159
170
|
- Reintroducing the removed shell engine as a supported fallback. Fix forward in
|
|
160
171
|
EngineNative; recover historical source only from `bash-engine-final`.
|
|
161
|
-
-
|
|
172
|
+
- Adding a compiled runner or CLI hook subcommand for Claude's Bun runtime.
|
|
162
173
|
- Adding new sync features while changing the engine contract.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process"
|
|
2
|
+
import { rmSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
|
|
5
|
+
import { p } from "./exec"
|
|
6
|
+
import type { Ctx } from "./index"
|
|
7
|
+
import { encodePowerShellCommand, powerShellLiteral } from "./powershell"
|
|
8
|
+
import type { EngineServices } from "./services"
|
|
9
|
+
import { field } from "./toolchain"
|
|
10
|
+
|
|
11
|
+
export type BunRuntimeState =
|
|
12
|
+
| { readonly kind: "ready"; readonly executable: string }
|
|
13
|
+
| { readonly kind: "deferred"; readonly reason: "missing-curl" | "install-failed" }
|
|
14
|
+
|
|
15
|
+
function remember(ctx: Ctx, state: BunRuntimeState): BunRuntimeState {
|
|
16
|
+
ctx.bunRuntime = state
|
|
17
|
+
return state
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function predictedExecutable(ctx: Ctx, services: EngineServices): string {
|
|
21
|
+
const root = process.env["BUN_INSTALL"] !== undefined && process.env["BUN_INSTALL"] !== ""
|
|
22
|
+
? process.env["BUN_INSTALL"]!
|
|
23
|
+
: p(ctx.home, ".bun")
|
|
24
|
+
return p(root, "bin", services.platform.isWindows() ? "bun.exe" : "bun")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function windowsDownloadScript(installer: string): string {
|
|
28
|
+
return `$ErrorActionPreference = 'Stop'; Invoke-WebRequest -Uri ${powerShellLiteral("https://bun.sh/install.ps1")} -OutFile ${powerShellLiteral(installer)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function installBun(ctx: Ctx, services: EngineServices, pin: string, installer: string): void {
|
|
32
|
+
if (services.platform.isWindows()) {
|
|
33
|
+
const encoded = encodePowerShellCommand(windowsDownloadScript(installer))
|
|
34
|
+
const download = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], { stdio: "ignore" })
|
|
35
|
+
if (download.error === undefined && download.status === 0) {
|
|
36
|
+
spawnSync(
|
|
37
|
+
"powershell.exe",
|
|
38
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", installer, "-Version", pin, "-DownloadWithoutCurl"],
|
|
39
|
+
{ stdio: "ignore" }
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const download = spawnSync("curl", ["-fsSL", "https://bun.sh/install", "-o", installer], { stdio: "ignore" })
|
|
46
|
+
if (download.error === undefined && download.status === 0) {
|
|
47
|
+
spawnSync("bash", [installer, `bun-v${pin}`], { stdio: "ignore" })
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function bunBootstrap(ctx: Ctx, services: EngineServices): BunRuntimeState {
|
|
52
|
+
if (ctx.bunRuntime !== undefined) return ctx.bunRuntime
|
|
53
|
+
|
|
54
|
+
const existing = services.deps.path("bun")
|
|
55
|
+
if (existing !== "") return remember(ctx, { kind: "ready", executable: existing })
|
|
56
|
+
|
|
57
|
+
const pin = field(ctx, "bun", "verified")
|
|
58
|
+
if (pin === "") {
|
|
59
|
+
services.logger.warn("Bun bootstrap aborted — SoT/toolchain.json has no verified Bun pin")
|
|
60
|
+
return remember(ctx, { kind: "deferred", reason: "install-failed" })
|
|
61
|
+
}
|
|
62
|
+
if (!services.platform.isWindows() && services.deps.probe("curl").state === "missing") {
|
|
63
|
+
services.deps.warnMissing("curl", services.logger, "cannot bootstrap Bun; install Bun manually, then re-run sync")
|
|
64
|
+
return remember(ctx, { kind: "deferred", reason: "missing-curl" })
|
|
65
|
+
}
|
|
66
|
+
if (ctx.dryRun) {
|
|
67
|
+
const executable = predictedExecutable(ctx, services)
|
|
68
|
+
services.logger.echo(`[dry-run] install Bun ${pin} (kit-verified) -> ${executable}`)
|
|
69
|
+
return remember(ctx, { kind: "ready", executable })
|
|
70
|
+
}
|
|
71
|
+
services.logger.warn(`Bun not found — installing Bun ${pin} (kit-verified)...`)
|
|
72
|
+
const installer = p(tmpdir(), `bun-install-${process.pid}.${services.platform.isWindows() ? "ps1" : "sh"}`)
|
|
73
|
+
try {
|
|
74
|
+
installBun(ctx, services, pin, installer)
|
|
75
|
+
} finally {
|
|
76
|
+
rmSync(installer, { force: true })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const installed = services.deps.path("bun")
|
|
80
|
+
if (installed === "") {
|
|
81
|
+
services.logger.warn("Bun install failed. Install manually from https://bun.sh/docs/installation, then re-run sync.")
|
|
82
|
+
return remember(ctx, { kind: "deferred", reason: "install-failed" })
|
|
83
|
+
}
|
|
84
|
+
const version = services.deps.version("bun")
|
|
85
|
+
services.logger.change(`Bun installed (${version !== "" ? version : "version unknown"})`)
|
|
86
|
+
return remember(ctx, { kind: "ready", executable: installed })
|
|
87
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { p } from "./exec"
|
|
2
|
+
import { isObject, parseJson, type Json } from "./jq"
|
|
3
|
+
import { encodePowerShellCommand, powerShellLiteral } from "./powershell"
|
|
4
|
+
import type { Platform } from "./services"
|
|
5
|
+
|
|
6
|
+
const BUN_SENTINEL = "__DOCKS_KIT_BUN__"
|
|
7
|
+
const SESSION_START_SENTINEL = "__DOCKS_KIT_SESSION_START__"
|
|
8
|
+
const NOTIFY_SENTINEL = "__DOCKS_KIT_NOTIFY__"
|
|
9
|
+
const STATUSLINE_SENTINEL = "__DOCKS_KIT_STATUSLINE__"
|
|
10
|
+
|
|
11
|
+
export interface ClaudeRuntimePaths {
|
|
12
|
+
readonly bun: string
|
|
13
|
+
readonly statusline: string
|
|
14
|
+
readonly sessionStart: string
|
|
15
|
+
readonly notify: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function claudeRuntimePaths(claudeDir: string, bun: string): ClaudeRuntimePaths {
|
|
19
|
+
return {
|
|
20
|
+
bun,
|
|
21
|
+
statusline: p(claudeDir, "bin", "statusline.mjs"),
|
|
22
|
+
sessionStart: p(claudeDir, "bin", "session-start.mjs"),
|
|
23
|
+
notify: p(claudeDir, "bin", "notify.mjs")
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function cloneJson(value: Json): Json {
|
|
28
|
+
const cloned = parseJson(JSON.stringify(value))
|
|
29
|
+
if (cloned === undefined) throw new Error("Claude settings template cannot be serialized")
|
|
30
|
+
return cloned
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function countString(value: Json, expected: string): number {
|
|
34
|
+
if (value === expected) return 1
|
|
35
|
+
if (Array.isArray(value)) return value.reduce<number>((total, item) => total + countString(item, expected), 0)
|
|
36
|
+
if (!isObject(value)) return 0
|
|
37
|
+
return Object.values(value).reduce<number>((total, item) => total + countString(item, expected), 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hooksObject(template: Json): { [key: string]: Json } {
|
|
41
|
+
if (!isObject(template) || !isObject(template["hooks"])) throw new Error("Claude settings hooks object is missing")
|
|
42
|
+
return template["hooks"]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function commandHandler(template: Json, event: "SessionStart" | "Notification"): { [key: string]: Json } {
|
|
46
|
+
const entries = hooksObject(template)[event]
|
|
47
|
+
if (!Array.isArray(entries) || entries.length !== 1 || !isObject(entries[0])) {
|
|
48
|
+
throw new Error(`${event} sentinel location is invalid`)
|
|
49
|
+
}
|
|
50
|
+
const handlers = entries[0]["hooks"]
|
|
51
|
+
if (!Array.isArray(handlers) || handlers.length !== 1 || !isObject(handlers[0])) {
|
|
52
|
+
throw new Error(`${event} sentinel handler is invalid`)
|
|
53
|
+
}
|
|
54
|
+
return handlers[0]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function oneArg(handler: { [key: string]: Json }, sentinel: string, event: string): void {
|
|
58
|
+
const args = handler["args"]
|
|
59
|
+
if (!Array.isArray(args) || args.length !== 1 || args[0] !== sentinel) {
|
|
60
|
+
throw new Error(`${event} argument sentinel location is invalid`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validateTemplate(template: Json): void {
|
|
65
|
+
const sessionStart = commandHandler(template, "SessionStart")
|
|
66
|
+
const notification = commandHandler(template, "Notification")
|
|
67
|
+
if (sessionStart["command"] !== BUN_SENTINEL) throw new Error("SessionStart Bun sentinel location is invalid")
|
|
68
|
+
if (notification["command"] !== BUN_SENTINEL) throw new Error("Notification Bun sentinel location is invalid")
|
|
69
|
+
oneArg(sessionStart, SESSION_START_SENTINEL, "SessionStart")
|
|
70
|
+
oneArg(notification, NOTIFY_SENTINEL, "Notification")
|
|
71
|
+
|
|
72
|
+
const hooks = hooksObject(template)
|
|
73
|
+
if (hooks["Stop"] !== undefined) throw new Error("hooks.Stop must be absent from the Claude settings template")
|
|
74
|
+
if (!isObject(template) || !isObject(template["statusLine"]) || template["statusLine"]["command"] !== STATUSLINE_SENTINEL) {
|
|
75
|
+
throw new Error("Statusline sentinel location is invalid")
|
|
76
|
+
}
|
|
77
|
+
const counts: ReadonlyArray<[string, string, number]> = [
|
|
78
|
+
["Bun", BUN_SENTINEL, 2],
|
|
79
|
+
["SessionStart", SESSION_START_SENTINEL, 1],
|
|
80
|
+
["notify", NOTIFY_SENTINEL, 1],
|
|
81
|
+
["statusline", STATUSLINE_SENTINEL, 1]
|
|
82
|
+
]
|
|
83
|
+
for (const [label, sentinel, expected] of counts) {
|
|
84
|
+
if (countString(template, sentinel) !== expected) throw new Error(`${label} sentinel residue/cardinality is invalid`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function posixLiteral(value: string): string {
|
|
89
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function statusLineCommand(runtime: ClaudeRuntimePaths, platform: Platform): string {
|
|
93
|
+
if (!platform.isWindows()) {
|
|
94
|
+
const bun = posixLiteral(runtime.bun)
|
|
95
|
+
const script = posixLiteral(runtime.statusline)
|
|
96
|
+
return `test -x ${bun} && test -f ${script} && exec ${bun} ${script} || true`
|
|
97
|
+
}
|
|
98
|
+
const bun = powerShellLiteral(runtime.bun.replaceAll("\\", "/"))
|
|
99
|
+
const script = powerShellLiteral(runtime.statusline.replaceAll("\\", "/"))
|
|
100
|
+
const guard = `$ProgressPreference = 'SilentlyContinue'; if ((Test-Path -LiteralPath ${bun} -PathType Leaf) -and (Test-Path -LiteralPath ${script} -PathType Leaf)) { & ${bun} ${script} }`
|
|
101
|
+
return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encodePowerShellCommand(guard)}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function materializeClaudeSettings(
|
|
105
|
+
template: Json,
|
|
106
|
+
runtime: ClaudeRuntimePaths | undefined,
|
|
107
|
+
platform: Platform
|
|
108
|
+
): Json {
|
|
109
|
+
validateTemplate(template)
|
|
110
|
+
const result = cloneJson(template)
|
|
111
|
+
const hooks = hooksObject(result)
|
|
112
|
+
if (runtime === undefined) {
|
|
113
|
+
delete hooks["SessionStart"]
|
|
114
|
+
delete hooks["Notification"]
|
|
115
|
+
if (!isObject(result)) throw new Error("Claude settings template must be an object")
|
|
116
|
+
delete result["statusLine"]
|
|
117
|
+
} else {
|
|
118
|
+
const sessionStart = commandHandler(result, "SessionStart")
|
|
119
|
+
sessionStart["command"] = runtime.bun
|
|
120
|
+
sessionStart["args"] = [runtime.sessionStart]
|
|
121
|
+
const notification = commandHandler(result, "Notification")
|
|
122
|
+
notification["command"] = runtime.bun
|
|
123
|
+
notification["args"] = [runtime.notify]
|
|
124
|
+
if (!isObject(result) || !isObject(result["statusLine"])) throw new Error("Claude statusLine object is missing")
|
|
125
|
+
result["statusLine"]["command"] = statusLineCommand(runtime, platform)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const sentinel of [BUN_SENTINEL, SESSION_START_SENTINEL, NOTIFY_SENTINEL, STATUSLINE_SENTINEL]) {
|
|
129
|
+
if (countString(result, sentinel) !== 0) throw new Error(`Claude settings sentinel residue: ${sentinel}`)
|
|
130
|
+
}
|
|
131
|
+
return result
|
|
132
|
+
}
|