@algolia/wizard 0.8.0 → 0.9.0-rc.53.58

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 +759 -539
  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 Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
7
+ import { Box as Box15, Text as Text15, 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,22 +241,21 @@ 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,
189
- // Advances past the welcome screen. Only meaningful from 'idle' once the
190
- // workflow is running there's nothing left to confirm.
191
- // Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
250
+ // `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
251
+ // welcome screen, so going back would gate the run a second time.
252
+ beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
253
+ endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
192
254
  confirmStart: () => set(
193
255
  (s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
194
256
  ),
195
- // Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
196
257
  openLearnMore: () => set({ homeScreen: "learnMore" }),
197
258
  backToHome: () => set({ homeScreen: "home" }),
198
- // Resolves once the phase leaves 'idle', whether that happens before or
199
- // after this is called (the welcome screen's enter handler is what
200
- // drives the transition via `confirmStart`).
201
259
  waitForStart: () => new Promise((resolve4) => {
202
260
  if (get().phase !== "idle") {
203
261
  resolve4();
@@ -220,15 +278,19 @@ var useWizard = create((set, get) => ({
220
278
  syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
221
279
  setActiveStep: (index) => {
222
280
  get()._clearNoticeQueue();
223
- set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
281
+ set({
282
+ phase: "running",
283
+ currentStepIndex: index,
284
+ output: "",
285
+ notices: [],
286
+ cliOutput: []
287
+ });
224
288
  },
225
289
  setUser: (user) => set({ user }),
226
290
  appendToken: (text) => set((s) => ({ output: s.output + text })),
227
291
  clearOutput: () => set({ output: "" }),
228
- // Renders the first notice of a burst immediately, then holds later
229
- // arrivals in `_noticeQueue` and drains one per `NOTICE_INTERVAL_MS` —
230
- // the timer stays armed through an empty drain so the cooldown always
231
- // covers the time since the last render, even across bursts.
292
+ // The timer stays armed through an empty drain, so the spacing covers the
293
+ // time since the last render even across bursts.
232
294
  pushNotice: (notice) => {
233
295
  const { notices, _noticeQueue, _noticeTimer } = get();
234
296
  if (_noticeTimer === null) {
@@ -261,6 +323,13 @@ var useWizard = create((set, get) => ({
261
323
  get()._clearNoticeQueue();
262
324
  set({ notices: [] });
263
325
  },
326
+ pushCliOutput: (stream, text) => set((s) => ({
327
+ cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
328
+ -CLI_OUTPUT_LIMIT
329
+ )
330
+ })),
331
+ clearCliOutput: () => set({ cliOutput: [] }),
332
+ setTargetIndex: (index) => set({ targetIndex: index }),
264
333
  logStart: (kind, name, input) => {
265
334
  const id = nanoid();
266
335
  set((s) => ({
@@ -283,9 +352,6 @@ var useWizard = create((set, get) => ({
283
352
  _resolve: resolve4
284
353
  });
285
354
  }),
286
- // Logs what the user picked — not the prompt text that was shown, which
287
- // may repeat or duplicate on-screen content and isn't the useful signal
288
- // here.
289
355
  submitInput: async (value) => {
290
356
  await markInteraction();
291
357
  get()._resolve?.(value);
@@ -305,6 +371,8 @@ var useWizard = create((set, get) => ({
305
371
  currentStepIndex: 0,
306
372
  output: "",
307
373
  notices: [],
374
+ cliOutput: [],
375
+ targetIndex: null,
308
376
  logs: [],
309
377
  error: null,
310
378
  inputReq: null,
@@ -313,16 +381,100 @@ var useWizard = create((set, get) => ({
313
381
  }
314
382
  }));
315
383
 
384
+ // src/ui/CliOutput.tsx
385
+ import { Box, Text, useWindowSize } from "ink";
386
+
387
+ // src/ui/theme.ts
388
+ var MARKER = {
389
+ pending: "\u25CB",
390
+ running: "\u25D0",
391
+ done: "\u2713",
392
+ error: "\u2716"
393
+ };
394
+ var BRAND = "#003DFF";
395
+ var SECONDARY = "#5468FF";
396
+ var DANGER = "#F86E7E";
397
+ var COLORS = {
398
+ brand: BRAND,
399
+ primary: "#E6EDF3",
400
+ secondary: SECONDARY,
401
+ strong: "#FFFFFF",
402
+ muted: "#8B949E",
403
+ dim: "#484F58",
404
+ highlight: { bg: "#12331C", fg: "#4ADE80" },
405
+ badge: "#E3B341",
406
+ danger: DANGER,
407
+ success: "#4ADE80",
408
+ bg: {
409
+ main: "#0B0E14",
410
+ sidebar: "#14171E"
411
+ },
412
+ border: "#30363D",
413
+ accent: "#76A0FF",
414
+ status: {
415
+ pending: "gray",
416
+ running: "#76A0FF",
417
+ done: "#4ADE80",
418
+ error: DANGER
419
+ }
420
+ };
421
+
422
+ // src/ui/CliOutput.tsx
423
+ import { jsxs } from "react/jsx-runtime";
424
+ var CLI_MARKER = "\u203A";
425
+ var RESERVED_ROWS = 16;
426
+ var MAX_ROWS = 12;
427
+ var PANEL_TEXT_WIDTH = 45;
428
+ var URL_PATTERN = /https?:\/\//;
429
+ function rowCost(text) {
430
+ return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
431
+ }
432
+ function CliOutput() {
433
+ const cliOutput = useWizard((s) => s.cliOutput);
434
+ const { rows } = useWindowSize();
435
+ if (!cliOutput.length) return null;
436
+ const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
437
+ const visible = [];
438
+ let usedRows = 0;
439
+ for (let i = cliOutput.length - 1; i >= 0; i--) {
440
+ const cost = rowCost(cliOutput[i].text);
441
+ if (usedRows + cost > rowBudget && visible.length > 0) break;
442
+ visible.unshift(cliOutput[i]);
443
+ usedRows += cost;
444
+ }
445
+ const hidden = cliOutput.length - visible.length;
446
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
447
+ hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
448
+ "\u2191 ",
449
+ hidden,
450
+ " earlier line(s)"
451
+ ] }),
452
+ visible.map((line) => /* @__PURE__ */ jsxs(
453
+ Text,
454
+ {
455
+ color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
456
+ wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
457
+ children: [
458
+ CLI_MARKER,
459
+ " ",
460
+ line.text
461
+ ]
462
+ },
463
+ line.id
464
+ ))
465
+ ] });
466
+ }
467
+
316
468
  // src/ui/Notices.tsx
317
- import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
469
+ import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
318
470
  import { useEffect as useEffect2, useState as useState2 } from "react";
319
471
 
320
472
  // src/ui/Table.tsx
321
- import { Box, Text, measureElement, useWindowSize } from "ink";
473
+ import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
322
474
  import { useEffect, useRef, useState } from "react";
323
475
  import { jsx } from "react/jsx-runtime";
324
476
  function Table({ columns, rows }) {
325
- const { columns: termCols } = useWindowSize();
477
+ const { columns: termCols } = useWindowSize2();
326
478
  const ref = useRef(null);
327
479
  const [width, setWidth] = useState(0);
328
480
  useEffect(() => {
@@ -330,7 +482,7 @@ function Table({ columns, rows }) {
330
482
  }, [termCols, columns, rows]);
331
483
  if (rows.length === 0) return null;
332
484
  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}`)) });
485
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
334
486
  }
335
487
  function formatTable(columns, rows, width) {
336
488
  const natural = columns.map(
@@ -370,48 +522,13 @@ function resize(widths, budget) {
370
522
  }
371
523
  var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
372
524
 
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
525
  // src/ui/Notices.tsx
409
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
526
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
410
527
  var AGENT_MARKER = "\u2726";
411
- var RESERVED_ROWS = 14;
412
- var PANEL_TEXT_WIDTH = 45;
528
+ var RESERVED_ROWS2 = 14;
529
+ var PANEL_TEXT_WIDTH2 = 45;
413
530
  function messageLineCount(text) {
414
- return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
531
+ return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
415
532
  }
416
533
  function noticeLineCount(notice) {
417
534
  const messageLines = (notice.messages ?? []).reduce((sum, m) => {
@@ -422,7 +539,7 @@ function noticeLineCount(notice) {
422
539
  return messageLines + tableLines;
423
540
  }
424
541
  function fitVisibleNotices(notices, windowRows) {
425
- const budget = Math.max(windowRows - RESERVED_ROWS, 3);
542
+ const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
426
543
  let used = 0;
427
544
  let count = 0;
428
545
  for (let i = notices.length - 1; i >= 0; i--) {
@@ -455,7 +572,7 @@ function parseHex(hex) {
455
572
  }
456
573
  function Notices() {
457
574
  const notices = useWizard((s) => s.notices);
458
- const { rows: windowRows } = useWindowSize2();
575
+ const { rows: windowRows } = useWindowSize3();
459
576
  const visible = fitVisibleNotices(notices, windowRows);
460
577
  const [pulseStep, setPulseStep] = useState2(0);
461
578
  useEffect2(() => {
@@ -472,14 +589,14 @@ function Notices() {
472
589
  }, []);
473
590
  if (!visible.length) return null;
474
591
  const pulseColor = PULSE_COLORS[pulseStep];
475
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
592
+ return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
476
593
  const isLatest = i === visible.length - 1;
477
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
594
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
478
595
  notice.messages?.map((m, j) => {
479
596
  const line = typeof m === "string" ? { text: m } : m;
480
597
  const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
481
- return /* @__PURE__ */ jsxs(
482
- Text2,
598
+ return /* @__PURE__ */ jsxs2(
599
+ Text3,
483
600
  {
484
601
  color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
485
602
  bold: line.bold,
@@ -497,41 +614,41 @@ function Notices() {
497
614
  }
498
615
 
499
616
  // src/ui/PromptInput.tsx
500
- import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
617
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
501
618
  import TextInput from "ink-text-input";
502
619
  import { useState as useState5 } from "react";
503
620
 
504
621
  // 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";
622
+ import { Box as Box4, Text as Text4 } from "ink";
623
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
507
624
  function NextAction({
508
625
  action,
509
626
  keyHint,
510
627
  hierarchy = "primary"
511
628
  }) {
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 })
629
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
630
+ hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
631
+ hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
632
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
633
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
517
634
  ] }),
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: `]` })
635
+ /* @__PURE__ */ jsxs3(Box4, { children: [
636
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
637
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
638
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
639
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
523
640
  ] })
524
641
  ] });
525
642
  }
526
643
 
527
644
  // src/ui/SelectPrompt.tsx
528
- import { Box as Box5, Text as Text5, useInput, useWindowSize as useWindowSize4 } from "ink";
645
+ import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
529
646
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
530
647
 
531
648
  // src/ui/ScrollView.tsx
532
- import { Box as Box4, Text as Text4, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
649
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
533
650
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
534
- import { jsxs as jsxs3 } from "react/jsx-runtime";
651
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
535
652
  var INDICATOR_ROWS = 2;
536
653
  function fittedWidth(node, columns) {
537
654
  let left = 0;
@@ -546,7 +663,7 @@ function useScrollWindow({
546
663
  followBottom = false
547
664
  }) {
548
665
  const viewportRef = useRef2(null);
549
- const { columns } = useWindowSize3();
666
+ const { columns } = useWindowSize4();
550
667
  const [size, setSize] = useState3(
551
668
  null
552
669
  );
@@ -601,14 +718,14 @@ function useScrollWindow({
601
718
  };
602
719
  }
603
720
  function ScrollView({ scroll, children }) {
604
- return /* @__PURE__ */ jsxs3(Box4, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
605
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
721
+ return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
722
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
606
723
  "\u2191 ",
607
724
  scroll.hiddenAbove,
608
725
  " more"
609
726
  ] }),
610
727
  children,
611
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
728
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
612
729
  "\u2193 ",
613
730
  scroll.hiddenBelow,
614
731
  " more"
@@ -617,7 +734,7 @@ function ScrollView({ scroll, children }) {
617
734
  }
618
735
 
619
736
  // src/ui/SelectPrompt.tsx
620
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
737
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
621
738
  var CANCEL = "cancel";
622
739
  var ARROW_WIDTH = 4;
623
740
  var COLUMN_GAP = 2;
@@ -648,7 +765,7 @@ function SelectPrompt({
648
765
  if (multi) hints.push({ key: "[space]", label: "select" });
649
766
  hints.push({ key: "[enter]", label: "confirm" });
650
767
  const containerRef = useRef3(null);
651
- const { columns } = useWindowSize4();
768
+ const { columns } = useWindowSize5();
652
769
  const [width, setWidth] = useState4(columns);
653
770
  useLayoutEffect2(() => {
654
771
  if (!containerRef.current) return;
@@ -702,14 +819,14 @@ function SelectPrompt({
702
819
  }
703
820
  }
704
821
  });
705
- return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
706
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
707
- error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
708
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
822
+ return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
823
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
824
+ error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
825
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
709
826
  table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
710
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
711
- question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
712
- helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
827
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
828
+ question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
829
+ helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
713
830
  ] })
714
831
  ] }),
715
832
  /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
@@ -719,39 +836,39 @@ function SelectPrompt({
719
836
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
720
837
  const sec = isCancel ? void 0 : secondary?.[i];
721
838
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
722
- const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
839
+ const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
723
840
  highlighted ? "\u276F " : " ",
724
841
  bullet,
725
842
  option
726
843
  ] });
727
844
  const isText = sec?.kind === "text";
728
- return /* @__PURE__ */ jsxs4(
729
- Box5,
845
+ return /* @__PURE__ */ jsxs5(
846
+ Box6,
730
847
  {
731
848
  width: isText ? "100%" : barWidth,
732
849
  paddingX: 1,
733
850
  paddingY: 1,
734
851
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
735
852
  children: [
736
- /* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
737
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
738
- Text5,
853
+ /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
854
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
855
+ Text6,
739
856
  {
740
857
  wrap: "truncate",
741
858
  color: highlighted ? COLORS.primary : COLORS.muted,
742
859
  children: sec.value
743
860
  }
744
861
  ) }),
745
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
862
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
746
863
  ]
747
864
  },
748
865
  `row-${i}`
749
866
  );
750
867
  }) }),
751
- /* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
868
+ /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
752
869
  i > 0 ? " " : "",
753
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
754
- /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
870
+ /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
871
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
755
872
  " ",
756
873
  label
757
874
  ] })
@@ -760,7 +877,7 @@ function SelectPrompt({
760
877
  }
761
878
 
762
879
  // src/ui/PromptInput.tsx
763
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
880
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
764
881
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
765
882
  function EnterToContinuePrompt({
766
883
  question,
@@ -771,10 +888,10 @@ function EnterToContinuePrompt({
771
888
  if (key.return) onDecide(true);
772
889
  else if (key.escape) onDecide(false);
773
890
  });
774
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
775
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
776
- question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
777
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
891
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
892
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
893
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
894
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
778
895
  /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
779
896
  /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
780
897
  ] })
@@ -784,11 +901,11 @@ function PromptInput() {
784
901
  const { phase, inputReq, submitInput } = useWizard();
785
902
  const [draft, setDraft] = useState5("");
786
903
  if (phase === "done" || phase === "error") {
787
- return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
904
+ return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
788
905
  }
789
906
  if (phase !== "awaitingInput" || !inputReq) return null;
790
907
  if (inputReq.promptType === "multipleChoice") {
791
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
908
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
792
909
  SelectPrompt,
793
910
  {
794
911
  question: inputReq.prompt,
@@ -805,7 +922,7 @@ function PromptInput() {
805
922
  ) });
806
923
  }
807
924
  if (inputReq.promptType === "multiSelect") {
808
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
925
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
809
926
  SelectPrompt,
810
927
  {
811
928
  multi: true,
@@ -820,7 +937,7 @@ function PromptInput() {
820
937
  ) });
821
938
  }
822
939
  if (inputReq.promptType === "notice") {
823
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
940
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
824
941
  SelectPrompt,
825
942
  {
826
943
  question: inputReq.prompt,
@@ -842,7 +959,7 @@ function PromptInput() {
842
959
  }
843
960
  if (inputReq.promptType === "acceptReject") {
844
961
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
845
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
962
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
846
963
  SelectPrompt,
847
964
  {
848
965
  question: inputReq.prompt,
@@ -853,11 +970,11 @@ function PromptInput() {
853
970
  }
854
971
  ) });
855
972
  }
856
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
857
- inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
858
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
859
- /* @__PURE__ */ jsxs5(Box6, { children: [
860
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
973
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
974
+ inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
975
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
976
+ /* @__PURE__ */ jsxs6(Box7, { children: [
977
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
861
978
  inputReq.prompt,
862
979
  " "
863
980
  ] }),
@@ -879,7 +996,7 @@ function PromptInput() {
879
996
  // src/ui/Welcome.tsx
880
997
  import { dirname as dirname2, join as join3 } from "node:path";
881
998
  import { fileURLToPath } from "node:url";
882
- import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
999
+ import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
883
1000
 
884
1001
  // src/ui/copy/welcome.ts
885
1002
  var sidebarItems = [
@@ -907,27 +1024,27 @@ var sidebarItems = [
907
1024
 
908
1025
  // src/ui/Welcome.tsx
909
1026
  import Image, { InkPictureProvider } from "ink-picture";
910
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1027
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
911
1028
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
912
1029
  function SidebarItem({
913
1030
  title,
914
1031
  description
915
1032
  }) {
916
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
917
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
918
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
919
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
1033
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1034
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1035
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1036
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
920
1037
  ] }),
921
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
1038
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
922
1039
  /* @__PURE__ */ jsx6(Spacer, {}),
923
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
1040
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
924
1041
  ] })
925
1042
  ] });
926
1043
  }
927
1044
  function Welcome() {
928
1045
  const confirmStart = useWizard((s) => s.confirmStart);
929
1046
  const openLearnMore = useWizard((s) => s.openLearnMore);
930
- const { rows } = useWindowSize5();
1047
+ const { rows } = useWindowSize6();
931
1048
  useInput3((input, key) => {
932
1049
  if (key.return) confirmStart();
933
1050
  else if (input === "i") openLearnMore();
@@ -946,15 +1063,15 @@ function Welcome() {
946
1063
  if (rows < 30) {
947
1064
  layout = scales["small"];
948
1065
  }
949
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1066
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
950
1067
  /* @__PURE__ */ jsx6(
951
- Box7,
1068
+ Box8,
952
1069
  {
953
1070
  paddingY: layout.main.padding.y,
954
1071
  paddingX: layout.main.padding.x,
955
1072
  flexDirection: "column",
956
1073
  justifyContent: "center",
957
- children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
1074
+ children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
958
1075
  /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
959
1076
  Image,
960
1077
  {
@@ -966,16 +1083,16 @@ function Welcome() {
966
1083
  protocol: "halfBlock"
967
1084
  }
968
1085
  ) }),
969
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
970
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
1086
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1087
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
971
1088
  /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
972
1089
  /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
973
1090
  ] })
974
1091
  ] })
975
1092
  }
976
1093
  ),
977
- /* @__PURE__ */ jsxs6(
978
- Box7,
1094
+ /* @__PURE__ */ jsxs7(
1095
+ Box8,
979
1096
  {
980
1097
  backgroundColor: COLORS.bg.sidebar,
981
1098
  width: 40,
@@ -985,7 +1102,7 @@ function Welcome() {
985
1102
  flexDirection: "column",
986
1103
  justifyContent: "center",
987
1104
  children: [
988
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1105
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
989
1106
  sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
990
1107
  ]
991
1108
  }
@@ -995,7 +1112,7 @@ function Welcome() {
995
1112
 
996
1113
  // src/ui/LearnMore.tsx
997
1114
  import { Fragment as Fragment2 } from "react";
998
- import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1115
+ import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
999
1116
 
1000
1117
  // src/ui/copy/learn-more.ts
1001
1118
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1032,7 +1149,7 @@ var policyLinks = [
1032
1149
  ];
1033
1150
 
1034
1151
  // src/ui/LearnMore.tsx
1035
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1152
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1036
1153
  var TAG_COLORS = {
1037
1154
  READ: COLORS.success,
1038
1155
  WRITE: COLORS.badge,
@@ -1048,25 +1165,25 @@ function NeverLine({
1048
1165
  }) {
1049
1166
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1050
1167
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1051
- return /* @__PURE__ */ jsxs7(Text8, { children: [
1052
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
1168
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
1169
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1053
1170
  " ".repeat(NEVER_BOX_PAD_X),
1054
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
1171
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1055
1172
  " ".repeat(rightPad),
1056
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
1173
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1057
1174
  ] });
1058
1175
  }
1059
1176
  function LearnMore() {
1060
1177
  const confirmStart = useWizard((s) => s.confirmStart);
1061
1178
  const backToHome = useWizard((s) => s.backToHome);
1062
- const { columns } = useWindowSize6();
1179
+ const { columns } = useWindowSize7();
1063
1180
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1064
1181
  useInput4((_input, key) => {
1065
1182
  if (key.escape) backToHome();
1066
1183
  else if (key.return) confirmStart();
1067
1184
  });
1068
- return /* @__PURE__ */ jsxs7(
1069
- Box8,
1185
+ return /* @__PURE__ */ jsxs8(
1186
+ Box9,
1070
1187
  {
1071
1188
  flexDirection: "column",
1072
1189
  paddingX: PADDING_X,
@@ -1074,20 +1191,20 @@ function LearnMore() {
1074
1191
  width: "100%",
1075
1192
  gap: 1,
1076
1193
  children: [
1077
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1078
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
1079
- /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
1080
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1081
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1082
- /* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1083
- /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
1084
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
1085
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1194
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1195
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1196
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1197
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1198
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1199
+ /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1200
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1201
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1202
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1086
1203
  ] }) })
1087
1204
  ] })
1088
1205
  ] }, item.tag)) }),
1089
- /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1090
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1206
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1207
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1091
1208
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1092
1209
  /* @__PURE__ */ jsx7(
1093
1210
  NeverLine,
@@ -1096,7 +1213,7 @@ function LearnMore() {
1096
1213
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1097
1214
  }
1098
1215
  ),
1099
- neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
1216
+ neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1100
1217
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1101
1218
  /* @__PURE__ */ jsx7(
1102
1219
  NeverLine,
@@ -1111,23 +1228,23 @@ function LearnMore() {
1111
1228
  )
1112
1229
  ] }, item)),
1113
1230
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1114
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1231
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1115
1232
  ] }),
1116
- /* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1117
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1118
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
1233
+ /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1234
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1235
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1119
1236
  ] }, link.label)) }),
1120
- /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1121
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1122
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1123
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
1124
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
1237
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1238
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1239
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1240
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1241
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1125
1242
  ] }),
1126
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1127
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1128
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "enter" }),
1129
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
1130
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
1243
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1244
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1245
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1246
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1247
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1131
1248
  ] })
1132
1249
  ] })
1133
1250
  ]
@@ -1136,10 +1253,10 @@ function LearnMore() {
1136
1253
  }
1137
1254
 
1138
1255
  // src/ui/Sidebar.tsx
1139
- import { Box as Box11, Text as Text11 } from "ink";
1256
+ import { Box as Box12, Text as Text12 } from "ink";
1140
1257
 
1141
1258
  // src/ui/Steps.tsx
1142
- import { Box as Box9, Text as Text9 } from "ink";
1259
+ import { Box as Box10, Text as Text10 } from "ink";
1143
1260
  import Spinner from "ink-spinner";
1144
1261
 
1145
1262
  // src/core/persistence.ts
@@ -1168,11 +1285,11 @@ async function clearWorkflowState(workflowId) {
1168
1285
  }
1169
1286
 
1170
1287
  // src/ui/Steps.tsx
1171
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1288
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1172
1289
  function Steps() {
1173
1290
  const { steps } = useWizard();
1174
1291
  const visibleSteps = steps.filter(isStepVisible);
1175
- 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: [
1292
+ return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
1176
1293
  s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1177
1294
  " ",
1178
1295
  s.title
@@ -1182,7 +1299,7 @@ function CurrentStep() {
1182
1299
  const { steps } = useWizard();
1183
1300
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1184
1301
  if (!currentStep) return null;
1185
- return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
1302
+ return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1186
1303
  /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1187
1304
  " ",
1188
1305
  ` ${currentStep.title}`
@@ -1190,19 +1307,19 @@ function CurrentStep() {
1190
1307
  }
1191
1308
 
1192
1309
  // src/ui/Progress.tsx
1193
- import { Box as Box10, Text as Text10 } from "ink";
1194
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1310
+ import { Box as Box11, Text as Text11 } from "ink";
1311
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1195
1312
  function Progress() {
1196
1313
  const { steps, currentStepIndex } = useWizard();
1197
1314
  const visibleSteps = steps.filter(isStepVisible);
1198
1315
  if (visibleSteps.length === 0) return null;
1199
1316
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1200
1317
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1201
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1202
- /* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
1203
- /* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
1204
- /* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
1205
- /* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
1318
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1319
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1320
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1321
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1322
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1206
1323
  ] });
1207
1324
  }
1208
1325
 
@@ -1213,10 +1330,10 @@ var sidebarCommands = [
1213
1330
  ];
1214
1331
 
1215
1332
  // src/ui/Sidebar.tsx
1216
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1333
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1217
1334
  function Sidebar() {
1218
- return /* @__PURE__ */ jsxs10(
1219
- Box11,
1335
+ return /* @__PURE__ */ jsxs11(
1336
+ Box12,
1220
1337
  {
1221
1338
  backgroundColor: "#14171E",
1222
1339
  width: 30,
@@ -1225,16 +1342,16 @@ function Sidebar() {
1225
1342
  flexDirection: "column",
1226
1343
  justifyContent: "space-between",
1227
1344
  children: [
1228
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1229
- /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
1345
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1346
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1230
1347
  /* @__PURE__ */ jsx10(Steps, {})
1231
1348
  ] }),
1232
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1349
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1233
1350
  /* @__PURE__ */ jsx10(Progress, {}),
1234
- /* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
1235
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1236
- /* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1237
- /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
1351
+ /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1352
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1353
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1354
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1238
1355
  ] });
1239
1356
  }) })
1240
1357
  ] })
@@ -1244,12 +1361,12 @@ function Sidebar() {
1244
1361
  }
1245
1362
 
1246
1363
  // src/ui/Ribbon.tsx
1247
- import { Box as Box12, Text as Text12 } from "ink";
1248
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1364
+ import { Box as Box13, Text as Text13 } from "ink";
1365
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1249
1366
  function Ribbon() {
1250
1367
  const firstCommand = sidebarCommands[0];
1251
- return /* @__PURE__ */ jsxs11(
1252
- Box12,
1368
+ return /* @__PURE__ */ jsxs12(
1369
+ Box13,
1253
1370
  {
1254
1371
  backgroundColor: "#14171E",
1255
1372
  flexDirection: "row",
@@ -1259,9 +1376,9 @@ function Ribbon() {
1259
1376
  children: [
1260
1377
  /* @__PURE__ */ jsx11(Progress, {}),
1261
1378
  /* @__PURE__ */ jsx11(CurrentStep, {}),
1262
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1263
- /* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1264
- /* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
1379
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1380
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1381
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1265
1382
  ] })
1266
1383
  ]
1267
1384
  }
@@ -1272,8 +1389,8 @@ function Ribbon() {
1272
1389
  import { useState as useState6 } from "react";
1273
1390
 
1274
1391
  // src/ui/Logs.tsx
1275
- import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
1276
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1392
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1393
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1277
1394
  var KIND_COLOR = {
1278
1395
  tool: COLORS.primary,
1279
1396
  prompt: COLORS.badge
@@ -1309,8 +1426,8 @@ function Logs() {
1309
1426
  else if (key.downArrow) scroll.scrollBy(1);
1310
1427
  });
1311
1428
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1312
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1313
- logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
1429
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1430
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1314
1431
  /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1315
1432
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1316
1433
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
@@ -1321,14 +1438,14 @@ function Logs() {
1321
1438
  const name = truncate2(entry.name, budget);
1322
1439
  budget -= name.length;
1323
1440
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1324
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
1325
- /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
1326
- /* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
1327
- preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
1328
- durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
1441
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1442
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1443
+ /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1444
+ preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1445
+ durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1329
1446
  ] }, entry.id);
1330
1447
  }) }),
1331
- /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1448
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1332
1449
  ] });
1333
1450
  }
1334
1451
 
@@ -1520,11 +1637,11 @@ function track(event, payload) {
1520
1637
  }
1521
1638
 
1522
1639
  // src/ui/App.tsx
1523
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1640
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1524
1641
  function App() {
1525
1642
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1526
1643
  const { exit } = useApp();
1527
- const { columns, rows } = useWindowSize7();
1644
+ const { columns, rows } = useWindowSize8();
1528
1645
  const [showLogs, setShowLogs] = useState6(false);
1529
1646
  const finished = phase === "done" || phase === "error";
1530
1647
  const currentStep = steps[currentStepIndex];
@@ -1537,7 +1654,7 @@ function App() {
1537
1654
  { isActive: finished }
1538
1655
  );
1539
1656
  useInput6((_input, key) => {
1540
- if (phase === "idle" || phase === "preflight") return;
1657
+ if (phase === "idle" || phase === "authenticating") return;
1541
1658
  if (key.tab) {
1542
1659
  setShowLogs(!showLogs);
1543
1660
  track("AI Wizard Interaction", {
@@ -1547,49 +1664,45 @@ function App() {
1547
1664
  });
1548
1665
  }
1549
1666
  });
1550
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1667
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1551
1668
  useInput6((_input, key) => {
1552
1669
  if (escOwnedElsewhere) return;
1553
1670
  if (key.escape) {
1554
1671
  track("AI Wizard Interaction", {
1555
1672
  context: "global",
1556
1673
  key: "esc",
1557
- // No step is active until `startWorkflow` — report the phase instead.
1558
1674
  currentStep: currentStep?.id ?? phase
1559
1675
  });
1560
1676
  exit();
1561
1677
  }
1562
1678
  });
1563
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1679
+ const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1564
1680
  const flexDirection = columns > 90 ? "row" : "column";
1565
1681
  const showSidebar = flexDirection === "row";
1566
- return /* @__PURE__ */ jsxs13(
1567
- Box14,
1568
- {
1569
- backgroundColor: COLORS.bg.main,
1570
- flexDirection: "row",
1571
- width: columns,
1572
- minHeight: rows,
1573
- children: [
1574
- mainWindowVisible && // Ink sizes the root by width only, so without a cap the scrolling
1575
- // lists in here grow to their content instead of windowing (see
1576
- // `useScrollWindow`). The home screens below stay uncapped: they are
1577
- // long static copy that would be clipped rather than windowed.
1578
- /* @__PURE__ */ jsxs13(
1579
- Box14,
1580
- {
1581
- flexDirection,
1582
- width: "100%",
1583
- maxHeight: rows,
1584
- justifyContent: "space-between",
1585
- children: [
1586
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1587
- /* Fill the space the sidebar/ribbon leaves — width beside the
1588
- sidebar, height above the ribbon. The height matters even
1589
- stacked: it is what the prompt's scrolling list measures itself
1590
- against (see SelectPrompt). */
1591
- /* @__PURE__ */ jsxs13(
1592
- Box14,
1682
+ const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
1683
+ return (
1684
+ /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1685
+ the whole screen, and the scrolling throws off its cursor arithmetic —
1686
+ flicker and leftover rows. */
1687
+ /* @__PURE__ */ jsxs14(
1688
+ Box15,
1689
+ {
1690
+ backgroundColor: COLORS.bg.main,
1691
+ flexDirection: "row",
1692
+ width: columns,
1693
+ height: scrollsPastViewport ? void 0 : rows,
1694
+ overflow: scrollsPastViewport ? "visible" : "hidden",
1695
+ children: [
1696
+ mainWindowVisible && /* @__PURE__ */ jsxs14(
1697
+ Box15,
1698
+ {
1699
+ flexDirection,
1700
+ width: "100%",
1701
+ maxHeight: rows,
1702
+ justifyContent: "space-between",
1703
+ children: [
1704
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1705
+ Box15,
1593
1706
  {
1594
1707
  flexDirection: "column",
1595
1708
  paddingX: 4,
@@ -1597,24 +1710,29 @@ function App() {
1597
1710
  width: showSidebar ? 70 : "100%",
1598
1711
  flexGrow: 1,
1599
1712
  children: [
1713
+ phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1714
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1715
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1716
+ ] }),
1717
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1600
1718
  /* @__PURE__ */ jsx13(Notices, {}),
1601
1719
  /* @__PURE__ */ jsx13(PromptInput, {}),
1602
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1603
- phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
1720
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1721
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1604
1722
  "\u2716 ",
1605
1723
  error
1606
1724
  ] }) })
1607
1725
  ]
1608
1726
  }
1609
- )
1610
- ),
1611
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1612
- ]
1613
- }
1614
- ),
1615
- (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1616
- ]
1617
- }
1727
+ ),
1728
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1729
+ ]
1730
+ }
1731
+ ),
1732
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1733
+ ]
1734
+ }
1735
+ )
1618
1736
  );
1619
1737
  }
1620
1738
 
@@ -1850,61 +1968,138 @@ async function runWorkflow(workflow, appId) {
1850
1968
  }
1851
1969
  }
1852
1970
 
1853
- // src/lib/algoliaProfile.ts
1854
- import { readFile as readFile3 } from "node:fs/promises";
1855
- import { createRequire as createRequire2 } from "node:module";
1856
- import { homedir as homedir2 } from "node:os";
1857
- import { join as join6 } from "node:path";
1858
- import { parse as parseToml } from "toml";
1859
- var require3 = createRequire2(import.meta.url);
1860
- function configPath() {
1861
- const base = process.env.XDG_CONFIG_HOME || join6(homedir2(), ".config");
1862
- return join6(base, "algolia", "config.toml");
1863
- }
1864
- function profilesFromConfig(tomlText) {
1865
- let parsed;
1971
+ // src/lib/algoliaApp.ts
1972
+ import { z as z4 } from "zod";
1973
+ var applicationSchema = z4.object({
1974
+ id: z4.string().min(1),
1975
+ name: z4.string().default(""),
1976
+ plan: z4.string().optional()
1977
+ });
1978
+ var listSchema = z4.array(
1979
+ z4.object({
1980
+ id: z4.string().min(1),
1981
+ name: z4.string().default(""),
1982
+ plan_label: z4.string().optional()
1983
+ }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1984
+ );
1985
+ async function currentApplication() {
1986
+ let raw;
1866
1987
  try {
1867
- parsed = parseToml(tomlText);
1988
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1868
1989
  } catch {
1869
- return [];
1990
+ return null;
1870
1991
  }
1871
- const profiles = Object.entries(parsed).filter(
1872
- ([, t]) => typeof t.application_id === "string" && typeof t.api_key === "string"
1873
- ).map(([name, t]) => ({
1874
- name,
1875
- appId: t.application_id,
1876
- apiKey: t.api_key,
1877
- isDefault: t.default === true
1878
- }));
1879
- profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
1880
- return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
1881
- }
1882
- async function loadActiveProfile() {
1883
- let profiles;
1992
+ const parsed = applicationSchema.safeParse(parseJson(raw));
1993
+ return parsed.success ? parsed.data : null;
1994
+ }
1995
+ async function requireApplication() {
1996
+ const app = await currentApplication();
1997
+ if (!app) {
1998
+ throw new Error(
1999
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2000
+ );
2001
+ }
2002
+ return app;
2003
+ }
2004
+ async function listApplications() {
2005
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2006
+ const parsed = listSchema.safeParse(parseJson(raw));
2007
+ if (!parsed.success) {
2008
+ throw new Error("Could not read the list of Algolia applications.");
2009
+ }
2010
+ return parsed.data;
2011
+ }
2012
+ async function selectApplication(id) {
2013
+ const raw = await runAlgoliaCli(
2014
+ ["application", "select", "--non-interactive", "--app-id", id],
2015
+ { onOutput: stderrSink }
2016
+ );
2017
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2018
+ if (!parsed.success) {
2019
+ throw new Error(
2020
+ `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2021
+ );
2022
+ }
2023
+ return parsed.data;
2024
+ }
2025
+ function parseJson(text) {
1884
2026
  try {
1885
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
2027
+ return JSON.parse(text);
1886
2028
  } catch {
1887
- profiles = [];
2029
+ return void 0;
1888
2030
  }
1889
- const profile = profiles[0];
1890
- if (!profile) {
2031
+ }
2032
+
2033
+ // src/lib/algoliaAppPicker.ts
2034
+ function secondaryFor(app) {
2035
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
2036
+ }
2037
+ function labelFor(app) {
2038
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2039
+ }
2040
+ function selectAndReport(app) {
2041
+ useWizard.getState().pushCliOutput(
2042
+ "stdout",
2043
+ `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
2044
+ );
2045
+ return selectApplication(app.id);
2046
+ }
2047
+ async function promptForApplication() {
2048
+ const store = useWizard.getState();
2049
+ const apps = await listApplications();
2050
+ if (apps.length === 0) {
1891
2051
  throw new Error(
1892
- "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
2052
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2053
+ );
2054
+ }
2055
+ if (apps.length === 1) {
2056
+ const only = apps[0];
2057
+ logger.info(
2058
+ { app: only.id },
2059
+ "single application on the account; selecting it"
1893
2060
  );
2061
+ return selectAndReport(only);
1894
2062
  }
1895
- return profile;
2063
+ const messages = ["Which Algolia application should the wizard work in?"];
2064
+ for (; ; ) {
2065
+ const choice = await store.requestUserInput({
2066
+ prompt: "Select an application",
2067
+ promptType: "multipleChoice",
2068
+ options: apps.map(labelFor),
2069
+ secondary: apps.map(secondaryFor),
2070
+ messages
2071
+ });
2072
+ const chosen = apps.find((app) => labelFor(app) === choice);
2073
+ if (!chosen) {
2074
+ throw new Error("Application picker received an unexpected selection");
2075
+ }
2076
+ try {
2077
+ return await selectAndReport(chosen);
2078
+ } catch (err) {
2079
+ logger.warn(
2080
+ { app: chosen.id, err: err.message },
2081
+ "application select failed; re-prompting"
2082
+ );
2083
+ messages.push(
2084
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2085
+ );
2086
+ }
2087
+ }
2088
+ }
2089
+ async function ensureApplication() {
2090
+ return await currentApplication() ?? await promptForApplication();
1896
2091
  }
1897
2092
 
1898
2093
  // src/workflows/default.ts
1899
- import { z as z25 } from "zod";
2094
+ import { z as z27 } from "zod";
1900
2095
 
1901
2096
  // src/actions/listIndices.ts
1902
- import { z as z3 } from "zod";
1903
- var indicesListSchema = z3.object({
1904
- items: z3.array(
1905
- z3.object({
1906
- name: z3.string(),
1907
- entries: z3.number().default(0)
2097
+ import { z as z5 } from "zod";
2098
+ var indicesListSchema = z5.object({
2099
+ items: z5.array(
2100
+ z5.object({
2101
+ name: z5.string(),
2102
+ entries: z5.number().default(0)
1908
2103
  })
1909
2104
  )
1910
2105
  });
@@ -1975,12 +2170,12 @@ import "zod";
1975
2170
 
1976
2171
  // src/lib/tools/listFiles.ts
1977
2172
  import { tool } from "ai";
1978
- import z4 from "zod";
2173
+ import z6 from "zod";
1979
2174
  import { readdir } from "node:fs/promises";
1980
2175
 
1981
2176
  // src/lib/tools/path.ts
1982
2177
  import { lstat } from "node:fs/promises";
1983
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2178
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1984
2179
  function resolveInRoot(ctx, path) {
1985
2180
  const target = resolve2(ctx.cwd, path);
1986
2181
  const rel = relative(ctx.root, target);
@@ -1996,7 +2191,7 @@ async function hasSymlinkParent(ctx, target) {
1996
2191
  let current = ctx.root;
1997
2192
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1998
2193
  for (const part of parts) {
1999
- current = join7(current, part);
2194
+ current = join6(current, part);
2000
2195
  try {
2001
2196
  if ((await lstat(current)).isSymbolicLink()) return true;
2002
2197
  } catch (err) {
@@ -2011,7 +2206,7 @@ async function hasSymlinkParent(ctx, target) {
2011
2206
  function listFilesTool(ctx) {
2012
2207
  return tool({
2013
2208
  description: "List files in the current working directory",
2014
- inputSchema: z4.object(),
2209
+ inputSchema: z6.object(),
2015
2210
  execute: async () => {
2016
2211
  logger.info("called listFiles tool");
2017
2212
  if (++ctx.counts.list > ctx.limits.list) {
@@ -2027,13 +2222,13 @@ function listFilesTool(ctx) {
2027
2222
 
2028
2223
  // src/lib/tools/changeDirectory.ts
2029
2224
  import { tool as tool2 } from "ai";
2030
- import z5 from "zod";
2225
+ import z7 from "zod";
2031
2226
  import { stat } from "node:fs/promises";
2032
2227
  function changeDirectoryTool(ctx) {
2033
2228
  return tool2({
2034
2229
  description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
2035
- inputSchema: z5.object({
2036
- path: z5.string().describe("Directory to change into")
2230
+ inputSchema: z7.object({
2231
+ path: z7.string().describe("Directory to change into")
2037
2232
  }),
2038
2233
  execute: async ({ path }) => {
2039
2234
  logger.info({ path }, "called changeDirectory tool");
@@ -2055,13 +2250,13 @@ function changeDirectoryTool(ctx) {
2055
2250
 
2056
2251
  // src/lib/tools/reportStatus.ts
2057
2252
  import { tool as tool3 } from "ai";
2058
- import z6 from "zod";
2253
+ import z8 from "zod";
2059
2254
  function reportStatusTool(output) {
2060
2255
  return tool3({
2061
2256
  description: "Report the status of your execution. Return a reason in case of failure.",
2062
- inputSchema: z6.object({
2063
- status: z6.enum(["success", "fail"]),
2064
- reason: z6.string().optional(),
2257
+ inputSchema: z8.object({
2258
+ status: z8.enum(["success", "fail"]),
2259
+ reason: z8.string().optional(),
2065
2260
  output
2066
2261
  }),
2067
2262
  execute: async ({ status, reason, output: output2 }) => {
@@ -2073,8 +2268,8 @@ function reportStatusTool(output) {
2073
2268
 
2074
2269
  // src/lib/tools/readFile.ts
2075
2270
  import { tool as tool4 } from "ai";
2076
- import z7 from "zod";
2077
- import { readFile as readFile4 } from "node:fs/promises";
2271
+ import z9 from "zod";
2272
+ import { readFile as readFile3 } from "node:fs/promises";
2078
2273
 
2079
2274
  // src/lib/tools/env.ts
2080
2275
  import { basename } from "node:path";
@@ -2101,8 +2296,8 @@ function redactEnvValues(content) {
2101
2296
  function readFileTool(ctx) {
2102
2297
  return tool4({
2103
2298
  description: "Read the contents of a file at the given path",
2104
- inputSchema: z7.object({
2105
- filePath: z7.string().describe("Path to the file to read")
2299
+ inputSchema: z9.object({
2300
+ filePath: z9.string().describe("Path to the file to read")
2106
2301
  }),
2107
2302
  execute: async ({ filePath }) => {
2108
2303
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2112,7 +2307,7 @@ function readFileTool(ctx) {
2112
2307
  const resolved = resolveInRoot(ctx, filePath);
2113
2308
  if (!resolved.ok) return resolved.error;
2114
2309
  try {
2115
- const content = await readFile4(resolved.target, "utf8");
2310
+ const content = await readFile3(resolved.target, "utf8");
2116
2311
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2117
2312
  } catch (err) {
2118
2313
  return `Error reading ${filePath}: ${err.message}`;
@@ -2123,15 +2318,15 @@ function readFileTool(ctx) {
2123
2318
 
2124
2319
  // src/lib/tools/writeFile.ts
2125
2320
  import { tool as tool5 } from "ai";
2126
- import z8 from "zod";
2321
+ import z10 from "zod";
2127
2322
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2128
2323
  import { dirname as dirname4 } from "node:path";
2129
2324
  function writeFileTool(ctx) {
2130
2325
  return tool5({
2131
2326
  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.",
2132
- inputSchema: z8.object({
2133
- filePath: z8.string().describe("Path to the file to write"),
2134
- content: z8.string().describe("Content to write to the file")
2327
+ inputSchema: z10.object({
2328
+ filePath: z10.string().describe("Path to the file to write"),
2329
+ content: z10.string().describe("Content to write to the file")
2135
2330
  }),
2136
2331
  execute: async ({ filePath, content }) => {
2137
2332
  logger.info({ filePath }, "called writeFile tool");
@@ -2156,9 +2351,95 @@ function writeFileTool(ctx) {
2156
2351
 
2157
2352
  // src/lib/tools/writeAlgoliaCredentials.ts
2158
2353
  import { tool as tool6 } from "ai";
2159
- import z9 from "zod";
2160
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2354
+ import z12 from "zod";
2355
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2161
2356
  import { dirname as dirname5 } from "node:path";
2357
+
2358
+ // src/lib/algoliaApiKey.ts
2359
+ import { z as z11 } from "zod";
2360
+ var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2361
+ var WRITE_ACLS = [
2362
+ "addObject",
2363
+ "deleteObject",
2364
+ "settings",
2365
+ "editSettings",
2366
+ "listIndexes"
2367
+ ];
2368
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2369
+ var apiKeySchema = z11.object({
2370
+ value: z11.string().min(1),
2371
+ acl: z11.array(z11.string()).default([]),
2372
+ indexes: z11.array(z11.string()).default([])
2373
+ });
2374
+ var apiKeyListSchema = z11.object({
2375
+ items: z11.array(apiKeySchema).optional(),
2376
+ keys: z11.array(apiKeySchema).optional()
2377
+ }).transform((o) => o.items ?? o.keys ?? []);
2378
+ var createdKeySchema = z11.object({
2379
+ key: z11.string().min(1).optional(),
2380
+ value: z11.string().min(1).optional()
2381
+ });
2382
+ function canReuse(key, index) {
2383
+ return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2384
+ }
2385
+ async function createSearchKey(index) {
2386
+ const stdout = await runAlgoliaCli([
2387
+ "apikeys",
2388
+ "create",
2389
+ "--indices",
2390
+ index,
2391
+ "--acl",
2392
+ "search,browse",
2393
+ "--description",
2394
+ `wizard search-only key for ${index}`,
2395
+ "-o",
2396
+ "json"
2397
+ ]);
2398
+ const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2399
+ const created = key ?? value;
2400
+ if (!created) throw new Error("apikeys create returned no key value");
2401
+ return created;
2402
+ }
2403
+ function canReuseForWrites(key, index) {
2404
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2405
+ }
2406
+ async function resolveWriteKey(index) {
2407
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2408
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2409
+ if (existing) {
2410
+ logger.info({ index }, "reusing existing write API key");
2411
+ return existing;
2412
+ }
2413
+ logger.info({ index }, "no reusable write key found; creating one");
2414
+ const created = await runAlgoliaCli([
2415
+ "apikeys",
2416
+ "create",
2417
+ "--indices",
2418
+ index,
2419
+ "--acl",
2420
+ WRITE_ACLS.join(","),
2421
+ "--description",
2422
+ `wizard write key for ${index}`,
2423
+ "-o",
2424
+ "json"
2425
+ ]);
2426
+ const { key, value } = createdKeySchema.parse(JSON.parse(created));
2427
+ const writeKey = key ?? value;
2428
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2429
+ return writeKey;
2430
+ }
2431
+ async function resolveSearchOnlyKey(index) {
2432
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2433
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2434
+ if (existing) {
2435
+ logger.info({ index }, "reusing existing search-only API key");
2436
+ return existing;
2437
+ }
2438
+ logger.info({ index }, "no reusable search-only key found; creating one");
2439
+ return createSearchKey(index);
2440
+ }
2441
+
2442
+ // src/lib/tools/writeAlgoliaCredentials.ts
2162
2443
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2163
2444
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2164
2445
  function appendEnv(content, entries) {
@@ -2172,9 +2453,9 @@ function hasEnv(content, name) {
2172
2453
  }
2173
2454
  function writeCredentialsTool(ctx) {
2174
2455
  return tool6({
2175
- 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.`,
2176
- inputSchema: z9.object({
2177
- filePath: z9.string().describe(
2456
+ 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.`,
2457
+ inputSchema: z12.object({
2458
+ filePath: z12.string().describe(
2178
2459
  'Path to the env file to write credentials into (e.g. ".env")'
2179
2460
  )
2180
2461
  }),
@@ -2182,11 +2463,17 @@ function writeCredentialsTool(ctx) {
2182
2463
  logger.info({ filePath }, "called writeCredentials tool");
2183
2464
  const resolved = resolveInRoot(ctx, filePath);
2184
2465
  if (resolved.ok === false) return resolved.error;
2185
- let profile;
2466
+ const targetIndex = useWizard.getState().targetIndex;
2467
+ if (!targetIndex) {
2468
+ return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2469
+ }
2470
+ let appId;
2471
+ let writeKey;
2186
2472
  try {
2187
- profile = await loadActiveProfile();
2188
- } catch {
2189
- return "Error: no Algolia profile is configured, so credentials cannot be written. Ask the user to authenticate with the Algolia CLI first.";
2473
+ appId = (await requireApplication()).id;
2474
+ writeKey = await resolveWriteKey(targetIndex);
2475
+ } catch (err) {
2476
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2190
2477
  }
2191
2478
  try {
2192
2479
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2194,7 +2481,7 @@ function writeCredentialsTool(ctx) {
2194
2481
  }
2195
2482
  let existing = "";
2196
2483
  try {
2197
- existing = await readFile5(resolved.target, "utf8");
2484
+ existing = await readFile4(resolved.target, "utf8");
2198
2485
  } catch (err) {
2199
2486
  if (err.code !== "ENOENT") throw err;
2200
2487
  }
@@ -2205,8 +2492,8 @@ function writeCredentialsTool(ctx) {
2205
2492
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2206
2493
  }
2207
2494
  const envWithCredentials = appendEnv(existing, [
2208
- [APP_ID_VAR, profile.appId],
2209
- [API_KEY_VAR, profile.apiKey]
2495
+ [APP_ID_VAR, appId],
2496
+ [API_KEY_VAR, writeKey]
2210
2497
  ]);
2211
2498
  await mkdir4(dirname5(resolved.target), { recursive: true });
2212
2499
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2220,16 +2507,16 @@ function writeCredentialsTool(ctx) {
2220
2507
 
2221
2508
  // src/lib/tools/searchFiles.ts
2222
2509
  import { tool as tool7 } from "ai";
2223
- import z10 from "zod";
2224
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2225
- import { join as join8 } from "node:path";
2510
+ import z13 from "zod";
2511
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2512
+ import { join as join7 } from "node:path";
2226
2513
  var MAX_QUERY_LENGTH = 1e3;
2227
2514
  async function walkFiles(dir) {
2228
2515
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2229
2516
  const out = [];
2230
2517
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2231
2518
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2232
- const full = join8(dir, e.name);
2519
+ const full = join7(dir, e.name);
2233
2520
  if (e.isDirectory()) out.push(...await walkFiles(full));
2234
2521
  else if (e.isFile()) out.push(full);
2235
2522
  }
@@ -2238,9 +2525,9 @@ async function walkFiles(dir) {
2238
2525
  function searchFilesTool(ctx) {
2239
2526
  return tool7({
2240
2527
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2241
- inputSchema: z10.object({
2242
- query: z10.string().describe("JavaScript RegExp pattern to search for"),
2243
- path: z10.string().optional().describe("Directory to search in (default: cwd)")
2528
+ inputSchema: z13.object({
2529
+ query: z13.string().describe("JavaScript RegExp pattern to search for"),
2530
+ path: z13.string().optional().describe("Directory to search in (default: cwd)")
2244
2531
  }),
2245
2532
  execute: async ({ query, path = "." }) => {
2246
2533
  logger.info({ query, path }, "called searchFiles tool");
@@ -2262,7 +2549,7 @@ function searchFilesTool(ctx) {
2262
2549
  for (const file of await walkFiles(resolved.target)) {
2263
2550
  let content;
2264
2551
  try {
2265
- content = await readFile6(file, "utf8");
2552
+ content = await readFile5(file, "utf8");
2266
2553
  } catch {
2267
2554
  continue;
2268
2555
  }
@@ -2284,7 +2571,7 @@ function searchFilesTool(ctx) {
2284
2571
 
2285
2572
  // src/lib/tools/verifyImplementation.ts
2286
2573
  import { tool as tool8 } from "ai";
2287
- import z11 from "zod";
2574
+ import z14 from "zod";
2288
2575
 
2289
2576
  // src/lib/tools/utils/runCommand.ts
2290
2577
  import { spawn as spawn2 } from "node:child_process";
@@ -2306,9 +2593,9 @@ function runCommand(command, args, cwd) {
2306
2593
  }
2307
2594
 
2308
2595
  // src/lib/tools/utils/packageManager.ts
2309
- import { readFile as readFile7 } from "node:fs/promises";
2596
+ import { readFile as readFile6 } from "node:fs/promises";
2310
2597
  import { existsSync } from "node:fs";
2311
- import { join as join9 } from "node:path";
2598
+ import { join as join8 } from "node:path";
2312
2599
  var LOCKFILES = [
2313
2600
  ["pnpm-lock.yaml", "pnpm"],
2314
2601
  ["yarn.lock", "yarn"],
@@ -2317,13 +2604,13 @@ var LOCKFILES = [
2317
2604
  ["package-lock.json", "npm"]
2318
2605
  ];
2319
2606
  async function readPackageJson(cwd = process.cwd()) {
2320
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2607
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2321
2608
  }
2322
2609
  function packageManagerFrom(pkg) {
2323
2610
  return pkg.packageManager?.split("@")[0] ?? "npm";
2324
2611
  }
2325
2612
  function packageManagerFromLockfile(cwd) {
2326
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2613
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2327
2614
  }
2328
2615
  async function detectPackageManager(cwd) {
2329
2616
  try {
@@ -2364,7 +2651,7 @@ async function runRepoVerificationCheck() {
2364
2651
  function verifyImplementationTool() {
2365
2652
  return tool8({
2366
2653
  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.",
2367
- inputSchema: z11.object(),
2654
+ inputSchema: z14.object(),
2368
2655
  execute: async () => {
2369
2656
  logger.info("called verifyImplementation tool");
2370
2657
  return runRepoVerificationCheck();
@@ -2378,7 +2665,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2378
2665
  import { nanoid as nanoid2 } from "nanoid";
2379
2666
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2380
2667
  import { dirname as dirname6 } from "node:path";
2381
- import z12 from "zod";
2668
+ import z15 from "zod";
2382
2669
  var DATA_DIR = ".algolia-wizard/data";
2383
2670
  var RECORD_MODEL = "claude-haiku-4-5";
2384
2671
  var MAX_RECORDS = 100;
@@ -2390,17 +2677,17 @@ var anthropic = createAnthropic({
2390
2677
  function generateRecordTool(ctx) {
2391
2678
  return tool9({
2392
2679
  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.",
2393
- inputSchema: z12.object({
2394
- entityName: z12.string().describe("Name of the entity to generate records for."),
2395
- attributes: z12.array(z12.string()).describe("Attribute names each record must contain."),
2396
- count: z12.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2397
- hint: z12.string().optional().describe("Optional context to steer realistic values.")
2680
+ inputSchema: z15.object({
2681
+ entityName: z15.string().describe("Name of the entity to generate records for."),
2682
+ attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2683
+ count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2684
+ hint: z15.string().optional().describe("Optional context to steer realistic values.")
2398
2685
  }),
2399
2686
  execute: async ({ entityName, attributes, count, hint }) => {
2400
2687
  logger.info({ entityName, count }, "called generateRecord tool");
2401
2688
  try {
2402
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2403
- const recordSchema = z12.object(
2689
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2690
+ const recordSchema = z15.object(
2404
2691
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2405
2692
  );
2406
2693
  const generateBatch = async (batchCount) => {
@@ -2410,8 +2697,8 @@ function generateRecordTool(ctx) {
2410
2697
  const { output } = await generateText({
2411
2698
  model: anthropic(RECORD_MODEL),
2412
2699
  output: Output.object({
2413
- schema: z12.object({
2414
- records: z12.array(recordSchema).length(batchCount)
2700
+ schema: z15.object({
2701
+ records: z15.array(recordSchema).length(batchCount)
2415
2702
  })
2416
2703
  }),
2417
2704
  prompt: [
@@ -2469,12 +2756,12 @@ function generateRecordTool(ctx) {
2469
2756
 
2470
2757
  // src/lib/tools/notifyUser.ts
2471
2758
  import { tool as tool10 } from "ai";
2472
- import z13 from "zod";
2759
+ import z16 from "zod";
2473
2760
  function notifyUserTool() {
2474
2761
  return tool10({
2475
2762
  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.`,
2476
- inputSchema: z13.object({
2477
- message: z13.string().describe(
2763
+ inputSchema: z16.object({
2764
+ message: z16.string().describe(
2478
2765
  "Short, plain-language description of what you are doing now."
2479
2766
  )
2480
2767
  }),
@@ -2654,10 +2941,10 @@ async function runAgent(req) {
2654
2941
  }
2655
2942
 
2656
2943
  // src/actions/detectLanguage.ts
2657
- import z16 from "zod";
2658
- var detectLanguageSchema = z16.object({
2659
- languages: z16.array(z16.object({ name: z16.string(), version: z16.string() })),
2660
- frameworks: z16.array(z16.object({ name: z16.string(), version: z16.string() }))
2944
+ import z19 from "zod";
2945
+ var detectLanguageSchema = z19.object({
2946
+ languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
2947
+ frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
2661
2948
  });
2662
2949
  var detectLanguage = () => runAgent({
2663
2950
  instructions: [
@@ -2675,31 +2962,31 @@ var detectLanguage = () => runAgent({
2675
2962
  });
2676
2963
 
2677
2964
  // src/actions/analyzeCodebase.ts
2678
- import z17 from "zod";
2965
+ import z20 from "zod";
2679
2966
  var READONLY_TOOLS = [
2680
2967
  "listFiles",
2681
2968
  "changeDirectory",
2682
2969
  "readFile",
2683
2970
  "searchFiles"
2684
2971
  ];
2685
- var ingestionAnalysisSchema = z17.object({
2686
- ingestionAnalysis: z17.array(
2687
- z17.object({
2688
- name: z17.string(),
2689
- paths: z17.array(z17.string()),
2972
+ var ingestionAnalysisSchema = z20.object({
2973
+ ingestionAnalysis: z20.array(
2974
+ z20.object({
2975
+ name: z20.string(),
2976
+ paths: z20.array(z20.string()),
2690
2977
  // indexable fields the agent found for this entity
2691
- attributes: z17.array(z17.string())
2978
+ attributes: z20.array(z20.string())
2692
2979
  })
2693
2980
  )
2694
2981
  });
2695
- var searchImplementationAnalysisSchema = z17.object({
2696
- searchImplementationAnalysis: z17.string()
2982
+ var searchImplementationAnalysisSchema = z20.object({
2983
+ searchImplementationAnalysis: z20.string()
2697
2984
  });
2698
- var verificationSchema = z17.object({
2699
- verification: z17.array(z17.string())
2985
+ var verificationSchema = z20.object({
2986
+ verification: z20.array(z20.string())
2700
2987
  });
2701
2988
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
2702
- var analyzeCodebaseSchema = z17.object({
2989
+ var analyzeCodebaseSchema = z20.object({
2703
2990
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2704
2991
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
2705
2992
  verification: verificationSchema.shape.verification.optional(),
@@ -2761,7 +3048,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2761
3048
  // package.json
2762
3049
  var package_default = {
2763
3050
  name: "@algolia/wizard",
2764
- version: "0.8.0",
3051
+ version: "0.9.0-rc.53.58",
2765
3052
  description: "Magically implement Algolia functionality in your codebase",
2766
3053
  type: "module",
2767
3054
  engines: {
@@ -2809,7 +3096,6 @@ var package_default = {
2809
3096
  dependencies: {
2810
3097
  "@ai-sdk/anthropic": "^3.0.81",
2811
3098
  "@ai-sdk/openai-compatible": "^2.0.47",
2812
- "@algolia/cli": "^5.11.0",
2813
3099
  "@hono/node-server": "^2.0.10",
2814
3100
  "@mishieck/ink-titled-box": "^0.4.2",
2815
3101
  "@segment/analytics-node": "^3.1.0",
@@ -2824,7 +3110,6 @@ var package_default = {
2824
3110
  nanoid: "^5.1.15",
2825
3111
  pino: "^10.3.1",
2826
3112
  react: "^19.2.7",
2827
- toml: "^4.1.1",
2828
3113
  varlock: "^1.5.1",
2829
3114
  zod: "^4.4.3",
2830
3115
  zustand: "^5.0.14"
@@ -2882,8 +3167,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
2882
3167
  }
2883
3168
 
2884
3169
  // src/actions/confirmLanguage.ts
2885
- import z19 from "zod";
2886
- var confirmLanguageSchema = z19.object({
3170
+ import z22 from "zod";
3171
+ var confirmLanguageSchema = z22.object({
2887
3172
  languages: detectLanguageSchema.shape.languages
2888
3173
  });
2889
3174
  async function confirmLanguage(ctx) {
@@ -2904,8 +3189,8 @@ async function confirmLanguage(ctx) {
2904
3189
  }
2905
3190
 
2906
3191
  // src/actions/confirmFramework.ts
2907
- import z20 from "zod";
2908
- var confirmFrameworkSchema = z20.object({
3192
+ import z23 from "zod";
3193
+ var confirmFrameworkSchema = z23.object({
2909
3194
  frameworks: detectLanguageSchema.shape.frameworks
2910
3195
  });
2911
3196
  var CURATED_FRAMEWORKS = [
@@ -3033,8 +3318,8 @@ async function promptUser(ctx, params) {
3033
3318
  }
3034
3319
 
3035
3320
  // src/actions/confirmEntities.ts
3036
- import z21 from "zod";
3037
- var confirmEntitiesSchema = z21.object({
3321
+ import z24 from "zod";
3322
+ var confirmEntitiesSchema = z24.object({
3038
3323
  // Final detection — the focused re-run may supersede project-scan's.
3039
3324
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3040
3325
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3104,15 +3389,15 @@ async function confirmEntities(ctx) {
3104
3389
  }
3105
3390
 
3106
3391
  // src/actions/review.ts
3107
- import { z as z22 } from "zod";
3108
- var reviewSchema = z22.object({
3392
+ import { z as z25 } from "zod";
3393
+ var reviewSchema = z25.object({
3109
3394
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3110
3395
  // not one entry per workflow step — a step's raw output can be a long,
3111
3396
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3112
3397
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3113
- summaryPoints: z22.array(z22.string()),
3114
- reviewPrompt: z22.string(),
3115
- nextSteps: z22.array(z22.string())
3398
+ summaryPoints: z25.array(z25.string()),
3399
+ reviewPrompt: z25.string(),
3400
+ nextSteps: z25.array(z25.string())
3116
3401
  });
3117
3402
  function formatCompletedSteps(steps) {
3118
3403
  if (!steps.length) return "(no prior steps completed)";
@@ -3163,16 +3448,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3163
3448
  };
3164
3449
 
3165
3450
  // src/actions/implement.ts
3166
- import z24 from "zod";
3451
+ import z26 from "zod";
3167
3452
 
3168
3453
  // src/lib/worktree.ts
3169
3454
  import { execFile, spawn as spawn3 } from "node:child_process";
3170
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3455
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3171
3456
  import {
3172
3457
  basename as basename2,
3173
3458
  dirname as dirname7,
3174
3459
  isAbsolute as isAbsolute2,
3175
- join as join10,
3460
+ join as join9,
3176
3461
  relative as relative2,
3177
3462
  resolve as resolve3
3178
3463
  } from "node:path";
@@ -3206,7 +3491,7 @@ async function isWorkingTreeDirty(repoRoot) {
3206
3491
  return out.trim().length > 0;
3207
3492
  }
3208
3493
  async function pruneOldWorktrees(repoRoot) {
3209
- const dir = join10(stateDir(repoRoot), "worktrees");
3494
+ const dir = join9(stateDir(repoRoot), "worktrees");
3210
3495
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3211
3496
  for (const slug of stale) {
3212
3497
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3217,7 +3502,7 @@ async function pruneOldWorktrees(repoRoot) {
3217
3502
  "worktree",
3218
3503
  "remove",
3219
3504
  "--force",
3220
- join10(dir, slug)
3505
+ join9(dir, slug)
3221
3506
  ]);
3222
3507
  await git(["-C", repoRoot, "branch", "-D", branch]);
3223
3508
  } catch (err) {
@@ -3231,7 +3516,7 @@ async function pruneOldWorktrees(repoRoot) {
3231
3516
  async function createWorktree(repoRoot) {
3232
3517
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3233
3518
  const dirSlug = branch.replace(/\//g, "-");
3234
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3519
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3235
3520
  await git(["-C", repoRoot, "worktree", "prune"]);
3236
3521
  await pruneOldWorktrees(repoRoot);
3237
3522
  await mkdir6(dirname7(path), { recursive: true });
@@ -3351,8 +3636,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3351
3636
  } catch {
3352
3637
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3353
3638
  }
3354
- const relPath = join10(ingestDir, basename2(source));
3355
- const dest = join10(worktreePath, relPath);
3639
+ const relPath = join9(ingestDir, basename2(source));
3640
+ const dest = join9(worktreePath, relPath);
3356
3641
  try {
3357
3642
  await mkdir6(dirname7(dest), { recursive: true });
3358
3643
  await copyFile(source, dest);
@@ -3368,10 +3653,10 @@ function hasEnvVar(content, name) {
3368
3653
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3369
3654
  }
3370
3655
  async function writeSearchEnvValues(worktreePath, vars) {
3371
- const target = join10(worktreePath, ".env");
3656
+ const target = join9(worktreePath, ".env");
3372
3657
  let existing = "";
3373
3658
  try {
3374
- existing = await readFile8(target, "utf8");
3659
+ existing = await readFile7(target, "utf8");
3375
3660
  } catch (err) {
3376
3661
  if (err.code !== "ENOENT") throw err;
3377
3662
  }
@@ -3439,63 +3724,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3439
3724
  }
3440
3725
  }
3441
3726
 
3442
- // src/lib/algoliaApiKey.ts
3443
- import { z as z23 } from "zod";
3444
- var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
3445
- var apiKeySchema = z23.object({
3446
- value: z23.string().min(1),
3447
- acl: z23.array(z23.string()).default([]),
3448
- indexes: z23.array(z23.string()).default([])
3449
- });
3450
- var apiKeyListSchema = z23.object({
3451
- items: z23.array(apiKeySchema).optional(),
3452
- keys: z23.array(apiKeySchema).optional()
3453
- }).transform((o) => o.items ?? o.keys ?? []);
3454
- var createdKeySchema = z23.object({
3455
- key: z23.string().min(1).optional(),
3456
- value: z23.string().min(1).optional()
3457
- });
3458
- function canReuse(key, index) {
3459
- return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
3460
- }
3461
- async function createSearchKey(index) {
3462
- const stdout = await runAlgoliaCli([
3463
- "apikeys",
3464
- "create",
3465
- "--indices",
3466
- index,
3467
- "--acl",
3468
- "search,browse",
3469
- "--description",
3470
- `wizard search-only key for ${index}`,
3471
- "-o",
3472
- "json"
3473
- ]);
3474
- const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
3475
- const created = key ?? value;
3476
- if (!created) throw new Error("apikeys create returned no key value");
3477
- return created;
3478
- }
3479
- async function resolveSearchOnlyKey(index) {
3480
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
3481
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
3482
- if (existing) {
3483
- logger.info({ index }, "reusing existing search-only API key");
3484
- return existing;
3485
- }
3486
- logger.info({ index }, "no reusable search-only key found; creating one");
3487
- return createSearchKey(index);
3488
- }
3489
-
3490
3727
  // src/lib/algoliaDocs.ts
3491
3728
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3492
- import { dirname as dirname8, join as join11 } from "node:path";
3729
+ import { dirname as dirname8, join as join10 } from "node:path";
3493
3730
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3494
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3731
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3495
3732
  function findDocsDir() {
3496
3733
  let dir = dirname8(fileURLToPath2(import.meta.url));
3497
3734
  for (; ; ) {
3498
- const candidate = join11(dir, DOCS_SUBPATH);
3735
+ const candidate = join10(dir, DOCS_SUBPATH);
3499
3736
  if (existsSync2(candidate)) return candidate;
3500
3737
  const parent = dirname8(dir);
3501
3738
  if (parent === dir) return void 0;
@@ -3518,7 +3755,7 @@ function loadAlgoliaDoc(language) {
3518
3755
  );
3519
3756
  return "";
3520
3757
  }
3521
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3758
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3522
3759
  }
3523
3760
  function getNamedDoc(name, language) {
3524
3761
  const docsDir = findDocsDir();
@@ -3526,7 +3763,7 @@ function getNamedDoc(name, language) {
3526
3763
  logger.warn("docs/algolia-sdk not found");
3527
3764
  return "";
3528
3765
  }
3529
- const file = join11(docsDir, `${name}-${language}.md`);
3766
+ const file = join10(docsDir, `${name}-${language}.md`);
3530
3767
  if (!existsSync2(file)) {
3531
3768
  logger.warn({ name, language }, "named SDK reference not found");
3532
3769
  return "";
@@ -3553,50 +3790,34 @@ function shellQuote(value) {
3553
3790
  }
3554
3791
 
3555
3792
  // src/actions/implement.ts
3556
- var implementSchema = z24.object({
3557
- filesChanged: z24.array(z24.string()),
3558
- summary: z24.string(),
3559
- // Absolute path to the throwaway worktree holding the generated changes, so
3560
- // the user can open it (`cd <worktreePath>`) or inspect the diff
3561
- // (`git -C <worktreePath> status/diff`).
3562
- worktreePath: z24.string().optional(),
3563
- ingestCommand: z24.string().optional(),
3564
- // True when the user accepted the run-now prompt and the wizard executed the
3565
- // ingestion script; downstream steps use this to avoid telling the user to run
3566
- // a script that already ran.
3567
- ingestScriptRan: z24.boolean().optional(),
3568
- // Records ingested by the run-now execution, parsed from the script's
3569
- // machine-readable count line; absent when the script didn't run or emitted
3570
- // no parseable count.
3571
- ingestRecordCount: z24.number().optional(),
3572
- // Wall-clock duration of the run-now ingestion execution, in ms.
3573
- ingestDurationMs: z24.number().optional(),
3574
- ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3575
- // Suggested names/values, built from framework detection. The search agent is
3576
- // instructed to rename the prefix if it doesn't match the project's build
3577
- // tool, so the names it actually wrote can differ — treat these as hints, not
3578
- // ground truth (the agent's summary carries the final names).
3579
- searchEnvVars: z24.array(
3580
- z24.object({
3581
- name: z24.string(),
3582
- value: z24.string()
3793
+ var implementSchema = z26.object({
3794
+ filesChanged: z26.array(z26.string()),
3795
+ summary: z26.string(),
3796
+ worktreePath: z26.string().optional(),
3797
+ ingestCommand: z26.string().optional(),
3798
+ ingestScriptRan: z26.boolean().optional(),
3799
+ ingestRecordCount: z26.number().optional(),
3800
+ ingestDurationMs: z26.number().optional(),
3801
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3802
+ searchEnvVars: z26.array(
3803
+ z26.object({
3804
+ name: z26.string(),
3805
+ value: z26.string()
3583
3806
  })
3584
3807
  ).optional()
3585
3808
  });
3586
- var implementationOutputSchema = z24.object({
3587
- summary: z24.string(),
3588
- // Ingestion only: how to run the generated script, as a structured pair the
3589
- // wizard turns into an argv (`<runtime> <entrypoint>`) never a free-form
3590
- // command string. `runtime` is constrained to an allowlisted interpreter and
3591
- // `entrypoint` is validated to a worktree-relative path before execution, so
3592
- // the agent cannot inject extra commands or swap the interpreter.
3593
- runtime: z24.enum(INGEST_RUNTIMES).optional(),
3594
- entrypoint: z24.string().optional()
3809
+ var implementationOutputSchema = z26.object({
3810
+ summary: z26.string(),
3811
+ // Ingestion only: a structured pair the wizard turns into an argv, never a
3812
+ // free-form command string. `runtime` is allowlisted and `entrypoint` is
3813
+ // validated worktree-relative, so the agent cannot inject extra commands.
3814
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3815
+ entrypoint: z26.string().optional()
3595
3816
  });
3596
- var verificationOutputSchema = z24.object({
3597
- summary: z24.string(),
3598
- sufficient: z24.boolean(),
3599
- additionalInstructions: z24.string().optional()
3817
+ var verificationOutputSchema = z26.object({
3818
+ summary: z26.string(),
3819
+ sufficient: z26.boolean(),
3820
+ additionalInstructions: z26.string().optional()
3600
3821
  });
3601
3822
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3602
3823
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3668,9 +3889,6 @@ function sourceSpecificInstructions(input) {
3668
3889
  "Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
3669
3890
  ],
3670
3891
  fileUpload: [
3671
- // The wizard already copied the developer's file into the worktree at this
3672
- // exact path, so the agent must read it directly — never search for or
3673
- // substitute another file.
3674
3892
  `Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
3675
3893
  "Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
3676
3894
  "Map parsed columns/fields to the confirmed entity attributes.",
@@ -3710,12 +3928,9 @@ function searchInstructions(input) {
3710
3928
  doc,
3711
3929
  `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.`,
3712
3930
  "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.",
3713
- // appId always resolves (loadActiveProfile throws otherwise); only the
3714
- // search-only key is best-effort and can fall back to a placeholder.
3715
3931
  `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3716
- // Names are fixed, not the agent's to rename: the wizard writes the
3717
- // resolved app id / search-only key into ".env" under these exact names
3718
- // right after this step, so a renamed prefix here would leave the code
3932
+ // Not the agent's to rename: the wizard writes these exact names into
3933
+ // ".env" right after this step, so a renamed prefix would leave the code
3719
3934
  // reading a var the wizard never wrote.
3720
3935
  `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3721
3936
  '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.',
@@ -3859,6 +4074,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3859
4074
  }
3860
4075
  }
3861
4076
  const targetIndex = selected?.selection;
4077
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
3862
4078
  await assertGitRepoWithHead(repoRoot);
3863
4079
  if (await isWorkingTreeDirty(repoRoot)) {
3864
4080
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -3869,7 +4085,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3869
4085
  let appId;
3870
4086
  let searchKey;
3871
4087
  if (useCases.includes("search")) {
3872
- appId = (await loadActiveProfile()).appId;
4088
+ appId = (await requireApplication()).id;
3873
4089
  try {
3874
4090
  searchKey = await resolveSearchOnlyKey(targetIndex);
3875
4091
  } catch (err) {
@@ -3914,8 +4130,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3914
4130
  ingestDir: INGEST_DIR,
3915
4131
  ingestionSource,
3916
4132
  uploadFilePath,
3917
- // language.frameworks already prefers the confirm-framework step output,
3918
- // so the user's confirmed stack (not just raw detection) picks the flavor.
3919
4133
  uiFramework: detectUiFramework(language)
3920
4134
  };
3921
4135
  const summaries = [];
@@ -3980,7 +4194,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3980
4194
  messages: []
3981
4195
  }) === true;
3982
4196
  if (runNow) {
3983
- const profile = await loadActiveProfile();
4197
+ const ingestApp = await requireApplication();
4198
+ const writeKey = await resolveWriteKey(targetIndex);
3984
4199
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
3985
4200
  const scriptLogId = ctx.logStart("runIngestScript", {
3986
4201
  runtime: ingestRuntime,
@@ -3992,8 +4207,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3992
4207
  ingestRuntime,
3993
4208
  ingestEntrypoint,
3994
4209
  {
3995
- [APP_ID_VAR]: profile.appId,
3996
- [API_KEY_VAR]: profile.apiKey
4210
+ [APP_ID_VAR]: ingestApp.id,
4211
+ [API_KEY_VAR]: writeKey
3997
4212
  }
3998
4213
  );
3999
4214
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4062,8 +4277,6 @@ ${run2.output}` : status;
4062
4277
  );
4063
4278
  }
4064
4279
  await ctx.requestUserInput({
4065
- // No question being asked here, just an acknowledgement — the
4066
- // continue/decline hints below already say "continue".
4067
4280
  prompt: "",
4068
4281
  promptType: "enterToContinue",
4069
4282
  options: [],
@@ -4204,8 +4417,8 @@ var defaultWorkflow = {
4204
4417
  defineStep({
4205
4418
  id: "select-index",
4206
4419
  title: "Set up index",
4207
- outputSchema: z25.object({
4208
- selection: z25.string()
4420
+ outputSchema: z27.object({
4421
+ selection: z27.string()
4209
4422
  }),
4210
4423
  run: (ctx) => selectIndexStep(ctx)
4211
4424
  }),
@@ -4484,7 +4697,7 @@ function parseCliArgs(argv) {
4484
4697
 
4485
4698
  // src/lib/resetState.ts
4486
4699
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4487
- import { join as join12 } from "node:path";
4700
+ import { join as join11 } from "node:path";
4488
4701
  var KEEP = ["wizard.log"];
4489
4702
  async function resetProjectState() {
4490
4703
  const dir = stateDir();
@@ -4496,7 +4709,7 @@ async function resetProjectState() {
4496
4709
  }
4497
4710
  const targets = entries.filter((name) => !KEEP.includes(name));
4498
4711
  await Promise.all(
4499
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4712
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4500
4713
  );
4501
4714
  return { dir, removed: targets };
4502
4715
  }
@@ -4551,31 +4764,38 @@ ${formatStepList(workflow)}`);
4551
4764
  }
4552
4765
  async function run(workflow) {
4553
4766
  const store = useWizard.getState();
4554
- let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4767
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4768
+ await store.waitForStart();
4555
4769
  let user = await getUser();
4556
4770
  if (!user) {
4557
- await instance.waitUntilRenderFlush();
4558
- instance.cleanup();
4771
+ store.beginAuth();
4559
4772
  try {
4560
4773
  await runAuthLogin();
4561
4774
  } catch (err) {
4562
- console.error(err instanceof Error ? err.message : String(err));
4775
+ store.setError(err instanceof Error ? err.message : String(err));
4776
+ await instance.waitUntilExit();
4563
4777
  process.exit(1);
4564
4778
  }
4565
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4779
+ store.endAuth();
4566
4780
  user = await getUser();
4567
4781
  if (!user) {
4568
4782
  store.setError(
4569
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4783
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
4570
4784
  );
4571
4785
  await instance.waitUntilExit();
4572
4786
  process.exit(1);
4573
4787
  }
4574
4788
  }
4575
4789
  store.setUser(user);
4576
- const profile = await loadActiveProfile();
4577
- await store.waitForStart();
4578
- runWorkflow(workflow, profile?.appId);
4790
+ let app;
4791
+ try {
4792
+ app = await ensureApplication();
4793
+ } catch (err) {
4794
+ store.setError(err instanceof Error ? err.message : String(err));
4795
+ await instance.waitUntilExit();
4796
+ process.exit(1);
4797
+ }
4798
+ runWorkflow(workflow, app.id);
4579
4799
  }
4580
4800
  var started = await startup();
4581
4801
  if (typeof started === "number") {