@algolia/wizard 0.9.0-rc.88.103 → 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";
@@ -622,10 +622,13 @@ function Notices() {
622
622
  }
623
623
 
624
624
  // src/ui/PromptInput.tsx
625
- 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";
626
626
  import TextInput from "ink-text-input";
627
627
  import { useState as useState5 } from "react";
628
628
 
629
+ // src/ui/CommandApproval.tsx
630
+ import { Box as Box5, Text as Text5, useInput } from "ink";
631
+
629
632
  // src/ui/NextAction.tsx
630
633
  import { Box as Box4, Text as Text4 } from "ink";
631
634
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -649,14 +652,60 @@ function NextAction({
649
652
  ] });
650
653
  }
651
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
+
652
701
  // src/ui/SelectPrompt.tsx
653
- 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";
654
703
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
655
704
 
656
705
  // src/ui/ScrollView.tsx
657
- 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";
658
707
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
659
- import { jsxs as jsxs4 } from "react/jsx-runtime";
708
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
660
709
  var INDICATOR_ROWS = 2;
661
710
  function fittedWidth(node, columns) {
662
711
  let left = 0;
@@ -726,14 +775,14 @@ function useScrollWindow({
726
775
  };
727
776
  }
728
777
  function ScrollView({ scroll, children }) {
729
- return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
730
- 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: [
731
780
  "\u2191 ",
732
781
  scroll.hiddenAbove,
733
782
  " more"
734
783
  ] }),
735
784
  children,
736
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
785
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
737
786
  "\u2193 ",
738
787
  scroll.hiddenBelow,
739
788
  " more"
@@ -742,7 +791,7 @@ function ScrollView({ scroll, children }) {
742
791
  }
743
792
 
744
793
  // src/ui/SelectPrompt.tsx
745
- import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
794
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
746
795
  var CANCEL = "cancel";
747
796
  var ARROW_WIDTH = 4;
748
797
  var COLUMN_GAP = 2;
@@ -804,7 +853,7 @@ function SelectPrompt({
804
853
  revealIndex(index);
805
854
  }, [index, revealIndex]);
806
855
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
807
- useInput((input, key) => {
856
+ useInput2((input, key) => {
808
857
  if (rows.length === 0) return;
809
858
  if (key.upArrow || input === "k") {
810
859
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -827,56 +876,56 @@ function SelectPrompt({
827
876
  }
828
877
  }
829
878
  });
830
- return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
831
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
832
- error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
833
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
834
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
835
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
836
- question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
837
- 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 })
838
887
  ] })
839
888
  ] }),
840
- /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
889
+ /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
841
890
  const i = scroll.offset + visibleIndex;
842
891
  const highlighted = i === index;
843
892
  const isCancel = i === cancelIndex;
844
893
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
845
894
  const sec = isCancel ? void 0 : secondary?.[i];
846
895
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
847
- const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
896
+ const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
848
897
  highlighted ? "\u276F " : " ",
849
898
  bullet,
850
899
  option
851
900
  ] });
852
901
  const isText = sec?.kind === "text";
853
- return /* @__PURE__ */ jsxs5(
854
- Box6,
902
+ return /* @__PURE__ */ jsxs6(
903
+ Box7,
855
904
  {
856
905
  width: isText ? "100%" : barWidth,
857
906
  paddingX: 1,
858
907
  paddingY: 1,
859
908
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
860
909
  children: [
861
- /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
862
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
863
- 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,
864
913
  {
865
914
  wrap: "truncate",
866
915
  color: highlighted ? COLORS.primary : COLORS.muted,
867
916
  children: sec.value
868
917
  }
869
918
  ) }),
870
- 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 }) })
871
920
  ]
872
921
  },
873
922
  `row-${i}`
874
923
  );
875
924
  }) }),
876
- /* @__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: [
877
926
  i > 0 ? " " : "",
878
- /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
879
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
927
+ /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
928
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
880
929
  " ",
881
930
  label
882
931
  ] })
@@ -885,23 +934,23 @@ function SelectPrompt({
885
934
  }
886
935
 
887
936
  // src/ui/PromptInput.tsx
888
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
937
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
889
938
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
890
939
  function EnterToContinuePrompt({
891
940
  question,
892
941
  messages,
893
942
  onDecide
894
943
  }) {
895
- useInput2((_input, key) => {
944
+ useInput3((_input, key) => {
896
945
  if (key.return) onDecide(true);
897
946
  else if (key.escape) onDecide(false);
898
947
  });
899
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
900
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
901
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
902
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
903
- /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
904
- /* @__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" })
905
954
  ] })
906
955
  ] });
907
956
  }
@@ -909,11 +958,11 @@ function PromptInput() {
909
958
  const { phase, inputReq, submitInput } = useWizard();
910
959
  const [draft, setDraft] = useState5("");
911
960
  if (phase === "done" || phase === "error") {
912
- 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" }) });
913
962
  }
914
963
  if (phase !== "awaitingInput" || !inputReq) return null;
915
964
  if (inputReq.promptType === "multipleChoice") {
916
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
965
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
917
966
  SelectPrompt,
918
967
  {
919
968
  question: inputReq.prompt,
@@ -930,7 +979,7 @@ function PromptInput() {
930
979
  ) });
931
980
  }
932
981
  if (inputReq.promptType === "multiSelect") {
933
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
982
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
934
983
  SelectPrompt,
935
984
  {
936
985
  multi: true,
@@ -945,7 +994,7 @@ function PromptInput() {
945
994
  ) });
946
995
  }
947
996
  if (inputReq.promptType === "notice") {
948
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
997
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
949
998
  SelectPrompt,
950
999
  {
951
1000
  question: inputReq.prompt,
@@ -956,7 +1005,7 @@ function PromptInput() {
956
1005
  ) });
957
1006
  }
958
1007
  if (inputReq.promptType === "enterToContinue") {
959
- return /* @__PURE__ */ jsx5(
1008
+ return /* @__PURE__ */ jsx6(
960
1009
  EnterToContinuePrompt,
961
1010
  {
962
1011
  question: inputReq.prompt,
@@ -965,9 +1014,12 @@ function PromptInput() {
965
1014
  }
966
1015
  );
967
1016
  }
1017
+ if (inputReq.promptType === "commandApproval" && inputReq.command) {
1018
+ return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1019
+ }
968
1020
  if (inputReq.promptType === "acceptReject") {
969
1021
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
970
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1022
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
971
1023
  SelectPrompt,
972
1024
  {
973
1025
  question: inputReq.prompt,
@@ -978,15 +1030,15 @@ function PromptInput() {
978
1030
  }
979
1031
  ) });
980
1032
  }
981
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
982
- inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
983
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
984
- /* @__PURE__ */ jsxs6(Box7, { children: [
985
- /* @__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: [
986
1038
  inputReq.prompt,
987
1039
  " "
988
1040
  ] }),
989
- /* @__PURE__ */ jsx5(
1041
+ /* @__PURE__ */ jsx6(
990
1042
  TextInput,
991
1043
  {
992
1044
  value: draft,
@@ -1004,7 +1056,7 @@ function PromptInput() {
1004
1056
  // src/ui/Welcome.tsx
1005
1057
  import { dirname as dirname2, join as join3 } from "node:path";
1006
1058
  import { fileURLToPath } from "node:url";
1007
- 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";
1008
1060
 
1009
1061
  // src/ui/copy/welcome.ts
1010
1062
  var sidebarItems = [
@@ -1017,12 +1069,12 @@ var sidebarItems = [
1017
1069
  description: "push 100 records to Algolia in seconds"
1018
1070
  },
1019
1071
  {
1020
- title: "detect your framework",
1021
- description: "React, Vue, Angular, Vanilla JS"
1072
+ title: "detect your stack",
1073
+ description: "whatever language and framework you already use"
1022
1074
  },
1023
1075
  {
1024
1076
  title: "scaffold a search UI",
1025
- description: "a styled InstantSearch component, wired into your app"
1077
+ description: "a search box and results, wired into your app"
1026
1078
  },
1027
1079
  {
1028
1080
  title: "ship it",
@@ -1032,20 +1084,20 @@ var sidebarItems = [
1032
1084
 
1033
1085
  // src/ui/Welcome.tsx
1034
1086
  import Image, { InkPictureProvider } from "ink-picture";
1035
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1087
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1036
1088
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1037
1089
  function SidebarItem({
1038
1090
  title,
1039
1091
  description
1040
1092
  }) {
1041
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1042
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1043
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1044
- /* @__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 })
1045
1097
  ] }),
1046
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1047
- /* @__PURE__ */ jsx6(Spacer, {}),
1048
- /* @__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 })
1049
1101
  ] })
1050
1102
  ] });
1051
1103
  }
@@ -1053,7 +1105,7 @@ function Welcome() {
1053
1105
  const confirmStart = useWizard((s) => s.confirmStart);
1054
1106
  const openLearnMore = useWizard((s) => s.openLearnMore);
1055
1107
  const { rows } = useWindowSize6();
1056
- useInput3((input, key) => {
1108
+ useInput4((input, key) => {
1057
1109
  if (key.return) confirmStart();
1058
1110
  else if (input === "i") openLearnMore();
1059
1111
  });
@@ -1071,16 +1123,16 @@ function Welcome() {
1071
1123
  if (rows < 30) {
1072
1124
  layout = scales["small"];
1073
1125
  }
1074
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1075
- /* @__PURE__ */ jsx6(
1076
- Box8,
1126
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1127
+ /* @__PURE__ */ jsx7(
1128
+ Box9,
1077
1129
  {
1078
1130
  paddingY: layout.main.padding.y,
1079
1131
  paddingX: layout.main.padding.x,
1080
1132
  flexDirection: "column",
1081
1133
  justifyContent: "center",
1082
- children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1083
- /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1134
+ children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1135
+ /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1084
1136
  Image,
1085
1137
  {
1086
1138
  src: IMAGE_PATH,
@@ -1091,16 +1143,16 @@ function Welcome() {
1091
1143
  protocol: "halfBlock"
1092
1144
  }
1093
1145
  ) }),
1094
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1095
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1096
- /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1097
- /* @__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" })
1098
1150
  ] })
1099
1151
  ] })
1100
1152
  }
1101
1153
  ),
1102
- /* @__PURE__ */ jsxs7(
1103
- Box8,
1154
+ /* @__PURE__ */ jsxs8(
1155
+ Box9,
1104
1156
  {
1105
1157
  backgroundColor: COLORS.bg.sidebar,
1106
1158
  width: 40,
@@ -1110,8 +1162,8 @@ function Welcome() {
1110
1162
  flexDirection: "column",
1111
1163
  justifyContent: "center",
1112
1164
  children: [
1113
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1114
- 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))
1115
1167
  ]
1116
1168
  }
1117
1169
  )
@@ -1120,7 +1172,7 @@ function Welcome() {
1120
1172
 
1121
1173
  // src/ui/LearnMore.tsx
1122
1174
  import { Fragment as Fragment2 } from "react";
1123
- 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";
1124
1176
 
1125
1177
  // src/ui/copy/learn-more.ts
1126
1178
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1128,12 +1180,17 @@ var accessItems = [
1128
1180
  {
1129
1181
  tag: "READ",
1130
1182
  title: "Project files",
1131
- 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."
1132
1184
  },
1133
1185
  {
1134
1186
  tag: "WRITE",
1135
1187
  title: "Code changes",
1136
- 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."
1137
1194
  },
1138
1195
  {
1139
1196
  tag: "NET",
@@ -1143,13 +1200,13 @@ var accessItems = [
1143
1200
  {
1144
1201
  tag: "KEY",
1145
1202
  title: "Credentials",
1146
- 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."
1147
1204
  }
1148
1205
  ];
1149
1206
  var neverItems = [
1150
1207
  "Send your source code to a model or third party",
1151
1208
  "Commit or push to git",
1152
- "Touch files outside your project directory"
1209
+ "Run a command you haven't approved"
1153
1210
  ];
1154
1211
  var policyLinks = [
1155
1212
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1157,10 +1214,11 @@ var policyLinks = [
1157
1214
  ];
1158
1215
 
1159
1216
  // src/ui/LearnMore.tsx
1160
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1217
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1161
1218
  var TAG_COLORS = {
1162
1219
  READ: COLORS.success,
1163
1220
  WRITE: COLORS.badge,
1221
+ EXEC: COLORS.danger,
1164
1222
  NET: COLORS.accent,
1165
1223
  KEY: COLORS.muted
1166
1224
  };
@@ -1173,12 +1231,12 @@ function NeverLine({
1173
1231
  }) {
1174
1232
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1175
1233
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1176
- return /* @__PURE__ */ jsxs8(Text9, { children: [
1177
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1234
+ return /* @__PURE__ */ jsxs9(Text10, { children: [
1235
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1178
1236
  " ".repeat(NEVER_BOX_PAD_X),
1179
- 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)),
1180
1238
  " ".repeat(rightPad),
1181
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1239
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1182
1240
  ] });
1183
1241
  }
1184
1242
  function LearnMore() {
@@ -1186,12 +1244,12 @@ function LearnMore() {
1186
1244
  const backToHome = useWizard((s) => s.backToHome);
1187
1245
  const { columns } = useWindowSize7();
1188
1246
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1189
- useInput4((_input, key) => {
1247
+ useInput5((_input, key) => {
1190
1248
  if (key.escape) backToHome();
1191
1249
  else if (key.return) confirmStart();
1192
1250
  });
1193
- return /* @__PURE__ */ jsxs8(
1194
- Box9,
1251
+ return /* @__PURE__ */ jsxs9(
1252
+ Box10,
1195
1253
  {
1196
1254
  flexDirection: "column",
1197
1255
  paddingX: PADDING_X,
@@ -1199,31 +1257,31 @@ function LearnMore() {
1199
1257
  width: "100%",
1200
1258
  gap: 1,
1201
1259
  children: [
1202
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1203
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1204
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1205
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1206
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1207
- /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1208
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1209
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1210
- /* @__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}` })
1211
1269
  ] }) })
1212
1270
  ] })
1213
1271
  ] }, item.tag)) }),
1214
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1215
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1216
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1217
- /* @__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(
1218
1276
  NeverLine,
1219
1277
  {
1220
1278
  width: dividerWidth,
1221
1279
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1222
1280
  }
1223
1281
  ),
1224
- neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1225
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1226
- /* @__PURE__ */ jsx7(
1282
+ neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1283
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1284
+ /* @__PURE__ */ jsx8(
1227
1285
  NeverLine,
1228
1286
  {
1229
1287
  width: dividerWidth,
@@ -1235,24 +1293,24 @@ function LearnMore() {
1235
1293
  }
1236
1294
  )
1237
1295
  ] }, item)),
1238
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1239
- /* @__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` })
1240
1298
  ] }),
1241
- /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1242
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1243
- /* @__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 })
1244
1302
  ] }, link.label)) }),
1245
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1246
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1247
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1248
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1249
- /* @__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" })
1250
1308
  ] }),
1251
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1252
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1253
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1254
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1255
- /* @__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" })
1256
1314
  ] })
1257
1315
  ] })
1258
1316
  ]
@@ -1261,10 +1319,10 @@ function LearnMore() {
1261
1319
  }
1262
1320
 
1263
1321
  // src/ui/Sidebar.tsx
1264
- import { Box as Box12, Text as Text12 } from "ink";
1322
+ import { Box as Box13, Text as Text13 } from "ink";
1265
1323
 
1266
1324
  // src/ui/Steps.tsx
1267
- import { Box as Box10, Text as Text10 } from "ink";
1325
+ import { Box as Box11, Text as Text11 } from "ink";
1268
1326
  import Spinner from "ink-spinner";
1269
1327
 
1270
1328
  // src/core/persistence.ts
@@ -1293,12 +1351,12 @@ async function clearWorkflowState(workflowId) {
1293
1351
  }
1294
1352
 
1295
1353
  // src/ui/Steps.tsx
1296
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1354
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1297
1355
  function Steps() {
1298
1356
  const { steps } = useWizard();
1299
1357
  const visibleSteps = steps.filter(isStepVisible);
1300
- 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: [
1301
- 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],
1302
1360
  " ",
1303
1361
  s.title
1304
1362
  ] }) }, s.id)) });
@@ -1307,27 +1365,27 @@ function CurrentStep() {
1307
1365
  const { steps } = useWizard();
1308
1366
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1309
1367
  if (!currentStep) return null;
1310
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1311
- /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1368
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1369
+ /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1312
1370
  " ",
1313
1371
  ` ${currentStep.title}`
1314
1372
  ] });
1315
1373
  }
1316
1374
 
1317
1375
  // src/ui/Progress.tsx
1318
- import { Box as Box11, Text as Text11 } from "ink";
1319
- 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";
1320
1378
  function Progress() {
1321
1379
  const { steps, currentStepIndex } = useWizard();
1322
1380
  const visibleSteps = steps.filter(isStepVisible);
1323
1381
  if (visibleSteps.length === 0) return null;
1324
1382
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1325
1383
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1326
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1327
- /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1328
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1329
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1330
- /* @__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 })
1331
1389
  ] });
1332
1390
  }
1333
1391
 
@@ -1338,10 +1396,10 @@ var sidebarCommands = [
1338
1396
  ];
1339
1397
 
1340
1398
  // src/ui/Sidebar.tsx
1341
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1399
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1342
1400
  function Sidebar() {
1343
- return /* @__PURE__ */ jsxs11(
1344
- Box12,
1401
+ return /* @__PURE__ */ jsxs12(
1402
+ Box13,
1345
1403
  {
1346
1404
  backgroundColor: "#14171E",
1347
1405
  width: 30,
@@ -1350,16 +1408,16 @@ function Sidebar() {
1350
1408
  flexDirection: "column",
1351
1409
  justifyContent: "space-between",
1352
1410
  children: [
1353
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1354
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1355
- /* @__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, {})
1356
1414
  ] }),
1357
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1358
- /* @__PURE__ */ jsx10(Progress, {}),
1359
- /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1360
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1361
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1362
- /* @__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 })
1363
1421
  ] });
1364
1422
  }) })
1365
1423
  ] })
@@ -1369,12 +1427,12 @@ function Sidebar() {
1369
1427
  }
1370
1428
 
1371
1429
  // src/ui/Ribbon.tsx
1372
- import { Box as Box13, Text as Text13 } from "ink";
1373
- 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";
1374
1432
  function Ribbon() {
1375
1433
  const firstCommand = sidebarCommands[0];
1376
- return /* @__PURE__ */ jsxs12(
1377
- Box13,
1434
+ return /* @__PURE__ */ jsxs13(
1435
+ Box14,
1378
1436
  {
1379
1437
  backgroundColor: "#14171E",
1380
1438
  flexDirection: "row",
@@ -1382,11 +1440,11 @@ function Ribbon() {
1382
1440
  paddingX: 2,
1383
1441
  paddingY: 1,
1384
1442
  children: [
1385
- /* @__PURE__ */ jsx11(Progress, {}),
1386
- /* @__PURE__ */ jsx11(CurrentStep, {}),
1387
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1388
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1389
- /* @__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 })
1390
1448
  ] })
1391
1449
  ]
1392
1450
  }
@@ -1397,8 +1455,8 @@ function Ribbon() {
1397
1455
  import { useState as useState6 } from "react";
1398
1456
 
1399
1457
  // src/ui/Logs.tsx
1400
- import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1401
- 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";
1402
1460
  var KIND_COLOR = {
1403
1461
  tool: COLORS.primary,
1404
1462
  prompt: COLORS.badge
@@ -1429,14 +1487,14 @@ function formatTimestamp(ms) {
1429
1487
  function Logs() {
1430
1488
  const logs = useWizard((s) => s.logs);
1431
1489
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1432
- useInput5((_input, key) => {
1490
+ useInput6((_input, key) => {
1433
1491
  if (key.upArrow) scroll.scrollBy(-1);
1434
1492
  else if (key.downArrow) scroll.scrollBy(1);
1435
1493
  });
1436
1494
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1437
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1438
- logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1439
- /* @__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) => {
1440
1498
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1441
1499
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1442
1500
  const rawPreview = rawInputText(entry.input);
@@ -1446,14 +1504,14 @@ function Logs() {
1446
1504
  const name = truncate2(entry.name, budget);
1447
1505
  budget -= name.length;
1448
1506
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1449
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1450
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1451
- /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1452
- preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1453
- 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 })
1454
1512
  ] }, entry.id);
1455
1513
  }) }),
1456
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1514
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1457
1515
  ] });
1458
1516
  }
1459
1517
 
@@ -1645,7 +1703,7 @@ function track(event, payload) {
1645
1703
  }
1646
1704
 
1647
1705
  // src/ui/App.tsx
1648
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1706
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1649
1707
  function App() {
1650
1708
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1651
1709
  const { exit } = useApp();
@@ -1653,7 +1711,7 @@ function App() {
1653
1711
  const [showLogs, setShowLogs] = useState6(false);
1654
1712
  const finished = phase === "done" || phase === "error";
1655
1713
  const currentStep = steps[currentStepIndex];
1656
- useInput6(
1714
+ useInput7(
1657
1715
  (_input, key) => {
1658
1716
  if (key.return) {
1659
1717
  exit();
@@ -1661,7 +1719,7 @@ function App() {
1661
1719
  },
1662
1720
  { isActive: finished }
1663
1721
  );
1664
- useInput6((_input, key) => {
1722
+ useInput7((_input, key) => {
1665
1723
  if (phase === "idle" || phase === "authenticating") return;
1666
1724
  if (key.tab) {
1667
1725
  setShowLogs(!showLogs);
@@ -1672,8 +1730,8 @@ function App() {
1672
1730
  });
1673
1731
  }
1674
1732
  });
1675
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1676
- useInput6((_input, key) => {
1733
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1734
+ useInput7((_input, key) => {
1677
1735
  if (escOwnedElsewhere) return;
1678
1736
  if (key.escape) {
1679
1737
  track("AI Wizard Interaction", {
@@ -1692,8 +1750,8 @@ function App() {
1692
1750
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1693
1751
  the whole screen, and the scrolling throws off its cursor arithmetic —
1694
1752
  flicker and leftover rows. */
1695
- /* @__PURE__ */ jsxs14(
1696
- Box15,
1753
+ /* @__PURE__ */ jsxs15(
1754
+ Box16,
1697
1755
  {
1698
1756
  backgroundColor: COLORS.bg.main,
1699
1757
  flexDirection: "row",
@@ -1701,16 +1759,16 @@ function App() {
1701
1759
  height: scrollsPastViewport ? void 0 : rows,
1702
1760
  overflow: scrollsPastViewport ? "visible" : "hidden",
1703
1761
  children: [
1704
- mainWindowVisible && /* @__PURE__ */ jsxs14(
1705
- Box15,
1762
+ mainWindowVisible && /* @__PURE__ */ jsxs15(
1763
+ Box16,
1706
1764
  {
1707
1765
  flexDirection,
1708
1766
  width: "100%",
1709
1767
  maxHeight: rows,
1710
1768
  justifyContent: "space-between",
1711
1769
  children: [
1712
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1713
- Box15,
1770
+ showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1771
+ Box16,
1714
1772
  {
1715
1773
  flexDirection: "column",
1716
1774
  paddingX: 4,
@@ -1718,26 +1776,26 @@ function App() {
1718
1776
  width: showSidebar ? 70 : "100%",
1719
1777
  flexGrow: 1,
1720
1778
  children: [
1721
- phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1722
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1723
- /* @__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." })
1724
1782
  ] }),
1725
- /* @__PURE__ */ jsx13(CliOutput, {}),
1726
- /* @__PURE__ */ jsx13(Notices, {}),
1727
- /* @__PURE__ */ jsx13(PromptInput, {}),
1728
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1729
- 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: [
1730
1788
  "\u2716 ",
1731
1789
  error
1732
1790
  ] }) })
1733
1791
  ]
1734
1792
  }
1735
1793
  ),
1736
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1794
+ showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1737
1795
  ]
1738
1796
  }
1739
1797
  ),
1740
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1798
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1741
1799
  ]
1742
1800
  }
1743
1801
  )
@@ -1817,7 +1875,7 @@ async function ensureConsent() {
1817
1875
  if (config.aiConsent) return;
1818
1876
  const store = useWizard.getState();
1819
1877
  const answer = await store.requestUserInput({
1820
- 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.",
1821
1879
  promptType: "enterToContinue",
1822
1880
  options: []
1823
1881
  });
@@ -2631,11 +2689,19 @@ import z14 from "zod";
2631
2689
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2632
2690
  import { join as join7 } from "node:path";
2633
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
+ ]);
2634
2701
  async function walkFiles(dir) {
2635
- const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2636
2702
  const out = [];
2637
2703
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2638
- if (e.name.startsWith(".") || skip.has(e.name)) continue;
2704
+ if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2639
2705
  const full = join7(dir, e.name);
2640
2706
  if (e.isDirectory()) out.push(...await walkFiles(full));
2641
2707
  else if (e.isFile()) out.push(full);
@@ -2689,92 +2755,188 @@ function searchFilesTool(ctx) {
2689
2755
  });
2690
2756
  }
2691
2757
 
2692
- // src/lib/tools/verifyImplementation.ts
2758
+ // src/lib/tools/runShell.ts
2693
2759
  import { tool as tool8 } from "ai";
2694
2760
  import z15 from "zod";
2761
+ import { relative as relative2 } from "node:path";
2695
2762
 
2696
- // src/lib/tools/utils/runCommand.ts
2763
+ // src/lib/tools/utils/runShell.ts
2697
2764
  import { spawn as spawn2 } from "node:child_process";
2698
- 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();
2699
2815
  return new Promise((resolve4) => {
2700
2816
  let output = "";
2701
- const child = spawn2(command, args, {
2702
- cwd,
2703
- 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 }
2704
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);
2705
2845
  child.stdout?.on("data", (d) => output += d);
2706
2846
  child.stderr?.on("data", (d) => output += d);
2707
- child.on(
2708
- "error",
2709
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2710
- );
2711
- 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));
2712
2852
  });
2713
2853
  }
2714
2854
 
2715
- // src/lib/tools/utils/packageManager.ts
2716
- import { readFile as readFile6 } from "node:fs/promises";
2717
- import { existsSync } from "node:fs";
2718
- import { join as join8 } from "node:path";
2719
- var LOCKFILES = [
2720
- ["pnpm-lock.yaml", "pnpm"],
2721
- ["yarn.lock", "yarn"],
2722
- ["bun.lockb", "bun"],
2723
- ["bun.lock", "bun"],
2724
- ["package-lock.json", "npm"]
2725
- ];
2726
- async function readPackageJson(cwd = process.cwd()) {
2727
- return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2728
- }
2729
- function packageManagerFrom(pkg) {
2730
- return pkg.packageManager?.split("@")[0] ?? "npm";
2731
- }
2732
- function packageManagerFromLockfile(cwd) {
2733
- return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2734
- }
2735
- async function detectPackageManager(cwd) {
2736
- try {
2737
- const pkg = await readPackageJson(cwd);
2738
- if (pkg.packageManager) return packageManagerFrom(pkg);
2739
- } catch {
2740
- }
2741
- return packageManagerFromLockfile(cwd) ?? "npm";
2742
- }
2743
-
2744
- // src/lib/tools/repoVerification.ts
2745
- var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2746
- async function runRepoVerificationCheck() {
2747
- let pkg;
2748
- try {
2749
- pkg = await readPackageJson();
2750
- } catch (err) {
2751
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2752
- return { ok: false, checks: [], limitation };
2753
- }
2754
- const scripts = pkg.scripts ?? {};
2755
- const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2756
- if (present.length === 0) {
2757
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2758
- return { ok: false, checks: [], limitation };
2759
- }
2760
- const pm = await detectPackageManager(process.cwd());
2761
- const checks = [];
2762
- for (const script of present) {
2763
- const command = `${pm} run ${script}`;
2764
- const { code, output } = await runCommand(pm, ["run", script]);
2765
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2766
- }
2767
- 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
+ };
2768
2870
  }
2769
-
2770
- // src/lib/tools/verifyImplementation.ts
2771
- function verifyImplementationTool() {
2871
+ function runShellTool(ctx) {
2772
2872
  return tool8({
2773
- 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.",
2774
- inputSchema: z15.object(),
2775
- execute: async () => {
2776
- logger.info("called verifyImplementation tool");
2777
- 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
+ };
2778
2940
  }
2779
2941
  });
2780
2942
  }
@@ -2865,7 +3027,7 @@ function generateRecordTool(ctx) {
2865
3027
  return {
2866
3028
  filePath: relPath,
2867
3029
  count: records.length,
2868
- 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.`
2869
3031
  };
2870
3032
  } catch (err) {
2871
3033
  return `Error generating records: ${err.message}`;
@@ -2893,22 +3055,6 @@ function notifyUserTool() {
2893
3055
  });
2894
3056
  }
2895
3057
 
2896
- // src/lib/tools/context.ts
2897
- var DEFAULT_TOOL_LIMITS = {
2898
- list: 10,
2899
- search: 10,
2900
- read: 20,
2901
- match: 100
2902
- };
2903
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2904
- return {
2905
- root: cwd,
2906
- cwd,
2907
- limits,
2908
- counts: { list: 0, search: 0, read: 0 }
2909
- };
2910
- }
2911
-
2912
3058
  // src/lib/tools/index.ts
2913
3059
  function withLogging(name, def) {
2914
3060
  const execute = def.execute;
@@ -2940,10 +3086,7 @@ function createTools(ctx, { output, tools }) {
2940
3086
  writeCredentialsTool(ctx)
2941
3087
  ),
2942
3088
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2943
- verifyImplementation: withLogging(
2944
- "verifyImplementation",
2945
- verifyImplementationTool()
2946
- ),
3089
+ runShell: withLogging("runShell", runShellTool(ctx)),
2947
3090
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2948
3091
  notifyUser: withLogging("notifyUser", notifyUserTool())
2949
3092
  };
@@ -2978,7 +3121,7 @@ async function runAgent(req) {
2978
3121
  baseURL: PROXY_BASE_URL,
2979
3122
  fetch: proxyFetch
2980
3123
  });
2981
- const toolContext = createToolContext();
3124
+ const toolContext = req.toolContext ?? createToolContext();
2982
3125
  const readTools = ["readFile", "searchFiles", "listFiles"];
2983
3126
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
2984
3127
  const instructions = [
@@ -3043,7 +3186,11 @@ async function runAgent(req) {
3043
3186
  "runAgent finished"
3044
3187
  );
3045
3188
  logger.info(
3046
- { counts: toolContext.counts, limits: toolContext.limits },
3189
+ {
3190
+ counts: toolContext.counts,
3191
+ limits: toolContext.limits,
3192
+ commandsRun: toolContext.shell.executions.length
3193
+ },
3047
3194
  "tool usage"
3048
3195
  );
3049
3196
  const toolResults = await stream.toolResults;
@@ -3069,8 +3216,8 @@ var detectLanguageSchema = z20.object({
3069
3216
  var detectLanguage = () => runAgent({
3070
3217
  instructions: [
3071
3218
  "Analyze the codebase and determine the programming languages and frameworks used",
3072
- "If a superset language is found, exclude the subset language. TS-over-JS.",
3073
- "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).",
3074
3221
  "Return the exact version",
3075
3222
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3076
3223
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3168,7 +3315,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3168
3315
  // package.json
3169
3316
  var package_default = {
3170
3317
  name: "@algolia/wizard",
3171
- version: "0.9.0-rc.88.103",
3318
+ version: "0.9.0-rc.93.108",
3172
3319
  description: "Magically implement Algolia functionality in your codebase",
3173
3320
  type: "module",
3174
3321
  engines: {
@@ -3317,9 +3464,11 @@ var CURATED_FRAMEWORKS = [
3317
3464
  "Next.js",
3318
3465
  "React",
3319
3466
  "Vue",
3320
- "Angular",
3321
- "Svelte",
3322
- "Vanilla JS"
3467
+ "Vanilla JS",
3468
+ "Django",
3469
+ "Laravel",
3470
+ "Rails",
3471
+ "Symfony"
3323
3472
  ];
3324
3473
  var OTHER_OPTION = "Other";
3325
3474
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3332,12 +3481,15 @@ var FRAMEWORK_ALIASES = {
3332
3481
  vuejs: "vue",
3333
3482
  angular: "angular",
3334
3483
  angularjs: "angular",
3335
- svelte: "svelte",
3336
- sveltekit: "svelte",
3337
3484
  vanillajs: "vanillajs",
3338
3485
  vanilla: "vanillajs",
3339
3486
  javascript: "vanillajs",
3340
- js: "vanillajs"
3487
+ js: "vanillajs",
3488
+ django: "django",
3489
+ laravel: "laravel",
3490
+ rails: "rails",
3491
+ rubyonrails: "rails",
3492
+ symfony: "symfony"
3341
3493
  };
3342
3494
  var isSameFramework = (a, b) => {
3343
3495
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3571,16 +3723,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3571
3723
  import z27 from "zod";
3572
3724
 
3573
3725
  // src/lib/worktree.ts
3574
- import { execFile, spawn as spawn3 } from "node:child_process";
3575
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3576
- import {
3577
- basename as basename2,
3578
- dirname as dirname7,
3579
- isAbsolute as isAbsolute2,
3580
- join as join9,
3581
- relative as relative2,
3582
- resolve as resolve3
3583
- } 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";
3584
3729
  var MAX_BUFFER = 32 * 1024 * 1024;
3585
3730
  var MAX_WIZARD_WORKTREES = 3;
3586
3731
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3611,7 +3756,7 @@ async function isWorkingTreeDirty(repoRoot) {
3611
3756
  return out.trim().length > 0;
3612
3757
  }
3613
3758
  async function pruneOldWorktrees(repoRoot) {
3614
- const dir = join9(stateDir(repoRoot), "worktrees");
3759
+ const dir = join8(stateDir(repoRoot), "worktrees");
3615
3760
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3616
3761
  for (const slug of stale) {
3617
3762
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3622,7 +3767,7 @@ async function pruneOldWorktrees(repoRoot) {
3622
3767
  "worktree",
3623
3768
  "remove",
3624
3769
  "--force",
3625
- join9(dir, slug)
3770
+ join8(dir, slug)
3626
3771
  ]);
3627
3772
  await git(["-C", repoRoot, "branch", "-D", branch]);
3628
3773
  } catch (err) {
@@ -3636,113 +3781,13 @@ async function pruneOldWorktrees(repoRoot) {
3636
3781
  async function createWorktree(repoRoot) {
3637
3782
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3638
3783
  const dirSlug = branch.replace(/\//g, "-");
3639
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3784
+ const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3640
3785
  await git(["-C", repoRoot, "worktree", "prune"]);
3641
3786
  await pruneOldWorktrees(repoRoot);
3642
3787
  await mkdir6(dirname7(path), { recursive: true });
3643
3788
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3644
3789
  return { path, branch };
3645
3790
  }
3646
- async function installWorktreeDeps(worktreePath) {
3647
- try {
3648
- await readPackageJson(worktreePath);
3649
- } catch {
3650
- return { ok: true, output: "no package.json; skipped install" };
3651
- }
3652
- const pm = await detectPackageManager(worktreePath);
3653
- return new Promise((resolve4) => {
3654
- let output = "";
3655
- const child = spawn3(pm, ["install"], {
3656
- cwd: worktreePath,
3657
- stdio: ["ignore", "pipe", "pipe"]
3658
- });
3659
- child.stdout?.on("data", (d) => output += d);
3660
- child.stderr?.on("data", (d) => output += d);
3661
- child.on(
3662
- "error",
3663
- (err) => resolve4({
3664
- ok: false,
3665
- output: `Failed to run ${pm} install: ${err.message}`
3666
- })
3667
- );
3668
- child.on(
3669
- "close",
3670
- (code) => resolve4({ ok: code === 0, output: output.trim() })
3671
- );
3672
- });
3673
- }
3674
- var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3675
- function validateIngestEntrypoint(worktreePath, entrypoint) {
3676
- if (!entrypoint || entrypoint.startsWith("-")) {
3677
- return {
3678
- ok: false,
3679
- reason: `entrypoint "${entrypoint}" is not a plain file path`
3680
- };
3681
- }
3682
- const target = resolve3(worktreePath, entrypoint);
3683
- const rel = relative2(worktreePath, target);
3684
- if (rel.startsWith("..") || isAbsolute2(rel)) {
3685
- return {
3686
- ok: false,
3687
- reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3688
- };
3689
- }
3690
- return { ok: true, target };
3691
- }
3692
- async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3693
- if (!INGEST_RUNTIMES.includes(runtime)) {
3694
- return {
3695
- ran: false,
3696
- ok: false,
3697
- output: "",
3698
- reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3699
- };
3700
- }
3701
- const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3702
- if (!validated.ok) {
3703
- return { ran: false, ok: false, output: "", reason: validated.reason };
3704
- }
3705
- try {
3706
- if (!(await stat2(validated.target)).isFile()) {
3707
- return {
3708
- ran: false,
3709
- ok: false,
3710
- output: "",
3711
- reason: `entrypoint "${entrypoint}" is not a file`
3712
- };
3713
- }
3714
- } catch {
3715
- return {
3716
- ran: false,
3717
- ok: false,
3718
- output: "",
3719
- reason: `entrypoint "${entrypoint}" does not exist`
3720
- };
3721
- }
3722
- return new Promise((resolveRun) => {
3723
- let output = "";
3724
- const child = spawn3(runtime, [entrypoint], {
3725
- cwd: worktreePath,
3726
- shell: false,
3727
- stdio: ["ignore", "pipe", "pipe"],
3728
- env: { ...process.env, ...env }
3729
- });
3730
- child.stdout?.on("data", (d) => output += d);
3731
- child.stderr?.on("data", (d) => output += d);
3732
- child.on(
3733
- "error",
3734
- (err) => resolveRun({
3735
- ran: true,
3736
- ok: false,
3737
- output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3738
- })
3739
- );
3740
- child.on(
3741
- "close",
3742
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3743
- );
3744
- });
3745
- }
3746
3791
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3747
3792
  const trimmed = sourcePath.trim();
3748
3793
  if (!trimmed) {
@@ -3756,8 +3801,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3756
3801
  } catch {
3757
3802
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3758
3803
  }
3759
- const relPath = join9(ingestDir, basename2(source));
3760
- const dest = join9(worktreePath, relPath);
3804
+ const relPath = join8(ingestDir, basename2(source));
3805
+ const dest = join8(worktreePath, relPath);
3761
3806
  try {
3762
3807
  await mkdir6(dirname7(dest), { recursive: true });
3763
3808
  await copyFile(source, dest);
@@ -3775,7 +3820,7 @@ function hasEnvVar(content, name) {
3775
3820
  async function readEnvVar(worktreePath, name) {
3776
3821
  let content;
3777
3822
  try {
3778
- content = await readFile7(join9(worktreePath, ".env"), "utf8");
3823
+ content = await readFile6(join8(worktreePath, ".env"), "utf8");
3779
3824
  } catch (err) {
3780
3825
  if (err.code !== "ENOENT") throw err;
3781
3826
  return void 0;
@@ -3790,10 +3835,10 @@ async function readEnvVar(worktreePath, name) {
3790
3835
  return value;
3791
3836
  }
3792
3837
  async function writeSearchEnvValues(worktreePath, vars) {
3793
- const target = join9(worktreePath, ".env");
3838
+ const target = join8(worktreePath, ".env");
3794
3839
  let existing = "";
3795
3840
  try {
3796
- existing = await readFile7(target, "utf8");
3841
+ existing = await readFile6(target, "utf8");
3797
3842
  } catch (err) {
3798
3843
  if (err.code !== "ENOENT") throw err;
3799
3844
  }
@@ -3862,15 +3907,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3862
3907
  }
3863
3908
 
3864
3909
  // src/lib/algoliaDocs.ts
3865
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3866
- 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";
3867
3912
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3868
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3913
+ var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3869
3914
  function findDocsDir() {
3870
3915
  let dir = dirname8(fileURLToPath2(import.meta.url));
3871
3916
  for (; ; ) {
3872
- const candidate = join10(dir, DOCS_SUBPATH);
3873
- if (existsSync2(candidate)) return candidate;
3917
+ const candidate = join9(dir, DOCS_SUBPATH);
3918
+ if (existsSync(candidate)) return candidate;
3874
3919
  const parent = dirname8(dir);
3875
3920
  if (parent === dir) return void 0;
3876
3921
  dir = parent;
@@ -3892,7 +3937,7 @@ function loadAlgoliaDoc(language) {
3892
3937
  );
3893
3938
  return "";
3894
3939
  }
3895
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3940
+ return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3896
3941
  }
3897
3942
  function getNamedDoc(name, language) {
3898
3943
  const docsDir = findDocsDir();
@@ -3900,14 +3945,15 @@ function getNamedDoc(name, language) {
3900
3945
  logger.warn("docs/algolia-sdk not found");
3901
3946
  return "";
3902
3947
  }
3903
- const file = join10(docsDir, `${name}-${language}.md`);
3904
- if (!existsSync2(file)) {
3948
+ const file = join9(docsDir, `${name}-${language}.md`);
3949
+ if (!existsSync(file)) {
3905
3950
  logger.warn({ name, language }, "named SDK reference not found");
3906
3951
  return "";
3907
3952
  }
3908
3953
  return readFileSync(file, "utf8").trim();
3909
3954
  }
3910
3955
  function getFrameworkSpecificDoc(frameworks) {
3956
+ if (frameworks.length === 0) return "";
3911
3957
  const fw = frameworks.map((f) => f.toLowerCase());
3912
3958
  if (fw.includes("vue") || fw.includes("nuxt")) {
3913
3959
  return loadAlgoliaDoc("vue");
@@ -3915,9 +3961,6 @@ function getFrameworkSpecificDoc(frameworks) {
3915
3961
  if (fw.includes("react") || fw.includes("next.js")) {
3916
3962
  return loadAlgoliaDoc("react");
3917
3963
  }
3918
- if (fw.includes("angular")) {
3919
- return loadAlgoliaDoc("angular");
3920
- }
3921
3964
  return loadAlgoliaDoc("js");
3922
3965
  }
3923
3966
 
@@ -3945,11 +3988,7 @@ var implementSchema = z27.object({
3945
3988
  });
3946
3989
  var implementationOutputSchema = z27.object({
3947
3990
  summary: z27.string(),
3948
- // Ingestion only: a structured pair the wizard turns into an argv, never a
3949
- // free-form command string. `runtime` is allowlisted and `entrypoint` is
3950
- // validated worktree-relative, so the agent cannot inject extra commands.
3951
- runtime: z27.enum(INGEST_RUNTIMES).optional(),
3952
- entrypoint: z27.string().optional()
3991
+ ingestCommand: z27.string().optional()
3953
3992
  });
3954
3993
  var verificationOutputSchema = z27.object({
3955
3994
  summary: z27.string(),
@@ -3959,30 +3998,35 @@ var verificationOutputSchema = z27.object({
3959
3998
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3960
3999
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3961
4000
  var INGEST_DIR = ".algolia-wizard";
3962
- function detectUiFramework(language) {
3963
- const names = language.frameworks.map((f) => f.name.toLowerCase());
3964
- if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3965
- if (names.some((n) => n.includes("react") || n.includes("next")))
3966
- return "React";
3967
- if (names.some((n) => n.includes("angular"))) return "Angular";
3968
- return "JavaScript";
3969
- }
3970
- function frameworksForDoc(framework) {
3971
- switch (framework) {
3972
- case "React":
3973
- return ["react"];
3974
- case "Vue":
3975
- return ["vue"];
3976
- case "Angular":
3977
- return ["angular"];
3978
- case "JavaScript":
3979
- return [];
3980
- }
4001
+ var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
4002
+ function lower(entries) {
4003
+ return entries.map((entry) => entry.name.toLowerCase());
3981
4004
  }
3982
- function publicEnvPrefix(language) {
3983
- const frameworkNames = language.frameworks.map(
3984
- (framework) => framework.name.toLowerCase()
4005
+ function isJsProject(language) {
4006
+ return lower(language.languages).some(
4007
+ (name) => JS_LANGUAGES.some((js) => name.includes(js))
3985
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);
3986
4030
  if (frameworkNames.some((name) => name.includes("next"))) {
3987
4031
  return "NEXT_PUBLIC_";
3988
4032
  }
@@ -3995,7 +4039,7 @@ function publicEnvPrefix(language) {
3995
4039
  if (frameworkNames.some((name) => name.includes("vite"))) {
3996
4040
  return "VITE_";
3997
4041
  }
3998
- return "PUBLIC_";
4042
+ return isJsProject(language) ? "PUBLIC_" : "";
3999
4043
  }
4000
4044
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
4001
4045
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
@@ -4034,7 +4078,10 @@ function baseInstructions(input) {
4034
4078
  // index-scoped keys then reject with a 403.
4035
4079
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4036
4080
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4037
- "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."
4038
4085
  ];
4039
4086
  }
4040
4087
  function sourceSpecificInstructions(input) {
@@ -4054,12 +4101,21 @@ function sourceSpecificInstructions(input) {
4054
4101
  generated: [
4055
4102
  "No real data source exists; use sample records for each confirmed entity.",
4056
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.",
4057
- "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.",
4058
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."
4059
4106
  ]
4060
4107
  };
4061
4108
  return byLine[input.ingestionSource];
4062
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
+ }
4063
4119
  function ingestionInstructions(input) {
4064
4120
  return [
4065
4121
  ...input.confirmed && input.confirmed.length ? [
@@ -4067,25 +4123,30 @@ function ingestionInstructions(input) {
4067
4123
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4068
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.`,
4069
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.`,
4070
- "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.",
4071
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.",
4072
- getNamedDoc("save-records", "js"),
4073
- '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.',
4074
4131
  "The summary should be extremely concise.",
4075
- `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.`,
4076
4132
  ...sourceSpecificInstructions(input)
4077
4133
  ] : []
4078
4134
  ];
4079
4135
  }
4080
4136
  function searchInstructions(input) {
4081
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4137
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4082
4138
  return [
4083
4139
  "Implement an in-app Algolia search experience.",
4084
- `Build the search UI for ${input.uiFramework}.`,
4085
- "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4086
- doc,
4087
- `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.`,
4088
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.",
4089
4150
  // The key is provisioned only after verification passes, so the agent never
4090
4151
  // sees one. It must also leave .env alone: the wizard reads that file to
4091
4152
  // decide whether a key already exists, and an agent-invented value there
@@ -4094,9 +4155,8 @@ function searchInstructions(input) {
4094
4155
  // Not the agent's to rename: the wizard writes these exact names into
4095
4156
  // ".env" right after this step, so a renamed prefix would leave the code
4096
4157
  // reading a var the wizard never wrote.
4097
- `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4098
- "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.",
4099
- '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.",
4100
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."
4101
4161
  ];
4102
4162
  }
@@ -4104,11 +4164,12 @@ function verificationInstructions(input) {
4104
4164
  return [
4105
4165
  "Verify the Algolia implementation changes in the current worktree.",
4106
4166
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4107
- "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4108
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4109
- "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.",
4110
4171
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4111
- `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.`,
4112
4173
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4113
4174
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4114
4175
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4129,14 +4190,15 @@ var IMPLEMENT_CONFIG = {
4129
4190
  }
4130
4191
  };
4131
4192
  var useCaseToolMap = {
4132
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4133
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4134
- verification: [
4193
+ ingestion: [
4135
4194
  ...FS_READ_TOOLS,
4136
4195
  "writeFile",
4137
- "verifyImplementation",
4196
+ "writeCredentials",
4197
+ "runShell",
4138
4198
  "notifyUser"
4139
- ]
4199
+ ],
4200
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4201
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4140
4202
  };
4141
4203
  function toolsForUseCase(useCase, ingestionSource) {
4142
4204
  const tools = useCaseToolMap[useCase];
@@ -4159,15 +4221,30 @@ function formatSummary(useCase, summary) {
4159
4221
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4160
4222
  return `${label}: ${summary}`;
4161
4223
  }
4162
- function buildIngestCommand(worktree, runtime, entrypoint) {
4163
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4164
- }
4165
4224
  function parseIngestRecordCount(output) {
4166
4225
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4167
4226
  if (!match) return void 0;
4168
4227
  const count = Number(match[1]);
4169
4228
  return Number.isFinite(count) ? count : void 0;
4170
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
+ }
4171
4248
  function verificationRetryInstructions(verification) {
4172
4249
  return [
4173
4250
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4246,9 +4323,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4246
4323
  const confirmed2 = normalized.confirmedEntities;
4247
4324
  const searchLocation = normalized.searchImplementationAnalysis;
4248
4325
  let appId;
4326
+ let ingestAppId;
4249
4327
  if (useCases.includes("search")) {
4250
4328
  appId = (await requireApplication()).id;
4251
4329
  }
4330
+ if (useCases.includes("ingestion")) {
4331
+ ingestAppId = appId ?? (await requireApplication()).id;
4332
+ }
4252
4333
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4253
4334
  try {
4254
4335
  process.chdir(worktree);
@@ -4285,7 +4366,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4285
4366
  ingestDir: INGEST_DIR,
4286
4367
  ingestionSource,
4287
4368
  uploadFilePath,
4288
- uiFramework: detectUiFramework(language)
4369
+ searchUiTarget: searchUiTarget(language)
4289
4370
  };
4290
4371
  const summaries = [];
4291
4372
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4308,41 +4389,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4308
4389
  }
4309
4390
  let finalSearchEnvVars = input.searchEnvVars;
4310
4391
  let agentRuns = 0;
4311
- let ingestRuntime;
4312
- let ingestEntrypoint;
4392
+ let ingestCommand;
4313
4393
  let ingestScriptRan = false;
4314
4394
  let ingestRecordCount;
4315
4395
  let ingestDurationMs;
4316
- let installFailed = false;
4317
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);
4318
4404
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4319
4405
  if (agentRuns > 0) ctx.recordStepExecution();
4320
4406
  agentRuns += 1;
4321
- const result = await runAgent({
4407
+ return runAgent({
4322
4408
  instructions: buildAgentInstructions(
4323
4409
  currentUseCase,
4324
4410
  input,
4325
4411
  extraInstructions
4326
4412
  ),
4327
4413
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4328
- outputSchema: implementationOutputSchema
4329
- });
4330
- ctx.notify({
4331
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4414
+ outputSchema: implementationOutputSchema,
4415
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4332
4416
  });
4333
- const installLogId = ctx.logStart("installWorktreeDeps", {
4334
- useCase: currentUseCase
4335
- });
4336
- const install = await installWorktreeDeps(worktree);
4337
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4338
- if (!install.ok) {
4339
- installFailed = true;
4340
- logger.warn(
4341
- { useCase: currentUseCase, output: install.output },
4342
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4343
- );
4344
- }
4345
- return result;
4346
4417
  }
4347
4418
  async function runVerificationUseCase() {
4348
4419
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4350,111 +4421,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4350
4421
  return runAgent({
4351
4422
  instructions: buildAgentInstructions("verification", input),
4352
4423
  tools: toolsForUseCase("verification"),
4353
- outputSchema: verificationOutputSchema
4424
+ outputSchema: verificationOutputSchema,
4425
+ toolContext: searchTools
4354
4426
  });
4355
4427
  }
4356
4428
  if (useCases.includes("ingestion")) {
4357
- const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4358
- summaries.push(formatSummary("ingestion", summary));
4359
- ingestRuntime = runtime;
4360
- ingestEntrypoint = entrypoint;
4361
- if (ingestRuntime && ingestEntrypoint && !installFailed) {
4362
- ctx.clearNotices();
4363
- const runNow = await ctx.requestUserInput({
4364
- prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4365
- promptType: "acceptReject",
4366
- options: ["Yes", "No"],
4367
- messages: []
4368
- }) === true;
4369
- if (runNow) {
4370
- const ingestApp = await requireApplication();
4371
- const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4372
- ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4373
- const scriptLogId = ctx.logStart("runIngestScript", {
4374
- runtime: ingestRuntime,
4375
- 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
4376
4447
  });
4377
- const startedAt = Date.now();
4378
- const run2 = await runIngestScript(
4379
- worktree,
4380
- ingestRuntime,
4381
- ingestEntrypoint,
4382
- {
4383
- [APP_ID_VAR]: ingestApp.id,
4384
- [API_KEY_VAR]: writeKey,
4385
- [INDEX_NAME_VAR]: targetIndex
4386
- }
4387
- );
4388
- ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4389
- ingestScriptRan = run2.ran && run2.ok;
4390
- if (ingestScriptRan) {
4391
- ingestDurationMs = Date.now() - startedAt;
4392
- ingestRecordCount = parseIngestRecordCount(run2.output);
4393
- if (ingestRecordCount != null) {
4394
- track("AI Wizard Ingest Successful", {
4395
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4396
- record_count: ingestRecordCount,
4397
- duration_ms: ingestDurationMs
4398
- });
4399
- }
4400
- }
4401
- let summaryLine;
4402
- let outcomeMessage;
4403
- if (!run2.ran) {
4404
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4405
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4406
- logger.warn(
4407
- {
4408
- runtime: ingestRuntime,
4409
- entrypoint: ingestEntrypoint,
4410
- reason: run2.reason
4411
- },
4412
- "implement: refused to auto-run ingestion script"
4413
- );
4414
- track("Error", {
4415
- step: "Push Data",
4416
- error: `ingestion script skipped: ${run2.reason}`,
4417
- product_area: "AI Wizard"
4418
- });
4419
- } else if (run2.ok) {
4420
- const status = "Ingestion run: succeeded.";
4421
- summaryLine = run2.output ? `${status}
4422
- ${run2.output}` : status;
4423
- outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4424
- } else {
4425
- const status = "\u26A0\uFE0F Ingestion run failed:";
4426
- summaryLine = run2.output ? `${status}
4427
- ${run2.output}` : status;
4428
- outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4429
- logger.warn(
4430
- {
4431
- runtime: ingestRuntime,
4432
- entrypoint: ingestEntrypoint,
4433
- output: run2.output
4434
- },
4435
- "implement: ingestion script run failed"
4436
- );
4437
- track("Error", {
4438
- step: "Push Data",
4439
- error: run2.output || "ingestion script exited non-zero",
4440
- product_area: "AI Wizard"
4441
- });
4442
- }
4443
- summaries.push(summaryLine);
4444
- ingestOutcomeMessage = outcomeMessage;
4445
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
+ });
4446
4463
  }
4447
4464
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4448
- if (ingestRuntime && ingestEntrypoint) {
4449
- commandMessages.push(
4450
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4451
- );
4465
+ if (ingestCommand) {
4466
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4452
4467
  }
4453
4468
  await ctx.requestUserInput({
4454
4469
  prompt: "",
4455
4470
  promptType: "enterToContinue",
4456
4471
  options: [],
4457
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4472
+ messages: [ingestOutcomeMessage, ...commandMessages]
4458
4473
  });
4459
4474
  }
4460
4475
  if (useCases.includes("search")) {
@@ -4571,22 +4586,13 @@ ${run2.output}` : status;
4571
4586
  "implement: agent reported success but no files changed in the worktree"
4572
4587
  );
4573
4588
  }
4574
- if (installFailed) {
4575
- summaries.push(
4576
- '\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".'
4577
- );
4578
- }
4579
4589
  return {
4580
4590
  ingestionSource,
4581
4591
  filesChanged,
4582
4592
  summary: summaries.join("\n\n"),
4583
4593
  worktreePath: worktree,
4584
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4585
- ingestCommand: buildIngestCommand(
4586
- worktree,
4587
- ingestRuntime,
4588
- ingestEntrypoint
4589
- ),
4594
+ ...useCases.includes("ingestion") && ingestCommand ? {
4595
+ ingestCommand,
4590
4596
  ingestScriptRan,
4591
4597
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4592
4598
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4917,7 +4923,7 @@ function parseCliArgs(argv) {
4917
4923
 
4918
4924
  // src/lib/resetState.ts
4919
4925
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4920
- import { join as join11 } from "node:path";
4926
+ import { join as join10 } from "node:path";
4921
4927
  var KEEP = ["wizard.log"];
4922
4928
  async function resetProjectState() {
4923
4929
  const dir = stateDir();
@@ -4931,14 +4937,14 @@ async function resetProjectState() {
4931
4937
  const targets = entries.filter((name) => !KEEP.includes(name));
4932
4938
  await Promise.all(
4933
4939
  targets.map(
4934
- (name) => rm2(join11(dir, name), { recursive: true, force: true })
4940
+ (name) => rm2(join10(dir, name), { recursive: true, force: true })
4935
4941
  )
4936
4942
  );
4937
4943
  return { dir, removed: targets };
4938
4944
  }
4939
4945
 
4940
4946
  // src/main.tsx
4941
- import { jsx as jsx14 } from "react/jsx-runtime";
4947
+ import { jsx as jsx15 } from "react/jsx-runtime";
4942
4948
  async function startup() {
4943
4949
  setProjectRoot(process.cwd());
4944
4950
  let args;
@@ -4988,7 +4994,7 @@ ${formatStepList(workflow)}`);
4988
4994
  }
4989
4995
  async function run(workflow) {
4990
4996
  const store = useWizard.getState();
4991
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4997
+ const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4992
4998
  await store.waitForStart();
4993
4999
  let user = await getUser();
4994
5000
  if (!user) {