docks-kit 0.4.0 → 0.6.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 +6 -2
- package/cli/docs/flags.md +6 -1
- package/cli/docs/install.md +24 -0
- package/cli/docs/models.md +8 -8
- package/cli/docs/modifiers.md +20 -3
- package/cli/docs/sync-layers.md +12 -9
- package/cli/src/commands/status.ts +9 -2
- package/cli/src/commands/sync.ts +34 -3
- package/cli/src/efforts.ts +91 -0
- package/cli/src/engine-native/claudeSettingsModifiers.ts +101 -0
- package/cli/src/engine-native/claudeSync.ts +9 -2
- package/cli/src/engine-native/codexSync.ts +7 -1
- package/cli/src/engine-native/codexToml.ts +41 -8
- package/cli/src/engine-native/index.ts +18 -2
- package/cli/src/engine-native/modes.ts +1 -1
- package/cli/src/engine-native/parseArgs.ts +114 -7
- package/cli/src/engine-native/sessionRelayReadiness.ts +94 -0
- package/cli/src/generated/sotPayload.ts +9 -7
- package/cli/src/main.ts +19 -9
- package/package.json +1 -1
- package/cli/src/engine-native/claudeModel.ts +0 -54
package/AGENTS.md
CHANGED
|
@@ -32,12 +32,14 @@ Tool-specific instructions live alongside this file:
|
|
|
32
32
|
|
|
33
33
|
Codex SoT notes:
|
|
34
34
|
- `SoT/.codex/AGENTS.md` deploys to `~/.codex/AGENTS.md` as global Codex instructions.
|
|
35
|
-
- `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.
|
|
35
|
+
- `SoT/.codex/config.toml` pins Codex to `model = "gpt-5.6-sol"`, sets reasoning effort/summaries (`xhigh` + `concise`), `model_verbosity = "low"`, `personality`, live top-level `web_search`, workspace-write sandboxing with sandboxed command network access, cross-session `memories` (+ dedicated note tools), `[agents]` subagent limits (`max_threads = 12`, `max_depth = 2` — intentionally above Codex defaults for broad parallel kit work; deeper recursion increases cost and predictability risk), a 128 KiB `project_doc_max_bytes` budget for the repo-side AGENTS.md chain (the global `~/.codex/AGENTS.md` is uncapped and not counted), and enables the Docks plugins as `docks@docks`, `session-relay@docks`, and `effect-kit@docks`.
|
|
36
36
|
- `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
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.
|
|
38
|
+
- `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.
|
|
38
39
|
- 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.
|
|
39
40
|
- `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
41
|
- 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.
|
|
42
|
+
- Claude's deployed SoT defaults are `model: fable` and `effortLevel: high`; `advisorModel` is deliberately absent/off. `--claude-advisor=on` is the per-machine opt-in and writes `advisorModel: fable` after the settings merge.
|
|
41
43
|
|
|
42
44
|
For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT directory.
|
|
43
45
|
|
|
@@ -45,7 +47,7 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
45
47
|
|
|
46
48
|
- **Idempotent operations.** Every EngineNative sync step must be safe to re-run. Settings merges, plugin installs, and marketplace adds are all idempotent — re-running with no SoT changes is a no-op.
|
|
47
49
|
- **Removed bash engine.** The bash engine was removed after the `bash-engine-final` tag. `DOCKS_KIT_ENGINE=bash` must fail with the removed-engine message; engine bugs are fixed forward in EngineNative.
|
|
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).
|
|
50
|
+
- **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>` / `--claude-effort=<level>` / `--claude-advisor=<on|off|default>` / `--codex-model=<m>` / `--codex-effort=<level>` (see `CLAUDE.md` § Deploy-time modifiers).
|
|
49
51
|
- **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.
|
|
50
52
|
- **`--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.
|
|
51
53
|
- **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`.
|
|
@@ -69,6 +71,8 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`), see the matching SoT d
|
|
|
69
71
|
|
|
70
72
|
Automated coverage includes `bun run test:unit`, `bun run golden:dryrun`, and `bun run golden:mutation`; prove-red modes must exit non-zero after detecting planted mismatches. Also verify user-facing changes via `./docks-kit sync --dry-run`, per-tool sanity (`/doctor`, `/plugin`, etc.), and `diff <(jq -S . <SoT>) <(jq -S . <deployed>)` recipes from the per-tool file.
|
|
71
73
|
|
|
74
|
+
Use direct acceptance and focused regressions while iterating, then run the full unit/golden gate once at the pre-commit or release boundary. Reuse still-matching evidence; a later relevant edit invalidates only the affected rung and final gate, not every prior check.
|
|
75
|
+
|
|
72
76
|
## Skills
|
|
73
77
|
|
|
74
78
|
This project ships **kit-mechanic skills** under `.claude/skills/` — narrowly-scoped references for how EngineNative works (settings merge, plugin bootstrap, universal-skill install, Codex TOML merge, sync orchestration). They cost prompt tokens only inside this repo's sessions and document regression-prone TypeScript sync logic in `cli/src/engine-native/`. **Pipeline content** (multi-agent slash commands, refactor/security/docs workflows, parallel-scanner agents) belongs in the separate [DocksDocks/docks](https://github.com/DocksDocks/docks) plugin — not here. Project-level agents under `.claude/agents/` follow the same rule: kit-mechanic agents that wrap kit-mechanic skills are permitted; pipeline agents live in the docks plugin.
|
package/cli/docs/flags.md
CHANGED
|
@@ -27,12 +27,17 @@ docks-kit sync claude agents # two
|
|
|
27
27
|
| Flag | Effect |
|
|
28
28
|
|------|--------|
|
|
29
29
|
| `--claude-model=<m>` | Deploy-time modifier: deployed model (aliases or full claude-* IDs; `default` unsets) |
|
|
30
|
+
| `--claude-effort=<level>` | Deploy-time modifier: `effortLevel`; valid `low`, `medium`, `high`, `xhigh`, or `default` (Claude SoT: `high`) |
|
|
31
|
+
| `--claude-advisor=<state>` | Deploy-time modifier: advisor `on`, `off`, or `default` (SoT off/unset) |
|
|
30
32
|
| `--claude-compact-window=<n>` | Deploy-time modifier: autocompact window in tokens (`680000` or `680k`) |
|
|
31
33
|
| `--claude-permissive` | Deploy-time modifier: empty permissions.ask/deny (sandboxes) |
|
|
32
34
|
| `--claude-plugin=<name>` | Sticky opt-in plugin (known: supabase, n8n); comma-separate for several |
|
|
33
35
|
| `--codex-model=<m>` | Deploy-time modifier: deployed Codex model |
|
|
36
|
+
| `--codex-effort=<level>` | Deploy-time modifier: `model_reasoning_effort`; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra`, or `default` (Codex SoT: `xhigh`; model-dependent) |
|
|
34
37
|
|
|
35
|
-
Bare
|
|
38
|
+
Bare model, effort, or advisor modifiers print the relevant valid-value catalog
|
|
39
|
+
and exit 2. A modifier for a target not selected by the positional arguments is
|
|
40
|
+
ignored with a warning; Claude modifiers never touch Codex config and vice versa.
|
|
36
41
|
|
|
37
42
|
## Renamed legacy flags (pre-CLI sync.sh)
|
|
38
43
|
|
package/cli/docs/install.md
CHANGED
|
@@ -26,6 +26,30 @@ are versioned config snapshots without publishing the authoring `SoT/` tree.
|
|
|
26
26
|
Kit-home resolution remains available for checkout/package update behavior and
|
|
27
27
|
display paths, but sync reads do not depend on it.
|
|
28
28
|
|
|
29
|
+
### Bun 1.3.14 blocked-postinstall notice
|
|
30
|
+
|
|
31
|
+
A supported global install may finish successfully with this exact notice:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Blocked 1 postinstall. Run `bun pm -g untrusted` for details.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For the pinned production dependency graph, the diagnostic names only:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
./node_modules/@parcel/watcher @2.5.6
|
|
41
|
+
» [install]: node scripts/build-from-source.js
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`@parcel/watcher` is a transitive of the Effect Bun runtime. Supported default
|
|
45
|
+
installs already carry its platform prebuilt package, and that install script
|
|
46
|
+
only attempts a source build when `npm_config_build_from_source=true` was
|
|
47
|
+
explicitly requested. `esbuild` is not in the consumer production graph. The
|
|
48
|
+
blocked notice therefore needs no trust action for the supported default
|
|
49
|
+
install; `docks-kit --version`, model catalogs, toolchain checks, and real sync
|
|
50
|
+
remain functional with the script blocked. CI pins the one-package/one-command
|
|
51
|
+
identity above and will fail if the script-bearing set changes.
|
|
52
|
+
|
|
29
53
|
## 3. curl installer (Unix-only)
|
|
30
54
|
|
|
31
55
|
```
|
package/cli/docs/models.md
CHANGED
|
@@ -18,8 +18,8 @@ the entry and date when a model ships or retires.
|
|
|
18
18
|
## The `best` alias and `default` pseudo-value
|
|
19
19
|
|
|
20
20
|
- `best` resolves to Fable 5 where the org has access, latest Opus otherwise.
|
|
21
|
-
Needs Claude Code >= 2.1.170.
|
|
22
|
-
|
|
21
|
+
Needs Claude Code >= 2.1.170. The kit SoT pins `fable` directly, so lack of
|
|
22
|
+
Fable access is surfaced instead of silently changing the configured model.
|
|
23
23
|
- `default` is an engine pseudo-value: it DELETES the deployed `model` key so
|
|
24
24
|
the account default applies. It never reaches the settings file as a value.
|
|
25
25
|
|
|
@@ -29,14 +29,14 @@ the entry and date when a model ships or retires.
|
|
|
29
29
|
docks-kit models # both catalogs
|
|
30
30
|
docks-kit models claude --json # machine-readable
|
|
31
31
|
docks-kit model claude # current deployed + SoT + picker (TTY)
|
|
32
|
-
docks-kit model claude
|
|
32
|
+
docks-kit model claude opus # per-machine override from the Fable SoT
|
|
33
33
|
docks-kit sync claude --claude-model=opus # same, as part of a sync
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Advisor pairing note (Claude)
|
|
37
37
|
|
|
38
|
-
The
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
The SoT ships `model: fable` with advisor off (`advisorModel` unset).
|
|
39
|
+
Advisor is a per-machine opt-in: `docks-kit sync claude --claude-advisor=on`
|
|
40
|
+
writes `advisorModel: fable`; `off` and `default` delete the key. Fable-main +
|
|
41
|
+
Fable-advisor is an accepted pairing. The advisor needs Fable org access and
|
|
42
|
+
Claude Code >= 2.1.170.
|
package/cli/docs/modifiers.md
CHANGED
|
@@ -5,15 +5,32 @@ the SoT is never touched. They all share one contract:
|
|
|
5
5
|
|
|
6
6
|
> A later flag-less sync reverts the modifier: the settings merge (Claude)
|
|
7
7
|
> and the config.toml merge (Codex) re-assert SoT values for kit-owned keys.
|
|
8
|
-
> Re-pass the flag on machines that should keep the override
|
|
9
|
-
>
|
|
8
|
+
> Re-pass the flag on machines that should keep the override. Claude-only
|
|
9
|
+
> profiles can instead use `~/.claude/settings.local.json`, which sync never
|
|
10
|
+
> touches.
|
|
11
|
+
|
|
12
|
+
Claude's embedded SoT is `model: fable`, `effortLevel: high`, with advisor
|
|
13
|
+
off (`advisorModel` unset). Codex's embedded reasoning effort is `xhigh`.
|
|
10
14
|
|
|
11
15
|
| Modifier | Deployed change | Typical use |
|
|
12
16
|
|----------|-----------------|-------------|
|
|
13
|
-
| `--claude-model=<m>` | `.model` in ~/.claude/settings.json (`default` deletes the key) |
|
|
17
|
+
| `--claude-model=<m>` | `.model` in ~/.claude/settings.json (`default` deletes the key) | Override one machine while the SoT retains `fable` |
|
|
18
|
+
| `--claude-effort=<level>` | `.effortLevel` in ~/.claude/settings.json (`default` writes `high`) | Tune persisted Claude effort per machine; valid `low`, `medium`, `high`, `xhigh` |
|
|
19
|
+
| `--claude-advisor=<state>` | `on` sets `.advisorModel = "fable"`; `off`/`default` remove it | Enable Claude advisor only on machines that need it |
|
|
14
20
|
| `--claude-compact-window=<n>` | `env.CLAUDE_CODE_AUTO_COMPACT_WINDOW` | Disposable containers running long autonomous work (e.g. `680k`) — not host machines |
|
|
15
21
|
| `--claude-permissive` | `permissions.ask = []`, `permissions.deny = []` | Sandboxes/containers where prompts stall unattended work. Never on a host — the deny list is the safety floor |
|
|
16
22
|
| `--codex-model=<m>` | top-level `model = "…"` in ~/.codex/config.toml | Same as claude-model, for Codex |
|
|
23
|
+
| `--codex-effort=<level>` | top-level `model_reasoning_effort = "…"` (`default` writes `xhigh`) | Tune Codex effort per machine; valid `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` (model-dependent) |
|
|
24
|
+
|
|
25
|
+
Bare effort/advisor flags print their verified catalog and exit 2. Invalid
|
|
26
|
+
values do the same with a clear error. Passing a tool-specific modifier without
|
|
27
|
+
selecting that positional target warns and ignores it.
|
|
28
|
+
|
|
29
|
+
A flag-less Claude sync also removes the formerly kit-owned `advisorModel`
|
|
30
|
+
from machines synced before advisor became opt-in. Any explicit advisor state
|
|
31
|
+
owns that key for the run: `on` writes `fable`; `off` and `default` delete it.
|
|
32
|
+
Codex has no advisor modifier because its documented config has no advisor
|
|
33
|
+
setting; `review_model` applies only to `/review`.
|
|
17
34
|
|
|
18
35
|
## Standalone get/set (no full sync)
|
|
19
36
|
|
package/cli/docs/sync-layers.md
CHANGED
|
@@ -15,18 +15,21 @@ Order matters — runtime readiness and settings form one transaction:
|
|
|
15
15
|
unavailable, omit only the new runtime pointers and preserve legacy ones.
|
|
16
16
|
3. When ready, write `bin/statusline.mjs`, `bin/session-start.mjs`,
|
|
17
17
|
`bin/notify.mjs`, and `notification.mp3`; deploy CLAUDE.md; atomically commit
|
|
18
|
-
settings
|
|
18
|
+
settings.
|
|
19
19
|
4. **settings.json merge semantics** — additive: SoT keys win, permissions arrays are
|
|
20
20
|
unioned, user-only keys survive. `--reconcile` replaces permissions arrays
|
|
21
21
|
wholesale instead.
|
|
22
|
-
5. **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
5. **Removed-artifact pruning** — prune old shell assets, the Stop hook, and
|
|
23
|
+
stale kit-owned settings. A flag-less sync removes `advisorModel`; an
|
|
24
|
+
explicit advisor state excludes only that key so the modifier owns it.
|
|
25
|
+
6. **Deploy-time modifiers** (`--claude-compact-window`, `--claude-permissive`,
|
|
26
|
+
`--claude-model`, `--claude-effort`, `--claude-advisor`) — deployed file only.
|
|
27
|
+
7. ~/.claude.json (showTurnDuration, user-scoped MCP servers) and connector env
|
|
28
|
+
export.
|
|
29
|
+
8. **Plugins** — seven idempotent passes via the `claude plugin` CLI
|
|
27
30
|
(marketplaces → install → update → [--prune: uninstall/remove] → re-assert
|
|
28
31
|
SoT enabled-state). Optional opt-ins via `--claude-plugin=<name>`.
|
|
29
|
-
|
|
32
|
+
9. LSP server binaries (npm globals).
|
|
30
33
|
|
|
31
34
|
The statusline reads Claude's native `rate_limits`. There is no OAuth request,
|
|
32
35
|
usage cache, jq/curl runtime dependency, or Stop fetch hook.
|
|
@@ -35,8 +38,8 @@ usage cache, jq/curl runtime dependency, or Stop fetch hook.
|
|
|
35
38
|
|
|
36
39
|
bubblewrap check (Linux), config.toml merge (top-level keys replaced
|
|
37
40
|
per-key, [table] blocks replaced wholesale, user-only keys/tables preserved),
|
|
38
|
-
`--codex-model`
|
|
39
|
-
`codex plugin add` refresh.
|
|
41
|
+
`--codex-model` then `--codex-effort` modifiers, rules, AGENTS.md, personal
|
|
42
|
+
marketplace file, `codex plugin add` refresh.
|
|
40
43
|
|
|
41
44
|
## agents (→ ~/.agents/skills, ~/.claude/skills symlinks)
|
|
42
45
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
sotCodexModel
|
|
11
11
|
} from "../manifests"
|
|
12
12
|
import { kitHome } from "../kitHome"
|
|
13
|
+
import { sessionRelayReadiness } from "../engine-native/sessionRelayReadiness"
|
|
13
14
|
|
|
14
15
|
const json = Options.boolean("json").pipe(
|
|
15
16
|
Options.withDescription("Machine-readable output")
|
|
@@ -47,11 +48,12 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
47
48
|
const drift = gatherDrift()
|
|
48
49
|
const plugins = pluginsView()
|
|
49
50
|
const skills = skillsView()
|
|
51
|
+
const sessionRelay = sessionRelayReadiness()
|
|
50
52
|
const toolchainTable = yield* engineCapture(["toolchain", "check"])
|
|
51
53
|
|
|
52
54
|
if (config.json) {
|
|
53
55
|
return yield* Console.log(
|
|
54
|
-
JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, toolchainTable }, null, 2)
|
|
56
|
+
JSON.stringify({ kitHome: kitHome(), drift, plugins, skills, sessionRelayReadiness: sessionRelay, toolchainTable }, null, 2)
|
|
55
57
|
)
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -67,6 +69,11 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
67
69
|
yield* Console.log(
|
|
68
70
|
`\nPlugins: ${plugins.length} known (${enabled} SoT-enabled) — details: docks-kit plugins list`
|
|
69
71
|
)
|
|
72
|
+
yield* Console.log(
|
|
73
|
+
sessionRelay.state === "ready"
|
|
74
|
+
? `Session Relay: ready for new Codex sessions (v${sessionRelay.version})`
|
|
75
|
+
: `Session Relay: unavailable for new Codex sessions (${sessionRelay.reason})`
|
|
76
|
+
)
|
|
70
77
|
const installed = skills.filter((s) => s.installed).length
|
|
71
78
|
yield* Console.log(
|
|
72
79
|
`Skills: ${skills.length} known (${installed} installed) — details: docks-kit skills list`
|
|
@@ -74,6 +81,6 @@ export const statusCommand = Command.make("status", { json }, (config) =>
|
|
|
74
81
|
})
|
|
75
82
|
).pipe(
|
|
76
83
|
Command.withDescription(
|
|
77
|
-
"Doctor view: deployed-vs-SoT drift
|
|
84
|
+
"Doctor view: deployed-vs-SoT drift, toolchain, plugin/skill counts, and Session Relay readiness for new Codex sessions."
|
|
78
85
|
)
|
|
79
86
|
)
|
package/cli/src/commands/sync.ts
CHANGED
|
@@ -5,6 +5,12 @@ import { existsSync } from "node:fs"
|
|
|
5
5
|
import { join } from "node:path"
|
|
6
6
|
import { bail, engine } from "../engine"
|
|
7
7
|
import type { Logger } from "../engine-native/logger"
|
|
8
|
+
import {
|
|
9
|
+
advisorCatalog,
|
|
10
|
+
advisorFlagGrammar,
|
|
11
|
+
effortCatalog,
|
|
12
|
+
effortFlagGrammar
|
|
13
|
+
} from "../efforts"
|
|
8
14
|
import { kitHome } from "../kitHome"
|
|
9
15
|
import { modelCatalog, type Tool } from "../manifests"
|
|
10
16
|
import { LoggerService } from "../services"
|
|
@@ -49,7 +55,7 @@ const LEGACY_HINTS: Record<string, string> = {
|
|
|
49
55
|
"--agents": "--agents was renamed: pass the target as a word, e.g. 'sync agents'"
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
const
|
|
58
|
+
const modelCatalogHint = (t: Tool): string => {
|
|
53
59
|
const c = modelCatalog(t)
|
|
54
60
|
const list = c.models
|
|
55
61
|
.map((m) => ` ${m.id}${m.note !== undefined ? ` — ${m.note}` : ""}`)
|
|
@@ -85,6 +91,14 @@ const claudeModel = Options.text("claude-model").pipe(
|
|
|
85
91
|
Options.withDescription("Deploy-time modifier: set deployed Claude model (see `docks-kit models claude`)"),
|
|
86
92
|
Options.optional
|
|
87
93
|
)
|
|
94
|
+
const claudeEffort = Options.text("claude-effort").pipe(
|
|
95
|
+
Options.withDescription("Deploy-time modifier: set Claude effortLevel (bare flag shows valid levels)"),
|
|
96
|
+
Options.optional
|
|
97
|
+
)
|
|
98
|
+
const claudeAdvisor = Options.text("claude-advisor").pipe(
|
|
99
|
+
Options.withDescription("Deploy-time modifier: set Claude advisor on/off/default"),
|
|
100
|
+
Options.optional
|
|
101
|
+
)
|
|
88
102
|
const claudeCompactWindow = Options.text("claude-compact-window").pipe(
|
|
89
103
|
Options.withDescription("Deploy-time modifier: set deployed autocompact window in tokens (e.g. 680000 or 680k)"),
|
|
90
104
|
Options.optional
|
|
@@ -102,6 +116,10 @@ const codexModel = Options.text("codex-model").pipe(
|
|
|
102
116
|
Options.withDescription("Deploy-time modifier: set deployed Codex model (see `docks-kit models codex`)"),
|
|
103
117
|
Options.optional
|
|
104
118
|
)
|
|
119
|
+
const codexEffort = Options.text("codex-effort").pipe(
|
|
120
|
+
Options.withDescription("Deploy-time modifier: set Codex model_reasoning_effort (bare flag shows valid levels)"),
|
|
121
|
+
Options.optional
|
|
122
|
+
)
|
|
105
123
|
|
|
106
124
|
export const syncCommand = Command.make(
|
|
107
125
|
"sync",
|
|
@@ -114,10 +132,13 @@ export const syncCommand = Command.make(
|
|
|
114
132
|
yes,
|
|
115
133
|
verbose,
|
|
116
134
|
claudeModel,
|
|
135
|
+
claudeEffort,
|
|
136
|
+
claudeAdvisor,
|
|
117
137
|
claudeCompactWindow,
|
|
118
138
|
claudePermissive,
|
|
119
139
|
claudePlugin,
|
|
120
|
-
codexModel
|
|
140
|
+
codexModel,
|
|
141
|
+
codexEffort
|
|
121
142
|
},
|
|
122
143
|
(config) =>
|
|
123
144
|
Effect.gen(function* () {
|
|
@@ -125,7 +146,14 @@ export const syncCommand = Command.make(
|
|
|
125
146
|
if (VALID_TARGETS.includes(t)) continue
|
|
126
147
|
if (t === "--claude-model" || t === "--codex-model") {
|
|
127
148
|
const tool: Tool = t === "--claude-model" ? "claude" : "codex"
|
|
128
|
-
return yield* bail(`${
|
|
149
|
+
return yield* bail(`${modelCatalogHint(tool)}\n${t} requires a value: ${t}=<model>`)
|
|
150
|
+
}
|
|
151
|
+
if (t === "--claude-effort" || t === "--codex-effort") {
|
|
152
|
+
const tool: Tool = t === "--claude-effort" ? "claude" : "codex"
|
|
153
|
+
return yield* bail(`${effortCatalog(tool)}\n${t} requires a value: ${effortFlagGrammar(tool)}`)
|
|
154
|
+
}
|
|
155
|
+
if (t === "--claude-advisor") {
|
|
156
|
+
return yield* bail(`${advisorCatalog()}\n${t} requires a value: ${advisorFlagGrammar()}`)
|
|
129
157
|
}
|
|
130
158
|
const hint = LEGACY_HINTS[t]
|
|
131
159
|
if (hint !== undefined) {
|
|
@@ -148,8 +176,11 @@ export const syncCommand = Command.make(
|
|
|
148
176
|
if (config.verbose) args.push("--verbose")
|
|
149
177
|
if (config.claudePermissive) args.push("--claude-permissive")
|
|
150
178
|
Option.map(config.claudeModel, (m) => args.push(`--claude-model=${m}`))
|
|
179
|
+
Option.map(config.claudeEffort, (level) => args.push(`--claude-effort=${level}`))
|
|
180
|
+
Option.map(config.claudeAdvisor, (state) => args.push(`--claude-advisor=${state}`))
|
|
151
181
|
Option.map(config.claudeCompactWindow, (w) => args.push(`--claude-compact-window=${w}`))
|
|
152
182
|
Option.map(config.codexModel, (m) => args.push(`--codex-model=${m}`))
|
|
183
|
+
Option.map(config.codexEffort, (level) => args.push(`--codex-effort=${level}`))
|
|
153
184
|
for (const occurrence of config.claudePlugin) {
|
|
154
185
|
occurrence
|
|
155
186
|
.split(",")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { sotClaudeSettings, type Tool } from "./manifests"
|
|
2
|
+
import { payloadText } from "./payload"
|
|
3
|
+
|
|
4
|
+
export const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"] as const
|
|
5
|
+
export const CODEX_REASONING_EFFORTS = [
|
|
6
|
+
"none",
|
|
7
|
+
"minimal",
|
|
8
|
+
"low",
|
|
9
|
+
"medium",
|
|
10
|
+
"high",
|
|
11
|
+
"xhigh",
|
|
12
|
+
"max",
|
|
13
|
+
"ultra"
|
|
14
|
+
] as const
|
|
15
|
+
export const CLAUDE_ADVISOR_STATES = ["on", "off", "default"] as const
|
|
16
|
+
|
|
17
|
+
const VERIFIED = "2026-07-10"
|
|
18
|
+
const DEFAULT = "default"
|
|
19
|
+
|
|
20
|
+
export type ClaudeEffortLevel = typeof CLAUDE_EFFORT_LEVELS[number]
|
|
21
|
+
export type CodexReasoningEffort = typeof CODEX_REASONING_EFFORTS[number]
|
|
22
|
+
export type ClaudeAdvisorState = typeof CLAUDE_ADVISOR_STATES[number]
|
|
23
|
+
|
|
24
|
+
const upstreamEfforts = (tool: Tool): ReadonlyArray<string> =>
|
|
25
|
+
tool === "claude" ? CLAUDE_EFFORT_LEVELS : CODEX_REASONING_EFFORTS
|
|
26
|
+
|
|
27
|
+
export const effortModifierValues = (tool: Tool): ReadonlyArray<string> => [
|
|
28
|
+
...upstreamEfforts(tool),
|
|
29
|
+
DEFAULT
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export const effortValueGrammar = (tool: Tool): string => effortModifierValues(tool).join("|")
|
|
33
|
+
|
|
34
|
+
export const effortFlagGrammar = (tool: Tool): string =>
|
|
35
|
+
`--${tool}-effort=<${effortValueGrammar(tool)}>`
|
|
36
|
+
|
|
37
|
+
export const advisorValueGrammar = (): string => CLAUDE_ADVISOR_STATES.join("|")
|
|
38
|
+
|
|
39
|
+
export const advisorFlagGrammar = (): string => `--claude-advisor=<${advisorValueGrammar()}>`
|
|
40
|
+
|
|
41
|
+
export const isEffortModifierValue = (tool: Tool, value: string): boolean =>
|
|
42
|
+
effortModifierValues(tool).includes(value)
|
|
43
|
+
|
|
44
|
+
export function validateEffortDefault(tool: Tool, value: unknown): string {
|
|
45
|
+
const toolName = tool === "claude" ? "Claude" : "Codex"
|
|
46
|
+
const setting = tool === "claude" ? "effortLevel" : "model_reasoning_effort"
|
|
47
|
+
if (typeof value !== "string" || value === "") {
|
|
48
|
+
throw new Error(`Embedded SoT ${toolName} ${setting} is missing`)
|
|
49
|
+
}
|
|
50
|
+
if (!upstreamEfforts(tool).includes(value)) {
|
|
51
|
+
throw new Error(`Embedded SoT ${toolName} ${setting} '${value}' is outside the verified catalog`)
|
|
52
|
+
}
|
|
53
|
+
return value
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function codexSotEffort(): string | undefined {
|
|
57
|
+
return payloadText("SoT/.codex/config.toml").match(/^model_reasoning_effort\s*=\s*"([^"]+)"/m)?.[1]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function sotEffort(tool: Tool): string {
|
|
61
|
+
const value = tool === "claude" ? sotClaudeSettings().effortLevel : codexSotEffort()
|
|
62
|
+
return validateEffortDefault(tool, value)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveEffort(tool: Tool, value: string): string {
|
|
66
|
+
if (value === DEFAULT) return sotEffort(tool)
|
|
67
|
+
if (!upstreamEfforts(tool).includes(value)) {
|
|
68
|
+
throw new Error(`Invalid ${tool} effort '${value}'`)
|
|
69
|
+
}
|
|
70
|
+
return value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function effortCatalog(tool: Tool): string {
|
|
74
|
+
const setting = tool === "claude" ? "effortLevel" : "model_reasoning_effort"
|
|
75
|
+
const lines = [
|
|
76
|
+
`Available ${tool} effort levels (${setting}; verified ${VERIFIED}):`,
|
|
77
|
+
...upstreamEfforts(tool).map((value) => ` ${value}`),
|
|
78
|
+
` default — SoT: ${sotEffort(tool)}`
|
|
79
|
+
]
|
|
80
|
+
if (tool === "codex") lines.push(" (support is model-dependent)")
|
|
81
|
+
return lines.join("\n")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function advisorCatalog(): string {
|
|
85
|
+
return [
|
|
86
|
+
`Available claude advisor states (advisorModel; verified ${VERIFIED}):`,
|
|
87
|
+
" on — set advisorModel: fable",
|
|
88
|
+
" off — unset advisorModel",
|
|
89
|
+
" default — SoT: off (unset)"
|
|
90
|
+
].join("\n")
|
|
91
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Atomic top-level Claude settings modifiers, including direct model mode. */
|
|
2
|
+
import { p } from "./exec"
|
|
3
|
+
import { readFileSync, renameSync, writeFileSync } from "node:fs"
|
|
4
|
+
|
|
5
|
+
import { resolveEffort } from "../efforts"
|
|
6
|
+
import type { Ctx } from "./index"
|
|
7
|
+
import { isObject, jqStringify, parseJson } from "./jq"
|
|
8
|
+
|
|
9
|
+
interface ClaudeSettingEdit {
|
|
10
|
+
readonly tag: string
|
|
11
|
+
readonly key: "model" | "effortLevel" | "advisorModel"
|
|
12
|
+
readonly value: string | undefined
|
|
13
|
+
readonly dryRun: string
|
|
14
|
+
readonly changed: string
|
|
15
|
+
readonly unchanged: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function syncClaudeSetting(ctx: Ctx, edit: ClaudeSettingEdit): void {
|
|
19
|
+
const { change, echo, err, verbose, warn } = ctx.services.logger
|
|
20
|
+
const userSettings = p(ctx.home, ".claude", "settings.json")
|
|
21
|
+
|
|
22
|
+
if (ctx.dryRun) {
|
|
23
|
+
echo(`[dry-run] (${edit.tag}) ${edit.dryRun} in ${userSettings}`)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let text: string
|
|
28
|
+
try {
|
|
29
|
+
text = readFileSync(userSettings, "utf8")
|
|
30
|
+
} catch {
|
|
31
|
+
warn(`(${edit.tag}) ${userSettings} missing — skipped`)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const doc = parseJson(text)
|
|
35
|
+
if (doc === undefined) {
|
|
36
|
+
err(`(${edit.tag}) ${userSettings} is not valid JSON — skipped`)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (isObject(doc)) {
|
|
40
|
+
if (edit.value === undefined) delete doc[edit.key]
|
|
41
|
+
else doc[edit.key] = edit.value
|
|
42
|
+
}
|
|
43
|
+
const out = jqStringify(doc)
|
|
44
|
+
if (out === text) {
|
|
45
|
+
verbose(edit.unchanged)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(`${userSettings}.tmp`, out)
|
|
49
|
+
renameSync(`${userSettings}.tmp`, userSettings)
|
|
50
|
+
change(edit.changed)
|
|
51
|
+
ctx.nextStepTriggers.claudeRestart = true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function syncClaudeModel(ctx: Ctx, model: string): void {
|
|
55
|
+
if (model === "") return
|
|
56
|
+
const unset = model === "default"
|
|
57
|
+
syncClaudeSetting(ctx, {
|
|
58
|
+
tag: "--claude-model",
|
|
59
|
+
key: "model",
|
|
60
|
+
value: unset ? undefined : model,
|
|
61
|
+
dryRun: unset ? "delete .model (account default applies)" : `set .model=${model}`,
|
|
62
|
+
changed: `Model: deployed settings model set to ${model} (SoT unchanged; flag-less sync reverts)`,
|
|
63
|
+
unchanged: `Model: deployed settings model already ${unset ? "unset (account default)" : model}`
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function syncClaudeEffort(ctx: Ctx, effort: string): void {
|
|
68
|
+
if (effort === "") return
|
|
69
|
+
const resolved = resolveEffort("claude", effort)
|
|
70
|
+
const useDefault = effort === "default"
|
|
71
|
+
syncClaudeSetting(ctx, {
|
|
72
|
+
tag: "--claude-effort",
|
|
73
|
+
key: "effortLevel",
|
|
74
|
+
value: resolved,
|
|
75
|
+
dryRun: `set .effortLevel=${resolved}`,
|
|
76
|
+
changed: useDefault
|
|
77
|
+
? `Effort: deployed settings effortLevel set to ${resolved} (SoT default)`
|
|
78
|
+
: `Effort: deployed settings effortLevel set to ${resolved} (SoT unchanged; flag-less sync reverts)`,
|
|
79
|
+
unchanged: `Effort: deployed settings effortLevel already ${resolved}`
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function syncClaudeAdvisor(ctx: Ctx, state: string): void {
|
|
84
|
+
if (state === "") return
|
|
85
|
+
const enabled = state === "on"
|
|
86
|
+
const useDefault = state === "default"
|
|
87
|
+
syncClaudeSetting(ctx, {
|
|
88
|
+
tag: "--claude-advisor",
|
|
89
|
+
key: "advisorModel",
|
|
90
|
+
value: enabled ? "fable" : undefined,
|
|
91
|
+
dryRun: enabled ? "set .advisorModel=fable" : "delete .advisorModel (advisor disabled)",
|
|
92
|
+
changed: enabled
|
|
93
|
+
? "Advisor: deployed settings advisorModel set to fable (SoT unchanged; flag-less sync reverts)"
|
|
94
|
+
: useDefault
|
|
95
|
+
? "Advisor: deployed settings advisorModel unset (SoT default: off)"
|
|
96
|
+
: "Advisor: deployed settings advisorModel unset (--claude-advisor=off; SoT unchanged)",
|
|
97
|
+
unchanged: enabled
|
|
98
|
+
? "Advisor: deployed settings advisorModel already fable"
|
|
99
|
+
: `Advisor: deployed settings advisorModel already unset (${useDefault ? "SoT default: off" : "advisor off"})`
|
|
100
|
+
})
|
|
101
|
+
}
|
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
} from "node:fs"
|
|
18
18
|
import { tmpdir } from "node:os"
|
|
19
19
|
import { bunBootstrap } from "./bun"
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
syncClaudeAdvisor,
|
|
22
|
+
syncClaudeEffort,
|
|
23
|
+
syncClaudeModel
|
|
24
|
+
} from "./claudeSettingsModifiers"
|
|
21
25
|
import { claudeRuntimePaths, materializeClaudeSettings, type ClaudeRuntimePaths } from "./claudeRuntime"
|
|
22
26
|
import { p, writeBytesIfChanged, writeFileIfChanged, writeTextIfChanged } from "./exec"
|
|
23
27
|
import type { Ctx } from "./index"
|
|
@@ -73,6 +77,8 @@ export function claudeSync(ctx: Ctx): ClaudeRuntimeState {
|
|
|
73
77
|
syncCompactWindow(ctx, claudeDir)
|
|
74
78
|
syncPermissive(ctx, claudeDir)
|
|
75
79
|
syncClaudeModel(ctx, ctx.claudeModel)
|
|
80
|
+
syncClaudeEffort(ctx, ctx.claudeEffort)
|
|
81
|
+
syncClaudeAdvisor(ctx, ctx.claudeAdvisor)
|
|
76
82
|
syncClaudeJson(ctx)
|
|
77
83
|
syncConnectorEnv(ctx)
|
|
78
84
|
syncPlugins(ctx, claudeDir)
|
|
@@ -489,6 +495,7 @@ const REMOVED_MANIFEST = {
|
|
|
489
495
|
files: ["alert_bubble.mp3"],
|
|
490
496
|
settingsKeys: [
|
|
491
497
|
"showTurnDuration",
|
|
498
|
+
"advisorModel",
|
|
492
499
|
"env.CLAUDE_CODE_SUBAGENT_MODEL",
|
|
493
500
|
"env.ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
494
501
|
"env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE",
|
|
@@ -550,7 +557,7 @@ function syncRemovals(ctx: Ctx, claudeDir: string, runtime: ClaudeRuntimeState):
|
|
|
550
557
|
...(runtime.kind === "ready" ? REMOVED_MANIFEST.runtimeReady.files : [])
|
|
551
558
|
]
|
|
552
559
|
const settingsKeys = [
|
|
553
|
-
...REMOVED_MANIFEST.settingsKeys,
|
|
560
|
+
...REMOVED_MANIFEST.settingsKeys.filter((key) => key !== "advisorModel" || ctx.claudeAdvisor === ""),
|
|
554
561
|
...(runtime.kind === "ready" ? REMOVED_MANIFEST.runtimeReady.settingsKeys : [])
|
|
555
562
|
]
|
|
556
563
|
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
import { spawnSync } from "node:child_process"
|
|
7
7
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
|
|
8
8
|
|
|
9
|
-
import { syncCodexModel, replaceTopLevelSettingInFile } from "./codexToml"
|
|
9
|
+
import { syncCodexEffort, syncCodexModel, replaceTopLevelSettingInFile } from "./codexToml"
|
|
10
10
|
import { p } from "./exec"
|
|
11
11
|
import type { Ctx } from "./index"
|
|
12
12
|
import { compareCodepoints, isObject, jqStringify, parseJson, type Json } from "./jq"
|
|
13
|
+
import { sessionRelayReadiness } from "./sessionRelayReadiness"
|
|
13
14
|
import { payloadBytes, payloadDisplayPath, payloadPaths, payloadText, type PayloadPath } from "../payload"
|
|
14
15
|
|
|
15
16
|
export function codexSync(ctx: Ctx): void {
|
|
@@ -21,6 +22,7 @@ export function codexSync(ctx: Ctx): void {
|
|
|
21
22
|
if (!ctx.dryRun) mkdirSync(codexDir, { recursive: true })
|
|
22
23
|
syncConfig(ctx, sotConfig, userConfig)
|
|
23
24
|
syncCodexModel(ctx, ctx.codexModel)
|
|
25
|
+
syncCodexEffort(ctx, ctx.codexEffort)
|
|
24
26
|
syncRules(ctx, payloadPaths("SoT/.codex/rules/"), p(codexDir, "rules"))
|
|
25
27
|
syncAgentsMd(ctx, payloadText("SoT/.codex/AGENTS.md"), p(codexDir, "AGENTS.md"))
|
|
26
28
|
syncMarketplace(ctx, payloadText("SoT/.codex/plugins/marketplace.json"), p(ctx.agentsDir, "plugins", "marketplace.json"))
|
|
@@ -475,6 +477,10 @@ function syncPlugins(ctx: Ctx, sotConfigText: string): void {
|
|
|
475
477
|
if (refreshed > 0) {
|
|
476
478
|
change(`Codex plugins synced (plugins: ~${refreshed})`)
|
|
477
479
|
ctx.nextStepTriggers.codexRestart = true
|
|
480
|
+
const readiness = sessionRelayReadiness()
|
|
481
|
+
if (readiness.state !== "ready") {
|
|
482
|
+
warn(`Session Relay readiness unavailable after refresh: ${readiness.reason}`)
|
|
483
|
+
}
|
|
478
484
|
}
|
|
479
485
|
if (failed > 0) warn(`${failed} Codex plugin operation(s) failed — re-run sync or install manually`)
|
|
480
486
|
}
|