@algolia/wizard 0.8.0-rc.58.43 → 0.8.0-rc.58.45

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 +543 -800
  3. package/package.json +3 -1
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 useWindowSize8 } from "ink";
7
+ import { Box as Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -12,86 +12,32 @@ import { nanoid } from "nanoid";
12
12
 
13
13
  // src/lib/algoliaCli.ts
14
14
  import { spawn } from "node:child_process";
15
- import { z } from "zod";
16
- function npxArgs(args) {
17
- return ["--yes", "@algolia/cli@latest", ...args];
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");
18
19
  }
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(" ")}`);
20
+ function runAlgoliaCli(args) {
46
21
  return new Promise((resolve4, reject) => {
47
- const child = spawn("npx", npxArgs(args), { shell });
22
+ const child = spawn(process.execPath, [algoliaCliEntry(), ...args]);
48
23
  let stdout = "";
49
24
  let stderr = "";
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
- });
25
+ child.stdout.on("data", (chunk) => stdout += chunk);
26
+ child.stderr.on("data", (chunk) => stderr += chunk);
64
27
  child.on("error", reject);
65
28
  child.on("close", (code) => {
66
- splitters.stdout.flush();
67
- splitters.stderr.flush();
68
29
  if (code === 0) {
69
30
  resolve4(stdout);
70
31
  } else {
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
- }
32
+ const detail = stderr.trim() || stdout.trim();
78
33
  reject(
79
34
  new Error(
80
- `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
35
+ `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail ? `: ${detail}` : ""}`
81
36
  )
82
37
  );
83
38
  }
84
39
  });
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
- );
40
+ });
95
41
  }
96
42
  async function getUser() {
97
43
  let raw;
@@ -106,23 +52,19 @@ async function getUser() {
106
52
  return null;
107
53
  }
108
54
  }
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
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
+ });
116
67
  });
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
- }
126
68
  }
127
69
 
128
70
  // src/lib/auth.ts
@@ -229,7 +171,6 @@ function describeInputValue(value) {
229
171
  return Array.isArray(value) ? value.join(", ") : value;
230
172
  }
231
173
  var NOTICE_INTERVAL_MS = 2e3;
232
- var CLI_OUTPUT_LIMIT = 200;
233
174
  var useWizard = create((set, get) => ({
234
175
  phase: "idle",
235
176
  homeScreen: "home",
@@ -241,18 +182,10 @@ var useWizard = create((set, get) => ({
241
182
  notices: [],
242
183
  _noticeQueue: [],
243
184
  _noticeTimer: null,
244
- cliOutput: [],
245
- targetIndex: null,
246
185
  logs: [],
247
186
  error: null,
248
187
  inputReq: null,
249
188
  _resolve: null,
250
- // Brackets a CLI subprocess that needs the screen. Sign-in happens after the
251
- // welcome screen's enter, so `endAuth` lands on 'preflight', not 'idle':
252
- // returning to 'idle' would put the welcome screen back up and ask the user
253
- // to confirm the run a second time.
254
- beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
255
- endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
256
189
  // Advances past the welcome screen. Only meaningful from 'idle' — once the
257
190
  // workflow is running there's nothing left to confirm.
258
191
  // Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
@@ -287,13 +220,7 @@ var useWizard = create((set, get) => ({
287
220
  syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
288
221
  setActiveStep: (index) => {
289
222
  get()._clearNoticeQueue();
290
- set({
291
- phase: "running",
292
- currentStepIndex: index,
293
- output: "",
294
- notices: [],
295
- cliOutput: []
296
- });
223
+ set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
297
224
  },
298
225
  setUser: (user) => set({ user }),
299
226
  appendToken: (text) => set((s) => ({ output: s.output + text })),
@@ -334,16 +261,6 @@ var useWizard = create((set, get) => ({
334
261
  get()._clearNoticeQueue();
335
262
  set({ notices: [] });
336
263
  },
337
- // Unthrottled, unlike `pushNotice`: these lines arrive at whatever rate the
338
- // subprocess emits them, and holding them back would land output after the
339
- // command it belongs to has already exited.
340
- pushCliOutput: (stream, text) => set((s) => ({
341
- cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
342
- -CLI_OUTPUT_LIMIT
343
- )
344
- })),
345
- clearCliOutput: () => set({ cliOutput: [] }),
346
- setTargetIndex: (index) => set({ targetIndex: index }),
347
264
  logStart: (kind, name, input) => {
348
265
  const id = nanoid();
349
266
  set((s) => ({
@@ -388,8 +305,6 @@ var useWizard = create((set, get) => ({
388
305
  currentStepIndex: 0,
389
306
  output: "",
390
307
  notices: [],
391
- cliOutput: [],
392
- targetIndex: null,
393
308
  logs: [],
394
309
  error: null,
395
310
  inputReq: null,
@@ -398,100 +313,16 @@ var useWizard = create((set, get) => ({
398
313
  }
399
314
  }));
400
315
 
401
- // src/ui/CliOutput.tsx
402
- import { Box, Text, useWindowSize } from "ink";
403
-
404
- // src/ui/theme.ts
405
- var MARKER = {
406
- pending: "\u25CB",
407
- running: "\u25D0",
408
- done: "\u2713",
409
- error: "\u2716"
410
- };
411
- var BRAND = "#003DFF";
412
- var SECONDARY = "#5468FF";
413
- var DANGER = "#F86E7E";
414
- var COLORS = {
415
- brand: BRAND,
416
- primary: "#E6EDF3",
417
- secondary: SECONDARY,
418
- strong: "#FFFFFF",
419
- muted: "#8B949E",
420
- dim: "#484F58",
421
- highlight: { bg: "#12331C", fg: "#4ADE80" },
422
- badge: "#E3B341",
423
- danger: DANGER,
424
- success: "#4ADE80",
425
- bg: {
426
- main: "#0B0E14",
427
- sidebar: "#14171E"
428
- },
429
- border: "#30363D",
430
- accent: "#76A0FF",
431
- status: {
432
- pending: "gray",
433
- running: "#76A0FF",
434
- done: "#4ADE80",
435
- error: DANGER
436
- }
437
- };
438
-
439
- // src/ui/CliOutput.tsx
440
- import { jsxs } from "react/jsx-runtime";
441
- var CLI_MARKER = "\u203A";
442
- var RESERVED_ROWS = 16;
443
- var MAX_ROWS = 12;
444
- var PANEL_TEXT_WIDTH = 45;
445
- var URL_PATTERN = /https?:\/\//;
446
- function rowCost(text) {
447
- return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
448
- }
449
- function CliOutput() {
450
- const cliOutput = useWizard((s) => s.cliOutput);
451
- const { rows } = useWindowSize();
452
- if (!cliOutput.length) return null;
453
- const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
454
- const visible = [];
455
- let usedRows = 0;
456
- for (let i = cliOutput.length - 1; i >= 0; i--) {
457
- const cost = rowCost(cliOutput[i].text);
458
- if (usedRows + cost > rowBudget && visible.length > 0) break;
459
- visible.unshift(cliOutput[i]);
460
- usedRows += cost;
461
- }
462
- const hidden = cliOutput.length - visible.length;
463
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
464
- hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
465
- "\u2191 ",
466
- hidden,
467
- " earlier line(s)"
468
- ] }),
469
- visible.map((line) => /* @__PURE__ */ jsxs(
470
- Text,
471
- {
472
- color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
473
- wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
474
- children: [
475
- CLI_MARKER,
476
- " ",
477
- line.text
478
- ]
479
- },
480
- line.id
481
- ))
482
- ] });
483
- }
484
-
485
316
  // src/ui/Notices.tsx
486
- import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
317
+ import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
487
318
  import { useEffect as useEffect2, useState as useState2 } from "react";
488
319
 
489
320
  // src/ui/Table.tsx
490
- import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
321
+ import { Box, Text, measureElement, useWindowSize } from "ink";
491
322
  import { useEffect, useRef, useState } from "react";
492
323
  import { jsx } from "react/jsx-runtime";
493
324
  function Table({ columns, rows }) {
494
- const { columns: termCols } = useWindowSize2();
325
+ const { columns: termCols } = useWindowSize();
495
326
  const ref = useRef(null);
496
327
  const [width, setWidth] = useState(0);
497
328
  useEffect(() => {
@@ -499,7 +330,7 @@ function Table({ columns, rows }) {
499
330
  }, [termCols, columns, rows]);
500
331
  if (rows.length === 0) return null;
501
332
  const lines = formatTable(columns, rows, width || void 0);
502
- return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
333
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: line }, `tbl-${i}`)) });
503
334
  }
504
335
  function formatTable(columns, rows, width) {
505
336
  const natural = columns.map(
@@ -539,13 +370,48 @@ function resize(widths, budget) {
539
370
  }
540
371
  var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
541
372
 
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
+
542
408
  // src/ui/Notices.tsx
543
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
409
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
544
410
  var AGENT_MARKER = "\u2726";
545
- var RESERVED_ROWS2 = 14;
546
- var PANEL_TEXT_WIDTH2 = 45;
411
+ var RESERVED_ROWS = 14;
412
+ var PANEL_TEXT_WIDTH = 45;
547
413
  function messageLineCount(text) {
548
- return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
414
+ return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
549
415
  }
550
416
  function noticeLineCount(notice) {
551
417
  const messageLines = (notice.messages ?? []).reduce((sum, m) => {
@@ -556,7 +422,7 @@ function noticeLineCount(notice) {
556
422
  return messageLines + tableLines;
557
423
  }
558
424
  function fitVisibleNotices(notices, windowRows) {
559
- const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
425
+ const budget = Math.max(windowRows - RESERVED_ROWS, 3);
560
426
  let used = 0;
561
427
  let count = 0;
562
428
  for (let i = notices.length - 1; i >= 0; i--) {
@@ -589,7 +455,7 @@ function parseHex(hex) {
589
455
  }
590
456
  function Notices() {
591
457
  const notices = useWizard((s) => s.notices);
592
- const { rows: windowRows } = useWindowSize3();
458
+ const { rows: windowRows } = useWindowSize2();
593
459
  const visible = fitVisibleNotices(notices, windowRows);
594
460
  const [pulseStep, setPulseStep] = useState2(0);
595
461
  useEffect2(() => {
@@ -606,14 +472,14 @@ function Notices() {
606
472
  }, []);
607
473
  if (!visible.length) return null;
608
474
  const pulseColor = PULSE_COLORS[pulseStep];
609
- return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
475
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
610
476
  const isLatest = i === visible.length - 1;
611
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
477
+ return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
612
478
  notice.messages?.map((m, j) => {
613
479
  const line = typeof m === "string" ? { text: m } : m;
614
480
  const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
615
- return /* @__PURE__ */ jsxs2(
616
- Text3,
481
+ return /* @__PURE__ */ jsxs(
482
+ Text2,
617
483
  {
618
484
  color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
619
485
  bold: line.bold,
@@ -633,40 +499,39 @@ function Notices() {
633
499
  // src/ui/PromptInput.tsx
634
500
  import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
635
501
  import TextInput from "ink-text-input";
636
- import { useState as useState4 } from "react";
502
+ import { useState as useState5 } from "react";
637
503
 
638
504
  // src/ui/NextAction.tsx
639
- import { Box as Box4, Text as Text4 } from "ink";
640
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
505
+ import { Box as Box3, Text as Text3 } from "ink";
506
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
641
507
  function NextAction({
642
508
  action,
643
509
  keyHint,
644
510
  hierarchy = "primary"
645
511
  }) {
646
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
647
- hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
648
- hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
649
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
650
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
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 })
651
517
  ] }),
652
- /* @__PURE__ */ jsxs3(Box4, { children: [
653
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
654
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
655
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
656
- /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
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: `]` })
657
523
  ] })
658
524
  ] });
659
525
  }
660
526
 
661
527
  // src/ui/SelectPrompt.tsx
662
- import { Box as Box5, Text as Text5, measureElement as measureElement2, useInput, useWindowSize as useWindowSize4 } from "ink";
663
- import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
664
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
665
- var CANCEL = "cancel";
666
- var ARROW_WIDTH = 4;
667
- var COLUMN_GAP = 2;
668
- var BAR_PADDING = 2;
669
- var ROW_HEIGHT = 3;
528
+ import { Box as Box5, Text as Text5, useInput, useWindowSize as useWindowSize4 } from "ink";
529
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
530
+
531
+ // src/ui/ScrollView.tsx
532
+ import { Box as Box4, Text as Text4, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
533
+ import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
534
+ import { jsxs as jsxs3 } from "react/jsx-runtime";
670
535
  var INDICATOR_ROWS = 2;
671
536
  function fittedWidth(node, columns) {
672
537
  let left = 0;
@@ -675,6 +540,89 @@ function fittedWidth(node, columns) {
675
540
  }
676
541
  return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
677
542
  }
543
+ function useScrollWindow({
544
+ itemCount,
545
+ rowHeight = 1,
546
+ followBottom = false
547
+ }) {
548
+ const viewportRef = useRef2(null);
549
+ const { columns } = useWindowSize3();
550
+ const [size, setSize] = useState3(
551
+ null
552
+ );
553
+ useLayoutEffect(() => {
554
+ if (!viewportRef.current) return;
555
+ const width = fittedWidth(viewportRef.current, columns);
556
+ const { height } = measureElement2(viewportRef.current);
557
+ setSize(
558
+ (prev) => prev?.width === width && prev.height === height ? prev : { width, height }
559
+ );
560
+ });
561
+ const capacity = size === null || itemCount * rowHeight <= size.height ? itemCount : Math.max(Math.floor((size.height - INDICATOR_ROWS) / rowHeight), 1);
562
+ const maxOffset = Math.max(itemCount - capacity, 0);
563
+ const [offset, setOffset] = useState3(0);
564
+ const prevMaxOffsetRef = useRef2(0);
565
+ useLayoutEffect(() => {
566
+ const wasAtBottom = offset >= prevMaxOffsetRef.current;
567
+ prevMaxOffsetRef.current = maxOffset;
568
+ setOffset(
569
+ (o) => followBottom && wasAtBottom ? maxOffset : Math.min(o, maxOffset)
570
+ );
571
+ }, [maxOffset, followBottom]);
572
+ const scrollBy = useCallback(
573
+ (delta) => {
574
+ setOffset((o) => Math.min(Math.max(o + delta, 0), maxOffset));
575
+ },
576
+ [maxOffset]
577
+ );
578
+ const revealIndex = useCallback(
579
+ (index) => {
580
+ setOffset((o) => {
581
+ if (index < o) return index;
582
+ if (index >= o + capacity) {
583
+ return Math.min(index - capacity + 1, maxOffset);
584
+ }
585
+ return o;
586
+ });
587
+ },
588
+ [capacity, maxOffset]
589
+ );
590
+ const visibleCount = Math.min(capacity, Math.max(itemCount - offset, 0));
591
+ return {
592
+ viewportRef,
593
+ width: size?.width ?? columns,
594
+ offset,
595
+ capacity,
596
+ maxOffset,
597
+ hiddenAbove: Math.min(offset, itemCount),
598
+ hiddenBelow: Math.max(itemCount - offset - visibleCount, 0),
599
+ scrollBy,
600
+ revealIndex
601
+ };
602
+ }
603
+ 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: [
606
+ "\u2191 ",
607
+ scroll.hiddenAbove,
608
+ " more"
609
+ ] }),
610
+ children,
611
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
612
+ "\u2193 ",
613
+ scroll.hiddenBelow,
614
+ " more"
615
+ ] })
616
+ ] });
617
+ }
618
+
619
+ // src/ui/SelectPrompt.tsx
620
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
621
+ var CANCEL = "cancel";
622
+ var ARROW_WIDTH = 4;
623
+ var COLUMN_GAP = 2;
624
+ var BAR_PADDING = 2;
625
+ var ROW_HEIGHT = 3;
678
626
  function SelectPrompt({
679
627
  options,
680
628
  onSelect,
@@ -688,10 +636,10 @@ function SelectPrompt({
688
636
  secondary,
689
637
  defaultSelectedIndex = 0
690
638
  }) {
691
- const [index, setIndex] = useState3(
639
+ const [index, setIndex] = useState4(
692
640
  () => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
693
641
  );
694
- const [checked, setChecked] = useState3(() => /* @__PURE__ */ new Set());
642
+ const [checked, setChecked] = useState4(() => /* @__PURE__ */ new Set());
695
643
  const hasCancel = Boolean(multi || cancelable);
696
644
  const rows = hasCancel ? [...options, "Cancel"] : options;
697
645
  const cancelIndex = hasCancel ? options.length : -1;
@@ -699,19 +647,14 @@ function SelectPrompt({
699
647
  if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
700
648
  if (multi) hints.push({ key: "[space]", label: "select" });
701
649
  hints.push({ key: "[enter]", label: "confirm" });
702
- const containerRef = useRef2(null);
703
- const viewportRef = useRef2(null);
704
- const { columns, rows: windowRows } = useWindowSize4();
705
- const [width, setWidth] = useState3(columns);
706
- const [viewportHeight, setViewportHeight] = useState3(null);
707
- useLayoutEffect(() => {
708
- if (containerRef.current) {
709
- setWidth(fittedWidth(containerRef.current, columns));
710
- }
711
- if (viewportRef.current) {
712
- setViewportHeight(measureElement2(viewportRef.current).height);
713
- }
714
- }, [columns, windowRows, error, question, helpText, messages, table]);
650
+ const containerRef = useRef3(null);
651
+ const { columns } = useWindowSize4();
652
+ const [width, setWidth] = useState4(columns);
653
+ useLayoutEffect2(() => {
654
+ if (!containerRef.current) return;
655
+ const measured = fittedWidth(containerRef.current, columns);
656
+ setWidth((prev) => prev === measured ? prev : measured);
657
+ });
715
658
  const inner = Math.max(width - BAR_PADDING, 0);
716
659
  const labelWidth = Math.min(
717
660
  ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
@@ -727,22 +670,15 @@ function SelectPrompt({
727
670
  const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
728
671
  const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
729
672
  const textWidth = inner - labelWidth;
730
- const capacity = viewportHeight === null || rows.length * ROW_HEIGHT <= viewportHeight ? rows.length : Math.max(Math.floor((viewportHeight - INDICATOR_ROWS) / ROW_HEIGHT), 1);
731
- const maxOffset = Math.max(rows.length - capacity, 0);
732
- const [offset, setOffset] = useState3(0);
733
- useLayoutEffect(() => {
734
- setOffset((o) => {
735
- const clamped = Math.min(o, maxOffset);
736
- if (index < clamped) return index;
737
- if (index >= clamped + capacity) {
738
- return Math.min(index - capacity + 1, maxOffset);
739
- }
740
- return clamped;
741
- });
742
- }, [index, capacity, maxOffset]);
743
- const visible = rows.slice(offset, offset + capacity);
744
- const hiddenAbove = offset;
745
- const hiddenBelow = rows.length - offset - visible.length;
673
+ const scroll = useScrollWindow({
674
+ itemCount: rows.length,
675
+ rowHeight: ROW_HEIGHT
676
+ });
677
+ const { revealIndex } = scroll;
678
+ useLayoutEffect2(() => {
679
+ revealIndex(index);
680
+ }, [index, revealIndex]);
681
+ const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
746
682
  useInput((input, key) => {
747
683
  if (rows.length === 0) return;
748
684
  if (key.upArrow || input === "k") {
@@ -776,54 +712,42 @@ function SelectPrompt({
776
712
  helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
777
713
  ] })
778
714
  ] }),
779
- /* @__PURE__ */ jsxs4(Box5, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
780
- hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
781
- "\u2191 ",
782
- hiddenAbove,
783
- " more"
784
- ] }),
785
- visible.map((option, visibleIndex) => {
786
- const i = offset + visibleIndex;
787
- const highlighted = i === index;
788
- const isCancel = i === cancelIndex;
789
- const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
790
- const sec = isCancel ? void 0 : secondary?.[i];
791
- const labelColor = highlighted ? COLORS.highlight.fg : void 0;
792
- const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
793
- highlighted ? "\u276F " : " ",
794
- bullet,
795
- option
796
- ] });
797
- const isText = sec?.kind === "text";
798
- return /* @__PURE__ */ jsxs4(
799
- Box5,
800
- {
801
- width: isText ? "100%" : barWidth,
802
- paddingX: 1,
803
- paddingY: 1,
804
- backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
805
- children: [
806
- /* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
807
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
808
- Text5,
809
- {
810
- wrap: "truncate",
811
- color: highlighted ? COLORS.primary : COLORS.muted,
812
- children: sec.value
813
- }
814
- ) }),
815
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
816
- ]
817
- },
818
- `row-${i}`
819
- );
820
- }),
821
- hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
822
- "\u2193 ",
823
- hiddenBelow,
824
- " more"
825
- ] })
826
- ] }),
715
+ /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
716
+ const i = scroll.offset + visibleIndex;
717
+ const highlighted = i === index;
718
+ const isCancel = i === cancelIndex;
719
+ const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
720
+ const sec = isCancel ? void 0 : secondary?.[i];
721
+ const labelColor = highlighted ? COLORS.highlight.fg : void 0;
722
+ const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
723
+ highlighted ? "\u276F " : " ",
724
+ bullet,
725
+ option
726
+ ] });
727
+ const isText = sec?.kind === "text";
728
+ return /* @__PURE__ */ jsxs4(
729
+ Box5,
730
+ {
731
+ width: isText ? "100%" : barWidth,
732
+ paddingX: 1,
733
+ paddingY: 1,
734
+ backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
735
+ 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,
739
+ {
740
+ wrap: "truncate",
741
+ color: highlighted ? COLORS.primary : COLORS.muted,
742
+ children: sec.value
743
+ }
744
+ ) }),
745
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
746
+ ]
747
+ },
748
+ `row-${i}`
749
+ );
750
+ }) }),
827
751
  /* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
828
752
  i > 0 ? " " : "",
829
753
  /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
@@ -858,7 +782,7 @@ function EnterToContinuePrompt({
858
782
  }
859
783
  function PromptInput() {
860
784
  const { phase, inputReq, submitInput } = useWizard();
861
- const [draft, setDraft] = useState4("");
785
+ const [draft, setDraft] = useState5("");
862
786
  if (phase === "done" || phase === "error") {
863
787
  return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
864
788
  }
@@ -1348,8 +1272,7 @@ function Ribbon() {
1348
1272
  import { useState as useState6 } from "react";
1349
1273
 
1350
1274
  // src/ui/Logs.tsx
1351
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
1352
- import { Box as Box13, Text as Text13, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1275
+ import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
1353
1276
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1354
1277
  var KIND_COLOR = {
1355
1278
  tool: COLORS.primary,
@@ -1380,74 +1303,31 @@ function formatTimestamp(ms) {
1380
1303
  }
1381
1304
  function Logs() {
1382
1305
  const logs = useWizard((s) => s.logs);
1383
- const { rows, columns } = useWindowSize7();
1384
- const viewportRef = useRef3(null);
1385
- const [viewportHeight, setViewportHeight] = useState5(0);
1386
- const [viewportWidth, setViewportWidth] = useState5(0);
1387
- const [scrollOffset, setScrollOffset] = useState5(0);
1388
- const prevMaxOffsetRef = useRef3(0);
1389
- useLayoutEffect2(() => {
1390
- if (!viewportRef.current) return;
1391
- const { width, height } = measureElement3(viewportRef.current);
1392
- setViewportHeight(height);
1393
- setViewportWidth(width);
1394
- }, [rows, columns, logs.length === 0]);
1395
- let capacity = viewportHeight;
1396
- for (let i = 0; i < 2; i++) {
1397
- const hasAbove = scrollOffset > 0;
1398
- const hasBelow = scrollOffset + capacity < logs.length;
1399
- capacity = Math.max(
1400
- viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
1401
- 0
1402
- );
1403
- }
1404
- const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
1405
- const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
1406
- useLayoutEffect2(() => {
1407
- const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
1408
- prevMaxOffsetRef.current = maxOffset;
1409
- setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
1410
- }, [maxOffset]);
1306
+ const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1411
1307
  useInput5((_input, key) => {
1412
- if (!key.upArrow && !key.downArrow) return;
1413
- setScrollOffset(
1414
- (o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
1415
- );
1308
+ if (key.upArrow) scroll.scrollBy(-1);
1309
+ else if (key.downArrow) scroll.scrollBy(1);
1416
1310
  });
1417
- const visible = logs.slice(scrollOffset, scrollOffset + capacity);
1418
- const hiddenAbove = scrollOffset;
1419
- const hiddenBelow = logs.length - scrollOffset - visible.length;
1311
+ const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1420
1312
  return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1421
1313
  logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
1422
- /* @__PURE__ */ jsxs12(Box13, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1423
- hiddenAbove > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1424
- "\u2191 ",
1425
- hiddenAbove,
1426
- " more"
1427
- ] }),
1428
- visible.map((entry) => {
1429
- const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1430
- const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1431
- const rawPreview = rawInputText(entry.input);
1432
- const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
1433
- const gaps = (partCount - 1) * ROW_GAP;
1434
- let budget = viewportWidth - timestamp.length - durationText.length - gaps;
1435
- const name = truncate2(entry.name, budget);
1436
- budget -= name.length;
1437
- const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1438
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
1439
- /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
1440
- /* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
1441
- preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
1442
- durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
1443
- ] }, entry.id);
1444
- }),
1445
- hiddenBelow > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1446
- "\u2193 ",
1447
- hiddenBelow,
1448
- " more"
1449
- ] })
1450
- ] }),
1314
+ /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1315
+ const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1316
+ const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1317
+ const rawPreview = rawInputText(entry.input);
1318
+ const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
1319
+ const gaps = (partCount - 1) * ROW_GAP;
1320
+ let budget = scroll.width - timestamp.length - durationText.length - gaps;
1321
+ const name = truncate2(entry.name, budget);
1322
+ budget -= name.length;
1323
+ 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 })
1329
+ ] }, entry.id);
1330
+ }) }),
1451
1331
  /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1452
1332
  ] });
1453
1333
  }
@@ -1644,7 +1524,7 @@ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1644
1524
  function App() {
1645
1525
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1646
1526
  const { exit } = useApp();
1647
- const { columns, rows } = useWindowSize8();
1527
+ const { columns, rows } = useWindowSize7();
1648
1528
  const [showLogs, setShowLogs] = useState6(false);
1649
1529
  const finished = phase === "done" || phase === "error";
1650
1530
  const currentStep = steps[currentStepIndex];
@@ -1657,7 +1537,7 @@ function App() {
1657
1537
  { isActive: finished }
1658
1538
  );
1659
1539
  useInput6((_input, key) => {
1660
- if (phase === "idle" || phase === "authenticating") return;
1540
+ if (phase === "idle" || phase === "preflight") return;
1661
1541
  if (key.tab) {
1662
1542
  setShowLogs(!showLogs);
1663
1543
  track("AI Wizard Interaction", {
@@ -1667,7 +1547,7 @@ function App() {
1667
1547
  });
1668
1548
  }
1669
1549
  });
1670
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1550
+ const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1671
1551
  useInput6((_input, key) => {
1672
1552
  if (escOwnedElsewhere) return;
1673
1553
  if (key.escape) {
@@ -1680,70 +1560,61 @@ function App() {
1680
1560
  exit();
1681
1561
  }
1682
1562
  });
1683
- const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1563
+ const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1684
1564
  const flexDirection = columns > 90 ? "row" : "column";
1685
1565
  const showSidebar = flexDirection === "row";
1686
- return (
1687
- /* Exactly the viewport, clipped — never `minHeight`, which lets the frame
1688
- grow past the terminal. Ink then abandons diffing to clear and repaint
1689
- the whole screen, and the scrolling that frame causes throws off its
1690
- cursor arithmetic: flicker and leftover rows, worst when a burst of CLI
1691
- output is swapped out. Clipping drops the bottom of an over-tall frame;
1692
- the per-panel row budgets are what keep it from coming to that. */
1693
- /* @__PURE__ */ jsxs13(
1694
- Box14,
1695
- {
1696
- backgroundColor: COLORS.bg.main,
1697
- flexDirection: "row",
1698
- width: columns,
1699
- height: rows,
1700
- overflow: "hidden",
1701
- children: [
1702
- mainWindowVisible && /* @__PURE__ */ jsxs13(
1703
- Box14,
1704
- {
1705
- flexDirection,
1706
- width: "100%",
1707
- justifyContent: "space-between",
1708
- children: [
1709
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1710
- /* Fill the space the sidebar/ribbon leaves — width beside the
1711
- sidebar, height above the ribbon. The height matters even
1712
- stacked: it is what the prompt's scrolling list measures itself
1713
- against (see SelectPrompt). */
1714
- /* @__PURE__ */ jsxs13(
1715
- Box14,
1716
- {
1717
- flexDirection: "column",
1718
- paddingX: 4,
1719
- paddingY: 2,
1720
- width: showSidebar ? 70 : "100%",
1721
- flexGrow: 1,
1722
- children: [
1723
- phase === "authenticating" && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
1724
- /* @__PURE__ */ jsx13(Text14, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1725
- /* @__PURE__ */ jsx13(Text14, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1726
- ] }),
1727
- /* @__PURE__ */ jsx13(CliOutput, {}),
1728
- /* @__PURE__ */ jsx13(Notices, {}),
1729
- /* @__PURE__ */ jsx13(PromptInput, {}),
1730
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1731
- phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
1732
- "\u2716 ",
1733
- error
1734
- ] }) })
1735
- ]
1736
- }
1737
- )
1738
- ),
1739
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1740
- ]
1741
- }
1742
- ),
1743
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1744
- ]
1745
- }
1746
- )
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
+ }
1747
1618
  );
1748
1619
  }
1749
1620
 
@@ -1979,138 +1850,61 @@ async function runWorkflow(workflow, appId) {
1979
1850
  }
1980
1851
  }
1981
1852
 
1982
- // src/lib/algoliaApp.ts
1983
- import { z as z4 } from "zod";
1984
- var applicationSchema = z4.object({
1985
- id: z4.string().min(1),
1986
- name: z4.string().default(""),
1987
- plan: z4.string().optional()
1988
- });
1989
- var listSchema = z4.array(
1990
- z4.object({
1991
- id: z4.string().min(1),
1992
- name: z4.string().default(""),
1993
- plan_label: z4.string().optional()
1994
- }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1995
- );
1996
- async function currentApplication() {
1997
- let raw;
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;
1998
1866
  try {
1999
- raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1867
+ parsed = parseToml(tomlText);
2000
1868
  } catch {
2001
- return null;
1869
+ return [];
2002
1870
  }
2003
- const parsed = applicationSchema.safeParse(parseJson(raw));
2004
- return parsed.success ? parsed.data : null;
2005
- }
2006
- async function requireApplication() {
2007
- const app = await currentApplication();
2008
- if (!app) {
2009
- throw new Error(
2010
- "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2011
- );
2012
- }
2013
- return app;
2014
- }
2015
- async function listApplications() {
2016
- const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2017
- const parsed = listSchema.safeParse(parseJson(raw));
2018
- if (!parsed.success) {
2019
- throw new Error("Could not read the list of Algolia applications.");
2020
- }
2021
- return parsed.data;
2022
- }
2023
- async function selectApplication(id) {
2024
- const raw = await runAlgoliaCli(
2025
- ["application", "select", "--non-interactive", "--app-id", id],
2026
- { onOutput: stderrSink }
2027
- );
2028
- const parsed = applicationSchema.safeParse(parseJson(raw));
2029
- if (!parsed.success) {
2030
- throw new Error(
2031
- `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2032
- );
2033
- }
2034
- return parsed.data;
2035
- }
2036
- function parseJson(text) {
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;
2037
1884
  try {
2038
- return JSON.parse(text);
1885
+ profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
2039
1886
  } catch {
2040
- return void 0;
1887
+ profiles = [];
2041
1888
  }
2042
- }
2043
-
2044
- // src/lib/algoliaAppPicker.ts
2045
- function secondaryFor(app) {
2046
- return app.plan ? { kind: "badge", value: app.plan } : void 0;
2047
- }
2048
- function labelFor(app) {
2049
- return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2050
- }
2051
- function selectAndReport(app) {
2052
- useWizard.getState().pushCliOutput(
2053
- "stdout",
2054
- `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
2055
- );
2056
- return selectApplication(app.id);
2057
- }
2058
- async function promptForApplication() {
2059
- const store = useWizard.getState();
2060
- const apps = await listApplications();
2061
- if (apps.length === 0) {
1889
+ const profile = profiles[0];
1890
+ if (!profile) {
2062
1891
  throw new Error(
2063
- "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2064
- );
2065
- }
2066
- if (apps.length === 1) {
2067
- const only = apps[0];
2068
- logger.info(
2069
- { app: only.id },
2070
- "single application on the account; selecting it"
1892
+ "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
2071
1893
  );
2072
- return selectAndReport(only);
2073
- }
2074
- const messages = ["Which Algolia application should the wizard work in?"];
2075
- for (; ; ) {
2076
- const choice = await store.requestUserInput({
2077
- prompt: "Select an application",
2078
- promptType: "multipleChoice",
2079
- options: apps.map(labelFor),
2080
- secondary: apps.map(secondaryFor),
2081
- messages
2082
- });
2083
- const chosen = apps.find((app) => labelFor(app) === choice);
2084
- if (!chosen) {
2085
- throw new Error("Application picker received an unexpected selection");
2086
- }
2087
- try {
2088
- return await selectAndReport(chosen);
2089
- } catch (err) {
2090
- logger.warn(
2091
- { app: chosen.id, err: err.message },
2092
- "application select failed; re-prompting"
2093
- );
2094
- messages.push(
2095
- `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2096
- );
2097
- }
2098
1894
  }
2099
- }
2100
- async function ensureApplication() {
2101
- return await currentApplication() ?? await promptForApplication();
1895
+ return profile;
2102
1896
  }
2103
1897
 
2104
1898
  // src/workflows/default.ts
2105
- import { z as z27 } from "zod";
1899
+ import { z as z25 } from "zod";
2106
1900
 
2107
1901
  // src/actions/listIndices.ts
2108
- import { z as z5 } from "zod";
2109
- var indicesListSchema = z5.object({
2110
- items: z5.array(
2111
- z5.object({
2112
- name: z5.string(),
2113
- entries: z5.number().default(0)
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)
2114
1908
  })
2115
1909
  )
2116
1910
  });
@@ -2181,12 +1975,12 @@ import "zod";
2181
1975
 
2182
1976
  // src/lib/tools/listFiles.ts
2183
1977
  import { tool } from "ai";
2184
- import z6 from "zod";
1978
+ import z4 from "zod";
2185
1979
  import { readdir } from "node:fs/promises";
2186
1980
 
2187
1981
  // src/lib/tools/path.ts
2188
1982
  import { lstat } from "node:fs/promises";
2189
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1983
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2190
1984
  function resolveInRoot(ctx, path) {
2191
1985
  const target = resolve2(ctx.cwd, path);
2192
1986
  const rel = relative(ctx.root, target);
@@ -2202,7 +1996,7 @@ async function hasSymlinkParent(ctx, target) {
2202
1996
  let current = ctx.root;
2203
1997
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
2204
1998
  for (const part of parts) {
2205
- current = join6(current, part);
1999
+ current = join7(current, part);
2206
2000
  try {
2207
2001
  if ((await lstat(current)).isSymbolicLink()) return true;
2208
2002
  } catch (err) {
@@ -2217,7 +2011,7 @@ async function hasSymlinkParent(ctx, target) {
2217
2011
  function listFilesTool(ctx) {
2218
2012
  return tool({
2219
2013
  description: "List files in the current working directory",
2220
- inputSchema: z6.object(),
2014
+ inputSchema: z4.object(),
2221
2015
  execute: async () => {
2222
2016
  logger.info("called listFiles tool");
2223
2017
  if (++ctx.counts.list > ctx.limits.list) {
@@ -2233,13 +2027,13 @@ function listFilesTool(ctx) {
2233
2027
 
2234
2028
  // src/lib/tools/changeDirectory.ts
2235
2029
  import { tool as tool2 } from "ai";
2236
- import z7 from "zod";
2030
+ import z5 from "zod";
2237
2031
  import { stat } from "node:fs/promises";
2238
2032
  function changeDirectoryTool(ctx) {
2239
2033
  return tool2({
2240
2034
  description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
2241
- inputSchema: z7.object({
2242
- path: z7.string().describe("Directory to change into")
2035
+ inputSchema: z5.object({
2036
+ path: z5.string().describe("Directory to change into")
2243
2037
  }),
2244
2038
  execute: async ({ path }) => {
2245
2039
  logger.info({ path }, "called changeDirectory tool");
@@ -2261,13 +2055,13 @@ function changeDirectoryTool(ctx) {
2261
2055
 
2262
2056
  // src/lib/tools/reportStatus.ts
2263
2057
  import { tool as tool3 } from "ai";
2264
- import z8 from "zod";
2058
+ import z6 from "zod";
2265
2059
  function reportStatusTool(output) {
2266
2060
  return tool3({
2267
2061
  description: "Report the status of your execution. Return a reason in case of failure.",
2268
- inputSchema: z8.object({
2269
- status: z8.enum(["success", "fail"]),
2270
- reason: z8.string().optional(),
2062
+ inputSchema: z6.object({
2063
+ status: z6.enum(["success", "fail"]),
2064
+ reason: z6.string().optional(),
2271
2065
  output
2272
2066
  }),
2273
2067
  execute: async ({ status, reason, output: output2 }) => {
@@ -2279,8 +2073,8 @@ function reportStatusTool(output) {
2279
2073
 
2280
2074
  // src/lib/tools/readFile.ts
2281
2075
  import { tool as tool4 } from "ai";
2282
- import z9 from "zod";
2283
- import { readFile as readFile3 } from "node:fs/promises";
2076
+ import z7 from "zod";
2077
+ import { readFile as readFile4 } from "node:fs/promises";
2284
2078
 
2285
2079
  // src/lib/tools/env.ts
2286
2080
  import { basename } from "node:path";
@@ -2307,8 +2101,8 @@ function redactEnvValues(content) {
2307
2101
  function readFileTool(ctx) {
2308
2102
  return tool4({
2309
2103
  description: "Read the contents of a file at the given path",
2310
- inputSchema: z9.object({
2311
- filePath: z9.string().describe("Path to the file to read")
2104
+ inputSchema: z7.object({
2105
+ filePath: z7.string().describe("Path to the file to read")
2312
2106
  }),
2313
2107
  execute: async ({ filePath }) => {
2314
2108
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2318,7 +2112,7 @@ function readFileTool(ctx) {
2318
2112
  const resolved = resolveInRoot(ctx, filePath);
2319
2113
  if (!resolved.ok) return resolved.error;
2320
2114
  try {
2321
- const content = await readFile3(resolved.target, "utf8");
2115
+ const content = await readFile4(resolved.target, "utf8");
2322
2116
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2323
2117
  } catch (err) {
2324
2118
  return `Error reading ${filePath}: ${err.message}`;
@@ -2329,15 +2123,15 @@ function readFileTool(ctx) {
2329
2123
 
2330
2124
  // src/lib/tools/writeFile.ts
2331
2125
  import { tool as tool5 } from "ai";
2332
- import z10 from "zod";
2126
+ import z8 from "zod";
2333
2127
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2334
2128
  import { dirname as dirname4 } from "node:path";
2335
2129
  function writeFileTool(ctx) {
2336
2130
  return tool5({
2337
2131
  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.",
2338
- inputSchema: z10.object({
2339
- filePath: z10.string().describe("Path to the file to write"),
2340
- content: z10.string().describe("Content to write to the file")
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")
2341
2135
  }),
2342
2136
  execute: async ({ filePath, content }) => {
2343
2137
  logger.info({ filePath }, "called writeFile tool");
@@ -2362,95 +2156,9 @@ function writeFileTool(ctx) {
2362
2156
 
2363
2157
  // src/lib/tools/writeAlgoliaCredentials.ts
2364
2158
  import { tool as tool6 } from "ai";
2365
- import z12 from "zod";
2366
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2159
+ import z9 from "zod";
2160
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2367
2161
  import { dirname as dirname5 } from "node:path";
2368
-
2369
- // src/lib/algoliaApiKey.ts
2370
- import { z as z11 } from "zod";
2371
- var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2372
- var WRITE_ACLS = [
2373
- "addObject",
2374
- "deleteObject",
2375
- "settings",
2376
- "editSettings",
2377
- "listIndexes"
2378
- ];
2379
- var WRITE_ACL_SET = new Set(WRITE_ACLS);
2380
- var apiKeySchema = z11.object({
2381
- value: z11.string().min(1),
2382
- acl: z11.array(z11.string()).default([]),
2383
- indexes: z11.array(z11.string()).default([])
2384
- });
2385
- var apiKeyListSchema = z11.object({
2386
- items: z11.array(apiKeySchema).optional(),
2387
- keys: z11.array(apiKeySchema).optional()
2388
- }).transform((o) => o.items ?? o.keys ?? []);
2389
- var createdKeySchema = z11.object({
2390
- key: z11.string().min(1).optional(),
2391
- value: z11.string().min(1).optional()
2392
- });
2393
- function canReuse(key, index) {
2394
- return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2395
- }
2396
- async function createSearchKey(index) {
2397
- const stdout = await runAlgoliaCli([
2398
- "apikeys",
2399
- "create",
2400
- "--indices",
2401
- index,
2402
- "--acl",
2403
- "search,browse",
2404
- "--description",
2405
- `wizard search-only key for ${index}`,
2406
- "-o",
2407
- "json"
2408
- ]);
2409
- const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2410
- const created = key ?? value;
2411
- if (!created) throw new Error("apikeys create returned no key value");
2412
- return created;
2413
- }
2414
- function canReuseForWrites(key, index) {
2415
- return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2416
- }
2417
- async function resolveWriteKey(index) {
2418
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2419
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2420
- if (existing) {
2421
- logger.info({ index }, "reusing existing write API key");
2422
- return existing;
2423
- }
2424
- logger.info({ index }, "no reusable write key found; creating one");
2425
- const created = await runAlgoliaCli([
2426
- "apikeys",
2427
- "create",
2428
- "--indices",
2429
- index,
2430
- "--acl",
2431
- WRITE_ACLS.join(","),
2432
- "--description",
2433
- `wizard write key for ${index}`,
2434
- "-o",
2435
- "json"
2436
- ]);
2437
- const { key, value } = createdKeySchema.parse(JSON.parse(created));
2438
- const writeKey = key ?? value;
2439
- if (!writeKey) throw new Error("apikeys create returned no key value");
2440
- return writeKey;
2441
- }
2442
- async function resolveSearchOnlyKey(index) {
2443
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2444
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2445
- if (existing) {
2446
- logger.info({ index }, "reusing existing search-only API key");
2447
- return existing;
2448
- }
2449
- logger.info({ index }, "no reusable search-only key found; creating one");
2450
- return createSearchKey(index);
2451
- }
2452
-
2453
- // src/lib/tools/writeAlgoliaCredentials.ts
2454
2162
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2455
2163
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2456
2164
  function appendEnv(content, entries) {
@@ -2464,9 +2172,9 @@ function hasEnv(content, name) {
2464
2172
  }
2465
2173
  function writeCredentialsTool(ctx) {
2466
2174
  return tool6({
2467
- description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
2468
- inputSchema: z12.object({
2469
- filePath: z12.string().describe(
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(
2470
2178
  'Path to the env file to write credentials into (e.g. ".env")'
2471
2179
  )
2472
2180
  }),
@@ -2474,17 +2182,11 @@ function writeCredentialsTool(ctx) {
2474
2182
  logger.info({ filePath }, "called writeCredentials tool");
2475
2183
  const resolved = resolveInRoot(ctx, filePath);
2476
2184
  if (resolved.ok === false) return resolved.error;
2477
- const targetIndex = useWizard.getState().targetIndex;
2478
- if (!targetIndex) {
2479
- return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2480
- }
2481
- let appId;
2482
- let writeKey;
2185
+ let profile;
2483
2186
  try {
2484
- appId = (await requireApplication()).id;
2485
- writeKey = await resolveWriteKey(targetIndex);
2486
- } catch (err) {
2487
- return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
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.";
2488
2190
  }
2489
2191
  try {
2490
2192
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2492,7 +2194,7 @@ function writeCredentialsTool(ctx) {
2492
2194
  }
2493
2195
  let existing = "";
2494
2196
  try {
2495
- existing = await readFile4(resolved.target, "utf8");
2197
+ existing = await readFile5(resolved.target, "utf8");
2496
2198
  } catch (err) {
2497
2199
  if (err.code !== "ENOENT") throw err;
2498
2200
  }
@@ -2503,8 +2205,8 @@ function writeCredentialsTool(ctx) {
2503
2205
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2504
2206
  }
2505
2207
  const envWithCredentials = appendEnv(existing, [
2506
- [APP_ID_VAR, appId],
2507
- [API_KEY_VAR, writeKey]
2208
+ [APP_ID_VAR, profile.appId],
2209
+ [API_KEY_VAR, profile.apiKey]
2508
2210
  ]);
2509
2211
  await mkdir4(dirname5(resolved.target), { recursive: true });
2510
2212
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2518,16 +2220,16 @@ function writeCredentialsTool(ctx) {
2518
2220
 
2519
2221
  // src/lib/tools/searchFiles.ts
2520
2222
  import { tool as tool7 } from "ai";
2521
- import z13 from "zod";
2522
- import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2523
- import { join as join7 } from "node:path";
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";
2524
2226
  var MAX_QUERY_LENGTH = 1e3;
2525
2227
  async function walkFiles(dir) {
2526
2228
  const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2527
2229
  const out = [];
2528
2230
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2529
2231
  if (e.name.startsWith(".") || skip.has(e.name)) continue;
2530
- const full = join7(dir, e.name);
2232
+ const full = join8(dir, e.name);
2531
2233
  if (e.isDirectory()) out.push(...await walkFiles(full));
2532
2234
  else if (e.isFile()) out.push(full);
2533
2235
  }
@@ -2536,9 +2238,9 @@ async function walkFiles(dir) {
2536
2238
  function searchFilesTool(ctx) {
2537
2239
  return tool7({
2538
2240
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2539
- inputSchema: z13.object({
2540
- query: z13.string().describe("JavaScript RegExp pattern to search for"),
2541
- path: z13.string().optional().describe("Directory to search in (default: cwd)")
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)")
2542
2244
  }),
2543
2245
  execute: async ({ query, path = "." }) => {
2544
2246
  logger.info({ query, path }, "called searchFiles tool");
@@ -2560,7 +2262,7 @@ function searchFilesTool(ctx) {
2560
2262
  for (const file of await walkFiles(resolved.target)) {
2561
2263
  let content;
2562
2264
  try {
2563
- content = await readFile5(file, "utf8");
2265
+ content = await readFile6(file, "utf8");
2564
2266
  } catch {
2565
2267
  continue;
2566
2268
  }
@@ -2582,7 +2284,7 @@ function searchFilesTool(ctx) {
2582
2284
 
2583
2285
  // src/lib/tools/verifyImplementation.ts
2584
2286
  import { tool as tool8 } from "ai";
2585
- import z14 from "zod";
2287
+ import z11 from "zod";
2586
2288
 
2587
2289
  // src/lib/tools/utils/runCommand.ts
2588
2290
  import { spawn as spawn2 } from "node:child_process";
@@ -2604,9 +2306,9 @@ function runCommand(command, args, cwd) {
2604
2306
  }
2605
2307
 
2606
2308
  // src/lib/tools/utils/packageManager.ts
2607
- import { readFile as readFile6 } from "node:fs/promises";
2309
+ import { readFile as readFile7 } from "node:fs/promises";
2608
2310
  import { existsSync } from "node:fs";
2609
- import { join as join8 } from "node:path";
2311
+ import { join as join9 } from "node:path";
2610
2312
  var LOCKFILES = [
2611
2313
  ["pnpm-lock.yaml", "pnpm"],
2612
2314
  ["yarn.lock", "yarn"],
@@ -2615,13 +2317,13 @@ var LOCKFILES = [
2615
2317
  ["package-lock.json", "npm"]
2616
2318
  ];
2617
2319
  async function readPackageJson(cwd = process.cwd()) {
2618
- return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2320
+ return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2619
2321
  }
2620
2322
  function packageManagerFrom(pkg) {
2621
2323
  return pkg.packageManager?.split("@")[0] ?? "npm";
2622
2324
  }
2623
2325
  function packageManagerFromLockfile(cwd) {
2624
- return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2326
+ return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2625
2327
  }
2626
2328
  async function detectPackageManager(cwd) {
2627
2329
  try {
@@ -2662,7 +2364,7 @@ async function runRepoVerificationCheck() {
2662
2364
  function verifyImplementationTool() {
2663
2365
  return tool8({
2664
2366
  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.",
2665
- inputSchema: z14.object(),
2367
+ inputSchema: z11.object(),
2666
2368
  execute: async () => {
2667
2369
  logger.info("called verifyImplementation tool");
2668
2370
  return runRepoVerificationCheck();
@@ -2676,7 +2378,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2676
2378
  import { nanoid as nanoid2 } from "nanoid";
2677
2379
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2678
2380
  import { dirname as dirname6 } from "node:path";
2679
- import z15 from "zod";
2381
+ import z12 from "zod";
2680
2382
  var DATA_DIR = ".algolia-wizard/data";
2681
2383
  var RECORD_MODEL = "claude-haiku-4-5";
2682
2384
  var MAX_RECORDS = 100;
@@ -2688,17 +2390,17 @@ var anthropic = createAnthropic({
2688
2390
  function generateRecordTool(ctx) {
2689
2391
  return tool9({
2690
2392
  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.",
2691
- inputSchema: z15.object({
2692
- entityName: z15.string().describe("Name of the entity to generate records for."),
2693
- attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2694
- count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2695
- hint: z15.string().optional().describe("Optional context to steer realistic values.")
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.")
2696
2398
  }),
2697
2399
  execute: async ({ entityName, attributes, count, hint }) => {
2698
2400
  logger.info({ entityName, count }, "called generateRecord tool");
2699
2401
  try {
2700
- const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2701
- const recordSchema = z15.object(
2402
+ const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
2403
+ const recordSchema = z12.object(
2702
2404
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2703
2405
  );
2704
2406
  const generateBatch = async (batchCount) => {
@@ -2708,8 +2410,8 @@ function generateRecordTool(ctx) {
2708
2410
  const { output } = await generateText({
2709
2411
  model: anthropic(RECORD_MODEL),
2710
2412
  output: Output.object({
2711
- schema: z15.object({
2712
- records: z15.array(recordSchema).length(batchCount)
2413
+ schema: z12.object({
2414
+ records: z12.array(recordSchema).length(batchCount)
2713
2415
  })
2714
2416
  }),
2715
2417
  prompt: [
@@ -2767,12 +2469,12 @@ function generateRecordTool(ctx) {
2767
2469
 
2768
2470
  // src/lib/tools/notifyUser.ts
2769
2471
  import { tool as tool10 } from "ai";
2770
- import z16 from "zod";
2472
+ import z13 from "zod";
2771
2473
  function notifyUserTool() {
2772
2474
  return tool10({
2773
2475
  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.`,
2774
- inputSchema: z16.object({
2775
- message: z16.string().describe(
2476
+ inputSchema: z13.object({
2477
+ message: z13.string().describe(
2776
2478
  "Short, plain-language description of what you are doing now."
2777
2479
  )
2778
2480
  }),
@@ -2952,10 +2654,10 @@ async function runAgent(req) {
2952
2654
  }
2953
2655
 
2954
2656
  // src/actions/detectLanguage.ts
2955
- import z19 from "zod";
2956
- var detectLanguageSchema = z19.object({
2957
- languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
2958
- frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
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() }))
2959
2661
  });
2960
2662
  var detectLanguage = () => runAgent({
2961
2663
  instructions: [
@@ -2973,31 +2675,31 @@ var detectLanguage = () => runAgent({
2973
2675
  });
2974
2676
 
2975
2677
  // src/actions/analyzeCodebase.ts
2976
- import z20 from "zod";
2678
+ import z17 from "zod";
2977
2679
  var READONLY_TOOLS = [
2978
2680
  "listFiles",
2979
2681
  "changeDirectory",
2980
2682
  "readFile",
2981
2683
  "searchFiles"
2982
2684
  ];
2983
- var ingestionAnalysisSchema = z20.object({
2984
- ingestionAnalysis: z20.array(
2985
- z20.object({
2986
- name: z20.string(),
2987
- paths: z20.array(z20.string()),
2685
+ var ingestionAnalysisSchema = z17.object({
2686
+ ingestionAnalysis: z17.array(
2687
+ z17.object({
2688
+ name: z17.string(),
2689
+ paths: z17.array(z17.string()),
2988
2690
  // indexable fields the agent found for this entity
2989
- attributes: z20.array(z20.string())
2691
+ attributes: z17.array(z17.string())
2990
2692
  })
2991
2693
  )
2992
2694
  });
2993
- var searchImplementationAnalysisSchema = z20.object({
2994
- searchImplementationAnalysis: z20.string()
2695
+ var searchImplementationAnalysisSchema = z17.object({
2696
+ searchImplementationAnalysis: z17.string()
2995
2697
  });
2996
- var verificationSchema = z20.object({
2997
- verification: z20.array(z20.string())
2698
+ var verificationSchema = z17.object({
2699
+ verification: z17.array(z17.string())
2998
2700
  });
2999
2701
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3000
- var analyzeCodebaseSchema = z20.object({
2702
+ var analyzeCodebaseSchema = z17.object({
3001
2703
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3002
2704
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3003
2705
  verification: verificationSchema.shape.verification.optional(),
@@ -3059,7 +2761,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3059
2761
  // package.json
3060
2762
  var package_default = {
3061
2763
  name: "@algolia/wizard",
3062
- version: "0.8.0-rc.58.43",
2764
+ version: "0.8.0-rc.58.45",
3063
2765
  description: "Magically implement Algolia functionality in your codebase",
3064
2766
  type: "module",
3065
2767
  engines: {
@@ -3107,6 +2809,7 @@ var package_default = {
3107
2809
  dependencies: {
3108
2810
  "@ai-sdk/anthropic": "^3.0.81",
3109
2811
  "@ai-sdk/openai-compatible": "^2.0.47",
2812
+ "@algolia/cli": "^5.11.0",
3110
2813
  "@hono/node-server": "^2.0.10",
3111
2814
  "@mishieck/ink-titled-box": "^0.4.2",
3112
2815
  "@segment/analytics-node": "^3.1.0",
@@ -3121,6 +2824,7 @@ var package_default = {
3121
2824
  nanoid: "^5.1.15",
3122
2825
  pino: "^10.3.1",
3123
2826
  react: "^19.2.7",
2827
+ toml: "^4.1.1",
3124
2828
  varlock: "^1.5.1",
3125
2829
  zod: "^4.4.3",
3126
2830
  zustand: "^5.0.14"
@@ -3178,8 +2882,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
3178
2882
  }
3179
2883
 
3180
2884
  // src/actions/confirmLanguage.ts
3181
- import z22 from "zod";
3182
- var confirmLanguageSchema = z22.object({
2885
+ import z19 from "zod";
2886
+ var confirmLanguageSchema = z19.object({
3183
2887
  languages: detectLanguageSchema.shape.languages
3184
2888
  });
3185
2889
  async function confirmLanguage(ctx) {
@@ -3200,8 +2904,8 @@ async function confirmLanguage(ctx) {
3200
2904
  }
3201
2905
 
3202
2906
  // src/actions/confirmFramework.ts
3203
- import z23 from "zod";
3204
- var confirmFrameworkSchema = z23.object({
2907
+ import z20 from "zod";
2908
+ var confirmFrameworkSchema = z20.object({
3205
2909
  frameworks: detectLanguageSchema.shape.frameworks
3206
2910
  });
3207
2911
  var CURATED_FRAMEWORKS = [
@@ -3329,8 +3033,8 @@ async function promptUser(ctx, params) {
3329
3033
  }
3330
3034
 
3331
3035
  // src/actions/confirmEntities.ts
3332
- import z24 from "zod";
3333
- var confirmEntitiesSchema = z24.object({
3036
+ import z21 from "zod";
3037
+ var confirmEntitiesSchema = z21.object({
3334
3038
  // Final detection — the focused re-run may supersede project-scan's.
3335
3039
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3336
3040
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3400,15 +3104,15 @@ async function confirmEntities(ctx) {
3400
3104
  }
3401
3105
 
3402
3106
  // src/actions/review.ts
3403
- import { z as z25 } from "zod";
3404
- var reviewSchema = z25.object({
3107
+ import { z as z22 } from "zod";
3108
+ var reviewSchema = z22.object({
3405
3109
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3406
3110
  // not one entry per workflow step — a step's raw output can be a long,
3407
3111
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3408
3112
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3409
- summaryPoints: z25.array(z25.string()),
3410
- reviewPrompt: z25.string(),
3411
- nextSteps: z25.array(z25.string())
3113
+ summaryPoints: z22.array(z22.string()),
3114
+ reviewPrompt: z22.string(),
3115
+ nextSteps: z22.array(z22.string())
3412
3116
  });
3413
3117
  function formatCompletedSteps(steps) {
3414
3118
  if (!steps.length) return "(no prior steps completed)";
@@ -3459,16 +3163,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3459
3163
  };
3460
3164
 
3461
3165
  // src/actions/implement.ts
3462
- import z26 from "zod";
3166
+ import z24 from "zod";
3463
3167
 
3464
3168
  // src/lib/worktree.ts
3465
3169
  import { execFile, spawn as spawn3 } from "node:child_process";
3466
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3170
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3467
3171
  import {
3468
3172
  basename as basename2,
3469
3173
  dirname as dirname7,
3470
3174
  isAbsolute as isAbsolute2,
3471
- join as join9,
3175
+ join as join10,
3472
3176
  relative as relative2,
3473
3177
  resolve as resolve3
3474
3178
  } from "node:path";
@@ -3502,7 +3206,7 @@ async function isWorkingTreeDirty(repoRoot) {
3502
3206
  return out.trim().length > 0;
3503
3207
  }
3504
3208
  async function pruneOldWorktrees(repoRoot) {
3505
- const dir = join9(stateDir(repoRoot), "worktrees");
3209
+ const dir = join10(stateDir(repoRoot), "worktrees");
3506
3210
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3507
3211
  for (const slug of stale) {
3508
3212
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3513,7 +3217,7 @@ async function pruneOldWorktrees(repoRoot) {
3513
3217
  "worktree",
3514
3218
  "remove",
3515
3219
  "--force",
3516
- join9(dir, slug)
3220
+ join10(dir, slug)
3517
3221
  ]);
3518
3222
  await git(["-C", repoRoot, "branch", "-D", branch]);
3519
3223
  } catch (err) {
@@ -3527,7 +3231,7 @@ async function pruneOldWorktrees(repoRoot) {
3527
3231
  async function createWorktree(repoRoot) {
3528
3232
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3529
3233
  const dirSlug = branch.replace(/\//g, "-");
3530
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3234
+ const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3531
3235
  await git(["-C", repoRoot, "worktree", "prune"]);
3532
3236
  await pruneOldWorktrees(repoRoot);
3533
3237
  await mkdir6(dirname7(path), { recursive: true });
@@ -3647,8 +3351,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3647
3351
  } catch {
3648
3352
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3649
3353
  }
3650
- const relPath = join9(ingestDir, basename2(source));
3651
- const dest = join9(worktreePath, relPath);
3354
+ const relPath = join10(ingestDir, basename2(source));
3355
+ const dest = join10(worktreePath, relPath);
3652
3356
  try {
3653
3357
  await mkdir6(dirname7(dest), { recursive: true });
3654
3358
  await copyFile(source, dest);
@@ -3664,10 +3368,10 @@ function hasEnvVar(content, name) {
3664
3368
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3665
3369
  }
3666
3370
  async function writeSearchEnvValues(worktreePath, vars) {
3667
- const target = join9(worktreePath, ".env");
3371
+ const target = join10(worktreePath, ".env");
3668
3372
  let existing = "";
3669
3373
  try {
3670
- existing = await readFile7(target, "utf8");
3374
+ existing = await readFile8(target, "utf8");
3671
3375
  } catch (err) {
3672
3376
  if (err.code !== "ENOENT") throw err;
3673
3377
  }
@@ -3735,15 +3439,63 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3735
3439
  }
3736
3440
  }
3737
3441
 
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
+
3738
3490
  // src/lib/algoliaDocs.ts
3739
3491
  import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3740
- import { dirname as dirname8, join as join10 } from "node:path";
3492
+ import { dirname as dirname8, join as join11 } from "node:path";
3741
3493
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3742
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3494
+ var DOCS_SUBPATH = join11("docs", "algolia-sdk");
3743
3495
  function findDocsDir() {
3744
3496
  let dir = dirname8(fileURLToPath2(import.meta.url));
3745
3497
  for (; ; ) {
3746
- const candidate = join10(dir, DOCS_SUBPATH);
3498
+ const candidate = join11(dir, DOCS_SUBPATH);
3747
3499
  if (existsSync2(candidate)) return candidate;
3748
3500
  const parent = dirname8(dir);
3749
3501
  if (parent === dir) return void 0;
@@ -3766,7 +3518,7 @@ function loadAlgoliaDoc(language) {
3766
3518
  );
3767
3519
  return "";
3768
3520
  }
3769
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3521
+ return readFileSync(join11(docsDir, files[0]), "utf8").trim();
3770
3522
  }
3771
3523
  function getNamedDoc(name, language) {
3772
3524
  const docsDir = findDocsDir();
@@ -3774,7 +3526,7 @@ function getNamedDoc(name, language) {
3774
3526
  logger.warn("docs/algolia-sdk not found");
3775
3527
  return "";
3776
3528
  }
3777
- const file = join10(docsDir, `${name}-${language}.md`);
3529
+ const file = join11(docsDir, `${name}-${language}.md`);
3778
3530
  if (!existsSync2(file)) {
3779
3531
  logger.warn({ name, language }, "named SDK reference not found");
3780
3532
  return "";
@@ -3801,50 +3553,50 @@ function shellQuote(value) {
3801
3553
  }
3802
3554
 
3803
3555
  // src/actions/implement.ts
3804
- var implementSchema = z26.object({
3805
- filesChanged: z26.array(z26.string()),
3806
- summary: z26.string(),
3556
+ var implementSchema = z24.object({
3557
+ filesChanged: z24.array(z24.string()),
3558
+ summary: z24.string(),
3807
3559
  // Absolute path to the throwaway worktree holding the generated changes, so
3808
3560
  // the user can open it (`cd <worktreePath>`) or inspect the diff
3809
3561
  // (`git -C <worktreePath> status/diff`).
3810
- worktreePath: z26.string().optional(),
3811
- ingestCommand: z26.string().optional(),
3562
+ worktreePath: z24.string().optional(),
3563
+ ingestCommand: z24.string().optional(),
3812
3564
  // True when the user accepted the run-now prompt and the wizard executed the
3813
3565
  // ingestion script; downstream steps use this to avoid telling the user to run
3814
3566
  // a script that already ran.
3815
- ingestScriptRan: z26.boolean().optional(),
3567
+ ingestScriptRan: z24.boolean().optional(),
3816
3568
  // Records ingested by the run-now execution, parsed from the script's
3817
3569
  // machine-readable count line; absent when the script didn't run or emitted
3818
3570
  // no parseable count.
3819
- ingestRecordCount: z26.number().optional(),
3571
+ ingestRecordCount: z24.number().optional(),
3820
3572
  // Wall-clock duration of the run-now ingestion execution, in ms.
3821
- ingestDurationMs: z26.number().optional(),
3822
- ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3573
+ ingestDurationMs: z24.number().optional(),
3574
+ ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3823
3575
  // Suggested names/values, built from framework detection. The search agent is
3824
3576
  // instructed to rename the prefix if it doesn't match the project's build
3825
3577
  // tool, so the names it actually wrote can differ — treat these as hints, not
3826
3578
  // ground truth (the agent's summary carries the final names).
3827
- searchEnvVars: z26.array(
3828
- z26.object({
3829
- name: z26.string(),
3830
- value: z26.string()
3579
+ searchEnvVars: z24.array(
3580
+ z24.object({
3581
+ name: z24.string(),
3582
+ value: z24.string()
3831
3583
  })
3832
3584
  ).optional()
3833
3585
  });
3834
- var implementationOutputSchema = z26.object({
3835
- summary: z26.string(),
3586
+ var implementationOutputSchema = z24.object({
3587
+ summary: z24.string(),
3836
3588
  // Ingestion only: how to run the generated script, as a structured pair the
3837
3589
  // wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
3838
3590
  // command string. `runtime` is constrained to an allowlisted interpreter and
3839
3591
  // `entrypoint` is validated to a worktree-relative path before execution, so
3840
3592
  // the agent cannot inject extra commands or swap the interpreter.
3841
- runtime: z26.enum(INGEST_RUNTIMES).optional(),
3842
- entrypoint: z26.string().optional()
3593
+ runtime: z24.enum(INGEST_RUNTIMES).optional(),
3594
+ entrypoint: z24.string().optional()
3843
3595
  });
3844
- var verificationOutputSchema = z26.object({
3845
- summary: z26.string(),
3846
- sufficient: z26.boolean(),
3847
- additionalInstructions: z26.string().optional()
3596
+ var verificationOutputSchema = z24.object({
3597
+ summary: z24.string(),
3598
+ sufficient: z24.boolean(),
3599
+ additionalInstructions: z24.string().optional()
3848
3600
  });
3849
3601
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3850
3602
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -3958,7 +3710,7 @@ function searchInstructions(input) {
3958
3710
  doc,
3959
3711
  `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.`,
3960
3712
  "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
3961
- // appId always resolves (requireApplication throws otherwise); only the
3713
+ // appId always resolves (loadActiveProfile throws otherwise); only the
3962
3714
  // search-only key is best-effort and can fall back to a placeholder.
3963
3715
  `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3964
3716
  // Names are fixed, not the agent's to rename: the wizard writes the
@@ -4107,7 +3859,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4107
3859
  }
4108
3860
  }
4109
3861
  const targetIndex = selected?.selection;
4110
- useWizard.getState().setTargetIndex(targetIndex ?? null);
4111
3862
  await assertGitRepoWithHead(repoRoot);
4112
3863
  if (await isWorkingTreeDirty(repoRoot)) {
4113
3864
  await confirmDirtyWorkingTree(ctx, repoRoot);
@@ -4118,7 +3869,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4118
3869
  let appId;
4119
3870
  let searchKey;
4120
3871
  if (useCases.includes("search")) {
4121
- appId = (await requireApplication()).id;
3872
+ appId = (await loadActiveProfile()).appId;
4122
3873
  try {
4123
3874
  searchKey = await resolveSearchOnlyKey(targetIndex);
4124
3875
  } catch (err) {
@@ -4229,8 +3980,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4229
3980
  messages: []
4230
3981
  }) === true;
4231
3982
  if (runNow) {
4232
- const ingestApp = await requireApplication();
4233
- const writeKey = await resolveWriteKey(targetIndex);
3983
+ const profile = await loadActiveProfile();
4234
3984
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4235
3985
  const scriptLogId = ctx.logStart("runIngestScript", {
4236
3986
  runtime: ingestRuntime,
@@ -4242,8 +3992,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4242
3992
  ingestRuntime,
4243
3993
  ingestEntrypoint,
4244
3994
  {
4245
- [APP_ID_VAR]: ingestApp.id,
4246
- [API_KEY_VAR]: writeKey
3995
+ [APP_ID_VAR]: profile.appId,
3996
+ [API_KEY_VAR]: profile.apiKey
4247
3997
  }
4248
3998
  );
4249
3999
  ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
@@ -4454,8 +4204,8 @@ var defaultWorkflow = {
4454
4204
  defineStep({
4455
4205
  id: "select-index",
4456
4206
  title: "Set up index",
4457
- outputSchema: z27.object({
4458
- selection: z27.string()
4207
+ outputSchema: z25.object({
4208
+ selection: z25.string()
4459
4209
  }),
4460
4210
  run: (ctx) => selectIndexStep(ctx)
4461
4211
  }),
@@ -4734,7 +4484,7 @@ function parseCliArgs(argv) {
4734
4484
 
4735
4485
  // src/lib/resetState.ts
4736
4486
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4737
- import { join as join11 } from "node:path";
4487
+ import { join as join12 } from "node:path";
4738
4488
  var KEEP = ["wizard.log"];
4739
4489
  async function resetProjectState() {
4740
4490
  const dir = stateDir();
@@ -4746,7 +4496,7 @@ async function resetProjectState() {
4746
4496
  }
4747
4497
  const targets = entries.filter((name) => !KEEP.includes(name));
4748
4498
  await Promise.all(
4749
- targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4499
+ targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4750
4500
  );
4751
4501
  return { dir, removed: targets };
4752
4502
  }
@@ -4801,38 +4551,31 @@ ${formatStepList(workflow)}`);
4801
4551
  }
4802
4552
  async function run(workflow) {
4803
4553
  const store = useWizard.getState();
4804
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4805
- await store.waitForStart();
4554
+ let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4806
4555
  let user = await getUser();
4807
4556
  if (!user) {
4808
- store.beginAuth();
4557
+ await instance.waitUntilRenderFlush();
4558
+ instance.cleanup();
4809
4559
  try {
4810
4560
  await runAuthLogin();
4811
4561
  } catch (err) {
4812
- store.setError(err instanceof Error ? err.message : String(err));
4813
- await instance.waitUntilExit();
4562
+ console.error(err instanceof Error ? err.message : String(err));
4814
4563
  process.exit(1);
4815
4564
  }
4816
- store.endAuth();
4565
+ instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4817
4566
  user = await getUser();
4818
4567
  if (!user) {
4819
4568
  store.setError(
4820
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
4569
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4821
4570
  );
4822
4571
  await instance.waitUntilExit();
4823
4572
  process.exit(1);
4824
4573
  }
4825
4574
  }
4826
4575
  store.setUser(user);
4827
- let app;
4828
- try {
4829
- app = await ensureApplication();
4830
- } catch (err) {
4831
- store.setError(err instanceof Error ? err.message : String(err));
4832
- await instance.waitUntilExit();
4833
- process.exit(1);
4834
- }
4835
- runWorkflow(workflow, app.id);
4576
+ const profile = await loadActiveProfile();
4577
+ await store.waitForStart();
4578
+ runWorkflow(workflow, profile?.appId);
4836
4579
  }
4837
4580
  var started = await startup();
4838
4581
  if (typeof started === "number") {