@aui.io/apollo 0.1.6 → 0.1.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.
Files changed (45) hide show
  1. package/node_modules/@aui.io/apollo-cli/dist/src/commands/bundles.js +27 -4
  2. package/node_modules/@aui.io/apollo-cli/dist/src/commands/bundles.js.map +1 -1
  3. package/node_modules/@aui.io/apollo-cli/dist/src/commands/env.js +1 -1
  4. package/node_modules/@aui.io/apollo-cli/dist/src/commands/env.js.map +1 -1
  5. package/node_modules/@aui.io/apollo-cli/dist/src/commands/hierarchy.js +116 -36
  6. package/node_modules/@aui.io/apollo-cli/dist/src/commands/hierarchy.js.map +1 -1
  7. package/node_modules/@aui.io/apollo-cli/dist/src/commands/picker.d.ts +27 -0
  8. package/node_modules/@aui.io/apollo-cli/dist/src/commands/picker.js +68 -0
  9. package/node_modules/@aui.io/apollo-cli/dist/src/commands/picker.js.map +1 -0
  10. package/node_modules/@aui.io/apollo-cli/dist/src/commands/shell.d.ts +9 -3
  11. package/node_modules/@aui.io/apollo-cli/dist/src/commands/shell.js +26 -4
  12. package/node_modules/@aui.io/apollo-cli/dist/src/commands/shell.js.map +1 -1
  13. package/node_modules/@aui.io/apollo-cli/dist/src/commands/workspace.js +38 -53
  14. package/node_modules/@aui.io/apollo-cli/dist/src/commands/workspace.js.map +1 -1
  15. package/node_modules/@aui.io/apollo-cli/package.json +3 -3
  16. package/node_modules/@aui.io/apollo-core/dist/src/services/hierarchy.js +7 -1
  17. package/node_modules/@aui.io/apollo-core/dist/src/services/hierarchy.js.map +1 -1
  18. package/node_modules/@aui.io/apollo-core/package.json +1 -1
  19. package/node_modules/@aui.io/apollo-tui/dist/src/index.d.ts +2 -1
  20. package/node_modules/@aui.io/apollo-tui/dist/src/index.js +2 -1
  21. package/node_modules/@aui.io/apollo-tui/dist/src/index.js.map +1 -1
  22. package/node_modules/@aui.io/apollo-tui/dist/src/lib/actions.d.ts +41 -0
  23. package/node_modules/@aui.io/apollo-tui/dist/src/lib/actions.js +927 -0
  24. package/node_modules/@aui.io/apollo-tui/dist/src/lib/actions.js.map +1 -0
  25. package/node_modules/@aui.io/apollo-tui/dist/src/lib/commands.d.ts +10 -4
  26. package/node_modules/@aui.io/apollo-tui/dist/src/lib/commands.js +402 -23
  27. package/node_modules/@aui.io/apollo-tui/dist/src/lib/commands.js.map +1 -1
  28. package/node_modules/@aui.io/apollo-tui/dist/src/lib/confirm.d.ts +10 -0
  29. package/node_modules/@aui.io/apollo-tui/dist/src/lib/confirm.js +12 -0
  30. package/node_modules/@aui.io/apollo-tui/dist/src/lib/confirm.js.map +1 -0
  31. package/node_modules/@aui.io/apollo-tui/dist/src/lib/picker.d.ts +28 -0
  32. package/node_modules/@aui.io/apollo-tui/dist/src/lib/picker.js +72 -0
  33. package/node_modules/@aui.io/apollo-tui/dist/src/lib/picker.js.map +1 -0
  34. package/node_modules/@aui.io/apollo-tui/dist/src/lib/place.d.ts +19 -0
  35. package/node_modules/@aui.io/apollo-tui/dist/src/lib/place.js +16 -0
  36. package/node_modules/@aui.io/apollo-tui/dist/src/lib/place.js.map +1 -0
  37. package/node_modules/@aui.io/apollo-tui/dist/src/lib/scope.d.ts +4 -0
  38. package/node_modules/@aui.io/apollo-tui/dist/src/lib/scope.js +9 -1
  39. package/node_modules/@aui.io/apollo-tui/dist/src/lib/scope.js.map +1 -1
  40. package/node_modules/@aui.io/apollo-tui/dist/src/lib/sheet.d.ts +2 -9
  41. package/node_modules/@aui.io/apollo-tui/dist/src/lib/sheet.js.map +1 -1
  42. package/node_modules/@aui.io/apollo-tui/dist/src/opentui-app.js +478 -182
  43. package/node_modules/@aui.io/apollo-tui/dist/src/opentui-app.js.map +1 -1
  44. package/node_modules/@aui.io/apollo-tui/package.json +2 -2
  45. package/package.json +4 -4
@@ -1,22 +1,22 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
2
  /**
3
- * Apollo TUI — OpenTUI engine (the real one, per the plan).
3
+ * Apollo TUI — OpenTUI engine.
4
4
  *
5
- * Renders the five-tab operator app with the animated brand glyph, slash
6
- * command palette, and command sheets on top of @opentui/react. Business
7
- * logic stays in @aui.io/apollo-core; presentation-only state lives here.
5
+ * Global slash command desk; tabs are views. Business logic in apollo-core
6
+ * via lib/actions.ts.
8
7
  */
9
8
  import { useEffect, useRef, useState } from "react";
10
9
  import { createCliRenderer } from "@opentui/core";
11
10
  import { createRoot, useKeyboard } from "@opentui/react";
12
11
  import { resolveContext } from "@aui.io/apollo-core/config/index.js";
13
- import { localDiff, pullBundle, pushBundle, remoteDiff, validateLocalBundle, } from "@aui.io/apollo-core/services/bundles.js";
14
12
  import { createClients } from "@aui.io/apollo-core/services/clients.js";
15
- import { provisionMockDb, wireMockDbConnection, } from "@aui.io/apollo-core/services/mockdb.js";
16
- import { getStatus } from "@aui.io/apollo-core/services/status.service.js";
17
13
  import { sendMessage } from "@aui.io/apollo-core/services/messaging.js";
18
14
  import { GlyphController } from "./components/glyph.js";
19
- import { filterCommands, resolveCommand, } from "./lib/commands.js";
15
+ import { applyPick, chatOverlay, destructiveSpec, loadPickerCatalog, refreshContext, runDestructive, runSheetAction, runSimpleCommand, } from "./lib/actions.js";
16
+ import { filterCommands, helpLines, PLACE_ONBOARD_COMMAND, resolveCommand, } from "./lib/commands.js";
17
+ import { checkConfirm, createConfirm } from "./lib/confirm.js";
18
+ import { clampPickerCursor, effectivePickerFilter, filterPickerItems, pickerColumnWidths, pickerPageSize, resolvePickerAnswer, truncatePickerText, } from "./lib/picker.js";
19
+ import { hasPlace } from "./lib/place.js";
20
20
  import { createSheet, moveFocus, setFocusedValue, validateSheet, visibleFields, } from "./lib/sheet.js";
21
21
  import { scopeFromContext } from "./lib/scope.js";
22
22
  import { TABS, nextTab, prevTab, tabByIndex } from "./lib/tabs.js";
@@ -27,30 +27,38 @@ const RED = "#D46A6A";
27
27
  const DIM = "#8A8A8A";
28
28
  const FAINT = "#5C5C5C";
29
29
  const FG = "#D8D8D8";
30
- const VERSION = process.env.npm_package_version ?? "0.1.2";
31
- function diffSummary(diff) {
32
- const total = diff.added.length + diff.changed.length + diff.removed.length;
33
- if (total === 0)
34
- return "no changes";
35
- return `+${diff.added.length} ~${diff.changed.length} -${diff.removed.length}`;
36
- }
37
- function App({ context, initialScope, glyph, onQuit }) {
30
+ const VERSION = process.env.npm_package_version ?? "0.1.7";
31
+ function App({ context: initialContext, initialScope, glyph, onQuit }) {
32
+ const [context, setContext] = useState(initialContext);
33
+ const [scope, setScope] = useState(initialScope);
38
34
  const [tab, setTab] = useState("overview");
39
35
  const [input, setInput] = useState("");
40
36
  const [chat, setChat] = useState([]);
41
- const [toast, setToast] = useState(undefined);
42
- const [sheet, setSheet] = useState(undefined);
43
- const [sheetCommand, setSheetCommand] = useState(undefined);
37
+ const [threadId, setThreadId] = useState();
38
+ const [toast, setToast] = useState();
39
+ const [sheet, setSheet] = useState();
40
+ const [sheetCommand, setSheetCommand] = useState();
41
+ const [picker, setPicker] = useState();
42
+ const [confirm, setConfirm] = useState();
43
+ const [paneTitle, setPaneTitle] = useState();
44
+ const [paneLines, setPaneLines] = useState([]);
44
45
  const [working, setWorking] = useState(false);
45
46
  const [trace, setTrace] = useState(false);
46
47
  const [localMode, setLocalMode] = useState(false);
47
48
  const [vars, setVars] = useState([]);
48
49
  const [frame, setFrame] = useState(glyph.frame());
49
- const scope = initialScope;
50
- // Keep chat state accessible from async handlers without stale closures.
51
- const stateRef = useRef({ tab, sheet, sheetCommand });
52
- stateRef.current = { tab, sheet, sheetCommand };
53
- // Animated glyph on the Overview tab (energized while working).
50
+ /** Soft place gate offered once per session (pick / skip / already had place). */
51
+ const placeGateOffered = useRef(false);
52
+ const stateRef = useRef({
53
+ tab,
54
+ sheet,
55
+ sheetCommand,
56
+ picker,
57
+ confirm,
58
+ context,
59
+ input,
60
+ });
61
+ stateRef.current = { tab, sheet, sheetCommand, picker, confirm, context, input };
54
62
  useEffect(() => {
55
63
  if (tab !== "overview")
56
64
  return;
@@ -60,24 +68,62 @@ function App({ context, initialScope, glyph, onQuit }) {
60
68
  useEffect(() => {
61
69
  glyph.setWorking(working);
62
70
  }, [working, glyph]);
63
- const paletteOpen = !sheet && input.startsWith("/");
64
- const paletteMatches = paletteOpen ? filterCommands(input).slice(0, 8) : [];
71
+ const paletteOpen = !sheet && !picker && !confirm && input.startsWith("/");
72
+ const paletteMatches = paletteOpen
73
+ ? filterCommands(input, tab === "overview" ? undefined : tab).slice(0, 10)
74
+ : [];
65
75
  useKeyboard((key) => {
66
76
  if (key.eventType === "release")
67
77
  return;
68
78
  const current = stateRef.current;
79
+ if (current.picker && (key.name === "up" || key.name === "down")) {
80
+ const visible = filterPickerItems(current.picker.items, effectivePickerFilter(current.input));
81
+ if (visible.length === 0)
82
+ return;
83
+ const delta = key.name === "down" ? 1 : -1;
84
+ setPicker({
85
+ ...current.picker,
86
+ cursor: clampPickerCursor(current.picker.cursor + delta, visible.length),
87
+ });
88
+ return;
89
+ }
69
90
  if (key.name === "tab") {
91
+ if (current.picker) {
92
+ const visible = filterPickerItems(current.picker.items, effectivePickerFilter(current.input));
93
+ if (visible.length === 0)
94
+ return;
95
+ setPicker({
96
+ ...current.picker,
97
+ cursor: clampPickerCursor(current.picker.cursor + (key.shift ? -1 : 1), visible.length),
98
+ });
99
+ return;
100
+ }
70
101
  if (current.sheet) {
71
102
  setSheet(moveFocus(current.sheet, key.shift ? -1 : 1));
72
103
  }
73
- else {
104
+ else if (!current.confirm) {
74
105
  setTab((t) => (key.shift ? prevTab(t) : nextTab(t)));
75
106
  setInput("");
76
107
  }
77
108
  return;
78
109
  }
79
110
  if (key.name === "escape") {
80
- if (current.sheet) {
111
+ if (current.confirm) {
112
+ setConfirm(undefined);
113
+ setToast("Cancelled");
114
+ }
115
+ else if (current.picker) {
116
+ const wasPlaceOnboard = current.picker.command.name === "place-onboard";
117
+ setPicker(undefined);
118
+ if (current.picker.sheet && current.picker.command) {
119
+ setSheet(current.picker.sheet);
120
+ setSheetCommand(current.picker.command);
121
+ }
122
+ setToast(wasPlaceOnboard
123
+ ? "Continuing without a workspace — /workspace when ready"
124
+ : "Cancelled");
125
+ }
126
+ else if (current.sheet) {
81
127
  setSheet(undefined);
82
128
  setSheetCommand(undefined);
83
129
  setToast("Cancelled");
@@ -95,6 +141,72 @@ function App({ context, initialScope, glyph, onQuit }) {
95
141
  }
96
142
  }
97
143
  });
144
+ async function applyResult(result) {
145
+ if (result.error) {
146
+ setToast(result.error);
147
+ return;
148
+ }
149
+ if (result.openWorkspaceCreate) {
150
+ const createCmd = resolveCommand("/workspace-create").command;
151
+ if (createCmd?.fields) {
152
+ const model = createSheet(createCmd.name, createCmd.fields);
153
+ setSheet(model);
154
+ setSheetCommand(createCmd);
155
+ setToast("Create a workspace — enter a path");
156
+ }
157
+ return;
158
+ }
159
+ if (result.toast)
160
+ setToast(result.toast);
161
+ if (result.jumpTab)
162
+ setTab(result.jumpTab);
163
+ if (result.chat)
164
+ setChat(result.chat);
165
+ if (result.threadId)
166
+ setThreadId(result.threadId);
167
+ if (result.paneLines) {
168
+ setPaneLines(result.paneLines);
169
+ setPaneTitle(result.paneTitle ?? "result");
170
+ }
171
+ if (result.needConfirm) {
172
+ setConfirm(createConfirm(result.needConfirm.title, result.needConfirm.phrase, result.needConfirm.action, result.needConfirm.args));
173
+ }
174
+ if (result.refreshScope) {
175
+ const next = await refreshContext();
176
+ setContext(next);
177
+ setScope(scopeFromContext(next));
178
+ await maybeOfferPlace(next);
179
+ }
180
+ }
181
+ async function openPlaceOnboarding() {
182
+ await openPick(PLACE_ONBOARD_COMMAND, "place-onboard");
183
+ }
184
+ async function maybeOfferPlace(ctx) {
185
+ if (placeGateOffered.current)
186
+ return;
187
+ if (hasPlace(ctx)) {
188
+ placeGateOffered.current = true;
189
+ return;
190
+ }
191
+ // Logged-out: wait for /login (mount toast covers the hint).
192
+ if (!ctx.token)
193
+ return;
194
+ placeGateOffered.current = true;
195
+ await openPlaceOnboarding();
196
+ }
197
+ useEffect(() => {
198
+ if (hasPlace(initialContext)) {
199
+ placeGateOffered.current = true;
200
+ return;
201
+ }
202
+ if (!initialContext.token) {
203
+ setToast("Not in a workspace · /login, then pick a place — Esc skips");
204
+ return;
205
+ }
206
+ placeGateOffered.current = true;
207
+ void openPlaceOnboarding();
208
+ // Once on mount — placeGateOffered guards re-entry.
209
+ }, []);
98
210
  function ensureAuth(command) {
99
211
  if (!command.requiresAuth || scope.loggedIn)
100
212
  return true;
@@ -102,83 +214,132 @@ function App({ context, initialScope, glyph, onQuit }) {
102
214
  setInput("/login");
103
215
  return false;
104
216
  }
217
+ async function openPick(command, kind, sheetModel, sheetField) {
218
+ setWorking(true);
219
+ try {
220
+ const items = await loadPickerCatalog(kind, stateRef.current.context);
221
+ if (items.length === 0) {
222
+ setToast(`No ${kind} options`);
223
+ return;
224
+ }
225
+ if (items.length === 1 && !sheetField && command.name !== "place-onboard") {
226
+ await finishPick(command, items[0].id, sheetModel, sheetField);
227
+ return;
228
+ }
229
+ setSheet(undefined);
230
+ setPicker({
231
+ command,
232
+ items,
233
+ cursor: 0,
234
+ ...(sheetField ? { sheetField } : {}),
235
+ ...(sheetModel ? { sheet: sheetModel } : {}),
236
+ });
237
+ setInput("");
238
+ }
239
+ catch (error) {
240
+ setToast(error instanceof Error ? error.message : String(error));
241
+ }
242
+ finally {
243
+ setWorking(false);
244
+ }
245
+ }
246
+ async function finishPick(command, id, sheetModel, sheetField) {
247
+ setPicker(undefined);
248
+ if (sheetModel && sheetField) {
249
+ const next = {
250
+ ...sheetModel,
251
+ values: { ...sheetModel.values, [sheetField]: id },
252
+ };
253
+ setSheet(next);
254
+ setSheetCommand(command);
255
+ setToast(`Selected ${id}`);
256
+ return;
257
+ }
258
+ if (command.destructive && (command.name === "version-publish" || command.name === "version-archive")) {
259
+ setConfirm(createConfirm(command.name, "yes", command.name, { id }));
260
+ return;
261
+ }
262
+ setWorking(true);
263
+ try {
264
+ await applyResult(await applyPick(command.name, id, stateRef.current.context));
265
+ }
266
+ catch (error) {
267
+ setToast(error instanceof Error ? error.message : String(error));
268
+ }
269
+ finally {
270
+ setWorking(false);
271
+ }
272
+ }
105
273
  async function runCommand(command, args) {
106
274
  if (command.jumpTab) {
107
275
  setTab(command.jumpTab);
108
276
  setToast(`Opened ${command.jumpTab}`);
109
277
  return;
110
278
  }
111
- if (!ensureAuth(command))
112
- return;
113
279
  if (command.name === "help") {
114
- setInput("/");
280
+ setPaneTitle("help");
281
+ setPaneLines(helpLines());
282
+ setTab("overview");
283
+ setToast("Help — see pane");
115
284
  return;
116
285
  }
117
286
  if (command.name === "quit") {
118
287
  onQuit(0);
119
288
  return;
120
289
  }
290
+ if (!ensureAuth(command))
291
+ return;
292
+ if (command.name === "trace") {
293
+ setTrace((v) => {
294
+ setToast(`trace ${v ? "off" : "on"}`);
295
+ return !v;
296
+ });
297
+ return;
298
+ }
299
+ if (command.name === "local") {
300
+ setLocalMode((v) => {
301
+ setToast(`local mode ${v ? "off" : "on"}`);
302
+ return !v;
303
+ });
304
+ return;
305
+ }
306
+ const confirmSpec = destructiveSpec(command.name, stateRef.current.context);
307
+ if (confirmSpec && !command.pick) {
308
+ setConfirm(createConfirm(confirmSpec.title, confirmSpec.phrase, confirmSpec.action, confirmSpec.args));
309
+ return;
310
+ }
311
+ if (command.pick) {
312
+ if (args.trim()) {
313
+ setWorking(true);
314
+ try {
315
+ await applyResult(await applyPick(command.name, args.trim(), stateRef.current.context));
316
+ }
317
+ catch (error) {
318
+ setToast(error instanceof Error ? error.message : String(error));
319
+ }
320
+ finally {
321
+ setWorking(false);
322
+ }
323
+ return;
324
+ }
325
+ await openPick(command, command.pick);
326
+ return;
327
+ }
121
328
  if (command.fields?.length) {
122
329
  let model = createSheet(command.name, command.fields);
123
330
  if (args)
124
331
  model = setFocusedValue(model, args);
125
332
  setSheet(model);
126
333
  setSheetCommand(command);
334
+ const focused = visibleFields(model)[model.focused];
335
+ if (focused?.control === "picker" && focused.picker) {
336
+ await openPick(command, focused.picker, model, focused.name);
337
+ }
127
338
  return;
128
339
  }
129
340
  setWorking(true);
130
341
  try {
131
- if (command.name === "status") {
132
- const status = await getStatus(context, { check: true });
133
- setToast(`status · env=${status.environment} · agent=${status.agentId ?? "—"}`);
134
- }
135
- else if (command.name === "validate") {
136
- setTab("bundle");
137
- const result = await validateLocalBundle(context, createClients(context).runtime);
138
- if (result.valid) {
139
- setToast(result.program?.id
140
- ? `Valid — ${result.program.id}@${result.program.version ?? "?"}`
141
- : "Program is valid");
142
- }
143
- else {
144
- const errors = result.findings.filter((f) => f.level !== "warning");
145
- const first = errors[0]?.message ?? "validation failed";
146
- setToast(`Invalid: ${first}${errors.length > 1 ? ` (+${errors.length - 1} more)` : ""}`);
147
- }
148
- }
149
- else if (command.name === "diff") {
150
- setTab("bundle");
151
- const remote = args.trim().toLowerCase() === "remote";
152
- const diff = remote
153
- ? await remoteDiff(context, createClients(context).management)
154
- : await localDiff(context);
155
- setToast(`diff vs ${remote ? "remote" : "baseline"} · ${diffSummary(diff)}`);
156
- }
157
- else if (command.name === "trace") {
158
- setTrace((v) => {
159
- setToast(`trace ${v ? "off" : "on"}`);
160
- return !v;
161
- });
162
- }
163
- else if (command.name === "local") {
164
- setLocalMode((v) => {
165
- setToast(`local mode ${v ? "off" : "on"}`);
166
- return !v;
167
- });
168
- }
169
- else if (command.name === "new") {
170
- setChat([]);
171
- setToast("New thread");
172
- }
173
- else if (command.name === "logout") {
174
- setToast("Use apollo logout in the CLI (confirm modal coming soon)");
175
- }
176
- else if (command.name === "upgrade") {
177
- setToast("Run apollo upgrade from the CLI");
178
- }
179
- else {
180
- setToast(`/${command.name} queued`);
181
- }
342
+ await applyResult(await runSimpleCommand(command.name, stateRef.current.context, args));
182
343
  }
183
344
  catch (error) {
184
345
  setToast(error instanceof Error ? error.message : String(error));
@@ -203,86 +364,59 @@ function App({ context, initialScope, glyph, onQuit }) {
203
364
  }
204
365
  return;
205
366
  }
206
- if (command.name === "login") {
207
- setTab("scope");
208
- setToast("Browser login isn't in the TUI yet — run apollo login from the CLI");
209
- return;
210
- }
211
- void executeSheet(command, model.values);
212
- }
213
- /** Run the service call behind a submitted command sheet. */
214
- async function executeSheet(command, values) {
215
- const text = (name) => {
216
- const value = values[name];
217
- return typeof value === "string" ? value.trim() : "";
218
- };
219
- setWorking(true);
220
- try {
221
- const clients = createClients(context);
222
- if (command.name === "pull") {
223
- setTab("bundle");
224
- const tag = text("tag");
225
- const dir = text("dir");
226
- const result = await pullBundle(context, clients.management, {
227
- // "latest" is the sheet default; omitting the tag pulls the
228
- // selected/live version, matching the CLI's bare `apollo pull`.
229
- ...(tag && tag !== "latest" ? { tag } : {}),
230
- ...(dir ? { destination: dir } : {}),
231
- force: false,
232
- });
233
- const removed = result.removed.length ? ` · removed ${result.removed.length}` : "";
234
- setToast(`Pulled ${Object.keys(result.files).length} file(s)${removed}`);
235
- }
236
- else if (command.name === "push") {
237
- setTab("bundle");
238
- const message = text("message");
239
- const dryRun = values.dryRun === true;
240
- const result = await pushBundle(context, clients.management, clients.runtime, {
241
- ...(message ? { commitMessage: message } : {}),
242
- dryRun,
243
- });
244
- setToast(result.push
245
- ? `Pushed ${result.push.new_version_tag} · ${diffSummary(result.diff)}`
246
- : `Dry run · ${diffSummary(result.diff)}`);
247
- }
248
- else if (command.name === "provision") {
249
- setTab("mockdb");
250
- const provisioned = await provisionMockDb(context);
251
- let note = `Provisioned mock DB for ${provisioned.agentId ?? "agent"}`;
252
- if (values.wire === true) {
253
- const wired = await wireMockDbConnection(context);
254
- note += ` · wired ${wired.connectionId}`;
255
- }
256
- setToast(note);
367
+ void (async () => {
368
+ setWorking(true);
369
+ try {
370
+ await applyResult(await runSheetAction(command.name, model.values, stateRef.current.context));
257
371
  }
258
- else if (command.name === "thread") {
259
- setChat([]);
260
- setToast(`Thread ${text("thread") || "new"} (per-thread history coming soon)`);
372
+ catch (err) {
373
+ setToast(err instanceof Error ? err.message : String(err));
261
374
  }
262
- else {
263
- setToast(`/${command.name} isn't wired in the TUI yet — use apollo ${command.name}`);
375
+ finally {
376
+ setWorking(false);
264
377
  }
265
- }
266
- catch (error) {
267
- setToast(error instanceof Error ? error.message : String(error));
268
- }
269
- finally {
270
- setWorking(false);
271
- }
378
+ })();
272
379
  }
273
- function handleSheetLine(model, command, line) {
380
+ async function handleSheetLine(model, command, line) {
381
+ if (line === "+" || line.toLowerCase() === "advanced") {
382
+ setSheet({ ...model, advancedOpen: !model.advancedOpen });
383
+ return;
384
+ }
274
385
  const fields = visibleFields(model);
275
386
  const focused = fields[model.focused];
276
387
  if (line === "") {
277
- // Empty entry advances; on the last field it submits.
278
388
  if (model.focused >= fields.length - 1) {
279
389
  submitSheet(model, command);
280
390
  }
281
391
  else {
282
- setSheet(moveFocus(model, 1));
392
+ const next = moveFocus(model, 1);
393
+ setSheet(next);
394
+ const nextField = visibleFields(next)[next.focused];
395
+ if (nextField?.control === "picker" && nextField.picker) {
396
+ await openPick(command, nextField.picker, next, nextField.name);
397
+ }
283
398
  }
284
399
  return;
285
400
  }
401
+ if (focused?.control === "picker" && focused.picker) {
402
+ await openPick(command, focused.picker, model, focused.name);
403
+ // Also try resolving against a fresh catalog if user typed a filter as the line
404
+ setWorking(true);
405
+ try {
406
+ const items = await loadPickerCatalog(focused.picker, stateRef.current.context);
407
+ const hit = resolvePickerAnswer(filterPickerItems(items, line), line);
408
+ if (hit) {
409
+ await finishPick(command, hit.id, model, focused.name);
410
+ return;
411
+ }
412
+ }
413
+ catch {
414
+ /* fall through */
415
+ }
416
+ finally {
417
+ setWorking(false);
418
+ }
419
+ }
286
420
  let next = model;
287
421
  if (focused?.control === "toggle") {
288
422
  const truthy = ["y", "yes", "true", "on", "x"].includes(line.toLowerCase());
@@ -295,7 +429,12 @@ function App({ context, initialScope, glyph, onQuit }) {
295
429
  submitSheet(next, command);
296
430
  }
297
431
  else {
298
- setSheet(moveFocus(next, 1));
432
+ const moved = moveFocus(next, 1);
433
+ setSheet(moved);
434
+ const nextField = visibleFields(moved)[moved.focused];
435
+ if (nextField?.control === "picker" && nextField.picker) {
436
+ await openPick(command, nextField.picker, moved, nextField.name);
437
+ }
299
438
  }
300
439
  }
301
440
  async function sendChat(text) {
@@ -306,20 +445,39 @@ function App({ context, initialScope, glyph, onQuit }) {
306
445
  }
307
446
  setWorking(true);
308
447
  try {
309
- const clients = createClients(context);
310
- const result = await sendMessage(context, clients.runtime, {
448
+ const clients = createClients(stateRef.current.context);
449
+ const result = await sendMessage(stateRef.current.context, clients.runtime, {
311
450
  text,
312
- ...(trace ? { includeTrace: true } : {}),
313
- ...(localMode && context.agentRoot ? { localDir: context.agentRoot } : {}),
451
+ ...(threadId ? { threadId } : {}),
452
+ includeTrace: true,
453
+ ...(localMode && stateRef.current.context.agentRoot
454
+ ? { localDir: stateRef.current.context.agentRoot }
455
+ : {}),
314
456
  variables: vars,
315
457
  });
458
+ if (result.thread_id)
459
+ setThreadId(result.thread_id);
316
460
  const reply = result.message.text ?? JSON.stringify(result);
317
- setChat((c) => [...c, { role: "agent", text: reply }]);
461
+ setChat((c) => [
462
+ ...c,
463
+ {
464
+ role: "agent",
465
+ text: reply,
466
+ messageId: result.message.id,
467
+ interactionId: result.message.id,
468
+ ...(result.message.trace_info
469
+ ? { trace: result.message.trace_info }
470
+ : {}),
471
+ },
472
+ ]);
318
473
  }
319
474
  catch (error) {
320
475
  setChat((c) => [
321
476
  ...c,
322
- { role: "system", text: error instanceof Error ? error.message : String(error) },
477
+ {
478
+ role: "system",
479
+ text: error instanceof Error ? error.message : String(error),
480
+ },
323
481
  ]);
324
482
  }
325
483
  finally {
@@ -331,8 +489,63 @@ function App({ context, initialScope, glyph, onQuit }) {
331
489
  setInput("");
332
490
  setToast(undefined);
333
491
  const current = stateRef.current;
492
+ if (current.confirm) {
493
+ const checked = checkConfirm(current.confirm, line);
494
+ if (checked === "ok") {
495
+ const spec = current.confirm;
496
+ setConfirm(undefined);
497
+ void (async () => {
498
+ setWorking(true);
499
+ try {
500
+ await applyResult(await runDestructive(spec.action, current.context, spec.args ?? {}));
501
+ }
502
+ catch (error) {
503
+ setToast(error instanceof Error ? error.message : String(error));
504
+ }
505
+ finally {
506
+ setWorking(false);
507
+ }
508
+ })();
509
+ }
510
+ else {
511
+ setConfirm(checked);
512
+ }
513
+ return;
514
+ }
515
+ if (current.picker) {
516
+ const visible = filterPickerItems(current.picker.items, effectivePickerFilter(line));
517
+ if (visible.length === 0) {
518
+ setToast(line ? `No match for "${line}"` : "No options");
519
+ setInput(line);
520
+ return;
521
+ }
522
+ // Empty Enter → highlighted row. Digits → index. Text → unique match only.
523
+ const trimmed = line.trim();
524
+ let hit;
525
+ if (!trimmed) {
526
+ hit = visible[clampPickerCursor(current.picker.cursor, visible.length)];
527
+ }
528
+ else if (/^\d+$/.test(trimmed)) {
529
+ hit = resolvePickerAnswer(visible, trimmed);
530
+ }
531
+ else {
532
+ hit = resolvePickerAnswer(visible, trimmed);
533
+ if (!hit && visible.length > 1) {
534
+ setToast("↑↓ to choose, Enter to select (or narrow the filter)");
535
+ setInput(line);
536
+ return;
537
+ }
538
+ }
539
+ if (!hit) {
540
+ setToast(line ? `No match for "${line}"` : "↑↓ to choose, Enter to select");
541
+ setInput(line);
542
+ return;
543
+ }
544
+ void finishPick(current.picker.command, hit.id, current.picker.sheet, current.picker.sheetField);
545
+ return;
546
+ }
334
547
  if (current.sheet && current.sheetCommand) {
335
- handleSheetLine(current.sheet, current.sheetCommand, line);
548
+ void handleSheetLine(current.sheet, current.sheetCommand, line);
336
549
  return;
337
550
  }
338
551
  if (line.startsWith("/")) {
@@ -348,17 +561,54 @@ function App({ context, initialScope, glyph, onQuit }) {
348
561
  if (line === "")
349
562
  return;
350
563
  if (current.tab === "chat") {
564
+ const overlay = /^[td](?:\s+(\d+))?$/i.exec(line);
565
+ if (overlay) {
566
+ const kind = line[0].toLowerCase() === "d" ? "debug" : "trace";
567
+ void (async () => {
568
+ setWorking(true);
569
+ try {
570
+ await applyResult(await chatOverlay(kind, chat, threadId, current.context, overlay[1]));
571
+ }
572
+ catch (error) {
573
+ setToast(error instanceof Error ? error.message : String(error));
574
+ }
575
+ finally {
576
+ setWorking(false);
577
+ }
578
+ })();
579
+ return;
580
+ }
351
581
  void sendChat(line);
352
582
  return;
353
583
  }
354
584
  setToast("Type / for commands, or use the Chat tab to message the agent");
355
585
  }
356
- const placeholder = tab === "chat"
357
- ? "message the agent, or / for commands"
358
- : sheet
359
- ? "type a value · Enter next · Esc cancel"
360
- : "/ for commands · Tab switches tabs";
361
- return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1, paddingLeft: 1, paddingRight: 1 }, children: [_jsx(TabBar, { active: tab }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, paddingTop: 1 }, children: [tab === "overview" && (_jsx(Overview, { frame: frame, working: working, agentPath: scope.agentPath })), tab === "chat" && (_jsx(ChatPane, { chat: chat, trace: trace, localMode: localMode, vars: vars })), tab === "bundle" && _jsx(BundlePane, {}), tab === "mockdb" && _jsx(MockDbPane, {}), tab === "scope" && _jsx(ScopePane, { scope: scope })] }), sheet && sheetCommand && _jsx(SheetPane, { sheet: sheet }), paletteOpen && paletteMatches.length > 0 && _jsx(Palette, { matches: paletteMatches }), toast && (_jsxs("text", { fg: DIM, style: { paddingLeft: 1 }, children: ["\u21B3 ", toast] })), !scope.loggedIn && (_jsx("box", { style: { flexDirection: "row", justifyContent: "flex-end" }, children: _jsx("text", { fg: AMBER, children: "Not logged in \u00B7 Run /login " }) })), _jsx("box", { style: {
586
+ const placeholder = confirm
587
+ ? `type "${confirm.phrase}" to confirm · Esc cancel`
588
+ : picker
589
+ ? "filter · ↑↓ move · Enter select · Esc cancel"
590
+ : tab === "chat"
591
+ ? "message · t/d trace/debug last reply · / commands"
592
+ : sheet
593
+ ? "value · Enter next · + advanced · Esc cancel"
594
+ : "/ for commands · Tab switches tabs";
595
+ const pickerFilter = picker ? effectivePickerFilter(input) : undefined;
596
+ const pickerVisible = picker
597
+ ? filterPickerItems(picker.items, pickerFilter)
598
+ : [];
599
+ const pickerCursor = picker
600
+ ? clampPickerCursor(picker.cursor, pickerVisible.length)
601
+ : 0;
602
+ // Keep the highlighted row on screen when the list is long.
603
+ const PICKER_PAGE = pickerPageSize();
604
+ const pickerWindowStart = pickerVisible.length <= PICKER_PAGE
605
+ ? 0
606
+ : Math.max(0, Math.min(pickerCursor - Math.floor(PICKER_PAGE / 3), pickerVisible.length - PICKER_PAGE));
607
+ const pickerWindow = pickerVisible.slice(pickerWindowStart, pickerWindowStart + PICKER_PAGE);
608
+ const modalOpen = Boolean(picker || confirm || sheet);
609
+ return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1, paddingLeft: 1, paddingRight: 1 }, children: [_jsx(TabBar, { active: tab }), _jsx("box", { style: { flexDirection: "column", flexGrow: 1, paddingTop: 1 }, children: picker ? (_jsx(PickerPane, { items: pickerWindow, cursor: pickerCursor, windowStart: pickerWindowStart, total: pickerVisible.length, title: picker.command.name, filter: pickerFilter, dismissHint: picker.command.name === "place-onboard"
610
+ ? "Esc continue without place"
611
+ : undefined })) : confirm ? (_jsx(ConfirmPane, { confirm: confirm })) : sheet && sheetCommand ? (_jsx(SheetPane, { sheet: sheet })) : (_jsxs(_Fragment, { children: [tab === "overview" && (_jsx(Overview, { frame: frame, working: working, agentPath: scope.agentPath, paneTitle: paneTitle, paneLines: paneLines })), tab === "chat" && (_jsx(ChatPane, { chat: chat, trace: trace, localMode: localMode, vars: vars, threadId: threadId, paneTitle: paneTitle, paneLines: paneLines })), tab === "bundle" && (_jsx(BundlePane, { paneTitle: paneTitle, paneLines: paneLines })), tab === "mockdb" && (_jsx(MockDbPane, { paneTitle: paneTitle, paneLines: paneLines })), tab === "scope" && _jsx(ScopePane, { scope: scope })] })) }), paletteOpen && !modalOpen && paletteMatches.length > 0 && (_jsx(Palette, { matches: paletteMatches })), toast && (_jsxs("text", { fg: DIM, style: { paddingLeft: 1 }, children: ["\u21B3 ", toast] })), !scope.loggedIn && (_jsx("box", { style: { flexDirection: "row", justifyContent: "flex-end" }, children: _jsx("text", { fg: AMBER, children: "Not logged in \u00B7 Run /login " }) })), _jsx("box", { style: {
362
612
  border: true,
363
613
  borderStyle: "rounded",
364
614
  borderColor: working ? AMBER : TEAL_DIM,
@@ -366,16 +616,22 @@ function App({ context, initialScope, glyph, onQuit }) {
366
616
  height: 3,
367
617
  paddingLeft: 1,
368
618
  paddingRight: 1,
369
- }, children: _jsx("input", { focused: true, value: input, placeholder: placeholder, placeholderColor: FAINT, textColor: FG, focusedTextColor: FG, cursorColor: TEAL, backgroundColor: "transparent", focusedBackgroundColor: "transparent", onInput: (value) => setInput(value),
370
- // ENTER emits the current widget value; using React state here would
371
- // be stale when typing and Enter arrive in the same input batch.
372
- onSubmit: (value) => handleSubmit(typeof value === "string" ? value : input) }) }), _jsxs("box", { style: { flexDirection: "row", justifyContent: "space-between" }, children: [_jsx("text", { fg: FAINT, children: " Tab tabs \u00B7 / commands \u00B7 Enter send \u00B7 Ctrl+C quit" }), _jsxs("text", { fg: FAINT, children: [scope.env, " \u00B7 ", scope.agent, " \u00B7 ", scope.version, " "] })] })] }));
619
+ }, children: _jsx("input", { focused: true, value: input, placeholder: placeholder, placeholderColor: FAINT, textColor: FG, focusedTextColor: FG, cursorColor: TEAL, backgroundColor: "transparent", focusedBackgroundColor: "transparent", onInput: (value) => {
620
+ setInput(value);
621
+ if (picker) {
622
+ const nextFilter = effectivePickerFilter(value);
623
+ const prevFilter = effectivePickerFilter(input);
624
+ if (nextFilter !== prevFilter) {
625
+ setPicker({ ...picker, cursor: 0 });
626
+ }
627
+ }
628
+ }, onSubmit: (value) => handleSubmit(typeof value === "string" ? value : input) }) }), _jsxs("box", { style: { flexDirection: "row", justifyContent: "space-between" }, children: [_jsx("text", { fg: FAINT, children: " Tab tabs \u00B7 / commands \u00B7 Enter send \u00B7 Ctrl+C quit" }), _jsxs("text", { fg: FAINT, children: [scope.profile, "/", scope.env, " \u00B7 ", scope.workspace, " \u00B7 ", scope.agent, " "] })] })] }));
373
629
  }
374
630
  function TabBar({ active }) {
375
631
  return (_jsx("box", { style: { flexDirection: "row", paddingTop: 1 }, children: TABS.map((t) => t.id === active ? (_jsx("text", { fg: TEAL, children: _jsx("b", { children: ` ${t.label} ` }) }, t.id)) : (_jsx("text", { fg: DIM, children: ` ${t.label} ` }, t.id))) }));
376
632
  }
377
- function Overview({ frame, working, agentPath, }) {
378
- return (_jsxs("box", { style: { flexDirection: "column", alignItems: "center" }, children: [_jsxs("box", { style: {
633
+ function Overview({ frame, working, agentPath, paneTitle, paneLines, }) {
634
+ return (_jsxs("box", { style: { flexDirection: "column", alignItems: "center", flexGrow: 1 }, children: [_jsxs("box", { style: {
379
635
  border: true,
380
636
  borderStyle: "rounded",
381
637
  borderColor: TEAL_DIM,
@@ -384,19 +640,25 @@ function Overview({ frame, working, agentPath, }) {
384
640
  width: 44,
385
641
  paddingTop: 1,
386
642
  paddingBottom: 1,
387
- }, children: [frame.map((line, i) => (_jsx("text", { fg: working ? AMBER : TEAL, children: line }, i))), _jsxs("text", { fg: FG, children: [_jsx("b", { children: "apollo" }), " ", _jsxs("span", { fg: DIM, children: ["v", VERSION] })] }), _jsx("text", { fg: DIM, children: "Manage and operate Apollo agents" }), agentPath && _jsx("text", { fg: FAINT, children: agentPath })] }), _jsx("text", { children: " " }), _jsx("text", { fg: DIM, children: "/status /push /chat /mockdb /help" })] }));
643
+ }, children: [frame.map((line, i) => (_jsx("text", { fg: working ? AMBER : TEAL, children: line }, i))), _jsxs("text", { fg: FG, children: [_jsx("b", { children: "apollo" }), " ", _jsxs("span", { fg: DIM, children: ["v", VERSION] })] }), _jsx("text", { fg: DIM, children: "Manage and operate Apollo agents" }), agentPath && _jsx("text", { fg: FAINT, children: agentPath })] }), _jsx("text", { children: " " }), _jsx("text", { fg: DIM, children: "/status /login /env /agent /push /help" }), paneTitle === "help" && paneLines.length > 0 && (_jsx("scrollbox", { style: { flexGrow: 1, width: "100%", marginTop: 1 }, children: paneLines.map((line, i) => (_jsx("text", { fg: FAINT, children: line }, i))) }))] }));
388
644
  }
389
- function ChatPane({ chat, trace, localMode, vars, }) {
390
- return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1 }, children: [_jsxs("text", { fg: FAINT, children: ["thread \u00B7 user=cli \u00B7 local=", localMode ? "on" : "off", " \u00B7 trace=", trace ? "on" : "off", vars.length > 0 ? ` · vars: ${vars.join(", ")}` : ""] }), _jsx("text", { children: " " }), _jsx("scrollbox", { style: { flexGrow: 1, flexDirection: "column" }, stickyScroll: true, children: chat.length === 0 ? (_jsx("text", { fg: FAINT, children: " (empty thread \u2014 type a message or /new)" })) : (chat.map((turn, i) => (_jsx("text", { fg: turn.role === "you" ? FG : turn.role === "agent" ? TEAL : AMBER, children: ` ${turn.role.padEnd(6)} ${turn.text}` }, i)))) })] }));
645
+ function ChatPane({ chat, trace, localMode, vars, threadId, paneTitle, paneLines, }) {
646
+ const agentCount = chat.filter((t) => t.role === "agent").length;
647
+ return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1 }, children: [_jsxs("text", { fg: FAINT, children: ["thread=", threadId?.slice(0, 12) ?? "new", " \u00B7 local=", localMode ? "on" : "off", " \u00B7 trace=", trace ? "on" : "off", " \u00B7 replies=", agentCount, vars.length > 0 ? ` · vars: ${vars.join(", ")}` : ""] }), _jsx("text", { fg: FAINT, children: " t / d = trace / debug last agent reply \u00B7 t 2 = 2nd reply" }), _jsx("text", { children: " " }), _jsxs("scrollbox", { style: { flexGrow: 1, flexDirection: "column" }, stickyScroll: true, children: [chat.length === 0 ? (_jsx("text", { fg: FAINT, children: " (empty \u2014 type a message, /thread, or /new)" })) : (chat.map((turn, i) => (_jsx("text", { fg: turn.role === "you" ? FG : turn.role === "agent" ? TEAL : AMBER, children: ` ${turn.role.padEnd(6)} ${turn.text}` }, i)))), paneLines.length > 0 &&
648
+ paneTitle &&
649
+ /^(trace|debug)/i.test(paneTitle) ? (_jsxs(_Fragment, { children: [_jsx("text", { children: " " }), _jsxs("text", { fg: AMBER, children: ["\u2014 ", paneTitle, " \u2014"] }), paneLines.map((line, i) => (_jsx("text", { fg: FAINT, children: line }, `p-${i}`)))] })) : null] })] }));
391
650
  }
392
- function BundlePane() {
393
- return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("text", { fg: FG, children: _jsx("b", { children: " Bundle" }) }), _jsx("text", { fg: DIM, children: " Validate \u00B7 Diff vs baseline \u00B7 Diff vs remote" }), _jsx("text", { fg: DIM, children: " Pull\u2026 \u00B7 Push\u2026 \u00B7 Versions" }), _jsx("text", { children: " " }), _jsx("text", { fg: FAINT, children: " Open a command sheet with /push, /pull, /validate, /diff" })] }));
651
+ function ResultPane({ title, hints, paneTitle, paneLines, }) {
652
+ return (_jsxs("box", { style: { flexDirection: "column", flexGrow: 1 }, children: [_jsx("text", { fg: FG, children: _jsxs("b", { children: [" ", title] }) }), hints.map((h, i) => (_jsxs("text", { fg: DIM, children: [" ", h] }, i))), _jsx("text", { children: " " }), paneLines.length > 0 ? (_jsxs("scrollbox", { style: { flexGrow: 1 }, children: [paneTitle && (_jsxs("text", { fg: TEAL, children: ["\u2014 ", paneTitle, " \u2014"] })), paneLines.map((line, i) => (_jsx("text", { fg: FAINT, children: line }, i)))] })) : (_jsx("text", { fg: FAINT, children: " Use / commands from any tab" }))] }));
394
653
  }
395
- function MockDbPane() {
396
- return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("text", { fg: FG, children: _jsx("b", { children: " Mock DB" }) }), _jsx("text", { fg: DIM, children: " Status \u00B7 Collections \u00B7 Endpoints \u00B7 Sessions" }), _jsx("text", { children: " " }), _jsx("text", { fg: FAINT, children: " /provision /wire /seed \u2014 or use the Scope agent context" })] }));
654
+ function BundlePane({ paneTitle, paneLines, }) {
655
+ return (_jsx(ResultPane, { title: "Bundle", hints: ["/validate /diff /pull /push /versions /version-publish"], paneTitle: paneTitle, paneLines: paneLines }));
656
+ }
657
+ function MockDbPane({ paneTitle, paneLines, }) {
658
+ return (_jsx(ResultPane, { title: "Mock DB", hints: ["/provision /wire /seed /mdb-collection-* /mdb-endpoint-* /mdb-status"], paneTitle: paneTitle, paneLines: paneLines }));
397
659
  }
398
660
  function ScopePane({ scope }) {
399
- return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("text", { fg: FG, children: _jsx("b", { children: " Scope" }) }), _jsxs("text", { fg: DIM, children: [" env: ", scope.env] }), _jsxs("text", { fg: DIM, children: [" agent: ", scope.agent] }), _jsxs("text", { fg: DIM, children: [" version: ", scope.version] }), _jsxs("text", { fg: scope.loggedIn ? DIM : AMBER, children: [" ", "auth: ", scope.loggedIn ? "logged in" : "not logged in"] }), _jsx("text", { children: " " }), _jsx("text", { fg: FAINT, children: " /login /logout \u2014 pickers for env/org/project/agent live here" })] }));
661
+ return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("text", { fg: FG, children: _jsx("b", { children: " Scope" }) }), _jsxs("text", { fg: DIM, children: [" profile: ", scope.profile] }), _jsxs("text", { fg: DIM, children: [" env: ", scope.env, " (account \u2014 no cd)"] }), _jsxs("text", { fg: DIM, children: [" workspace: ", scope.workspace] }), _jsxs("text", { fg: DIM, children: [" org: ", scope.org] }), _jsxs("text", { fg: DIM, children: [" project: ", scope.project] }), _jsxs("text", { fg: DIM, children: [" agent: ", scope.agent] }), _jsxs("text", { fg: DIM, children: [" version: ", scope.version] }), _jsxs("text", { fg: scope.loggedIn ? DIM : AMBER, children: [" ", "auth: ", scope.loggedIn ? "logged in" : "not logged in"] }), _jsx("text", { children: " " }), _jsxs("text", { fg: FAINT, children: [" ", "/env /profile /workspace /org /project /agent /agent-import /login"] })] }));
400
662
  }
401
663
  function SheetPane({ sheet }) {
402
664
  const fields = visibleFields(sheet);
@@ -405,14 +667,50 @@ function SheetPane({ sheet }) {
405
667
  borderStyle: "rounded",
406
668
  borderColor: TEAL_DIM,
407
669
  flexDirection: "column",
670
+ flexGrow: 1,
408
671
  paddingLeft: 1,
409
672
  paddingRight: 1,
410
673
  }, children: [fields.map((field, i) => {
411
674
  const raw = sheet.values[field.name];
412
675
  const display = field.control === "toggle" ? (raw ? "[x]" : "[ ]") : String(raw ?? "");
413
676
  const active = i === sheet.focused;
414
- return (_jsxs("text", { fg: active ? FG : DIM, children: [active ? "› " : " ", field.label.padEnd(18), " ", display, field.required && !display ? _jsx("span", { fg: FAINT, children: " (required)" }) : null] }, field.name));
415
- }), sheet.error && _jsxs("text", { fg: RED, children: ["! ", sheet.error] }), _jsx("text", { fg: FAINT, children: "type value + Enter \u00B7 Enter skip \u00B7 Tab move \u00B7 Esc cancel" })] }));
677
+ return (_jsxs("text", { fg: active ? FG : DIM, children: [active ? "› " : " ", field.label.padEnd(18), " ", display, field.control === "picker" ? _jsx("span", { fg: FAINT, children: " (picker)" }) : null, field.required && !display ? _jsx("span", { fg: FAINT, children: " (required)" }) : null] }, field.name));
678
+ }), sheet.fields.some((f) => f.advanced) && (_jsxs("text", { fg: FAINT, children: [" ", sheet.advancedOpen ? "▾" : "▸", " advanced (+ to toggle)"] })), sheet.error && _jsxs("text", { fg: RED, children: ["! ", sheet.error] }), _jsx("text", { fg: FAINT, children: "type value + Enter \u00B7 Enter skip \u00B7 Tab move \u00B7 Esc cancel" })] }));
679
+ }
680
+ function PickerPane({ items, cursor, windowStart, total, title, filter, dismissHint, }) {
681
+ const { nameWidth, idWidth } = pickerColumnWidths();
682
+ return (_jsxs("box", { title: ` pick ${title}${filter ? ` · “${filter}”` : ""} `, style: {
683
+ border: true,
684
+ borderStyle: "rounded",
685
+ borderColor: TEAL_DIM,
686
+ flexDirection: "column",
687
+ flexGrow: 1,
688
+ paddingLeft: 1,
689
+ paddingRight: 1,
690
+ }, children: [items.length === 0 ? (_jsx("text", { fg: AMBER, children: " No matches" })) : (items.map((item, offset) => {
691
+ const index = windowStart + offset;
692
+ const active = index === cursor;
693
+ const marker = active ? "›" : " ";
694
+ const number = String(index + 1).padStart(2);
695
+ const name = truncatePickerText(item.label, nameWidth).padEnd(nameWidth);
696
+ const secondary = item.detail && item.detail !== item.label
697
+ ? truncatePickerText(item.detail, idWidth)
698
+ : "";
699
+ return (_jsxs("text", { fg: active ? TEAL : FG, children: [`${marker} ${number} ${name}`, secondary ? (_jsx("span", { fg: FAINT, children: ` ${secondary}` })) : null] }, `${item.id}-${index}`));
700
+ })), _jsxs("text", { fg: FAINT, children: [total > items.length
701
+ ? ` ${cursor + 1}/${total} · ↑↓ move · type to filter · Enter`
702
+ : ` ↑↓ move · type to filter · Enter select`, dismissHint ? ` · ${dismissHint}` : ""] })] }));
703
+ }
704
+ function ConfirmPane({ confirm }) {
705
+ return (_jsxs("box", { title: ` confirm ${confirm.title} `, style: {
706
+ border: true,
707
+ borderStyle: "rounded",
708
+ borderColor: AMBER,
709
+ flexDirection: "column",
710
+ flexGrow: 1,
711
+ paddingLeft: 1,
712
+ paddingRight: 1,
713
+ }, children: [_jsxs("text", { fg: AMBER, children: ["Type ", _jsx("b", { children: confirm.phrase }), " to confirm"] }), confirm.error && _jsxs("text", { fg: RED, children: ["! ", confirm.error] })] }));
416
714
  }
417
715
  function Palette({ matches }) {
418
716
  return (_jsx("box", { title: " commands ", style: {
@@ -422,7 +720,7 @@ function Palette({ matches }) {
422
720
  flexDirection: "column",
423
721
  paddingLeft: 1,
424
722
  paddingRight: 1,
425
- }, children: matches.map((cmd) => (_jsxs("text", { children: [_jsxs("span", { fg: TEAL, children: ["/", cmd.name.padEnd(12)] }), _jsx("span", { fg: DIM, children: cmd.summary })] }, cmd.name))) }));
723
+ }, children: matches.map((cmd) => (_jsxs("text", { children: [_jsxs("span", { fg: TEAL, children: ["/", cmd.name.padEnd(16)] }), _jsx("span", { fg: DIM, children: cmd.summary })] }, cmd.name))) }));
426
724
  }
427
725
  export async function runOpenTui() {
428
726
  const context = await resolveContext({});
@@ -442,8 +740,6 @@ export async function runOpenTui() {
442
740
  });
443
741
  root.unmount();
444
742
  renderer.destroy();
445
- // The raw-mode stdin handle keeps the event loop alive after destroy;
446
- // exit explicitly so /quit actually returns to the shell.
447
743
  process.exit(code);
448
744
  }
449
745
  //# sourceMappingURL=opentui-app.js.map