@algolia/wizard 0.9.0-rc.96.109 → 0.10.0

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 Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
7
+ import { Box as Box15, Text as Text15, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -17,9 +17,6 @@ function npxArgs(args) {
17
17
  return ["--yes", "@algolia/cli@latest", ...args];
18
18
  }
19
19
  var shell = process.platform === "win32";
20
- function mask(text, secret) {
21
- return secret ? text.replaceAll(secret, "***") : text;
22
- }
23
20
  function lineSplitter(emit) {
24
21
  let buffer = "";
25
22
  return {
@@ -43,10 +40,9 @@ var stderrSink = (stream, line) => {
43
40
  if (stream === "stdout") return;
44
41
  wizardSink(stream, line);
45
42
  };
46
- function runAlgoliaCli(args, { onOutput, redact } = {}) {
43
+ function runAlgoliaCli(args, { onOutput } = {}) {
47
44
  const store = useWizard.getState();
48
- const command = mask(args.join(" "), redact);
49
- const logId = store.logStart("tool", `algolia ${command}`);
45
+ const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
50
46
  return new Promise((resolve4, reject) => {
51
47
  const child = spawn("npx", npxArgs(args), { shell });
52
48
  let stdout = "";
@@ -75,13 +71,13 @@ function runAlgoliaCli(args, { onOutput, redact } = {}) {
75
71
  const failed = stderr.trim();
76
72
  let detail = "";
77
73
  if (failed) {
78
- detail = `: ${mask(failed, redact)}`;
74
+ detail = `: ${failed}`;
79
75
  } else if (stdout.trim()) {
80
76
  detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
81
77
  }
82
78
  reject(
83
79
  new Error(
84
- `Algolia CLI \`${command}\` failed (exit ${code})${detail}`
80
+ `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
85
81
  )
86
82
  );
87
83
  }
@@ -162,14 +158,10 @@ import { join, resolve } from "node:path";
162
158
  function rootDir() {
163
159
  return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
164
160
  }
165
- var pinnedRoot;
166
- function setProjectRoot(cwd) {
167
- pinnedRoot = resolve(cwd);
168
- }
169
- function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
161
+ function projectSlug(cwd = process.cwd()) {
170
162
  return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
171
163
  }
172
- function stateDir(cwd = pinnedRoot ?? process.cwd()) {
164
+ function stateDir(cwd = process.cwd()) {
173
165
  return join(rootDir(), projectSlug(cwd));
174
166
  }
175
167
 
@@ -622,13 +614,10 @@ function Notices() {
622
614
  }
623
615
 
624
616
  // src/ui/PromptInput.tsx
625
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
617
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
626
618
  import TextInput from "ink-text-input";
627
619
  import { useState as useState5 } from "react";
628
620
 
629
- // src/ui/CommandApproval.tsx
630
- import { Box as Box5, Text as Text5, useInput } from "ink";
631
-
632
621
  // src/ui/NextAction.tsx
633
622
  import { Box as Box4, Text as Text4 } from "ink";
634
623
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -652,60 +641,14 @@ function NextAction({
652
641
  ] });
653
642
  }
654
643
 
655
- // src/ui/CommandApproval.tsx
656
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
657
- function CommandApproval({
658
- command,
659
- onDecide
660
- }) {
661
- useInput((_input, key) => {
662
- if (key.return) onDecide("approve");
663
- else if (key.escape) onDecide("reject");
664
- });
665
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
666
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
667
- /* @__PURE__ */ jsxs4(
668
- Box5,
669
- {
670
- flexDirection: "column",
671
- paddingLeft: 2,
672
- borderStyle: "single",
673
- borderColor: COLORS.success,
674
- borderTop: false,
675
- borderBottom: false,
676
- borderRight: false,
677
- gap: 1,
678
- children: [
679
- /* @__PURE__ */ jsxs4(Box5, { children: [
680
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
681
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
682
- ] }),
683
- /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
684
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
685
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
686
- ] }),
687
- command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
688
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
689
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
690
- ] })
691
- ]
692
- }
693
- ),
694
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
695
- /* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
696
- /* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" })
697
- ] })
698
- ] });
699
- }
700
-
701
644
  // src/ui/SelectPrompt.tsx
702
- import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
645
+ import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
703
646
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
704
647
 
705
648
  // src/ui/ScrollView.tsx
706
- import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
649
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
707
650
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
708
- import { jsxs as jsxs5 } from "react/jsx-runtime";
651
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
709
652
  var INDICATOR_ROWS = 2;
710
653
  function fittedWidth(node, columns) {
711
654
  let left = 0;
@@ -775,14 +718,14 @@ function useScrollWindow({
775
718
  };
776
719
  }
777
720
  function ScrollView({ scroll, children }) {
778
- return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
779
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
721
+ return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
722
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
780
723
  "\u2191 ",
781
724
  scroll.hiddenAbove,
782
725
  " more"
783
726
  ] }),
784
727
  children,
785
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
728
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
786
729
  "\u2193 ",
787
730
  scroll.hiddenBelow,
788
731
  " more"
@@ -791,7 +734,7 @@ function ScrollView({ scroll, children }) {
791
734
  }
792
735
 
793
736
  // src/ui/SelectPrompt.tsx
794
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
737
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
795
738
  var CANCEL = "cancel";
796
739
  var ARROW_WIDTH = 4;
797
740
  var COLUMN_GAP = 2;
@@ -853,7 +796,7 @@ function SelectPrompt({
853
796
  revealIndex(index);
854
797
  }, [index, revealIndex]);
855
798
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
856
- useInput2((input, key) => {
799
+ useInput((input, key) => {
857
800
  if (rows.length === 0) return;
858
801
  if (key.upArrow || input === "k") {
859
802
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -876,56 +819,56 @@ function SelectPrompt({
876
819
  }
877
820
  }
878
821
  });
879
- return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
880
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
881
- error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
882
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
883
- table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
884
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
885
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
886
- helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
822
+ return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
823
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
824
+ error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
825
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
826
+ table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
827
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
828
+ question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
829
+ helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
887
830
  ] })
888
831
  ] }),
889
- /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
832
+ /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
890
833
  const i = scroll.offset + visibleIndex;
891
834
  const highlighted = i === index;
892
835
  const isCancel = i === cancelIndex;
893
836
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
894
837
  const sec = isCancel ? void 0 : secondary?.[i];
895
838
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
896
- const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
839
+ const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
897
840
  highlighted ? "\u276F " : " ",
898
841
  bullet,
899
842
  option
900
843
  ] });
901
844
  const isText = sec?.kind === "text";
902
- return /* @__PURE__ */ jsxs6(
903
- Box7,
845
+ return /* @__PURE__ */ jsxs5(
846
+ Box6,
904
847
  {
905
848
  width: isText ? "100%" : barWidth,
906
849
  paddingX: 1,
907
850
  paddingY: 1,
908
851
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
909
852
  children: [
910
- /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
911
- isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
912
- Text7,
853
+ /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
854
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
855
+ Text6,
913
856
  {
914
857
  wrap: "truncate",
915
858
  color: highlighted ? COLORS.primary : COLORS.muted,
916
859
  children: sec.value
917
860
  }
918
861
  ) }),
919
- sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
862
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
920
863
  ]
921
864
  },
922
865
  `row-${i}`
923
866
  );
924
867
  }) }),
925
- /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
868
+ /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
926
869
  i > 0 ? " " : "",
927
- /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
928
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
870
+ /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
871
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
929
872
  " ",
930
873
  label
931
874
  ] })
@@ -934,23 +877,23 @@ function SelectPrompt({
934
877
  }
935
878
 
936
879
  // src/ui/PromptInput.tsx
937
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
880
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
938
881
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
939
882
  function EnterToContinuePrompt({
940
883
  question,
941
884
  messages,
942
885
  onDecide
943
886
  }) {
944
- useInput3((_input, key) => {
887
+ useInput2((_input, key) => {
945
888
  if (key.return) onDecide(true);
946
889
  else if (key.escape) onDecide(false);
947
890
  });
948
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
949
- messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
950
- question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
951
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
952
- /* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
953
- /* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
891
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
892
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
893
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
894
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
895
+ /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
896
+ /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
954
897
  ] })
955
898
  ] });
956
899
  }
@@ -958,11 +901,11 @@ function PromptInput() {
958
901
  const { phase, inputReq, submitInput } = useWizard();
959
902
  const [draft, setDraft] = useState5("");
960
903
  if (phase === "done" || phase === "error") {
961
- return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
904
+ return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
962
905
  }
963
906
  if (phase !== "awaitingInput" || !inputReq) return null;
964
907
  if (inputReq.promptType === "multipleChoice") {
965
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
908
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
966
909
  SelectPrompt,
967
910
  {
968
911
  question: inputReq.prompt,
@@ -979,7 +922,7 @@ function PromptInput() {
979
922
  ) });
980
923
  }
981
924
  if (inputReq.promptType === "multiSelect") {
982
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
925
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
983
926
  SelectPrompt,
984
927
  {
985
928
  multi: true,
@@ -994,7 +937,7 @@ function PromptInput() {
994
937
  ) });
995
938
  }
996
939
  if (inputReq.promptType === "notice") {
997
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
940
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
998
941
  SelectPrompt,
999
942
  {
1000
943
  question: inputReq.prompt,
@@ -1005,7 +948,7 @@ function PromptInput() {
1005
948
  ) });
1006
949
  }
1007
950
  if (inputReq.promptType === "enterToContinue") {
1008
- return /* @__PURE__ */ jsx6(
951
+ return /* @__PURE__ */ jsx5(
1009
952
  EnterToContinuePrompt,
1010
953
  {
1011
954
  question: inputReq.prompt,
@@ -1014,12 +957,9 @@ function PromptInput() {
1014
957
  }
1015
958
  );
1016
959
  }
1017
- if (inputReq.promptType === "commandApproval" && inputReq.command) {
1018
- return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1019
- }
1020
960
  if (inputReq.promptType === "acceptReject") {
1021
961
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
1022
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
962
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1023
963
  SelectPrompt,
1024
964
  {
1025
965
  question: inputReq.prompt,
@@ -1030,15 +970,15 @@ function PromptInput() {
1030
970
  }
1031
971
  ) });
1032
972
  }
1033
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1034
- inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
1035
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
1036
- /* @__PURE__ */ jsxs7(Box8, { children: [
1037
- /* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
973
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
974
+ inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
975
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
976
+ /* @__PURE__ */ jsxs6(Box7, { children: [
977
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
1038
978
  inputReq.prompt,
1039
979
  " "
1040
980
  ] }),
1041
- /* @__PURE__ */ jsx6(
981
+ /* @__PURE__ */ jsx5(
1042
982
  TextInput,
1043
983
  {
1044
984
  value: draft,
@@ -1056,7 +996,7 @@ function PromptInput() {
1056
996
  // src/ui/Welcome.tsx
1057
997
  import { dirname as dirname2, join as join3 } from "node:path";
1058
998
  import { fileURLToPath } from "node:url";
1059
- import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
999
+ import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1060
1000
 
1061
1001
  // src/ui/copy/welcome.ts
1062
1002
  var sidebarItems = [
@@ -1069,12 +1009,12 @@ var sidebarItems = [
1069
1009
  description: "push 100 records to Algolia in seconds"
1070
1010
  },
1071
1011
  {
1072
- title: "detect your stack",
1073
- description: "whatever language and framework you already use"
1012
+ title: "detect your framework",
1013
+ description: "React, Vue, Angular, Vanilla JS"
1074
1014
  },
1075
1015
  {
1076
1016
  title: "scaffold a search UI",
1077
- description: "a search box and results, wired into your app"
1017
+ description: "a styled InstantSearch component, wired into your app"
1078
1018
  },
1079
1019
  {
1080
1020
  title: "ship it",
@@ -1084,20 +1024,20 @@ var sidebarItems = [
1084
1024
 
1085
1025
  // src/ui/Welcome.tsx
1086
1026
  import Image, { InkPictureProvider } from "ink-picture";
1087
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1027
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1088
1028
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1089
1029
  function SidebarItem({
1090
1030
  title,
1091
1031
  description
1092
1032
  }) {
1093
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1094
- /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
1095
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
1096
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
1033
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1034
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1035
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1036
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
1097
1037
  ] }),
1098
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1099
- /* @__PURE__ */ jsx7(Spacer, {}),
1100
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1038
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1039
+ /* @__PURE__ */ jsx6(Spacer, {}),
1040
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1101
1041
  ] })
1102
1042
  ] });
1103
1043
  }
@@ -1105,7 +1045,7 @@ function Welcome() {
1105
1045
  const confirmStart = useWizard((s) => s.confirmStart);
1106
1046
  const openLearnMore = useWizard((s) => s.openLearnMore);
1107
1047
  const { rows } = useWindowSize6();
1108
- useInput4((input, key) => {
1048
+ useInput3((input, key) => {
1109
1049
  if (key.return) confirmStart();
1110
1050
  else if (input === "i") openLearnMore();
1111
1051
  });
@@ -1123,16 +1063,16 @@ function Welcome() {
1123
1063
  if (rows < 30) {
1124
1064
  layout = scales["small"];
1125
1065
  }
1126
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1127
- /* @__PURE__ */ jsx7(
1128
- Box9,
1066
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1067
+ /* @__PURE__ */ jsx6(
1068
+ Box8,
1129
1069
  {
1130
1070
  paddingY: layout.main.padding.y,
1131
1071
  paddingX: layout.main.padding.x,
1132
1072
  flexDirection: "column",
1133
1073
  justifyContent: "center",
1134
- children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1135
- /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1074
+ children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1075
+ /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1136
1076
  Image,
1137
1077
  {
1138
1078
  src: IMAGE_PATH,
@@ -1143,16 +1083,16 @@ function Welcome() {
1143
1083
  protocol: "halfBlock"
1144
1084
  }
1145
1085
  ) }),
1146
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1147
- /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
1148
- /* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
1149
- /* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1086
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1087
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1088
+ /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1089
+ /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1150
1090
  ] })
1151
1091
  ] })
1152
1092
  }
1153
1093
  ),
1154
- /* @__PURE__ */ jsxs8(
1155
- Box9,
1094
+ /* @__PURE__ */ jsxs7(
1095
+ Box8,
1156
1096
  {
1157
1097
  backgroundColor: COLORS.bg.sidebar,
1158
1098
  width: 40,
@@ -1162,8 +1102,8 @@ function Welcome() {
1162
1102
  flexDirection: "column",
1163
1103
  justifyContent: "center",
1164
1104
  children: [
1165
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1166
- sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
1105
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1106
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
1167
1107
  ]
1168
1108
  }
1169
1109
  )
@@ -1172,7 +1112,7 @@ function Welcome() {
1172
1112
 
1173
1113
  // src/ui/LearnMore.tsx
1174
1114
  import { Fragment as Fragment2 } from "react";
1175
- import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1115
+ import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1176
1116
 
1177
1117
  // src/ui/copy/learn-more.ts
1178
1118
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1180,17 +1120,12 @@ var accessItems = [
1180
1120
  {
1181
1121
  tag: "READ",
1182
1122
  title: "Project files",
1183
- description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1123
+ description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1184
1124
  },
1185
1125
  {
1186
1126
  tag: "WRITE",
1187
1127
  title: "Code changes",
1188
- description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1189
- },
1190
- {
1191
- tag: "EXEC",
1192
- title: "Setup commands",
1193
- description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
1128
+ description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1194
1129
  },
1195
1130
  {
1196
1131
  tag: "NET",
@@ -1200,13 +1135,13 @@ var accessItems = [
1200
1135
  {
1201
1136
  tag: "KEY",
1202
1137
  title: "Credentials",
1203
- description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1138
+ description: "saves your Admin API key to .env and adds it to .gitignore."
1204
1139
  }
1205
1140
  ];
1206
1141
  var neverItems = [
1207
1142
  "Send your source code to a model or third party",
1208
1143
  "Commit or push to git",
1209
- "Run a command you haven't approved"
1144
+ "Touch files outside your project directory"
1210
1145
  ];
1211
1146
  var policyLinks = [
1212
1147
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1214,11 +1149,10 @@ var policyLinks = [
1214
1149
  ];
1215
1150
 
1216
1151
  // src/ui/LearnMore.tsx
1217
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1152
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1218
1153
  var TAG_COLORS = {
1219
1154
  READ: COLORS.success,
1220
1155
  WRITE: COLORS.badge,
1221
- EXEC: COLORS.danger,
1222
1156
  NET: COLORS.accent,
1223
1157
  KEY: COLORS.muted
1224
1158
  };
@@ -1231,12 +1165,12 @@ function NeverLine({
1231
1165
  }) {
1232
1166
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1233
1167
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1234
- return /* @__PURE__ */ jsxs9(Text10, { children: [
1235
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1168
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
1169
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1236
1170
  " ".repeat(NEVER_BOX_PAD_X),
1237
- segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1171
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1238
1172
  " ".repeat(rightPad),
1239
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1173
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1240
1174
  ] });
1241
1175
  }
1242
1176
  function LearnMore() {
@@ -1244,12 +1178,12 @@ function LearnMore() {
1244
1178
  const backToHome = useWizard((s) => s.backToHome);
1245
1179
  const { columns } = useWindowSize7();
1246
1180
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1247
- useInput5((_input, key) => {
1181
+ useInput4((_input, key) => {
1248
1182
  if (key.escape) backToHome();
1249
1183
  else if (key.return) confirmStart();
1250
1184
  });
1251
- return /* @__PURE__ */ jsxs9(
1252
- Box10,
1185
+ return /* @__PURE__ */ jsxs8(
1186
+ Box9,
1253
1187
  {
1254
1188
  flexDirection: "column",
1255
1189
  paddingX: PADDING_X,
@@ -1257,31 +1191,31 @@ function LearnMore() {
1257
1191
  width: "100%",
1258
1192
  gap: 1,
1259
1193
  children: [
1260
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1261
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
1262
- /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
1263
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1264
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1265
- /* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1266
- /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
1267
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1268
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1194
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1195
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1196
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1197
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1198
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1199
+ /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1200
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1201
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1202
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1269
1203
  ] }) })
1270
1204
  ] })
1271
1205
  ] }, item.tag)) }),
1272
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
1273
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1274
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1275
- /* @__PURE__ */ jsx8(
1206
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1207
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1208
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1209
+ /* @__PURE__ */ jsx7(
1276
1210
  NeverLine,
1277
1211
  {
1278
1212
  width: dividerWidth,
1279
1213
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1280
1214
  }
1281
1215
  ),
1282
- neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1283
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1284
- /* @__PURE__ */ jsx8(
1216
+ neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1217
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1218
+ /* @__PURE__ */ jsx7(
1285
1219
  NeverLine,
1286
1220
  {
1287
1221
  width: dividerWidth,
@@ -1293,24 +1227,24 @@ function LearnMore() {
1293
1227
  }
1294
1228
  )
1295
1229
  ] }, item)),
1296
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1297
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1230
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1231
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1298
1232
  ] }),
1299
- /* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1300
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1301
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
1233
+ /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1234
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1235
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1302
1236
  ] }, link.label)) }),
1303
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1304
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1305
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1306
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
1307
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
1237
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1238
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1239
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1240
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1241
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1308
1242
  ] }),
1309
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1310
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1311
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
1312
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
1313
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
1243
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1244
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1245
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1246
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1247
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1314
1248
  ] })
1315
1249
  ] })
1316
1250
  ]
@@ -1319,10 +1253,10 @@ function LearnMore() {
1319
1253
  }
1320
1254
 
1321
1255
  // src/ui/Sidebar.tsx
1322
- import { Box as Box13, Text as Text13 } from "ink";
1256
+ import { Box as Box12, Text as Text12 } from "ink";
1323
1257
 
1324
1258
  // src/ui/Steps.tsx
1325
- import { Box as Box11, Text as Text11 } from "ink";
1259
+ import { Box as Box10, Text as Text10 } from "ink";
1326
1260
  import Spinner from "ink-spinner";
1327
1261
 
1328
1262
  // src/core/persistence.ts
@@ -1351,12 +1285,12 @@ async function clearWorkflowState(workflowId) {
1351
1285
  }
1352
1286
 
1353
1287
  // src/ui/Steps.tsx
1354
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1288
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1355
1289
  function Steps() {
1356
1290
  const { steps } = useWizard();
1357
1291
  const visibleSteps = steps.filter(isStepVisible);
1358
- return /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status[s.status], children: [
1359
- s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
1292
+ return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
1293
+ s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1360
1294
  " ",
1361
1295
  s.title
1362
1296
  ] }) }, s.id)) });
@@ -1365,27 +1299,27 @@ function CurrentStep() {
1365
1299
  const { steps } = useWizard();
1366
1300
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1367
1301
  if (!currentStep) return null;
1368
- return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1369
- /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1302
+ return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1303
+ /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1370
1304
  " ",
1371
1305
  ` ${currentStep.title}`
1372
1306
  ] });
1373
1307
  }
1374
1308
 
1375
1309
  // src/ui/Progress.tsx
1376
- import { Box as Box12, Text as Text12 } from "ink";
1377
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1310
+ import { Box as Box11, Text as Text11 } from "ink";
1311
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1378
1312
  function Progress() {
1379
1313
  const { steps, currentStepIndex } = useWizard();
1380
1314
  const visibleSteps = steps.filter(isStepVisible);
1381
1315
  if (visibleSteps.length === 0) return null;
1382
1316
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1383
1317
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1384
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1385
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1386
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
1387
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
1388
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
1318
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1319
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1320
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1321
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1322
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1389
1323
  ] });
1390
1324
  }
1391
1325
 
@@ -1396,10 +1330,10 @@ var sidebarCommands = [
1396
1330
  ];
1397
1331
 
1398
1332
  // src/ui/Sidebar.tsx
1399
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1333
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1400
1334
  function Sidebar() {
1401
- return /* @__PURE__ */ jsxs12(
1402
- Box13,
1335
+ return /* @__PURE__ */ jsxs11(
1336
+ Box12,
1403
1337
  {
1404
1338
  backgroundColor: "#14171E",
1405
1339
  width: 30,
@@ -1408,16 +1342,16 @@ function Sidebar() {
1408
1342
  flexDirection: "column",
1409
1343
  justifyContent: "space-between",
1410
1344
  children: [
1411
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1412
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1413
- /* @__PURE__ */ jsx11(Steps, {})
1345
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1346
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1347
+ /* @__PURE__ */ jsx10(Steps, {})
1414
1348
  ] }),
1415
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1416
- /* @__PURE__ */ jsx11(Progress, {}),
1417
- /* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
1418
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1419
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1420
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
1349
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1350
+ /* @__PURE__ */ jsx10(Progress, {}),
1351
+ /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1352
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1353
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1354
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1421
1355
  ] });
1422
1356
  }) })
1423
1357
  ] })
@@ -1427,12 +1361,12 @@ function Sidebar() {
1427
1361
  }
1428
1362
 
1429
1363
  // src/ui/Ribbon.tsx
1430
- import { Box as Box14, Text as Text14 } from "ink";
1431
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1364
+ import { Box as Box13, Text as Text13 } from "ink";
1365
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1432
1366
  function Ribbon() {
1433
1367
  const firstCommand = sidebarCommands[0];
1434
- return /* @__PURE__ */ jsxs13(
1435
- Box14,
1368
+ return /* @__PURE__ */ jsxs12(
1369
+ Box13,
1436
1370
  {
1437
1371
  backgroundColor: "#14171E",
1438
1372
  flexDirection: "row",
@@ -1440,11 +1374,11 @@ function Ribbon() {
1440
1374
  paddingX: 2,
1441
1375
  paddingY: 1,
1442
1376
  children: [
1443
- /* @__PURE__ */ jsx12(Progress, {}),
1444
- /* @__PURE__ */ jsx12(CurrentStep, {}),
1445
- /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1446
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1447
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
1377
+ /* @__PURE__ */ jsx11(Progress, {}),
1378
+ /* @__PURE__ */ jsx11(CurrentStep, {}),
1379
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1380
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1381
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1448
1382
  ] })
1449
1383
  ]
1450
1384
  }
@@ -1455,8 +1389,8 @@ function Ribbon() {
1455
1389
  import { useState as useState6 } from "react";
1456
1390
 
1457
1391
  // src/ui/Logs.tsx
1458
- import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1459
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1392
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1393
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1460
1394
  var KIND_COLOR = {
1461
1395
  tool: COLORS.primary,
1462
1396
  prompt: COLORS.badge
@@ -1487,14 +1421,14 @@ function formatTimestamp(ms) {
1487
1421
  function Logs() {
1488
1422
  const logs = useWizard((s) => s.logs);
1489
1423
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1490
- useInput6((_input, key) => {
1424
+ useInput5((_input, key) => {
1491
1425
  if (key.upArrow) scroll.scrollBy(-1);
1492
1426
  else if (key.downArrow) scroll.scrollBy(1);
1493
1427
  });
1494
1428
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1495
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1496
- logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
1497
- /* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
1429
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1430
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1431
+ /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1498
1432
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1499
1433
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1500
1434
  const rawPreview = rawInputText(entry.input);
@@ -1504,14 +1438,14 @@ function Logs() {
1504
1438
  const name = truncate2(entry.name, budget);
1505
1439
  budget -= name.length;
1506
1440
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1507
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
1508
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
1509
- /* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
1510
- preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
1511
- durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
1441
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1442
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1443
+ /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1444
+ preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1445
+ durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1512
1446
  ] }, entry.id);
1513
1447
  }) }),
1514
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1448
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1515
1449
  ] });
1516
1450
  }
1517
1451
 
@@ -1703,7 +1637,7 @@ function track(event, payload) {
1703
1637
  }
1704
1638
 
1705
1639
  // src/ui/App.tsx
1706
- import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1640
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1707
1641
  function App() {
1708
1642
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1709
1643
  const { exit } = useApp();
@@ -1711,7 +1645,7 @@ function App() {
1711
1645
  const [showLogs, setShowLogs] = useState6(false);
1712
1646
  const finished = phase === "done" || phase === "error";
1713
1647
  const currentStep = steps[currentStepIndex];
1714
- useInput7(
1648
+ useInput6(
1715
1649
  (_input, key) => {
1716
1650
  if (key.return) {
1717
1651
  exit();
@@ -1719,7 +1653,7 @@ function App() {
1719
1653
  },
1720
1654
  { isActive: finished }
1721
1655
  );
1722
- useInput7((_input, key) => {
1656
+ useInput6((_input, key) => {
1723
1657
  if (phase === "idle" || phase === "authenticating") return;
1724
1658
  if (key.tab) {
1725
1659
  setShowLogs(!showLogs);
@@ -1730,8 +1664,8 @@ function App() {
1730
1664
  });
1731
1665
  }
1732
1666
  });
1733
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1734
- useInput7((_input, key) => {
1667
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1668
+ useInput6((_input, key) => {
1735
1669
  if (escOwnedElsewhere) return;
1736
1670
  if (key.escape) {
1737
1671
  track("AI Wizard Interaction", {
@@ -1750,8 +1684,8 @@ function App() {
1750
1684
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1751
1685
  the whole screen, and the scrolling throws off its cursor arithmetic —
1752
1686
  flicker and leftover rows. */
1753
- /* @__PURE__ */ jsxs15(
1754
- Box16,
1687
+ /* @__PURE__ */ jsxs14(
1688
+ Box15,
1755
1689
  {
1756
1690
  backgroundColor: COLORS.bg.main,
1757
1691
  flexDirection: "row",
@@ -1759,16 +1693,16 @@ function App() {
1759
1693
  height: scrollsPastViewport ? void 0 : rows,
1760
1694
  overflow: scrollsPastViewport ? "visible" : "hidden",
1761
1695
  children: [
1762
- mainWindowVisible && /* @__PURE__ */ jsxs15(
1763
- Box16,
1696
+ mainWindowVisible && /* @__PURE__ */ jsxs14(
1697
+ Box15,
1764
1698
  {
1765
1699
  flexDirection,
1766
1700
  width: "100%",
1767
1701
  maxHeight: rows,
1768
1702
  justifyContent: "space-between",
1769
1703
  children: [
1770
- showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1771
- Box16,
1704
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1705
+ Box15,
1772
1706
  {
1773
1707
  flexDirection: "column",
1774
1708
  paddingX: 4,
@@ -1776,26 +1710,26 @@ function App() {
1776
1710
  width: showSidebar ? 70 : "100%",
1777
1711
  flexGrow: 1,
1778
1712
  children: [
1779
- phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
1780
- /* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1781
- /* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1713
+ phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1714
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1715
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1782
1716
  ] }),
1783
- /* @__PURE__ */ jsx14(CliOutput, {}),
1784
- /* @__PURE__ */ jsx14(Notices, {}),
1785
- /* @__PURE__ */ jsx14(PromptInput, {}),
1786
- phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
1787
- phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1717
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1718
+ /* @__PURE__ */ jsx13(Notices, {}),
1719
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1720
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1721
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1788
1722
  "\u2716 ",
1789
1723
  error
1790
1724
  ] }) })
1791
1725
  ]
1792
1726
  }
1793
1727
  ),
1794
- showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1728
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1795
1729
  ]
1796
1730
  }
1797
1731
  ),
1798
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1732
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1799
1733
  ]
1800
1734
  }
1801
1735
  )
@@ -1809,17 +1743,18 @@ import "zod";
1809
1743
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1810
1744
  import { join as join5 } from "node:path";
1811
1745
  var configFile = () => join5(stateDir(), "config.json");
1812
- var defaultConfig = () => ({
1746
+ var DEFAULT_CONFIG = {
1813
1747
  version: 1,
1814
1748
  aiConsent: false,
1815
- workflowsRun: []
1816
- });
1749
+ workflowsRun: [],
1750
+ searchApiKeys: {}
1751
+ };
1817
1752
  async function loadConfig() {
1818
1753
  try {
1819
1754
  const raw = await readFile2(configFile(), "utf8");
1820
- return { ...defaultConfig(), ...JSON.parse(raw) };
1755
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1821
1756
  } catch {
1822
- return defaultConfig();
1757
+ return { ...DEFAULT_CONFIG };
1823
1758
  }
1824
1759
  }
1825
1760
  async function saveConfig(config) {
@@ -1831,6 +1766,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
1831
1766
  config.workflowsRun.push({ workflowId, completedAt });
1832
1767
  await saveConfig(config);
1833
1768
  }
1769
+ function isStoredSearchKey(value) {
1770
+ if (typeof value !== "object" || value === null) return false;
1771
+ const { appId, key } = value;
1772
+ return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
1773
+ }
1774
+ function storedSearchKeys(config) {
1775
+ const stored = config.searchApiKeys;
1776
+ if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
1777
+ return {};
1778
+ }
1779
+ return stored;
1780
+ }
1781
+ async function getStoredSearchKey(index, appId) {
1782
+ const entry = storedSearchKeys(await loadConfig())[index];
1783
+ if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
1784
+ return entry.key;
1785
+ }
1786
+ async function storeSearchKey(index, appId, key) {
1787
+ const config = await loadConfig();
1788
+ config.searchApiKeys = {
1789
+ ...storedSearchKeys(config),
1790
+ [index]: { appId, key }
1791
+ };
1792
+ await saveConfig(config);
1793
+ }
1794
+ async function forgetSearchKey(index) {
1795
+ const config = await loadConfig();
1796
+ const remaining = { ...storedSearchKeys(config) };
1797
+ delete remaining[index];
1798
+ config.searchApiKeys = remaining;
1799
+ await saveConfig(config);
1800
+ }
1834
1801
 
1835
1802
  // src/core/orchestrator.ts
1836
1803
  function defineStep(step) {
@@ -1875,7 +1842,7 @@ async function ensureConsent() {
1875
1842
  if (config.aiConsent) return;
1876
1843
  const store = useWizard.getState();
1877
1844
  const answer = await store.requestUserInput({
1878
- prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
1845
+ prompt: "Wizard will make AI-authored changes to this repository.",
1879
1846
  promptType: "enterToContinue",
1880
1847
  options: []
1881
1848
  });
@@ -2157,7 +2124,7 @@ async function ensureApplication() {
2157
2124
  }
2158
2125
 
2159
2126
  // src/workflows/default.ts
2160
- import { z as z28 } from "zod";
2127
+ import { z as z27 } from "zod";
2161
2128
 
2162
2129
  // src/actions/listIndices.ts
2163
2130
  import { z as z5 } from "zod";
@@ -2278,9 +2245,9 @@ function listFilesTool(ctx) {
2278
2245
  if (++ctx.counts.list > ctx.limits.list) {
2279
2246
  return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
2280
2247
  }
2281
- const resolved2 = resolveInRoot(ctx, ".");
2282
- if (!resolved2.ok) return resolved2.error;
2283
- const entries = await readdir(resolved2.target, { withFileTypes: true });
2248
+ const resolved = resolveInRoot(ctx, ".");
2249
+ if (!resolved.ok) return resolved.error;
2250
+ const entries = await readdir(resolved.target, { withFileTypes: true });
2284
2251
  return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
2285
2252
  }
2286
2253
  });
@@ -2298,14 +2265,14 @@ function changeDirectoryTool(ctx) {
2298
2265
  }),
2299
2266
  execute: async ({ path }) => {
2300
2267
  logger.info({ path }, "called changeDirectory tool");
2301
- const resolved2 = resolveInRoot(ctx, path);
2302
- if (!resolved2.ok) return resolved2.error;
2268
+ const resolved = resolveInRoot(ctx, path);
2269
+ if (!resolved.ok) return resolved.error;
2303
2270
  try {
2304
- const info = await stat(resolved2.target);
2271
+ const info = await stat(resolved.target);
2305
2272
  if (!info.isDirectory()) {
2306
2273
  return `Error changing directory to ${path}: not a directory`;
2307
2274
  }
2308
- ctx.cwd = resolved2.target;
2275
+ ctx.cwd = resolved.target;
2309
2276
  return `Changed working directory to ${ctx.cwd}`;
2310
2277
  } catch (err) {
2311
2278
  return `Error changing directory to ${path}: ${err.message}`;
@@ -2370,11 +2337,11 @@ function readFileTool(ctx) {
2370
2337
  return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
2371
2338
  }
2372
2339
  logger.info({ filePath }, "called readFile tool");
2373
- const resolved2 = resolveInRoot(ctx, filePath);
2374
- if (!resolved2.ok) return resolved2.error;
2340
+ const resolved = resolveInRoot(ctx, filePath);
2341
+ if (!resolved.ok) return resolved.error;
2375
2342
  try {
2376
- const content = await readFile3(resolved2.target, "utf8");
2377
- return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
2343
+ const content = await readFile3(resolved.target, "utf8");
2344
+ return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2378
2345
  } catch (err) {
2379
2346
  return `Error reading ${filePath}: ${err.message}`;
2380
2347
  }
@@ -2396,17 +2363,17 @@ function writeFileTool(ctx) {
2396
2363
  }),
2397
2364
  execute: async ({ filePath, content }) => {
2398
2365
  logger.info({ filePath }, "called writeFile tool");
2399
- const resolved2 = resolveInRoot(ctx, filePath);
2400
- if (resolved2.ok === false) return resolved2.error;
2401
- if (isSecretEnvFile(resolved2.target)) {
2366
+ const resolved = resolveInRoot(ctx, filePath);
2367
+ if (resolved.ok === false) return resolved.error;
2368
+ if (isSecretEnvFile(resolved.target)) {
2402
2369
  return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
2403
2370
  }
2404
2371
  try {
2405
- if (await hasSymlinkParent(ctx, resolved2.target)) {
2406
- return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2372
+ if (await hasSymlinkParent(ctx, resolved.target)) {
2373
+ return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2407
2374
  }
2408
- await mkdir3(dirname4(resolved2.target), { recursive: true });
2409
- await writeFile3(resolved2.target, content, "utf8");
2375
+ await mkdir3(dirname4(resolved.target), { recursive: true });
2376
+ await writeFile3(resolved.target, content, "utf8");
2410
2377
  return `Wrote to ${filePath}`;
2411
2378
  } catch (err) {
2412
2379
  return `Error writing ${filePath}: ${err.message}`;
@@ -2417,93 +2384,12 @@ function writeFileTool(ctx) {
2417
2384
 
2418
2385
  // src/lib/tools/writeAlgoliaCredentials.ts
2419
2386
  import { tool as tool6 } from "ai";
2420
- import z13 from "zod";
2387
+ import z12 from "zod";
2421
2388
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2422
2389
  import { dirname as dirname5 } from "node:path";
2423
2390
 
2424
2391
  // src/lib/algoliaApiKey.ts
2425
- import { z as z12 } from "zod";
2426
-
2427
- // src/lib/keychain.ts
2428
- import { deletePassword, getPassword, setPassword } from "cross-keychain";
2429
2392
  import { z as z11 } from "zod";
2430
- var SERVICE = "algolia-wizard";
2431
- var ACCOUNT = "api-keys";
2432
- var storedKeysSchema = z11.record(z11.string(), z11.string());
2433
- function entryId(kind, index, appId) {
2434
- return `${kind}:${appId}:${index}`;
2435
- }
2436
- async function loadKeys() {
2437
- const raw = await getPassword(SERVICE, ACCOUNT);
2438
- if (!raw) return {};
2439
- let payload;
2440
- try {
2441
- payload = JSON.parse(raw);
2442
- } catch {
2443
- payload = null;
2444
- }
2445
- const keys = storedKeysSchema.safeParse(payload);
2446
- if (!keys.success) {
2447
- logger.warn("the stored API keys are unreadable; treating them as empty");
2448
- return {};
2449
- }
2450
- return keys.data;
2451
- }
2452
- var queue = Promise.resolve();
2453
- function serialized(op) {
2454
- const next = queue.then(op);
2455
- queue = next.catch(() => {
2456
- });
2457
- return next;
2458
- }
2459
- async function readStoredKey(kind, index, appId) {
2460
- try {
2461
- return (await loadKeys())[entryId(kind, index, appId)] ?? null;
2462
- } catch (err) {
2463
- logger.warn(
2464
- { err: err.message, kind, index, appId },
2465
- "could not read the API key from the keychain"
2466
- );
2467
- return null;
2468
- }
2469
- }
2470
- function storeKey(kind, index, appId, value) {
2471
- return serialized(async () => {
2472
- const id = entryId(kind, index, appId);
2473
- try {
2474
- const keys = await loadKeys();
2475
- await setPassword(
2476
- SERVICE,
2477
- ACCOUNT,
2478
- JSON.stringify({ ...keys, [id]: value })
2479
- );
2480
- if ((await loadKeys())[id] !== value) {
2481
- throw new Error("the keychain did not store the value");
2482
- }
2483
- } catch (err) {
2484
- logger.warn(
2485
- { err: err.message, kind, index, appId },
2486
- "could not store the API key in the keychain; the next run will create another"
2487
- );
2488
- }
2489
- });
2490
- }
2491
- function deleteStoredKeys() {
2492
- return serialized(async () => {
2493
- try {
2494
- await deletePassword(SERVICE, ACCOUNT);
2495
- } catch (err) {
2496
- const message = err.message;
2497
- if (/not found/i.test(message)) return;
2498
- logger.warn(
2499
- { err: message },
2500
- "could not delete the API keys from the keychain"
2501
- );
2502
- }
2503
- });
2504
- }
2505
-
2506
- // src/lib/algoliaApiKey.ts
2507
2393
  var WRITE_ACLS = [
2508
2394
  "addObject",
2509
2395
  "deleteObject",
@@ -2511,21 +2397,58 @@ var WRITE_ACLS = [
2511
2397
  "editSettings",
2512
2398
  "listIndexes"
2513
2399
  ];
2514
- var createdKeySchema = z12.object({
2515
- key: z12.string().min(1).optional(),
2516
- value: z12.string().min(1).optional()
2400
+ var WRITE_ACL_SET = new Set(WRITE_ACLS);
2401
+ var apiKeySchema = z11.object({
2402
+ value: z11.string().min(1),
2403
+ acl: z11.array(z11.string()).default([]),
2404
+ indexes: z11.array(z11.string()).default([])
2405
+ });
2406
+ var apiKeyListSchema = z11.object({
2407
+ items: z11.array(apiKeySchema).optional(),
2408
+ keys: z11.array(apiKeySchema).optional()
2409
+ }).transform((o) => o.items ?? o.keys ?? []);
2410
+ var createdKeySchema = z11.object({
2411
+ key: z11.string().min(1).optional(),
2412
+ value: z11.string().min(1).optional()
2517
2413
  }).transform((o) => o.key ?? o.value);
2518
- async function createKey(index, acls, description) {
2519
- logger.info({ index, acls }, "creating an API key");
2414
+ function canReuseForWrites(key, index) {
2415
+ return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2416
+ }
2417
+ async function resolveWriteKey(index) {
2418
+ const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2419
+ const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuseForWrites(key, index))?.value;
2420
+ if (existing) {
2421
+ logger.info({ index }, "reusing existing write API key");
2422
+ return existing;
2423
+ }
2424
+ logger.info({ index }, "no reusable write key found; creating one");
2425
+ const created = await runAlgoliaCli([
2426
+ "apikeys",
2427
+ "create",
2428
+ "--indices",
2429
+ index,
2430
+ "--acl",
2431
+ WRITE_ACLS.join(","),
2432
+ "--description",
2433
+ `wizard write key for ${index}`,
2434
+ "-o",
2435
+ "json"
2436
+ ]);
2437
+ const writeKey = createdKeySchema.parse(JSON.parse(created));
2438
+ if (!writeKey) throw new Error("apikeys create returned no key value");
2439
+ return writeKey;
2440
+ }
2441
+ async function createSearchOnlyKey(index) {
2442
+ logger.info({ index }, "creating a search-only API key");
2520
2443
  const stdout = await runAlgoliaCli([
2521
2444
  "apikeys",
2522
2445
  "create",
2523
2446
  "--acl",
2524
- acls.join(","),
2447
+ "search",
2525
2448
  "--indices",
2526
2449
  index,
2527
2450
  "--description",
2528
- description,
2451
+ `Algolia Wizard search-only key for ${index}`,
2529
2452
  "-o",
2530
2453
  "json"
2531
2454
  ]);
@@ -2539,72 +2462,49 @@ async function createKey(index, acls, description) {
2539
2462
  if (!created) throw new Error("apikeys create returned no key value");
2540
2463
  return created;
2541
2464
  }
2542
- async function keyExists(key) {
2465
+ async function apiKeyExists(key) {
2543
2466
  try {
2544
- await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
2467
+ await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
2545
2468
  return true;
2546
2469
  } catch (err) {
2547
- return !/does not exist/i.test(err.message);
2470
+ return !/does not exist|not found|404/i.test(err.message);
2548
2471
  }
2549
2472
  }
2550
- var resolved = /* @__PURE__ */ new Map();
2551
- async function forgetResolvedKeys() {
2552
- resolved.clear();
2553
- await deleteStoredKeys();
2554
- }
2555
- function resolveKey(kind, index, appId, acls, description) {
2556
- const cacheKey = `${kind}:${appId}:${index}`;
2557
- const cached = resolved.get(cacheKey);
2558
- if (cached) return cached;
2559
- const pending = provisionKey(kind, index, appId, acls, description).catch(
2560
- (err) => {
2561
- resolved.delete(cacheKey);
2562
- throw err;
2563
- }
2564
- );
2565
- resolved.set(cacheKey, pending);
2566
- return pending;
2567
- }
2568
- async function provisionKey(kind, index, appId, acls, description) {
2569
- const stored = await readStoredKey(kind, index, appId);
2473
+ async function resolveSearchOnlyKey(index, appId, envKey) {
2474
+ if (envKey) {
2475
+ await recordSearchKey(index, appId, envKey);
2476
+ return { key: envKey, source: "env" };
2477
+ }
2478
+ const stored = await getStoredSearchKey(index, appId);
2570
2479
  if (stored) {
2571
- if (await keyExists(stored)) {
2572
- logger.info({ kind, index, appId }, "reusing the stored API key");
2573
- return { key: stored, source: "keychain" };
2480
+ if (await apiKeyExists(stored)) {
2481
+ logger.info({ index, appId }, "reusing the stored search-only API key");
2482
+ return { key: stored, source: "config" };
2574
2483
  }
2575
- logger.info(
2576
- { kind, index, appId },
2577
- "the stored API key no longer exists; creating another"
2484
+ logger.warn(
2485
+ { index, appId },
2486
+ "the stored search-only API key no longer exists; creating a replacement"
2578
2487
  );
2488
+ await forgetSearchKey(index);
2579
2489
  }
2580
- const key = await createKey(index, acls, description);
2581
- await storeKey(kind, index, appId, key);
2490
+ const key = await createSearchOnlyKey(index);
2491
+ await recordSearchKey(index, appId, key);
2582
2492
  return { key, source: "created" };
2583
2493
  }
2584
- function resolveWriteKey(index, appId) {
2585
- return resolveKey(
2586
- "write",
2587
- index,
2588
- appId,
2589
- WRITE_ACLS,
2590
- `Algolia Wizard write key for ${index} index`
2591
- );
2592
- }
2593
- async function resolveSearchOnlyKey(index, appId, envKey) {
2594
- if (envKey) return { key: envKey, source: "env" };
2595
- return resolveKey(
2596
- "search",
2597
- index,
2598
- appId,
2599
- ["search"],
2600
- `Algolia Wizard search-only key for ${index} index`
2601
- );
2494
+ async function recordSearchKey(index, appId, key) {
2495
+ try {
2496
+ await storeSearchKey(index, appId, key);
2497
+ } catch (err) {
2498
+ logger.warn(
2499
+ { err: err.message, index },
2500
+ "could not record the search-only API key; a later run may create another"
2501
+ );
2502
+ }
2602
2503
  }
2603
2504
 
2604
2505
  // src/lib/tools/writeAlgoliaCredentials.ts
2605
2506
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2606
2507
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2607
- var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
2608
2508
  function appendEnv(content, entries) {
2609
2509
  const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
2610
2510
  const lines = entries.map(([name, value]) => `${name}=${value}
@@ -2614,68 +2514,53 @@ function appendEnv(content, entries) {
2614
2514
  function hasEnv(content, name) {
2615
2515
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
2616
2516
  }
2617
- function upsertEnv(content, name, value) {
2618
- if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
2619
- return content.replace(
2620
- new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m"),
2621
- `${name}=${value}`
2622
- );
2623
- }
2624
2517
  function writeCredentialsTool(ctx) {
2625
2518
  return tool6({
2626
- description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_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"). Any name the file already defines is left untouched.`,
2627
- inputSchema: z13.object({
2628
- filePath: z13.string().describe(
2519
+ 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.`,
2520
+ inputSchema: z12.object({
2521
+ filePath: z12.string().describe(
2629
2522
  'Path to the env file to write credentials into (e.g. ".env")'
2630
2523
  )
2631
2524
  }),
2632
2525
  execute: async ({ filePath }) => {
2633
2526
  logger.info({ filePath }, "called writeCredentials tool");
2634
- const resolved2 = resolveInRoot(ctx, filePath);
2635
- if (resolved2.ok === false) return resolved2.error;
2527
+ const resolved = resolveInRoot(ctx, filePath);
2528
+ if (resolved.ok === false) return resolved.error;
2636
2529
  const targetIndex = useWizard.getState().targetIndex;
2637
2530
  if (!targetIndex) {
2638
2531
  return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2639
2532
  }
2640
- let existing = "";
2641
- let present;
2533
+ let appId;
2534
+ let writeKey;
2535
+ try {
2536
+ appId = (await requireApplication()).id;
2537
+ writeKey = await resolveWriteKey(targetIndex);
2538
+ } catch (err) {
2539
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2540
+ }
2642
2541
  try {
2643
- if (await hasSymlinkParent(ctx, resolved2.target)) {
2644
- return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2542
+ if (await hasSymlinkParent(ctx, resolved.target)) {
2543
+ return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2645
2544
  }
2545
+ let existing = "";
2646
2546
  try {
2647
- existing = await readFile4(resolved2.target, "utf8");
2547
+ existing = await readFile4(resolved.target, "utf8");
2648
2548
  } catch (err) {
2649
2549
  if (err.code !== "ENOENT") throw err;
2650
2550
  }
2651
- present = [APP_ID_VAR, API_KEY_VAR].filter(
2551
+ const present = [APP_ID_VAR, API_KEY_VAR].filter(
2652
2552
  (name) => hasEnv(existing, name)
2653
2553
  );
2654
- } catch (err) {
2655
- return `Error writing credentials to ${filePath}: ${err.message}`;
2656
- }
2657
- let credentials = [];
2658
- if (present.length === 0) {
2659
- try {
2660
- const appId = (await requireApplication()).id;
2661
- credentials = [
2662
- [APP_ID_VAR, appId],
2663
- [API_KEY_VAR, (await resolveWriteKey(targetIndex, appId)).key]
2664
- ];
2665
- } catch (err) {
2666
- return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2554
+ if (present.length > 0) {
2555
+ return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2667
2556
  }
2668
- }
2669
- try {
2670
- const updated = upsertEnv(
2671
- appendEnv(existing, credentials),
2672
- INDEX_NAME_VAR,
2673
- targetIndex
2674
- );
2675
- await mkdir4(dirname5(resolved2.target), { recursive: true });
2676
- await writeFile4(resolved2.target, updated, "utf8");
2677
- const wrote = `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}`;
2678
- return present.length > 0 ? `${wrote}. Skipped ${present.join(" and ")}: already defined there.` : wrote;
2557
+ const envWithCredentials = appendEnv(existing, [
2558
+ [APP_ID_VAR, appId],
2559
+ [API_KEY_VAR, writeKey]
2560
+ ]);
2561
+ await mkdir4(dirname5(resolved.target), { recursive: true });
2562
+ await writeFile4(resolved.target, envWithCredentials, "utf8");
2563
+ return `Wrote Algolia credentials to ${filePath}`;
2679
2564
  } catch (err) {
2680
2565
  return `Error writing credentials to ${filePath}: ${err.message}`;
2681
2566
  }
@@ -2685,23 +2570,15 @@ function writeCredentialsTool(ctx) {
2685
2570
 
2686
2571
  // src/lib/tools/searchFiles.ts
2687
2572
  import { tool as tool7 } from "ai";
2688
- import z14 from "zod";
2573
+ import z13 from "zod";
2689
2574
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2690
2575
  import { join as join7 } from "node:path";
2691
2576
  var MAX_QUERY_LENGTH = 1e3;
2692
- var SKIP_DIRS = /* @__PURE__ */ new Set([
2693
- "node_modules",
2694
- "dist",
2695
- "build",
2696
- "vendor",
2697
- "venv",
2698
- "__pycache__",
2699
- "target"
2700
- ]);
2701
2577
  async function walkFiles(dir) {
2578
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2702
2579
  const out = [];
2703
2580
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2704
- if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2581
+ if (e.name.startsWith(".") || skip.has(e.name)) continue;
2705
2582
  const full = join7(dir, e.name);
2706
2583
  if (e.isDirectory()) out.push(...await walkFiles(full));
2707
2584
  else if (e.isFile()) out.push(full);
@@ -2711,9 +2588,9 @@ async function walkFiles(dir) {
2711
2588
  function searchFilesTool(ctx) {
2712
2589
  return tool7({
2713
2590
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2714
- inputSchema: z14.object({
2715
- query: z14.string().describe("JavaScript RegExp pattern to search for"),
2716
- path: z14.string().optional().describe("Directory to search in (default: cwd)")
2591
+ inputSchema: z13.object({
2592
+ query: z13.string().describe("JavaScript RegExp pattern to search for"),
2593
+ path: z13.string().optional().describe("Directory to search in (default: cwd)")
2717
2594
  }),
2718
2595
  execute: async ({ query, path = "." }) => {
2719
2596
  logger.info({ query, path }, "called searchFiles tool");
@@ -2723,8 +2600,8 @@ function searchFilesTool(ctx) {
2723
2600
  if (query.length > MAX_QUERY_LENGTH) {
2724
2601
  return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
2725
2602
  }
2726
- const resolved2 = resolveInRoot(ctx, path);
2727
- if (!resolved2.ok) return resolved2.error;
2603
+ const resolved = resolveInRoot(ctx, path);
2604
+ if (!resolved.ok) return resolved.error;
2728
2605
  let re;
2729
2606
  try {
2730
2607
  re = new RegExp(query);
@@ -2732,7 +2609,7 @@ function searchFilesTool(ctx) {
2732
2609
  return `Invalid regex: ${err.message}`;
2733
2610
  }
2734
2611
  const matches = [];
2735
- for (const file of await walkFiles(resolved2.target)) {
2612
+ for (const file of await walkFiles(resolved.target)) {
2736
2613
  let content;
2737
2614
  try {
2738
2615
  content = await readFile5(file, "utf8");
@@ -2755,188 +2632,92 @@ function searchFilesTool(ctx) {
2755
2632
  });
2756
2633
  }
2757
2634
 
2758
- // src/lib/tools/runShell.ts
2635
+ // src/lib/tools/verifyImplementation.ts
2759
2636
  import { tool as tool8 } from "ai";
2760
- import z15 from "zod";
2761
- import { relative as relative2 } from "node:path";
2637
+ import z14 from "zod";
2762
2638
 
2763
- // src/lib/tools/utils/runShell.ts
2639
+ // src/lib/tools/utils/runCommand.ts
2764
2640
  import { spawn as spawn2 } from "node:child_process";
2765
-
2766
- // src/lib/tools/context.ts
2767
- var DEFAULT_TOOL_LIMITS = {
2768
- list: 10,
2769
- search: 10,
2770
- read: 20,
2771
- match: 100,
2772
- shell: 30
2773
- };
2774
- var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2775
- async function refuseByDefault() {
2776
- return "reject";
2777
- }
2778
- function createShellContext(overrides = {}) {
2779
- return {
2780
- env: async () => ({}),
2781
- timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2782
- executions: [],
2783
- approve: refuseByDefault,
2784
- ...overrides
2785
- };
2786
- }
2787
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2788
- return {
2789
- root: cwd,
2790
- cwd,
2791
- limits: { ...limits },
2792
- counts: { list: 0, search: 0, read: 0, shell: 0 },
2793
- shell: shell2
2794
- };
2795
- }
2796
-
2797
- // src/lib/tools/utils/runShell.ts
2798
- var SIGKILL_DELAY_MS = 5e3;
2799
- var HEAD_CHARS = 4e3;
2800
- var TAIL_CHARS = 8e3;
2801
- function truncateOutput(output) {
2802
- if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
2803
- const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
2804
- return [
2805
- output.slice(0, HEAD_CHARS),
2806
- `
2807
- \u2026 [${omitted} characters omitted] \u2026
2808
- `,
2809
- output.slice(-TAIL_CHARS)
2810
- ].join("");
2811
- }
2812
- function runShell(command, opts) {
2813
- const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2814
- const startedAt = Date.now();
2641
+ function runCommand(command, args, cwd) {
2815
2642
  return new Promise((resolve4) => {
2816
2643
  let output = "";
2817
- let timedOut = false;
2818
- let settled = false;
2819
- const child = spawn2(command, {
2820
- shell: true,
2821
- cwd: opts.cwd,
2822
- stdio: ["ignore", "pipe", "pipe"],
2823
- env: { ...process.env, ...opts.env }
2644
+ const child = spawn2(command, args, {
2645
+ cwd,
2646
+ stdio: ["ignore", "pipe", "pipe"]
2824
2647
  });
2825
- const finish = (exitCode) => {
2826
- if (settled) return;
2827
- settled = true;
2828
- clearTimeout(timer);
2829
- clearTimeout(killTimer);
2830
- resolve4({
2831
- exitCode,
2832
- output: truncateOutput(output.trim()),
2833
- timedOut,
2834
- durationMs: Date.now() - startedAt
2835
- });
2836
- };
2837
- let killTimer;
2838
- const timer = setTimeout(() => {
2839
- timedOut = true;
2840
- output += `
2841
- [timed out after ${timeoutMs}ms]`;
2842
- child.kill("SIGTERM");
2843
- killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
2844
- }, timeoutMs);
2845
2648
  child.stdout?.on("data", (d) => output += d);
2846
2649
  child.stderr?.on("data", (d) => output += d);
2847
- child.on("error", (err) => {
2848
- output += `Failed to run ${command}: ${err.message}`;
2849
- finish(1);
2850
- });
2851
- child.on("close", (code) => finish(code ?? 1));
2650
+ child.on(
2651
+ "error",
2652
+ (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2653
+ );
2654
+ child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2852
2655
  });
2853
2656
  }
2854
2657
 
2855
- // src/lib/tools/runShell.ts
2856
- function storeApproval(root) {
2857
- return async (req) => {
2858
- const rel = relative2(root, req.cwd);
2859
- const answer = await useWizard.getState().requestUserInput({
2860
- prompt: "Run this command?",
2861
- promptType: "commandApproval",
2862
- options: [],
2863
- command: {
2864
- ...req,
2865
- cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2866
- }
2867
- });
2868
- return answer === "approve" ? "approve" : "reject";
2869
- };
2658
+ // src/lib/tools/utils/packageManager.ts
2659
+ import { readFile as readFile6 } from "node:fs/promises";
2660
+ import { existsSync } from "node:fs";
2661
+ import { join as join8 } from "node:path";
2662
+ var LOCKFILES = [
2663
+ ["pnpm-lock.yaml", "pnpm"],
2664
+ ["yarn.lock", "yarn"],
2665
+ ["bun.lockb", "bun"],
2666
+ ["bun.lock", "bun"],
2667
+ ["package-lock.json", "npm"]
2668
+ ];
2669
+ async function readPackageJson(cwd = process.cwd()) {
2670
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2671
+ }
2672
+ function packageManagerFrom(pkg) {
2673
+ return pkg.packageManager?.split("@")[0] ?? "npm";
2870
2674
  }
2871
- function runShellTool(ctx) {
2675
+ function packageManagerFromLockfile(cwd) {
2676
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2677
+ }
2678
+ async function detectPackageManager(cwd) {
2679
+ try {
2680
+ const pkg = await readPackageJson(cwd);
2681
+ if (pkg.packageManager) return packageManagerFrom(pkg);
2682
+ } catch {
2683
+ }
2684
+ return packageManagerFromLockfile(cwd) ?? "npm";
2685
+ }
2686
+
2687
+ // src/lib/tools/repoVerification.ts
2688
+ var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2689
+ async function runRepoVerificationCheck() {
2690
+ let pkg;
2691
+ try {
2692
+ pkg = await readPackageJson();
2693
+ } catch (err) {
2694
+ const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2695
+ return { ok: false, checks: [], limitation };
2696
+ }
2697
+ const scripts = pkg.scripts ?? {};
2698
+ const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2699
+ if (present.length === 0) {
2700
+ const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2701
+ return { ok: false, checks: [], limitation };
2702
+ }
2703
+ const pm = await detectPackageManager(process.cwd());
2704
+ const checks = [];
2705
+ for (const script of present) {
2706
+ const command = `${pm} run ${script}`;
2707
+ const { code, output } = await runCommand(pm, ["run", script]);
2708
+ checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2709
+ }
2710
+ return { ok: checks.every((c) => c.ok), checks };
2711
+ }
2712
+
2713
+ // src/lib/tools/verifyImplementation.ts
2714
+ function verifyImplementationTool() {
2872
2715
  return tool8({
2873
- description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach. Never `cd`, `pushd` or `popd` inside the command \u2014 pass `cwd` instead. Never leave the project root: no `..` in any path, no absolute paths outside the root, and no `-C`/`--prefix` flag pointing outside it. Everything you need is inside the root.",
2874
- inputSchema: z15.object({
2875
- command: z15.string().describe(
2876
- "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
2877
- ),
2878
- cwd: z15.string().optional().describe(
2879
- "Directory to run in, relative to the project root. Defaults to the project root."
2880
- ),
2881
- explanation: z15.string().describe(
2882
- "One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
2883
- )
2884
- }),
2885
- execute: async ({ command, cwd, explanation }) => {
2886
- if (++ctx.counts.shell > ctx.limits.shell) {
2887
- return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
2888
- }
2889
- const resolved2 = resolveInRoot(ctx, cwd ?? ".");
2890
- if (!resolved2.ok) return resolved2.error;
2891
- logger.info({ command, cwd: resolved2.target }, "called runShell tool");
2892
- const decision = await ctx.shell.approve({
2893
- command,
2894
- cwd: resolved2.target,
2895
- explanation
2896
- });
2897
- if (decision === "reject") {
2898
- ctx.shell.executions.push({
2899
- command,
2900
- cwd: resolved2.target,
2901
- approved: false
2902
- });
2903
- logger.info({ command }, "runShell: user rejected the command");
2904
- return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
2905
- }
2906
- useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
2907
- const env = await ctx.shell.env().catch((err) => {
2908
- logger.warn({ err, command }, "runShell: could not resolve command env");
2909
- return {};
2910
- });
2911
- const run2 = await (ctx.shell.run ?? runShell)(command, {
2912
- cwd: resolved2.target,
2913
- env,
2914
- timeoutMs: ctx.shell.timeoutMs
2915
- });
2916
- logger.info(
2917
- {
2918
- command,
2919
- exitCode: run2.exitCode,
2920
- timedOut: run2.timedOut,
2921
- durationMs: run2.durationMs
2922
- },
2923
- "runShell finished"
2924
- );
2925
- await markInteraction();
2926
- ctx.shell.executions.push({
2927
- command,
2928
- cwd: resolved2.target,
2929
- approved: true,
2930
- exitCode: run2.exitCode,
2931
- output: run2.output,
2932
- timedOut: run2.timedOut,
2933
- durationMs: run2.durationMs
2934
- });
2935
- return {
2936
- exitCode: run2.exitCode,
2937
- timedOut: run2.timedOut,
2938
- output: run2.output
2939
- };
2716
+ 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.",
2717
+ inputSchema: z14.object(),
2718
+ execute: async () => {
2719
+ logger.info("called verifyImplementation tool");
2720
+ return runRepoVerificationCheck();
2940
2721
  }
2941
2722
  });
2942
2723
  }
@@ -2947,7 +2728,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2947
2728
  import { nanoid as nanoid2 } from "nanoid";
2948
2729
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2949
2730
  import { dirname as dirname6 } from "node:path";
2950
- import z16 from "zod";
2731
+ import z15 from "zod";
2951
2732
  var DATA_DIR = ".algolia-wizard/data";
2952
2733
  var RECORD_MODEL = "claude-haiku-4-5";
2953
2734
  var MAX_RECORDS = 100;
@@ -2959,17 +2740,17 @@ var anthropic = createAnthropic({
2959
2740
  function generateRecordTool(ctx) {
2960
2741
  return tool9({
2961
2742
  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.",
2962
- inputSchema: z16.object({
2963
- entityName: z16.string().describe("Name of the entity to generate records for."),
2964
- attributes: z16.array(z16.string()).describe("Attribute names each record must contain."),
2965
- count: z16.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2966
- hint: z16.string().optional().describe("Optional context to steer realistic values.")
2743
+ inputSchema: z15.object({
2744
+ entityName: z15.string().describe("Name of the entity to generate records for."),
2745
+ attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2746
+ count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2747
+ hint: z15.string().optional().describe("Optional context to steer realistic values.")
2967
2748
  }),
2968
2749
  execute: async ({ entityName, attributes, count, hint }) => {
2969
2750
  logger.info({ entityName, count }, "called generateRecord tool");
2970
2751
  try {
2971
- const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
2972
- const recordSchema = z16.object(
2752
+ const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2753
+ const recordSchema = z15.object(
2973
2754
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2974
2755
  );
2975
2756
  const generateBatch = async (batchCount) => {
@@ -2979,8 +2760,8 @@ function generateRecordTool(ctx) {
2979
2760
  const { output } = await generateText({
2980
2761
  model: anthropic(RECORD_MODEL),
2981
2762
  output: Output.object({
2982
- schema: z16.object({
2983
- records: z16.array(recordSchema).length(batchCount)
2763
+ schema: z15.object({
2764
+ records: z15.array(recordSchema).length(batchCount)
2984
2765
  })
2985
2766
  }),
2986
2767
  prompt: [
@@ -3016,18 +2797,18 @@ function generateRecordTool(ctx) {
3016
2797
  }));
3017
2798
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
3018
2799
  const relPath = `${DATA_DIR}/${slug}.json`;
3019
- const resolved2 = resolveInRoot(ctx, relPath);
3020
- if (resolved2.ok === false) return resolved2.error;
3021
- if (await hasSymlinkParent(ctx, resolved2.target)) {
3022
- return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2800
+ const resolved = resolveInRoot(ctx, relPath);
2801
+ if (resolved.ok === false) return resolved.error;
2802
+ if (await hasSymlinkParent(ctx, resolved.target)) {
2803
+ return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
3023
2804
  }
3024
- await mkdir5(dirname6(resolved2.target), { recursive: true });
3025
- await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
2805
+ await mkdir5(dirname6(resolved.target), { recursive: true });
2806
+ await writeFile5(resolved.target, JSON.stringify(records, null, 2), "utf8");
3026
2807
  logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
3027
2808
  return {
3028
2809
  filePath: relPath,
3029
2810
  count: records.length,
3030
- message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime using your language's standard JSON support \u2014 do not inline the records as literals.`
2811
+ message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) \u2014 do not inline the records as literals.`
3031
2812
  };
3032
2813
  } catch (err) {
3033
2814
  return `Error generating records: ${err.message}`;
@@ -3038,12 +2819,12 @@ function generateRecordTool(ctx) {
3038
2819
 
3039
2820
  // src/lib/tools/notifyUser.ts
3040
2821
  import { tool as tool10 } from "ai";
3041
- import z17 from "zod";
2822
+ import z16 from "zod";
3042
2823
  function notifyUserTool() {
3043
2824
  return tool10({
3044
2825
  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.`,
3045
- inputSchema: z17.object({
3046
- message: z17.string().describe(
2826
+ inputSchema: z16.object({
2827
+ message: z16.string().describe(
3047
2828
  "Short, plain-language description of what you are doing now."
3048
2829
  )
3049
2830
  }),
@@ -3055,6 +2836,22 @@ function notifyUserTool() {
3055
2836
  });
3056
2837
  }
3057
2838
 
2839
+ // src/lib/tools/context.ts
2840
+ var DEFAULT_TOOL_LIMITS = {
2841
+ list: 10,
2842
+ search: 10,
2843
+ read: 20,
2844
+ match: 100
2845
+ };
2846
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2847
+ return {
2848
+ root: cwd,
2849
+ cwd,
2850
+ limits,
2851
+ counts: { list: 0, search: 0, read: 0 }
2852
+ };
2853
+ }
2854
+
3058
2855
  // src/lib/tools/index.ts
3059
2856
  function withLogging(name, def) {
3060
2857
  const execute = def.execute;
@@ -3086,7 +2883,10 @@ function createTools(ctx, { output, tools }) {
3086
2883
  writeCredentialsTool(ctx)
3087
2884
  ),
3088
2885
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3089
- runShell: withLogging("runShell", runShellTool(ctx)),
2886
+ verifyImplementation: withLogging(
2887
+ "verifyImplementation",
2888
+ verifyImplementationTool()
2889
+ ),
3090
2890
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3091
2891
  notifyUser: withLogging("notifyUser", notifyUserTool())
3092
2892
  };
@@ -3121,7 +2921,7 @@ async function runAgent(req) {
3121
2921
  baseURL: PROXY_BASE_URL,
3122
2922
  fetch: proxyFetch
3123
2923
  });
3124
- const toolContext = req.toolContext ?? createToolContext();
2924
+ const toolContext = createToolContext();
3125
2925
  const readTools = ["readFile", "searchFiles", "listFiles"];
3126
2926
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3127
2927
  const instructions = [
@@ -3186,11 +2986,7 @@ async function runAgent(req) {
3186
2986
  "runAgent finished"
3187
2987
  );
3188
2988
  logger.info(
3189
- {
3190
- counts: toolContext.counts,
3191
- limits: toolContext.limits,
3192
- commandsRun: toolContext.shell.executions.length
3193
- },
2989
+ { counts: toolContext.counts, limits: toolContext.limits },
3194
2990
  "tool usage"
3195
2991
  );
3196
2992
  const toolResults = await stream.toolResults;
@@ -3208,16 +3004,16 @@ async function runAgent(req) {
3208
3004
  }
3209
3005
 
3210
3006
  // src/actions/detectLanguage.ts
3211
- import z20 from "zod";
3212
- var detectLanguageSchema = z20.object({
3213
- languages: z20.array(z20.object({ name: z20.string(), version: z20.string() })),
3214
- frameworks: z20.array(z20.object({ name: z20.string(), version: z20.string() }))
3007
+ import z19 from "zod";
3008
+ var detectLanguageSchema = z19.object({
3009
+ languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
3010
+ frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
3215
3011
  });
3216
3012
  var detectLanguage = () => runAgent({
3217
3013
  instructions: [
3218
3014
  "Analyze the codebase and determine the programming languages and frameworks used",
3219
- "If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
3220
- "If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
3015
+ "If a superset language is found, exclude the subset language. TS-over-JS.",
3016
+ "If a meta-framework is used, exclude the framework. Next-over-React.",
3221
3017
  "Return the exact version",
3222
3018
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3223
3019
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3229,31 +3025,31 @@ var detectLanguage = () => runAgent({
3229
3025
  });
3230
3026
 
3231
3027
  // src/actions/analyzeCodebase.ts
3232
- import z21 from "zod";
3028
+ import z20 from "zod";
3233
3029
  var READONLY_TOOLS = [
3234
3030
  "listFiles",
3235
3031
  "changeDirectory",
3236
3032
  "readFile",
3237
3033
  "searchFiles"
3238
3034
  ];
3239
- var ingestionAnalysisSchema = z21.object({
3240
- ingestionAnalysis: z21.array(
3241
- z21.object({
3242
- name: z21.string(),
3243
- paths: z21.array(z21.string()),
3035
+ var ingestionAnalysisSchema = z20.object({
3036
+ ingestionAnalysis: z20.array(
3037
+ z20.object({
3038
+ name: z20.string(),
3039
+ paths: z20.array(z20.string()),
3244
3040
  // indexable fields the agent found for this entity
3245
- attributes: z21.array(z21.string())
3041
+ attributes: z20.array(z20.string())
3246
3042
  })
3247
3043
  )
3248
3044
  });
3249
- var searchImplementationAnalysisSchema = z21.object({
3250
- searchImplementationAnalysis: z21.string()
3045
+ var searchImplementationAnalysisSchema = z20.object({
3046
+ searchImplementationAnalysis: z20.string()
3251
3047
  });
3252
- var verificationSchema = z21.object({
3253
- verification: z21.array(z21.string())
3048
+ var verificationSchema = z20.object({
3049
+ verification: z20.array(z20.string())
3254
3050
  });
3255
3051
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3256
- var analyzeCodebaseSchema = z21.object({
3052
+ var analyzeCodebaseSchema = z20.object({
3257
3053
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3258
3054
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3259
3055
  verification: verificationSchema.shape.verification.optional(),
@@ -3315,7 +3111,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3315
3111
  // package.json
3316
3112
  var package_default = {
3317
3113
  name: "@algolia/wizard",
3318
- version: "0.9.0-rc.96.109",
3114
+ version: "0.10.0",
3319
3115
  description: "Magically implement Algolia functionality in your codebase",
3320
3116
  type: "module",
3321
3117
  engines: {
@@ -3366,7 +3162,6 @@ var package_default = {
3366
3162
  "@hono/node-server": "^2.0.10",
3367
3163
  "@segment/analytics-node": "^3.1.0",
3368
3164
  ai: "^6.0.190",
3369
- "cross-keychain": "^1.1.0",
3370
3165
  dotenv: "^17.4.2",
3371
3166
  hono: "^4.12.27",
3372
3167
  ink: "^7.0.5",
@@ -3434,8 +3229,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
3434
3229
  }
3435
3230
 
3436
3231
  // src/actions/confirmLanguage.ts
3437
- import z23 from "zod";
3438
- var confirmLanguageSchema = z23.object({
3232
+ import z22 from "zod";
3233
+ var confirmLanguageSchema = z22.object({
3439
3234
  languages: detectLanguageSchema.shape.languages
3440
3235
  });
3441
3236
  async function confirmLanguage(ctx) {
@@ -3456,19 +3251,17 @@ async function confirmLanguage(ctx) {
3456
3251
  }
3457
3252
 
3458
3253
  // src/actions/confirmFramework.ts
3459
- import z24 from "zod";
3460
- var confirmFrameworkSchema = z24.object({
3254
+ import z23 from "zod";
3255
+ var confirmFrameworkSchema = z23.object({
3461
3256
  frameworks: detectLanguageSchema.shape.frameworks
3462
3257
  });
3463
3258
  var CURATED_FRAMEWORKS = [
3464
3259
  "Next.js",
3465
3260
  "React",
3466
3261
  "Vue",
3467
- "Vanilla JS",
3468
- "Django",
3469
- "Laravel",
3470
- "Rails",
3471
- "Symfony"
3262
+ "Angular",
3263
+ "Svelte",
3264
+ "Vanilla JS"
3472
3265
  ];
3473
3266
  var OTHER_OPTION = "Other";
3474
3267
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3481,15 +3274,12 @@ var FRAMEWORK_ALIASES = {
3481
3274
  vuejs: "vue",
3482
3275
  angular: "angular",
3483
3276
  angularjs: "angular",
3277
+ svelte: "svelte",
3278
+ sveltekit: "svelte",
3484
3279
  vanillajs: "vanillajs",
3485
3280
  vanilla: "vanillajs",
3486
3281
  javascript: "vanillajs",
3487
- js: "vanillajs",
3488
- django: "django",
3489
- laravel: "laravel",
3490
- rails: "rails",
3491
- rubyonrails: "rails",
3492
- symfony: "symfony"
3282
+ js: "vanillajs"
3493
3283
  };
3494
3284
  var isSameFramework = (a, b) => {
3495
3285
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3590,8 +3380,8 @@ async function promptUser(ctx, params) {
3590
3380
  }
3591
3381
 
3592
3382
  // src/actions/confirmEntities.ts
3593
- import z25 from "zod";
3594
- var confirmEntitiesSchema = z25.object({
3383
+ import z24 from "zod";
3384
+ var confirmEntitiesSchema = z24.object({
3595
3385
  // Final detection — the focused re-run may supersede project-scan's.
3596
3386
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3597
3387
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3661,15 +3451,15 @@ async function confirmEntities(ctx) {
3661
3451
  }
3662
3452
 
3663
3453
  // src/actions/review.ts
3664
- import { z as z26 } from "zod";
3665
- var reviewSchema = z26.object({
3454
+ import { z as z25 } from "zod";
3455
+ var reviewSchema = z25.object({
3666
3456
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3667
3457
  // not one entry per workflow step — a step's raw output can be a long,
3668
3458
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3669
3459
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3670
- summaryPoints: z26.array(z26.string()),
3671
- reviewPrompt: z26.string(),
3672
- nextSteps: z26.array(z26.string())
3460
+ summaryPoints: z25.array(z25.string()),
3461
+ reviewPrompt: z25.string(),
3462
+ nextSteps: z25.array(z25.string())
3673
3463
  });
3674
3464
  function formatCompletedSteps(steps) {
3675
3465
  if (!steps.length) return "(no prior steps completed)";
@@ -3720,12 +3510,19 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3720
3510
  };
3721
3511
 
3722
3512
  // src/actions/implement.ts
3723
- import z27 from "zod";
3513
+ import z26 from "zod";
3724
3514
 
3725
3515
  // src/lib/worktree.ts
3726
- import { execFile } from "node:child_process";
3727
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3728
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3516
+ import { execFile, spawn as spawn3 } from "node:child_process";
3517
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3518
+ import {
3519
+ basename as basename2,
3520
+ dirname as dirname7,
3521
+ isAbsolute as isAbsolute2,
3522
+ join as join9,
3523
+ relative as relative2,
3524
+ resolve as resolve3
3525
+ } from "node:path";
3729
3526
  var MAX_BUFFER = 32 * 1024 * 1024;
3730
3527
  var MAX_WIZARD_WORKTREES = 3;
3731
3528
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3756,7 +3553,7 @@ async function isWorkingTreeDirty(repoRoot) {
3756
3553
  return out.trim().length > 0;
3757
3554
  }
3758
3555
  async function pruneOldWorktrees(repoRoot) {
3759
- const dir = join8(stateDir(repoRoot), "worktrees");
3556
+ const dir = join9(stateDir(repoRoot), "worktrees");
3760
3557
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3761
3558
  for (const slug of stale) {
3762
3559
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3767,7 +3564,7 @@ async function pruneOldWorktrees(repoRoot) {
3767
3564
  "worktree",
3768
3565
  "remove",
3769
3566
  "--force",
3770
- join8(dir, slug)
3567
+ join9(dir, slug)
3771
3568
  ]);
3772
3569
  await git(["-C", repoRoot, "branch", "-D", branch]);
3773
3570
  } catch (err) {
@@ -3781,13 +3578,113 @@ async function pruneOldWorktrees(repoRoot) {
3781
3578
  async function createWorktree(repoRoot) {
3782
3579
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3783
3580
  const dirSlug = branch.replace(/\//g, "-");
3784
- const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3581
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3785
3582
  await git(["-C", repoRoot, "worktree", "prune"]);
3786
3583
  await pruneOldWorktrees(repoRoot);
3787
3584
  await mkdir6(dirname7(path), { recursive: true });
3788
3585
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3789
3586
  return { path, branch };
3790
3587
  }
3588
+ async function installWorktreeDeps(worktreePath) {
3589
+ try {
3590
+ await readPackageJson(worktreePath);
3591
+ } catch {
3592
+ return { ok: true, output: "no package.json; skipped install" };
3593
+ }
3594
+ const pm = await detectPackageManager(worktreePath);
3595
+ return new Promise((resolve4) => {
3596
+ let output = "";
3597
+ const child = spawn3(pm, ["install"], {
3598
+ cwd: worktreePath,
3599
+ stdio: ["ignore", "pipe", "pipe"]
3600
+ });
3601
+ child.stdout?.on("data", (d) => output += d);
3602
+ child.stderr?.on("data", (d) => output += d);
3603
+ child.on(
3604
+ "error",
3605
+ (err) => resolve4({
3606
+ ok: false,
3607
+ output: `Failed to run ${pm} install: ${err.message}`
3608
+ })
3609
+ );
3610
+ child.on(
3611
+ "close",
3612
+ (code) => resolve4({ ok: code === 0, output: output.trim() })
3613
+ );
3614
+ });
3615
+ }
3616
+ var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3617
+ function validateIngestEntrypoint(worktreePath, entrypoint) {
3618
+ if (!entrypoint || entrypoint.startsWith("-")) {
3619
+ return {
3620
+ ok: false,
3621
+ reason: `entrypoint "${entrypoint}" is not a plain file path`
3622
+ };
3623
+ }
3624
+ const target = resolve3(worktreePath, entrypoint);
3625
+ const rel = relative2(worktreePath, target);
3626
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
3627
+ return {
3628
+ ok: false,
3629
+ reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3630
+ };
3631
+ }
3632
+ return { ok: true, target };
3633
+ }
3634
+ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3635
+ if (!INGEST_RUNTIMES.includes(runtime)) {
3636
+ return {
3637
+ ran: false,
3638
+ ok: false,
3639
+ output: "",
3640
+ reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3641
+ };
3642
+ }
3643
+ const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3644
+ if (!validated.ok) {
3645
+ return { ran: false, ok: false, output: "", reason: validated.reason };
3646
+ }
3647
+ try {
3648
+ if (!(await stat2(validated.target)).isFile()) {
3649
+ return {
3650
+ ran: false,
3651
+ ok: false,
3652
+ output: "",
3653
+ reason: `entrypoint "${entrypoint}" is not a file`
3654
+ };
3655
+ }
3656
+ } catch {
3657
+ return {
3658
+ ran: false,
3659
+ ok: false,
3660
+ output: "",
3661
+ reason: `entrypoint "${entrypoint}" does not exist`
3662
+ };
3663
+ }
3664
+ return new Promise((resolveRun) => {
3665
+ let output = "";
3666
+ const child = spawn3(runtime, [entrypoint], {
3667
+ cwd: worktreePath,
3668
+ shell: false,
3669
+ stdio: ["ignore", "pipe", "pipe"],
3670
+ env: { ...process.env, ...env }
3671
+ });
3672
+ child.stdout?.on("data", (d) => output += d);
3673
+ child.stderr?.on("data", (d) => output += d);
3674
+ child.on(
3675
+ "error",
3676
+ (err) => resolveRun({
3677
+ ran: true,
3678
+ ok: false,
3679
+ output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3680
+ })
3681
+ );
3682
+ child.on(
3683
+ "close",
3684
+ (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3685
+ );
3686
+ });
3687
+ }
3791
3688
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3792
3689
  const trimmed = sourcePath.trim();
3793
3690
  if (!trimmed) {
@@ -3801,8 +3698,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3801
3698
  } catch {
3802
3699
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3803
3700
  }
3804
- const relPath = join8(ingestDir, basename2(source));
3805
- const dest = join8(worktreePath, relPath);
3701
+ const relPath = join9(ingestDir, basename2(source));
3702
+ const dest = join9(worktreePath, relPath);
3806
3703
  try {
3807
3704
  await mkdir6(dirname7(dest), { recursive: true });
3808
3705
  await copyFile(source, dest);
@@ -3820,7 +3717,7 @@ function hasEnvVar(content, name) {
3820
3717
  async function readEnvVar(worktreePath, name) {
3821
3718
  let content;
3822
3719
  try {
3823
- content = await readFile6(join8(worktreePath, ".env"), "utf8");
3720
+ content = await readFile7(join9(worktreePath, ".env"), "utf8");
3824
3721
  } catch (err) {
3825
3722
  if (err.code !== "ENOENT") throw err;
3826
3723
  return void 0;
@@ -3835,10 +3732,10 @@ async function readEnvVar(worktreePath, name) {
3835
3732
  return value;
3836
3733
  }
3837
3734
  async function writeSearchEnvValues(worktreePath, vars) {
3838
- const target = join8(worktreePath, ".env");
3735
+ const target = join9(worktreePath, ".env");
3839
3736
  let existing = "";
3840
3737
  try {
3841
- existing = await readFile6(target, "utf8");
3738
+ existing = await readFile7(target, "utf8");
3842
3739
  } catch (err) {
3843
3740
  if (err.code !== "ENOENT") throw err;
3844
3741
  }
@@ -3907,15 +3804,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3907
3804
  }
3908
3805
 
3909
3806
  // src/lib/algoliaDocs.ts
3910
- import { readFileSync, readdirSync, existsSync } from "node:fs";
3911
- import { dirname as dirname8, join as join9 } from "node:path";
3807
+ import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3808
+ import { dirname as dirname8, join as join10 } from "node:path";
3912
3809
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3913
- var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3810
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3914
3811
  function findDocsDir() {
3915
3812
  let dir = dirname8(fileURLToPath2(import.meta.url));
3916
3813
  for (; ; ) {
3917
- const candidate = join9(dir, DOCS_SUBPATH);
3918
- if (existsSync(candidate)) return candidate;
3814
+ const candidate = join10(dir, DOCS_SUBPATH);
3815
+ if (existsSync2(candidate)) return candidate;
3919
3816
  const parent = dirname8(dir);
3920
3817
  if (parent === dir) return void 0;
3921
3818
  dir = parent;
@@ -3937,7 +3834,7 @@ function loadAlgoliaDoc(language) {
3937
3834
  );
3938
3835
  return "";
3939
3836
  }
3940
- return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3837
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3941
3838
  }
3942
3839
  function getNamedDoc(name, language) {
3943
3840
  const docsDir = findDocsDir();
@@ -3945,15 +3842,14 @@ function getNamedDoc(name, language) {
3945
3842
  logger.warn("docs/algolia-sdk not found");
3946
3843
  return "";
3947
3844
  }
3948
- const file = join9(docsDir, `${name}-${language}.md`);
3949
- if (!existsSync(file)) {
3845
+ const file = join10(docsDir, `${name}-${language}.md`);
3846
+ if (!existsSync2(file)) {
3950
3847
  logger.warn({ name, language }, "named SDK reference not found");
3951
3848
  return "";
3952
3849
  }
3953
3850
  return readFileSync(file, "utf8").trim();
3954
3851
  }
3955
3852
  function getFrameworkSpecificDoc(frameworks) {
3956
- if (frameworks.length === 0) return "";
3957
3853
  const fw = frameworks.map((f) => f.toLowerCase());
3958
3854
  if (fw.includes("vue") || fw.includes("nuxt")) {
3959
3855
  return loadAlgoliaDoc("vue");
@@ -3961,6 +3857,9 @@ function getFrameworkSpecificDoc(frameworks) {
3961
3857
  if (fw.includes("react") || fw.includes("next.js")) {
3962
3858
  return loadAlgoliaDoc("react");
3963
3859
  }
3860
+ if (fw.includes("angular")) {
3861
+ return loadAlgoliaDoc("angular");
3862
+ }
3964
3863
  return loadAlgoliaDoc("js");
3965
3864
  }
3966
3865
 
@@ -3970,63 +3869,62 @@ function shellQuote(value) {
3970
3869
  }
3971
3870
 
3972
3871
  // src/actions/implement.ts
3973
- var implementSchema = z27.object({
3974
- filesChanged: z27.array(z27.string()),
3975
- summary: z27.string(),
3976
- worktreePath: z27.string().optional(),
3977
- ingestCommand: z27.string().optional(),
3978
- ingestScriptRan: z27.boolean().optional(),
3979
- ingestRecordCount: z27.number().optional(),
3980
- ingestDurationMs: z27.number().optional(),
3981
- ingestionSource: z27.enum(["local", "fileUpload", "generated"]),
3982
- searchEnvVars: z27.array(
3983
- z27.object({
3984
- name: z27.string(),
3985
- value: z27.string()
3872
+ var implementSchema = z26.object({
3873
+ filesChanged: z26.array(z26.string()),
3874
+ summary: z26.string(),
3875
+ worktreePath: z26.string().optional(),
3876
+ ingestCommand: z26.string().optional(),
3877
+ ingestScriptRan: z26.boolean().optional(),
3878
+ ingestRecordCount: z26.number().optional(),
3879
+ ingestDurationMs: z26.number().optional(),
3880
+ ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3881
+ searchEnvVars: z26.array(
3882
+ z26.object({
3883
+ name: z26.string(),
3884
+ value: z26.string()
3986
3885
  })
3987
3886
  ).optional()
3988
3887
  });
3989
- var implementationOutputSchema = z27.object({
3990
- summary: z27.string(),
3991
- ingestCommand: z27.string().optional()
3888
+ var implementationOutputSchema = z26.object({
3889
+ summary: z26.string(),
3890
+ // Ingestion only: a structured pair the wizard turns into an argv, never a
3891
+ // free-form command string. `runtime` is allowlisted and `entrypoint` is
3892
+ // validated worktree-relative, so the agent cannot inject extra commands.
3893
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3894
+ entrypoint: z26.string().optional()
3992
3895
  });
3993
- var verificationOutputSchema = z27.object({
3994
- summary: z27.string(),
3995
- sufficient: z27.boolean(),
3996
- additionalInstructions: z27.string().optional()
3896
+ var verificationOutputSchema = z26.object({
3897
+ summary: z26.string(),
3898
+ sufficient: z26.boolean(),
3899
+ additionalInstructions: z26.string().optional()
3997
3900
  });
3998
3901
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3999
3902
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
4000
3903
  var INGEST_DIR = ".algolia-wizard";
4001
- var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
4002
- function lower(entries) {
4003
- return entries.map((entry) => entry.name.toLowerCase());
4004
- }
4005
- function isJsProject(language) {
4006
- return lower(language.languages).some(
4007
- (name) => JS_LANGUAGES.some((js) => name.includes(js))
4008
- );
4009
- }
4010
- var UI_FRAMEWORKS = [
4011
- { match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
4012
- { match: ["react", "next"], target: "React", doc: "react" },
4013
- { match: ["angular"], target: "Angular" }
4014
- ];
4015
- function matchUiFramework(language) {
4016
- const names = lower(language.frameworks);
4017
- return UI_FRAMEWORKS.find(
4018
- (ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
4019
- );
4020
- }
4021
- function searchUiTarget(language) {
4022
- return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
4023
- }
4024
- function frameworksForDoc(language) {
4025
- if (!isJsProject(language)) return [];
4026
- return [matchUiFramework(language)?.doc ?? "js"];
3904
+ function detectUiFramework(language) {
3905
+ const names = language.frameworks.map((f) => f.name.toLowerCase());
3906
+ if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3907
+ if (names.some((n) => n.includes("react") || n.includes("next")))
3908
+ return "React";
3909
+ if (names.some((n) => n.includes("angular"))) return "Angular";
3910
+ return "JavaScript";
3911
+ }
3912
+ function frameworksForDoc(framework) {
3913
+ switch (framework) {
3914
+ case "React":
3915
+ return ["react"];
3916
+ case "Vue":
3917
+ return ["vue"];
3918
+ case "Angular":
3919
+ return ["angular"];
3920
+ case "JavaScript":
3921
+ return [];
3922
+ }
4027
3923
  }
4028
3924
  function publicEnvPrefix(language) {
4029
- const frameworkNames = lower(language.frameworks);
3925
+ const frameworkNames = language.frameworks.map(
3926
+ (framework) => framework.name.toLowerCase()
3927
+ );
4030
3928
  if (frameworkNames.some((name) => name.includes("next"))) {
4031
3929
  return "NEXT_PUBLIC_";
4032
3930
  }
@@ -4039,21 +3937,17 @@ function publicEnvPrefix(language) {
4039
3937
  if (frameworkNames.some((name) => name.includes("vite"))) {
4040
3938
  return "VITE_";
4041
3939
  }
4042
- return isJsProject(language) ? "PUBLIC_" : "";
3940
+ return "PUBLIC_";
4043
3941
  }
4044
3942
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
4045
3943
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
4046
- var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
4047
3944
  function appIdVar(language) {
4048
3945
  return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
4049
3946
  }
4050
3947
  function searchKeyVar(language) {
4051
3948
  return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
4052
3949
  }
4053
- function searchIndexVar(language) {
4054
- return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
4055
- }
4056
- function searchEnvVars(language, index, appId, searchKey) {
3950
+ function searchEnvVars(language, appId, searchKey) {
4057
3951
  return [
4058
3952
  {
4059
3953
  name: appIdVar(language),
@@ -4062,26 +3956,14 @@ function searchEnvVars(language, index, appId, searchKey) {
4062
3956
  {
4063
3957
  name: searchKeyVar(language),
4064
3958
  value: searchKey ?? "<your-algolia-search-only-api-key>"
4065
- },
4066
- // Wizard-supplied rather than written into the generated code, because an
4067
- // agent that retypes the name (appending the project name, re-casing it)
4068
- // leaves the UI querying an index that does not exist.
4069
- {
4070
- name: searchIndexVar(language),
4071
- value: index
4072
3959
  }
4073
3960
  ];
4074
3961
  }
4075
3962
  function baseInstructions(input) {
4076
3963
  return [
4077
- // Agents have renamed this (e.g. appending the project name), which the
4078
- // index-scoped keys then reject with a 403.
4079
- `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
3964
+ `Target Algolia index: ${input.targetIndex}`,
4080
3965
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4081
- "Make minimal, idiomatic changes; do not touch unrelated code.",
4082
- `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4083
- "Use the project's own tooling for every command \u2014 its package manager, task runner, and test/lint commands. Do not assume a JavaScript toolchain.",
4084
- "runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
3966
+ "Make minimal, idiomatic changes; do not touch unrelated code."
4085
3967
  ];
4086
3968
  }
4087
3969
  function sourceSpecificInstructions(input) {
@@ -4101,52 +3983,36 @@ function sourceSpecificInstructions(input) {
4101
3983
  generated: [
4102
3984
  "No real data source exists; use sample records for each confirmed entity.",
4103
3985
  "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.",
4104
- "In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
3986
+ "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.",
4105
3987
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4106
3988
  ]
4107
3989
  };
4108
3990
  return byLine[input.ingestionSource];
4109
3991
  }
4110
- function algoliaClientDoc(input) {
4111
- const doc = getNamedDoc("save-records", "js");
4112
- if (!doc) return [];
4113
- if (isJsProject(input.language)) return [doc];
4114
- return [
4115
- "The reference below is written in JavaScript. Use it for the method names, arguments, and record shape, then translate to this project's language and its official Algolia client:",
4116
- doc
4117
- ];
4118
- }
4119
3992
  function ingestionInstructions(input) {
4120
3993
  return [
4121
3994
  ...input.confirmed && input.confirmed.length ? [
4122
3995
  `Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
4123
3996
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4124
3997
  `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.`,
4125
- `Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
4126
- "Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
3998
+ "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4127
3999
  "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.",
4128
- ...algoliaClientDoc(input),
4129
- "Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
4130
- 'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
4000
+ getNamedDoc("save-records", "js"),
4001
+ 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4131
4002
  "The summary should be extremely concise.",
4003
+ `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.`,
4132
4004
  ...sourceSpecificInstructions(input)
4133
4005
  ] : []
4134
4006
  ];
4135
4007
  }
4136
4008
  function searchInstructions(input) {
4137
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4009
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4138
4010
  return [
4139
4011
  "Implement an in-app Algolia search experience.",
4140
- `Build the search UI for ${input.searchUiTarget}.`,
4141
- ...doc ? [
4142
- "Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
4143
- doc
4144
- ] : [
4145
- "No bundled Algolia SDK reference exists for this stack, so rely on the project's own conventions and Algolia's official client for its language. Do not invent APIs \u2014 keep to the documented search endpoint and its parameters."
4146
- ],
4147
- `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 search input and results list against the target index.`,
4148
- `Read the index name from the ${searchIndexVar(input.language)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
4149
- "Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
4012
+ `Build the search UI for ${input.uiFramework}.`,
4013
+ "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4014
+ doc,
4015
+ `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.`,
4150
4016
  // The key is provisioned only after verification passes, so the agent never
4151
4017
  // sees one. It must also leave .env alone: the wizard reads that file to
4152
4018
  // decide whether a key already exists, and an agent-invented value there
@@ -4155,8 +4021,9 @@ function searchInstructions(input) {
4155
4021
  // Not the agent's to rename: the wizard writes these exact names into
4156
4022
  // ".env" right after this step, so a renamed prefix would leave the code
4157
4023
  // reading a var the wizard never wrote.
4158
- `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4159
- "Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
4024
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4025
+ "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.",
4026
+ '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.',
4160
4027
  "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."
4161
4028
  ];
4162
4029
  }
@@ -4164,12 +4031,11 @@ function verificationInstructions(input) {
4164
4031
  return [
4165
4032
  "Verify the Algolia implementation changes in the current worktree.",
4166
4033
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4167
- "Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
4168
- "This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
4169
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4170
- "Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4034
+ "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4035
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4036
+ "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.",
4171
4037
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4172
- `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4038
+ `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4173
4039
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4174
4040
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4175
4041
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4190,15 +4056,14 @@ var IMPLEMENT_CONFIG = {
4190
4056
  }
4191
4057
  };
4192
4058
  var useCaseToolMap = {
4193
- ingestion: [
4059
+ ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4060
+ search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4061
+ verification: [
4194
4062
  ...FS_READ_TOOLS,
4195
4063
  "writeFile",
4196
- "writeCredentials",
4197
- "runShell",
4064
+ "verifyImplementation",
4198
4065
  "notifyUser"
4199
- ],
4200
- search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4201
- verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4066
+ ]
4202
4067
  };
4203
4068
  function toolsForUseCase(useCase, ingestionSource) {
4204
4069
  const tools = useCaseToolMap[useCase];
@@ -4221,30 +4086,15 @@ function formatSummary(useCase, summary) {
4221
4086
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4222
4087
  return `${label}: ${summary}`;
4223
4088
  }
4089
+ function buildIngestCommand(worktree, runtime, entrypoint) {
4090
+ return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4091
+ }
4224
4092
  function parseIngestRecordCount(output) {
4225
4093
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4226
4094
  if (!match) return void 0;
4227
4095
  const count = Number(match[1]);
4228
4096
  return Number.isFinite(count) ? count : void 0;
4229
4097
  }
4230
- function ingestOutcome(executions, ingestCommand) {
4231
- const newestFirst = [...executions].reverse();
4232
- const withCount = newestFirst.filter(
4233
- (e) => parseIngestRecordCount(e.output ?? "") != null
4234
- );
4235
- const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4236
- return {
4237
- run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4238
- recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4239
- };
4240
- }
4241
- function makeToolContext(worktree, env = async () => ({})) {
4242
- return createToolContext(
4243
- DEFAULT_TOOL_LIMITS,
4244
- worktree,
4245
- createShellContext({ env, approve: storeApproval(worktree) })
4246
- );
4247
- }
4248
4098
  function verificationRetryInstructions(verification) {
4249
4099
  return [
4250
4100
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4323,13 +4173,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4323
4173
  const confirmed2 = normalized.confirmedEntities;
4324
4174
  const searchLocation = normalized.searchImplementationAnalysis;
4325
4175
  let appId;
4326
- let ingestAppId;
4327
4176
  if (useCases.includes("search")) {
4328
4177
  appId = (await requireApplication()).id;
4329
4178
  }
4330
- if (useCases.includes("ingestion")) {
4331
- ingestAppId = appId ?? (await requireApplication()).id;
4332
- }
4333
4179
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4334
4180
  try {
4335
4181
  process.chdir(worktree);
@@ -4362,11 +4208,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4362
4208
  appId,
4363
4209
  // Names only: the search-only key is provisioned after verification, so
4364
4210
  // every value here is still a placeholder when the agent reads them.
4365
- searchEnvVars: searchEnvVars(language, targetIndex, appId),
4211
+ searchEnvVars: searchEnvVars(language, appId),
4366
4212
  ingestDir: INGEST_DIR,
4367
4213
  ingestionSource,
4368
4214
  uploadFilePath,
4369
- searchUiTarget: searchUiTarget(language)
4215
+ uiFramework: detectUiFramework(language)
4370
4216
  };
4371
4217
  const summaries = [];
4372
4218
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4389,31 +4235,41 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4389
4235
  }
4390
4236
  let finalSearchEnvVars = input.searchEnvVars;
4391
4237
  let agentRuns = 0;
4392
- let ingestCommand;
4238
+ let ingestRuntime;
4239
+ let ingestEntrypoint;
4393
4240
  let ingestScriptRan = false;
4394
4241
  let ingestRecordCount;
4395
4242
  let ingestDurationMs;
4243
+ let installFailed = false;
4396
4244
  let ingestOutcomeMessage;
4397
- const ingestKeyAppId = ingestAppId;
4398
- const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4399
- [APP_ID_VAR]: ingestKeyAppId,
4400
- [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4401
- [INDEX_NAME_VAR]: targetIndex
4402
- })) : void 0;
4403
- const searchTools = makeToolContext(worktree);
4404
4245
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4405
4246
  if (agentRuns > 0) ctx.recordStepExecution();
4406
4247
  agentRuns += 1;
4407
- return runAgent({
4248
+ const result = await runAgent({
4408
4249
  instructions: buildAgentInstructions(
4409
4250
  currentUseCase,
4410
4251
  input,
4411
4252
  extraInstructions
4412
4253
  ),
4413
4254
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4414
- outputSchema: implementationOutputSchema,
4415
- toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4255
+ outputSchema: implementationOutputSchema
4416
4256
  });
4257
+ ctx.notify({
4258
+ messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4259
+ });
4260
+ const installLogId = ctx.logStart("installWorktreeDeps", {
4261
+ useCase: currentUseCase
4262
+ });
4263
+ const install = await installWorktreeDeps(worktree);
4264
+ ctx.logEnd(installLogId, install.ok ? "success" : "error");
4265
+ if (!install.ok) {
4266
+ installFailed = true;
4267
+ logger.warn(
4268
+ { useCase: currentUseCase, output: install.output },
4269
+ "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4270
+ );
4271
+ }
4272
+ return result;
4417
4273
  }
4418
4274
  async function runVerificationUseCase() {
4419
4275
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4421,55 +4277,110 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4421
4277
  return runAgent({
4422
4278
  instructions: buildAgentInstructions("verification", input),
4423
4279
  tools: toolsForUseCase("verification"),
4424
- outputSchema: verificationOutputSchema,
4425
- toolContext: searchTools
4280
+ outputSchema: verificationOutputSchema
4426
4281
  });
4427
4282
  }
4428
4283
  if (useCases.includes("ingestion")) {
4429
- const result = await runImplementationUseCase("ingestion");
4430
- summaries.push(formatSummary("ingestion", result.summary));
4431
- ingestCommand = result.ingestCommand;
4432
- const executions = (ingestionTools ?? searchTools).shell.executions;
4433
- const { run: ingestRun, recordCount } = ingestOutcome(
4434
- executions,
4435
- ingestCommand
4436
- );
4437
- ingestScriptRan = ingestRun != null;
4438
- ingestRecordCount = recordCount;
4439
- ingestDurationMs = ingestRun?.durationMs;
4440
- if (ingestScriptRan) {
4441
- ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4442
- if (ingestRecordCount != null) {
4443
- track("AI Wizard Ingest Successful", {
4444
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4445
- record_count: ingestRecordCount,
4446
- duration_ms: ingestDurationMs ?? 0
4284
+ const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4285
+ summaries.push(formatSummary("ingestion", summary));
4286
+ ingestRuntime = runtime;
4287
+ ingestEntrypoint = entrypoint;
4288
+ if (ingestRuntime && ingestEntrypoint && !installFailed) {
4289
+ ctx.clearNotices();
4290
+ const runNow = await ctx.requestUserInput({
4291
+ prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4292
+ promptType: "acceptReject",
4293
+ options: ["Yes", "No"],
4294
+ messages: []
4295
+ }) === true;
4296
+ if (runNow) {
4297
+ const ingestApp = await requireApplication();
4298
+ const writeKey = await resolveWriteKey(targetIndex);
4299
+ ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4300
+ const scriptLogId = ctx.logStart("runIngestScript", {
4301
+ runtime: ingestRuntime,
4302
+ entrypoint: ingestEntrypoint
4447
4303
  });
4304
+ const startedAt = Date.now();
4305
+ const run2 = await runIngestScript(
4306
+ worktree,
4307
+ ingestRuntime,
4308
+ ingestEntrypoint,
4309
+ {
4310
+ [APP_ID_VAR]: ingestApp.id,
4311
+ [API_KEY_VAR]: writeKey
4312
+ }
4313
+ );
4314
+ ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4315
+ ingestScriptRan = run2.ran && run2.ok;
4316
+ if (ingestScriptRan) {
4317
+ ingestDurationMs = Date.now() - startedAt;
4318
+ ingestRecordCount = parseIngestRecordCount(run2.output);
4319
+ if (ingestRecordCount != null) {
4320
+ track("AI Wizard Ingest Successful", {
4321
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4322
+ record_count: ingestRecordCount,
4323
+ duration_ms: ingestDurationMs
4324
+ });
4325
+ }
4326
+ }
4327
+ let summaryLine;
4328
+ let outcomeMessage;
4329
+ if (!run2.ran) {
4330
+ summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4331
+ outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4332
+ logger.warn(
4333
+ {
4334
+ runtime: ingestRuntime,
4335
+ entrypoint: ingestEntrypoint,
4336
+ reason: run2.reason
4337
+ },
4338
+ "implement: refused to auto-run ingestion script"
4339
+ );
4340
+ track("Error", {
4341
+ step: "Push Data",
4342
+ error: `ingestion script skipped: ${run2.reason}`,
4343
+ product_area: "AI Wizard"
4344
+ });
4345
+ } else if (run2.ok) {
4346
+ const status = "Ingestion run: succeeded.";
4347
+ summaryLine = run2.output ? `${status}
4348
+ ${run2.output}` : status;
4349
+ outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4350
+ } else {
4351
+ const status = "\u26A0\uFE0F Ingestion run failed:";
4352
+ summaryLine = run2.output ? `${status}
4353
+ ${run2.output}` : status;
4354
+ outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4355
+ logger.warn(
4356
+ {
4357
+ runtime: ingestRuntime,
4358
+ entrypoint: ingestEntrypoint,
4359
+ output: run2.output
4360
+ },
4361
+ "implement: ingestion script run failed"
4362
+ );
4363
+ track("Error", {
4364
+ step: "Push Data",
4365
+ error: run2.output || "ingestion script exited non-zero",
4366
+ product_area: "AI Wizard"
4367
+ });
4368
+ }
4369
+ summaries.push(summaryLine);
4370
+ ingestOutcomeMessage = outcomeMessage;
4448
4371
  }
4449
- } else {
4450
- const rejected = executions.some((e) => !e.approved);
4451
- const reason = rejected ? "you declined to run it" : "no successful run was recorded";
4452
- ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4453
- summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
4454
- logger.warn(
4455
- { ingestCommand, rejected, commandsRun: executions.length },
4456
- "implement: ingestion script did not complete successfully"
4457
- );
4458
- track("Error", {
4459
- step: "Push Data",
4460
- error: `ingestion did not run: ${reason}`,
4461
- product_area: "AI Wizard"
4462
- });
4463
4372
  }
4464
4373
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4465
- if (ingestCommand) {
4466
- commandMessages.push(`Ingestion command: ${ingestCommand}`);
4374
+ if (ingestRuntime && ingestEntrypoint) {
4375
+ commandMessages.push(
4376
+ `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4377
+ );
4467
4378
  }
4468
4379
  await ctx.requestUserInput({
4469
4380
  prompt: "",
4470
4381
  promptType: "enterToContinue",
4471
4382
  options: [],
4472
- messages: [ingestOutcomeMessage, ...commandMessages]
4383
+ messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4473
4384
  });
4474
4385
  }
4475
4386
  if (useCases.includes("search")) {
@@ -4518,14 +4429,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4518
4429
  let searchKeyError;
4519
4430
  if (appId) {
4520
4431
  try {
4521
- const resolved2 = await resolveSearchOnlyKey(
4432
+ const resolved = await resolveSearchOnlyKey(
4522
4433
  targetIndex,
4523
4434
  appId,
4524
4435
  envSearchKey
4525
4436
  );
4526
- searchKey = resolved2.key;
4437
+ searchKey = resolved.key;
4527
4438
  summaries.push(
4528
- resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4439
+ resolved.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4529
4440
  );
4530
4441
  } catch (err) {
4531
4442
  searchKeyError = err.message;
@@ -4535,12 +4446,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4535
4446
  );
4536
4447
  }
4537
4448
  }
4538
- finalSearchEnvVars = searchEnvVars(
4539
- language,
4540
- targetIndex,
4541
- appId,
4542
- searchKey
4543
- );
4449
+ finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
4544
4450
  const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4545
4451
  (v) => !v.value.startsWith("<")
4546
4452
  );
@@ -4586,13 +4492,22 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4586
4492
  "implement: agent reported success but no files changed in the worktree"
4587
4493
  );
4588
4494
  }
4495
+ if (installFailed) {
4496
+ summaries.push(
4497
+ '\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".'
4498
+ );
4499
+ }
4589
4500
  return {
4590
4501
  ingestionSource,
4591
4502
  filesChanged,
4592
4503
  summary: summaries.join("\n\n"),
4593
4504
  worktreePath: worktree,
4594
- ...useCases.includes("ingestion") && ingestCommand ? {
4595
- ingestCommand,
4505
+ ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4506
+ ingestCommand: buildIngestCommand(
4507
+ worktree,
4508
+ ingestRuntime,
4509
+ ingestEntrypoint
4510
+ ),
4596
4511
  ingestScriptRan,
4597
4512
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4598
4513
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4640,8 +4555,8 @@ var defaultWorkflow = {
4640
4555
  defineStep({
4641
4556
  id: "select-index",
4642
4557
  title: "Set up index",
4643
- outputSchema: z28.object({
4644
- selection: z28.string()
4558
+ outputSchema: z27.object({
4559
+ selection: z27.string()
4645
4560
  }),
4646
4561
  run: (ctx) => selectIndexStep(ctx)
4647
4562
  }),
@@ -4883,10 +4798,7 @@ Options:
4883
4798
  --no-telemetry Send no telemetry or analytics for this run.
4884
4799
  --reset-on-run Wipe this project's wizard state (run state, AI consent,
4885
4800
  worktrees) before starting, so the run behaves like a
4886
- first-ever run. Also drops every API key the wizard has
4887
- stored in your keychain, for this project and any other, so
4888
- later runs create new ones. Your Algolia login is not
4889
- touched.
4801
+ first-ever run. Algolia credentials are not touched.
4890
4802
  -h, --help Print this message.`;
4891
4803
  function parseCliArgs(argv) {
4892
4804
  const positionals = [];
@@ -4923,11 +4835,10 @@ function parseCliArgs(argv) {
4923
4835
 
4924
4836
  // src/lib/resetState.ts
4925
4837
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4926
- import { join as join10 } from "node:path";
4838
+ import { join as join11 } from "node:path";
4927
4839
  var KEEP = ["wizard.log"];
4928
4840
  async function resetProjectState() {
4929
4841
  const dir = stateDir();
4930
- await forgetResolvedKeys();
4931
4842
  let entries;
4932
4843
  try {
4933
4844
  entries = await readdir4(dir);
@@ -4936,17 +4847,14 @@ async function resetProjectState() {
4936
4847
  }
4937
4848
  const targets = entries.filter((name) => !KEEP.includes(name));
4938
4849
  await Promise.all(
4939
- targets.map(
4940
- (name) => rm2(join10(dir, name), { recursive: true, force: true })
4941
- )
4850
+ targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
4942
4851
  );
4943
4852
  return { dir, removed: targets };
4944
4853
  }
4945
4854
 
4946
4855
  // src/main.tsx
4947
- import { jsx as jsx15 } from "react/jsx-runtime";
4856
+ import { jsx as jsx14 } from "react/jsx-runtime";
4948
4857
  async function startup() {
4949
- setProjectRoot(process.cwd());
4950
4858
  let args;
4951
4859
  try {
4952
4860
  args = parseCliArgs(process.argv.slice(2));
@@ -4994,7 +4902,7 @@ ${formatStepList(workflow)}`);
4994
4902
  }
4995
4903
  async function run(workflow) {
4996
4904
  const store = useWizard.getState();
4997
- const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4905
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4998
4906
  await store.waitForStart();
4999
4907
  let user = await getUser();
5000
4908
  if (!user) {