@algolia/wizard 0.8.0-rc.49.42 → 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 +871 -679
  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
 
@@ -1568,8 +1757,7 @@ var configFile = () => join5(stateDir(), "config.json");
1568
1757
  var DEFAULT_CONFIG = {
1569
1758
  version: 1,
1570
1759
  aiConsent: false,
1571
- workflowsRun: [],
1572
- searchApiKeys: {}
1760
+ workflowsRun: []
1573
1761
  };
1574
1762
  async function loadConfig() {
1575
1763
  try {
@@ -1588,38 +1776,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
1588
1776
  config.workflowsRun.push({ workflowId, completedAt });
1589
1777
  await saveConfig(config);
1590
1778
  }
1591
- function isStoredSearchKey(value) {
1592
- if (typeof value !== "object" || value === null) return false;
1593
- const { appId, key } = value;
1594
- return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
1595
- }
1596
- function storedSearchKeys(config) {
1597
- const stored = config.searchApiKeys;
1598
- if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
1599
- return {};
1600
- }
1601
- return stored;
1602
- }
1603
- async function getStoredSearchKey(index, appId) {
1604
- const entry = storedSearchKeys(await loadConfig())[index];
1605
- if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
1606
- return entry.key;
1607
- }
1608
- async function storeSearchKey(index, appId, key) {
1609
- const config = await loadConfig();
1610
- config.searchApiKeys = {
1611
- ...storedSearchKeys(config),
1612
- [index]: { appId, key }
1613
- };
1614
- await saveConfig(config);
1615
- }
1616
- async function forgetSearchKey(index) {
1617
- const config = await loadConfig();
1618
- const remaining = { ...storedSearchKeys(config) };
1619
- delete remaining[index];
1620
- config.searchApiKeys = remaining;
1621
- await saveConfig(config);
1622
- }
1623
1779
 
1624
1780
  // src/core/orchestrator.ts
1625
1781
  function defineStep(step) {
@@ -1823,61 +1979,138 @@ async function runWorkflow(workflow, appId) {
1823
1979
  }
1824
1980
  }
1825
1981
 
1826
- // src/lib/algoliaProfile.ts
1827
- import { readFile as readFile3 } from "node:fs/promises";
1828
- import { createRequire as createRequire2 } from "node:module";
1829
- import { homedir as homedir2 } from "node:os";
1830
- import { join as join6 } from "node:path";
1831
- import { parse as parseToml } from "toml";
1832
- var require3 = createRequire2(import.meta.url);
1833
- function configPath() {
1834
- const base = process.env.XDG_CONFIG_HOME || join6(homedir2(), ".config");
1835
- return join6(base, "algolia", "config.toml");
1836
- }
1837
- function profilesFromConfig(tomlText) {
1838
- 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;
1839
1998
  try {
1840
- parsed = parseToml(tomlText);
1999
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1841
2000
  } catch {
1842
- 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
+ );
1843
2033
  }
1844
- const profiles = Object.entries(parsed).filter(
1845
- ([, t]) => typeof t.application_id === "string" && typeof t.api_key === "string"
1846
- ).map(([name, t]) => ({
1847
- name,
1848
- appId: t.application_id,
1849
- apiKey: t.api_key,
1850
- isDefault: t.default === true
1851
- }));
1852
- profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
1853
- return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
1854
- }
1855
- async function loadActiveProfile() {
1856
- let profiles;
2034
+ return parsed.data;
2035
+ }
2036
+ function parseJson(text) {
1857
2037
  try {
1858
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
2038
+ return JSON.parse(text);
1859
2039
  } catch {
1860
- profiles = [];
2040
+ return void 0;
1861
2041
  }
1862
- const profile = profiles[0];
1863
- 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) {
1864
2062
  throw new Error(
1865
- "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"
1866
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
+ }
1867
2098
  }
1868
- return profile;
2099
+ }
2100
+ async function ensureApplication() {
2101
+ return await currentApplication() ?? await promptForApplication();
1869
2102
  }
1870
2103
 
1871
2104
  // src/workflows/default.ts
1872
- import { z as z25 } from "zod";
2105
+ import { z as z27 } from "zod";
1873
2106
 
1874
2107
  // src/actions/listIndices.ts
1875
- import { z as z3 } from "zod";
1876
- var indicesListSchema = z3.object({
1877
- items: z3.array(
1878
- z3.object({
1879
- name: z3.string(),
1880
- 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)
1881
2114
  })
1882
2115
  )
1883
2116
  });
@@ -1948,12 +2181,12 @@ import "zod";
1948
2181
 
1949
2182
  // src/lib/tools/listFiles.ts
1950
2183
  import { tool } from "ai";
1951
- import z4 from "zod";
2184
+ import z6 from "zod";
1952
2185
  import { readdir } from "node:fs/promises";
1953
2186
 
1954
2187
  // src/lib/tools/path.ts
1955
2188
  import { lstat } from "node:fs/promises";
1956
- 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";
1957
2190
  function resolveInRoot(ctx, path) {
1958
2191
  const target = resolve2(ctx.cwd, path);
1959
2192
  const rel = relative(ctx.root, target);
@@ -1969,7 +2202,7 @@ async function hasSymlinkParent(ctx, target) {
1969
2202
  let current = ctx.root;
1970
2203
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1971
2204
  for (const part of parts) {
1972
- current = join7(current, part);
2205
+ current = join6(current, part);
1973
2206
  try {
1974
2207
  if ((await lstat(current)).isSymbolicLink()) return true;
1975
2208
  } catch (err) {
@@ -1984,7 +2217,7 @@ async function hasSymlinkParent(ctx, target) {
1984
2217
  function listFilesTool(ctx) {
1985
2218
  return tool({
1986
2219
  description: "List files in the current working directory",
1987
- inputSchema: z4.object(),
2220
+ inputSchema: z6.object(),
1988
2221
  execute: async () => {
1989
2222
  logger.info("called listFiles tool");
1990
2223
  if (++ctx.counts.list > ctx.limits.list) {
@@ -2000,13 +2233,13 @@ function listFilesTool(ctx) {
2000
2233
 
2001
2234
  // src/lib/tools/changeDirectory.ts
2002
2235
  import { tool as tool2 } from "ai";
2003
- import z5 from "zod";
2236
+ import z7 from "zod";
2004
2237
  import { stat } from "node:fs/promises";
2005
2238
  function changeDirectoryTool(ctx) {
2006
2239
  return tool2({
2007
2240
  description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
2008
- inputSchema: z5.object({
2009
- path: z5.string().describe("Directory to change into")
2241
+ inputSchema: z7.object({
2242
+ path: z7.string().describe("Directory to change into")
2010
2243
  }),
2011
2244
  execute: async ({ path }) => {
2012
2245
  logger.info({ path }, "called changeDirectory tool");
@@ -2028,13 +2261,13 @@ function changeDirectoryTool(ctx) {
2028
2261
 
2029
2262
  // src/lib/tools/reportStatus.ts
2030
2263
  import { tool as tool3 } from "ai";
2031
- import z6 from "zod";
2264
+ import z8 from "zod";
2032
2265
  function reportStatusTool(output) {
2033
2266
  return tool3({
2034
2267
  description: "Report the status of your execution. Return a reason in case of failure.",
2035
- inputSchema: z6.object({
2036
- status: z6.enum(["success", "fail"]),
2037
- reason: z6.string().optional(),
2268
+ inputSchema: z8.object({
2269
+ status: z8.enum(["success", "fail"]),
2270
+ reason: z8.string().optional(),
2038
2271
  output
2039
2272
  }),
2040
2273
  execute: async ({ status, reason, output: output2 }) => {
@@ -2046,8 +2279,8 @@ function reportStatusTool(output) {
2046
2279
 
2047
2280
  // src/lib/tools/readFile.ts
2048
2281
  import { tool as tool4 } from "ai";
2049
- import z7 from "zod";
2050
- import { readFile as readFile4 } from "node:fs/promises";
2282
+ import z9 from "zod";
2283
+ import { readFile as readFile3 } from "node:fs/promises";
2051
2284
 
2052
2285
  // src/lib/tools/env.ts
2053
2286
  import { basename } from "node:path";
@@ -2074,8 +2307,8 @@ function redactEnvValues(content) {
2074
2307
  function readFileTool(ctx) {
2075
2308
  return tool4({
2076
2309
  description: "Read the contents of a file at the given path",
2077
- inputSchema: z7.object({
2078
- 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")
2079
2312
  }),
2080
2313
  execute: async ({ filePath }) => {
2081
2314
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2085,7 +2318,7 @@ function readFileTool(ctx) {
2085
2318
  const resolved = resolveInRoot(ctx, filePath);
2086
2319
  if (!resolved.ok) return resolved.error;
2087
2320
  try {
2088
- const content = await readFile4(resolved.target, "utf8");
2321
+ const content = await readFile3(resolved.target, "utf8");
2089
2322
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2090
2323
  } catch (err) {
2091
2324
  return `Error reading ${filePath}: ${err.message}`;
@@ -2096,15 +2329,15 @@ function readFileTool(ctx) {
2096
2329
 
2097
2330
  // src/lib/tools/writeFile.ts
2098
2331
  import { tool as tool5 } from "ai";
2099
- import z8 from "zod";
2332
+ import z10 from "zod";
2100
2333
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2101
2334
  import { dirname as dirname4 } from "node:path";
2102
2335
  function writeFileTool(ctx) {
2103
2336
  return tool5({
2104
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.",
2105
- inputSchema: z8.object({
2106
- filePath: z8.string().describe("Path to the file to write"),
2107
- 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")
2108
2341
  }),
2109
2342
  execute: async ({ filePath, content }) => {
2110
2343
  logger.info({ filePath }, "called writeFile tool");
@@ -2129,9 +2362,95 @@ function writeFileTool(ctx) {
2129
2362
 
2130
2363
  // src/lib/tools/writeAlgoliaCredentials.ts
2131
2364
  import { tool as tool6 } from "ai";
2132
- import z9 from "zod";
2133
- 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";
2134
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
2135
2454
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2136
2455
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2137
2456
  function appendEnv(content, entries) {
@@ -2145,9 +2464,9 @@ function hasEnv(content, name) {
2145
2464
  }
2146
2465
  function writeCredentialsTool(ctx) {
2147
2466
  return tool6({
2148
- 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.`,
2149
- inputSchema: z9.object({
2150
- 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(
2151
2470
  'Path to the env file to write credentials into (e.g. ".env")'
2152
2471
  )
2153
2472
  }),
@@ -2155,11 +2474,17 @@ function writeCredentialsTool(ctx) {
2155
2474
  logger.info({ filePath }, "called writeCredentials tool");
2156
2475
  const resolved = resolveInRoot(ctx, filePath);
2157
2476
  if (resolved.ok === false) return resolved.error;
2158
- 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;
2159
2483
  try {
2160
- profile = await loadActiveProfile();
2161
- } catch {
2162
- 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.`;
2163
2488
  }
2164
2489
  try {
2165
2490
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2167,7 +2492,7 @@ function writeCredentialsTool(ctx) {
2167
2492
  }
2168
2493
  let existing = "";
2169
2494
  try {
2170
- existing = await readFile5(resolved.target, "utf8");
2495
+ existing = await readFile4(resolved.target, "utf8");
2171
2496
  } catch (err) {
2172
2497
  if (err.code !== "ENOENT") throw err;
2173
2498
  }
@@ -2178,8 +2503,8 @@ function writeCredentialsTool(ctx) {
2178
2503
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2179
2504
  }
2180
2505
  const envWithCredentials = appendEnv(existing, [
2181
- [APP_ID_VAR, profile.appId],
2182
- [API_KEY_VAR, profile.apiKey]
2506
+ [APP_ID_VAR, appId],
2507
+ [API_KEY_VAR, writeKey]
2183
2508
  ]);
2184
2509
  await mkdir4(dirname5(resolved.target), { recursive: true });
2185
2510
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2193,16 +2518,16 @@ function writeCredentialsTool(ctx) {
2193
2518
 
2194
2519
  // src/lib/tools/searchFiles.ts
2195
2520
  import { tool as tool7 } from "ai";
2196
- import z10 from "zod";
2197
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2198
- 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";
2199
2524
  var MAX_QUERY_LENGTH = 1e3;
2200
2525
  async function walkFiles(dir) {
2201
2526
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2202
2527
  const out = [];
2203
2528
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2204
2529
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2205
- const full = join8(dir, e.name);
2530
+ const full = join7(dir, e.name);
2206
2531
  if (e.isDirectory()) out.push(...await walkFiles(full));
2207
2532
  else if (e.isFile()) out.push(full);
2208
2533
  }
@@ -2211,9 +2536,9 @@ async function walkFiles(dir) {
2211
2536
  function searchFilesTool(ctx) {
2212
2537
  return tool7({
2213
2538
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2214
- inputSchema: z10.object({
2215
- query: z10.string().describe("JavaScript RegExp pattern to search for"),
2216
- 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)")
2217
2542
  }),
2218
2543
  execute: async ({ query, path = "." }) => {
2219
2544
  logger.info({ query, path }, "called searchFiles tool");
@@ -2235,7 +2560,7 @@ function searchFilesTool(ctx) {
2235
2560
  for (const file of await walkFiles(resolved.target)) {
2236
2561
  let content;
2237
2562
  try {
2238
- content = await readFile6(file, "utf8");
2563
+ content = await readFile5(file, "utf8");
2239
2564
  } catch {
2240
2565
  continue;
2241
2566
  }
@@ -2257,7 +2582,7 @@ function searchFilesTool(ctx) {
2257
2582
 
2258
2583
  // src/lib/tools/verifyImplementation.ts
2259
2584
  import { tool as tool8 } from "ai";
2260
- import z11 from "zod";
2585
+ import z14 from "zod";
2261
2586
 
2262
2587
  // src/lib/tools/utils/runCommand.ts
2263
2588
  import { spawn as spawn2 } from "node:child_process";
@@ -2279,9 +2604,9 @@ function runCommand(command, args, cwd) {
2279
2604
  }
2280
2605
 
2281
2606
  // src/lib/tools/utils/packageManager.ts
2282
- import { readFile as readFile7 } from "node:fs/promises";
2607
+ import { readFile as readFile6 } from "node:fs/promises";
2283
2608
  import { existsSync } from "node:fs";
2284
- import { join as join9 } from "node:path";
2609
+ import { join as join8 } from "node:path";
2285
2610
  var LOCKFILES = [
2286
2611
  ["pnpm-lock.yaml", "pnpm"],
2287
2612
  ["yarn.lock", "yarn"],
@@ -2290,13 +2615,13 @@ var LOCKFILES = [
2290
2615
  ["package-lock.json", "npm"]
2291
2616
  ];
2292
2617
  async function readPackageJson(cwd = process.cwd()) {
2293
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2618
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2294
2619
  }
2295
2620
  function packageManagerFrom(pkg) {
2296
2621
  return pkg.packageManager?.split("@")[0] ?? "npm";
2297
2622
  }
2298
2623
  function packageManagerFromLockfile(cwd) {
2299
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2624
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2300
2625
  }
2301
2626
  async function detectPackageManager(cwd) {
2302
2627
  try {
@@ -2337,7 +2662,7 @@ async function runRepoVerificationCheck() {
2337
2662
  function verifyImplementationTool() {
2338
2663
  return tool8({
2339
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.",
2340
- inputSchema: z11.object(),
2665
+ inputSchema: z14.object(),
2341
2666
  execute: async () => {
2342
2667
  logger.info("called verifyImplementation tool");
2343
2668
  return runRepoVerificationCheck();
@@ -2351,7 +2676,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2351
2676
  import { nanoid as nanoid2 } from "nanoid";
2352
2677
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2353
2678
  import { dirname as dirname6 } from "node:path";
2354
- import z12 from "zod";
2679
+ import z15 from "zod";
2355
2680
  var DATA_DIR = ".algolia-wizard/data";
2356
2681
  var RECORD_MODEL = "claude-haiku-4-5";
2357
2682
  var MAX_RECORDS = 100;
@@ -2363,17 +2688,17 @@ var anthropic = createAnthropic({
2363
2688
  function generateRecordTool(ctx) {
2364
2689
  return tool9({
2365
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.",
2366
- inputSchema: z12.object({
2367
- entityName: z12.string().describe("Name of the entity to generate records for."),
2368
- attributes: z12.array(z12.string()).describe("Attribute names each record must contain."),
2369
- count: z12.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2370
- 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.")
2371
2696
  }),
2372
2697
  execute: async ({ entityName, attributes, count, hint }) => {
2373
2698
  logger.info({ entityName, count }, "called generateRecord tool");
2374
2699
  try {
2375
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2376
- const recordSchema = z12.object(
2700
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2701
+ const recordSchema = z15.object(
2377
2702
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2378
2703
  );
2379
2704
  const generateBatch = async (batchCount) => {
@@ -2383,8 +2708,8 @@ function generateRecordTool(ctx) {
2383
2708
  const { output } = await generateText({
2384
2709
  model: anthropic(RECORD_MODEL),
2385
2710
  output: Output.object({
2386
- schema: z12.object({
2387
- records: z12.array(recordSchema).length(batchCount)
2711
+ schema: z15.object({
2712
+ records: z15.array(recordSchema).length(batchCount)
2388
2713
  })
2389
2714
  }),
2390
2715
  prompt: [
@@ -2442,12 +2767,12 @@ function generateRecordTool(ctx) {
2442
2767
 
2443
2768
  // src/lib/tools/notifyUser.ts
2444
2769
  import { tool as tool10 } from "ai";
2445
- import z13 from "zod";
2770
+ import z16 from "zod";
2446
2771
  function notifyUserTool() {
2447
2772
  return tool10({
2448
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.`,
2449
- inputSchema: z13.object({
2450
- message: z13.string().describe(
2774
+ inputSchema: z16.object({
2775
+ message: z16.string().describe(
2451
2776
  "Short, plain-language description of what you are doing now."
2452
2777
  )
2453
2778
  }),
@@ -2627,10 +2952,10 @@ async function runAgent(req) {
2627
2952
  }
2628
2953
 
2629
2954
  // src/actions/detectLanguage.ts
2630
- import z16 from "zod";
2631
- var detectLanguageSchema = z16.object({
2632
- languages: z16.array(z16.object({ name: z16.string(), version: z16.string() })),
2633
- 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() }))
2634
2959
  });
2635
2960
  var detectLanguage = () => runAgent({
2636
2961
  instructions: [
@@ -2648,31 +2973,31 @@ var detectLanguage = () => runAgent({
2648
2973
  });
2649
2974
 
2650
2975
  // src/actions/analyzeCodebase.ts
2651
- import z17 from "zod";
2976
+ import z20 from "zod";
2652
2977
  var READONLY_TOOLS = [
2653
2978
  "listFiles",
2654
2979
  "changeDirectory",
2655
2980
  "readFile",
2656
2981
  "searchFiles"
2657
2982
  ];
2658
- var ingestionAnalysisSchema = z17.object({
2659
- ingestionAnalysis: z17.array(
2660
- z17.object({
2661
- name: z17.string(),
2662
- 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()),
2663
2988
  // indexable fields the agent found for this entity
2664
- attributes: z17.array(z17.string())
2989
+ attributes: z20.array(z20.string())
2665
2990
  })
2666
2991
  )
2667
2992
  });
2668
- var searchImplementationAnalysisSchema = z17.object({
2669
- searchImplementationAnalysis: z17.string()
2993
+ var searchImplementationAnalysisSchema = z20.object({
2994
+ searchImplementationAnalysis: z20.string()
2670
2995
  });
2671
- var verificationSchema = z17.object({
2672
- verification: z17.array(z17.string())
2996
+ var verificationSchema = z20.object({
2997
+ verification: z20.array(z20.string())
2673
2998
  });
2674
2999
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
2675
- var analyzeCodebaseSchema = z17.object({
3000
+ var analyzeCodebaseSchema = z20.object({
2676
3001
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2677
3002
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
2678
3003
  verification: verificationSchema.shape.verification.optional(),
@@ -2734,7 +3059,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2734
3059
  // package.json
2735
3060
  var package_default = {
2736
3061
  name: "@algolia/wizard",
2737
- version: "0.8.0-rc.49.42",
3062
+ version: "0.8.0-rc.58.43",
2738
3063
  description: "Magically implement Algolia functionality in your codebase",
2739
3064
  type: "module",
2740
3065
  engines: {
@@ -2782,7 +3107,6 @@ var package_default = {
2782
3107
  dependencies: {
2783
3108
  "@ai-sdk/anthropic": "^3.0.81",
2784
3109
  "@ai-sdk/openai-compatible": "^2.0.47",
2785
- "@algolia/cli": "^5.15.0",
2786
3110
  "@hono/node-server": "^2.0.10",
2787
3111
  "@mishieck/ink-titled-box": "^0.4.2",
2788
3112
  "@segment/analytics-node": "^3.1.0",
@@ -2797,7 +3121,6 @@ var package_default = {
2797
3121
  nanoid: "^5.1.15",
2798
3122
  pino: "^10.3.1",
2799
3123
  react: "^19.2.7",
2800
- toml: "^4.1.1",
2801
3124
  varlock: "^1.5.1",
2802
3125
  zod: "^4.4.3",
2803
3126
  zustand: "^5.0.14"
@@ -2855,8 +3178,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
2855
3178
  }
2856
3179
 
2857
3180
  // src/actions/confirmLanguage.ts
2858
- import z19 from "zod";
2859
- var confirmLanguageSchema = z19.object({
3181
+ import z22 from "zod";
3182
+ var confirmLanguageSchema = z22.object({
2860
3183
  languages: detectLanguageSchema.shape.languages
2861
3184
  });
2862
3185
  async function confirmLanguage(ctx) {
@@ -2877,8 +3200,8 @@ async function confirmLanguage(ctx) {
2877
3200
  }
2878
3201
 
2879
3202
  // src/actions/confirmFramework.ts
2880
- import z20 from "zod";
2881
- var confirmFrameworkSchema = z20.object({
3203
+ import z23 from "zod";
3204
+ var confirmFrameworkSchema = z23.object({
2882
3205
  frameworks: detectLanguageSchema.shape.frameworks
2883
3206
  });
2884
3207
  var CURATED_FRAMEWORKS = [
@@ -3006,8 +3329,8 @@ async function promptUser(ctx, params) {
3006
3329
  }
3007
3330
 
3008
3331
  // src/actions/confirmEntities.ts
3009
- import z21 from "zod";
3010
- var confirmEntitiesSchema = z21.object({
3332
+ import z24 from "zod";
3333
+ var confirmEntitiesSchema = z24.object({
3011
3334
  // Final detection — the focused re-run may supersede project-scan's.
3012
3335
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3013
3336
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3077,15 +3400,15 @@ async function confirmEntities(ctx) {
3077
3400
  }
3078
3401
 
3079
3402
  // src/actions/review.ts
3080
- import { z as z22 } from "zod";
3081
- var reviewSchema = z22.object({
3403
+ import { z as z25 } from "zod";
3404
+ var reviewSchema = z25.object({
3082
3405
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3083
3406
  // not one entry per workflow step — a step's raw output can be a long,
3084
3407
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3085
3408
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3086
- summaryPoints: z22.array(z22.string()),
3087
- reviewPrompt: z22.string(),
3088
- nextSteps: z22.array(z22.string())
3409
+ summaryPoints: z25.array(z25.string()),
3410
+ reviewPrompt: z25.string(),
3411
+ nextSteps: z25.array(z25.string())
3089
3412
  });
3090
3413
  function formatCompletedSteps(steps) {
3091
3414
  if (!steps.length) return "(no prior steps completed)";
@@ -3136,16 +3459,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3136
3459
  };
3137
3460
 
3138
3461
  // src/actions/implement.ts
3139
- import z24 from "zod";
3462
+ import z26 from "zod";
3140
3463
 
3141
3464
  // src/lib/worktree.ts
3142
3465
  import { execFile, spawn as spawn3 } from "node:child_process";
3143
- 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";
3144
3467
  import {
3145
3468
  basename as basename2,
3146
3469
  dirname as dirname7,
3147
3470
  isAbsolute as isAbsolute2,
3148
- join as join10,
3471
+ join as join9,
3149
3472
  relative as relative2,
3150
3473
  resolve as resolve3
3151
3474
  } from "node:path";
@@ -3179,7 +3502,7 @@ async function isWorkingTreeDirty(repoRoot) {
3179
3502
  return out.trim().length > 0;
3180
3503
  }
3181
3504
  async function pruneOldWorktrees(repoRoot) {
3182
- const dir = join10(stateDir(repoRoot), "worktrees");
3505
+ const dir = join9(stateDir(repoRoot), "worktrees");
3183
3506
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3184
3507
  for (const slug of stale) {
3185
3508
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3190,7 +3513,7 @@ async function pruneOldWorktrees(repoRoot) {
3190
3513
  "worktree",
3191
3514
  "remove",
3192
3515
  "--force",
3193
- join10(dir, slug)
3516
+ join9(dir, slug)
3194
3517
  ]);
3195
3518
  await git(["-C", repoRoot, "branch", "-D", branch]);
3196
3519
  } catch (err) {
@@ -3204,7 +3527,7 @@ async function pruneOldWorktrees(repoRoot) {
3204
3527
  async function createWorktree(repoRoot) {
3205
3528
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3206
3529
  const dirSlug = branch.replace(/\//g, "-");
3207
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3530
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3208
3531
  await git(["-C", repoRoot, "worktree", "prune"]);
3209
3532
  await pruneOldWorktrees(repoRoot);
3210
3533
  await mkdir6(dirname7(path), { recursive: true });
@@ -3324,8 +3647,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3324
3647
  } catch {
3325
3648
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3326
3649
  }
3327
- const relPath = join10(ingestDir, basename2(source));
3328
- const dest = join10(worktreePath, relPath);
3650
+ const relPath = join9(ingestDir, basename2(source));
3651
+ const dest = join9(worktreePath, relPath);
3329
3652
  try {
3330
3653
  await mkdir6(dirname7(dest), { recursive: true });
3331
3654
  await copyFile(source, dest);
@@ -3340,28 +3663,11 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3340
3663
  function hasEnvVar(content, name) {
3341
3664
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3342
3665
  }
3343
- async function readEnvVar(worktreePath, name) {
3344
- let content;
3345
- try {
3346
- content = await readFile8(join10(worktreePath, ".env"), "utf8");
3347
- } catch (err) {
3348
- if (err.code !== "ENOENT") throw err;
3349
- return void 0;
3350
- }
3351
- const match = new RegExp(
3352
- `^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
3353
- "m"
3354
- ).exec(content);
3355
- if (!match) return void 0;
3356
- const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
3357
- if (!value || value.startsWith("<")) return void 0;
3358
- return value;
3359
- }
3360
3666
  async function writeSearchEnvValues(worktreePath, vars) {
3361
- const target = join10(worktreePath, ".env");
3667
+ const target = join9(worktreePath, ".env");
3362
3668
  let existing = "";
3363
3669
  try {
3364
- existing = await readFile8(target, "utf8");
3670
+ existing = await readFile7(target, "utf8");
3365
3671
  } catch (err) {
3366
3672
  if (err.code !== "ENOENT") throw err;
3367
3673
  }
@@ -3429,85 +3735,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3429
3735
  }
3430
3736
  }
3431
3737
 
3432
- // src/lib/algoliaApiKey.ts
3433
- import { z as z23 } from "zod";
3434
- var createdKeySchema = z23.object({
3435
- key: z23.string().min(1).optional(),
3436
- value: z23.string().min(1).optional()
3437
- }).transform((o) => o.key ?? o.value);
3438
- async function createSearchOnlyKey(index) {
3439
- logger.info({ index }, "creating a search-only API key");
3440
- const stdout = await runAlgoliaCli([
3441
- "apikeys",
3442
- "create",
3443
- "--acl",
3444
- "search",
3445
- "--indices",
3446
- index,
3447
- "--description",
3448
- `Algolia Wizard search-only key for ${index}`,
3449
- "-o",
3450
- "json"
3451
- ]);
3452
- let payload;
3453
- try {
3454
- payload = JSON.parse(stdout);
3455
- } catch {
3456
- throw new Error("apikeys create returned output that is not valid JSON");
3457
- }
3458
- const created = createdKeySchema.parse(payload);
3459
- if (!created) throw new Error("apikeys create returned no key value");
3460
- return created;
3461
- }
3462
- async function apiKeyExists(key) {
3463
- try {
3464
- await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
3465
- return true;
3466
- } catch (err) {
3467
- return !/does not exist|not found|404/i.test(err.message);
3468
- }
3469
- }
3470
- async function resolveSearchOnlyKey(index, appId, envKey) {
3471
- if (envKey) {
3472
- await recordSearchKey(index, appId, envKey);
3473
- return { key: envKey, source: "env" };
3474
- }
3475
- const stored = await getStoredSearchKey(index, appId);
3476
- if (stored) {
3477
- if (await apiKeyExists(stored)) {
3478
- logger.info({ index, appId }, "reusing the stored search-only API key");
3479
- return { key: stored, source: "config" };
3480
- }
3481
- logger.warn(
3482
- { index, appId },
3483
- "the stored search-only API key no longer exists; creating a replacement"
3484
- );
3485
- await forgetSearchKey(index);
3486
- }
3487
- const key = await createSearchOnlyKey(index);
3488
- await recordSearchKey(index, appId, key);
3489
- return { key, source: "created" };
3490
- }
3491
- async function recordSearchKey(index, appId, key) {
3492
- try {
3493
- await storeSearchKey(index, appId, key);
3494
- } catch (err) {
3495
- logger.warn(
3496
- { err: err.message, index },
3497
- "could not record the search-only API key; a later run may create another"
3498
- );
3499
- }
3500
- }
3501
-
3502
3738
  // src/lib/algoliaDocs.ts
3503
3739
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3504
- import { dirname as dirname8, join as join11 } from "node:path";
3740
+ import { dirname as dirname8, join as join10 } from "node:path";
3505
3741
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3506
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3742
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3507
3743
  function findDocsDir() {
3508
3744
  let dir = dirname8(fileURLToPath2(import.meta.url));
3509
3745
  for (; ; ) {
3510
- const candidate = join11(dir, DOCS_SUBPATH);
3746
+ const candidate = join10(dir, DOCS_SUBPATH);
3511
3747
  if (existsSync2(candidate)) return candidate;
3512
3748
  const parent = dirname8(dir);
3513
3749
  if (parent === dir) return void 0;
@@ -3530,7 +3766,7 @@ function loadAlgoliaDoc(language) {
3530
3766
  );
3531
3767
  return "";
3532
3768
  }
3533
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3769
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3534
3770
  }
3535
3771
  function getNamedDoc(name, language) {
3536
3772
  const docsDir = findDocsDir();
@@ -3538,7 +3774,7 @@ function getNamedDoc(name, language) {
3538
3774
  logger.warn("docs/algolia-sdk not found");
3539
3775
  return "";
3540
3776
  }
3541
- const file = join11(docsDir, `${name}-${language}.md`);
3777
+ const file = join10(docsDir, `${name}-${language}.md`);
3542
3778
  if (!existsSync2(file)) {
3543
3779
  logger.warn({ name, language }, "named SDK reference not found");
3544
3780
  return "";
@@ -3565,50 +3801,50 @@ function shellQuote(value) {
3565
3801
  }
3566
3802
 
3567
3803
  // src/actions/implement.ts
3568
- var implementSchema = z24.object({
3569
- filesChanged: z24.array(z24.string()),
3570
- summary: z24.string(),
3804
+ var implementSchema = z26.object({
3805
+ filesChanged: z26.array(z26.string()),
3806
+ summary: z26.string(),
3571
3807
  // Absolute path to the throwaway worktree holding the generated changes, so
3572
3808
  // the user can open it (`cd <worktreePath>`) or inspect the diff
3573
3809
  // (`git -C <worktreePath> status/diff`).
3574
- worktreePath: z24.string().optional(),
3575
- ingestCommand: z24.string().optional(),
3810
+ worktreePath: z26.string().optional(),
3811
+ ingestCommand: z26.string().optional(),
3576
3812
  // True when the user accepted the run-now prompt and the wizard executed the
3577
3813
  // ingestion script; downstream steps use this to avoid telling the user to run
3578
3814
  // a script that already ran.
3579
- ingestScriptRan: z24.boolean().optional(),
3815
+ ingestScriptRan: z26.boolean().optional(),
3580
3816
  // Records ingested by the run-now execution, parsed from the script's
3581
3817
  // machine-readable count line; absent when the script didn't run or emitted
3582
3818
  // no parseable count.
3583
- ingestRecordCount: z24.number().optional(),
3819
+ ingestRecordCount: z26.number().optional(),
3584
3820
  // Wall-clock duration of the run-now ingestion execution, in ms.
3585
- ingestDurationMs: z24.number().optional(),
3586
- ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3821
+ ingestDurationMs: z26.number().optional(),
3822
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3587
3823
  // Suggested names/values, built from framework detection. The search agent is
3588
3824
  // instructed to rename the prefix if it doesn't match the project's build
3589
3825
  // tool, so the names it actually wrote can differ — treat these as hints, not
3590
3826
  // ground truth (the agent's summary carries the final names).
3591
- searchEnvVars: z24.array(
3592
- z24.object({
3593
- name: z24.string(),
3594
- value: z24.string()
3827
+ searchEnvVars: z26.array(
3828
+ z26.object({
3829
+ name: z26.string(),
3830
+ value: z26.string()
3595
3831
  })
3596
3832
  ).optional()
3597
3833
  });
3598
- var implementationOutputSchema = z24.object({
3599
- summary: z24.string(),
3834
+ var implementationOutputSchema = z26.object({
3835
+ summary: z26.string(),
3600
3836
  // Ingestion only: how to run the generated script, as a structured pair the
3601
3837
  // wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
3602
3838
  // command string. `runtime` is constrained to an allowlisted interpreter and
3603
3839
  // `entrypoint` is validated to a worktree-relative path before execution, so
3604
3840
  // the agent cannot inject extra commands or swap the interpreter.
3605
- runtime: z24.enum(INGEST_RUNTIMES).optional(),
3606
- entrypoint: z24.string().optional()
3841
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3842
+ entrypoint: z26.string().optional()
3607
3843
  });
3608
- var verificationOutputSchema = z24.object({
3609
- summary: z24.string(),
3610
- sufficient: z24.boolean(),
3611
- additionalInstructions: z24.string().optional()
3844
+ var verificationOutputSchema = z26.object({
3845
+ summary: z26.string(),
3846
+ sufficient: z26.boolean(),
3847
+ additionalInstructions: z26.string().optional()
3612
3848
  });
3613
3849
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3614
3850
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3651,22 +3887,15 @@ function publicEnvPrefix(language) {
3651
3887
  }
3652
3888
  return "PUBLIC_";
3653
3889
  }
3654
- var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
3655
- var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
3656
- function appIdVar(language) {
3657
- return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
3658
- }
3659
- function searchKeyVar(language) {
3660
- return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
3661
- }
3662
3890
  function searchEnvVars(language, appId, searchKey) {
3891
+ const prefix = publicEnvPrefix(language);
3663
3892
  return [
3664
3893
  {
3665
- name: appIdVar(language),
3894
+ name: `${prefix}ALGOLIA_APP_ID`,
3666
3895
  value: appId ?? "<your-algolia-app-id>"
3667
3896
  },
3668
3897
  {
3669
- name: searchKeyVar(language),
3898
+ name: `${prefix}ALGOLIA_SEARCH_API_KEY`,
3670
3899
  value: searchKey ?? "<your-algolia-search-only-api-key>"
3671
3900
  }
3672
3901
  ];
@@ -3728,13 +3957,15 @@ function searchInstructions(input) {
3728
3957
  "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
3729
3958
  doc,
3730
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.`,
3731
- // The key is provisioned only after verification passes, so the agent never
3732
- // sees one. It must also leave .env alone: the wizard reads that file to
3733
- // decide whether a key already exists, and an agent-invented value there
3734
- // would be reused as if it were real.
3735
- `Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
3736
- `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3737
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.",
3961
+ // appId always resolves (requireApplication throws otherwise); only the
3962
+ // search-only key is best-effort and can fall back to a placeholder.
3963
+ `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3964
+ // Names are fixed, not the agent's to rename: the wizard writes the
3965
+ // resolved app id / search-only key into ".env" under these exact names
3966
+ // right after this step, so a renamed prefix here would leave the code
3967
+ // reading a var the wizard never wrote.
3968
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3738
3969
  'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
3739
3970
  "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
3740
3971
  ];
@@ -3876,6 +4107,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3876
4107
  }
3877
4108
  }
3878
4109
  const targetIndex = selected?.selection;
4110
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
3879
4111
  await assertGitRepoWithHead(repoRoot);
3880
4112
  if (await isWorkingTreeDirty(repoRoot)) {
3881
4113
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -3884,8 +4116,17 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3884
4116
  const confirmed2 = normalized.confirmedEntities;
3885
4117
  const searchLocation = normalized.searchImplementationAnalysis;
3886
4118
  let appId;
4119
+ let searchKey;
3887
4120
  if (useCases.includes("search")) {
3888
- appId = (await loadActiveProfile()).appId;
4121
+ appId = (await requireApplication()).id;
4122
+ try {
4123
+ searchKey = await resolveSearchOnlyKey(targetIndex);
4124
+ } catch (err) {
4125
+ logger.warn(
4126
+ { err: err.message },
4127
+ "implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
4128
+ );
4129
+ }
3889
4130
  }
3890
4131
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
3891
4132
  try {
@@ -3917,9 +4158,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3917
4158
  targetIndex,
3918
4159
  language,
3919
4160
  appId,
3920
- // Names only: the search-only key is provisioned after verification, so
3921
- // every value here is still a placeholder when the agent reads them.
3922
- searchEnvVars: searchEnvVars(language, appId),
4161
+ searchKey,
4162
+ searchEnvVars: searchEnvVars(language, appId, searchKey),
3923
4163
  ingestDir: INGEST_DIR,
3924
4164
  ingestionSource,
3925
4165
  uploadFilePath,
@@ -3929,24 +4169,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3929
4169
  };
3930
4170
  const summaries = [];
3931
4171
  if (uploadWarning) summaries.push(uploadWarning);
3932
- let envSearchKey;
3933
- let envAppIdMismatch = false;
3934
- if (useCases.includes("search") && appId) {
3935
- const envAppId = await readEnvVar(worktree, appIdVar(language));
3936
- if (envAppId === appId) {
3937
- envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
3938
- } else if (envAppId) {
3939
- envAppIdMismatch = true;
3940
- summaries.push(
3941
- `\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
3942
- );
3943
- logger.warn(
3944
- { envAppId, appId },
3945
- "implement: .env holds credentials for a different Algolia application; not reusing its search key"
3946
- );
3947
- }
3948
- }
3949
- let finalSearchEnvVars = input.searchEnvVars;
3950
4172
  let agentRuns = 0;
3951
4173
  let ingestRuntime;
3952
4174
  let ingestEntrypoint;
@@ -4007,7 +4229,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4007
4229
  messages: []
4008
4230
  }) === true;
4009
4231
  if (runNow) {
4010
- const profile = await loadActiveProfile();
4232
+ const ingestApp = await requireApplication();
4233
+ const writeKey = await resolveWriteKey(targetIndex);
4011
4234
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4012
4235
  const scriptLogId = ctx.logStart("runIngestScript", {
4013
4236
  runtime: ingestRuntime,
@@ -4019,8 +4242,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4019
4242
  ingestRuntime,
4020
4243
  ingestEntrypoint,
4021
4244
  {
4022
- [APP_ID_VAR]: profile.appId,
4023
- [API_KEY_VAR]: profile.apiKey
4245
+ [APP_ID_VAR]: ingestApp.id,
4246
+ [API_KEY_VAR]: writeKey
4024
4247
  }
4025
4248
  );
4026
4249
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4139,29 +4362,7 @@ ${run2.output}` : status;
4139
4362
  }
4140
4363
  extraInstructions = verificationRetryInstructions(verification);
4141
4364
  }
4142
- let searchKey;
4143
- let searchKeyError;
4144
- if (appId) {
4145
- try {
4146
- const resolved = await resolveSearchOnlyKey(
4147
- targetIndex,
4148
- appId,
4149
- envSearchKey
4150
- );
4151
- searchKey = resolved.key;
4152
- summaries.push(
4153
- resolved.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4154
- );
4155
- } catch (err) {
4156
- searchKeyError = err.message;
4157
- logger.warn(
4158
- { err: searchKeyError },
4159
- "implement: could not provision a search-only API key; the .env value stays a placeholder"
4160
- );
4161
- }
4162
- }
4163
- finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
4164
- const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4365
+ const resolvedSearchEnvVars = input.searchEnvVars.filter(
4165
4366
  (v) => !v.value.startsWith("<")
4166
4367
  );
4167
4368
  if (resolvedSearchEnvVars.length > 0) {
@@ -4172,29 +4373,13 @@ ${run2.output}` : status;
4172
4373
  if (written.length > 0) {
4173
4374
  summaries.push(`Wrote ${written.join(", ")} to .env.`);
4174
4375
  }
4175
- const stale = [];
4176
- for (const v of resolvedSearchEnvVars) {
4177
- if (written.includes(v.name)) continue;
4178
- const current = await readEnvVar(worktree, v.name);
4179
- if (current && current !== v.value) stale.push(v);
4180
- }
4181
- if (stale.length > 0 && !envAppIdMismatch) {
4182
- summaries.push(
4183
- `\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
4184
- );
4185
- logger.warn(
4186
- { vars: stale.map((v) => v.name) },
4187
- "implement: .env holds different values for the resolved search credentials; not overwriting them"
4188
- );
4189
- }
4190
4376
  }
4191
- const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
4377
+ const unresolvedSearchEnvVars = input.searchEnvVars.filter(
4192
4378
  (v) => v.value.startsWith("<")
4193
4379
  );
4194
4380
  if (unresolvedSearchEnvVars.length > 0) {
4195
4381
  summaries.push(
4196
- `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
4197
- (searchKeyError ? ` Reason: ${searchKeyError}` : "")
4382
+ `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
4198
4383
  );
4199
4384
  }
4200
4385
  } else {
@@ -4226,7 +4411,7 @@ ${run2.output}` : status;
4226
4411
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4227
4412
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
4228
4413
  } : {},
4229
- ...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
4414
+ ...useCases.includes("search") ? { searchEnvVars: input.searchEnvVars } : {}
4230
4415
  };
4231
4416
  } finally {
4232
4417
  process.chdir(repoRoot);
@@ -4269,8 +4454,8 @@ var defaultWorkflow = {
4269
4454
  defineStep({
4270
4455
  id: "select-index",
4271
4456
  title: "Set up index",
4272
- outputSchema: z25.object({
4273
- selection: z25.string()
4457
+ outputSchema: z27.object({
4458
+ selection: z27.string()
4274
4459
  }),
4275
4460
  run: (ctx) => selectIndexStep(ctx)
4276
4461
  }),
@@ -4549,7 +4734,7 @@ function parseCliArgs(argv) {
4549
4734
 
4550
4735
  // src/lib/resetState.ts
4551
4736
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4552
- import { join as join12 } from "node:path";
4737
+ import { join as join11 } from "node:path";
4553
4738
  var KEEP = ["wizard.log"];
4554
4739
  async function resetProjectState() {
4555
4740
  const dir = stateDir();
@@ -4561,7 +4746,7 @@ async function resetProjectState() {
4561
4746
  }
4562
4747
  const targets = entries.filter((name) => !KEEP.includes(name));
4563
4748
  await Promise.all(
4564
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4749
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4565
4750
  );
4566
4751
  return { dir, removed: targets };
4567
4752
  }
@@ -4616,31 +4801,38 @@ ${formatStepList(workflow)}`);
4616
4801
  }
4617
4802
  async function run(workflow) {
4618
4803
  const store = useWizard.getState();
4619
- let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4804
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4805
+ await store.waitForStart();
4620
4806
  let user = await getUser();
4621
4807
  if (!user) {
4622
- await instance.waitUntilRenderFlush();
4623
- instance.cleanup();
4808
+ store.beginAuth();
4624
4809
  try {
4625
4810
  await runAuthLogin();
4626
4811
  } catch (err) {
4627
- console.error(err instanceof Error ? err.message : String(err));
4812
+ store.setError(err instanceof Error ? err.message : String(err));
4813
+ await instance.waitUntilExit();
4628
4814
  process.exit(1);
4629
4815
  }
4630
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4816
+ store.endAuth();
4631
4817
  user = await getUser();
4632
4818
  if (!user) {
4633
4819
  store.setError(
4634
- "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."
4635
4821
  );
4636
4822
  await instance.waitUntilExit();
4637
4823
  process.exit(1);
4638
4824
  }
4639
4825
  }
4640
4826
  store.setUser(user);
4641
- const profile = await loadActiveProfile();
4642
- await store.waitForStart();
4643
- 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);
4644
4836
  }
4645
4837
  var started = await startup();
4646
4838
  if (typeof started === "number") {