docks-kit 0.1.5 → 0.2.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/README.md +1 -1
- package/SoT/.codex/config.toml +1 -1
- package/SoT/models.json +5 -2
- package/cli/docs/flags.md +1 -0
- package/cli/docs/overview.md +3 -0
- package/cli/docs/platforms.md +6 -5
- 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 +75 -2
- package/cli/src/engine-native/claudeModel.ts +9 -3
- package/cli/src/engine-native/claudeSync.ts +203 -97
- package/cli/src/engine-native/codexSync.ts +95 -47
- 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 +50 -2
- package/cli/src/engine-native/index.ts +45 -9
- package/cli/src/engine-native/logger.ts +35 -0
- package/cli/src/engine-native/models.ts +13 -12
- package/cli/src/engine-native/modes.ts +17 -10
- 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 +70 -51
- package/cli/src/engine-native/toolchain.ts +45 -63
- package/cli/src/engine.ts +11 -2
- package/cli/src/main.ts +3 -2
- package/cli/src/services.ts +34 -0
- package/package.json +1 -1
- package/cli/src/engine-native/output.ts +0 -20
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { readFileSync, readSync } from "node:fs"
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { ToolId } from "./deps"
|
|
8
|
+
import { p } from "./exec"
|
|
8
9
|
import type { Ctx } from "./index"
|
|
9
10
|
import { compareCodepoints, isObject, parseJson, type Json } from "./jq"
|
|
10
|
-
import {
|
|
11
|
+
import type { EngineServices } from "./services"
|
|
11
12
|
|
|
12
|
-
type InstallFn = (mode: "install" | "upgrade", version: string) => number
|
|
13
|
+
type InstallFn = (mode: "install" | "upgrade", version: string, services: EngineServices) => number
|
|
13
14
|
|
|
14
15
|
function manifest(ctx: Ctx): { [k: string]: Json } {
|
|
15
16
|
const doc = parseJson(readFileSync(p(ctx.repoDir, "SoT", "toolchain.json"), "utf8"))
|
|
@@ -37,15 +38,8 @@ export function isNewer(a: string, b: string): boolean {
|
|
|
37
38
|
return compareCodepoints(a, b) > 0
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
export function present(ctx: Ctx, tool:
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
commandExists("bun") ||
|
|
44
|
-
isExecutable(p(process.env["BUN_INSTALL"] ?? p(ctx.home, ".bun"), "bin", "bun")) ||
|
|
45
|
-
isExecutable(p(ctx.home, ".bun", "bin", "bun"))
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
return commandExists(tool)
|
|
41
|
+
export function present(ctx: Ctx, tool: ToolId): boolean {
|
|
42
|
+
return ctx.services.deps.probe(tool).state === "present"
|
|
49
43
|
}
|
|
50
44
|
|
|
51
45
|
function firstLineField(out: string, index: number): string {
|
|
@@ -53,67 +47,51 @@ function firstLineField(out: string, index: number): string {
|
|
|
53
47
|
return fields[index === -1 ? fields.length - 1 : index] ?? ""
|
|
54
48
|
}
|
|
55
49
|
|
|
56
|
-
export function installedVersion(ctx: Ctx, tool:
|
|
57
|
-
|
|
50
|
+
export function installedVersion(ctx: Ctx, tool: ToolId): string {
|
|
51
|
+
const version = (): string => ctx.services.deps.version(tool)
|
|
58
52
|
switch (tool) {
|
|
59
53
|
case "rtk":
|
|
60
|
-
return firstLineField(
|
|
54
|
+
return firstLineField(version(), 1)
|
|
61
55
|
case "claude":
|
|
62
|
-
return firstLineField(
|
|
56
|
+
return firstLineField(version(), 0)
|
|
63
57
|
case "codex":
|
|
64
|
-
return firstLineField(capture("codex", ["--version"]), -1)
|
|
65
|
-
case "bun":
|
|
66
|
-
return commandExists("bun") ? capture("bun", ["--version"]) : capture(p(ctx.home, ".bun", "bin", "bun"), ["--version"])
|
|
67
58
|
case "agent-browser":
|
|
68
|
-
return firstLineField(
|
|
69
|
-
case "effect-solutions": {
|
|
70
|
-
const bunbin = commandExists("bun") ? "bun" : p(ctx.home, ".bun", "bin", "bun")
|
|
71
|
-
if (bunbin !== "bun" && !isExecutable(bunbin)) return ""
|
|
72
|
-
const m = /effect-solutions@([0-9][0-9.]*)/.exec(capture(bunbin, ["pm", "-g", "ls"]))
|
|
73
|
-
return m?.[1] ?? ""
|
|
74
|
-
}
|
|
59
|
+
return firstLineField(version(), -1)
|
|
75
60
|
case "git":
|
|
76
|
-
return firstLineField(
|
|
61
|
+
return firstLineField(version(), 2)
|
|
77
62
|
case "node":
|
|
78
|
-
return
|
|
79
|
-
case "npm":
|
|
80
|
-
return capture("npm", ["--version"])
|
|
63
|
+
return version().replace(/^v/, "")
|
|
81
64
|
case "jq":
|
|
82
|
-
return
|
|
65
|
+
return version().replace(/^jq-/, "")
|
|
83
66
|
case "curl":
|
|
84
|
-
return firstLineField(capture("curl", ["--version"]), 1)
|
|
85
67
|
case "tsc":
|
|
86
|
-
return firstLineField(
|
|
68
|
+
return firstLineField(version(), 1)
|
|
69
|
+
case "bun":
|
|
70
|
+
case "effect-solutions":
|
|
71
|
+
case "npm":
|
|
72
|
+
return version()
|
|
87
73
|
default:
|
|
88
74
|
return ""
|
|
89
75
|
}
|
|
90
76
|
}
|
|
91
77
|
|
|
92
|
-
export function latestVersion(tool:
|
|
93
|
-
|
|
94
|
-
case "rtk": {
|
|
95
|
-
const body = capture("curl", ["-fsSL", "--max-time", "5", "https://api.github.com/repos/rtk-ai/rtk/releases/latest"])
|
|
96
|
-
const doc = parseJson(body)
|
|
97
|
-
const tag = doc !== undefined && isObject(doc) && typeof doc["tag_name"] === "string" ? doc["tag_name"] : ""
|
|
98
|
-
return tag.replace(/^v/, "")
|
|
99
|
-
}
|
|
100
|
-
case "agent-browser":
|
|
101
|
-
case "effect-solutions":
|
|
102
|
-
return commandExists("npm") ? capture("npm", ["view", tool, "version"]) : ""
|
|
103
|
-
default:
|
|
104
|
-
return ""
|
|
105
|
-
}
|
|
78
|
+
export function latestVersion(ctx: Ctx, tool: ToolId): string {
|
|
79
|
+
return ctx.services.deps.latest(tool)
|
|
106
80
|
}
|
|
107
81
|
|
|
108
82
|
/** Blocking TTY prompt matching bash `read -r -p` (prompt on stderr). */
|
|
109
|
-
function promptLine(
|
|
110
|
-
|
|
83
|
+
export function promptLine(
|
|
84
|
+
prompt: string,
|
|
85
|
+
write: (chunk: string) => void = (chunk) => void process.stderr.write(chunk),
|
|
86
|
+
readByte: (buffer: Buffer) => number = (buffer) => readSync(0, buffer, 0, 1, null)
|
|
87
|
+
): string {
|
|
88
|
+
write(prompt)
|
|
111
89
|
const buf = Buffer.alloc(1)
|
|
112
90
|
let line = ""
|
|
113
91
|
for (;;) {
|
|
114
92
|
let n: number
|
|
115
93
|
try {
|
|
116
|
-
n =
|
|
94
|
+
n = readByte(buf)
|
|
117
95
|
} catch {
|
|
118
96
|
break
|
|
119
97
|
}
|
|
@@ -127,6 +105,7 @@ function promptLine(prompt: string): string {
|
|
|
127
105
|
|
|
128
106
|
/** toolchain::_gate — { proceed, target } ("" target = latest). */
|
|
129
107
|
function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: string): { proceed: boolean; target: string } {
|
|
108
|
+
const { warn } = ctx.services.logger
|
|
130
109
|
const verified = field(ctx, tool, "verified")
|
|
131
110
|
const pinnable = field(ctx, tool, "pinnable")
|
|
132
111
|
|
|
@@ -138,7 +117,7 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
138
117
|
}
|
|
139
118
|
|
|
140
119
|
if (process.stdin.isTTY === true) {
|
|
141
|
-
|
|
120
|
+
ctx.services.logger.warn(`${tool} ${latest} is not kit-verified (verified: ${verified}).`)
|
|
142
121
|
const answer = promptLine(`Install ${tool} ${latest} anyway? [y/N] `)
|
|
143
122
|
if (/^[yY]/.test(answer)) return { proceed: true, target: "" }
|
|
144
123
|
}
|
|
@@ -153,11 +132,12 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
153
132
|
return { proceed: false, target: "" }
|
|
154
133
|
}
|
|
155
134
|
|
|
156
|
-
export function ensure(ctx: Ctx, tool:
|
|
135
|
+
export function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): number {
|
|
136
|
+
const { echo, verbose, warn } = ctx.services.logger
|
|
157
137
|
const policy = field(ctx, tool, "policy")
|
|
158
138
|
|
|
159
139
|
if (!present(ctx, tool)) {
|
|
160
|
-
const latest = latestVersion(tool)
|
|
140
|
+
const latest = latestVersion(ctx, tool)
|
|
161
141
|
if (ctx.dryRun) {
|
|
162
142
|
echo(`[dry-run] would install ${tool} (${latest !== "" ? latest : "latest"}, gated by toolchain.json verified pin)`)
|
|
163
143
|
return 0
|
|
@@ -176,7 +156,7 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
176
156
|
if (!g.proceed) return 0
|
|
177
157
|
target = g.target
|
|
178
158
|
}
|
|
179
|
-
return installFn("install", target !== "" ? target : latest)
|
|
159
|
+
return installFn("install", target !== "" ? target : latest, ctx.services)
|
|
180
160
|
}
|
|
181
161
|
|
|
182
162
|
const installed = installedVersion(ctx, tool)
|
|
@@ -187,17 +167,17 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
187
167
|
echo(`[dry-run] ${tool} present (${installedLabel})`)
|
|
188
168
|
return 0
|
|
189
169
|
}
|
|
190
|
-
|
|
170
|
+
verbose(`${tool} present (${installedLabel})`)
|
|
191
171
|
return 0
|
|
192
172
|
}
|
|
193
173
|
|
|
194
|
-
const latest = latestVersion(tool)
|
|
174
|
+
const latest = latestVersion(ctx, tool)
|
|
195
175
|
if (latest === "") {
|
|
196
176
|
if (ctx.dryRun) {
|
|
197
177
|
echo(`[dry-run] ${tool} present (${installedLabel}); latest unknown (offline?) — no action`)
|
|
198
178
|
return 0
|
|
199
179
|
}
|
|
200
|
-
|
|
180
|
+
verbose(`${tool} present (${installedLabel}; latest unknown — no action)`)
|
|
201
181
|
return 0
|
|
202
182
|
}
|
|
203
183
|
|
|
@@ -208,14 +188,14 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
208
188
|
}
|
|
209
189
|
const g = gate(ctx, tool, "upgrade", latest)
|
|
210
190
|
if (!g.proceed) return 0
|
|
211
|
-
return installFn("upgrade", g.target !== "" ? g.target : latest)
|
|
191
|
+
return installFn("upgrade", g.target !== "" ? g.target : latest, ctx.services)
|
|
212
192
|
}
|
|
213
193
|
|
|
214
194
|
if (ctx.dryRun) {
|
|
215
195
|
echo(`[dry-run] ${tool} up to date (${installed})`)
|
|
216
196
|
return 0
|
|
217
197
|
}
|
|
218
|
-
|
|
198
|
+
verbose(`${tool} up to date (${installed})`)
|
|
219
199
|
return 0
|
|
220
200
|
}
|
|
221
201
|
|
|
@@ -225,9 +205,10 @@ function row(cells: [string, string, string, string, string, string]): string {
|
|
|
225
205
|
}
|
|
226
206
|
|
|
227
207
|
export function report(ctx: Ctx): void {
|
|
208
|
+
const { echo } = ctx.services.logger
|
|
228
209
|
echo(row(["TOOL", "KIND", "INSTALLED", "FLOOR", "VERIFIED", "STATUS"]))
|
|
229
|
-
const
|
|
230
|
-
|
|
210
|
+
const pn = ctx.services.platform.name()
|
|
211
|
+
const platformOs = pn === "unknown" ? "" : pn
|
|
231
212
|
for (const tool of Object.keys(manifest(ctx)).sort(compareCodepoints)) {
|
|
232
213
|
const os = field(ctx, tool, "os")
|
|
233
214
|
if (os !== "" && platformOs !== "" && os !== platformOs) continue
|
|
@@ -241,8 +222,9 @@ export function report(ctx: Ctx): void {
|
|
|
241
222
|
}
|
|
242
223
|
let installed: string
|
|
243
224
|
let status: string
|
|
244
|
-
|
|
245
|
-
|
|
225
|
+
const toolId = tool as ToolId
|
|
226
|
+
if (present(ctx, toolId)) {
|
|
227
|
+
installed = installedVersion(ctx, toolId)
|
|
246
228
|
status = "ok"
|
|
247
229
|
if (floor !== "" && installed !== "" && isNewer(floor, installed)) {
|
|
248
230
|
status = "below-floor"
|
package/cli/src/engine.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Console, Effect } from "effect"
|
|
2
2
|
import { spawnSync } from "node:child_process"
|
|
3
3
|
import { runEngineNative } from "./engine-native"
|
|
4
|
+
import { makeEngineServices } from "./engine-native/services"
|
|
4
5
|
import { kitHome } from "./kitHome"
|
|
6
|
+
import { DependencyManagerService, LoggerService, PlatformService } from "./services"
|
|
7
|
+
|
|
8
|
+
// Same factory as the Effect rim's live layers — this path runs outside the
|
|
9
|
+
// runtime (child-spawn capture), so it takes the services directly.
|
|
10
|
+
const services = makeEngineServices()
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* The single seam between the typed CLI and EngineNative. Engine execution
|
|
@@ -24,7 +30,10 @@ export const engine = (args: ReadonlyArray<string>) =>
|
|
|
24
30
|
if (bashEngineRequested()) {
|
|
25
31
|
yield* bail(bashRemovedMessage, 2)
|
|
26
32
|
}
|
|
27
|
-
const
|
|
33
|
+
const logger = yield* LoggerService
|
|
34
|
+
const deps = yield* DependencyManagerService
|
|
35
|
+
const platform = yield* PlatformService
|
|
36
|
+
const code = yield* Effect.sync(() => runEngineNative(args, { logger, deps, platform }))
|
|
28
37
|
if (code !== 0) {
|
|
29
38
|
yield* Effect.sync(() => process.exit(code))
|
|
30
39
|
}
|
|
@@ -43,7 +52,7 @@ export const engineCapture = (args: ReadonlyArray<string>) =>
|
|
|
43
52
|
stdio: ["ignore", "pipe", "inherit"]
|
|
44
53
|
})
|
|
45
54
|
if (res.error !== undefined || res.status !== 0) {
|
|
46
|
-
|
|
55
|
+
services.logger.warn(`engine capture failed (${args.join(" ")} exited ${res.status ?? "spawn-error"})`)
|
|
47
56
|
}
|
|
48
57
|
return res.stdout ?? ""
|
|
49
58
|
})
|
package/cli/src/main.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { Command } from "@effect/cli"
|
|
3
3
|
import { BunContext, BunRuntime } from "@effect/platform-bun"
|
|
4
|
-
import { Console, Effect } from "effect"
|
|
4
|
+
import { Console, Effect, Layer } from "effect"
|
|
5
|
+
import { EngineServicesLive } from "./services"
|
|
5
6
|
import { docsCommand } from "./commands/docs"
|
|
6
7
|
import { modelCommand } from "./commands/model"
|
|
7
8
|
import { modelsCommand } from "./commands/models"
|
|
@@ -70,4 +71,4 @@ const argv = process.argv.flatMap((a) =>
|
|
|
70
71
|
: [a]
|
|
71
72
|
)
|
|
72
73
|
|
|
73
|
-
cli(argv).pipe(Effect.provide(BunContext.layer), BunRuntime.runMain)
|
|
74
|
+
cli(argv).pipe(Effect.provide(Layer.mergeAll(BunContext.layer, EngineServicesLive)), BunRuntime.runMain)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect rim over the engine's injectable capabilities: Context.Tags with
|
|
3
|
+
* live layers built from the shared factory (engine-native/services.ts) plus
|
|
4
|
+
* named test-layer constructors. Composed ONCE at main.ts — command code
|
|
5
|
+
* accesses services via `yield*`, never by re-providing.
|
|
6
|
+
*/
|
|
7
|
+
import { Context, Layer } from "effect"
|
|
8
|
+
import { makeLogger, type Logger, type LoggerSinks } from "./engine-native/logger"
|
|
9
|
+
import { makeEngineServices, makePlatform, type DependencyManager, type Platform } from "./engine-native/services"
|
|
10
|
+
|
|
11
|
+
export class LoggerService extends Context.Tag("docks-kit/Logger")<LoggerService, Logger>() {}
|
|
12
|
+
|
|
13
|
+
export class DependencyManagerService extends Context.Tag("docks-kit/DependencyManager")<
|
|
14
|
+
DependencyManagerService,
|
|
15
|
+
DependencyManager
|
|
16
|
+
>() {}
|
|
17
|
+
|
|
18
|
+
export class PlatformService extends Context.Tag("docks-kit/Platform")<PlatformService, Platform>() {}
|
|
19
|
+
|
|
20
|
+
const live = makeEngineServices()
|
|
21
|
+
|
|
22
|
+
export const LoggerLive = Layer.succeed(LoggerService, live.logger)
|
|
23
|
+
export const DependencyManagerLive = Layer.succeed(DependencyManagerService, live.deps)
|
|
24
|
+
export const PlatformLive = Layer.succeed(PlatformService, live.platform)
|
|
25
|
+
export const EngineServicesLive = Layer.mergeAll(LoggerLive, DependencyManagerLive, PlatformLive)
|
|
26
|
+
|
|
27
|
+
export const LoggerTest = (sinks: LoggerSinks): Layer.Layer<LoggerService> =>
|
|
28
|
+
Layer.succeed(LoggerService, makeLogger(sinks))
|
|
29
|
+
|
|
30
|
+
export const PlatformTest = (pf: NodeJS.Platform): Layer.Layer<PlatformService> =>
|
|
31
|
+
Layer.succeed(PlatformService, makePlatform(pf))
|
|
32
|
+
|
|
33
|
+
export const DependencyManagerTest = (impl: DependencyManager): Layer.Layer<DependencyManagerService> =>
|
|
34
|
+
Layer.succeed(DependencyManagerService, impl)
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared log/warn/err and dry-run emitters. Prefixes go to stderr with stable
|
|
3
|
-
* ANSI codes; dry-run lines go to stdout.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export function log(msg: string): void {
|
|
7
|
-
process.stderr.write(`\x1b[1;32m[ok]\x1b[0m ${msg}\n`)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function warn(msg: string): void {
|
|
11
|
-
process.stderr.write(`\x1b[1;33m[warn]\x1b[0m ${msg}\n`)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function err(msg: string): void {
|
|
15
|
-
process.stderr.write(`\x1b[1;31m[err]\x1b[0m ${msg}\n`)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function echo(line: string): void {
|
|
19
|
-
process.stdout.write(`${line}\n`)
|
|
20
|
-
}
|