thoth-agents 0.3.8 → 0.3.9

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/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  getOpenCodeVersion,
5
5
  isOpenCodeInstalled,
6
6
  opencodeAdapter
7
- } from "../chunk-6GQYMWWT.js";
7
+ } from "../chunk-MY2GIOOH.js";
8
8
  import {
9
9
  CODEX_ROLE_NAMES,
10
10
  REQUIRED_SKILLS,
@@ -12,6 +12,7 @@ import {
12
12
  applyClaudeCodePlan,
13
13
  applyClaudeCodeSetup,
14
14
  applyCodexPlan,
15
+ applyCodexPluginSetup,
15
16
  applyCodexSetup,
16
17
  applyOpenCodePlan,
17
18
  buildClaudeCodeModelPlan,
@@ -19,6 +20,7 @@ import {
19
20
  buildClaudeCodeSyncPlan,
20
21
  buildClaudeCodeUpdatePlan,
21
22
  buildCodexModelPlan,
23
+ buildCodexPluginSetupPlan,
22
24
  buildCodexSetupPlan,
23
25
  buildCodexSyncPlan,
24
26
  buildCodexUpdatePlan,
@@ -28,14 +30,15 @@ import {
28
30
  claudeCodeAdapter,
29
31
  codexAdapter,
30
32
  defaultClaudeCodeModelRoles,
33
+ finalizeHarnessInstall,
31
34
  formatClaudeCodeSetupPlan,
35
+ formatCodexPluginSetupPlan,
32
36
  formatCodexSetupPlan,
33
37
  getClaudeCodeModelRoles,
34
38
  getClaudeCodeStatus,
35
39
  getCodexModelRoles,
36
40
  getCodexStatus,
37
41
  getModelOptions,
38
- getNpxCommand,
39
42
  getOpenCodeModelRoles,
40
43
  getOpenCodeStatus,
41
44
  getOperationHarness,
@@ -44,7 +47,7 @@ import {
44
47
  listOperationHarnesses,
45
48
  normalizeEffortSelection,
46
49
  syncOpenCodeOwnedSkills
47
- } from "../chunk-3WNTCPVK.js";
50
+ } from "../chunk-XEEYNMPC.js";
48
51
  import {
49
52
  ALL_AGENT_NAMES,
50
53
  detectCurrentConfig,
@@ -52,9 +55,10 @@ import {
52
55
  getDefaultOpenCodeModel,
53
56
  getDefaultOpenCodeVariant,
54
57
  getExistingLiteConfigPath,
58
+ resolveExecutingPackageVersion,
55
59
  updateOpenCodeMainConfig,
56
60
  writeLiteConfig
57
- } from "../chunk-F2K4CS5J.js";
61
+ } from "../chunk-2TLT6WWY.js";
58
62
 
59
63
  // src/cli/index.ts
60
64
  import { pathToFileURL } from "url";
@@ -74,402 +78,6 @@ function getHarnessAdapter(harness) {
74
78
  import { existsSync } from "fs";
75
79
  import { homedir } from "os";
76
80
  import { cwd } from "process";
77
-
78
- // src/cli/codex-plugin-install.ts
79
- import { spawnSync } from "child_process";
80
- var MARKETPLACE_NAME = "thoth-agents";
81
- var MARKETPLACE_SOURCE = "EremesNG/thoth-agents";
82
- var PLUGIN_ID = "thoth-agents@thoth-agents";
83
- var MARKETPLACE_TARGET = "codex://marketplaces/thoth-agents";
84
- var PLUGIN_TARGET = "codex://plugins/thoth-agents@thoth-agents";
85
- function getCodexCommand(args, options = {}) {
86
- if ((options.platform ?? process.platform) !== "win32") {
87
- return { command: "codex", args: [...args] };
88
- }
89
- return {
90
- command: options.commandShell ?? process.env.ComSpec ?? "cmd.exe",
91
- args: ["/d", "/s", "/c", "codex", ...args]
92
- };
93
- }
94
- function defaultCommandExecutor(command, args, options) {
95
- const invocation = command === "codex" ? getCodexCommand(args) : { command, args: [...args] };
96
- const result = spawnSync(invocation.command, invocation.args, {
97
- cwd: options.cwd,
98
- encoding: "utf8",
99
- shell: false,
100
- windowsHide: true
101
- });
102
- return {
103
- exitCode: result.status ?? (result.error ? 1 : 0),
104
- stdout: result.stdout ?? "",
105
- stderr: result.stderr ?? result.error?.message ?? ""
106
- };
107
- }
108
- function isRecord(value) {
109
- return typeof value === "object" && value !== null && !Array.isArray(value);
110
- }
111
- function parseRecordArray(value) {
112
- return Array.isArray(value) && value.every(isRecord) ? value : null;
113
- }
114
- function parseManagerRecord(content) {
115
- try {
116
- const parsed = JSON.parse(content);
117
- return isRecord(parsed) ? parsed : null;
118
- } catch {
119
- return null;
120
- }
121
- }
122
- function normalizeMarketplaceSource(value) {
123
- return value.trim().replace(/^git@github\.com:/i, "").replace(/^(?:https?|ssh):\/\/(?:git@)?github\.com\//i, "").replace(/^github\.com\//i, "").replace(/\/?\.git\/?$/i, "").replace(/\/$/, "").toLowerCase();
124
- }
125
- function marketplaceSources(entry) {
126
- const sources = [entry.repo, entry.source];
127
- if (isRecord(entry.marketplaceSource)) {
128
- sources.push(entry.marketplaceSource.repo, entry.marketplaceSource.source);
129
- }
130
- return sources.filter(
131
- (source) => typeof source === "string"
132
- );
133
- }
134
- function isCanonicalMarketplace(entry) {
135
- if (entry.name !== MARKETPLACE_NAME) return false;
136
- const canonical = MARKETPLACE_SOURCE.toLowerCase();
137
- return marketplaceSources(entry).some(
138
- (source) => normalizeMarketplaceSource(source) === canonical
139
- );
140
- }
141
- function inspectCodexManager(executor, cwd2) {
142
- const marketplaceResult = executor(
143
- "codex",
144
- ["plugin", "marketplace", "list", "--json"],
145
- { cwd: cwd2 }
146
- );
147
- const pluginResult = executor(
148
- "codex",
149
- ["plugin", "list", "--available", "--json"],
150
- { cwd: cwd2 }
151
- );
152
- const diagnostics = [];
153
- if (marketplaceResult.exitCode !== 0 || pluginResult.exitCode !== 0) {
154
- diagnostics.push(
155
- "Codex native plugin state could not be inspected; no manager mutation will be attempted."
156
- );
157
- return {
158
- ready: false,
159
- marketplace: "unknown",
160
- plugin: "unknown",
161
- diagnostics
162
- };
163
- }
164
- const marketplaceRecord = parseManagerRecord(marketplaceResult.stdout);
165
- const pluginRecord = parseManagerRecord(pluginResult.stdout);
166
- const marketplaces = marketplaceRecord ? parseRecordArray(marketplaceRecord.marketplaces) : null;
167
- const installedPlugins = pluginRecord ? parseRecordArray(pluginRecord.installed) : null;
168
- if (!marketplaces || !installedPlugins) {
169
- diagnostics.push(
170
- "Codex returned unparseable plugin manager JSON; no manager mutation will be attempted."
171
- );
172
- return {
173
- ready: false,
174
- marketplace: "unknown",
175
- plugin: "unknown",
176
- diagnostics
177
- };
178
- }
179
- const namedMarketplaces = marketplaces.filter(
180
- (entry) => entry.name === MARKETPLACE_NAME
181
- );
182
- let marketplace = "absent";
183
- if (namedMarketplaces.length > 0) {
184
- marketplace = namedMarketplaces.every(isCanonicalMarketplace) ? "installed" : "conflict";
185
- }
186
- if (marketplace === "conflict") {
187
- diagnostics.push(
188
- "A Codex marketplace named thoth-agents is registered from a different source; resolve it through Codex before retrying."
189
- );
190
- }
191
- const matchingPlugins = installedPlugins.filter(
192
- (entry) => entry.pluginId === PLUGIN_ID
193
- );
194
- let plugin = "absent";
195
- if (matchingPlugins.some((entry) => entry.enabled === true)) {
196
- plugin = "installed";
197
- } else if (matchingPlugins.length > 0) {
198
- plugin = "disabled";
199
- }
200
- return {
201
- ready: marketplace !== "conflict",
202
- marketplace,
203
- plugin,
204
- diagnostics
205
- };
206
- }
207
- function commandItem(action, projectRoot) {
208
- if (action === "register-marketplace") {
209
- return {
210
- action,
211
- targetPath: MARKETPLACE_TARGET,
212
- description: "Register the package-owned thoth-agents Codex marketplace.",
213
- command: {
214
- executable: "codex",
215
- args: ["plugin", "marketplace", "add", MARKETPLACE_SOURCE, "--json"],
216
- cwd: projectRoot
217
- }
218
- };
219
- }
220
- return {
221
- action,
222
- targetPath: PLUGIN_TARGET,
223
- description: "Install and enable the thoth-agents Codex plugin from its native marketplace.",
224
- command: {
225
- executable: "codex",
226
- args: ["plugin", "add", PLUGIN_ID, "--json"],
227
- cwd: projectRoot
228
- }
229
- };
230
- }
231
- function buildCodexPluginSetupPlan(config) {
232
- const commandExecutor = config.commandExecutor ?? defaultCommandExecutor;
233
- const inspection = inspectCodexManager(commandExecutor, config.projectRoot);
234
- const items = [];
235
- if (inspection.ready) {
236
- if (inspection.marketplace === "absent") {
237
- items.push(commandItem("register-marketplace", config.projectRoot));
238
- }
239
- if (inspection.plugin === "absent" || inspection.plugin === "disabled") {
240
- items.push(commandItem("install-plugin", config.projectRoot));
241
- }
242
- }
243
- return {
244
- dryRun: config.dryRun === true,
245
- items,
246
- ready: inspection.ready,
247
- projectRoot: config.projectRoot,
248
- commandExecutor,
249
- diagnostics: [
250
- ...inspection.diagnostics,
251
- "Codex installs thoth-agents from EremesNG/thoth-agents through its native plugin manager and owns the cached plugin files.",
252
- "Restart Codex after installation; use /plugins to inspect marketplace and plugin state."
253
- ]
254
- };
255
- }
256
- function uniqueMessages(messages) {
257
- return [...new Set(messages)];
258
- }
259
- function boundedCommandFailure(item, result) {
260
- const detail = result.stderr.trim().replace(/\s+/g, " ").slice(0, 240);
261
- return `${item.description} Codex exited with code ${result.exitCode}${detail ? `: ${detail}` : "."}`;
262
- }
263
- function applyCodexPluginSetup(plan) {
264
- const changed = [];
265
- const diagnostics = uniqueMessages(plan.diagnostics);
266
- if (!plan.ready) {
267
- return {
268
- success: false,
269
- changed,
270
- diagnostics,
271
- error: "Codex native plugin manager state is not safe to mutate."
272
- };
273
- }
274
- if (plan.dryRun) return { success: true, changed, diagnostics };
275
- for (const item of plan.items) {
276
- const result = plan.commandExecutor(
277
- item.command.executable,
278
- item.command.args,
279
- { cwd: item.command.cwd }
280
- );
281
- if (result.exitCode !== 0) {
282
- return {
283
- success: false,
284
- changed,
285
- diagnostics,
286
- error: boundedCommandFailure(item, result)
287
- };
288
- }
289
- changed.push(item.targetPath);
290
- }
291
- const inspection = inspectCodexManager(
292
- plan.commandExecutor,
293
- plan.projectRoot
294
- );
295
- diagnostics.push(...inspection.diagnostics);
296
- if (inspection.marketplace !== "installed" || inspection.plugin !== "installed") {
297
- return {
298
- success: false,
299
- changed,
300
- diagnostics: uniqueMessages(diagnostics),
301
- error: "Codex did not verify the expected marketplace and enabled plugin after mutation."
302
- };
303
- }
304
- return { success: true, changed, diagnostics: uniqueMessages(diagnostics) };
305
- }
306
- function formatCodexPluginSetupPlan(plan) {
307
- const lines = plan.items.map(
308
- (item) => `- ${item.action}: ${item.command.executable} ${item.command.args.join(" ")}`
309
- );
310
- return ["Codex plugin setup plan:", ...lines].join("\n");
311
- }
312
-
313
- // src/cli/thoth-mem-install.ts
314
- import { spawnSync as spawnSync2 } from "child_process";
315
- var THOTH_MEM_SETUP_TIMEOUT_MS = 12e4;
316
- var STATUS_EXIT_CODE = {
317
- complete: 0,
318
- failed: 1,
319
- partial: 2,
320
- requires_user_action: 3
321
- };
322
- var SETUP_STATUSES = new Set(
323
- Object.keys(STATUS_EXIT_CODE)
324
- );
325
- var STEP_OUTCOMES = /* @__PURE__ */ new Set([
326
- "planned",
327
- "skipped",
328
- "confirmed",
329
- "failed",
330
- "unavailable"
331
- ]);
332
- function isRecord2(value) {
333
- return typeof value === "object" && value !== null && !Array.isArray(value);
334
- }
335
- function isStringArray(value) {
336
- return Array.isArray(value) && value.every((item) => typeof item === "string");
337
- }
338
- function isSetupStep(value) {
339
- return isRecord2(value) && typeof value.name === "string" && value.name.length > 0 && typeof value.outcome === "string" && STEP_OUTCOMES.has(value.outcome);
340
- }
341
- function isProviderSetupResult(value, expectedHarness) {
342
- return isRecord2(value) && typeof value.status === "string" && SETUP_STATUSES.has(value.status) && typeof value.changed === "boolean" && value.harness === expectedHarness && value.scope === "global" && typeof value.target === "string" && value.target.length > 0 && Array.isArray(value.steps) && value.steps.every(isSetupStep) && isStringArray(value.diagnostics) && isStringArray(value.manual_actions) && (value.receipt === null || typeof value.receipt === "string");
343
- }
344
- function parseProviderSetupResult(stdout, expectedHarness) {
345
- let value;
346
- try {
347
- value = JSON.parse(stdout.trim());
348
- } catch {
349
- return void 0;
350
- }
351
- return isProviderSetupResult(value, expectedHarness) ? value : void 0;
352
- }
353
- function defaultCommandExecutor2(command, args, options) {
354
- const result = spawnSync2(command, [...args], {
355
- cwd: options.cwd,
356
- encoding: "utf8",
357
- stdio: ["ignore", "pipe", "pipe"],
358
- timeout: options.timeoutMs
359
- });
360
- return {
361
- exitCode: result.status,
362
- stdout: result.stdout ?? "",
363
- stderr: result.stderr ?? "",
364
- error: result.error
365
- };
366
- }
367
- function describeError(error) {
368
- return error instanceof Error ? error.message : String(error);
369
- }
370
- function isTimeoutError(error) {
371
- return isRecord2(error) && error.code === "ETIMEDOUT";
372
- }
373
- function invalidResult(harness, command, args, exitCode, error) {
374
- return {
375
- success: false,
376
- evidenceValid: false,
377
- status: "invalid",
378
- changed: false,
379
- harness,
380
- steps: [],
381
- diagnostics: [],
382
- manualActions: [],
383
- receipt: null,
384
- command,
385
- args,
386
- exitCode,
387
- error
388
- };
389
- }
390
- function getThothMemSetupCommand(harness, dryRun, options = {}) {
391
- return getNpxCommand(
392
- [
393
- "-y",
394
- "thoth-mem@latest",
395
- "setup",
396
- harness,
397
- "--scope",
398
- "global",
399
- ...dryRun ? ["--plan"] : [],
400
- "--json"
401
- ],
402
- options
403
- );
404
- }
405
- function runThothMemSetup(options) {
406
- const { command, args } = getThothMemSetupCommand(
407
- options.harness,
408
- options.dryRun ?? false
409
- );
410
- const executor = options.commandExecutor ?? defaultCommandExecutor2;
411
- let commandResult;
412
- try {
413
- commandResult = executor(command, args, {
414
- cwd: options.cwd,
415
- timeoutMs: THOTH_MEM_SETUP_TIMEOUT_MS
416
- });
417
- } catch (error) {
418
- return invalidResult(
419
- options.harness,
420
- command,
421
- args,
422
- null,
423
- `Unable to launch thoth-mem setup: ${describeError(error)}`
424
- );
425
- }
426
- if (commandResult.error) {
427
- const stderr = commandResult.stderr.trim().slice(0, 2e3);
428
- return invalidResult(
429
- options.harness,
430
- command,
431
- args,
432
- commandResult.exitCode,
433
- isTimeoutError(commandResult.error) ? `thoth-mem setup timed out after ${THOTH_MEM_SETUP_TIMEOUT_MS} ms${stderr ? `: ${stderr}` : "."}` : `Unable to launch thoth-mem setup: ${describeError(commandResult.error)}`
434
- );
435
- }
436
- const provider = parseProviderSetupResult(
437
- commandResult.stdout,
438
- options.harness
439
- );
440
- if (!provider) {
441
- const stderr = commandResult.stderr.trim();
442
- return invalidResult(
443
- options.harness,
444
- command,
445
- args,
446
- commandResult.exitCode,
447
- stderr ? `thoth-mem returned invalid JSON evidence: ${stderr.slice(0, 2e3)}` : "thoth-mem returned invalid JSON evidence."
448
- );
449
- }
450
- const expectedExitCode = STATUS_EXIT_CODE[provider.status];
451
- const evidenceValid = commandResult.exitCode === expectedExitCode;
452
- return {
453
- success: evidenceValid && provider.status === "complete",
454
- evidenceValid,
455
- status: provider.status,
456
- changed: provider.changed,
457
- harness: provider.harness,
458
- target: provider.target,
459
- steps: provider.steps,
460
- diagnostics: provider.diagnostics,
461
- manualActions: provider.manual_actions,
462
- receipt: provider.receipt,
463
- command,
464
- args,
465
- exitCode: commandResult.exitCode,
466
- ...!evidenceValid ? {
467
- error: `thoth-mem exit code ${String(commandResult.exitCode)} contradicts reported status ${provider.status} (expected ${expectedExitCode}).`
468
- } : {}
469
- };
470
- }
471
-
472
- // src/cli/install.ts
473
81
  var GREEN = "\x1B[32m";
474
82
  var BLUE = "\x1B[34m";
475
83
  var YELLOW = "\x1B[33m";
@@ -628,14 +236,32 @@ function printThothMemSetupResult(result, dryRun) {
628
236
  );
629
237
  return true;
630
238
  }
631
- function installThothMemForHarness(harness, dryRun, dependencies) {
632
- const setup = dependencies.runThothMemSetup ?? runThothMemSetup;
633
- return printThothMemSetupResult(
634
- setup({ harness, dryRun, cwd: cwd() }),
635
- dryRun
636
- );
239
+ function finalizeInstallForHarness(harness, dryRun, version, dependencies) {
240
+ const finalize = dependencies.finalizeHarnessInstall ?? finalizeHarnessInstall;
241
+ const result = finalize({
242
+ harness,
243
+ version,
244
+ dryRun,
245
+ cwd: cwd(),
246
+ runThothMemSetup: dependencies.runThothMemSetup,
247
+ ledgerOptions: dependencies.installLedgerOptions ?? {
248
+ homeDir: dependencies.homeDir ?? homedir()
249
+ }
250
+ });
251
+ const providerComplete = printThothMemSetupResult(result.provider, dryRun);
252
+ if (!providerComplete) return false;
253
+ if (!result.success) {
254
+ printError(result.error ?? "Failed to record the completed CLI install.");
255
+ return false;
256
+ }
257
+ if (result.ledger.status === "planned") {
258
+ printInfo(`CLI-managed install record planned: ${result.ledger.path}`);
259
+ } else if (result.ledger.status === "recorded") {
260
+ printSuccess(`CLI-managed install version recorded: ${result.ledger.path}`);
261
+ }
262
+ return true;
637
263
  }
638
- async function runInstall(config, dependencies) {
264
+ async function runInstall(config, dependencies, pluginVersion) {
639
265
  const detected = detectCurrentConfig();
640
266
  const isUpdate = detected.isInstalled;
641
267
  printHeader(isUpdate);
@@ -656,8 +282,10 @@ async function runInstall(config, dependencies) {
656
282
  if (config.dryRun) {
657
283
  printInfo("Dry run mode - skipping main config update");
658
284
  } else {
659
- const mainConfigResult = updateOpenCodeMainConfig({
285
+ const updateMainConfig = dependencies.updateOpenCodeMainConfig ?? updateOpenCodeMainConfig;
286
+ const mainConfigResult = updateMainConfig({
660
287
  ensurePlugin: true,
288
+ pluginVersion,
661
289
  disableDefaults: true
662
290
  });
663
291
  if (!handleStepResult(mainConfigResult, "Main config updated")) return 1;
@@ -702,7 +330,12 @@ ${JSON.stringify(liteConfig, null, 2)}
702
330
  ))
703
331
  return 1;
704
332
  printStep(step++, totalSteps, "Configuring provider-owned thoth-mem...");
705
- if (!installThothMemForHarness("opencode", config.dryRun, dependencies)) {
333
+ if (!finalizeInstallForHarness(
334
+ "opencode",
335
+ config.dryRun,
336
+ pluginVersion,
337
+ dependencies
338
+ )) {
706
339
  return 1;
707
340
  }
708
341
  console.log();
@@ -733,6 +366,14 @@ function createInstallConfig(args) {
733
366
  }
734
367
  async function install(args, dependencies = {}) {
735
368
  const config = createInstallConfig(args);
369
+ const resolvePackageVersion = dependencies.resolveExecutingPackageVersion ?? resolveExecutingPackageVersion;
370
+ const packageVersion = resolvePackageVersion();
371
+ if (!packageVersion.ok) {
372
+ printError(
373
+ `Could not resolve the executing thoth-agents package version: ${packageVersion.error.message}`
374
+ );
375
+ return 1;
376
+ }
736
377
  if (config.agent === "codex") {
737
378
  const projectRoot = cwd();
738
379
  const pluginPlan = buildCodexPluginSetupPlan({
@@ -751,7 +392,7 @@ async function install(args, dependencies = {}) {
751
392
  reset: config.reset,
752
393
  scope: "user",
753
394
  projectRoot,
754
- homeDir: homedir()
395
+ homeDir: dependencies.homeDir ?? homedir()
755
396
  });
756
397
  console.log(formatCodexSetupPlan(plan));
757
398
  const result = applyCodexSetup(plan);
@@ -760,8 +401,18 @@ async function install(args, dependencies = {}) {
760
401
  printError(`Codex install failed: ${result.error}`);
761
402
  return 1;
762
403
  }
763
- if (!installRequiredSkillsForHarness("codex", config.dryRun)) return 1;
764
- if (!installThothMemForHarness("codex", config.dryRun, dependencies)) {
404
+ if (!installRequiredSkillsForHarness(
405
+ "codex",
406
+ config.dryRun,
407
+ dependencies.homeDir ?? homedir()
408
+ ))
409
+ return 1;
410
+ if (!finalizeInstallForHarness(
411
+ "codex",
412
+ config.dryRun,
413
+ packageVersion.version,
414
+ dependencies
415
+ )) {
765
416
  return 1;
766
417
  }
767
418
  printSuccess(
@@ -783,8 +434,18 @@ async function install(args, dependencies = {}) {
783
434
  printError(`Claude Code install failed: ${result.error}`);
784
435
  return 1;
785
436
  }
786
- if (!installRequiredSkillsForHarness("claude", config.dryRun)) return 1;
787
- if (!installThothMemForHarness("claude", config.dryRun, dependencies)) {
437
+ if (!installRequiredSkillsForHarness(
438
+ "claude",
439
+ config.dryRun,
440
+ dependencies.homeDir ?? homedir()
441
+ ))
442
+ return 1;
443
+ if (!finalizeInstallForHarness(
444
+ "claude",
445
+ config.dryRun,
446
+ packageVersion.version,
447
+ dependencies
448
+ )) {
788
449
  return 1;
789
450
  }
790
451
  printSuccess(
@@ -792,7 +453,7 @@ async function install(args, dependencies = {}) {
792
453
  );
793
454
  return 0;
794
455
  }
795
- return runInstall(config, dependencies);
456
+ return runInstall(config, dependencies, packageVersion.version);
796
457
  }
797
458
 
798
459
  // src/cli/commands.ts
@@ -808,6 +469,20 @@ function formatTarget(target) {
808
469
  const observed = target.observed ? ` observed ${target.observed}` : "";
809
470
  return `- ${label}${location}${state}${expected}${observed}`;
810
471
  }
472
+ function formatCliManagedInstall(targets) {
473
+ const target = targets.find(
474
+ ({ label }) => label === "CLI-managed install version"
475
+ );
476
+ if (!target) return [];
477
+ const executing = target.expected?.replace(/^executing\s+/, "") ?? "unknown";
478
+ const recorded = target.observed?.replace(/^recorded\s+/, "") ?? "unknown";
479
+ return [
480
+ "Official CLI-managed install:",
481
+ `Executing CLI version: ${executing}`,
482
+ `Recorded complete-install version: ${recorded}`,
483
+ "Native marketplace versions do not advance this record."
484
+ ];
485
+ }
811
486
  function formatPaths(paths) {
812
487
  return paths.map((path) => {
813
488
  const label = path.label ? `${path.label}: ` : "";
@@ -843,6 +518,7 @@ function formatHarnessStatusReport(reports) {
843
518
  `${report.displayName ?? getOperationHarness(report.harness).displayName} (${report.harness})`,
844
519
  `State: ${report.state}`,
845
520
  `Summary: ${report.summary}`,
521
+ ...formatCliManagedInstall(report.targets),
846
522
  ...report.providerCapability ? [
847
523
  "Provider evidence:",
848
524
  `Provider capability: ${report.providerCapability.state}`,
@@ -924,6 +600,8 @@ function formatOperationApplyResult(result) {
924
600
  ]);
925
601
  }
926
602
  function printHelp() {
603
+ const packageVersion = resolveExecutingPackageVersion();
604
+ const exactPluginEntry = packageVersion.ok ? `thoth-agents@${packageVersion.version}` : "thoth-agents@<executing-version-unavailable>";
927
605
  console.log(`
928
606
  thoth-agents CLI (npm binary: thoth-agents)
929
607
 
@@ -937,9 +615,9 @@ Commands:
937
615
  (no command) Open the interactive TUI in a TTY; fall back to OpenCode install in CI/non-TTY
938
616
  install Install OpenCode, Codex, or Claude Code agent assets
939
617
  generate Generate harness-specific artifacts
940
- status Show managed install status
618
+ status Show official CLI-managed versions and managed install status
941
619
  list List managed surfaces and actions
942
- update Preview managed updates
620
+ update Preview a complete selected-harness CLI refresh
943
621
  sync Preview managed configuration sync
944
622
  model Preview role model/provider settings
945
623
 
@@ -947,6 +625,7 @@ Options:
947
625
  --tmux=yes|no Enable tmux integration (yes/no)
948
626
  --no-tui Non-interactive mode
949
627
  --dry-run Simulate install without writing files
628
+ --apply Apply a reviewed update, sync, or model plan
950
629
  --reset Repair managed installer-owned targets
951
630
  --agent=opencode|codex|claude
952
631
  Select OpenCode plugin install (default), Codex agent-pack, or Claude Code plugin setup
@@ -961,10 +640,18 @@ Generate options:
961
640
 
962
641
  OpenCode plugin config and the npm binary are separate surfaces.
963
642
  OpenCode loads the plugin with config such as:
964
- plugin: ["thoth-agents@latest"]
643
+ plugin: ["${exactPluginEntry}"]
965
644
 
966
645
  That plugin entry does not create a global thoth-agents command.
967
646
  Run this CLI through a global install, npx, or pnpm dlx.
647
+ @latest selects the CLI release; OpenCode receives that exact version pin.
648
+
649
+ Update performs the complete selected-harness CLI refresh and records success last.
650
+ The official record is $XDG_CONFIG_HOME/thoth-agents/install-state.json,
651
+ or ~/.config/thoth-agents/install-state.json when XDG_CONFIG_HOME is unset.
652
+ Codex and Claude marketplace versions remain native-manager-owned and do not prove
653
+ that CLI-managed agents, skills, configuration, or provider setup are aligned.
654
+ Runtime release checks notify only; use the latest CLI install or interactive CLI Update.
968
655
 
969
656
  OpenCode install configures the adaptive seven-role roster and native task delegation.
970
657
  Provider capability is external and reported only from caller-supplied evidence.
@@ -997,18 +684,16 @@ function operationContext() {
997
684
  function selectedHarness(args, fallback = "opencode") {
998
685
  return args.harness ?? fallback;
999
686
  }
1000
- function statusReports(args) {
687
+ function statusReports(args, context = operationContext()) {
1001
688
  const harnesses = args.all || !args.harness ? SUPPORTED_OPERATION_HARNESSES : [args.harness];
1002
- const context = operationContext();
1003
689
  return harnesses.map((harness) => {
1004
690
  if (harness === "opencode") return getOpenCodeStatus(context);
1005
691
  if (harness === "claude") return getClaudeCodeStatus(context);
1006
692
  return getCodexStatus(context);
1007
693
  });
1008
694
  }
1009
- function buildOperationPlan(command, args) {
695
+ function buildOperationPlan(command, args, context = operationContext()) {
1010
696
  const harness = selectedHarness(args);
1011
- const context = operationContext();
1012
697
  if (harness === "opencode") {
1013
698
  return command === "update" ? buildOpenCodeUpdatePlan(context) : buildOpenCodeSyncPlan(context);
1014
699
  }
@@ -1099,7 +784,7 @@ function applyOperationPlan(plan) {
1099
784
  if (plan.harness === "claude") return applyClaudeCodePlan(plan);
1100
785
  return applyCodexPlan(plan);
1101
786
  }
1102
- function printPlanOrApply(plan, args) {
787
+ function printPlanOrApply(plan, args, applyPlan) {
1103
788
  if (args.apply && args.dryRun) {
1104
789
  console.error("--apply cannot be combined with --dry-run.");
1105
790
  return 1;
@@ -1108,12 +793,17 @@ function printPlanOrApply(plan, args) {
1108
793
  console.log(formatOperationPlan(plan));
1109
794
  return 0;
1110
795
  }
1111
- console.log(formatOperationApplyResult(applyOperationPlan(plan)));
1112
- return 0;
796
+ const result = applyPlan(plan);
797
+ console.log(formatOperationApplyResult(result));
798
+ return result.applied ? 0 : 1;
1113
799
  }
1114
800
  async function runOperationCommand(command, args, services) {
1115
801
  if (command === "status") {
1116
- console.log(formatHarnessStatusReport(statusReports(args)));
802
+ console.log(
803
+ formatHarnessStatusReport(
804
+ statusReports(args, services.operationContext())
805
+ )
806
+ );
1117
807
  return 0;
1118
808
  }
1119
809
  if (command === "list") {
@@ -1121,11 +811,19 @@ async function runOperationCommand(command, args, services) {
1121
811
  return 0;
1122
812
  }
1123
813
  if (command === "update" || command === "sync") {
1124
- return printPlanOrApply(buildOperationPlan(command, args), args);
814
+ return printPlanOrApply(
815
+ buildOperationPlan(command, args, services.operationContext()),
816
+ args,
817
+ services.applyOperationPlan ?? applyOperationPlan
818
+ );
1125
819
  }
1126
820
  const plan = await buildModelPlan(args, services);
1127
821
  if (!plan) return printModelGuidance();
1128
- return printPlanOrApply(plan, args);
822
+ return printPlanOrApply(
823
+ plan,
824
+ args,
825
+ services.applyOperationPlan ?? applyOperationPlan
826
+ );
1129
827
  }
1130
828
  function printHarnessGeneration(args) {
1131
829
  if (!args.dryRun) {