@algolia/wizard 0.9.0-rc.88.85 → 0.9.0-rc.93.108

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 Box15, Text as Text15, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
7
+ import { Box as Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -17,10 +17,8 @@ function npxArgs(args) {
17
17
  return ["--yes", "@algolia/cli@latest", ...args];
18
18
  }
19
19
  var shell = process.platform === "win32";
20
- function childEnv(withoutAdminKey) {
21
- if (!withoutAdminKey) return void 0;
22
- const { ALGOLIA_API_KEY: _adminKey, ...rest } = process.env;
23
- return rest;
20
+ function mask(text, secret) {
21
+ return secret ? text.replaceAll(secret, "***") : text;
24
22
  }
25
23
  function lineSplitter(emit) {
26
24
  let buffer = "";
@@ -45,14 +43,12 @@ var stderrSink = (stream, line) => {
45
43
  if (stream === "stdout") return;
46
44
  wizardSink(stream, line);
47
45
  };
48
- function runAlgoliaCli(args, { onOutput, withoutAdminKey } = {}) {
46
+ function runAlgoliaCli(args, { onOutput, redact } = {}) {
49
47
  const store = useWizard.getState();
50
- const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
48
+ const command = mask(args.join(" "), redact);
49
+ const logId = store.logStart("tool", `algolia ${command}`);
51
50
  return new Promise((resolve4, reject) => {
52
- const child = spawn("npx", npxArgs(args), {
53
- shell,
54
- env: childEnv(withoutAdminKey)
55
- });
51
+ const child = spawn("npx", npxArgs(args), { shell });
56
52
  let stdout = "";
57
53
  let stderr = "";
58
54
  const splitters = {
@@ -79,13 +75,13 @@ function runAlgoliaCli(args, { onOutput, withoutAdminKey } = {}) {
79
75
  const failed = stderr.trim();
80
76
  let detail = "";
81
77
  if (failed) {
82
- detail = `: ${failed}`;
78
+ detail = `: ${mask(failed, redact)}`;
83
79
  } else if (stdout.trim()) {
84
80
  detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
85
81
  }
86
82
  reject(
87
83
  new Error(
88
- `Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
84
+ `Algolia CLI \`${command}\` failed (exit ${code})${detail}`
89
85
  )
90
86
  );
91
87
  }
@@ -626,10 +622,13 @@ function Notices() {
626
622
  }
627
623
 
628
624
  // src/ui/PromptInput.tsx
629
- import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
625
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
630
626
  import TextInput from "ink-text-input";
631
627
  import { useState as useState5 } from "react";
632
628
 
629
+ // src/ui/CommandApproval.tsx
630
+ import { Box as Box5, Text as Text5, useInput } from "ink";
631
+
633
632
  // src/ui/NextAction.tsx
634
633
  import { Box as Box4, Text as Text4 } from "ink";
635
634
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -653,14 +652,60 @@ function NextAction({
653
652
  ] });
654
653
  }
655
654
 
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
+
656
701
  // src/ui/SelectPrompt.tsx
657
- import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
702
+ import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
658
703
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
659
704
 
660
705
  // src/ui/ScrollView.tsx
661
- import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
706
+ import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
662
707
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
663
- import { jsxs as jsxs4 } from "react/jsx-runtime";
708
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
664
709
  var INDICATOR_ROWS = 2;
665
710
  function fittedWidth(node, columns) {
666
711
  let left = 0;
@@ -730,14 +775,14 @@ function useScrollWindow({
730
775
  };
731
776
  }
732
777
  function ScrollView({ scroll, children }) {
733
- return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
734
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, 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: [
735
780
  "\u2191 ",
736
781
  scroll.hiddenAbove,
737
782
  " more"
738
783
  ] }),
739
784
  children,
740
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
785
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
741
786
  "\u2193 ",
742
787
  scroll.hiddenBelow,
743
788
  " more"
@@ -746,7 +791,7 @@ function ScrollView({ scroll, children }) {
746
791
  }
747
792
 
748
793
  // src/ui/SelectPrompt.tsx
749
- import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
794
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
750
795
  var CANCEL = "cancel";
751
796
  var ARROW_WIDTH = 4;
752
797
  var COLUMN_GAP = 2;
@@ -808,7 +853,7 @@ function SelectPrompt({
808
853
  revealIndex(index);
809
854
  }, [index, revealIndex]);
810
855
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
811
- useInput((input, key) => {
856
+ useInput2((input, key) => {
812
857
  if (rows.length === 0) return;
813
858
  if (key.upArrow || input === "k") {
814
859
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -831,56 +876,56 @@ function SelectPrompt({
831
876
  }
832
877
  }
833
878
  });
834
- return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
835
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
836
- error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
837
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
838
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
839
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
840
- question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
841
- helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
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 })
842
887
  ] })
843
888
  ] }),
844
- /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
889
+ /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
845
890
  const i = scroll.offset + visibleIndex;
846
891
  const highlighted = i === index;
847
892
  const isCancel = i === cancelIndex;
848
893
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
849
894
  const sec = isCancel ? void 0 : secondary?.[i];
850
895
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
851
- const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
896
+ const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
852
897
  highlighted ? "\u276F " : " ",
853
898
  bullet,
854
899
  option
855
900
  ] });
856
901
  const isText = sec?.kind === "text";
857
- return /* @__PURE__ */ jsxs5(
858
- Box6,
902
+ return /* @__PURE__ */ jsxs6(
903
+ Box7,
859
904
  {
860
905
  width: isText ? "100%" : barWidth,
861
906
  paddingX: 1,
862
907
  paddingY: 1,
863
908
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
864
909
  children: [
865
- /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
866
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
867
- Text6,
910
+ /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
911
+ isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
912
+ Text7,
868
913
  {
869
914
  wrap: "truncate",
870
915
  color: highlighted ? COLORS.primary : COLORS.muted,
871
916
  children: sec.value
872
917
  }
873
918
  ) }),
874
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
919
+ sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
875
920
  ]
876
921
  },
877
922
  `row-${i}`
878
923
  );
879
924
  }) }),
880
- /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
925
+ /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
881
926
  i > 0 ? " " : "",
882
- /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
883
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
927
+ /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
928
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
884
929
  " ",
885
930
  label
886
931
  ] })
@@ -889,23 +934,23 @@ function SelectPrompt({
889
934
  }
890
935
 
891
936
  // src/ui/PromptInput.tsx
892
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
937
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
893
938
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
894
939
  function EnterToContinuePrompt({
895
940
  question,
896
941
  messages,
897
942
  onDecide
898
943
  }) {
899
- useInput2((_input, key) => {
944
+ useInput3((_input, key) => {
900
945
  if (key.return) onDecide(true);
901
946
  else if (key.escape) onDecide(false);
902
947
  });
903
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
904
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
905
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
906
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
907
- /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
908
- /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
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" })
909
954
  ] })
910
955
  ] });
911
956
  }
@@ -913,11 +958,11 @@ function PromptInput() {
913
958
  const { phase, inputReq, submitInput } = useWizard();
914
959
  const [draft, setDraft] = useState5("");
915
960
  if (phase === "done" || phase === "error") {
916
- return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
961
+ return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
917
962
  }
918
963
  if (phase !== "awaitingInput" || !inputReq) return null;
919
964
  if (inputReq.promptType === "multipleChoice") {
920
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
965
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
921
966
  SelectPrompt,
922
967
  {
923
968
  question: inputReq.prompt,
@@ -934,7 +979,7 @@ function PromptInput() {
934
979
  ) });
935
980
  }
936
981
  if (inputReq.promptType === "multiSelect") {
937
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
982
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
938
983
  SelectPrompt,
939
984
  {
940
985
  multi: true,
@@ -949,7 +994,7 @@ function PromptInput() {
949
994
  ) });
950
995
  }
951
996
  if (inputReq.promptType === "notice") {
952
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
997
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
953
998
  SelectPrompt,
954
999
  {
955
1000
  question: inputReq.prompt,
@@ -960,7 +1005,7 @@ function PromptInput() {
960
1005
  ) });
961
1006
  }
962
1007
  if (inputReq.promptType === "enterToContinue") {
963
- return /* @__PURE__ */ jsx5(
1008
+ return /* @__PURE__ */ jsx6(
964
1009
  EnterToContinuePrompt,
965
1010
  {
966
1011
  question: inputReq.prompt,
@@ -969,9 +1014,12 @@ function PromptInput() {
969
1014
  }
970
1015
  );
971
1016
  }
1017
+ if (inputReq.promptType === "commandApproval" && inputReq.command) {
1018
+ return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1019
+ }
972
1020
  if (inputReq.promptType === "acceptReject") {
973
1021
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
974
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1022
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
975
1023
  SelectPrompt,
976
1024
  {
977
1025
  question: inputReq.prompt,
@@ -982,15 +1030,15 @@ function PromptInput() {
982
1030
  }
983
1031
  ) });
984
1032
  }
985
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
986
- inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
987
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
988
- /* @__PURE__ */ jsxs6(Box7, { children: [
989
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
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: [
990
1038
  inputReq.prompt,
991
1039
  " "
992
1040
  ] }),
993
- /* @__PURE__ */ jsx5(
1041
+ /* @__PURE__ */ jsx6(
994
1042
  TextInput,
995
1043
  {
996
1044
  value: draft,
@@ -1008,7 +1056,7 @@ function PromptInput() {
1008
1056
  // src/ui/Welcome.tsx
1009
1057
  import { dirname as dirname2, join as join3 } from "node:path";
1010
1058
  import { fileURLToPath } from "node:url";
1011
- import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1059
+ import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1012
1060
 
1013
1061
  // src/ui/copy/welcome.ts
1014
1062
  var sidebarItems = [
@@ -1021,12 +1069,12 @@ var sidebarItems = [
1021
1069
  description: "push 100 records to Algolia in seconds"
1022
1070
  },
1023
1071
  {
1024
- title: "detect your framework",
1025
- description: "React, Vue, Angular, Vanilla JS"
1072
+ title: "detect your stack",
1073
+ description: "whatever language and framework you already use"
1026
1074
  },
1027
1075
  {
1028
1076
  title: "scaffold a search UI",
1029
- description: "a styled InstantSearch component, wired into your app"
1077
+ description: "a search box and results, wired into your app"
1030
1078
  },
1031
1079
  {
1032
1080
  title: "ship it",
@@ -1036,20 +1084,20 @@ var sidebarItems = [
1036
1084
 
1037
1085
  // src/ui/Welcome.tsx
1038
1086
  import Image, { InkPictureProvider } from "ink-picture";
1039
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1087
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1040
1088
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1041
1089
  function SidebarItem({
1042
1090
  title,
1043
1091
  description
1044
1092
  }) {
1045
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1046
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1047
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1048
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
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 })
1049
1097
  ] }),
1050
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1051
- /* @__PURE__ */ jsx6(Spacer, {}),
1052
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1098
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1099
+ /* @__PURE__ */ jsx7(Spacer, {}),
1100
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1053
1101
  ] })
1054
1102
  ] });
1055
1103
  }
@@ -1057,7 +1105,7 @@ function Welcome() {
1057
1105
  const confirmStart = useWizard((s) => s.confirmStart);
1058
1106
  const openLearnMore = useWizard((s) => s.openLearnMore);
1059
1107
  const { rows } = useWindowSize6();
1060
- useInput3((input, key) => {
1108
+ useInput4((input, key) => {
1061
1109
  if (key.return) confirmStart();
1062
1110
  else if (input === "i") openLearnMore();
1063
1111
  });
@@ -1075,16 +1123,16 @@ function Welcome() {
1075
1123
  if (rows < 30) {
1076
1124
  layout = scales["small"];
1077
1125
  }
1078
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1079
- /* @__PURE__ */ jsx6(
1080
- Box8,
1126
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1127
+ /* @__PURE__ */ jsx7(
1128
+ Box9,
1081
1129
  {
1082
1130
  paddingY: layout.main.padding.y,
1083
1131
  paddingX: layout.main.padding.x,
1084
1132
  flexDirection: "column",
1085
1133
  justifyContent: "center",
1086
- children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1087
- /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1134
+ children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1135
+ /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1088
1136
  Image,
1089
1137
  {
1090
1138
  src: IMAGE_PATH,
@@ -1095,16 +1143,16 @@ function Welcome() {
1095
1143
  protocol: "halfBlock"
1096
1144
  }
1097
1145
  ) }),
1098
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1099
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1100
- /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1101
- /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
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" })
1102
1150
  ] })
1103
1151
  ] })
1104
1152
  }
1105
1153
  ),
1106
- /* @__PURE__ */ jsxs7(
1107
- Box8,
1154
+ /* @__PURE__ */ jsxs8(
1155
+ Box9,
1108
1156
  {
1109
1157
  backgroundColor: COLORS.bg.sidebar,
1110
1158
  width: 40,
@@ -1114,8 +1162,8 @@ function Welcome() {
1114
1162
  flexDirection: "column",
1115
1163
  justifyContent: "center",
1116
1164
  children: [
1117
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1118
- sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
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))
1119
1167
  ]
1120
1168
  }
1121
1169
  )
@@ -1124,7 +1172,7 @@ function Welcome() {
1124
1172
 
1125
1173
  // src/ui/LearnMore.tsx
1126
1174
  import { Fragment as Fragment2 } from "react";
1127
- import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1175
+ import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1128
1176
 
1129
1177
  // src/ui/copy/learn-more.ts
1130
1178
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1132,12 +1180,17 @@ var accessItems = [
1132
1180
  {
1133
1181
  tag: "READ",
1134
1182
  title: "Project files",
1135
- description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1183
+ description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1136
1184
  },
1137
1185
  {
1138
1186
  tag: "WRITE",
1139
1187
  title: "Code changes",
1140
- description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
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."
1141
1194
  },
1142
1195
  {
1143
1196
  tag: "NET",
@@ -1147,13 +1200,13 @@ var accessItems = [
1147
1200
  {
1148
1201
  tag: "KEY",
1149
1202
  title: "Credentials",
1150
- description: "saves your Admin API key to .env and adds it to .gitignore."
1203
+ description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1151
1204
  }
1152
1205
  ];
1153
1206
  var neverItems = [
1154
1207
  "Send your source code to a model or third party",
1155
1208
  "Commit or push to git",
1156
- "Touch files outside your project directory"
1209
+ "Run a command you haven't approved"
1157
1210
  ];
1158
1211
  var policyLinks = [
1159
1212
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1161,10 +1214,11 @@ var policyLinks = [
1161
1214
  ];
1162
1215
 
1163
1216
  // src/ui/LearnMore.tsx
1164
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1217
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1165
1218
  var TAG_COLORS = {
1166
1219
  READ: COLORS.success,
1167
1220
  WRITE: COLORS.badge,
1221
+ EXEC: COLORS.danger,
1168
1222
  NET: COLORS.accent,
1169
1223
  KEY: COLORS.muted
1170
1224
  };
@@ -1177,12 +1231,12 @@ function NeverLine({
1177
1231
  }) {
1178
1232
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1179
1233
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1180
- return /* @__PURE__ */ jsxs8(Text9, { children: [
1181
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1234
+ return /* @__PURE__ */ jsxs9(Text10, { children: [
1235
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1182
1236
  " ".repeat(NEVER_BOX_PAD_X),
1183
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1237
+ segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1184
1238
  " ".repeat(rightPad),
1185
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1239
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1186
1240
  ] });
1187
1241
  }
1188
1242
  function LearnMore() {
@@ -1190,12 +1244,12 @@ function LearnMore() {
1190
1244
  const backToHome = useWizard((s) => s.backToHome);
1191
1245
  const { columns } = useWindowSize7();
1192
1246
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1193
- useInput4((_input, key) => {
1247
+ useInput5((_input, key) => {
1194
1248
  if (key.escape) backToHome();
1195
1249
  else if (key.return) confirmStart();
1196
1250
  });
1197
- return /* @__PURE__ */ jsxs8(
1198
- Box9,
1251
+ return /* @__PURE__ */ jsxs9(
1252
+ Box10,
1199
1253
  {
1200
1254
  flexDirection: "column",
1201
1255
  paddingX: PADDING_X,
@@ -1203,31 +1257,31 @@ function LearnMore() {
1203
1257
  width: "100%",
1204
1258
  gap: 1,
1205
1259
  children: [
1206
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1207
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1208
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1209
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1210
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1211
- /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1212
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1213
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1214
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
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}` })
1215
1269
  ] }) })
1216
1270
  ] })
1217
1271
  ] }, item.tag)) }),
1218
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1219
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1220
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1221
- /* @__PURE__ */ jsx7(
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(
1222
1276
  NeverLine,
1223
1277
  {
1224
1278
  width: dividerWidth,
1225
1279
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1226
1280
  }
1227
1281
  ),
1228
- neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1229
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1230
- /* @__PURE__ */ jsx7(
1282
+ neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1283
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1284
+ /* @__PURE__ */ jsx8(
1231
1285
  NeverLine,
1232
1286
  {
1233
1287
  width: dividerWidth,
@@ -1239,24 +1293,24 @@ function LearnMore() {
1239
1293
  }
1240
1294
  )
1241
1295
  ] }, item)),
1242
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1243
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1296
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1297
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1244
1298
  ] }),
1245
- /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1246
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1247
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
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 })
1248
1302
  ] }, link.label)) }),
1249
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1250
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1251
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1252
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1253
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
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" })
1254
1308
  ] }),
1255
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1256
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1257
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1258
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1259
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
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" })
1260
1314
  ] })
1261
1315
  ] })
1262
1316
  ]
@@ -1265,10 +1319,10 @@ function LearnMore() {
1265
1319
  }
1266
1320
 
1267
1321
  // src/ui/Sidebar.tsx
1268
- import { Box as Box12, Text as Text12 } from "ink";
1322
+ import { Box as Box13, Text as Text13 } from "ink";
1269
1323
 
1270
1324
  // src/ui/Steps.tsx
1271
- import { Box as Box10, Text as Text10 } from "ink";
1325
+ import { Box as Box11, Text as Text11 } from "ink";
1272
1326
  import Spinner from "ink-spinner";
1273
1327
 
1274
1328
  // src/core/persistence.ts
@@ -1297,12 +1351,12 @@ async function clearWorkflowState(workflowId) {
1297
1351
  }
1298
1352
 
1299
1353
  // src/ui/Steps.tsx
1300
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1354
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1301
1355
  function Steps() {
1302
1356
  const { steps } = useWizard();
1303
1357
  const visibleSteps = steps.filter(isStepVisible);
1304
- 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: [
1305
- s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
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],
1306
1360
  " ",
1307
1361
  s.title
1308
1362
  ] }) }, s.id)) });
@@ -1311,27 +1365,27 @@ function CurrentStep() {
1311
1365
  const { steps } = useWizard();
1312
1366
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1313
1367
  if (!currentStep) return null;
1314
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1315
- /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1368
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1369
+ /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1316
1370
  " ",
1317
1371
  ` ${currentStep.title}`
1318
1372
  ] });
1319
1373
  }
1320
1374
 
1321
1375
  // src/ui/Progress.tsx
1322
- import { Box as Box11, Text as Text11 } from "ink";
1323
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1376
+ import { Box as Box12, Text as Text12 } from "ink";
1377
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1324
1378
  function Progress() {
1325
1379
  const { steps, currentStepIndex } = useWizard();
1326
1380
  const visibleSteps = steps.filter(isStepVisible);
1327
1381
  if (visibleSteps.length === 0) return null;
1328
1382
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1329
1383
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1330
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1331
- /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1332
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1333
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1334
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
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 })
1335
1389
  ] });
1336
1390
  }
1337
1391
 
@@ -1342,10 +1396,10 @@ var sidebarCommands = [
1342
1396
  ];
1343
1397
 
1344
1398
  // src/ui/Sidebar.tsx
1345
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1399
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1346
1400
  function Sidebar() {
1347
- return /* @__PURE__ */ jsxs11(
1348
- Box12,
1401
+ return /* @__PURE__ */ jsxs12(
1402
+ Box13,
1349
1403
  {
1350
1404
  backgroundColor: "#14171E",
1351
1405
  width: 30,
@@ -1354,16 +1408,16 @@ function Sidebar() {
1354
1408
  flexDirection: "column",
1355
1409
  justifyContent: "space-between",
1356
1410
  children: [
1357
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1358
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1359
- /* @__PURE__ */ jsx10(Steps, {})
1411
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1412
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1413
+ /* @__PURE__ */ jsx11(Steps, {})
1360
1414
  ] }),
1361
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1362
- /* @__PURE__ */ jsx10(Progress, {}),
1363
- /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1364
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1365
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1366
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
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 })
1367
1421
  ] });
1368
1422
  }) })
1369
1423
  ] })
@@ -1373,12 +1427,12 @@ function Sidebar() {
1373
1427
  }
1374
1428
 
1375
1429
  // src/ui/Ribbon.tsx
1376
- import { Box as Box13, Text as Text13 } from "ink";
1377
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1430
+ import { Box as Box14, Text as Text14 } from "ink";
1431
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1378
1432
  function Ribbon() {
1379
1433
  const firstCommand = sidebarCommands[0];
1380
- return /* @__PURE__ */ jsxs12(
1381
- Box13,
1434
+ return /* @__PURE__ */ jsxs13(
1435
+ Box14,
1382
1436
  {
1383
1437
  backgroundColor: "#14171E",
1384
1438
  flexDirection: "row",
@@ -1386,11 +1440,11 @@ function Ribbon() {
1386
1440
  paddingX: 2,
1387
1441
  paddingY: 1,
1388
1442
  children: [
1389
- /* @__PURE__ */ jsx11(Progress, {}),
1390
- /* @__PURE__ */ jsx11(CurrentStep, {}),
1391
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1392
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1393
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
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 })
1394
1448
  ] })
1395
1449
  ]
1396
1450
  }
@@ -1401,8 +1455,8 @@ function Ribbon() {
1401
1455
  import { useState as useState6 } from "react";
1402
1456
 
1403
1457
  // src/ui/Logs.tsx
1404
- import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1405
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
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";
1406
1460
  var KIND_COLOR = {
1407
1461
  tool: COLORS.primary,
1408
1462
  prompt: COLORS.badge
@@ -1433,14 +1487,14 @@ function formatTimestamp(ms) {
1433
1487
  function Logs() {
1434
1488
  const logs = useWizard((s) => s.logs);
1435
1489
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1436
- useInput5((_input, key) => {
1490
+ useInput6((_input, key) => {
1437
1491
  if (key.upArrow) scroll.scrollBy(-1);
1438
1492
  else if (key.downArrow) scroll.scrollBy(1);
1439
1493
  });
1440
1494
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1441
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1442
- logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1443
- /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
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) => {
1444
1498
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1445
1499
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1446
1500
  const rawPreview = rawInputText(entry.input);
@@ -1450,14 +1504,14 @@ function Logs() {
1450
1504
  const name = truncate2(entry.name, budget);
1451
1505
  budget -= name.length;
1452
1506
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1453
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1454
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1455
- /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1456
- preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1457
- durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
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 })
1458
1512
  ] }, entry.id);
1459
1513
  }) }),
1460
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1514
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1461
1515
  ] });
1462
1516
  }
1463
1517
 
@@ -1649,7 +1703,7 @@ function track(event, payload) {
1649
1703
  }
1650
1704
 
1651
1705
  // src/ui/App.tsx
1652
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1706
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1653
1707
  function App() {
1654
1708
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1655
1709
  const { exit } = useApp();
@@ -1657,7 +1711,7 @@ function App() {
1657
1711
  const [showLogs, setShowLogs] = useState6(false);
1658
1712
  const finished = phase === "done" || phase === "error";
1659
1713
  const currentStep = steps[currentStepIndex];
1660
- useInput6(
1714
+ useInput7(
1661
1715
  (_input, key) => {
1662
1716
  if (key.return) {
1663
1717
  exit();
@@ -1665,7 +1719,7 @@ function App() {
1665
1719
  },
1666
1720
  { isActive: finished }
1667
1721
  );
1668
- useInput6((_input, key) => {
1722
+ useInput7((_input, key) => {
1669
1723
  if (phase === "idle" || phase === "authenticating") return;
1670
1724
  if (key.tab) {
1671
1725
  setShowLogs(!showLogs);
@@ -1676,8 +1730,8 @@ function App() {
1676
1730
  });
1677
1731
  }
1678
1732
  });
1679
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1680
- useInput6((_input, key) => {
1733
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1734
+ useInput7((_input, key) => {
1681
1735
  if (escOwnedElsewhere) return;
1682
1736
  if (key.escape) {
1683
1737
  track("AI Wizard Interaction", {
@@ -1696,8 +1750,8 @@ function App() {
1696
1750
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1697
1751
  the whole screen, and the scrolling throws off its cursor arithmetic —
1698
1752
  flicker and leftover rows. */
1699
- /* @__PURE__ */ jsxs14(
1700
- Box15,
1753
+ /* @__PURE__ */ jsxs15(
1754
+ Box16,
1701
1755
  {
1702
1756
  backgroundColor: COLORS.bg.main,
1703
1757
  flexDirection: "row",
@@ -1705,16 +1759,16 @@ function App() {
1705
1759
  height: scrollsPastViewport ? void 0 : rows,
1706
1760
  overflow: scrollsPastViewport ? "visible" : "hidden",
1707
1761
  children: [
1708
- mainWindowVisible && /* @__PURE__ */ jsxs14(
1709
- Box15,
1762
+ mainWindowVisible && /* @__PURE__ */ jsxs15(
1763
+ Box16,
1710
1764
  {
1711
1765
  flexDirection,
1712
1766
  width: "100%",
1713
1767
  maxHeight: rows,
1714
1768
  justifyContent: "space-between",
1715
1769
  children: [
1716
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1717
- Box15,
1770
+ showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1771
+ Box16,
1718
1772
  {
1719
1773
  flexDirection: "column",
1720
1774
  paddingX: 4,
@@ -1722,26 +1776,26 @@ function App() {
1722
1776
  width: showSidebar ? 70 : "100%",
1723
1777
  flexGrow: 1,
1724
1778
  children: [
1725
- phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1726
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1727
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
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." })
1728
1782
  ] }),
1729
- /* @__PURE__ */ jsx13(CliOutput, {}),
1730
- /* @__PURE__ */ jsx13(Notices, {}),
1731
- /* @__PURE__ */ jsx13(PromptInput, {}),
1732
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1733
- phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
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: [
1734
1788
  "\u2716 ",
1735
1789
  error
1736
1790
  ] }) })
1737
1791
  ]
1738
1792
  }
1739
1793
  ),
1740
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1794
+ showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1741
1795
  ]
1742
1796
  }
1743
1797
  ),
1744
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1798
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1745
1799
  ]
1746
1800
  }
1747
1801
  )
@@ -1821,7 +1875,7 @@ async function ensureConsent() {
1821
1875
  if (config.aiConsent) return;
1822
1876
  const store = useWizard.getState();
1823
1877
  const answer = await store.requestUserInput({
1824
- prompt: "Wizard will make AI-authored changes to this repository.",
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.",
1825
1879
  promptType: "enterToContinue",
1826
1880
  options: []
1827
1881
  });
@@ -2103,7 +2157,7 @@ async function ensureApplication() {
2103
2157
  }
2104
2158
 
2105
2159
  // src/workflows/default.ts
2106
- import { z as z27 } from "zod";
2160
+ import { z as z28 } from "zod";
2107
2161
 
2108
2162
  // src/actions/listIndices.ts
2109
2163
  import { z as z5 } from "zod";
@@ -2363,22 +2417,48 @@ function writeFileTool(ctx) {
2363
2417
 
2364
2418
  // src/lib/tools/writeAlgoliaCredentials.ts
2365
2419
  import { tool as tool6 } from "ai";
2366
- import z12 from "zod";
2420
+ import z13 from "zod";
2367
2421
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2368
2422
  import { dirname as dirname5 } from "node:path";
2369
2423
 
2370
2424
  // src/lib/algoliaApiKey.ts
2371
- import { z as z11 } from "zod";
2425
+ import { z as z12 } from "zod";
2372
2426
 
2373
2427
  // src/lib/keychain.ts
2374
- import { getPassword, setPassword } from "cross-keychain";
2428
+ import { deletePassword, getPassword, setPassword } from "cross-keychain";
2429
+ import { z as z11 } from "zod";
2375
2430
  var SERVICE = "algolia-wizard";
2376
- function account(kind, index, appId) {
2431
+ var ACCOUNT = "api-keys";
2432
+ var storedKeysSchema = z11.record(z11.string(), z11.string());
2433
+ function entryId(kind, index, appId) {
2377
2434
  return `${kind}:${appId}:${index}`;
2378
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
+ }
2379
2459
  async function readStoredKey(kind, index, appId) {
2380
2460
  try {
2381
- return await getPassword(SERVICE, account(kind, index, appId));
2461
+ return (await loadKeys())[entryId(kind, index, appId)] ?? null;
2382
2462
  } catch (err) {
2383
2463
  logger.warn(
2384
2464
  { err: err.message, kind, index, appId },
@@ -2387,19 +2467,40 @@ async function readStoredKey(kind, index, appId) {
2387
2467
  return null;
2388
2468
  }
2389
2469
  }
2390
- async function storeKey(kind, index, appId, value) {
2391
- const name = account(kind, index, appId);
2392
- try {
2393
- await setPassword(SERVICE, name, value);
2394
- if (await getPassword(SERVICE, name) !== value) {
2395
- throw new Error("the keychain did not store the value");
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
+ );
2396
2488
  }
2397
- } catch (err) {
2398
- logger.warn(
2399
- { err: err.message, kind, index, appId },
2400
- "could not store the API key in the keychain; the next run will create another"
2401
- );
2402
- }
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
+ });
2403
2504
  }
2404
2505
 
2405
2506
  // src/lib/algoliaApiKey.ts
@@ -2410,27 +2511,24 @@ var WRITE_ACLS = [
2410
2511
  "editSettings",
2411
2512
  "listIndexes"
2412
2513
  ];
2413
- var createdKeySchema = z11.object({
2414
- key: z11.string().min(1).optional(),
2415
- value: z11.string().min(1).optional()
2514
+ var createdKeySchema = z12.object({
2515
+ key: z12.string().min(1).optional(),
2516
+ value: z12.string().min(1).optional()
2416
2517
  }).transform((o) => o.key ?? o.value);
2417
2518
  async function createKey(index, acls, description) {
2418
2519
  logger.info({ index, acls }, "creating an API key");
2419
- const stdout = await runAlgoliaCli(
2420
- [
2421
- "apikeys",
2422
- "create",
2423
- "--acl",
2424
- acls.join(","),
2425
- "--indices",
2426
- index,
2427
- "--description",
2428
- description,
2429
- "-o",
2430
- "json"
2431
- ],
2432
- { withoutAdminKey: true }
2433
- );
2520
+ const stdout = await runAlgoliaCli([
2521
+ "apikeys",
2522
+ "create",
2523
+ "--acl",
2524
+ acls.join(","),
2525
+ "--indices",
2526
+ index,
2527
+ "--description",
2528
+ description,
2529
+ "-o",
2530
+ "json"
2531
+ ]);
2434
2532
  let payload;
2435
2533
  try {
2436
2534
  payload = JSON.parse(stdout);
@@ -2441,9 +2539,18 @@ async function createKey(index, acls, description) {
2441
2539
  if (!created) throw new Error("apikeys create returned no key value");
2442
2540
  return created;
2443
2541
  }
2542
+ async function keyExists(key) {
2543
+ try {
2544
+ await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
2545
+ return true;
2546
+ } catch (err) {
2547
+ return !/does not exist/i.test(err.message);
2548
+ }
2549
+ }
2444
2550
  var resolved = /* @__PURE__ */ new Map();
2445
- function forgetResolvedKeys() {
2551
+ async function forgetResolvedKeys() {
2446
2552
  resolved.clear();
2553
+ await deleteStoredKeys();
2447
2554
  }
2448
2555
  function resolveKey(kind, index, appId, acls, description) {
2449
2556
  const cacheKey = `${kind}:${appId}:${index}`;
@@ -2461,8 +2568,14 @@ function resolveKey(kind, index, appId, acls, description) {
2461
2568
  async function provisionKey(kind, index, appId, acls, description) {
2462
2569
  const stored = await readStoredKey(kind, index, appId);
2463
2570
  if (stored) {
2464
- logger.info({ kind, index, appId }, "reusing the stored API key");
2465
- return { key: stored, source: "keychain" };
2571
+ if (await keyExists(stored)) {
2572
+ logger.info({ kind, index, appId }, "reusing the stored API key");
2573
+ return { key: stored, source: "keychain" };
2574
+ }
2575
+ logger.info(
2576
+ { kind, index, appId },
2577
+ "the stored API key no longer exists; creating another"
2578
+ );
2466
2579
  }
2467
2580
  const key = await createKey(index, acls, description);
2468
2581
  await storeKey(kind, index, appId, key);
@@ -2511,8 +2624,8 @@ function upsertEnv(content, name, value) {
2511
2624
  function writeCredentialsTool(ctx) {
2512
2625
  return tool6({
2513
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.`,
2514
- inputSchema: z12.object({
2515
- filePath: z12.string().describe(
2627
+ inputSchema: z13.object({
2628
+ filePath: z13.string().describe(
2516
2629
  'Path to the env file to write credentials into (e.g. ".env")'
2517
2630
  )
2518
2631
  }),
@@ -2572,15 +2685,23 @@ function writeCredentialsTool(ctx) {
2572
2685
 
2573
2686
  // src/lib/tools/searchFiles.ts
2574
2687
  import { tool as tool7 } from "ai";
2575
- import z13 from "zod";
2688
+ import z14 from "zod";
2576
2689
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2577
2690
  import { join as join7 } from "node:path";
2578
2691
  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
+ ]);
2579
2701
  async function walkFiles(dir) {
2580
- const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2581
2702
  const out = [];
2582
2703
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2583
- if (e.name.startsWith(".") || skip.has(e.name)) continue;
2704
+ if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2584
2705
  const full = join7(dir, e.name);
2585
2706
  if (e.isDirectory()) out.push(...await walkFiles(full));
2586
2707
  else if (e.isFile()) out.push(full);
@@ -2590,9 +2711,9 @@ async function walkFiles(dir) {
2590
2711
  function searchFilesTool(ctx) {
2591
2712
  return tool7({
2592
2713
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2593
- inputSchema: z13.object({
2594
- query: z13.string().describe("JavaScript RegExp pattern to search for"),
2595
- path: z13.string().optional().describe("Directory to search in (default: cwd)")
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)")
2596
2717
  }),
2597
2718
  execute: async ({ query, path = "." }) => {
2598
2719
  logger.info({ query, path }, "called searchFiles tool");
@@ -2634,92 +2755,188 @@ function searchFilesTool(ctx) {
2634
2755
  });
2635
2756
  }
2636
2757
 
2637
- // src/lib/tools/verifyImplementation.ts
2758
+ // src/lib/tools/runShell.ts
2638
2759
  import { tool as tool8 } from "ai";
2639
- import z14 from "zod";
2760
+ import z15 from "zod";
2761
+ import { relative as relative2 } from "node:path";
2640
2762
 
2641
- // src/lib/tools/utils/runCommand.ts
2763
+ // src/lib/tools/utils/runShell.ts
2642
2764
  import { spawn as spawn2 } from "node:child_process";
2643
- function runCommand(command, args, cwd) {
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();
2644
2815
  return new Promise((resolve4) => {
2645
2816
  let output = "";
2646
- const child = spawn2(command, args, {
2647
- cwd,
2648
- stdio: ["ignore", "pipe", "pipe"]
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 }
2649
2824
  });
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);
2650
2845
  child.stdout?.on("data", (d) => output += d);
2651
2846
  child.stderr?.on("data", (d) => output += d);
2652
- child.on(
2653
- "error",
2654
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2655
- );
2656
- child.on("close", (code) => resolve4({ code: code ?? 1, output }));
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));
2657
2852
  });
2658
2853
  }
2659
2854
 
2660
- // src/lib/tools/utils/packageManager.ts
2661
- import { readFile as readFile6 } from "node:fs/promises";
2662
- import { existsSync } from "node:fs";
2663
- import { join as join8 } from "node:path";
2664
- var LOCKFILES = [
2665
- ["pnpm-lock.yaml", "pnpm"],
2666
- ["yarn.lock", "yarn"],
2667
- ["bun.lockb", "bun"],
2668
- ["bun.lock", "bun"],
2669
- ["package-lock.json", "npm"]
2670
- ];
2671
- async function readPackageJson(cwd = process.cwd()) {
2672
- return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2673
- }
2674
- function packageManagerFrom(pkg) {
2675
- return pkg.packageManager?.split("@")[0] ?? "npm";
2676
- }
2677
- function packageManagerFromLockfile(cwd) {
2678
- return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2679
- }
2680
- async function detectPackageManager(cwd) {
2681
- try {
2682
- const pkg = await readPackageJson(cwd);
2683
- if (pkg.packageManager) return packageManagerFrom(pkg);
2684
- } catch {
2685
- }
2686
- return packageManagerFromLockfile(cwd) ?? "npm";
2687
- }
2688
-
2689
- // src/lib/tools/repoVerification.ts
2690
- var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2691
- async function runRepoVerificationCheck() {
2692
- let pkg;
2693
- try {
2694
- pkg = await readPackageJson();
2695
- } catch (err) {
2696
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2697
- return { ok: false, checks: [], limitation };
2698
- }
2699
- const scripts = pkg.scripts ?? {};
2700
- const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2701
- if (present.length === 0) {
2702
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2703
- return { ok: false, checks: [], limitation };
2704
- }
2705
- const pm = await detectPackageManager(process.cwd());
2706
- const checks = [];
2707
- for (const script of present) {
2708
- const command = `${pm} run ${script}`;
2709
- const { code, output } = await runCommand(pm, ["run", script]);
2710
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2711
- }
2712
- return { ok: checks.every((c) => c.ok), checks };
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
+ };
2713
2870
  }
2714
-
2715
- // src/lib/tools/verifyImplementation.ts
2716
- function verifyImplementationTool() {
2871
+ function runShellTool(ctx) {
2717
2872
  return tool8({
2718
- 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.",
2719
- inputSchema: z14.object(),
2720
- execute: async () => {
2721
- logger.info("called verifyImplementation tool");
2722
- return runRepoVerificationCheck();
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.",
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
+ };
2723
2940
  }
2724
2941
  });
2725
2942
  }
@@ -2730,7 +2947,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2730
2947
  import { nanoid as nanoid2 } from "nanoid";
2731
2948
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2732
2949
  import { dirname as dirname6 } from "node:path";
2733
- import z15 from "zod";
2950
+ import z16 from "zod";
2734
2951
  var DATA_DIR = ".algolia-wizard/data";
2735
2952
  var RECORD_MODEL = "claude-haiku-4-5";
2736
2953
  var MAX_RECORDS = 100;
@@ -2742,17 +2959,17 @@ var anthropic = createAnthropic({
2742
2959
  function generateRecordTool(ctx) {
2743
2960
  return tool9({
2744
2961
  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.",
2745
- inputSchema: z15.object({
2746
- entityName: z15.string().describe("Name of the entity to generate records for."),
2747
- attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2748
- count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2749
- hint: z15.string().optional().describe("Optional context to steer realistic values.")
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.")
2750
2967
  }),
2751
2968
  execute: async ({ entityName, attributes, count, hint }) => {
2752
2969
  logger.info({ entityName, count }, "called generateRecord tool");
2753
2970
  try {
2754
- const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2755
- const recordSchema = z15.object(
2971
+ const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
2972
+ const recordSchema = z16.object(
2756
2973
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2757
2974
  );
2758
2975
  const generateBatch = async (batchCount) => {
@@ -2762,8 +2979,8 @@ function generateRecordTool(ctx) {
2762
2979
  const { output } = await generateText({
2763
2980
  model: anthropic(RECORD_MODEL),
2764
2981
  output: Output.object({
2765
- schema: z15.object({
2766
- records: z15.array(recordSchema).length(batchCount)
2982
+ schema: z16.object({
2983
+ records: z16.array(recordSchema).length(batchCount)
2767
2984
  })
2768
2985
  }),
2769
2986
  prompt: [
@@ -2810,7 +3027,7 @@ function generateRecordTool(ctx) {
2810
3027
  return {
2811
3028
  filePath: relPath,
2812
3029
  count: records.length,
2813
- 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.`
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.`
2814
3031
  };
2815
3032
  } catch (err) {
2816
3033
  return `Error generating records: ${err.message}`;
@@ -2821,12 +3038,12 @@ function generateRecordTool(ctx) {
2821
3038
 
2822
3039
  // src/lib/tools/notifyUser.ts
2823
3040
  import { tool as tool10 } from "ai";
2824
- import z16 from "zod";
3041
+ import z17 from "zod";
2825
3042
  function notifyUserTool() {
2826
3043
  return tool10({
2827
3044
  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.`,
2828
- inputSchema: z16.object({
2829
- message: z16.string().describe(
3045
+ inputSchema: z17.object({
3046
+ message: z17.string().describe(
2830
3047
  "Short, plain-language description of what you are doing now."
2831
3048
  )
2832
3049
  }),
@@ -2838,22 +3055,6 @@ function notifyUserTool() {
2838
3055
  });
2839
3056
  }
2840
3057
 
2841
- // src/lib/tools/context.ts
2842
- var DEFAULT_TOOL_LIMITS = {
2843
- list: 10,
2844
- search: 10,
2845
- read: 20,
2846
- match: 100
2847
- };
2848
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2849
- return {
2850
- root: cwd,
2851
- cwd,
2852
- limits,
2853
- counts: { list: 0, search: 0, read: 0 }
2854
- };
2855
- }
2856
-
2857
3058
  // src/lib/tools/index.ts
2858
3059
  function withLogging(name, def) {
2859
3060
  const execute = def.execute;
@@ -2885,10 +3086,7 @@ function createTools(ctx, { output, tools }) {
2885
3086
  writeCredentialsTool(ctx)
2886
3087
  ),
2887
3088
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2888
- verifyImplementation: withLogging(
2889
- "verifyImplementation",
2890
- verifyImplementationTool()
2891
- ),
3089
+ runShell: withLogging("runShell", runShellTool(ctx)),
2892
3090
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2893
3091
  notifyUser: withLogging("notifyUser", notifyUserTool())
2894
3092
  };
@@ -2923,7 +3121,7 @@ async function runAgent(req) {
2923
3121
  baseURL: PROXY_BASE_URL,
2924
3122
  fetch: proxyFetch
2925
3123
  });
2926
- const toolContext = createToolContext();
3124
+ const toolContext = req.toolContext ?? createToolContext();
2927
3125
  const readTools = ["readFile", "searchFiles", "listFiles"];
2928
3126
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
2929
3127
  const instructions = [
@@ -2988,7 +3186,11 @@ async function runAgent(req) {
2988
3186
  "runAgent finished"
2989
3187
  );
2990
3188
  logger.info(
2991
- { counts: toolContext.counts, limits: toolContext.limits },
3189
+ {
3190
+ counts: toolContext.counts,
3191
+ limits: toolContext.limits,
3192
+ commandsRun: toolContext.shell.executions.length
3193
+ },
2992
3194
  "tool usage"
2993
3195
  );
2994
3196
  const toolResults = await stream.toolResults;
@@ -3006,16 +3208,16 @@ async function runAgent(req) {
3006
3208
  }
3007
3209
 
3008
3210
  // src/actions/detectLanguage.ts
3009
- import z19 from "zod";
3010
- var detectLanguageSchema = z19.object({
3011
- languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
3012
- frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
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() }))
3013
3215
  });
3014
3216
  var detectLanguage = () => runAgent({
3015
3217
  instructions: [
3016
3218
  "Analyze the codebase and determine the programming languages and frameworks used",
3017
- "If a superset language is found, exclude the subset language. TS-over-JS.",
3018
- "If a meta-framework is used, exclude the framework. Next-over-React.",
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).",
3019
3221
  "Return the exact version",
3020
3222
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3021
3223
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3027,31 +3229,31 @@ var detectLanguage = () => runAgent({
3027
3229
  });
3028
3230
 
3029
3231
  // src/actions/analyzeCodebase.ts
3030
- import z20 from "zod";
3232
+ import z21 from "zod";
3031
3233
  var READONLY_TOOLS = [
3032
3234
  "listFiles",
3033
3235
  "changeDirectory",
3034
3236
  "readFile",
3035
3237
  "searchFiles"
3036
3238
  ];
3037
- var ingestionAnalysisSchema = z20.object({
3038
- ingestionAnalysis: z20.array(
3039
- z20.object({
3040
- name: z20.string(),
3041
- paths: z20.array(z20.string()),
3239
+ var ingestionAnalysisSchema = z21.object({
3240
+ ingestionAnalysis: z21.array(
3241
+ z21.object({
3242
+ name: z21.string(),
3243
+ paths: z21.array(z21.string()),
3042
3244
  // indexable fields the agent found for this entity
3043
- attributes: z20.array(z20.string())
3245
+ attributes: z21.array(z21.string())
3044
3246
  })
3045
3247
  )
3046
3248
  });
3047
- var searchImplementationAnalysisSchema = z20.object({
3048
- searchImplementationAnalysis: z20.string()
3249
+ var searchImplementationAnalysisSchema = z21.object({
3250
+ searchImplementationAnalysis: z21.string()
3049
3251
  });
3050
- var verificationSchema = z20.object({
3051
- verification: z20.array(z20.string())
3252
+ var verificationSchema = z21.object({
3253
+ verification: z21.array(z21.string())
3052
3254
  });
3053
3255
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3054
- var analyzeCodebaseSchema = z20.object({
3256
+ var analyzeCodebaseSchema = z21.object({
3055
3257
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3056
3258
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3057
3259
  verification: verificationSchema.shape.verification.optional(),
@@ -3113,7 +3315,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3113
3315
  // package.json
3114
3316
  var package_default = {
3115
3317
  name: "@algolia/wizard",
3116
- version: "0.9.0-rc.88.85",
3318
+ version: "0.9.0-rc.93.108",
3117
3319
  description: "Magically implement Algolia functionality in your codebase",
3118
3320
  type: "module",
3119
3321
  engines: {
@@ -3232,8 +3434,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
3232
3434
  }
3233
3435
 
3234
3436
  // src/actions/confirmLanguage.ts
3235
- import z22 from "zod";
3236
- var confirmLanguageSchema = z22.object({
3437
+ import z23 from "zod";
3438
+ var confirmLanguageSchema = z23.object({
3237
3439
  languages: detectLanguageSchema.shape.languages
3238
3440
  });
3239
3441
  async function confirmLanguage(ctx) {
@@ -3254,17 +3456,19 @@ async function confirmLanguage(ctx) {
3254
3456
  }
3255
3457
 
3256
3458
  // src/actions/confirmFramework.ts
3257
- import z23 from "zod";
3258
- var confirmFrameworkSchema = z23.object({
3459
+ import z24 from "zod";
3460
+ var confirmFrameworkSchema = z24.object({
3259
3461
  frameworks: detectLanguageSchema.shape.frameworks
3260
3462
  });
3261
3463
  var CURATED_FRAMEWORKS = [
3262
3464
  "Next.js",
3263
3465
  "React",
3264
3466
  "Vue",
3265
- "Angular",
3266
- "Svelte",
3267
- "Vanilla JS"
3467
+ "Vanilla JS",
3468
+ "Django",
3469
+ "Laravel",
3470
+ "Rails",
3471
+ "Symfony"
3268
3472
  ];
3269
3473
  var OTHER_OPTION = "Other";
3270
3474
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3277,12 +3481,15 @@ var FRAMEWORK_ALIASES = {
3277
3481
  vuejs: "vue",
3278
3482
  angular: "angular",
3279
3483
  angularjs: "angular",
3280
- svelte: "svelte",
3281
- sveltekit: "svelte",
3282
3484
  vanillajs: "vanillajs",
3283
3485
  vanilla: "vanillajs",
3284
3486
  javascript: "vanillajs",
3285
- js: "vanillajs"
3487
+ js: "vanillajs",
3488
+ django: "django",
3489
+ laravel: "laravel",
3490
+ rails: "rails",
3491
+ rubyonrails: "rails",
3492
+ symfony: "symfony"
3286
3493
  };
3287
3494
  var isSameFramework = (a, b) => {
3288
3495
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3383,8 +3590,8 @@ async function promptUser(ctx, params) {
3383
3590
  }
3384
3591
 
3385
3592
  // src/actions/confirmEntities.ts
3386
- import z24 from "zod";
3387
- var confirmEntitiesSchema = z24.object({
3593
+ import z25 from "zod";
3594
+ var confirmEntitiesSchema = z25.object({
3388
3595
  // Final detection — the focused re-run may supersede project-scan's.
3389
3596
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3390
3597
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3454,15 +3661,15 @@ async function confirmEntities(ctx) {
3454
3661
  }
3455
3662
 
3456
3663
  // src/actions/review.ts
3457
- import { z as z25 } from "zod";
3458
- var reviewSchema = z25.object({
3664
+ import { z as z26 } from "zod";
3665
+ var reviewSchema = z26.object({
3459
3666
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3460
3667
  // not one entry per workflow step — a step's raw output can be a long,
3461
3668
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3462
3669
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3463
- summaryPoints: z25.array(z25.string()),
3464
- reviewPrompt: z25.string(),
3465
- nextSteps: z25.array(z25.string())
3670
+ summaryPoints: z26.array(z26.string()),
3671
+ reviewPrompt: z26.string(),
3672
+ nextSteps: z26.array(z26.string())
3466
3673
  });
3467
3674
  function formatCompletedSteps(steps) {
3468
3675
  if (!steps.length) return "(no prior steps completed)";
@@ -3513,19 +3720,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3513
3720
  };
3514
3721
 
3515
3722
  // src/actions/implement.ts
3516
- import z26 from "zod";
3723
+ import z27 from "zod";
3517
3724
 
3518
3725
  // src/lib/worktree.ts
3519
- import { execFile, spawn as spawn3 } from "node:child_process";
3520
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3521
- import {
3522
- basename as basename2,
3523
- dirname as dirname7,
3524
- isAbsolute as isAbsolute2,
3525
- join as join9,
3526
- relative as relative2,
3527
- resolve as resolve3
3528
- } from "node:path";
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";
3529
3729
  var MAX_BUFFER = 32 * 1024 * 1024;
3530
3730
  var MAX_WIZARD_WORKTREES = 3;
3531
3731
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3556,7 +3756,7 @@ async function isWorkingTreeDirty(repoRoot) {
3556
3756
  return out.trim().length > 0;
3557
3757
  }
3558
3758
  async function pruneOldWorktrees(repoRoot) {
3559
- const dir = join9(stateDir(repoRoot), "worktrees");
3759
+ const dir = join8(stateDir(repoRoot), "worktrees");
3560
3760
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3561
3761
  for (const slug of stale) {
3562
3762
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3567,7 +3767,7 @@ async function pruneOldWorktrees(repoRoot) {
3567
3767
  "worktree",
3568
3768
  "remove",
3569
3769
  "--force",
3570
- join9(dir, slug)
3770
+ join8(dir, slug)
3571
3771
  ]);
3572
3772
  await git(["-C", repoRoot, "branch", "-D", branch]);
3573
3773
  } catch (err) {
@@ -3581,113 +3781,13 @@ async function pruneOldWorktrees(repoRoot) {
3581
3781
  async function createWorktree(repoRoot) {
3582
3782
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3583
3783
  const dirSlug = branch.replace(/\//g, "-");
3584
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3784
+ const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3585
3785
  await git(["-C", repoRoot, "worktree", "prune"]);
3586
3786
  await pruneOldWorktrees(repoRoot);
3587
3787
  await mkdir6(dirname7(path), { recursive: true });
3588
3788
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3589
3789
  return { path, branch };
3590
3790
  }
3591
- async function installWorktreeDeps(worktreePath) {
3592
- try {
3593
- await readPackageJson(worktreePath);
3594
- } catch {
3595
- return { ok: true, output: "no package.json; skipped install" };
3596
- }
3597
- const pm = await detectPackageManager(worktreePath);
3598
- return new Promise((resolve4) => {
3599
- let output = "";
3600
- const child = spawn3(pm, ["install"], {
3601
- cwd: worktreePath,
3602
- stdio: ["ignore", "pipe", "pipe"]
3603
- });
3604
- child.stdout?.on("data", (d) => output += d);
3605
- child.stderr?.on("data", (d) => output += d);
3606
- child.on(
3607
- "error",
3608
- (err) => resolve4({
3609
- ok: false,
3610
- output: `Failed to run ${pm} install: ${err.message}`
3611
- })
3612
- );
3613
- child.on(
3614
- "close",
3615
- (code) => resolve4({ ok: code === 0, output: output.trim() })
3616
- );
3617
- });
3618
- }
3619
- var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3620
- function validateIngestEntrypoint(worktreePath, entrypoint) {
3621
- if (!entrypoint || entrypoint.startsWith("-")) {
3622
- return {
3623
- ok: false,
3624
- reason: `entrypoint "${entrypoint}" is not a plain file path`
3625
- };
3626
- }
3627
- const target = resolve3(worktreePath, entrypoint);
3628
- const rel = relative2(worktreePath, target);
3629
- if (rel.startsWith("..") || isAbsolute2(rel)) {
3630
- return {
3631
- ok: false,
3632
- reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3633
- };
3634
- }
3635
- return { ok: true, target };
3636
- }
3637
- async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3638
- if (!INGEST_RUNTIMES.includes(runtime)) {
3639
- return {
3640
- ran: false,
3641
- ok: false,
3642
- output: "",
3643
- reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3644
- };
3645
- }
3646
- const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3647
- if (!validated.ok) {
3648
- return { ran: false, ok: false, output: "", reason: validated.reason };
3649
- }
3650
- try {
3651
- if (!(await stat2(validated.target)).isFile()) {
3652
- return {
3653
- ran: false,
3654
- ok: false,
3655
- output: "",
3656
- reason: `entrypoint "${entrypoint}" is not a file`
3657
- };
3658
- }
3659
- } catch {
3660
- return {
3661
- ran: false,
3662
- ok: false,
3663
- output: "",
3664
- reason: `entrypoint "${entrypoint}" does not exist`
3665
- };
3666
- }
3667
- return new Promise((resolveRun) => {
3668
- let output = "";
3669
- const child = spawn3(runtime, [entrypoint], {
3670
- cwd: worktreePath,
3671
- shell: false,
3672
- stdio: ["ignore", "pipe", "pipe"],
3673
- env: { ...process.env, ...env }
3674
- });
3675
- child.stdout?.on("data", (d) => output += d);
3676
- child.stderr?.on("data", (d) => output += d);
3677
- child.on(
3678
- "error",
3679
- (err) => resolveRun({
3680
- ran: true,
3681
- ok: false,
3682
- output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3683
- })
3684
- );
3685
- child.on(
3686
- "close",
3687
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3688
- );
3689
- });
3690
- }
3691
3791
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3692
3792
  const trimmed = sourcePath.trim();
3693
3793
  if (!trimmed) {
@@ -3701,8 +3801,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3701
3801
  } catch {
3702
3802
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3703
3803
  }
3704
- const relPath = join9(ingestDir, basename2(source));
3705
- const dest = join9(worktreePath, relPath);
3804
+ const relPath = join8(ingestDir, basename2(source));
3805
+ const dest = join8(worktreePath, relPath);
3706
3806
  try {
3707
3807
  await mkdir6(dirname7(dest), { recursive: true });
3708
3808
  await copyFile(source, dest);
@@ -3720,7 +3820,7 @@ function hasEnvVar(content, name) {
3720
3820
  async function readEnvVar(worktreePath, name) {
3721
3821
  let content;
3722
3822
  try {
3723
- content = await readFile7(join9(worktreePath, ".env"), "utf8");
3823
+ content = await readFile6(join8(worktreePath, ".env"), "utf8");
3724
3824
  } catch (err) {
3725
3825
  if (err.code !== "ENOENT") throw err;
3726
3826
  return void 0;
@@ -3735,10 +3835,10 @@ async function readEnvVar(worktreePath, name) {
3735
3835
  return value;
3736
3836
  }
3737
3837
  async function writeSearchEnvValues(worktreePath, vars) {
3738
- const target = join9(worktreePath, ".env");
3838
+ const target = join8(worktreePath, ".env");
3739
3839
  let existing = "";
3740
3840
  try {
3741
- existing = await readFile7(target, "utf8");
3841
+ existing = await readFile6(target, "utf8");
3742
3842
  } catch (err) {
3743
3843
  if (err.code !== "ENOENT") throw err;
3744
3844
  }
@@ -3807,15 +3907,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3807
3907
  }
3808
3908
 
3809
3909
  // src/lib/algoliaDocs.ts
3810
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3811
- import { dirname as dirname8, join as join10 } from "node:path";
3910
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
3911
+ import { dirname as dirname8, join as join9 } from "node:path";
3812
3912
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3813
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3913
+ var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3814
3914
  function findDocsDir() {
3815
3915
  let dir = dirname8(fileURLToPath2(import.meta.url));
3816
3916
  for (; ; ) {
3817
- const candidate = join10(dir, DOCS_SUBPATH);
3818
- if (existsSync2(candidate)) return candidate;
3917
+ const candidate = join9(dir, DOCS_SUBPATH);
3918
+ if (existsSync(candidate)) return candidate;
3819
3919
  const parent = dirname8(dir);
3820
3920
  if (parent === dir) return void 0;
3821
3921
  dir = parent;
@@ -3837,7 +3937,7 @@ function loadAlgoliaDoc(language) {
3837
3937
  );
3838
3938
  return "";
3839
3939
  }
3840
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3940
+ return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3841
3941
  }
3842
3942
  function getNamedDoc(name, language) {
3843
3943
  const docsDir = findDocsDir();
@@ -3845,14 +3945,15 @@ function getNamedDoc(name, language) {
3845
3945
  logger.warn("docs/algolia-sdk not found");
3846
3946
  return "";
3847
3947
  }
3848
- const file = join10(docsDir, `${name}-${language}.md`);
3849
- if (!existsSync2(file)) {
3948
+ const file = join9(docsDir, `${name}-${language}.md`);
3949
+ if (!existsSync(file)) {
3850
3950
  logger.warn({ name, language }, "named SDK reference not found");
3851
3951
  return "";
3852
3952
  }
3853
3953
  return readFileSync(file, "utf8").trim();
3854
3954
  }
3855
3955
  function getFrameworkSpecificDoc(frameworks) {
3956
+ if (frameworks.length === 0) return "";
3856
3957
  const fw = frameworks.map((f) => f.toLowerCase());
3857
3958
  if (fw.includes("vue") || fw.includes("nuxt")) {
3858
3959
  return loadAlgoliaDoc("vue");
@@ -3860,9 +3961,6 @@ function getFrameworkSpecificDoc(frameworks) {
3860
3961
  if (fw.includes("react") || fw.includes("next.js")) {
3861
3962
  return loadAlgoliaDoc("react");
3862
3963
  }
3863
- if (fw.includes("angular")) {
3864
- return loadAlgoliaDoc("angular");
3865
- }
3866
3964
  return loadAlgoliaDoc("js");
3867
3965
  }
3868
3966
 
@@ -3872,62 +3970,63 @@ function shellQuote(value) {
3872
3970
  }
3873
3971
 
3874
3972
  // src/actions/implement.ts
3875
- var implementSchema = z26.object({
3876
- filesChanged: z26.array(z26.string()),
3877
- summary: z26.string(),
3878
- worktreePath: z26.string().optional(),
3879
- ingestCommand: z26.string().optional(),
3880
- ingestScriptRan: z26.boolean().optional(),
3881
- ingestRecordCount: z26.number().optional(),
3882
- ingestDurationMs: z26.number().optional(),
3883
- ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3884
- searchEnvVars: z26.array(
3885
- z26.object({
3886
- name: z26.string(),
3887
- value: z26.string()
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()
3888
3986
  })
3889
3987
  ).optional()
3890
3988
  });
3891
- var implementationOutputSchema = z26.object({
3892
- summary: z26.string(),
3893
- // Ingestion only: a structured pair the wizard turns into an argv, never a
3894
- // free-form command string. `runtime` is allowlisted and `entrypoint` is
3895
- // validated worktree-relative, so the agent cannot inject extra commands.
3896
- runtime: z26.enum(INGEST_RUNTIMES).optional(),
3897
- entrypoint: z26.string().optional()
3989
+ var implementationOutputSchema = z27.object({
3990
+ summary: z27.string(),
3991
+ ingestCommand: z27.string().optional()
3898
3992
  });
3899
- var verificationOutputSchema = z26.object({
3900
- summary: z26.string(),
3901
- sufficient: z26.boolean(),
3902
- additionalInstructions: z26.string().optional()
3993
+ var verificationOutputSchema = z27.object({
3994
+ summary: z27.string(),
3995
+ sufficient: z27.boolean(),
3996
+ additionalInstructions: z27.string().optional()
3903
3997
  });
3904
3998
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3905
3999
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3906
4000
  var INGEST_DIR = ".algolia-wizard";
3907
- function detectUiFramework(language) {
3908
- const names = language.frameworks.map((f) => f.name.toLowerCase());
3909
- if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3910
- if (names.some((n) => n.includes("react") || n.includes("next")))
3911
- return "React";
3912
- if (names.some((n) => n.includes("angular"))) return "Angular";
3913
- return "JavaScript";
3914
- }
3915
- function frameworksForDoc(framework) {
3916
- switch (framework) {
3917
- case "React":
3918
- return ["react"];
3919
- case "Vue":
3920
- return ["vue"];
3921
- case "Angular":
3922
- return ["angular"];
3923
- case "JavaScript":
3924
- return [];
3925
- }
4001
+ var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
4002
+ function lower(entries) {
4003
+ return entries.map((entry) => entry.name.toLowerCase());
3926
4004
  }
3927
- function publicEnvPrefix(language) {
3928
- const frameworkNames = language.frameworks.map(
3929
- (framework) => framework.name.toLowerCase()
4005
+ function isJsProject(language) {
4006
+ return lower(language.languages).some(
4007
+ (name) => JS_LANGUAGES.some((js) => name.includes(js))
3930
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"];
4027
+ }
4028
+ function publicEnvPrefix(language) {
4029
+ const frameworkNames = lower(language.frameworks);
3931
4030
  if (frameworkNames.some((name) => name.includes("next"))) {
3932
4031
  return "NEXT_PUBLIC_";
3933
4032
  }
@@ -3940,7 +4039,7 @@ function publicEnvPrefix(language) {
3940
4039
  if (frameworkNames.some((name) => name.includes("vite"))) {
3941
4040
  return "VITE_";
3942
4041
  }
3943
- return "PUBLIC_";
4042
+ return isJsProject(language) ? "PUBLIC_" : "";
3944
4043
  }
3945
4044
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
3946
4045
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
@@ -3979,7 +4078,10 @@ function baseInstructions(input) {
3979
4078
  // index-scoped keys then reject with a 403.
3980
4079
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
3981
4080
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
3982
- "Make minimal, idiomatic changes; do not touch unrelated code."
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."
3983
4085
  ];
3984
4086
  }
3985
4087
  function sourceSpecificInstructions(input) {
@@ -3999,12 +4101,21 @@ function sourceSpecificInstructions(input) {
3999
4101
  generated: [
4000
4102
  "No real data source exists; use sample records for each confirmed entity.",
4001
4103
  "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.",
4002
- "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.",
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.",
4003
4105
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4004
4106
  ]
4005
4107
  };
4006
4108
  return byLine[input.ingestionSource];
4007
4109
  }
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
+ }
4008
4119
  function ingestionInstructions(input) {
4009
4120
  return [
4010
4121
  ...input.confirmed && input.confirmed.length ? [
@@ -4012,25 +4123,30 @@ function ingestionInstructions(input) {
4012
4123
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4013
4124
  `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.`,
4014
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.`,
4015
- "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
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.",
4016
4127
  "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.",
4017
- getNamedDoc("save-records", "js"),
4018
- 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
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.',
4019
4131
  "The summary should be extremely concise.",
4020
- `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.`,
4021
4132
  ...sourceSpecificInstructions(input)
4022
4133
  ] : []
4023
4134
  ];
4024
4135
  }
4025
4136
  function searchInstructions(input) {
4026
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4137
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4027
4138
  return [
4028
4139
  "Implement an in-app Algolia search experience.",
4029
- `Build the search UI for ${input.uiFramework}.`,
4030
- "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4031
- doc,
4032
- `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 target index.`,
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.`,
4033
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.",
4034
4150
  // The key is provisioned only after verification passes, so the agent never
4035
4151
  // sees one. It must also leave .env alone: the wizard reads that file to
4036
4152
  // decide whether a key already exists, and an agent-invented value there
@@ -4039,9 +4155,8 @@ function searchInstructions(input) {
4039
4155
  // Not the agent's to rename: the wizard writes these exact names into
4040
4156
  // ".env" right after this step, so a renamed prefix would leave the code
4041
4157
  // reading a var the wizard never wrote.
4042
- `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4043
- "Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
4044
- '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.',
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.",
4045
4160
  "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."
4046
4161
  ];
4047
4162
  }
@@ -4049,11 +4164,12 @@ function verificationInstructions(input) {
4049
4164
  return [
4050
4165
  "Verify the Algolia implementation changes in the current worktree.",
4051
4166
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4052
- "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4053
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4054
- "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.",
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.",
4055
4171
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4056
- `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4172
+ `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4057
4173
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4058
4174
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4059
4175
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4074,14 +4190,15 @@ var IMPLEMENT_CONFIG = {
4074
4190
  }
4075
4191
  };
4076
4192
  var useCaseToolMap = {
4077
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4078
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4079
- verification: [
4193
+ ingestion: [
4080
4194
  ...FS_READ_TOOLS,
4081
4195
  "writeFile",
4082
- "verifyImplementation",
4196
+ "writeCredentials",
4197
+ "runShell",
4083
4198
  "notifyUser"
4084
- ]
4199
+ ],
4200
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4201
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4085
4202
  };
4086
4203
  function toolsForUseCase(useCase, ingestionSource) {
4087
4204
  const tools = useCaseToolMap[useCase];
@@ -4104,15 +4221,30 @@ function formatSummary(useCase, summary) {
4104
4221
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4105
4222
  return `${label}: ${summary}`;
4106
4223
  }
4107
- function buildIngestCommand(worktree, runtime, entrypoint) {
4108
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4109
- }
4110
4224
  function parseIngestRecordCount(output) {
4111
4225
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4112
4226
  if (!match) return void 0;
4113
4227
  const count = Number(match[1]);
4114
4228
  return Number.isFinite(count) ? count : void 0;
4115
4229
  }
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
+ }
4116
4248
  function verificationRetryInstructions(verification) {
4117
4249
  return [
4118
4250
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4191,9 +4323,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4191
4323
  const confirmed2 = normalized.confirmedEntities;
4192
4324
  const searchLocation = normalized.searchImplementationAnalysis;
4193
4325
  let appId;
4326
+ let ingestAppId;
4194
4327
  if (useCases.includes("search")) {
4195
4328
  appId = (await requireApplication()).id;
4196
4329
  }
4330
+ if (useCases.includes("ingestion")) {
4331
+ ingestAppId = appId ?? (await requireApplication()).id;
4332
+ }
4197
4333
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4198
4334
  try {
4199
4335
  process.chdir(worktree);
@@ -4230,7 +4366,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4230
4366
  ingestDir: INGEST_DIR,
4231
4367
  ingestionSource,
4232
4368
  uploadFilePath,
4233
- uiFramework: detectUiFramework(language)
4369
+ searchUiTarget: searchUiTarget(language)
4234
4370
  };
4235
4371
  const summaries = [];
4236
4372
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4253,41 +4389,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4253
4389
  }
4254
4390
  let finalSearchEnvVars = input.searchEnvVars;
4255
4391
  let agentRuns = 0;
4256
- let ingestRuntime;
4257
- let ingestEntrypoint;
4392
+ let ingestCommand;
4258
4393
  let ingestScriptRan = false;
4259
4394
  let ingestRecordCount;
4260
4395
  let ingestDurationMs;
4261
- let installFailed = false;
4262
4396
  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);
4263
4404
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4264
4405
  if (agentRuns > 0) ctx.recordStepExecution();
4265
4406
  agentRuns += 1;
4266
- const result = await runAgent({
4407
+ return runAgent({
4267
4408
  instructions: buildAgentInstructions(
4268
4409
  currentUseCase,
4269
4410
  input,
4270
4411
  extraInstructions
4271
4412
  ),
4272
4413
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4273
- outputSchema: implementationOutputSchema
4414
+ outputSchema: implementationOutputSchema,
4415
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4274
4416
  });
4275
- ctx.notify({
4276
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4277
- });
4278
- const installLogId = ctx.logStart("installWorktreeDeps", {
4279
- useCase: currentUseCase
4280
- });
4281
- const install = await installWorktreeDeps(worktree);
4282
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4283
- if (!install.ok) {
4284
- installFailed = true;
4285
- logger.warn(
4286
- { useCase: currentUseCase, output: install.output },
4287
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4288
- );
4289
- }
4290
- return result;
4291
4417
  }
4292
4418
  async function runVerificationUseCase() {
4293
4419
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4295,111 +4421,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4295
4421
  return runAgent({
4296
4422
  instructions: buildAgentInstructions("verification", input),
4297
4423
  tools: toolsForUseCase("verification"),
4298
- outputSchema: verificationOutputSchema
4424
+ outputSchema: verificationOutputSchema,
4425
+ toolContext: searchTools
4299
4426
  });
4300
4427
  }
4301
4428
  if (useCases.includes("ingestion")) {
4302
- const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4303
- summaries.push(formatSummary("ingestion", summary));
4304
- ingestRuntime = runtime;
4305
- ingestEntrypoint = entrypoint;
4306
- if (ingestRuntime && ingestEntrypoint && !installFailed) {
4307
- ctx.clearNotices();
4308
- const runNow = await ctx.requestUserInput({
4309
- prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4310
- promptType: "acceptReject",
4311
- options: ["Yes", "No"],
4312
- messages: []
4313
- }) === true;
4314
- if (runNow) {
4315
- const ingestApp = await requireApplication();
4316
- const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4317
- ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4318
- const scriptLogId = ctx.logStart("runIngestScript", {
4319
- runtime: ingestRuntime,
4320
- entrypoint: ingestEntrypoint
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
4321
4447
  });
4322
- const startedAt = Date.now();
4323
- const run2 = await runIngestScript(
4324
- worktree,
4325
- ingestRuntime,
4326
- ingestEntrypoint,
4327
- {
4328
- [APP_ID_VAR]: ingestApp.id,
4329
- [API_KEY_VAR]: writeKey,
4330
- [INDEX_NAME_VAR]: targetIndex
4331
- }
4332
- );
4333
- ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4334
- ingestScriptRan = run2.ran && run2.ok;
4335
- if (ingestScriptRan) {
4336
- ingestDurationMs = Date.now() - startedAt;
4337
- ingestRecordCount = parseIngestRecordCount(run2.output);
4338
- if (ingestRecordCount != null) {
4339
- track("AI Wizard Ingest Successful", {
4340
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4341
- record_count: ingestRecordCount,
4342
- duration_ms: ingestDurationMs
4343
- });
4344
- }
4345
- }
4346
- let summaryLine;
4347
- let outcomeMessage;
4348
- if (!run2.ran) {
4349
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4350
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4351
- logger.warn(
4352
- {
4353
- runtime: ingestRuntime,
4354
- entrypoint: ingestEntrypoint,
4355
- reason: run2.reason
4356
- },
4357
- "implement: refused to auto-run ingestion script"
4358
- );
4359
- track("Error", {
4360
- step: "Push Data",
4361
- error: `ingestion script skipped: ${run2.reason}`,
4362
- product_area: "AI Wizard"
4363
- });
4364
- } else if (run2.ok) {
4365
- const status = "Ingestion run: succeeded.";
4366
- summaryLine = run2.output ? `${status}
4367
- ${run2.output}` : status;
4368
- outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4369
- } else {
4370
- const status = "\u26A0\uFE0F Ingestion run failed:";
4371
- summaryLine = run2.output ? `${status}
4372
- ${run2.output}` : status;
4373
- outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4374
- logger.warn(
4375
- {
4376
- runtime: ingestRuntime,
4377
- entrypoint: ingestEntrypoint,
4378
- output: run2.output
4379
- },
4380
- "implement: ingestion script run failed"
4381
- );
4382
- track("Error", {
4383
- step: "Push Data",
4384
- error: run2.output || "ingestion script exited non-zero",
4385
- product_area: "AI Wizard"
4386
- });
4387
- }
4388
- summaries.push(summaryLine);
4389
- ingestOutcomeMessage = outcomeMessage;
4390
4448
  }
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
+ });
4391
4463
  }
4392
4464
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4393
- if (ingestRuntime && ingestEntrypoint) {
4394
- commandMessages.push(
4395
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4396
- );
4465
+ if (ingestCommand) {
4466
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4397
4467
  }
4398
4468
  await ctx.requestUserInput({
4399
4469
  prompt: "",
4400
4470
  promptType: "enterToContinue",
4401
4471
  options: [],
4402
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4472
+ messages: [ingestOutcomeMessage, ...commandMessages]
4403
4473
  });
4404
4474
  }
4405
4475
  if (useCases.includes("search")) {
@@ -4516,22 +4586,13 @@ ${run2.output}` : status;
4516
4586
  "implement: agent reported success but no files changed in the worktree"
4517
4587
  );
4518
4588
  }
4519
- if (installFailed) {
4520
- summaries.push(
4521
- '\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".'
4522
- );
4523
- }
4524
4589
  return {
4525
4590
  ingestionSource,
4526
4591
  filesChanged,
4527
4592
  summary: summaries.join("\n\n"),
4528
4593
  worktreePath: worktree,
4529
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4530
- ingestCommand: buildIngestCommand(
4531
- worktree,
4532
- ingestRuntime,
4533
- ingestEntrypoint
4534
- ),
4594
+ ...useCases.includes("ingestion") && ingestCommand ? {
4595
+ ingestCommand,
4535
4596
  ingestScriptRan,
4536
4597
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4537
4598
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4579,8 +4640,8 @@ var defaultWorkflow = {
4579
4640
  defineStep({
4580
4641
  id: "select-index",
4581
4642
  title: "Set up index",
4582
- outputSchema: z27.object({
4583
- selection: z27.string()
4643
+ outputSchema: z28.object({
4644
+ selection: z28.string()
4584
4645
  }),
4585
4646
  run: (ctx) => selectIndexStep(ctx)
4586
4647
  }),
@@ -4822,7 +4883,10 @@ Options:
4822
4883
  --no-telemetry Send no telemetry or analytics for this run.
4823
4884
  --reset-on-run Wipe this project's wizard state (run state, AI consent,
4824
4885
  worktrees) before starting, so the run behaves like a
4825
- first-ever run. Algolia credentials are not touched.
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.
4826
4890
  -h, --help Print this message.`;
4827
4891
  function parseCliArgs(argv) {
4828
4892
  const positionals = [];
@@ -4859,11 +4923,11 @@ function parseCliArgs(argv) {
4859
4923
 
4860
4924
  // src/lib/resetState.ts
4861
4925
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4862
- import { join as join11 } from "node:path";
4926
+ import { join as join10 } from "node:path";
4863
4927
  var KEEP = ["wizard.log"];
4864
4928
  async function resetProjectState() {
4865
4929
  const dir = stateDir();
4866
- forgetResolvedKeys();
4930
+ await forgetResolvedKeys();
4867
4931
  let entries;
4868
4932
  try {
4869
4933
  entries = await readdir4(dir);
@@ -4873,14 +4937,14 @@ async function resetProjectState() {
4873
4937
  const targets = entries.filter((name) => !KEEP.includes(name));
4874
4938
  await Promise.all(
4875
4939
  targets.map(
4876
- (name) => rm2(join11(dir, name), { recursive: true, force: true })
4940
+ (name) => rm2(join10(dir, name), { recursive: true, force: true })
4877
4941
  )
4878
4942
  );
4879
4943
  return { dir, removed: targets };
4880
4944
  }
4881
4945
 
4882
4946
  // src/main.tsx
4883
- import { jsx as jsx14 } from "react/jsx-runtime";
4947
+ import { jsx as jsx15 } from "react/jsx-runtime";
4884
4948
  async function startup() {
4885
4949
  setProjectRoot(process.cwd());
4886
4950
  let args;
@@ -4930,7 +4994,7 @@ ${formatStepList(workflow)}`);
4930
4994
  }
4931
4995
  async function run(workflow) {
4932
4996
  const store = useWizard.getState();
4933
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4997
+ const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4934
4998
  await store.waitForStart();
4935
4999
  let user = await getUser();
4936
5000
  if (!user) {