@algolia/wizard 0.8.0-rc.49.42 → 0.8.0-rc.53.46

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 +800 -649
  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,37 +631,37 @@ 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;
@@ -564,7 +698,7 @@ function SelectPrompt({
564
698
  if (multi) hints.push({ key: "[space]", label: "select" });
565
699
  hints.push({ key: "[enter]", label: "confirm" });
566
700
  const containerRef = useRef2(null);
567
- const { columns } = useWindowSize3();
701
+ const { columns } = useWindowSize4();
568
702
  const [width, setWidth] = useState3(columns);
569
703
  useLayoutEffect(() => {
570
704
  if (containerRef.current) {
@@ -609,53 +743,53 @@ function SelectPrompt({
609
743
  }
610
744
  }
611
745
  });
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}`)),
746
+ return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
747
+ error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
748
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
615
749
  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 })
750
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
751
+ question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
752
+ helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
619
753
  ] }),
620
- /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
754
+ /* @__PURE__ */ jsx4(Box5, { flexDirection: "column", children: rows.map((option, i) => {
621
755
  const highlighted = i === index;
622
756
  const isCancel = i === cancelIndex;
623
757
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
624
758
  const sec = isCancel ? void 0 : secondary?.[i];
625
759
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
626
- const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
760
+ const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
627
761
  highlighted ? "\u276F " : " ",
628
762
  bullet,
629
763
  option
630
764
  ] });
631
765
  const isText = sec?.kind === "text";
632
- return /* @__PURE__ */ jsxs3(
633
- Box4,
766
+ return /* @__PURE__ */ jsxs4(
767
+ Box5,
634
768
  {
635
769
  width: isText ? "100%" : barWidth,
636
770
  paddingX: 1,
637
771
  paddingY: 1,
638
772
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
639
773
  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,
774
+ /* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
775
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
776
+ Text5,
643
777
  {
644
778
  wrap: "truncate",
645
779
  color: highlighted ? COLORS.primary : COLORS.muted,
646
780
  children: sec.value
647
781
  }
648
782
  ) }),
649
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
783
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
650
784
  ]
651
785
  },
652
786
  `row-${i}`
653
787
  );
654
788
  }) }),
655
- /* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
789
+ /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
656
790
  i > 0 ? " " : "",
657
- /* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
658
- /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
791
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
792
+ /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
659
793
  " ",
660
794
  label
661
795
  ] })
@@ -664,7 +798,7 @@ function SelectPrompt({
664
798
  }
665
799
 
666
800
  // src/ui/PromptInput.tsx
667
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
801
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
668
802
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
669
803
  function EnterToContinuePrompt({
670
804
  question,
@@ -675,10 +809,10 @@ function EnterToContinuePrompt({
675
809
  if (key.return) onDecide(true);
676
810
  else if (key.escape) onDecide(false);
677
811
  });
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: [
812
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
813
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
814
+ question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
815
+ /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
682
816
  /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
683
817
  /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
684
818
  ] })
@@ -688,11 +822,11 @@ function PromptInput() {
688
822
  const { phase, inputReq, submitInput } = useWizard();
689
823
  const [draft, setDraft] = useState4("");
690
824
  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" }) });
825
+ return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
692
826
  }
693
827
  if (phase !== "awaitingInput" || !inputReq) return null;
694
828
  if (inputReq.promptType === "multipleChoice") {
695
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
829
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
696
830
  SelectPrompt,
697
831
  {
698
832
  question: inputReq.prompt,
@@ -709,7 +843,7 @@ function PromptInput() {
709
843
  ) });
710
844
  }
711
845
  if (inputReq.promptType === "multiSelect") {
712
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
846
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
713
847
  SelectPrompt,
714
848
  {
715
849
  multi: true,
@@ -724,7 +858,7 @@ function PromptInput() {
724
858
  ) });
725
859
  }
726
860
  if (inputReq.promptType === "notice") {
727
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
861
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
728
862
  SelectPrompt,
729
863
  {
730
864
  question: inputReq.prompt,
@@ -746,7 +880,7 @@ function PromptInput() {
746
880
  }
747
881
  if (inputReq.promptType === "acceptReject") {
748
882
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
749
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
883
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
750
884
  SelectPrompt,
751
885
  {
752
886
  question: inputReq.prompt,
@@ -757,11 +891,11 @@ function PromptInput() {
757
891
  }
758
892
  ) });
759
893
  }
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: [
894
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
895
+ inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
896
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
897
+ /* @__PURE__ */ jsxs5(Box6, { children: [
898
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
765
899
  inputReq.prompt,
766
900
  " "
767
901
  ] }),
@@ -783,7 +917,7 @@ function PromptInput() {
783
917
  // src/ui/Welcome.tsx
784
918
  import { dirname as dirname2, join as join3 } from "node:path";
785
919
  import { fileURLToPath } from "node:url";
786
- import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
920
+ import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
787
921
 
788
922
  // src/ui/copy/welcome.ts
789
923
  var sidebarItems = [
@@ -811,27 +945,27 @@ var sidebarItems = [
811
945
 
812
946
  // src/ui/Welcome.tsx
813
947
  import Image, { InkPictureProvider } from "ink-picture";
814
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
948
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
815
949
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
816
950
  function SidebarItem({
817
951
  title,
818
952
  description
819
953
  }) {
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 })
954
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
955
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
956
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
957
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
824
958
  ] }),
825
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
959
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
826
960
  /* @__PURE__ */ jsx6(Spacer, {}),
827
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
961
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
828
962
  ] })
829
963
  ] });
830
964
  }
831
965
  function Welcome() {
832
966
  const confirmStart = useWizard((s) => s.confirmStart);
833
967
  const openLearnMore = useWizard((s) => s.openLearnMore);
834
- const { rows } = useWindowSize4();
968
+ const { rows } = useWindowSize5();
835
969
  useInput3((input, key) => {
836
970
  if (key.return) confirmStart();
837
971
  else if (input === "i") openLearnMore();
@@ -850,15 +984,15 @@ function Welcome() {
850
984
  if (rows < 30) {
851
985
  layout = scales["small"];
852
986
  }
853
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
987
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
854
988
  /* @__PURE__ */ jsx6(
855
- Box6,
989
+ Box7,
856
990
  {
857
991
  paddingY: layout.main.padding.y,
858
992
  paddingX: layout.main.padding.x,
859
993
  flexDirection: "column",
860
994
  justifyContent: "center",
861
- children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
995
+ children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
862
996
  /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
863
997
  Image,
864
998
  {
@@ -870,16 +1004,16 @@ function Welcome() {
870
1004
  protocol: "halfBlock"
871
1005
  }
872
1006
  ) }),
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: [
1007
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1008
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
875
1009
  /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
876
1010
  /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
877
1011
  ] })
878
1012
  ] })
879
1013
  }
880
1014
  ),
881
- /* @__PURE__ */ jsxs5(
882
- Box6,
1015
+ /* @__PURE__ */ jsxs6(
1016
+ Box7,
883
1017
  {
884
1018
  backgroundColor: COLORS.bg.sidebar,
885
1019
  width: 40,
@@ -889,7 +1023,7 @@ function Welcome() {
889
1023
  flexDirection: "column",
890
1024
  justifyContent: "center",
891
1025
  children: [
892
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1026
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
893
1027
  sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
894
1028
  ]
895
1029
  }
@@ -899,7 +1033,7 @@ function Welcome() {
899
1033
 
900
1034
  // src/ui/LearnMore.tsx
901
1035
  import { Fragment as Fragment2 } from "react";
902
- import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
1036
+ import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
903
1037
 
904
1038
  // src/ui/copy/learn-more.ts
905
1039
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -936,7 +1070,7 @@ var policyLinks = [
936
1070
  ];
937
1071
 
938
1072
  // src/ui/LearnMore.tsx
939
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1073
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
940
1074
  var TAG_COLORS = {
941
1075
  READ: COLORS.success,
942
1076
  WRITE: COLORS.badge,
@@ -952,25 +1086,25 @@ function NeverLine({
952
1086
  }) {
953
1087
  const used = segments.reduce((n, s) => n + s.text.length, 0);
954
1088
  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" }),
1089
+ return /* @__PURE__ */ jsxs7(Text8, { children: [
1090
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
957
1091
  " ".repeat(NEVER_BOX_PAD_X),
958
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
1092
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
959
1093
  " ".repeat(rightPad),
960
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" })
1094
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
961
1095
  ] });
962
1096
  }
963
1097
  function LearnMore() {
964
1098
  const confirmStart = useWizard((s) => s.confirmStart);
965
1099
  const backToHome = useWizard((s) => s.backToHome);
966
- const { columns } = useWindowSize5();
1100
+ const { columns } = useWindowSize6();
967
1101
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
968
1102
  useInput4((_input, key) => {
969
1103
  if (key.escape) backToHome();
970
1104
  else if (key.return) confirmStart();
971
1105
  });
972
- return /* @__PURE__ */ jsxs6(
973
- Box7,
1106
+ return /* @__PURE__ */ jsxs7(
1107
+ Box8,
974
1108
  {
975
1109
  flexDirection: "column",
976
1110
  paddingX: PADDING_X,
@@ -978,20 +1112,20 @@ function LearnMore() {
978
1112
  width: "100%",
979
1113
  gap: 1,
980
1114
  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}` })
1115
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1116
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
1117
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
1118
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1119
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1120
+ /* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1121
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
1122
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
1123
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
990
1124
  ] }) })
991
1125
  ] })
992
1126
  ] }, 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` }),
1127
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1128
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
995
1129
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
996
1130
  /* @__PURE__ */ jsx7(
997
1131
  NeverLine,
@@ -1000,7 +1134,7 @@ function LearnMore() {
1000
1134
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1001
1135
  }
1002
1136
  ),
1003
- neverItems.map((item) => /* @__PURE__ */ jsxs6(Fragment2, { children: [
1137
+ neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
1004
1138
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1005
1139
  /* @__PURE__ */ jsx7(
1006
1140
  NeverLine,
@@ -1015,23 +1149,23 @@ function LearnMore() {
1015
1149
  )
1016
1150
  ] }, item)),
1017
1151
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1018
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1152
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1019
1153
  ] }),
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 })
1154
+ /* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1155
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1156
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
1023
1157
  ] }, 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" })
1158
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1159
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1160
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1161
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
1162
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
1029
1163
  ] }),
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" })
1164
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1165
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1166
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "enter" }),
1167
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
1168
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
1035
1169
  ] })
1036
1170
  ] })
1037
1171
  ]
@@ -1040,10 +1174,10 @@ function LearnMore() {
1040
1174
  }
1041
1175
 
1042
1176
  // src/ui/Sidebar.tsx
1043
- import { Box as Box10, Text as Text10 } from "ink";
1177
+ import { Box as Box11, Text as Text11 } from "ink";
1044
1178
 
1045
1179
  // src/ui/Steps.tsx
1046
- import { Box as Box8, Text as Text8 } from "ink";
1180
+ import { Box as Box9, Text as Text9 } from "ink";
1047
1181
  import Spinner from "ink-spinner";
1048
1182
 
1049
1183
  // src/core/persistence.ts
@@ -1072,11 +1206,11 @@ async function clearWorkflowState(workflowId) {
1072
1206
  }
1073
1207
 
1074
1208
  // src/ui/Steps.tsx
1075
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1209
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1076
1210
  function Steps() {
1077
1211
  const { steps } = useWizard();
1078
1212
  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: [
1213
+ 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
1214
  s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1081
1215
  " ",
1082
1216
  s.title
@@ -1086,7 +1220,7 @@ function CurrentStep() {
1086
1220
  const { steps } = useWizard();
1087
1221
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1088
1222
  if (!currentStep) return null;
1089
- return /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
1223
+ return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
1090
1224
  /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1091
1225
  " ",
1092
1226
  ` ${currentStep.title}`
@@ -1094,19 +1228,19 @@ function CurrentStep() {
1094
1228
  }
1095
1229
 
1096
1230
  // 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";
1231
+ import { Box as Box10, Text as Text10 } from "ink";
1232
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1099
1233
  function Progress() {
1100
1234
  const { steps, currentStepIndex } = useWizard();
1101
1235
  const visibleSteps = steps.filter(isStepVisible);
1102
1236
  if (visibleSteps.length === 0) return null;
1103
1237
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1104
1238
  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 })
1239
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1240
+ /* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
1241
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
1242
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
1243
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
1110
1244
  ] });
1111
1245
  }
1112
1246
 
@@ -1117,10 +1251,10 @@ var sidebarCommands = [
1117
1251
  ];
1118
1252
 
1119
1253
  // src/ui/Sidebar.tsx
1120
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1254
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1121
1255
  function Sidebar() {
1122
- return /* @__PURE__ */ jsxs9(
1123
- Box10,
1256
+ return /* @__PURE__ */ jsxs10(
1257
+ Box11,
1124
1258
  {
1125
1259
  backgroundColor: "#14171E",
1126
1260
  width: 30,
@@ -1129,16 +1263,16 @@ function Sidebar() {
1129
1263
  flexDirection: "column",
1130
1264
  justifyContent: "space-between",
1131
1265
  children: [
1132
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1133
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
1266
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1267
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
1134
1268
  /* @__PURE__ */ jsx10(Steps, {})
1135
1269
  ] }),
1136
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1270
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1137
1271
  /* @__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 })
1272
+ /* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
1273
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1274
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1275
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
1142
1276
  ] });
1143
1277
  }) })
1144
1278
  ] })
@@ -1148,12 +1282,12 @@ function Sidebar() {
1148
1282
  }
1149
1283
 
1150
1284
  // 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";
1285
+ import { Box as Box12, Text as Text12 } from "ink";
1286
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1153
1287
  function Ribbon() {
1154
1288
  const firstCommand = sidebarCommands[0];
1155
- return /* @__PURE__ */ jsxs10(
1156
- Box11,
1289
+ return /* @__PURE__ */ jsxs11(
1290
+ Box12,
1157
1291
  {
1158
1292
  backgroundColor: "#14171E",
1159
1293
  flexDirection: "row",
@@ -1163,9 +1297,9 @@ function Ribbon() {
1163
1297
  children: [
1164
1298
  /* @__PURE__ */ jsx11(Progress, {}),
1165
1299
  /* @__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 })
1300
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1301
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1302
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
1169
1303
  ] })
1170
1304
  ]
1171
1305
  }
@@ -1177,8 +1311,8 @@ import { useState as useState6 } from "react";
1177
1311
 
1178
1312
  // src/ui/Logs.tsx
1179
1313
  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";
1314
+ import { Box as Box13, Text as Text13, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1315
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1182
1316
  var KIND_COLOR = {
1183
1317
  tool: COLORS.primary,
1184
1318
  prompt: COLORS.badge
@@ -1208,7 +1342,7 @@ function formatTimestamp(ms) {
1208
1342
  }
1209
1343
  function Logs() {
1210
1344
  const logs = useWizard((s) => s.logs);
1211
- const { rows, columns } = useWindowSize6();
1345
+ const { rows, columns } = useWindowSize7();
1212
1346
  const viewportRef = useRef3(null);
1213
1347
  const [viewportHeight, setViewportHeight] = useState5(0);
1214
1348
  const [viewportWidth, setViewportWidth] = useState5(0);
@@ -1245,10 +1379,10 @@ function Logs() {
1245
1379
  const visible = logs.slice(scrollOffset, scrollOffset + capacity);
1246
1380
  const hiddenAbove = scrollOffset;
1247
1381
  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: [
1382
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1383
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
1384
+ /* @__PURE__ */ jsxs12(Box13, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1385
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1252
1386
  "\u2191 ",
1253
1387
  hiddenAbove,
1254
1388
  " more"
@@ -1263,20 +1397,20 @@ function Logs() {
1263
1397
  const name = truncate2(entry.name, budget);
1264
1398
  budget -= name.length;
1265
1399
  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 })
1400
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
1401
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
1402
+ /* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
1403
+ preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
1404
+ durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
1271
1405
  ] }, entry.id);
1272
1406
  }),
1273
- hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1407
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1274
1408
  "\u2193 ",
1275
1409
  hiddenBelow,
1276
1410
  " more"
1277
1411
  ] })
1278
1412
  ] }),
1279
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1413
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1280
1414
  ] });
1281
1415
  }
1282
1416
 
@@ -1468,11 +1602,11 @@ function track(event, payload) {
1468
1602
  }
1469
1603
 
1470
1604
  // src/ui/App.tsx
1471
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1605
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1472
1606
  function App() {
1473
1607
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1474
1608
  const { exit } = useApp();
1475
- const { columns, rows } = useWindowSize7();
1609
+ const { columns, rows } = useWindowSize8();
1476
1610
  const [showLogs, setShowLogs] = useState6(false);
1477
1611
  const finished = phase === "done" || phase === "error";
1478
1612
  const currentStep = steps[currentStepIndex];
@@ -1485,7 +1619,7 @@ function App() {
1485
1619
  { isActive: finished }
1486
1620
  );
1487
1621
  useInput6((_input, key) => {
1488
- if (phase === "idle" || phase === "preflight") return;
1622
+ if (phase === "idle" || phase === "authenticating") return;
1489
1623
  if (key.tab) {
1490
1624
  setShowLogs(!showLogs);
1491
1625
  track("AI Wizard Interaction", {
@@ -1495,7 +1629,7 @@ function App() {
1495
1629
  });
1496
1630
  }
1497
1631
  });
1498
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1632
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1499
1633
  useInput6((_input, key) => {
1500
1634
  if (escOwnedElsewhere) return;
1501
1635
  if (key.escape) {
@@ -1508,53 +1642,67 @@ function App() {
1508
1642
  exit();
1509
1643
  }
1510
1644
  });
1511
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1645
+ const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1512
1646
  const flexDirection = columns > 90 ? "row" : "column";
1513
1647
  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
- }
1648
+ return (
1649
+ /* Exactly the viewport, clipped — never `minHeight`, which lets the frame
1650
+ grow past the terminal. Ink then abandons diffing to clear and repaint
1651
+ the whole screen, and the scrolling that frame causes throws off its
1652
+ cursor arithmetic: flicker and leftover rows, worst when a burst of CLI
1653
+ output is swapped out. Clipping drops the bottom of an over-tall frame;
1654
+ the per-panel row budgets are what keep it from coming to that. */
1655
+ /* @__PURE__ */ jsxs13(
1656
+ Box14,
1657
+ {
1658
+ backgroundColor: COLORS.bg.main,
1659
+ flexDirection: "row",
1660
+ width: columns,
1661
+ height: rows,
1662
+ overflow: "hidden",
1663
+ children: [
1664
+ mainWindowVisible && /* @__PURE__ */ jsxs13(
1665
+ Box14,
1666
+ {
1667
+ flexDirection,
1668
+ width: "100%",
1669
+ justifyContent: "space-between",
1670
+ children: [
1671
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1672
+ /* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
1673
+ /* @__PURE__ */ jsxs13(
1674
+ Box14,
1675
+ {
1676
+ flexDirection: "column",
1677
+ paddingX: 4,
1678
+ paddingY: 2,
1679
+ width: showSidebar ? 70 : "100%",
1680
+ flexGrow: showSidebar ? 1 : 0,
1681
+ children: [
1682
+ phase === "authenticating" && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
1683
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1684
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1685
+ ] }),
1686
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1687
+ /* @__PURE__ */ jsx13(Notices, {}),
1688
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1689
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1690
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
1691
+ "\u2716 ",
1692
+ error
1693
+ ] }) })
1694
+ ]
1695
+ }
1696
+ )
1697
+ ),
1698
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1699
+ ]
1700
+ }
1701
+ ),
1702
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1703
+ ]
1704
+ }
1705
+ )
1558
1706
  );
1559
1707
  }
1560
1708
 
@@ -1568,8 +1716,7 @@ var configFile = () => join5(stateDir(), "config.json");
1568
1716
  var DEFAULT_CONFIG = {
1569
1717
  version: 1,
1570
1718
  aiConsent: false,
1571
- workflowsRun: [],
1572
- searchApiKeys: {}
1719
+ workflowsRun: []
1573
1720
  };
1574
1721
  async function loadConfig() {
1575
1722
  try {
@@ -1588,38 +1735,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
1588
1735
  config.workflowsRun.push({ workflowId, completedAt });
1589
1736
  await saveConfig(config);
1590
1737
  }
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
1738
 
1624
1739
  // src/core/orchestrator.ts
1625
1740
  function defineStep(step) {
@@ -1823,61 +1938,138 @@ async function runWorkflow(workflow, appId) {
1823
1938
  }
1824
1939
  }
1825
1940
 
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;
1941
+ // src/lib/algoliaApp.ts
1942
+ import { z as z4 } from "zod";
1943
+ var applicationSchema = z4.object({
1944
+ id: z4.string().min(1),
1945
+ name: z4.string().default(""),
1946
+ plan: z4.string().optional()
1947
+ });
1948
+ var listSchema = z4.array(
1949
+ z4.object({
1950
+ id: z4.string().min(1),
1951
+ name: z4.string().default(""),
1952
+ plan_label: z4.string().optional()
1953
+ }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1954
+ );
1955
+ async function currentApplication() {
1956
+ let raw;
1839
1957
  try {
1840
- parsed = parseToml(tomlText);
1958
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1841
1959
  } catch {
1842
- return [];
1960
+ return null;
1961
+ }
1962
+ const parsed = applicationSchema.safeParse(parseJson(raw));
1963
+ return parsed.success ? parsed.data : null;
1964
+ }
1965
+ async function requireApplication() {
1966
+ const app = await currentApplication();
1967
+ if (!app) {
1968
+ throw new Error(
1969
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
1970
+ );
1971
+ }
1972
+ return app;
1973
+ }
1974
+ async function listApplications() {
1975
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
1976
+ const parsed = listSchema.safeParse(parseJson(raw));
1977
+ if (!parsed.success) {
1978
+ throw new Error("Could not read the list of Algolia applications.");
1979
+ }
1980
+ return parsed.data;
1981
+ }
1982
+ async function selectApplication(id) {
1983
+ const raw = await runAlgoliaCli(
1984
+ ["application", "select", "--non-interactive", "--app-id", id],
1985
+ { onOutput: stderrSink }
1986
+ );
1987
+ const parsed = applicationSchema.safeParse(parseJson(raw));
1988
+ if (!parsed.success) {
1989
+ throw new Error(
1990
+ `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
1991
+ );
1843
1992
  }
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;
1993
+ return parsed.data;
1994
+ }
1995
+ function parseJson(text) {
1857
1996
  try {
1858
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
1997
+ return JSON.parse(text);
1859
1998
  } catch {
1860
- profiles = [];
1999
+ return void 0;
1861
2000
  }
1862
- const profile = profiles[0];
1863
- if (!profile) {
2001
+ }
2002
+
2003
+ // src/lib/algoliaAppPicker.ts
2004
+ function secondaryFor(app) {
2005
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
2006
+ }
2007
+ function labelFor(app) {
2008
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2009
+ }
2010
+ function selectAndReport(app) {
2011
+ useWizard.getState().pushCliOutput(
2012
+ "stdout",
2013
+ `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
2014
+ );
2015
+ return selectApplication(app.id);
2016
+ }
2017
+ async function promptForApplication() {
2018
+ const store = useWizard.getState();
2019
+ const apps = await listApplications();
2020
+ if (apps.length === 0) {
1864
2021
  throw new Error(
1865
- "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
2022
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2023
+ );
2024
+ }
2025
+ if (apps.length === 1) {
2026
+ const only = apps[0];
2027
+ logger.info(
2028
+ { app: only.id },
2029
+ "single application on the account; selecting it"
1866
2030
  );
2031
+ return selectAndReport(only);
2032
+ }
2033
+ const messages = ["Which Algolia application should the wizard work in?"];
2034
+ for (; ; ) {
2035
+ const choice = await store.requestUserInput({
2036
+ prompt: "Select an application",
2037
+ promptType: "multipleChoice",
2038
+ options: apps.map(labelFor),
2039
+ secondary: apps.map(secondaryFor),
2040
+ messages
2041
+ });
2042
+ const chosen = apps.find((app) => labelFor(app) === choice);
2043
+ if (!chosen) {
2044
+ throw new Error("Application picker received an unexpected selection");
2045
+ }
2046
+ try {
2047
+ return await selectAndReport(chosen);
2048
+ } catch (err) {
2049
+ logger.warn(
2050
+ { app: chosen.id, err: err.message },
2051
+ "application select failed; re-prompting"
2052
+ );
2053
+ messages.push(
2054
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2055
+ );
2056
+ }
1867
2057
  }
1868
- return profile;
2058
+ }
2059
+ async function ensureApplication() {
2060
+ return await currentApplication() ?? await promptForApplication();
1869
2061
  }
1870
2062
 
1871
2063
  // src/workflows/default.ts
1872
- import { z as z25 } from "zod";
2064
+ import { z as z27 } from "zod";
1873
2065
 
1874
2066
  // 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)
2067
+ import { z as z5 } from "zod";
2068
+ var indicesListSchema = z5.object({
2069
+ items: z5.array(
2070
+ z5.object({
2071
+ name: z5.string(),
2072
+ entries: z5.number().default(0)
1881
2073
  })
1882
2074
  )
1883
2075
  });
@@ -1948,12 +2140,12 @@ import "zod";
1948
2140
 
1949
2141
  // src/lib/tools/listFiles.ts
1950
2142
  import { tool } from "ai";
1951
- import z4 from "zod";
2143
+ import z6 from "zod";
1952
2144
  import { readdir } from "node:fs/promises";
1953
2145
 
1954
2146
  // src/lib/tools/path.ts
1955
2147
  import { lstat } from "node:fs/promises";
1956
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2148
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1957
2149
  function resolveInRoot(ctx, path) {
1958
2150
  const target = resolve2(ctx.cwd, path);
1959
2151
  const rel = relative(ctx.root, target);
@@ -1969,7 +2161,7 @@ async function hasSymlinkParent(ctx, target) {
1969
2161
  let current = ctx.root;
1970
2162
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1971
2163
  for (const part of parts) {
1972
- current = join7(current, part);
2164
+ current = join6(current, part);
1973
2165
  try {
1974
2166
  if ((await lstat(current)).isSymbolicLink()) return true;
1975
2167
  } catch (err) {
@@ -1984,7 +2176,7 @@ async function hasSymlinkParent(ctx, target) {
1984
2176
  function listFilesTool(ctx) {
1985
2177
  return tool({
1986
2178
  description: "List files in the current working directory",
1987
- inputSchema: z4.object(),
2179
+ inputSchema: z6.object(),
1988
2180
  execute: async () => {
1989
2181
  logger.info("called listFiles tool");
1990
2182
  if (++ctx.counts.list > ctx.limits.list) {
@@ -2000,13 +2192,13 @@ function listFilesTool(ctx) {
2000
2192
 
2001
2193
  // src/lib/tools/changeDirectory.ts
2002
2194
  import { tool as tool2 } from "ai";
2003
- import z5 from "zod";
2195
+ import z7 from "zod";
2004
2196
  import { stat } from "node:fs/promises";
2005
2197
  function changeDirectoryTool(ctx) {
2006
2198
  return tool2({
2007
2199
  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")
2200
+ inputSchema: z7.object({
2201
+ path: z7.string().describe("Directory to change into")
2010
2202
  }),
2011
2203
  execute: async ({ path }) => {
2012
2204
  logger.info({ path }, "called changeDirectory tool");
@@ -2028,13 +2220,13 @@ function changeDirectoryTool(ctx) {
2028
2220
 
2029
2221
  // src/lib/tools/reportStatus.ts
2030
2222
  import { tool as tool3 } from "ai";
2031
- import z6 from "zod";
2223
+ import z8 from "zod";
2032
2224
  function reportStatusTool(output) {
2033
2225
  return tool3({
2034
2226
  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(),
2227
+ inputSchema: z8.object({
2228
+ status: z8.enum(["success", "fail"]),
2229
+ reason: z8.string().optional(),
2038
2230
  output
2039
2231
  }),
2040
2232
  execute: async ({ status, reason, output: output2 }) => {
@@ -2046,8 +2238,8 @@ function reportStatusTool(output) {
2046
2238
 
2047
2239
  // src/lib/tools/readFile.ts
2048
2240
  import { tool as tool4 } from "ai";
2049
- import z7 from "zod";
2050
- import { readFile as readFile4 } from "node:fs/promises";
2241
+ import z9 from "zod";
2242
+ import { readFile as readFile3 } from "node:fs/promises";
2051
2243
 
2052
2244
  // src/lib/tools/env.ts
2053
2245
  import { basename } from "node:path";
@@ -2074,8 +2266,8 @@ function redactEnvValues(content) {
2074
2266
  function readFileTool(ctx) {
2075
2267
  return tool4({
2076
2268
  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")
2269
+ inputSchema: z9.object({
2270
+ filePath: z9.string().describe("Path to the file to read")
2079
2271
  }),
2080
2272
  execute: async ({ filePath }) => {
2081
2273
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2085,7 +2277,7 @@ function readFileTool(ctx) {
2085
2277
  const resolved = resolveInRoot(ctx, filePath);
2086
2278
  if (!resolved.ok) return resolved.error;
2087
2279
  try {
2088
- const content = await readFile4(resolved.target, "utf8");
2280
+ const content = await readFile3(resolved.target, "utf8");
2089
2281
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2090
2282
  } catch (err) {
2091
2283
  return `Error reading ${filePath}: ${err.message}`;
@@ -2096,15 +2288,15 @@ function readFileTool(ctx) {
2096
2288
 
2097
2289
  // src/lib/tools/writeFile.ts
2098
2290
  import { tool as tool5 } from "ai";
2099
- import z8 from "zod";
2291
+ import z10 from "zod";
2100
2292
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2101
2293
  import { dirname as dirname4 } from "node:path";
2102
2294
  function writeFileTool(ctx) {
2103
2295
  return tool5({
2104
2296
  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")
2297
+ inputSchema: z10.object({
2298
+ filePath: z10.string().describe("Path to the file to write"),
2299
+ content: z10.string().describe("Content to write to the file")
2108
2300
  }),
2109
2301
  execute: async ({ filePath, content }) => {
2110
2302
  logger.info({ filePath }, "called writeFile tool");
@@ -2129,9 +2321,95 @@ function writeFileTool(ctx) {
2129
2321
 
2130
2322
  // src/lib/tools/writeAlgoliaCredentials.ts
2131
2323
  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";
2324
+ import z12 from "zod";
2325
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2134
2326
  import { dirname as dirname5 } from "node:path";
2327
+
2328
+ // src/lib/algoliaApiKey.ts
2329
+ import { z as z11 } from "zod";
2330
+ var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2331
+ var WRITE_ACLS = [
2332
+ "addObject",
2333
+ "deleteObject",
2334
+ "settings",
2335
+ "editSettings",
2336
+ "listIndexes"
2337
+ ];
2338
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2339
+ var apiKeySchema = z11.object({
2340
+ value: z11.string().min(1),
2341
+ acl: z11.array(z11.string()).default([]),
2342
+ indexes: z11.array(z11.string()).default([])
2343
+ });
2344
+ var apiKeyListSchema = z11.object({
2345
+ items: z11.array(apiKeySchema).optional(),
2346
+ keys: z11.array(apiKeySchema).optional()
2347
+ }).transform((o) => o.items ?? o.keys ?? []);
2348
+ var createdKeySchema = z11.object({
2349
+ key: z11.string().min(1).optional(),
2350
+ value: z11.string().min(1).optional()
2351
+ });
2352
+ function canReuse(key, index) {
2353
+ return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2354
+ }
2355
+ async function createSearchKey(index) {
2356
+ const stdout = await runAlgoliaCli([
2357
+ "apikeys",
2358
+ "create",
2359
+ "--indices",
2360
+ index,
2361
+ "--acl",
2362
+ "search,browse",
2363
+ "--description",
2364
+ `wizard search-only key for ${index}`,
2365
+ "-o",
2366
+ "json"
2367
+ ]);
2368
+ const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2369
+ const created = key ?? value;
2370
+ if (!created) throw new Error("apikeys create returned no key value");
2371
+ return created;
2372
+ }
2373
+ function canReuseForWrites(key, index) {
2374
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2375
+ }
2376
+ async function resolveWriteKey(index) {
2377
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2378
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2379
+ if (existing) {
2380
+ logger.info({ index }, "reusing existing write API key");
2381
+ return existing;
2382
+ }
2383
+ logger.info({ index }, "no reusable write key found; creating one");
2384
+ const created = await runAlgoliaCli([
2385
+ "apikeys",
2386
+ "create",
2387
+ "--indices",
2388
+ index,
2389
+ "--acl",
2390
+ WRITE_ACLS.join(","),
2391
+ "--description",
2392
+ `wizard write key for ${index}`,
2393
+ "-o",
2394
+ "json"
2395
+ ]);
2396
+ const { key, value } = createdKeySchema.parse(JSON.parse(created));
2397
+ const writeKey = key ?? value;
2398
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2399
+ return writeKey;
2400
+ }
2401
+ async function resolveSearchOnlyKey(index) {
2402
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2403
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2404
+ if (existing) {
2405
+ logger.info({ index }, "reusing existing search-only API key");
2406
+ return existing;
2407
+ }
2408
+ logger.info({ index }, "no reusable search-only key found; creating one");
2409
+ return createSearchKey(index);
2410
+ }
2411
+
2412
+ // src/lib/tools/writeAlgoliaCredentials.ts
2135
2413
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2136
2414
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2137
2415
  function appendEnv(content, entries) {
@@ -2145,9 +2423,9 @@ function hasEnv(content, name) {
2145
2423
  }
2146
2424
  function writeCredentialsTool(ctx) {
2147
2425
  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(
2426
+ 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.`,
2427
+ inputSchema: z12.object({
2428
+ filePath: z12.string().describe(
2151
2429
  'Path to the env file to write credentials into (e.g. ".env")'
2152
2430
  )
2153
2431
  }),
@@ -2155,11 +2433,17 @@ function writeCredentialsTool(ctx) {
2155
2433
  logger.info({ filePath }, "called writeCredentials tool");
2156
2434
  const resolved = resolveInRoot(ctx, filePath);
2157
2435
  if (resolved.ok === false) return resolved.error;
2158
- let profile;
2436
+ const targetIndex = useWizard.getState().targetIndex;
2437
+ if (!targetIndex) {
2438
+ return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2439
+ }
2440
+ let appId;
2441
+ let writeKey;
2159
2442
  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.";
2443
+ appId = (await requireApplication()).id;
2444
+ writeKey = await resolveWriteKey(targetIndex);
2445
+ } catch (err) {
2446
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2163
2447
  }
2164
2448
  try {
2165
2449
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2167,7 +2451,7 @@ function writeCredentialsTool(ctx) {
2167
2451
  }
2168
2452
  let existing = "";
2169
2453
  try {
2170
- existing = await readFile5(resolved.target, "utf8");
2454
+ existing = await readFile4(resolved.target, "utf8");
2171
2455
  } catch (err) {
2172
2456
  if (err.code !== "ENOENT") throw err;
2173
2457
  }
@@ -2178,8 +2462,8 @@ function writeCredentialsTool(ctx) {
2178
2462
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2179
2463
  }
2180
2464
  const envWithCredentials = appendEnv(existing, [
2181
- [APP_ID_VAR, profile.appId],
2182
- [API_KEY_VAR, profile.apiKey]
2465
+ [APP_ID_VAR, appId],
2466
+ [API_KEY_VAR, writeKey]
2183
2467
  ]);
2184
2468
  await mkdir4(dirname5(resolved.target), { recursive: true });
2185
2469
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2193,16 +2477,16 @@ function writeCredentialsTool(ctx) {
2193
2477
 
2194
2478
  // src/lib/tools/searchFiles.ts
2195
2479
  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";
2480
+ import z13 from "zod";
2481
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2482
+ import { join as join7 } from "node:path";
2199
2483
  var MAX_QUERY_LENGTH = 1e3;
2200
2484
  async function walkFiles(dir) {
2201
2485
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2202
2486
  const out = [];
2203
2487
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2204
2488
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2205
- const full = join8(dir, e.name);
2489
+ const full = join7(dir, e.name);
2206
2490
  if (e.isDirectory()) out.push(...await walkFiles(full));
2207
2491
  else if (e.isFile()) out.push(full);
2208
2492
  }
@@ -2211,9 +2495,9 @@ async function walkFiles(dir) {
2211
2495
  function searchFilesTool(ctx) {
2212
2496
  return tool7({
2213
2497
  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)")
2498
+ inputSchema: z13.object({
2499
+ query: z13.string().describe("JavaScript RegExp pattern to search for"),
2500
+ path: z13.string().optional().describe("Directory to search in (default: cwd)")
2217
2501
  }),
2218
2502
  execute: async ({ query, path = "." }) => {
2219
2503
  logger.info({ query, path }, "called searchFiles tool");
@@ -2235,7 +2519,7 @@ function searchFilesTool(ctx) {
2235
2519
  for (const file of await walkFiles(resolved.target)) {
2236
2520
  let content;
2237
2521
  try {
2238
- content = await readFile6(file, "utf8");
2522
+ content = await readFile5(file, "utf8");
2239
2523
  } catch {
2240
2524
  continue;
2241
2525
  }
@@ -2257,7 +2541,7 @@ function searchFilesTool(ctx) {
2257
2541
 
2258
2542
  // src/lib/tools/verifyImplementation.ts
2259
2543
  import { tool as tool8 } from "ai";
2260
- import z11 from "zod";
2544
+ import z14 from "zod";
2261
2545
 
2262
2546
  // src/lib/tools/utils/runCommand.ts
2263
2547
  import { spawn as spawn2 } from "node:child_process";
@@ -2279,9 +2563,9 @@ function runCommand(command, args, cwd) {
2279
2563
  }
2280
2564
 
2281
2565
  // src/lib/tools/utils/packageManager.ts
2282
- import { readFile as readFile7 } from "node:fs/promises";
2566
+ import { readFile as readFile6 } from "node:fs/promises";
2283
2567
  import { existsSync } from "node:fs";
2284
- import { join as join9 } from "node:path";
2568
+ import { join as join8 } from "node:path";
2285
2569
  var LOCKFILES = [
2286
2570
  ["pnpm-lock.yaml", "pnpm"],
2287
2571
  ["yarn.lock", "yarn"],
@@ -2290,13 +2574,13 @@ var LOCKFILES = [
2290
2574
  ["package-lock.json", "npm"]
2291
2575
  ];
2292
2576
  async function readPackageJson(cwd = process.cwd()) {
2293
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2577
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2294
2578
  }
2295
2579
  function packageManagerFrom(pkg) {
2296
2580
  return pkg.packageManager?.split("@")[0] ?? "npm";
2297
2581
  }
2298
2582
  function packageManagerFromLockfile(cwd) {
2299
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2583
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2300
2584
  }
2301
2585
  async function detectPackageManager(cwd) {
2302
2586
  try {
@@ -2337,7 +2621,7 @@ async function runRepoVerificationCheck() {
2337
2621
  function verifyImplementationTool() {
2338
2622
  return tool8({
2339
2623
  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(),
2624
+ inputSchema: z14.object(),
2341
2625
  execute: async () => {
2342
2626
  logger.info("called verifyImplementation tool");
2343
2627
  return runRepoVerificationCheck();
@@ -2351,7 +2635,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2351
2635
  import { nanoid as nanoid2 } from "nanoid";
2352
2636
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2353
2637
  import { dirname as dirname6 } from "node:path";
2354
- import z12 from "zod";
2638
+ import z15 from "zod";
2355
2639
  var DATA_DIR = ".algolia-wizard/data";
2356
2640
  var RECORD_MODEL = "claude-haiku-4-5";
2357
2641
  var MAX_RECORDS = 100;
@@ -2363,17 +2647,17 @@ var anthropic = createAnthropic({
2363
2647
  function generateRecordTool(ctx) {
2364
2648
  return tool9({
2365
2649
  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.")
2650
+ inputSchema: z15.object({
2651
+ entityName: z15.string().describe("Name of the entity to generate records for."),
2652
+ attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2653
+ count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2654
+ hint: z15.string().optional().describe("Optional context to steer realistic values.")
2371
2655
  }),
2372
2656
  execute: async ({ entityName, attributes, count, hint }) => {
2373
2657
  logger.info({ entityName, count }, "called generateRecord tool");
2374
2658
  try {
2375
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2376
- const recordSchema = z12.object(
2659
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2660
+ const recordSchema = z15.object(
2377
2661
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2378
2662
  );
2379
2663
  const generateBatch = async (batchCount) => {
@@ -2383,8 +2667,8 @@ function generateRecordTool(ctx) {
2383
2667
  const { output } = await generateText({
2384
2668
  model: anthropic(RECORD_MODEL),
2385
2669
  output: Output.object({
2386
- schema: z12.object({
2387
- records: z12.array(recordSchema).length(batchCount)
2670
+ schema: z15.object({
2671
+ records: z15.array(recordSchema).length(batchCount)
2388
2672
  })
2389
2673
  }),
2390
2674
  prompt: [
@@ -2442,12 +2726,12 @@ function generateRecordTool(ctx) {
2442
2726
 
2443
2727
  // src/lib/tools/notifyUser.ts
2444
2728
  import { tool as tool10 } from "ai";
2445
- import z13 from "zod";
2729
+ import z16 from "zod";
2446
2730
  function notifyUserTool() {
2447
2731
  return tool10({
2448
2732
  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(
2733
+ inputSchema: z16.object({
2734
+ message: z16.string().describe(
2451
2735
  "Short, plain-language description of what you are doing now."
2452
2736
  )
2453
2737
  }),
@@ -2627,10 +2911,10 @@ async function runAgent(req) {
2627
2911
  }
2628
2912
 
2629
2913
  // 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() }))
2914
+ import z19 from "zod";
2915
+ var detectLanguageSchema = z19.object({
2916
+ languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
2917
+ frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
2634
2918
  });
2635
2919
  var detectLanguage = () => runAgent({
2636
2920
  instructions: [
@@ -2648,31 +2932,31 @@ var detectLanguage = () => runAgent({
2648
2932
  });
2649
2933
 
2650
2934
  // src/actions/analyzeCodebase.ts
2651
- import z17 from "zod";
2935
+ import z20 from "zod";
2652
2936
  var READONLY_TOOLS = [
2653
2937
  "listFiles",
2654
2938
  "changeDirectory",
2655
2939
  "readFile",
2656
2940
  "searchFiles"
2657
2941
  ];
2658
- var ingestionAnalysisSchema = z17.object({
2659
- ingestionAnalysis: z17.array(
2660
- z17.object({
2661
- name: z17.string(),
2662
- paths: z17.array(z17.string()),
2942
+ var ingestionAnalysisSchema = z20.object({
2943
+ ingestionAnalysis: z20.array(
2944
+ z20.object({
2945
+ name: z20.string(),
2946
+ paths: z20.array(z20.string()),
2663
2947
  // indexable fields the agent found for this entity
2664
- attributes: z17.array(z17.string())
2948
+ attributes: z20.array(z20.string())
2665
2949
  })
2666
2950
  )
2667
2951
  });
2668
- var searchImplementationAnalysisSchema = z17.object({
2669
- searchImplementationAnalysis: z17.string()
2952
+ var searchImplementationAnalysisSchema = z20.object({
2953
+ searchImplementationAnalysis: z20.string()
2670
2954
  });
2671
- var verificationSchema = z17.object({
2672
- verification: z17.array(z17.string())
2955
+ var verificationSchema = z20.object({
2956
+ verification: z20.array(z20.string())
2673
2957
  });
2674
2958
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
2675
- var analyzeCodebaseSchema = z17.object({
2959
+ var analyzeCodebaseSchema = z20.object({
2676
2960
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2677
2961
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
2678
2962
  verification: verificationSchema.shape.verification.optional(),
@@ -2734,7 +3018,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2734
3018
  // package.json
2735
3019
  var package_default = {
2736
3020
  name: "@algolia/wizard",
2737
- version: "0.8.0-rc.49.42",
3021
+ version: "0.8.0-rc.53.46",
2738
3022
  description: "Magically implement Algolia functionality in your codebase",
2739
3023
  type: "module",
2740
3024
  engines: {
@@ -2782,7 +3066,6 @@ var package_default = {
2782
3066
  dependencies: {
2783
3067
  "@ai-sdk/anthropic": "^3.0.81",
2784
3068
  "@ai-sdk/openai-compatible": "^2.0.47",
2785
- "@algolia/cli": "^5.15.0",
2786
3069
  "@hono/node-server": "^2.0.10",
2787
3070
  "@mishieck/ink-titled-box": "^0.4.2",
2788
3071
  "@segment/analytics-node": "^3.1.0",
@@ -2797,7 +3080,6 @@ var package_default = {
2797
3080
  nanoid: "^5.1.15",
2798
3081
  pino: "^10.3.1",
2799
3082
  react: "^19.2.7",
2800
- toml: "^4.1.1",
2801
3083
  varlock: "^1.5.1",
2802
3084
  zod: "^4.4.3",
2803
3085
  zustand: "^5.0.14"
@@ -2855,8 +3137,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
2855
3137
  }
2856
3138
 
2857
3139
  // src/actions/confirmLanguage.ts
2858
- import z19 from "zod";
2859
- var confirmLanguageSchema = z19.object({
3140
+ import z22 from "zod";
3141
+ var confirmLanguageSchema = z22.object({
2860
3142
  languages: detectLanguageSchema.shape.languages
2861
3143
  });
2862
3144
  async function confirmLanguage(ctx) {
@@ -2877,8 +3159,8 @@ async function confirmLanguage(ctx) {
2877
3159
  }
2878
3160
 
2879
3161
  // src/actions/confirmFramework.ts
2880
- import z20 from "zod";
2881
- var confirmFrameworkSchema = z20.object({
3162
+ import z23 from "zod";
3163
+ var confirmFrameworkSchema = z23.object({
2882
3164
  frameworks: detectLanguageSchema.shape.frameworks
2883
3165
  });
2884
3166
  var CURATED_FRAMEWORKS = [
@@ -3006,8 +3288,8 @@ async function promptUser(ctx, params) {
3006
3288
  }
3007
3289
 
3008
3290
  // src/actions/confirmEntities.ts
3009
- import z21 from "zod";
3010
- var confirmEntitiesSchema = z21.object({
3291
+ import z24 from "zod";
3292
+ var confirmEntitiesSchema = z24.object({
3011
3293
  // Final detection — the focused re-run may supersede project-scan's.
3012
3294
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3013
3295
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3077,15 +3359,15 @@ async function confirmEntities(ctx) {
3077
3359
  }
3078
3360
 
3079
3361
  // src/actions/review.ts
3080
- import { z as z22 } from "zod";
3081
- var reviewSchema = z22.object({
3362
+ import { z as z25 } from "zod";
3363
+ var reviewSchema = z25.object({
3082
3364
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3083
3365
  // not one entry per workflow step — a step's raw output can be a long,
3084
3366
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3085
3367
  // 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())
3368
+ summaryPoints: z25.array(z25.string()),
3369
+ reviewPrompt: z25.string(),
3370
+ nextSteps: z25.array(z25.string())
3089
3371
  });
3090
3372
  function formatCompletedSteps(steps) {
3091
3373
  if (!steps.length) return "(no prior steps completed)";
@@ -3136,16 +3418,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3136
3418
  };
3137
3419
 
3138
3420
  // src/actions/implement.ts
3139
- import z24 from "zod";
3421
+ import z26 from "zod";
3140
3422
 
3141
3423
  // src/lib/worktree.ts
3142
3424
  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";
3425
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3144
3426
  import {
3145
3427
  basename as basename2,
3146
3428
  dirname as dirname7,
3147
3429
  isAbsolute as isAbsolute2,
3148
- join as join10,
3430
+ join as join9,
3149
3431
  relative as relative2,
3150
3432
  resolve as resolve3
3151
3433
  } from "node:path";
@@ -3179,7 +3461,7 @@ async function isWorkingTreeDirty(repoRoot) {
3179
3461
  return out.trim().length > 0;
3180
3462
  }
3181
3463
  async function pruneOldWorktrees(repoRoot) {
3182
- const dir = join10(stateDir(repoRoot), "worktrees");
3464
+ const dir = join9(stateDir(repoRoot), "worktrees");
3183
3465
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3184
3466
  for (const slug of stale) {
3185
3467
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3190,7 +3472,7 @@ async function pruneOldWorktrees(repoRoot) {
3190
3472
  "worktree",
3191
3473
  "remove",
3192
3474
  "--force",
3193
- join10(dir, slug)
3475
+ join9(dir, slug)
3194
3476
  ]);
3195
3477
  await git(["-C", repoRoot, "branch", "-D", branch]);
3196
3478
  } catch (err) {
@@ -3204,7 +3486,7 @@ async function pruneOldWorktrees(repoRoot) {
3204
3486
  async function createWorktree(repoRoot) {
3205
3487
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3206
3488
  const dirSlug = branch.replace(/\//g, "-");
3207
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3489
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3208
3490
  await git(["-C", repoRoot, "worktree", "prune"]);
3209
3491
  await pruneOldWorktrees(repoRoot);
3210
3492
  await mkdir6(dirname7(path), { recursive: true });
@@ -3324,8 +3606,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3324
3606
  } catch {
3325
3607
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3326
3608
  }
3327
- const relPath = join10(ingestDir, basename2(source));
3328
- const dest = join10(worktreePath, relPath);
3609
+ const relPath = join9(ingestDir, basename2(source));
3610
+ const dest = join9(worktreePath, relPath);
3329
3611
  try {
3330
3612
  await mkdir6(dirname7(dest), { recursive: true });
3331
3613
  await copyFile(source, dest);
@@ -3340,28 +3622,11 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3340
3622
  function hasEnvVar(content, name) {
3341
3623
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3342
3624
  }
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
3625
  async function writeSearchEnvValues(worktreePath, vars) {
3361
- const target = join10(worktreePath, ".env");
3626
+ const target = join9(worktreePath, ".env");
3362
3627
  let existing = "";
3363
3628
  try {
3364
- existing = await readFile8(target, "utf8");
3629
+ existing = await readFile7(target, "utf8");
3365
3630
  } catch (err) {
3366
3631
  if (err.code !== "ENOENT") throw err;
3367
3632
  }
@@ -3429,85 +3694,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3429
3694
  }
3430
3695
  }
3431
3696
 
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
3697
  // src/lib/algoliaDocs.ts
3503
3698
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3504
- import { dirname as dirname8, join as join11 } from "node:path";
3699
+ import { dirname as dirname8, join as join10 } from "node:path";
3505
3700
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3506
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3701
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3507
3702
  function findDocsDir() {
3508
3703
  let dir = dirname8(fileURLToPath2(import.meta.url));
3509
3704
  for (; ; ) {
3510
- const candidate = join11(dir, DOCS_SUBPATH);
3705
+ const candidate = join10(dir, DOCS_SUBPATH);
3511
3706
  if (existsSync2(candidate)) return candidate;
3512
3707
  const parent = dirname8(dir);
3513
3708
  if (parent === dir) return void 0;
@@ -3530,7 +3725,7 @@ function loadAlgoliaDoc(language) {
3530
3725
  );
3531
3726
  return "";
3532
3727
  }
3533
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3728
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3534
3729
  }
3535
3730
  function getNamedDoc(name, language) {
3536
3731
  const docsDir = findDocsDir();
@@ -3538,7 +3733,7 @@ function getNamedDoc(name, language) {
3538
3733
  logger.warn("docs/algolia-sdk not found");
3539
3734
  return "";
3540
3735
  }
3541
- const file = join11(docsDir, `${name}-${language}.md`);
3736
+ const file = join10(docsDir, `${name}-${language}.md`);
3542
3737
  if (!existsSync2(file)) {
3543
3738
  logger.warn({ name, language }, "named SDK reference not found");
3544
3739
  return "";
@@ -3565,50 +3760,50 @@ function shellQuote(value) {
3565
3760
  }
3566
3761
 
3567
3762
  // src/actions/implement.ts
3568
- var implementSchema = z24.object({
3569
- filesChanged: z24.array(z24.string()),
3570
- summary: z24.string(),
3763
+ var implementSchema = z26.object({
3764
+ filesChanged: z26.array(z26.string()),
3765
+ summary: z26.string(),
3571
3766
  // Absolute path to the throwaway worktree holding the generated changes, so
3572
3767
  // the user can open it (`cd <worktreePath>`) or inspect the diff
3573
3768
  // (`git -C <worktreePath> status/diff`).
3574
- worktreePath: z24.string().optional(),
3575
- ingestCommand: z24.string().optional(),
3769
+ worktreePath: z26.string().optional(),
3770
+ ingestCommand: z26.string().optional(),
3576
3771
  // True when the user accepted the run-now prompt and the wizard executed the
3577
3772
  // ingestion script; downstream steps use this to avoid telling the user to run
3578
3773
  // a script that already ran.
3579
- ingestScriptRan: z24.boolean().optional(),
3774
+ ingestScriptRan: z26.boolean().optional(),
3580
3775
  // Records ingested by the run-now execution, parsed from the script's
3581
3776
  // machine-readable count line; absent when the script didn't run or emitted
3582
3777
  // no parseable count.
3583
- ingestRecordCount: z24.number().optional(),
3778
+ ingestRecordCount: z26.number().optional(),
3584
3779
  // Wall-clock duration of the run-now ingestion execution, in ms.
3585
- ingestDurationMs: z24.number().optional(),
3586
- ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3780
+ ingestDurationMs: z26.number().optional(),
3781
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3587
3782
  // Suggested names/values, built from framework detection. The search agent is
3588
3783
  // instructed to rename the prefix if it doesn't match the project's build
3589
3784
  // tool, so the names it actually wrote can differ — treat these as hints, not
3590
3785
  // 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()
3786
+ searchEnvVars: z26.array(
3787
+ z26.object({
3788
+ name: z26.string(),
3789
+ value: z26.string()
3595
3790
  })
3596
3791
  ).optional()
3597
3792
  });
3598
- var implementationOutputSchema = z24.object({
3599
- summary: z24.string(),
3793
+ var implementationOutputSchema = z26.object({
3794
+ summary: z26.string(),
3600
3795
  // Ingestion only: how to run the generated script, as a structured pair the
3601
3796
  // wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
3602
3797
  // command string. `runtime` is constrained to an allowlisted interpreter and
3603
3798
  // `entrypoint` is validated to a worktree-relative path before execution, so
3604
3799
  // the agent cannot inject extra commands or swap the interpreter.
3605
- runtime: z24.enum(INGEST_RUNTIMES).optional(),
3606
- entrypoint: z24.string().optional()
3800
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3801
+ entrypoint: z26.string().optional()
3607
3802
  });
3608
- var verificationOutputSchema = z24.object({
3609
- summary: z24.string(),
3610
- sufficient: z24.boolean(),
3611
- additionalInstructions: z24.string().optional()
3803
+ var verificationOutputSchema = z26.object({
3804
+ summary: z26.string(),
3805
+ sufficient: z26.boolean(),
3806
+ additionalInstructions: z26.string().optional()
3612
3807
  });
3613
3808
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3614
3809
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3651,22 +3846,15 @@ function publicEnvPrefix(language) {
3651
3846
  }
3652
3847
  return "PUBLIC_";
3653
3848
  }
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
3849
  function searchEnvVars(language, appId, searchKey) {
3850
+ const prefix = publicEnvPrefix(language);
3663
3851
  return [
3664
3852
  {
3665
- name: appIdVar(language),
3853
+ name: `${prefix}ALGOLIA_APP_ID`,
3666
3854
  value: appId ?? "<your-algolia-app-id>"
3667
3855
  },
3668
3856
  {
3669
- name: searchKeyVar(language),
3857
+ name: `${prefix}ALGOLIA_SEARCH_API_KEY`,
3670
3858
  value: searchKey ?? "<your-algolia-search-only-api-key>"
3671
3859
  }
3672
3860
  ];
@@ -3728,13 +3916,15 @@ function searchInstructions(input) {
3728
3916
  "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
3729
3917
  doc,
3730
3918
  `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
3919
  "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.",
3920
+ // appId always resolves (requireApplication throws otherwise); only the
3921
+ // search-only key is best-effort and can fall back to a placeholder.
3922
+ `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3923
+ // Names are fixed, not the agent's to rename: the wizard writes the
3924
+ // resolved app id / search-only key into ".env" under these exact names
3925
+ // right after this step, so a renamed prefix here would leave the code
3926
+ // reading a var the wizard never wrote.
3927
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3738
3928
  '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
3929
  "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
3930
  ];
@@ -3876,6 +4066,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3876
4066
  }
3877
4067
  }
3878
4068
  const targetIndex = selected?.selection;
4069
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
3879
4070
  await assertGitRepoWithHead(repoRoot);
3880
4071
  if (await isWorkingTreeDirty(repoRoot)) {
3881
4072
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -3884,8 +4075,17 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3884
4075
  const confirmed2 = normalized.confirmedEntities;
3885
4076
  const searchLocation = normalized.searchImplementationAnalysis;
3886
4077
  let appId;
4078
+ let searchKey;
3887
4079
  if (useCases.includes("search")) {
3888
- appId = (await loadActiveProfile()).appId;
4080
+ appId = (await requireApplication()).id;
4081
+ try {
4082
+ searchKey = await resolveSearchOnlyKey(targetIndex);
4083
+ } catch (err) {
4084
+ logger.warn(
4085
+ { err: err.message },
4086
+ "implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
4087
+ );
4088
+ }
3889
4089
  }
3890
4090
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
3891
4091
  try {
@@ -3917,9 +4117,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3917
4117
  targetIndex,
3918
4118
  language,
3919
4119
  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),
4120
+ searchKey,
4121
+ searchEnvVars: searchEnvVars(language, appId, searchKey),
3923
4122
  ingestDir: INGEST_DIR,
3924
4123
  ingestionSource,
3925
4124
  uploadFilePath,
@@ -3929,24 +4128,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3929
4128
  };
3930
4129
  const summaries = [];
3931
4130
  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
4131
  let agentRuns = 0;
3951
4132
  let ingestRuntime;
3952
4133
  let ingestEntrypoint;
@@ -4007,7 +4188,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4007
4188
  messages: []
4008
4189
  }) === true;
4009
4190
  if (runNow) {
4010
- const profile = await loadActiveProfile();
4191
+ const ingestApp = await requireApplication();
4192
+ const writeKey = await resolveWriteKey(targetIndex);
4011
4193
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4012
4194
  const scriptLogId = ctx.logStart("runIngestScript", {
4013
4195
  runtime: ingestRuntime,
@@ -4019,8 +4201,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4019
4201
  ingestRuntime,
4020
4202
  ingestEntrypoint,
4021
4203
  {
4022
- [APP_ID_VAR]: profile.appId,
4023
- [API_KEY_VAR]: profile.apiKey
4204
+ [APP_ID_VAR]: ingestApp.id,
4205
+ [API_KEY_VAR]: writeKey
4024
4206
  }
4025
4207
  );
4026
4208
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4139,29 +4321,7 @@ ${run2.output}` : status;
4139
4321
  }
4140
4322
  extraInstructions = verificationRetryInstructions(verification);
4141
4323
  }
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(
4324
+ const resolvedSearchEnvVars = input.searchEnvVars.filter(
4165
4325
  (v) => !v.value.startsWith("<")
4166
4326
  );
4167
4327
  if (resolvedSearchEnvVars.length > 0) {
@@ -4172,29 +4332,13 @@ ${run2.output}` : status;
4172
4332
  if (written.length > 0) {
4173
4333
  summaries.push(`Wrote ${written.join(", ")} to .env.`);
4174
4334
  }
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
4335
  }
4191
- const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
4336
+ const unresolvedSearchEnvVars = input.searchEnvVars.filter(
4192
4337
  (v) => v.value.startsWith("<")
4193
4338
  );
4194
4339
  if (unresolvedSearchEnvVars.length > 0) {
4195
4340
  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}` : "")
4341
+ `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
4198
4342
  );
4199
4343
  }
4200
4344
  } else {
@@ -4226,7 +4370,7 @@ ${run2.output}` : status;
4226
4370
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4227
4371
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
4228
4372
  } : {},
4229
- ...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
4373
+ ...useCases.includes("search") ? { searchEnvVars: input.searchEnvVars } : {}
4230
4374
  };
4231
4375
  } finally {
4232
4376
  process.chdir(repoRoot);
@@ -4269,8 +4413,8 @@ var defaultWorkflow = {
4269
4413
  defineStep({
4270
4414
  id: "select-index",
4271
4415
  title: "Set up index",
4272
- outputSchema: z25.object({
4273
- selection: z25.string()
4416
+ outputSchema: z27.object({
4417
+ selection: z27.string()
4274
4418
  }),
4275
4419
  run: (ctx) => selectIndexStep(ctx)
4276
4420
  }),
@@ -4549,7 +4693,7 @@ function parseCliArgs(argv) {
4549
4693
 
4550
4694
  // src/lib/resetState.ts
4551
4695
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4552
- import { join as join12 } from "node:path";
4696
+ import { join as join11 } from "node:path";
4553
4697
  var KEEP = ["wizard.log"];
4554
4698
  async function resetProjectState() {
4555
4699
  const dir = stateDir();
@@ -4561,7 +4705,7 @@ async function resetProjectState() {
4561
4705
  }
4562
4706
  const targets = entries.filter((name) => !KEEP.includes(name));
4563
4707
  await Promise.all(
4564
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4708
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4565
4709
  );
4566
4710
  return { dir, removed: targets };
4567
4711
  }
@@ -4616,31 +4760,38 @@ ${formatStepList(workflow)}`);
4616
4760
  }
4617
4761
  async function run(workflow) {
4618
4762
  const store = useWizard.getState();
4619
- let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4763
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4764
+ await store.waitForStart();
4620
4765
  let user = await getUser();
4621
4766
  if (!user) {
4622
- await instance.waitUntilRenderFlush();
4623
- instance.cleanup();
4767
+ store.beginAuth();
4624
4768
  try {
4625
4769
  await runAuthLogin();
4626
4770
  } catch (err) {
4627
- console.error(err instanceof Error ? err.message : String(err));
4771
+ store.setError(err instanceof Error ? err.message : String(err));
4772
+ await instance.waitUntilExit();
4628
4773
  process.exit(1);
4629
4774
  }
4630
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4775
+ store.endAuth();
4631
4776
  user = await getUser();
4632
4777
  if (!user) {
4633
4778
  store.setError(
4634
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4779
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
4635
4780
  );
4636
4781
  await instance.waitUntilExit();
4637
4782
  process.exit(1);
4638
4783
  }
4639
4784
  }
4640
4785
  store.setUser(user);
4641
- const profile = await loadActiveProfile();
4642
- await store.waitForStart();
4643
- runWorkflow(workflow, profile?.appId);
4786
+ let app;
4787
+ try {
4788
+ app = await ensureApplication();
4789
+ } catch (err) {
4790
+ store.setError(err instanceof Error ? err.message : String(err));
4791
+ await instance.waitUntilExit();
4792
+ process.exit(1);
4793
+ }
4794
+ runWorkflow(workflow, app.id);
4644
4795
  }
4645
4796
  var started = await startup();
4646
4797
  if (typeof started === "number") {