docks-kit 0.16.4 → 0.16.5
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/cli/src/argv.ts +2 -1
- package/cli/src/commands/model.ts +2 -1
- package/cli/src/commands/models.ts +2 -1
- package/cli/src/commands/status.ts +2 -11
- package/cli/src/efforts.ts +0 -4
- package/cli/src/engine-native/bun.ts +1 -1
- package/cli/src/engine-native/claudePlugins.ts +390 -0
- package/cli/src/engine-native/claudeSync.ts +2 -387
- package/cli/src/engine-native/codexSync.ts +6 -146
- package/cli/src/engine-native/codexToml.ts +144 -3
- package/cli/src/engine-native/exec.ts +1 -5
- package/cli/src/engine-native/jq.ts +7 -0
- package/cli/src/engine-native/models.ts +31 -9
- package/cli/src/engine-native/ompSync.ts +3 -3
- package/cli/src/engine-native/os/darwin.ts +8 -56
- package/cli/src/engine-native/os/linux.ts +8 -56
- package/cli/src/engine-native/os/posix.ts +67 -0
- package/cli/src/engine-native/os/targets.ts +1 -1
- package/cli/src/engine-native/skillsSync.ts +3 -3
- package/cli/src/engine-native/toolchain.ts +7 -7
- package/cli/src/generated/sotPayload.ts +1 -1
- package/cli/src/manifests.ts +1 -18
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Line-based Codex TOML passes: top-level setting replacement, the SoT
|
|
3
|
+
* top-level and table merges used by `sync codex`, and `--codex-model` /
|
|
4
|
+
* `--codex-effort`. A TOML library would reformat user configs and break the
|
|
5
|
+
* golden contract. The key is used as a raw regex fragment, preserving the
|
|
5
6
|
* historical record-oriented behavior.
|
|
6
7
|
*/
|
|
7
8
|
import { p } from "./exec"
|
|
@@ -110,3 +111,143 @@ export function syncCodexEffort(ctx: Ctx, effort: string): void {
|
|
|
110
111
|
unchanged: `Effort: deployed Codex model_reasoning_effort already ${resolved}`
|
|
111
112
|
})
|
|
112
113
|
}
|
|
114
|
+
|
|
115
|
+
export function mergeTopLevelSettings(sotConfigText: string, userConfig: string): void {
|
|
116
|
+
for (const line of sotConfigText.split("\n")) {
|
|
117
|
+
if (line.startsWith("[")) break
|
|
118
|
+
if (/^[ \t]*($|#)/.test(line)) continue
|
|
119
|
+
if (!/^[A-Za-z0-9_.-]+[ \t]*=/.test(line)) continue
|
|
120
|
+
const key = line.slice(0, line.indexOf("=")).replace(/[ \t]+$/, "")
|
|
121
|
+
replaceTopLevelSettingInFile(userConfig, key, line)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface TomlTableHeader {
|
|
126
|
+
readonly path: string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const TOML_BASIC_ESCAPES: Readonly<Record<string, string>> = {
|
|
130
|
+
b: "\b",
|
|
131
|
+
t: "\t",
|
|
132
|
+
n: "\n",
|
|
133
|
+
f: "\f",
|
|
134
|
+
r: "\r",
|
|
135
|
+
'"': '"',
|
|
136
|
+
"\\": "\\"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function tomlBasicEscape(line: string, offset: number): { readonly next: number; readonly value: string } | undefined {
|
|
140
|
+
const escaped = line[offset]
|
|
141
|
+
const simple = escaped === undefined ? undefined : TOML_BASIC_ESCAPES[escaped]
|
|
142
|
+
if (simple !== undefined) return { next: offset + 1, value: simple }
|
|
143
|
+
const digits = escaped === "u" ? 4 : escaped === "U" ? 8 : 0
|
|
144
|
+
if (digits === 0) return undefined
|
|
145
|
+
const hex = line.slice(offset + 1, offset + 1 + digits)
|
|
146
|
+
if (hex.length !== digits || !/^[0-9A-Fa-f]+$/.test(hex)) return undefined
|
|
147
|
+
const codePoint = Number.parseInt(hex, 16)
|
|
148
|
+
if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) return undefined
|
|
149
|
+
return { next: offset + 1 + digits, value: String.fromCodePoint(codePoint) }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Decode a table header to the TOML path that determines managed ownership. */
|
|
153
|
+
function tomlTableHeader(line: string): TomlTableHeader | undefined {
|
|
154
|
+
let offset = 0
|
|
155
|
+
const skipWhitespace = (): void => {
|
|
156
|
+
while (line[offset] === " " || line[offset] === "\t") offset++
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
skipWhitespace()
|
|
160
|
+
if (line[offset] !== "[") return undefined
|
|
161
|
+
offset++
|
|
162
|
+
const array = line[offset] === "["
|
|
163
|
+
if (array) offset++
|
|
164
|
+
|
|
165
|
+
const keys: Array<string> = []
|
|
166
|
+
while (true) {
|
|
167
|
+
skipWhitespace()
|
|
168
|
+
const quote = line[offset]
|
|
169
|
+
let key = ""
|
|
170
|
+
if (quote === '"' || quote === "'") {
|
|
171
|
+
offset++
|
|
172
|
+
let closed = false
|
|
173
|
+
while (offset < line.length) {
|
|
174
|
+
const char = line[offset]!
|
|
175
|
+
if (char === quote) {
|
|
176
|
+
offset++
|
|
177
|
+
closed = true
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
if (quote === '"' && char === "\\") {
|
|
181
|
+
const escape = tomlBasicEscape(line, offset + 1)
|
|
182
|
+
if (escape === undefined) return undefined
|
|
183
|
+
key += escape.value
|
|
184
|
+
offset = escape.next
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
if (char === "\n" || char === "\r") return undefined
|
|
188
|
+
key += char
|
|
189
|
+
offset++
|
|
190
|
+
}
|
|
191
|
+
if (!closed) return undefined
|
|
192
|
+
} else {
|
|
193
|
+
const start = offset
|
|
194
|
+
while (offset < line.length && /[A-Za-z0-9_-]/.test(line[offset]!)) offset++
|
|
195
|
+
if (offset === start) return undefined
|
|
196
|
+
key = line.slice(start, offset)
|
|
197
|
+
}
|
|
198
|
+
keys.push(key)
|
|
199
|
+
|
|
200
|
+
skipWhitespace()
|
|
201
|
+
if (line[offset] === ".") {
|
|
202
|
+
offset++
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
if (line[offset] !== "]") return undefined
|
|
206
|
+
offset++
|
|
207
|
+
if (array) {
|
|
208
|
+
if (line[offset] !== "]") return undefined
|
|
209
|
+
offset++
|
|
210
|
+
}
|
|
211
|
+
skipWhitespace()
|
|
212
|
+
if (offset < line.length && line[offset] !== "#") return undefined
|
|
213
|
+
return { path: JSON.stringify(keys) }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function mergeTableSettingsText(sotConfigText: string, userConfigText: string): string {
|
|
218
|
+
const sotLines = sotConfigText.split("\n")
|
|
219
|
+
let merged = userConfigText
|
|
220
|
+
for (let tableOffset = 0; tableOffset < sotLines.length; tableOffset++) {
|
|
221
|
+
const managedHeader = tomlTableHeader(sotLines[tableOffset]!)
|
|
222
|
+
if (managedHeader === undefined) continue
|
|
223
|
+
|
|
224
|
+
const block: Array<string> = []
|
|
225
|
+
for (let blockOffset = tableOffset; blockOffset < sotLines.length; blockOffset++) {
|
|
226
|
+
const line = sotLines[blockOffset]!
|
|
227
|
+
if (blockOffset !== tableOffset && tomlTableHeader(line) !== undefined) break
|
|
228
|
+
block.push(line)
|
|
229
|
+
}
|
|
230
|
+
const tableBlock = block.join("\n").replace(/\n+$/, "")
|
|
231
|
+
|
|
232
|
+
const userLines = merged.split("\n")
|
|
233
|
+
if (userLines[userLines.length - 1] === "") userLines.pop()
|
|
234
|
+
let skip = false
|
|
235
|
+
const kept: Array<string> = []
|
|
236
|
+
for (const line of userLines) {
|
|
237
|
+
const header = tomlTableHeader(line)
|
|
238
|
+
if (header !== undefined) {
|
|
239
|
+
skip = header.path === managedHeader.path
|
|
240
|
+
if (skip) continue
|
|
241
|
+
}
|
|
242
|
+
if (!skip) kept.push(line)
|
|
243
|
+
}
|
|
244
|
+
merged = `${kept.join("\n")}\n\n${tableBlock}\n`
|
|
245
|
+
}
|
|
246
|
+
return merged
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function mergeTableSettings(sotConfigText: string, userConfig: string): void {
|
|
250
|
+
const next = mergeTableSettingsText(sotConfigText, readFileSync(userConfig, "utf8"))
|
|
251
|
+
writeFileSync(`${userConfig}.tmp`, next)
|
|
252
|
+
renameSync(`${userConfig}.tmp`, userConfig)
|
|
253
|
+
}
|
|
@@ -116,7 +116,7 @@ export function commandExists(name: string): boolean {
|
|
|
116
116
|
return which(name) !== ""
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
function isExecutable(path: string, suffixes: ReadonlyArray<string> = hostOs().executableSuffixes): boolean {
|
|
120
120
|
try {
|
|
121
121
|
if (!statSync(path).isFile()) return false
|
|
122
122
|
if (suffixes.some((suffix) => suffix !== "")) {
|
|
@@ -148,7 +148,3 @@ export function writeBytesIfChanged(path: string, content: Uint8Array): boolean
|
|
|
148
148
|
writeFileSync(path, bytes)
|
|
149
149
|
return true
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
export function writeFileIfChanged(path: string, content: string): boolean {
|
|
153
|
-
return writeTextIfChanged(path, content)
|
|
154
|
-
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* - output formatting: 2-space indent, UTF-8 raw, trailing newline.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
15
|
+
|
|
14
16
|
export type Json = null | boolean | number | string | Array<Json> | { [key: string]: Json }
|
|
15
17
|
|
|
16
18
|
export function isObject(v: Json): v is { [key: string]: Json } {
|
|
@@ -61,3 +63,8 @@ export function parseJson(text: string): Json | undefined {
|
|
|
61
63
|
return undefined
|
|
62
64
|
}
|
|
63
65
|
}
|
|
66
|
+
|
|
67
|
+
/** Parse a JSON file with `parseJson` semantics; a missing file is `undefined`. */
|
|
68
|
+
export function readJsonFile(file: string): Json | undefined {
|
|
69
|
+
return existsSync(file) ? parseJson(readFileSync(file, "utf8")) : undefined
|
|
70
|
+
}
|
|
@@ -6,6 +6,16 @@ import type { Ctx } from "./index"
|
|
|
6
6
|
import { isObject, parseJson, type Json } from "./jq"
|
|
7
7
|
import { payloadDisplayPath, payloadText } from "../payload"
|
|
8
8
|
|
|
9
|
+
export interface ModelEntry {
|
|
10
|
+
readonly id: string
|
|
11
|
+
readonly kind: "alias" | "id"
|
|
12
|
+
readonly note?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ModelCatalog {
|
|
16
|
+
readonly verified: string
|
|
17
|
+
readonly models: ReadonlyArray<ModelEntry>
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
function toolEntry(tool: string): { [k: string]: Json } | undefined {
|
|
11
21
|
const doc = parseJson(payloadText("SoT/models.json"))
|
|
@@ -14,16 +24,28 @@ function toolEntry(tool: string): { [k: string]: Json } | undefined {
|
|
|
14
24
|
return entry !== undefined && isObject(entry) ? entry : undefined
|
|
15
25
|
}
|
|
16
26
|
|
|
17
|
-
function modelEntries(
|
|
18
|
-
const entry = toolEntry(tool)
|
|
27
|
+
function modelEntries(entry: { [k: string]: Json } | undefined): Array<{ [k: string]: Json }> {
|
|
19
28
|
const models = entry?.["models"]
|
|
20
29
|
return Array.isArray(models) ? models.filter(isObject) : []
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Typed view of one tool's `SoT/models.json` section. An entry whose `id` is not a
|
|
34
|
+
* string or whose `kind` is neither `alias` nor `id` is skipped; a missing section
|
|
35
|
+
* yields `verified: "?"` and no models.
|
|
36
|
+
*/
|
|
37
|
+
export function modelCatalog(tool: string): ModelCatalog {
|
|
38
|
+
const entry = toolEntry(tool)
|
|
39
|
+
const verified = entry?.["verified"]
|
|
40
|
+
const models: Array<ModelEntry> = []
|
|
41
|
+
for (const m of modelEntries(entry)) {
|
|
42
|
+
const id = m["id"]
|
|
43
|
+
const kind = m["kind"]
|
|
44
|
+
if (typeof id !== "string" || (kind !== "alias" && kind !== "id")) continue
|
|
45
|
+
const note = m["note"]
|
|
46
|
+
models.push(typeof note === "string" ? { id, kind, note } : { id, kind })
|
|
47
|
+
}
|
|
48
|
+
return { verified: typeof verified === "string" ? verified : "?", models }
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
export function printModels(ctx: Ctx, tool: string): void {
|
|
@@ -35,7 +57,7 @@ export function printModels(ctx: Ctx, tool: string): void {
|
|
|
35
57
|
}
|
|
36
58
|
const verified = typeof entry["verified"] === "string" ? entry["verified"] : "?"
|
|
37
59
|
const lines = [`Available ${tool} models (kit-verified ${verified} — SoT/models.json):`]
|
|
38
|
-
for (const m of modelEntries(
|
|
60
|
+
for (const m of modelEntries(entry)) {
|
|
39
61
|
const note = typeof m["note"] === "string" ? ` — ${m["note"]}` : ""
|
|
40
62
|
lines.push(` ${String(m["id"] ?? "")}${note}`)
|
|
41
63
|
}
|
|
@@ -46,7 +68,7 @@ export function printModels(ctx: Ctx, tool: string): void {
|
|
|
46
68
|
|
|
47
69
|
export function validateClaudeModel(ctx: Ctx, m: string): boolean {
|
|
48
70
|
if (m === "") return false
|
|
49
|
-
if (
|
|
71
|
+
if (modelCatalog("claude").models.some((entry) => entry.id === m)) return true
|
|
50
72
|
if (m.startsWith("claude-")) {
|
|
51
73
|
ctx.services.logger.warn(`Claude model '${m}' is not in the kit-verified catalog (SoT/models.json) — applying anyway`)
|
|
52
74
|
return true
|
|
@@ -56,7 +78,7 @@ export function validateClaudeModel(ctx: Ctx, m: string): boolean {
|
|
|
56
78
|
|
|
57
79
|
export function validateCodexModel(ctx: Ctx, m: string): boolean {
|
|
58
80
|
if (!/^[A-Za-z0-9._-]+$/.test(m)) return false
|
|
59
|
-
if (!
|
|
81
|
+
if (!modelCatalog("codex").models.some((entry) => entry.id === m)) {
|
|
60
82
|
ctx.services.logger.warn(
|
|
61
83
|
`Codex model '${m}' is not in the kit-verified catalog (SoT/models.json) — applying anyway (check ~/.codex/config.toml if Codex rejects it)`
|
|
62
84
|
)
|
|
@@ -10,7 +10,7 @@ import { isAbsolute, resolve } from "node:path"
|
|
|
10
10
|
|
|
11
11
|
import { payloadDisplayPath, payloadText, type PayloadPath } from "../payload"
|
|
12
12
|
import { bunBootstrap } from "./bun"
|
|
13
|
-
import {
|
|
13
|
+
import { p, spawnProcess, type AsyncProcessResult } from "./exec"
|
|
14
14
|
import type { Ctx } from "./index"
|
|
15
15
|
import { isObject, parseJson } from "./jq"
|
|
16
16
|
import { ompPaths } from "./ompPaths"
|
|
@@ -317,7 +317,7 @@ async function syncPlugins(ctx: Ctx): Promise<number> {
|
|
|
317
317
|
const { change, clearProgress, echo, progress, verbose, warn } = ctx.services.logger
|
|
318
318
|
|
|
319
319
|
if (ctx.dryRun) {
|
|
320
|
-
const piIntercomPin = field(
|
|
320
|
+
const piIntercomPin = field("pi-intercom", "verified")
|
|
321
321
|
for (const pluginId of MARKETPLACE_PLUGIN_IDS) {
|
|
322
322
|
echo(`[dry-run] omp plugin install --scope user ${pluginId}`)
|
|
323
323
|
}
|
|
@@ -345,7 +345,7 @@ async function syncPlugins(ctx: Ctx): Promise<number> {
|
|
|
345
345
|
)
|
|
346
346
|
return 0
|
|
347
347
|
}
|
|
348
|
-
const piIntercomPin = field(
|
|
348
|
+
const piIntercomPin = field("pi-intercom", "verified")
|
|
349
349
|
|
|
350
350
|
progress("Checking installed omp plugins...")
|
|
351
351
|
const installed = await installedPluginIdsFromCli()
|
|
@@ -1,62 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { posixHost } from "./posix"
|
|
2
2
|
import type { HostOs } from "./types"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const darwin: HostOs = {
|
|
4
|
+
export const darwin: HostOs = posixHost({
|
|
9
5
|
id: "darwin",
|
|
10
6
|
toolchainOs: "darwin",
|
|
11
7
|
supportsBubblewrap: false,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const scriptPath = p(directory, "install.sh")
|
|
18
|
-
return {
|
|
19
|
-
scriptPath,
|
|
20
|
-
download: {
|
|
21
|
-
command: "curl",
|
|
22
|
-
args: ["-fsSL", "https://bun.sh/install", "-o", scriptPath]
|
|
23
|
-
},
|
|
24
|
-
run: {
|
|
25
|
-
command: "bash",
|
|
26
|
-
args: [scriptPath, `bun-v${pin}`]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
environmentSetting: (name, value) => ({
|
|
31
|
-
kind: "profile",
|
|
32
|
-
candidates: [".zshrc", ".bashrc", ".bash_profile", ".profile", ".zshenv"],
|
|
33
|
-
target: (shell) => {
|
|
34
|
-
const shellPath = shell ?? "bash"
|
|
35
|
-
const shellName = shellPath.slice(shellPath.lastIndexOf("/") + 1)
|
|
36
|
-
return shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile"
|
|
37
|
-
},
|
|
38
|
-
line: `export ${name}=${value}`
|
|
39
|
-
}),
|
|
40
|
-
statusLineCommand: (bun, script) => {
|
|
41
|
-
const bunLiteral = posixLiteral(bun)
|
|
42
|
-
const scriptLiteral = posixLiteral(script)
|
|
43
|
-
return `test -x ${bunLiteral} && test -f ${scriptLiteral} && exec ${bunLiteral} ${scriptLiteral} || true`
|
|
44
|
-
},
|
|
45
|
-
failureHookCommand: (command) => command,
|
|
46
|
-
installHint: (tool) => {
|
|
47
|
-
switch (tool) {
|
|
48
|
-
case "git":
|
|
49
|
-
return "brew install git"
|
|
50
|
-
case "jq":
|
|
51
|
-
return "brew install jq"
|
|
52
|
-
case "curl":
|
|
53
|
-
return "brew install curl"
|
|
54
|
-
case "ffplay":
|
|
55
|
-
return "brew install ffmpeg"
|
|
56
|
-
case "claude":
|
|
57
|
-
return "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh"
|
|
58
|
-
case "codex":
|
|
59
|
-
return 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"'
|
|
60
|
-
}
|
|
8
|
+
packageManagerHints: {
|
|
9
|
+
git: "brew install git",
|
|
10
|
+
jq: "brew install jq",
|
|
11
|
+
curl: "brew install curl",
|
|
12
|
+
ffplay: "brew install ffmpeg"
|
|
61
13
|
}
|
|
62
|
-
}
|
|
14
|
+
})
|
|
@@ -1,62 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { posixHost } from "./posix"
|
|
2
2
|
import type { HostOs } from "./types"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const linux: HostOs = {
|
|
4
|
+
export const linux: HostOs = posixHost({
|
|
9
5
|
id: "linux",
|
|
10
6
|
toolchainOs: "linux",
|
|
11
7
|
supportsBubblewrap: true,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const scriptPath = p(directory, "install.sh")
|
|
18
|
-
return {
|
|
19
|
-
scriptPath,
|
|
20
|
-
download: {
|
|
21
|
-
command: "curl",
|
|
22
|
-
args: ["-fsSL", "https://bun.sh/install", "-o", scriptPath]
|
|
23
|
-
},
|
|
24
|
-
run: {
|
|
25
|
-
command: "bash",
|
|
26
|
-
args: [scriptPath, `bun-v${pin}`]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
environmentSetting: (name, value) => ({
|
|
31
|
-
kind: "profile",
|
|
32
|
-
candidates: [".zshrc", ".bashrc", ".bash_profile", ".profile", ".zshenv"],
|
|
33
|
-
target: (shell) => {
|
|
34
|
-
const shellPath = shell ?? "bash"
|
|
35
|
-
const shellName = shellPath.slice(shellPath.lastIndexOf("/") + 1)
|
|
36
|
-
return shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile"
|
|
37
|
-
},
|
|
38
|
-
line: `export ${name}=${value}`
|
|
39
|
-
}),
|
|
40
|
-
statusLineCommand: (bun, script) => {
|
|
41
|
-
const bunLiteral = posixLiteral(bun)
|
|
42
|
-
const scriptLiteral = posixLiteral(script)
|
|
43
|
-
return `test -x ${bunLiteral} && test -f ${scriptLiteral} && exec ${bunLiteral} ${scriptLiteral} || true`
|
|
44
|
-
},
|
|
45
|
-
failureHookCommand: (command) => command,
|
|
46
|
-
installHint: (tool) => {
|
|
47
|
-
switch (tool) {
|
|
48
|
-
case "git":
|
|
49
|
-
return "sudo apt install -y git (or your distro's package manager)"
|
|
50
|
-
case "jq":
|
|
51
|
-
return "sudo apt install -y jq"
|
|
52
|
-
case "curl":
|
|
53
|
-
return "sudo apt install -y curl"
|
|
54
|
-
case "ffplay":
|
|
55
|
-
return "sudo apt install -y ffmpeg"
|
|
56
|
-
case "claude":
|
|
57
|
-
return "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh"
|
|
58
|
-
case "codex":
|
|
59
|
-
return 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"'
|
|
60
|
-
}
|
|
8
|
+
packageManagerHints: {
|
|
9
|
+
git: "sudo apt install -y git (or your distro's package manager)",
|
|
10
|
+
jq: "sudo apt install -y jq",
|
|
11
|
+
curl: "sudo apt install -y curl",
|
|
12
|
+
ffplay: "sudo apt install -y ffmpeg"
|
|
61
13
|
}
|
|
62
|
-
}
|
|
14
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// No import from "../exec": exec.ts imports "./os", which evaluates linux and darwin
|
|
2
|
+
// through this module. A slash join keeps the same host-stable path p() would produce.
|
|
3
|
+
import type { HintedTool, HostOs } from "./types"
|
|
4
|
+
|
|
5
|
+
function posixLiteral(value: string): string {
|
|
6
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Package-manager hints for the tools a POSIX host installs through its own package manager. */
|
|
10
|
+
export type PackageManagerHints = Readonly<Record<Exclude<HintedTool, "claude" | "codex">, string>>
|
|
11
|
+
|
|
12
|
+
const UPSTREAM_INSTALLER_HINTS: Readonly<Record<"claude" | "codex", string>> = {
|
|
13
|
+
claude: "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
|
|
14
|
+
codex: 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface PosixHostInputs {
|
|
18
|
+
readonly id: HostOs["id"]
|
|
19
|
+
readonly toolchainOs: HostOs["toolchainOs"]
|
|
20
|
+
readonly supportsBubblewrap: boolean
|
|
21
|
+
readonly packageManagerHints: PackageManagerHints
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Members shared by every POSIX host; linux and darwin differ only in the inputs. */
|
|
25
|
+
export function posixHost(inputs: PosixHostInputs): HostOs {
|
|
26
|
+
return {
|
|
27
|
+
id: inputs.id,
|
|
28
|
+
toolchainOs: inputs.toolchainOs,
|
|
29
|
+
supportsBubblewrap: inputs.supportsBubblewrap,
|
|
30
|
+
directoryLinkKinds: ["symlink"],
|
|
31
|
+
executableSuffixes: [""],
|
|
32
|
+
invoke: (executablePath, args) => ({ command: executablePath, args }),
|
|
33
|
+
bunExecutableName: "bun",
|
|
34
|
+
bunInstaller: (pin, directory) => {
|
|
35
|
+
const scriptPath = `${directory}/install.sh`
|
|
36
|
+
return {
|
|
37
|
+
scriptPath,
|
|
38
|
+
download: {
|
|
39
|
+
command: "curl",
|
|
40
|
+
args: ["-fsSL", "https://bun.sh/install", "-o", scriptPath]
|
|
41
|
+
},
|
|
42
|
+
run: {
|
|
43
|
+
command: "bash",
|
|
44
|
+
args: [scriptPath, `bun-v${pin}`]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
environmentSetting: (name, value) => ({
|
|
49
|
+
kind: "profile",
|
|
50
|
+
candidates: [".zshrc", ".bashrc", ".bash_profile", ".profile", ".zshenv"],
|
|
51
|
+
target: (shell) => {
|
|
52
|
+
const shellPath = shell ?? "bash"
|
|
53
|
+
const shellName = shellPath.slice(shellPath.lastIndexOf("/") + 1)
|
|
54
|
+
return shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile"
|
|
55
|
+
},
|
|
56
|
+
line: `export ${name}=${value}`
|
|
57
|
+
}),
|
|
58
|
+
statusLineCommand: (bun, script) => {
|
|
59
|
+
const bunLiteral = posixLiteral(bun)
|
|
60
|
+
const scriptLiteral = posixLiteral(script)
|
|
61
|
+
return `test -x ${bunLiteral} && test -f ${scriptLiteral} && exec ${bunLiteral} ${scriptLiteral} || true`
|
|
62
|
+
},
|
|
63
|
+
failureHookCommand: (command) => command,
|
|
64
|
+
installHint: (tool) =>
|
|
65
|
+
tool === "claude" || tool === "codex" ? UPSTREAM_INSTALLER_HINTS[tool] : inputs.packageManagerHints[tool]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -63,7 +63,7 @@ export const HOST_TARGETS: ReadonlyArray<HostTarget> = [
|
|
|
63
63
|
target("windows-arm64", "windows", "arm64", [], ["ARM64"])
|
|
64
64
|
]
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
function targetFor(platform: PlatformName, arch: string): HostTarget | undefined {
|
|
67
67
|
return HOST_TARGETS.find((t) => t.platform === platform && t.arch === arch)
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "node:fs"
|
|
18
18
|
import { dirname, relative, resolve } from "node:path"
|
|
19
19
|
import { payloadText } from "../payload"
|
|
20
|
-
import { p, spawnProcess,
|
|
20
|
+
import { p, spawnProcess, writeTextIfChanged } from "./exec"
|
|
21
21
|
import type { Ctx } from "./index"
|
|
22
22
|
import { compareCodepoints } from "./jq"
|
|
23
23
|
import { hostOs, type DirectoryLinkKind } from "./os"
|
|
@@ -50,7 +50,7 @@ export async function skillsSync(ctx: Ctx): Promise<SkillsState> {
|
|
|
50
50
|
|
|
51
51
|
/** skills::_skills_cli — the pinned npx package spec. */
|
|
52
52
|
function skillsCli(ctx: Ctx): string {
|
|
53
|
-
const version = field(
|
|
53
|
+
const version = field("skills-cli", "verified")
|
|
54
54
|
if (version !== "") return `skills@${version}`
|
|
55
55
|
ctx.services.logger.err("Universal skills sync aborted because SoT/toolchain.json has no verified skills-cli pin")
|
|
56
56
|
throw new ExitError(1)
|
|
@@ -365,7 +365,7 @@ function updateSnapshot(ctx: Ctx, manifest: string, snapshot: string, failedRemo
|
|
|
365
365
|
|
|
366
366
|
mkdirSync(ctx.agentsDir, { recursive: true })
|
|
367
367
|
const sorted = [...new Set([...normalizeManifest(manifest), ...failedRemovals])].sort(compareCodepoints)
|
|
368
|
-
|
|
368
|
+
writeTextIfChanged(snapshot, sorted.length > 0 ? `${sorted.join("\n")}\n` : "")
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
// -------------------------------------------------------------- summary ----
|
|
@@ -14,7 +14,7 @@ function manifest(): { [k: string]: Json } {
|
|
|
14
14
|
return tools !== undefined && isObject(tools) ? tools : {}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function field(
|
|
17
|
+
export function field(tool: string, name: string): string {
|
|
18
18
|
const entry = manifest()[tool]
|
|
19
19
|
if (entry === undefined || !isObject(entry)) return ""
|
|
20
20
|
const v = entry[name]
|
|
@@ -22,7 +22,7 @@ export function field(ctx: Ctx, tool: string, name: string): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** toolchain::_is_newer — numeric per dotted field, GNU-sort last-resort tie-break. */
|
|
25
|
-
|
|
25
|
+
function isNewer(a: string, b: string): boolean {
|
|
26
26
|
if (a === "" || b === "" || a === b) return false
|
|
27
27
|
const fa = a.split(".")
|
|
28
28
|
const fb = b.split(".")
|
|
@@ -86,14 +86,14 @@ export async function report(ctx: Ctx): Promise<void> {
|
|
|
86
86
|
const pn = ctx.services.platform.name()
|
|
87
87
|
const platformOs = hostOs(pn).toolchainOs
|
|
88
88
|
for (const tool of Object.keys(manifest()).sort(compareCodepoints)) {
|
|
89
|
-
const os = field(
|
|
89
|
+
const os = field(tool, "os")
|
|
90
90
|
if (os !== "" && platformOs !== "" && os !== platformOs) continue
|
|
91
|
-
const kind = field(
|
|
92
|
-
const floor = field(
|
|
93
|
-
const verified = field(
|
|
91
|
+
const kind = field(tool, "kind")
|
|
92
|
+
const floor = field(tool, "floor")
|
|
93
|
+
const verified = field(tool, "verified")
|
|
94
94
|
const dash = (v: string): string => (v !== "" ? v : "-")
|
|
95
95
|
if (kind === "pin") {
|
|
96
|
-
const via = field(
|
|
96
|
+
const via = field(tool, "via")
|
|
97
97
|
echo(row([tool, kind, `(${via !== "" ? via : "npx"})`, dash(floor), dash(verified), "pinned"]))
|
|
98
98
|
continue
|
|
99
99
|
}
|
|
@@ -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.16.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.16.5"
|
|
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/manifests.ts
CHANGED
|
@@ -1,34 +1,17 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs"
|
|
2
2
|
import { homedir } from "node:os"
|
|
3
3
|
import { dirname, join, resolve } from "node:path"
|
|
4
|
-
import { pluginUserScopeInstalled } from "./engine-native/
|
|
4
|
+
import { pluginUserScopeInstalled } from "./engine-native/claudePlugins"
|
|
5
5
|
import { COPY_MARKER } from "./engine-native/skillsSync"
|
|
6
6
|
import { payloadText } from "./payload"
|
|
7
7
|
|
|
8
8
|
export { homedir }
|
|
9
9
|
|
|
10
|
-
export interface ModelEntry {
|
|
11
|
-
readonly id: string
|
|
12
|
-
readonly kind: "alias" | "id"
|
|
13
|
-
readonly note?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ModelCatalog {
|
|
17
|
-
readonly verified: string
|
|
18
|
-
readonly models: ReadonlyArray<ModelEntry>
|
|
19
|
-
}
|
|
20
|
-
|
|
21
10
|
export type Tool = "claude" | "codex"
|
|
22
11
|
|
|
23
12
|
const readJson = (path: string): any => JSON.parse(readFileSync(path, "utf8"))
|
|
24
13
|
const readJsonText = (text: string): any => JSON.parse(text)
|
|
25
14
|
|
|
26
|
-
export const modelCatalog = (tool: Tool): ModelCatalog =>
|
|
27
|
-
readJsonText(payloadText("SoT/models.json"))[tool]
|
|
28
|
-
|
|
29
|
-
export const toolchainManifest = (): Record<string, any> =>
|
|
30
|
-
readJsonText(payloadText("SoT/toolchain.json")).tools
|
|
31
|
-
|
|
32
15
|
/** SoT settings (claude) — model/effort/env for drift display. */
|
|
33
16
|
export const sotClaudeSettings = (): any =>
|
|
34
17
|
readJsonText(payloadText("SoT/.claude/settings.json"))
|
package/package.json
CHANGED