@algolia/wizard 0.8.0 → 0.9.0-rc.53.57

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 +796 -552
  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,26 @@ 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
+ // Sign-in happens after the welcome screen's enter, so `endAuth` lands on
251
+ // 'preflight': returning to 'idle' would put the welcome screen back up and
252
+ // ask the user to confirm the run a second time.
253
+ beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
254
+ endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
255
+ // Only meaningful from 'idle' — once the workflow is running there's nothing
256
+ // left to confirm. `homeScreen` resets so preflight shows Welcome rather than
257
+ // the Learn more sub-view.
192
258
  confirmStart: () => set(
193
259
  (s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
194
260
  ),
195
- // Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
196
261
  openLearnMore: () => set({ homeScreen: "learnMore" }),
197
262
  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`).
263
+ // Resolves whether the phase left 'idle' before or after this is called.
201
264
  waitForStart: () => new Promise((resolve4) => {
202
265
  if (get().phase !== "idle") {
203
266
  resolve4();
@@ -220,15 +283,20 @@ var useWizard = create((set, get) => ({
220
283
  syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
221
284
  setActiveStep: (index) => {
222
285
  get()._clearNoticeQueue();
223
- set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
286
+ set({
287
+ phase: "running",
288
+ currentStepIndex: index,
289
+ output: "",
290
+ notices: [],
291
+ cliOutput: []
292
+ });
224
293
  },
225
294
  setUser: (user) => set({ user }),
226
295
  appendToken: (text) => set((s) => ({ output: s.output + text })),
227
296
  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.
297
+ // Renders the first notice of a burst immediately, then drains later arrivals
298
+ // one per `NOTICE_INTERVAL_MS`. The timer stays armed through an empty drain
299
+ // so the cooldown covers the time since the last render, across bursts.
232
300
  pushNotice: (notice) => {
233
301
  const { notices, _noticeQueue, _noticeTimer } = get();
234
302
  if (_noticeTimer === null) {
@@ -261,6 +329,15 @@ var useWizard = create((set, get) => ({
261
329
  get()._clearNoticeQueue();
262
330
  set({ notices: [] });
263
331
  },
332
+ // Unthrottled, unlike `pushNotice`: holding these back would land output
333
+ // after the command it belongs to has already exited.
334
+ pushCliOutput: (stream, text) => set((s) => ({
335
+ cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
336
+ -CLI_OUTPUT_LIMIT
337
+ )
338
+ })),
339
+ clearCliOutput: () => set({ cliOutput: [] }),
340
+ setTargetIndex: (index) => set({ targetIndex: index }),
264
341
  logStart: (kind, name, input) => {
265
342
  const id = nanoid();
266
343
  set((s) => ({
@@ -283,9 +360,8 @@ var useWizard = create((set, get) => ({
283
360
  _resolve: resolve4
284
361
  });
285
362
  }),
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.
363
+ // Logs what the user picked, not the prompt text that just duplicates
364
+ // on-screen content.
289
365
  submitInput: async (value) => {
290
366
  await markInteraction();
291
367
  get()._resolve?.(value);
@@ -305,6 +381,8 @@ var useWizard = create((set, get) => ({
305
381
  currentStepIndex: 0,
306
382
  output: "",
307
383
  notices: [],
384
+ cliOutput: [],
385
+ targetIndex: null,
308
386
  logs: [],
309
387
  error: null,
310
388
  inputReq: null,
@@ -313,16 +391,100 @@ var useWizard = create((set, get) => ({
313
391
  }
314
392
  }));
315
393
 
394
+ // src/ui/CliOutput.tsx
395
+ import { Box, Text, useWindowSize } from "ink";
396
+
397
+ // src/ui/theme.ts
398
+ var MARKER = {
399
+ pending: "\u25CB",
400
+ running: "\u25D0",
401
+ done: "\u2713",
402
+ error: "\u2716"
403
+ };
404
+ var BRAND = "#003DFF";
405
+ var SECONDARY = "#5468FF";
406
+ var DANGER = "#F86E7E";
407
+ var COLORS = {
408
+ brand: BRAND,
409
+ primary: "#E6EDF3",
410
+ secondary: SECONDARY,
411
+ strong: "#FFFFFF",
412
+ muted: "#8B949E",
413
+ dim: "#484F58",
414
+ highlight: { bg: "#12331C", fg: "#4ADE80" },
415
+ badge: "#E3B341",
416
+ danger: DANGER,
417
+ success: "#4ADE80",
418
+ bg: {
419
+ main: "#0B0E14",
420
+ sidebar: "#14171E"
421
+ },
422
+ border: "#30363D",
423
+ accent: "#76A0FF",
424
+ status: {
425
+ pending: "gray",
426
+ running: "#76A0FF",
427
+ done: "#4ADE80",
428
+ error: DANGER
429
+ }
430
+ };
431
+
432
+ // src/ui/CliOutput.tsx
433
+ import { jsxs } from "react/jsx-runtime";
434
+ var CLI_MARKER = "\u203A";
435
+ var RESERVED_ROWS = 16;
436
+ var MAX_ROWS = 12;
437
+ var PANEL_TEXT_WIDTH = 45;
438
+ var URL_PATTERN = /https?:\/\//;
439
+ function rowCost(text) {
440
+ return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
441
+ }
442
+ function CliOutput() {
443
+ const cliOutput = useWizard((s) => s.cliOutput);
444
+ const { rows } = useWindowSize();
445
+ if (!cliOutput.length) return null;
446
+ const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
447
+ const visible = [];
448
+ let usedRows = 0;
449
+ for (let i = cliOutput.length - 1; i >= 0; i--) {
450
+ const cost = rowCost(cliOutput[i].text);
451
+ if (usedRows + cost > rowBudget && visible.length > 0) break;
452
+ visible.unshift(cliOutput[i]);
453
+ usedRows += cost;
454
+ }
455
+ const hidden = cliOutput.length - visible.length;
456
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
457
+ hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
458
+ "\u2191 ",
459
+ hidden,
460
+ " earlier line(s)"
461
+ ] }),
462
+ visible.map((line) => /* @__PURE__ */ jsxs(
463
+ Text,
464
+ {
465
+ color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
466
+ wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
467
+ children: [
468
+ CLI_MARKER,
469
+ " ",
470
+ line.text
471
+ ]
472
+ },
473
+ line.id
474
+ ))
475
+ ] });
476
+ }
477
+
316
478
  // src/ui/Notices.tsx
317
- import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
479
+ import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
318
480
  import { useEffect as useEffect2, useState as useState2 } from "react";
319
481
 
320
482
  // src/ui/Table.tsx
321
- import { Box, Text, measureElement, useWindowSize } from "ink";
483
+ import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
322
484
  import { useEffect, useRef, useState } from "react";
323
485
  import { jsx } from "react/jsx-runtime";
324
486
  function Table({ columns, rows }) {
325
- const { columns: termCols } = useWindowSize();
487
+ const { columns: termCols } = useWindowSize2();
326
488
  const ref = useRef(null);
327
489
  const [width, setWidth] = useState(0);
328
490
  useEffect(() => {
@@ -330,7 +492,7 @@ function Table({ columns, rows }) {
330
492
  }, [termCols, columns, rows]);
331
493
  if (rows.length === 0) return null;
332
494
  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}`)) });
495
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
334
496
  }
335
497
  function formatTable(columns, rows, width) {
336
498
  const natural = columns.map(
@@ -370,48 +532,13 @@ function resize(widths, budget) {
370
532
  }
371
533
  var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
372
534
 
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
535
  // src/ui/Notices.tsx
409
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
536
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
410
537
  var AGENT_MARKER = "\u2726";
411
- var RESERVED_ROWS = 14;
412
- var PANEL_TEXT_WIDTH = 45;
538
+ var RESERVED_ROWS2 = 14;
539
+ var PANEL_TEXT_WIDTH2 = 45;
413
540
  function messageLineCount(text) {
414
- return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
541
+ return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
415
542
  }
416
543
  function noticeLineCount(notice) {
417
544
  const messageLines = (notice.messages ?? []).reduce((sum, m) => {
@@ -422,7 +549,7 @@ function noticeLineCount(notice) {
422
549
  return messageLines + tableLines;
423
550
  }
424
551
  function fitVisibleNotices(notices, windowRows) {
425
- const budget = Math.max(windowRows - RESERVED_ROWS, 3);
552
+ const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
426
553
  let used = 0;
427
554
  let count = 0;
428
555
  for (let i = notices.length - 1; i >= 0; i--) {
@@ -455,7 +582,7 @@ function parseHex(hex) {
455
582
  }
456
583
  function Notices() {
457
584
  const notices = useWizard((s) => s.notices);
458
- const { rows: windowRows } = useWindowSize2();
585
+ const { rows: windowRows } = useWindowSize3();
459
586
  const visible = fitVisibleNotices(notices, windowRows);
460
587
  const [pulseStep, setPulseStep] = useState2(0);
461
588
  useEffect2(() => {
@@ -472,14 +599,14 @@ function Notices() {
472
599
  }, []);
473
600
  if (!visible.length) return null;
474
601
  const pulseColor = PULSE_COLORS[pulseStep];
475
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
602
+ return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
476
603
  const isLatest = i === visible.length - 1;
477
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
604
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
478
605
  notice.messages?.map((m, j) => {
479
606
  const line = typeof m === "string" ? { text: m } : m;
480
607
  const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
481
- return /* @__PURE__ */ jsxs(
482
- Text2,
608
+ return /* @__PURE__ */ jsxs2(
609
+ Text3,
483
610
  {
484
611
  color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
485
612
  bold: line.bold,
@@ -497,41 +624,41 @@ function Notices() {
497
624
  }
498
625
 
499
626
  // src/ui/PromptInput.tsx
500
- import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
627
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
501
628
  import TextInput from "ink-text-input";
502
629
  import { useState as useState5 } from "react";
503
630
 
504
631
  // 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";
632
+ import { Box as Box4, Text as Text4 } from "ink";
633
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
507
634
  function NextAction({
508
635
  action,
509
636
  keyHint,
510
637
  hierarchy = "primary"
511
638
  }) {
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 })
639
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
640
+ hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
641
+ hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
642
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
643
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
517
644
  ] }),
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: `]` })
645
+ /* @__PURE__ */ jsxs3(Box4, { children: [
646
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
647
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
648
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
649
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
523
650
  ] })
524
651
  ] });
525
652
  }
526
653
 
527
654
  // src/ui/SelectPrompt.tsx
528
- import { Box as Box5, Text as Text5, useInput, useWindowSize as useWindowSize4 } from "ink";
655
+ import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
529
656
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
530
657
 
531
658
  // src/ui/ScrollView.tsx
532
- import { Box as Box4, Text as Text4, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
659
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
533
660
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
534
- import { jsxs as jsxs3 } from "react/jsx-runtime";
661
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
535
662
  var INDICATOR_ROWS = 2;
536
663
  function fittedWidth(node, columns) {
537
664
  let left = 0;
@@ -546,7 +673,7 @@ function useScrollWindow({
546
673
  followBottom = false
547
674
  }) {
548
675
  const viewportRef = useRef2(null);
549
- const { columns } = useWindowSize3();
676
+ const { columns } = useWindowSize4();
550
677
  const [size, setSize] = useState3(
551
678
  null
552
679
  );
@@ -601,14 +728,14 @@ function useScrollWindow({
601
728
  };
602
729
  }
603
730
  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: [
731
+ return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
732
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
606
733
  "\u2191 ",
607
734
  scroll.hiddenAbove,
608
735
  " more"
609
736
  ] }),
610
737
  children,
611
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
738
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
612
739
  "\u2193 ",
613
740
  scroll.hiddenBelow,
614
741
  " more"
@@ -617,7 +744,7 @@ function ScrollView({ scroll, children }) {
617
744
  }
618
745
 
619
746
  // src/ui/SelectPrompt.tsx
620
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
747
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
621
748
  var CANCEL = "cancel";
622
749
  var ARROW_WIDTH = 4;
623
750
  var COLUMN_GAP = 2;
@@ -648,7 +775,7 @@ function SelectPrompt({
648
775
  if (multi) hints.push({ key: "[space]", label: "select" });
649
776
  hints.push({ key: "[enter]", label: "confirm" });
650
777
  const containerRef = useRef3(null);
651
- const { columns } = useWindowSize4();
778
+ const { columns } = useWindowSize5();
652
779
  const [width, setWidth] = useState4(columns);
653
780
  useLayoutEffect2(() => {
654
781
  if (!containerRef.current) return;
@@ -702,14 +829,14 @@ function SelectPrompt({
702
829
  }
703
830
  }
704
831
  });
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}`)),
832
+ return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
833
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
834
+ error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
835
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
709
836
  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 })
837
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
838
+ question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
839
+ helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
713
840
  ] })
714
841
  ] }),
715
842
  /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
@@ -719,39 +846,39 @@ function SelectPrompt({
719
846
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
720
847
  const sec = isCancel ? void 0 : secondary?.[i];
721
848
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
722
- const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
849
+ const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
723
850
  highlighted ? "\u276F " : " ",
724
851
  bullet,
725
852
  option
726
853
  ] });
727
854
  const isText = sec?.kind === "text";
728
- return /* @__PURE__ */ jsxs4(
729
- Box5,
855
+ return /* @__PURE__ */ jsxs5(
856
+ Box6,
730
857
  {
731
858
  width: isText ? "100%" : barWidth,
732
859
  paddingX: 1,
733
860
  paddingY: 1,
734
861
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
735
862
  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,
863
+ /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
864
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
865
+ Text6,
739
866
  {
740
867
  wrap: "truncate",
741
868
  color: highlighted ? COLORS.primary : COLORS.muted,
742
869
  children: sec.value
743
870
  }
744
871
  ) }),
745
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
872
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
746
873
  ]
747
874
  },
748
875
  `row-${i}`
749
876
  );
750
877
  }) }),
751
- /* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
878
+ /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
752
879
  i > 0 ? " " : "",
753
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
754
- /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
880
+ /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
881
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
755
882
  " ",
756
883
  label
757
884
  ] })
@@ -760,7 +887,7 @@ function SelectPrompt({
760
887
  }
761
888
 
762
889
  // src/ui/PromptInput.tsx
763
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
890
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
764
891
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
765
892
  function EnterToContinuePrompt({
766
893
  question,
@@ -771,10 +898,10 @@ function EnterToContinuePrompt({
771
898
  if (key.return) onDecide(true);
772
899
  else if (key.escape) onDecide(false);
773
900
  });
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: [
901
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
902
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
903
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
904
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
778
905
  /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
779
906
  /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
780
907
  ] })
@@ -784,11 +911,11 @@ function PromptInput() {
784
911
  const { phase, inputReq, submitInput } = useWizard();
785
912
  const [draft, setDraft] = useState5("");
786
913
  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" }) });
914
+ return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
788
915
  }
789
916
  if (phase !== "awaitingInput" || !inputReq) return null;
790
917
  if (inputReq.promptType === "multipleChoice") {
791
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
918
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
792
919
  SelectPrompt,
793
920
  {
794
921
  question: inputReq.prompt,
@@ -805,7 +932,7 @@ function PromptInput() {
805
932
  ) });
806
933
  }
807
934
  if (inputReq.promptType === "multiSelect") {
808
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
935
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
809
936
  SelectPrompt,
810
937
  {
811
938
  multi: true,
@@ -820,7 +947,7 @@ function PromptInput() {
820
947
  ) });
821
948
  }
822
949
  if (inputReq.promptType === "notice") {
823
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
950
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
824
951
  SelectPrompt,
825
952
  {
826
953
  question: inputReq.prompt,
@@ -842,7 +969,7 @@ function PromptInput() {
842
969
  }
843
970
  if (inputReq.promptType === "acceptReject") {
844
971
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
845
- return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
972
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
846
973
  SelectPrompt,
847
974
  {
848
975
  question: inputReq.prompt,
@@ -853,11 +980,11 @@ function PromptInput() {
853
980
  }
854
981
  ) });
855
982
  }
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: [
983
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
984
+ inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
985
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
986
+ /* @__PURE__ */ jsxs6(Box7, { children: [
987
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
861
988
  inputReq.prompt,
862
989
  " "
863
990
  ] }),
@@ -879,7 +1006,7 @@ function PromptInput() {
879
1006
  // src/ui/Welcome.tsx
880
1007
  import { dirname as dirname2, join as join3 } from "node:path";
881
1008
  import { fileURLToPath } from "node:url";
882
- import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
1009
+ import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
883
1010
 
884
1011
  // src/ui/copy/welcome.ts
885
1012
  var sidebarItems = [
@@ -907,27 +1034,27 @@ var sidebarItems = [
907
1034
 
908
1035
  // src/ui/Welcome.tsx
909
1036
  import Image, { InkPictureProvider } from "ink-picture";
910
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1037
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
911
1038
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
912
1039
  function SidebarItem({
913
1040
  title,
914
1041
  description
915
1042
  }) {
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 })
1043
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1044
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1045
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1046
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
920
1047
  ] }),
921
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
1048
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
922
1049
  /* @__PURE__ */ jsx6(Spacer, {}),
923
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
1050
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
924
1051
  ] })
925
1052
  ] });
926
1053
  }
927
1054
  function Welcome() {
928
1055
  const confirmStart = useWizard((s) => s.confirmStart);
929
1056
  const openLearnMore = useWizard((s) => s.openLearnMore);
930
- const { rows } = useWindowSize5();
1057
+ const { rows } = useWindowSize6();
931
1058
  useInput3((input, key) => {
932
1059
  if (key.return) confirmStart();
933
1060
  else if (input === "i") openLearnMore();
@@ -946,15 +1073,15 @@ function Welcome() {
946
1073
  if (rows < 30) {
947
1074
  layout = scales["small"];
948
1075
  }
949
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1076
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
950
1077
  /* @__PURE__ */ jsx6(
951
- Box7,
1078
+ Box8,
952
1079
  {
953
1080
  paddingY: layout.main.padding.y,
954
1081
  paddingX: layout.main.padding.x,
955
1082
  flexDirection: "column",
956
1083
  justifyContent: "center",
957
- children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
1084
+ children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
958
1085
  /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
959
1086
  Image,
960
1087
  {
@@ -966,16 +1093,16 @@ function Welcome() {
966
1093
  protocol: "halfBlock"
967
1094
  }
968
1095
  ) }),
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: [
1096
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1097
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
971
1098
  /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
972
1099
  /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
973
1100
  ] })
974
1101
  ] })
975
1102
  }
976
1103
  ),
977
- /* @__PURE__ */ jsxs6(
978
- Box7,
1104
+ /* @__PURE__ */ jsxs7(
1105
+ Box8,
979
1106
  {
980
1107
  backgroundColor: COLORS.bg.sidebar,
981
1108
  width: 40,
@@ -985,7 +1112,7 @@ function Welcome() {
985
1112
  flexDirection: "column",
986
1113
  justifyContent: "center",
987
1114
  children: [
988
- /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1115
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
989
1116
  sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
990
1117
  ]
991
1118
  }
@@ -995,7 +1122,7 @@ function Welcome() {
995
1122
 
996
1123
  // src/ui/LearnMore.tsx
997
1124
  import { Fragment as Fragment2 } from "react";
998
- import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1125
+ import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
999
1126
 
1000
1127
  // src/ui/copy/learn-more.ts
1001
1128
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1032,7 +1159,7 @@ var policyLinks = [
1032
1159
  ];
1033
1160
 
1034
1161
  // src/ui/LearnMore.tsx
1035
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1162
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1036
1163
  var TAG_COLORS = {
1037
1164
  READ: COLORS.success,
1038
1165
  WRITE: COLORS.badge,
@@ -1048,25 +1175,25 @@ function NeverLine({
1048
1175
  }) {
1049
1176
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1050
1177
  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" }),
1178
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
1179
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1053
1180
  " ".repeat(NEVER_BOX_PAD_X),
1054
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
1181
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1055
1182
  " ".repeat(rightPad),
1056
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
1183
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1057
1184
  ] });
1058
1185
  }
1059
1186
  function LearnMore() {
1060
1187
  const confirmStart = useWizard((s) => s.confirmStart);
1061
1188
  const backToHome = useWizard((s) => s.backToHome);
1062
- const { columns } = useWindowSize6();
1189
+ const { columns } = useWindowSize7();
1063
1190
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1064
1191
  useInput4((_input, key) => {
1065
1192
  if (key.escape) backToHome();
1066
1193
  else if (key.return) confirmStart();
1067
1194
  });
1068
- return /* @__PURE__ */ jsxs7(
1069
- Box8,
1195
+ return /* @__PURE__ */ jsxs8(
1196
+ Box9,
1070
1197
  {
1071
1198
  flexDirection: "column",
1072
1199
  paddingX: PADDING_X,
@@ -1074,20 +1201,20 @@ function LearnMore() {
1074
1201
  width: "100%",
1075
1202
  gap: 1,
1076
1203
  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}` })
1204
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1205
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1206
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1207
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1208
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1209
+ /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1210
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1211
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1212
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1086
1213
  ] }) })
1087
1214
  ] })
1088
1215
  ] }, 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` }),
1216
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1217
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1091
1218
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1092
1219
  /* @__PURE__ */ jsx7(
1093
1220
  NeverLine,
@@ -1096,7 +1223,7 @@ function LearnMore() {
1096
1223
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1097
1224
  }
1098
1225
  ),
1099
- neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
1226
+ neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1100
1227
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1101
1228
  /* @__PURE__ */ jsx7(
1102
1229
  NeverLine,
@@ -1111,23 +1238,23 @@ function LearnMore() {
1111
1238
  )
1112
1239
  ] }, item)),
1113
1240
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1114
- /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1241
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1115
1242
  ] }),
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 })
1243
+ /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1244
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1245
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1119
1246
  ] }, 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" })
1247
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1248
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1249
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1250
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1251
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1125
1252
  ] }),
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" })
1253
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1254
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1255
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1256
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1257
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1131
1258
  ] })
1132
1259
  ] })
1133
1260
  ]
@@ -1136,10 +1263,10 @@ function LearnMore() {
1136
1263
  }
1137
1264
 
1138
1265
  // src/ui/Sidebar.tsx
1139
- import { Box as Box11, Text as Text11 } from "ink";
1266
+ import { Box as Box12, Text as Text12 } from "ink";
1140
1267
 
1141
1268
  // src/ui/Steps.tsx
1142
- import { Box as Box9, Text as Text9 } from "ink";
1269
+ import { Box as Box10, Text as Text10 } from "ink";
1143
1270
  import Spinner from "ink-spinner";
1144
1271
 
1145
1272
  // src/core/persistence.ts
@@ -1168,11 +1295,11 @@ async function clearWorkflowState(workflowId) {
1168
1295
  }
1169
1296
 
1170
1297
  // src/ui/Steps.tsx
1171
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1298
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1172
1299
  function Steps() {
1173
1300
  const { steps } = useWizard();
1174
1301
  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: [
1302
+ 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
1303
  s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1177
1304
  " ",
1178
1305
  s.title
@@ -1182,7 +1309,7 @@ function CurrentStep() {
1182
1309
  const { steps } = useWizard();
1183
1310
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1184
1311
  if (!currentStep) return null;
1185
- return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
1312
+ return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1186
1313
  /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1187
1314
  " ",
1188
1315
  ` ${currentStep.title}`
@@ -1190,19 +1317,19 @@ function CurrentStep() {
1190
1317
  }
1191
1318
 
1192
1319
  // 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";
1320
+ import { Box as Box11, Text as Text11 } from "ink";
1321
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1195
1322
  function Progress() {
1196
1323
  const { steps, currentStepIndex } = useWizard();
1197
1324
  const visibleSteps = steps.filter(isStepVisible);
1198
1325
  if (visibleSteps.length === 0) return null;
1199
1326
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1200
1327
  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 })
1328
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1329
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1330
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1331
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1332
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1206
1333
  ] });
1207
1334
  }
1208
1335
 
@@ -1213,10 +1340,10 @@ var sidebarCommands = [
1213
1340
  ];
1214
1341
 
1215
1342
  // src/ui/Sidebar.tsx
1216
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1343
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1217
1344
  function Sidebar() {
1218
- return /* @__PURE__ */ jsxs10(
1219
- Box11,
1345
+ return /* @__PURE__ */ jsxs11(
1346
+ Box12,
1220
1347
  {
1221
1348
  backgroundColor: "#14171E",
1222
1349
  width: 30,
@@ -1225,16 +1352,16 @@ function Sidebar() {
1225
1352
  flexDirection: "column",
1226
1353
  justifyContent: "space-between",
1227
1354
  children: [
1228
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1229
- /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
1355
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1356
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1230
1357
  /* @__PURE__ */ jsx10(Steps, {})
1231
1358
  ] }),
1232
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1359
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1233
1360
  /* @__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 })
1361
+ /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1362
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1363
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1364
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1238
1365
  ] });
1239
1366
  }) })
1240
1367
  ] })
@@ -1244,12 +1371,12 @@ function Sidebar() {
1244
1371
  }
1245
1372
 
1246
1373
  // 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";
1374
+ import { Box as Box13, Text as Text13 } from "ink";
1375
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1249
1376
  function Ribbon() {
1250
1377
  const firstCommand = sidebarCommands[0];
1251
- return /* @__PURE__ */ jsxs11(
1252
- Box12,
1378
+ return /* @__PURE__ */ jsxs12(
1379
+ Box13,
1253
1380
  {
1254
1381
  backgroundColor: "#14171E",
1255
1382
  flexDirection: "row",
@@ -1259,9 +1386,9 @@ function Ribbon() {
1259
1386
  children: [
1260
1387
  /* @__PURE__ */ jsx11(Progress, {}),
1261
1388
  /* @__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 })
1389
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1390
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1391
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1265
1392
  ] })
1266
1393
  ]
1267
1394
  }
@@ -1272,8 +1399,8 @@ function Ribbon() {
1272
1399
  import { useState as useState6 } from "react";
1273
1400
 
1274
1401
  // 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";
1402
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1403
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1277
1404
  var KIND_COLOR = {
1278
1405
  tool: COLORS.primary,
1279
1406
  prompt: COLORS.badge
@@ -1309,8 +1436,8 @@ function Logs() {
1309
1436
  else if (key.downArrow) scroll.scrollBy(1);
1310
1437
  });
1311
1438
  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." }),
1439
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1440
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1314
1441
  /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1315
1442
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1316
1443
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
@@ -1321,14 +1448,14 @@ function Logs() {
1321
1448
  const name = truncate2(entry.name, budget);
1322
1449
  budget -= name.length;
1323
1450
  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 })
1451
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1452
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1453
+ /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1454
+ preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1455
+ durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1329
1456
  ] }, entry.id);
1330
1457
  }) }),
1331
- /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1458
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1332
1459
  ] });
1333
1460
  }
1334
1461
 
@@ -1520,11 +1647,11 @@ function track(event, payload) {
1520
1647
  }
1521
1648
 
1522
1649
  // src/ui/App.tsx
1523
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1650
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1524
1651
  function App() {
1525
1652
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1526
1653
  const { exit } = useApp();
1527
- const { columns, rows } = useWindowSize7();
1654
+ const { columns, rows } = useWindowSize8();
1528
1655
  const [showLogs, setShowLogs] = useState6(false);
1529
1656
  const finished = phase === "done" || phase === "error";
1530
1657
  const currentStep = steps[currentStepIndex];
@@ -1537,7 +1664,7 @@ function App() {
1537
1664
  { isActive: finished }
1538
1665
  );
1539
1666
  useInput6((_input, key) => {
1540
- if (phase === "idle" || phase === "preflight") return;
1667
+ if (phase === "idle" || phase === "authenticating") return;
1541
1668
  if (key.tab) {
1542
1669
  setShowLogs(!showLogs);
1543
1670
  track("AI Wizard Interaction", {
@@ -1547,7 +1674,7 @@ function App() {
1547
1674
  });
1548
1675
  }
1549
1676
  });
1550
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1677
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1551
1678
  useInput6((_input, key) => {
1552
1679
  if (escOwnedElsewhere) return;
1553
1680
  if (key.escape) {
@@ -1560,61 +1687,71 @@ function App() {
1560
1687
  exit();
1561
1688
  }
1562
1689
  });
1563
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1690
+ const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1564
1691
  const flexDirection = columns > 90 ? "row" : "column";
1565
1692
  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,
1593
- {
1594
- flexDirection: "column",
1595
- paddingX: 4,
1596
- paddingY: 2,
1597
- width: showSidebar ? 70 : "100%",
1598
- flexGrow: 1,
1599
- children: [
1600
- /* @__PURE__ */ jsx13(Notices, {}),
1601
- /* @__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: [
1604
- "\u2716 ",
1605
- error
1606
- ] }) })
1607
- ]
1608
- }
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
- }
1693
+ const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
1694
+ return (
1695
+ /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1696
+ the whole screen, and the scrolling throws off its cursor arithmetic —
1697
+ flicker and leftover rows. */
1698
+ /* @__PURE__ */ jsxs14(
1699
+ Box15,
1700
+ {
1701
+ backgroundColor: COLORS.bg.main,
1702
+ flexDirection: "row",
1703
+ width: columns,
1704
+ height: scrollsPastViewport ? void 0 : rows,
1705
+ overflow: scrollsPastViewport ? "visible" : "hidden",
1706
+ children: [
1707
+ mainWindowVisible && // Without a cap the scrolling lists in here grow to their content
1708
+ // instead of windowing (see `useScrollWindow`).
1709
+ /* @__PURE__ */ jsxs14(
1710
+ Box15,
1711
+ {
1712
+ flexDirection,
1713
+ width: "100%",
1714
+ maxHeight: rows,
1715
+ justifyContent: "space-between",
1716
+ children: [
1717
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1718
+ /* Fill the space the sidebar/ribbon leaves — width beside the
1719
+ sidebar, height above the ribbon. The height matters even
1720
+ stacked: it is what the prompt's scrolling list measures itself
1721
+ against (see SelectPrompt). */
1722
+ /* @__PURE__ */ jsxs14(
1723
+ Box15,
1724
+ {
1725
+ flexDirection: "column",
1726
+ paddingX: 4,
1727
+ paddingY: 2,
1728
+ width: showSidebar ? 70 : "100%",
1729
+ flexGrow: 1,
1730
+ children: [
1731
+ phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1732
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1733
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1734
+ ] }),
1735
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1736
+ /* @__PURE__ */ jsx13(Notices, {}),
1737
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1738
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1739
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1740
+ "\u2716 ",
1741
+ error
1742
+ ] }) })
1743
+ ]
1744
+ }
1745
+ )
1746
+ ),
1747
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1748
+ ]
1749
+ }
1750
+ ),
1751
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1752
+ ]
1753
+ }
1754
+ )
1618
1755
  );
1619
1756
  }
1620
1757
 
@@ -1850,61 +1987,138 @@ async function runWorkflow(workflow, appId) {
1850
1987
  }
1851
1988
  }
1852
1989
 
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;
1990
+ // src/lib/algoliaApp.ts
1991
+ import { z as z4 } from "zod";
1992
+ var applicationSchema = z4.object({
1993
+ id: z4.string().min(1),
1994
+ name: z4.string().default(""),
1995
+ plan: z4.string().optional()
1996
+ });
1997
+ var listSchema = z4.array(
1998
+ z4.object({
1999
+ id: z4.string().min(1),
2000
+ name: z4.string().default(""),
2001
+ plan_label: z4.string().optional()
2002
+ }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
2003
+ );
2004
+ async function currentApplication() {
2005
+ let raw;
1866
2006
  try {
1867
- parsed = parseToml(tomlText);
2007
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1868
2008
  } catch {
1869
- return [];
2009
+ return null;
1870
2010
  }
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;
2011
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2012
+ return parsed.success ? parsed.data : null;
2013
+ }
2014
+ async function requireApplication() {
2015
+ const app = await currentApplication();
2016
+ if (!app) {
2017
+ throw new Error(
2018
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2019
+ );
2020
+ }
2021
+ return app;
2022
+ }
2023
+ async function listApplications() {
2024
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2025
+ const parsed = listSchema.safeParse(parseJson(raw));
2026
+ if (!parsed.success) {
2027
+ throw new Error("Could not read the list of Algolia applications.");
2028
+ }
2029
+ return parsed.data;
2030
+ }
2031
+ async function selectApplication(id) {
2032
+ const raw = await runAlgoliaCli(
2033
+ ["application", "select", "--non-interactive", "--app-id", id],
2034
+ { onOutput: stderrSink }
2035
+ );
2036
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2037
+ if (!parsed.success) {
2038
+ throw new Error(
2039
+ `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2040
+ );
2041
+ }
2042
+ return parsed.data;
2043
+ }
2044
+ function parseJson(text) {
1884
2045
  try {
1885
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
2046
+ return JSON.parse(text);
1886
2047
  } catch {
1887
- profiles = [];
2048
+ return void 0;
1888
2049
  }
1889
- const profile = profiles[0];
1890
- if (!profile) {
2050
+ }
2051
+
2052
+ // src/lib/algoliaAppPicker.ts
2053
+ function secondaryFor(app) {
2054
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
2055
+ }
2056
+ function labelFor(app) {
2057
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2058
+ }
2059
+ function selectAndReport(app) {
2060
+ useWizard.getState().pushCliOutput(
2061
+ "stdout",
2062
+ `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
2063
+ );
2064
+ return selectApplication(app.id);
2065
+ }
2066
+ async function promptForApplication() {
2067
+ const store = useWizard.getState();
2068
+ const apps = await listApplications();
2069
+ if (apps.length === 0) {
1891
2070
  throw new Error(
1892
- "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
2071
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2072
+ );
2073
+ }
2074
+ if (apps.length === 1) {
2075
+ const only = apps[0];
2076
+ logger.info(
2077
+ { app: only.id },
2078
+ "single application on the account; selecting it"
1893
2079
  );
2080
+ return selectAndReport(only);
1894
2081
  }
1895
- return profile;
2082
+ const messages = ["Which Algolia application should the wizard work in?"];
2083
+ for (; ; ) {
2084
+ const choice = await store.requestUserInput({
2085
+ prompt: "Select an application",
2086
+ promptType: "multipleChoice",
2087
+ options: apps.map(labelFor),
2088
+ secondary: apps.map(secondaryFor),
2089
+ messages
2090
+ });
2091
+ const chosen = apps.find((app) => labelFor(app) === choice);
2092
+ if (!chosen) {
2093
+ throw new Error("Application picker received an unexpected selection");
2094
+ }
2095
+ try {
2096
+ return await selectAndReport(chosen);
2097
+ } catch (err) {
2098
+ logger.warn(
2099
+ { app: chosen.id, err: err.message },
2100
+ "application select failed; re-prompting"
2101
+ );
2102
+ messages.push(
2103
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2104
+ );
2105
+ }
2106
+ }
2107
+ }
2108
+ async function ensureApplication() {
2109
+ return await currentApplication() ?? await promptForApplication();
1896
2110
  }
1897
2111
 
1898
2112
  // src/workflows/default.ts
1899
- import { z as z25 } from "zod";
2113
+ import { z as z27 } from "zod";
1900
2114
 
1901
2115
  // 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)
2116
+ import { z as z5 } from "zod";
2117
+ var indicesListSchema = z5.object({
2118
+ items: z5.array(
2119
+ z5.object({
2120
+ name: z5.string(),
2121
+ entries: z5.number().default(0)
1908
2122
  })
1909
2123
  )
1910
2124
  });
@@ -1975,12 +2189,12 @@ import "zod";
1975
2189
 
1976
2190
  // src/lib/tools/listFiles.ts
1977
2191
  import { tool } from "ai";
1978
- import z4 from "zod";
2192
+ import z6 from "zod";
1979
2193
  import { readdir } from "node:fs/promises";
1980
2194
 
1981
2195
  // src/lib/tools/path.ts
1982
2196
  import { lstat } from "node:fs/promises";
1983
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2197
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1984
2198
  function resolveInRoot(ctx, path) {
1985
2199
  const target = resolve2(ctx.cwd, path);
1986
2200
  const rel = relative(ctx.root, target);
@@ -1996,7 +2210,7 @@ async function hasSymlinkParent(ctx, target) {
1996
2210
  let current = ctx.root;
1997
2211
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1998
2212
  for (const part of parts) {
1999
- current = join7(current, part);
2213
+ current = join6(current, part);
2000
2214
  try {
2001
2215
  if ((await lstat(current)).isSymbolicLink()) return true;
2002
2216
  } catch (err) {
@@ -2011,7 +2225,7 @@ async function hasSymlinkParent(ctx, target) {
2011
2225
  function listFilesTool(ctx) {
2012
2226
  return tool({
2013
2227
  description: "List files in the current working directory",
2014
- inputSchema: z4.object(),
2228
+ inputSchema: z6.object(),
2015
2229
  execute: async () => {
2016
2230
  logger.info("called listFiles tool");
2017
2231
  if (++ctx.counts.list > ctx.limits.list) {
@@ -2027,13 +2241,13 @@ function listFilesTool(ctx) {
2027
2241
 
2028
2242
  // src/lib/tools/changeDirectory.ts
2029
2243
  import { tool as tool2 } from "ai";
2030
- import z5 from "zod";
2244
+ import z7 from "zod";
2031
2245
  import { stat } from "node:fs/promises";
2032
2246
  function changeDirectoryTool(ctx) {
2033
2247
  return tool2({
2034
2248
  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")
2249
+ inputSchema: z7.object({
2250
+ path: z7.string().describe("Directory to change into")
2037
2251
  }),
2038
2252
  execute: async ({ path }) => {
2039
2253
  logger.info({ path }, "called changeDirectory tool");
@@ -2055,13 +2269,13 @@ function changeDirectoryTool(ctx) {
2055
2269
 
2056
2270
  // src/lib/tools/reportStatus.ts
2057
2271
  import { tool as tool3 } from "ai";
2058
- import z6 from "zod";
2272
+ import z8 from "zod";
2059
2273
  function reportStatusTool(output) {
2060
2274
  return tool3({
2061
2275
  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(),
2276
+ inputSchema: z8.object({
2277
+ status: z8.enum(["success", "fail"]),
2278
+ reason: z8.string().optional(),
2065
2279
  output
2066
2280
  }),
2067
2281
  execute: async ({ status, reason, output: output2 }) => {
@@ -2073,8 +2287,8 @@ function reportStatusTool(output) {
2073
2287
 
2074
2288
  // src/lib/tools/readFile.ts
2075
2289
  import { tool as tool4 } from "ai";
2076
- import z7 from "zod";
2077
- import { readFile as readFile4 } from "node:fs/promises";
2290
+ import z9 from "zod";
2291
+ import { readFile as readFile3 } from "node:fs/promises";
2078
2292
 
2079
2293
  // src/lib/tools/env.ts
2080
2294
  import { basename } from "node:path";
@@ -2101,8 +2315,8 @@ function redactEnvValues(content) {
2101
2315
  function readFileTool(ctx) {
2102
2316
  return tool4({
2103
2317
  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")
2318
+ inputSchema: z9.object({
2319
+ filePath: z9.string().describe("Path to the file to read")
2106
2320
  }),
2107
2321
  execute: async ({ filePath }) => {
2108
2322
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2112,7 +2326,7 @@ function readFileTool(ctx) {
2112
2326
  const resolved = resolveInRoot(ctx, filePath);
2113
2327
  if (!resolved.ok) return resolved.error;
2114
2328
  try {
2115
- const content = await readFile4(resolved.target, "utf8");
2329
+ const content = await readFile3(resolved.target, "utf8");
2116
2330
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2117
2331
  } catch (err) {
2118
2332
  return `Error reading ${filePath}: ${err.message}`;
@@ -2123,15 +2337,15 @@ function readFileTool(ctx) {
2123
2337
 
2124
2338
  // src/lib/tools/writeFile.ts
2125
2339
  import { tool as tool5 } from "ai";
2126
- import z8 from "zod";
2340
+ import z10 from "zod";
2127
2341
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2128
2342
  import { dirname as dirname4 } from "node:path";
2129
2343
  function writeFileTool(ctx) {
2130
2344
  return tool5({
2131
2345
  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")
2346
+ inputSchema: z10.object({
2347
+ filePath: z10.string().describe("Path to the file to write"),
2348
+ content: z10.string().describe("Content to write to the file")
2135
2349
  }),
2136
2350
  execute: async ({ filePath, content }) => {
2137
2351
  logger.info({ filePath }, "called writeFile tool");
@@ -2156,9 +2370,95 @@ function writeFileTool(ctx) {
2156
2370
 
2157
2371
  // src/lib/tools/writeAlgoliaCredentials.ts
2158
2372
  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";
2373
+ import z12 from "zod";
2374
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2161
2375
  import { dirname as dirname5 } from "node:path";
2376
+
2377
+ // src/lib/algoliaApiKey.ts
2378
+ import { z as z11 } from "zod";
2379
+ var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2380
+ var WRITE_ACLS = [
2381
+ "addObject",
2382
+ "deleteObject",
2383
+ "settings",
2384
+ "editSettings",
2385
+ "listIndexes"
2386
+ ];
2387
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2388
+ var apiKeySchema = z11.object({
2389
+ value: z11.string().min(1),
2390
+ acl: z11.array(z11.string()).default([]),
2391
+ indexes: z11.array(z11.string()).default([])
2392
+ });
2393
+ var apiKeyListSchema = z11.object({
2394
+ items: z11.array(apiKeySchema).optional(),
2395
+ keys: z11.array(apiKeySchema).optional()
2396
+ }).transform((o) => o.items ?? o.keys ?? []);
2397
+ var createdKeySchema = z11.object({
2398
+ key: z11.string().min(1).optional(),
2399
+ value: z11.string().min(1).optional()
2400
+ });
2401
+ function canReuse(key, index) {
2402
+ return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2403
+ }
2404
+ async function createSearchKey(index) {
2405
+ const stdout = await runAlgoliaCli([
2406
+ "apikeys",
2407
+ "create",
2408
+ "--indices",
2409
+ index,
2410
+ "--acl",
2411
+ "search,browse",
2412
+ "--description",
2413
+ `wizard search-only key for ${index}`,
2414
+ "-o",
2415
+ "json"
2416
+ ]);
2417
+ const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2418
+ const created = key ?? value;
2419
+ if (!created) throw new Error("apikeys create returned no key value");
2420
+ return created;
2421
+ }
2422
+ function canReuseForWrites(key, index) {
2423
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2424
+ }
2425
+ async function resolveWriteKey(index) {
2426
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2427
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2428
+ if (existing) {
2429
+ logger.info({ index }, "reusing existing write API key");
2430
+ return existing;
2431
+ }
2432
+ logger.info({ index }, "no reusable write key found; creating one");
2433
+ const created = await runAlgoliaCli([
2434
+ "apikeys",
2435
+ "create",
2436
+ "--indices",
2437
+ index,
2438
+ "--acl",
2439
+ WRITE_ACLS.join(","),
2440
+ "--description",
2441
+ `wizard write key for ${index}`,
2442
+ "-o",
2443
+ "json"
2444
+ ]);
2445
+ const { key, value } = createdKeySchema.parse(JSON.parse(created));
2446
+ const writeKey = key ?? value;
2447
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2448
+ return writeKey;
2449
+ }
2450
+ async function resolveSearchOnlyKey(index) {
2451
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2452
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2453
+ if (existing) {
2454
+ logger.info({ index }, "reusing existing search-only API key");
2455
+ return existing;
2456
+ }
2457
+ logger.info({ index }, "no reusable search-only key found; creating one");
2458
+ return createSearchKey(index);
2459
+ }
2460
+
2461
+ // src/lib/tools/writeAlgoliaCredentials.ts
2162
2462
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2163
2463
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2164
2464
  function appendEnv(content, entries) {
@@ -2172,9 +2472,9 @@ function hasEnv(content, name) {
2172
2472
  }
2173
2473
  function writeCredentialsTool(ctx) {
2174
2474
  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(
2475
+ 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.`,
2476
+ inputSchema: z12.object({
2477
+ filePath: z12.string().describe(
2178
2478
  'Path to the env file to write credentials into (e.g. ".env")'
2179
2479
  )
2180
2480
  }),
@@ -2182,11 +2482,17 @@ function writeCredentialsTool(ctx) {
2182
2482
  logger.info({ filePath }, "called writeCredentials tool");
2183
2483
  const resolved = resolveInRoot(ctx, filePath);
2184
2484
  if (resolved.ok === false) return resolved.error;
2185
- let profile;
2485
+ const targetIndex = useWizard.getState().targetIndex;
2486
+ if (!targetIndex) {
2487
+ return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2488
+ }
2489
+ let appId;
2490
+ let writeKey;
2186
2491
  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.";
2492
+ appId = (await requireApplication()).id;
2493
+ writeKey = await resolveWriteKey(targetIndex);
2494
+ } catch (err) {
2495
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2190
2496
  }
2191
2497
  try {
2192
2498
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2194,7 +2500,7 @@ function writeCredentialsTool(ctx) {
2194
2500
  }
2195
2501
  let existing = "";
2196
2502
  try {
2197
- existing = await readFile5(resolved.target, "utf8");
2503
+ existing = await readFile4(resolved.target, "utf8");
2198
2504
  } catch (err) {
2199
2505
  if (err.code !== "ENOENT") throw err;
2200
2506
  }
@@ -2205,8 +2511,8 @@ function writeCredentialsTool(ctx) {
2205
2511
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2206
2512
  }
2207
2513
  const envWithCredentials = appendEnv(existing, [
2208
- [APP_ID_VAR, profile.appId],
2209
- [API_KEY_VAR, profile.apiKey]
2514
+ [APP_ID_VAR, appId],
2515
+ [API_KEY_VAR, writeKey]
2210
2516
  ]);
2211
2517
  await mkdir4(dirname5(resolved.target), { recursive: true });
2212
2518
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2220,16 +2526,16 @@ function writeCredentialsTool(ctx) {
2220
2526
 
2221
2527
  // src/lib/tools/searchFiles.ts
2222
2528
  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";
2529
+ import z13 from "zod";
2530
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2531
+ import { join as join7 } from "node:path";
2226
2532
  var MAX_QUERY_LENGTH = 1e3;
2227
2533
  async function walkFiles(dir) {
2228
2534
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2229
2535
  const out = [];
2230
2536
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2231
2537
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2232
- const full = join8(dir, e.name);
2538
+ const full = join7(dir, e.name);
2233
2539
  if (e.isDirectory()) out.push(...await walkFiles(full));
2234
2540
  else if (e.isFile()) out.push(full);
2235
2541
  }
@@ -2238,9 +2544,9 @@ async function walkFiles(dir) {
2238
2544
  function searchFilesTool(ctx) {
2239
2545
  return tool7({
2240
2546
  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)")
2547
+ inputSchema: z13.object({
2548
+ query: z13.string().describe("JavaScript RegExp pattern to search for"),
2549
+ path: z13.string().optional().describe("Directory to search in (default: cwd)")
2244
2550
  }),
2245
2551
  execute: async ({ query, path = "." }) => {
2246
2552
  logger.info({ query, path }, "called searchFiles tool");
@@ -2262,7 +2568,7 @@ function searchFilesTool(ctx) {
2262
2568
  for (const file of await walkFiles(resolved.target)) {
2263
2569
  let content;
2264
2570
  try {
2265
- content = await readFile6(file, "utf8");
2571
+ content = await readFile5(file, "utf8");
2266
2572
  } catch {
2267
2573
  continue;
2268
2574
  }
@@ -2284,7 +2590,7 @@ function searchFilesTool(ctx) {
2284
2590
 
2285
2591
  // src/lib/tools/verifyImplementation.ts
2286
2592
  import { tool as tool8 } from "ai";
2287
- import z11 from "zod";
2593
+ import z14 from "zod";
2288
2594
 
2289
2595
  // src/lib/tools/utils/runCommand.ts
2290
2596
  import { spawn as spawn2 } from "node:child_process";
@@ -2306,9 +2612,9 @@ function runCommand(command, args, cwd) {
2306
2612
  }
2307
2613
 
2308
2614
  // src/lib/tools/utils/packageManager.ts
2309
- import { readFile as readFile7 } from "node:fs/promises";
2615
+ import { readFile as readFile6 } from "node:fs/promises";
2310
2616
  import { existsSync } from "node:fs";
2311
- import { join as join9 } from "node:path";
2617
+ import { join as join8 } from "node:path";
2312
2618
  var LOCKFILES = [
2313
2619
  ["pnpm-lock.yaml", "pnpm"],
2314
2620
  ["yarn.lock", "yarn"],
@@ -2317,13 +2623,13 @@ var LOCKFILES = [
2317
2623
  ["package-lock.json", "npm"]
2318
2624
  ];
2319
2625
  async function readPackageJson(cwd = process.cwd()) {
2320
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2626
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2321
2627
  }
2322
2628
  function packageManagerFrom(pkg) {
2323
2629
  return pkg.packageManager?.split("@")[0] ?? "npm";
2324
2630
  }
2325
2631
  function packageManagerFromLockfile(cwd) {
2326
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2632
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2327
2633
  }
2328
2634
  async function detectPackageManager(cwd) {
2329
2635
  try {
@@ -2364,7 +2670,7 @@ async function runRepoVerificationCheck() {
2364
2670
  function verifyImplementationTool() {
2365
2671
  return tool8({
2366
2672
  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(),
2673
+ inputSchema: z14.object(),
2368
2674
  execute: async () => {
2369
2675
  logger.info("called verifyImplementation tool");
2370
2676
  return runRepoVerificationCheck();
@@ -2378,7 +2684,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2378
2684
  import { nanoid as nanoid2 } from "nanoid";
2379
2685
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2380
2686
  import { dirname as dirname6 } from "node:path";
2381
- import z12 from "zod";
2687
+ import z15 from "zod";
2382
2688
  var DATA_DIR = ".algolia-wizard/data";
2383
2689
  var RECORD_MODEL = "claude-haiku-4-5";
2384
2690
  var MAX_RECORDS = 100;
@@ -2390,17 +2696,17 @@ var anthropic = createAnthropic({
2390
2696
  function generateRecordTool(ctx) {
2391
2697
  return tool9({
2392
2698
  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.")
2699
+ inputSchema: z15.object({
2700
+ entityName: z15.string().describe("Name of the entity to generate records for."),
2701
+ attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2702
+ count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2703
+ hint: z15.string().optional().describe("Optional context to steer realistic values.")
2398
2704
  }),
2399
2705
  execute: async ({ entityName, attributes, count, hint }) => {
2400
2706
  logger.info({ entityName, count }, "called generateRecord tool");
2401
2707
  try {
2402
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2403
- const recordSchema = z12.object(
2708
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2709
+ const recordSchema = z15.object(
2404
2710
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2405
2711
  );
2406
2712
  const generateBatch = async (batchCount) => {
@@ -2410,8 +2716,8 @@ function generateRecordTool(ctx) {
2410
2716
  const { output } = await generateText({
2411
2717
  model: anthropic(RECORD_MODEL),
2412
2718
  output: Output.object({
2413
- schema: z12.object({
2414
- records: z12.array(recordSchema).length(batchCount)
2719
+ schema: z15.object({
2720
+ records: z15.array(recordSchema).length(batchCount)
2415
2721
  })
2416
2722
  }),
2417
2723
  prompt: [
@@ -2469,12 +2775,12 @@ function generateRecordTool(ctx) {
2469
2775
 
2470
2776
  // src/lib/tools/notifyUser.ts
2471
2777
  import { tool as tool10 } from "ai";
2472
- import z13 from "zod";
2778
+ import z16 from "zod";
2473
2779
  function notifyUserTool() {
2474
2780
  return tool10({
2475
2781
  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(
2782
+ inputSchema: z16.object({
2783
+ message: z16.string().describe(
2478
2784
  "Short, plain-language description of what you are doing now."
2479
2785
  )
2480
2786
  }),
@@ -2654,10 +2960,10 @@ async function runAgent(req) {
2654
2960
  }
2655
2961
 
2656
2962
  // 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() }))
2963
+ import z19 from "zod";
2964
+ var detectLanguageSchema = z19.object({
2965
+ languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
2966
+ frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
2661
2967
  });
2662
2968
  var detectLanguage = () => runAgent({
2663
2969
  instructions: [
@@ -2675,31 +2981,31 @@ var detectLanguage = () => runAgent({
2675
2981
  });
2676
2982
 
2677
2983
  // src/actions/analyzeCodebase.ts
2678
- import z17 from "zod";
2984
+ import z20 from "zod";
2679
2985
  var READONLY_TOOLS = [
2680
2986
  "listFiles",
2681
2987
  "changeDirectory",
2682
2988
  "readFile",
2683
2989
  "searchFiles"
2684
2990
  ];
2685
- var ingestionAnalysisSchema = z17.object({
2686
- ingestionAnalysis: z17.array(
2687
- z17.object({
2688
- name: z17.string(),
2689
- paths: z17.array(z17.string()),
2991
+ var ingestionAnalysisSchema = z20.object({
2992
+ ingestionAnalysis: z20.array(
2993
+ z20.object({
2994
+ name: z20.string(),
2995
+ paths: z20.array(z20.string()),
2690
2996
  // indexable fields the agent found for this entity
2691
- attributes: z17.array(z17.string())
2997
+ attributes: z20.array(z20.string())
2692
2998
  })
2693
2999
  )
2694
3000
  });
2695
- var searchImplementationAnalysisSchema = z17.object({
2696
- searchImplementationAnalysis: z17.string()
3001
+ var searchImplementationAnalysisSchema = z20.object({
3002
+ searchImplementationAnalysis: z20.string()
2697
3003
  });
2698
- var verificationSchema = z17.object({
2699
- verification: z17.array(z17.string())
3004
+ var verificationSchema = z20.object({
3005
+ verification: z20.array(z20.string())
2700
3006
  });
2701
3007
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
2702
- var analyzeCodebaseSchema = z17.object({
3008
+ var analyzeCodebaseSchema = z20.object({
2703
3009
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
2704
3010
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
2705
3011
  verification: verificationSchema.shape.verification.optional(),
@@ -2761,7 +3067,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2761
3067
  // package.json
2762
3068
  var package_default = {
2763
3069
  name: "@algolia/wizard",
2764
- version: "0.8.0",
3070
+ version: "0.9.0-rc.53.57",
2765
3071
  description: "Magically implement Algolia functionality in your codebase",
2766
3072
  type: "module",
2767
3073
  engines: {
@@ -2809,7 +3115,6 @@ var package_default = {
2809
3115
  dependencies: {
2810
3116
  "@ai-sdk/anthropic": "^3.0.81",
2811
3117
  "@ai-sdk/openai-compatible": "^2.0.47",
2812
- "@algolia/cli": "^5.11.0",
2813
3118
  "@hono/node-server": "^2.0.10",
2814
3119
  "@mishieck/ink-titled-box": "^0.4.2",
2815
3120
  "@segment/analytics-node": "^3.1.0",
@@ -2824,7 +3129,6 @@ var package_default = {
2824
3129
  nanoid: "^5.1.15",
2825
3130
  pino: "^10.3.1",
2826
3131
  react: "^19.2.7",
2827
- toml: "^4.1.1",
2828
3132
  varlock: "^1.5.1",
2829
3133
  zod: "^4.4.3",
2830
3134
  zustand: "^5.0.14"
@@ -2882,8 +3186,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
2882
3186
  }
2883
3187
 
2884
3188
  // src/actions/confirmLanguage.ts
2885
- import z19 from "zod";
2886
- var confirmLanguageSchema = z19.object({
3189
+ import z22 from "zod";
3190
+ var confirmLanguageSchema = z22.object({
2887
3191
  languages: detectLanguageSchema.shape.languages
2888
3192
  });
2889
3193
  async function confirmLanguage(ctx) {
@@ -2904,8 +3208,8 @@ async function confirmLanguage(ctx) {
2904
3208
  }
2905
3209
 
2906
3210
  // src/actions/confirmFramework.ts
2907
- import z20 from "zod";
2908
- var confirmFrameworkSchema = z20.object({
3211
+ import z23 from "zod";
3212
+ var confirmFrameworkSchema = z23.object({
2909
3213
  frameworks: detectLanguageSchema.shape.frameworks
2910
3214
  });
2911
3215
  var CURATED_FRAMEWORKS = [
@@ -3033,8 +3337,8 @@ async function promptUser(ctx, params) {
3033
3337
  }
3034
3338
 
3035
3339
  // src/actions/confirmEntities.ts
3036
- import z21 from "zod";
3037
- var confirmEntitiesSchema = z21.object({
3340
+ import z24 from "zod";
3341
+ var confirmEntitiesSchema = z24.object({
3038
3342
  // Final detection — the focused re-run may supersede project-scan's.
3039
3343
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3040
3344
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3104,15 +3408,15 @@ async function confirmEntities(ctx) {
3104
3408
  }
3105
3409
 
3106
3410
  // src/actions/review.ts
3107
- import { z as z22 } from "zod";
3108
- var reviewSchema = z22.object({
3411
+ import { z as z25 } from "zod";
3412
+ var reviewSchema = z25.object({
3109
3413
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3110
3414
  // not one entry per workflow step — a step's raw output can be a long,
3111
3415
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3112
3416
  // 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())
3417
+ summaryPoints: z25.array(z25.string()),
3418
+ reviewPrompt: z25.string(),
3419
+ nextSteps: z25.array(z25.string())
3116
3420
  });
3117
3421
  function formatCompletedSteps(steps) {
3118
3422
  if (!steps.length) return "(no prior steps completed)";
@@ -3163,16 +3467,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3163
3467
  };
3164
3468
 
3165
3469
  // src/actions/implement.ts
3166
- import z24 from "zod";
3470
+ import z26 from "zod";
3167
3471
 
3168
3472
  // src/lib/worktree.ts
3169
3473
  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";
3474
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3171
3475
  import {
3172
3476
  basename as basename2,
3173
3477
  dirname as dirname7,
3174
3478
  isAbsolute as isAbsolute2,
3175
- join as join10,
3479
+ join as join9,
3176
3480
  relative as relative2,
3177
3481
  resolve as resolve3
3178
3482
  } from "node:path";
@@ -3206,7 +3510,7 @@ async function isWorkingTreeDirty(repoRoot) {
3206
3510
  return out.trim().length > 0;
3207
3511
  }
3208
3512
  async function pruneOldWorktrees(repoRoot) {
3209
- const dir = join10(stateDir(repoRoot), "worktrees");
3513
+ const dir = join9(stateDir(repoRoot), "worktrees");
3210
3514
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3211
3515
  for (const slug of stale) {
3212
3516
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3217,7 +3521,7 @@ async function pruneOldWorktrees(repoRoot) {
3217
3521
  "worktree",
3218
3522
  "remove",
3219
3523
  "--force",
3220
- join10(dir, slug)
3524
+ join9(dir, slug)
3221
3525
  ]);
3222
3526
  await git(["-C", repoRoot, "branch", "-D", branch]);
3223
3527
  } catch (err) {
@@ -3231,7 +3535,7 @@ async function pruneOldWorktrees(repoRoot) {
3231
3535
  async function createWorktree(repoRoot) {
3232
3536
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3233
3537
  const dirSlug = branch.replace(/\//g, "-");
3234
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3538
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3235
3539
  await git(["-C", repoRoot, "worktree", "prune"]);
3236
3540
  await pruneOldWorktrees(repoRoot);
3237
3541
  await mkdir6(dirname7(path), { recursive: true });
@@ -3351,8 +3655,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3351
3655
  } catch {
3352
3656
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3353
3657
  }
3354
- const relPath = join10(ingestDir, basename2(source));
3355
- const dest = join10(worktreePath, relPath);
3658
+ const relPath = join9(ingestDir, basename2(source));
3659
+ const dest = join9(worktreePath, relPath);
3356
3660
  try {
3357
3661
  await mkdir6(dirname7(dest), { recursive: true });
3358
3662
  await copyFile(source, dest);
@@ -3368,10 +3672,10 @@ function hasEnvVar(content, name) {
3368
3672
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3369
3673
  }
3370
3674
  async function writeSearchEnvValues(worktreePath, vars) {
3371
- const target = join10(worktreePath, ".env");
3675
+ const target = join9(worktreePath, ".env");
3372
3676
  let existing = "";
3373
3677
  try {
3374
- existing = await readFile8(target, "utf8");
3678
+ existing = await readFile7(target, "utf8");
3375
3679
  } catch (err) {
3376
3680
  if (err.code !== "ENOENT") throw err;
3377
3681
  }
@@ -3439,63 +3743,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3439
3743
  }
3440
3744
  }
3441
3745
 
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
3746
  // src/lib/algoliaDocs.ts
3491
3747
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3492
- import { dirname as dirname8, join as join11 } from "node:path";
3748
+ import { dirname as dirname8, join as join10 } from "node:path";
3493
3749
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3494
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3750
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3495
3751
  function findDocsDir() {
3496
3752
  let dir = dirname8(fileURLToPath2(import.meta.url));
3497
3753
  for (; ; ) {
3498
- const candidate = join11(dir, DOCS_SUBPATH);
3754
+ const candidate = join10(dir, DOCS_SUBPATH);
3499
3755
  if (existsSync2(candidate)) return candidate;
3500
3756
  const parent = dirname8(dir);
3501
3757
  if (parent === dir) return void 0;
@@ -3518,7 +3774,7 @@ function loadAlgoliaDoc(language) {
3518
3774
  );
3519
3775
  return "";
3520
3776
  }
3521
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3777
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3522
3778
  }
3523
3779
  function getNamedDoc(name, language) {
3524
3780
  const docsDir = findDocsDir();
@@ -3526,7 +3782,7 @@ function getNamedDoc(name, language) {
3526
3782
  logger.warn("docs/algolia-sdk not found");
3527
3783
  return "";
3528
3784
  }
3529
- const file = join11(docsDir, `${name}-${language}.md`);
3785
+ const file = join10(docsDir, `${name}-${language}.md`);
3530
3786
  if (!existsSync2(file)) {
3531
3787
  logger.warn({ name, language }, "named SDK reference not found");
3532
3788
  return "";
@@ -3553,50 +3809,36 @@ function shellQuote(value) {
3553
3809
  }
3554
3810
 
3555
3811
  // 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()
3812
+ var implementSchema = z26.object({
3813
+ filesChanged: z26.array(z26.string()),
3814
+ summary: z26.string(),
3815
+ worktreePath: z26.string().optional(),
3816
+ ingestCommand: z26.string().optional(),
3817
+ ingestScriptRan: z26.boolean().optional(),
3818
+ ingestRecordCount: z26.number().optional(),
3819
+ ingestDurationMs: z26.number().optional(),
3820
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3821
+ // Hints, not ground truth: the search agent may rename the prefix to match
3822
+ // the project's build tool, and its summary carries the final names.
3823
+ searchEnvVars: z26.array(
3824
+ z26.object({
3825
+ name: z26.string(),
3826
+ value: z26.string()
3583
3827
  })
3584
3828
  ).optional()
3585
3829
  });
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()
3830
+ var implementationOutputSchema = z26.object({
3831
+ summary: z26.string(),
3832
+ // Ingestion only: a structured pair the wizard turns into an argv, never a
3833
+ // free-form command string. `runtime` is allowlisted and `entrypoint` is
3834
+ // validated worktree-relative, so the agent cannot inject extra commands.
3835
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3836
+ entrypoint: z26.string().optional()
3595
3837
  });
3596
- var verificationOutputSchema = z24.object({
3597
- summary: z24.string(),
3598
- sufficient: z24.boolean(),
3599
- additionalInstructions: z24.string().optional()
3838
+ var verificationOutputSchema = z26.object({
3839
+ summary: z26.string(),
3840
+ sufficient: z26.boolean(),
3841
+ additionalInstructions: z26.string().optional()
3600
3842
  });
3601
3843
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3602
3844
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3668,9 +3910,6 @@ function sourceSpecificInstructions(input) {
3668
3910
  "Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
3669
3911
  ],
3670
3912
  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
3913
  `Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
3675
3914
  "Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
3676
3915
  "Map parsed columns/fields to the confirmed entity attributes.",
@@ -3710,12 +3949,11 @@ function searchInstructions(input) {
3710
3949
  doc,
3711
3950
  `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
3951
  "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.
3952
+ // appId always resolves (requireApplication throws otherwise); only the key
3953
+ // can fall back to a placeholder.
3715
3954
  `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
3955
+ // Not the agent's to rename: the wizard writes these exact names into
3956
+ // ".env" right after this step, so a renamed prefix would leave the code
3719
3957
  // reading a var the wizard never wrote.
3720
3958
  `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3721
3959
  '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 +4097,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3859
4097
  }
3860
4098
  }
3861
4099
  const targetIndex = selected?.selection;
4100
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
3862
4101
  await assertGitRepoWithHead(repoRoot);
3863
4102
  if (await isWorkingTreeDirty(repoRoot)) {
3864
4103
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -3869,7 +4108,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3869
4108
  let appId;
3870
4109
  let searchKey;
3871
4110
  if (useCases.includes("search")) {
3872
- appId = (await loadActiveProfile()).appId;
4111
+ appId = (await requireApplication()).id;
3873
4112
  try {
3874
4113
  searchKey = await resolveSearchOnlyKey(targetIndex);
3875
4114
  } catch (err) {
@@ -3914,8 +4153,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3914
4153
  ingestDir: INGEST_DIR,
3915
4154
  ingestionSource,
3916
4155
  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
4156
  uiFramework: detectUiFramework(language)
3920
4157
  };
3921
4158
  const summaries = [];
@@ -3980,7 +4217,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3980
4217
  messages: []
3981
4218
  }) === true;
3982
4219
  if (runNow) {
3983
- const profile = await loadActiveProfile();
4220
+ const ingestApp = await requireApplication();
4221
+ const writeKey = await resolveWriteKey(targetIndex);
3984
4222
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
3985
4223
  const scriptLogId = ctx.logStart("runIngestScript", {
3986
4224
  runtime: ingestRuntime,
@@ -3992,8 +4230,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3992
4230
  ingestRuntime,
3993
4231
  ingestEntrypoint,
3994
4232
  {
3995
- [APP_ID_VAR]: profile.appId,
3996
- [API_KEY_VAR]: profile.apiKey
4233
+ [APP_ID_VAR]: ingestApp.id,
4234
+ [API_KEY_VAR]: writeKey
3997
4235
  }
3998
4236
  );
3999
4237
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4062,8 +4300,7 @@ ${run2.output}` : status;
4062
4300
  );
4063
4301
  }
4064
4302
  await ctx.requestUserInput({
4065
- // No question being asked here, just an acknowledgement the
4066
- // continue/decline hints below already say "continue".
4303
+ // Nothing to ask the continue/decline hints carry the whole prompt.
4067
4304
  prompt: "",
4068
4305
  promptType: "enterToContinue",
4069
4306
  options: [],
@@ -4204,8 +4441,8 @@ var defaultWorkflow = {
4204
4441
  defineStep({
4205
4442
  id: "select-index",
4206
4443
  title: "Set up index",
4207
- outputSchema: z25.object({
4208
- selection: z25.string()
4444
+ outputSchema: z27.object({
4445
+ selection: z27.string()
4209
4446
  }),
4210
4447
  run: (ctx) => selectIndexStep(ctx)
4211
4448
  }),
@@ -4484,7 +4721,7 @@ function parseCliArgs(argv) {
4484
4721
 
4485
4722
  // src/lib/resetState.ts
4486
4723
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4487
- import { join as join12 } from "node:path";
4724
+ import { join as join11 } from "node:path";
4488
4725
  var KEEP = ["wizard.log"];
4489
4726
  async function resetProjectState() {
4490
4727
  const dir = stateDir();
@@ -4496,7 +4733,7 @@ async function resetProjectState() {
4496
4733
  }
4497
4734
  const targets = entries.filter((name) => !KEEP.includes(name));
4498
4735
  await Promise.all(
4499
- targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4736
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4500
4737
  );
4501
4738
  return { dir, removed: targets };
4502
4739
  }
@@ -4551,31 +4788,38 @@ ${formatStepList(workflow)}`);
4551
4788
  }
4552
4789
  async function run(workflow) {
4553
4790
  const store = useWizard.getState();
4554
- let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4791
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4792
+ await store.waitForStart();
4555
4793
  let user = await getUser();
4556
4794
  if (!user) {
4557
- await instance.waitUntilRenderFlush();
4558
- instance.cleanup();
4795
+ store.beginAuth();
4559
4796
  try {
4560
4797
  await runAuthLogin();
4561
4798
  } catch (err) {
4562
- console.error(err instanceof Error ? err.message : String(err));
4799
+ store.setError(err instanceof Error ? err.message : String(err));
4800
+ await instance.waitUntilExit();
4563
4801
  process.exit(1);
4564
4802
  }
4565
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4803
+ store.endAuth();
4566
4804
  user = await getUser();
4567
4805
  if (!user) {
4568
4806
  store.setError(
4569
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4807
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
4570
4808
  );
4571
4809
  await instance.waitUntilExit();
4572
4810
  process.exit(1);
4573
4811
  }
4574
4812
  }
4575
4813
  store.setUser(user);
4576
- const profile = await loadActiveProfile();
4577
- await store.waitForStart();
4578
- runWorkflow(workflow, profile?.appId);
4814
+ let app;
4815
+ try {
4816
+ app = await ensureApplication();
4817
+ } catch (err) {
4818
+ store.setError(err instanceof Error ? err.message : String(err));
4819
+ await instance.waitUntilExit();
4820
+ process.exit(1);
4821
+ }
4822
+ runWorkflow(workflow, app.id);
4579
4823
  }
4580
4824
  var started = await startup();
4581
4825
  if (typeof started === "number") {