@algolia/wizard 0.7.0 → 0.8.0-rc.58.43

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +845 -528
  3. package/package.json +1 -3
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/ui/App.tsx
7
- import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
7
+ import { Box as Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -12,32 +12,86 @@ import { nanoid } from "nanoid";
12
12
 
13
13
  // src/lib/algoliaCli.ts
14
14
  import { spawn } from "node:child_process";
15
- import { createRequire } from "node:module";
16
- var require2 = createRequire(import.meta.url);
17
- function algoliaCliEntry() {
18
- return require2.resolve("@algolia/cli/bin/run.js");
15
+ import { z } from "zod";
16
+ function npxArgs(args) {
17
+ return ["--yes", "@algolia/cli@latest", ...args];
19
18
  }
20
- function runAlgoliaCli(args) {
19
+ var shell = process.platform === "win32";
20
+ function lineSplitter(emit) {
21
+ let buffer = "";
22
+ return {
23
+ push(chunk) {
24
+ buffer += chunk;
25
+ const lines = buffer.split("\n");
26
+ buffer = lines.pop() ?? "";
27
+ for (const line of lines) emit(line.replace(/\r$/, ""));
28
+ },
29
+ flush() {
30
+ if (buffer) emit(buffer.replace(/\r$/, ""));
31
+ buffer = "";
32
+ }
33
+ };
34
+ }
35
+ var wizardSink = (stream, line) => {
36
+ if (!line.trim()) return;
37
+ useWizard.getState().pushCliOutput(stream, line);
38
+ };
39
+ var stderrSink = (stream, line) => {
40
+ if (stream === "stdout") return;
41
+ wizardSink(stream, line);
42
+ };
43
+ function runAlgoliaCli(args, { onOutput } = {}) {
44
+ const store = useWizard.getState();
45
+ const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
21
46
  return new Promise((resolve4, reject) => {
22
- const child = spawn(process.execPath, [algoliaCliEntry(), ...args]);
47
+ const child = spawn("npx", npxArgs(args), { shell });
23
48
  let stdout = "";
24
49
  let stderr = "";
25
- child.stdout.on("data", (chunk) => stdout += chunk);
26
- child.stderr.on("data", (chunk) => stderr += chunk);
50
+ const splitters = {
51
+ stdout: lineSplitter((line) => onOutput?.("stdout", line)),
52
+ stderr: lineSplitter((line) => onOutput?.("stderr", line))
53
+ };
54
+ child.stdout.on("data", (chunk) => {
55
+ const text = String(chunk);
56
+ stdout += text;
57
+ splitters.stdout.push(text);
58
+ });
59
+ child.stderr.on("data", (chunk) => {
60
+ const text = String(chunk);
61
+ stderr += text;
62
+ splitters.stderr.push(text);
63
+ });
27
64
  child.on("error", reject);
28
65
  child.on("close", (code) => {
66
+ splitters.stdout.flush();
67
+ splitters.stderr.flush();
29
68
  if (code === 0) {
30
69
  resolve4(stdout);
31
70
  } else {
32
- const detail = stderr.trim() || stdout.trim();
71
+ const failed = stderr.trim();
72
+ let detail = "";
73
+ if (failed) {
74
+ detail = `: ${failed}`;
75
+ } else if (stdout.trim()) {
76
+ detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
77
+ }
33
78
  reject(
34
79
  new Error(
35
- `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail ? `: ${detail}` : ""}`
80
+ `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
36
81
  )
37
82
  );
38
83
  }
39
84
  });
40
- });
85
+ }).then(
86
+ (out) => {
87
+ useWizard.getState().logEnd(logId, "success");
88
+ return out;
89
+ },
90
+ (err) => {
91
+ useWizard.getState().logEnd(logId, "error");
92
+ throw err;
93
+ }
94
+ );
41
95
  }
42
96
  async function getUser() {
43
97
  let raw;
@@ -52,19 +106,23 @@ async function getUser() {
52
106
  return null;
53
107
  }
54
108
  }
55
- function runAuthLogin() {
56
- return new Promise((resolve4, reject) => {
57
- const child = spawn(
58
- process.execPath,
59
- [algoliaCliEntry(), "auth", "login", "--default"],
60
- { stdio: "inherit" }
61
- );
62
- child.on("error", reject);
63
- child.on("close", (code) => {
64
- if (code === 0) resolve4();
65
- else reject(new Error(`Algolia authentication failed (exit ${code}).`));
66
- });
109
+ var loginResultSchema = z.object({
110
+ success: z.boolean(),
111
+ email: z.string().optional()
112
+ });
113
+ async function runAuthLogin() {
114
+ const raw = await runAlgoliaCli(["auth", "login", "--non-interactive"], {
115
+ onOutput: stderrSink
67
116
  });
117
+ let parsed;
118
+ try {
119
+ parsed = loginResultSchema.safeParse(JSON.parse(raw));
120
+ } catch {
121
+ parsed = void 0;
122
+ }
123
+ if (parsed?.success && !parsed.data.success) {
124
+ throw new Error("Algolia sign-in did not report success.");
125
+ }
68
126
  }
69
127
 
70
128
  // src/lib/auth.ts
@@ -171,6 +229,7 @@ function describeInputValue(value) {
171
229
  return Array.isArray(value) ? value.join(", ") : value;
172
230
  }
173
231
  var NOTICE_INTERVAL_MS = 2e3;
232
+ var CLI_OUTPUT_LIMIT = 200;
174
233
  var useWizard = create((set, get) => ({
175
234
  phase: "idle",
176
235
  homeScreen: "home",
@@ -182,10 +241,18 @@ var useWizard = create((set, get) => ({
182
241
  notices: [],
183
242
  _noticeQueue: [],
184
243
  _noticeTimer: null,
244
+ cliOutput: [],
245
+ targetIndex: null,
185
246
  logs: [],
186
247
  error: null,
187
248
  inputReq: null,
188
249
  _resolve: null,
250
+ // Brackets a CLI subprocess that needs the screen. Sign-in happens after the
251
+ // welcome screen's enter, so `endAuth` lands on 'preflight', not 'idle':
252
+ // returning to 'idle' would put the welcome screen back up and ask the user
253
+ // to confirm the run a second time.
254
+ beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
255
+ endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
189
256
  // Advances past the welcome screen. Only meaningful from 'idle' — once the
190
257
  // workflow is running there's nothing left to confirm.
191
258
  // Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
@@ -220,7 +287,13 @@ var useWizard = create((set, get) => ({
220
287
  syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
221
288
  setActiveStep: (index) => {
222
289
  get()._clearNoticeQueue();
223
- set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
290
+ set({
291
+ phase: "running",
292
+ currentStepIndex: index,
293
+ output: "",
294
+ notices: [],
295
+ cliOutput: []
296
+ });
224
297
  },
225
298
  setUser: (user) => set({ user }),
226
299
  appendToken: (text) => set((s) => ({ output: s.output + text })),
@@ -261,6 +334,16 @@ var useWizard = create((set, get) => ({
261
334
  get()._clearNoticeQueue();
262
335
  set({ notices: [] });
263
336
  },
337
+ // Unthrottled, unlike `pushNotice`: these lines arrive at whatever rate the
338
+ // subprocess emits them, and holding them back would land output after the
339
+ // command it belongs to has already exited.
340
+ pushCliOutput: (stream, text) => set((s) => ({
341
+ cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
342
+ -CLI_OUTPUT_LIMIT
343
+ )
344
+ })),
345
+ clearCliOutput: () => set({ cliOutput: [] }),
346
+ setTargetIndex: (index) => set({ targetIndex: index }),
264
347
  logStart: (kind, name, input) => {
265
348
  const id = nanoid();
266
349
  set((s) => ({
@@ -305,6 +388,8 @@ var useWizard = create((set, get) => ({
305
388
  currentStepIndex: 0,
306
389
  output: "",
307
390
  notices: [],
391
+ cliOutput: [],
392
+ targetIndex: null,
308
393
  logs: [],
309
394
  error: null,
310
395
  inputReq: null,
@@ -313,16 +398,100 @@ var useWizard = create((set, get) => ({
313
398
  }
314
399
  }));
315
400
 
401
+ // src/ui/CliOutput.tsx
402
+ import { Box, Text, useWindowSize } from "ink";
403
+
404
+ // src/ui/theme.ts
405
+ var MARKER = {
406
+ pending: "\u25CB",
407
+ running: "\u25D0",
408
+ done: "\u2713",
409
+ error: "\u2716"
410
+ };
411
+ var BRAND = "#003DFF";
412
+ var SECONDARY = "#5468FF";
413
+ var DANGER = "#F86E7E";
414
+ var COLORS = {
415
+ brand: BRAND,
416
+ primary: "#E6EDF3",
417
+ secondary: SECONDARY,
418
+ strong: "#FFFFFF",
419
+ muted: "#8B949E",
420
+ dim: "#484F58",
421
+ highlight: { bg: "#12331C", fg: "#4ADE80" },
422
+ badge: "#E3B341",
423
+ danger: DANGER,
424
+ success: "#4ADE80",
425
+ bg: {
426
+ main: "#0B0E14",
427
+ sidebar: "#14171E"
428
+ },
429
+ border: "#30363D",
430
+ accent: "#76A0FF",
431
+ status: {
432
+ pending: "gray",
433
+ running: "#76A0FF",
434
+ done: "#4ADE80",
435
+ error: DANGER
436
+ }
437
+ };
438
+
439
+ // src/ui/CliOutput.tsx
440
+ import { jsxs } from "react/jsx-runtime";
441
+ var CLI_MARKER = "\u203A";
442
+ var RESERVED_ROWS = 16;
443
+ var MAX_ROWS = 12;
444
+ var PANEL_TEXT_WIDTH = 45;
445
+ var URL_PATTERN = /https?:\/\//;
446
+ function rowCost(text) {
447
+ return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
448
+ }
449
+ function CliOutput() {
450
+ const cliOutput = useWizard((s) => s.cliOutput);
451
+ const { rows } = useWindowSize();
452
+ if (!cliOutput.length) return null;
453
+ const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
454
+ const visible = [];
455
+ let usedRows = 0;
456
+ for (let i = cliOutput.length - 1; i >= 0; i--) {
457
+ const cost = rowCost(cliOutput[i].text);
458
+ if (usedRows + cost > rowBudget && visible.length > 0) break;
459
+ visible.unshift(cliOutput[i]);
460
+ usedRows += cost;
461
+ }
462
+ const hidden = cliOutput.length - visible.length;
463
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
464
+ hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
465
+ "\u2191 ",
466
+ hidden,
467
+ " earlier line(s)"
468
+ ] }),
469
+ visible.map((line) => /* @__PURE__ */ jsxs(
470
+ Text,
471
+ {
472
+ color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
473
+ wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
474
+ children: [
475
+ CLI_MARKER,
476
+ " ",
477
+ line.text
478
+ ]
479
+ },
480
+ line.id
481
+ ))
482
+ ] });
483
+ }
484
+
316
485
  // src/ui/Notices.tsx
317
- import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
486
+ import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
318
487
  import { useEffect as useEffect2, useState as useState2 } from "react";
319
488
 
320
489
  // src/ui/Table.tsx
321
- import { Box, Text, measureElement, useWindowSize } from "ink";
490
+ import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
322
491
  import { useEffect, useRef, useState } from "react";
323
492
  import { jsx } from "react/jsx-runtime";
324
493
  function Table({ columns, rows }) {
325
- const { columns: termCols } = useWindowSize();
494
+ const { columns: termCols } = useWindowSize2();
326
495
  const ref = useRef(null);
327
496
  const [width, setWidth] = useState(0);
328
497
  useEffect(() => {
@@ -330,7 +499,7 @@ function Table({ columns, rows }) {
330
499
  }, [termCols, columns, rows]);
331
500
  if (rows.length === 0) return null;
332
501
  const lines = formatTable(columns, rows, width || void 0);
333
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: line }, `tbl-${i}`)) });
502
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
334
503
  }
335
504
  function formatTable(columns, rows, width) {
336
505
  const natural = columns.map(
@@ -370,48 +539,13 @@ function resize(widths, budget) {
370
539
  }
371
540
  var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
372
541
 
373
- // src/ui/theme.ts
374
- var MARKER = {
375
- pending: "\u25CB",
376
- running: "\u25D0",
377
- done: "\u2713",
378
- error: "\u2716"
379
- };
380
- var BRAND = "#003DFF";
381
- var SECONDARY = "#5468FF";
382
- var DANGER = "#F86E7E";
383
- var COLORS = {
384
- brand: BRAND,
385
- primary: "#E6EDF3",
386
- secondary: SECONDARY,
387
- strong: "#FFFFFF",
388
- muted: "#8B949E",
389
- dim: "#484F58",
390
- highlight: { bg: "#12331C", fg: "#4ADE80" },
391
- badge: "#E3B341",
392
- danger: DANGER,
393
- success: "#4ADE80",
394
- bg: {
395
- main: "#0B0E14",
396
- sidebar: "#14171E"
397
- },
398
- border: "#30363D",
399
- accent: "#76A0FF",
400
- status: {
401
- pending: "gray",
402
- running: "#76A0FF",
403
- done: "#4ADE80",
404
- error: DANGER
405
- }
406
- };
407
-
408
542
  // src/ui/Notices.tsx
409
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
543
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
410
544
  var AGENT_MARKER = "\u2726";
411
- var RESERVED_ROWS = 14;
412
- var PANEL_TEXT_WIDTH = 45;
545
+ var RESERVED_ROWS2 = 14;
546
+ var PANEL_TEXT_WIDTH2 = 45;
413
547
  function messageLineCount(text) {
414
- return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
548
+ return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
415
549
  }
416
550
  function noticeLineCount(notice) {
417
551
  const messageLines = (notice.messages ?? []).reduce((sum, m) => {
@@ -422,7 +556,7 @@ function noticeLineCount(notice) {
422
556
  return messageLines + tableLines;
423
557
  }
424
558
  function fitVisibleNotices(notices, windowRows) {
425
- const budget = Math.max(windowRows - RESERVED_ROWS, 3);
559
+ const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
426
560
  let used = 0;
427
561
  let count = 0;
428
562
  for (let i = notices.length - 1; i >= 0; i--) {
@@ -455,7 +589,7 @@ function parseHex(hex) {
455
589
  }
456
590
  function Notices() {
457
591
  const notices = useWizard((s) => s.notices);
458
- const { rows: windowRows } = useWindowSize2();
592
+ const { rows: windowRows } = useWindowSize3();
459
593
  const visible = fitVisibleNotices(notices, windowRows);
460
594
  const [pulseStep, setPulseStep] = useState2(0);
461
595
  useEffect2(() => {
@@ -472,14 +606,14 @@ function Notices() {
472
606
  }, []);
473
607
  if (!visible.length) return null;
474
608
  const pulseColor = PULSE_COLORS[pulseStep];
475
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
609
+ return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
476
610
  const isLatest = i === visible.length - 1;
477
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
611
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
478
612
  notice.messages?.map((m, j) => {
479
613
  const line = typeof m === "string" ? { text: m } : m;
480
614
  const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
481
- return /* @__PURE__ */ jsxs(
482
- Text2,
615
+ return /* @__PURE__ */ jsxs2(
616
+ Text3,
483
617
  {
484
618
  color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
485
619
  bold: line.bold,
@@ -497,41 +631,43 @@ function Notices() {
497
631
  }
498
632
 
499
633
  // src/ui/PromptInput.tsx
500
- import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
634
+ import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
501
635
  import TextInput from "ink-text-input";
502
636
  import { useState as useState4 } from "react";
503
637
 
504
638
  // src/ui/NextAction.tsx
505
- import { Box as Box3, Text as Text3 } from "ink";
506
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
639
+ import { Box as Box4, Text as Text4 } from "ink";
640
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
507
641
  function NextAction({
508
642
  action,
509
643
  keyHint,
510
644
  hierarchy = "primary"
511
645
  }) {
512
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "row", gap: 1, children: [
513
- hierarchy === "primary" && /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: `> ${action}` }),
514
- hierarchy === "secondary" && /* @__PURE__ */ jsxs2(Fragment, { children: [
515
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: `>` }),
516
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.primary, bold: true, children: action })
646
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
647
+ hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
648
+ hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
649
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
650
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
517
651
  ] }),
518
- /* @__PURE__ */ jsxs2(Box3, { children: [
519
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: "press " }),
520
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: `[` }),
521
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.primary, children: keyHint }),
522
- /* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: `]` })
652
+ /* @__PURE__ */ jsxs3(Box4, { children: [
653
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
654
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
655
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
656
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
523
657
  ] })
524
658
  ] });
525
659
  }
526
660
 
527
661
  // src/ui/SelectPrompt.tsx
528
- import { Box as Box4, Text as Text4, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
662
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useInput, useWindowSize as useWindowSize4 } from "ink";
529
663
  import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
530
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
664
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
531
665
  var CANCEL = "cancel";
532
666
  var ARROW_WIDTH = 4;
533
667
  var COLUMN_GAP = 2;
534
668
  var BAR_PADDING = 2;
669
+ var ROW_HEIGHT = 3;
670
+ var INDICATOR_ROWS = 2;
535
671
  function fittedWidth(node, columns) {
536
672
  let left = 0;
537
673
  for (let n = node; n; n = n.parentNode) {
@@ -564,13 +700,18 @@ function SelectPrompt({
564
700
  if (multi) hints.push({ key: "[space]", label: "select" });
565
701
  hints.push({ key: "[enter]", label: "confirm" });
566
702
  const containerRef = useRef2(null);
567
- const { columns } = useWindowSize3();
703
+ const viewportRef = useRef2(null);
704
+ const { columns, rows: windowRows } = useWindowSize4();
568
705
  const [width, setWidth] = useState3(columns);
706
+ const [viewportHeight, setViewportHeight] = useState3(null);
569
707
  useLayoutEffect(() => {
570
708
  if (containerRef.current) {
571
709
  setWidth(fittedWidth(containerRef.current, columns));
572
710
  }
573
- }, [columns]);
711
+ if (viewportRef.current) {
712
+ setViewportHeight(measureElement2(viewportRef.current).height);
713
+ }
714
+ }, [columns, windowRows, error, question, helpText, messages, table]);
574
715
  const inner = Math.max(width - BAR_PADDING, 0);
575
716
  const labelWidth = Math.min(
576
717
  ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
@@ -586,6 +727,22 @@ function SelectPrompt({
586
727
  const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
587
728
  const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
588
729
  const textWidth = inner - labelWidth;
730
+ const capacity = viewportHeight === null || rows.length * ROW_HEIGHT <= viewportHeight ? rows.length : Math.max(Math.floor((viewportHeight - INDICATOR_ROWS) / ROW_HEIGHT), 1);
731
+ const maxOffset = Math.max(rows.length - capacity, 0);
732
+ const [offset, setOffset] = useState3(0);
733
+ useLayoutEffect(() => {
734
+ setOffset((o) => {
735
+ const clamped = Math.min(o, maxOffset);
736
+ if (index < clamped) return index;
737
+ if (index >= clamped + capacity) {
738
+ return Math.min(index - capacity + 1, maxOffset);
739
+ }
740
+ return clamped;
741
+ });
742
+ }, [index, capacity, maxOffset]);
743
+ const visible = rows.slice(offset, offset + capacity);
744
+ const hiddenAbove = offset;
745
+ const hiddenBelow = rows.length - offset - visible.length;
589
746
  useInput((input, key) => {
590
747
  if (rows.length === 0) return;
591
748
  if (key.upArrow || input === "k") {
@@ -609,62 +766,77 @@ function SelectPrompt({
609
766
  }
610
767
  }
611
768
  });
612
- return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
613
- error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
614
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
615
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
616
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
617
- question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
618
- helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
769
+ return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
770
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
771
+ error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
772
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
773
+ table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
774
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
775
+ question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
776
+ helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
777
+ ] })
619
778
  ] }),
620
- /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
621
- const highlighted = i === index;
622
- const isCancel = i === cancelIndex;
623
- const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
624
- const sec = isCancel ? void 0 : secondary?.[i];
625
- const labelColor = highlighted ? COLORS.highlight.fg : void 0;
626
- const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
627
- highlighted ? "\u276F " : " ",
628
- bullet,
629
- option
630
- ] });
631
- const isText = sec?.kind === "text";
632
- return /* @__PURE__ */ jsxs3(
633
- Box4,
634
- {
635
- width: isText ? "100%" : barWidth,
636
- paddingX: 1,
637
- paddingY: 1,
638
- backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
639
- children: [
640
- /* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
641
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
642
- Text4,
643
- {
644
- wrap: "truncate",
645
- color: highlighted ? COLORS.primary : COLORS.muted,
646
- children: sec.value
647
- }
648
- ) }),
649
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
650
- ]
651
- },
652
- `row-${i}`
653
- );
654
- }) }),
655
- /* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
779
+ /* @__PURE__ */ jsxs4(Box5, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
780
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
781
+ "\u2191 ",
782
+ hiddenAbove,
783
+ " more"
784
+ ] }),
785
+ visible.map((option, visibleIndex) => {
786
+ const i = offset + visibleIndex;
787
+ const highlighted = i === index;
788
+ const isCancel = i === cancelIndex;
789
+ const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
790
+ const sec = isCancel ? void 0 : secondary?.[i];
791
+ const labelColor = highlighted ? COLORS.highlight.fg : void 0;
792
+ const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
793
+ highlighted ? "\u276F " : " ",
794
+ bullet,
795
+ option
796
+ ] });
797
+ const isText = sec?.kind === "text";
798
+ return /* @__PURE__ */ jsxs4(
799
+ Box5,
800
+ {
801
+ width: isText ? "100%" : barWidth,
802
+ paddingX: 1,
803
+ paddingY: 1,
804
+ backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
805
+ children: [
806
+ /* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
807
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
808
+ Text5,
809
+ {
810
+ wrap: "truncate",
811
+ color: highlighted ? COLORS.primary : COLORS.muted,
812
+ children: sec.value
813
+ }
814
+ ) }),
815
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
816
+ ]
817
+ },
818
+ `row-${i}`
819
+ );
820
+ }),
821
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
822
+ "\u2193 ",
823
+ hiddenBelow,
824
+ " more"
825
+ ] })
826
+ ] }),
827
+ /* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
656
828
  i > 0 ? " " : "",
657
- /* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
658
- /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
829
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
830
+ /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
659
831
  " ",
660
832
  label
661
833
  ] })
662
- ] }, label)) })
834
+ ] }, label)) }) })
663
835
  ] }) });
664
836
  }
665
837
 
666
838
  // src/ui/PromptInput.tsx
667
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
839
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
668
840
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
669
841
  function EnterToContinuePrompt({
670
842
  question,
@@ -675,10 +847,10 @@ function EnterToContinuePrompt({
675
847
  if (key.return) onDecide(true);
676
848
  else if (key.escape) onDecide(false);
677
849
  });
678
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
679
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
680
- question && /* @__PURE__ */ jsx5(Text5, { color: COLORS.primary, children: question }),
681
- /* @__PURE__ */ jsxs4(Box5, { gap: 1, flexDirection: "column", children: [
850
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
851
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
852
+ question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
853
+ /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
682
854
  /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
683
855
  /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
684
856
  ] })
@@ -688,11 +860,11 @@ function PromptInput() {
688
860
  const { phase, inputReq, submitInput } = useWizard();
689
861
  const [draft, setDraft] = useState4("");
690
862
  if (phase === "done" || phase === "error") {
691
- return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
863
+ return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
692
864
  }
693
865
  if (phase !== "awaitingInput" || !inputReq) return null;
694
866
  if (inputReq.promptType === "multipleChoice") {
695
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
867
+ return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
696
868
  SelectPrompt,
697
869
  {
698
870
  question: inputReq.prompt,
@@ -709,7 +881,7 @@ function PromptInput() {
709
881
  ) });
710
882
  }
711
883
  if (inputReq.promptType === "multiSelect") {
712
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
884
+ return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
713
885
  SelectPrompt,
714
886
  {
715
887
  multi: true,
@@ -724,7 +896,7 @@ function PromptInput() {
724
896
  ) });
725
897
  }
726
898
  if (inputReq.promptType === "notice") {
727
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
899
+ return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
728
900
  SelectPrompt,
729
901
  {
730
902
  question: inputReq.prompt,
@@ -746,7 +918,7 @@ function PromptInput() {
746
918
  }
747
919
  if (inputReq.promptType === "acceptReject") {
748
920
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
749
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
921
+ return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
750
922
  SelectPrompt,
751
923
  {
752
924
  question: inputReq.prompt,
@@ -757,11 +929,11 @@ function PromptInput() {
757
929
  }
758
930
  ) });
759
931
  }
760
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
761
- inputReq.error && /* @__PURE__ */ jsx5(Text5, { color: COLORS.danger, children: inputReq.error }),
762
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
763
- /* @__PURE__ */ jsxs4(Box5, { children: [
764
- /* @__PURE__ */ jsxs4(Text5, { color: COLORS.primary, children: [
932
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
933
+ inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
934
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
935
+ /* @__PURE__ */ jsxs5(Box6, { children: [
936
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
765
937
  inputReq.prompt,
766
938
  " "
767
939
  ] }),
@@ -783,7 +955,7 @@ function PromptInput() {
783
955
  // src/ui/Welcome.tsx
784
956
  import { dirname as dirname2, join as join3 } from "node:path";
785
957
  import { fileURLToPath } from "node:url";
786
- import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
958
+ import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
787
959
 
788
960
  // src/ui/copy/welcome.ts
789
961
  var sidebarItems = [
@@ -811,27 +983,27 @@ var sidebarItems = [
811
983
 
812
984
  // src/ui/Welcome.tsx
813
985
  import Image, { InkPictureProvider } from "ink-picture";
814
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
986
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
815
987
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
816
988
  function SidebarItem({
817
989
  title,
818
990
  description
819
991
  }) {
820
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
821
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
822
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.success, children: "\u2192" }),
823
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.strong, bold: true, children: title })
992
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
993
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
994
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
995
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
824
996
  ] }),
825
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
997
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
826
998
  /* @__PURE__ */ jsx6(Spacer, {}),
827
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
999
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
828
1000
  ] })
829
1001
  ] });
830
1002
  }
831
1003
  function Welcome() {
832
1004
  const confirmStart = useWizard((s) => s.confirmStart);
833
1005
  const openLearnMore = useWizard((s) => s.openLearnMore);
834
- const { rows } = useWindowSize4();
1006
+ const { rows } = useWindowSize5();
835
1007
  useInput3((input, key) => {
836
1008
  if (key.return) confirmStart();
837
1009
  else if (input === "i") openLearnMore();
@@ -850,15 +1022,15 @@ function Welcome() {
850
1022
  if (rows < 30) {
851
1023
  layout = scales["small"];
852
1024
  }
853
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1025
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
854
1026
  /* @__PURE__ */ jsx6(
855
- Box6,
1027
+ Box7,
856
1028
  {
857
1029
  paddingY: layout.main.padding.y,
858
1030
  paddingX: layout.main.padding.x,
859
1031
  flexDirection: "column",
860
1032
  justifyContent: "center",
861
- children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
1033
+ children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
862
1034
  /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
863
1035
  Image,
864
1036
  {
@@ -870,16 +1042,16 @@ function Welcome() {
870
1042
  protocol: "halfBlock"
871
1043
  }
872
1044
  ) }),
873
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
874
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
1045
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1046
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
875
1047
  /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
876
1048
  /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
877
1049
  ] })
878
1050
  ] })
879
1051
  }
880
1052
  ),
881
- /* @__PURE__ */ jsxs5(
882
- Box6,
1053
+ /* @__PURE__ */ jsxs6(
1054
+ Box7,
883
1055
  {
884
1056
  backgroundColor: COLORS.bg.sidebar,
885
1057
  width: 40,
@@ -889,7 +1061,7 @@ function Welcome() {
889
1061
  flexDirection: "column",
890
1062
  justifyContent: "center",
891
1063
  children: [
892
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1064
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
893
1065
  sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
894
1066
  ]
895
1067
  }
@@ -899,7 +1071,7 @@ function Welcome() {
899
1071
 
900
1072
  // src/ui/LearnMore.tsx
901
1073
  import { Fragment as Fragment2 } from "react";
902
- import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
1074
+ import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
903
1075
 
904
1076
  // src/ui/copy/learn-more.ts
905
1077
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -936,7 +1108,7 @@ var policyLinks = [
936
1108
  ];
937
1109
 
938
1110
  // src/ui/LearnMore.tsx
939
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1111
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
940
1112
  var TAG_COLORS = {
941
1113
  READ: COLORS.success,
942
1114
  WRITE: COLORS.badge,
@@ -952,25 +1124,25 @@ function NeverLine({
952
1124
  }) {
953
1125
  const used = segments.reduce((n, s) => n + s.text.length, 0);
954
1126
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
955
- return /* @__PURE__ */ jsxs6(Text7, { children: [
956
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" }),
1127
+ return /* @__PURE__ */ jsxs7(Text8, { children: [
1128
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
957
1129
  " ".repeat(NEVER_BOX_PAD_X),
958
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
1130
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
959
1131
  " ".repeat(rightPad),
960
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" })
1132
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
961
1133
  ] });
962
1134
  }
963
1135
  function LearnMore() {
964
1136
  const confirmStart = useWizard((s) => s.confirmStart);
965
1137
  const backToHome = useWizard((s) => s.backToHome);
966
- const { columns } = useWindowSize5();
1138
+ const { columns } = useWindowSize6();
967
1139
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
968
1140
  useInput4((_input, key) => {
969
1141
  if (key.escape) backToHome();
970
1142
  else if (key.return) confirmStart();
971
1143
  });
972
- return /* @__PURE__ */ jsxs6(
973
- Box7,
1144
+ return /* @__PURE__ */ jsxs7(
1145
+ Box8,
974
1146
  {
975
1147
  flexDirection: "column",
976
1148
  paddingX: PADDING_X,
@@ -978,20 +1150,20 @@ function LearnMore() {
978
1150
  width: "100%",
979
1151
  gap: 1,
980
1152
  children: [
981
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
982
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: accessIntro }),
983
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
984
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
985
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, marginTop: 1, children: [
986
- /* @__PURE__ */ jsx7(Box7, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text7, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
987
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Text7, { children: [
988
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: item.title }),
989
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1153
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1154
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
1155
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
1156
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1157
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1158
+ /* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1159
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
1160
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
1161
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
990
1162
  ] }) })
991
1163
  ] })
992
1164
  ] }, item.tag)) }),
993
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
994
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1165
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1166
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
995
1167
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
996
1168
  /* @__PURE__ */ jsx7(
997
1169
  NeverLine,
@@ -1000,7 +1172,7 @@ function LearnMore() {
1000
1172
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1001
1173
  }
1002
1174
  ),
1003
- neverItems.map((item) => /* @__PURE__ */ jsxs6(Fragment2, { children: [
1175
+ neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
1004
1176
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1005
1177
  /* @__PURE__ */ jsx7(
1006
1178
  NeverLine,
@@ -1015,23 +1187,23 @@ function LearnMore() {
1015
1187
  )
1016
1188
  ] }, item)),
1017
1189
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1018
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1190
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1019
1191
  ] }),
1020
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1021
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1022
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.accent, children: link.url })
1192
+ /* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1193
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1194
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
1023
1195
  ] }, link.label)) }),
1024
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1025
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1026
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1027
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "esc" }),
1028
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "] back" })
1196
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1197
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1198
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1199
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
1200
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
1029
1201
  ] }),
1030
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1031
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1032
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "enter" }),
1033
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "]" }),
1034
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.success, bold: true, children: "start wizard" })
1202
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1203
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1204
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "enter" }),
1205
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
1206
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
1035
1207
  ] })
1036
1208
  ] })
1037
1209
  ]
@@ -1040,10 +1212,10 @@ function LearnMore() {
1040
1212
  }
1041
1213
 
1042
1214
  // src/ui/Sidebar.tsx
1043
- import { Box as Box10, Text as Text10 } from "ink";
1215
+ import { Box as Box11, Text as Text11 } from "ink";
1044
1216
 
1045
1217
  // src/ui/Steps.tsx
1046
- import { Box as Box8, Text as Text8 } from "ink";
1218
+ import { Box as Box9, Text as Text9 } from "ink";
1047
1219
  import Spinner from "ink-spinner";
1048
1220
 
1049
1221
  // src/core/persistence.ts
@@ -1072,11 +1244,11 @@ async function clearWorkflowState(workflowId) {
1072
1244
  }
1073
1245
 
1074
1246
  // src/ui/Steps.tsx
1075
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1247
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1076
1248
  function Steps() {
1077
1249
  const { steps } = useWizard();
1078
1250
  const visibleSteps = steps.filter(isStepVisible);
1079
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status[s.status], children: [
1251
+ return /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status[s.status], children: [
1080
1252
  s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1081
1253
  " ",
1082
1254
  s.title
@@ -1086,7 +1258,7 @@ function CurrentStep() {
1086
1258
  const { steps } = useWizard();
1087
1259
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1088
1260
  if (!currentStep) return null;
1089
- return /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
1261
+ return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
1090
1262
  /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1091
1263
  " ",
1092
1264
  ` ${currentStep.title}`
@@ -1094,19 +1266,19 @@ function CurrentStep() {
1094
1266
  }
1095
1267
 
1096
1268
  // src/ui/Progress.tsx
1097
- import { Box as Box9, Text as Text9 } from "ink";
1098
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1269
+ import { Box as Box10, Text as Text10 } from "ink";
1270
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1099
1271
  function Progress() {
1100
1272
  const { steps, currentStepIndex } = useWizard();
1101
1273
  const visibleSteps = steps.filter(isStepVisible);
1102
1274
  if (visibleSteps.length === 0) return null;
1103
1275
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1104
1276
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1105
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1106
- /* @__PURE__ */ jsx9(Text9, { color: COLORS.muted, children: "STEP" }),
1107
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: activeStepNumber }),
1108
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "/" }),
1109
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: visibleSteps.length })
1277
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1278
+ /* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
1279
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
1280
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
1281
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
1110
1282
  ] });
1111
1283
  }
1112
1284
 
@@ -1117,10 +1289,10 @@ var sidebarCommands = [
1117
1289
  ];
1118
1290
 
1119
1291
  // src/ui/Sidebar.tsx
1120
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1292
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1121
1293
  function Sidebar() {
1122
- return /* @__PURE__ */ jsxs9(
1123
- Box10,
1294
+ return /* @__PURE__ */ jsxs10(
1295
+ Box11,
1124
1296
  {
1125
1297
  backgroundColor: "#14171E",
1126
1298
  width: 30,
@@ -1129,16 +1301,16 @@ function Sidebar() {
1129
1301
  flexDirection: "column",
1130
1302
  justifyContent: "space-between",
1131
1303
  children: [
1132
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1133
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
1304
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1305
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
1134
1306
  /* @__PURE__ */ jsx10(Steps, {})
1135
1307
  ] }),
1136
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1308
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1137
1309
  /* @__PURE__ */ jsx10(Progress, {}),
1138
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: sidebarCommands.map((c) => {
1139
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1140
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1141
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: c.description })
1310
+ /* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
1311
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1312
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1313
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
1142
1314
  ] });
1143
1315
  }) })
1144
1316
  ] })
@@ -1148,12 +1320,12 @@ function Sidebar() {
1148
1320
  }
1149
1321
 
1150
1322
  // src/ui/Ribbon.tsx
1151
- import { Box as Box11, Text as Text11 } from "ink";
1152
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1323
+ import { Box as Box12, Text as Text12 } from "ink";
1324
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1153
1325
  function Ribbon() {
1154
1326
  const firstCommand = sidebarCommands[0];
1155
- return /* @__PURE__ */ jsxs10(
1156
- Box11,
1327
+ return /* @__PURE__ */ jsxs11(
1328
+ Box12,
1157
1329
  {
1158
1330
  backgroundColor: "#14171E",
1159
1331
  flexDirection: "row",
@@ -1163,9 +1335,9 @@ function Ribbon() {
1163
1335
  children: [
1164
1336
  /* @__PURE__ */ jsx11(Progress, {}),
1165
1337
  /* @__PURE__ */ jsx11(CurrentStep, {}),
1166
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1167
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1168
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.muted, children: firstCommand.description })
1338
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1339
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1340
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
1169
1341
  ] })
1170
1342
  ]
1171
1343
  }
@@ -1177,8 +1349,8 @@ import { useState as useState6 } from "react";
1177
1349
 
1178
1350
  // src/ui/Logs.tsx
1179
1351
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
1180
- import { Box as Box12, Text as Text12, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
1181
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1352
+ import { Box as Box13, Text as Text13, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1353
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1182
1354
  var KIND_COLOR = {
1183
1355
  tool: COLORS.primary,
1184
1356
  prompt: COLORS.badge
@@ -1208,7 +1380,7 @@ function formatTimestamp(ms) {
1208
1380
  }
1209
1381
  function Logs() {
1210
1382
  const logs = useWizard((s) => s.logs);
1211
- const { rows, columns } = useWindowSize6();
1383
+ const { rows, columns } = useWindowSize7();
1212
1384
  const viewportRef = useRef3(null);
1213
1385
  const [viewportHeight, setViewportHeight] = useState5(0);
1214
1386
  const [viewportWidth, setViewportWidth] = useState5(0);
@@ -1245,10 +1417,10 @@ function Logs() {
1245
1417
  const visible = logs.slice(scrollOffset, scrollOffset + capacity);
1246
1418
  const hiddenAbove = scrollOffset;
1247
1419
  const hiddenBelow = logs.length - scrollOffset - visible.length;
1248
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1249
- logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
1250
- /* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1251
- hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1420
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1421
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
1422
+ /* @__PURE__ */ jsxs12(Box13, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1423
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1252
1424
  "\u2191 ",
1253
1425
  hiddenAbove,
1254
1426
  " more"
@@ -1263,20 +1435,20 @@ function Logs() {
1263
1435
  const name = truncate2(entry.name, budget);
1264
1436
  budget -= name.length;
1265
1437
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1266
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
1267
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
1268
- /* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
1269
- preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
1270
- durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
1438
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
1439
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
1440
+ /* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
1441
+ preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
1442
+ durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
1271
1443
  ] }, entry.id);
1272
1444
  }),
1273
- hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1445
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1274
1446
  "\u2193 ",
1275
1447
  hiddenBelow,
1276
1448
  " more"
1277
1449
  ] })
1278
1450
  ] }),
1279
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1451
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1280
1452
  ] });
1281
1453
  }
1282
1454
 
@@ -1468,11 +1640,11 @@ function track(event, payload) {
1468
1640
  }
1469
1641
 
1470
1642
  // src/ui/App.tsx
1471
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1643
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1472
1644
  function App() {
1473
1645
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1474
1646
  const { exit } = useApp();
1475
- const { columns, rows } = useWindowSize7();
1647
+ const { columns, rows } = useWindowSize8();
1476
1648
  const [showLogs, setShowLogs] = useState6(false);
1477
1649
  const finished = phase === "done" || phase === "error";
1478
1650
  const currentStep = steps[currentStepIndex];
@@ -1485,7 +1657,7 @@ function App() {
1485
1657
  { isActive: finished }
1486
1658
  );
1487
1659
  useInput6((_input, key) => {
1488
- if (phase === "idle" || phase === "preflight") return;
1660
+ if (phase === "idle" || phase === "authenticating") return;
1489
1661
  if (key.tab) {
1490
1662
  setShowLogs(!showLogs);
1491
1663
  track("AI Wizard Interaction", {
@@ -1495,7 +1667,7 @@ function App() {
1495
1667
  });
1496
1668
  }
1497
1669
  });
1498
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1670
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1499
1671
  useInput6((_input, key) => {
1500
1672
  if (escOwnedElsewhere) return;
1501
1673
  if (key.escape) {
@@ -1508,53 +1680,70 @@ function App() {
1508
1680
  exit();
1509
1681
  }
1510
1682
  });
1511
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1683
+ const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1512
1684
  const flexDirection = columns > 90 ? "row" : "column";
1513
1685
  const showSidebar = flexDirection === "row";
1514
- return /* @__PURE__ */ jsxs12(
1515
- Box13,
1516
- {
1517
- backgroundColor: COLORS.bg.main,
1518
- flexDirection: "row",
1519
- width: columns,
1520
- minHeight: rows,
1521
- children: [
1522
- mainWindowVisible && /* @__PURE__ */ jsxs12(
1523
- Box13,
1524
- {
1525
- flexDirection,
1526
- width: "100%",
1527
- justifyContent: "space-between",
1528
- children: [
1529
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1530
- /* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
1531
- /* @__PURE__ */ jsxs12(
1532
- Box13,
1533
- {
1534
- flexDirection: "column",
1535
- paddingX: 4,
1536
- paddingY: 2,
1537
- width: showSidebar ? 70 : "100%",
1538
- flexGrow: showSidebar ? 1 : 0,
1539
- children: [
1540
- /* @__PURE__ */ jsx13(Notices, {}),
1541
- /* @__PURE__ */ jsx13(PromptInput, {}),
1542
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1543
- phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1544
- "\u2716 ",
1545
- error
1546
- ] }) })
1547
- ]
1548
- }
1549
- )
1550
- ),
1551
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1552
- ]
1553
- }
1554
- ),
1555
- (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1556
- ]
1557
- }
1686
+ return (
1687
+ /* Exactly the viewport, clipped — never `minHeight`, which lets the frame
1688
+ grow past the terminal. Ink then abandons diffing to clear and repaint
1689
+ the whole screen, and the scrolling that frame causes throws off its
1690
+ cursor arithmetic: flicker and leftover rows, worst when a burst of CLI
1691
+ output is swapped out. Clipping drops the bottom of an over-tall frame;
1692
+ the per-panel row budgets are what keep it from coming to that. */
1693
+ /* @__PURE__ */ jsxs13(
1694
+ Box14,
1695
+ {
1696
+ backgroundColor: COLORS.bg.main,
1697
+ flexDirection: "row",
1698
+ width: columns,
1699
+ height: rows,
1700
+ overflow: "hidden",
1701
+ children: [
1702
+ mainWindowVisible && /* @__PURE__ */ jsxs13(
1703
+ Box14,
1704
+ {
1705
+ flexDirection,
1706
+ width: "100%",
1707
+ justifyContent: "space-between",
1708
+ children: [
1709
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1710
+ /* Fill the space the sidebar/ribbon leaves — width beside the
1711
+ sidebar, height above the ribbon. The height matters even
1712
+ stacked: it is what the prompt's scrolling list measures itself
1713
+ against (see SelectPrompt). */
1714
+ /* @__PURE__ */ jsxs13(
1715
+ Box14,
1716
+ {
1717
+ flexDirection: "column",
1718
+ paddingX: 4,
1719
+ paddingY: 2,
1720
+ width: showSidebar ? 70 : "100%",
1721
+ flexGrow: 1,
1722
+ children: [
1723
+ phase === "authenticating" && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
1724
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1725
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1726
+ ] }),
1727
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1728
+ /* @__PURE__ */ jsx13(Notices, {}),
1729
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1730
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1731
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
1732
+ "\u2716 ",
1733
+ error
1734
+ ] }) })
1735
+ ]
1736
+ }
1737
+ )
1738
+ ),
1739
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1740
+ ]
1741
+ }
1742
+ ),
1743
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1744
+ ]
1745
+ }
1746
+ )
1558
1747
  );
1559
1748
  }
1560
1749
 
@@ -1790,61 +1979,138 @@ async function runWorkflow(workflow, appId) {
1790
1979
  }
1791
1980
  }
1792
1981
 
1793
- // src/lib/algoliaProfile.ts
1794
- import { readFile as readFile3 } from "node:fs/promises";
1795
- import { createRequire as createRequire2 } from "node:module";
1796
- import { homedir as homedir2 } from "node:os";
1797
- import { join as join6 } from "node:path";
1798
- import { parse as parseToml } from "toml";
1799
- var require3 = createRequire2(import.meta.url);
1800
- function configPath() {
1801
- const base = process.env.XDG_CONFIG_HOME || join6(homedir2(), ".config");
1802
- return join6(base, "algolia", "config.toml");
1803
- }
1804
- function profilesFromConfig(tomlText) {
1805
- let parsed;
1982
+ // src/lib/algoliaApp.ts
1983
+ import { z as z4 } from "zod";
1984
+ var applicationSchema = z4.object({
1985
+ id: z4.string().min(1),
1986
+ name: z4.string().default(""),
1987
+ plan: z4.string().optional()
1988
+ });
1989
+ var listSchema = z4.array(
1990
+ z4.object({
1991
+ id: z4.string().min(1),
1992
+ name: z4.string().default(""),
1993
+ plan_label: z4.string().optional()
1994
+ }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1995
+ );
1996
+ async function currentApplication() {
1997
+ let raw;
1806
1998
  try {
1807
- parsed = parseToml(tomlText);
1999
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1808
2000
  } catch {
1809
- return [];
2001
+ return null;
2002
+ }
2003
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2004
+ return parsed.success ? parsed.data : null;
2005
+ }
2006
+ async function requireApplication() {
2007
+ const app = await currentApplication();
2008
+ if (!app) {
2009
+ throw new Error(
2010
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2011
+ );
2012
+ }
2013
+ return app;
2014
+ }
2015
+ async function listApplications() {
2016
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2017
+ const parsed = listSchema.safeParse(parseJson(raw));
2018
+ if (!parsed.success) {
2019
+ throw new Error("Could not read the list of Algolia applications.");
2020
+ }
2021
+ return parsed.data;
2022
+ }
2023
+ async function selectApplication(id) {
2024
+ const raw = await runAlgoliaCli(
2025
+ ["application", "select", "--non-interactive", "--app-id", id],
2026
+ { onOutput: stderrSink }
2027
+ );
2028
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2029
+ if (!parsed.success) {
2030
+ throw new Error(
2031
+ `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2032
+ );
1810
2033
  }
1811
- const profiles = Object.entries(parsed).filter(
1812
- ([, t]) => typeof t.application_id === "string" && typeof t.api_key === "string"
1813
- ).map(([name, t]) => ({
1814
- name,
1815
- appId: t.application_id,
1816
- apiKey: t.api_key,
1817
- isDefault: t.default === true
1818
- }));
1819
- profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
1820
- return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
1821
- }
1822
- async function loadActiveProfile() {
1823
- let profiles;
2034
+ return parsed.data;
2035
+ }
2036
+ function parseJson(text) {
1824
2037
  try {
1825
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
2038
+ return JSON.parse(text);
1826
2039
  } catch {
1827
- profiles = [];
2040
+ return void 0;
1828
2041
  }
1829
- const profile = profiles[0];
1830
- if (!profile) {
2042
+ }
2043
+
2044
+ // src/lib/algoliaAppPicker.ts
2045
+ function secondaryFor(app) {
2046
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
2047
+ }
2048
+ function labelFor(app) {
2049
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2050
+ }
2051
+ function selectAndReport(app) {
2052
+ useWizard.getState().pushCliOutput(
2053
+ "stdout",
2054
+ `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
2055
+ );
2056
+ return selectApplication(app.id);
2057
+ }
2058
+ async function promptForApplication() {
2059
+ const store = useWizard.getState();
2060
+ const apps = await listApplications();
2061
+ if (apps.length === 0) {
1831
2062
  throw new Error(
1832
- "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
2063
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2064
+ );
2065
+ }
2066
+ if (apps.length === 1) {
2067
+ const only = apps[0];
2068
+ logger.info(
2069
+ { app: only.id },
2070
+ "single application on the account; selecting it"
1833
2071
  );
2072
+ return selectAndReport(only);
2073
+ }
2074
+ const messages = ["Which Algolia application should the wizard work in?"];
2075
+ for (; ; ) {
2076
+ const choice = await store.requestUserInput({
2077
+ prompt: "Select an application",
2078
+ promptType: "multipleChoice",
2079
+ options: apps.map(labelFor),
2080
+ secondary: apps.map(secondaryFor),
2081
+ messages
2082
+ });
2083
+ const chosen = apps.find((app) => labelFor(app) === choice);
2084
+ if (!chosen) {
2085
+ throw new Error("Application picker received an unexpected selection");
2086
+ }
2087
+ try {
2088
+ return await selectAndReport(chosen);
2089
+ } catch (err) {
2090
+ logger.warn(
2091
+ { app: chosen.id, err: err.message },
2092
+ "application select failed; re-prompting"
2093
+ );
2094
+ messages.push(
2095
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2096
+ );
2097
+ }
1834
2098
  }
1835
- return profile;
2099
+ }
2100
+ async function ensureApplication() {
2101
+ return await currentApplication() ?? await promptForApplication();
1836
2102
  }
1837
2103
 
1838
2104
  // src/workflows/default.ts
1839
- import { z as z25 } from "zod";
2105
+ import { z as z27 } from "zod";
1840
2106
 
1841
2107
  // src/actions/listIndices.ts
1842
- import { z as z3 } from "zod";
1843
- var indicesListSchema = z3.object({
1844
- items: z3.array(
1845
- z3.object({
1846
- name: z3.string(),
1847
- entries: z3.number().default(0)
2108
+ import { z as z5 } from "zod";
2109
+ var indicesListSchema = z5.object({
2110
+ items: z5.array(
2111
+ z5.object({
2112
+ name: z5.string(),
2113
+ entries: z5.number().default(0)
1848
2114
  })
1849
2115
  )
1850
2116
  });
@@ -1915,12 +2181,12 @@ import "zod";
1915
2181
 
1916
2182
  // src/lib/tools/listFiles.ts
1917
2183
  import { tool } from "ai";
1918
- import z4 from "zod";
2184
+ import z6 from "zod";
1919
2185
  import { readdir } from "node:fs/promises";
1920
2186
 
1921
2187
  // src/lib/tools/path.ts
1922
2188
  import { lstat } from "node:fs/promises";
1923
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2189
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1924
2190
  function resolveInRoot(ctx, path) {
1925
2191
  const target = resolve2(ctx.cwd, path);
1926
2192
  const rel = relative(ctx.root, target);
@@ -1936,7 +2202,7 @@ async function hasSymlinkParent(ctx, target) {
1936
2202
  let current = ctx.root;
1937
2203
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1938
2204
  for (const part of parts) {
1939
- current = join7(current, part);
2205
+ current = join6(current, part);
1940
2206
  try {
1941
2207
  if ((await lstat(current)).isSymbolicLink()) return true;
1942
2208
  } catch (err) {
@@ -1951,7 +2217,7 @@ async function hasSymlinkParent(ctx, target) {
1951
2217
  function listFilesTool(ctx) {
1952
2218
  return tool({
1953
2219
  description: "List files in the current working directory",
1954
- inputSchema: z4.object(),
2220
+ inputSchema: z6.object(),
1955
2221
  execute: async () => {
1956
2222
  logger.info("called listFiles tool");
1957
2223
  if (++ctx.counts.list > ctx.limits.list) {
@@ -1967,13 +2233,13 @@ function listFilesTool(ctx) {
1967
2233
 
1968
2234
  // src/lib/tools/changeDirectory.ts
1969
2235
  import { tool as tool2 } from "ai";
1970
- import z5 from "zod";
2236
+ import z7 from "zod";
1971
2237
  import { stat } from "node:fs/promises";
1972
2238
  function changeDirectoryTool(ctx) {
1973
2239
  return tool2({
1974
2240
  description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
1975
- inputSchema: z5.object({
1976
- path: z5.string().describe("Directory to change into")
2241
+ inputSchema: z7.object({
2242
+ path: z7.string().describe("Directory to change into")
1977
2243
  }),
1978
2244
  execute: async ({ path }) => {
1979
2245
  logger.info({ path }, "called changeDirectory tool");
@@ -1995,13 +2261,13 @@ function changeDirectoryTool(ctx) {
1995
2261
 
1996
2262
  // src/lib/tools/reportStatus.ts
1997
2263
  import { tool as tool3 } from "ai";
1998
- import z6 from "zod";
2264
+ import z8 from "zod";
1999
2265
  function reportStatusTool(output) {
2000
2266
  return tool3({
2001
2267
  description: "Report the status of your execution. Return a reason in case of failure.",
2002
- inputSchema: z6.object({
2003
- status: z6.enum(["success", "fail"]),
2004
- reason: z6.string().optional(),
2268
+ inputSchema: z8.object({
2269
+ status: z8.enum(["success", "fail"]),
2270
+ reason: z8.string().optional(),
2005
2271
  output
2006
2272
  }),
2007
2273
  execute: async ({ status, reason, output: output2 }) => {
@@ -2013,8 +2279,8 @@ function reportStatusTool(output) {
2013
2279
 
2014
2280
  // src/lib/tools/readFile.ts
2015
2281
  import { tool as tool4 } from "ai";
2016
- import z7 from "zod";
2017
- import { readFile as readFile4 } from "node:fs/promises";
2282
+ import z9 from "zod";
2283
+ import { readFile as readFile3 } from "node:fs/promises";
2018
2284
 
2019
2285
  // src/lib/tools/env.ts
2020
2286
  import { basename } from "node:path";
@@ -2041,8 +2307,8 @@ function redactEnvValues(content) {
2041
2307
  function readFileTool(ctx) {
2042
2308
  return tool4({
2043
2309
  description: "Read the contents of a file at the given path",
2044
- inputSchema: z7.object({
2045
- filePath: z7.string().describe("Path to the file to read")
2310
+ inputSchema: z9.object({
2311
+ filePath: z9.string().describe("Path to the file to read")
2046
2312
  }),
2047
2313
  execute: async ({ filePath }) => {
2048
2314
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2052,7 +2318,7 @@ function readFileTool(ctx) {
2052
2318
  const resolved = resolveInRoot(ctx, filePath);
2053
2319
  if (!resolved.ok) return resolved.error;
2054
2320
  try {
2055
- const content = await readFile4(resolved.target, "utf8");
2321
+ const content = await readFile3(resolved.target, "utf8");
2056
2322
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2057
2323
  } catch (err) {
2058
2324
  return `Error reading ${filePath}: ${err.message}`;
@@ -2063,15 +2329,15 @@ function readFileTool(ctx) {
2063
2329
 
2064
2330
  // src/lib/tools/writeFile.ts
2065
2331
  import { tool as tool5 } from "ai";
2066
- import z8 from "zod";
2332
+ import z10 from "zod";
2067
2333
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2068
2334
  import { dirname as dirname4 } from "node:path";
2069
2335
  function writeFileTool(ctx) {
2070
2336
  return tool5({
2071
2337
  description: "Write content to a file at the given path, overwriting it. To set Algolia credentials in an env file, use writeCredentials instead of this tool.",
2072
- inputSchema: z8.object({
2073
- filePath: z8.string().describe("Path to the file to write"),
2074
- content: z8.string().describe("Content to write to the file")
2338
+ inputSchema: z10.object({
2339
+ filePath: z10.string().describe("Path to the file to write"),
2340
+ content: z10.string().describe("Content to write to the file")
2075
2341
  }),
2076
2342
  execute: async ({ filePath, content }) => {
2077
2343
  logger.info({ filePath }, "called writeFile tool");
@@ -2096,9 +2362,95 @@ function writeFileTool(ctx) {
2096
2362
 
2097
2363
  // src/lib/tools/writeAlgoliaCredentials.ts
2098
2364
  import { tool as tool6 } from "ai";
2099
- import z9 from "zod";
2100
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2365
+ import z12 from "zod";
2366
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2101
2367
  import { dirname as dirname5 } from "node:path";
2368
+
2369
+ // src/lib/algoliaApiKey.ts
2370
+ import { z as z11 } from "zod";
2371
+ var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2372
+ var WRITE_ACLS = [
2373
+ "addObject",
2374
+ "deleteObject",
2375
+ "settings",
2376
+ "editSettings",
2377
+ "listIndexes"
2378
+ ];
2379
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2380
+ var apiKeySchema = z11.object({
2381
+ value: z11.string().min(1),
2382
+ acl: z11.array(z11.string()).default([]),
2383
+ indexes: z11.array(z11.string()).default([])
2384
+ });
2385
+ var apiKeyListSchema = z11.object({
2386
+ items: z11.array(apiKeySchema).optional(),
2387
+ keys: z11.array(apiKeySchema).optional()
2388
+ }).transform((o) => o.items ?? o.keys ?? []);
2389
+ var createdKeySchema = z11.object({
2390
+ key: z11.string().min(1).optional(),
2391
+ value: z11.string().min(1).optional()
2392
+ });
2393
+ function canReuse(key, index) {
2394
+ return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2395
+ }
2396
+ async function createSearchKey(index) {
2397
+ const stdout = await runAlgoliaCli([
2398
+ "apikeys",
2399
+ "create",
2400
+ "--indices",
2401
+ index,
2402
+ "--acl",
2403
+ "search,browse",
2404
+ "--description",
2405
+ `wizard search-only key for ${index}`,
2406
+ "-o",
2407
+ "json"
2408
+ ]);
2409
+ const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2410
+ const created = key ?? value;
2411
+ if (!created) throw new Error("apikeys create returned no key value");
2412
+ return created;
2413
+ }
2414
+ function canReuseForWrites(key, index) {
2415
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2416
+ }
2417
+ async function resolveWriteKey(index) {
2418
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2419
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2420
+ if (existing) {
2421
+ logger.info({ index }, "reusing existing write API key");
2422
+ return existing;
2423
+ }
2424
+ logger.info({ index }, "no reusable write key found; creating one");
2425
+ const created = await runAlgoliaCli([
2426
+ "apikeys",
2427
+ "create",
2428
+ "--indices",
2429
+ index,
2430
+ "--acl",
2431
+ WRITE_ACLS.join(","),
2432
+ "--description",
2433
+ `wizard write key for ${index}`,
2434
+ "-o",
2435
+ "json"
2436
+ ]);
2437
+ const { key, value } = createdKeySchema.parse(JSON.parse(created));
2438
+ const writeKey = key ?? value;
2439
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2440
+ return writeKey;
2441
+ }
2442
+ async function resolveSearchOnlyKey(index) {
2443
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2444
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2445
+ if (existing) {
2446
+ logger.info({ index }, "reusing existing search-only API key");
2447
+ return existing;
2448
+ }
2449
+ logger.info({ index }, "no reusable search-only key found; creating one");
2450
+ return createSearchKey(index);
2451
+ }
2452
+
2453
+ // src/lib/tools/writeAlgoliaCredentials.ts
2102
2454
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2103
2455
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2104
2456
  function appendEnv(content, entries) {
@@ -2112,9 +2464,9 @@ function hasEnv(content, name) {
2112
2464
  }
2113
2465
  function writeCredentialsTool(ctx) {
2114
2466
  return tool6({
2115
- description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials are read from the local Algolia CLI profile; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
2116
- inputSchema: z9.object({
2117
- filePath: z9.string().describe(
2467
+ description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
2468
+ inputSchema: z12.object({
2469
+ filePath: z12.string().describe(
2118
2470
  'Path to the env file to write credentials into (e.g. ".env")'
2119
2471
  )
2120
2472
  }),
@@ -2122,11 +2474,17 @@ function writeCredentialsTool(ctx) {
2122
2474
  logger.info({ filePath }, "called writeCredentials tool");
2123
2475
  const resolved = resolveInRoot(ctx, filePath);
2124
2476
  if (resolved.ok === false) return resolved.error;
2125
- let profile;
2477
+ const targetIndex = useWizard.getState().targetIndex;
2478
+ if (!targetIndex) {
2479
+ return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2480
+ }
2481
+ let appId;
2482
+ let writeKey;
2126
2483
  try {
2127
- profile = await loadActiveProfile();
2128
- } catch {
2129
- return "Error: no Algolia profile is configured, so credentials cannot be written. Ask the user to authenticate with the Algolia CLI first.";
2484
+ appId = (await requireApplication()).id;
2485
+ writeKey = await resolveWriteKey(targetIndex);
2486
+ } catch (err) {
2487
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2130
2488
  }
2131
2489
  try {
2132
2490
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2134,7 +2492,7 @@ function writeCredentialsTool(ctx) {
2134
2492
  }
2135
2493
  let existing = "";
2136
2494
  try {
2137
- existing = await readFile5(resolved.target, "utf8");
2495
+ existing = await readFile4(resolved.target, "utf8");
2138
2496
  } catch (err) {
2139
2497
  if (err.code !== "ENOENT") throw err;
2140
2498
  }
@@ -2145,8 +2503,8 @@ function writeCredentialsTool(ctx) {
2145
2503
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2146
2504
  }
2147
2505
  const envWithCredentials = appendEnv(existing, [
2148
- [APP_ID_VAR, profile.appId],
2149
- [API_KEY_VAR, profile.apiKey]
2506
+ [APP_ID_VAR, appId],
2507
+ [API_KEY_VAR, writeKey]
2150
2508
  ]);
2151
2509
  await mkdir4(dirname5(resolved.target), { recursive: true });
2152
2510
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2160,16 +2518,16 @@ function writeCredentialsTool(ctx) {
2160
2518
 
2161
2519
  // src/lib/tools/searchFiles.ts
2162
2520
  import { tool as tool7 } from "ai";
2163
- import z10 from "zod";
2164
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2165
- import { join as join8 } from "node:path";
2521
+ import z13 from "zod";
2522
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2523
+ import { join as join7 } from "node:path";
2166
2524
  var MAX_QUERY_LENGTH = 1e3;
2167
2525
  async function walkFiles(dir) {
2168
2526
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2169
2527
  const out = [];
2170
2528
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2171
2529
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2172
- const full = join8(dir, e.name);
2530
+ const full = join7(dir, e.name);
2173
2531
  if (e.isDirectory()) out.push(...await walkFiles(full));
2174
2532
  else if (e.isFile()) out.push(full);
2175
2533
  }
@@ -2178,9 +2536,9 @@ async function walkFiles(dir) {
2178
2536
  function searchFilesTool(ctx) {
2179
2537
  return tool7({
2180
2538
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2181
- inputSchema: z10.object({
2182
- query: z10.string().describe("JavaScript RegExp pattern to search for"),
2183
- path: z10.string().optional().describe("Directory to search in (default: cwd)")
2539
+ inputSchema: z13.object({
2540
+ query: z13.string().describe("JavaScript RegExp pattern to search for"),
2541
+ path: z13.string().optional().describe("Directory to search in (default: cwd)")
2184
2542
  }),
2185
2543
  execute: async ({ query, path = "." }) => {
2186
2544
  logger.info({ query, path }, "called searchFiles tool");
@@ -2202,7 +2560,7 @@ function searchFilesTool(ctx) {
2202
2560
  for (const file of await walkFiles(resolved.target)) {
2203
2561
  let content;
2204
2562
  try {
2205
- content = await readFile6(file, "utf8");
2563
+ content = await readFile5(file, "utf8");
2206
2564
  } catch {
2207
2565
  continue;
2208
2566
  }
@@ -2224,7 +2582,7 @@ function searchFilesTool(ctx) {
2224
2582
 
2225
2583
  // src/lib/tools/verifyImplementation.ts
2226
2584
  import { tool as tool8 } from "ai";
2227
- import z11 from "zod";
2585
+ import z14 from "zod";
2228
2586
 
2229
2587
  // src/lib/tools/utils/runCommand.ts
2230
2588
  import { spawn as spawn2 } from "node:child_process";
@@ -2246,9 +2604,9 @@ function runCommand(command, args, cwd) {
2246
2604
  }
2247
2605
 
2248
2606
  // src/lib/tools/utils/packageManager.ts
2249
- import { readFile as readFile7 } from "node:fs/promises";
2607
+ import { readFile as readFile6 } from "node:fs/promises";
2250
2608
  import { existsSync } from "node:fs";
2251
- import { join as join9 } from "node:path";
2609
+ import { join as join8 } from "node:path";
2252
2610
  var LOCKFILES = [
2253
2611
  ["pnpm-lock.yaml", "pnpm"],
2254
2612
  ["yarn.lock", "yarn"],
@@ -2257,13 +2615,13 @@ var LOCKFILES = [
2257
2615
  ["package-lock.json", "npm"]
2258
2616
  ];
2259
2617
  async function readPackageJson(cwd = process.cwd()) {
2260
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2618
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2261
2619
  }
2262
2620
  function packageManagerFrom(pkg) {
2263
2621
  return pkg.packageManager?.split("@")[0] ?? "npm";
2264
2622
  }
2265
2623
  function packageManagerFromLockfile(cwd) {
2266
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2624
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2267
2625
  }
2268
2626
  async function detectPackageManager(cwd) {
2269
2627
  try {
@@ -2304,7 +2662,7 @@ async function runRepoVerificationCheck() {
2304
2662
  function verifyImplementationTool() {
2305
2663
  return tool8({
2306
2664
  description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
2307
- inputSchema: z11.object(),
2665
+ inputSchema: z14.object(),
2308
2666
  execute: async () => {
2309
2667
  logger.info("called verifyImplementation tool");
2310
2668
  return runRepoVerificationCheck();
@@ -2318,7 +2676,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2318
2676
  import { nanoid as nanoid2 } from "nanoid";
2319
2677
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2320
2678
  import { dirname as dirname6 } from "node:path";
2321
- import z12 from "zod";
2679
+ import z15 from "zod";
2322
2680
  var DATA_DIR = ".algolia-wizard/data";
2323
2681
  var RECORD_MODEL = "claude-haiku-4-5";
2324
2682
  var MAX_RECORDS = 100;
@@ -2330,17 +2688,17 @@ var anthropic = createAnthropic({
2330
2688
  function generateRecordTool(ctx) {
2331
2689
  return tool9({
2332
2690
  description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
2333
- inputSchema: z12.object({
2334
- entityName: z12.string().describe("Name of the entity to generate records for."),
2335
- attributes: z12.array(z12.string()).describe("Attribute names each record must contain."),
2336
- count: z12.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2337
- hint: z12.string().optional().describe("Optional context to steer realistic values.")
2691
+ inputSchema: z15.object({
2692
+ entityName: z15.string().describe("Name of the entity to generate records for."),
2693
+ attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2694
+ count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2695
+ hint: z15.string().optional().describe("Optional context to steer realistic values.")
2338
2696
  }),
2339
2697
  execute: async ({ entityName, attributes, count, hint }) => {
2340
2698
  logger.info({ entityName, count }, "called generateRecord tool");
2341
2699
  try {
2342
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2343
- const recordSchema = z12.object(
2700
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2701
+ const recordSchema = z15.object(
2344
2702
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2345
2703
  );
2346
2704
  const generateBatch = async (batchCount) => {
@@ -2350,8 +2708,8 @@ function generateRecordTool(ctx) {
2350
2708
  const { output } = await generateText({
2351
2709
  model: anthropic(RECORD_MODEL),
2352
2710
  output: Output.object({
2353
- schema: z12.object({
2354
- records: z12.array(recordSchema).length(batchCount)
2711
+ schema: z15.object({
2712
+ records: z15.array(recordSchema).length(batchCount)
2355
2713
  })
2356
2714
  }),
2357
2715
  prompt: [
@@ -2409,12 +2767,12 @@ function generateRecordTool(ctx) {
2409
2767
 
2410
2768
  // src/lib/tools/notifyUser.ts
2411
2769
  import { tool as tool10 } from "ai";
2412
- import z13 from "zod";
2770
+ import z16 from "zod";
2413
2771
  function notifyUserTool() {
2414
2772
  return tool10({
2415
2773
  description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
2416
- inputSchema: z13.object({
2417
- message: z13.string().describe(
2774
+ inputSchema: z16.object({
2775
+ message: z16.string().describe(
2418
2776
  "Short, plain-language description of what you are doing now."
2419
2777
  )
2420
2778
  }),
@@ -2594,10 +2952,10 @@ async function runAgent(req) {
2594
2952
  }
2595
2953
 
2596
2954
  // src/actions/detectLanguage.ts
2597
- import z16 from "zod";
2598
- var detectLanguageSchema = z16.object({
2599
- languages: z16.array(z16.object({ name: z16.string(), version: z16.string() })),
2600
- frameworks: z16.array(z16.object({ name: z16.string(), version: z16.string() }))
2955
+ import z19 from "zod";
2956
+ var detectLanguageSchema = z19.object({
2957
+ languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
2958
+ frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
2601
2959
  });
2602
2960
  var detectLanguage = () => runAgent({
2603
2961
  instructions: [
@@ -2615,31 +2973,31 @@ var detectLanguage = () => runAgent({
2615
2973
  });
2616
2974
 
2617
2975
  // src/actions/analyzeCodebase.ts
2618
- import z17 from "zod";
2976
+ import z20 from "zod";
2619
2977
  var READONLY_TOOLS = [
2620
2978
  "listFiles",
2621
2979
  "changeDirectory",
2622
2980
  "readFile",
2623
2981
  "searchFiles"
2624
2982
  ];
2625
- var ingestionAnalysisSchema = z17.object({
2626
- ingestionAnalysis: z17.array(
2627
- z17.object({
2628
- name: z17.string(),
2629
- paths: z17.array(z17.string()),
2983
+ var ingestionAnalysisSchema = z20.object({
2984
+ ingestionAnalysis: z20.array(
2985
+ z20.object({
2986
+ name: z20.string(),
2987
+ paths: z20.array(z20.string()),
2630
2988
  // indexable fields the agent found for this entity
2631
- attributes: z17.array(z17.string())
2989
+ attributes: z20.array(z20.string())
2632
2990
  })
2633
2991
  )
2634
2992
  });
2635
- var searchImplementationAnalysisSchema = z17.object({
2636
- searchImplementationAnalysis: z17.string()
2993
+ var searchImplementationAnalysisSchema = z20.object({
2994
+ searchImplementationAnalysis: z20.string()
2637
2995
  });
2638
- var verificationSchema = z17.object({
2639
- verification: z17.array(z17.string())
2996
+ var verificationSchema = z20.object({
2997
+ verification: z20.array(z20.string())
2640
2998
  });
2641
2999
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
2642
- var analyzeCodebaseSchema = z17.object({
3000
+ var analyzeCodebaseSchema = z20.object({
2643
3001
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2644
3002
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
2645
3003
  verification: verificationSchema.shape.verification.optional(),
@@ -2701,7 +3059,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2701
3059
  // package.json
2702
3060
  var package_default = {
2703
3061
  name: "@algolia/wizard",
2704
- version: "0.7.0",
3062
+ version: "0.8.0-rc.58.43",
2705
3063
  description: "Magically implement Algolia functionality in your codebase",
2706
3064
  type: "module",
2707
3065
  engines: {
@@ -2749,7 +3107,6 @@ var package_default = {
2749
3107
  dependencies: {
2750
3108
  "@ai-sdk/anthropic": "^3.0.81",
2751
3109
  "@ai-sdk/openai-compatible": "^2.0.47",
2752
- "@algolia/cli": "^5.11.0",
2753
3110
  "@hono/node-server": "^2.0.10",
2754
3111
  "@mishieck/ink-titled-box": "^0.4.2",
2755
3112
  "@segment/analytics-node": "^3.1.0",
@@ -2764,7 +3121,6 @@ var package_default = {
2764
3121
  nanoid: "^5.1.15",
2765
3122
  pino: "^10.3.1",
2766
3123
  react: "^19.2.7",
2767
- toml: "^4.1.1",
2768
3124
  varlock: "^1.5.1",
2769
3125
  zod: "^4.4.3",
2770
3126
  zustand: "^5.0.14"
@@ -2822,8 +3178,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
2822
3178
  }
2823
3179
 
2824
3180
  // src/actions/confirmLanguage.ts
2825
- import z19 from "zod";
2826
- var confirmLanguageSchema = z19.object({
3181
+ import z22 from "zod";
3182
+ var confirmLanguageSchema = z22.object({
2827
3183
  languages: detectLanguageSchema.shape.languages
2828
3184
  });
2829
3185
  async function confirmLanguage(ctx) {
@@ -2844,8 +3200,8 @@ async function confirmLanguage(ctx) {
2844
3200
  }
2845
3201
 
2846
3202
  // src/actions/confirmFramework.ts
2847
- import z20 from "zod";
2848
- var confirmFrameworkSchema = z20.object({
3203
+ import z23 from "zod";
3204
+ var confirmFrameworkSchema = z23.object({
2849
3205
  frameworks: detectLanguageSchema.shape.frameworks
2850
3206
  });
2851
3207
  var CURATED_FRAMEWORKS = [
@@ -2973,8 +3329,8 @@ async function promptUser(ctx, params) {
2973
3329
  }
2974
3330
 
2975
3331
  // src/actions/confirmEntities.ts
2976
- import z21 from "zod";
2977
- var confirmEntitiesSchema = z21.object({
3332
+ import z24 from "zod";
3333
+ var confirmEntitiesSchema = z24.object({
2978
3334
  // Final detection — the focused re-run may supersede project-scan's.
2979
3335
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2980
3336
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3044,15 +3400,15 @@ async function confirmEntities(ctx) {
3044
3400
  }
3045
3401
 
3046
3402
  // src/actions/review.ts
3047
- import { z as z22 } from "zod";
3048
- var reviewSchema = z22.object({
3403
+ import { z as z25 } from "zod";
3404
+ var reviewSchema = z25.object({
3049
3405
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3050
3406
  // not one entry per workflow step — a step's raw output can be a long,
3051
3407
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3052
3408
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3053
- summaryPoints: z22.array(z22.string()),
3054
- reviewPrompt: z22.string(),
3055
- nextSteps: z22.array(z22.string())
3409
+ summaryPoints: z25.array(z25.string()),
3410
+ reviewPrompt: z25.string(),
3411
+ nextSteps: z25.array(z25.string())
3056
3412
  });
3057
3413
  function formatCompletedSteps(steps) {
3058
3414
  if (!steps.length) return "(no prior steps completed)";
@@ -3103,16 +3459,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3103
3459
  };
3104
3460
 
3105
3461
  // src/actions/implement.ts
3106
- import z24 from "zod";
3462
+ import z26 from "zod";
3107
3463
 
3108
3464
  // src/lib/worktree.ts
3109
3465
  import { execFile, spawn as spawn3 } from "node:child_process";
3110
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3466
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3111
3467
  import {
3112
3468
  basename as basename2,
3113
3469
  dirname as dirname7,
3114
3470
  isAbsolute as isAbsolute2,
3115
- join as join10,
3471
+ join as join9,
3116
3472
  relative as relative2,
3117
3473
  resolve as resolve3
3118
3474
  } from "node:path";
@@ -3146,7 +3502,7 @@ async function isWorkingTreeDirty(repoRoot) {
3146
3502
  return out.trim().length > 0;
3147
3503
  }
3148
3504
  async function pruneOldWorktrees(repoRoot) {
3149
- const dir = join10(stateDir(repoRoot), "worktrees");
3505
+ const dir = join9(stateDir(repoRoot), "worktrees");
3150
3506
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3151
3507
  for (const slug of stale) {
3152
3508
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3157,7 +3513,7 @@ async function pruneOldWorktrees(repoRoot) {
3157
3513
  "worktree",
3158
3514
  "remove",
3159
3515
  "--force",
3160
- join10(dir, slug)
3516
+ join9(dir, slug)
3161
3517
  ]);
3162
3518
  await git(["-C", repoRoot, "branch", "-D", branch]);
3163
3519
  } catch (err) {
@@ -3171,7 +3527,7 @@ async function pruneOldWorktrees(repoRoot) {
3171
3527
  async function createWorktree(repoRoot) {
3172
3528
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3173
3529
  const dirSlug = branch.replace(/\//g, "-");
3174
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3530
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3175
3531
  await git(["-C", repoRoot, "worktree", "prune"]);
3176
3532
  await pruneOldWorktrees(repoRoot);
3177
3533
  await mkdir6(dirname7(path), { recursive: true });
@@ -3291,8 +3647,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3291
3647
  } catch {
3292
3648
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3293
3649
  }
3294
- const relPath = join10(ingestDir, basename2(source));
3295
- const dest = join10(worktreePath, relPath);
3650
+ const relPath = join9(ingestDir, basename2(source));
3651
+ const dest = join9(worktreePath, relPath);
3296
3652
  try {
3297
3653
  await mkdir6(dirname7(dest), { recursive: true });
3298
3654
  await copyFile(source, dest);
@@ -3308,10 +3664,10 @@ function hasEnvVar(content, name) {
3308
3664
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3309
3665
  }
3310
3666
  async function writeSearchEnvValues(worktreePath, vars) {
3311
- const target = join10(worktreePath, ".env");
3667
+ const target = join9(worktreePath, ".env");
3312
3668
  let existing = "";
3313
3669
  try {
3314
- existing = await readFile8(target, "utf8");
3670
+ existing = await readFile7(target, "utf8");
3315
3671
  } catch (err) {
3316
3672
  if (err.code !== "ENOENT") throw err;
3317
3673
  }
@@ -3379,63 +3735,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3379
3735
  }
3380
3736
  }
3381
3737
 
3382
- // src/lib/algoliaApiKey.ts
3383
- import { z as z23 } from "zod";
3384
- var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
3385
- var apiKeySchema = z23.object({
3386
- value: z23.string().min(1),
3387
- acl: z23.array(z23.string()).default([]),
3388
- indexes: z23.array(z23.string()).default([])
3389
- });
3390
- var apiKeyListSchema = z23.object({
3391
- items: z23.array(apiKeySchema).optional(),
3392
- keys: z23.array(apiKeySchema).optional()
3393
- }).transform((o) => o.items ?? o.keys ?? []);
3394
- var createdKeySchema = z23.object({
3395
- key: z23.string().min(1).optional(),
3396
- value: z23.string().min(1).optional()
3397
- });
3398
- function canReuse(key, index) {
3399
- return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
3400
- }
3401
- async function createSearchKey(index) {
3402
- const stdout = await runAlgoliaCli([
3403
- "apikeys",
3404
- "create",
3405
- "--indices",
3406
- index,
3407
- "--acl",
3408
- "search,browse",
3409
- "--description",
3410
- `wizard search-only key for ${index}`,
3411
- "-o",
3412
- "json"
3413
- ]);
3414
- const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
3415
- const created = key ?? value;
3416
- if (!created) throw new Error("apikeys create returned no key value");
3417
- return created;
3418
- }
3419
- async function resolveSearchOnlyKey(index) {
3420
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
3421
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
3422
- if (existing) {
3423
- logger.info({ index }, "reusing existing search-only API key");
3424
- return existing;
3425
- }
3426
- logger.info({ index }, "no reusable search-only key found; creating one");
3427
- return createSearchKey(index);
3428
- }
3429
-
3430
3738
  // src/lib/algoliaDocs.ts
3431
3739
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3432
- import { dirname as dirname8, join as join11 } from "node:path";
3740
+ import { dirname as dirname8, join as join10 } from "node:path";
3433
3741
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3434
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3742
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3435
3743
  function findDocsDir() {
3436
3744
  let dir = dirname8(fileURLToPath2(import.meta.url));
3437
3745
  for (; ; ) {
3438
- const candidate = join11(dir, DOCS_SUBPATH);
3746
+ const candidate = join10(dir, DOCS_SUBPATH);
3439
3747
  if (existsSync2(candidate)) return candidate;
3440
3748
  const parent = dirname8(dir);
3441
3749
  if (parent === dir) return void 0;
@@ -3458,7 +3766,7 @@ function loadAlgoliaDoc(language) {
3458
3766
  );
3459
3767
  return "";
3460
3768
  }
3461
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3769
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3462
3770
  }
3463
3771
  function getNamedDoc(name, language) {
3464
3772
  const docsDir = findDocsDir();
@@ -3466,7 +3774,7 @@ function getNamedDoc(name, language) {
3466
3774
  logger.warn("docs/algolia-sdk not found");
3467
3775
  return "";
3468
3776
  }
3469
- const file = join11(docsDir, `${name}-${language}.md`);
3777
+ const file = join10(docsDir, `${name}-${language}.md`);
3470
3778
  if (!existsSync2(file)) {
3471
3779
  logger.warn({ name, language }, "named SDK reference not found");
3472
3780
  return "";
@@ -3493,50 +3801,50 @@ function shellQuote(value) {
3493
3801
  }
3494
3802
 
3495
3803
  // src/actions/implement.ts
3496
- var implementSchema = z24.object({
3497
- filesChanged: z24.array(z24.string()),
3498
- summary: z24.string(),
3804
+ var implementSchema = z26.object({
3805
+ filesChanged: z26.array(z26.string()),
3806
+ summary: z26.string(),
3499
3807
  // Absolute path to the throwaway worktree holding the generated changes, so
3500
3808
  // the user can open it (`cd <worktreePath>`) or inspect the diff
3501
3809
  // (`git -C <worktreePath> status/diff`).
3502
- worktreePath: z24.string().optional(),
3503
- ingestCommand: z24.string().optional(),
3810
+ worktreePath: z26.string().optional(),
3811
+ ingestCommand: z26.string().optional(),
3504
3812
  // True when the user accepted the run-now prompt and the wizard executed the
3505
3813
  // ingestion script; downstream steps use this to avoid telling the user to run
3506
3814
  // a script that already ran.
3507
- ingestScriptRan: z24.boolean().optional(),
3815
+ ingestScriptRan: z26.boolean().optional(),
3508
3816
  // Records ingested by the run-now execution, parsed from the script's
3509
3817
  // machine-readable count line; absent when the script didn't run or emitted
3510
3818
  // no parseable count.
3511
- ingestRecordCount: z24.number().optional(),
3819
+ ingestRecordCount: z26.number().optional(),
3512
3820
  // Wall-clock duration of the run-now ingestion execution, in ms.
3513
- ingestDurationMs: z24.number().optional(),
3514
- ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3821
+ ingestDurationMs: z26.number().optional(),
3822
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3515
3823
  // Suggested names/values, built from framework detection. The search agent is
3516
3824
  // instructed to rename the prefix if it doesn't match the project's build
3517
3825
  // tool, so the names it actually wrote can differ — treat these as hints, not
3518
3826
  // ground truth (the agent's summary carries the final names).
3519
- searchEnvVars: z24.array(
3520
- z24.object({
3521
- name: z24.string(),
3522
- value: z24.string()
3827
+ searchEnvVars: z26.array(
3828
+ z26.object({
3829
+ name: z26.string(),
3830
+ value: z26.string()
3523
3831
  })
3524
3832
  ).optional()
3525
3833
  });
3526
- var implementationOutputSchema = z24.object({
3527
- summary: z24.string(),
3834
+ var implementationOutputSchema = z26.object({
3835
+ summary: z26.string(),
3528
3836
  // Ingestion only: how to run the generated script, as a structured pair the
3529
3837
  // wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
3530
3838
  // command string. `runtime` is constrained to an allowlisted interpreter and
3531
3839
  // `entrypoint` is validated to a worktree-relative path before execution, so
3532
3840
  // the agent cannot inject extra commands or swap the interpreter.
3533
- runtime: z24.enum(INGEST_RUNTIMES).optional(),
3534
- entrypoint: z24.string().optional()
3841
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3842
+ entrypoint: z26.string().optional()
3535
3843
  });
3536
- var verificationOutputSchema = z24.object({
3537
- summary: z24.string(),
3538
- sufficient: z24.boolean(),
3539
- additionalInstructions: z24.string().optional()
3844
+ var verificationOutputSchema = z26.object({
3845
+ summary: z26.string(),
3846
+ sufficient: z26.boolean(),
3847
+ additionalInstructions: z26.string().optional()
3540
3848
  });
3541
3849
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3542
3850
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3650,7 +3958,7 @@ function searchInstructions(input) {
3650
3958
  doc,
3651
3959
  `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
3652
3960
  "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
3653
- // appId always resolves (loadActiveProfile throws otherwise); only the
3961
+ // appId always resolves (requireApplication throws otherwise); only the
3654
3962
  // search-only key is best-effort and can fall back to a placeholder.
3655
3963
  `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3656
3964
  // Names are fixed, not the agent's to rename: the wizard writes the
@@ -3799,6 +4107,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3799
4107
  }
3800
4108
  }
3801
4109
  const targetIndex = selected?.selection;
4110
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
3802
4111
  await assertGitRepoWithHead(repoRoot);
3803
4112
  if (await isWorkingTreeDirty(repoRoot)) {
3804
4113
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -3809,7 +4118,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3809
4118
  let appId;
3810
4119
  let searchKey;
3811
4120
  if (useCases.includes("search")) {
3812
- appId = (await loadActiveProfile()).appId;
4121
+ appId = (await requireApplication()).id;
3813
4122
  try {
3814
4123
  searchKey = await resolveSearchOnlyKey(targetIndex);
3815
4124
  } catch (err) {
@@ -3920,7 +4229,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3920
4229
  messages: []
3921
4230
  }) === true;
3922
4231
  if (runNow) {
3923
- const profile = await loadActiveProfile();
4232
+ const ingestApp = await requireApplication();
4233
+ const writeKey = await resolveWriteKey(targetIndex);
3924
4234
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
3925
4235
  const scriptLogId = ctx.logStart("runIngestScript", {
3926
4236
  runtime: ingestRuntime,
@@ -3932,8 +4242,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3932
4242
  ingestRuntime,
3933
4243
  ingestEntrypoint,
3934
4244
  {
3935
- [APP_ID_VAR]: profile.appId,
3936
- [API_KEY_VAR]: profile.apiKey
4245
+ [APP_ID_VAR]: ingestApp.id,
4246
+ [API_KEY_VAR]: writeKey
3937
4247
  }
3938
4248
  );
3939
4249
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4144,8 +4454,8 @@ var defaultWorkflow = {
4144
4454
  defineStep({
4145
4455
  id: "select-index",
4146
4456
  title: "Set up index",
4147
- outputSchema: z25.object({
4148
- selection: z25.string()
4457
+ outputSchema: z27.object({
4458
+ selection: z27.string()
4149
4459
  }),
4150
4460
  run: (ctx) => selectIndexStep(ctx)
4151
4461
  }),
@@ -4424,7 +4734,7 @@ function parseCliArgs(argv) {
4424
4734
 
4425
4735
  // src/lib/resetState.ts
4426
4736
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4427
- import { join as join12 } from "node:path";
4737
+ import { join as join11 } from "node:path";
4428
4738
  var KEEP = ["wizard.log"];
4429
4739
  async function resetProjectState() {
4430
4740
  const dir = stateDir();
@@ -4436,7 +4746,7 @@ async function resetProjectState() {
4436
4746
  }
4437
4747
  const targets = entries.filter((name) => !KEEP.includes(name));
4438
4748
  await Promise.all(
4439
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4749
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4440
4750
  );
4441
4751
  return { dir, removed: targets };
4442
4752
  }
@@ -4491,31 +4801,38 @@ ${formatStepList(workflow)}`);
4491
4801
  }
4492
4802
  async function run(workflow) {
4493
4803
  const store = useWizard.getState();
4494
- let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4804
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4805
+ await store.waitForStart();
4495
4806
  let user = await getUser();
4496
4807
  if (!user) {
4497
- await instance.waitUntilRenderFlush();
4498
- instance.cleanup();
4808
+ store.beginAuth();
4499
4809
  try {
4500
4810
  await runAuthLogin();
4501
4811
  } catch (err) {
4502
- console.error(err instanceof Error ? err.message : String(err));
4812
+ store.setError(err instanceof Error ? err.message : String(err));
4813
+ await instance.waitUntilExit();
4503
4814
  process.exit(1);
4504
4815
  }
4505
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4816
+ store.endAuth();
4506
4817
  user = await getUser();
4507
4818
  if (!user) {
4508
4819
  store.setError(
4509
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4820
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
4510
4821
  );
4511
4822
  await instance.waitUntilExit();
4512
4823
  process.exit(1);
4513
4824
  }
4514
4825
  }
4515
4826
  store.setUser(user);
4516
- const profile = await loadActiveProfile();
4517
- await store.waitForStart();
4518
- runWorkflow(workflow, profile?.appId);
4827
+ let app;
4828
+ try {
4829
+ app = await ensureApplication();
4830
+ } catch (err) {
4831
+ store.setError(err instanceof Error ? err.message : String(err));
4832
+ await instance.waitUntilExit();
4833
+ process.exit(1);
4834
+ }
4835
+ runWorkflow(workflow, app.id);
4519
4836
  }
4520
4837
  var started = await startup();
4521
4838
  if (typeof started === "number") {