thoth-agents 0.1.5 → 0.1.7

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.
@@ -0,0 +1,1285 @@
1
+ import {
2
+ CODEX_ROLE_NAMES,
3
+ applyCodexPlan,
4
+ applyOpenCodePlan,
5
+ buildCodexInstallPlan,
6
+ buildCodexModelPlan,
7
+ buildCodexSetupPlan,
8
+ buildCodexSyncPlan,
9
+ buildCodexUpdatePlan,
10
+ buildOpenCodeInstallPlan,
11
+ buildOpenCodeModelPlan,
12
+ buildOpenCodeSyncPlan,
13
+ buildOpenCodeUpdatePlan,
14
+ getCodexStatus,
15
+ getOpenCodeStatus,
16
+ listOperationHarnesses,
17
+ parseRoleTomlModel
18
+ } from "../../chunk-OJCEGZSA.js";
19
+ import {
20
+ ALL_AGENT_NAMES,
21
+ DEFAULT_MODELS,
22
+ getExistingLiteConfigPath,
23
+ parseConfig
24
+ } from "../../chunk-OES76C67.js";
25
+
26
+ // src/cli/tui/index.tsx
27
+ import { render } from "ink";
28
+
29
+ // src/cli/tui/App.tsx
30
+ import { Box as Box8, Text as Text8, useApp, useInput } from "ink";
31
+ import { useMemo, useState } from "react";
32
+
33
+ // src/cli/tui/components/Header.tsx
34
+ import { Box, Text } from "ink";
35
+
36
+ // src/cli/tui/theme.ts
37
+ var theme = {
38
+ accent: "cyan",
39
+ danger: "red",
40
+ dim: "gray",
41
+ ok: "green",
42
+ title: "white",
43
+ warning: "yellow"
44
+ };
45
+ function stateColor(state) {
46
+ if (state === "installed") return "green";
47
+ if (state === "missing" || state === "outdated") return "yellow";
48
+ if (state === "drift" || state === "unknown") return "red";
49
+ return "gray";
50
+ }
51
+
52
+ // src/cli/tui/components/Header.tsx
53
+ import { jsx, jsxs } from "react/jsx-runtime";
54
+ function Header({ title, subtitle }) {
55
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
56
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.title, children: "thoth-agents" }),
57
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: title }),
58
+ subtitle ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: subtitle }) : null
59
+ ] });
60
+ }
61
+
62
+ // src/cli/tui/components/Menu.tsx
63
+ import { Box as Box2, Text as Text2 } from "ink";
64
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
65
+ function Menu({ items, selected }) {
66
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsxs2(
67
+ Text2,
68
+ {
69
+ color: item.disabled ? theme.dim : index === selected ? theme.accent : void 0,
70
+ children: [
71
+ index === selected ? ">" : " ",
72
+ " ",
73
+ item.label,
74
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
75
+ " - ",
76
+ item.detail
77
+ ] })
78
+ ]
79
+ },
80
+ item.id
81
+ )) });
82
+ }
83
+
84
+ // src/cli/tui/components/ModelChoiceScreen.tsx
85
+ import { Box as Box3, Text as Text3 } from "ink";
86
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
87
+ var VISIBLE_MODEL_OPTIONS = 8;
88
+ function modelWindowStart(optionsLength, selected) {
89
+ const maxStart = Math.max(0, optionsLength - VISIBLE_MODEL_OPTIONS);
90
+ if (selected >= optionsLength) return maxStart;
91
+ return Math.min(Math.max(0, selected - VISIBLE_MODEL_OPTIONS + 1), maxStart);
92
+ }
93
+ function ModelChoiceScreen({
94
+ currentModel,
95
+ draftModel,
96
+ options,
97
+ selected
98
+ }) {
99
+ const windowStart = modelWindowStart(options.length, selected);
100
+ const visibleOptions = options.slice(
101
+ windowStart,
102
+ windowStart + VISIBLE_MODEL_OPTIONS
103
+ );
104
+ const windowEnd = windowStart + visibleOptions.length;
105
+ const hasHiddenBefore = windowStart > 0;
106
+ const hasHiddenAfter = windowEnd < options.length;
107
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
108
+ /* @__PURE__ */ jsxs3(Text3, { children: [
109
+ "Current: ",
110
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: currentModel })
111
+ ] }),
112
+ /* @__PURE__ */ jsxs3(Text3, { children: [
113
+ "New: ",
114
+ /* @__PURE__ */ jsx3(Text3, { color: theme.warning, children: draftModel })
115
+ ] }),
116
+ hasHiddenBefore ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
117
+ " ... ",
118
+ windowStart,
119
+ " earlier option(s)"
120
+ ] }) : null,
121
+ visibleOptions.map((option, offset) => {
122
+ const index = windowStart + offset;
123
+ return /* @__PURE__ */ jsxs3(
124
+ Text3,
125
+ {
126
+ color: index === selected ? theme.accent : void 0,
127
+ children: [
128
+ index === selected ? ">" : " ",
129
+ " ",
130
+ option.id,
131
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
132
+ " - ",
133
+ option.provider
134
+ ] })
135
+ ]
136
+ },
137
+ option.id
138
+ );
139
+ }),
140
+ hasHiddenAfter ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
141
+ " ",
142
+ "... ",
143
+ options.length - windowEnd,
144
+ " later option(s)"
145
+ ] }) : null,
146
+ /* @__PURE__ */ jsxs3(Text3, { color: options.length === selected ? theme.accent : void 0, children: [
147
+ options.length === selected ? ">" : " ",
148
+ " Manual entry"
149
+ ] }),
150
+ options.length > VISIBLE_MODEL_OPTIONS ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
151
+ "Showing ",
152
+ windowStart + 1,
153
+ "-",
154
+ windowEnd,
155
+ " of ",
156
+ options.length,
157
+ ". Use j/k to move through all options; Manual entry follows the catalog."
158
+ ] }) : null
159
+ ] });
160
+ }
161
+
162
+ // src/cli/tui/components/ModelScreen.tsx
163
+ import { Box as Box4, Text as Text4 } from "ink";
164
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
165
+ var CODEX_MODEL_CATALOG_NOTE = [
166
+ "This list may not include every available model.",
167
+ "Official Codex model list: https://developers.openai.com/codex/models"
168
+ ];
169
+ function ModelScreen({
170
+ harness,
171
+ roles,
172
+ selected,
173
+ actions
174
+ }) {
175
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
176
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: harness === "codex" ? "Codex writes generated subagent model lines only." : "OpenCode writes role model overrides in thoth-agents config." }),
177
+ harness === "codex" ? CODEX_MODEL_CATALOG_NOTE.map((note) => /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: note }, note)) : null,
178
+ roles.map((role, index) => /* @__PURE__ */ jsxs4(
179
+ Text4,
180
+ {
181
+ color: index === selected ? theme.accent : void 0,
182
+ children: [
183
+ index === selected ? ">" : " ",
184
+ " ",
185
+ role.dirty ? "*" : " ",
186
+ role.role,
187
+ ": ",
188
+ role.model,
189
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: role.dirty ? ` (was ${role.currentModel})` : "" })
190
+ ]
191
+ },
192
+ role.role
193
+ )),
194
+ actions.map((action, offset) => {
195
+ const index = roles.length + offset;
196
+ return /* @__PURE__ */ jsxs4(
197
+ Text4,
198
+ {
199
+ color: index === selected ? theme.accent : void 0,
200
+ children: [
201
+ index === selected ? ">" : " ",
202
+ " ",
203
+ action
204
+ ]
205
+ },
206
+ action
207
+ );
208
+ })
209
+ ] });
210
+ }
211
+
212
+ // src/cli/tui/components/PlanPreview.tsx
213
+ import { Box as Box6, Text as Text6 } from "ink";
214
+
215
+ // src/cli/tui/components/PathLine.tsx
216
+ import { Box as Box5, Text as Text5 } from "ink";
217
+ import { jsx as jsx5 } from "react/jsx-runtime";
218
+ function wrapTerminalText(text, width = 74) {
219
+ if (text.length <= width) return [text];
220
+ const chunks = [];
221
+ let cursor = 0;
222
+ while (cursor < text.length) {
223
+ chunks.push(text.slice(cursor, cursor + width));
224
+ cursor += width;
225
+ }
226
+ return chunks;
227
+ }
228
+ function PathLine({ label, value, width }) {
229
+ const lines = wrapTerminalText(value, width);
230
+ return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: lines.map((line, index) => /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: index === 0 ? `${label ? `${label}: ` : ""}${line}` : ` ${line}` }, line)) });
231
+ }
232
+
233
+ // src/cli/tui/components/PlanPreview.tsx
234
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
235
+ function PlanPreview({
236
+ plan,
237
+ selectedAction,
238
+ result
239
+ }) {
240
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
241
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: plan.title }),
242
+ /* @__PURE__ */ jsx6(Text6, { children: plan.summary }),
243
+ /* @__PURE__ */ jsxs5(Text6, { children: [
244
+ "Target harness: ",
245
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: plan.harness })
246
+ ] }),
247
+ /* @__PURE__ */ jsxs5(Text6, { children: [
248
+ "Action: ",
249
+ plan.action
250
+ ] }),
251
+ /* @__PURE__ */ jsxs5(Text6, { children: [
252
+ "Can apply: ",
253
+ plan.canApply ? "yes" : "no"
254
+ ] }),
255
+ plan.surfaces.length > 0 ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Managed surfaces" }) : null,
256
+ plan.surfaces.map((surface) => /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
257
+ /* @__PURE__ */ jsxs5(Text6, { children: [
258
+ "- ",
259
+ surface.label
260
+ ] }),
261
+ surface.path ? /* @__PURE__ */ jsx6(PathLine, { value: surface.path }) : null
262
+ ] }, surface.id)),
263
+ /* @__PURE__ */ jsxs5(Text6, { color: theme.dim, children: [
264
+ "Backup: ",
265
+ plan.backup.required ? "required" : "not required",
266
+ " (",
267
+ plan.backup.strategy,
268
+ ")"
269
+ ] }),
270
+ plan.backup.destinations?.map((path) => /* @__PURE__ */ jsx6(PathLine, { label: path.label, value: path.path }, path.path)),
271
+ plan.items.length > 0 ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Preview" }) : null,
272
+ plan.items.slice(0, 5).map((item) => /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
273
+ /* @__PURE__ */ jsxs5(Text6, { children: [
274
+ "- ",
275
+ item.title
276
+ ] }),
277
+ item.target.path ? /* @__PURE__ */ jsx6(PathLine, { value: item.target.path }) : null,
278
+ item.preview ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: item.preview.length > 120 ? `${item.preview.slice(0, 120)}...` : item.preview }) : null
279
+ ] }, item.title)),
280
+ plan.items.length > 5 ? /* @__PURE__ */ jsxs5(Text6, { color: theme.dim, children: [
281
+ "...",
282
+ plan.items.length - 5,
283
+ " more items"
284
+ ] }) : null,
285
+ plan.warnings.map((warning) => /* @__PURE__ */ jsxs5(Text6, { color: theme.warning, children: [
286
+ "- [",
287
+ warning.severity,
288
+ "] ",
289
+ warning.message
290
+ ] }, warning.code ?? warning.message)),
291
+ plan.disclaimers.map((disclaimer) => /* @__PURE__ */ jsxs5(Text6, { color: theme.dim, children: [
292
+ "- ",
293
+ disclaimer.message
294
+ ] }, disclaimer.code ?? disclaimer.message)),
295
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, children: [
296
+ /* @__PURE__ */ jsx6(Text6, { color: selectedAction === "apply" ? theme.accent : void 0, children: "[Apply]" }),
297
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
298
+ /* @__PURE__ */ jsx6(Text6, { color: selectedAction === "cancel" ? theme.accent : void 0, children: "[Cancel]" })
299
+ ] }),
300
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Enter selects. a applies. c cancels." }),
301
+ result ? /* @__PURE__ */ jsx6(Text6, { color: result.applied ? theme.ok : theme.warning, children: result.summary }) : null
302
+ ] });
303
+ }
304
+
305
+ // src/cli/tui/components/StatusView.tsx
306
+ import { Box as Box7, Text as Text7 } from "ink";
307
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
308
+ function countByState(report) {
309
+ return report.targets.reduce(
310
+ (counts, target) => {
311
+ const state = target.state ?? "unknown";
312
+ counts[state] = (counts[state] ?? 0) + 1;
313
+ return counts;
314
+ },
315
+ {}
316
+ );
317
+ }
318
+ var categoryOrder = [
319
+ "Agents",
320
+ "Skills",
321
+ "Plugin/MCP",
322
+ "Marketplace",
323
+ "Config",
324
+ "Root instructions",
325
+ "Model state",
326
+ "Other"
327
+ ];
328
+ function titleCase(value, separator = " ") {
329
+ return value.split(/[-_\s]+/).filter(Boolean).map(
330
+ (part) => part.toLowerCase() === "sdd" ? "SDD" : part.toLowerCase() === "cli" ? "CLI" : part.toLowerCase() === "mcp" ? "MCP" : part.toLowerCase() === "opencode" ? "OpenCode" : `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`
331
+ ).join(separator);
332
+ }
333
+ function compactPath(path) {
334
+ return path.replaceAll("\\", "/");
335
+ }
336
+ function categorizeTarget(target) {
337
+ const path = target.path ? compactPath(target.path) : "";
338
+ const label = target.label ?? target.kind;
339
+ const agent = path.match(/thoth-agents-([a-z0-9_-]+)\.toml$/i);
340
+ const skill = path.match(/skills\/([^/]+)\/SKILL\.md$/i);
341
+ if (agent?.[1]) {
342
+ return {
343
+ category: "Agents",
344
+ label: titleCase(agent[1]),
345
+ state: target.state
346
+ };
347
+ }
348
+ if (skill?.[1] || target.kind === "skill") {
349
+ return {
350
+ category: "Skills",
351
+ label: titleCase(skill?.[1] ?? label, "-"),
352
+ state: target.state
353
+ };
354
+ }
355
+ if (/marketplace\.json$/i.test(path)) {
356
+ return {
357
+ category: "Marketplace",
358
+ label: "Marketplace",
359
+ state: target.state
360
+ };
361
+ }
362
+ if (/\.mcp\.json$/i.test(path) || /plugin\.json$/i.test(path) || /plugin-assets/i.test(path) || /manifest/i.test(path)) {
363
+ return {
364
+ category: "Plugin/MCP",
365
+ label: titleCase(label.replace(/^codex\s+/i, "")),
366
+ state: target.state
367
+ };
368
+ }
369
+ if (/AGENTS\.md$/i.test(path) || /root instructions/i.test(label)) {
370
+ return {
371
+ category: "Root instructions",
372
+ label: "AGENTS.md",
373
+ state: target.state
374
+ };
375
+ }
376
+ if (/config\.toml$/i.test(path) || target.kind === "config") {
377
+ return { category: "Config", label: titleCase(label), state: target.state };
378
+ }
379
+ if (target.kind === "memory-state" || /model/i.test(label)) {
380
+ return {
381
+ category: "Model state",
382
+ label: titleCase(label),
383
+ state: target.state,
384
+ detail: target.observed
385
+ };
386
+ }
387
+ return {
388
+ category: "Other",
389
+ label: titleCase(label),
390
+ state: target.state,
391
+ detail: target.observed
392
+ };
393
+ }
394
+ function groupedTargets(report) {
395
+ const grouped = /* @__PURE__ */ new Map();
396
+ for (const target of report.targets.map(categorizeTarget)) {
397
+ grouped.set(target.category, [
398
+ ...grouped.get(target.category) ?? [],
399
+ target
400
+ ]);
401
+ }
402
+ return grouped;
403
+ }
404
+ function uniqueTargets(targets) {
405
+ const seen = /* @__PURE__ */ new Set();
406
+ return targets.filter((target) => {
407
+ const key = `${target.label}:${target.state ?? "unknown"}:${target.detail ?? ""}`;
408
+ if (seen.has(key)) return false;
409
+ seen.add(key);
410
+ return true;
411
+ });
412
+ }
413
+ function StatusView({ report }) {
414
+ const counts = countByState(report);
415
+ const countText = Object.entries(counts).map(([state, count]) => `${state}: ${count}`).join(" ");
416
+ const grouped = groupedTargets(report);
417
+ const warnings = report.diagnostics.slice(0, 4);
418
+ const hiddenWarnings = report.diagnostics.length - warnings.length;
419
+ const notes = (report.disclaimers ?? []).slice(0, 2);
420
+ const hiddenNotes = (report.disclaimers?.length ?? 0) - notes.length;
421
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
422
+ /* @__PURE__ */ jsxs6(Text7, { children: [
423
+ "State: ",
424
+ /* @__PURE__ */ jsx7(Text7, { color: stateColor(report.state), children: report.state })
425
+ ] }),
426
+ /* @__PURE__ */ jsx7(Text7, { children: report.summary }),
427
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: countText || "No managed targets." }),
428
+ /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
429
+ "Warnings: ",
430
+ report.diagnostics.length,
431
+ " Notes:",
432
+ " ",
433
+ report.disclaimers?.length ?? 0
434
+ ] }),
435
+ categoryOrder.map((category) => {
436
+ const targets = uniqueTargets(grouped.get(category) ?? []);
437
+ if (targets.length === 0) return null;
438
+ const visible = targets.slice(0, 6);
439
+ const hidden = targets.length - visible.length;
440
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
441
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: category }),
442
+ visible.map((target) => /* @__PURE__ */ jsxs6(Text7, { children: [
443
+ "- ",
444
+ target.label,
445
+ target.state ? /* @__PURE__ */ jsxs6(Text7, { color: stateColor(target.state), children: [
446
+ ": [",
447
+ target.state,
448
+ "]"
449
+ ] }) : null,
450
+ target.detail ? /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
451
+ " - ",
452
+ target.detail
453
+ ] }) : null
454
+ ] }, `${category}-${target.label}-${target.state}`)),
455
+ hidden > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
456
+ " +",
457
+ hidden,
458
+ " more"
459
+ ] }) : null
460
+ ] }, category);
461
+ }),
462
+ report.diagnostics.length > 0 ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
463
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warning, children: "Warnings" }),
464
+ warnings.map((warning) => /* @__PURE__ */ jsxs6(Text7, { color: theme.warning, children: [
465
+ "- [",
466
+ warning.severity,
467
+ "] ",
468
+ warning.message
469
+ ] }, warning.code ?? warning.message)),
470
+ hiddenWarnings > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
471
+ " +",
472
+ hiddenWarnings,
473
+ " more warnings"
474
+ ] }) : null
475
+ ] }) : null,
476
+ notes.length > 0 ? /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
477
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Notes" }),
478
+ notes.map((note) => /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
479
+ "- ",
480
+ note.message
481
+ ] }, note.code ?? note.message)),
482
+ hiddenNotes > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: theme.dim, children: [
483
+ " +",
484
+ hiddenNotes,
485
+ " more notes"
486
+ ] }) : null
487
+ ] }) : null
488
+ ] });
489
+ }
490
+
491
+ // src/cli/tui/model-catalog.ts
492
+ import { execFileSync } from "child_process";
493
+ var MODEL_CATALOG_TIMEOUT_MS = 5e3;
494
+ var MODELS_DEV_MAX_BUFFER = 8 * 1024 * 1024;
495
+ function parseOpenCodeModels(output) {
496
+ const seen = /* @__PURE__ */ new Set();
497
+ const options = [];
498
+ for (const line of output.split(/\r?\n/)) {
499
+ const match = line.trim().match(/^([a-z0-9_-]+)\/([^\s{]+)/i);
500
+ if (!match) continue;
501
+ const id = `${match[1]}/${match[2]}`;
502
+ if (seen.has(id)) continue;
503
+ seen.add(id);
504
+ options.push({ id, label: id, provider: match[1] ?? "unknown" });
505
+ }
506
+ return options;
507
+ }
508
+ function parseModelsDevOpenAi(output) {
509
+ const catalog = JSON.parse(output);
510
+ return Object.entries(catalog.openai?.models ?? {}).filter(([id]) => isCodexOpenAiModelId(id)).map(([id, model]) => ({
511
+ id,
512
+ label: model.name ?? id,
513
+ provider: "openai"
514
+ }));
515
+ }
516
+ function isCodexOpenAiModelId(id) {
517
+ const match = id.match(/^gpt-(\d+)(?:[.-]|$)/);
518
+ return match?.[1] !== void 0 && Number(match[1]) >= 5;
519
+ }
520
+ function getOpenCodeModelsInvocation(platform = process.platform) {
521
+ const options = {
522
+ encoding: "utf8",
523
+ stdio: ["ignore", "pipe", "ignore"],
524
+ timeout: MODEL_CATALOG_TIMEOUT_MS
525
+ };
526
+ if (platform === "win32") {
527
+ return {
528
+ command: "opencode models",
529
+ args: [],
530
+ options: { ...options, shell: true }
531
+ };
532
+ }
533
+ return {
534
+ command: "opencode",
535
+ args: ["models"],
536
+ options
537
+ };
538
+ }
539
+ function getModelsDevCatalog() {
540
+ try {
541
+ const output = execFileSync(
542
+ process.execPath,
543
+ [
544
+ "-e",
545
+ [
546
+ "const controller = new AbortController();",
547
+ `const timeout = setTimeout(() => controller.abort(), ${MODEL_CATALOG_TIMEOUT_MS});`,
548
+ "if (typeof fetch !== 'function') {",
549
+ " console.error('fetch unavailable');",
550
+ " process.exit(1);",
551
+ "}",
552
+ "fetch('https://models.dev/api.json', { signal: controller.signal })",
553
+ " .then(async (response) => {",
554
+ " if (!response.ok) throw new Error(String(response.status));",
555
+ " const catalog = await response.json();",
556
+ " const models = catalog?.openai?.models ?? {};",
557
+ " return JSON.stringify({ openai: { models } });",
558
+ " })",
559
+ " .then((body) => {",
560
+ " clearTimeout(timeout);",
561
+ " process.stdout.write(body);",
562
+ " })",
563
+ " .catch((error) => { clearTimeout(timeout); console.error(error.message); process.exit(1); });"
564
+ ].join("\n")
565
+ ],
566
+ {
567
+ encoding: "utf8",
568
+ maxBuffer: MODELS_DEV_MAX_BUFFER,
569
+ stdio: ["ignore", "pipe", "ignore"],
570
+ timeout: MODEL_CATALOG_TIMEOUT_MS
571
+ }
572
+ );
573
+ return parseModelsDevOpenAi(output);
574
+ } catch {
575
+ return [];
576
+ }
577
+ }
578
+ function getModelOptions(harness) {
579
+ if (harness === "codex") return getModelsDevCatalog();
580
+ try {
581
+ const invocation = getOpenCodeModelsInvocation();
582
+ const output = execFileSync(
583
+ invocation.command,
584
+ invocation.args,
585
+ invocation.options
586
+ );
587
+ return parseOpenCodeModels(output);
588
+ } catch {
589
+ return [];
590
+ }
591
+ }
592
+
593
+ // src/cli/tui/operations.ts
594
+ var context = { cwd: process.cwd() };
595
+ var codexContext = { cwd: process.cwd() };
596
+ var opencodeModelRoles = ALL_AGENT_NAMES.map(
597
+ (role) => ({
598
+ role,
599
+ model: DEFAULT_MODELS[role] ?? "openai/gpt-5.4"
600
+ })
601
+ );
602
+ var codexModelRoles = CODEX_ROLE_NAMES.map(
603
+ (role) => ({
604
+ role,
605
+ model: "gpt-5.4-mini"
606
+ })
607
+ );
608
+ var codexDefaultModels = new Map(
609
+ codexModelRoles.map((role) => [role.role, role.model])
610
+ );
611
+ function codexInstallConfig(source, dryRun) {
612
+ return {
613
+ dryRun,
614
+ reset: false,
615
+ scope: source.scope ?? "user",
616
+ projectRoot: source.cwd,
617
+ homeDir: source.homeDir,
618
+ codexHome: source.codexHome,
619
+ packageRoot: source.packageRoot,
620
+ pluginId: source.pluginId
621
+ };
622
+ }
623
+ function getCodexModelRoles(source = codexContext) {
624
+ try {
625
+ const plan = buildCodexSetupPlan(codexInstallConfig(source, true));
626
+ return CODEX_ROLE_NAMES.map((role) => {
627
+ const item = plan.items.find(
628
+ (candidate) => candidate.action === "write-role-toml" && candidate.role === role
629
+ );
630
+ return {
631
+ role,
632
+ model: (item?.content ? parseRoleTomlModel(item.content) : void 0) ?? codexDefaultModels.get(role) ?? "gpt-5.4-mini"
633
+ };
634
+ });
635
+ } catch {
636
+ return codexModelRoles.map((role) => ({ ...role }));
637
+ }
638
+ }
639
+ function readRoleModel(config, role) {
640
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
641
+ return void 0;
642
+ }
643
+ const record = config;
644
+ const value = record[role];
645
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
646
+ return void 0;
647
+ }
648
+ const model = value.model;
649
+ return typeof model === "string" && model.length > 0 ? model : void 0;
650
+ }
651
+ function getOpenCodeModelRoles() {
652
+ const parsed = parseConfig(getExistingLiteConfigPath());
653
+ const agents = parsed.config?.agents && typeof parsed.config.agents === "object" ? parsed.config.agents : void 0;
654
+ const presets = parsed.config?.presets && typeof parsed.config.presets === "object" ? parsed.config.presets : {};
655
+ const openaiPreset = presets.openai && typeof presets.openai === "object" ? presets.openai : void 0;
656
+ return ALL_AGENT_NAMES.map((role) => ({
657
+ role,
658
+ model: readRoleModel(agents, role) ?? readRoleModel(openaiPreset, role) ?? DEFAULT_MODELS[role] ?? "openai/gpt-5.4"
659
+ }));
660
+ }
661
+ function buildTuiModelPlan(harness, roles) {
662
+ return harness === "opencode" ? buildOpenCodeModelPlan({ harness, dryRun: true, roles }, context) : buildCodexModelPlan({ harness, dryRun: true, roles }, codexContext);
663
+ }
664
+ var defaultTuiOperations = {
665
+ status(harness) {
666
+ return harness === "opencode" ? getOpenCodeStatus(context) : getCodexStatus(codexContext);
667
+ },
668
+ modelRoles(harness) {
669
+ return harness === "opencode" ? getOpenCodeModelRoles() : getCodexModelRoles(codexContext);
670
+ },
671
+ modelOptions(harness) {
672
+ return getModelOptions(harness);
673
+ },
674
+ plan(harness, action) {
675
+ if (harness === "opencode") {
676
+ if (action === "install") return buildOpenCodeInstallPlan(context);
677
+ if (action === "update") return buildOpenCodeUpdatePlan(context);
678
+ if (action === "sync") return buildOpenCodeSyncPlan(context);
679
+ return buildTuiModelPlan(harness, getOpenCodeModelRoles());
680
+ }
681
+ if (action === "install") return buildCodexInstallPlan(codexContext);
682
+ if (action === "update") return buildCodexUpdatePlan(codexContext);
683
+ if (action === "sync") return buildCodexSyncPlan(codexContext);
684
+ return buildTuiModelPlan(harness, getCodexModelRoles(codexContext));
685
+ },
686
+ modelPlan(harness, roles) {
687
+ return buildTuiModelPlan(harness, roles);
688
+ },
689
+ apply(plan) {
690
+ return plan.harness === "opencode" ? applyOpenCodePlan(plan) : applyCodexPlan(plan);
691
+ }
692
+ };
693
+
694
+ // src/cli/tui/App.tsx
695
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
696
+ var rootItems = [
697
+ {
698
+ id: "status",
699
+ action: "status",
700
+ label: "Status",
701
+ detail: "Inspect a harness"
702
+ },
703
+ {
704
+ id: "manage",
705
+ action: "manage",
706
+ label: "Manage Harnesses",
707
+ detail: "List managed surfaces and actions"
708
+ },
709
+ {
710
+ id: "sync",
711
+ action: "sync",
712
+ label: "Sync / Update",
713
+ detail: "Preview managed setup changes"
714
+ },
715
+ {
716
+ id: "exit",
717
+ action: "exit",
718
+ label: "Exit",
719
+ detail: "Close the interactive setup"
720
+ }
721
+ ];
722
+ var actionItems = [
723
+ {
724
+ id: "update",
725
+ action: "update",
726
+ label: "Update",
727
+ detail: "Refresh managed plugin/setup entries"
728
+ },
729
+ {
730
+ id: "sync",
731
+ action: "sync",
732
+ label: "Sync",
733
+ detail: "Reconcile managed configuration"
734
+ },
735
+ { id: "back", action: "back", label: "Back", detail: "Return to root" }
736
+ ];
737
+ var installManageItems = [
738
+ {
739
+ id: "install",
740
+ action: "install",
741
+ label: "Install",
742
+ detail: "Preview managed setup install"
743
+ },
744
+ { id: "back", action: "back", label: "Back", detail: "Choose harness" }
745
+ ];
746
+ var manageItems = [
747
+ {
748
+ id: "status",
749
+ action: "status",
750
+ label: "View status",
751
+ detail: "Open compact categorized health"
752
+ },
753
+ {
754
+ id: "update",
755
+ action: "update",
756
+ label: "Update preview",
757
+ detail: "Preview managed plugin/setup refresh"
758
+ },
759
+ {
760
+ id: "sync",
761
+ action: "sync",
762
+ label: "Sync preview",
763
+ detail: "Preview configuration reconciliation"
764
+ },
765
+ {
766
+ id: "models",
767
+ action: "models",
768
+ label: "Configure models",
769
+ detail: "Edit role model assignments"
770
+ },
771
+ { id: "back", action: "back", label: "Back", detail: "Choose harness" }
772
+ ];
773
+ function buildHarnessItems() {
774
+ return [
775
+ ...listOperationHarnesses().map((harness) => ({
776
+ id: harness.id,
777
+ harness: harness.id,
778
+ label: harness.displayName,
779
+ detail: harness.description,
780
+ disabled: !harness.available
781
+ })),
782
+ { id: "back", action: "back", label: "Back", detail: "Return" }
783
+ ];
784
+ }
785
+ function moveSelection(current, direction, items) {
786
+ let next = current;
787
+ for (let index = 0; index < items.length; index += 1) {
788
+ next = (next + direction + items.length) % items.length;
789
+ if (!items[next]?.disabled) return next;
790
+ }
791
+ return current;
792
+ }
793
+ function normalizeSelection(current, items) {
794
+ if (items.length === 0) return 0;
795
+ return Math.min(current, items.length - 1);
796
+ }
797
+ function changedRoles(rows) {
798
+ return rows.filter((role) => role.dirty).map((role) => ({ role: role.role, model: role.model }));
799
+ }
800
+ function App({
801
+ operations = defaultTuiOperations,
802
+ exitOnQuit = true
803
+ }) {
804
+ const { exit } = useApp();
805
+ const harnessItems = useMemo(buildHarnessItems, []);
806
+ const [view, setView] = useState("root");
807
+ const [rootSelected, setRootSelected] = useState(0);
808
+ const [harnessSelected, setHarnessSelected] = useState(0);
809
+ const [actionSelected, setActionSelected] = useState(0);
810
+ const [manageSelected, setManageSelected] = useState(0);
811
+ const [harnessPurpose, setHarnessPurpose] = useState("status");
812
+ const [activeHarness, setActiveHarness] = useState("opencode");
813
+ const [activeAction, setActiveAction] = useState("update");
814
+ const [reportVersion, setReportVersion] = useState(0);
815
+ const [plan, setPlan] = useState();
816
+ const [result, setResult] = useState();
817
+ const [previewAction, setPreviewAction] = useState(
818
+ "cancel"
819
+ );
820
+ const [previewBackView, setPreviewBackView] = useState("harness");
821
+ const [modelHarness, setModelHarness] = useState("codex");
822
+ const [modelRoles, setModelRoles] = useState([]);
823
+ const [modelOptions, setModelOptions] = useState([]);
824
+ const [choiceSelected, setChoiceSelected] = useState(0);
825
+ const [editedModels, setEditedModels] = useState({});
826
+ const [modelSelected, setModelSelected] = useState(0);
827
+ const [editingRole, setEditingRole] = useState();
828
+ const [editDraft, setEditDraft] = useState("");
829
+ const [modelResult, setModelResult] = useState();
830
+ const report = view === "status" || view === "manageHarness" ? operations.status(activeHarness) : void 0;
831
+ const currentManageItems = report?.state === "missing" ? installManageItems : manageItems;
832
+ const modelRows = modelRoles.map((role) => {
833
+ const model = editedModels[role.role] ?? role.model;
834
+ return {
835
+ ...role,
836
+ model,
837
+ currentModel: role.model,
838
+ dirty: model !== role.model
839
+ };
840
+ });
841
+ const dirtyRoles = changedRoles(modelRows);
842
+ const modelActions = dirtyRoles.length > 0 ? ["Preview changes", "Apply changes", "Back"] : ["Back"];
843
+ const modelMenuItems = [
844
+ ...modelRows.map((role) => ({
845
+ id: role.role,
846
+ label: `${role.dirty ? "* " : ""}${role.role}`,
847
+ detail: role.model
848
+ })),
849
+ ...modelActions.map((action) => ({
850
+ id: action,
851
+ label: action,
852
+ detail: action === "Back" ? "Return to harness selection" : `${dirtyRoles.length} changed role(s)`
853
+ }))
854
+ ];
855
+ function goBack() {
856
+ setResult(void 0);
857
+ if (view === "status") {
858
+ setView("harness");
859
+ } else if (view === "harness") {
860
+ setView(harnessPurpose === "action" ? "action" : "root");
861
+ } else if (view === "manageHarness") {
862
+ setView("harness");
863
+ } else if (view === "action") {
864
+ setView("root");
865
+ } else if (view === "preview") {
866
+ setView(previewBackView);
867
+ } else if (view === "modelRoles") {
868
+ setView("manageHarness");
869
+ } else if (view === "modelChoice") {
870
+ setView("modelRoles");
871
+ } else if (view === "modelEdit") {
872
+ setView("modelChoice");
873
+ }
874
+ }
875
+ function openHarnessPicker(purpose) {
876
+ setHarnessPurpose(purpose);
877
+ setHarnessSelected(0);
878
+ setView("harness");
879
+ }
880
+ function openPlan(harness, action, backView = "harness") {
881
+ setActiveHarness(harness);
882
+ setPlan(operations.plan(harness, action));
883
+ setResult(void 0);
884
+ setPreviewAction("cancel");
885
+ setPreviewBackView(backView);
886
+ setView("preview");
887
+ }
888
+ function openModelRoles(harness) {
889
+ setModelHarness(harness);
890
+ setModelRoles(operations.modelRoles(harness));
891
+ setModelOptions(operations.modelOptions(harness));
892
+ setEditedModels({});
893
+ setModelResult(void 0);
894
+ setModelSelected(0);
895
+ setView("modelRoles");
896
+ }
897
+ function previewModelChanges(applyImmediately) {
898
+ const nextPlan = operations.modelPlan(modelHarness, dirtyRoles);
899
+ setPlan(nextPlan);
900
+ setPreviewAction(applyImmediately ? "apply" : "cancel");
901
+ setPreviewBackView("modelRoles");
902
+ if (applyImmediately) {
903
+ const applyResult = operations.apply(nextPlan);
904
+ setModelResult(applyResult);
905
+ if (applyResult.applied) {
906
+ setModelRoles(
907
+ (roles) => roles.map((role) => ({
908
+ ...role,
909
+ model: editedModels[role.role] ?? role.model
910
+ }))
911
+ );
912
+ setEditedModels({});
913
+ }
914
+ return;
915
+ }
916
+ setResult(void 0);
917
+ setView("preview");
918
+ }
919
+ useInput((input, key) => {
920
+ if (view === "modelEdit") {
921
+ if (key.escape) {
922
+ goBack();
923
+ return;
924
+ }
925
+ if (key.return && editingRole) {
926
+ setEditedModels((models) => ({
927
+ ...models,
928
+ [editingRole.role]: editDraft
929
+ }));
930
+ setView("modelRoles");
931
+ return;
932
+ }
933
+ if (key.tab) {
934
+ const firstOption = modelOptions[0]?.id;
935
+ if (firstOption) setEditDraft(firstOption);
936
+ return;
937
+ }
938
+ if (key.backspace || key.delete) {
939
+ setEditDraft((current) => current.slice(0, -1));
940
+ return;
941
+ }
942
+ if (input && !key.ctrl && !key.meta) {
943
+ setEditDraft((current) => `${current}${input}`);
944
+ }
945
+ return;
946
+ }
947
+ if (view === "modelChoice") {
948
+ if (key.escape) {
949
+ goBack();
950
+ return;
951
+ }
952
+ const choiceItems = [
953
+ ...modelOptions.map((option) => ({
954
+ id: option.id,
955
+ label: option.id,
956
+ detail: option.provider
957
+ })),
958
+ { id: "manual", label: "Manual entry", detail: "Type a model ID" }
959
+ ];
960
+ if (key.upArrow || input === "k") {
961
+ setChoiceSelected((current) => moveSelection(current, -1, choiceItems));
962
+ }
963
+ if (key.downArrow || input === "j") {
964
+ setChoiceSelected((current) => moveSelection(current, 1, choiceItems));
965
+ }
966
+ if (key.return && editingRole) {
967
+ const option = modelOptions[choiceSelected];
968
+ if (option) {
969
+ setEditedModels((models) => ({
970
+ ...models,
971
+ [editingRole.role]: option.id
972
+ }));
973
+ setView("modelRoles");
974
+ return;
975
+ }
976
+ setEditDraft(editingRole.model);
977
+ setView("modelEdit");
978
+ }
979
+ return;
980
+ }
981
+ if (input === "q" || key.escape) {
982
+ if (view === "root" && exitOnQuit) exit();
983
+ if (view !== "root") goBack();
984
+ return;
985
+ }
986
+ if (view === "root") {
987
+ if (key.upArrow || input === "k") {
988
+ setRootSelected((current) => moveSelection(current, -1, rootItems));
989
+ }
990
+ if (key.downArrow || input === "j") {
991
+ setRootSelected((current) => moveSelection(current, 1, rootItems));
992
+ }
993
+ if (key.return) {
994
+ const item = rootItems[rootSelected];
995
+ if (item?.action === "status") openHarnessPicker("status");
996
+ if (item?.action === "manage") openHarnessPicker("list");
997
+ if (item?.action === "sync") setView("action");
998
+ if (item?.action === "exit" && exitOnQuit) exit();
999
+ }
1000
+ return;
1001
+ }
1002
+ if (view === "action") {
1003
+ if (key.upArrow || input === "k") {
1004
+ setActionSelected((current) => moveSelection(current, -1, actionItems));
1005
+ }
1006
+ if (key.downArrow || input === "j") {
1007
+ setActionSelected((current) => moveSelection(current, 1, actionItems));
1008
+ }
1009
+ if (key.return) {
1010
+ const item = actionItems[actionSelected];
1011
+ if (item?.action === "back") {
1012
+ setView("root");
1013
+ } else if (item) {
1014
+ setActiveAction(item.action);
1015
+ openHarnessPicker("action");
1016
+ }
1017
+ }
1018
+ return;
1019
+ }
1020
+ if (view === "harness") {
1021
+ const selected = normalizeSelection(harnessSelected, harnessItems);
1022
+ if (key.upArrow || input === "k") {
1023
+ setHarnessSelected(
1024
+ (current) => moveSelection(current, -1, harnessItems)
1025
+ );
1026
+ }
1027
+ if (key.downArrow || input === "j") {
1028
+ setHarnessSelected(
1029
+ (current) => moveSelection(current, 1, harnessItems)
1030
+ );
1031
+ }
1032
+ if (key.return) {
1033
+ const item = harnessItems[selected];
1034
+ if (item?.action === "back") {
1035
+ goBack();
1036
+ } else if (item?.harness) {
1037
+ setActiveHarness(item.harness);
1038
+ setReportVersion((current) => current + 1);
1039
+ if (harnessPurpose === "status") setView("status");
1040
+ if (harnessPurpose === "list") {
1041
+ setManageSelected(0);
1042
+ setView("manageHarness");
1043
+ }
1044
+ if (harnessPurpose === "action") {
1045
+ openPlan(item.harness, activeAction);
1046
+ }
1047
+ }
1048
+ }
1049
+ return;
1050
+ }
1051
+ if (view === "manageHarness") {
1052
+ if (key.upArrow || input === "k") {
1053
+ setManageSelected(
1054
+ (current) => moveSelection(current, -1, currentManageItems)
1055
+ );
1056
+ }
1057
+ if (key.downArrow || input === "j") {
1058
+ setManageSelected(
1059
+ (current) => moveSelection(current, 1, currentManageItems)
1060
+ );
1061
+ }
1062
+ if (key.return) {
1063
+ const item = currentManageItems[manageSelected];
1064
+ if (item?.action === "back") setView("harness");
1065
+ if (item?.action === "install") {
1066
+ openPlan(activeHarness, "install", "manageHarness");
1067
+ }
1068
+ if (item?.action === "status") {
1069
+ setView("status");
1070
+ }
1071
+ if (item?.action === "update") {
1072
+ openPlan(activeHarness, "update", "manageHarness");
1073
+ }
1074
+ if (item?.action === "sync") {
1075
+ openPlan(activeHarness, "sync", "manageHarness");
1076
+ }
1077
+ if (item?.action === "models") openModelRoles(activeHarness);
1078
+ }
1079
+ return;
1080
+ }
1081
+ if (view === "modelRoles") {
1082
+ if (key.upArrow || input === "k") {
1083
+ setModelSelected(
1084
+ (current) => moveSelection(current, -1, modelMenuItems)
1085
+ );
1086
+ }
1087
+ if (key.downArrow || input === "j") {
1088
+ setModelSelected(
1089
+ (current) => moveSelection(current, 1, modelMenuItems)
1090
+ );
1091
+ }
1092
+ if (key.return) {
1093
+ if (modelSelected < modelRows.length) {
1094
+ const role = modelRows[modelSelected];
1095
+ setEditingRole(role);
1096
+ setEditDraft(role?.model ?? "");
1097
+ setChoiceSelected(0);
1098
+ setView("modelChoice");
1099
+ return;
1100
+ }
1101
+ const action = modelActions[modelSelected - modelRows.length];
1102
+ if (action === "Back") setView("manageHarness");
1103
+ if (action === "Preview changes") previewModelChanges(false);
1104
+ if (action === "Apply changes") previewModelChanges(true);
1105
+ }
1106
+ return;
1107
+ }
1108
+ if (view === "status") {
1109
+ if (input === "r") setReportVersion((current) => current + 1);
1110
+ return;
1111
+ }
1112
+ if (view === "preview") {
1113
+ if (key.leftArrow || key.rightArrow || key.tab) {
1114
+ setPreviewAction(
1115
+ (current) => current === "apply" ? "cancel" : "apply"
1116
+ );
1117
+ }
1118
+ if (input === "a" && plan?.canApply) {
1119
+ setPreviewAction("apply");
1120
+ setResult(operations.apply(plan));
1121
+ }
1122
+ if (input === "c") goBack();
1123
+ if (key.return && plan) {
1124
+ if (previewAction === "cancel" || !plan.canApply) {
1125
+ goBack();
1126
+ } else {
1127
+ setResult(operations.apply(plan));
1128
+ }
1129
+ }
1130
+ }
1131
+ });
1132
+ if (view === "root") {
1133
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1134
+ /* @__PURE__ */ jsx8(
1135
+ Header,
1136
+ {
1137
+ title: "Interactive setup",
1138
+ subtitle: "Use arrows and Enter. Escape goes back. q exits."
1139
+ }
1140
+ ),
1141
+ /* @__PURE__ */ jsx8(Menu, { items: rootItems, selected: rootSelected })
1142
+ ] });
1143
+ }
1144
+ if (view === "action") {
1145
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1146
+ /* @__PURE__ */ jsx8(
1147
+ Header,
1148
+ {
1149
+ title: "Sync / Update",
1150
+ subtitle: "Choose an operation before selecting a harness."
1151
+ }
1152
+ ),
1153
+ /* @__PURE__ */ jsx8(Menu, { items: actionItems, selected: actionSelected })
1154
+ ] });
1155
+ }
1156
+ if (view === "harness") {
1157
+ const title = harnessPurpose === "status" ? "Status" : harnessPurpose === "list" ? "Manage Harnesses" : activeAction === "update" ? "Update" : "Sync";
1158
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1159
+ /* @__PURE__ */ jsx8(Header, { title, subtitle: "Choose a harness." }),
1160
+ /* @__PURE__ */ jsx8(Menu, { items: harnessItems, selected: harnessSelected })
1161
+ ] });
1162
+ }
1163
+ if (view === "manageHarness" && report) {
1164
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1165
+ /* @__PURE__ */ jsx8(
1166
+ Header,
1167
+ {
1168
+ title: `Manage ${report.displayName ?? activeHarness}`,
1169
+ subtitle: "Choose an action. Escape returns to harness selection."
1170
+ }
1171
+ ),
1172
+ /* @__PURE__ */ jsxs7(Text8, { children: [
1173
+ "Health: ",
1174
+ /* @__PURE__ */ jsx8(Text8, { color: stateColor(report.state), children: report.state }),
1175
+ /* @__PURE__ */ jsxs7(Text8, { color: theme.dim, children: [
1176
+ " - ",
1177
+ report.summary
1178
+ ] })
1179
+ ] }),
1180
+ /* @__PURE__ */ jsx8(Menu, { items: currentManageItems, selected: manageSelected })
1181
+ ] });
1182
+ }
1183
+ if (view === "modelRoles") {
1184
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1185
+ /* @__PURE__ */ jsx8(
1186
+ Header,
1187
+ {
1188
+ title: `${modelHarness === "codex" ? "Codex" : "OpenCode"} Models`,
1189
+ subtitle: "Enter edits a role. Dirty rows are marked with *."
1190
+ }
1191
+ ),
1192
+ /* @__PURE__ */ jsx8(
1193
+ ModelScreen,
1194
+ {
1195
+ harness: modelHarness,
1196
+ roles: modelRows,
1197
+ selected: normalizeSelection(modelSelected, modelMenuItems),
1198
+ actions: modelActions
1199
+ }
1200
+ ),
1201
+ modelResult ? /* @__PURE__ */ jsx8(Text8, { color: modelResult.applied ? theme.ok : theme.warning, children: modelResult.summary }) : null
1202
+ ] });
1203
+ }
1204
+ if (view === "modelEdit" && editingRole) {
1205
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1206
+ /* @__PURE__ */ jsx8(
1207
+ Header,
1208
+ {
1209
+ title: `Edit ${editingRole.role}`,
1210
+ subtitle: "Type a model ID. Tab inserts the first catalog option. Enter saves."
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsxs7(Text8, { children: [
1214
+ "Current: ",
1215
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: editingRole.model })
1216
+ ] }),
1217
+ /* @__PURE__ */ jsxs7(Text8, { children: [
1218
+ "New: ",
1219
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warning, children: editDraft })
1220
+ ] })
1221
+ ] });
1222
+ }
1223
+ if (view === "modelChoice" && editingRole) {
1224
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1225
+ /* @__PURE__ */ jsx8(
1226
+ Header,
1227
+ {
1228
+ title: `Choose ${editingRole.role} model`,
1229
+ subtitle: "Select a catalog option or Manual entry."
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsx8(
1233
+ ModelChoiceScreen,
1234
+ {
1235
+ currentModel: editingRole.currentModel,
1236
+ draftModel: editedModels[editingRole.role] ?? editingRole.model,
1237
+ options: modelOptions,
1238
+ selected: choiceSelected
1239
+ }
1240
+ )
1241
+ ] });
1242
+ }
1243
+ if (view === "preview" && plan) {
1244
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1245
+ /* @__PURE__ */ jsx8(
1246
+ Header,
1247
+ {
1248
+ title: plan.title,
1249
+ subtitle: "Enter selects. Escape or c returns one level."
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsx8(
1253
+ PlanPreview,
1254
+ {
1255
+ plan,
1256
+ selectedAction: previewAction,
1257
+ result
1258
+ }
1259
+ )
1260
+ ] });
1261
+ }
1262
+ if (!report) return null;
1263
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1264
+ /* @__PURE__ */ jsx8(
1265
+ Header,
1266
+ {
1267
+ title: `${report.displayName ?? activeHarness} Status`,
1268
+ subtitle: "Categorized summary. Escape returns. r refreshes."
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx8(StatusView, { report })
1272
+ ] }, reportVersion);
1273
+ }
1274
+
1275
+ // src/cli/tui/index.tsx
1276
+ import { jsx as jsx9 } from "react/jsx-runtime";
1277
+ async function runInteractiveTui() {
1278
+ const instance = render(/* @__PURE__ */ jsx9(App, {}));
1279
+ await instance.waitUntilExit();
1280
+ return 0;
1281
+ }
1282
+ export {
1283
+ App,
1284
+ runInteractiveTui
1285
+ };