@algolia/wizard 0.6.0-rc.51.28 → 0.6.0-rc.53.32

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.
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/ui/App.tsx
7
- import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
7
+ import { Box as Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -12,20 +12,54 @@ import { nanoid } from "nanoid";
12
12
 
13
13
  // src/lib/algoliaCli.ts
14
14
  import { spawn } from "node:child_process";
15
- import { createRequire } from "node:module";
16
- var require2 = createRequire(import.meta.url);
17
- function algoliaCliEntry() {
18
- return require2.resolve("@algolia/cli/bin/run.js");
15
+ function npxArgs(args) {
16
+ return ["--yes", "@algolia/cli@latest", ...args];
19
17
  }
20
- function runAlgoliaCli(args) {
18
+ var shell = process.platform === "win32";
19
+ function lineSplitter(emit) {
20
+ let buffer = "";
21
+ return {
22
+ push(chunk) {
23
+ buffer += chunk;
24
+ const lines = buffer.split("\n");
25
+ buffer = lines.pop() ?? "";
26
+ for (const line of lines) emit(line.replace(/\r$/, ""));
27
+ },
28
+ flush() {
29
+ if (buffer) emit(buffer.replace(/\r$/, ""));
30
+ buffer = "";
31
+ }
32
+ };
33
+ }
34
+ var wizardSink = (stream, line) => {
35
+ if (!line.trim()) return;
36
+ useWizard.getState().pushCliOutput(stream, line);
37
+ };
38
+ function runAlgoliaCli(args, { onOutput } = {}) {
39
+ const store2 = useWizard.getState();
40
+ const logId = store2.logStart("tool", `algolia ${args.join(" ")}`);
21
41
  return new Promise((resolve4, reject) => {
22
- const child = spawn(process.execPath, [algoliaCliEntry(), ...args]);
42
+ const child = spawn("npx", npxArgs(args), { shell });
23
43
  let stdout = "";
24
44
  let stderr = "";
25
- child.stdout.on("data", (chunk) => stdout += chunk);
26
- child.stderr.on("data", (chunk) => stderr += chunk);
45
+ const splitters = {
46
+ stdout: lineSplitter((line) => onOutput?.("stdout", line)),
47
+ stderr: lineSplitter((line) => onOutput?.("stderr", line))
48
+ };
49
+ child.stdout.on("data", (chunk) => {
50
+ const text = String(chunk);
51
+ stdout += text;
52
+ splitters.stdout.push(text);
53
+ });
54
+ child.stderr.on("data", (chunk) => {
55
+ const text = String(chunk);
56
+ stderr += text;
57
+ splitters.stderr.push(text);
58
+ });
27
59
  child.on("error", reject);
28
60
  child.on("close", (code) => {
61
+ splitters.stdout.flush();
62
+ splitters.stderr.flush();
29
63
  if (code === 0) {
30
64
  resolve4(stdout);
31
65
  } else {
@@ -37,7 +71,16 @@ function runAlgoliaCli(args) {
37
71
  );
38
72
  }
39
73
  });
40
- });
74
+ }).then(
75
+ (out) => {
76
+ useWizard.getState().logEnd(logId, "success");
77
+ return out;
78
+ },
79
+ (err) => {
80
+ useWizard.getState().logEnd(logId, "error");
81
+ throw err;
82
+ }
83
+ );
41
84
  }
42
85
  async function getUser() {
43
86
  let raw;
@@ -52,13 +95,21 @@ async function getUser() {
52
95
  return null;
53
96
  }
54
97
  }
98
+ function needsInteractiveTerminal(err) {
99
+ const message = err instanceof Error ? err.message : String(err);
100
+ return /non-interactive mode/i.test(message);
101
+ }
55
102
  function runAuthLogin() {
103
+ return runAlgoliaCli(["auth", "login", "--default"], {
104
+ onOutput: wizardSink
105
+ }).then(() => void 0);
106
+ }
107
+ function runAuthLoginInTerminal() {
56
108
  return new Promise((resolve4, reject) => {
57
- const child = spawn(
58
- process.execPath,
59
- [algoliaCliEntry(), "auth", "login", "--default"],
60
- { stdio: "inherit" }
61
- );
109
+ const child = spawn("npx", npxArgs(["auth", "login", "--default"]), {
110
+ shell,
111
+ stdio: "inherit"
112
+ });
62
113
  child.on("error", reject);
63
114
  child.on("close", (code) => {
64
115
  if (code === 0) resolve4();
@@ -171,6 +222,7 @@ function describeInputValue(value) {
171
222
  return Array.isArray(value) ? value.join(", ") : value;
172
223
  }
173
224
  var NOTICE_INTERVAL_MS = 2e3;
225
+ var CLI_OUTPUT_LIMIT = 200;
174
226
  var useWizard = create((set, get) => ({
175
227
  phase: "idle",
176
228
  homeScreen: "home",
@@ -182,10 +234,23 @@ var useWizard = create((set, get) => ({
182
234
  notices: [],
183
235
  _noticeQueue: [],
184
236
  _noticeTimer: null,
237
+ cliOutput: [],
238
+ targetIndex: null,
185
239
  logs: [],
186
240
  error: null,
187
241
  inputReq: null,
188
242
  _resolve: null,
243
+ // Brackets a CLI subprocess that needs the screen. `endAuth` must land back
244
+ // on exactly 'idle': `confirmStart` is a no-op from any other phase and
245
+ // `waitForStart` resolves on any non-idle phase, so ending anywhere else
246
+ // either skips the welcome screen or ignores its spacebar forever.
247
+ beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
248
+ // Re-enters the auth phase *keeping* the CLI output on screen, for returning
249
+ // from a prompt raised mid-sign-in (`submitInput` leaves the phase at
250
+ // 'running'). Clearing here would blank the login output the user was reading
251
+ // the instant they answered.
252
+ resumeAuth: () => set({ phase: "authenticating" }),
253
+ endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "idle" } : {}),
189
254
  // Advances past the welcome screen. Only meaningful from 'idle' — once the
190
255
  // workflow is running there's nothing left to confirm.
191
256
  // Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
@@ -220,7 +285,13 @@ var useWizard = create((set, get) => ({
220
285
  syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
221
286
  setActiveStep: (index) => {
222
287
  get()._clearNoticeQueue();
223
- set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
288
+ set({
289
+ phase: "running",
290
+ currentStepIndex: index,
291
+ output: "",
292
+ notices: [],
293
+ cliOutput: []
294
+ });
224
295
  },
225
296
  setUser: (user2) => set({ user: user2 }),
226
297
  appendToken: (text) => set((s) => ({ output: s.output + text })),
@@ -261,6 +332,16 @@ var useWizard = create((set, get) => ({
261
332
  get()._clearNoticeQueue();
262
333
  set({ notices: [] });
263
334
  },
335
+ // Unthrottled, unlike `pushNotice`: these lines arrive at whatever rate the
336
+ // subprocess emits them, and holding them back would land output after the
337
+ // command it belongs to has already exited.
338
+ pushCliOutput: (stream, text) => set((s) => ({
339
+ cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
340
+ -CLI_OUTPUT_LIMIT
341
+ )
342
+ })),
343
+ clearCliOutput: () => set({ cliOutput: [] }),
344
+ setTargetIndex: (index) => set({ targetIndex: index }),
264
345
  logStart: (kind, name, input) => {
265
346
  const id = nanoid();
266
347
  set((s) => ({
@@ -305,6 +386,8 @@ var useWizard = create((set, get) => ({
305
386
  currentStepIndex: 0,
306
387
  output: "",
307
388
  notices: [],
389
+ cliOutput: [],
390
+ targetIndex: null,
308
391
  logs: [],
309
392
  error: null,
310
393
  inputReq: null,
@@ -313,16 +396,88 @@ var useWizard = create((set, get) => ({
313
396
  }
314
397
  }));
315
398
 
399
+ // src/ui/CliOutput.tsx
400
+ import { Box, Text, useWindowSize } from "ink";
401
+
402
+ // src/ui/theme.ts
403
+ var MARKER = {
404
+ pending: "\u25CB",
405
+ running: "\u25D0",
406
+ done: "\u2713",
407
+ error: "\u2716"
408
+ };
409
+ var BRAND = "#003DFF";
410
+ var SECONDARY = "#5468FF";
411
+ var DANGER = "#F86E7E";
412
+ var COLORS = {
413
+ brand: BRAND,
414
+ primary: "#E6EDF3",
415
+ secondary: SECONDARY,
416
+ strong: "#FFFFFF",
417
+ muted: "#8B949E",
418
+ dim: "#484F58",
419
+ highlight: { bg: "#12331C", fg: "#4ADE80" },
420
+ badge: "#E3B341",
421
+ danger: DANGER,
422
+ success: "#4ADE80",
423
+ bg: {
424
+ main: "#0B0E14",
425
+ sidebar: "#14171E"
426
+ },
427
+ border: "#30363D",
428
+ accent: "#76A0FF",
429
+ status: {
430
+ pending: "gray",
431
+ running: "#76A0FF",
432
+ done: "#4ADE80",
433
+ error: DANGER
434
+ }
435
+ };
436
+
437
+ // src/ui/CliOutput.tsx
438
+ import { jsxs } from "react/jsx-runtime";
439
+ var CLI_MARKER = "\u203A";
440
+ var RESERVED_ROWS = 16;
441
+ var MAX_LINES = 12;
442
+ function CliOutput() {
443
+ const cliOutput = useWizard((s) => s.cliOutput);
444
+ const { rows } = useWindowSize();
445
+ if (!cliOutput.length) return null;
446
+ const budget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_LINES);
447
+ const visible = cliOutput.slice(-budget);
448
+ const hidden = cliOutput.length - visible.length;
449
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
450
+ hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
451
+ "\u2191 ",
452
+ hidden,
453
+ " earlier line(s)"
454
+ ] }),
455
+ visible.map((line) => /* @__PURE__ */ jsxs(
456
+ Text,
457
+ {
458
+ color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
459
+ wrap: "truncate",
460
+ children: [
461
+ CLI_MARKER,
462
+ " ",
463
+ line.text
464
+ ]
465
+ },
466
+ line.id
467
+ ))
468
+ ] });
469
+ }
470
+
316
471
  // src/ui/Notices.tsx
317
- import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
472
+ import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
318
473
  import { useEffect as useEffect2, useState as useState2 } from "react";
319
474
 
320
475
  // src/ui/Table.tsx
321
- import { Box, Text, measureElement, useWindowSize } from "ink";
476
+ import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
322
477
  import { useEffect, useRef, useState } from "react";
323
478
  import { jsx } from "react/jsx-runtime";
324
479
  function Table({ columns, rows }) {
325
- const { columns: termCols } = useWindowSize();
480
+ const { columns: termCols } = useWindowSize2();
326
481
  const ref = useRef(null);
327
482
  const [width, setWidth] = useState(0);
328
483
  useEffect(() => {
@@ -330,7 +485,7 @@ function Table({ columns, rows }) {
330
485
  }, [termCols, columns, rows]);
331
486
  if (rows.length === 0) return null;
332
487
  const lines = formatTable(columns, rows, width || void 0);
333
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: line }, `tbl-${i}`)) });
488
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
334
489
  }
335
490
  function formatTable(columns, rows, width) {
336
491
  const natural = columns.map(
@@ -370,45 +525,10 @@ function resize(widths, budget) {
370
525
  }
371
526
  var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
372
527
 
373
- // src/ui/theme.ts
374
- var MARKER = {
375
- pending: "\u25CB",
376
- running: "\u25D0",
377
- done: "\u2713",
378
- error: "\u2716"
379
- };
380
- var BRAND = "#003DFF";
381
- var SECONDARY = "#5468FF";
382
- var DANGER = "#F86E7E";
383
- var COLORS = {
384
- brand: BRAND,
385
- primary: "#E6EDF3",
386
- secondary: SECONDARY,
387
- strong: "#FFFFFF",
388
- muted: "#8B949E",
389
- dim: "#484F58",
390
- highlight: { bg: "#12331C", fg: "#4ADE80" },
391
- badge: "#E3B341",
392
- danger: DANGER,
393
- success: "#4ADE80",
394
- bg: {
395
- main: "#0B0E14",
396
- sidebar: "#14171E"
397
- },
398
- border: "#30363D",
399
- accent: "#76A0FF",
400
- status: {
401
- pending: "gray",
402
- running: "#76A0FF",
403
- done: "#4ADE80",
404
- error: DANGER
405
- }
406
- };
407
-
408
528
  // src/ui/Notices.tsx
409
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
529
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
410
530
  var AGENT_MARKER = "\u2726";
411
- var RESERVED_ROWS = 14;
531
+ var RESERVED_ROWS2 = 14;
412
532
  var PANEL_TEXT_WIDTH = 45;
413
533
  function messageLineCount(text) {
414
534
  return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
@@ -422,7 +542,7 @@ function noticeLineCount(notice) {
422
542
  return messageLines + tableLines;
423
543
  }
424
544
  function fitVisibleNotices(notices, windowRows) {
425
- const budget = Math.max(windowRows - RESERVED_ROWS, 3);
545
+ const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
426
546
  let used = 0;
427
547
  let count = 0;
428
548
  for (let i = notices.length - 1; i >= 0; i--) {
@@ -455,7 +575,7 @@ function parseHex(hex) {
455
575
  }
456
576
  function Notices() {
457
577
  const notices = useWizard((s) => s.notices);
458
- const { rows: windowRows } = useWindowSize2();
578
+ const { rows: windowRows } = useWindowSize3();
459
579
  const visible = fitVisibleNotices(notices, windowRows);
460
580
  const [pulseStep, setPulseStep] = useState2(0);
461
581
  useEffect2(() => {
@@ -472,14 +592,14 @@ function Notices() {
472
592
  }, []);
473
593
  if (!visible.length) return null;
474
594
  const pulseColor = PULSE_COLORS[pulseStep];
475
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
595
+ return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
476
596
  const isLatest = i === visible.length - 1;
477
- return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
597
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
478
598
  notice.messages?.map((m, j) => {
479
599
  const line = typeof m === "string" ? { text: m } : m;
480
600
  const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
481
- return /* @__PURE__ */ jsxs(
482
- Text2,
601
+ return /* @__PURE__ */ jsxs2(
602
+ Text3,
483
603
  {
484
604
  color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
485
605
  bold: line.bold,
@@ -497,37 +617,37 @@ function Notices() {
497
617
  }
498
618
 
499
619
  // src/ui/PromptInput.tsx
500
- import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
620
+ import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
501
621
  import TextInput from "ink-text-input";
502
622
  import { useState as useState4 } from "react";
503
623
 
504
624
  // src/ui/NextAction.tsx
505
- import { Box as Box3, Text as Text3 } from "ink";
506
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
625
+ import { Box as Box4, Text as Text4 } from "ink";
626
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
507
627
  function NextAction({
508
628
  action,
509
629
  keyHint,
510
630
  hierarchy = "primary"
511
631
  }) {
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 })
632
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
633
+ hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
634
+ hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
635
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
636
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
517
637
  ] }),
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: `]` })
638
+ /* @__PURE__ */ jsxs3(Box4, { children: [
639
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
640
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
641
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
642
+ /* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
523
643
  ] })
524
644
  ] });
525
645
  }
526
646
 
527
647
  // src/ui/SelectPrompt.tsx
528
- import { Box as Box4, Text as Text4, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
648
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useInput, useWindowSize as useWindowSize4 } from "ink";
529
649
  import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
530
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
650
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
531
651
  var CANCEL = "cancel";
532
652
  var ARROW_WIDTH = 4;
533
653
  var COLUMN_GAP = 2;
@@ -564,7 +684,7 @@ function SelectPrompt({
564
684
  if (multi) hints.push({ key: "[space]", label: "select" });
565
685
  hints.push({ key: "[enter]", label: "confirm" });
566
686
  const containerRef = useRef2(null);
567
- const { columns } = useWindowSize3();
687
+ const { columns } = useWindowSize4();
568
688
  const [width, setWidth] = useState3(columns);
569
689
  useLayoutEffect(() => {
570
690
  if (containerRef.current) {
@@ -609,53 +729,53 @@ function SelectPrompt({
609
729
  }
610
730
  }
611
731
  });
612
- return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
613
- error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
614
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
732
+ return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
733
+ error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
734
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
615
735
  table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
616
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
617
- question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
618
- helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
736
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
737
+ question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
738
+ helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
619
739
  ] }),
620
- /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
740
+ /* @__PURE__ */ jsx4(Box5, { flexDirection: "column", children: rows.map((option, i) => {
621
741
  const highlighted = i === index;
622
742
  const isCancel = i === cancelIndex;
623
743
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
624
744
  const sec = isCancel ? void 0 : secondary?.[i];
625
745
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
626
- const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
746
+ const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
627
747
  highlighted ? "\u276F " : " ",
628
748
  bullet,
629
749
  option
630
750
  ] });
631
751
  const isText = sec?.kind === "text";
632
- return /* @__PURE__ */ jsxs3(
633
- Box4,
752
+ return /* @__PURE__ */ jsxs4(
753
+ Box5,
634
754
  {
635
755
  width: isText ? "100%" : barWidth,
636
756
  paddingX: 1,
637
757
  paddingY: 1,
638
758
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
639
759
  children: [
640
- /* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
641
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
642
- Text4,
760
+ /* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
761
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
762
+ Text5,
643
763
  {
644
764
  wrap: "truncate",
645
765
  color: highlighted ? COLORS.primary : COLORS.muted,
646
766
  children: sec.value
647
767
  }
648
768
  ) }),
649
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
769
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
650
770
  ]
651
771
  },
652
772
  `row-${i}`
653
773
  );
654
774
  }) }),
655
- /* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
775
+ /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
656
776
  i > 0 ? " " : "",
657
- /* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
658
- /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
777
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
778
+ /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
659
779
  " ",
660
780
  label
661
781
  ] })
@@ -664,7 +784,7 @@ function SelectPrompt({
664
784
  }
665
785
 
666
786
  // src/ui/PromptInput.tsx
667
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
787
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
668
788
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
669
789
  function SpaceToContinuePrompt({
670
790
  question,
@@ -675,10 +795,10 @@ function SpaceToContinuePrompt({
675
795
  if (input === " ") onDecide(true);
676
796
  else if (key.escape) onDecide(false);
677
797
  });
678
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
679
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
680
- question && /* @__PURE__ */ jsx5(Text5, { color: COLORS.primary, children: question }),
681
- /* @__PURE__ */ jsxs4(Box5, { gap: 1, flexDirection: "column", children: [
798
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
799
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
800
+ question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
801
+ /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
682
802
  /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "space" }),
683
803
  /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
684
804
  ] })
@@ -688,11 +808,11 @@ function PromptInput() {
688
808
  const { phase, inputReq, submitInput } = useWizard();
689
809
  const [draft, setDraft] = useState4("");
690
810
  if (phase === "done" || phase === "error") {
691
- return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
811
+ return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
692
812
  }
693
813
  if (phase !== "awaitingInput" || !inputReq) return null;
694
814
  if (inputReq.promptType === "multipleChoice") {
695
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
815
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
696
816
  SelectPrompt,
697
817
  {
698
818
  question: inputReq.prompt,
@@ -709,7 +829,7 @@ function PromptInput() {
709
829
  ) });
710
830
  }
711
831
  if (inputReq.promptType === "multiSelect") {
712
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
832
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
713
833
  SelectPrompt,
714
834
  {
715
835
  multi: true,
@@ -724,7 +844,7 @@ function PromptInput() {
724
844
  ) });
725
845
  }
726
846
  if (inputReq.promptType === "notice") {
727
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
847
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
728
848
  SelectPrompt,
729
849
  {
730
850
  question: inputReq.prompt,
@@ -746,7 +866,7 @@ function PromptInput() {
746
866
  }
747
867
  if (inputReq.promptType === "acceptReject") {
748
868
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
749
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
869
+ return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
750
870
  SelectPrompt,
751
871
  {
752
872
  question: inputReq.prompt,
@@ -757,11 +877,11 @@ function PromptInput() {
757
877
  }
758
878
  ) });
759
879
  }
760
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
761
- inputReq.error && /* @__PURE__ */ jsx5(Text5, { color: COLORS.danger, children: inputReq.error }),
762
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
763
- /* @__PURE__ */ jsxs4(Box5, { children: [
764
- /* @__PURE__ */ jsxs4(Text5, { color: COLORS.primary, children: [
880
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
881
+ inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
882
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
883
+ /* @__PURE__ */ jsxs5(Box6, { children: [
884
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
765
885
  inputReq.prompt,
766
886
  " "
767
887
  ] }),
@@ -783,7 +903,7 @@ function PromptInput() {
783
903
  // src/ui/Welcome.tsx
784
904
  import { dirname as dirname2, join as join3 } from "node:path";
785
905
  import { fileURLToPath } from "node:url";
786
- import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
906
+ import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
787
907
 
788
908
  // src/ui/copy/welcome.ts
789
909
  var sidebarItems = [
@@ -796,12 +916,12 @@ var sidebarItems = [
796
916
  description: "push 100 records to Algolia in seconds"
797
917
  },
798
918
  {
799
- title: "detect your stack",
800
- description: "React, Vue, Angular, Rails, Django, Laravel & more"
919
+ title: "detect your framework",
920
+ description: "React, Vue, Angular, Vanilla JS"
801
921
  },
802
922
  {
803
923
  title: "scaffold a search UI",
804
- description: "a styled InstantSearch UI, wired into your app or templates"
924
+ description: "a styled InstantSearch component, wired into your app"
805
925
  },
806
926
  {
807
927
  title: "ship it",
@@ -811,27 +931,27 @@ var sidebarItems = [
811
931
 
812
932
  // src/ui/Welcome.tsx
813
933
  import Image, { InkPictureProvider } from "ink-picture";
814
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
934
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
815
935
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
816
936
  function SidebarItem({
817
937
  title,
818
938
  description
819
939
  }) {
820
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
821
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
822
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.success, children: "\u2192" }),
823
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.strong, bold: true, children: title })
940
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
941
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
942
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
943
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
824
944
  ] }),
825
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
945
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
826
946
  /* @__PURE__ */ jsx6(Spacer, {}),
827
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
947
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
828
948
  ] })
829
949
  ] });
830
950
  }
831
951
  function Welcome() {
832
952
  const confirmStart = useWizard((s) => s.confirmStart);
833
953
  const openLearnMore = useWizard((s) => s.openLearnMore);
834
- const { rows } = useWindowSize4();
954
+ const { rows } = useWindowSize5();
835
955
  useInput3((input) => {
836
956
  if (input === " ") confirmStart();
837
957
  else if (input === "i") openLearnMore();
@@ -850,15 +970,15 @@ function Welcome() {
850
970
  if (rows < 30) {
851
971
  layout = scales["small"];
852
972
  }
853
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
973
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
854
974
  /* @__PURE__ */ jsx6(
855
- Box6,
975
+ Box7,
856
976
  {
857
977
  paddingY: layout.main.padding.y,
858
978
  paddingX: layout.main.padding.x,
859
979
  flexDirection: "column",
860
980
  justifyContent: "center",
861
- children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
981
+ children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
862
982
  /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
863
983
  Image,
864
984
  {
@@ -870,16 +990,16 @@ function Welcome() {
870
990
  protocol: "halfBlock"
871
991
  }
872
992
  ) }),
873
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
874
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
993
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
994
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
875
995
  /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "space" }),
876
996
  /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
877
997
  ] })
878
998
  ] })
879
999
  }
880
1000
  ),
881
- /* @__PURE__ */ jsxs5(
882
- Box6,
1001
+ /* @__PURE__ */ jsxs6(
1002
+ Box7,
883
1003
  {
884
1004
  backgroundColor: COLORS.bg.sidebar,
885
1005
  width: 40,
@@ -889,7 +1009,7 @@ function Welcome() {
889
1009
  flexDirection: "column",
890
1010
  justifyContent: "center",
891
1011
  children: [
892
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1012
+ /* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
893
1013
  sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
894
1014
  ]
895
1015
  }
@@ -899,7 +1019,7 @@ function Welcome() {
899
1019
 
900
1020
  // src/ui/LearnMore.tsx
901
1021
  import { Fragment as Fragment2 } from "react";
902
- import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
1022
+ import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
903
1023
 
904
1024
  // src/ui/copy/learn-more.ts
905
1025
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -907,7 +1027,7 @@ var accessItems = [
907
1027
  {
908
1028
  tag: "READ",
909
1029
  title: "Project files",
910
- description: "reads your dependency manifests (package.json, Gemfile, go.mod, pom.xml\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
1030
+ description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
911
1031
  },
912
1032
  {
913
1033
  tag: "WRITE",
@@ -936,7 +1056,7 @@ var policyLinks = [
936
1056
  ];
937
1057
 
938
1058
  // src/ui/LearnMore.tsx
939
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1059
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
940
1060
  var TAG_COLORS = {
941
1061
  READ: COLORS.success,
942
1062
  WRITE: COLORS.badge,
@@ -952,25 +1072,25 @@ function NeverLine({
952
1072
  }) {
953
1073
  const used = segments.reduce((n, s) => n + s.text.length, 0);
954
1074
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
955
- return /* @__PURE__ */ jsxs6(Text7, { children: [
956
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" }),
1075
+ return /* @__PURE__ */ jsxs7(Text8, { children: [
1076
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
957
1077
  " ".repeat(NEVER_BOX_PAD_X),
958
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
1078
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
959
1079
  " ".repeat(rightPad),
960
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" })
1080
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
961
1081
  ] });
962
1082
  }
963
1083
  function LearnMore() {
964
1084
  const confirmStart = useWizard((s) => s.confirmStart);
965
1085
  const backToHome = useWizard((s) => s.backToHome);
966
- const { columns } = useWindowSize5();
1086
+ const { columns } = useWindowSize6();
967
1087
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
968
1088
  useInput4((input, key) => {
969
1089
  if (key.escape) backToHome();
970
1090
  else if (input === " ") confirmStart();
971
1091
  });
972
- return /* @__PURE__ */ jsxs6(
973
- Box7,
1092
+ return /* @__PURE__ */ jsxs7(
1093
+ Box8,
974
1094
  {
975
1095
  flexDirection: "column",
976
1096
  paddingX: PADDING_X,
@@ -978,20 +1098,20 @@ function LearnMore() {
978
1098
  width: "100%",
979
1099
  gap: 1,
980
1100
  children: [
981
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
982
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: accessIntro }),
983
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
984
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
985
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, marginTop: 1, children: [
986
- /* @__PURE__ */ jsx7(Box7, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text7, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
987
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Text7, { children: [
988
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: item.title }),
989
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1101
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1102
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
1103
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
1104
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1105
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1106
+ /* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1107
+ /* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
1108
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
1109
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
990
1110
  ] }) })
991
1111
  ] })
992
1112
  ] }, item.tag)) }),
993
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
994
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1113
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1114
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
995
1115
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
996
1116
  /* @__PURE__ */ jsx7(
997
1117
  NeverLine,
@@ -1000,7 +1120,7 @@ function LearnMore() {
1000
1120
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1001
1121
  }
1002
1122
  ),
1003
- neverItems.map((item) => /* @__PURE__ */ jsxs6(Fragment2, { children: [
1123
+ neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
1004
1124
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1005
1125
  /* @__PURE__ */ jsx7(
1006
1126
  NeverLine,
@@ -1015,23 +1135,23 @@ function LearnMore() {
1015
1135
  )
1016
1136
  ] }, item)),
1017
1137
  /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1018
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1138
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1019
1139
  ] }),
1020
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1021
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1022
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.accent, children: link.url })
1140
+ /* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1141
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1142
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
1023
1143
  ] }, link.label)) }),
1024
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1025
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1026
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1027
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "esc" }),
1028
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "] back" })
1144
+ /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1145
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1146
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1147
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
1148
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
1029
1149
  ] }),
1030
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1031
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1032
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "space" }),
1033
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "]" }),
1034
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.success, bold: true, children: "start wizard" })
1150
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
1151
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
1152
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "space" }),
1153
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
1154
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
1035
1155
  ] })
1036
1156
  ] })
1037
1157
  ]
@@ -1040,10 +1160,10 @@ function LearnMore() {
1040
1160
  }
1041
1161
 
1042
1162
  // src/ui/Sidebar.tsx
1043
- import { Box as Box10, Text as Text10 } from "ink";
1163
+ import { Box as Box11, Text as Text11 } from "ink";
1044
1164
 
1045
1165
  // src/ui/Steps.tsx
1046
- import { Box as Box8, Text as Text8 } from "ink";
1166
+ import { Box as Box9, Text as Text9 } from "ink";
1047
1167
  import Spinner from "ink-spinner";
1048
1168
 
1049
1169
  // src/core/persistence.ts
@@ -1072,11 +1192,11 @@ async function clearWorkflowState(workflowId) {
1072
1192
  }
1073
1193
 
1074
1194
  // src/ui/Steps.tsx
1075
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1195
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1076
1196
  function Steps() {
1077
1197
  const { steps } = useWizard();
1078
1198
  const visibleSteps = steps.filter(isStepVisible);
1079
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status[s.status], children: [
1199
+ return /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status[s.status], children: [
1080
1200
  s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1081
1201
  " ",
1082
1202
  s.title
@@ -1086,7 +1206,7 @@ function CurrentStep() {
1086
1206
  const { steps } = useWizard();
1087
1207
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1088
1208
  if (!currentStep) return null;
1089
- return /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
1209
+ return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
1090
1210
  /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1091
1211
  " ",
1092
1212
  ` ${currentStep.title}`
@@ -1094,19 +1214,19 @@ function CurrentStep() {
1094
1214
  }
1095
1215
 
1096
1216
  // src/ui/Progress.tsx
1097
- import { Box as Box9, Text as Text9 } from "ink";
1098
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1217
+ import { Box as Box10, Text as Text10 } from "ink";
1218
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1099
1219
  function Progress() {
1100
1220
  const { steps, currentStepIndex } = useWizard();
1101
1221
  const visibleSteps = steps.filter(isStepVisible);
1102
1222
  if (visibleSteps.length === 0) return null;
1103
1223
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1104
1224
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1105
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1106
- /* @__PURE__ */ jsx9(Text9, { color: COLORS.muted, children: "STEP" }),
1107
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: activeStepNumber }),
1108
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "/" }),
1109
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: visibleSteps.length })
1225
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1226
+ /* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
1227
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
1228
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
1229
+ /* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
1110
1230
  ] });
1111
1231
  }
1112
1232
 
@@ -1117,10 +1237,10 @@ var sidebarCommands = [
1117
1237
  ];
1118
1238
 
1119
1239
  // src/ui/Sidebar.tsx
1120
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1240
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1121
1241
  function Sidebar() {
1122
- return /* @__PURE__ */ jsxs9(
1123
- Box10,
1242
+ return /* @__PURE__ */ jsxs10(
1243
+ Box11,
1124
1244
  {
1125
1245
  backgroundColor: "#14171E",
1126
1246
  width: 30,
@@ -1129,16 +1249,16 @@ function Sidebar() {
1129
1249
  flexDirection: "column",
1130
1250
  justifyContent: "space-between",
1131
1251
  children: [
1132
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1133
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
1252
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1253
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
1134
1254
  /* @__PURE__ */ jsx10(Steps, {})
1135
1255
  ] }),
1136
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1256
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1137
1257
  /* @__PURE__ */ jsx10(Progress, {}),
1138
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: sidebarCommands.map((c) => {
1139
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1140
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1141
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: c.description })
1258
+ /* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
1259
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1260
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1261
+ /* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
1142
1262
  ] });
1143
1263
  }) })
1144
1264
  ] })
@@ -1148,12 +1268,12 @@ function Sidebar() {
1148
1268
  }
1149
1269
 
1150
1270
  // src/ui/Ribbon.tsx
1151
- import { Box as Box11, Text as Text11 } from "ink";
1152
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1271
+ import { Box as Box12, Text as Text12 } from "ink";
1272
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1153
1273
  function Ribbon() {
1154
1274
  const firstCommand = sidebarCommands[0];
1155
- return /* @__PURE__ */ jsxs10(
1156
- Box11,
1275
+ return /* @__PURE__ */ jsxs11(
1276
+ Box12,
1157
1277
  {
1158
1278
  backgroundColor: "#14171E",
1159
1279
  flexDirection: "row",
@@ -1163,9 +1283,9 @@ function Ribbon() {
1163
1283
  children: [
1164
1284
  /* @__PURE__ */ jsx11(Progress, {}),
1165
1285
  /* @__PURE__ */ jsx11(CurrentStep, {}),
1166
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1167
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1168
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.muted, children: firstCommand.description })
1286
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1287
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1288
+ /* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
1169
1289
  ] })
1170
1290
  ]
1171
1291
  }
@@ -1177,8 +1297,8 @@ import { useState as useState6 } from "react";
1177
1297
 
1178
1298
  // src/ui/Logs.tsx
1179
1299
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
1180
- import { Box as Box12, Text as Text12, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
1181
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1300
+ import { Box as Box13, Text as Text13, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1301
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1182
1302
  var KIND_COLOR = {
1183
1303
  tool: COLORS.primary,
1184
1304
  prompt: COLORS.badge
@@ -1208,7 +1328,7 @@ function formatTimestamp(ms) {
1208
1328
  }
1209
1329
  function Logs() {
1210
1330
  const logs = useWizard((s) => s.logs);
1211
- const { rows, columns } = useWindowSize6();
1331
+ const { rows, columns } = useWindowSize7();
1212
1332
  const viewportRef = useRef3(null);
1213
1333
  const [viewportHeight, setViewportHeight] = useState5(0);
1214
1334
  const [viewportWidth, setViewportWidth] = useState5(0);
@@ -1245,10 +1365,10 @@ function Logs() {
1245
1365
  const visible = logs.slice(scrollOffset, scrollOffset + capacity);
1246
1366
  const hiddenAbove = scrollOffset;
1247
1367
  const hiddenBelow = logs.length - scrollOffset - visible.length;
1248
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1249
- logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
1250
- /* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1251
- hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1368
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1369
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
1370
+ /* @__PURE__ */ jsxs12(Box13, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1371
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1252
1372
  "\u2191 ",
1253
1373
  hiddenAbove,
1254
1374
  " more"
@@ -1263,20 +1383,20 @@ function Logs() {
1263
1383
  const name = truncate2(entry.name, budget);
1264
1384
  budget -= name.length;
1265
1385
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1266
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
1267
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
1268
- /* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
1269
- preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
1270
- durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
1386
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
1387
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
1388
+ /* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
1389
+ preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
1390
+ durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
1271
1391
  ] }, entry.id);
1272
1392
  }),
1273
- hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1393
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
1274
1394
  "\u2193 ",
1275
1395
  hiddenBelow,
1276
1396
  " more"
1277
1397
  ] })
1278
1398
  ] }),
1279
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1399
+ /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1280
1400
  ] });
1281
1401
  }
1282
1402
 
@@ -1300,11 +1420,11 @@ function track(event, payload) {
1300
1420
  }
1301
1421
 
1302
1422
  // src/ui/App.tsx
1303
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1423
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1304
1424
  function App() {
1305
1425
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1306
1426
  const { exit } = useApp();
1307
- const { columns, rows } = useWindowSize7();
1427
+ const { columns, rows } = useWindowSize8();
1308
1428
  const [showLogs, setShowLogs] = useState6(false);
1309
1429
  const finished = phase === "done" || phase === "error";
1310
1430
  const currentStep = steps[currentStepIndex];
@@ -1317,7 +1437,8 @@ function App() {
1317
1437
  { isActive: finished }
1318
1438
  );
1319
1439
  useInput6((_input, key) => {
1320
- if (phase === "idle" || phase === "preflight") return;
1440
+ if (phase === "idle" || phase === "preflight" || phase === "authenticating")
1441
+ return;
1321
1442
  if (key.tab) {
1322
1443
  setShowLogs(!showLogs);
1323
1444
  track("AI Wizard Interaction", {
@@ -1327,7 +1448,7 @@ function App() {
1327
1448
  });
1328
1449
  }
1329
1450
  });
1330
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
1451
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
1331
1452
  useInput6((_input, key) => {
1332
1453
  if (escOwnedElsewhere) return;
1333
1454
  if (key.escape) {
@@ -1340,19 +1461,19 @@ function App() {
1340
1461
  exit();
1341
1462
  }
1342
1463
  });
1343
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1464
+ const mainWindowVisible = phase === "authenticating" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1344
1465
  const flexDirection = columns > 90 ? "row" : "column";
1345
1466
  const showSidebar = flexDirection === "row";
1346
- return /* @__PURE__ */ jsxs12(
1347
- Box13,
1467
+ return /* @__PURE__ */ jsxs13(
1468
+ Box14,
1348
1469
  {
1349
1470
  backgroundColor: COLORS.bg.main,
1350
1471
  flexDirection: "row",
1351
1472
  width: columns,
1352
1473
  minHeight: rows,
1353
1474
  children: [
1354
- mainWindowVisible && /* @__PURE__ */ jsxs12(
1355
- Box13,
1475
+ mainWindowVisible && /* @__PURE__ */ jsxs13(
1476
+ Box14,
1356
1477
  {
1357
1478
  flexDirection,
1358
1479
  width: "100%",
@@ -1360,8 +1481,8 @@ function App() {
1360
1481
  children: [
1361
1482
  showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1362
1483
  /* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
1363
- /* @__PURE__ */ jsxs12(
1364
- Box13,
1484
+ /* @__PURE__ */ jsxs13(
1485
+ Box14,
1365
1486
  {
1366
1487
  flexDirection: "column",
1367
1488
  paddingX: 4,
@@ -1369,10 +1490,15 @@ function App() {
1369
1490
  width: showSidebar ? 70 : "100%",
1370
1491
  flexGrow: showSidebar ? 1 : 0,
1371
1492
  children: [
1493
+ phase === "authenticating" && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
1494
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1495
+ /* @__PURE__ */ jsx13(Text14, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1496
+ ] }),
1497
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1372
1498
  /* @__PURE__ */ jsx13(Notices, {}),
1373
1499
  /* @__PURE__ */ jsx13(PromptInput, {}),
1374
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1375
- phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1500
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1501
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
1376
1502
  "\u2716 ",
1377
1503
  error
1378
1504
  ] }) })
@@ -1784,61 +1910,134 @@ async function runWorkflow(workflow2, appId) {
1784
1910
  }
1785
1911
  }
1786
1912
 
1787
- // src/lib/algoliaProfile.ts
1788
- import { readFile as readFile3 } from "node:fs/promises";
1789
- import { createRequire as createRequire2 } from "node:module";
1790
- import { homedir as homedir2 } from "node:os";
1791
- import { join as join6 } from "node:path";
1792
- import { parse as parseToml } from "toml";
1793
- var require3 = createRequire2(import.meta.url);
1794
- function configPath() {
1795
- const base = process.env.XDG_CONFIG_HOME || join6(homedir2(), ".config");
1796
- return join6(base, "algolia", "config.toml");
1797
- }
1798
- function profilesFromConfig(tomlText) {
1799
- let parsed;
1913
+ // src/lib/algoliaApp.ts
1914
+ import { z as z3 } from "zod";
1915
+ var currentSchema = z3.object({
1916
+ id: z3.string().min(1),
1917
+ name: z3.string().default(""),
1918
+ plan: z3.string().optional()
1919
+ });
1920
+ var listSchema = z3.array(
1921
+ z3.object({
1922
+ id: z3.string().min(1),
1923
+ name: z3.string().default(""),
1924
+ plan_label: z3.string().optional()
1925
+ }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1926
+ );
1927
+ async function currentApplication() {
1928
+ let raw;
1800
1929
  try {
1801
- parsed = parseToml(tomlText);
1930
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
1802
1931
  } catch {
1803
- return [];
1932
+ return null;
1933
+ }
1934
+ const parsed = currentSchema.safeParse(parseJson(raw));
1935
+ return parsed.success ? parsed.data : null;
1936
+ }
1937
+ async function requireApplication() {
1938
+ const app2 = await currentApplication();
1939
+ if (!app2) {
1940
+ throw new Error(
1941
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
1942
+ );
1943
+ }
1944
+ return app2;
1945
+ }
1946
+ async function listApplications() {
1947
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
1948
+ const parsed = listSchema.safeParse(parseJson(raw));
1949
+ if (!parsed.success) {
1950
+ throw new Error("Could not read the list of Algolia applications.");
1951
+ }
1952
+ return parsed.data;
1953
+ }
1954
+ function selectableApplications(apps) {
1955
+ const nameCounts = /* @__PURE__ */ new Map();
1956
+ for (const app2 of apps) {
1957
+ const name = app2.name.trim();
1958
+ if (name) nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
1804
1959
  }
1805
- const profiles = Object.entries(parsed).filter(
1806
- ([, t]) => typeof t.application_id === "string" && typeof t.api_key === "string"
1807
- ).map(([name, t]) => ({
1808
- name,
1809
- appId: t.application_id,
1810
- apiKey: t.api_key,
1811
- isDefault: t.default === true
1812
- }));
1813
- profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
1814
- return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
1815
- }
1816
- async function loadActiveProfile() {
1817
- let profiles;
1960
+ return apps.filter((app2) => nameCounts.get(app2.name.trim()) === 1);
1961
+ }
1962
+ async function selectApplication(name) {
1963
+ await runAlgoliaCli(["application", "select", "--app-name", name]);
1964
+ }
1965
+ function parseJson(text) {
1818
1966
  try {
1819
- profiles = profilesFromConfig(await readFile3(configPath(), "utf8"));
1967
+ return JSON.parse(text);
1820
1968
  } catch {
1821
- profiles = [];
1969
+ return void 0;
1822
1970
  }
1823
- const profile2 = profiles[0];
1824
- if (!profile2) {
1971
+ }
1972
+
1973
+ // src/lib/algoliaAppPicker.ts
1974
+ function secondaryFor(app2) {
1975
+ return app2.plan ? { kind: "badge", value: app2.plan } : void 0;
1976
+ }
1977
+ async function promptForApplication() {
1978
+ const store2 = useWizard.getState();
1979
+ const phaseBefore = store2.phase;
1980
+ const all = await listApplications();
1981
+ const selectable = selectableApplications(all);
1982
+ const skipped = all.length - selectable.length;
1983
+ if (selectable.length === 0) {
1984
+ const why = all.length === 0 ? "this account has no applications" : "none of this account\u2019s applications have a unique name";
1825
1985
  throw new Error(
1826
- "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
1986
+ `No selectable Algolia application was found: the CLI selects an application by name, and ${why}. Name one in the Algolia dashboard, or run \`npx @algolia/cli@latest application select\` directly.`
1987
+ );
1988
+ }
1989
+ const messages = ["Which Algolia application should the wizard work in?"];
1990
+ if (skipped > 0) {
1991
+ messages.push(
1992
+ `${skipped} application${skipped === 1 ? "" : "s"} not listed: the CLI selects by name, so unnamed and duplicate-named ones can\u2019t be chosen here.`
1993
+ );
1994
+ }
1995
+ for (; ; ) {
1996
+ const choice = await store2.requestUserInput({
1997
+ prompt: "Select an application",
1998
+ promptType: "multipleChoice",
1999
+ options: selectable.map((app2) => `${app2.name} \u2014 ${app2.id}`),
2000
+ secondary: selectable.map(secondaryFor),
2001
+ messages
2002
+ });
2003
+ const index = selectable.findIndex(
2004
+ (app2) => `${app2.name} \u2014 ${app2.id}` === choice
1827
2005
  );
2006
+ const chosen = selectable[index];
2007
+ if (!chosen) {
2008
+ throw new Error("Application picker received an unexpected selection");
2009
+ }
2010
+ try {
2011
+ await selectApplication(chosen.name);
2012
+ } catch (err) {
2013
+ logger.warn(
2014
+ { app: chosen.id, err: err.message },
2015
+ "application select failed; re-prompting"
2016
+ );
2017
+ messages.push(
2018
+ `Could not select \u201C${chosen.name}\u201D. It may have been renamed or removed \u2014 pick another.`
2019
+ );
2020
+ continue;
2021
+ }
2022
+ if (phaseBefore === "authenticating") store2.resumeAuth();
2023
+ logger.info({ app: chosen.id }, "selected Algolia application");
2024
+ return chosen;
1828
2025
  }
1829
- return profile2;
2026
+ }
2027
+ async function ensureApplication() {
2028
+ return await currentApplication() ?? await promptForApplication();
1830
2029
  }
1831
2030
 
1832
2031
  // src/workflows/default.ts
1833
- import { z as z25 } from "zod";
2032
+ import { z as z26 } from "zod";
1834
2033
 
1835
2034
  // src/actions/listIndices.ts
1836
- import { z as z3 } from "zod";
1837
- var indicesListSchema = z3.object({
1838
- items: z3.array(
1839
- z3.object({
1840
- name: z3.string(),
1841
- entries: z3.number().default(0)
2035
+ import { z as z4 } from "zod";
2036
+ var indicesListSchema = z4.object({
2037
+ items: z4.array(
2038
+ z4.object({
2039
+ name: z4.string(),
2040
+ entries: z4.number().default(0)
1842
2041
  })
1843
2042
  )
1844
2043
  });
@@ -1909,12 +2108,12 @@ import "zod";
1909
2108
 
1910
2109
  // src/lib/tools/listFiles.ts
1911
2110
  import { tool } from "ai";
1912
- import z4 from "zod";
2111
+ import z5 from "zod";
1913
2112
  import { readdir } from "node:fs/promises";
1914
2113
 
1915
2114
  // src/lib/tools/path.ts
1916
2115
  import { lstat } from "node:fs/promises";
1917
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join7, sep } from "node:path";
2116
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
1918
2117
  function resolveInRoot(ctx, path) {
1919
2118
  const target = resolve2(ctx.cwd, path);
1920
2119
  const rel = relative(ctx.root, target);
@@ -1930,7 +2129,7 @@ async function hasSymlinkParent(ctx, target) {
1930
2129
  let current = ctx.root;
1931
2130
  const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
1932
2131
  for (const part of parts) {
1933
- current = join7(current, part);
2132
+ current = join6(current, part);
1934
2133
  try {
1935
2134
  if ((await lstat(current)).isSymbolicLink()) return true;
1936
2135
  } catch (err) {
@@ -1945,7 +2144,7 @@ async function hasSymlinkParent(ctx, target) {
1945
2144
  function listFilesTool(ctx) {
1946
2145
  return tool({
1947
2146
  description: "List files in the current working directory",
1948
- inputSchema: z4.object(),
2147
+ inputSchema: z5.object(),
1949
2148
  execute: async () => {
1950
2149
  logger.info("called listFiles tool");
1951
2150
  if (++ctx.counts.list > ctx.limits.list) {
@@ -1961,13 +2160,13 @@ function listFilesTool(ctx) {
1961
2160
 
1962
2161
  // src/lib/tools/changeDirectory.ts
1963
2162
  import { tool as tool2 } from "ai";
1964
- import z5 from "zod";
2163
+ import z6 from "zod";
1965
2164
  import { stat } from "node:fs/promises";
1966
2165
  function changeDirectoryTool(ctx) {
1967
2166
  return tool2({
1968
2167
  description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
1969
- inputSchema: z5.object({
1970
- path: z5.string().describe("Directory to change into")
2168
+ inputSchema: z6.object({
2169
+ path: z6.string().describe("Directory to change into")
1971
2170
  }),
1972
2171
  execute: async ({ path }) => {
1973
2172
  logger.info({ path }, "called changeDirectory tool");
@@ -1989,13 +2188,13 @@ function changeDirectoryTool(ctx) {
1989
2188
 
1990
2189
  // src/lib/tools/reportStatus.ts
1991
2190
  import { tool as tool3 } from "ai";
1992
- import z6 from "zod";
2191
+ import z7 from "zod";
1993
2192
  function reportStatusTool(output) {
1994
2193
  return tool3({
1995
2194
  description: "Report the status of your execution. Return a reason in case of failure.",
1996
- inputSchema: z6.object({
1997
- status: z6.enum(["success", "fail"]),
1998
- reason: z6.string().optional(),
2195
+ inputSchema: z7.object({
2196
+ status: z7.enum(["success", "fail"]),
2197
+ reason: z7.string().optional(),
1999
2198
  output
2000
2199
  }),
2001
2200
  execute: async ({ status, reason, output: output2 }) => {
@@ -2007,8 +2206,8 @@ function reportStatusTool(output) {
2007
2206
 
2008
2207
  // src/lib/tools/readFile.ts
2009
2208
  import { tool as tool4 } from "ai";
2010
- import z7 from "zod";
2011
- import { readFile as readFile4 } from "node:fs/promises";
2209
+ import z8 from "zod";
2210
+ import { readFile as readFile3 } from "node:fs/promises";
2012
2211
 
2013
2212
  // src/lib/tools/env.ts
2014
2213
  import { basename } from "node:path";
@@ -2035,8 +2234,8 @@ function redactEnvValues(content) {
2035
2234
  function readFileTool(ctx) {
2036
2235
  return tool4({
2037
2236
  description: "Read the contents of a file at the given path",
2038
- inputSchema: z7.object({
2039
- filePath: z7.string().describe("Path to the file to read")
2237
+ inputSchema: z8.object({
2238
+ filePath: z8.string().describe("Path to the file to read")
2040
2239
  }),
2041
2240
  execute: async ({ filePath }) => {
2042
2241
  if (++ctx.counts.read > ctx.limits.read) {
@@ -2046,7 +2245,7 @@ function readFileTool(ctx) {
2046
2245
  const resolved = resolveInRoot(ctx, filePath);
2047
2246
  if (!resolved.ok) return resolved.error;
2048
2247
  try {
2049
- const content = await readFile4(resolved.target, "utf8");
2248
+ const content = await readFile3(resolved.target, "utf8");
2050
2249
  return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2051
2250
  } catch (err) {
2052
2251
  return `Error reading ${filePath}: ${err.message}`;
@@ -2057,15 +2256,15 @@ function readFileTool(ctx) {
2057
2256
 
2058
2257
  // src/lib/tools/writeFile.ts
2059
2258
  import { tool as tool5 } from "ai";
2060
- import z8 from "zod";
2259
+ import z9 from "zod";
2061
2260
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2062
2261
  import { dirname as dirname4 } from "node:path";
2063
2262
  function writeFileTool(ctx) {
2064
2263
  return tool5({
2065
2264
  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.",
2066
- inputSchema: z8.object({
2067
- filePath: z8.string().describe("Path to the file to write"),
2068
- content: z8.string().describe("Content to write to the file")
2265
+ inputSchema: z9.object({
2266
+ filePath: z9.string().describe("Path to the file to write"),
2267
+ content: z9.string().describe("Content to write to the file")
2069
2268
  }),
2070
2269
  execute: async ({ filePath, content }) => {
2071
2270
  logger.info({ filePath }, "called writeFile tool");
@@ -2090,9 +2289,95 @@ function writeFileTool(ctx) {
2090
2289
 
2091
2290
  // src/lib/tools/writeAlgoliaCredentials.ts
2092
2291
  import { tool as tool6 } from "ai";
2093
- import z9 from "zod";
2094
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2292
+ import z11 from "zod";
2293
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2095
2294
  import { dirname as dirname5 } from "node:path";
2295
+
2296
+ // src/lib/algoliaApiKey.ts
2297
+ import { z as z10 } from "zod";
2298
+ var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
2299
+ var WRITE_ACLS = [
2300
+ "addObject",
2301
+ "deleteObject",
2302
+ "settings",
2303
+ "editSettings",
2304
+ "listIndexes"
2305
+ ];
2306
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2307
+ var apiKeySchema = z10.object({
2308
+ value: z10.string().min(1),
2309
+ acl: z10.array(z10.string()).default([]),
2310
+ indexes: z10.array(z10.string()).default([])
2311
+ });
2312
+ var apiKeyListSchema = z10.object({
2313
+ items: z10.array(apiKeySchema).optional(),
2314
+ keys: z10.array(apiKeySchema).optional()
2315
+ }).transform((o) => o.items ?? o.keys ?? []);
2316
+ var createdKeySchema = z10.object({
2317
+ key: z10.string().min(1).optional(),
2318
+ value: z10.string().min(1).optional()
2319
+ });
2320
+ function canReuse(key, index) {
2321
+ return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
2322
+ }
2323
+ async function createSearchKey(index) {
2324
+ const stdout = await runAlgoliaCli([
2325
+ "apikeys",
2326
+ "create",
2327
+ "--indices",
2328
+ index,
2329
+ "--acl",
2330
+ "search,browse",
2331
+ "--description",
2332
+ `wizard search-only key for ${index}`,
2333
+ "-o",
2334
+ "json"
2335
+ ]);
2336
+ const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
2337
+ const created = key ?? value;
2338
+ if (!created) throw new Error("apikeys create returned no key value");
2339
+ return created;
2340
+ }
2341
+ function canReuseForWrites(key, index) {
2342
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2343
+ }
2344
+ async function resolveWriteKey(index) {
2345
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2346
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
2347
+ if (existing) {
2348
+ logger.info({ index }, "reusing existing write API key");
2349
+ return existing;
2350
+ }
2351
+ logger.info({ index }, "no reusable write key found; creating one");
2352
+ const created = await runAlgoliaCli([
2353
+ "apikeys",
2354
+ "create",
2355
+ "--indices",
2356
+ index,
2357
+ "--acl",
2358
+ WRITE_ACLS.join(","),
2359
+ "--description",
2360
+ `wizard write key for ${index}`,
2361
+ "-o",
2362
+ "json"
2363
+ ]);
2364
+ const { key, value } = createdKeySchema.parse(JSON.parse(created));
2365
+ const writeKey = key ?? value;
2366
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2367
+ return writeKey;
2368
+ }
2369
+ async function resolveSearchOnlyKey(index) {
2370
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2371
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
2372
+ if (existing) {
2373
+ logger.info({ index }, "reusing existing search-only API key");
2374
+ return existing;
2375
+ }
2376
+ logger.info({ index }, "no reusable search-only key found; creating one");
2377
+ return createSearchKey(index);
2378
+ }
2379
+
2380
+ // src/lib/tools/writeAlgoliaCredentials.ts
2096
2381
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2097
2382
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2098
2383
  function appendEnv(content, entries) {
@@ -2106,9 +2391,9 @@ function hasEnv(content, name) {
2106
2391
  }
2107
2392
  function writeCredentialsTool(ctx) {
2108
2393
  return tool6({
2109
- 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.`,
2110
- inputSchema: z9.object({
2111
- filePath: z9.string().describe(
2394
+ 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.`,
2395
+ inputSchema: z11.object({
2396
+ filePath: z11.string().describe(
2112
2397
  'Path to the env file to write credentials into (e.g. ".env")'
2113
2398
  )
2114
2399
  }),
@@ -2116,11 +2401,17 @@ function writeCredentialsTool(ctx) {
2116
2401
  logger.info({ filePath }, "called writeCredentials tool");
2117
2402
  const resolved = resolveInRoot(ctx, filePath);
2118
2403
  if (resolved.ok === false) return resolved.error;
2119
- let profile2;
2404
+ const targetIndex = useWizard.getState().targetIndex;
2405
+ if (!targetIndex) {
2406
+ return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2407
+ }
2408
+ let appId;
2409
+ let writeKey;
2120
2410
  try {
2121
- profile2 = await loadActiveProfile();
2122
- } catch {
2123
- return "Error: no Algolia profile is configured, so credentials cannot be written. Ask the user to authenticate with the Algolia CLI first.";
2411
+ appId = (await requireApplication()).id;
2412
+ writeKey = await resolveWriteKey(targetIndex);
2413
+ } catch (err) {
2414
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2124
2415
  }
2125
2416
  try {
2126
2417
  if (await hasSymlinkParent(ctx, resolved.target)) {
@@ -2128,7 +2419,7 @@ function writeCredentialsTool(ctx) {
2128
2419
  }
2129
2420
  let existing = "";
2130
2421
  try {
2131
- existing = await readFile5(resolved.target, "utf8");
2422
+ existing = await readFile4(resolved.target, "utf8");
2132
2423
  } catch (err) {
2133
2424
  if (err.code !== "ENOENT") throw err;
2134
2425
  }
@@ -2139,8 +2430,8 @@ function writeCredentialsTool(ctx) {
2139
2430
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2140
2431
  }
2141
2432
  const envWithCredentials = appendEnv(existing, [
2142
- [APP_ID_VAR, profile2.appId],
2143
- [API_KEY_VAR, profile2.apiKey]
2433
+ [APP_ID_VAR, appId],
2434
+ [API_KEY_VAR, writeKey]
2144
2435
  ]);
2145
2436
  await mkdir4(dirname5(resolved.target), { recursive: true });
2146
2437
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2154,731 +2445,27 @@ function writeCredentialsTool(ctx) {
2154
2445
 
2155
2446
  // src/lib/tools/searchFiles.ts
2156
2447
  import { tool as tool7 } from "ai";
2157
- import z10 from "zod";
2158
- import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
2159
- import { join as join10 } from "node:path";
2160
-
2161
- // src/lib/languages.ts
2162
- import { readdir as readdir2 } from "node:fs/promises";
2163
- import { existsSync as existsSync2 } from "node:fs";
2164
- import { join as join9 } from "node:path";
2165
-
2166
- // src/lib/tools/utils/packageManager.ts
2167
- import { readFile as readFile6 } from "node:fs/promises";
2168
- import { existsSync } from "node:fs";
2169
- import { join as join8 } from "node:path";
2170
- var LOCKFILES = [
2171
- ["pnpm-lock.yaml", "pnpm"],
2172
- ["yarn.lock", "yarn"],
2173
- ["bun.lockb", "bun"],
2174
- ["bun.lock", "bun"],
2175
- ["package-lock.json", "npm"]
2176
- ];
2177
- async function readPackageJson(cwd = process.cwd()) {
2178
- return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2179
- }
2180
- function packageManagerFrom(pkg) {
2181
- return pkg.packageManager?.split("@")[0] ?? "npm";
2182
- }
2183
- function packageManagerFromLockfile(cwd) {
2184
- return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2185
- }
2186
- async function detectPackageManager(cwd) {
2187
- try {
2188
- const pkg = await readPackageJson(cwd);
2189
- if (pkg.packageManager) return packageManagerFrom(pkg);
2190
- } catch {
2191
- }
2192
- return packageManagerFromLockfile(cwd) ?? "npm";
2193
- }
2194
-
2195
- // src/lib/shell.ts
2196
- function shellQuote(value) {
2197
- return "'" + value.replace(/'/g, "'\\''") + "'";
2198
- }
2199
-
2200
- // src/lib/languages.ts
2201
- var ENTRYPOINT_TOKEN = "{entrypoint}";
2202
- var INGEST_DIR = ".algolia-wizard";
2203
- var VISIBLE_INGEST_DIR = "algolia-wizard";
2204
- var PY_VENV = `${INGEST_DIR}/.venv`;
2205
- var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
2206
- var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
2207
- var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
2208
- var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
2209
- var LANGUAGE_PROFILES = {
2210
- javascript: {
2211
- id: "javascript",
2212
- displayName: "JavaScript/TypeScript",
2213
- aliases: [
2214
- "javascript",
2215
- "js",
2216
- "typescript",
2217
- "ts",
2218
- "node",
2219
- "nodejs",
2220
- "node.js",
2221
- "bun",
2222
- "deno",
2223
- "ecmascript",
2224
- "jsx",
2225
- "tsx"
2226
- ],
2227
- manifests: ["package.json"],
2228
- // The concrete npm-family manager is resolved by detectPackageManager (it
2229
- // honours the package.json `packageManager` field, which lockfiles can't
2230
- // express), so one spec covers all four and `resolveToolchain` rewrites the
2231
- // binary below.
2232
- packageManagers: [
2233
- {
2234
- id: "npm",
2235
- dependency: { mode: "agent-declares", file: "package.json" },
2236
- installSteps: [{ argv: ["npm", "install"] }],
2237
- ingest: {
2238
- kind: "auto",
2239
- argv: ["node", ENTRYPOINT_TOKEN],
2240
- entrypointExtensions: [".mjs", ".cjs", ".js"]
2241
- }
2242
- }
2243
- ],
2244
- sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
2245
- ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
2246
- // package.json scripts are repo-defined, so they're resolved at run time by
2247
- // repoVerification rather than listed here.
2248
- verification: [],
2249
- envReadInstruction: "Read them from `process.env`.",
2250
- skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
2251
- },
2252
- python: {
2253
- id: "python",
2254
- displayName: "Python",
2255
- aliases: ["python", "python3", "py", "cpython"],
2256
- manifests: [
2257
- "pyproject.toml",
2258
- "requirements.txt",
2259
- "setup.py",
2260
- "setup.cfg",
2261
- "Pipfile"
2262
- ],
2263
- // Deliberately one path for every Python repo: a wizard-owned venv under
2264
- // .algolia-wizard. Reusing the project's uv/poetry environment would mean
2265
- // mutating the developer's real dependency manifest and lockfile, and the
2266
- // declare-here/install-there split is the main way ingestion silently ends
2267
- // up without the SDK installed. The tradeoff: the script can import the
2268
- // Algolia client and anything it declares itself, but not the project's own
2269
- // packages (see the optional root-requirements step below).
2270
- packageManagers: [
2271
- {
2272
- id: "pip-venv",
2273
- dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
2274
- installSteps: [
2275
- { argv: ["python3", "-m", "venv", PY_VENV] },
2276
- {
2277
- argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
2278
- },
2279
- // Best-effort access to the project's own dependencies (DB drivers,
2280
- // ORMs) when the repo pins them the classic way.
2281
- {
2282
- argv: [
2283
- PY_VENV_PYTHON,
2284
- "-m",
2285
- "pip",
2286
- "install",
2287
- "-r",
2288
- "requirements.txt"
2289
- ],
2290
- requiresFile: "requirements.txt",
2291
- optional: true
2292
- }
2293
- ],
2294
- ingest: {
2295
- kind: "auto",
2296
- argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
2297
- entrypointExtensions: [".py"]
2298
- }
2299
- }
2300
- ],
2301
- sdk: {
2302
- packageName: "algoliasearch",
2303
- versionPin: ">=4,<5",
2304
- docKey: "python"
2305
- },
2306
- ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
2307
- verification: [
2308
- {
2309
- // -x skips the venv this same directory holds; without it the check
2310
- // compiles every installed package instead of the generated script.
2311
- label: "python compileall",
2312
- argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
2313
- requiresFile: INGEST_DIR
2314
- }
2315
- ],
2316
- envReadInstruction: "Read them from `os.environ`.",
2317
- skipDirs: [
2318
- "venv",
2319
- "__pycache__",
2320
- "site-packages",
2321
- "dist",
2322
- "build",
2323
- "htmlcov"
2324
- ]
2325
- },
2326
- ruby: {
2327
- id: "ruby",
2328
- displayName: "Ruby",
2329
- aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
2330
- manifests: ["Gemfile", "*.gemspec"],
2331
- packageManagers: [
2332
- {
2333
- id: "bundler",
2334
- dependency: { mode: "agent-declares", file: "Gemfile" },
2335
- installSteps: [{ argv: ["bundle", "install"] }],
2336
- ingest: {
2337
- kind: "auto",
2338
- argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
2339
- entrypointExtensions: [".rb"]
2340
- }
2341
- }
2342
- ],
2343
- sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
2344
- ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
2345
- // Ruby has no directory-level syntax check (`ruby -c` is one file at a
2346
- // time), so verification relies on the agent's own review here.
2347
- verification: [],
2348
- envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
2349
- skipDirs: ["vendor", "tmp", "log", "coverage"]
2350
- },
2351
- php: {
2352
- id: "php",
2353
- displayName: "PHP",
2354
- aliases: ["php", "laravel", "symfony"],
2355
- manifests: ["composer.json"],
2356
- packageManagers: [
2357
- {
2358
- id: "composer",
2359
- // `composer require` both declares and installs, and unlike editing
2360
- // composer.json by hand it can't leave composer.lock out of date (which
2361
- // makes a later `composer install` refuse to run).
2362
- dependency: { mode: "wizard-installs" },
2363
- installSteps: [
2364
- {
2365
- argv: [
2366
- "composer",
2367
- "require",
2368
- "algolia/algoliasearch-client-php:^4",
2369
- "--no-interaction",
2370
- // Repo post-install scripts are the project's code, not ours to
2371
- // trigger; Laravel's package:discover also fails in a bare tree.
2372
- "--no-scripts"
2373
- ]
2374
- }
2375
- ],
2376
- ingest: {
2377
- kind: "auto",
2378
- argv: ["php", ENTRYPOINT_TOKEN],
2379
- entrypointExtensions: [".php"]
2380
- }
2381
- }
2382
- ],
2383
- sdk: {
2384
- packageName: "algolia/algoliasearch-client-php",
2385
- versionPin: "^4",
2386
- docKey: "php"
2387
- },
2388
- ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
2389
- verification: [],
2390
- envReadInstruction: "Read them from `getenv('NAME')`.",
2391
- skipDirs: ["vendor", "node_modules"]
2392
- },
2393
- go: {
2394
- id: "go",
2395
- displayName: "Go",
2396
- aliases: ["go", "golang"],
2397
- manifests: ["go.mod"],
2398
- packageManagers: [
2399
- {
2400
- id: "gomod",
2401
- // Imports in the generated file are the declaration; `go mod tidy`
2402
- // resolves and fetches them — which only works because the script lives
2403
- // outside INGEST_DIR (see VISIBLE_INGEST_DIR).
2404
- dependency: { mode: "code-imports" },
2405
- installSteps: [{ argv: ["go", "mod", "tidy"] }],
2406
- ingest: {
2407
- kind: "auto",
2408
- argv: ["go", "run", ENTRYPOINT_TOKEN],
2409
- entrypointExtensions: [".go"]
2410
- }
2411
- }
2412
- ],
2413
- sdk: {
2414
- packageName: "github.com/algolia/algoliasearch-client-go/v4",
2415
- versionPin: "v4",
2416
- docKey: "go"
2417
- },
2418
- ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
2419
- verification: [
2420
- { label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
2421
- ],
2422
- envReadInstruction: "Read them from `os.Getenv`.",
2423
- skipDirs: ["vendor", "bin"]
2424
- },
2425
- java: {
2426
- id: "java",
2427
- displayName: "Java",
2428
- aliases: ["java"],
2429
- manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
2430
- packageManagers: [
2431
- {
2432
- id: "maven",
2433
- detectFiles: ["pom.xml"],
2434
- dependency: { mode: "agent-declares", file: "pom.xml" },
2435
- installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
2436
- // The main class is a wizard constant the instructions require the agent
2437
- // to use, so execution can't be redirected by agent output. Runnable only
2438
- // because the install step above compiles src/main/java first — which is
2439
- // why the entrypoint lives there rather than under .algolia-wizard/.
2440
- ingest: {
2441
- kind: "auto",
2442
- argv: [
2443
- "mvn",
2444
- "-q",
2445
- "org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
2446
- "-Dexec.mainClass=AlgoliaWizardIngest"
2447
- ],
2448
- entrypointExtensions: [".java"]
2449
- }
2450
- },
2451
- {
2452
- id: "gradle",
2453
- detectFiles: ["build.gradle", "build.gradle.kts"],
2454
- dependency: {
2455
- mode: "agent-declares",
2456
- file: "build.gradle",
2457
- alternatives: ["build.gradle.kts"]
2458
- },
2459
- installSteps: [],
2460
- // Auto-running means executing the repo's own ./gradlew wrapper; out of
2461
- // scope for now, so the wizard writes the code and prints the command.
2462
- ingest: {
2463
- kind: "manual",
2464
- entrypointExtensions: [".java"],
2465
- runCommand: "./gradlew runAlgoliaIngest"
2466
- }
2467
- }
2468
- ],
2469
- sdk: {
2470
- packageName: "com.algolia:algoliasearch",
2471
- versionPin: "4.+",
2472
- docKey: "java",
2473
- alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
2474
- },
2475
- // Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
2476
- // so a class outside it never makes it onto the classpath and the run command
2477
- // fails with "class not found".
2478
- ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
2479
- verification: [
2480
- {
2481
- label: "mvn compile",
2482
- argv: ["mvn", "-q", "-DskipTests", "compile"],
2483
- requiresFile: "pom.xml"
2484
- }
2485
- ],
2486
- envReadInstruction: "Read them from `System.getenv`.",
2487
- skipDirs: ["target", "build", "out"]
2488
- },
2489
- kotlin: {
2490
- id: "kotlin",
2491
- displayName: "Kotlin",
2492
- aliases: ["kotlin", "kt", "ktor"],
2493
- manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
2494
- packageManagers: [
2495
- {
2496
- id: "gradle",
2497
- detectFiles: ["build.gradle.kts", "build.gradle"],
2498
- dependency: {
2499
- mode: "agent-declares",
2500
- file: "build.gradle.kts",
2501
- alternatives: ["build.gradle"]
2502
- },
2503
- installSteps: [],
2504
- ingest: {
2505
- kind: "manual",
2506
- entrypointExtensions: [".kt"],
2507
- runCommand: "./gradlew runAlgoliaIngest"
2508
- }
2509
- },
2510
- // Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
2511
- // this spec such a repo falls through to Gradle and is told to run a
2512
- // ./gradlew task that doesn't exist. Compiling needs the repo's own
2513
- // kotlin-maven-plugin, so the run stays the developer's step.
2514
- {
2515
- id: "maven",
2516
- detectFiles: ["pom.xml"],
2517
- dependency: { mode: "agent-declares", file: "pom.xml" },
2518
- installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
2519
- ingest: {
2520
- kind: "manual",
2521
- entrypointExtensions: [".kt"],
2522
- runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
2523
- }
2524
- }
2525
- ],
2526
- sdk: {
2527
- packageName: "com.algolia:algoliasearch-client-kotlin",
2528
- versionPin: "3.+",
2529
- docKey: "kotlin",
2530
- // The published client's commonMain ships only ktor-client-core; without an
2531
- // engine the script compiles and then fails at its first request.
2532
- alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
2533
- },
2534
- ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
2535
- verification: [],
2536
- envReadInstruction: "Read them from `System.getenv`.",
2537
- skipDirs: ["build", "out"]
2538
- },
2539
- scala: {
2540
- id: "scala",
2541
- displayName: "Scala",
2542
- aliases: ["scala", "sbt"],
2543
- manifests: ["build.sbt", "build.sc"],
2544
- packageManagers: [
2545
- {
2546
- id: "sbt",
2547
- dependency: { mode: "agent-declares", file: "build.sbt" },
2548
- installSteps: [],
2549
- ingest: {
2550
- kind: "manual",
2551
- entrypointExtensions: [".scala"],
2552
- runCommand: 'sbt "runMain AlgoliaWizardIngest"'
2553
- }
2554
- }
2555
- ],
2556
- sdk: {
2557
- packageName: "com.algolia:algoliasearch-scala_2.13",
2558
- versionPin: "2.+",
2559
- docKey: "scala",
2560
- alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
2561
- },
2562
- ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
2563
- verification: [],
2564
- envReadInstruction: "Read them from `sys.env`.",
2565
- // `project/` holds sbt's build definition, but the name is generic enough
2566
- // that some repos use it for source; scanning it is cheap, missing source
2567
- // is not.
2568
- skipDirs: ["target"]
2569
- },
2570
- csharp: {
2571
- id: "csharp",
2572
- displayName: "C#",
2573
- aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
2574
- manifests: ["*.csproj", "*.sln", "global.json"],
2575
- packageManagers: [
2576
- {
2577
- id: "dotnet",
2578
- // A self-contained project under .algolia-wizard keeps the ingest script
2579
- // out of the repo's own build graph.
2580
- dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
2581
- installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
2582
- ingest: {
2583
- kind: "auto",
2584
- argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
2585
- entrypointExtensions: [".csproj"]
2586
- }
2587
- }
2588
- ],
2589
- sdk: {
2590
- packageName: "Algolia.Search",
2591
- versionPin: "7.*",
2592
- docKey: "csharp"
2593
- },
2594
- ingestEntrypointExample: CSHARP_PROJECT,
2595
- verification: [
2596
- {
2597
- label: "dotnet build",
2598
- argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
2599
- requiresFile: CSHARP_PROJECT
2600
- }
2601
- ],
2602
- envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
2603
- // Deliberately not `packages`: modern .NET uses PackageReference, and
2604
- // `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
2605
- // skipping it would hide the entities the scan is looking for.
2606
- skipDirs: ["bin", "obj"]
2607
- },
2608
- swift: {
2609
- id: "swift",
2610
- displayName: "Swift",
2611
- aliases: ["swift", "swiftui", "ios", "vapor"],
2612
- manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
2613
- packageManagers: [
2614
- {
2615
- id: "swiftpm",
2616
- dependency: {
2617
- mode: "agent-declares",
2618
- file: `${SWIFT_PACKAGE_DIR}/Package.swift`
2619
- },
2620
- // `swift build` resolves and fetches; a cold build of the client is slow
2621
- // (minutes), which is why the caller degrades to the manual command when
2622
- // this fails.
2623
- installSteps: [
2624
- {
2625
- argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
2626
- requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
2627
- }
2628
- ],
2629
- ingest: {
2630
- kind: "auto",
2631
- argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
2632
- entrypointExtensions: [".swift"]
2633
- }
2634
- }
2635
- ],
2636
- sdk: {
2637
- packageName: "algoliasearch-client-swift",
2638
- // SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
2639
- // Package.swift dependency pins the patch.
2640
- versionPin: 'from: "9.0.0"',
2641
- docKey: "swift"
2642
- },
2643
- ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
2644
- verification: [
2645
- {
2646
- label: "swift build",
2647
- argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
2648
- requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
2649
- }
2650
- ],
2651
- envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
2652
- skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
2653
- },
2654
- dart: {
2655
- id: "dart",
2656
- displayName: "Dart",
2657
- aliases: ["dart", "flutter"],
2658
- manifests: ["pubspec.yaml"],
2659
- packageManagers: [
2660
- {
2661
- id: "flutter-pub",
2662
- detectFiles: [".metadata"],
2663
- dependency: { mode: "agent-declares", file: "pubspec.yaml" },
2664
- installSteps: [{ argv: ["flutter", "pub", "get"] }],
2665
- ingest: {
2666
- kind: "auto",
2667
- argv: ["dart", "run", ENTRYPOINT_TOKEN],
2668
- entrypointExtensions: [".dart"]
2669
- }
2670
- },
2671
- {
2672
- id: "pub",
2673
- dependency: { mode: "agent-declares", file: "pubspec.yaml" },
2674
- installSteps: [{ argv: ["dart", "pub", "get"] }],
2675
- ingest: {
2676
- kind: "auto",
2677
- argv: ["dart", "run", ENTRYPOINT_TOKEN],
2678
- entrypointExtensions: [".dart"]
2679
- }
2680
- }
2681
- ],
2682
- sdk: {
2683
- packageName: "algolia_client_search",
2684
- versionPin: "^1.0.0",
2685
- docKey: "dart"
2686
- },
2687
- ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
2688
- verification: [
2689
- {
2690
- // Gated on the directory it analyzes, not just pubspec.yaml: a run that
2691
- // only built a search UI never created it, and `dart analyze` on a
2692
- // missing path fails the whole verification pass.
2693
- label: "dart analyze",
2694
- argv: ["dart", "analyze", INGEST_DIR],
2695
- requiresFile: INGEST_DIR
2696
- }
2697
- ],
2698
- envReadInstruction: "Read them from `Platform.environment`.",
2699
- skipDirs: ["build"]
2700
- }
2701
- };
2702
- var DEFAULT_LANGUAGE_ID = "javascript";
2703
- var JAVASCRIPT = "javascript";
2704
- var CURATED_LANGUAGES = Object.values(
2705
- LANGUAGE_PROFILES
2706
- ).map((profile2) => profile2.displayName);
2707
- function isBackendLanguage(profile2) {
2708
- return profile2.id !== JAVASCRIPT;
2709
- }
2710
- function normalizeLanguageName(name) {
2711
- return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
2712
- }
2713
- var ALIAS_TO_ID = /* @__PURE__ */ new Map();
2714
- for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
2715
- for (const alias of [profile2.id, profile2.displayName, ...profile2.aliases]) {
2716
- ALIAS_TO_ID.set(normalizeLanguageName(alias), profile2.id);
2717
- }
2718
- }
2719
- function resolveLanguageProfile(name) {
2720
- const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
2721
- return id ? LANGUAGE_PROFILES[id] : void 0;
2722
- }
2723
- function isSameLanguage(a, b) {
2724
- const x = resolveLanguageProfile(a);
2725
- const y = resolveLanguageProfile(b);
2726
- if (x && y) return x.id === y.id;
2727
- if (x || y) return false;
2728
- const folded = normalizeLanguageName(a);
2729
- return folded !== "" && folded === normalizeLanguageName(b);
2730
- }
2731
- var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
2732
- var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
2733
- ...BASE_SKIP_DIRS,
2734
- ...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
2735
- ]);
2736
- var ALLOWED_BINARIES = new Set(
2737
- Object.values(LANGUAGE_PROFILES).flatMap((profile2) => [
2738
- ...profile2.packageManagers.flatMap((pm) => [
2739
- ...pm.installSteps.map((s) => s.argv[0]),
2740
- ...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
2741
- ]),
2742
- ...profile2.verification.map((v) => v.argv[0])
2743
- ])
2744
- );
2745
- var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
2746
- function isWorktreeRelativeCommand(command) {
2747
- return command.includes("/");
2748
- }
2749
- function withCommand(argv, command) {
2750
- return [command, ...argv.slice(1)];
2751
- }
2752
- function resolveDeclaredManifest(root, packageManager) {
2753
- const { dependency } = packageManager;
2754
- if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
2755
- return packageManager;
2756
- }
2757
- const present = [dependency.file, ...dependency.alternatives].find(
2758
- (file) => existsSync2(join9(root, file))
2759
- );
2760
- if (!present || present === dependency.file) return packageManager;
2761
- return { ...packageManager, dependency: { ...dependency, file: present } };
2762
- }
2763
- async function manifestPresent(root, manifest, listing) {
2764
- if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
2765
- if (!listing.entries) {
2766
- const entries = await readdir2(root).catch(() => []);
2767
- listing.entries = Array.isArray(entries) ? entries : [];
2768
- }
2769
- const suffix = manifest.slice(1);
2770
- return listing.entries.some((e) => e.endsWith(suffix));
2771
- }
2772
- async function profileManifestPresent(root, profile2, listing) {
2773
- for (const manifest of profile2.manifests) {
2774
- if (await manifestPresent(root, manifest, listing)) return true;
2775
- }
2776
- return false;
2777
- }
2778
- async function detectProfilesFromManifests(root) {
2779
- const listing = {};
2780
- const found = [];
2781
- for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
2782
- if (await profileManifestPresent(root, profile2, listing)) found.push(profile2);
2783
- }
2784
- return found;
2785
- }
2786
- async function hasProfileManifest(root, profile2) {
2787
- return profileManifestPresent(root, profile2, {});
2788
- }
2789
- async function pickIngestionCandidates(root, confirmedNames) {
2790
- const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
2791
- const onDisk = await detectProfilesFromManifests(root);
2792
- const onDiskIds = new Set(onDisk.map((p) => p.id));
2793
- const candidates = [
2794
- ...new Map(
2795
- confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
2796
- ).values()
2797
- ];
2798
- return { candidates, confirmed: confirmed3, onDisk };
2799
- }
2800
- async function resolveToolchain(root, profile2) {
2801
- const matched = profile2.packageManagers.find(
2802
- (pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
2803
- (f) => existsSync2(join9(root, f))
2804
- )
2805
- );
2806
- const packageManager = resolveDeclaredManifest(
2807
- root,
2808
- matched ?? profile2.packageManagers[0]
2809
- );
2810
- let { installSteps, ingest } = packageManager;
2811
- installSteps = installSteps.map(
2812
- (step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
2813
- );
2814
- if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
2815
- ingest = {
2816
- ...ingest,
2817
- argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
2818
- };
2819
- }
2820
- if (profile2.id === "javascript") {
2821
- const pm = await detectPackageManager(root);
2822
- if (JS_PACKAGE_MANAGERS.has(pm)) {
2823
- installSteps = installSteps.map((step) => ({
2824
- ...step,
2825
- argv: withCommand(step.argv, pm)
2826
- }));
2827
- if (pm === "bun" && ingest.kind === "auto") {
2828
- ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
2829
- }
2830
- }
2831
- }
2832
- return { profile: profile2, packageManager, installSteps, ingest };
2833
- }
2834
- function resolveIngestArgv(ingest, entrypoint) {
2835
- if (ingest.kind !== "auto") {
2836
- throw new Error("resolveIngestArgv called for a manual-run toolchain");
2837
- }
2838
- return ingest.argv.map(
2839
- (part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
2840
- );
2841
- }
2842
- function describeIngestCommand(ingest, entrypoint) {
2843
- if (ingest.kind !== "auto") return ingest.runCommand;
2844
- return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
2845
- }
2846
- function ingestScriptDir(profile2) {
2847
- const parts = profile2.ingestEntrypointExample.split("/");
2848
- return parts.slice(0, -1).join("/") || ".";
2849
- }
2850
- function dependencyInstruction(toolchain) {
2851
- const { profile: profile2, packageManager } = toolchain;
2852
- const { packageName, versionPin } = profile2.sdk;
2853
- const also = profile2.sdk.alsoRequires ? ` ${profile2.sdk.alsoRequires}` : "";
2854
- switch (packageManager.dependency.mode) {
2855
- case "wizard-installs":
2856
- return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
2857
- case "code-imports":
2858
- return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
2859
- case "agent-declares":
2860
- return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
2861
- }
2862
- }
2863
-
2864
- // src/lib/tools/searchFiles.ts
2865
- var MAX_QUERY_LENGTH = 1e3;
2866
- async function walkFiles(dir) {
2867
- const out = [];
2868
- for (const e of await readdir3(dir, { withFileTypes: true })) {
2869
- if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
2870
- const full = join10(dir, e.name);
2871
- if (e.isDirectory()) out.push(...await walkFiles(full));
2872
- else if (e.isFile()) out.push(full);
2873
- }
2874
- return out;
2448
+ import z12 from "zod";
2449
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2450
+ import { join as join7 } from "node:path";
2451
+ var MAX_QUERY_LENGTH = 1e3;
2452
+ async function walkFiles(dir) {
2453
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2454
+ const out = [];
2455
+ for (const e of await readdir2(dir, { withFileTypes: true })) {
2456
+ if (e.name.startsWith(".") || skip.has(e.name)) continue;
2457
+ const full = join7(dir, e.name);
2458
+ if (e.isDirectory()) out.push(...await walkFiles(full));
2459
+ else if (e.isFile()) out.push(full);
2460
+ }
2461
+ return out;
2875
2462
  }
2876
2463
  function searchFilesTool(ctx) {
2877
2464
  return tool7({
2878
2465
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2879
- inputSchema: z10.object({
2880
- query: z10.string().describe("JavaScript RegExp pattern to search for"),
2881
- path: z10.string().optional().describe("Directory to search in (default: cwd)")
2466
+ inputSchema: z12.object({
2467
+ query: z12.string().describe("JavaScript RegExp pattern to search for"),
2468
+ path: z12.string().optional().describe("Directory to search in (default: cwd)")
2882
2469
  }),
2883
2470
  execute: async ({ query, path = "." }) => {
2884
2471
  logger.info({ query, path }, "called searchFiles tool");
@@ -2900,7 +2487,7 @@ function searchFilesTool(ctx) {
2900
2487
  for (const file of await walkFiles(resolved.target)) {
2901
2488
  let content;
2902
2489
  try {
2903
- content = await readFile7(file, "utf8");
2490
+ content = await readFile5(file, "utf8");
2904
2491
  } catch {
2905
2492
  continue;
2906
2493
  }
@@ -2922,148 +2509,90 @@ function searchFilesTool(ctx) {
2922
2509
 
2923
2510
  // src/lib/tools/verifyImplementation.ts
2924
2511
  import { tool as tool8 } from "ai";
2925
- import z11 from "zod";
2926
-
2927
- // src/lib/tools/repoVerification.ts
2928
- import { existsSync as existsSync3 } from "node:fs";
2929
- import { join as join11 } from "node:path";
2512
+ import z13 from "zod";
2930
2513
 
2931
2514
  // src/lib/tools/utils/runCommand.ts
2932
2515
  import { spawn as spawn2 } from "node:child_process";
2933
- var INSTALL_TIMEOUT_MS = 15 * 6e4;
2934
- var INGEST_TIMEOUT_MS = 15 * 6e4;
2935
- var VERIFY_TIMEOUT_MS = 10 * 6e4;
2936
- var KILL_GRACE_MS = 5e3;
2937
- function runCommand(command, args, options = {}) {
2938
- const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
2516
+ function runCommand(command, args, cwd) {
2939
2517
  return new Promise((resolve4) => {
2940
2518
  let output = "";
2941
- let settled = false;
2942
2519
  const child = spawn2(command, args, {
2943
2520
  cwd,
2944
- shell: false,
2945
- stdio: ["ignore", "pipe", "pipe"],
2946
- ...env ? { env: { ...process.env, ...env } } : {}
2521
+ stdio: ["ignore", "pipe", "pipe"]
2947
2522
  });
2948
- const settle = (result) => {
2949
- if (settled) return;
2950
- settled = true;
2951
- clearTimeout(timer);
2952
- resolve4(result);
2953
- };
2954
- const timer = setTimeout(() => {
2955
- child.kill("SIGTERM");
2956
- setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
2957
- const seconds = Math.round(timeoutMs / 1e3);
2958
- settle({
2959
- code: 1,
2960
- output: `${output}
2961
- Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
2962
- timedOut: true
2963
- });
2964
- }, timeoutMs);
2965
2523
  child.stdout?.on("data", (d) => output += d);
2966
2524
  child.stderr?.on("data", (d) => output += d);
2967
2525
  child.on(
2968
2526
  "error",
2969
- (err) => settle({
2970
- code: 1,
2971
- output: `Failed to run ${command}: ${err.message}`,
2972
- timedOut: false
2973
- })
2974
- );
2975
- child.on(
2976
- "close",
2977
- (code) => settle({ code: code ?? 1, output, timedOut: false })
2527
+ (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2978
2528
  );
2529
+ child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2979
2530
  });
2980
2531
  }
2981
2532
 
2533
+ // src/lib/tools/utils/packageManager.ts
2534
+ import { readFile as readFile6 } from "node:fs/promises";
2535
+ import { existsSync } from "node:fs";
2536
+ import { join as join8 } from "node:path";
2537
+ var LOCKFILES = [
2538
+ ["pnpm-lock.yaml", "pnpm"],
2539
+ ["yarn.lock", "yarn"],
2540
+ ["bun.lockb", "bun"],
2541
+ ["bun.lock", "bun"],
2542
+ ["package-lock.json", "npm"]
2543
+ ];
2544
+ async function readPackageJson(cwd = process.cwd()) {
2545
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2546
+ }
2547
+ function packageManagerFrom(pkg) {
2548
+ return pkg.packageManager?.split("@")[0] ?? "npm";
2549
+ }
2550
+ function packageManagerFromLockfile(cwd) {
2551
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2552
+ }
2553
+ async function detectPackageManager(cwd) {
2554
+ try {
2555
+ const pkg = await readPackageJson(cwd);
2556
+ if (pkg.packageManager) return packageManagerFrom(pkg);
2557
+ } catch {
2558
+ }
2559
+ return packageManagerFromLockfile(cwd) ?? "npm";
2560
+ }
2561
+
2982
2562
  // src/lib/tools/repoVerification.ts
2983
2563
  var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2984
- async function runCheck(command, binary, args) {
2985
- const { code, output } = await runCommand(binary, args, {
2986
- timeoutMs: VERIFY_TIMEOUT_MS
2987
- });
2988
- return { command, exitCode: code, ok: code === 0, output: output.trim() };
2989
- }
2990
- async function javascriptChecks() {
2564
+ async function runRepoVerificationCheck() {
2991
2565
  let pkg;
2992
2566
  try {
2993
2567
  pkg = await readPackageJson();
2994
2568
  } catch (err) {
2995
- return {
2996
- limitation: `Could not read package.json to detect verification conventions: ${err.message}`
2997
- };
2569
+ const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2570
+ return { ok: false, checks: [], limitation };
2998
2571
  }
2999
2572
  const scripts = pkg.scripts ?? {};
3000
2573
  const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
3001
2574
  if (present.length === 0) {
3002
- return {
3003
- limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
3004
- };
2575
+ const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2576
+ return { ok: false, checks: [], limitation };
3005
2577
  }
3006
2578
  const pm = await detectPackageManager(process.cwd());
3007
2579
  const checks = [];
3008
2580
  for (const script of present) {
3009
- checks.push(
3010
- await runCheck(`${pm} run ${script}`, pm, ["run", script])
3011
- );
3012
- }
3013
- return { checks };
3014
- }
3015
- async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
3016
- const ids = [...new Set(languages)];
3017
- if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
3018
- const checks = [];
3019
- const limitations = [];
3020
- for (const id of ids) {
3021
- if (id === JAVASCRIPT) {
3022
- const result = await javascriptChecks();
3023
- if ("checks" in result) checks.push(...result.checks);
3024
- else limitations.push(result.limitation);
3025
- continue;
3026
- }
3027
- const profile2 = LANGUAGE_PROFILES[id];
3028
- const runnable = profile2.verification.filter(
3029
- (spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
3030
- );
3031
- if (runnable.length === 0) {
3032
- limitations.push(
3033
- `No mechanical verification available for ${profile2.displayName} in this repo.`
3034
- );
3035
- continue;
3036
- }
3037
- for (const spec of runnable) {
3038
- checks.push(
3039
- await runCheck(spec.argv.join(" "), spec.argv[0], [
3040
- ...spec.argv.slice(1)
3041
- ])
3042
- );
3043
- }
3044
- }
3045
- if (checks.length === 0) {
3046
- return {
3047
- ok: false,
3048
- checks: [],
3049
- limitation: limitations.join(" ") || "No verification checks available."
3050
- };
2581
+ const command = `${pm} run ${script}`;
2582
+ const { code, output } = await runCommand(pm, ["run", script]);
2583
+ checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
3051
2584
  }
3052
- return {
3053
- ok: checks.every((c) => c.ok),
3054
- checks,
3055
- ...limitations.length ? { limitation: limitations.join(" ") } : {}
3056
- };
2585
+ return { ok: checks.every((c) => c.ok), checks };
3057
2586
  }
3058
2587
 
3059
2588
  // src/lib/tools/verifyImplementation.ts
3060
- function verifyImplementationTool(ctx) {
2589
+ function verifyImplementationTool() {
3061
2590
  return tool8({
3062
- description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
3063
- inputSchema: z11.object(),
2591
+ 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.",
2592
+ inputSchema: z13.object(),
3064
2593
  execute: async () => {
3065
- logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
3066
- return runRepoVerificationCheck(ctx.languages);
2594
+ logger.info("called verifyImplementation tool");
2595
+ return runRepoVerificationCheck();
3067
2596
  }
3068
2597
  });
3069
2598
  }
@@ -3074,7 +2603,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
3074
2603
  import { nanoid as nanoid2 } from "nanoid";
3075
2604
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
3076
2605
  import { dirname as dirname6 } from "node:path";
3077
- import z12 from "zod";
2606
+ import z14 from "zod";
3078
2607
  var DATA_DIR = ".algolia-wizard/data";
3079
2608
  var RECORD_MODEL = "claude-haiku-4-5";
3080
2609
  var MAX_RECORDS = 100;
@@ -3086,17 +2615,17 @@ var anthropic = createAnthropic({
3086
2615
  function generateRecordTool(ctx) {
3087
2616
  return tool9({
3088
2617
  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.",
3089
- inputSchema: z12.object({
3090
- entityName: z12.string().describe("Name of the entity to generate records for."),
3091
- attributes: z12.array(z12.string()).describe("Attribute names each record must contain."),
3092
- count: z12.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
3093
- hint: z12.string().optional().describe("Optional context to steer realistic values.")
2618
+ inputSchema: z14.object({
2619
+ entityName: z14.string().describe("Name of the entity to generate records for."),
2620
+ attributes: z14.array(z14.string()).describe("Attribute names each record must contain."),
2621
+ count: z14.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2622
+ hint: z14.string().optional().describe("Optional context to steer realistic values.")
3094
2623
  }),
3095
2624
  execute: async ({ entityName, attributes, count, hint }) => {
3096
2625
  logger.info({ entityName, count }, "called generateRecord tool");
3097
2626
  try {
3098
- const value = z12.union([z12.string(), z12.number(), z12.boolean(), z12.null()]);
3099
- const recordSchema = z12.object(
2627
+ const value = z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()]);
2628
+ const recordSchema = z14.object(
3100
2629
  Object.fromEntries(attributes.map((attr) => [attr, value]))
3101
2630
  );
3102
2631
  const generateBatch = async (batchCount) => {
@@ -3106,8 +2635,8 @@ function generateRecordTool(ctx) {
3106
2635
  const { output } = await generateText({
3107
2636
  model: anthropic(RECORD_MODEL),
3108
2637
  output: Output.object({
3109
- schema: z12.object({
3110
- records: z12.array(recordSchema).length(batchCount)
2638
+ schema: z14.object({
2639
+ records: z14.array(recordSchema).length(batchCount)
3111
2640
  })
3112
2641
  }),
3113
2642
  prompt: [
@@ -3165,12 +2694,12 @@ function generateRecordTool(ctx) {
3165
2694
 
3166
2695
  // src/lib/tools/notifyUser.ts
3167
2696
  import { tool as tool10 } from "ai";
3168
- import z13 from "zod";
2697
+ import z15 from "zod";
3169
2698
  function notifyUserTool() {
3170
2699
  return tool10({
3171
2700
  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.`,
3172
- inputSchema: z13.object({
3173
- message: z13.string().describe(
2701
+ inputSchema: z15.object({
2702
+ message: z15.string().describe(
3174
2703
  "Short, plain-language description of what you are doing now."
3175
2704
  )
3176
2705
  }),
@@ -3189,17 +2718,12 @@ var DEFAULT_TOOL_LIMITS = {
3189
2718
  read: 20,
3190
2719
  match: 100
3191
2720
  };
3192
- function createToolContext({
3193
- limits = DEFAULT_TOOL_LIMITS,
3194
- cwd = process.cwd(),
3195
- languages = [DEFAULT_LANGUAGE_ID]
3196
- } = {}) {
2721
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
3197
2722
  return {
3198
2723
  root: cwd,
3199
2724
  cwd,
3200
2725
  limits,
3201
- counts: { list: 0, search: 0, read: 0 },
3202
- languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
2726
+ counts: { list: 0, search: 0, read: 0 }
3203
2727
  };
3204
2728
  }
3205
2729
 
@@ -3236,7 +2760,7 @@ function createTools(ctx, { output, tools }) {
3236
2760
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3237
2761
  verifyImplementation: withLogging(
3238
2762
  "verifyImplementation",
3239
- verifyImplementationTool(ctx)
2763
+ verifyImplementationTool()
3240
2764
  ),
3241
2765
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3242
2766
  notifyUser: withLogging("notifyUser", notifyUserTool())
@@ -3272,7 +2796,7 @@ async function runAgent(req) {
3272
2796
  baseURL: PROXY_BASE_URL,
3273
2797
  fetch: proxyFetch
3274
2798
  });
3275
- const toolContext = createToolContext({ languages: req.languages });
2799
+ const toolContext = createToolContext();
3276
2800
  const readTools = ["readFile", "searchFiles", "listFiles"];
3277
2801
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3278
2802
  const instructions = [
@@ -3355,19 +2879,16 @@ async function runAgent(req) {
3355
2879
  }
3356
2880
 
3357
2881
  // src/actions/detectLanguage.ts
3358
- import z16 from "zod";
3359
- var detectLanguageSchema = z16.object({
3360
- languages: z16.array(z16.object({ name: z16.string(), version: z16.string() })),
3361
- frameworks: z16.array(z16.object({ name: z16.string(), version: z16.string() }))
2882
+ import z18 from "zod";
2883
+ var detectLanguageSchema = z18.object({
2884
+ languages: z18.array(z18.object({ name: z18.string(), version: z18.string() })),
2885
+ frameworks: z18.array(z18.object({ name: z18.string(), version: z18.string() }))
3362
2886
  });
3363
2887
  var detectLanguage = () => runAgent({
3364
2888
  instructions: [
3365
2889
  "Analyze the codebase and determine the programming languages and frameworks used",
3366
- "Start from the dependency manifests: package.json, pyproject.toml, requirements.txt, Gemfile, composer.json, go.mod, pom.xml, build.gradle(.kts), *.csproj, build.sbt, Package.swift, pubspec.yaml.",
3367
- "List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
3368
- "If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
2890
+ "If a superset language is found, exclude the subset language. TS-over-JS.",
3369
2891
  "If a meta-framework is used, exclude the framework. Next-over-React.",
3370
- "Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
3371
2892
  "Return the exact version",
3372
2893
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3373
2894
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3379,31 +2900,31 @@ var detectLanguage = () => runAgent({
3379
2900
  });
3380
2901
 
3381
2902
  // src/actions/analyzeCodebase.ts
3382
- import z17 from "zod";
2903
+ import z19 from "zod";
3383
2904
  var READONLY_TOOLS = [
3384
2905
  "listFiles",
3385
2906
  "changeDirectory",
3386
2907
  "readFile",
3387
2908
  "searchFiles"
3388
2909
  ];
3389
- var ingestionAnalysisSchema = z17.object({
3390
- ingestionAnalysis: z17.array(
3391
- z17.object({
3392
- name: z17.string(),
3393
- paths: z17.array(z17.string()),
2910
+ var ingestionAnalysisSchema = z19.object({
2911
+ ingestionAnalysis: z19.array(
2912
+ z19.object({
2913
+ name: z19.string(),
2914
+ paths: z19.array(z19.string()),
3394
2915
  // indexable fields the agent found for this entity
3395
- attributes: z17.array(z17.string())
2916
+ attributes: z19.array(z19.string())
3396
2917
  })
3397
2918
  )
3398
2919
  });
3399
- var searchImplementationAnalysisSchema = z17.object({
3400
- searchImplementationAnalysis: z17.string()
2920
+ var searchImplementationAnalysisSchema = z19.object({
2921
+ searchImplementationAnalysis: z19.string()
3401
2922
  });
3402
- var verificationSchema = z17.object({
3403
- verification: z17.array(z17.string())
2923
+ var verificationSchema = z19.object({
2924
+ verification: z19.array(z19.string())
3404
2925
  });
3405
2926
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3406
- var analyzeCodebaseSchema = z17.object({
2927
+ var analyzeCodebaseSchema = z19.object({
3407
2928
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3408
2929
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3409
2930
  verification: verificationSchema.shape.verification.optional(),
@@ -3416,7 +2937,6 @@ var MODE_CONFIG = {
3416
2937
  "Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
3417
2938
  "For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
3418
2939
  "Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
3419
- "Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
3420
2940
  "Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
3421
2941
  "Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
3422
2942
  "Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
@@ -3428,9 +2948,8 @@ var MODE_CONFIG = {
3428
2948
  instructions: [
3429
2949
  "Analyze the codebase to determine the single best location to add search UI functionality.",
3430
2950
  "Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
3431
- "It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
3432
- "Return one file path as searchImplementationAnalysis.",
3433
- 'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
2951
+ "Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
2952
+ 'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
3434
2953
  "Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
3435
2954
  "When done, call reportStatus"
3436
2955
  ],
@@ -3439,8 +2958,8 @@ var MODE_CONFIG = {
3439
2958
  verification: {
3440
2959
  instructions: [
3441
2960
  "Analyze the codebase to determine which code-quality tools are available to validate changes.",
3442
- "Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier, pyproject.toml or setup.cfg (ruff, mypy, black), Gemfile with .rubocop.yml, composer.json scripts (phpstan, pint), go.mod with a golangci-lint config, Maven/Gradle verification tasks, .NET analyzers, analysis_options.yaml.",
3443
- 'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"] or ["ruff", "mypy"].',
2961
+ "Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
2962
+ 'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
3444
2963
  "Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
3445
2964
  "Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
3446
2965
  "When done, call reportStatus"
@@ -3467,7 +2986,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3467
2986
  // package.json
3468
2987
  var package_default = {
3469
2988
  name: "@algolia/wizard",
3470
- version: "0.6.0-rc.51.28",
2989
+ version: "0.6.0-rc.53.32",
3471
2990
  description: "Magically implement Algolia functionality in your codebase",
3472
2991
  type: "module",
3473
2992
  engines: {
@@ -3515,7 +3034,6 @@ var package_default = {
3515
3034
  dependencies: {
3516
3035
  "@ai-sdk/anthropic": "^3.0.81",
3517
3036
  "@ai-sdk/openai-compatible": "^2.0.47",
3518
- "@algolia/cli": "^5.11.0",
3519
3037
  "@hono/node-server": "^2.0.10",
3520
3038
  "@mishieck/ink-titled-box": "^0.4.2",
3521
3039
  "@segment/analytics-node": "^3.1.0",
@@ -3530,7 +3048,6 @@ var package_default = {
3530
3048
  nanoid: "^5.1.15",
3531
3049
  pino: "^10.3.1",
3532
3050
  react: "^19.2.7",
3533
- toml: "^4.1.1",
3534
3051
  varlock: "^1.5.1",
3535
3052
  zod: "^4.4.3",
3536
3053
  zustand: "^5.0.14"
@@ -3570,185 +3087,82 @@ function parseEntries(raw) {
3570
3087
  return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
3571
3088
  }
3572
3089
  var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
3573
-
3574
- // src/actions/confirmLanguage.ts
3575
- import z19 from "zod";
3576
- var confirmLanguageSchema = z19.object({
3577
- languages: detectLanguageSchema.shape.languages
3578
- });
3579
- var OTHER_OPTION = "Other";
3580
- function confirmed(languages) {
3581
- track("AI Wizard Language Confirmed", { languages });
3582
- return { languages };
3583
- }
3584
- async function askOtherLanguage(ctx) {
3585
- let prompt = "enter the language for your ingestion script";
3090
+ async function askList(ctx, prompt, { required = false } = {}) {
3586
3091
  for (; ; ) {
3587
3092
  const answer = await ctx.requestUserInput({
3588
3093
  prompt,
3589
3094
  promptType: "textInput",
3590
- options: []
3095
+ options: [],
3096
+ helpText: 'Comma-separated, e.g. "TypeScript, Node".'
3591
3097
  });
3592
3098
  if (typeof answer !== "string") {
3593
- throw new Error("confirmLanguage received an unexpected non-text result");
3099
+ throw new Error("askList received an unexpected non-text result");
3594
3100
  }
3595
- const name = parseEntries(answer)[0]?.name;
3596
- if (name) return name;
3597
- prompt = "please enter a language name:";
3101
+ const entries = parseEntries(answer);
3102
+ if (entries.length || !required) return entries;
3103
+ prompt = "Please enter at least one entry:";
3598
3104
  }
3599
3105
  }
3106
+
3107
+ // src/actions/confirmLanguage.ts
3108
+ import z21 from "zod";
3109
+ var confirmLanguageSchema = z21.object({
3110
+ languages: detectLanguageSchema.shape.languages
3111
+ });
3600
3112
  async function confirmLanguage(ctx) {
3601
3113
  const detected = ctx.getStepOutput("project-scan");
3602
- const detectedLanguages = detected.languages ?? [];
3603
- const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
3604
- const primary = detectedLanguages[0];
3605
- if (primary) {
3606
- const accepted = await ctx.requestUserInput({
3607
- prompt: `Write the ingestion script in ${primary.name}?`,
3608
- promptType: "acceptReject",
3609
- options: [`Confirm ${primary.name}`, "Use a different language"],
3610
- secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
3611
- messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
3612
- });
3613
- if (accepted === true) return confirmed(detectedLanguages);
3614
- }
3615
- const options = [...CURATED_LANGUAGES];
3616
- for (const language of detectedLanguages) {
3617
- if (!options.some((o) => isSameLanguage(o, language.name))) {
3618
- options.push(language.name);
3619
- }
3620
- }
3621
- options.push(OTHER_OPTION);
3622
- const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
3623
- const secondary = options.map(
3624
- (o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
3625
- );
3626
- const defaultSelectedIndex = Math.max(
3627
- options.findIndex((o) => detectedFor(o)),
3628
- 0
3629
- );
3630
- const selection = await ctx.requestUserInput({
3631
- prompt: "select the language for your ingestion script",
3632
- promptType: "multipleChoice",
3633
- options,
3634
- secondary,
3635
- defaultSelectedIndex
3114
+ const answer = await ctx.requestUserInput({
3115
+ prompt: "Did we detect your language(s) correctly?",
3116
+ promptType: "acceptReject",
3117
+ options: ["Yes", "No"],
3118
+ messages: [`Languages: ${summarize(detected.languages)}`]
3636
3119
  });
3637
- if (typeof selection !== "string") {
3638
- throw new Error("confirmLanguage received an unexpected non-text result");
3639
- }
3640
- const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
3641
- const version = detectedFor(name)?.version ?? "unknown";
3642
- return confirmed([{ name, version }, ...others(name)]);
3120
+ const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
3121
+ required: true
3122
+ });
3123
+ track("AI Wizard Language Confirmed", {
3124
+ languages
3125
+ });
3126
+ return { languages };
3643
3127
  }
3644
3128
 
3645
3129
  // src/actions/confirmFramework.ts
3646
- import z20 from "zod";
3647
-
3648
- // src/lib/frameworks.ts
3649
- var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
3650
- var FRAMEWORKS = [
3651
- // Frontend — InstantSearch component flavors.
3652
- { name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
3653
- { name: "React", strategy: "react", aliases: ["reactjs"] },
3654
- { name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
3655
- { name: "Angular", strategy: "angular", aliases: ["angularjs"] },
3656
- // No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
3657
- { name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
3658
- {
3659
- name: "Vanilla JS",
3660
- strategy: "js",
3661
- aliases: ["vanilla", "javascript", "js", "astro", "vite"]
3662
- },
3663
- // Backend — Algolia's official framework integrations. Server-rendered
3664
- // templates get InstantSearch.js from a CDN.
3665
- {
3666
- name: "Rails",
3667
- strategy: "cdn-template",
3668
- aliases: ["rubyonrails", "ruby on rails", "erb"]
3669
- },
3670
- { name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
3671
- { name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
3672
- { name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
3673
- // Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
3674
- // wizard can't scaffold a native UI, so it points at the docs instead.
3675
- { name: "Flutter", strategy: "none", aliases: [] },
3676
- { name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
3677
- { name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
3678
- { name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
3130
+ import z22 from "zod";
3131
+ var confirmFrameworkSchema = z22.object({
3132
+ frameworks: detectLanguageSchema.shape.frameworks
3133
+ });
3134
+ var CURATED_FRAMEWORKS = [
3135
+ "Next.js",
3136
+ "React",
3137
+ "Vue",
3138
+ "Angular",
3139
+ "Svelte",
3140
+ "Vanilla JS"
3679
3141
  ];
3680
- var CURATED_FRAMEWORKS = FRAMEWORKS.map(
3681
- (f) => f.name
3682
- );
3142
+ var OTHER_OPTION = "Other";
3683
3143
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
3684
- var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
3685
- for (const framework of FRAMEWORKS) {
3686
- for (const alias of [framework.name, ...framework.aliases]) {
3687
- ALIAS_TO_NAME.set(normalize(alias), framework.name);
3688
- }
3689
- }
3690
- var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
3691
- function canonicalFrameworkName(name) {
3692
- return ALIAS_TO_NAME.get(normalize(name));
3693
- }
3694
- function isSameFramework(a, b) {
3695
- const x = canonicalFrameworkName(a) ?? normalize(a);
3696
- const y = canonicalFrameworkName(b) ?? normalize(b);
3144
+ var FRAMEWORK_ALIASES = {
3145
+ next: "nextjs",
3146
+ nextjs: "nextjs",
3147
+ react: "react",
3148
+ reactjs: "react",
3149
+ vue: "vue",
3150
+ vuejs: "vue",
3151
+ angular: "angular",
3152
+ angularjs: "angular",
3153
+ svelte: "svelte",
3154
+ sveltekit: "svelte",
3155
+ vanillajs: "vanillajs",
3156
+ vanilla: "vanillajs",
3157
+ javascript: "vanillajs",
3158
+ js: "vanillajs"
3159
+ };
3160
+ var isSameFramework = (a, b) => {
3161
+ const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
3162
+ const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
3697
3163
  return x !== "" && x === y;
3698
- }
3699
- function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
3700
- const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
3701
- const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
3702
- if (strategy) return strategy;
3703
- return hasJavaScriptInStack ? "js" : "cdn-template";
3704
- }
3705
- function searchDocKey(strategy) {
3706
- return strategy === "cdn-template" ? "templates" : strategy;
3707
- }
3708
- function bundlesJavaScript(strategy) {
3709
- return strategy !== "cdn-template" && strategy !== "none";
3710
- }
3711
- function canScaffoldSearchUI(strategy) {
3712
- return strategy !== "none";
3713
- }
3714
- var ENV_PREFIXES = [
3715
- { aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
3716
- { aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
3717
- { aliases: ["astro"], prefix: "PUBLIC_" },
3718
- { aliases: ["vite"], prefix: "VITE_" }
3719
- ];
3720
- var DEFAULT_ENV_PREFIX = "PUBLIC_";
3721
- function publicEnvPrefix(frameworkNames, strategy) {
3722
- if (!bundlesJavaScript(strategy)) return "";
3723
- const present = new Set(frameworkNames.map(normalize));
3724
- for (const { aliases, prefix } of ENV_PREFIXES) {
3725
- if (aliases.some((alias) => present.has(alias))) return prefix;
3726
- }
3727
- return DEFAULT_ENV_PREFIX;
3728
- }
3729
- function describeSearchTarget(strategy, frameworkName) {
3730
- switch (strategy) {
3731
- case "react":
3732
- return "React (react-instantsearch)";
3733
- case "vue":
3734
- return "Vue (vue-instantsearch)";
3735
- case "angular":
3736
- return "Angular (angular-instantsearch)";
3737
- case "js":
3738
- return "plain JavaScript (InstantSearch.js)";
3739
- case "cdn-template":
3740
- return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
3741
- case "none":
3742
- return frameworkName ?? "a native mobile app";
3743
- }
3744
- }
3745
-
3746
- // src/actions/confirmFramework.ts
3747
- var confirmFrameworkSchema = z20.object({
3748
- frameworks: detectLanguageSchema.shape.frameworks
3749
- });
3750
- var OTHER_OPTION2 = "Other";
3751
- function confirmed2(name, version) {
3164
+ };
3165
+ function confirmed(name, version) {
3752
3166
  const frameworks = [{ name, version: version ?? "unknown" }];
3753
3167
  track("AI Wizard Frontend Framework Confirmed", { frameworks });
3754
3168
  return { frameworks };
@@ -3776,7 +3190,7 @@ async function confirmFramework(ctx) {
3776
3190
  for (const fw of detectedFrameworks) {
3777
3191
  if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
3778
3192
  }
3779
- options.push(OTHER_OPTION2);
3193
+ options.push(OTHER_OPTION);
3780
3194
  const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
3781
3195
  const primary = detectedFrameworks[0];
3782
3196
  if (primary) {
@@ -3786,7 +3200,7 @@ async function confirmFramework(ctx) {
3786
3200
  options: [`Confirm ${primary.name}`, "Use a different framework"],
3787
3201
  secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
3788
3202
  });
3789
- if (accepted === true) return confirmed2(primary.name, primary.version);
3203
+ if (accepted === true) return confirmed(primary.name, primary.version);
3790
3204
  }
3791
3205
  const secondary = options.map(
3792
3206
  (o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
@@ -3796,7 +3210,7 @@ async function confirmFramework(ctx) {
3796
3210
  0
3797
3211
  );
3798
3212
  const selection = await ctx.requestUserInput({
3799
- prompt: "select the framework that renders your UI",
3213
+ prompt: "select a framework",
3800
3214
  promptType: "multipleChoice",
3801
3215
  options,
3802
3216
  secondary,
@@ -3805,10 +3219,10 @@ async function confirmFramework(ctx) {
3805
3219
  if (typeof selection !== "string") {
3806
3220
  throw new Error("confirmFramework received an unexpected non-text result");
3807
3221
  }
3808
- if (selection === OTHER_OPTION2) {
3809
- return confirmed2(await askOtherFramework(ctx));
3222
+ if (selection === OTHER_OPTION) {
3223
+ return confirmed(await askOtherFramework(ctx));
3810
3224
  }
3811
- return confirmed2(selection, detectedFor(selection)?.version);
3225
+ return confirmed(selection, detectedFor(selection)?.version);
3812
3226
  }
3813
3227
 
3814
3228
  // src/actions/promptUser.ts
@@ -3842,8 +3256,8 @@ async function promptUser(ctx, params) {
3842
3256
  }
3843
3257
 
3844
3258
  // src/actions/confirmEntities.ts
3845
- import z21 from "zod";
3846
- var confirmEntitiesSchema = z21.object({
3259
+ import z23 from "zod";
3260
+ var confirmEntitiesSchema = z23.object({
3847
3261
  // Final detection — the focused re-run may supersede project-scan's.
3848
3262
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3849
3263
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3901,27 +3315,27 @@ async function confirmEntities(ctx) {
3901
3315
  onSubmit: () => {
3902
3316
  }
3903
3317
  });
3904
- const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
3905
- if (confirmed3.length === 0) {
3318
+ const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
3319
+ if (confirmed2.length === 0) {
3906
3320
  throw new Error("User cancelled entity selection \u2014 analysis halted.");
3907
3321
  }
3908
- ctx.setUserInput("confirmedEntities", confirmed3);
3322
+ ctx.setUserInput("confirmedEntities", confirmed2);
3909
3323
  track("AI Wizard Entities Confirmed", {
3910
- entities: toEntitySummary(confirmed3)
3324
+ entities: toEntitySummary(confirmed2)
3911
3325
  });
3912
- return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
3326
+ return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
3913
3327
  }
3914
3328
 
3915
3329
  // src/actions/review.ts
3916
- import { z as z22 } from "zod";
3917
- var reviewSchema = z22.object({
3330
+ import { z as z24 } from "zod";
3331
+ var reviewSchema = z24.object({
3918
3332
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3919
3333
  // not one entry per workflow step — a step's raw output can be a long,
3920
3334
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3921
3335
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3922
- summaryPoints: z22.array(z22.string()),
3923
- reviewPrompt: z22.string(),
3924
- nextSteps: z22.array(z22.string())
3336
+ summaryPoints: z24.array(z24.string()),
3337
+ reviewPrompt: z24.string(),
3338
+ nextSteps: z24.array(z24.string())
3925
3339
  });
3926
3340
  function formatCompletedSteps(steps) {
3927
3341
  if (!steps.length) return "(no prior steps completed)";
@@ -3933,7 +3347,7 @@ ${JSON.stringify(s.output, null, 2)}`
3933
3347
  }
3934
3348
  function formatReviewSummary(result) {
3935
3349
  const nextStepLines = result.nextSteps.map((step) => {
3936
- const isIngestCommand = step.includes("algolia-wizard/") || step.includes("AlgoliaWizardIngest");
3350
+ const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
3937
3351
  const isWorktreeCommand = step.includes("/worktrees/");
3938
3352
  return {
3939
3353
  text: `\u2192 ${step}`,
@@ -3972,17 +3386,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3972
3386
  };
3973
3387
 
3974
3388
  // src/actions/implement.ts
3975
- import z24 from "zod";
3389
+ import z25 from "zod";
3976
3390
 
3977
3391
  // src/lib/worktree.ts
3978
- import { execFile } from "node:child_process";
3979
- import { existsSync as existsSync4 } from "node:fs";
3980
- import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3392
+ import { execFile, spawn as spawn3 } from "node:child_process";
3393
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3981
3394
  import {
3982
3395
  basename as basename2,
3983
3396
  dirname as dirname7,
3984
3397
  isAbsolute as isAbsolute2,
3985
- join as join12,
3398
+ join as join9,
3986
3399
  relative as relative2,
3987
3400
  resolve as resolve3
3988
3401
  } from "node:path";
@@ -4016,8 +3429,8 @@ async function isWorkingTreeDirty(repoRoot) {
4016
3429
  return out.trim().length > 0;
4017
3430
  }
4018
3431
  async function pruneOldWorktrees(repoRoot) {
4019
- const dir = join12(stateDir(repoRoot), "worktrees");
4020
- const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3432
+ const dir = join9(stateDir(repoRoot), "worktrees");
3433
+ const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
4021
3434
  for (const slug of stale) {
4022
3435
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
4023
3436
  try {
@@ -4027,7 +3440,7 @@ async function pruneOldWorktrees(repoRoot) {
4027
3440
  "worktree",
4028
3441
  "remove",
4029
3442
  "--force",
4030
- join12(dir, slug)
3443
+ join9(dir, slug)
4031
3444
  ]);
4032
3445
  await git(["-C", repoRoot, "branch", "-D", branch]);
4033
3446
  } catch (err) {
@@ -4041,55 +3454,43 @@ async function pruneOldWorktrees(repoRoot) {
4041
3454
  async function createWorktree(repoRoot) {
4042
3455
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
4043
3456
  const dirSlug = branch.replace(/\//g, "-");
4044
- const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
3457
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
4045
3458
  await git(["-C", repoRoot, "worktree", "prune"]);
4046
3459
  await pruneOldWorktrees(repoRoot);
4047
3460
  await mkdir6(dirname7(path), { recursive: true });
4048
3461
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
4049
3462
  return { path, branch };
4050
3463
  }
4051
- async function spawnStep(worktreePath, argv) {
4052
- const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
4053
- cwd: worktreePath,
4054
- timeoutMs: INSTALL_TIMEOUT_MS
4055
- });
4056
- return { ok: code === 0, output: output.trim() };
4057
- }
4058
- async function installWorktreeDeps(worktreePath, toolchain) {
4059
- const { profile: profile2, installSteps, packageManager } = toolchain;
4060
- const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
4061
- const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile2) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
4062
- if (!haveSomethingToInstall) {
4063
- return {
4064
- ok: true,
4065
- output: `no ${profile2.displayName} manifest; skipped install`
4066
- };
4067
- }
4068
- if (installSteps.length === 0) {
4069
- return {
4070
- ok: true,
4071
- output: `${profile2.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
4072
- };
4073
- }
4074
- const outputs = [];
4075
- for (const step of installSteps) {
4076
- if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
4077
- continue;
4078
- const result = await spawnStep(worktreePath, step.argv);
4079
- if (result.output) outputs.push(result.output);
4080
- if (result.ok) continue;
4081
- if (step.optional) {
4082
- logger.warn(
4083
- { step: step.argv.join(" "), output: result.output },
4084
- "installWorktreeDeps: optional install step failed; continuing"
4085
- );
4086
- continue;
4087
- }
4088
- return { ok: false, output: outputs.join("\n").trim() };
3464
+ async function installWorktreeDeps(worktreePath) {
3465
+ try {
3466
+ await readPackageJson(worktreePath);
3467
+ } catch {
3468
+ return { ok: true, output: "no package.json; skipped install" };
4089
3469
  }
4090
- return { ok: true, output: outputs.join("\n").trim() };
3470
+ const pm = await detectPackageManager(worktreePath);
3471
+ return new Promise((resolve4) => {
3472
+ let output = "";
3473
+ const child = spawn3(pm, ["install"], {
3474
+ cwd: worktreePath,
3475
+ stdio: ["ignore", "pipe", "pipe"]
3476
+ });
3477
+ child.stdout?.on("data", (d) => output += d);
3478
+ child.stderr?.on("data", (d) => output += d);
3479
+ child.on(
3480
+ "error",
3481
+ (err) => resolve4({
3482
+ ok: false,
3483
+ output: `Failed to run ${pm} install: ${err.message}`
3484
+ })
3485
+ );
3486
+ child.on(
3487
+ "close",
3488
+ (code) => resolve4({ ok: code === 0, output: output.trim() })
3489
+ );
3490
+ });
4091
3491
  }
4092
- function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
3492
+ var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3493
+ function validateIngestEntrypoint(worktreePath, entrypoint) {
4093
3494
  if (!entrypoint || entrypoint.startsWith("-")) {
4094
3495
  return {
4095
3496
  ok: false,
@@ -4104,29 +3505,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
4104
3505
  reason: `entrypoint "${entrypoint}" resolves outside the worktree`
4105
3506
  };
4106
3507
  }
4107
- if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
4108
- return {
4109
- ok: false,
4110
- reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
4111
- };
4112
- }
4113
3508
  return { ok: true, target };
4114
3509
  }
4115
- async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
4116
- const { ingest, profile: profile2, packageManager } = toolchain;
4117
- if (ingest.kind !== "auto") {
3510
+ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3511
+ if (!INGEST_RUNTIMES.includes(runtime)) {
4118
3512
  return {
4119
3513
  ran: false,
4120
3514
  ok: false,
4121
3515
  output: "",
4122
- reason: `${profile2.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
3516
+ reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
4123
3517
  };
4124
3518
  }
4125
- const validated = validateIngestEntrypoint(
4126
- worktreePath,
4127
- entrypoint,
4128
- ingest.entrypointExtensions
4129
- );
3519
+ const validated = validateIngestEntrypoint(worktreePath, entrypoint);
4130
3520
  if (!validated.ok) {
4131
3521
  return { ran: false, ok: false, output: "", reason: validated.reason };
4132
3522
  }
@@ -4147,13 +3537,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
4147
3537
  reason: `entrypoint "${entrypoint}" does not exist`
4148
3538
  };
4149
3539
  }
4150
- const argv = resolveIngestArgv(ingest, entrypoint);
4151
- const { code, output } = await runCommand(argv[0], argv.slice(1), {
4152
- cwd: worktreePath,
4153
- env,
4154
- timeoutMs: INGEST_TIMEOUT_MS
3540
+ return new Promise((resolveRun) => {
3541
+ let output = "";
3542
+ const child = spawn3(runtime, [entrypoint], {
3543
+ cwd: worktreePath,
3544
+ shell: false,
3545
+ stdio: ["ignore", "pipe", "pipe"],
3546
+ env: { ...process.env, ...env }
3547
+ });
3548
+ child.stdout?.on("data", (d) => output += d);
3549
+ child.stderr?.on("data", (d) => output += d);
3550
+ child.on(
3551
+ "error",
3552
+ (err) => resolveRun({
3553
+ ran: true,
3554
+ ok: false,
3555
+ output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3556
+ })
3557
+ );
3558
+ child.on(
3559
+ "close",
3560
+ (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3561
+ );
4155
3562
  });
4156
- return { ran: true, ok: code === 0, output: output.trim() };
4157
3563
  }
4158
3564
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
4159
3565
  const trimmed = sourcePath.trim();
@@ -4168,8 +3574,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
4168
3574
  } catch {
4169
3575
  return { ok: false, reason: `"${sourcePath}" does not exist` };
4170
3576
  }
4171
- const relPath = join12(ingestDir, basename2(source));
4172
- const dest = join12(worktreePath, relPath);
3577
+ const relPath = join9(ingestDir, basename2(source));
3578
+ const dest = join9(worktreePath, relPath);
4173
3579
  try {
4174
3580
  await mkdir6(dirname7(dest), { recursive: true });
4175
3581
  await copyFile(source, dest);
@@ -4185,10 +3591,10 @@ function hasEnvVar(content, name) {
4185
3591
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
4186
3592
  }
4187
3593
  async function writeSearchEnvValues(worktreePath, vars) {
4188
- const target = join12(worktreePath, ".env");
3594
+ const target = join9(worktreePath, ".env");
4189
3595
  let existing = "";
4190
3596
  try {
4191
- existing = await readFile8(target, "utf8");
3597
+ existing = await readFile7(target, "utf8");
4192
3598
  } catch (err) {
4193
3599
  if (err.code !== "ENOENT") throw err;
4194
3600
  }
@@ -4256,135 +3662,160 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
4256
3662
  }
4257
3663
  }
4258
3664
 
4259
- // src/lib/algoliaApiKey.ts
4260
- import { z as z23 } from "zod";
4261
- var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
4262
- var apiKeySchema = z23.object({
4263
- value: z23.string().min(1),
4264
- acl: z23.array(z23.string()).default([]),
4265
- indexes: z23.array(z23.string()).default([])
4266
- });
4267
- var apiKeyListSchema = z23.object({
4268
- items: z23.array(apiKeySchema).optional(),
4269
- keys: z23.array(apiKeySchema).optional()
4270
- }).transform((o) => o.items ?? o.keys ?? []);
4271
- var createdKeySchema = z23.object({
4272
- key: z23.string().min(1).optional(),
4273
- value: z23.string().min(1).optional()
4274
- });
4275
- function canReuse(key, index) {
4276
- return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
4277
- }
4278
- async function createSearchKey(index) {
4279
- const stdout = await runAlgoliaCli([
4280
- "apikeys",
4281
- "create",
4282
- "--indices",
4283
- index,
4284
- "--acl",
4285
- "search,browse",
4286
- "--description",
4287
- `wizard search-only key for ${index}`,
4288
- "-o",
4289
- "json"
4290
- ]);
4291
- const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
4292
- const created = key ?? value;
4293
- if (!created) throw new Error("apikeys create returned no key value");
4294
- return created;
4295
- }
4296
- async function resolveSearchOnlyKey(index) {
4297
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
4298
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
4299
- if (existing) {
4300
- logger.info({ index }, "reusing existing search-only API key");
4301
- return existing;
4302
- }
4303
- logger.info({ index }, "no reusable search-only key found; creating one");
4304
- return createSearchKey(index);
4305
- }
4306
-
4307
3665
  // src/lib/algoliaDocs.ts
4308
- import { readFileSync, existsSync as existsSync5 } from "node:fs";
4309
- import { dirname as dirname8, join as join13 } from "node:path";
3666
+ import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3667
+ import { dirname as dirname8, join as join10 } from "node:path";
4310
3668
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4311
- var DOCS_SUBPATH = join13("docs", "algolia-sdk");
3669
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
4312
3670
  function findDocsDir() {
4313
3671
  let dir = dirname8(fileURLToPath2(import.meta.url));
4314
3672
  for (; ; ) {
4315
- const candidate = join13(dir, DOCS_SUBPATH);
4316
- if (existsSync5(candidate)) return candidate;
3673
+ const candidate = join10(dir, DOCS_SUBPATH);
3674
+ if (existsSync2(candidate)) return candidate;
4317
3675
  const parent = dirname8(dir);
4318
3676
  if (parent === dir) return void 0;
4319
3677
  dir = parent;
4320
3678
  }
4321
3679
  }
4322
- function getNamedDoc(name, key) {
3680
+ function loadAlgoliaDoc(language) {
3681
+ const docsDir = findDocsDir();
3682
+ if (!docsDir) {
3683
+ logger.warn(
3684
+ "algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
3685
+ );
3686
+ return "";
3687
+ }
3688
+ const files = readdirSync(docsDir).filter((f) => f.includes(language));
3689
+ if (files.length === 0) {
3690
+ logger.warn(
3691
+ { language },
3692
+ "algoliaDocs: no SDK reference found for language; skipping"
3693
+ );
3694
+ return "";
3695
+ }
3696
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3697
+ }
3698
+ function getNamedDoc(name, language) {
4323
3699
  const docsDir = findDocsDir();
4324
3700
  if (!docsDir) {
4325
3701
  logger.warn("docs/algolia-sdk not found");
4326
3702
  return "";
4327
3703
  }
4328
- const file = join13(docsDir, `${name}-${key}.md`);
4329
- if (!existsSync5(file)) {
4330
- logger.warn({ name, key }, "named SDK reference not found");
3704
+ const file = join10(docsDir, `${name}-${language}.md`);
3705
+ if (!existsSync2(file)) {
3706
+ logger.warn({ name, language }, "named SDK reference not found");
4331
3707
  return "";
4332
3708
  }
4333
3709
  return readFileSync(file, "utf8").trim();
4334
3710
  }
3711
+ function getFrameworkSpecificDoc(frameworks) {
3712
+ const fw = frameworks.map((f) => f.toLowerCase());
3713
+ if (fw.includes("vue") || fw.includes("nuxt")) {
3714
+ return loadAlgoliaDoc("vue");
3715
+ }
3716
+ if (fw.includes("react") || fw.includes("next.js")) {
3717
+ return loadAlgoliaDoc("react");
3718
+ }
3719
+ if (fw.includes("angular")) {
3720
+ return loadAlgoliaDoc("angular");
3721
+ }
3722
+ return loadAlgoliaDoc("js");
3723
+ }
3724
+
3725
+ // src/lib/shell.ts
3726
+ function shellQuote(value) {
3727
+ return "'" + value.replace(/'/g, "'\\''") + "'";
3728
+ }
4335
3729
 
4336
3730
  // src/actions/implement.ts
4337
- var implementSchema = z24.object({
4338
- filesChanged: z24.array(z24.string()),
4339
- summary: z24.string(),
3731
+ var implementSchema = z25.object({
3732
+ filesChanged: z25.array(z25.string()),
3733
+ summary: z25.string(),
4340
3734
  // Absolute path to the throwaway worktree holding the generated changes, so
4341
3735
  // the user can open it (`cd <worktreePath>`) or inspect the diff
4342
3736
  // (`git -C <worktreePath> status/diff`).
4343
- worktreePath: z24.string().optional(),
4344
- ingestCommand: z24.string().optional(),
3737
+ worktreePath: z25.string().optional(),
3738
+ ingestCommand: z25.string().optional(),
4345
3739
  // True when the user accepted the run-now prompt and the wizard executed the
4346
3740
  // ingestion script; downstream steps use this to avoid telling the user to run
4347
3741
  // a script that already ran.
4348
- ingestScriptRan: z24.boolean().optional(),
3742
+ ingestScriptRan: z25.boolean().optional(),
4349
3743
  // Records ingested by the run-now execution, parsed from the script's
4350
3744
  // machine-readable count line; absent when the script didn't run or emitted
4351
3745
  // no parseable count.
4352
- ingestRecordCount: z24.number().optional(),
3746
+ ingestRecordCount: z25.number().optional(),
4353
3747
  // Wall-clock duration of the run-now ingestion execution, in ms.
4354
- ingestDurationMs: z24.number().optional(),
4355
- ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
3748
+ ingestDurationMs: z25.number().optional(),
3749
+ ingestionSource: z25.enum(["local", "fileUpload", "generated"]),
4356
3750
  // Suggested names/values, built from framework detection. The search agent is
4357
3751
  // instructed to rename the prefix if it doesn't match the project's build
4358
3752
  // tool, so the names it actually wrote can differ — treat these as hints, not
4359
3753
  // ground truth (the agent's summary carries the final names).
4360
- searchEnvVars: z24.array(
4361
- z24.object({
4362
- name: z24.string(),
4363
- value: z24.string()
3754
+ searchEnvVars: z25.array(
3755
+ z25.object({
3756
+ name: z25.string(),
3757
+ value: z25.string()
4364
3758
  })
4365
3759
  ).optional()
4366
3760
  });
4367
- var implementationOutputSchema = z24.object({
4368
- summary: z24.string(),
4369
- // Ingestion only: the script the wizard should run, as a bare path — never a
4370
- // command string, and never the interpreter. The command comes from the
4371
- // resolved language toolchain (a registry constant); this path is validated to
4372
- // a worktree-relative file with a runnable extension and substituted into it.
4373
- // So the agent contributes no part of the command that gets executed.
4374
- entrypoint: z24.string().optional()
3761
+ var implementationOutputSchema = z25.object({
3762
+ summary: z25.string(),
3763
+ // Ingestion only: how to run the generated script, as a structured pair the
3764
+ // wizard turns into an argv (`<runtime> <entrypoint>`) never a free-form
3765
+ // command string. `runtime` is constrained to an allowlisted interpreter and
3766
+ // `entrypoint` is validated to a worktree-relative path before execution, so
3767
+ // the agent cannot inject extra commands or swap the interpreter.
3768
+ runtime: z25.enum(INGEST_RUNTIMES).optional(),
3769
+ entrypoint: z25.string().optional()
4375
3770
  });
4376
- var verificationOutputSchema = z24.object({
4377
- summary: z24.string(),
4378
- sufficient: z24.boolean(),
4379
- additionalInstructions: z24.string().optional()
3771
+ var verificationOutputSchema = z25.object({
3772
+ summary: z25.string(),
3773
+ sufficient: z25.boolean(),
3774
+ additionalInstructions: z25.string().optional()
4380
3775
  });
4381
3776
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
4382
3777
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
4383
- function buildSearchEnvVars(language, strategy, appId, searchKey) {
4384
- const prefix = publicEnvPrefix(
4385
- language.frameworks.map((framework) => framework.name),
4386
- strategy
3778
+ var INGEST_DIR = ".algolia-wizard";
3779
+ function detectUiFramework(language) {
3780
+ const names = language.frameworks.map((f) => f.name.toLowerCase());
3781
+ if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3782
+ if (names.some((n) => n.includes("react") || n.includes("next")))
3783
+ return "React";
3784
+ if (names.some((n) => n.includes("angular"))) return "Angular";
3785
+ return "JavaScript";
3786
+ }
3787
+ function frameworksForDoc(framework) {
3788
+ switch (framework) {
3789
+ case "React":
3790
+ return ["react"];
3791
+ case "Vue":
3792
+ return ["vue"];
3793
+ case "Angular":
3794
+ return ["angular"];
3795
+ case "JavaScript":
3796
+ return [];
3797
+ }
3798
+ }
3799
+ function publicEnvPrefix(language) {
3800
+ const frameworkNames = language.frameworks.map(
3801
+ (framework) => framework.name.toLowerCase()
4387
3802
  );
3803
+ if (frameworkNames.some((name) => name.includes("next"))) {
3804
+ return "NEXT_PUBLIC_";
3805
+ }
3806
+ if (frameworkNames.some((name) => name.includes("nuxt"))) {
3807
+ return "NUXT_PUBLIC_";
3808
+ }
3809
+ if (frameworkNames.some((name) => name.includes("astro"))) {
3810
+ return "PUBLIC_";
3811
+ }
3812
+ if (frameworkNames.some((name) => name.includes("vite"))) {
3813
+ return "VITE_";
3814
+ }
3815
+ return "PUBLIC_";
3816
+ }
3817
+ function searchEnvVars(language, appId, searchKey) {
3818
+ const prefix = publicEnvPrefix(language);
4388
3819
  return [
4389
3820
  {
4390
3821
  name: `${prefix}ALGOLIA_APP_ID`,
@@ -4396,38 +3827,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
4396
3827
  }
4397
3828
  ];
4398
3829
  }
4399
- async function resolveIngestionProfile(ctx, language, repoRoot) {
4400
- const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
4401
- repoRoot,
4402
- language.languages.map((l) => l.name)
4403
- );
4404
- if (candidates.length === 0) {
4405
- const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
4406
- logger.warn(
4407
- {
4408
- confirmed: language.languages.map((l) => l.name),
4409
- onDisk: onDisk.map((p) => p.id),
4410
- chosen: chosen.id
4411
- },
4412
- "implement: no confirmed language matched a manifest on disk; falling back"
4413
- );
4414
- return chosen;
4415
- }
4416
- if (candidates.length === 1) return candidates[0];
4417
- const backends = candidates.filter(isBackendLanguage);
4418
- if (backends.length === 1) return backends[0];
4419
- if (backends.length === 0) return candidates[0];
4420
- if (isBackendLanguage(candidates[0])) return candidates[0];
4421
- const options = backends.map((p) => p.displayName);
4422
- const selection = await ctx.requestUserInput({
4423
- prompt: "Which language should the ingestion script use?",
4424
- promptType: "multipleChoice",
4425
- options,
4426
- defaultSelectedIndex: 0
4427
- });
4428
- const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
4429
- return picked ?? backends[0];
4430
- }
4431
3830
  function baseInstructions(input) {
4432
3831
  return [
4433
3832
  `Target Algolia index: ${input.targetIndex}`,
@@ -4455,71 +3854,58 @@ function sourceSpecificInstructions(input) {
4455
3854
  generated: [
4456
3855
  "No real data source exists; use sample records for each confirmed entity.",
4457
3856
  "Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
4458
- "In the script, read and parse each returned JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
3857
+ "In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
4459
3858
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4460
3859
  ]
4461
3860
  };
4462
3861
  return byLine[input.ingestionSource];
4463
3862
  }
4464
3863
  function ingestionInstructions(input) {
4465
- const { ingestionProfile: profile2, toolchain } = input;
4466
- const { ingest } = toolchain;
4467
- const extensions = ingest.entrypointExtensions.join(", ");
4468
- const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile2.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs.`;
4469
3864
  return [
4470
3865
  ...input.confirmed && input.confirmed.length ? [
4471
- `Write the ingestion script in ${profile2.displayName}, at "${ingestScriptDir(profile2)}/" in the repo.`,
3866
+ `Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
4472
3867
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4473
- `Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. ${profile2.envReadInstruction} The wizard sets these when it runs the script.`,
4474
- `Use the official Algolia ${profile2.displayName} client (${profile2.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
3868
+ `Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
3869
+ "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4475
3870
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
4476
- getNamedDoc("save-records", profile2.sdk.docKey),
4477
- dependencyInstruction(toolchain),
3871
+ getNamedDoc("save-records", "js"),
3872
+ 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4478
3873
  "The summary should be extremely concise.",
4479
- runInstruction,
3874
+ `Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
4480
3875
  ...sourceSpecificInstructions(input)
4481
3876
  ] : []
4482
3877
  ];
4483
3878
  }
4484
3879
  function searchInstructions(input) {
4485
- const doc = getNamedDoc(
4486
- "instantsearch-setup",
4487
- searchDocKey(input.searchStrategy)
4488
- );
4489
- const isTemplate = input.searchStrategy === "cdn-template";
4490
- const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `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.`;
3880
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4491
3881
  return [
4492
3882
  "Implement an in-app Algolia search experience.",
4493
- `Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
4494
- "Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
3883
+ `Build the search UI for ${input.uiFramework}.`,
3884
+ "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4495
3885
  doc,
4496
- placement,
4497
- `It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
4498
- isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4499
- isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "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.",
4500
- // appId always resolves (loadActiveProfile throws otherwise); only the
3886
+ `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.`,
3887
+ "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.",
3888
+ // appId always resolves (requireApplication throws otherwise); only the
4501
3889
  // search-only key is best-effort and can fall back to a placeholder.
4502
3890
  `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
4503
3891
  // Names are fixed, not the agent's to rename: the wizard writes the
4504
3892
  // resolved app id / search-only key into ".env" under these exact names
4505
3893
  // right after this step, so a renamed prefix here would leave the code
4506
3894
  // reading a var the wizard never wrote.
4507
- `Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3895
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3896
+ 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4508
3897
  "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
4509
3898
  ];
4510
3899
  }
4511
3900
  function verificationInstructions(input) {
4512
- const protectedDirs = [
4513
- .../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
4514
- ];
4515
3901
  return [
4516
3902
  "Verify the Algolia implementation changes in the current worktree.",
4517
3903
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4518
- `Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
3904
+ "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4519
3905
  "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4520
3906
  "Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4521
3907
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4522
- `Do not modify ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
3908
+ `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4523
3909
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4524
3910
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4525
3911
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4528,17 +3914,14 @@ function verificationInstructions(input) {
4528
3914
  var IMPLEMENT_CONFIG = {
4529
3915
  ingestion: {
4530
3916
  title: "Algolia ingestion",
4531
- label: "Ingestion",
4532
3917
  buildInstructions: ingestionInstructions
4533
3918
  },
4534
3919
  search: {
4535
3920
  title: "Algolia search",
4536
- label: "Search",
4537
3921
  buildInstructions: searchInstructions
4538
3922
  },
4539
3923
  verification: {
4540
3924
  title: "Algolia verification",
4541
- label: "Verification",
4542
3925
  buildInstructions: verificationInstructions
4543
3926
  }
4544
3927
  };
@@ -4570,10 +3953,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
4570
3953
  ];
4571
3954
  }
4572
3955
  function formatSummary(useCase, summary) {
4573
- return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
3956
+ const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
3957
+ return `${label}: ${summary}`;
4574
3958
  }
4575
- function buildIngestCommand(worktree, toolchain, entrypoint) {
4576
- return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
3959
+ function buildIngestCommand(worktree, runtime, entrypoint) {
3960
+ return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4577
3961
  }
4578
3962
  function parseIngestRecordCount(output) {
4579
3963
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
@@ -4650,17 +4034,18 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4650
4034
  }
4651
4035
  }
4652
4036
  const targetIndex = selected?.selection;
4037
+ useWizard.getState().setTargetIndex(targetIndex ?? null);
4653
4038
  await assertGitRepoWithHead(repoRoot);
4654
4039
  if (await isWorkingTreeDirty(repoRoot)) {
4655
4040
  await confirmDirtyWorkingTree(ctx, repoRoot);
4656
4041
  }
4657
4042
  const normalized = normalizeFindingPaths(findings);
4658
- const confirmed3 = normalized.confirmedEntities;
4043
+ const confirmed2 = normalized.confirmedEntities;
4659
4044
  const searchLocation = normalized.searchImplementationAnalysis;
4660
4045
  let appId;
4661
4046
  let searchKey;
4662
4047
  if (useCases.includes("search")) {
4663
- appId = (await loadActiveProfile()).appId;
4048
+ appId = (await requireApplication()).id;
4664
4049
  try {
4665
4050
  searchKey = await resolveSearchOnlyKey(targetIndex);
4666
4051
  } catch (err) {
@@ -4693,66 +4078,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4693
4078
  );
4694
4079
  }
4695
4080
  }
4696
- const ingestionProfile = await resolveIngestionProfile(
4697
- ctx,
4698
- language,
4699
- worktree
4700
- );
4701
- const toolchain = await resolveToolchain(worktree, ingestionProfile);
4702
- const verificationLanguages = [
4703
- .../* @__PURE__ */ new Set([
4704
- ingestionProfile.id,
4705
- ...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
4706
- ])
4707
- ];
4708
- const frameworkName = language.frameworks[0]?.name;
4709
- const searchStrategy = resolveSearchStrategy(
4710
- frameworkName,
4711
- verificationLanguages.includes(JAVASCRIPT)
4712
- );
4713
- logger.info(
4714
- {
4715
- language: ingestionProfile.id,
4716
- packageManager: toolchain.packageManager.id,
4717
- ingest: toolchain.ingest.kind,
4718
- framework: frameworkName,
4719
- searchStrategy
4720
- },
4721
- "implement: resolved ingestion toolchain and search strategy"
4722
- );
4723
4081
  const input = {
4724
4082
  findings: normalized,
4725
- confirmed: confirmed3,
4083
+ confirmed: confirmed2,
4726
4084
  searchLocation,
4727
4085
  targetIndex,
4728
4086
  language,
4729
4087
  appId,
4730
4088
  searchKey,
4731
- searchEnvVars: buildSearchEnvVars(
4732
- language,
4733
- searchStrategy,
4734
- appId,
4735
- searchKey
4736
- ),
4089
+ searchEnvVars: searchEnvVars(language, appId, searchKey),
4737
4090
  ingestDir: INGEST_DIR,
4738
4091
  ingestionSource,
4739
4092
  uploadFilePath,
4740
- searchStrategy,
4741
- frameworkName,
4742
- ingestionProfile,
4743
- toolchain,
4744
- verificationLanguages
4093
+ // language.frameworks already prefers the confirm-framework step output,
4094
+ // so the user's confirmed stack (not just raw detection) picks the flavor.
4095
+ uiFramework: detectUiFramework(language)
4745
4096
  };
4746
- const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
4747
- const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
4748
4097
  const summaries = [];
4749
4098
  if (uploadWarning) summaries.push(uploadWarning);
4750
4099
  let agentRuns = 0;
4100
+ let ingestRuntime;
4751
4101
  let ingestEntrypoint;
4752
4102
  let ingestScriptRan = false;
4753
4103
  let ingestRecordCount;
4754
4104
  let ingestDurationMs;
4755
- const failedInstalls = /* @__PURE__ */ new Set();
4105
+ let installFailed = false;
4756
4106
  let ingestOutcomeMessage;
4757
4107
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4758
4108
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4766,19 +4116,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4766
4116
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4767
4117
  outputSchema: implementationOutputSchema
4768
4118
  });
4769
- const useCaseToolchain = toolchainForUseCase(currentUseCase);
4770
- if (!useCaseToolchain) return result;
4771
4119
  ctx.notify({
4772
4120
  messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4773
4121
  });
4774
4122
  const installLogId = ctx.logStart("installWorktreeDeps", {
4775
- useCase: currentUseCase,
4776
- language: useCaseToolchain.profile.id
4123
+ useCase: currentUseCase
4777
4124
  });
4778
- const install = await installWorktreeDeps(worktree, useCaseToolchain);
4125
+ const install = await installWorktreeDeps(worktree);
4779
4126
  ctx.logEnd(installLogId, install.ok ? "success" : "error");
4780
4127
  if (!install.ok) {
4781
- failedInstalls.add(useCaseToolchain.profile.displayName);
4128
+ installFailed = true;
4782
4129
  logger.warn(
4783
4130
  { useCase: currentUseCase, output: install.output },
4784
4131
  "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
@@ -4792,16 +4139,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4792
4139
  return runAgent({
4793
4140
  instructions: buildAgentInstructions("verification", input),
4794
4141
  tools: toolsForUseCase("verification"),
4795
- outputSchema: verificationOutputSchema,
4796
- // So verifyImplementation runs this repo's checks, not just npm scripts.
4797
- languages: input.verificationLanguages
4142
+ outputSchema: verificationOutputSchema
4798
4143
  });
4799
4144
  }
4800
4145
  if (useCases.includes("ingestion")) {
4801
- const { summary, entrypoint } = await runImplementationUseCase("ingestion");
4146
+ const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4802
4147
  summaries.push(formatSummary("ingestion", summary));
4148
+ ingestRuntime = runtime;
4803
4149
  ingestEntrypoint = entrypoint;
4804
- if (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
4150
+ if (ingestRuntime && ingestEntrypoint && !installFailed) {
4805
4151
  ctx.clearNotices();
4806
4152
  const runNow = await ctx.requestUserInput({
4807
4153
  prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
@@ -4810,20 +4156,21 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4810
4156
  messages: []
4811
4157
  }) === true;
4812
4158
  if (runNow) {
4813
- const profile2 = await loadActiveProfile();
4159
+ const ingestApp = await requireApplication();
4160
+ const writeKey = await resolveWriteKey(targetIndex);
4814
4161
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4815
4162
  const scriptLogId = ctx.logStart("runIngestScript", {
4816
- language: ingestionProfile.id,
4163
+ runtime: ingestRuntime,
4817
4164
  entrypoint: ingestEntrypoint
4818
4165
  });
4819
4166
  const startedAt = Date.now();
4820
4167
  const run = await runIngestScript(
4821
4168
  worktree,
4822
- toolchain,
4169
+ ingestRuntime,
4823
4170
  ingestEntrypoint,
4824
4171
  {
4825
- [APP_ID_VAR]: profile2.appId,
4826
- [API_KEY_VAR]: profile2.apiKey
4172
+ [APP_ID_VAR]: ingestApp.id,
4173
+ [API_KEY_VAR]: writeKey
4827
4174
  }
4828
4175
  );
4829
4176
  ctx.logEnd(scriptLogId, run.ok ? "success" : "error");
@@ -4833,7 +4180,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4833
4180
  ingestRecordCount = parseIngestRecordCount(run.output);
4834
4181
  if (ingestRecordCount != null) {
4835
4182
  track("AI Wizard Ingest Successful", {
4836
- entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
4183
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4837
4184
  record_count: ingestRecordCount,
4838
4185
  duration_ms: ingestDurationMs
4839
4186
  });
@@ -4846,7 +4193,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4846
4193
  outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
4847
4194
  logger.warn(
4848
4195
  {
4849
- language: ingestionProfile.id,
4196
+ runtime: ingestRuntime,
4850
4197
  entrypoint: ingestEntrypoint,
4851
4198
  reason: run.reason
4852
4199
  },
@@ -4869,7 +4216,7 @@ ${run.output}` : status;
4869
4216
  outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
4870
4217
  logger.warn(
4871
4218
  {
4872
- language: ingestionProfile.id,
4219
+ runtime: ingestRuntime,
4873
4220
  entrypoint: ingestEntrypoint,
4874
4221
  output: run.output
4875
4222
  },
@@ -4886,15 +4233,10 @@ ${run.output}` : status;
4886
4233
  }
4887
4234
  }
4888
4235
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4889
- if (ingestEntrypoint) {
4236
+ if (ingestRuntime && ingestEntrypoint) {
4890
4237
  commandMessages.push(
4891
- `Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
4238
+ `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4892
4239
  );
4893
- if (toolchain.ingest.kind === "manual") {
4894
- commandMessages.push(
4895
- `The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
4896
- );
4897
- }
4898
4240
  }
4899
4241
  await ctx.requestUserInput({
4900
4242
  // No question being asked here, just an acknowledgement — the
@@ -4905,20 +4247,7 @@ ${run.output}` : status;
4905
4247
  messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4906
4248
  });
4907
4249
  }
4908
- const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
4909
- if (skipSearch) {
4910
- const target = describeSearchTarget(
4911
- input.searchStrategy,
4912
- input.frameworkName
4913
- );
4914
- summaries.push(
4915
- `Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
4916
- );
4917
- track("AI Wizard Search UI Skipped", {
4918
- framework: input.frameworkName ?? "unknown"
4919
- });
4920
- }
4921
- if (useCases.includes("search") && !skipSearch) {
4250
+ if (useCases.includes("search")) {
4922
4251
  let extraInstructions = [];
4923
4252
  const preSearchFiles = new Set(await listChangedFiles(worktree));
4924
4253
  for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
@@ -4989,9 +4318,9 @@ ${run.output}` : status;
4989
4318
  "implement: agent reported success but no files changed in the worktree"
4990
4319
  );
4991
4320
  }
4992
- if (failedInstalls.size > 0) {
4321
+ if (installFailed) {
4993
4322
  summaries.push(
4994
- `\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
4323
+ '\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
4995
4324
  );
4996
4325
  }
4997
4326
  return {
@@ -4999,10 +4328,10 @@ ${run.output}` : status;
4999
4328
  filesChanged,
5000
4329
  summary: summaries.join("\n\n"),
5001
4330
  worktreePath: worktree,
5002
- ...useCases.includes("ingestion") && ingestEntrypoint ? {
4331
+ ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
5003
4332
  ingestCommand: buildIngestCommand(
5004
4333
  worktree,
5005
- toolchain,
4334
+ ingestRuntime,
5006
4335
  ingestEntrypoint
5007
4336
  ),
5008
4337
  ingestScriptRan,
@@ -5052,8 +4381,8 @@ var defaultWorkflow = {
5052
4381
  defineStep({
5053
4382
  id: "select-index",
5054
4383
  title: "Set up index",
5055
- outputSchema: z25.object({
5056
- selection: z25.string()
4384
+ outputSchema: z26.object({
4385
+ selection: z26.string()
5057
4386
  }),
5058
4387
  run: (ctx) => selectIndexStep(ctx)
5059
4388
  }),
@@ -5134,25 +4463,54 @@ var store = useWizard.getState();
5134
4463
  var instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
5135
4464
  var user = await getUser();
5136
4465
  if (!user) {
5137
- await instance.waitUntilRenderFlush();
5138
- instance.cleanup();
4466
+ store.beginAuth();
5139
4467
  try {
5140
4468
  await runAuthLogin();
5141
4469
  } catch (err) {
5142
- console.error(err instanceof Error ? err.message : String(err));
5143
- process.exit(1);
4470
+ if (!needsInteractiveTerminal(err)) {
4471
+ store.setError(err instanceof Error ? err.message : String(err));
4472
+ await instance.waitUntilExit();
4473
+ process.exit(1);
4474
+ }
4475
+ try {
4476
+ await promptForApplication();
4477
+ } catch (pickErr) {
4478
+ logger.warn(
4479
+ { err: pickErr.message },
4480
+ "in-wizard application selection failed; handing over the terminal"
4481
+ );
4482
+ await instance.waitUntilRenderFlush();
4483
+ instance.cleanup();
4484
+ try {
4485
+ await runAuthLoginInTerminal();
4486
+ } catch (fallbackErr) {
4487
+ console.error(
4488
+ fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
4489
+ );
4490
+ process.exit(1);
4491
+ }
4492
+ instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4493
+ store.clearCliOutput();
4494
+ }
5144
4495
  }
5145
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4496
+ store.endAuth();
5146
4497
  user = await getUser();
5147
4498
  if (!user) {
5148
4499
  store.setError(
5149
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4500
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
5150
4501
  );
5151
4502
  await instance.waitUntilExit();
5152
4503
  process.exit(1);
5153
4504
  }
5154
4505
  }
5155
4506
  store.setUser(user);
5156
- var profile = await loadActiveProfile();
5157
4507
  await store.waitForStart();
5158
- runWorkflow(workflow, profile?.appId);
4508
+ var app;
4509
+ try {
4510
+ app = await ensureApplication();
4511
+ } catch (err) {
4512
+ store.setError(err instanceof Error ? err.message : String(err));
4513
+ await instance.waitUntilExit();
4514
+ process.exit(1);
4515
+ }
4516
+ runWorkflow(workflow, app.id);