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

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,60 @@ 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
+ });
669
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
670
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
671
+ /* @__PURE__ */ jsxs4(
672
+ Box5,
673
+ {
674
+ flexDirection: "column",
675
+ paddingLeft: 2,
676
+ borderStyle: "single",
677
+ borderColor: COLORS.success,
678
+ borderTop: false,
679
+ borderBottom: false,
680
+ borderRight: false,
681
+ gap: 1,
682
+ children: [
683
+ /* @__PURE__ */ jsxs4(Box5, { children: [
684
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
685
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
686
+ ] }),
687
+ /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
688
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
689
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
690
+ ] }),
691
+ command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
692
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
693
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
694
+ ] })
695
+ ]
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
699
+ /* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
700
+ /* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" })
701
+ ] })
702
+ ] });
703
+ }
704
+
656
705
  // src/ui/SelectPrompt.tsx
657
- import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
706
+ import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
658
707
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
659
708
 
660
709
  // src/ui/ScrollView.tsx
661
- import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
710
+ import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
662
711
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
663
- import { jsxs as jsxs4 } from "react/jsx-runtime";
712
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
664
713
  var INDICATOR_ROWS = 2;
665
714
  function fittedWidth(node, columns) {
666
715
  let left = 0;
@@ -730,14 +779,14 @@ function useScrollWindow({
730
779
  };
731
780
  }
732
781
  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: [
782
+ return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
783
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
735
784
  "\u2191 ",
736
785
  scroll.hiddenAbove,
737
786
  " more"
738
787
  ] }),
739
788
  children,
740
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
789
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
741
790
  "\u2193 ",
742
791
  scroll.hiddenBelow,
743
792
  " more"
@@ -746,7 +795,7 @@ function ScrollView({ scroll, children }) {
746
795
  }
747
796
 
748
797
  // src/ui/SelectPrompt.tsx
749
- import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
798
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
750
799
  var CANCEL = "cancel";
751
800
  var ARROW_WIDTH = 4;
752
801
  var COLUMN_GAP = 2;
@@ -808,7 +857,7 @@ function SelectPrompt({
808
857
  revealIndex(index);
809
858
  }, [index, revealIndex]);
810
859
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
811
- useInput((input, key) => {
860
+ useInput2((input, key) => {
812
861
  if (rows.length === 0) return;
813
862
  if (key.upArrow || input === "k") {
814
863
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -831,56 +880,56 @@ function SelectPrompt({
831
880
  }
832
881
  }
833
882
  });
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 })
883
+ return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
884
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
885
+ error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
886
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
887
+ table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
888
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
889
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
890
+ helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
842
891
  ] })
843
892
  ] }),
844
- /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
893
+ /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
845
894
  const i = scroll.offset + visibleIndex;
846
895
  const highlighted = i === index;
847
896
  const isCancel = i === cancelIndex;
848
897
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
849
898
  const sec = isCancel ? void 0 : secondary?.[i];
850
899
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
851
- const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
900
+ const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
852
901
  highlighted ? "\u276F " : " ",
853
902
  bullet,
854
903
  option
855
904
  ] });
856
905
  const isText = sec?.kind === "text";
857
- return /* @__PURE__ */ jsxs5(
858
- Box6,
906
+ return /* @__PURE__ */ jsxs6(
907
+ Box7,
859
908
  {
860
909
  width: isText ? "100%" : barWidth,
861
910
  paddingX: 1,
862
911
  paddingY: 1,
863
912
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
864
913
  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,
914
+ /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
915
+ isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
916
+ Text7,
868
917
  {
869
918
  wrap: "truncate",
870
919
  color: highlighted ? COLORS.primary : COLORS.muted,
871
920
  children: sec.value
872
921
  }
873
922
  ) }),
874
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
923
+ sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
875
924
  ]
876
925
  },
877
926
  `row-${i}`
878
927
  );
879
928
  }) }),
880
- /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
929
+ /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
881
930
  i > 0 ? " " : "",
882
- /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
883
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
931
+ /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
932
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
884
933
  " ",
885
934
  label
886
935
  ] })
@@ -889,23 +938,23 @@ function SelectPrompt({
889
938
  }
890
939
 
891
940
  // src/ui/PromptInput.tsx
892
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
941
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
893
942
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
894
943
  function EnterToContinuePrompt({
895
944
  question,
896
945
  messages,
897
946
  onDecide
898
947
  }) {
899
- useInput2((_input, key) => {
948
+ useInput3((_input, key) => {
900
949
  if (key.return) onDecide(true);
901
950
  else if (key.escape) onDecide(false);
902
951
  });
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" })
952
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
953
+ messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
954
+ question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
955
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
956
+ /* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
957
+ /* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
909
958
  ] })
910
959
  ] });
911
960
  }
@@ -913,11 +962,11 @@ function PromptInput() {
913
962
  const { phase, inputReq, submitInput } = useWizard();
914
963
  const [draft, setDraft] = useState5("");
915
964
  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" }) });
965
+ return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
917
966
  }
918
967
  if (phase !== "awaitingInput" || !inputReq) return null;
919
968
  if (inputReq.promptType === "multipleChoice") {
920
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
969
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
921
970
  SelectPrompt,
922
971
  {
923
972
  question: inputReq.prompt,
@@ -934,7 +983,7 @@ function PromptInput() {
934
983
  ) });
935
984
  }
936
985
  if (inputReq.promptType === "multiSelect") {
937
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
986
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
938
987
  SelectPrompt,
939
988
  {
940
989
  multi: true,
@@ -949,7 +998,7 @@ function PromptInput() {
949
998
  ) });
950
999
  }
951
1000
  if (inputReq.promptType === "notice") {
952
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1001
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
953
1002
  SelectPrompt,
954
1003
  {
955
1004
  question: inputReq.prompt,
@@ -960,7 +1009,7 @@ function PromptInput() {
960
1009
  ) });
961
1010
  }
962
1011
  if (inputReq.promptType === "enterToContinue") {
963
- return /* @__PURE__ */ jsx5(
1012
+ return /* @__PURE__ */ jsx6(
964
1013
  EnterToContinuePrompt,
965
1014
  {
966
1015
  question: inputReq.prompt,
@@ -969,9 +1018,12 @@ function PromptInput() {
969
1018
  }
970
1019
  );
971
1020
  }
1021
+ if (inputReq.promptType === "commandApproval" && inputReq.command) {
1022
+ return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1023
+ }
972
1024
  if (inputReq.promptType === "acceptReject") {
973
1025
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
974
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1026
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
975
1027
  SelectPrompt,
976
1028
  {
977
1029
  question: inputReq.prompt,
@@ -982,15 +1034,15 @@ function PromptInput() {
982
1034
  }
983
1035
  ) });
984
1036
  }
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: [
1037
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1038
+ inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
1039
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
1040
+ /* @__PURE__ */ jsxs7(Box8, { children: [
1041
+ /* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
990
1042
  inputReq.prompt,
991
1043
  " "
992
1044
  ] }),
993
- /* @__PURE__ */ jsx5(
1045
+ /* @__PURE__ */ jsx6(
994
1046
  TextInput,
995
1047
  {
996
1048
  value: draft,
@@ -1008,7 +1060,7 @@ function PromptInput() {
1008
1060
  // src/ui/Welcome.tsx
1009
1061
  import { dirname as dirname2, join as join3 } from "node:path";
1010
1062
  import { fileURLToPath } from "node:url";
1011
- import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1063
+ import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1012
1064
 
1013
1065
  // src/ui/copy/welcome.ts
1014
1066
  var sidebarItems = [
@@ -1021,12 +1073,12 @@ var sidebarItems = [
1021
1073
  description: "push 100 records to Algolia in seconds"
1022
1074
  },
1023
1075
  {
1024
- title: "detect your framework",
1025
- description: "React, Vue, Angular, Vanilla JS"
1076
+ title: "detect your stack",
1077
+ description: "whatever language and framework you already use"
1026
1078
  },
1027
1079
  {
1028
1080
  title: "scaffold a search UI",
1029
- description: "a styled InstantSearch component, wired into your app"
1081
+ description: "a search box and results, wired into your app"
1030
1082
  },
1031
1083
  {
1032
1084
  title: "ship it",
@@ -1036,20 +1088,20 @@ var sidebarItems = [
1036
1088
 
1037
1089
  // src/ui/Welcome.tsx
1038
1090
  import Image, { InkPictureProvider } from "ink-picture";
1039
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1091
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1040
1092
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1041
1093
  function SidebarItem({
1042
1094
  title,
1043
1095
  description
1044
1096
  }) {
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 })
1097
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1098
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
1099
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
1100
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
1049
1101
  ] }),
1050
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1051
- /* @__PURE__ */ jsx6(Spacer, {}),
1052
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1102
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1103
+ /* @__PURE__ */ jsx7(Spacer, {}),
1104
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1053
1105
  ] })
1054
1106
  ] });
1055
1107
  }
@@ -1057,7 +1109,7 @@ function Welcome() {
1057
1109
  const confirmStart = useWizard((s) => s.confirmStart);
1058
1110
  const openLearnMore = useWizard((s) => s.openLearnMore);
1059
1111
  const { rows } = useWindowSize6();
1060
- useInput3((input, key) => {
1112
+ useInput4((input, key) => {
1061
1113
  if (key.return) confirmStart();
1062
1114
  else if (input === "i") openLearnMore();
1063
1115
  });
@@ -1075,16 +1127,16 @@ function Welcome() {
1075
1127
  if (rows < 30) {
1076
1128
  layout = scales["small"];
1077
1129
  }
1078
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1079
- /* @__PURE__ */ jsx6(
1080
- Box8,
1130
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1131
+ /* @__PURE__ */ jsx7(
1132
+ Box9,
1081
1133
  {
1082
1134
  paddingY: layout.main.padding.y,
1083
1135
  paddingX: layout.main.padding.x,
1084
1136
  flexDirection: "column",
1085
1137
  justifyContent: "center",
1086
- children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1087
- /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1138
+ children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1139
+ /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1088
1140
  Image,
1089
1141
  {
1090
1142
  src: IMAGE_PATH,
@@ -1095,16 +1147,16 @@ function Welcome() {
1095
1147
  protocol: "halfBlock"
1096
1148
  }
1097
1149
  ) }),
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" })
1150
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1151
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
1152
+ /* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
1153
+ /* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1102
1154
  ] })
1103
1155
  ] })
1104
1156
  }
1105
1157
  ),
1106
- /* @__PURE__ */ jsxs7(
1107
- Box8,
1158
+ /* @__PURE__ */ jsxs8(
1159
+ Box9,
1108
1160
  {
1109
1161
  backgroundColor: COLORS.bg.sidebar,
1110
1162
  width: 40,
@@ -1114,8 +1166,8 @@ function Welcome() {
1114
1166
  flexDirection: "column",
1115
1167
  justifyContent: "center",
1116
1168
  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))
1169
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1170
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
1119
1171
  ]
1120
1172
  }
1121
1173
  )
@@ -1124,7 +1176,7 @@ function Welcome() {
1124
1176
 
1125
1177
  // src/ui/LearnMore.tsx
1126
1178
  import { Fragment as Fragment2 } from "react";
1127
- import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1179
+ import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1128
1180
 
1129
1181
  // src/ui/copy/learn-more.ts
1130
1182
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1132,12 +1184,17 @@ var accessItems = [
1132
1184
  {
1133
1185
  tag: "READ",
1134
1186
  title: "Project files",
1135
- description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1187
+ description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1136
1188
  },
1137
1189
  {
1138
1190
  tag: "WRITE",
1139
1191
  title: "Code changes",
1140
- description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1192
+ description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1193
+ },
1194
+ {
1195
+ tag: "EXEC",
1196
+ title: "Setup commands",
1197
+ 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
1198
  },
1142
1199
  {
1143
1200
  tag: "NET",
@@ -1147,13 +1204,13 @@ var accessItems = [
1147
1204
  {
1148
1205
  tag: "KEY",
1149
1206
  title: "Credentials",
1150
- description: "saves your Admin API key to .env and adds it to .gitignore."
1207
+ description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1151
1208
  }
1152
1209
  ];
1153
1210
  var neverItems = [
1154
1211
  "Send your source code to a model or third party",
1155
1212
  "Commit or push to git",
1156
- "Touch files outside your project directory"
1213
+ "Run a command you haven't approved"
1157
1214
  ];
1158
1215
  var policyLinks = [
1159
1216
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1161,10 +1218,11 @@ var policyLinks = [
1161
1218
  ];
1162
1219
 
1163
1220
  // src/ui/LearnMore.tsx
1164
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1221
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1165
1222
  var TAG_COLORS = {
1166
1223
  READ: COLORS.success,
1167
1224
  WRITE: COLORS.badge,
1225
+ EXEC: COLORS.danger,
1168
1226
  NET: COLORS.accent,
1169
1227
  KEY: COLORS.muted
1170
1228
  };
@@ -1177,12 +1235,12 @@ function NeverLine({
1177
1235
  }) {
1178
1236
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1179
1237
  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" }),
1238
+ return /* @__PURE__ */ jsxs9(Text10, { children: [
1239
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1182
1240
  " ".repeat(NEVER_BOX_PAD_X),
1183
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1241
+ segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1184
1242
  " ".repeat(rightPad),
1185
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1243
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1186
1244
  ] });
1187
1245
  }
1188
1246
  function LearnMore() {
@@ -1190,12 +1248,12 @@ function LearnMore() {
1190
1248
  const backToHome = useWizard((s) => s.backToHome);
1191
1249
  const { columns } = useWindowSize7();
1192
1250
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1193
- useInput4((_input, key) => {
1251
+ useInput5((_input, key) => {
1194
1252
  if (key.escape) backToHome();
1195
1253
  else if (key.return) confirmStart();
1196
1254
  });
1197
- return /* @__PURE__ */ jsxs8(
1198
- Box9,
1255
+ return /* @__PURE__ */ jsxs9(
1256
+ Box10,
1199
1257
  {
1200
1258
  flexDirection: "column",
1201
1259
  paddingX: PADDING_X,
@@ -1203,31 +1261,31 @@ function LearnMore() {
1203
1261
  width: "100%",
1204
1262
  gap: 1,
1205
1263
  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}` })
1264
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1265
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
1266
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
1267
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1268
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1269
+ /* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1270
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
1271
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1272
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1215
1273
  ] }) })
1216
1274
  ] })
1217
1275
  ] }, 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(
1276
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
1277
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1278
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1279
+ /* @__PURE__ */ jsx8(
1222
1280
  NeverLine,
1223
1281
  {
1224
1282
  width: dividerWidth,
1225
1283
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1226
1284
  }
1227
1285
  ),
1228
- neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1229
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1230
- /* @__PURE__ */ jsx7(
1286
+ neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1287
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1288
+ /* @__PURE__ */ jsx8(
1231
1289
  NeverLine,
1232
1290
  {
1233
1291
  width: dividerWidth,
@@ -1239,24 +1297,24 @@ function LearnMore() {
1239
1297
  }
1240
1298
  )
1241
1299
  ] }, item)),
1242
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1243
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1300
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1301
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1244
1302
  ] }),
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 })
1303
+ /* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1304
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1305
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
1248
1306
  ] }, 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" })
1307
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1308
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1309
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1310
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
1311
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
1254
1312
  ] }),
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" })
1313
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1314
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1315
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
1316
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
1317
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
1260
1318
  ] })
1261
1319
  ] })
1262
1320
  ]
@@ -1265,10 +1323,10 @@ function LearnMore() {
1265
1323
  }
1266
1324
 
1267
1325
  // src/ui/Sidebar.tsx
1268
- import { Box as Box12, Text as Text12 } from "ink";
1326
+ import { Box as Box13, Text as Text13 } from "ink";
1269
1327
 
1270
1328
  // src/ui/Steps.tsx
1271
- import { Box as Box10, Text as Text10 } from "ink";
1329
+ import { Box as Box11, Text as Text11 } from "ink";
1272
1330
  import Spinner from "ink-spinner";
1273
1331
 
1274
1332
  // src/core/persistence.ts
@@ -1297,12 +1355,12 @@ async function clearWorkflowState(workflowId) {
1297
1355
  }
1298
1356
 
1299
1357
  // src/ui/Steps.tsx
1300
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1358
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1301
1359
  function Steps() {
1302
1360
  const { steps } = useWizard();
1303
1361
  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],
1362
+ 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: [
1363
+ s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
1306
1364
  " ",
1307
1365
  s.title
1308
1366
  ] }) }, s.id)) });
@@ -1311,27 +1369,27 @@ function CurrentStep() {
1311
1369
  const { steps } = useWizard();
1312
1370
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1313
1371
  if (!currentStep) return null;
1314
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1315
- /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1372
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1373
+ /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1316
1374
  " ",
1317
1375
  ` ${currentStep.title}`
1318
1376
  ] });
1319
1377
  }
1320
1378
 
1321
1379
  // 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";
1380
+ import { Box as Box12, Text as Text12 } from "ink";
1381
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1324
1382
  function Progress() {
1325
1383
  const { steps, currentStepIndex } = useWizard();
1326
1384
  const visibleSteps = steps.filter(isStepVisible);
1327
1385
  if (visibleSteps.length === 0) return null;
1328
1386
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1329
1387
  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 })
1388
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1389
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1390
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
1391
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
1392
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
1335
1393
  ] });
1336
1394
  }
1337
1395
 
@@ -1342,10 +1400,10 @@ var sidebarCommands = [
1342
1400
  ];
1343
1401
 
1344
1402
  // src/ui/Sidebar.tsx
1345
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1403
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1346
1404
  function Sidebar() {
1347
- return /* @__PURE__ */ jsxs11(
1348
- Box12,
1405
+ return /* @__PURE__ */ jsxs12(
1406
+ Box13,
1349
1407
  {
1350
1408
  backgroundColor: "#14171E",
1351
1409
  width: 30,
@@ -1354,16 +1412,16 @@ function Sidebar() {
1354
1412
  flexDirection: "column",
1355
1413
  justifyContent: "space-between",
1356
1414
  children: [
1357
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1358
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1359
- /* @__PURE__ */ jsx10(Steps, {})
1415
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1416
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1417
+ /* @__PURE__ */ jsx11(Steps, {})
1360
1418
  ] }),
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 })
1419
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1420
+ /* @__PURE__ */ jsx11(Progress, {}),
1421
+ /* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
1422
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1423
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1424
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
1367
1425
  ] });
1368
1426
  }) })
1369
1427
  ] })
@@ -1373,12 +1431,12 @@ function Sidebar() {
1373
1431
  }
1374
1432
 
1375
1433
  // 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";
1434
+ import { Box as Box14, Text as Text14 } from "ink";
1435
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1378
1436
  function Ribbon() {
1379
1437
  const firstCommand = sidebarCommands[0];
1380
- return /* @__PURE__ */ jsxs12(
1381
- Box13,
1438
+ return /* @__PURE__ */ jsxs13(
1439
+ Box14,
1382
1440
  {
1383
1441
  backgroundColor: "#14171E",
1384
1442
  flexDirection: "row",
@@ -1386,11 +1444,11 @@ function Ribbon() {
1386
1444
  paddingX: 2,
1387
1445
  paddingY: 1,
1388
1446
  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 })
1447
+ /* @__PURE__ */ jsx12(Progress, {}),
1448
+ /* @__PURE__ */ jsx12(CurrentStep, {}),
1449
+ /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1450
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1451
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
1394
1452
  ] })
1395
1453
  ]
1396
1454
  }
@@ -1401,8 +1459,8 @@ function Ribbon() {
1401
1459
  import { useState as useState6 } from "react";
1402
1460
 
1403
1461
  // 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";
1462
+ import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1463
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1406
1464
  var KIND_COLOR = {
1407
1465
  tool: COLORS.primary,
1408
1466
  prompt: COLORS.badge
@@ -1433,14 +1491,14 @@ function formatTimestamp(ms) {
1433
1491
  function Logs() {
1434
1492
  const logs = useWizard((s) => s.logs);
1435
1493
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1436
- useInput5((_input, key) => {
1494
+ useInput6((_input, key) => {
1437
1495
  if (key.upArrow) scroll.scrollBy(-1);
1438
1496
  else if (key.downArrow) scroll.scrollBy(1);
1439
1497
  });
1440
1498
  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) => {
1499
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1500
+ logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
1501
+ /* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
1444
1502
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1445
1503
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1446
1504
  const rawPreview = rawInputText(entry.input);
@@ -1450,14 +1508,14 @@ function Logs() {
1450
1508
  const name = truncate2(entry.name, budget);
1451
1509
  budget -= name.length;
1452
1510
  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 })
1511
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
1512
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
1513
+ /* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
1514
+ preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
1515
+ durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
1458
1516
  ] }, entry.id);
1459
1517
  }) }),
1460
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1518
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1461
1519
  ] });
1462
1520
  }
1463
1521
 
@@ -1649,7 +1707,7 @@ function track(event, payload) {
1649
1707
  }
1650
1708
 
1651
1709
  // src/ui/App.tsx
1652
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1710
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1653
1711
  function App() {
1654
1712
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1655
1713
  const { exit } = useApp();
@@ -1657,7 +1715,7 @@ function App() {
1657
1715
  const [showLogs, setShowLogs] = useState6(false);
1658
1716
  const finished = phase === "done" || phase === "error";
1659
1717
  const currentStep = steps[currentStepIndex];
1660
- useInput6(
1718
+ useInput7(
1661
1719
  (_input, key) => {
1662
1720
  if (key.return) {
1663
1721
  exit();
@@ -1665,7 +1723,7 @@ function App() {
1665
1723
  },
1666
1724
  { isActive: finished }
1667
1725
  );
1668
- useInput6((_input, key) => {
1726
+ useInput7((_input, key) => {
1669
1727
  if (phase === "idle" || phase === "authenticating") return;
1670
1728
  if (key.tab) {
1671
1729
  setShowLogs(!showLogs);
@@ -1676,8 +1734,8 @@ function App() {
1676
1734
  });
1677
1735
  }
1678
1736
  });
1679
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1680
- useInput6((_input, key) => {
1737
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1738
+ useInput7((_input, key) => {
1681
1739
  if (escOwnedElsewhere) return;
1682
1740
  if (key.escape) {
1683
1741
  track("AI Wizard Interaction", {
@@ -1696,8 +1754,8 @@ function App() {
1696
1754
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1697
1755
  the whole screen, and the scrolling throws off its cursor arithmetic —
1698
1756
  flicker and leftover rows. */
1699
- /* @__PURE__ */ jsxs14(
1700
- Box15,
1757
+ /* @__PURE__ */ jsxs15(
1758
+ Box16,
1701
1759
  {
1702
1760
  backgroundColor: COLORS.bg.main,
1703
1761
  flexDirection: "row",
@@ -1705,16 +1763,16 @@ function App() {
1705
1763
  height: scrollsPastViewport ? void 0 : rows,
1706
1764
  overflow: scrollsPastViewport ? "visible" : "hidden",
1707
1765
  children: [
1708
- mainWindowVisible && /* @__PURE__ */ jsxs14(
1709
- Box15,
1766
+ mainWindowVisible && /* @__PURE__ */ jsxs15(
1767
+ Box16,
1710
1768
  {
1711
1769
  flexDirection,
1712
1770
  width: "100%",
1713
1771
  maxHeight: rows,
1714
1772
  justifyContent: "space-between",
1715
1773
  children: [
1716
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1717
- Box15,
1774
+ showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1775
+ Box16,
1718
1776
  {
1719
1777
  flexDirection: "column",
1720
1778
  paddingX: 4,
@@ -1722,26 +1780,26 @@ function App() {
1722
1780
  width: showSidebar ? 70 : "100%",
1723
1781
  flexGrow: 1,
1724
1782
  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." })
1783
+ phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
1784
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1785
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1728
1786
  ] }),
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: [
1787
+ /* @__PURE__ */ jsx14(CliOutput, {}),
1788
+ /* @__PURE__ */ jsx14(Notices, {}),
1789
+ /* @__PURE__ */ jsx14(PromptInput, {}),
1790
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
1791
+ phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1734
1792
  "\u2716 ",
1735
1793
  error
1736
1794
  ] }) })
1737
1795
  ]
1738
1796
  }
1739
1797
  ),
1740
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1798
+ showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1741
1799
  ]
1742
1800
  }
1743
1801
  ),
1744
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1802
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1745
1803
  ]
1746
1804
  }
1747
1805
  )
@@ -1821,7 +1879,7 @@ async function ensureConsent() {
1821
1879
  if (config.aiConsent) return;
1822
1880
  const store = useWizard.getState();
1823
1881
  const answer = await store.requestUserInput({
1824
- prompt: "Wizard will make AI-authored changes to this repository.",
1882
+ 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
1883
  promptType: "enterToContinue",
1826
1884
  options: []
1827
1885
  });
@@ -2576,11 +2634,19 @@ import z13 from "zod";
2576
2634
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2577
2635
  import { join as join7 } from "node:path";
2578
2636
  var MAX_QUERY_LENGTH = 1e3;
2637
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
2638
+ "node_modules",
2639
+ "dist",
2640
+ "build",
2641
+ "vendor",
2642
+ "venv",
2643
+ "__pycache__",
2644
+ "target"
2645
+ ]);
2579
2646
  async function walkFiles(dir) {
2580
- const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2581
2647
  const out = [];
2582
2648
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2583
- if (e.name.startsWith(".") || skip.has(e.name)) continue;
2649
+ if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2584
2650
  const full = join7(dir, e.name);
2585
2651
  if (e.isDirectory()) out.push(...await walkFiles(full));
2586
2652
  else if (e.isFile()) out.push(full);
@@ -2634,92 +2700,188 @@ function searchFilesTool(ctx) {
2634
2700
  });
2635
2701
  }
2636
2702
 
2637
- // src/lib/tools/verifyImplementation.ts
2703
+ // src/lib/tools/runShell.ts
2638
2704
  import { tool as tool8 } from "ai";
2639
2705
  import z14 from "zod";
2706
+ import { relative as relative2 } from "node:path";
2640
2707
 
2641
- // src/lib/tools/utils/runCommand.ts
2708
+ // src/lib/tools/utils/runShell.ts
2642
2709
  import { spawn as spawn2 } from "node:child_process";
2643
- function runCommand(command, args, cwd) {
2710
+
2711
+ // src/lib/tools/context.ts
2712
+ var DEFAULT_TOOL_LIMITS = {
2713
+ list: 10,
2714
+ search: 10,
2715
+ read: 20,
2716
+ match: 100,
2717
+ shell: 30
2718
+ };
2719
+ var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2720
+ async function refuseByDefault() {
2721
+ return "reject";
2722
+ }
2723
+ function createShellContext(overrides = {}) {
2724
+ return {
2725
+ env: async () => ({}),
2726
+ timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2727
+ executions: [],
2728
+ approve: refuseByDefault,
2729
+ ...overrides
2730
+ };
2731
+ }
2732
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2733
+ return {
2734
+ root: cwd,
2735
+ cwd,
2736
+ limits: { ...limits },
2737
+ counts: { list: 0, search: 0, read: 0, shell: 0 },
2738
+ shell: shell2
2739
+ };
2740
+ }
2741
+
2742
+ // src/lib/tools/utils/runShell.ts
2743
+ var SIGKILL_DELAY_MS = 5e3;
2744
+ var HEAD_CHARS = 4e3;
2745
+ var TAIL_CHARS = 8e3;
2746
+ function truncateOutput(output) {
2747
+ if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
2748
+ const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
2749
+ return [
2750
+ output.slice(0, HEAD_CHARS),
2751
+ `
2752
+ \u2026 [${omitted} characters omitted] \u2026
2753
+ `,
2754
+ output.slice(-TAIL_CHARS)
2755
+ ].join("");
2756
+ }
2757
+ function runShell(command, opts) {
2758
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2759
+ const startedAt = Date.now();
2644
2760
  return new Promise((resolve4) => {
2645
2761
  let output = "";
2646
- const child = spawn2(command, args, {
2647
- cwd,
2648
- stdio: ["ignore", "pipe", "pipe"]
2762
+ let timedOut = false;
2763
+ let settled = false;
2764
+ const child = spawn2(command, {
2765
+ shell: true,
2766
+ cwd: opts.cwd,
2767
+ stdio: ["ignore", "pipe", "pipe"],
2768
+ env: { ...process.env, ...opts.env }
2649
2769
  });
2770
+ const finish = (exitCode) => {
2771
+ if (settled) return;
2772
+ settled = true;
2773
+ clearTimeout(timer);
2774
+ clearTimeout(killTimer);
2775
+ resolve4({
2776
+ exitCode,
2777
+ output: truncateOutput(output.trim()),
2778
+ timedOut,
2779
+ durationMs: Date.now() - startedAt
2780
+ });
2781
+ };
2782
+ let killTimer;
2783
+ const timer = setTimeout(() => {
2784
+ timedOut = true;
2785
+ output += `
2786
+ [timed out after ${timeoutMs}ms]`;
2787
+ child.kill("SIGTERM");
2788
+ killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
2789
+ }, timeoutMs);
2650
2790
  child.stdout?.on("data", (d) => output += d);
2651
2791
  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 }));
2792
+ child.on("error", (err) => {
2793
+ output += `Failed to run ${command}: ${err.message}`;
2794
+ finish(1);
2795
+ });
2796
+ child.on("close", (code) => finish(code ?? 1));
2657
2797
  });
2658
2798
  }
2659
2799
 
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 };
2800
+ // src/lib/tools/runShell.ts
2801
+ function storeApproval(root) {
2802
+ return async (req) => {
2803
+ const rel = relative2(root, req.cwd);
2804
+ const answer = await useWizard.getState().requestUserInput({
2805
+ prompt: "Run this command?",
2806
+ promptType: "commandApproval",
2807
+ options: [],
2808
+ command: {
2809
+ ...req,
2810
+ cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2811
+ }
2812
+ });
2813
+ return answer === "approve" ? "approve" : "reject";
2814
+ };
2713
2815
  }
2714
-
2715
- // src/lib/tools/verifyImplementation.ts
2716
- function verifyImplementationTool() {
2816
+ function runShellTool(ctx) {
2717
2817
  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();
2818
+ 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.",
2819
+ inputSchema: z14.object({
2820
+ command: z14.string().describe(
2821
+ "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
2822
+ ),
2823
+ cwd: z14.string().optional().describe(
2824
+ "Directory to run in, relative to the project root. Defaults to the project root."
2825
+ ),
2826
+ explanation: z14.string().describe(
2827
+ "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."
2828
+ )
2829
+ }),
2830
+ execute: async ({ command, cwd, explanation }) => {
2831
+ if (++ctx.counts.shell > ctx.limits.shell) {
2832
+ return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
2833
+ }
2834
+ const resolved2 = resolveInRoot(ctx, cwd ?? ".");
2835
+ if (!resolved2.ok) return resolved2.error;
2836
+ logger.info({ command, cwd: resolved2.target }, "called runShell tool");
2837
+ const decision = await ctx.shell.approve({
2838
+ command,
2839
+ cwd: resolved2.target,
2840
+ explanation
2841
+ });
2842
+ if (decision === "reject") {
2843
+ ctx.shell.executions.push({
2844
+ command,
2845
+ cwd: resolved2.target,
2846
+ approved: false
2847
+ });
2848
+ logger.info({ command }, "runShell: user rejected the command");
2849
+ return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
2850
+ }
2851
+ useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
2852
+ const env = await ctx.shell.env().catch((err) => {
2853
+ logger.warn({ err, command }, "runShell: could not resolve command env");
2854
+ return {};
2855
+ });
2856
+ const run2 = await (ctx.shell.run ?? runShell)(command, {
2857
+ cwd: resolved2.target,
2858
+ env,
2859
+ timeoutMs: ctx.shell.timeoutMs
2860
+ });
2861
+ logger.info(
2862
+ {
2863
+ command,
2864
+ exitCode: run2.exitCode,
2865
+ timedOut: run2.timedOut,
2866
+ durationMs: run2.durationMs
2867
+ },
2868
+ "runShell finished"
2869
+ );
2870
+ await markInteraction();
2871
+ ctx.shell.executions.push({
2872
+ command,
2873
+ cwd: resolved2.target,
2874
+ approved: true,
2875
+ exitCode: run2.exitCode,
2876
+ output: run2.output,
2877
+ timedOut: run2.timedOut,
2878
+ durationMs: run2.durationMs
2879
+ });
2880
+ return {
2881
+ exitCode: run2.exitCode,
2882
+ timedOut: run2.timedOut,
2883
+ output: run2.output
2884
+ };
2723
2885
  }
2724
2886
  });
2725
2887
  }
@@ -2810,7 +2972,7 @@ function generateRecordTool(ctx) {
2810
2972
  return {
2811
2973
  filePath: relPath,
2812
2974
  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.`
2975
+ 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
2976
  };
2815
2977
  } catch (err) {
2816
2978
  return `Error generating records: ${err.message}`;
@@ -2838,22 +3000,6 @@ function notifyUserTool() {
2838
3000
  });
2839
3001
  }
2840
3002
 
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
3003
  // src/lib/tools/index.ts
2858
3004
  function withLogging(name, def) {
2859
3005
  const execute = def.execute;
@@ -2885,10 +3031,7 @@ function createTools(ctx, { output, tools }) {
2885
3031
  writeCredentialsTool(ctx)
2886
3032
  ),
2887
3033
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2888
- verifyImplementation: withLogging(
2889
- "verifyImplementation",
2890
- verifyImplementationTool()
2891
- ),
3034
+ runShell: withLogging("runShell", runShellTool(ctx)),
2892
3035
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2893
3036
  notifyUser: withLogging("notifyUser", notifyUserTool())
2894
3037
  };
@@ -2923,7 +3066,7 @@ async function runAgent(req) {
2923
3066
  baseURL: PROXY_BASE_URL,
2924
3067
  fetch: proxyFetch
2925
3068
  });
2926
- const toolContext = createToolContext();
3069
+ const toolContext = req.toolContext ?? createToolContext();
2927
3070
  const readTools = ["readFile", "searchFiles", "listFiles"];
2928
3071
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
2929
3072
  const instructions = [
@@ -2988,7 +3131,11 @@ async function runAgent(req) {
2988
3131
  "runAgent finished"
2989
3132
  );
2990
3133
  logger.info(
2991
- { counts: toolContext.counts, limits: toolContext.limits },
3134
+ {
3135
+ counts: toolContext.counts,
3136
+ limits: toolContext.limits,
3137
+ commandsRun: toolContext.shell.executions.length
3138
+ },
2992
3139
  "tool usage"
2993
3140
  );
2994
3141
  const toolResults = await stream.toolResults;
@@ -3014,8 +3161,8 @@ var detectLanguageSchema = z19.object({
3014
3161
  var detectLanguage = () => runAgent({
3015
3162
  instructions: [
3016
3163
  "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.",
3164
+ "If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
3165
+ "If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
3019
3166
  "Return the exact version",
3020
3167
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3021
3168
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3113,7 +3260,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3113
3260
  // package.json
3114
3261
  var package_default = {
3115
3262
  name: "@algolia/wizard",
3116
- version: "0.9.0-rc.88.85",
3263
+ version: "0.9.0-rc.93.95",
3117
3264
  description: "Magically implement Algolia functionality in your codebase",
3118
3265
  type: "module",
3119
3266
  engines: {
@@ -3262,9 +3409,11 @@ var CURATED_FRAMEWORKS = [
3262
3409
  "Next.js",
3263
3410
  "React",
3264
3411
  "Vue",
3265
- "Angular",
3266
- "Svelte",
3267
- "Vanilla JS"
3412
+ "Vanilla JS",
3413
+ "Django",
3414
+ "Laravel",
3415
+ "Rails",
3416
+ "Symfony"
3268
3417
  ];
3269
3418
  var OTHER_OPTION = "Other";
3270
3419
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3277,12 +3426,15 @@ var FRAMEWORK_ALIASES = {
3277
3426
  vuejs: "vue",
3278
3427
  angular: "angular",
3279
3428
  angularjs: "angular",
3280
- svelte: "svelte",
3281
- sveltekit: "svelte",
3282
3429
  vanillajs: "vanillajs",
3283
3430
  vanilla: "vanillajs",
3284
3431
  javascript: "vanillajs",
3285
- js: "vanillajs"
3432
+ js: "vanillajs",
3433
+ django: "django",
3434
+ laravel: "laravel",
3435
+ rails: "rails",
3436
+ rubyonrails: "rails",
3437
+ symfony: "symfony"
3286
3438
  };
3287
3439
  var isSameFramework = (a, b) => {
3288
3440
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3516,16 +3668,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3516
3668
  import z26 from "zod";
3517
3669
 
3518
3670
  // 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";
3671
+ import { execFile } from "node:child_process";
3672
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3673
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3529
3674
  var MAX_BUFFER = 32 * 1024 * 1024;
3530
3675
  var MAX_WIZARD_WORKTREES = 3;
3531
3676
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3556,7 +3701,7 @@ async function isWorkingTreeDirty(repoRoot) {
3556
3701
  return out.trim().length > 0;
3557
3702
  }
3558
3703
  async function pruneOldWorktrees(repoRoot) {
3559
- const dir = join9(stateDir(repoRoot), "worktrees");
3704
+ const dir = join8(stateDir(repoRoot), "worktrees");
3560
3705
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3561
3706
  for (const slug of stale) {
3562
3707
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3567,7 +3712,7 @@ async function pruneOldWorktrees(repoRoot) {
3567
3712
  "worktree",
3568
3713
  "remove",
3569
3714
  "--force",
3570
- join9(dir, slug)
3715
+ join8(dir, slug)
3571
3716
  ]);
3572
3717
  await git(["-C", repoRoot, "branch", "-D", branch]);
3573
3718
  } catch (err) {
@@ -3581,113 +3726,13 @@ async function pruneOldWorktrees(repoRoot) {
3581
3726
  async function createWorktree(repoRoot) {
3582
3727
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3583
3728
  const dirSlug = branch.replace(/\//g, "-");
3584
- const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3729
+ const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3585
3730
  await git(["-C", repoRoot, "worktree", "prune"]);
3586
3731
  await pruneOldWorktrees(repoRoot);
3587
3732
  await mkdir6(dirname7(path), { recursive: true });
3588
3733
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3589
3734
  return { path, branch };
3590
3735
  }
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
3736
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3692
3737
  const trimmed = sourcePath.trim();
3693
3738
  if (!trimmed) {
@@ -3701,8 +3746,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3701
3746
  } catch {
3702
3747
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3703
3748
  }
3704
- const relPath = join9(ingestDir, basename2(source));
3705
- const dest = join9(worktreePath, relPath);
3749
+ const relPath = join8(ingestDir, basename2(source));
3750
+ const dest = join8(worktreePath, relPath);
3706
3751
  try {
3707
3752
  await mkdir6(dirname7(dest), { recursive: true });
3708
3753
  await copyFile(source, dest);
@@ -3720,7 +3765,7 @@ function hasEnvVar(content, name) {
3720
3765
  async function readEnvVar(worktreePath, name) {
3721
3766
  let content;
3722
3767
  try {
3723
- content = await readFile7(join9(worktreePath, ".env"), "utf8");
3768
+ content = await readFile6(join8(worktreePath, ".env"), "utf8");
3724
3769
  } catch (err) {
3725
3770
  if (err.code !== "ENOENT") throw err;
3726
3771
  return void 0;
@@ -3735,10 +3780,10 @@ async function readEnvVar(worktreePath, name) {
3735
3780
  return value;
3736
3781
  }
3737
3782
  async function writeSearchEnvValues(worktreePath, vars) {
3738
- const target = join9(worktreePath, ".env");
3783
+ const target = join8(worktreePath, ".env");
3739
3784
  let existing = "";
3740
3785
  try {
3741
- existing = await readFile7(target, "utf8");
3786
+ existing = await readFile6(target, "utf8");
3742
3787
  } catch (err) {
3743
3788
  if (err.code !== "ENOENT") throw err;
3744
3789
  }
@@ -3807,15 +3852,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3807
3852
  }
3808
3853
 
3809
3854
  // 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";
3855
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
3856
+ import { dirname as dirname8, join as join9 } from "node:path";
3812
3857
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3813
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3858
+ var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3814
3859
  function findDocsDir() {
3815
3860
  let dir = dirname8(fileURLToPath2(import.meta.url));
3816
3861
  for (; ; ) {
3817
- const candidate = join10(dir, DOCS_SUBPATH);
3818
- if (existsSync2(candidate)) return candidate;
3862
+ const candidate = join9(dir, DOCS_SUBPATH);
3863
+ if (existsSync(candidate)) return candidate;
3819
3864
  const parent = dirname8(dir);
3820
3865
  if (parent === dir) return void 0;
3821
3866
  dir = parent;
@@ -3837,7 +3882,7 @@ function loadAlgoliaDoc(language) {
3837
3882
  );
3838
3883
  return "";
3839
3884
  }
3840
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3885
+ return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3841
3886
  }
3842
3887
  function getNamedDoc(name, language) {
3843
3888
  const docsDir = findDocsDir();
@@ -3845,14 +3890,15 @@ function getNamedDoc(name, language) {
3845
3890
  logger.warn("docs/algolia-sdk not found");
3846
3891
  return "";
3847
3892
  }
3848
- const file = join10(docsDir, `${name}-${language}.md`);
3849
- if (!existsSync2(file)) {
3893
+ const file = join9(docsDir, `${name}-${language}.md`);
3894
+ if (!existsSync(file)) {
3850
3895
  logger.warn({ name, language }, "named SDK reference not found");
3851
3896
  return "";
3852
3897
  }
3853
3898
  return readFileSync(file, "utf8").trim();
3854
3899
  }
3855
3900
  function getFrameworkSpecificDoc(frameworks) {
3901
+ if (frameworks.length === 0) return "";
3856
3902
  const fw = frameworks.map((f) => f.toLowerCase());
3857
3903
  if (fw.includes("vue") || fw.includes("nuxt")) {
3858
3904
  return loadAlgoliaDoc("vue");
@@ -3860,9 +3906,6 @@ function getFrameworkSpecificDoc(frameworks) {
3860
3906
  if (fw.includes("react") || fw.includes("next.js")) {
3861
3907
  return loadAlgoliaDoc("react");
3862
3908
  }
3863
- if (fw.includes("angular")) {
3864
- return loadAlgoliaDoc("angular");
3865
- }
3866
3909
  return loadAlgoliaDoc("js");
3867
3910
  }
3868
3911
 
@@ -3890,11 +3933,7 @@ var implementSchema = z26.object({
3890
3933
  });
3891
3934
  var implementationOutputSchema = z26.object({
3892
3935
  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()
3936
+ ingestCommand: z26.string().optional()
3898
3937
  });
3899
3938
  var verificationOutputSchema = z26.object({
3900
3939
  summary: z26.string(),
@@ -3904,30 +3943,35 @@ var verificationOutputSchema = z26.object({
3904
3943
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3905
3944
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3906
3945
  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
- }
3946
+ var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
3947
+ function lower(entries) {
3948
+ return entries.map((entry) => entry.name.toLowerCase());
3926
3949
  }
3927
- function publicEnvPrefix(language) {
3928
- const frameworkNames = language.frameworks.map(
3929
- (framework) => framework.name.toLowerCase()
3950
+ function isJsProject(language) {
3951
+ return lower(language.languages).some(
3952
+ (name) => JS_LANGUAGES.some((js) => name.includes(js))
3930
3953
  );
3954
+ }
3955
+ var UI_FRAMEWORKS = [
3956
+ { match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
3957
+ { match: ["react", "next"], target: "React", doc: "react" },
3958
+ { match: ["angular"], target: "Angular" }
3959
+ ];
3960
+ function matchUiFramework(language) {
3961
+ const names = lower(language.frameworks);
3962
+ return UI_FRAMEWORKS.find(
3963
+ (ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
3964
+ );
3965
+ }
3966
+ function searchUiTarget(language) {
3967
+ return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
3968
+ }
3969
+ function frameworksForDoc(language) {
3970
+ if (!isJsProject(language)) return [];
3971
+ return [matchUiFramework(language)?.doc ?? "js"];
3972
+ }
3973
+ function publicEnvPrefix(language) {
3974
+ const frameworkNames = lower(language.frameworks);
3931
3975
  if (frameworkNames.some((name) => name.includes("next"))) {
3932
3976
  return "NEXT_PUBLIC_";
3933
3977
  }
@@ -3940,7 +3984,7 @@ function publicEnvPrefix(language) {
3940
3984
  if (frameworkNames.some((name) => name.includes("vite"))) {
3941
3985
  return "VITE_";
3942
3986
  }
3943
- return "PUBLIC_";
3987
+ return isJsProject(language) ? "PUBLIC_" : "";
3944
3988
  }
3945
3989
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
3946
3990
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
@@ -3979,7 +4023,10 @@ function baseInstructions(input) {
3979
4023
  // index-scoped keys then reject with a 403.
3980
4024
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
3981
4025
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
3982
- "Make minimal, idiomatic changes; do not touch unrelated code."
4026
+ "Make minimal, idiomatic changes; do not touch unrelated code.",
4027
+ `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4028
+ "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.",
4029
+ "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
4030
  ];
3984
4031
  }
3985
4032
  function sourceSpecificInstructions(input) {
@@ -3999,12 +4046,21 @@ function sourceSpecificInstructions(input) {
3999
4046
  generated: [
4000
4047
  "No real data source exists; use sample records for each confirmed entity.",
4001
4048
  "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.",
4049
+ "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
4050
  "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
4051
  ]
4005
4052
  };
4006
4053
  return byLine[input.ingestionSource];
4007
4054
  }
4055
+ function algoliaClientDoc(input) {
4056
+ const doc = getNamedDoc("save-records", "js");
4057
+ if (!doc) return [];
4058
+ if (isJsProject(input.language)) return [doc];
4059
+ return [
4060
+ "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:",
4061
+ doc
4062
+ ];
4063
+ }
4008
4064
  function ingestionInstructions(input) {
4009
4065
  return [
4010
4066
  ...input.confirmed && input.confirmed.length ? [
@@ -4012,25 +4068,30 @@ function ingestionInstructions(input) {
4012
4068
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4013
4069
  `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
4070
  `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.",
4071
+ "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
4072
  "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.',
4073
+ ...algoliaClientDoc(input),
4074
+ "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.",
4075
+ '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
4076
  "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
4077
  ...sourceSpecificInstructions(input)
4022
4078
  ] : []
4023
4079
  ];
4024
4080
  }
4025
4081
  function searchInstructions(input) {
4026
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4082
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4027
4083
  return [
4028
4084
  "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.`,
4085
+ `Build the search UI for ${input.searchUiTarget}.`,
4086
+ ...doc ? [
4087
+ "Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
4088
+ doc
4089
+ ] : [
4090
+ "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."
4091
+ ],
4092
+ `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
4093
  `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.`,
4094
+ "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
4095
  // The key is provisioned only after verification passes, so the agent never
4035
4096
  // sees one. It must also leave .env alone: the wizard reads that file to
4036
4097
  // decide whether a key already exists, and an agent-invented value there
@@ -4039,9 +4100,8 @@ function searchInstructions(input) {
4039
4100
  // Not the agent's to rename: the wizard writes these exact names into
4040
4101
  // ".env" right after this step, so a renamed prefix would leave the code
4041
4102
  // 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.',
4103
+ `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4104
+ "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
4105
  "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
4106
  ];
4047
4107
  }
@@ -4049,11 +4109,12 @@ function verificationInstructions(input) {
4049
4109
  return [
4050
4110
  "Verify the Algolia implementation changes in the current worktree.",
4051
4111
  `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.",
4112
+ "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.",
4113
+ "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.",
4114
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4115
+ "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
4116
  "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.`,
4117
+ `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4057
4118
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4058
4119
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4059
4120
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4074,14 +4135,15 @@ var IMPLEMENT_CONFIG = {
4074
4135
  }
4075
4136
  };
4076
4137
  var useCaseToolMap = {
4077
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4078
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4079
- verification: [
4138
+ ingestion: [
4080
4139
  ...FS_READ_TOOLS,
4081
4140
  "writeFile",
4082
- "verifyImplementation",
4141
+ "writeCredentials",
4142
+ "runShell",
4083
4143
  "notifyUser"
4084
- ]
4144
+ ],
4145
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4146
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4085
4147
  };
4086
4148
  function toolsForUseCase(useCase, ingestionSource) {
4087
4149
  const tools = useCaseToolMap[useCase];
@@ -4104,15 +4166,30 @@ function formatSummary(useCase, summary) {
4104
4166
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4105
4167
  return `${label}: ${summary}`;
4106
4168
  }
4107
- function buildIngestCommand(worktree, runtime, entrypoint) {
4108
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4109
- }
4110
4169
  function parseIngestRecordCount(output) {
4111
4170
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4112
4171
  if (!match) return void 0;
4113
4172
  const count = Number(match[1]);
4114
4173
  return Number.isFinite(count) ? count : void 0;
4115
4174
  }
4175
+ function ingestOutcome(executions, ingestCommand) {
4176
+ const newestFirst = [...executions].reverse();
4177
+ const withCount = newestFirst.filter(
4178
+ (e) => parseIngestRecordCount(e.output ?? "") != null
4179
+ );
4180
+ const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4181
+ return {
4182
+ run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4183
+ recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4184
+ };
4185
+ }
4186
+ function makeToolContext(worktree, env = async () => ({})) {
4187
+ return createToolContext(
4188
+ DEFAULT_TOOL_LIMITS,
4189
+ worktree,
4190
+ createShellContext({ env, approve: storeApproval(worktree) })
4191
+ );
4192
+ }
4116
4193
  function verificationRetryInstructions(verification) {
4117
4194
  return [
4118
4195
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4191,9 +4268,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4191
4268
  const confirmed2 = normalized.confirmedEntities;
4192
4269
  const searchLocation = normalized.searchImplementationAnalysis;
4193
4270
  let appId;
4271
+ let ingestAppId;
4194
4272
  if (useCases.includes("search")) {
4195
4273
  appId = (await requireApplication()).id;
4196
4274
  }
4275
+ if (useCases.includes("ingestion")) {
4276
+ ingestAppId = appId ?? (await requireApplication()).id;
4277
+ }
4197
4278
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4198
4279
  try {
4199
4280
  process.chdir(worktree);
@@ -4230,7 +4311,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4230
4311
  ingestDir: INGEST_DIR,
4231
4312
  ingestionSource,
4232
4313
  uploadFilePath,
4233
- uiFramework: detectUiFramework(language)
4314
+ searchUiTarget: searchUiTarget(language)
4234
4315
  };
4235
4316
  const summaries = [];
4236
4317
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4253,41 +4334,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4253
4334
  }
4254
4335
  let finalSearchEnvVars = input.searchEnvVars;
4255
4336
  let agentRuns = 0;
4256
- let ingestRuntime;
4257
- let ingestEntrypoint;
4337
+ let ingestCommand;
4258
4338
  let ingestScriptRan = false;
4259
4339
  let ingestRecordCount;
4260
4340
  let ingestDurationMs;
4261
- let installFailed = false;
4262
4341
  let ingestOutcomeMessage;
4342
+ const ingestKeyAppId = ingestAppId;
4343
+ const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4344
+ [APP_ID_VAR]: ingestKeyAppId,
4345
+ [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4346
+ [INDEX_NAME_VAR]: targetIndex
4347
+ })) : void 0;
4348
+ const searchTools = makeToolContext(worktree);
4263
4349
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4264
4350
  if (agentRuns > 0) ctx.recordStepExecution();
4265
4351
  agentRuns += 1;
4266
- const result = await runAgent({
4352
+ return runAgent({
4267
4353
  instructions: buildAgentInstructions(
4268
4354
  currentUseCase,
4269
4355
  input,
4270
4356
  extraInstructions
4271
4357
  ),
4272
4358
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4273
- outputSchema: implementationOutputSchema
4274
- });
4275
- ctx.notify({
4276
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4359
+ outputSchema: implementationOutputSchema,
4360
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4277
4361
  });
4278
- const installLogId = ctx.logStart("installWorktreeDeps", {
4279
- useCase: currentUseCase
4280
- });
4281
- const install = await installWorktreeDeps(worktree);
4282
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4283
- if (!install.ok) {
4284
- installFailed = true;
4285
- logger.warn(
4286
- { useCase: currentUseCase, output: install.output },
4287
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4288
- );
4289
- }
4290
- return result;
4291
4362
  }
4292
4363
  async function runVerificationUseCase() {
4293
4364
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4295,111 +4366,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4295
4366
  return runAgent({
4296
4367
  instructions: buildAgentInstructions("verification", input),
4297
4368
  tools: toolsForUseCase("verification"),
4298
- outputSchema: verificationOutputSchema
4369
+ outputSchema: verificationOutputSchema,
4370
+ toolContext: searchTools
4299
4371
  });
4300
4372
  }
4301
4373
  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
4374
+ const result = await runImplementationUseCase("ingestion");
4375
+ summaries.push(formatSummary("ingestion", result.summary));
4376
+ ingestCommand = result.ingestCommand;
4377
+ const executions = (ingestionTools ?? searchTools).shell.executions;
4378
+ const { run: ingestRun, recordCount } = ingestOutcome(
4379
+ executions,
4380
+ ingestCommand
4381
+ );
4382
+ ingestScriptRan = ingestRun != null;
4383
+ ingestRecordCount = recordCount;
4384
+ ingestDurationMs = ingestRun?.durationMs;
4385
+ if (ingestScriptRan) {
4386
+ ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4387
+ if (ingestRecordCount != null) {
4388
+ track("AI Wizard Ingest Successful", {
4389
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4390
+ record_count: ingestRecordCount,
4391
+ duration_ms: ingestDurationMs ?? 0
4321
4392
  });
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
4393
  }
4394
+ } else {
4395
+ const rejected = executions.some((e) => !e.approved);
4396
+ const reason = rejected ? "you declined to run it" : "no successful run was recorded";
4397
+ ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4398
+ summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
4399
+ logger.warn(
4400
+ { ingestCommand, rejected, commandsRun: executions.length },
4401
+ "implement: ingestion script did not complete successfully"
4402
+ );
4403
+ track("Error", {
4404
+ step: "Push Data",
4405
+ error: `ingestion did not run: ${reason}`,
4406
+ product_area: "AI Wizard"
4407
+ });
4391
4408
  }
4392
4409
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4393
- if (ingestRuntime && ingestEntrypoint) {
4394
- commandMessages.push(
4395
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4396
- );
4410
+ if (ingestCommand) {
4411
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4397
4412
  }
4398
4413
  await ctx.requestUserInput({
4399
4414
  prompt: "",
4400
4415
  promptType: "enterToContinue",
4401
4416
  options: [],
4402
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4417
+ messages: [ingestOutcomeMessage, ...commandMessages]
4403
4418
  });
4404
4419
  }
4405
4420
  if (useCases.includes("search")) {
@@ -4516,22 +4531,13 @@ ${run2.output}` : status;
4516
4531
  "implement: agent reported success but no files changed in the worktree"
4517
4532
  );
4518
4533
  }
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
4534
  return {
4525
4535
  ingestionSource,
4526
4536
  filesChanged,
4527
4537
  summary: summaries.join("\n\n"),
4528
4538
  worktreePath: worktree,
4529
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4530
- ingestCommand: buildIngestCommand(
4531
- worktree,
4532
- ingestRuntime,
4533
- ingestEntrypoint
4534
- ),
4539
+ ...useCases.includes("ingestion") && ingestCommand ? {
4540
+ ingestCommand,
4535
4541
  ingestScriptRan,
4536
4542
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4537
4543
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4859,7 +4865,7 @@ function parseCliArgs(argv) {
4859
4865
 
4860
4866
  // src/lib/resetState.ts
4861
4867
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4862
- import { join as join11 } from "node:path";
4868
+ import { join as join10 } from "node:path";
4863
4869
  var KEEP = ["wizard.log"];
4864
4870
  async function resetProjectState() {
4865
4871
  const dir = stateDir();
@@ -4873,14 +4879,14 @@ async function resetProjectState() {
4873
4879
  const targets = entries.filter((name) => !KEEP.includes(name));
4874
4880
  await Promise.all(
4875
4881
  targets.map(
4876
- (name) => rm2(join11(dir, name), { recursive: true, force: true })
4882
+ (name) => rm2(join10(dir, name), { recursive: true, force: true })
4877
4883
  )
4878
4884
  );
4879
4885
  return { dir, removed: targets };
4880
4886
  }
4881
4887
 
4882
4888
  // src/main.tsx
4883
- import { jsx as jsx14 } from "react/jsx-runtime";
4889
+ import { jsx as jsx15 } from "react/jsx-runtime";
4884
4890
  async function startup() {
4885
4891
  setProjectRoot(process.cwd());
4886
4892
  let args;
@@ -4930,7 +4936,7 @@ ${formatStepList(workflow)}`);
4930
4936
  }
4931
4937
  async function run(workflow) {
4932
4938
  const store = useWizard.getState();
4933
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4939
+ const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4934
4940
  await store.waitForStart();
4935
4941
  let user = await getUser();
4936
4942
  if (!user) {