docks-kit 0.18.1 → 0.19.1
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 +7 -5
- package/README.md +9 -8
- package/cli/docs/flags.md +2 -2
- package/cli/docs/models.md +16 -7
- package/cli/docs/omp-models.md +2 -2
- package/cli/docs/overview.md +1 -1
- package/cli/docs/sync-layers.md +1 -1
- package/cli/docs/toolchain.md +10 -0
- package/cli/src/commands/docs.ts +8 -2
- package/cli/src/commands/harnesses.ts +2 -2
- package/cli/src/commands/model.ts +5 -2
- package/cli/src/commands/models.ts +50 -23
- package/cli/src/commands/toolchain.ts +36 -18
- package/cli/src/commands/update.ts +6 -6
- package/cli/src/engine-native/claudePluginPasses.ts +26 -11
- package/cli/src/engine-native/codexConfig.ts +6 -6
- package/cli/src/engine-native/codexPlugins.ts +6 -5
- package/cli/src/engine-native/codexToml.ts +10 -6
- package/cli/src/engine-native/harnesses.ts +52 -78
- package/cli/src/engine-native/index.ts +1 -1
- package/cli/src/engine-native/kitDb.ts +250 -0
- package/cli/src/engine-native/liveModels.ts +263 -0
- package/cli/src/engine-native/models.ts +32 -26
- package/cli/src/engine-native/modes.ts +22 -21
- package/cli/src/engine-native/parseArgs.ts +3 -2
- package/cli/src/engine-native/parseModifiers.ts +8 -5
- package/cli/src/engine-native/sharedTypes.d.ts +19 -0
- package/cli/src/engine-native/syncDispatch.ts +1 -1
- package/cli/src/engine-native/toolchain.ts +161 -2
- package/cli/src/generated/sotPayload.ts +4 -4
- package/cli/src/main.ts +2 -2
- package/cli/src/manifests.ts +2 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -35,12 +35,14 @@ launcher can fall back to Bun source.
|
|
|
35
35
|
| `cli/src/commands/omp.ts` | `docks-kit omp` session launcher: renders the free-model run overlay and forwards args to omp |
|
|
36
36
|
| `cli/src/engine-native/ompOverlay.ts` | Free-model overlay render plus catalog parse and thinking-ceiling helpers |
|
|
37
37
|
| `cli/src/engine-native/<axis>.ts` | One change axis per file: `codexConfig`/`codexHooks`/`codexPlugins`/`codexStatus`, `claudeSettings`/`claudeRemovals`/`claudePluginPasses`/`claudeOptionalPlugins`/`claudeLsp`, `parseModifiers`/`parseHelp`, `ompFileDeploy`/`ompMarketplace`/`ompPlugins`, `skillsManifest`/`skillsLinks`/`skillsInstall`/`skillsPrune`, `engineCtx`/`syncConcurrency`/`syncDispatch`; orchestrators keep prior exports |
|
|
38
|
-
| `cli/src/engine-native/sharedTypes.d.ts` | Single definition per shared shape (manifest records, settings edits, omp session model, scalar modifier flags) |
|
|
38
|
+
| `cli/src/engine-native/sharedTypes.d.ts` | Single definition per shared shape (manifest records, settings edits, omp session model, scalar modifier flags, resolved model catalog) |
|
|
39
|
+
| `cli/src/engine-native/kitDb.ts` | Only opener of the SQLite store `~/.docks-kit/kit.db`: append-only migrations, one-time `state.json` import, TTL cache |
|
|
40
|
+
| `cli/src/engine-native/liveModels.ts` | Live model list per enabled harness (Anthropic API via Claude Code login, Codex model cache, `omp models --json`) merged over `SoT/models.json` |
|
|
39
41
|
| `.oxlintrc.json` / `.oxfmtrc.json` | Mechanical lint (correctness, suspicious, eqeqeq, no-var, prefer-const, no-unused) and code format scope |
|
|
40
42
|
| `cli/test/fixtures/` | Golden fixtures: `home-fresh`, `home-drift`, `home-invalid-json`, `codex-toml`, `statusline` |
|
|
41
43
|
| `cli/` | Effect 4 RC CLI + bundled docs topics |
|
|
42
|
-
| `SoT/models.json` |
|
|
43
|
-
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative) |
|
|
44
|
+
| `SoT/models.json` | Curated Claude and Codex model overlay: aliases and notes always apply; id rows are the fallback when a harness live list is unavailable |
|
|
45
|
+
| `SoT/toolchain.json` | Toolchain floors manifest (verified pins consumed by EngineNative; optional `upstream` source read by `docks-kit toolchain outdated`) |
|
|
44
46
|
| `SoT/.claude/bin/` | Dependency-free Bun runtime programs for Claude's statusline, SessionStart, and Notification |
|
|
45
47
|
| `SoT/.omp/` | Kit-owned omp SoT: `AGENTS.md`, `config.yml`, `models.yml`, `mcp.json`, and `intercom.json` |
|
|
46
48
|
| `install.sh` / `install.ps1` | POSIX and Windows global installers |
|
|
@@ -84,7 +86,7 @@ omp SoT notes:
|
|
|
84
86
|
- Sync installs `pi-intercom` at the verified version from `SoT/toolchain.json`.
|
|
85
87
|
- The omp CLI is upstream-owned and self-updating through `omp update`. Sync never installs or upgrades the CLI.
|
|
86
88
|
- `docks-kit omp [--model <selector>|--pick] [args...]` starts one interactive omp session with every model role set to one free model and every retry chain empty. Higher-precedence model selection can replace those overlay values. It renders the overlay to `~/.cache/docks-kit/omp-free-<model>-<digest>.yml` (mode 0600) and passes it through omp's repeatable `--config` flag. `ompOverlay.ts overlayFileName` gives each model its own file, because omp can re-read the overlay during a live session and a second launcher on another model must not rewrite it. Deployed `~/.omp/agent/` files stay untouched, so the next plain `omp` run uses the paid configuration again. Remaining arguments forward verbatim to omp.
|
|
87
|
-
- The session model persists per machine in `~/.docks-kit/
|
|
89
|
+
- The session model persists per machine in `~/.docks-kit/kit.db` (table `omp_session`), next to the harness selection. The default is `opencode-zen/muse-spark-1.3-contributor-free` at `xhigh`. `ompOverlay.ts ladderCeiling, advisorLevelFor` derive both levels from the ladder of the chosen model for the `--model` path, and `ompOverlay.ts planEffortChoice` drives the `--pick` wizard, which asks whether every role shares one level and otherwise takes one level for the main roles and one for the advisor from that model's own ladder. Levels never come from a fixed list: free ladders are not uniform, three free models publish no `medium`, and two publish no ladder, which the overlay renders as bare selectors. The picker lists only zero-cost catalog models.
|
|
88
90
|
|
|
89
91
|
For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`, `SoT/.omp/`), see the matching SoT directory.
|
|
90
92
|
|
|
@@ -96,7 +98,7 @@ For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`, `SoT/.omp/`), see the m
|
|
|
96
98
|
- **Effect 4 CLI stack.** The CLI pins `effect@4.0.0-rc.115` (including `effect/unstable/cli`), `@effect/platform-bun@4.0.0-rc.115` (`BunServices.layer`, `BunRuntime.runMain`), `@effect/vitest@4.0.0-rc.115`, and `vitest@5.0.0` (inside the `@effect/vitest` peer range `>=5.0.0 <6.0.0`; 5.0.0 is past the 5.x patch point for GHSA-82fw-gwwq-j7x9, fixed in 5.0.0-rc.2). `@effect/cli` and `@effect/platform` are removed and must not be reintroduced.
|
|
97
99
|
- **Effect skill routing.** Effect work in this checkout must verify migration and API call shapes against the installed declarations under `node_modules/effect/dist/unstable/cli/`, never from memory or a mutable dist-tag. The `effect-ts-setup`, `effect-ts-port`, and `effect-ts-specialist` skills target Effect 3.x and do not apply.
|
|
98
100
|
- **Targeted syncs.** `./docks-kit sync` accepts positional targets: `claude`, `codex`, `agents`, and `omp`. Use the narrowest target that matches the SoT change (for example, `./docks-kit sync omp` for omp-only config edits); targets can be combined with `--dry-run`, `--skip-bubblewrap` (skip optional bubblewrap bootstrap for the Codex Linux sandbox), `--skip-plugin-refresh` (install missing plugins without refreshing existing caches), `--reconcile`, `--prune`, 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).
|
|
99
|
-
- **Per-machine harness selection.** `~/.docks-kit/
|
|
101
|
+
- **Per-machine harness selection.** The SQLite store `~/.docks-kit/kit.db` drives a flag-less sync. A missing selection selects `claude`, `codex`, and `agents`; it never selects `omp` implicitly. `sync` never prompts and never writes the selection. `docks-kit harnesses` is the only command that writes the `harness_selection` table. The same store holds the independent `omp_session` table, which only `docks-kit omp` writes, and a TTL cache (`cache_entry`) for the live Anthropic model list and `toolchain outdated` lookups. `kitDb.ts withKitDb` is the only opener: it runs the append-only `MIGRATIONS`, imports a legacy `state.json` once and renames it to `state.json.migrated`, and refuses a store from a newer schema. Cache rows carry the kit version as fingerprint, so a new release refetches them.
|
|
100
102
|
- **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 (`claudeSync.ts REMOVED_MANIFEST, baseline removal inventory`), a curated list of unambiguous kit-owned artifacts that `claudeSync.ts syncRemovals, baseline artifact prune` force-prunes on every sync, including the home-relative `~/.local/bin/session-relay` artifact installed outside `~/.claude`; see `CLAUDE.md` § Pruning stale artifacts.
|
|
101
103
|
- **`--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.
|
|
102
104
|
- **SOLID-aligned modules.** Axis modules own one concern each (see layout); orchestrators (`claudeSync.ts`, `codexSync.ts`, `skillsSync.ts`, `ompSync.ts`, `index.ts`, `argv.ts`, `parseArgs.ts`) keep prior exports. New shared shapes go in `sharedTypes.d.ts`, never a second local copy. The public CLI seam is `cli/src/engine.ts`.
|
package/README.md
CHANGED
|
@@ -47,13 +47,13 @@ docks-kit sync [claude] [codex] [agents] [omp] deploy explicit targets or the m
|
|
|
47
47
|
docks-kit harnesses view or change this machine's selection
|
|
48
48
|
docks-kit update [--no-sync] self-update the kit (autodetects checkout vs global install), then sync
|
|
49
49
|
docks-kit model <claude|codex> [value] get/set the DEPLOYED model (TTY picker)
|
|
50
|
-
docks-kit models [claude|codex]
|
|
50
|
+
docks-kit models [claude|codex|omp] [--refresh] live catalogs for enabled harnesses (`--json`)
|
|
51
51
|
docks-kit omp [--model <m>|--pick] [args...] one omp session on a free model, nothing deployed changes
|
|
52
|
-
docks-kit toolchain [check|ensure <tool
|
|
52
|
+
docks-kit toolchain [check|ensure <tool>|outdated] verified-version floors for external tools
|
|
53
53
|
docks-kit status [--json] deployed-vs-SoT drift + toolchain + counts
|
|
54
54
|
docks-kit plugins list [--json] enabledPlugins tri-state vs installed
|
|
55
55
|
docks-kit skills list [--json] universal skills vs manifest
|
|
56
|
-
docks-kit docs [topic] self-documentation (
|
|
56
|
+
docks-kit docs [topic] self-documentation (11 topics)
|
|
57
57
|
--help --version --wizard --completions built-in
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -91,16 +91,17 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
91
91
|
- **Additive by default** — user-only settings keys, plugins, and skills
|
|
92
92
|
survive a plain sync. Reconciliation toward the SoT is explicit
|
|
93
93
|
(`--reconcile` / `--prune`).
|
|
94
|
-
- **Per-machine selection** — `~/.docks-kit/
|
|
95
|
-
A missing
|
|
94
|
+
- **Per-machine selection** — `~/.docks-kit/kit.db` drives a flag-less sync.
|
|
95
|
+
A missing selection selects Claude Code, Codex, and universal skills. It does not
|
|
96
96
|
select omp. Use `docks-kit harnesses` to view or change the selection.
|
|
97
97
|
- **Idempotent** — every step is safe to re-run; no-change syncs are no-ops.
|
|
98
98
|
- **Toolchain floors** — `SoT/toolchain.json` records the kit-verified version
|
|
99
99
|
floors for external tools (bun, bwrap, …). `docks-kit toolchain check` prints
|
|
100
100
|
the full doctor table. Bun is the one managed install and is pinned to its
|
|
101
101
|
verified version.
|
|
102
|
-
- **Model catalog** —
|
|
103
|
-
model
|
|
102
|
+
- **Model catalog** — enabled harnesses provide live Claude, Codex, or omp
|
|
103
|
+
model IDs; `SoT/models.json` supplies Claude and Codex aliases, notes, and the
|
|
104
|
+
offline fallback. omp has no curated fallback list.
|
|
104
105
|
- **Claude runtime** — sync materializes three dependency-free Bun `.mjs`
|
|
105
106
|
programs for statusline, SessionStart, and Notification. Quota display uses
|
|
106
107
|
Claude's native `rate_limits`; there is no OAuth fetch, shared usage cache,
|
|
@@ -115,7 +116,7 @@ and a later flag-less sync reverts them. Full reference: `docks-kit docs flags`
|
|
|
115
116
|
| `SoT/.codex/` | Codex SoT (config.toml, rules, AGENTS.md, marketplace) |
|
|
116
117
|
| `SoT/.omp/` | omp SoT (AGENTS.md, config.yml, models.yml, mcp.json, intercom.json) |
|
|
117
118
|
| `SoT/.agents/` | Universal-skill manifest |
|
|
118
|
-
| `SoT/models.json` |
|
|
119
|
+
| `SoT/models.json` | Curated aliases, notes, and offline Claude/Codex fallback |
|
|
119
120
|
| `SoT/toolchain.json` | Verified-version floors |
|
|
120
121
|
| `cli/src/engine-native/` | EngineNative sync/model/toolchain implementation |
|
|
121
122
|
| `cli/src/generated/sotPayload.ts` | Generated in-memory payload used by standalone and npm installs |
|
package/cli/docs/flags.md
CHANGED
|
@@ -14,7 +14,7 @@ docks-kit sync omp # opt-in harness
|
|
|
14
14
|
|
|
15
15
|
Positional targets are `claude`, `codex`, `agents`, and `omp`. A flag-less
|
|
16
16
|
`sync` deploys the per-machine harness selection stored in
|
|
17
|
-
`~/.docks-kit/
|
|
17
|
+
`~/.docks-kit/kit.db`. A missing selection or an unreadable store selects `claude`,
|
|
18
18
|
`codex`, and `agents`, and never selects `omp`. Choose the stored selection
|
|
19
19
|
with `docks-kit harnesses`.
|
|
20
20
|
|
|
@@ -56,7 +56,7 @@ ignored with a warning; Claude modifiers never touch Codex config and vice versa
|
|
|
56
56
|
|
|
57
57
|
| Flag | Effect |
|
|
58
58
|
|------|--------|
|
|
59
|
-
| `--model <selector>` | Session model for this run and later runs; rejected unless the live catalog reports zero input and output cost; recorded in `~/.docks-kit/
|
|
59
|
+
| `--model <selector>` | Session model for this run and later runs; rejected unless the live catalog reports zero input and output cost; recorded in `~/.docks-kit/kit.db` (table `omp_session`) |
|
|
60
60
|
| `--pick` | Interactive picker over free catalog models; records the choice the same way |
|
|
61
61
|
|
|
62
62
|
Remaining arguments forward verbatim to omp after the launcher flags.
|
package/cli/docs/models.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# Models
|
|
2
2
|
|
|
3
|
-
`SoT/models.json`
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
the
|
|
3
|
+
`SoT/models.json` supplies aliases and notes for Claude and Codex. For each
|
|
4
|
+
enabled harness, `docks-kit models` and the picker list live model IDs when
|
|
5
|
+
available. Claude uses the Claude Code login to query the Anthropic models API
|
|
6
|
+
and caches the result for six hours in `~/.docks-kit/kit.db`; Codex reads
|
|
7
|
+
`~/.codex/models_cache.json`; omp runs `omp models --json`. The kit never needs
|
|
8
|
+
omp to list Claude models.
|
|
9
|
+
|
|
10
|
+
Live lists retain the curated aliases and notes, but omit curated IDs absent
|
|
11
|
+
from the live source. A disabled harness, missing login/cache, or unavailable
|
|
12
|
+
source falls back to the curated IDs. omp has no curated fallback models.
|
|
13
|
+
The `verified` field is the curated section date, not the live fetch date.
|
|
14
|
+
Bare model flags without a value print the curated list without a network call.
|
|
7
15
|
|
|
8
16
|
## Validation rules
|
|
9
17
|
|
|
@@ -33,9 +41,10 @@ the entry and date when a model ships or retires.
|
|
|
33
41
|
## Commands
|
|
34
42
|
|
|
35
43
|
```
|
|
36
|
-
docks-kit models #
|
|
37
|
-
docks-kit models claude --json #
|
|
38
|
-
docks-kit
|
|
44
|
+
docks-kit models # enabled Claude, Codex, and omp catalogs
|
|
45
|
+
docks-kit models claude --json # source, fetch date, and fallback reason
|
|
46
|
+
docks-kit models claude --refresh # bypass the six-hour Anthropic cache
|
|
47
|
+
docks-kit model claude # current deployed + live list + picker (TTY)
|
|
39
48
|
docks-kit model claude opus # per-machine override from the Opus SoT
|
|
40
49
|
docks-kit sync claude --claude-model=opus # same, as part of a sync
|
|
41
50
|
```
|
package/cli/docs/omp-models.md
CHANGED
|
@@ -418,8 +418,8 @@ distinct ladders: 21 include `medium`, 3 stop at `low, high, max` or
|
|
|
418
418
|
bare selectors with no `:level` suffix, because an invented level makes omp
|
|
419
419
|
fail when the session starts.
|
|
420
420
|
|
|
421
|
-
The choice persists per machine in `~/.docks-kit/
|
|
422
|
-
`
|
|
421
|
+
The choice persists per machine in `~/.docks-kit/kit.db` (table
|
|
422
|
+
`omp_session`), next to the harness selection. It survives across sessions. It is never
|
|
423
423
|
committed. `--model <selector>` records one selector and derives both levels
|
|
424
424
|
from the catalog row. `--pick` opens an interactive wizard.
|
|
425
425
|
|
package/cli/docs/overview.md
CHANGED
|
@@ -13,7 +13,7 @@ AI-assisted dev environment on every machine.
|
|
|
13
13
|
| `SoT/.codex/` | Codex config (config.toml, rules, AGENTS.md, marketplace) |
|
|
14
14
|
| `SoT/.agents/` | Universal agent skills manifest (agentskills.io standard) |
|
|
15
15
|
| `SoT/.omp/` | omp config deployed to `~/.omp/agent/` (AGENTS.md, config.yml, models.yml, mcp.json) plus `intercom.json` for pi intercom |
|
|
16
|
-
| `SoT/models.json` |
|
|
16
|
+
| `SoT/models.json` | Curated Claude and Codex aliases, notes, and offline fallback for the live model lists (see `docks-kit docs models`) |
|
|
17
17
|
| `SoT/toolchain.json` | Verified-version floors for external tools (see `docks-kit docs toolchain`) |
|
|
18
18
|
| `cli/src/generated/sotPayload.ts` | Deterministic generated payload embedded in standalone/npm execution |
|
|
19
19
|
| `cli/src/engine-native/` | EngineNative mutation logic for sync/model/toolchain and the `docks-kit omp` session overlay |
|
package/cli/docs/sync-layers.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`docks-kit sync [claude] [codex] [agents] [omp]` — targets are positional
|
|
4
4
|
words; no target means this machine's harness selection in
|
|
5
|
-
`~/.docks-kit/
|
|
5
|
+
`~/.docks-kit/kit.db`. The default is `claude codex agents`, and
|
|
6
6
|
`docks-kit harnesses` changes it.
|
|
7
7
|
|
|
8
8
|
## claude (→ ~/.claude, ~/.claude.json, shell rc)
|
package/cli/docs/toolchain.md
CHANGED
|
@@ -85,4 +85,14 @@ manifest's `verified` version.
|
|
|
85
85
|
```text
|
|
86
86
|
docks-kit toolchain check # doctor table (also inside docks-kit status)
|
|
87
87
|
docks-kit toolchain ensure bun # ensure the only managed tool
|
|
88
|
+
docks-kit toolchain outdated [--refresh] # verified pins vs newest upstream release
|
|
88
89
|
```
|
|
90
|
+
|
|
91
|
+
`docks-kit toolchain outdated` reads each tool's optional `upstream` entry in
|
|
92
|
+
`SoT/toolchain.json`. An `npm` entry reads the registry `latest` tag. With a
|
|
93
|
+
`line`, it reads the newest release on that major line instead, so `tsc` stays
|
|
94
|
+
on 6.x. A `github` entry reads the latest release tag and strips `tagPrefix`.
|
|
95
|
+
Set `GITHUB_TOKEN` or `GH_TOKEN` to raise the GitHub rate limit. Successful
|
|
96
|
+
lookups cache for 24 hours in `~/.docks-kit/kit.db`; `--refresh` bypasses the
|
|
97
|
+
cache. The report never installs anything and never edits a pin. A failed
|
|
98
|
+
lookup prints `lookup failed: <reason>` and the command still exits 0.
|
package/cli/src/commands/docs.ts
CHANGED
|
@@ -24,8 +24,14 @@ const TOPICS: Record<string, { summary: string; body: string }> = {
|
|
|
24
24
|
summary: "Deploy-time modifiers and the flag-less-sync-reverts contract",
|
|
25
25
|
body: modifiers,
|
|
26
26
|
},
|
|
27
|
-
models: {
|
|
28
|
-
|
|
27
|
+
models: {
|
|
28
|
+
summary: "Live model lists, the curated overlay, validation, model get/set",
|
|
29
|
+
body: models,
|
|
30
|
+
},
|
|
31
|
+
toolchain: {
|
|
32
|
+
summary: "Verified-version floors, the doctor table, and the upstream outdated report",
|
|
33
|
+
body: toolchain,
|
|
34
|
+
},
|
|
29
35
|
plugins: { summary: "enabledPlugins tri-state + optional plugin opt-ins", body: plugins },
|
|
30
36
|
install: {
|
|
31
37
|
summary: "Install paths: repo checkout, bun add -g, POSIX/Windows installers",
|
|
@@ -4,12 +4,12 @@ import { bail } from "../engine";
|
|
|
4
4
|
import {
|
|
5
5
|
engineHome,
|
|
6
6
|
HARNESSES,
|
|
7
|
-
harnessStateFile,
|
|
8
7
|
LEGACY_SELECTION,
|
|
9
8
|
readHarnessSelection,
|
|
10
9
|
writeHarnessSelection,
|
|
11
10
|
type Harness,
|
|
12
11
|
} from "../engine-native/harnesses";
|
|
12
|
+
import { kitDbFile } from "../engine-native/kitDb";
|
|
13
13
|
|
|
14
14
|
const DESCRIPTIONS: Record<Harness, string> = {
|
|
15
15
|
claude: "Claude Code user configuration",
|
|
@@ -50,7 +50,7 @@ export const harnessesCommand = Command.make("harnesses", {}, () =>
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
yield* Effect.sync(() => writeHarnessSelection(home, answer));
|
|
53
|
-
yield* Console.log(`Saved harness selection: ${answer.join(", ")} (${
|
|
53
|
+
yield* Console.log(`Saved harness selection: ${answer.join(", ")} (${kitDbFile(home)})`);
|
|
54
54
|
}),
|
|
55
55
|
).pipe(
|
|
56
56
|
Command.withDescription("Choose the harness selection that drives a flag-less docks-kit sync."),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Argument, Command, Flag, Prompt } from "effect/unstable/cli";
|
|
2
2
|
import { Effect, Option } from "effect";
|
|
3
3
|
import { bail, engine } from "../engine";
|
|
4
|
-
import {
|
|
4
|
+
import { engineHome } from "../engine-native/harnesses";
|
|
5
|
+
import { defaultLiveInputs, resolveCatalog } from "../engine-native/liveModels";
|
|
5
6
|
import type { Tool } from "../manifests";
|
|
6
7
|
|
|
7
8
|
const tool = Argument.String("tool").pipe(Argument.withDescription("Which tool: claude | codex"));
|
|
@@ -42,7 +43,9 @@ export const modelCommand = Command.make("model", { tool, value, dryRun, verbose
|
|
|
42
43
|
// … and offer an interactive picker when attached to a terminal.
|
|
43
44
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
44
45
|
|
|
45
|
-
const catalog =
|
|
46
|
+
const catalog = yield* Effect.promise(() =>
|
|
47
|
+
resolveCatalog(t, defaultLiveInputs(engineHome(process.env))),
|
|
48
|
+
);
|
|
46
49
|
const chosen = yield* Prompt.Select({
|
|
47
50
|
message: `Set the deployed ${t} model (deployed config only; a flag-less sync reverts to SoT)`,
|
|
48
51
|
choices: [
|
|
@@ -1,48 +1,75 @@
|
|
|
1
1
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
2
2
|
import { Console, Effect, Option } from "effect";
|
|
3
3
|
import { bail } from "../engine";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { engineHome, LEGACY_SELECTION, readHarnessSelection } from "../engine-native/harnesses";
|
|
5
|
+
import { defaultLiveInputs, resolveCatalog } from "../engine-native/liveModels";
|
|
6
|
+
import type { CatalogTool, ResolvedCatalog } from "../engine-native/sharedTypes";
|
|
6
7
|
|
|
7
8
|
const tool = Argument.String("tool").pipe(
|
|
8
|
-
Argument.withDescription("claude | codex (omit for
|
|
9
|
+
Argument.withDescription("claude | codex | omp (omit for enabled harnesses)"),
|
|
9
10
|
Argument.optional,
|
|
10
11
|
);
|
|
11
12
|
const json = Flag.Boolean("json").pipe(
|
|
12
13
|
Flag.withDescription("Machine-readable output"),
|
|
13
14
|
Flag.withDefault(false),
|
|
14
15
|
);
|
|
16
|
+
const refresh = Flag.Boolean("refresh").pipe(
|
|
17
|
+
Flag.withDescription("Bypass the kit cache (~/.docks-kit/kit.db) and fetch live lists again"),
|
|
18
|
+
Flag.withDefault(false),
|
|
19
|
+
);
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
function isCatalogTool(value: string): value is CatalogTool {
|
|
22
|
+
return value === "claude" || value === "codex" || value === "omp";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const renderTool = (t: CatalogTool, catalog: ResolvedCatalog) =>
|
|
17
26
|
Effect.gen(function* () {
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
yield* Console.log(
|
|
28
|
+
catalog.source === "curated"
|
|
29
|
+
? `${t} models (kit-verified ${catalog.verified}):`
|
|
30
|
+
: `${t} models (live — ${catalog.source}, fetched ${catalog.fetchedAt ?? "?"}):`,
|
|
31
|
+
);
|
|
32
|
+
if (catalog.fallbackReason !== undefined) {
|
|
33
|
+
yield* Console.log(` (live list unavailable: ${catalog.fallbackReason})`);
|
|
34
|
+
}
|
|
20
35
|
for (const m of catalog.models) {
|
|
21
36
|
yield* Console.log(` ${m.id.padEnd(28)} ${m.kind.padEnd(6)} ${m.note ?? ""}`);
|
|
22
37
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
38
|
+
if (t === "claude") {
|
|
39
|
+
yield* Console.log(
|
|
40
|
+
" (full claude-* model IDs outside the catalog are accepted with a warning)",
|
|
41
|
+
);
|
|
42
|
+
} else if (t === "codex") {
|
|
43
|
+
yield* Console.log(" (well-formed IDs outside the catalog are accepted with a warning)");
|
|
44
|
+
}
|
|
28
45
|
yield* Console.log("");
|
|
29
46
|
});
|
|
30
47
|
|
|
31
|
-
export const modelsCommand = Command.make("models", { tool, json }, (config) =>
|
|
48
|
+
export const modelsCommand = Command.make("models", { tool, json, refresh }, (config) =>
|
|
32
49
|
Effect.gen(function* () {
|
|
33
50
|
const requested = Option.getOrUndefined(config.tool);
|
|
34
|
-
if (requested !== undefined && requested
|
|
35
|
-
return yield* bail(`Unknown tool '${requested}' (valid: claude, codex)`);
|
|
36
|
-
}
|
|
37
|
-
const tools: Array<Tool> = requested !== undefined ? [requested as Tool] : ["claude", "codex"];
|
|
38
|
-
|
|
39
|
-
if (config.json) {
|
|
40
|
-
const out = Object.fromEntries(tools.map((t) => [t, modelCatalog(t)]));
|
|
41
|
-
return yield* Console.log(JSON.stringify(out, null, 2));
|
|
51
|
+
if (requested !== undefined && !isCatalogTool(requested)) {
|
|
52
|
+
return yield* bail(`Unknown tool '${requested}' (valid: claude, codex, omp)`);
|
|
42
53
|
}
|
|
43
|
-
|
|
54
|
+
const home = engineHome(process.env);
|
|
55
|
+
const tools: ReadonlyArray<CatalogTool> =
|
|
56
|
+
requested !== undefined
|
|
57
|
+
? [requested]
|
|
58
|
+
: (readHarnessSelection(home) ?? LEGACY_SELECTION).filter(isCatalogTool);
|
|
59
|
+
const inputs = defaultLiveInputs(home, config.refresh);
|
|
60
|
+
const out: Partial<Record<CatalogTool, ResolvedCatalog>> = {};
|
|
44
61
|
for (const t of tools) {
|
|
45
|
-
yield*
|
|
62
|
+
const catalog = yield* Effect.promise(() => resolveCatalog(t, inputs));
|
|
63
|
+
if (config.json) {
|
|
64
|
+
out[t] = catalog;
|
|
65
|
+
} else {
|
|
66
|
+
yield* renderTool(t, catalog);
|
|
67
|
+
}
|
|
46
68
|
}
|
|
69
|
+
if (config.json) yield* Console.log(JSON.stringify(out, null, 2));
|
|
47
70
|
}),
|
|
48
|
-
).pipe(
|
|
71
|
+
).pipe(
|
|
72
|
+
Command.withDescription(
|
|
73
|
+
"List models per enabled harness: live from each harness's login or cache, with SoT/models.json aliases and notes as overlay and offline fallback.",
|
|
74
|
+
),
|
|
75
|
+
);
|
|
@@ -5,7 +5,7 @@ import { bail, engine } from "../engine";
|
|
|
5
5
|
const MANAGED = ["bun"];
|
|
6
6
|
|
|
7
7
|
const op = Argument.String("op").pipe(
|
|
8
|
-
Argument.withDescription("check (default) | ensure <tool>"),
|
|
8
|
+
Argument.withDescription("check (default) | ensure <tool> | outdated"),
|
|
9
9
|
Argument.optional,
|
|
10
10
|
);
|
|
11
11
|
const tool = Argument.String("tool").pipe(
|
|
@@ -17,28 +17,46 @@ const verbose = Flag.Boolean("verbose").pipe(
|
|
|
17
17
|
Flag.withDescription("Also print no-op confirmations (present, up to date)"),
|
|
18
18
|
Flag.withDefault(false),
|
|
19
19
|
);
|
|
20
|
+
const refresh = Flag.Boolean("refresh").pipe(
|
|
21
|
+
Flag.withDescription("outdated: bypass the 24 h cache"),
|
|
22
|
+
Flag.withDefault(false),
|
|
23
|
+
);
|
|
20
24
|
|
|
21
|
-
export const toolchainCommand = Command.make(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
export const toolchainCommand = Command.make(
|
|
26
|
+
"toolchain",
|
|
27
|
+
{ op, tool, verbose, refresh },
|
|
28
|
+
(config) =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const operation = Option.getOrElse(config.op, () => "check");
|
|
31
|
+
const flags = config.verbose ? ["--verbose"] : [];
|
|
32
|
+
const operand = Option.getOrUndefined(config.tool);
|
|
33
|
+
const words = operand === undefined ? [] : [operand];
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
switch (operation) {
|
|
36
|
+
case "check":
|
|
37
|
+
return yield* engine(["toolchain", "check", ...words, ...flags]);
|
|
38
|
+
case "ensure": {
|
|
39
|
+
const t = Option.getOrUndefined(config.tool);
|
|
40
|
+
if (t === undefined || !MANAGED.includes(t)) {
|
|
41
|
+
return yield* bail(`toolchain ensure needs a managed tool: ${MANAGED.join(", ")}`);
|
|
42
|
+
}
|
|
43
|
+
return yield* engine(["toolchain", "ensure", t, ...flags]);
|
|
33
44
|
}
|
|
34
|
-
|
|
45
|
+
case "outdated":
|
|
46
|
+
return yield* engine([
|
|
47
|
+
"toolchain",
|
|
48
|
+
"outdated",
|
|
49
|
+
...words,
|
|
50
|
+
...(config.refresh ? ["--refresh"] : []),
|
|
51
|
+
]);
|
|
52
|
+
default:
|
|
53
|
+
return yield* bail(
|
|
54
|
+
`Unknown toolchain op '${operation}' (valid: check, ensure, outdated)`,
|
|
55
|
+
);
|
|
35
56
|
}
|
|
36
|
-
|
|
37
|
-
return yield* bail(`Unknown toolchain op '${operation}' (valid: check, ensure)`);
|
|
38
|
-
}
|
|
39
|
-
}),
|
|
57
|
+
}),
|
|
40
58
|
).pipe(
|
|
41
59
|
Command.withDescription(
|
|
42
|
-
"Verified-version floors for external tools (SoT/toolchain.json): check prints the doctor table; ensure installs one managed tool when it is missing.",
|
|
60
|
+
"Verified-version floors for external tools (SoT/toolchain.json): check prints the doctor table; ensure installs one managed tool when it is missing; outdated compares verified pins with the newest upstream release (network, report only).",
|
|
43
61
|
),
|
|
44
62
|
);
|
|
@@ -60,9 +60,8 @@ const capturePackageRoot: CapturePackageRoot = (command, args) => {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* A Bun global home is `<root>/.bun/install/global/node_modules/<pkg>`. Windows
|
|
63
|
-
* reports that path with backslashes
|
|
64
|
-
*
|
|
65
|
-
* character and must never be read as a separator there.
|
|
63
|
+
* reports that path with backslashes and compares it without case sensitivity.
|
|
64
|
+
* On POSIX, a backslash remains a legal filename character, not a separator.
|
|
66
65
|
*/
|
|
67
66
|
export const packageManagerForHome = (
|
|
68
67
|
home: string,
|
|
@@ -70,13 +69,14 @@ export const packageManagerForHome = (
|
|
|
70
69
|
host: HostOs = hostOs(),
|
|
71
70
|
): PackageManager => {
|
|
72
71
|
const normalize = (value: string): string =>
|
|
73
|
-
host.id === "windows" ? value.replaceAll("\\", "/") : value;
|
|
72
|
+
host.id === "windows" ? value.replaceAll("\\", "/").toLowerCase() : value;
|
|
74
73
|
const normalizedHome = normalize(home);
|
|
75
74
|
const underEnvironmentRoot = (name: "BUN_INSTALL_GLOBAL_DIR" | "BUN_INSTALL"): boolean => {
|
|
76
75
|
const root = environment[name]?.trim();
|
|
77
76
|
if (root === undefined || root === "") return false;
|
|
78
|
-
const normalizedRoot = normalize(root);
|
|
79
|
-
|
|
77
|
+
const normalizedRoot = normalize(root).replace(/\/+$/, "") || "/";
|
|
78
|
+
const prefix = normalizedRoot.endsWith("/") ? normalizedRoot : `${normalizedRoot}/`;
|
|
79
|
+
return normalizedHome === normalizedRoot || normalizedHome.startsWith(prefix);
|
|
80
80
|
};
|
|
81
81
|
return normalizedHome.includes("/.bun/") ||
|
|
82
82
|
underEnvironmentRoot("BUN_INSTALL_GLOBAL_DIR") ||
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
import { payloadText } from "../payload";
|
|
20
20
|
import type { JsonObject } from "./sharedTypes";
|
|
21
21
|
|
|
22
|
+
/** Claude Code's built-in marketplace: always present, never added or pruned by the kit. */
|
|
23
|
+
const OFFICIAL_MARKETPLACE = "claude-plugins-official";
|
|
24
|
+
|
|
22
25
|
export async function cli(
|
|
23
26
|
args: Array<string>,
|
|
24
27
|
): Promise<{ ok: boolean; out: string; detail: string }> {
|
|
@@ -113,20 +116,31 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
|
|
|
113
116
|
? repoObj["extraKnownMarketplaces"]
|
|
114
117
|
: {};
|
|
115
118
|
const sotPlugins = isObject(repoObj["enabledPlugins"]) ? repoObj["enabledPlugins"] : {};
|
|
116
|
-
|
|
117
|
-
// Pass 1 — add missing marketplaces (SoT insertion order, like to_entries).
|
|
118
|
-
let addedMp = 0;
|
|
119
|
-
let f1 = 0;
|
|
120
|
-
for (const [mpName, mpValue] of Object.entries(sotMarketplaces)) {
|
|
119
|
+
const knownMarketplace = (mpName: string): boolean => {
|
|
121
120
|
const known = readJsonFile(knownMarketplaces);
|
|
122
|
-
|
|
121
|
+
return (
|
|
123
122
|
known !== undefined &&
|
|
124
123
|
isObject(known) &&
|
|
125
124
|
known[mpName] !== undefined &&
|
|
126
125
|
known[mpName] !== null &&
|
|
127
126
|
known[mpName] !== false
|
|
128
|
-
)
|
|
129
|
-
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
const addedMarketplaces = new Set<string>();
|
|
130
|
+
// The official marketplace is built in; a successful add is usable before its inventory is written.
|
|
131
|
+
// A missing inventory proves absence (fresh home); an unreadable one does not, so refresh as before.
|
|
132
|
+
const marketplaceReady = (mpName: string): boolean => {
|
|
133
|
+
if (mpName === OFFICIAL_MARKETPLACE || addedMarketplaces.has(mpName)) return true;
|
|
134
|
+
if (!existsSync(knownMarketplaces)) return false;
|
|
135
|
+
const known = readJsonFile(knownMarketplaces);
|
|
136
|
+
return known === undefined || !isObject(known) || knownMarketplace(mpName);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Pass 1 — add missing marketplaces (SoT insertion order, like to_entries).
|
|
140
|
+
let addedMp = 0;
|
|
141
|
+
let f1 = 0;
|
|
142
|
+
for (const [mpName, mpValue] of Object.entries(sotMarketplaces)) {
|
|
143
|
+
if (knownMarketplace(mpName)) continue;
|
|
130
144
|
const repo =
|
|
131
145
|
isObject(mpValue) && isObject(mpValue["source"])
|
|
132
146
|
? String((mpValue["source"] as JsonObject)["repo"] ?? "")
|
|
@@ -136,6 +150,7 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
|
|
|
136
150
|
clearProgress();
|
|
137
151
|
if (marketplaceResult.ok) {
|
|
138
152
|
addedMp++;
|
|
153
|
+
addedMarketplaces.add(mpName);
|
|
139
154
|
} else {
|
|
140
155
|
recordFailure(
|
|
141
156
|
ctx,
|
|
@@ -156,7 +171,7 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
|
|
|
156
171
|
if (pluginUserScopeInstalled(installedPlugins, pluginId)) continue;
|
|
157
172
|
const separator = pluginId.lastIndexOf("@");
|
|
158
173
|
const mpName = separator > 0 ? pluginId.slice(separator + 1) : "";
|
|
159
|
-
if (mpName !== "" && !refreshedMarketplaces.has(mpName)) {
|
|
174
|
+
if (mpName !== "" && marketplaceReady(mpName) && !refreshedMarketplaces.has(mpName)) {
|
|
160
175
|
progress(`Refreshing marketplace ${mpName}...`);
|
|
161
176
|
const refreshResult = await cli(["plugin", "marketplace", "update", mpName]);
|
|
162
177
|
clearProgress();
|
|
@@ -207,7 +222,7 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
|
|
|
207
222
|
// would duplicate one failure in the ledger and in the failed-operation count.
|
|
208
223
|
if (!ctx.skipPluginRefresh) {
|
|
209
224
|
for (const mpName of [...kitMarketplaces].sort(compareCodepoints)) {
|
|
210
|
-
if (refreshedMarketplaces.has(mpName)) continue;
|
|
225
|
+
if (refreshedMarketplaces.has(mpName) || !marketplaceReady(mpName)) continue;
|
|
211
226
|
progress(`Refreshing marketplace ${mpName}...`);
|
|
212
227
|
const refreshResult = await cli(["plugin", "marketplace", "update", mpName]);
|
|
213
228
|
clearProgress();
|
|
@@ -263,7 +278,7 @@ export async function syncPlugins(ctx: Ctx, claudeDir: string): Promise<void> {
|
|
|
263
278
|
// Pass 6 — prune-gated marketplace removal.
|
|
264
279
|
const known = readJsonFile(knownMarketplaces);
|
|
265
280
|
for (const mpName of sortedKeys(known)) {
|
|
266
|
-
if (mpName ===
|
|
281
|
+
if (mpName === OFFICIAL_MARKETPLACE) continue;
|
|
267
282
|
if (nonUserMarketplaces.has(mpName)) continue;
|
|
268
283
|
if (kitMarketplaces.has(mpName)) continue;
|
|
269
284
|
progress(`Removing marketplace ${mpName}...`);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
|
|
9
9
|
import { payloadDisplayPath } from "../payload";
|
|
10
|
-
import { mergeTableSettings, mergeTopLevelSettings } from "./codexToml";
|
|
10
|
+
import { isTomlHeaderLine, mergeTableSettings, mergeTopLevelSettings } from "./codexToml";
|
|
11
11
|
import type { Ctx } from "./index";
|
|
12
12
|
|
|
13
13
|
// --------------------------------------------------------------- config ----
|
|
@@ -68,14 +68,14 @@ function scrubDeprecatedFeaturesText(content: string): string {
|
|
|
68
68
|
let changed = false;
|
|
69
69
|
for (const line of lines) {
|
|
70
70
|
if (inFeatures) {
|
|
71
|
-
if (line
|
|
71
|
+
if (isTomlHeaderLine(line)) {
|
|
72
72
|
inFeatures = false;
|
|
73
73
|
if (keep) out += `${header}\n${body}`;
|
|
74
74
|
else changed = true;
|
|
75
75
|
out += `${line}\n`;
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
|
-
if (/^use_legacy_landlock[ \t]*=/.test(line)) {
|
|
78
|
+
if (/^[ \t]*use_legacy_landlock[ \t]*=/.test(line)) {
|
|
79
79
|
changed = true;
|
|
80
80
|
continue;
|
|
81
81
|
}
|
|
@@ -83,7 +83,7 @@ function scrubDeprecatedFeaturesText(content: string): string {
|
|
|
83
83
|
if (/[^ \t\f\v\r]/.test(line)) keep = true;
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
86
|
-
if (
|
|
86
|
+
if (/^[ \t]*\[features\][ \t]*$/.test(line)) {
|
|
87
87
|
inFeatures = true;
|
|
88
88
|
header = line;
|
|
89
89
|
body = "";
|
|
@@ -111,7 +111,7 @@ function scrubDeprecatedFeatures(ctx: Ctx, userConfig: string): void {
|
|
|
111
111
|
change("Codex: scrubbed deprecated [features].use_legacy_landlock");
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export const PLUGIN_TABLE_HEADER =
|
|
114
|
+
export const PLUGIN_TABLE_HEADER = /^[ \t]*\[plugins\."([^"]+)"\][ \t]*$/;
|
|
115
115
|
/** Plugin ids the kit retired; their deployed tables are stripped on every sync. */
|
|
116
116
|
const RETIRED_PLUGIN_IDS: Readonly<Record<string, true>> = {
|
|
117
117
|
"effect-kit@docks": true,
|
|
@@ -130,7 +130,7 @@ export function removeRetiredPluginTablesText(content: string): string {
|
|
|
130
130
|
skipping = RETIRED_PLUGIN_IDS[header[1]!] === true;
|
|
131
131
|
if (skipping) continue;
|
|
132
132
|
} else if (skipping) {
|
|
133
|
-
if (!line
|
|
133
|
+
if (!isTomlHeaderLine(line)) continue;
|
|
134
134
|
skipping = false;
|
|
135
135
|
}
|
|
136
136
|
out += `${line}\n`;
|