docks-kit 0.9.0 → 0.10.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 +9 -4
- package/README.md +21 -16
- package/cli/docs/install.md +11 -20
- package/cli/docs/platforms.md +16 -44
- package/cli/docs/toolchain.md +6 -6
- package/cli/src/commands/docs.ts +1 -1
- package/cli/src/commands/toolchain.ts +1 -1
- package/cli/src/engine-native/DESIGN.md +19 -27
- package/cli/src/engine-native/bun.ts +7 -25
- package/cli/src/engine-native/claudeRuntime.ts +6 -15
- package/cli/src/engine-native/claudeSync.ts +3 -34
- package/cli/src/engine-native/codexSync.ts +1 -1
- package/cli/src/engine-native/deps.ts +28 -49
- package/cli/src/engine-native/exec.ts +6 -13
- package/cli/src/engine-native/os.ts +7 -12
- package/cli/src/engine-native/services.ts +2 -4
- package/cli/src/engine-native/sessionRelayCli.ts +1 -1
- package/cli/src/engine-native/skillsSync.ts +13 -38
- package/cli/src/engine.ts +23 -15
- package/cli/src/generated/sotPayload.ts +1 -1
- package/docks-kit +11 -10
- package/package.json +5 -1
- package/cli/src/engine-native/powershell.ts +0 -11
package/AGENTS.md
CHANGED
|
@@ -4,24 +4,29 @@ Canonical instructions for coding agents working on this project. Compatible wit
|
|
|
4
4
|
|
|
5
5
|
## Repository purpose
|
|
6
6
|
|
|
7
|
-
Portable configuration kit for AI coding agents. Per-tool Single Source of Truth (SoT) directories get deployed to each tool's user-config location via `./docks-kit sync` — clone once
|
|
7
|
+
Portable configuration kit for AI coding agents. Per-tool Single Source of Truth (SoT) directories get deployed to each tool's user-config location via `./docks-kit sync` — clone once and get a consistent AI-assisted dev environment across supported Linux and macOS hosts. The kit focuses on **token efficiency without sacrificing quality**: every setting and hook is tuned to minimize token consumption while preserving rigorous output. When adding or editing anything, ask: *does this change reduce tokens without weakening correctness?*
|
|
8
8
|
|
|
9
9
|
Tool-specific instructions live alongside this file:
|
|
10
10
|
- **`CLAUDE.md`** — Claude Code SoT (`SoT/.claude/`), env vars, hooks, plugins, RTK, status line, session management, permission mode, open concerns.
|
|
11
11
|
- Codex uses this `AGENTS.md` file plus the Codex SoT under `SoT/.codex/`; no separate root `CODEX.md` is needed.
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
docks-kit runtime, standalone binary, and Session Relay prebuilt support is
|
|
15
|
+
exactly Linux x64/arm64 and macOS x64/arm64. Unsupported hosts fail before the
|
|
16
|
+
launcher can fall back to Bun source.
|
|
17
|
+
|
|
13
18
|
## Repository layout (cross-cutting)
|
|
14
19
|
|
|
15
20
|
| Path | Purpose |
|
|
16
21
|
|------|---------|
|
|
17
|
-
| `docks-kit` | CLI launcher: runs the platform binary in `cli/dist/` only when its `--version` matches `package.json`, otherwise Bun-from-source (auto-installs Bun + `node_modules`). No-Bun recovery is the standalone platform release binary |
|
|
22
|
+
| `docks-kit` | CLI launcher: on supported hosts, runs the platform binary in `cli/dist/` only when its `--version` matches `package.json`, otherwise Bun-from-source (auto-installs Bun + `node_modules`). Unsupported hosts fail before source fallback. No-Bun recovery is the standalone platform release binary |
|
|
18
23
|
| `cli/src/engine-native/` | EngineNative implementation for `sync`, `model`, `workflow`, and `toolchain`; idempotent, flag-gated for destructive reconciliation |
|
|
19
24
|
| `cli/` | Effect-TS CLI + bundled docs topics |
|
|
20
25
|
| `SoT/models.json` | Kit-verified model catalog plus the strict Docks workflow-role registry |
|
|
21
26
|
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative) |
|
|
22
27
|
| `SoT/.claude/bin/` | Dependency-free Bun runtime programs for Claude's statusline, SessionStart, and Notification |
|
|
23
28
|
| `install.sh` | Global installer |
|
|
24
|
-
| `.github/workflows/release-cli.yml` | `cli-v*` release binaries
|
|
29
|
+
| `.github/workflows/release-cli.yml` | `cli-v*` release: four Linux/macOS x64/arm64 binaries, `SHA256SUMS`, and npm publish |
|
|
25
30
|
| `README.md` | Front door |
|
|
26
31
|
| `package.json` / `bun.lock` | npm package: `bin` = `cli/src/main.ts`; bundles `cli/` with the generated in-memory SoT payload |
|
|
27
32
|
| `SoT/.agents/skills.txt` | Universal-skill manifest, intentionally empty by default. Adding an [agentskills.io](https://agentskills.io/specification) slug opts it into EngineNative's shared `~/.agents/skills/` bootstrap and Claude symlink. |
|
|
@@ -36,7 +41,7 @@ Codex SoT notes:
|
|
|
36
41
|
- `SoT/.codex/rules/*.rules` deploys to `~/.codex/rules/` as kit-managed Codex command policy. This is Codex's equivalent of permission allow/prompt/block rules; user-learned approvals in `~/.codex/rules/default.rules` are preserved.
|
|
37
42
|
- `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.
|
|
38
43
|
- `docks-kit status` verifies Session Relay only through the supported `codex plugin list --json` inventory. `ready` means installed and enabled for a newly started Codex session; it is not evidence about an old process, lifecycle state, receive-path health, or worker quiescence. The global prompt SoTs carry the owner's standing authorization for Docks cross-company plan review, which never overrides host or platform denial.
|
|
39
|
-
- Claude and Codex sync call `sessionRelayCli.ts ensureSessionRelayCli` immediately before their plugin passes. It installs the exact source-pinned precompiled command at `~/.local/bin/session-relay` for Linux/macOS x64/arm64 only, requires committed digest = same-release `SHA256SUMS` row = downloaded bytes, smoke-tests the staged version, and atomically replaces the stable path. `agents`-only sync never enters this boundary. The
|
|
44
|
+
- Claude and Codex sync call `sessionRelayCli.ts ensureSessionRelayCli` immediately before their plugin passes. It installs the exact source-pinned precompiled command at `~/.local/bin/session-relay` for Linux/macOS x64/arm64 only, requires committed digest = same-release `SHA256SUMS` row = downloaded bytes, smoke-tests the staged version, and atomically replaces the stable path. `agents`-only sync never enters this boundary. The four pinned production digests correspond to the stable `session-relay--v0.12.0` assets and its `SHA256SUMS`.
|
|
40
45
|
- Both global prompt SoTs carry one byte-identical compact `Docks-workflow-models:` record. Root `--model-orchestrator` / `--model-reviewer` / `--model-implementer` / review-bound flags update only that deployed record; `docks-kit models workflow` lists the closed selectors, and a flag-less sync restores defaults.
|
|
41
46
|
- Codex workflow selectors use the closed `<tool>:<model>@<effort>[+fast]` routing grammar. Fast is intentionally absent from the global SoT: no suffix means Standard and keeps the backward-compatible schema-1 record; `+fast` emits a schema-2 candidate with `service_tier: "fast"`. Docks and Session Relay consumers must explicitly launch unsuffixed Codex roles with the default service tier so a user's global Fast preference cannot leak into workflows.
|
|
42
47
|
- 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.
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Portable configuration kit for AI coding agents. Per-tool **Single Source of
|
|
4
4
|
Truth (SoT)** directories are deployed to each tool's user-config location —
|
|
5
|
-
clone once
|
|
6
|
-
|
|
5
|
+
clone once and get a consistent AI-assisted dev environment across supported
|
|
6
|
+
Linux and macOS machines. Tuned for **token efficiency without sacrificing quality**.
|
|
7
7
|
|
|
8
8
|
Supported tools: **Claude Code**, **Codex**, and any agent consuming
|
|
9
9
|
[agentskills.io](https://agentskills.io) universal skills.
|
|
@@ -17,10 +17,12 @@ cd ~/projects/public
|
|
|
17
17
|
./docks-kit status # doctor view: drift, toolchain, plugins, skills
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The `./docks-kit` launcher
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
The `./docks-kit` launcher supports Linux x64/arm64 and macOS x64/arm64.
|
|
21
|
+
On those hosts it prefers a compiled binary (`cli/dist/`) only when its version
|
|
22
|
+
matches the checkout, then runs from source via Bun—auto-installing Bun and
|
|
23
|
+
dependencies when missing. Unsupported hosts fail immediately and never fall
|
|
24
|
+
back to Bun source. Stale ignored build artifacts cannot mask newer checkout
|
|
25
|
+
code.
|
|
24
26
|
|
|
25
27
|
Other install paths (global `bun add -g docks-kit`, curl installer) —
|
|
26
28
|
see `./docks-kit docs install`.
|
|
@@ -32,7 +34,7 @@ The executable carries the generated sync payload; no checkout or adjacent
|
|
|
32
34
|
`SoT/` directory is required.
|
|
33
35
|
|
|
34
36
|
Prerequisites for source/global installs: Bun; Node/npm for npm-global tools.
|
|
35
|
-
`jq` is optional doctor/test tooling. `curl` is used only at requested
|
|
37
|
+
`jq` is optional doctor/test tooling. `curl` is used only at requested Linux/macOS
|
|
36
38
|
RTK/Bun download boundaries, not as a global sync prerequisite.
|
|
37
39
|
|
|
38
40
|
## CLI
|
|
@@ -107,7 +109,8 @@ current valid values, while a later flag-less sync restores all defaults.
|
|
|
107
109
|
Session Relay plugin reconciliation. Linux/macOS x64/arm64 assets must match
|
|
108
110
|
both the committed digest and their release `SHA256SUMS`, then pass an exact
|
|
109
111
|
staged version smoke before atomic replacement; failures preserve any prior
|
|
110
|
-
command.
|
|
112
|
+
command. The four production digests are pinned for the stable
|
|
113
|
+
`session-relay--v0.12.0` assets and match the same-release `SHA256SUMS`.
|
|
111
114
|
- **Model catalog** — `SoT/models.json` is the research-verified source for
|
|
112
115
|
model validation, listings, pickers, and the Docks workflow-role registry.
|
|
113
116
|
- **Claude runtime** — sync materializes three dependency-free Bun `.mjs`
|
|
@@ -135,19 +138,21 @@ current valid values, while a later flag-less sync restores all defaults.
|
|
|
135
138
|
|
|
136
139
|
## Platform support
|
|
137
140
|
|
|
138
|
-
| Platform |
|
|
139
|
-
|
|
140
|
-
| Linux | ✅
|
|
141
|
-
|
|
|
142
|
-
|
|
|
141
|
+
| Platform | Architecture | docks-kit binary | Session Relay prebuilt | Sync engine |
|
|
142
|
+
|----------|--------------|------------------|------------------------|-------------|
|
|
143
|
+
| Linux | x64 | ✅ | ✅ | ✅ native |
|
|
144
|
+
| Linux | arm64 | ✅ | ✅ | ✅ native |
|
|
145
|
+
| macOS | x64 | ✅ | ✅ | ✅ native |
|
|
146
|
+
| macOS | arm64 | ✅ | ✅ | ✅ native |
|
|
143
147
|
|
|
144
148
|
Details: `docks-kit docs platforms`.
|
|
145
149
|
|
|
146
150
|
## Releases
|
|
147
151
|
|
|
148
|
-
Tagging `cli-v*` builds
|
|
149
|
-
them to the GitHub release; npm
|
|
150
|
-
|
|
152
|
+
Tagging `cli-v*` builds four standalone binaries (Linux x64/arm64 and macOS
|
|
153
|
+
x64/arm64) plus `SHA256SUMS` and attaches them to the GitHub release; npm
|
|
154
|
+
publish runs when the `NPM_TOKEN` secret is configured.
|
|
155
|
+
Package bundles the CLI + generated payload, so npm releases are
|
|
151
156
|
versioned config snapshots without shipping the authoring `SoT/` tree.
|
|
152
157
|
|
|
153
158
|
## Deeper docs
|
package/cli/docs/install.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Installing docks-kit
|
|
2
2
|
|
|
3
|
+
Supported hosts are Linux x64/arm64 and macOS x64/arm64. Unsupported hosts
|
|
4
|
+
fail before the launcher can fall back to Bun source.
|
|
5
|
+
|
|
3
6
|
## 1. Repo checkout (development / current users)
|
|
4
7
|
|
|
5
8
|
```
|
|
@@ -8,10 +11,11 @@ cd ~/projects/public
|
|
|
8
11
|
./docks-kit sync
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
its reported version matches `package.json`, then falls
|
|
13
|
-
(auto-installing Bun via download-then-run and
|
|
14
|
-
when needed). An ignored stale build is reported
|
|
14
|
+
On a supported host, the `./docks-kit` launcher prefers a compiled binary in
|
|
15
|
+
`cli/dist/` only when its reported version matches `package.json`, then falls
|
|
16
|
+
back to Bun-from-source (auto-installing Bun via download-then-run and
|
|
17
|
+
`bun install --frozen-lockfile` when needed). An ignored stale build is reported
|
|
18
|
+
and bypassed.
|
|
15
19
|
|
|
16
20
|
## 2. Global via Bun (effect-solutions-style)
|
|
17
21
|
|
|
@@ -51,7 +55,7 @@ install; `docks-kit --version`, model catalogs, toolchain checks, and real sync
|
|
|
51
55
|
remain functional with the script blocked. CI pins the one-package/one-command
|
|
52
56
|
identity above and will fail if the script-bearing set changes.
|
|
53
57
|
|
|
54
|
-
## 3. curl installer (
|
|
58
|
+
## 3. curl installer (Linux/macOS)
|
|
55
59
|
|
|
56
60
|
```
|
|
57
61
|
curl -fsSL https://raw.githubusercontent.com/DocksDocks/public/main/install.sh -o /tmp/docks-kit-install.sh
|
|
@@ -61,20 +65,7 @@ bash /tmp/docks-kit-install.sh && rm /tmp/docks-kit-install.sh
|
|
|
61
65
|
Download-then-run, never `curl | bash` — stream truncation has bitten this
|
|
62
66
|
kit before. The installer bootstraps Bun when absent, runs
|
|
63
67
|
`bun add -g docks-kit`, and links the binary into `~/.local/bin`.
|
|
64
|
-
|
|
65
|
-
(both CI-verified; see below).
|
|
66
|
-
|
|
67
|
-
## Windows entrypoints
|
|
68
|
-
|
|
69
|
-
Two supported paths (CI-verified on windows-2025, native PowerShell):
|
|
70
|
-
|
|
71
|
-
- **Compiled binary** — `docks-kit-windows-x64.exe` release asset. No Bun,
|
|
72
|
-
no Git Bash: the exe embeds the runtime and generated payload, and
|
|
73
|
-
EngineNative runs in-process from any working directory.
|
|
74
|
-
- **`bun add -g docks-kit`** — bun creates a working shim for the
|
|
75
|
-
`#!/usr/bin/env bun` bin; the package carries the same generated payload.
|
|
76
|
-
|
|
77
|
-
`install.sh` is not a Windows path.
|
|
68
|
+
This installer supports Linux and macOS only.
|
|
78
69
|
|
|
79
70
|
## Keeping the kit up to date
|
|
80
71
|
|
|
@@ -105,6 +96,6 @@ sync/config reads.
|
|
|
105
96
|
- Bun for source/global installs; release binaries embed the runtime
|
|
106
97
|
- Node/npm for npm-global tools (agent-browser, LSP servers)
|
|
107
98
|
- jq is optional doctor/test tooling; sync has no jq runtime dependency
|
|
108
|
-
- curl is required only when a requested
|
|
99
|
+
- curl is required only when a requested Linux/macOS RTK/Bun bootstrap must download
|
|
109
100
|
an installer; an already-present Bun does not require it
|
|
110
101
|
- See `docks-kit toolchain check` for the full picture on this machine
|
package/cli/docs/platforms.md
CHANGED
|
@@ -1,46 +1,18 @@
|
|
|
1
1
|
# Platform support
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
No Bun, no Git Bash; the exe embeds the runtime and generated payload, and
|
|
20
|
-
EngineNative runs in-process from any working directory.
|
|
21
|
-
- **`bun add -g docks-kit`** — bun creates a working Windows shim for the
|
|
22
|
-
`#!/usr/bin/env bun` bin; the package carries the same generated payload.
|
|
23
|
-
- `install.sh` is **Unix-only** and not a Windows path.
|
|
24
|
-
|
|
25
|
-
The managed tools all run natively on Windows: Claude Code (requires Git
|
|
26
|
-
Bash for its own Bash tool — that requirement is Claude Code's, not this
|
|
27
|
-
kit's), Codex CLI (native PowerShell + Windows sandbox), RTK (native
|
|
28
|
-
binaries; since rtk 0.37.2 the PreToolUse hook is a native binary command —
|
|
29
|
-
`rtk hook claude`, no shell or jq. Only the kit's *auto-installer* is
|
|
30
|
-
Unix-only: install rtk natively, then sync).
|
|
31
|
-
|
|
32
|
-
CI coverage (all on the pinned windows-2025 label): EngineNative PowerShell
|
|
33
|
-
smoke with `HOME` unset — `%USERPROFILE%` path resolution, `.cmd` tool
|
|
34
|
-
spawning (npm), toolchain gate branches (`.github/workflows/parity.yml`,
|
|
35
|
-
`native-windows` job); the two entrypoints above (`windows-entrypoints.yml`).
|
|
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.
|
|
41
|
-
|
|
42
|
-
**Status: supported** — real-machine verified 2026-07-09 (Claude Code loads
|
|
43
|
-
the synced `%USERPROFILE%\.claude`; full sync, self-update, plugin passes,
|
|
44
|
-
universal skills, and agent-browser all green on native PowerShell). Needs
|
|
45
|
-
git on PATH for plugin marketplaces; rtk installs natively via its Windows
|
|
46
|
-
release (the kit's auto-install is Unix-only).
|
|
3
|
+
The supported host matrix is exact:
|
|
4
|
+
|
|
5
|
+
| Platform | Architecture | docks-kit binary | Session Relay prebuilt | Sync engine |
|
|
6
|
+
|----------|--------------|------------------|------------------------|-------------|
|
|
7
|
+
| Linux | x64 | `docks-kit-linux-x64` | `x86_64-unknown-linux-musl` | EngineNative (TS) |
|
|
8
|
+
| Linux | arm64 | `docks-kit-linux-arm64` | `aarch64-unknown-linux-musl` | EngineNative (TS) |
|
|
9
|
+
| macOS | x64 | `docks-kit-darwin-x64` | `x86_64-apple-darwin` | EngineNative (TS) |
|
|
10
|
+
| macOS | arm64 | `docks-kit-darwin-arm64` | `aarch64-apple-darwin` | EngineNative (TS) |
|
|
11
|
+
|
|
12
|
+
Windows and every other host are unsupported. The launcher fails before any
|
|
13
|
+
Bun-from-source fallback, and Session Relay installation reports the unsupported
|
|
14
|
+
host without downloading an asset.
|
|
15
|
+
|
|
16
|
+
EngineNative is the only supported engine on supported hosts.
|
|
17
|
+
`DOCKS_KIT_ENGINE=bash` exits with the removed-engine message and points at the
|
|
18
|
+
`bash-engine-final` tag for historical recovery.
|
package/cli/docs/toolchain.md
CHANGED
|
@@ -33,7 +33,7 @@ now kit-approved" act.
|
|
|
33
33
|
- **bun** — policy `present`: bootstrap only (pinned to `verified` via the
|
|
34
34
|
installer's version argument), never auto-upgraded. `bun.ts` owns one
|
|
35
35
|
per-engine-run memo shared by Claude runtime, effect-solutions, and direct
|
|
36
|
-
toolchain ensure
|
|
36
|
+
toolchain ensure on supported Linux/macOS hosts.
|
|
37
37
|
- **effect-solutions**, **agent-browser** — policy `track`: self-upgrade
|
|
38
38
|
toward npm latest, gated by their `verified` pins.
|
|
39
39
|
- **session-relay** — policy `exact`: Claude/Codex sync and
|
|
@@ -44,13 +44,13 @@ now kit-approved" act.
|
|
|
44
44
|
source pin = selected row = downloaded bytes, chmods/smoke-tests only the
|
|
45
45
|
sibling stage, then renames it over `~/.local/bin/session-relay`. Offline,
|
|
46
46
|
unsupported-host, checksum, chmod, version, download, or rename failures
|
|
47
|
-
preserve an existing command byte-for-byte. The four
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
preserve an existing command byte-for-byte. The four pinned production
|
|
48
|
+
digests correspond to the stable `session-relay--v0.12.0` assets and match
|
|
49
|
+
its `SHA256SUMS`.
|
|
50
50
|
|
|
51
51
|
jq and curl are `check` rows, not global prerequisites. jq is not consumed by
|
|
52
|
-
normal sync. curl is checked only at a requested
|
|
53
|
-
download boundary
|
|
52
|
+
normal sync. curl is checked only at a requested Linux/macOS RTK/Bun installer
|
|
53
|
+
download boundary.
|
|
54
54
|
|
|
55
55
|
## Supply-chain stance
|
|
56
56
|
|
package/cli/src/commands/docs.ts
CHANGED
|
@@ -20,7 +20,7 @@ const TOPICS: Record<string, { summary: string; body: string }> = {
|
|
|
20
20
|
"toolchain": { summary: "Verified-version floors, gate policy, --yes semantics", body: toolchain },
|
|
21
21
|
"plugins": { summary: "enabledPlugins tri-state + optional plugin opt-ins", body: plugins },
|
|
22
22
|
"install": { summary: "Install paths: repo checkout, bun add -g, curl installer", body: install },
|
|
23
|
-
"platforms": { summary: "Platform support: Linux/macOS
|
|
23
|
+
"platforms": { summary: "Platform support: Linux/macOS x64 and arm64", body: platforms }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const topic = Args.text({ name: "topic" }).pipe(
|
|
@@ -2,7 +2,7 @@ import { Args, Command, Options } from "@effect/cli"
|
|
|
2
2
|
import { Effect, Option } from "effect"
|
|
3
3
|
import { bail, engine } from "../engine"
|
|
4
4
|
|
|
5
|
-
const MANAGED = ["rtk", "bun", "effect-solutions", "agent-browser"]
|
|
5
|
+
const MANAGED = ["rtk", "bun", "effect-solutions", "agent-browser", "session-relay"]
|
|
6
6
|
|
|
7
7
|
const op = Args.text({ name: "op" }).pipe(
|
|
8
8
|
Args.withDescription("check (default) | ensure <tool>"),
|
|
@@ -96,12 +96,10 @@ hooks/env changed → restart line; skills changed → discovery line) or under
|
|
|
96
96
|
|
|
97
97
|
### Platform seam
|
|
98
98
|
|
|
99
|
-
All
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
default their platform from `os.ts` and keep the parameter injectable for
|
|
104
|
-
tests.
|
|
99
|
+
All host detection routes through `os.ts`, the engine module that reads
|
|
100
|
+
`process.platform`. `exec.ts` contains only POSIX executable and PATH probes.
|
|
101
|
+
`deps.ts` install hints default their platform from `os.ts` and keep the
|
|
102
|
+
parameter injectable for tests.
|
|
105
103
|
|
|
106
104
|
### Verbosity plumbing
|
|
107
105
|
|
|
@@ -122,7 +120,7 @@ active logger binding.
|
|
|
122
120
|
| `../payload.ts` | generated text/byte payload reads and presentation-only source labels |
|
|
123
121
|
| `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
122
|
| `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
|
|
123
|
+
| `claudeRuntime.ts` | sentinel validation, absolute runtime paths, no-cutover settings projection, and POSIX statusline commands |
|
|
126
124
|
| `settings.ts` | pure Claude settings merge/reconcile semantics and permission-array union |
|
|
127
125
|
| `claudeModel.ts` | deployed Claude model modifier and direct `model claude` write path |
|
|
128
126
|
| `codexSync.ts` | Codex pipeline: bubblewrap check, config merge, rules, AGENTS.md, personal marketplace, plugin refresh |
|
|
@@ -132,24 +130,20 @@ active logger binding.
|
|
|
132
130
|
| `modes.ts` | direct `model` and `toolchain` modes |
|
|
133
131
|
| `models.ts` | model catalog listing and validation |
|
|
134
132
|
| `jq.ts` | JSON helpers that preserve jq-style merge/order/stringify behavior where the deployed file contract needs it |
|
|
135
|
-
| `exec.ts` | path helpers, command probes, capture/spawn wrappers,
|
|
133
|
+
| `exec.ts` | slash-stable path helpers, POSIX command probes, capture/spawn wrappers, and change-detecting write/copy helpers |
|
|
136
134
|
| `logger.ts` | Logger shape + stable raw stdout/stderr sink factory; the run-scoped verbosity gate lives in `index.ts` |
|
|
137
|
-
| `deps.ts` | external-tool registry: identity, requirement class, presence probe,
|
|
138
|
-
| `os.ts` | platform capability seam — the single `process.platform` reader (`platformName`, `
|
|
135
|
+
| `deps.ts` | external-tool registry: identity, requirement class, presence probe, supported-host install hints, per-manager missing-tool dedup; callers supply the current run Logger to `warnMissing` |
|
|
136
|
+
| `os.ts` | platform capability seam — the single `process.platform` reader (`platformName`, `isLinux`, shell-rc applicability) |
|
|
139
137
|
| `services.ts` | shared raw-Logger + DependencyManager + Platform factory; wrapped in Effect Layers at `cli/src/services.ts`, with the run-scoped Logger gate applied only by `runEngineNative` |
|
|
140
138
|
|
|
141
|
-
##
|
|
139
|
+
## Platform Support
|
|
142
140
|
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
- Symlink creation
|
|
148
|
-
|
|
149
|
-
- Bubblewrap and shell-rc work are Linux/macOS only.
|
|
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.
|
|
141
|
+
- EngineNative supports Linux and macOS on x64 and arm64.
|
|
142
|
+
- Unsupported hosts fail before launcher fallback, dependency probes, downloads,
|
|
143
|
+
settings writes, or sync work.
|
|
144
|
+
- Runtime hooks use POSIX commands and absolute Bun paths.
|
|
145
|
+
- Symlink creation remains capability-driven: permission or filesystem failures
|
|
146
|
+
fall back to copy without predicting the host.
|
|
153
147
|
|
|
154
148
|
## Tests
|
|
155
149
|
|
|
@@ -158,12 +152,10 @@ active logger binding.
|
|
|
158
152
|
`cli/test/goldens/dryrun.json`.
|
|
159
153
|
- `bun run golden:mutation` compares live native mutation snapshots, argv logs,
|
|
160
154
|
output, and TOML invariants to `cli/test/goldens/mutation.json`.
|
|
161
|
-
- `.github/workflows/parity.yml` is
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- `.github/workflows/windows-entrypoints.yml` verifies the release binary and
|
|
166
|
-
`bun add -g` entrypoints on Windows.
|
|
155
|
+
- `.github/workflows/parity.yml` is the golden-regression workflow: Linux runs
|
|
156
|
+
unit + golden + prove-red plus the exact materialized POSIX runtime commands.
|
|
157
|
+
- `.github/workflows/release-cli.yml` publishes the four Linux/macOS x64/arm64
|
|
158
|
+
binaries, `SHA256SUMS`, and the npm package.
|
|
167
159
|
|
|
168
160
|
## Non-Goals
|
|
169
161
|
|
|
@@ -4,7 +4,6 @@ import { tmpdir } from "node:os"
|
|
|
4
4
|
|
|
5
5
|
import { p } from "./exec"
|
|
6
6
|
import type { Ctx } from "./index"
|
|
7
|
-
import { encodePowerShellCommand, powerShellLiteral } from "./powershell"
|
|
8
7
|
import type { EngineServices } from "./services"
|
|
9
8
|
import { field } from "./toolchain"
|
|
10
9
|
|
|
@@ -17,31 +16,14 @@ function remember(ctx: Ctx, state: BunRuntimeState): BunRuntimeState {
|
|
|
17
16
|
return state
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
function predictedExecutable(ctx: Ctx
|
|
19
|
+
function predictedExecutable(ctx: Ctx): string {
|
|
21
20
|
const root = process.env["BUN_INSTALL"] !== undefined && process.env["BUN_INSTALL"] !== ""
|
|
22
21
|
? process.env["BUN_INSTALL"]!
|
|
23
22
|
: p(ctx.home, ".bun")
|
|
24
|
-
return p(root, "bin",
|
|
23
|
+
return p(root, "bin", "bun")
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
function
|
|
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
|
-
|
|
26
|
+
function installBun(pin: string, installer: string): void {
|
|
45
27
|
const download = spawnSync("curl", ["-fsSL", "https://bun.sh/install", "-o", installer], { stdio: "ignore" })
|
|
46
28
|
if (download.error === undefined && download.status === 0) {
|
|
47
29
|
spawnSync("bash", [installer, `bun-v${pin}`], { stdio: "ignore" })
|
|
@@ -59,19 +41,19 @@ export function bunBootstrap(ctx: Ctx, services: EngineServices): BunRuntimeStat
|
|
|
59
41
|
services.logger.warn("Bun bootstrap aborted — SoT/toolchain.json has no verified Bun pin")
|
|
60
42
|
return remember(ctx, { kind: "deferred", reason: "install-failed" })
|
|
61
43
|
}
|
|
62
|
-
if (
|
|
44
|
+
if (services.deps.probe("curl").state === "missing") {
|
|
63
45
|
services.deps.warnMissing("curl", services.logger, "cannot bootstrap Bun; install Bun manually, then re-run sync")
|
|
64
46
|
return remember(ctx, { kind: "deferred", reason: "missing-curl" })
|
|
65
47
|
}
|
|
66
48
|
if (ctx.dryRun) {
|
|
67
|
-
const executable = predictedExecutable(ctx
|
|
49
|
+
const executable = predictedExecutable(ctx)
|
|
68
50
|
services.logger.echo(`[dry-run] install Bun ${pin} (kit-verified) -> ${executable}`)
|
|
69
51
|
return remember(ctx, { kind: "ready", executable })
|
|
70
52
|
}
|
|
71
53
|
services.logger.warn(`Bun not found — installing Bun ${pin} (kit-verified)...`)
|
|
72
|
-
const installer = p(tmpdir(), `bun-install-${process.pid}
|
|
54
|
+
const installer = p(tmpdir(), `bun-install-${process.pid}.sh`)
|
|
73
55
|
try {
|
|
74
|
-
installBun(
|
|
56
|
+
installBun(pin, installer)
|
|
75
57
|
} finally {
|
|
76
58
|
rmSync(installer, { force: true })
|
|
77
59
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { p } from "./exec"
|
|
2
2
|
import { isObject, parseJson, type Json } from "./jq"
|
|
3
|
-
import { encodePowerShellCommand, powerShellLiteral } from "./powershell"
|
|
4
|
-
import type { Platform } from "./services"
|
|
5
3
|
|
|
6
4
|
const BUN_SENTINEL = "__DOCKS_KIT_BUN__"
|
|
7
5
|
const SESSION_START_SENTINEL = "__DOCKS_KIT_SESSION_START__"
|
|
@@ -89,22 +87,15 @@ function posixLiteral(value: string): string {
|
|
|
89
87
|
return `'${value.replaceAll("'", `'"'"'`)}'`
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
export function statusLineCommand(runtime: ClaudeRuntimePaths
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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)}`
|
|
90
|
+
export function statusLineCommand(runtime: ClaudeRuntimePaths): string {
|
|
91
|
+
const bun = posixLiteral(runtime.bun)
|
|
92
|
+
const script = posixLiteral(runtime.statusline)
|
|
93
|
+
return `test -x ${bun} && test -f ${script} && exec ${bun} ${script} || true`
|
|
102
94
|
}
|
|
103
95
|
|
|
104
96
|
export function materializeClaudeSettings(
|
|
105
97
|
template: Json,
|
|
106
|
-
runtime: ClaudeRuntimePaths | undefined
|
|
107
|
-
platform: Platform
|
|
98
|
+
runtime: ClaudeRuntimePaths | undefined
|
|
108
99
|
): Json {
|
|
109
100
|
validateTemplate(template)
|
|
110
101
|
const result = cloneJson(template)
|
|
@@ -122,7 +113,7 @@ export function materializeClaudeSettings(
|
|
|
122
113
|
notification["command"] = runtime.bun
|
|
123
114
|
notification["args"] = [runtime.notify]
|
|
124
115
|
if (!isObject(result) || !isObject(result["statusLine"])) throw new Error("Claude statusLine object is missing")
|
|
125
|
-
result["statusLine"]["command"] = statusLineCommand(runtime
|
|
116
|
+
result["statusLine"]["command"] = statusLineCommand(runtime)
|
|
126
117
|
}
|
|
127
118
|
|
|
128
119
|
for (const sentinel of [BUN_SENTINEL, SESSION_START_SENTINEL, NOTIFY_SENTINEL, STATUSLINE_SENTINEL]) {
|
|
@@ -62,8 +62,7 @@ export function claudeSync(ctx: Ctx): ClaudeRuntimeState {
|
|
|
62
62
|
}
|
|
63
63
|
const materialized = materializeClaudeSettings(
|
|
64
64
|
template,
|
|
65
|
-
runtime.kind === "ready" ? runtime.paths : undefined
|
|
66
|
-
ctx.services.platform
|
|
65
|
+
runtime.kind === "ready" ? runtime.paths : undefined
|
|
67
66
|
)
|
|
68
67
|
const prepared = ctx.dryRun ? undefined : prepareClaudeSettings(ctx, claudeDir, materialized)
|
|
69
68
|
|
|
@@ -150,12 +149,7 @@ function syncRtk(ctx: Ctx, claudeDir: string): void {
|
|
|
150
149
|
return
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
if (ctx
|
|
154
|
-
if (ctx.services.deps.probe("rtk").state === "missing") {
|
|
155
|
-
warn("rtk not installed — the kit's auto-install is Unix-only. Install natively (winget, or the rtk-*-windows-msvc.zip release), then re-run sync")
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
} else if (ensureRtk(ctx, "cannot download RTK installer; continuing sync without RTK", 0) !== 0) {
|
|
152
|
+
if (ensureRtk(ctx, "cannot download RTK installer; continuing sync without RTK", 0) !== 0) {
|
|
159
153
|
warn("RTK bootstrap failed — continuing sync without it")
|
|
160
154
|
}
|
|
161
155
|
|
|
@@ -412,32 +406,7 @@ function syncClaudeJson(ctx: Ctx): void {
|
|
|
412
406
|
// -------------------------------------------------------- connector env ----
|
|
413
407
|
|
|
414
408
|
function syncConnectorEnv(ctx: Ctx): void {
|
|
415
|
-
const { change, echo, verbose
|
|
416
|
-
// win32: Claude Code launches from PowerShell/GUI, so the flag must be a
|
|
417
|
-
// real user env var (setx), not a Git-Bash-only shell-rc export. Never
|
|
418
|
-
// clobbers an existing value (set =true yourself to keep connectors).
|
|
419
|
-
if (ctx.services.platform.isWindows()) {
|
|
420
|
-
const existing = spawnSync("reg", ["query", "HKCU\\Environment", "/v", "ENABLE_CLAUDEAI_MCP_SERVERS"], {
|
|
421
|
-
stdio: "ignore"
|
|
422
|
-
})
|
|
423
|
-
if (existing.error === undefined && existing.status === 0) {
|
|
424
|
-
if (ctx.dryRun) echo("[dry-run] ENABLE_CLAUDEAI_MCP_SERVERS already in user environment — would skip")
|
|
425
|
-
else verbose("claude.ai connectors: ENABLE_CLAUDEAI_MCP_SERVERS already set in user environment (left as-is)")
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
if (ctx.dryRun) {
|
|
429
|
-
echo("[dry-run] setx ENABLE_CLAUDEAI_MCP_SERVERS false (user environment)")
|
|
430
|
-
return
|
|
431
|
-
}
|
|
432
|
-
const res = spawnSync("setx", ["ENABLE_CLAUDEAI_MCP_SERVERS", "false"], { stdio: "ignore" })
|
|
433
|
-
if (res.error === undefined && res.status === 0) {
|
|
434
|
-
change("claude.ai connectors disabled via setx (open a new terminal to apply)")
|
|
435
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
436
|
-
} else {
|
|
437
|
-
warn("setx ENABLE_CLAUDEAI_MCP_SERVERS false failed — set it manually in System Properties > Environment Variables")
|
|
438
|
-
}
|
|
439
|
-
return
|
|
440
|
-
}
|
|
409
|
+
const { change, echo, verbose } = ctx.services.logger
|
|
441
410
|
|
|
442
411
|
const line = "export ENABLE_CLAUDEAI_MCP_SERVERS=false"
|
|
443
412
|
const marker = "# docks-kit: disable claude.ai cloud MCP connectors (set =true to keep them)"
|
|
@@ -86,7 +86,7 @@ function bwrapSupportedOs(ctx: Ctx): boolean {
|
|
|
86
86
|
const { warn } = ctx.services.logger
|
|
87
87
|
const pn = ctx.services.platform.name()
|
|
88
88
|
if (pn === "linux") return true
|
|
89
|
-
if (pn === "darwin"
|
|
89
|
+
if (pn === "darwin") return false
|
|
90
90
|
warn("Unknown OS — skipping bubblewrap check; Codex sandbox may not work")
|
|
91
91
|
return false
|
|
92
92
|
}
|
|
@@ -112,34 +112,29 @@ const home = (): string => {
|
|
|
112
112
|
return envHome !== undefined && envHome !== "" ? envHome : homedir()
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const absoluteWindowsExe = (path: string): boolean =>
|
|
116
|
-
/\.exe$/i.test(path) && (/^[A-Za-z]:[\\/]/.test(path) || /^\\\\/.test(path))
|
|
117
115
|
|
|
118
116
|
// The resolved path gets persisted into global direct-exec hooks, so a
|
|
119
117
|
// relative `which` hit (relative PATH entry, relative BUN_INSTALL) would
|
|
120
118
|
// break outside the sync working directory.
|
|
121
|
-
const absoluteExecutable = (path: string, platform: NodeJS.Platform): boolean =>
|
|
122
|
-
platform === "win32" ? absoluteWindowsExe(path) : isAbsolute(path)
|
|
123
119
|
|
|
124
|
-
const findBun = (exec: ProbeExecutor
|
|
120
|
+
const findBun = (exec: ProbeExecutor): { command: string; path: string } | undefined => {
|
|
125
121
|
const onPath = exec.which("bun")
|
|
126
|
-
if (onPath !== "" &&
|
|
127
|
-
return { command:
|
|
122
|
+
if (onPath !== "" && isAbsolute(onPath)) {
|
|
123
|
+
return { command: "bun", path: onPath }
|
|
128
124
|
}
|
|
129
125
|
const root =
|
|
130
126
|
process.env["BUN_INSTALL"] !== undefined && process.env["BUN_INSTALL"] !== ""
|
|
131
127
|
? process.env["BUN_INSTALL"]!
|
|
132
128
|
: p(home(), ".bun")
|
|
133
|
-
const
|
|
134
|
-
for (const candidate of [p(root, "bin", name), p(home(), ".bun", "bin", name)]) {
|
|
129
|
+
for (const candidate of [p(root, "bin", "bun"), p(home(), ".bun", "bin", "bun")]) {
|
|
135
130
|
const found = exec.which(candidate)
|
|
136
|
-
if (found !== "" &&
|
|
131
|
+
if (found !== "" && isAbsolute(found)) return { command: found, path: found }
|
|
137
132
|
}
|
|
138
133
|
return undefined
|
|
139
134
|
}
|
|
140
135
|
|
|
141
|
-
const resolveBun = (exec: ProbeExecutor
|
|
142
|
-
const bun = findBun(exec
|
|
136
|
+
const resolveBun = (exec: ProbeExecutor): ProbeResult => {
|
|
137
|
+
const bun = findBun(exec)
|
|
143
138
|
return bun === undefined
|
|
144
139
|
? { state: "missing" }
|
|
145
140
|
: { state: "present", path: bun.path }
|
|
@@ -161,28 +156,23 @@ const versionEffectSolutions = (exec: ProbeExecutor): string => {
|
|
|
161
156
|
return match?.[1] ?? ""
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
const locateEffectSolutions = (exec: ProbeExecutor
|
|
165
|
-
const strictBun = findBun(exec
|
|
159
|
+
const locateEffectSolutions = (exec: ProbeExecutor): DependencyLocation => {
|
|
160
|
+
const strictBun = findBun(exec)
|
|
166
161
|
const pathBun = exec.which("bun")
|
|
167
162
|
const bun = strictBun ?? (pathBun !== "" ? { command: "bun", path: pathBun } : undefined)
|
|
168
163
|
if (bun === undefined) return { path: "", binDir: "" }
|
|
169
164
|
const globalBin = exec.capture(bun.command, ["pm", "-g", "bin"])
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: ["effect-solutions"]
|
|
174
|
-
const path = names.map((name) => p(globalBin, name)).find((candidate) => globalBin !== "" && exec.which(candidate) !== "")
|
|
175
|
-
return { path: path ?? "", binDir: globalBin }
|
|
165
|
+
const path = globalBin !== "" ? p(globalBin, "effect-solutions") : ""
|
|
166
|
+
const resolved = path !== "" && exec.which(path) !== "" ? path : ""
|
|
167
|
+
return { path: resolved, binDir: globalBin }
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
const resolveChrome = (exec: ProbeExecutor, platform: NodeJS.Platform): ProbeResult => {
|
|
179
171
|
const root = p(home(), ".agent-browser", "browsers")
|
|
180
172
|
const relative =
|
|
181
|
-
platform === "
|
|
182
|
-
? "
|
|
183
|
-
:
|
|
184
|
-
? "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
185
|
-
: "chrome"
|
|
173
|
+
platform === "darwin"
|
|
174
|
+
? "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
175
|
+
: "chrome"
|
|
186
176
|
if (existsSync(root)) {
|
|
187
177
|
for (const directory of readdirSync(root).filter((name) => name.startsWith("chrome-")).sort().reverse()) {
|
|
188
178
|
const path = exec.which(p(root, directory, relative))
|
|
@@ -215,23 +205,19 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
215
205
|
"git",
|
|
216
206
|
"optional",
|
|
217
207
|
(pf = rawPlatform()) =>
|
|
218
|
-
pf === "
|
|
219
|
-
? "
|
|
220
|
-
:
|
|
221
|
-
? "brew install git"
|
|
222
|
-
: "sudo apt install -y git (or your distro's package manager)",
|
|
208
|
+
pf === "darwin"
|
|
209
|
+
? "brew install git"
|
|
210
|
+
: "sudo apt install -y git (or your distro's package manager)",
|
|
223
211
|
{ version: versionProbe("git") }
|
|
224
212
|
),
|
|
225
213
|
jq: spec("jq", "optional", (pf = rawPlatform()) =>
|
|
226
|
-
pf === "
|
|
227
|
-
? "
|
|
228
|
-
:
|
|
229
|
-
? "brew install jq"
|
|
230
|
-
: "sudo apt install -y jq",
|
|
214
|
+
pf === "darwin"
|
|
215
|
+
? "brew install jq"
|
|
216
|
+
: "sudo apt install -y jq",
|
|
231
217
|
{ version: versionProbe("jq") }
|
|
232
218
|
),
|
|
233
219
|
curl: spec("curl", "optional", (pf = rawPlatform()) =>
|
|
234
|
-
pf === "
|
|
220
|
+
pf === "darwin" ? "brew install curl" : "sudo apt install -y curl",
|
|
235
221
|
{ version: versionProbe("curl") }
|
|
236
222
|
),
|
|
237
223
|
node: spec("node", "optional", () => "install Node.js via https://nodejs.org (or your package manager)", {
|
|
@@ -242,22 +228,16 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
242
228
|
claude: spec(
|
|
243
229
|
"claude",
|
|
244
230
|
"optional",
|
|
245
|
-
(
|
|
246
|
-
pf === "win32"
|
|
247
|
-
? "winget install Anthropic.ClaudeCode"
|
|
248
|
-
: "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
|
|
231
|
+
() => "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
|
|
249
232
|
{ version: versionProbe("claude") }
|
|
250
233
|
),
|
|
251
234
|
codex: spec(
|
|
252
235
|
"codex",
|
|
253
236
|
"optional",
|
|
254
|
-
(
|
|
255
|
-
pf === "win32"
|
|
256
|
-
? `powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"`
|
|
257
|
-
: 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"',
|
|
237
|
+
() => 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"',
|
|
258
238
|
{ version: versionProbe("codex") }
|
|
259
239
|
),
|
|
260
|
-
rtk: spec("rtk", "optional", () => "see https://github.com/rtk-ai/rtk (kit auto-install is
|
|
240
|
+
rtk: spec("rtk", "optional", () => "see https://github.com/rtk-ai/rtk (kit auto-install is Linux/macOS-only)", {
|
|
261
241
|
version: versionProbe("rtk"),
|
|
262
242
|
latest: latestRtk
|
|
263
243
|
}),
|
|
@@ -269,12 +249,11 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
269
249
|
bun: spec(
|
|
270
250
|
"bun",
|
|
271
251
|
"optional",
|
|
272
|
-
(
|
|
273
|
-
pf === "win32" ? `powershell -c "irm bun.sh/install.ps1 | iex"` : "curl -fsSL https://bun.sh/install | bash",
|
|
252
|
+
() => "curl -fsSL https://bun.sh/install | bash",
|
|
274
253
|
{
|
|
275
254
|
resolve: resolveBun,
|
|
276
255
|
version: (exec) => exec.capture(versionBunCommand(exec), ["--version"]),
|
|
277
|
-
locate: (exec
|
|
256
|
+
locate: (exec) => ({ path: findBun(exec)?.path ?? "", binDir: "" })
|
|
278
257
|
}
|
|
279
258
|
),
|
|
280
259
|
bwrap: spec("bwrap", "optional", () => "sudo apt install -y bubblewrap (or dnf/pacman/zypper equivalent)"),
|
|
@@ -298,7 +277,7 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
298
277
|
"ffplay",
|
|
299
278
|
"optional",
|
|
300
279
|
(pf = rawPlatform()) =>
|
|
301
|
-
pf === "
|
|
280
|
+
pf === "darwin" ? "brew install ffmpeg" : "sudo apt install -y ffmpeg",
|
|
302
281
|
{ versionArgs: ["-version"], resolve: pathProbe("ffplay") }
|
|
303
282
|
),
|
|
304
283
|
intelephense: spec("intelephense", "optional", () => "npm install -g intelephense", {
|
|
@@ -7,11 +7,7 @@ import { spawnSync } from "node:child_process"
|
|
|
7
7
|
import { accessSync, chmodSync, constants, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
8
8
|
import { delimiter, isAbsolute, join } from "node:path"
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Engine paths are built with "/" because they appear verbatim in output
|
|
12
|
-
* (dry-run lines, warns), where node:path.join would print "\" on Windows
|
|
13
|
-
* and break the golden contract. fs accepts "/" on every platform.
|
|
14
|
-
*/
|
|
10
|
+
/** Keep engine paths slash-separated so rendered output is host-stable. */
|
|
15
11
|
export function p(...parts: Array<string>): string {
|
|
16
12
|
return parts.join("/")
|
|
17
13
|
}
|
|
@@ -22,18 +18,15 @@ export function capture(cmd: string, args: ReadonlyArray<string>): string {
|
|
|
22
18
|
return (res.stdout ?? "").replace(/[\r\n]+$/, "")
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
/** `command -v` — resolve
|
|
21
|
+
/** `command -v` — resolve an executable name on PATH. */
|
|
26
22
|
export function which(name: string): string {
|
|
27
|
-
if (isAbsolute(name) || name.includes("/")
|
|
23
|
+
if (isAbsolute(name) || name.includes("/")) {
|
|
28
24
|
return isExecutable(name) ? name : ""
|
|
29
25
|
}
|
|
30
|
-
const exts = process.platform === "win32" ? (process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").concat("") : [""]
|
|
31
26
|
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
32
27
|
if (dir === "") continue
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (isExecutable(cand)) return cand
|
|
36
|
-
}
|
|
28
|
+
const candidate = join(dir, name)
|
|
29
|
+
if (isExecutable(candidate)) return candidate
|
|
37
30
|
}
|
|
38
31
|
return ""
|
|
39
32
|
}
|
|
@@ -45,7 +38,7 @@ export function commandExists(name: string): boolean {
|
|
|
45
38
|
export function isExecutable(p: string): boolean {
|
|
46
39
|
try {
|
|
47
40
|
if (!statSync(p).isFile()) return false
|
|
48
|
-
|
|
41
|
+
accessSync(p, constants.X_OK)
|
|
49
42
|
return true
|
|
50
43
|
} catch {
|
|
51
44
|
return false
|
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Platform capability seam (Output Policy in DESIGN.md)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* stays try-then-fallback at the call site (capability-driven, not predicted).
|
|
2
|
+
* Platform capability seam (Output Policy in DESIGN.md). Per-tool package
|
|
3
|
+
* identifiers stay in deps.ts; symlink handling stays try-then-fallback at
|
|
4
|
+
* the call site (capability-driven, not predicted).
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
export type PlatformName = "linux" | "darwin" | "
|
|
7
|
+
export type PlatformName = "linux" | "darwin" | "unknown"
|
|
9
8
|
|
|
10
9
|
export function rawPlatform(): NodeJS.Platform {
|
|
11
10
|
return process.platform
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export function platformName(pf: NodeJS.Platform = rawPlatform()): PlatformName {
|
|
15
|
-
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" :
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function isWindows(): boolean {
|
|
19
|
-
return rawPlatform() === "win32"
|
|
14
|
+
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" : "unknown"
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
export function isLinux(): boolean {
|
|
23
18
|
return rawPlatform() === "linux"
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
/** Shell-rc exports
|
|
21
|
+
/** Shell-rc exports apply only on supported hosts. */
|
|
27
22
|
export function shellRcApplicable(): boolean {
|
|
28
|
-
return
|
|
23
|
+
return platformName() !== "unknown"
|
|
29
24
|
}
|
|
@@ -36,7 +36,6 @@ export interface DependencyManager {
|
|
|
36
36
|
export interface Platform {
|
|
37
37
|
readonly raw: () => NodeJS.Platform
|
|
38
38
|
readonly name: () => PlatformName
|
|
39
|
-
readonly isWindows: () => boolean
|
|
40
39
|
readonly isLinux: () => boolean
|
|
41
40
|
readonly shellRcApplicable: () => boolean
|
|
42
41
|
}
|
|
@@ -51,13 +50,12 @@ export interface EngineServiceOptions {
|
|
|
51
50
|
readonly sinks?: LoggerSinks
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
/** Platform view over an injectable platform id
|
|
53
|
+
/** Platform view over an injectable platform id. */
|
|
55
54
|
export const makePlatform = (pf: NodeJS.Platform = rawPlatform()): Platform => ({
|
|
56
55
|
raw: () => pf,
|
|
57
56
|
name: () => platformName(pf),
|
|
58
|
-
isWindows: () => pf === "win32",
|
|
59
57
|
isLinux: () => pf === "linux",
|
|
60
|
-
shellRcApplicable: () => pf
|
|
58
|
+
shellRcApplicable: () => pf === "linux" || pf === "darwin"
|
|
61
59
|
})
|
|
62
60
|
|
|
63
61
|
/** DependencyManager whose hints default to the INJECTED platform, not the host. */
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* and the snapshot write.
|
|
6
6
|
*/
|
|
7
7
|
import { spawnSync } from "node:child_process"
|
|
8
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync,
|
|
8
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync } from "node:fs"
|
|
9
9
|
import { p, writeFileIfChanged } from "./exec"
|
|
10
10
|
import { bunBootstrap } from "./bun"
|
|
11
11
|
import type { Ctx } from "./index"
|
|
12
12
|
import { compareCodepoints } from "./jq"
|
|
13
|
-
import type { EngineServices
|
|
13
|
+
import type { EngineServices } from "./services"
|
|
14
14
|
import { ensure, field } from "./toolchain"
|
|
15
15
|
import { payloadText } from "../payload"
|
|
16
16
|
|
|
@@ -139,9 +139,6 @@ function healClaudeSymlink(ctx: Ctx, skillsDir: string, base: string): boolean {
|
|
|
139
139
|
if (linkStat?.isSymbolicLink() === true) {
|
|
140
140
|
const current = safeReadlink(claudeLink)
|
|
141
141
|
if (current === relTarget) return false
|
|
142
|
-
// win32: `npx skills add` creates absolute symlinks/junctions — any link
|
|
143
|
-
// that RESOLVES to the canonical dir is healthy, not stale.
|
|
144
|
-
if (ctx.services.platform.isWindows() && realpathEquals(claudeLink, canonical)) return false
|
|
145
142
|
if (ctx.dryRun) {
|
|
146
143
|
echo(`[dry-run] would replace stale Claude symlink: ~/.claude/skills/${base} -> ${current} (correct: ${relTarget})`)
|
|
147
144
|
return true
|
|
@@ -178,36 +175,22 @@ function safeReadlink(path: string): string {
|
|
|
178
175
|
}
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
function realpathEquals(a: string, b: string): boolean {
|
|
182
|
-
try {
|
|
183
|
-
return realpathSync(a) === realpathSync(b)
|
|
184
|
-
} catch {
|
|
185
|
-
return false
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
178
|
|
|
189
|
-
/**
|
|
190
|
-
* Remove a symlink/junction without crashing: Bun's rmSync throws EFAULT on
|
|
191
|
-
* win32 directory symlinks. unlink handles file symlinks, rmdir handles
|
|
192
|
-
* dir symlinks/junctions, rmSync is the POSIX catch-all.
|
|
193
|
-
*/
|
|
179
|
+
/** Remove a symlink without touching a real directory. */
|
|
194
180
|
function removeLink(path: string): boolean {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// try the next removal shape
|
|
201
|
-
}
|
|
181
|
+
try {
|
|
182
|
+
rmSync(path, { force: true })
|
|
183
|
+
return true
|
|
184
|
+
} catch {
|
|
185
|
+
return lstat(path) === undefined
|
|
202
186
|
}
|
|
203
|
-
return lstat(path) === undefined
|
|
204
187
|
}
|
|
205
188
|
|
|
206
|
-
/** skills::_link_or_copy — real symlink preferred, copy fallback
|
|
207
|
-
export function linkOrCopy(target: string, link: string
|
|
189
|
+
/** skills::_link_or_copy — real symlink preferred, copy fallback. */
|
|
190
|
+
export function linkOrCopy(target: string, link: string): boolean {
|
|
208
191
|
removeLink(link)
|
|
209
192
|
try {
|
|
210
|
-
symlinkSync(target, link
|
|
193
|
+
symlinkSync(target, link)
|
|
211
194
|
} catch {
|
|
212
195
|
// fall through to the copy fallback below
|
|
213
196
|
}
|
|
@@ -223,11 +206,11 @@ export function linkOrCopy(target: string, link: string, platform: Platform): bo
|
|
|
223
206
|
}
|
|
224
207
|
|
|
225
208
|
function linkOrCopyWithWarnings(target: string, link: string, services: EngineServices): boolean {
|
|
226
|
-
const linked = linkOrCopy(target, link
|
|
209
|
+
const linked = linkOrCopy(target, link)
|
|
227
210
|
if (!linked) {
|
|
228
211
|
services.logger.warn(`could not create ${link} (symlink and copy both failed)`)
|
|
229
212
|
} else if (lstat(link)?.isSymbolicLink() !== true) {
|
|
230
|
-
services.logger.warn(`symlinks unsupported here — ${link} is a copy
|
|
213
|
+
services.logger.warn(`symlinks unsupported here — ${link} is a copy refreshed on sync`)
|
|
231
214
|
}
|
|
232
215
|
return linked
|
|
233
216
|
}
|
|
@@ -298,14 +281,6 @@ export function effectSolutionsInstall(
|
|
|
298
281
|
|
|
299
282
|
const location = services.deps.location("effect-solutions")
|
|
300
283
|
const gbin = location.binDir
|
|
301
|
-
// win32: bun writes an .exe shim (not the bare Unix name), and the
|
|
302
|
-
// ~/.local/bin link step below is Unix-only plumbing (non-interactive
|
|
303
|
-
// agent PATH) — bun's global bin is already the Windows PATH entry.
|
|
304
|
-
if (services.platform.isWindows()) {
|
|
305
|
-
if (location.path !== "") change(`effect-solutions CLI ready (${gbin})`)
|
|
306
|
-
else warn(`effect-solutions installed but no shim found under '${gbin !== "" ? gbin : "<unknown>"}' — check bun pm -g bin`)
|
|
307
|
-
return 0
|
|
308
|
-
}
|
|
309
284
|
if (location.path !== "") {
|
|
310
285
|
mkdirSync(p(ctx.home, ".local", "bin"), { recursive: true })
|
|
311
286
|
linkOrCopyWithWarnings(bun, p(ctx.home, ".local", "bin", "bun"), services)
|
package/cli/src/engine.ts
CHANGED
|
@@ -5,28 +5,32 @@ import { makeEngineServices } from "./engine-native/services"
|
|
|
5
5
|
import { kitHome } from "./kitHome"
|
|
6
6
|
import { DependencyManagerService, LoggerService, PlatformService } from "./services"
|
|
7
7
|
|
|
8
|
-
// Same factory as the Effect rim's live layers — this path runs outside the
|
|
9
|
-
// runtime (child-spawn capture), so it takes the services directly.
|
|
10
|
-
const services = makeEngineServices()
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* The single seam between the typed CLI and EngineNative. Engine execution
|
|
14
10
|
* stays in-process after @effect/cli has parsed pickers and flag spellings.
|
|
15
11
|
*/
|
|
16
12
|
const bashRemovedMessage = "bash engine removed — recover at tag bash-engine-final"
|
|
17
13
|
const bashEngineRequested = (): boolean => process.env["DOCKS_KIT_ENGINE"] === "bash"
|
|
14
|
+
const requireSupportedHost = () => {
|
|
15
|
+
const platform = process.platform
|
|
16
|
+
const arch = process.arch
|
|
17
|
+
return (platform === "linux" || platform === "darwin") && (arch === "x64" || arch === "arm64")
|
|
18
|
+
? Effect.void
|
|
19
|
+
: bail(
|
|
20
|
+
`unsupported host ${platform}/${arch}; docks-kit supports only Linux and macOS on x64 or arm64`,
|
|
21
|
+
2
|
|
22
|
+
)
|
|
23
|
+
}
|
|
18
24
|
|
|
19
|
-
// bun build --compile runs the embedded entry from a virtual path
|
|
20
|
-
// ("/$bunfs/root/…"
|
|
21
|
-
//
|
|
22
|
-
// The Windows check is anchored to the drive-rooted "~BUN" segment so a
|
|
23
|
-
// real checkout under a ~BUN-named directory can't false-positive.
|
|
25
|
+
// bun build --compile runs the embedded entry from a virtual POSIX path
|
|
26
|
+
// ("/$bunfs/root/…"). There process.execPath IS the CLI, so a re-spawn must
|
|
27
|
+
// not pass main.ts.
|
|
24
28
|
export const compiled =
|
|
25
|
-
process.argv[1] !== undefined &&
|
|
26
|
-
(process.argv[1].startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(process.argv[1]))
|
|
29
|
+
process.argv[1] !== undefined && process.argv[1].startsWith("/$bunfs/")
|
|
27
30
|
|
|
28
31
|
export const engine = (args: ReadonlyArray<string>) =>
|
|
29
32
|
Effect.gen(function* () {
|
|
33
|
+
yield* requireSupportedHost()
|
|
30
34
|
if (bashEngineRequested()) {
|
|
31
35
|
yield* bail(bashRemovedMessage, 2)
|
|
32
36
|
}
|
|
@@ -41,9 +45,12 @@ export const engine = (args: ReadonlyArray<string>) =>
|
|
|
41
45
|
|
|
42
46
|
/** Run the engine capturing stdout (engine logs/warns go to stderr and pass through). */
|
|
43
47
|
export const engineCapture = (args: ReadonlyArray<string>) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
yield* requireSupportedHost()
|
|
50
|
+
if (bashEngineRequested()) {
|
|
51
|
+
return yield* bail(bashRemovedMessage, 2)
|
|
52
|
+
}
|
|
53
|
+
return yield* Effect.sync(() => {
|
|
47
54
|
// Child process (raw channel): runEngineNative writes straight to
|
|
48
55
|
// process.stdout, so in-process capture isn't possible.
|
|
49
56
|
const res = spawnSync(process.execPath, compiled ? [...args] : [`${kitHome()}/cli/src/main.ts`, ...args], {
|
|
@@ -52,10 +59,11 @@ export const engineCapture = (args: ReadonlyArray<string>) =>
|
|
|
52
59
|
stdio: ["ignore", "pipe", "inherit"]
|
|
53
60
|
})
|
|
54
61
|
if (res.error !== undefined || res.status !== 0) {
|
|
55
|
-
|
|
62
|
+
makeEngineServices().logger.warn(`engine capture failed (${args.join(" ")} exited ${res.status ?? "spawn-error"})`)
|
|
56
63
|
}
|
|
57
64
|
return res.stdout ?? ""
|
|
58
65
|
})
|
|
66
|
+
})
|
|
59
67
|
|
|
60
68
|
/** Print a message to stderr and exit — for CLI-side validation failures. */
|
|
61
69
|
export const bail = (message: string, code = 2) =>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by cli/scripts/generate-sot-payload.ts. DO NOT EDIT.
|
|
2
2
|
// Edit SoT/, notification.mp3, or package.json, then run: bun cli/scripts/generate-sot-payload.ts
|
|
3
3
|
|
|
4
|
-
export const GENERATED_PACKAGE_VERSION = "0.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.10.0"
|
|
5
5
|
|
|
6
6
|
export const GENERATED_PAYLOAD_TEXT = {
|
|
7
7
|
"SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
|
package/docks-kit
CHANGED
|
@@ -11,17 +11,18 @@ REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
11
11
|
BUN_PIN="1.3.14"
|
|
12
12
|
# END GENERATED BUN PIN
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Linux-
|
|
17
|
-
|
|
18
|
-
Darwin-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
HOST="$(uname -s)-$(uname -m)"
|
|
15
|
+
case "$HOST" in
|
|
16
|
+
Linux-x86_64) KIT_BIN="docks-kit-linux-x64" ;;
|
|
17
|
+
Linux-aarch64) KIT_BIN="docks-kit-linux-arm64" ;;
|
|
18
|
+
Darwin-x86_64) KIT_BIN="docks-kit-darwin-x64" ;;
|
|
19
|
+
Darwin-arm64) KIT_BIN="docks-kit-darwin-arm64" ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "[docks-kit] unsupported host $HOST; docks-kit supports only Linux and macOS on x64 or arm64." >&2
|
|
22
|
+
exit 1
|
|
23
|
+
;;
|
|
23
24
|
esac
|
|
24
|
-
if [[ -
|
|
25
|
+
if [[ -x "$REPO_DIR/cli/dist/$KIT_BIN" ]]; then
|
|
25
26
|
CHECKOUT_VERSION=""
|
|
26
27
|
while IFS= read -r line; do
|
|
27
28
|
if [[ "$line" =~ ^[[:space:]]*\"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docks-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Portable AI coding agent config kit — SoT sync engine + typed CLI for Claude Code, Codex, and universal agent skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"os": [
|
|
8
|
+
"linux",
|
|
9
|
+
"darwin"
|
|
10
|
+
],
|
|
7
11
|
"repository": {
|
|
8
12
|
"type": "git",
|
|
9
13
|
"url": "git+https://github.com/DocksDocks/public.git"
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export function powerShellLiteral(value: string): string {
|
|
2
|
-
return `'${value.replaceAll("'", "''")}'`
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function encodePowerShellCommand(script: string): string {
|
|
6
|
-
return Buffer.from(script, "utf16le").toString("base64")
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function decodePowerShellCommand(encoded: string): string {
|
|
10
|
-
return Buffer.from(encoded, "base64").toString("utf16le")
|
|
11
|
-
}
|