docks-kit 0.1.5 → 0.3.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 +1 -1
- package/README.md +7 -5
- package/cli/docs/flags.md +1 -0
- package/cli/docs/install.md +9 -11
- package/cli/docs/overview.md +6 -0
- package/cli/docs/platforms.md +9 -10
- package/cli/src/commands/model.ts +7 -3
- package/cli/src/commands/sync.ts +14 -3
- package/cli/src/commands/toolchain.ts +7 -3
- package/cli/src/engine-native/DESIGN.md +79 -2
- package/cli/src/engine-native/claudeModel.ts +9 -3
- package/cli/src/engine-native/claudeSync.ts +220 -116
- package/cli/src/engine-native/codexSync.ts +131 -77
- package/cli/src/engine-native/codexToml.ts +12 -5
- package/cli/src/engine-native/deps.ts +325 -0
- package/cli/src/engine-native/exec.ts +35 -2
- package/cli/src/engine-native/index.ts +48 -13
- package/cli/src/engine-native/logger.ts +35 -0
- package/cli/src/engine-native/models.ts +22 -23
- package/cli/src/engine-native/modes.ts +30 -14
- package/cli/src/engine-native/os.ts +29 -0
- package/cli/src/engine-native/parseArgs.ts +19 -17
- package/cli/src/engine-native/services.ts +96 -0
- package/cli/src/engine-native/skillsSync.ts +77 -61
- package/cli/src/engine-native/toolchain.ts +50 -68
- package/cli/src/engine.ts +11 -2
- package/cli/src/generated/sotPayload.ts +41 -0
- package/cli/src/kitHome.ts +15 -11
- package/cli/src/main.ts +3 -2
- package/cli/src/manifests.ts +17 -15
- package/cli/src/payload.ts +28 -0
- package/cli/src/services.ts +34 -0
- package/docks-kit +6 -6
- package/package.json +2 -3
- package/SoT/.agents/skills.txt +0 -14
- package/SoT/.claude/CLAUDE.md +0 -146
- package/SoT/.claude/fetch-usage.sh +0 -66
- package/SoT/.claude/hooks/notify.sh +0 -14
- package/SoT/.claude/mcp-servers.json +0 -10
- package/SoT/.claude/settings.json +0 -235
- package/SoT/.claude/statusline.sh +0 -175
- package/SoT/.codex/AGENTS.md +0 -75
- package/SoT/.codex/agents/.gitkeep +0 -1
- package/SoT/.codex/config.toml +0 -45
- package/SoT/.codex/plugins/marketplace.json +0 -50
- package/SoT/.codex/rules/docks.rules +0 -116
- package/SoT/models.json +0 -28
- package/SoT/toolchain.json +0 -27
- package/cli/src/engine-native/output.ts +0 -20
- package/notification.mp3 +0 -0
|
@@ -4,23 +4,26 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { p } from "./exec"
|
|
6
6
|
import { readFileSync } from "node:fs"
|
|
7
|
+
import { payloadText } from "../payload"
|
|
7
8
|
|
|
8
9
|
import { syncClaudeModel } from "./claudeModel"
|
|
9
10
|
import { syncCodexModel } from "./codexToml"
|
|
10
11
|
import type { Ctx } from "./index"
|
|
11
12
|
import { isObject, parseJson, type Json } from "./jq"
|
|
12
13
|
import { printModels, validateClaudeModel, validateCodexModel } from "./models"
|
|
13
|
-
import { echo, err, warn } from "./output"
|
|
14
14
|
import { rtkInstall } from "./claudeSync"
|
|
15
15
|
import { agentBrowserInstall, bunBootstrap, effectSolutionsInstall } from "./skillsSync"
|
|
16
16
|
import { ensure, report } from "./toolchain"
|
|
17
17
|
|
|
18
18
|
export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
19
|
+
const { echo, err, warn } = ctx.services.logger
|
|
19
20
|
let tool = ""
|
|
20
21
|
let value = ""
|
|
21
22
|
for (const arg of args) {
|
|
22
23
|
if (arg === "--dry-run") ctx.dryRun = true
|
|
23
|
-
else if (arg === "
|
|
24
|
+
else if (arg === "--verbose") {
|
|
25
|
+
ctx.verbose = true
|
|
26
|
+
} else if (arg === "claude" || arg === "codex") tool = arg
|
|
24
27
|
else if (arg.startsWith("-")) {
|
|
25
28
|
err(`Unknown flag for model: ${arg}`)
|
|
26
29
|
return 2
|
|
@@ -39,7 +42,7 @@ export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
39
42
|
return 0
|
|
40
43
|
}
|
|
41
44
|
echo(`deployed: ${jsonModelField(deployed)}`)
|
|
42
|
-
echo(`SoT: ${
|
|
45
|
+
echo(`SoT: ${jsonModelText(payloadText("SoT/.claude/settings.json"))}`)
|
|
43
46
|
} else {
|
|
44
47
|
const deployed = p(ctx.home, ".codex", "config.toml")
|
|
45
48
|
if (!fileReadable(deployed)) {
|
|
@@ -47,22 +50,22 @@ export function modeModel(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
47
50
|
return 0
|
|
48
51
|
}
|
|
49
52
|
echo(`deployed: ${tomlModelField(deployed)}`)
|
|
50
|
-
echo(`SoT: ${
|
|
53
|
+
echo(`SoT: ${tomlModelText(payloadText("SoT/.codex/config.toml"))}`)
|
|
51
54
|
}
|
|
52
|
-
printModels(ctx
|
|
55
|
+
printModels(ctx, tool)
|
|
53
56
|
return 0
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
if (tool === "claude") {
|
|
57
|
-
if (!validateClaudeModel(ctx
|
|
58
|
-
printModels(ctx
|
|
60
|
+
if (!validateClaudeModel(ctx, value)) {
|
|
61
|
+
printModels(ctx, "claude")
|
|
59
62
|
err(`Invalid Claude model '${value}'`)
|
|
60
63
|
return 2
|
|
61
64
|
}
|
|
62
65
|
syncClaudeModel(ctx, value)
|
|
63
66
|
} else {
|
|
64
|
-
if (!validateCodexModel(ctx
|
|
65
|
-
printModels(ctx
|
|
67
|
+
if (!validateCodexModel(ctx, value)) {
|
|
68
|
+
printModels(ctx, "codex")
|
|
66
69
|
err(`Invalid Codex model '${value}'`)
|
|
67
70
|
return 2
|
|
68
71
|
}
|
|
@@ -82,7 +85,11 @@ function fileReadable(p: string): boolean {
|
|
|
82
85
|
|
|
83
86
|
/** `jq -r '.model // "default (unset)"'` — empty on unparseable input. */
|
|
84
87
|
function jsonModelField(file: string): string {
|
|
85
|
-
|
|
88
|
+
return jsonModelText(readFileSync(file, "utf8"))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function jsonModelText(text: string): string {
|
|
92
|
+
const doc = parseJson(text)
|
|
86
93
|
if (doc === undefined) return ""
|
|
87
94
|
const v: Json | undefined = isObject(doc) ? doc["model"] : undefined
|
|
88
95
|
if (v === undefined || v === null || v === false) return "default (unset)"
|
|
@@ -91,17 +98,26 @@ function jsonModelField(file: string): string {
|
|
|
91
98
|
|
|
92
99
|
/** `awk -F'"' '/^model[[:space:]]*=/{print $2; exit}'`. */
|
|
93
100
|
function tomlModelField(file: string): string {
|
|
94
|
-
|
|
101
|
+
return tomlModelText(readFileSync(file, "utf8"))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function tomlModelText(text: string): string {
|
|
105
|
+
for (const line of text.split("\n")) {
|
|
95
106
|
if (/^model[ \t]*=/.test(line)) return line.split('"')[1] ?? ""
|
|
96
107
|
}
|
|
97
108
|
return ""
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
export function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
112
|
+
const { err } = ctx.services.logger
|
|
113
|
+
const words = args.filter((a) => !a.startsWith("--"))
|
|
114
|
+
const op = words[0] ?? args[0] ?? "check"
|
|
115
|
+
const tool = words[1] ?? args[1] ?? ""
|
|
103
116
|
for (const arg of args) {
|
|
104
117
|
if (arg === "--yes") ctx.assumeYes = true
|
|
118
|
+
else if (arg === "--verbose") {
|
|
119
|
+
ctx.verbose = true
|
|
120
|
+
}
|
|
105
121
|
}
|
|
106
122
|
|
|
107
123
|
if (op === "check") {
|
|
@@ -121,7 +137,7 @@ export function modeToolchain(ctx: Ctx, args: ReadonlyArray<string>): number {
|
|
|
121
137
|
return ensure(ctx, "rtk", rtkInstall(ctx))
|
|
122
138
|
case "bun":
|
|
123
139
|
// skills::_bun_bootstrap >/dev/null — the found-bun stdout is discarded.
|
|
124
|
-
return bunBootstrap(ctx) !== "" ? 0 : 1
|
|
140
|
+
return bunBootstrap(ctx, ctx.services) !== "" ? 0 : 1
|
|
125
141
|
case "effect-solutions":
|
|
126
142
|
return ensure(ctx, "effect-solutions", effectSolutionsInstall(ctx))
|
|
127
143
|
case "agent-browser":
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform capability seam (Output Policy in DESIGN.md) — the only engine
|
|
3
|
+
* module that reads process.platform besides exec.ts's PATH/executability
|
|
4
|
+
* primitives. Per-tool package identifiers stay in deps.ts; symlink handling
|
|
5
|
+
* stays try-then-fallback at the call site (capability-driven, not predicted).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type PlatformName = "linux" | "darwin" | "windows" | "unknown"
|
|
9
|
+
|
|
10
|
+
export function rawPlatform(): NodeJS.Platform {
|
|
11
|
+
return process.platform
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function platformName(pf: NodeJS.Platform = rawPlatform()): PlatformName {
|
|
15
|
+
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" : pf === "win32" ? "windows" : "unknown"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isWindows(): boolean {
|
|
19
|
+
return rawPlatform() === "win32"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isLinux(): boolean {
|
|
23
|
+
return rawPlatform() === "linux"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Shell-rc exports (bashrc/zshrc) apply only off Windows. */
|
|
27
|
+
export function shellRcApplicable(): boolean {
|
|
28
|
+
return !isWindows()
|
|
29
|
+
}
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* early parser exit and is caught once in runEngineNative.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { commandExists } from "./exec"
|
|
8
7
|
import type { Ctx } from "./index"
|
|
9
8
|
import { printModels, validateClaudeModel, validateCodexModel } from "./models"
|
|
10
|
-
import { echo, err, warn } from "./output"
|
|
11
9
|
|
|
12
10
|
export class ExitError extends Error {
|
|
13
11
|
constructor(readonly code: number) {
|
|
@@ -18,6 +16,7 @@ export class ExitError extends Error {
|
|
|
18
16
|
const KNOWN_CLAUDE_OPTIN_PLUGINS = ["supabase", "n8n"]
|
|
19
17
|
|
|
20
18
|
function usage(ctx: Ctx): void {
|
|
19
|
+
const { echo } = ctx.services.logger
|
|
21
20
|
const argv0 = "docks-kit sync"
|
|
22
21
|
echo(`Usage: ${argv0} [claude] [codex] [agents] [flags]`)
|
|
23
22
|
echo("")
|
|
@@ -36,6 +35,7 @@ function usage(ctx: Ctx): void {
|
|
|
36
35
|
)
|
|
37
36
|
echo(" --skip-rtk skip optional tool bootstrap (RTK, bubblewrap)")
|
|
38
37
|
echo(" --yes auto-accept toolchain prompts (containers/CI)")
|
|
38
|
+
echo(" --verbose also print no-op confirmations (already in sync, up to date, left as-is)")
|
|
39
39
|
echo("")
|
|
40
40
|
echo("Deploy-time modifiers (deployed config only; SoT untouched; a later flag-less sync reverts)")
|
|
41
41
|
echo(
|
|
@@ -66,6 +66,7 @@ export function parseCompactWindow(v: string): string | undefined {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function addClaudePlugin(ctx: Ctx, name: string): void {
|
|
69
|
+
const { err } = ctx.services.logger
|
|
69
70
|
if (!KNOWN_CLAUDE_OPTIN_PLUGINS.includes(name)) {
|
|
70
71
|
err(`Unknown opt-in plugin '${name}'. Known: ${KNOWN_CLAUDE_OPTIN_PLUGINS.join(", ")}`)
|
|
71
72
|
throw new ExitError(2)
|
|
@@ -81,6 +82,7 @@ function selectTarget(ctx: Ctx, target: string): void {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
85
|
+
const { err } = ctx.services.logger
|
|
84
86
|
for (const arg of args) {
|
|
85
87
|
switch (arg) {
|
|
86
88
|
case "claude":
|
|
@@ -103,12 +105,15 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
|
103
105
|
case "--yes":
|
|
104
106
|
ctx.assumeYes = true
|
|
105
107
|
continue
|
|
108
|
+
case "--verbose":
|
|
109
|
+
ctx.verbose = true
|
|
110
|
+
continue
|
|
106
111
|
case "--claude-model":
|
|
107
|
-
printModels(ctx
|
|
112
|
+
printModels(ctx, "claude")
|
|
108
113
|
err("--claude-model requires a value: --claude-model=<model>")
|
|
109
114
|
throw new ExitError(2)
|
|
110
115
|
case "--codex-model":
|
|
111
|
-
printModels(ctx
|
|
116
|
+
printModels(ctx, "codex")
|
|
112
117
|
err("--codex-model requires a value: --codex-model=<model>")
|
|
113
118
|
throw new ExitError(2)
|
|
114
119
|
case "--claude-compact-window":
|
|
@@ -180,32 +185,29 @@ export function parseArgs(ctx: Ctx, args: ReadonlyArray<string>): void {
|
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
export function preflight(ctx: Ctx): void {
|
|
188
|
+
const { err } = ctx.services.logger
|
|
183
189
|
if (ctx.syncClaude || ctx.syncCodex) {
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
? "winget install jqlang.jq (then open a new terminal)"
|
|
187
|
-
: process.platform === "darwin"
|
|
188
|
-
? "brew install jq"
|
|
189
|
-
: "sudo apt install -y jq"
|
|
190
|
-
err(`jq is required (deployed statusline/hooks call it). Install: ${hint}`)
|
|
190
|
+
if (ctx.services.deps.probe("jq").state === "missing") {
|
|
191
|
+
err(`jq is required (deployed statusline/hooks call it). Install: ${ctx.services.deps.spec("jq").installHint()}`)
|
|
191
192
|
throw new ExitError(1)
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
if (ctx.syncClaude) {
|
|
195
|
-
if (
|
|
196
|
-
err(
|
|
196
|
+
if (ctx.services.deps.probe("curl").state === "missing") {
|
|
197
|
+
err(`curl is required. Install: ${ctx.services.deps.spec("curl").installHint()}`)
|
|
197
198
|
throw new ExitError(1)
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
export function validateModelFlags(ctx: Ctx): void {
|
|
204
|
+
const { err, warn } = ctx.services.logger
|
|
203
205
|
if (ctx.claudeModel !== "") {
|
|
204
206
|
if (!ctx.syncClaude) {
|
|
205
207
|
warn("--claude-model ignored: claude target not selected")
|
|
206
208
|
ctx.claudeModel = ""
|
|
207
|
-
} else if (!validateClaudeModel(ctx
|
|
208
|
-
printModels(ctx
|
|
209
|
+
} else if (!validateClaudeModel(ctx, ctx.claudeModel)) {
|
|
210
|
+
printModels(ctx, "claude")
|
|
209
211
|
err(`Invalid Claude model '${ctx.claudeModel}' — use an alias above or a full claude-* ID`)
|
|
210
212
|
throw new ExitError(2)
|
|
211
213
|
}
|
|
@@ -214,8 +216,8 @@ export function validateModelFlags(ctx: Ctx): void {
|
|
|
214
216
|
if (!ctx.syncCodex) {
|
|
215
217
|
warn("--codex-model ignored: codex target not selected")
|
|
216
218
|
ctx.codexModel = ""
|
|
217
|
-
} else if (!validateCodexModel(ctx
|
|
218
|
-
printModels(ctx
|
|
219
|
+
} else if (!validateCodexModel(ctx, ctx.codexModel)) {
|
|
220
|
+
printModels(ctx, "codex")
|
|
219
221
|
err(`Invalid Codex model '${ctx.codexModel}' — must match ^[A-Za-z0-9._-]+$`)
|
|
220
222
|
throw new ExitError(2)
|
|
221
223
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared service factory — the single construction point for the engine's
|
|
3
|
+
* injectable capabilities (Output Policy in DESIGN.md). The Effect rim wraps
|
|
4
|
+
* these in Layers (cli/src/services.ts); the harness-private native-raw entry
|
|
5
|
+
* and engineCapture's parent-side warn call it directly, so every execution
|
|
6
|
+
* path shares one implementation.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
DEPENDENCIES,
|
|
10
|
+
defaultProbeExecutor,
|
|
11
|
+
resolveDependency,
|
|
12
|
+
resolveLocation,
|
|
13
|
+
resolvePath,
|
|
14
|
+
resolveVersion,
|
|
15
|
+
type DependencySpec,
|
|
16
|
+
type DependencyLocation,
|
|
17
|
+
type ProbeExecutor,
|
|
18
|
+
type ProbeResult,
|
|
19
|
+
type ToolId
|
|
20
|
+
} from "./deps"
|
|
21
|
+
import { makeLogger, type Logger, type LoggerSinks } from "./logger"
|
|
22
|
+
import { platformName, rawPlatform, type PlatformName } from "./os"
|
|
23
|
+
|
|
24
|
+
export type { Logger } from "./logger"
|
|
25
|
+
|
|
26
|
+
export interface DependencyManager {
|
|
27
|
+
readonly spec: (id: ToolId) => DependencySpec
|
|
28
|
+
readonly probe: (id: ToolId) => ProbeResult
|
|
29
|
+
readonly version: (id: ToolId) => string
|
|
30
|
+
readonly path: (id: ToolId) => string
|
|
31
|
+
readonly location: (id: ToolId) => DependencyLocation
|
|
32
|
+
readonly latest: (id: ToolId) => string
|
|
33
|
+
readonly warnMissing: (id: ToolId, logger: Logger, context?: string) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Platform {
|
|
37
|
+
readonly raw: () => NodeJS.Platform
|
|
38
|
+
readonly name: () => PlatformName
|
|
39
|
+
readonly isWindows: () => boolean
|
|
40
|
+
readonly isLinux: () => boolean
|
|
41
|
+
readonly shellRcApplicable: () => boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EngineServices {
|
|
45
|
+
readonly logger: Logger
|
|
46
|
+
readonly deps: DependencyManager
|
|
47
|
+
readonly platform: Platform
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EngineServiceOptions {
|
|
51
|
+
readonly sinks?: LoggerSinks
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Platform view over an injectable platform id (tests pass e.g. "win32"). */
|
|
55
|
+
export const makePlatform = (pf: NodeJS.Platform = rawPlatform()): Platform => ({
|
|
56
|
+
raw: () => pf,
|
|
57
|
+
name: () => platformName(pf),
|
|
58
|
+
isWindows: () => pf === "win32",
|
|
59
|
+
isLinux: () => pf === "linux",
|
|
60
|
+
shellRcApplicable: () => pf !== "win32"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/** DependencyManager whose hints default to the INJECTED platform, not the host. */
|
|
64
|
+
export const makeDependencyManager = (
|
|
65
|
+
platform: Platform,
|
|
66
|
+
exec: ProbeExecutor = defaultProbeExecutor
|
|
67
|
+
): DependencyManager => {
|
|
68
|
+
const warned = new Set<ToolId>()
|
|
69
|
+
return {
|
|
70
|
+
spec: (id) => {
|
|
71
|
+
const s = DEPENDENCIES[id]
|
|
72
|
+
return { ...s, installHint: (pf = platform.raw()) => s.installHint(pf) }
|
|
73
|
+
},
|
|
74
|
+
probe: (id) => resolveDependency(DEPENDENCIES[id], exec, platform.raw()),
|
|
75
|
+
version: (id) => resolveVersion(DEPENDENCIES[id], exec),
|
|
76
|
+
path: (id) => resolvePath(DEPENDENCIES[id], exec, platform.raw()),
|
|
77
|
+
location: (id) => resolveLocation(DEPENDENCIES[id], exec, platform.raw()),
|
|
78
|
+
latest: (id) => DEPENDENCIES[id].latest?.(exec) ?? "",
|
|
79
|
+
warnMissing: (id, logger, context) => {
|
|
80
|
+
if (warned.has(id)) return
|
|
81
|
+
warned.add(id)
|
|
82
|
+
const suffix = context !== undefined && context !== "" ? ` (${context})` : ""
|
|
83
|
+
logger.warn(`${id} not installed — ${DEPENDENCIES[id].installHint(platform.raw())}${suffix}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const makeEngineServices = (opts?: EngineServiceOptions): EngineServices => {
|
|
89
|
+
const platform = makePlatform()
|
|
90
|
+
const logger = makeLogger(opts?.sinks ?? {})
|
|
91
|
+
return {
|
|
92
|
+
logger,
|
|
93
|
+
deps: makeDependencyManager(platform),
|
|
94
|
+
platform
|
|
95
|
+
}
|
|
96
|
+
}
|