docks-kit 0.14.3 → 0.14.4
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/cli/docs/flags.md +6 -0
- package/cli/src/engine-native/DESIGN.md +15 -4
- package/cli/src/engine-native/bun.ts +22 -21
- package/cli/src/engine-native/claudeSync.ts +30 -31
- package/cli/src/engine-native/codexSync.ts +24 -24
- package/cli/src/engine-native/deps.ts +43 -35
- package/cli/src/engine-native/exec.ts +61 -5
- package/cli/src/engine-native/index.ts +141 -33
- package/cli/src/engine-native/logger.ts +114 -14
- package/cli/src/engine-native/modes.ts +4 -4
- package/cli/src/engine-native/services.ts +5 -5
- package/cli/src/engine-native/skillsSync.ts +19 -20
- package/cli/src/engine-native/toolchain.ts +40 -27
- package/cli/src/engine.ts +1 -1
- package/cli/src/generated/sotPayload.ts +1 -1
- package/cli/src/main.ts +1 -1
- package/package.json +1 -1
|
@@ -26,10 +26,10 @@ export type { Logger } from "./logger"
|
|
|
26
26
|
export interface DependencyManager {
|
|
27
27
|
readonly spec: (id: ToolId) => DependencySpec
|
|
28
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
|
|
29
|
+
readonly version: (id: ToolId) => Promise<string>
|
|
30
|
+
readonly path: (id: ToolId) => Promise<string>
|
|
31
|
+
readonly location: (id: ToolId) => Promise<DependencyLocation>
|
|
32
|
+
readonly latest: (id: ToolId) => Promise<string>
|
|
33
33
|
readonly warnMissing: (id: ToolId, logger: Logger, context?: string) => void
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -73,7 +73,7 @@ export const makeDependencyManager = (
|
|
|
73
73
|
version: (id) => resolveVersion(DEPENDENCIES[id], exec),
|
|
74
74
|
path: (id) => resolvePath(DEPENDENCIES[id], exec, platform.raw()),
|
|
75
75
|
location: (id) => resolveLocation(DEPENDENCIES[id], exec, platform.raw()),
|
|
76
|
-
latest: (id) => DEPENDENCIES[id].latest?.(exec) ?? "",
|
|
76
|
+
latest: (id) => DEPENDENCIES[id].latest?.(exec) ?? Promise.resolve(""),
|
|
77
77
|
warnMissing: (id, logger, context) => {
|
|
78
78
|
if (warned.has(id)) return
|
|
79
79
|
warned.add(id)
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* the kit-managed snapshot, the effect-solutions toolchain callback, and the
|
|
5
5
|
* snapshot write.
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from "node:child_process"
|
|
8
7
|
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync } from "node:fs"
|
|
9
|
-
import { p, writeFileIfChanged } from "./exec"
|
|
8
|
+
import { p, spawnProcess, writeFileIfChanged } from "./exec"
|
|
10
9
|
import { bunBootstrap } from "./bun"
|
|
11
10
|
import type { Ctx } from "./index"
|
|
12
11
|
import { compareCodepoints } from "./jq"
|
|
@@ -18,7 +17,7 @@ export interface SkillsState {
|
|
|
18
17
|
present: number
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export function skillsSync(ctx: Ctx): SkillsState {
|
|
20
|
+
export async function skillsSync(ctx: Ctx): Promise<SkillsState> {
|
|
22
21
|
const state: SkillsState = { present: 0 }
|
|
23
22
|
const skillsDir = p(ctx.agentsDir, "skills")
|
|
24
23
|
const manifest = payloadText("SoT/.agents/skills.txt")
|
|
@@ -26,9 +25,9 @@ export function skillsSync(ctx: Ctx): SkillsState {
|
|
|
26
25
|
|
|
27
26
|
if (!ctx.dryRun) mkdirSync(skillsDir, { recursive: true })
|
|
28
27
|
|
|
29
|
-
syncUniversal(ctx, state, skillsDir, manifest)
|
|
30
|
-
if (ctx.prune) reconcileRemovals(ctx, manifest, snapshot)
|
|
31
|
-
syncEffectSolutionsCli(ctx)
|
|
28
|
+
await syncUniversal(ctx, state, skillsDir, manifest)
|
|
29
|
+
if (ctx.prune) await reconcileRemovals(ctx, manifest, snapshot)
|
|
30
|
+
await syncEffectSolutionsCli(ctx)
|
|
32
31
|
updateSnapshot(ctx, manifest, snapshot)
|
|
33
32
|
return state
|
|
34
33
|
}
|
|
@@ -55,7 +54,7 @@ function readSlugs(file: string): Array<string> {
|
|
|
55
54
|
return existsSync(file) ? normalizeManifest(readFileSync(file, "utf8")) : []
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
function syncUniversal(ctx: Ctx, state: SkillsState, skillsDir: string, manifest: string): void {
|
|
57
|
+
async function syncUniversal(ctx: Ctx, state: SkillsState, skillsDir: string, manifest: string): Promise<void> {
|
|
59
58
|
const { change, clearProgress, echo, progress, verbose, warn } = ctx.services.logger
|
|
60
59
|
if (ctx.services.deps.probe("npx").state === "missing") {
|
|
61
60
|
ctx.services.deps.warnMissing("npx", ctx.services.logger, "skipping universal skills bootstrap")
|
|
@@ -87,11 +86,11 @@ function syncUniversal(ctx: Ctx, state: SkillsState, skillsDir: string, manifest
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
progress(`Installing universal skill ${slug}...`)
|
|
90
|
-
const res =
|
|
89
|
+
const res = await spawnProcess("npx", ["--yes", skillsCli(ctx), "add", slug, "-g", "-y", "-a", "claude-code", "codex"], {
|
|
91
90
|
stdio: "ignore"
|
|
92
91
|
})
|
|
93
92
|
clearProgress()
|
|
94
|
-
if (res.error === undefined && res.
|
|
93
|
+
if (res.error === undefined && res.exitCode === 0) {
|
|
95
94
|
added++
|
|
96
95
|
} else {
|
|
97
96
|
warn(`Failed to install universal skill: ${slug}`)
|
|
@@ -221,26 +220,26 @@ function linkOrCopyWithWarnings(target: string, link: string, services: EngineSe
|
|
|
221
220
|
/** skills::_effect_solutions_install. */
|
|
222
221
|
export function effectSolutionsInstall(
|
|
223
222
|
ctx: Ctx
|
|
224
|
-
): (mode: "install" | "upgrade", version: string, services: EngineServices) => number {
|
|
225
|
-
return (mode, version, services) => {
|
|
223
|
+
): (mode: "install" | "upgrade", version: string, services: EngineServices) => Promise<number> {
|
|
224
|
+
return async (mode, version, services) => {
|
|
226
225
|
const { change, clearProgress, progress, verbose, warn } = services.logger
|
|
227
226
|
const verb = mode === "upgrade" ? "Upgrading" : "Installing"
|
|
228
227
|
const pkg = `effect-solutions@${version !== "" ? version : "latest"}`
|
|
229
228
|
|
|
230
|
-
const bunState = bunBootstrap(ctx, services)
|
|
229
|
+
const bunState = await bunBootstrap(ctx, services)
|
|
231
230
|
if (bunState.kind === "deferred") return 1
|
|
232
231
|
const bun = bunState.executable
|
|
233
232
|
|
|
234
233
|
verbose(`${verb} effect-solutions CLI via bun${version !== "" ? ` (pinned ${version})` : ""}...`)
|
|
235
234
|
progress(`${verb} effect-solutions CLI...`)
|
|
236
|
-
const installResult =
|
|
235
|
+
const installResult = await spawnProcess(bun, ["add", "-g", pkg], { stdio: "ignore" })
|
|
237
236
|
clearProgress()
|
|
238
|
-
if (installResult.
|
|
237
|
+
if (installResult.exitCode !== 0) {
|
|
239
238
|
warn(`bun add -g ${pkg} failed. Try manually: bun add -g ${pkg}`)
|
|
240
239
|
return 1
|
|
241
240
|
}
|
|
242
241
|
|
|
243
|
-
const location = services.deps.location("effect-solutions")
|
|
242
|
+
const location = await services.deps.location("effect-solutions")
|
|
244
243
|
const gbin = location.binDir
|
|
245
244
|
if (location.path !== "") {
|
|
246
245
|
mkdirSync(p(ctx.home, ".local", "bin"), { recursive: true })
|
|
@@ -254,12 +253,12 @@ export function effectSolutionsInstall(
|
|
|
254
253
|
}
|
|
255
254
|
}
|
|
256
255
|
|
|
257
|
-
function syncEffectSolutionsCli(ctx: Ctx): void {
|
|
256
|
+
async function syncEffectSolutionsCli(ctx: Ctx): Promise<void> {
|
|
258
257
|
const { clearProgress, progress, warn } = ctx.services.logger
|
|
259
258
|
if (!/"effect-kit@docks"[ \t]*:[ \t]*true/.test(payloadText("SoT/.claude/settings.json"))) return
|
|
260
259
|
|
|
261
260
|
progress("Checking effect-solutions CLI...")
|
|
262
|
-
const result = ensure(ctx, "effect-solutions", effectSolutionsInstall(ctx))
|
|
261
|
+
const result = await ensure(ctx, "effect-solutions", effectSolutionsInstall(ctx))
|
|
263
262
|
clearProgress()
|
|
264
263
|
if (result !== 0) {
|
|
265
264
|
warn("effect-solutions bootstrap failed — continuing sync")
|
|
@@ -268,7 +267,7 @@ function syncEffectSolutionsCli(ctx: Ctx): void {
|
|
|
268
267
|
|
|
269
268
|
// ----------------------------------------------------- prune + snapshot ----
|
|
270
269
|
|
|
271
|
-
function reconcileRemovals(ctx: Ctx, manifest: string, snapshot: string): void {
|
|
270
|
+
async function reconcileRemovals(ctx: Ctx, manifest: string, snapshot: string): Promise<void> {
|
|
272
271
|
const { change, clearProgress, echo, progress, warn } = ctx.services.logger
|
|
273
272
|
if (!existsSync(snapshot)) {
|
|
274
273
|
if (ctx.dryRun) {
|
|
@@ -290,11 +289,11 @@ function reconcileRemovals(ctx: Ctx, manifest: string, snapshot: string): void {
|
|
|
290
289
|
continue
|
|
291
290
|
}
|
|
292
291
|
progress(`Removing universal skill ${base}...`)
|
|
293
|
-
const res =
|
|
292
|
+
const res = await spawnProcess("npx", ["--yes", skillsCli(ctx), "remove", "--global", base, "-y"], {
|
|
294
293
|
stdio: "ignore"
|
|
295
294
|
})
|
|
296
295
|
clearProgress()
|
|
297
|
-
if (res.error === undefined && res.
|
|
296
|
+
if (res.error === undefined && res.exitCode === 0) {
|
|
298
297
|
removed++
|
|
299
298
|
} else {
|
|
300
299
|
warn(`Failed to remove kit-managed skill: ${base}`)
|
|
@@ -10,7 +10,11 @@ import { compareCodepoints, isObject, parseJson, type Json } from "./jq"
|
|
|
10
10
|
import type { EngineServices } from "./services"
|
|
11
11
|
import { payloadText } from "../payload"
|
|
12
12
|
|
|
13
|
-
type InstallFn = (
|
|
13
|
+
type InstallFn = (
|
|
14
|
+
mode: "install" | "upgrade",
|
|
15
|
+
version: string,
|
|
16
|
+
services: EngineServices
|
|
17
|
+
) => number | Promise<number>
|
|
14
18
|
|
|
15
19
|
function manifest(): { [k: string]: Json } {
|
|
16
20
|
const doc = parseJson(payloadText("SoT/toolchain.json"))
|
|
@@ -47,40 +51,40 @@ function firstLineField(out: string, index: number): string {
|
|
|
47
51
|
return fields[index === -1 ? fields.length - 1 : index] ?? ""
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
export function installedVersion(ctx: Ctx, tool: ToolId): string {
|
|
51
|
-
const version = ()
|
|
54
|
+
export async function installedVersion(ctx: Ctx, tool: ToolId): Promise<string> {
|
|
55
|
+
const version = () => ctx.services.deps.version(tool)
|
|
52
56
|
switch (tool) {
|
|
53
57
|
case "claude":
|
|
54
|
-
return firstLineField(version(), 0)
|
|
58
|
+
return firstLineField(await version(), 0)
|
|
55
59
|
case "codex":
|
|
56
|
-
return firstLineField(version(), -1)
|
|
60
|
+
return firstLineField(await version(), -1)
|
|
57
61
|
case "git":
|
|
58
|
-
return firstLineField(version(), 2)
|
|
62
|
+
return firstLineField(await version(), 2)
|
|
59
63
|
case "node":
|
|
60
|
-
return version().replace(/^v/, "")
|
|
64
|
+
return (await version()).replace(/^v/, "")
|
|
61
65
|
case "jq":
|
|
62
|
-
return version().replace(/^jq-/, "")
|
|
66
|
+
return (await version()).replace(/^jq-/, "")
|
|
63
67
|
case "curl":
|
|
64
68
|
case "tsc":
|
|
65
|
-
return firstLineField(version(), 1)
|
|
69
|
+
return firstLineField(await version(), 1)
|
|
66
70
|
case "bun":
|
|
67
71
|
case "effect-solutions":
|
|
68
72
|
case "npm":
|
|
69
|
-
return version()
|
|
73
|
+
return await version()
|
|
70
74
|
case "bwrap":
|
|
71
|
-
return firstLineField(version(), 1)
|
|
75
|
+
return firstLineField(await version(), 1)
|
|
72
76
|
case "ffplay":
|
|
73
|
-
return firstLineField(version(), 2).replace(/-.*$/, "")
|
|
77
|
+
return firstLineField(await version(), 2).replace(/-.*$/, "")
|
|
74
78
|
case "intelephense":
|
|
75
79
|
case "typescript-language-server":
|
|
76
|
-
return version().trim()
|
|
80
|
+
return (await version()).trim()
|
|
77
81
|
default:
|
|
78
82
|
return ""
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
export function latestVersion(ctx: Ctx, tool: ToolId): string {
|
|
83
|
-
return ctx.services.deps.latest(tool)
|
|
86
|
+
export async function latestVersion(ctx: Ctx, tool: ToolId): Promise<string> {
|
|
87
|
+
return await ctx.services.deps.latest(tool)
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
/** Blocking TTY prompt matching bash `read -r -p` (prompt on stderr). */
|
|
@@ -108,7 +112,12 @@ export function promptLine(
|
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
/** toolchain::_gate — { proceed, target } ("" target = latest). */
|
|
111
|
-
function gate(
|
|
115
|
+
async function gate(
|
|
116
|
+
ctx: Ctx,
|
|
117
|
+
tool: string,
|
|
118
|
+
mode: "install" | "upgrade",
|
|
119
|
+
latest: string
|
|
120
|
+
): Promise<{ proceed: boolean; target: string }> {
|
|
112
121
|
const { warn } = ctx.services.logger
|
|
113
122
|
const verified = field(ctx, tool, "verified")
|
|
114
123
|
const pinnable = field(ctx, tool, "pinnable")
|
|
@@ -122,7 +131,11 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
122
131
|
|
|
123
132
|
if (process.stdin.isTTY === true) {
|
|
124
133
|
ctx.services.logger.warn(`${tool} ${latest} is not kit-verified (verified: ${verified}).`)
|
|
125
|
-
const
|
|
134
|
+
const question = `Install ${tool} ${latest} anyway? [y/N] `
|
|
135
|
+
const answer =
|
|
136
|
+
ctx.terminalLease === undefined
|
|
137
|
+
? promptLine(question)
|
|
138
|
+
: await ctx.terminalLease.withExclusive(() => promptLine(question))
|
|
126
139
|
if (/^[yY]/.test(answer)) return { proceed: true, target: "" }
|
|
127
140
|
}
|
|
128
141
|
|
|
@@ -136,12 +149,12 @@ function gate(ctx: Ctx, tool: string, mode: "install" | "upgrade", latest: strin
|
|
|
136
149
|
return { proceed: false, target: "" }
|
|
137
150
|
}
|
|
138
151
|
|
|
139
|
-
export function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): number {
|
|
152
|
+
export async function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): Promise<number> {
|
|
140
153
|
const { echo, verbose, warn } = ctx.services.logger
|
|
141
154
|
const policy = field(ctx, tool, "policy")
|
|
142
155
|
|
|
143
156
|
if (!present(ctx, tool)) {
|
|
144
|
-
const latest = latestVersion(ctx, tool)
|
|
157
|
+
const latest = await latestVersion(ctx, tool)
|
|
145
158
|
if (ctx.dryRun) {
|
|
146
159
|
echo(`[dry-run] would install ${tool} (${latest !== "" ? latest : "latest"}, gated by toolchain.json verified pin)`)
|
|
147
160
|
return 0
|
|
@@ -156,14 +169,14 @@ export function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): number {
|
|
|
156
169
|
warn(`${tool} latest version unknown (offline?) and not pinnable — installing latest unverified`)
|
|
157
170
|
}
|
|
158
171
|
} else {
|
|
159
|
-
const g = gate(ctx, tool, "install", latest)
|
|
172
|
+
const g = await gate(ctx, tool, "install", latest)
|
|
160
173
|
if (!g.proceed) return 0
|
|
161
174
|
target = g.target
|
|
162
175
|
}
|
|
163
|
-
return installFn("install", target !== "" ? target : latest, ctx.services)
|
|
176
|
+
return await installFn("install", target !== "" ? target : latest, ctx.services)
|
|
164
177
|
}
|
|
165
178
|
|
|
166
|
-
const installed = installedVersion(ctx, tool)
|
|
179
|
+
const installed = await installedVersion(ctx, tool)
|
|
167
180
|
const installedLabel = installed !== "" ? installed : "version unknown"
|
|
168
181
|
|
|
169
182
|
if (policy !== "track") {
|
|
@@ -175,7 +188,7 @@ export function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): number {
|
|
|
175
188
|
return 0
|
|
176
189
|
}
|
|
177
190
|
|
|
178
|
-
const latest = latestVersion(ctx, tool)
|
|
191
|
+
const latest = await latestVersion(ctx, tool)
|
|
179
192
|
if (latest === "") {
|
|
180
193
|
if (ctx.dryRun) {
|
|
181
194
|
echo(`[dry-run] ${tool} present (${installedLabel}); latest unknown (offline?) — no action`)
|
|
@@ -190,9 +203,9 @@ export function ensure(ctx: Ctx, tool: ToolId, installFn: InstallFn): number {
|
|
|
190
203
|
echo(`[dry-run] would upgrade ${tool} (${installed !== "" ? installed : "unknown"} -> ${latest}, gated by toolchain.json verified pin)`)
|
|
191
204
|
return 0
|
|
192
205
|
}
|
|
193
|
-
const g = gate(ctx, tool, "upgrade", latest)
|
|
206
|
+
const g = await gate(ctx, tool, "upgrade", latest)
|
|
194
207
|
if (!g.proceed) return 0
|
|
195
|
-
return installFn("upgrade", g.target !== "" ? g.target : latest, ctx.services)
|
|
208
|
+
return await installFn("upgrade", g.target !== "" ? g.target : latest, ctx.services)
|
|
196
209
|
}
|
|
197
210
|
|
|
198
211
|
if (ctx.dryRun) {
|
|
@@ -208,7 +221,7 @@ function row(cells: [string, string, string, string, string, string]): string {
|
|
|
208
221
|
return cells.map((c, i) => (i < widths.length ? c.padEnd(widths[i]!) : c)).join(" ")
|
|
209
222
|
}
|
|
210
223
|
|
|
211
|
-
export function report(ctx: Ctx): void {
|
|
224
|
+
export async function report(ctx: Ctx): Promise<void> {
|
|
212
225
|
const { echo } = ctx.services.logger
|
|
213
226
|
echo(row(["TOOL", "KIND", "INSTALLED", "FLOOR", "VERIFIED", "STATUS"]))
|
|
214
227
|
const pn = ctx.services.platform.name()
|
|
@@ -228,7 +241,7 @@ export function report(ctx: Ctx): void {
|
|
|
228
241
|
let status: string
|
|
229
242
|
const toolId = tool as ToolId
|
|
230
243
|
if (present(ctx, toolId)) {
|
|
231
|
-
installed = installedVersion(ctx, toolId)
|
|
244
|
+
installed = await installedVersion(ctx, toolId)
|
|
232
245
|
status = "ok"
|
|
233
246
|
if (floor !== "" && installed !== "" && isNewer(floor, installed)) {
|
|
234
247
|
status = "below-floor"
|
package/cli/src/engine.ts
CHANGED
|
@@ -37,7 +37,7 @@ export const engine = (args: ReadonlyArray<string>) =>
|
|
|
37
37
|
const logger = yield* LoggerService
|
|
38
38
|
const deps = yield* DependencyManagerService
|
|
39
39
|
const platform = yield* PlatformService
|
|
40
|
-
const code = yield* Effect.
|
|
40
|
+
const code = yield* Effect.promise(() => runEngineNative(args, { logger, deps, platform }))
|
|
41
41
|
if (code !== 0) {
|
|
42
42
|
yield* Effect.sync(() => process.exit(code))
|
|
43
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by cli/scripts/generate-sot-payload.ts. DO NOT EDIT.
|
|
2
2
|
// Edit SoT/, notification.mp3, or package.json, then run: bun cli/scripts/generate-sot-payload.ts
|
|
3
3
|
|
|
4
|
-
export const GENERATED_PACKAGE_VERSION = "0.14.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.14.4"
|
|
5
5
|
|
|
6
6
|
export const GENERATED_PAYLOAD_TEXT = {
|
|
7
7
|
"SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
|
package/cli/src/main.ts
CHANGED
|
@@ -57,7 +57,7 @@ const root = Command.make("docks-kit", {}, () =>
|
|
|
57
57
|
// parsed/normalized pickers, --flag value forms, and non-engine commands.
|
|
58
58
|
if (process.env["DOCKS_KIT_ENGINE"] === "native-raw") {
|
|
59
59
|
const { runEngineNative } = await import("./engine-native")
|
|
60
|
-
process.exit(runEngineNative(process.argv.slice(2)))
|
|
60
|
+
process.exit(await runEngineNative(process.argv.slice(2)))
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const cli = Command.run(root, {
|
package/package.json
CHANGED