docks-kit 0.1.4 → 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/SoT/toolchain.json +1 -0
- 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 +205 -94
- package/cli/src/engine-native/codexSync.ts +97 -44
- 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 +46 -62
- 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,65 +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 "
|
|
70
|
-
|
|
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)
|
|
60
|
+
case "git":
|
|
61
|
+
return firstLineField(version(), 2)
|
|
75
62
|
case "node":
|
|
76
|
-
return
|
|
77
|
-
case "npm":
|
|
78
|
-
return capture("npm", ["--version"])
|
|
63
|
+
return version().replace(/^v/, "")
|
|
79
64
|
case "jq":
|
|
80
|
-
return
|
|
65
|
+
return version().replace(/^jq-/, "")
|
|
81
66
|
case "curl":
|
|
82
|
-
return firstLineField(capture("curl", ["--version"]), 1)
|
|
83
67
|
case "tsc":
|
|
84
|
-
return firstLineField(
|
|
68
|
+
return firstLineField(version(), 1)
|
|
69
|
+
case "bun":
|
|
70
|
+
case "effect-solutions":
|
|
71
|
+
case "npm":
|
|
72
|
+
return version()
|
|
85
73
|
default:
|
|
86
74
|
return ""
|
|
87
75
|
}
|
|
88
76
|
}
|
|
89
77
|
|
|
90
|
-
export function latestVersion(tool:
|
|
91
|
-
|
|
92
|
-
case "rtk": {
|
|
93
|
-
const body = capture("curl", ["-fsSL", "--max-time", "5", "https://api.github.com/repos/rtk-ai/rtk/releases/latest"])
|
|
94
|
-
const doc = parseJson(body)
|
|
95
|
-
const tag = doc !== undefined && isObject(doc) && typeof doc["tag_name"] === "string" ? doc["tag_name"] : ""
|
|
96
|
-
return tag.replace(/^v/, "")
|
|
97
|
-
}
|
|
98
|
-
case "agent-browser":
|
|
99
|
-
case "effect-solutions":
|
|
100
|
-
return commandExists("npm") ? capture("npm", ["view", tool, "version"]) : ""
|
|
101
|
-
default:
|
|
102
|
-
return ""
|
|
103
|
-
}
|
|
78
|
+
export function latestVersion(ctx: Ctx, tool: ToolId): string {
|
|
79
|
+
return ctx.services.deps.latest(tool)
|
|
104
80
|
}
|
|
105
81
|
|
|
106
82
|
/** Blocking TTY prompt matching bash `read -r -p` (prompt on stderr). */
|
|
107
|
-
function promptLine(
|
|
108
|
-
|
|
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)
|
|
109
89
|
const buf = Buffer.alloc(1)
|
|
110
90
|
let line = ""
|
|
111
91
|
for (;;) {
|
|
112
92
|
let n: number
|
|
113
93
|
try {
|
|
114
|
-
n =
|
|
94
|
+
n = readByte(buf)
|
|
115
95
|
} catch {
|
|
116
96
|
break
|
|
117
97
|
}
|
|
@@ -125,6 +105,7 @@ function promptLine(prompt: string): string {
|
|
|
125
105
|
|
|
126
106
|
/** toolchain::_gate — { proceed, target } ("" target = latest). */
|
|
127
107
|
function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: string): { proceed: boolean; target: string } {
|
|
108
|
+
const { warn } = ctx.services.logger
|
|
128
109
|
const verified = field(ctx, tool, "verified")
|
|
129
110
|
const pinnable = field(ctx, tool, "pinnable")
|
|
130
111
|
|
|
@@ -136,7 +117,7 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
136
117
|
}
|
|
137
118
|
|
|
138
119
|
if (process.stdin.isTTY === true) {
|
|
139
|
-
|
|
120
|
+
ctx.services.logger.warn(`${tool} ${latest} is not kit-verified (verified: ${verified}).`)
|
|
140
121
|
const answer = promptLine(`Install ${tool} ${latest} anyway? [y/N] `)
|
|
141
122
|
if (/^[yY]/.test(answer)) return { proceed: true, target: "" }
|
|
142
123
|
}
|
|
@@ -151,11 +132,12 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
151
132
|
return { proceed: false, target: "" }
|
|
152
133
|
}
|
|
153
134
|
|
|
154
|
-
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
|
|
155
137
|
const policy = field(ctx, tool, "policy")
|
|
156
138
|
|
|
157
139
|
if (!present(ctx, tool)) {
|
|
158
|
-
const latest = latestVersion(tool)
|
|
140
|
+
const latest = latestVersion(ctx, tool)
|
|
159
141
|
if (ctx.dryRun) {
|
|
160
142
|
echo(`[dry-run] would install ${tool} (${latest !== "" ? latest : "latest"}, gated by toolchain.json verified pin)`)
|
|
161
143
|
return 0
|
|
@@ -174,7 +156,7 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
174
156
|
if (!g.proceed) return 0
|
|
175
157
|
target = g.target
|
|
176
158
|
}
|
|
177
|
-
return installFn("install", target !== "" ? target : latest)
|
|
159
|
+
return installFn("install", target !== "" ? target : latest, ctx.services)
|
|
178
160
|
}
|
|
179
161
|
|
|
180
162
|
const installed = installedVersion(ctx, tool)
|
|
@@ -185,17 +167,17 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
185
167
|
echo(`[dry-run] ${tool} present (${installedLabel})`)
|
|
186
168
|
return 0
|
|
187
169
|
}
|
|
188
|
-
|
|
170
|
+
verbose(`${tool} present (${installedLabel})`)
|
|
189
171
|
return 0
|
|
190
172
|
}
|
|
191
173
|
|
|
192
|
-
const latest = latestVersion(tool)
|
|
174
|
+
const latest = latestVersion(ctx, tool)
|
|
193
175
|
if (latest === "") {
|
|
194
176
|
if (ctx.dryRun) {
|
|
195
177
|
echo(`[dry-run] ${tool} present (${installedLabel}); latest unknown (offline?) — no action`)
|
|
196
178
|
return 0
|
|
197
179
|
}
|
|
198
|
-
|
|
180
|
+
verbose(`${tool} present (${installedLabel}; latest unknown — no action)`)
|
|
199
181
|
return 0
|
|
200
182
|
}
|
|
201
183
|
|
|
@@ -206,14 +188,14 @@ export function ensure(ctx: Ctx, tool: string, installFn: InstallFn): number {
|
|
|
206
188
|
}
|
|
207
189
|
const g = gate(ctx, tool, "upgrade", latest)
|
|
208
190
|
if (!g.proceed) return 0
|
|
209
|
-
return installFn("upgrade", g.target !== "" ? g.target : latest)
|
|
191
|
+
return installFn("upgrade", g.target !== "" ? g.target : latest, ctx.services)
|
|
210
192
|
}
|
|
211
193
|
|
|
212
194
|
if (ctx.dryRun) {
|
|
213
195
|
echo(`[dry-run] ${tool} up to date (${installed})`)
|
|
214
196
|
return 0
|
|
215
197
|
}
|
|
216
|
-
|
|
198
|
+
verbose(`${tool} up to date (${installed})`)
|
|
217
199
|
return 0
|
|
218
200
|
}
|
|
219
201
|
|
|
@@ -223,9 +205,10 @@ function row(cells: [string, string, string, string, string, string]): string {
|
|
|
223
205
|
}
|
|
224
206
|
|
|
225
207
|
export function report(ctx: Ctx): void {
|
|
208
|
+
const { echo } = ctx.services.logger
|
|
226
209
|
echo(row(["TOOL", "KIND", "INSTALLED", "FLOOR", "VERIFIED", "STATUS"]))
|
|
227
|
-
const
|
|
228
|
-
|
|
210
|
+
const pn = ctx.services.platform.name()
|
|
211
|
+
const platformOs = pn === "unknown" ? "" : pn
|
|
229
212
|
for (const tool of Object.keys(manifest(ctx)).sort(compareCodepoints)) {
|
|
230
213
|
const os = field(ctx, tool, "os")
|
|
231
214
|
if (os !== "" && platformOs !== "" && os !== platformOs) continue
|
|
@@ -239,8 +222,9 @@ export function report(ctx: Ctx): void {
|
|
|
239
222
|
}
|
|
240
223
|
let installed: string
|
|
241
224
|
let status: string
|
|
242
|
-
|
|
243
|
-
|
|
225
|
+
const toolId = tool as ToolId
|
|
226
|
+
if (present(ctx, toolId)) {
|
|
227
|
+
installed = installedVersion(ctx, toolId)
|
|
244
228
|
status = "ok"
|
|
245
229
|
if (floor !== "" && installed !== "" && isNewer(floor, installed)) {
|
|
246
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
|
-
}
|