docks-kit 0.16.4 → 0.16.6

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.
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Top-level Codex TOML setting replacement plus `--codex-model`. The pass is
3
- * line-based on purpose: a TOML library would reformat user configs and break
4
- * the golden contract. The key is used as a raw regex fragment, preserving the
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
- export function isExecutable(path: string, suffixes: ReadonlyArray<string> = hostOs().executableSuffixes): boolean {
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(tool: string): Array<{ [k: string]: Json }> {
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
- export function modelsFromManifest(tool: string): Array<string> {
24
- return modelEntries(tool)
25
- .map((m) => m["id"])
26
- .filter((id): id is string => typeof id === "string")
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(tool)) {
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 (modelsFromManifest("claude").includes(m)) return true
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 (!modelsFromManifest("codex").includes(m)) {
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 { commandExists, p, spawnProcess, type AsyncProcessResult } from "./exec"
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(ctx, "pi-intercom", "verified")
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(ctx, "pi-intercom", "verified")
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 { p } from "../exec"
1
+ import { posixHost } from "./posix"
2
2
  import type { HostOs } from "./types"
3
3
 
4
- function posixLiteral(value: string): string {
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
- directoryLinkKinds: ["symlink"],
13
- executableSuffixes: [""],
14
- invoke: (executablePath, args) => ({ command: executablePath, args }),
15
- bunExecutableName: "bun",
16
- bunInstaller: (pin, directory) => {
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 { p } from "../exec"
1
+ import { posixHost } from "./posix"
2
2
  import type { HostOs } from "./types"
3
3
 
4
- function posixLiteral(value: string): string {
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
- directoryLinkKinds: ["symlink"],
13
- executableSuffixes: [""],
14
- invoke: (executablePath, args) => ({ command: executablePath, args }),
15
- bunExecutableName: "bun",
16
- bunInstaller: (pin, directory) => {
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
- export function targetFor(platform: PlatformName, arch: string): HostTarget | undefined {
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, writeFileIfChanged } from "./exec"
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(ctx, "skills-cli", "verified")
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
- writeFileIfChanged(snapshot, sorted.length > 0 ? `${sorted.join("\n")}\n` : "")
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(ctx: Ctx, tool: string, name: string): string {
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
- export function isNewer(a: string, b: string): boolean {
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(ctx, tool, "os")
89
+ const os = field(tool, "os")
90
90
  if (os !== "" && platformOs !== "" && os !== platformOs) continue
91
- const kind = field(ctx, tool, "kind")
92
- const floor = field(ctx, tool, "floor")
93
- const verified = field(ctx, tool, "verified")
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(ctx, tool, "via")
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"
4
+ export const GENERATED_PACKAGE_VERSION = "0.16.6"
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",
@@ -18,7 +18,7 @@ export const GENERATED_PAYLOAD_TEXT = {
18
18
  "SoT/.codex/plugins/marketplace.json": "{\n \"name\": \"docks\",\n \"interface\": {\n \"displayName\": \"DocksDocks\"\n },\n \"plugins\": [\n {\n \"name\": \"docks\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/docks\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n },\n {\n \"name\": \"plan-lifecycle\",\n \"source\": {\n \"source\": \"git-subdir\",\n \"url\": \"https://github.com/DocksDocks/docks.git\",\n \"path\": \"./plugins/plan-lifecycle\",\n \"ref\": \"main\"\n },\n \"policy\": {\n \"installation\": \"AVAILABLE\",\n \"authentication\": \"ON_INSTALL\"\n },\n \"category\": \"Productivity\"\n }\n ]\n}\n",
19
19
  "SoT/.codex/rules/docks.rules": "prefix_rule(pattern=[\"pwd\"], decision=\"allow\")\nprefix_rule(pattern=[\"ls\"], decision=\"allow\")\nprefix_rule(pattern=[\"cat\"], decision=\"allow\")\nprefix_rule(pattern=[\"head\"], decision=\"allow\")\nprefix_rule(pattern=[\"tail\"], decision=\"allow\")\nprefix_rule(pattern=[\"wc\"], decision=\"allow\")\nprefix_rule(pattern=[\"nl\"], decision=\"allow\")\nprefix_rule(pattern=[\"grep\"], decision=\"allow\")\nprefix_rule(pattern=[\"sort\"], decision=\"allow\")\nprefix_rule(pattern=[\"uniq\"], decision=\"allow\")\nprefix_rule(pattern=[\"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"which\"], decision=\"allow\")\nprefix_rule(pattern=[\"date\"], decision=\"allow\")\nprefix_rule(pattern=[\"basename\"], decision=\"allow\")\nprefix_rule(pattern=[\"dirname\"], decision=\"allow\")\nprefix_rule(pattern=[\"realpath\"], decision=\"allow\")\nprefix_rule(pattern=[\"readlink\"], decision=\"allow\")\nprefix_rule(pattern=[\"jq\"], decision=\"allow\")\nprefix_rule(pattern=[\"tree\"], decision=\"allow\")\nprefix_rule(pattern=[\"cut\"], decision=\"allow\")\nprefix_rule(pattern=[\"tr\"], decision=\"allow\")\nprefix_rule(pattern=[\"echo\"], decision=\"allow\")\nprefix_rule(pattern=[\"printf\"], decision=\"allow\")\nprefix_rule(pattern=[\"printenv\"], decision=\"allow\")\nprefix_rule(pattern=[\"uname\"], decision=\"allow\")\nprefix_rule(pattern=[\"file\"], decision=\"allow\")\nprefix_rule(pattern=[\"stat\"], decision=\"allow\")\nprefix_rule(pattern=[\"du\"], decision=\"allow\")\nprefix_rule(pattern=[\"id\"], decision=\"allow\")\nprefix_rule(pattern=[\"whoami\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"git\", \"status\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"log\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"show\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"blame\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"rev-parse\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"ls-files\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"grep\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"ls-tree\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"branch\", \"--show-current\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"branch\", \"-vv\"], decision=\"allow\")\nprefix_rule(pattern=[\"git\", \"mv\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"gh\", \"pr\", \"view\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"list\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"diff\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"status\"], decision=\"allow\")\nprefix_rule(pattern=[\"gh\", \"pr\", \"checks\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"docker\", \"ps\"], decision=\"allow\")\n\nprefix_rule(pattern=[\"git\", \"push\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"reset\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"clean\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"merge\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"rebase\"], decision=\"prompt\")\nprefix_rule(pattern=[\"git\", \"checkout\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"mv\"], decision=\"prompt\")\nprefix_rule(pattern=[\"chmod\"], decision=\"prompt\")\nprefix_rule(pattern=[\"chown\"], decision=\"prompt\")\nprefix_rule(pattern=[\"kill\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pkill\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"npm\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"npm\", \"uninstall\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pnpm\", \"add\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pnpm\", \"remove\"], decision=\"prompt\")\nprefix_rule(pattern=[\"yarn\", \"add\"], decision=\"prompt\")\nprefix_rule(pattern=[\"yarn\", \"remove\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip\", \"uninstall\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip3\", \"install\"], decision=\"prompt\")\nprefix_rule(pattern=[\"pip3\", \"uninstall\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"docker\", \"run\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"stop\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"volume\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"system\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"up\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"down\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker\", \"compose\", \"stop\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"up\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"down\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"rm\"], decision=\"prompt\")\nprefix_rule(pattern=[\"docker-compose\", \"stop\"], decision=\"prompt\")\n\n# `tail -f`/`--follow` never returns and hangs the agent (bare `tail` stays allowed above).\nprefix_rule(pattern=[\"tail\", \"-f\"], decision=\"prompt\")\nprefix_rule(pattern=[\"tail\", \"--follow\"], decision=\"prompt\")\n# rg and `sed -n` are prompt, NOT allow: argv-prefix matching cannot gate their\n# code-exec forms (rg --pre=CMD or a reordered --pre; sed -n 'e CMD' / -ni) while a\n# shorter allow prefix would auto-approve the whole command.\nprefix_rule(pattern=[\"rg\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"-n\"], decision=\"prompt\")\nprefix_rule(pattern=[\"find\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"-i\"], decision=\"prompt\")\nprefix_rule(pattern=[\"sed\", \"--in-place\"], decision=\"prompt\")\nprefix_rule(pattern=[\"awk\"], decision=\"prompt\")\nprefix_rule(pattern=[\"xargs\"], decision=\"prompt\")\nprefix_rule(pattern=[\"tee\"], decision=\"prompt\")\nprefix_rule(pattern=[\"curl\"], decision=\"prompt\")\nprefix_rule(pattern=[\"env\"], decision=\"prompt\")\n\nprefix_rule(pattern=[\"sudo\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"eval\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"mkfs\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"dd\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"--force\", \"origin\", \"main\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"--force\", \"origin\", \"master\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"-f\", \"origin\", \"main\"], decision=\"forbidden\")\nprefix_rule(pattern=[\"git\", \"push\", \"-f\", \"origin\", \"master\"], decision=\"forbidden\")\n",
20
20
  "SoT/.omp/AGENTS.md": "# Global OMP guidance\n\n- Verify unfamiliar or version-sensitive APIs and configuration against current official documentation before implementation.\n- When the user asks for an assessment rather than a change, report findings without editing.\n- For Docks plan reviews, cross-company review is standing-authorized; host security policy still applies.\n- Please remove all mannered prose.\n\n## Asking me things\n\nA question typed in prose is just text I may or may not act on. The `ask` tool renders a\nblocking picker, waits indefinitely (`ask.timeout = 0`), and records my answer in the\ntranscript. If you actually need an answer, it MUST go through `ask`.\n\nMUST use `ask` before:\n- Anything irreversible or destructive: deleting/overwriting files or data you did not create,\n force-push, history rewrite, dropping tables, running migrations, mass rename, touching\n secrets/credentials, or publishing outward (release, upstream PR, issue, comment).\n- Two or more viable approaches whose tradeoffs are mine to own: schema/API/protocol shape,\n adding a dependency, or establishing a convention this repo does not already have.\n- A fact only I hold: intended semantics of an ambiguous requirement, which of several\n conflicting existing patterns is canonical, or which environment/account/target to use.\n- A request that contradicts the repo: surface the conflict and let me resolve it; never\n silently pick one side.\n\nIf `ask` is not registered — subagent, headless, or `-p` print runs, where `hasUI` is false —\nthe MUST above cannot be satisfied: do not fabricate the call and do not stall on it. Take the\nconservative reversible option and put the question, plus the assumption you made, in your\nfinal report so whoever spawned you can decide.\n\nNEVER use `ask` for:\n- Permission to begin, or to confirm scope already stated in the request.\n- Anything a tool, grep, or doc can answer — go read it.\n- A cheap reversible choice — take the conservative option and say which you took.\n- Something already answered earlier in the conversation.\n\nBatch every open question into one `ask` call with multiple questions; do not serialize\nround trips. Being overruled ends the discussion — execute my call without relitigating.\n\n## Output Standard\n\nApply Simplified Technical English to all agent text. This includes responses, messages,\ndocumentation, comments, and interface text.\n\nReply in the language I use, and apply every rule below to that language.\n\nA rule that names English grammar applies only to English. The contraction ban is one\nsuch rule. In another language, follow the normal grammar of that language. Portuguese,\nSpanish, French, Italian, and German merge a preposition with an article, and that merge\nis required, not optional.\n\nTreat the word limits as approximate outside English. Some languages need more words to\ncarry the same content.\n\n- Use the simplest precise technical term.\n- Use each term consistently.\n- Expand an abbreviation at its first occurrence.\n- Explain a technical term when I ask for an explanation.\n- Write complete and grammatically correct sentences.\n- Use active voice and identify the actor.\n- Use the imperative form for instructions.\n- Put only one action in each instruction sentence.\n- Put a necessary condition before its instruction.\n- Use simple verb tenses.\n- Do not use contractions, idioms, or slang. Avoid humor and rhetorical questions.\n- Keep procedural sentences to 20 words or fewer.\n- Keep descriptive sentences to 25 words or fewer.\n- Keep each paragraph to one topic and six sentences or fewer.\n- Do not use more than three nouns together.\n- Use vertical lists for complex information.\n- Put a warning or caution before a related hazardous instruction.\n\n### Naming\n\n- Say what the thing does before you name it. Put the technical term after the plain\n description, once, in parentheses.\n- Do not use a technical term as the only name for something you just introduced.\n- Prefer the short common word. Use \"use\", not \"utilize\". Use \"set up\", not \"provision\".\n- Do not explain by metaphor alone. A metaphor may follow a literal statement.\n",
21
- "SoT/.omp/config.yml": "symbolPreset: unicode\n\ntheme:\n dark: titanium\n\nstatusLine:\n preset: default\n compactThinkingLevel: false\n showHookStatus: true\n sessionAccent: true\n transparent: false\n\nterminal:\n showProgress: false\n\ntui:\n textSizing: false\n tight: false\n\ndefaultThinkingLevel: high\n\ncycleOrder:\n - smol\n - default\n - slow\n - fable\n\ntier:\n openai: none\n anthropic: none\n\nadvisor:\n enabled: true\n syncBacklog: \"1\"\n\ngithub:\n enabled: true\n\ntask:\n eager: always\n showResolvedModelBadge: true\n softRequestBudget: 200\n softRequestBudgetNotice: true\n batch: true\n enableLsp: true\n enableEffort: true\n maxEffort: high\n maxConcurrency: 16\n maxRecursionDepth: 2\n maxRuntimeMs: 3600000\n agentModelOverrides:\n reviewer: \"@task\"\n security-reviewer: \"@task\"\n code-reviewer: \"@task\"\n plan-reviewer: \"@task\"\n\nmodelRoles:\n smol: openai-codex/gpt-5.6-luna:medium\n advisor: openai-codex/gpt-5.6-sol:medium\n designer: anthropic/claude-opus-5:high\n plan: anthropic/claude-opus-5:xhigh\n commit: openai-codex/gpt-5.6-luna:medium\n task: openai-codex/gpt-6-astra:low\n vision: anthropic/claude-opus-5:medium\n tiny: openai-codex/gpt-5.6-luna:low\n default: anthropic/claude-opus-5:high\n slow: anthropic/claude-opus-5:xhigh\n fable: anthropic/claude-fable-5-1:medium\n switch_fable: anthropic/claude-fable-5-1:medium\n\nmodelTags:\n fable:\n name: Fable 5.1\n switch_fable:\n name: Fable switch default\n hidden: true\n\ndisplay:\n shimmer: classic\n showTokenUsage: true\n\nproviders:\n anthropic:\n serverSideFallback: false\n fetch: auto\n webSearchTimeoutSeconds: 30\n webSearchOrder:\n - firecrawl\n - exa\n - perplexity\n - gemini\n - codex\n\nretry:\n usageAwareFallback: true\n fallbackChains:\n default:\n - openai-codex/gpt-5.6-sol:high\n advisor:\n - anthropic/claude-opus-5:medium\n task:\n - anthropic/claude-opus-5:high\n vision:\n - openai-codex/gpt-5.6-sol:medium\n smol:\n - anthropic/claude-opus-5:low\n tiny:\n - anthropic/claude-opus-5:low\n commit:\n - anthropic/claude-opus-5:medium\n switch_fable: []\n fable: []\n\nomitThinking: false\nincludeWorkspaceTree: false\nautocompleteMaxVisible: 10\nemojiAutocomplete: true\n\nbranchSummary:\n enabled: true\n\nreadLineNumbers: false\n\ncommands:\n enableOpencodeProject: false\n enableOpencodeUser: false\n\nskills:\n enableClaudeUser: false\n enableCodexUser: false\n enableAgentsUser: true\n\nsteeringMode: all\ninterruptMode: immediate\n\ndev:\n autoqa: false\n autoqaConsent: denied\n\ncompaction:\n thresholdTokens: 231200\n idleEnabled: true\n handoffSaveToDisk: true\n\ndoubleEscapeAction: tree\nhideThinkingBlock: false\nautoResume: false\ntextVerbosity: low\n\nfeatures:\n unexpectedStopDetection: smart\n\ncodexResets:\n autoRedeem: \"no\"\n\nstartup:\n quiet: true\n changelogMode: summary\n\ntools:\n approvalMode: yolo\n",
21
+ "SoT/.omp/config.yml": "symbolPreset: unicode\n\ntheme:\n dark: titanium\n\nstatusLine:\n preset: default\n compactThinkingLevel: false\n showHookStatus: true\n sessionAccent: true\n transparent: false\n\nterminal:\n showProgress: false\n\ntui:\n textSizing: false\n tight: false\n\ndefaultThinkingLevel: high\n\ncycleOrder:\n - smol\n - default\n - slow\n - fable\n\ntier:\n openai: none\n anthropic: none\n\nadvisor:\n enabled: true\n syncBacklog: \"1\"\n\ngithub:\n enabled: true\n\ntask:\n eager: always\n showResolvedModelBadge: true\n softRequestBudget: 200\n softRequestBudgetNotice: true\n batch: true\n enableLsp: true\n enableEffort: true\n maxEffort: low\n maxConcurrency: 16\n maxRecursionDepth: 2\n maxRuntimeMs: 3600000\n agentModelOverrides:\n reviewer: \"@task\"\n security-reviewer: \"@task\"\n code-reviewer: \"@task\"\n plan-reviewer: \"@task\"\n\nmodelRoles:\n smol: openai-codex/gpt-5.6-luna:medium\n advisor: openai-codex/gpt-5.6-sol:medium\n designer: anthropic/claude-opus-5:high\n plan: anthropic/claude-opus-5:xhigh\n commit: openai-codex/gpt-5.6-luna:medium\n task: openai-codex/gpt-6-astra:low\n vision: anthropic/claude-opus-5:medium\n tiny: openai-codex/gpt-5.6-luna:low\n default: anthropic/claude-opus-5:high\n slow: anthropic/claude-opus-5:xhigh\n fable: anthropic/claude-fable-5-1:medium\n switch_fable: anthropic/claude-fable-5-1:medium\n\nmodelTags:\n fable:\n name: Fable 5.1\n switch_fable:\n name: Fable switch default\n hidden: true\n\ndisplay:\n shimmer: classic\n showTokenUsage: true\n\nproviders:\n anthropic:\n serverSideFallback: false\n fetch: auto\n webSearchTimeoutSeconds: 30\n webSearchOrder:\n - firecrawl\n - exa\n - perplexity\n - gemini\n - codex\n\nretry:\n usageAwareFallback: true\n fallbackChains:\n default:\n - openai-codex/gpt-5.6-sol:high\n advisor:\n - anthropic/claude-opus-5:medium\n task:\n - anthropic/claude-opus-5:high\n vision:\n - openai-codex/gpt-5.6-sol:medium\n smol:\n - anthropic/claude-opus-5:low\n tiny:\n - anthropic/claude-opus-5:low\n commit:\n - anthropic/claude-opus-5:medium\n switch_fable: []\n fable: []\n\nomitThinking: false\nincludeWorkspaceTree: false\nautocompleteMaxVisible: 10\nemojiAutocomplete: true\n\nbranchSummary:\n enabled: true\n\nreadLineNumbers: false\n\ncommands:\n enableOpencodeProject: false\n enableOpencodeUser: false\n\nskills:\n enableClaudeUser: false\n enableCodexUser: false\n enableAgentsUser: true\n\nsteeringMode: all\ninterruptMode: immediate\n\ndev:\n autoqa: false\n autoqaConsent: denied\n\ncompaction:\n thresholdTokens: 231200\n idleEnabled: true\n handoffSaveToDisk: true\n\ndoubleEscapeAction: tree\nhideThinkingBlock: false\nautoResume: false\ntextVerbosity: low\n\nfeatures:\n unexpectedStopDetection: smart\n\ncodexResets:\n autoRedeem: \"no\"\n\nstartup:\n quiet: true\n changelogMode: summary\n\ntools:\n approvalMode: yolo\n",
22
22
  "SoT/.omp/intercom.json": "{\n \"brokerCommand\": \"bun\",\n \"brokerArgs\": []\n}\n",
23
23
  "SoT/.omp/mcp.json": "{\n \"$schema\": \"https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json\",\n \"disabledServers\": [\n \"chrome-devtools\",\n \"context7:context7\",\n \"openaiDeveloperDocs\"\n ]\n}\n"
24
24
  } as const
@@ -48,4 +48,4 @@ export const GENERATED_PAYLOAD_PATHS = [
48
48
  "notification.mp3"
49
49
  ] as const
50
50
 
51
- export const GENERATED_PAYLOAD_HASH = "b5d7c2aee0741d56a5f6b427bd096d8548501bf59c4e818f41c01ba92e085aaa"
51
+ export const GENERATED_PAYLOAD_HASH = "df37d563d668ab4f09d4b0ab50f9e7ff81612c2032b57870b0f919aacef43855"