docks-kit 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SoT/.codex/config.toml +1 -1
- package/SoT/models.json +5 -2
- package/cli/docs/flags.md +1 -0
- package/cli/docs/overview.md +3 -0
- package/cli/docs/platforms.md +6 -5
- package/cli/src/commands/model.ts +7 -3
- package/cli/src/commands/sync.ts +14 -3
- package/cli/src/commands/toolchain.ts +7 -3
- package/cli/src/engine-native/DESIGN.md +75 -2
- package/cli/src/engine-native/claudeModel.ts +9 -3
- package/cli/src/engine-native/claudeSync.ts +203 -97
- package/cli/src/engine-native/codexSync.ts +95 -47
- package/cli/src/engine-native/codexToml.ts +12 -5
- package/cli/src/engine-native/deps.ts +325 -0
- package/cli/src/engine-native/exec.ts +50 -2
- package/cli/src/engine-native/index.ts +45 -9
- package/cli/src/engine-native/logger.ts +35 -0
- package/cli/src/engine-native/models.ts +13 -12
- package/cli/src/engine-native/modes.ts +17 -10
- package/cli/src/engine-native/os.ts +29 -0
- package/cli/src/engine-native/parseArgs.ts +19 -17
- package/cli/src/engine-native/services.ts +96 -0
- package/cli/src/engine-native/skillsSync.ts +70 -51
- package/cli/src/engine-native/toolchain.ts +45 -63
- package/cli/src/engine.ts +11 -2
- package/cli/src/main.ts +3 -2
- package/cli/src/services.ts +34 -0
- package/package.json +1 -1
- package/cli/src/engine-native/output.ts +0 -20
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* change. Guard order, message strings, and backup behavior are golden-tested.
|
|
5
5
|
*/
|
|
6
6
|
import { spawnSync } from "node:child_process"
|
|
7
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"
|
|
7
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
|
|
8
8
|
|
|
9
9
|
import { syncCodexModel, replaceTopLevelSettingInFile } from "./codexToml"
|
|
10
|
-
import {
|
|
10
|
+
import { p } from "./exec"
|
|
11
11
|
import type { Ctx } from "./index"
|
|
12
12
|
import { compareCodepoints, isObject, jqStringify, parseJson, type Json } from "./jq"
|
|
13
|
-
import { echo, err, log, warn } from "./output"
|
|
14
13
|
|
|
15
14
|
export function codexSync(ctx: Ctx): void {
|
|
16
15
|
const codexDir = p(ctx.home, ".codex")
|
|
@@ -31,14 +30,15 @@ export function codexSync(ctx: Ctx): void {
|
|
|
31
30
|
// ---------------------------------------------------------- bubblewrap ----
|
|
32
31
|
|
|
33
32
|
function ensureBubblewrap(ctx: Ctx): void {
|
|
34
|
-
|
|
33
|
+
const { change, echo, warn } = ctx.services.logger
|
|
34
|
+
if (!bwrapSupportedOs(ctx)) return
|
|
35
35
|
|
|
36
36
|
if (ctx.dryRun) {
|
|
37
37
|
echo("[dry-run] verify bubblewrap installed (recommended Codex Linux sandbox runtime)")
|
|
38
38
|
return
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (
|
|
41
|
+
if (ctx.services.deps.probe("bwrap").state === "present") return
|
|
42
42
|
|
|
43
43
|
if (ctx.skipRtk) {
|
|
44
44
|
warn(
|
|
@@ -47,7 +47,7 @@ function ensureBubblewrap(ctx: Ctx): void {
|
|
|
47
47
|
return
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const pmInstall = bwrapDetectPmInstallCmd()
|
|
50
|
+
const pmInstall = bwrapDetectPmInstallCmd(ctx)
|
|
51
51
|
if (pmInstall === "") {
|
|
52
52
|
warn(
|
|
53
53
|
"bubblewrap not installed and no supported package manager found (apt-get/dnf/pacman/zypper). Codex may use its bundled helper if user namespaces work; install system bubblewrap manually when possible."
|
|
@@ -62,14 +62,13 @@ function ensureBubblewrap(ctx: Ctx): void {
|
|
|
62
62
|
return
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if (
|
|
65
|
+
if (ctx.services.deps.probe("bwrap").state === "missing") {
|
|
66
66
|
warn("Package install reported success but bwrap not on PATH — check installation manually")
|
|
67
67
|
return
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
if (spawnSync("unshare", ["-Ur", "true"], { stdio: "ignore" }).status === 0) {
|
|
71
|
-
|
|
72
|
-
log(`bubblewrap installed and functional (${version})`)
|
|
71
|
+
change(`bubblewrap installed and functional (${ctx.services.deps.version("bwrap")})`)
|
|
73
72
|
} else {
|
|
74
73
|
warn(
|
|
75
74
|
"bubblewrap installed but unprivileged user namespaces appear blocked. On Ubuntu 24.04+, prefer loading the AppArmor bwrap-userns-restrict profile; fallback: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
|
|
@@ -77,24 +76,27 @@ function ensureBubblewrap(ctx: Ctx): void {
|
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
function bwrapSupportedOs(): boolean {
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
function bwrapSupportedOs(ctx: Ctx): boolean {
|
|
80
|
+
const { warn } = ctx.services.logger
|
|
81
|
+
const pn = ctx.services.platform.name()
|
|
82
|
+
if (pn === "linux") return true
|
|
83
|
+
if (pn === "darwin" || pn === "windows") return false
|
|
83
84
|
warn("Unknown OS — skipping bubblewrap check; Codex sandbox may not work")
|
|
84
85
|
return false
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function bwrapDetectPmInstallCmd(): string {
|
|
88
|
-
if (
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
88
|
+
function bwrapDetectPmInstallCmd(ctx: Ctx): string {
|
|
89
|
+
if (ctx.services.deps.probe("apt-get").state === "present") return "sudo apt-get install -y bubblewrap"
|
|
90
|
+
if (ctx.services.deps.probe("dnf").state === "present") return "sudo dnf install -y bubblewrap"
|
|
91
|
+
if (ctx.services.deps.probe("pacman").state === "present") return "sudo pacman -S --noconfirm bubblewrap"
|
|
92
|
+
if (ctx.services.deps.probe("zypper").state === "present") return "sudo zypper install -y bubblewrap"
|
|
92
93
|
return ""
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// --------------------------------------------------------------- config ----
|
|
96
97
|
|
|
97
98
|
function syncConfig(ctx: Ctx, sotConfig: string, userConfig: string): void {
|
|
99
|
+
const { change, echo, verbose } = ctx.services.logger
|
|
98
100
|
if (!existsSync(sotConfig)) return
|
|
99
101
|
|
|
100
102
|
if (ctx.dryRun) {
|
|
@@ -104,17 +106,31 @@ function syncConfig(ctx: Ctx, sotConfig: string, userConfig: string): void {
|
|
|
104
106
|
|
|
105
107
|
if (!existsSync(userConfig)) {
|
|
106
108
|
copyFileSync(sotConfig, userConfig)
|
|
107
|
-
|
|
109
|
+
change("Codex config installed")
|
|
110
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
108
111
|
return
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
// Merge into a staging copy so `.bak` is written only when the config
|
|
115
|
+
// actually changes — an unconditional early backup lets a later no-op run
|
|
116
|
+
// overwrite the recovery copy with already-merged content.
|
|
117
|
+
const before = readFileSync(userConfig, "utf8")
|
|
118
|
+
const staging = `${userConfig}.merge.tmp`
|
|
119
|
+
writeFileSync(staging, before)
|
|
112
120
|
|
|
113
|
-
scrubDeprecatedFeatures(
|
|
114
|
-
mergeTopLevelSettings(sotConfig,
|
|
115
|
-
mergeTableSettings(sotConfig,
|
|
121
|
+
scrubDeprecatedFeatures(ctx, staging)
|
|
122
|
+
mergeTopLevelSettings(sotConfig, staging)
|
|
123
|
+
mergeTableSettings(sotConfig, staging)
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
if (readFileSync(staging, "utf8") === before) {
|
|
126
|
+
rmSync(staging, { force: true })
|
|
127
|
+
verbose("Codex config already in sync")
|
|
128
|
+
} else {
|
|
129
|
+
copyFileSync(userConfig, `${userConfig}.bak`)
|
|
130
|
+
renameSync(staging, userConfig)
|
|
131
|
+
change("Codex config merged (backup at config.toml.bak; user-only keys/tables preserved)")
|
|
132
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
/** codex::scrub_deprecated_features — the [features].use_legacy_landlock awk pass. */
|
|
@@ -152,14 +168,15 @@ export function scrubDeprecatedFeaturesText(content: string): string {
|
|
|
152
168
|
return out
|
|
153
169
|
}
|
|
154
170
|
|
|
155
|
-
function scrubDeprecatedFeatures(userConfig: string): void {
|
|
171
|
+
function scrubDeprecatedFeatures(ctx: Ctx, userConfig: string): void {
|
|
172
|
+
const { change } = ctx.services.logger
|
|
156
173
|
if (!existsSync(userConfig)) return
|
|
157
174
|
const content = readFileSync(userConfig, "utf8")
|
|
158
175
|
if (!content.split("\n").some((l) => /^use_legacy_landlock[ \t]*=/.test(l))) return
|
|
159
176
|
|
|
160
177
|
writeFileSync(`${userConfig}.tmp`, scrubDeprecatedFeaturesText(content))
|
|
161
178
|
renameSync(`${userConfig}.tmp`, userConfig)
|
|
162
|
-
|
|
179
|
+
change("Codex: scrubbed deprecated [features].use_legacy_landlock")
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
function mergeTopLevelSettings(sotConfig: string, userConfig: string): void {
|
|
@@ -209,6 +226,7 @@ function mergeTableSettings(sotConfig: string, userConfig: string): void {
|
|
|
209
226
|
// ------------------------------------------------------- rules + agents ----
|
|
210
227
|
|
|
211
228
|
function syncRules(ctx: Ctx, sotRulesDir: string, userRulesDir: string): void {
|
|
229
|
+
const { change, echo, verbose } = ctx.services.logger
|
|
212
230
|
if (!existsSync(sotRulesDir)) return
|
|
213
231
|
|
|
214
232
|
if (ctx.dryRun) {
|
|
@@ -217,21 +235,29 @@ function syncRules(ctx: Ctx, sotRulesDir: string, userRulesDir: string): void {
|
|
|
217
235
|
}
|
|
218
236
|
|
|
219
237
|
mkdirSync(userRulesDir, { recursive: true })
|
|
220
|
-
let
|
|
238
|
+
let sawRules = false
|
|
239
|
+
let rulesChanged = false
|
|
221
240
|
const ruleFiles = readdirSync(sotRulesDir, { withFileTypes: true })
|
|
222
241
|
.filter((e) => e.isFile() && e.name.endsWith(".rules"))
|
|
223
242
|
.map((e) => p(sotRulesDir, e.name))
|
|
224
243
|
.sort(compareCodepoints)
|
|
225
244
|
for (const ruleFile of ruleFiles) {
|
|
245
|
+
sawRules = true
|
|
226
246
|
const userRuleFile = p(userRulesDir, ruleFile.slice(ruleFile.lastIndexOf("/") + 1))
|
|
247
|
+
const identical = existsSync(userRuleFile) && readFileSync(userRuleFile).equals(readFileSync(ruleFile))
|
|
248
|
+
if (identical) continue
|
|
227
249
|
if (existsSync(userRuleFile)) copyFileSync(userRuleFile, `${userRuleFile}.bak`)
|
|
228
250
|
copyFileSync(ruleFile, userRuleFile)
|
|
229
|
-
|
|
251
|
+
rulesChanged = true
|
|
230
252
|
}
|
|
231
|
-
if (
|
|
253
|
+
if (rulesChanged) {
|
|
254
|
+
change("Codex rules synced")
|
|
255
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
256
|
+
} else if (sawRules) verbose("Codex rules already in sync")
|
|
232
257
|
}
|
|
233
258
|
|
|
234
259
|
function syncAgentsMd(ctx: Ctx, sotAgentsMd: string, userAgentsMd: string): void {
|
|
260
|
+
const { change, echo, verbose } = ctx.services.logger
|
|
235
261
|
if (!existsSync(sotAgentsMd)) return
|
|
236
262
|
|
|
237
263
|
if (ctx.dryRun) {
|
|
@@ -239,14 +265,20 @@ function syncAgentsMd(ctx: Ctx, sotAgentsMd: string, userAgentsMd: string): void
|
|
|
239
265
|
return
|
|
240
266
|
}
|
|
241
267
|
|
|
268
|
+
if (existsSync(userAgentsMd) && readFileSync(userAgentsMd).equals(readFileSync(sotAgentsMd))) {
|
|
269
|
+
verbose("Codex AGENTS.md already in sync")
|
|
270
|
+
return
|
|
271
|
+
}
|
|
242
272
|
if (existsSync(userAgentsMd)) copyFileSync(userAgentsMd, `${userAgentsMd}.bak`)
|
|
243
273
|
copyFileSync(sotAgentsMd, userAgentsMd)
|
|
244
|
-
|
|
274
|
+
change("Codex AGENTS.md synced")
|
|
275
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
245
276
|
}
|
|
246
277
|
|
|
247
278
|
// ---------------------------------------------------------- marketplace ----
|
|
248
279
|
|
|
249
280
|
function syncMarketplace(ctx: Ctx, sotMarketplace: string, userMarketplace: string): void {
|
|
281
|
+
const { change, echo, err, verbose } = ctx.services.logger
|
|
250
282
|
if (!existsSync(sotMarketplace)) return
|
|
251
283
|
|
|
252
284
|
if (ctx.dryRun) {
|
|
@@ -264,13 +296,20 @@ function syncMarketplace(ctx: Ctx, sotMarketplace: string, userMarketplace: stri
|
|
|
264
296
|
err(`Skipping marketplace sync: ${userMarketplace} is not valid JSON. Fix or delete it.`)
|
|
265
297
|
return
|
|
266
298
|
}
|
|
299
|
+
const out = jqStringify(mergeMarketplace(repo, user))
|
|
300
|
+
if (out === readFileSync(userMarketplace, "utf8")) {
|
|
301
|
+
verbose("Codex marketplace already in sync")
|
|
302
|
+
return
|
|
303
|
+
}
|
|
267
304
|
copyFileSync(userMarketplace, `${userMarketplace}.bak`)
|
|
268
|
-
writeFileSync(`${userMarketplace}.tmp`,
|
|
305
|
+
writeFileSync(`${userMarketplace}.tmp`, out)
|
|
269
306
|
renameSync(`${userMarketplace}.tmp`, userMarketplace)
|
|
270
|
-
|
|
307
|
+
change("Codex marketplace merged (backup at marketplace.json.bak)")
|
|
308
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
271
309
|
} else {
|
|
272
310
|
copyFileSync(sotMarketplace, userMarketplace)
|
|
273
|
-
|
|
311
|
+
change("Codex marketplace installed")
|
|
312
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
274
313
|
}
|
|
275
314
|
}
|
|
276
315
|
|
|
@@ -326,28 +365,27 @@ export function marketplaceSource(marketplace: string, configFile: string): stri
|
|
|
326
365
|
}
|
|
327
366
|
|
|
328
367
|
function removeLegacyDocksMarketplace(ctx: Ctx, userConfig: string): void {
|
|
368
|
+
const { change, echo, warn } = ctx.services.logger
|
|
329
369
|
if (ctx.dryRun) {
|
|
330
370
|
echo("[dry-run] remove legacy configured Codex Docks marketplace when personal marketplace is deployed")
|
|
331
371
|
return
|
|
332
372
|
}
|
|
333
373
|
|
|
334
|
-
if (
|
|
374
|
+
if (ctx.services.deps.probe("codex").state === "missing") return
|
|
335
375
|
|
|
336
376
|
const source = marketplaceSource("docks", userConfig)
|
|
337
377
|
if (source !== "https://github.com/DocksDocks/docks.git" && source !== "DocksDocks/docks") return
|
|
338
378
|
const res = spawnSync("codex", ["plugin", "marketplace", "remove", "docks"], { stdio: "ignore" })
|
|
339
379
|
if (res.error === undefined && res.status === 0) {
|
|
340
|
-
|
|
380
|
+
change("Removed legacy configured Codex Docks marketplace; using personal marketplace file")
|
|
381
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
341
382
|
} else {
|
|
342
383
|
warn("Failed to remove legacy configured Codex Docks marketplace")
|
|
343
384
|
}
|
|
344
385
|
}
|
|
345
386
|
|
|
346
387
|
/** codex::_standalone_install_command — per-OS official standalone installer. */
|
|
347
|
-
const standaloneInstallCommand = (): string =>
|
|
348
|
-
process.platform === "win32"
|
|
349
|
-
? `powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"`
|
|
350
|
-
: 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"'
|
|
388
|
+
const standaloneInstallCommand = (ctx: Ctx): string => ctx.services.deps.spec("codex").installHint()
|
|
351
389
|
|
|
352
390
|
/** codex::_enabled_plugin_ids — [plugins."<id>"] tables with enabled = true. */
|
|
353
391
|
export function enabledPluginIds(configFile: string): Array<string> {
|
|
@@ -386,20 +424,24 @@ function manualPluginRefreshCommand(sotConfig: string): string {
|
|
|
386
424
|
}
|
|
387
425
|
|
|
388
426
|
function syncPlugins(ctx: Ctx, sotConfig: string): void {
|
|
427
|
+
const { change, echo, warn } = ctx.services.logger
|
|
389
428
|
if (ctx.dryRun) {
|
|
390
429
|
echo("[dry-run] add enabled Codex plugins from SoT")
|
|
391
430
|
return
|
|
392
431
|
}
|
|
393
432
|
|
|
394
|
-
if (
|
|
433
|
+
if (ctx.services.deps.probe("codex").state === "missing") {
|
|
395
434
|
warn(
|
|
396
|
-
`codex CLI not in PATH - deployed config/marketplace only; install Codex with: ${standaloneInstallCommand()} | docs: https://developers.openai.com/codex/cli; then run: ${manualPluginRefreshCommand(sotConfig)}`
|
|
435
|
+
`codex CLI not in PATH - deployed config/marketplace only; install Codex with: ${standaloneInstallCommand(ctx)} | docs: https://developers.openai.com/codex/cli; then run: ${manualPluginRefreshCommand(sotConfig)}`
|
|
397
436
|
)
|
|
398
437
|
return
|
|
399
438
|
}
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
439
|
+
if (ctx.services.deps.probe("git").state === "missing") {
|
|
440
|
+
ctx.services.deps.warnMissing(
|
|
441
|
+
"git",
|
|
442
|
+
ctx.services.logger,
|
|
443
|
+
"plugin marketplaces are git repos — Codex plugin refresh skipped; re-run sync after installing"
|
|
444
|
+
)
|
|
403
445
|
return
|
|
404
446
|
}
|
|
405
447
|
|
|
@@ -412,7 +454,7 @@ function syncPlugins(ctx: Ctx, sotConfig: string): void {
|
|
|
412
454
|
refreshed++
|
|
413
455
|
} else if (addOut.includes("could not find a Codex CLI binary")) {
|
|
414
456
|
warn(
|
|
415
|
-
`Codex plugin refresh hit a stale launcher/wrapper on PATH - install current standalone Codex with: ${standaloneInstallCommand()}`
|
|
457
|
+
`Codex plugin refresh hit a stale launcher/wrapper on PATH - install current standalone Codex with: ${standaloneInstallCommand(ctx)}`
|
|
416
458
|
)
|
|
417
459
|
failed++
|
|
418
460
|
} else {
|
|
@@ -424,13 +466,17 @@ function syncPlugins(ctx: Ctx, sotConfig: string): void {
|
|
|
424
466
|
}
|
|
425
467
|
}
|
|
426
468
|
|
|
427
|
-
if (refreshed > 0)
|
|
469
|
+
if (refreshed > 0) {
|
|
470
|
+
change(`Codex plugins synced (plugins: ~${refreshed})`)
|
|
471
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
472
|
+
}
|
|
428
473
|
if (failed > 0) warn(`${failed} Codex plugin operation(s) failed — re-run sync or install manually`)
|
|
429
474
|
}
|
|
430
475
|
|
|
431
476
|
// -------------------------------------------------------------- summary ----
|
|
432
477
|
|
|
433
478
|
export function codexSummary(ctx: Ctx): void {
|
|
479
|
+
const { echo } = ctx.services.logger
|
|
434
480
|
const codexDir = p(ctx.home, ".codex")
|
|
435
481
|
echo(`Codex: ${codexDir}`)
|
|
436
482
|
if (!ctx.dryRun) {
|
|
@@ -439,6 +485,8 @@ export function codexSummary(ctx: Ctx): void {
|
|
|
439
485
|
}
|
|
440
486
|
}
|
|
441
487
|
|
|
442
|
-
export function codexNextSteps():
|
|
443
|
-
|
|
488
|
+
export function codexNextSteps(ctx: Ctx): Array<string> {
|
|
489
|
+
return ctx.verbose || ctx.nextStepTriggers.codexRestart
|
|
490
|
+
? ["Restart Codex to load any refreshed plugins, skills, or tools."]
|
|
491
|
+
: []
|
|
444
492
|
}
|
|
@@ -8,7 +8,6 @@ import { p } from "./exec"
|
|
|
8
8
|
import { readFileSync, renameSync, writeFileSync } from "node:fs"
|
|
9
9
|
|
|
10
10
|
import type { Ctx } from "./index"
|
|
11
|
-
import { echo, log, warn } from "./output"
|
|
12
11
|
|
|
13
12
|
export function replaceTopLevelSetting(content: string, key: string, replacement: string): string {
|
|
14
13
|
const lines = content.split("\n")
|
|
@@ -40,13 +39,17 @@ export function replaceTopLevelSetting(content: string, key: string, replacement
|
|
|
40
39
|
return `${out.join("\n")}\n`
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
export function replaceTopLevelSettingInFile(file: string, key: string, replacement: string):
|
|
44
|
-
const
|
|
42
|
+
export function replaceTopLevelSettingInFile(file: string, key: string, replacement: string): boolean {
|
|
43
|
+
const before = readFileSync(file, "utf8")
|
|
44
|
+
const next = replaceTopLevelSetting(before, key, replacement)
|
|
45
|
+
if (next === before) return false
|
|
45
46
|
writeFileSync(`${file}.tmp`, next)
|
|
46
47
|
renameSync(`${file}.tmp`, file)
|
|
48
|
+
return true
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function syncCodexModel(ctx: Ctx, model: string): void {
|
|
52
|
+
const { change, echo, verbose, warn } = ctx.services.logger
|
|
50
53
|
const userCodexSettings = p(ctx.home, ".codex", "config.toml")
|
|
51
54
|
|
|
52
55
|
if (model === "") return
|
|
@@ -62,6 +65,10 @@ export function syncCodexModel(ctx: Ctx, model: string): void {
|
|
|
62
65
|
warn(`(--codex-model) ${userCodexSettings} missing — skipped`)
|
|
63
66
|
return
|
|
64
67
|
}
|
|
65
|
-
replaceTopLevelSettingInFile(userCodexSettings, "model", `model = "${model}"`)
|
|
66
|
-
|
|
68
|
+
if (replaceTopLevelSettingInFile(userCodexSettings, "model", `model = "${model}"`)) {
|
|
69
|
+
change(`Model: deployed Codex model set to ${model} (SoT unchanged; flag-less sync reverts)`)
|
|
70
|
+
ctx.nextStepTriggers.codexRestart = true
|
|
71
|
+
} else {
|
|
72
|
+
verbose(`Model: deployed Codex model already ${model}`)
|
|
73
|
+
}
|
|
67
74
|
}
|