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

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";
@@ -626,10 +626,13 @@ function Notices() {
626
626
  }
627
627
 
628
628
  // src/ui/PromptInput.tsx
629
- import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
629
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
630
630
  import TextInput from "ink-text-input";
631
631
  import { useState as useState5 } from "react";
632
632
 
633
+ // src/ui/CommandApproval.tsx
634
+ import { Box as Box5, Text as Text5, useInput } from "ink";
635
+
633
636
  // src/ui/NextAction.tsx
634
637
  import { Box as Box4, Text as Text4 } from "ink";
635
638
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -653,14 +656,69 @@ function NextAction({
653
656
  ] });
654
657
  }
655
658
 
659
+ // src/ui/CommandApproval.tsx
660
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
661
+ function CommandApproval({
662
+ command,
663
+ onDecide
664
+ }) {
665
+ useInput((input, key) => {
666
+ if (key.return) onDecide("approve");
667
+ else if (key.escape) onDecide("reject");
668
+ else if (input.toLowerCase() === "a") onDecide("always");
669
+ });
670
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
671
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
672
+ /* @__PURE__ */ jsxs4(
673
+ Box5,
674
+ {
675
+ flexDirection: "column",
676
+ paddingLeft: 2,
677
+ borderStyle: "single",
678
+ borderColor: COLORS.success,
679
+ borderTop: false,
680
+ borderBottom: false,
681
+ borderRight: false,
682
+ gap: 1,
683
+ children: [
684
+ /* @__PURE__ */ jsxs4(Box5, { children: [
685
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
686
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
687
+ ] }),
688
+ /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
689
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
690
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
691
+ ] }),
692
+ command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
693
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
694
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
695
+ ] })
696
+ ]
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
700
+ /* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
701
+ /* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" }),
702
+ /* @__PURE__ */ jsx4(
703
+ NextAction,
704
+ {
705
+ action: "approve, and don't ask again for this command",
706
+ keyHint: "a",
707
+ hierarchy: "secondary"
708
+ }
709
+ )
710
+ ] })
711
+ ] });
712
+ }
713
+
656
714
  // src/ui/SelectPrompt.tsx
657
- import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
715
+ import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
658
716
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
659
717
 
660
718
  // src/ui/ScrollView.tsx
661
- import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
719
+ import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
662
720
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
663
- import { jsxs as jsxs4 } from "react/jsx-runtime";
721
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
664
722
  var INDICATOR_ROWS = 2;
665
723
  function fittedWidth(node, columns) {
666
724
  let left = 0;
@@ -730,14 +788,14 @@ function useScrollWindow({
730
788
  };
731
789
  }
732
790
  function ScrollView({ scroll, children }) {
733
- return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
734
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
791
+ return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
792
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
735
793
  "\u2191 ",
736
794
  scroll.hiddenAbove,
737
795
  " more"
738
796
  ] }),
739
797
  children,
740
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
798
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
741
799
  "\u2193 ",
742
800
  scroll.hiddenBelow,
743
801
  " more"
@@ -746,7 +804,7 @@ function ScrollView({ scroll, children }) {
746
804
  }
747
805
 
748
806
  // src/ui/SelectPrompt.tsx
749
- import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
807
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
750
808
  var CANCEL = "cancel";
751
809
  var ARROW_WIDTH = 4;
752
810
  var COLUMN_GAP = 2;
@@ -808,7 +866,7 @@ function SelectPrompt({
808
866
  revealIndex(index);
809
867
  }, [index, revealIndex]);
810
868
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
811
- useInput((input, key) => {
869
+ useInput2((input, key) => {
812
870
  if (rows.length === 0) return;
813
871
  if (key.upArrow || input === "k") {
814
872
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -831,56 +889,56 @@ function SelectPrompt({
831
889
  }
832
890
  }
833
891
  });
834
- return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
835
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
836
- error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
837
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
838
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
839
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
840
- question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
841
- helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
892
+ return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
893
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
894
+ error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
895
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
896
+ table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
897
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
898
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
899
+ helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
842
900
  ] })
843
901
  ] }),
844
- /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
902
+ /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
845
903
  const i = scroll.offset + visibleIndex;
846
904
  const highlighted = i === index;
847
905
  const isCancel = i === cancelIndex;
848
906
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
849
907
  const sec = isCancel ? void 0 : secondary?.[i];
850
908
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
851
- const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
909
+ const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
852
910
  highlighted ? "\u276F " : " ",
853
911
  bullet,
854
912
  option
855
913
  ] });
856
914
  const isText = sec?.kind === "text";
857
- return /* @__PURE__ */ jsxs5(
858
- Box6,
915
+ return /* @__PURE__ */ jsxs6(
916
+ Box7,
859
917
  {
860
918
  width: isText ? "100%" : barWidth,
861
919
  paddingX: 1,
862
920
  paddingY: 1,
863
921
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
864
922
  children: [
865
- /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
866
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
867
- Text6,
923
+ /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
924
+ isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
925
+ Text7,
868
926
  {
869
927
  wrap: "truncate",
870
928
  color: highlighted ? COLORS.primary : COLORS.muted,
871
929
  children: sec.value
872
930
  }
873
931
  ) }),
874
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
932
+ sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
875
933
  ]
876
934
  },
877
935
  `row-${i}`
878
936
  );
879
937
  }) }),
880
- /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
938
+ /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
881
939
  i > 0 ? " " : "",
882
- /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
883
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
940
+ /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
941
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
884
942
  " ",
885
943
  label
886
944
  ] })
@@ -889,23 +947,23 @@ function SelectPrompt({
889
947
  }
890
948
 
891
949
  // src/ui/PromptInput.tsx
892
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
950
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
893
951
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
894
952
  function EnterToContinuePrompt({
895
953
  question,
896
954
  messages,
897
955
  onDecide
898
956
  }) {
899
- useInput2((_input, key) => {
957
+ useInput3((_input, key) => {
900
958
  if (key.return) onDecide(true);
901
959
  else if (key.escape) onDecide(false);
902
960
  });
903
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
904
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
905
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
906
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
907
- /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
908
- /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
961
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
962
+ messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
963
+ question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
964
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
965
+ /* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
966
+ /* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
909
967
  ] })
910
968
  ] });
911
969
  }
@@ -913,11 +971,11 @@ function PromptInput() {
913
971
  const { phase, inputReq, submitInput } = useWizard();
914
972
  const [draft, setDraft] = useState5("");
915
973
  if (phase === "done" || phase === "error") {
916
- return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
974
+ return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
917
975
  }
918
976
  if (phase !== "awaitingInput" || !inputReq) return null;
919
977
  if (inputReq.promptType === "multipleChoice") {
920
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
978
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
921
979
  SelectPrompt,
922
980
  {
923
981
  question: inputReq.prompt,
@@ -934,7 +992,7 @@ function PromptInput() {
934
992
  ) });
935
993
  }
936
994
  if (inputReq.promptType === "multiSelect") {
937
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
995
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
938
996
  SelectPrompt,
939
997
  {
940
998
  multi: true,
@@ -949,7 +1007,7 @@ function PromptInput() {
949
1007
  ) });
950
1008
  }
951
1009
  if (inputReq.promptType === "notice") {
952
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1010
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
953
1011
  SelectPrompt,
954
1012
  {
955
1013
  question: inputReq.prompt,
@@ -960,7 +1018,7 @@ function PromptInput() {
960
1018
  ) });
961
1019
  }
962
1020
  if (inputReq.promptType === "enterToContinue") {
963
- return /* @__PURE__ */ jsx5(
1021
+ return /* @__PURE__ */ jsx6(
964
1022
  EnterToContinuePrompt,
965
1023
  {
966
1024
  question: inputReq.prompt,
@@ -969,9 +1027,12 @@ function PromptInput() {
969
1027
  }
970
1028
  );
971
1029
  }
1030
+ if (inputReq.promptType === "commandApproval" && inputReq.command) {
1031
+ return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1032
+ }
972
1033
  if (inputReq.promptType === "acceptReject") {
973
1034
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
974
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1035
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
975
1036
  SelectPrompt,
976
1037
  {
977
1038
  question: inputReq.prompt,
@@ -982,15 +1043,15 @@ function PromptInput() {
982
1043
  }
983
1044
  ) });
984
1045
  }
985
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
986
- inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
987
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
988
- /* @__PURE__ */ jsxs6(Box7, { children: [
989
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
1046
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1047
+ inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
1048
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
1049
+ /* @__PURE__ */ jsxs7(Box8, { children: [
1050
+ /* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
990
1051
  inputReq.prompt,
991
1052
  " "
992
1053
  ] }),
993
- /* @__PURE__ */ jsx5(
1054
+ /* @__PURE__ */ jsx6(
994
1055
  TextInput,
995
1056
  {
996
1057
  value: draft,
@@ -1008,7 +1069,7 @@ function PromptInput() {
1008
1069
  // src/ui/Welcome.tsx
1009
1070
  import { dirname as dirname2, join as join3 } from "node:path";
1010
1071
  import { fileURLToPath } from "node:url";
1011
- import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1072
+ import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1012
1073
 
1013
1074
  // src/ui/copy/welcome.ts
1014
1075
  var sidebarItems = [
@@ -1021,12 +1082,12 @@ var sidebarItems = [
1021
1082
  description: "push 100 records to Algolia in seconds"
1022
1083
  },
1023
1084
  {
1024
- title: "detect your framework",
1025
- description: "React, Vue, Angular, Vanilla JS"
1085
+ title: "detect your stack",
1086
+ description: "whatever language and framework you already use"
1026
1087
  },
1027
1088
  {
1028
1089
  title: "scaffold a search UI",
1029
- description: "a styled InstantSearch component, wired into your app"
1090
+ description: "a search box and results, wired into your app"
1030
1091
  },
1031
1092
  {
1032
1093
  title: "ship it",
@@ -1036,20 +1097,20 @@ var sidebarItems = [
1036
1097
 
1037
1098
  // src/ui/Welcome.tsx
1038
1099
  import Image, { InkPictureProvider } from "ink-picture";
1039
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1100
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1040
1101
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1041
1102
  function SidebarItem({
1042
1103
  title,
1043
1104
  description
1044
1105
  }) {
1045
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1046
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1047
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1048
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
1106
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1107
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
1108
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
1109
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
1049
1110
  ] }),
1050
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1051
- /* @__PURE__ */ jsx6(Spacer, {}),
1052
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1111
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1112
+ /* @__PURE__ */ jsx7(Spacer, {}),
1113
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1053
1114
  ] })
1054
1115
  ] });
1055
1116
  }
@@ -1057,7 +1118,7 @@ function Welcome() {
1057
1118
  const confirmStart = useWizard((s) => s.confirmStart);
1058
1119
  const openLearnMore = useWizard((s) => s.openLearnMore);
1059
1120
  const { rows } = useWindowSize6();
1060
- useInput3((input, key) => {
1121
+ useInput4((input, key) => {
1061
1122
  if (key.return) confirmStart();
1062
1123
  else if (input === "i") openLearnMore();
1063
1124
  });
@@ -1075,16 +1136,16 @@ function Welcome() {
1075
1136
  if (rows < 30) {
1076
1137
  layout = scales["small"];
1077
1138
  }
1078
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1079
- /* @__PURE__ */ jsx6(
1080
- Box8,
1139
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1140
+ /* @__PURE__ */ jsx7(
1141
+ Box9,
1081
1142
  {
1082
1143
  paddingY: layout.main.padding.y,
1083
1144
  paddingX: layout.main.padding.x,
1084
1145
  flexDirection: "column",
1085
1146
  justifyContent: "center",
1086
- children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1087
- /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1147
+ children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1148
+ /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1088
1149
  Image,
1089
1150
  {
1090
1151
  src: IMAGE_PATH,
@@ -1095,16 +1156,16 @@ function Welcome() {
1095
1156
  protocol: "halfBlock"
1096
1157
  }
1097
1158
  ) }),
1098
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1099
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1100
- /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1101
- /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1159
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1160
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
1161
+ /* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
1162
+ /* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1102
1163
  ] })
1103
1164
  ] })
1104
1165
  }
1105
1166
  ),
1106
- /* @__PURE__ */ jsxs7(
1107
- Box8,
1167
+ /* @__PURE__ */ jsxs8(
1168
+ Box9,
1108
1169
  {
1109
1170
  backgroundColor: COLORS.bg.sidebar,
1110
1171
  width: 40,
@@ -1114,8 +1175,8 @@ function Welcome() {
1114
1175
  flexDirection: "column",
1115
1176
  justifyContent: "center",
1116
1177
  children: [
1117
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1118
- sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
1178
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1179
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
1119
1180
  ]
1120
1181
  }
1121
1182
  )
@@ -1124,7 +1185,7 @@ function Welcome() {
1124
1185
 
1125
1186
  // src/ui/LearnMore.tsx
1126
1187
  import { Fragment as Fragment2 } from "react";
1127
- import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1188
+ import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1128
1189
 
1129
1190
  // src/ui/copy/learn-more.ts
1130
1191
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1132,12 +1193,17 @@ var accessItems = [
1132
1193
  {
1133
1194
  tag: "READ",
1134
1195
  title: "Project files",
1135
- description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1196
+ description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1136
1197
  },
1137
1198
  {
1138
1199
  tag: "WRITE",
1139
1200
  title: "Code changes",
1140
- description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1201
+ description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1202
+ },
1203
+ {
1204
+ tag: "EXEC",
1205
+ title: "Setup commands",
1206
+ description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
1141
1207
  },
1142
1208
  {
1143
1209
  tag: "NET",
@@ -1147,13 +1213,13 @@ var accessItems = [
1147
1213
  {
1148
1214
  tag: "KEY",
1149
1215
  title: "Credentials",
1150
- description: "saves your Admin API key to .env and adds it to .gitignore."
1216
+ description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1151
1217
  }
1152
1218
  ];
1153
1219
  var neverItems = [
1154
1220
  "Send your source code to a model or third party",
1155
1221
  "Commit or push to git",
1156
- "Touch files outside your project directory"
1222
+ "Run a command you haven't approved"
1157
1223
  ];
1158
1224
  var policyLinks = [
1159
1225
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1161,10 +1227,11 @@ var policyLinks = [
1161
1227
  ];
1162
1228
 
1163
1229
  // src/ui/LearnMore.tsx
1164
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1230
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1165
1231
  var TAG_COLORS = {
1166
1232
  READ: COLORS.success,
1167
1233
  WRITE: COLORS.badge,
1234
+ EXEC: COLORS.danger,
1168
1235
  NET: COLORS.accent,
1169
1236
  KEY: COLORS.muted
1170
1237
  };
@@ -1177,12 +1244,12 @@ function NeverLine({
1177
1244
  }) {
1178
1245
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1179
1246
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1180
- return /* @__PURE__ */ jsxs8(Text9, { children: [
1181
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1247
+ return /* @__PURE__ */ jsxs9(Text10, { children: [
1248
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1182
1249
  " ".repeat(NEVER_BOX_PAD_X),
1183
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1250
+ segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1184
1251
  " ".repeat(rightPad),
1185
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1252
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1186
1253
  ] });
1187
1254
  }
1188
1255
  function LearnMore() {
@@ -1190,12 +1257,12 @@ function LearnMore() {
1190
1257
  const backToHome = useWizard((s) => s.backToHome);
1191
1258
  const { columns } = useWindowSize7();
1192
1259
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1193
- useInput4((_input, key) => {
1260
+ useInput5((_input, key) => {
1194
1261
  if (key.escape) backToHome();
1195
1262
  else if (key.return) confirmStart();
1196
1263
  });
1197
- return /* @__PURE__ */ jsxs8(
1198
- Box9,
1264
+ return /* @__PURE__ */ jsxs9(
1265
+ Box10,
1199
1266
  {
1200
1267
  flexDirection: "column",
1201
1268
  paddingX: PADDING_X,
@@ -1203,31 +1270,31 @@ function LearnMore() {
1203
1270
  width: "100%",
1204
1271
  gap: 1,
1205
1272
  children: [
1206
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1207
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1208
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1209
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1210
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1211
- /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1212
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1213
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1214
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1273
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1274
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
1275
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
1276
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1277
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1278
+ /* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1279
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
1280
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1281
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1215
1282
  ] }) })
1216
1283
  ] })
1217
1284
  ] }, item.tag)) }),
1218
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1219
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1220
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1221
- /* @__PURE__ */ jsx7(
1285
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
1286
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1287
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1288
+ /* @__PURE__ */ jsx8(
1222
1289
  NeverLine,
1223
1290
  {
1224
1291
  width: dividerWidth,
1225
1292
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1226
1293
  }
1227
1294
  ),
1228
- neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1229
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1230
- /* @__PURE__ */ jsx7(
1295
+ neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1296
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1297
+ /* @__PURE__ */ jsx8(
1231
1298
  NeverLine,
1232
1299
  {
1233
1300
  width: dividerWidth,
@@ -1239,24 +1306,24 @@ function LearnMore() {
1239
1306
  }
1240
1307
  )
1241
1308
  ] }, item)),
1242
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1243
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1309
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1310
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1244
1311
  ] }),
1245
- /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1246
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1247
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1312
+ /* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1313
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1314
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
1248
1315
  ] }, link.label)) }),
1249
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1250
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1251
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1252
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1253
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1316
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1317
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1318
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1319
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
1320
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
1254
1321
  ] }),
1255
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1256
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1257
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1258
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1259
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1322
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1323
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1324
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
1325
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
1326
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
1260
1327
  ] })
1261
1328
  ] })
1262
1329
  ]
@@ -1265,10 +1332,10 @@ function LearnMore() {
1265
1332
  }
1266
1333
 
1267
1334
  // src/ui/Sidebar.tsx
1268
- import { Box as Box12, Text as Text12 } from "ink";
1335
+ import { Box as Box13, Text as Text13 } from "ink";
1269
1336
 
1270
1337
  // src/ui/Steps.tsx
1271
- import { Box as Box10, Text as Text10 } from "ink";
1338
+ import { Box as Box11, Text as Text11 } from "ink";
1272
1339
  import Spinner from "ink-spinner";
1273
1340
 
1274
1341
  // src/core/persistence.ts
@@ -1297,12 +1364,12 @@ async function clearWorkflowState(workflowId) {
1297
1364
  }
1298
1365
 
1299
1366
  // src/ui/Steps.tsx
1300
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1367
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1301
1368
  function Steps() {
1302
1369
  const { steps } = useWizard();
1303
1370
  const visibleSteps = steps.filter(isStepVisible);
1304
- return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
1305
- s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1371
+ 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: [
1372
+ s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
1306
1373
  " ",
1307
1374
  s.title
1308
1375
  ] }) }, s.id)) });
@@ -1311,27 +1378,27 @@ function CurrentStep() {
1311
1378
  const { steps } = useWizard();
1312
1379
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1313
1380
  if (!currentStep) return null;
1314
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1315
- /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1381
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1382
+ /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1316
1383
  " ",
1317
1384
  ` ${currentStep.title}`
1318
1385
  ] });
1319
1386
  }
1320
1387
 
1321
1388
  // src/ui/Progress.tsx
1322
- import { Box as Box11, Text as Text11 } from "ink";
1323
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1389
+ import { Box as Box12, Text as Text12 } from "ink";
1390
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1324
1391
  function Progress() {
1325
1392
  const { steps, currentStepIndex } = useWizard();
1326
1393
  const visibleSteps = steps.filter(isStepVisible);
1327
1394
  if (visibleSteps.length === 0) return null;
1328
1395
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1329
1396
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1330
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1331
- /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1332
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1333
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1334
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1397
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1398
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1399
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
1400
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
1401
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
1335
1402
  ] });
1336
1403
  }
1337
1404
 
@@ -1342,10 +1409,10 @@ var sidebarCommands = [
1342
1409
  ];
1343
1410
 
1344
1411
  // src/ui/Sidebar.tsx
1345
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1412
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1346
1413
  function Sidebar() {
1347
- return /* @__PURE__ */ jsxs11(
1348
- Box12,
1414
+ return /* @__PURE__ */ jsxs12(
1415
+ Box13,
1349
1416
  {
1350
1417
  backgroundColor: "#14171E",
1351
1418
  width: 30,
@@ -1354,16 +1421,16 @@ function Sidebar() {
1354
1421
  flexDirection: "column",
1355
1422
  justifyContent: "space-between",
1356
1423
  children: [
1357
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1358
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1359
- /* @__PURE__ */ jsx10(Steps, {})
1424
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1425
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1426
+ /* @__PURE__ */ jsx11(Steps, {})
1360
1427
  ] }),
1361
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1362
- /* @__PURE__ */ jsx10(Progress, {}),
1363
- /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1364
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1365
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1366
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1428
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1429
+ /* @__PURE__ */ jsx11(Progress, {}),
1430
+ /* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
1431
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1432
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1433
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
1367
1434
  ] });
1368
1435
  }) })
1369
1436
  ] })
@@ -1373,12 +1440,12 @@ function Sidebar() {
1373
1440
  }
1374
1441
 
1375
1442
  // src/ui/Ribbon.tsx
1376
- import { Box as Box13, Text as Text13 } from "ink";
1377
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1443
+ import { Box as Box14, Text as Text14 } from "ink";
1444
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1378
1445
  function Ribbon() {
1379
1446
  const firstCommand = sidebarCommands[0];
1380
- return /* @__PURE__ */ jsxs12(
1381
- Box13,
1447
+ return /* @__PURE__ */ jsxs13(
1448
+ Box14,
1382
1449
  {
1383
1450
  backgroundColor: "#14171E",
1384
1451
  flexDirection: "row",
@@ -1386,11 +1453,11 @@ function Ribbon() {
1386
1453
  paddingX: 2,
1387
1454
  paddingY: 1,
1388
1455
  children: [
1389
- /* @__PURE__ */ jsx11(Progress, {}),
1390
- /* @__PURE__ */ jsx11(CurrentStep, {}),
1391
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1392
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1393
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1456
+ /* @__PURE__ */ jsx12(Progress, {}),
1457
+ /* @__PURE__ */ jsx12(CurrentStep, {}),
1458
+ /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1459
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1460
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
1394
1461
  ] })
1395
1462
  ]
1396
1463
  }
@@ -1401,8 +1468,8 @@ function Ribbon() {
1401
1468
  import { useState as useState6 } from "react";
1402
1469
 
1403
1470
  // src/ui/Logs.tsx
1404
- import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1405
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1471
+ import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1472
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1406
1473
  var KIND_COLOR = {
1407
1474
  tool: COLORS.primary,
1408
1475
  prompt: COLORS.badge
@@ -1433,14 +1500,14 @@ function formatTimestamp(ms) {
1433
1500
  function Logs() {
1434
1501
  const logs = useWizard((s) => s.logs);
1435
1502
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1436
- useInput5((_input, key) => {
1503
+ useInput6((_input, key) => {
1437
1504
  if (key.upArrow) scroll.scrollBy(-1);
1438
1505
  else if (key.downArrow) scroll.scrollBy(1);
1439
1506
  });
1440
1507
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1441
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1442
- logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1443
- /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1508
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1509
+ logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
1510
+ /* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
1444
1511
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1445
1512
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1446
1513
  const rawPreview = rawInputText(entry.input);
@@ -1450,14 +1517,14 @@ function Logs() {
1450
1517
  const name = truncate2(entry.name, budget);
1451
1518
  budget -= name.length;
1452
1519
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1453
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1454
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1455
- /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1456
- preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1457
- durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1520
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
1521
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
1522
+ /* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
1523
+ preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
1524
+ durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
1458
1525
  ] }, entry.id);
1459
1526
  }) }),
1460
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1527
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1461
1528
  ] });
1462
1529
  }
1463
1530
 
@@ -1649,7 +1716,7 @@ function track(event, payload) {
1649
1716
  }
1650
1717
 
1651
1718
  // src/ui/App.tsx
1652
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1719
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1653
1720
  function App() {
1654
1721
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1655
1722
  const { exit } = useApp();
@@ -1657,7 +1724,7 @@ function App() {
1657
1724
  const [showLogs, setShowLogs] = useState6(false);
1658
1725
  const finished = phase === "done" || phase === "error";
1659
1726
  const currentStep = steps[currentStepIndex];
1660
- useInput6(
1727
+ useInput7(
1661
1728
  (_input, key) => {
1662
1729
  if (key.return) {
1663
1730
  exit();
@@ -1665,7 +1732,7 @@ function App() {
1665
1732
  },
1666
1733
  { isActive: finished }
1667
1734
  );
1668
- useInput6((_input, key) => {
1735
+ useInput7((_input, key) => {
1669
1736
  if (phase === "idle" || phase === "authenticating") return;
1670
1737
  if (key.tab) {
1671
1738
  setShowLogs(!showLogs);
@@ -1676,8 +1743,8 @@ function App() {
1676
1743
  });
1677
1744
  }
1678
1745
  });
1679
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1680
- useInput6((_input, key) => {
1746
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1747
+ useInput7((_input, key) => {
1681
1748
  if (escOwnedElsewhere) return;
1682
1749
  if (key.escape) {
1683
1750
  track("AI Wizard Interaction", {
@@ -1696,8 +1763,8 @@ function App() {
1696
1763
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1697
1764
  the whole screen, and the scrolling throws off its cursor arithmetic —
1698
1765
  flicker and leftover rows. */
1699
- /* @__PURE__ */ jsxs14(
1700
- Box15,
1766
+ /* @__PURE__ */ jsxs15(
1767
+ Box16,
1701
1768
  {
1702
1769
  backgroundColor: COLORS.bg.main,
1703
1770
  flexDirection: "row",
@@ -1705,16 +1772,16 @@ function App() {
1705
1772
  height: scrollsPastViewport ? void 0 : rows,
1706
1773
  overflow: scrollsPastViewport ? "visible" : "hidden",
1707
1774
  children: [
1708
- mainWindowVisible && /* @__PURE__ */ jsxs14(
1709
- Box15,
1775
+ mainWindowVisible && /* @__PURE__ */ jsxs15(
1776
+ Box16,
1710
1777
  {
1711
1778
  flexDirection,
1712
1779
  width: "100%",
1713
1780
  maxHeight: rows,
1714
1781
  justifyContent: "space-between",
1715
1782
  children: [
1716
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1717
- Box15,
1783
+ showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1784
+ Box16,
1718
1785
  {
1719
1786
  flexDirection: "column",
1720
1787
  paddingX: 4,
@@ -1722,26 +1789,26 @@ function App() {
1722
1789
  width: showSidebar ? 70 : "100%",
1723
1790
  flexGrow: 1,
1724
1791
  children: [
1725
- phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1726
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1727
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1792
+ phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
1793
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1794
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1728
1795
  ] }),
1729
- /* @__PURE__ */ jsx13(CliOutput, {}),
1730
- /* @__PURE__ */ jsx13(Notices, {}),
1731
- /* @__PURE__ */ jsx13(PromptInput, {}),
1732
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1733
- phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1796
+ /* @__PURE__ */ jsx14(CliOutput, {}),
1797
+ /* @__PURE__ */ jsx14(Notices, {}),
1798
+ /* @__PURE__ */ jsx14(PromptInput, {}),
1799
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
1800
+ phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1734
1801
  "\u2716 ",
1735
1802
  error
1736
1803
  ] }) })
1737
1804
  ]
1738
1805
  }
1739
1806
  ),
1740
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1807
+ showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1741
1808
  ]
1742
1809
  }
1743
1810
  ),
1744
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1811
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1745
1812
  ]
1746
1813
  }
1747
1814
  )
@@ -1821,7 +1888,7 @@ async function ensureConsent() {
1821
1888
  if (config.aiConsent) return;
1822
1889
  const store = useWizard.getState();
1823
1890
  const answer = await store.requestUserInput({
1824
- prompt: "Wizard will make AI-authored changes to this repository.",
1891
+ prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
1825
1892
  promptType: "enterToContinue",
1826
1893
  options: []
1827
1894
  });
@@ -2576,11 +2643,19 @@ import z13 from "zod";
2576
2643
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2577
2644
  import { join as join7 } from "node:path";
2578
2645
  var MAX_QUERY_LENGTH = 1e3;
2646
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
2647
+ "node_modules",
2648
+ "dist",
2649
+ "build",
2650
+ "vendor",
2651
+ "venv",
2652
+ "__pycache__",
2653
+ "target"
2654
+ ]);
2579
2655
  async function walkFiles(dir) {
2580
- const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2581
2656
  const out = [];
2582
2657
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2583
- if (e.name.startsWith(".") || skip.has(e.name)) continue;
2658
+ if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2584
2659
  const full = join7(dir, e.name);
2585
2660
  if (e.isDirectory()) out.push(...await walkFiles(full));
2586
2661
  else if (e.isFile()) out.push(full);
@@ -2634,92 +2709,194 @@ function searchFilesTool(ctx) {
2634
2709
  });
2635
2710
  }
2636
2711
 
2637
- // src/lib/tools/verifyImplementation.ts
2712
+ // src/lib/tools/runShell.ts
2638
2713
  import { tool as tool8 } from "ai";
2639
2714
  import z14 from "zod";
2715
+ import { relative as relative2 } from "node:path";
2640
2716
 
2641
- // src/lib/tools/utils/runCommand.ts
2717
+ // src/lib/tools/utils/runShell.ts
2642
2718
  import { spawn as spawn2 } from "node:child_process";
2643
- function runCommand(command, args, cwd) {
2719
+
2720
+ // src/lib/tools/context.ts
2721
+ var DEFAULT_TOOL_LIMITS = {
2722
+ list: 10,
2723
+ search: 10,
2724
+ read: 20,
2725
+ match: 100,
2726
+ shell: 30
2727
+ };
2728
+ var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2729
+ async function refuseByDefault() {
2730
+ return "reject";
2731
+ }
2732
+ function createShellContext(overrides = {}) {
2733
+ return {
2734
+ env: async () => ({}),
2735
+ timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2736
+ approved: /* @__PURE__ */ new Set(),
2737
+ executions: [],
2738
+ approve: refuseByDefault,
2739
+ ...overrides
2740
+ };
2741
+ }
2742
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2743
+ return {
2744
+ root: cwd,
2745
+ cwd,
2746
+ limits: { ...limits },
2747
+ counts: { list: 0, search: 0, read: 0, shell: 0 },
2748
+ shell: shell2
2749
+ };
2750
+ }
2751
+
2752
+ // src/lib/tools/utils/runShell.ts
2753
+ var SIGKILL_DELAY_MS = 5e3;
2754
+ var HEAD_CHARS = 4e3;
2755
+ var TAIL_CHARS = 8e3;
2756
+ function truncateOutput(output) {
2757
+ if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
2758
+ const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
2759
+ return [
2760
+ output.slice(0, HEAD_CHARS),
2761
+ `
2762
+ \u2026 [${omitted} characters omitted] \u2026
2763
+ `,
2764
+ output.slice(-TAIL_CHARS)
2765
+ ].join("");
2766
+ }
2767
+ function runShell(command, opts) {
2768
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2769
+ const startedAt = Date.now();
2644
2770
  return new Promise((resolve4) => {
2645
2771
  let output = "";
2646
- const child = spawn2(command, args, {
2647
- cwd,
2648
- stdio: ["ignore", "pipe", "pipe"]
2772
+ let timedOut = false;
2773
+ let settled = false;
2774
+ const child = spawn2(command, {
2775
+ shell: true,
2776
+ cwd: opts.cwd,
2777
+ stdio: ["ignore", "pipe", "pipe"],
2778
+ env: { ...process.env, ...opts.env }
2649
2779
  });
2780
+ const finish = (exitCode) => {
2781
+ if (settled) return;
2782
+ settled = true;
2783
+ clearTimeout(timer);
2784
+ clearTimeout(killTimer);
2785
+ resolve4({
2786
+ exitCode,
2787
+ output: truncateOutput(output.trim()),
2788
+ timedOut,
2789
+ durationMs: Date.now() - startedAt
2790
+ });
2791
+ };
2792
+ let killTimer;
2793
+ const timer = setTimeout(() => {
2794
+ timedOut = true;
2795
+ output += `
2796
+ [timed out after ${timeoutMs}ms]`;
2797
+ child.kill("SIGTERM");
2798
+ killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
2799
+ }, timeoutMs);
2650
2800
  child.stdout?.on("data", (d) => output += d);
2651
2801
  child.stderr?.on("data", (d) => output += d);
2652
- child.on(
2653
- "error",
2654
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2655
- );
2656
- child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2802
+ child.on("error", (err) => {
2803
+ output += `Failed to run ${command}: ${err.message}`;
2804
+ finish(1);
2805
+ });
2806
+ child.on("close", (code) => finish(code ?? 1));
2657
2807
  });
2658
2808
  }
2659
2809
 
2660
- // src/lib/tools/utils/packageManager.ts
2661
- import { readFile as readFile6 } from "node:fs/promises";
2662
- import { existsSync } from "node:fs";
2663
- import { join as join8 } from "node:path";
2664
- var LOCKFILES = [
2665
- ["pnpm-lock.yaml", "pnpm"],
2666
- ["yarn.lock", "yarn"],
2667
- ["bun.lockb", "bun"],
2668
- ["bun.lock", "bun"],
2669
- ["package-lock.json", "npm"]
2670
- ];
2671
- async function readPackageJson(cwd = process.cwd()) {
2672
- return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2673
- }
2674
- function packageManagerFrom(pkg) {
2675
- return pkg.packageManager?.split("@")[0] ?? "npm";
2676
- }
2677
- function packageManagerFromLockfile(cwd) {
2678
- return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2679
- }
2680
- async function detectPackageManager(cwd) {
2681
- try {
2682
- const pkg = await readPackageJson(cwd);
2683
- if (pkg.packageManager) return packageManagerFrom(pkg);
2684
- } catch {
2685
- }
2686
- return packageManagerFromLockfile(cwd) ?? "npm";
2687
- }
2688
-
2689
- // src/lib/tools/repoVerification.ts
2690
- var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2691
- async function runRepoVerificationCheck() {
2692
- let pkg;
2693
- try {
2694
- pkg = await readPackageJson();
2695
- } catch (err) {
2696
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2697
- return { ok: false, checks: [], limitation };
2698
- }
2699
- const scripts = pkg.scripts ?? {};
2700
- const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2701
- if (present.length === 0) {
2702
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2703
- return { ok: false, checks: [], limitation };
2704
- }
2705
- const pm = await detectPackageManager(process.cwd());
2706
- const checks = [];
2707
- for (const script of present) {
2708
- const command = `${pm} run ${script}`;
2709
- const { code, output } = await runCommand(pm, ["run", script]);
2710
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2711
- }
2712
- return { ok: checks.every((c) => c.ok), checks };
2810
+ // src/lib/tools/runShell.ts
2811
+ function approvalKey(cwd, command) {
2812
+ return `${cwd}\0${command}`;
2813
+ }
2814
+ function storeApproval(root) {
2815
+ return async (req) => {
2816
+ const rel = relative2(root, req.cwd);
2817
+ const answer = await useWizard.getState().requestUserInput({
2818
+ prompt: "Run this command?",
2819
+ promptType: "commandApproval",
2820
+ options: [],
2821
+ command: {
2822
+ ...req,
2823
+ cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2824
+ }
2825
+ });
2826
+ return answer === "approve" || answer === "always" ? answer : "reject";
2827
+ };
2713
2828
  }
2714
-
2715
- // src/lib/tools/verifyImplementation.ts
2716
- function verifyImplementationTool() {
2829
+ function runShellTool(ctx) {
2717
2830
  return tool8({
2718
- description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
2719
- inputSchema: z14.object(),
2720
- execute: async () => {
2721
- logger.info("called verifyImplementation tool");
2722
- return runRepoVerificationCheck();
2831
+ 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.",
2832
+ inputSchema: z14.object({
2833
+ command: z14.string().describe(
2834
+ "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
2835
+ ),
2836
+ cwd: z14.string().optional().describe(
2837
+ "Directory to run in, relative to the project root. Defaults to the project root."
2838
+ ),
2839
+ explanation: z14.string().describe(
2840
+ "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."
2841
+ )
2842
+ }),
2843
+ execute: async ({ command, cwd, explanation }) => {
2844
+ if (++ctx.counts.shell > ctx.limits.shell) {
2845
+ return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
2846
+ }
2847
+ const resolved2 = resolveInRoot(ctx, cwd ?? ".");
2848
+ if (!resolved2.ok) return resolved2.error;
2849
+ logger.info({ command, cwd: resolved2.target }, "called runShell tool");
2850
+ const key = approvalKey(resolved2.target, command);
2851
+ const decision = ctx.shell.approved.has(key) ? "approve" : await ctx.shell.approve({
2852
+ command,
2853
+ cwd: resolved2.target,
2854
+ explanation
2855
+ });
2856
+ if (decision === "reject") {
2857
+ ctx.shell.executions.push({
2858
+ command,
2859
+ cwd: resolved2.target,
2860
+ approved: false
2861
+ });
2862
+ logger.info({ command }, "runShell: user rejected the command");
2863
+ return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
2864
+ }
2865
+ if (decision === "always") ctx.shell.approved.add(key);
2866
+ useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
2867
+ const env = await ctx.shell.env().catch((err) => {
2868
+ logger.warn({ err, command }, "runShell: could not resolve command env");
2869
+ return {};
2870
+ });
2871
+ const run2 = await (ctx.shell.run ?? runShell)(command, {
2872
+ cwd: resolved2.target,
2873
+ env,
2874
+ timeoutMs: ctx.shell.timeoutMs
2875
+ });
2876
+ logger.info(
2877
+ {
2878
+ command,
2879
+ exitCode: run2.exitCode,
2880
+ timedOut: run2.timedOut,
2881
+ durationMs: run2.durationMs
2882
+ },
2883
+ "runShell finished"
2884
+ );
2885
+ await markInteraction();
2886
+ ctx.shell.executions.push({
2887
+ command,
2888
+ cwd: resolved2.target,
2889
+ approved: true,
2890
+ exitCode: run2.exitCode,
2891
+ output: run2.output,
2892
+ timedOut: run2.timedOut,
2893
+ durationMs: run2.durationMs
2894
+ });
2895
+ return {
2896
+ exitCode: run2.exitCode,
2897
+ timedOut: run2.timedOut,
2898
+ output: run2.output
2899
+ };
2723
2900
  }
2724
2901
  });
2725
2902
  }
@@ -2810,7 +2987,7 @@ function generateRecordTool(ctx) {
2810
2987
  return {
2811
2988
  filePath: relPath,
2812
2989
  count: records.length,
2813
- message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) \u2014 do not inline the records as literals.`
2990
+ message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime using your language's standard JSON support \u2014 do not inline the records as literals.`
2814
2991
  };
2815
2992
  } catch (err) {
2816
2993
  return `Error generating records: ${err.message}`;
@@ -2838,22 +3015,6 @@ function notifyUserTool() {
2838
3015
  });
2839
3016
  }
2840
3017
 
2841
- // src/lib/tools/context.ts
2842
- var DEFAULT_TOOL_LIMITS = {
2843
- list: 10,
2844
- search: 10,
2845
- read: 20,
2846
- match: 100
2847
- };
2848
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2849
- return {
2850
- root: cwd,
2851
- cwd,
2852
- limits,
2853
- counts: { list: 0, search: 0, read: 0 }
2854
- };
2855
- }
2856
-
2857
3018
  // src/lib/tools/index.ts
2858
3019
  function withLogging(name, def) {
2859
3020
  const execute = def.execute;
@@ -2885,10 +3046,7 @@ function createTools(ctx, { output, tools }) {
2885
3046
  writeCredentialsTool(ctx)
2886
3047
  ),
2887
3048
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2888
- verifyImplementation: withLogging(
2889
- "verifyImplementation",
2890
- verifyImplementationTool()
2891
- ),
3049
+ runShell: withLogging("runShell", runShellTool(ctx)),
2892
3050
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2893
3051
  notifyUser: withLogging("notifyUser", notifyUserTool())
2894
3052
  };
@@ -2923,7 +3081,7 @@ async function runAgent(req) {
2923
3081
  baseURL: PROXY_BASE_URL,
2924
3082
  fetch: proxyFetch
2925
3083
  });
2926
- const toolContext = createToolContext();
3084
+ const toolContext = req.toolContext ?? createToolContext();
2927
3085
  const readTools = ["readFile", "searchFiles", "listFiles"];
2928
3086
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
2929
3087
  const instructions = [
@@ -2988,7 +3146,11 @@ async function runAgent(req) {
2988
3146
  "runAgent finished"
2989
3147
  );
2990
3148
  logger.info(
2991
- { counts: toolContext.counts, limits: toolContext.limits },
3149
+ {
3150
+ counts: toolContext.counts,
3151
+ limits: toolContext.limits,
3152
+ commandsRun: toolContext.shell.executions.length
3153
+ },
2992
3154
  "tool usage"
2993
3155
  );
2994
3156
  const toolResults = await stream.toolResults;
@@ -3014,8 +3176,8 @@ var detectLanguageSchema = z19.object({
3014
3176
  var detectLanguage = () => runAgent({
3015
3177
  instructions: [
3016
3178
  "Analyze the codebase and determine the programming languages and frameworks used",
3017
- "If a superset language is found, exclude the subset language. TS-over-JS.",
3018
- "If a meta-framework is used, exclude the framework. Next-over-React.",
3179
+ "If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
3180
+ "If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
3019
3181
  "Return the exact version",
3020
3182
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3021
3183
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3113,7 +3275,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3113
3275
  // package.json
3114
3276
  var package_default = {
3115
3277
  name: "@algolia/wizard",
3116
- version: "0.9.0-rc.88.85",
3278
+ version: "0.9.0-rc.93.91",
3117
3279
  description: "Magically implement Algolia functionality in your codebase",
3118
3280
  type: "module",
3119
3281
  engines: {
@@ -3262,9 +3424,11 @@ var CURATED_FRAMEWORKS = [
3262
3424
  "Next.js",
3263
3425
  "React",
3264
3426
  "Vue",
3265
- "Angular",
3266
- "Svelte",
3267
- "Vanilla JS"
3427
+ "Vanilla JS",
3428
+ "Django",
3429
+ "Laravel",
3430
+ "Rails",
3431
+ "Symfony"
3268
3432
  ];
3269
3433
  var OTHER_OPTION = "Other";
3270
3434
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3277,12 +3441,15 @@ var FRAMEWORK_ALIASES = {
3277
3441
  vuejs: "vue",
3278
3442
  angular: "angular",
3279
3443
  angularjs: "angular",
3280
- svelte: "svelte",
3281
- sveltekit: "svelte",
3282
3444
  vanillajs: "vanillajs",
3283
3445
  vanilla: "vanillajs",
3284
3446
  javascript: "vanillajs",
3285
- js: "vanillajs"
3447
+ js: "vanillajs",
3448
+ django: "django",
3449
+ laravel: "laravel",
3450
+ rails: "rails",
3451
+ rubyonrails: "rails",
3452
+ symfony: "symfony"
3286
3453
  };
3287
3454
  var isSameFramework = (a, b) => {
3288
3455
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3516,16 +3683,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3516
3683
  import z26 from "zod";
3517
3684
 
3518
3685
  // src/lib/worktree.ts
3519
- import { execFile, spawn as spawn3 } from "node:child_process";
3520
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3521
- import {
3522
- basename as basename2,
3523
- dirname as dirname7,
3524
- isAbsolute as isAbsolute2,
3525
- join as join9,
3526
- relative as relative2,
3527
- resolve as resolve3
3528
- } from "node:path";
3686
+ import { execFile } from "node:child_process";
3687
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3688
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3529
3689
  var MAX_BUFFER = 32 * 1024 * 1024;
3530
3690
  var MAX_WIZARD_WORKTREES = 3;
3531
3691
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3556,7 +3716,7 @@ async function isWorkingTreeDirty(repoRoot) {
3556
3716
  return out.trim().length > 0;
3557
3717
  }
3558
3718
  async function pruneOldWorktrees(repoRoot) {
3559
- const dir = join9(stateDir(repoRoot), "worktrees");
3719
+ const dir = join8(stateDir(repoRoot), "worktrees");
3560
3720
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3561
3721
  for (const slug of stale) {
3562
3722
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3567,7 +3727,7 @@ async function pruneOldWorktrees(repoRoot) {
3567
3727
  "worktree",
3568
3728
  "remove",
3569
3729
  "--force",
3570
- join9(dir, slug)
3730
+ join8(dir, slug)
3571
3731
  ]);
3572
3732
  await git(["-C", repoRoot, "branch", "-D", branch]);
3573
3733
  } catch (err) {
@@ -3581,113 +3741,13 @@ async function pruneOldWorktrees(repoRoot) {
3581
3741
  async function createWorktree(repoRoot) {
3582
3742
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3583
3743
  const dirSlug = branch.replace(/\//g, "-");
3584
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3744
+ const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3585
3745
  await git(["-C", repoRoot, "worktree", "prune"]);
3586
3746
  await pruneOldWorktrees(repoRoot);
3587
3747
  await mkdir6(dirname7(path), { recursive: true });
3588
3748
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3589
3749
  return { path, branch };
3590
3750
  }
3591
- async function installWorktreeDeps(worktreePath) {
3592
- try {
3593
- await readPackageJson(worktreePath);
3594
- } catch {
3595
- return { ok: true, output: "no package.json; skipped install" };
3596
- }
3597
- const pm = await detectPackageManager(worktreePath);
3598
- return new Promise((resolve4) => {
3599
- let output = "";
3600
- const child = spawn3(pm, ["install"], {
3601
- cwd: worktreePath,
3602
- stdio: ["ignore", "pipe", "pipe"]
3603
- });
3604
- child.stdout?.on("data", (d) => output += d);
3605
- child.stderr?.on("data", (d) => output += d);
3606
- child.on(
3607
- "error",
3608
- (err) => resolve4({
3609
- ok: false,
3610
- output: `Failed to run ${pm} install: ${err.message}`
3611
- })
3612
- );
3613
- child.on(
3614
- "close",
3615
- (code) => resolve4({ ok: code === 0, output: output.trim() })
3616
- );
3617
- });
3618
- }
3619
- var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3620
- function validateIngestEntrypoint(worktreePath, entrypoint) {
3621
- if (!entrypoint || entrypoint.startsWith("-")) {
3622
- return {
3623
- ok: false,
3624
- reason: `entrypoint "${entrypoint}" is not a plain file path`
3625
- };
3626
- }
3627
- const target = resolve3(worktreePath, entrypoint);
3628
- const rel = relative2(worktreePath, target);
3629
- if (rel.startsWith("..") || isAbsolute2(rel)) {
3630
- return {
3631
- ok: false,
3632
- reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3633
- };
3634
- }
3635
- return { ok: true, target };
3636
- }
3637
- async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3638
- if (!INGEST_RUNTIMES.includes(runtime)) {
3639
- return {
3640
- ran: false,
3641
- ok: false,
3642
- output: "",
3643
- reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3644
- };
3645
- }
3646
- const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3647
- if (!validated.ok) {
3648
- return { ran: false, ok: false, output: "", reason: validated.reason };
3649
- }
3650
- try {
3651
- if (!(await stat2(validated.target)).isFile()) {
3652
- return {
3653
- ran: false,
3654
- ok: false,
3655
- output: "",
3656
- reason: `entrypoint "${entrypoint}" is not a file`
3657
- };
3658
- }
3659
- } catch {
3660
- return {
3661
- ran: false,
3662
- ok: false,
3663
- output: "",
3664
- reason: `entrypoint "${entrypoint}" does not exist`
3665
- };
3666
- }
3667
- return new Promise((resolveRun) => {
3668
- let output = "";
3669
- const child = spawn3(runtime, [entrypoint], {
3670
- cwd: worktreePath,
3671
- shell: false,
3672
- stdio: ["ignore", "pipe", "pipe"],
3673
- env: { ...process.env, ...env }
3674
- });
3675
- child.stdout?.on("data", (d) => output += d);
3676
- child.stderr?.on("data", (d) => output += d);
3677
- child.on(
3678
- "error",
3679
- (err) => resolveRun({
3680
- ran: true,
3681
- ok: false,
3682
- output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3683
- })
3684
- );
3685
- child.on(
3686
- "close",
3687
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3688
- );
3689
- });
3690
- }
3691
3751
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3692
3752
  const trimmed = sourcePath.trim();
3693
3753
  if (!trimmed) {
@@ -3701,8 +3761,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3701
3761
  } catch {
3702
3762
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3703
3763
  }
3704
- const relPath = join9(ingestDir, basename2(source));
3705
- const dest = join9(worktreePath, relPath);
3764
+ const relPath = join8(ingestDir, basename2(source));
3765
+ const dest = join8(worktreePath, relPath);
3706
3766
  try {
3707
3767
  await mkdir6(dirname7(dest), { recursive: true });
3708
3768
  await copyFile(source, dest);
@@ -3720,7 +3780,7 @@ function hasEnvVar(content, name) {
3720
3780
  async function readEnvVar(worktreePath, name) {
3721
3781
  let content;
3722
3782
  try {
3723
- content = await readFile7(join9(worktreePath, ".env"), "utf8");
3783
+ content = await readFile6(join8(worktreePath, ".env"), "utf8");
3724
3784
  } catch (err) {
3725
3785
  if (err.code !== "ENOENT") throw err;
3726
3786
  return void 0;
@@ -3735,10 +3795,10 @@ async function readEnvVar(worktreePath, name) {
3735
3795
  return value;
3736
3796
  }
3737
3797
  async function writeSearchEnvValues(worktreePath, vars) {
3738
- const target = join9(worktreePath, ".env");
3798
+ const target = join8(worktreePath, ".env");
3739
3799
  let existing = "";
3740
3800
  try {
3741
- existing = await readFile7(target, "utf8");
3801
+ existing = await readFile6(target, "utf8");
3742
3802
  } catch (err) {
3743
3803
  if (err.code !== "ENOENT") throw err;
3744
3804
  }
@@ -3807,15 +3867,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3807
3867
  }
3808
3868
 
3809
3869
  // src/lib/algoliaDocs.ts
3810
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3811
- import { dirname as dirname8, join as join10 } from "node:path";
3870
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
3871
+ import { dirname as dirname8, join as join9 } from "node:path";
3812
3872
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3813
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3873
+ var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3814
3874
  function findDocsDir() {
3815
3875
  let dir = dirname8(fileURLToPath2(import.meta.url));
3816
3876
  for (; ; ) {
3817
- const candidate = join10(dir, DOCS_SUBPATH);
3818
- if (existsSync2(candidate)) return candidate;
3877
+ const candidate = join9(dir, DOCS_SUBPATH);
3878
+ if (existsSync(candidate)) return candidate;
3819
3879
  const parent = dirname8(dir);
3820
3880
  if (parent === dir) return void 0;
3821
3881
  dir = parent;
@@ -3837,7 +3897,7 @@ function loadAlgoliaDoc(language) {
3837
3897
  );
3838
3898
  return "";
3839
3899
  }
3840
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3900
+ return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3841
3901
  }
3842
3902
  function getNamedDoc(name, language) {
3843
3903
  const docsDir = findDocsDir();
@@ -3845,14 +3905,15 @@ function getNamedDoc(name, language) {
3845
3905
  logger.warn("docs/algolia-sdk not found");
3846
3906
  return "";
3847
3907
  }
3848
- const file = join10(docsDir, `${name}-${language}.md`);
3849
- if (!existsSync2(file)) {
3908
+ const file = join9(docsDir, `${name}-${language}.md`);
3909
+ if (!existsSync(file)) {
3850
3910
  logger.warn({ name, language }, "named SDK reference not found");
3851
3911
  return "";
3852
3912
  }
3853
3913
  return readFileSync(file, "utf8").trim();
3854
3914
  }
3855
3915
  function getFrameworkSpecificDoc(frameworks) {
3916
+ if (frameworks.length === 0) return "";
3856
3917
  const fw = frameworks.map((f) => f.toLowerCase());
3857
3918
  if (fw.includes("vue") || fw.includes("nuxt")) {
3858
3919
  return loadAlgoliaDoc("vue");
@@ -3860,9 +3921,6 @@ function getFrameworkSpecificDoc(frameworks) {
3860
3921
  if (fw.includes("react") || fw.includes("next.js")) {
3861
3922
  return loadAlgoliaDoc("react");
3862
3923
  }
3863
- if (fw.includes("angular")) {
3864
- return loadAlgoliaDoc("angular");
3865
- }
3866
3924
  return loadAlgoliaDoc("js");
3867
3925
  }
3868
3926
 
@@ -3890,11 +3948,7 @@ var implementSchema = z26.object({
3890
3948
  });
3891
3949
  var implementationOutputSchema = z26.object({
3892
3950
  summary: z26.string(),
3893
- // Ingestion only: a structured pair the wizard turns into an argv, never a
3894
- // free-form command string. `runtime` is allowlisted and `entrypoint` is
3895
- // validated worktree-relative, so the agent cannot inject extra commands.
3896
- runtime: z26.enum(INGEST_RUNTIMES).optional(),
3897
- entrypoint: z26.string().optional()
3951
+ ingestCommand: z26.string().optional()
3898
3952
  });
3899
3953
  var verificationOutputSchema = z26.object({
3900
3954
  summary: z26.string(),
@@ -3904,30 +3958,35 @@ var verificationOutputSchema = z26.object({
3904
3958
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3905
3959
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3906
3960
  var INGEST_DIR = ".algolia-wizard";
3907
- function detectUiFramework(language) {
3908
- const names = language.frameworks.map((f) => f.name.toLowerCase());
3909
- if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3910
- if (names.some((n) => n.includes("react") || n.includes("next")))
3911
- return "React";
3912
- if (names.some((n) => n.includes("angular"))) return "Angular";
3913
- return "JavaScript";
3914
- }
3915
- function frameworksForDoc(framework) {
3916
- switch (framework) {
3917
- case "React":
3918
- return ["react"];
3919
- case "Vue":
3920
- return ["vue"];
3921
- case "Angular":
3922
- return ["angular"];
3923
- case "JavaScript":
3924
- return [];
3925
- }
3961
+ var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
3962
+ function lower(entries) {
3963
+ return entries.map((entry) => entry.name.toLowerCase());
3926
3964
  }
3927
- function publicEnvPrefix(language) {
3928
- const frameworkNames = language.frameworks.map(
3929
- (framework) => framework.name.toLowerCase()
3965
+ function isJsProject(language) {
3966
+ return lower(language.languages).some(
3967
+ (name) => JS_LANGUAGES.some((js) => name.includes(js))
3930
3968
  );
3969
+ }
3970
+ var UI_FRAMEWORKS = [
3971
+ { match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
3972
+ { match: ["react", "next"], target: "React", doc: "react" },
3973
+ { match: ["angular"], target: "Angular" }
3974
+ ];
3975
+ function matchUiFramework(language) {
3976
+ const names = lower(language.frameworks);
3977
+ return UI_FRAMEWORKS.find(
3978
+ (ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
3979
+ );
3980
+ }
3981
+ function searchUiTarget(language) {
3982
+ return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
3983
+ }
3984
+ function frameworksForDoc(language) {
3985
+ if (!isJsProject(language)) return [];
3986
+ return [matchUiFramework(language)?.doc ?? "js"];
3987
+ }
3988
+ function publicEnvPrefix(language) {
3989
+ const frameworkNames = lower(language.frameworks);
3931
3990
  if (frameworkNames.some((name) => name.includes("next"))) {
3932
3991
  return "NEXT_PUBLIC_";
3933
3992
  }
@@ -3940,7 +3999,7 @@ function publicEnvPrefix(language) {
3940
3999
  if (frameworkNames.some((name) => name.includes("vite"))) {
3941
4000
  return "VITE_";
3942
4001
  }
3943
- return "PUBLIC_";
4002
+ return isJsProject(language) ? "PUBLIC_" : "";
3944
4003
  }
3945
4004
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
3946
4005
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
@@ -3979,7 +4038,10 @@ function baseInstructions(input) {
3979
4038
  // index-scoped keys then reject with a 403.
3980
4039
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
3981
4040
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
3982
- "Make minimal, idiomatic changes; do not touch unrelated code."
4041
+ "Make minimal, idiomatic changes; do not touch unrelated code.",
4042
+ `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4043
+ "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.",
4044
+ "runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
3983
4045
  ];
3984
4046
  }
3985
4047
  function sourceSpecificInstructions(input) {
@@ -3999,12 +4061,21 @@ function sourceSpecificInstructions(input) {
3999
4061
  generated: [
4000
4062
  "No real data source exists; use sample records for each confirmed entity.",
4001
4063
  "Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
4002
- "In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
4064
+ "In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
4003
4065
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4004
4066
  ]
4005
4067
  };
4006
4068
  return byLine[input.ingestionSource];
4007
4069
  }
4070
+ function algoliaClientDoc(input) {
4071
+ const doc = getNamedDoc("save-records", "js");
4072
+ if (!doc) return [];
4073
+ if (isJsProject(input.language)) return [doc];
4074
+ return [
4075
+ "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:",
4076
+ doc
4077
+ ];
4078
+ }
4008
4079
  function ingestionInstructions(input) {
4009
4080
  return [
4010
4081
  ...input.confirmed && input.confirmed.length ? [
@@ -4012,25 +4083,30 @@ function ingestionInstructions(input) {
4012
4083
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4013
4084
  `Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
4014
4085
  `Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
4015
- "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4086
+ "Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
4016
4087
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
4017
- getNamedDoc("save-records", "js"),
4018
- 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4088
+ ...algoliaClientDoc(input),
4089
+ "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.",
4090
+ 'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
4019
4091
  "The summary should be extremely concise.",
4020
- `Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
4021
4092
  ...sourceSpecificInstructions(input)
4022
4093
  ] : []
4023
4094
  ];
4024
4095
  }
4025
4096
  function searchInstructions(input) {
4026
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4097
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4027
4098
  return [
4028
4099
  "Implement an in-app Algolia search experience.",
4029
- `Build the search UI for ${input.uiFramework}.`,
4030
- "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4031
- doc,
4032
- `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
4100
+ `Build the search UI for ${input.searchUiTarget}.`,
4101
+ ...doc ? [
4102
+ "Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
4103
+ doc
4104
+ ] : [
4105
+ "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."
4106
+ ],
4107
+ `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working search input and results list against the target index.`,
4033
4108
  `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.`,
4109
+ "Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
4034
4110
  // The key is provisioned only after verification passes, so the agent never
4035
4111
  // sees one. It must also leave .env alone: the wizard reads that file to
4036
4112
  // decide whether a key already exists, and an agent-invented value there
@@ -4039,9 +4115,8 @@ function searchInstructions(input) {
4039
4115
  // Not the agent's to rename: the wizard writes these exact names into
4040
4116
  // ".env" right after this step, so a renamed prefix would leave the code
4041
4117
  // reading a var the wizard never wrote.
4042
- `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4043
- "Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
4044
- 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4118
+ `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4119
+ "Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
4045
4120
  "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
4046
4121
  ];
4047
4122
  }
@@ -4049,11 +4124,12 @@ function verificationInstructions(input) {
4049
4124
  return [
4050
4125
  "Verify the Algolia implementation changes in the current worktree.",
4051
4126
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4052
- "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4053
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4054
- "Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4127
+ "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.",
4128
+ "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.",
4129
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4130
+ "Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4055
4131
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4056
- `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4132
+ `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4057
4133
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4058
4134
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4059
4135
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4074,14 +4150,15 @@ var IMPLEMENT_CONFIG = {
4074
4150
  }
4075
4151
  };
4076
4152
  var useCaseToolMap = {
4077
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4078
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4079
- verification: [
4153
+ ingestion: [
4080
4154
  ...FS_READ_TOOLS,
4081
4155
  "writeFile",
4082
- "verifyImplementation",
4156
+ "writeCredentials",
4157
+ "runShell",
4083
4158
  "notifyUser"
4084
- ]
4159
+ ],
4160
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4161
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4085
4162
  };
4086
4163
  function toolsForUseCase(useCase, ingestionSource) {
4087
4164
  const tools = useCaseToolMap[useCase];
@@ -4104,15 +4181,30 @@ function formatSummary(useCase, summary) {
4104
4181
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4105
4182
  return `${label}: ${summary}`;
4106
4183
  }
4107
- function buildIngestCommand(worktree, runtime, entrypoint) {
4108
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4109
- }
4110
4184
  function parseIngestRecordCount(output) {
4111
4185
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4112
4186
  if (!match) return void 0;
4113
4187
  const count = Number(match[1]);
4114
4188
  return Number.isFinite(count) ? count : void 0;
4115
4189
  }
4190
+ function ingestOutcome(executions, ingestCommand) {
4191
+ const newestFirst = [...executions].reverse();
4192
+ const withCount = newestFirst.filter(
4193
+ (e) => parseIngestRecordCount(e.output ?? "") != null
4194
+ );
4195
+ const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4196
+ return {
4197
+ run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4198
+ recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4199
+ };
4200
+ }
4201
+ function makeToolContext(worktree, env = async () => ({})) {
4202
+ return createToolContext(
4203
+ DEFAULT_TOOL_LIMITS,
4204
+ worktree,
4205
+ createShellContext({ env, approve: storeApproval(worktree) })
4206
+ );
4207
+ }
4116
4208
  function verificationRetryInstructions(verification) {
4117
4209
  return [
4118
4210
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4191,9 +4283,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4191
4283
  const confirmed2 = normalized.confirmedEntities;
4192
4284
  const searchLocation = normalized.searchImplementationAnalysis;
4193
4285
  let appId;
4286
+ let ingestAppId;
4194
4287
  if (useCases.includes("search")) {
4195
4288
  appId = (await requireApplication()).id;
4196
4289
  }
4290
+ if (useCases.includes("ingestion")) {
4291
+ ingestAppId = appId ?? (await requireApplication()).id;
4292
+ }
4197
4293
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4198
4294
  try {
4199
4295
  process.chdir(worktree);
@@ -4230,7 +4326,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4230
4326
  ingestDir: INGEST_DIR,
4231
4327
  ingestionSource,
4232
4328
  uploadFilePath,
4233
- uiFramework: detectUiFramework(language)
4329
+ searchUiTarget: searchUiTarget(language)
4234
4330
  };
4235
4331
  const summaries = [];
4236
4332
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4253,41 +4349,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4253
4349
  }
4254
4350
  let finalSearchEnvVars = input.searchEnvVars;
4255
4351
  let agentRuns = 0;
4256
- let ingestRuntime;
4257
- let ingestEntrypoint;
4352
+ let ingestCommand;
4258
4353
  let ingestScriptRan = false;
4259
4354
  let ingestRecordCount;
4260
4355
  let ingestDurationMs;
4261
- let installFailed = false;
4262
4356
  let ingestOutcomeMessage;
4357
+ const ingestKeyAppId = ingestAppId;
4358
+ const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4359
+ [APP_ID_VAR]: ingestKeyAppId,
4360
+ [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4361
+ [INDEX_NAME_VAR]: targetIndex
4362
+ })) : void 0;
4363
+ const searchTools = makeToolContext(worktree);
4263
4364
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4264
4365
  if (agentRuns > 0) ctx.recordStepExecution();
4265
4366
  agentRuns += 1;
4266
- const result = await runAgent({
4367
+ return runAgent({
4267
4368
  instructions: buildAgentInstructions(
4268
4369
  currentUseCase,
4269
4370
  input,
4270
4371
  extraInstructions
4271
4372
  ),
4272
4373
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4273
- outputSchema: implementationOutputSchema
4274
- });
4275
- ctx.notify({
4276
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4277
- });
4278
- const installLogId = ctx.logStart("installWorktreeDeps", {
4279
- useCase: currentUseCase
4374
+ outputSchema: implementationOutputSchema,
4375
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4280
4376
  });
4281
- const install = await installWorktreeDeps(worktree);
4282
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4283
- if (!install.ok) {
4284
- installFailed = true;
4285
- logger.warn(
4286
- { useCase: currentUseCase, output: install.output },
4287
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4288
- );
4289
- }
4290
- return result;
4291
4377
  }
4292
4378
  async function runVerificationUseCase() {
4293
4379
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4295,111 +4381,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4295
4381
  return runAgent({
4296
4382
  instructions: buildAgentInstructions("verification", input),
4297
4383
  tools: toolsForUseCase("verification"),
4298
- outputSchema: verificationOutputSchema
4384
+ outputSchema: verificationOutputSchema,
4385
+ toolContext: searchTools
4299
4386
  });
4300
4387
  }
4301
4388
  if (useCases.includes("ingestion")) {
4302
- const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4303
- summaries.push(formatSummary("ingestion", summary));
4304
- ingestRuntime = runtime;
4305
- ingestEntrypoint = entrypoint;
4306
- if (ingestRuntime && ingestEntrypoint && !installFailed) {
4307
- ctx.clearNotices();
4308
- const runNow = await ctx.requestUserInput({
4309
- prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4310
- promptType: "acceptReject",
4311
- options: ["Yes", "No"],
4312
- messages: []
4313
- }) === true;
4314
- if (runNow) {
4315
- const ingestApp = await requireApplication();
4316
- const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4317
- ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4318
- const scriptLogId = ctx.logStart("runIngestScript", {
4319
- runtime: ingestRuntime,
4320
- entrypoint: ingestEntrypoint
4389
+ const result = await runImplementationUseCase("ingestion");
4390
+ summaries.push(formatSummary("ingestion", result.summary));
4391
+ ingestCommand = result.ingestCommand;
4392
+ const executions = (ingestionTools ?? searchTools).shell.executions;
4393
+ const { run: ingestRun, recordCount } = ingestOutcome(
4394
+ executions,
4395
+ ingestCommand
4396
+ );
4397
+ ingestScriptRan = ingestRun != null;
4398
+ ingestRecordCount = recordCount;
4399
+ ingestDurationMs = ingestRun?.durationMs;
4400
+ if (ingestScriptRan) {
4401
+ ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4402
+ if (ingestRecordCount != null) {
4403
+ track("AI Wizard Ingest Successful", {
4404
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4405
+ record_count: ingestRecordCount,
4406
+ duration_ms: ingestDurationMs ?? 0
4321
4407
  });
4322
- const startedAt = Date.now();
4323
- const run2 = await runIngestScript(
4324
- worktree,
4325
- ingestRuntime,
4326
- ingestEntrypoint,
4327
- {
4328
- [APP_ID_VAR]: ingestApp.id,
4329
- [API_KEY_VAR]: writeKey,
4330
- [INDEX_NAME_VAR]: targetIndex
4331
- }
4332
- );
4333
- ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4334
- ingestScriptRan = run2.ran && run2.ok;
4335
- if (ingestScriptRan) {
4336
- ingestDurationMs = Date.now() - startedAt;
4337
- ingestRecordCount = parseIngestRecordCount(run2.output);
4338
- if (ingestRecordCount != null) {
4339
- track("AI Wizard Ingest Successful", {
4340
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4341
- record_count: ingestRecordCount,
4342
- duration_ms: ingestDurationMs
4343
- });
4344
- }
4345
- }
4346
- let summaryLine;
4347
- let outcomeMessage;
4348
- if (!run2.ran) {
4349
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4350
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4351
- logger.warn(
4352
- {
4353
- runtime: ingestRuntime,
4354
- entrypoint: ingestEntrypoint,
4355
- reason: run2.reason
4356
- },
4357
- "implement: refused to auto-run ingestion script"
4358
- );
4359
- track("Error", {
4360
- step: "Push Data",
4361
- error: `ingestion script skipped: ${run2.reason}`,
4362
- product_area: "AI Wizard"
4363
- });
4364
- } else if (run2.ok) {
4365
- const status = "Ingestion run: succeeded.";
4366
- summaryLine = run2.output ? `${status}
4367
- ${run2.output}` : status;
4368
- outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4369
- } else {
4370
- const status = "\u26A0\uFE0F Ingestion run failed:";
4371
- summaryLine = run2.output ? `${status}
4372
- ${run2.output}` : status;
4373
- outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4374
- logger.warn(
4375
- {
4376
- runtime: ingestRuntime,
4377
- entrypoint: ingestEntrypoint,
4378
- output: run2.output
4379
- },
4380
- "implement: ingestion script run failed"
4381
- );
4382
- track("Error", {
4383
- step: "Push Data",
4384
- error: run2.output || "ingestion script exited non-zero",
4385
- product_area: "AI Wizard"
4386
- });
4387
- }
4388
- summaries.push(summaryLine);
4389
- ingestOutcomeMessage = outcomeMessage;
4390
4408
  }
4409
+ } else {
4410
+ const rejected = executions.some((e) => !e.approved);
4411
+ const reason = rejected ? "you declined to run it" : "no successful run was recorded";
4412
+ ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4413
+ summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
4414
+ logger.warn(
4415
+ { ingestCommand, rejected, commandsRun: executions.length },
4416
+ "implement: ingestion script did not complete successfully"
4417
+ );
4418
+ track("Error", {
4419
+ step: "Push Data",
4420
+ error: `ingestion did not run: ${reason}`,
4421
+ product_area: "AI Wizard"
4422
+ });
4391
4423
  }
4392
4424
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4393
- if (ingestRuntime && ingestEntrypoint) {
4394
- commandMessages.push(
4395
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4396
- );
4425
+ if (ingestCommand) {
4426
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4397
4427
  }
4398
4428
  await ctx.requestUserInput({
4399
4429
  prompt: "",
4400
4430
  promptType: "enterToContinue",
4401
4431
  options: [],
4402
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4432
+ messages: [ingestOutcomeMessage, ...commandMessages]
4403
4433
  });
4404
4434
  }
4405
4435
  if (useCases.includes("search")) {
@@ -4516,22 +4546,13 @@ ${run2.output}` : status;
4516
4546
  "implement: agent reported success but no files changed in the worktree"
4517
4547
  );
4518
4548
  }
4519
- if (installFailed) {
4520
- summaries.push(
4521
- '\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
4522
- );
4523
- }
4524
4549
  return {
4525
4550
  ingestionSource,
4526
4551
  filesChanged,
4527
4552
  summary: summaries.join("\n\n"),
4528
4553
  worktreePath: worktree,
4529
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4530
- ingestCommand: buildIngestCommand(
4531
- worktree,
4532
- ingestRuntime,
4533
- ingestEntrypoint
4534
- ),
4554
+ ...useCases.includes("ingestion") && ingestCommand ? {
4555
+ ingestCommand,
4535
4556
  ingestScriptRan,
4536
4557
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4537
4558
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4859,7 +4880,7 @@ function parseCliArgs(argv) {
4859
4880
 
4860
4881
  // src/lib/resetState.ts
4861
4882
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4862
- import { join as join11 } from "node:path";
4883
+ import { join as join10 } from "node:path";
4863
4884
  var KEEP = ["wizard.log"];
4864
4885
  async function resetProjectState() {
4865
4886
  const dir = stateDir();
@@ -4873,14 +4894,14 @@ async function resetProjectState() {
4873
4894
  const targets = entries.filter((name) => !KEEP.includes(name));
4874
4895
  await Promise.all(
4875
4896
  targets.map(
4876
- (name) => rm2(join11(dir, name), { recursive: true, force: true })
4897
+ (name) => rm2(join10(dir, name), { recursive: true, force: true })
4877
4898
  )
4878
4899
  );
4879
4900
  return { dir, removed: targets };
4880
4901
  }
4881
4902
 
4882
4903
  // src/main.tsx
4883
- import { jsx as jsx14 } from "react/jsx-runtime";
4904
+ import { jsx as jsx15 } from "react/jsx-runtime";
4884
4905
  async function startup() {
4885
4906
  setProjectRoot(process.cwd());
4886
4907
  let args;
@@ -4930,7 +4951,7 @@ ${formatStepList(workflow)}`);
4930
4951
  }
4931
4952
  async function run(workflow) {
4932
4953
  const store = useWizard.getState();
4933
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4954
+ const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4934
4955
  await store.waitForStart();
4935
4956
  let user = await getUser();
4936
4957
  if (!user) {