@algolia/wizard 0.12.0-rc.104.151 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +588 -557
  2. package/package.json +1 -1
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";
@@ -304,7 +304,10 @@ var useWizard = create((set, get) => ({
304
304
  if (_noticeTimer === null) {
305
305
  set({
306
306
  notices: [...notices, notice],
307
- _noticeTimer: setTimeout(() => get()._drainNoticeQueue(), NOTICE_INTERVAL_MS)
307
+ _noticeTimer: setTimeout(
308
+ () => get()._drainNoticeQueue(),
309
+ NOTICE_INTERVAL_MS
310
+ )
308
311
  });
309
312
  } else {
310
313
  set({ _noticeQueue: [..._noticeQueue, notice] });
@@ -316,7 +319,10 @@ var useWizard = create((set, get) => ({
316
319
  set((s) => ({
317
320
  notices: [...s.notices, next],
318
321
  _noticeQueue: rest,
319
- _noticeTimer: setTimeout(() => get()._drainNoticeQueue(), NOTICE_INTERVAL_MS)
322
+ _noticeTimer: setTimeout(
323
+ () => get()._drainNoticeQueue(),
324
+ NOTICE_INTERVAL_MS
325
+ )
320
326
  }));
321
327
  } else {
322
328
  set({ _noticeTimer: null });
@@ -364,13 +370,17 @@ var useWizard = create((set, get) => ({
364
370
  await markInteraction();
365
371
  get()._resolve?.(value);
366
372
  set({ inputReq: null, _resolve: null, phase: "running" });
367
- const id = get().logStart("prompt", `User input: ${describeInputValue(value)}`);
373
+ const id = get().logStart(
374
+ "prompt",
375
+ `User input: ${describeInputValue(value)}`
376
+ );
368
377
  get().logEnd(id, "success");
369
378
  },
370
379
  setDone: () => set({ phase: "done" }),
371
380
  setError: (message) => set({ phase: "error", error: message }),
372
381
  reset: () => {
373
382
  get()._clearNoticeQueue();
383
+ get()._resolve?.(false);
374
384
  set({
375
385
  phase: "idle",
376
386
  homeScreen: "home",
@@ -622,10 +632,13 @@ function Notices() {
622
632
  }
623
633
 
624
634
  // src/ui/PromptInput.tsx
625
- import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
635
+ import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
626
636
  import TextInput from "ink-text-input";
627
637
  import { useState as useState5 } from "react";
628
638
 
639
+ // src/ui/CommandApproval.tsx
640
+ import { Box as Box5, Text as Text5, useInput } from "ink";
641
+
629
642
  // src/ui/NextAction.tsx
630
643
  import { Box as Box4, Text as Text4 } from "ink";
631
644
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -649,14 +662,60 @@ function NextAction({
649
662
  ] });
650
663
  }
651
664
 
665
+ // src/ui/CommandApproval.tsx
666
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
667
+ function CommandApproval({
668
+ command,
669
+ onDecide
670
+ }) {
671
+ useInput((_input, key) => {
672
+ if (key.return) onDecide("approve");
673
+ else if (key.escape) onDecide("reject");
674
+ });
675
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
676
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
677
+ /* @__PURE__ */ jsxs4(
678
+ Box5,
679
+ {
680
+ flexDirection: "column",
681
+ paddingLeft: 2,
682
+ borderStyle: "single",
683
+ borderColor: COLORS.success,
684
+ borderTop: false,
685
+ borderBottom: false,
686
+ borderRight: false,
687
+ gap: 1,
688
+ children: [
689
+ /* @__PURE__ */ jsxs4(Box5, { children: [
690
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
691
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
692
+ ] }),
693
+ /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
694
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
695
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
696
+ ] }),
697
+ command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
698
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
699
+ /* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
700
+ ] })
701
+ ]
702
+ }
703
+ ),
704
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
705
+ /* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
706
+ /* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" })
707
+ ] })
708
+ ] });
709
+ }
710
+
652
711
  // src/ui/SelectPrompt.tsx
653
- import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
712
+ import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
654
713
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
655
714
 
656
715
  // src/ui/ScrollView.tsx
657
- import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
716
+ import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
658
717
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
659
- import { jsxs as jsxs4 } from "react/jsx-runtime";
718
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
660
719
  var INDICATOR_ROWS = 2;
661
720
  function fittedWidth(node, columns) {
662
721
  let left = 0;
@@ -726,14 +785,14 @@ function useScrollWindow({
726
785
  };
727
786
  }
728
787
  function ScrollView({ scroll, children }) {
729
- return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
730
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
788
+ return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
789
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
731
790
  "\u2191 ",
732
791
  scroll.hiddenAbove,
733
792
  " more"
734
793
  ] }),
735
794
  children,
736
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
795
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
737
796
  "\u2193 ",
738
797
  scroll.hiddenBelow,
739
798
  " more"
@@ -742,7 +801,7 @@ function ScrollView({ scroll, children }) {
742
801
  }
743
802
 
744
803
  // src/ui/SelectPrompt.tsx
745
- import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
804
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
746
805
  var CANCEL = "cancel";
747
806
  var ARROW_WIDTH = 4;
748
807
  var COLUMN_GAP = 2;
@@ -804,7 +863,7 @@ function SelectPrompt({
804
863
  revealIndex(index);
805
864
  }, [index, revealIndex]);
806
865
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
807
- useInput((input, key) => {
866
+ useInput2((input, key) => {
808
867
  if (rows.length === 0) return;
809
868
  if (key.upArrow || input === "k") {
810
869
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -827,56 +886,56 @@ function SelectPrompt({
827
886
  }
828
887
  }
829
888
  });
830
- return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
831
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
832
- error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
833
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
834
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
835
- /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
836
- question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
837
- helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
889
+ return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
890
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
891
+ error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
892
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
893
+ table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
894
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
895
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
896
+ helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
838
897
  ] })
839
898
  ] }),
840
- /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
899
+ /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
841
900
  const i = scroll.offset + visibleIndex;
842
901
  const highlighted = i === index;
843
902
  const isCancel = i === cancelIndex;
844
903
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
845
904
  const sec = isCancel ? void 0 : secondary?.[i];
846
905
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
847
- const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
906
+ const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
848
907
  highlighted ? "\u276F " : " ",
849
908
  bullet,
850
909
  option
851
910
  ] });
852
911
  const isText = sec?.kind === "text";
853
- return /* @__PURE__ */ jsxs5(
854
- Box6,
912
+ return /* @__PURE__ */ jsxs6(
913
+ Box7,
855
914
  {
856
915
  width: isText ? "100%" : barWidth,
857
916
  paddingX: 1,
858
917
  paddingY: 1,
859
918
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
860
919
  children: [
861
- /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
862
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
863
- Text6,
920
+ /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
921
+ isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
922
+ Text7,
864
923
  {
865
924
  wrap: "truncate",
866
925
  color: highlighted ? COLORS.primary : COLORS.muted,
867
926
  children: sec.value
868
927
  }
869
928
  ) }),
870
- sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
929
+ sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
871
930
  ]
872
931
  },
873
932
  `row-${i}`
874
933
  );
875
934
  }) }),
876
- /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
935
+ /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
877
936
  i > 0 ? " " : "",
878
- /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
879
- /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
937
+ /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
938
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
880
939
  " ",
881
940
  label
882
941
  ] })
@@ -885,23 +944,23 @@ function SelectPrompt({
885
944
  }
886
945
 
887
946
  // src/ui/PromptInput.tsx
888
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
947
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
889
948
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
890
949
  function EnterToContinuePrompt({
891
950
  question,
892
951
  messages,
893
952
  onDecide
894
953
  }) {
895
- useInput2((_input, key) => {
954
+ useInput3((_input, key) => {
896
955
  if (key.return) onDecide(true);
897
956
  else if (key.escape) onDecide(false);
898
957
  });
899
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
900
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
901
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
902
- /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
903
- /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
904
- /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
958
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
959
+ messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
960
+ question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
961
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
962
+ /* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
963
+ /* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
905
964
  ] })
906
965
  ] });
907
966
  }
@@ -909,11 +968,11 @@ function PromptInput() {
909
968
  const { phase, inputReq, submitInput } = useWizard();
910
969
  const [draft, setDraft] = useState5("");
911
970
  if (phase === "done" || phase === "error") {
912
- return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
971
+ return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
913
972
  }
914
973
  if (phase !== "awaitingInput" || !inputReq) return null;
915
974
  if (inputReq.promptType === "multipleChoice") {
916
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
975
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
917
976
  SelectPrompt,
918
977
  {
919
978
  question: inputReq.prompt,
@@ -930,7 +989,7 @@ function PromptInput() {
930
989
  ) });
931
990
  }
932
991
  if (inputReq.promptType === "multiSelect") {
933
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
992
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
934
993
  SelectPrompt,
935
994
  {
936
995
  multi: true,
@@ -945,7 +1004,7 @@ function PromptInput() {
945
1004
  ) });
946
1005
  }
947
1006
  if (inputReq.promptType === "notice") {
948
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1007
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
949
1008
  SelectPrompt,
950
1009
  {
951
1010
  question: inputReq.prompt,
@@ -956,7 +1015,7 @@ function PromptInput() {
956
1015
  ) });
957
1016
  }
958
1017
  if (inputReq.promptType === "enterToContinue") {
959
- return /* @__PURE__ */ jsx5(
1018
+ return /* @__PURE__ */ jsx6(
960
1019
  EnterToContinuePrompt,
961
1020
  {
962
1021
  question: inputReq.prompt,
@@ -965,9 +1024,12 @@ function PromptInput() {
965
1024
  }
966
1025
  );
967
1026
  }
1027
+ if (inputReq.promptType === "commandApproval" && inputReq.command) {
1028
+ return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1029
+ }
968
1030
  if (inputReq.promptType === "acceptReject") {
969
1031
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
970
- return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1032
+ return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
971
1033
  SelectPrompt,
972
1034
  {
973
1035
  question: inputReq.prompt,
@@ -978,15 +1040,15 @@ function PromptInput() {
978
1040
  }
979
1041
  ) });
980
1042
  }
981
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
982
- inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
983
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
984
- /* @__PURE__ */ jsxs6(Box7, { children: [
985
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
1043
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1044
+ inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
1045
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
1046
+ /* @__PURE__ */ jsxs7(Box8, { children: [
1047
+ /* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
986
1048
  inputReq.prompt,
987
1049
  " "
988
1050
  ] }),
989
- /* @__PURE__ */ jsx5(
1051
+ /* @__PURE__ */ jsx6(
990
1052
  TextInput,
991
1053
  {
992
1054
  value: draft,
@@ -1004,7 +1066,7 @@ function PromptInput() {
1004
1066
  // src/ui/Welcome.tsx
1005
1067
  import { dirname as dirname2, join as join3 } from "node:path";
1006
1068
  import { fileURLToPath } from "node:url";
1007
- import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1069
+ import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1008
1070
 
1009
1071
  // src/ui/copy/welcome.ts
1010
1072
  var sidebarItems = [
@@ -1032,7 +1094,7 @@ var sidebarItems = [
1032
1094
 
1033
1095
  // src/ui/Welcome.tsx
1034
1096
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
1035
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1097
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1036
1098
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1037
1099
  var TERMINAL_INFO = {
1038
1100
  ...defaultTerminalInfo,
@@ -1043,14 +1105,14 @@ function SidebarItem({
1043
1105
  title,
1044
1106
  description
1045
1107
  }) {
1046
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1047
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1048
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1049
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
1108
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1109
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
1110
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
1111
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
1050
1112
  ] }),
1051
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1052
- /* @__PURE__ */ jsx6(Spacer, {}),
1053
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1113
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1114
+ /* @__PURE__ */ jsx7(Spacer, {}),
1115
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1054
1116
  ] })
1055
1117
  ] });
1056
1118
  }
@@ -1058,7 +1120,7 @@ function Welcome() {
1058
1120
  const confirmStart = useWizard((s) => s.confirmStart);
1059
1121
  const openLearnMore = useWizard((s) => s.openLearnMore);
1060
1122
  const { rows } = useWindowSize6();
1061
- useInput3((input, key) => {
1123
+ useInput4((input, key) => {
1062
1124
  if (key.return) confirmStart();
1063
1125
  else if (input === "i") openLearnMore();
1064
1126
  });
@@ -1076,16 +1138,16 @@ function Welcome() {
1076
1138
  if (rows < 30) {
1077
1139
  layout = scales["small"];
1078
1140
  }
1079
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1080
- /* @__PURE__ */ jsx6(
1081
- Box8,
1141
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1142
+ /* @__PURE__ */ jsx7(
1143
+ Box9,
1082
1144
  {
1083
1145
  paddingY: layout.main.padding.y,
1084
1146
  paddingX: layout.main.padding.x,
1085
1147
  flexDirection: "column",
1086
1148
  justifyContent: "center",
1087
- children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1088
- /* @__PURE__ */ jsx6(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx6(
1149
+ children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1150
+ /* @__PURE__ */ jsx7(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx7(
1089
1151
  Image,
1090
1152
  {
1091
1153
  src: IMAGE_PATH,
@@ -1096,16 +1158,16 @@ function Welcome() {
1096
1158
  protocol: "halfBlock"
1097
1159
  }
1098
1160
  ) }),
1099
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1100
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1101
- /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1102
- /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1161
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1162
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
1163
+ /* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
1164
+ /* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1103
1165
  ] })
1104
1166
  ] })
1105
1167
  }
1106
1168
  ),
1107
- /* @__PURE__ */ jsxs7(
1108
- Box8,
1169
+ /* @__PURE__ */ jsxs8(
1170
+ Box9,
1109
1171
  {
1110
1172
  backgroundColor: COLORS.bg.sidebar,
1111
1173
  width: 40,
@@ -1115,8 +1177,8 @@ function Welcome() {
1115
1177
  flexDirection: "column",
1116
1178
  justifyContent: "center",
1117
1179
  children: [
1118
- /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1119
- sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
1180
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1181
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
1120
1182
  ]
1121
1183
  }
1122
1184
  )
@@ -1125,7 +1187,7 @@ function Welcome() {
1125
1187
 
1126
1188
  // src/ui/LearnMore.tsx
1127
1189
  import { Fragment as Fragment2 } from "react";
1128
- import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1190
+ import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1129
1191
 
1130
1192
  // src/ui/copy/learn-more.ts
1131
1193
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1133,12 +1195,17 @@ var accessItems = [
1133
1195
  {
1134
1196
  tag: "READ",
1135
1197
  title: "Project files",
1136
- description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1198
+ description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1137
1199
  },
1138
1200
  {
1139
1201
  tag: "WRITE",
1140
1202
  title: "Code changes",
1141
- description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1203
+ description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1204
+ },
1205
+ {
1206
+ tag: "EXEC",
1207
+ title: "Setup commands",
1208
+ 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."
1142
1209
  },
1143
1210
  {
1144
1211
  tag: "NET",
@@ -1148,13 +1215,13 @@ var accessItems = [
1148
1215
  {
1149
1216
  tag: "KEY",
1150
1217
  title: "Credentials",
1151
- description: "saves your Admin API key to .env and adds it to .gitignore."
1218
+ description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1152
1219
  }
1153
1220
  ];
1154
1221
  var neverItems = [
1155
1222
  "Send your source code to a model or third party",
1156
1223
  "Commit or push to git",
1157
- "Touch files outside your project directory"
1224
+ "Run a command you haven't approved"
1158
1225
  ];
1159
1226
  var policyLinks = [
1160
1227
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1162,10 +1229,11 @@ var policyLinks = [
1162
1229
  ];
1163
1230
 
1164
1231
  // src/ui/LearnMore.tsx
1165
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1232
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1166
1233
  var TAG_COLORS = {
1167
1234
  READ: COLORS.success,
1168
1235
  WRITE: COLORS.badge,
1236
+ EXEC: COLORS.danger,
1169
1237
  NET: COLORS.accent,
1170
1238
  KEY: COLORS.muted
1171
1239
  };
@@ -1178,12 +1246,12 @@ function NeverLine({
1178
1246
  }) {
1179
1247
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1180
1248
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1181
- return /* @__PURE__ */ jsxs8(Text9, { children: [
1182
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1249
+ return /* @__PURE__ */ jsxs9(Text10, { children: [
1250
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1183
1251
  " ".repeat(NEVER_BOX_PAD_X),
1184
- segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1252
+ segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1185
1253
  " ".repeat(rightPad),
1186
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1254
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1187
1255
  ] });
1188
1256
  }
1189
1257
  function LearnMore() {
@@ -1191,12 +1259,12 @@ function LearnMore() {
1191
1259
  const backToHome = useWizard((s) => s.backToHome);
1192
1260
  const { columns } = useWindowSize7();
1193
1261
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1194
- useInput4((_input, key) => {
1262
+ useInput5((_input, key) => {
1195
1263
  if (key.escape) backToHome();
1196
1264
  else if (key.return) confirmStart();
1197
1265
  });
1198
- return /* @__PURE__ */ jsxs8(
1199
- Box9,
1266
+ return /* @__PURE__ */ jsxs9(
1267
+ Box10,
1200
1268
  {
1201
1269
  flexDirection: "column",
1202
1270
  paddingX: PADDING_X,
@@ -1204,31 +1272,31 @@ function LearnMore() {
1204
1272
  width: "100%",
1205
1273
  gap: 1,
1206
1274
  children: [
1207
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1208
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1209
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1210
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1211
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1212
- /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1213
- /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1214
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1215
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1275
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1276
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
1277
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
1278
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1279
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1280
+ /* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1281
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
1282
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1283
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1216
1284
  ] }) })
1217
1285
  ] })
1218
1286
  ] }, item.tag)) }),
1219
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1220
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1221
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1222
- /* @__PURE__ */ jsx7(
1287
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
1288
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1289
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1290
+ /* @__PURE__ */ jsx8(
1223
1291
  NeverLine,
1224
1292
  {
1225
1293
  width: dividerWidth,
1226
1294
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1227
1295
  }
1228
1296
  ),
1229
- neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1230
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1231
- /* @__PURE__ */ jsx7(
1297
+ neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1298
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1299
+ /* @__PURE__ */ jsx8(
1232
1300
  NeverLine,
1233
1301
  {
1234
1302
  width: dividerWidth,
@@ -1240,24 +1308,24 @@ function LearnMore() {
1240
1308
  }
1241
1309
  )
1242
1310
  ] }, item)),
1243
- /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1244
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1311
+ /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1312
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1245
1313
  ] }),
1246
- /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1247
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1248
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1314
+ /* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1315
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1316
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
1249
1317
  ] }, link.label)) }),
1250
- /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1251
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1252
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1253
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1254
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1318
+ /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1319
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1320
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1321
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
1322
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
1255
1323
  ] }),
1256
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1257
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1258
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1259
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1260
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1324
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1325
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1326
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
1327
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
1328
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
1261
1329
  ] })
1262
1330
  ] })
1263
1331
  ]
@@ -1266,10 +1334,10 @@ function LearnMore() {
1266
1334
  }
1267
1335
 
1268
1336
  // src/ui/Sidebar.tsx
1269
- import { Box as Box12, Text as Text12 } from "ink";
1337
+ import { Box as Box13, Text as Text13 } from "ink";
1270
1338
 
1271
1339
  // src/ui/Steps.tsx
1272
- import { Box as Box10, Text as Text10 } from "ink";
1340
+ import { Box as Box11, Text as Text11 } from "ink";
1273
1341
  import Spinner from "ink-spinner";
1274
1342
 
1275
1343
  // src/core/persistence.ts
@@ -1298,12 +1366,12 @@ async function clearWorkflowState(workflowId) {
1298
1366
  }
1299
1367
 
1300
1368
  // src/ui/Steps.tsx
1301
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1369
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1302
1370
  function Steps() {
1303
1371
  const { steps } = useWizard();
1304
1372
  const visibleSteps = steps.filter(isStepVisible);
1305
- 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: [
1306
- s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1373
+ 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: [
1374
+ s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
1307
1375
  " ",
1308
1376
  s.title
1309
1377
  ] }) }, s.id)) });
@@ -1312,27 +1380,27 @@ function CurrentStep() {
1312
1380
  const { steps } = useWizard();
1313
1381
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1314
1382
  if (!currentStep) return null;
1315
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1316
- /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1383
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1384
+ /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1317
1385
  " ",
1318
1386
  ` ${currentStep.title}`
1319
1387
  ] });
1320
1388
  }
1321
1389
 
1322
1390
  // src/ui/Progress.tsx
1323
- import { Box as Box11, Text as Text11 } from "ink";
1324
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1391
+ import { Box as Box12, Text as Text12 } from "ink";
1392
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1325
1393
  function Progress() {
1326
1394
  const { steps, currentStepIndex } = useWizard();
1327
1395
  const visibleSteps = steps.filter(isStepVisible);
1328
1396
  if (visibleSteps.length === 0) return null;
1329
1397
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1330
1398
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1331
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1332
- /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1333
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1334
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1335
- /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1399
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1400
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1401
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
1402
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
1403
+ /* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
1336
1404
  ] });
1337
1405
  }
1338
1406
 
@@ -1343,10 +1411,10 @@ var sidebarCommands = [
1343
1411
  ];
1344
1412
 
1345
1413
  // src/ui/Sidebar.tsx
1346
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1414
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1347
1415
  function Sidebar() {
1348
- return /* @__PURE__ */ jsxs11(
1349
- Box12,
1416
+ return /* @__PURE__ */ jsxs12(
1417
+ Box13,
1350
1418
  {
1351
1419
  backgroundColor: "#14171E",
1352
1420
  width: 30,
@@ -1355,16 +1423,16 @@ function Sidebar() {
1355
1423
  flexDirection: "column",
1356
1424
  justifyContent: "space-between",
1357
1425
  children: [
1358
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1359
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1360
- /* @__PURE__ */ jsx10(Steps, {})
1426
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1427
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1428
+ /* @__PURE__ */ jsx11(Steps, {})
1361
1429
  ] }),
1362
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1363
- /* @__PURE__ */ jsx10(Progress, {}),
1364
- /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1365
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1366
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1367
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1430
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1431
+ /* @__PURE__ */ jsx11(Progress, {}),
1432
+ /* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
1433
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1434
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1435
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
1368
1436
  ] });
1369
1437
  }) })
1370
1438
  ] })
@@ -1374,12 +1442,12 @@ function Sidebar() {
1374
1442
  }
1375
1443
 
1376
1444
  // src/ui/Ribbon.tsx
1377
- import { Box as Box13, Text as Text13 } from "ink";
1378
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1445
+ import { Box as Box14, Text as Text14 } from "ink";
1446
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1379
1447
  function Ribbon() {
1380
1448
  const firstCommand = sidebarCommands[0];
1381
- return /* @__PURE__ */ jsxs12(
1382
- Box13,
1449
+ return /* @__PURE__ */ jsxs13(
1450
+ Box14,
1383
1451
  {
1384
1452
  backgroundColor: "#14171E",
1385
1453
  flexDirection: "row",
@@ -1387,11 +1455,11 @@ function Ribbon() {
1387
1455
  paddingX: 2,
1388
1456
  paddingY: 1,
1389
1457
  children: [
1390
- /* @__PURE__ */ jsx11(Progress, {}),
1391
- /* @__PURE__ */ jsx11(CurrentStep, {}),
1392
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1393
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1394
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1458
+ /* @__PURE__ */ jsx12(Progress, {}),
1459
+ /* @__PURE__ */ jsx12(CurrentStep, {}),
1460
+ /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1461
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1462
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
1395
1463
  ] })
1396
1464
  ]
1397
1465
  }
@@ -1402,8 +1470,8 @@ function Ribbon() {
1402
1470
  import { useState as useState6 } from "react";
1403
1471
 
1404
1472
  // src/ui/Logs.tsx
1405
- import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1406
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1473
+ import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1474
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1407
1475
  var KIND_COLOR = {
1408
1476
  tool: COLORS.primary,
1409
1477
  prompt: COLORS.badge
@@ -1434,14 +1502,14 @@ function formatTimestamp(ms) {
1434
1502
  function Logs() {
1435
1503
  const logs = useWizard((s) => s.logs);
1436
1504
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1437
- useInput5((_input, key) => {
1505
+ useInput6((_input, key) => {
1438
1506
  if (key.upArrow) scroll.scrollBy(-1);
1439
1507
  else if (key.downArrow) scroll.scrollBy(1);
1440
1508
  });
1441
1509
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1442
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1443
- logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1444
- /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1510
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1511
+ logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
1512
+ /* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
1445
1513
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1446
1514
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1447
1515
  const rawPreview = rawInputText(entry.input);
@@ -1451,14 +1519,14 @@ function Logs() {
1451
1519
  const name = truncate2(entry.name, budget);
1452
1520
  budget -= name.length;
1453
1521
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1454
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1455
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1456
- /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1457
- preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1458
- durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1522
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
1523
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
1524
+ /* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
1525
+ preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
1526
+ durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
1459
1527
  ] }, entry.id);
1460
1528
  }) }),
1461
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1529
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1462
1530
  ] });
1463
1531
  }
1464
1532
 
@@ -1650,7 +1718,7 @@ function track(event, payload) {
1650
1718
  }
1651
1719
 
1652
1720
  // src/ui/App.tsx
1653
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1721
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1654
1722
  function App() {
1655
1723
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1656
1724
  const { exit } = useApp();
@@ -1658,7 +1726,7 @@ function App() {
1658
1726
  const [showLogs, setShowLogs] = useState6(false);
1659
1727
  const finished = phase === "done" || phase === "error";
1660
1728
  const currentStep = steps[currentStepIndex];
1661
- useInput6(
1729
+ useInput7(
1662
1730
  (_input, key) => {
1663
1731
  if (key.return) {
1664
1732
  exit();
@@ -1666,7 +1734,7 @@ function App() {
1666
1734
  },
1667
1735
  { isActive: finished }
1668
1736
  );
1669
- useInput6((_input, key) => {
1737
+ useInput7((_input, key) => {
1670
1738
  if (phase === "idle" || phase === "authenticating") return;
1671
1739
  if (key.tab) {
1672
1740
  setShowLogs(!showLogs);
@@ -1677,8 +1745,8 @@ function App() {
1677
1745
  });
1678
1746
  }
1679
1747
  });
1680
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1681
- useInput6((_input, key) => {
1748
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1749
+ useInput7((_input, key) => {
1682
1750
  if (escOwnedElsewhere) return;
1683
1751
  if (key.escape) {
1684
1752
  track("AI Wizard Interaction", {
@@ -1697,8 +1765,8 @@ function App() {
1697
1765
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1698
1766
  the whole screen, and the scrolling throws off its cursor arithmetic —
1699
1767
  flicker and leftover rows. */
1700
- /* @__PURE__ */ jsxs14(
1701
- Box15,
1768
+ /* @__PURE__ */ jsxs15(
1769
+ Box16,
1702
1770
  {
1703
1771
  backgroundColor: COLORS.bg.main,
1704
1772
  flexDirection: "row",
@@ -1706,16 +1774,16 @@ function App() {
1706
1774
  height: scrollsPastViewport ? void 0 : rows,
1707
1775
  overflow: scrollsPastViewport ? "visible" : "hidden",
1708
1776
  children: [
1709
- mainWindowVisible && /* @__PURE__ */ jsxs14(
1710
- Box15,
1777
+ mainWindowVisible && /* @__PURE__ */ jsxs15(
1778
+ Box16,
1711
1779
  {
1712
1780
  flexDirection,
1713
1781
  width: "100%",
1714
1782
  maxHeight: rows,
1715
1783
  justifyContent: "space-between",
1716
1784
  children: [
1717
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1718
- Box15,
1785
+ showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1786
+ Box16,
1719
1787
  {
1720
1788
  flexDirection: "column",
1721
1789
  paddingX: 4,
@@ -1723,26 +1791,26 @@ function App() {
1723
1791
  width: showSidebar ? 70 : "100%",
1724
1792
  flexGrow: 1,
1725
1793
  children: [
1726
- phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1727
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1728
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1794
+ phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
1795
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1796
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1729
1797
  ] }),
1730
- /* @__PURE__ */ jsx13(CliOutput, {}),
1731
- /* @__PURE__ */ jsx13(Notices, {}),
1732
- /* @__PURE__ */ jsx13(PromptInput, {}),
1733
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1734
- phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1798
+ /* @__PURE__ */ jsx14(CliOutput, {}),
1799
+ /* @__PURE__ */ jsx14(Notices, {}),
1800
+ /* @__PURE__ */ jsx14(PromptInput, {}),
1801
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
1802
+ phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1735
1803
  "\u2716 ",
1736
1804
  error
1737
1805
  ] }) })
1738
1806
  ]
1739
1807
  }
1740
1808
  ),
1741
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1809
+ showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1742
1810
  ]
1743
1811
  }
1744
1812
  ),
1745
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1813
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1746
1814
  ]
1747
1815
  }
1748
1816
  )
@@ -1822,7 +1890,7 @@ async function ensureConsent() {
1822
1890
  if (config.aiConsent) return;
1823
1891
  const store = useWizard.getState();
1824
1892
  const answer = await store.requestUserInput({
1825
- prompt: "Wizard will make AI-authored changes to this repository.",
1893
+ 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.",
1826
1894
  promptType: "enterToContinue",
1827
1895
  options: []
1828
1896
  });
@@ -2811,92 +2879,192 @@ function searchFilesTool(ctx) {
2811
2879
  });
2812
2880
  }
2813
2881
 
2814
- // src/lib/tools/verifyImplementation.ts
2882
+ // src/lib/tools/runShell.ts
2815
2883
  import { tool as tool8 } from "ai";
2816
2884
  import z15 from "zod";
2885
+ import { relative as relative2 } from "node:path";
2817
2886
 
2818
- // src/lib/tools/utils/runCommand.ts
2887
+ // src/lib/tools/utils/runShell.ts
2819
2888
  import { spawn as spawn2 } from "node:child_process";
2820
- function runCommand(command, args, cwd) {
2889
+
2890
+ // src/lib/tools/context.ts
2891
+ var DEFAULT_TOOL_LIMITS = {
2892
+ list: 10,
2893
+ search: 10,
2894
+ read: 20,
2895
+ match: 100,
2896
+ shell: 30
2897
+ };
2898
+ var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2899
+ async function refuseByDefault() {
2900
+ return "reject";
2901
+ }
2902
+ function createShellContext(overrides = {}) {
2903
+ return {
2904
+ env: async () => ({}),
2905
+ timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2906
+ executions: [],
2907
+ approve: refuseByDefault,
2908
+ ...overrides
2909
+ };
2910
+ }
2911
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2912
+ return {
2913
+ root: cwd,
2914
+ cwd,
2915
+ limits: { ...limits },
2916
+ counts: { list: 0, search: 0, read: 0, shell: 0 },
2917
+ shell: shell2
2918
+ };
2919
+ }
2920
+
2921
+ // src/lib/tools/utils/runShell.ts
2922
+ var SIGKILL_DELAY_MS = 5e3;
2923
+ var HEAD_CHARS = 4e3;
2924
+ var TAIL_CHARS = 8e3;
2925
+ function truncateOutput(output) {
2926
+ if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
2927
+ const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
2928
+ return [
2929
+ output.slice(0, HEAD_CHARS),
2930
+ `
2931
+ \u2026 [${omitted} characters omitted] \u2026
2932
+ `,
2933
+ output.slice(-TAIL_CHARS)
2934
+ ].join("");
2935
+ }
2936
+ function runShell(command, opts) {
2937
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2938
+ const startedAt = Date.now();
2821
2939
  return new Promise((resolve4) => {
2822
2940
  let output = "";
2823
- const child = spawn2(command, args, {
2824
- cwd,
2825
- stdio: ["ignore", "pipe", "pipe"]
2941
+ let timedOut = false;
2942
+ let settled = false;
2943
+ const child = spawn2(command, {
2944
+ shell: true,
2945
+ cwd: opts.cwd,
2946
+ stdio: ["ignore", "pipe", "pipe"],
2947
+ env: { ...process.env, ...opts.env }
2826
2948
  });
2949
+ const finish = (exitCode) => {
2950
+ if (settled) return;
2951
+ settled = true;
2952
+ clearTimeout(timer);
2953
+ clearTimeout(killTimer);
2954
+ resolve4({
2955
+ exitCode,
2956
+ output: truncateOutput(output.trim()),
2957
+ timedOut,
2958
+ durationMs: Date.now() - startedAt
2959
+ });
2960
+ };
2961
+ let killTimer;
2962
+ const timer = setTimeout(() => {
2963
+ timedOut = true;
2964
+ output += `
2965
+ [timed out after ${timeoutMs}ms]`;
2966
+ child.kill("SIGTERM");
2967
+ killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
2968
+ }, timeoutMs);
2827
2969
  child.stdout?.on("data", (d) => output += d);
2828
2970
  child.stderr?.on("data", (d) => output += d);
2829
- child.on(
2830
- "error",
2831
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2832
- );
2833
- child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2971
+ child.on("error", (err) => {
2972
+ output += `Failed to run ${command}: ${err.message}`;
2973
+ finish(1);
2974
+ });
2975
+ child.on("close", (code) => finish(code ?? 1));
2834
2976
  });
2835
2977
  }
2836
2978
 
2837
- // src/lib/tools/utils/packageManager.ts
2838
- import { readFile as readFile7 } from "node:fs/promises";
2839
- import { existsSync } from "node:fs";
2840
- import { join as join9 } from "node:path";
2841
- var LOCKFILES = [
2842
- ["pnpm-lock.yaml", "pnpm"],
2843
- ["yarn.lock", "yarn"],
2844
- ["bun.lockb", "bun"],
2845
- ["bun.lock", "bun"],
2846
- ["package-lock.json", "npm"]
2847
- ];
2848
- async function readPackageJson(cwd = process.cwd()) {
2849
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2850
- }
2851
- function packageManagerFrom(pkg) {
2852
- return pkg.packageManager?.split("@")[0] ?? "npm";
2853
- }
2854
- function packageManagerFromLockfile(cwd) {
2855
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2979
+ // src/lib/tools/runShell.ts
2980
+ function storeApproval(root) {
2981
+ return async (req) => {
2982
+ const rel = relative2(root, req.cwd);
2983
+ const answer = await useWizard.getState().requestUserInput({
2984
+ prompt: "Run this command?",
2985
+ promptType: "commandApproval",
2986
+ options: [],
2987
+ command: {
2988
+ ...req,
2989
+ cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2990
+ }
2991
+ });
2992
+ return answer === "approve" ? "approve" : "reject";
2993
+ };
2856
2994
  }
2857
- async function detectPackageManager(cwd) {
2858
- try {
2859
- const pkg = await readPackageJson(cwd);
2860
- if (pkg.packageManager) return packageManagerFrom(pkg);
2861
- } catch {
2862
- }
2863
- return packageManagerFromLockfile(cwd) ?? "npm";
2995
+ var shellChain = Promise.resolve();
2996
+ function serialize(work) {
2997
+ const result = shellChain.then(work);
2998
+ shellChain = result.catch(() => {
2999
+ });
3000
+ return result;
2864
3001
  }
2865
-
2866
- // src/lib/tools/repoVerification.ts
2867
- var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2868
- async function runRepoVerificationCheck() {
2869
- let pkg;
2870
- try {
2871
- pkg = await readPackageJson();
2872
- } catch (err) {
2873
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2874
- return { ok: false, checks: [], limitation };
3002
+ async function approveAndRun(ctx, command, cwd, explanation) {
3003
+ const decision = await ctx.shell.approve({ command, cwd, explanation });
3004
+ if (decision === "reject") {
3005
+ ctx.shell.executions.push({ command, cwd, approved: false });
3006
+ logger.info({ command }, "runShell: user rejected the command");
3007
+ return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
2875
3008
  }
2876
- const scripts = pkg.scripts ?? {};
2877
- const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2878
- if (present.length === 0) {
2879
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2880
- return { ok: false, checks: [], limitation };
2881
- }
2882
- const pm = await detectPackageManager(process.cwd());
2883
- const checks = [];
2884
- for (const script of present) {
2885
- const command = `${pm} run ${script}`;
2886
- const { code, output } = await runCommand(pm, ["run", script]);
2887
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2888
- }
2889
- return { ok: checks.every((c) => c.ok), checks };
3009
+ useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
3010
+ const env = await ctx.shell.env().catch((err) => {
3011
+ logger.warn({ err, command }, "runShell: could not resolve command env");
3012
+ return {};
3013
+ });
3014
+ const run2 = await (ctx.shell.run ?? runShell)(command, {
3015
+ cwd,
3016
+ env,
3017
+ timeoutMs: ctx.shell.timeoutMs
3018
+ });
3019
+ logger.info(
3020
+ {
3021
+ command,
3022
+ exitCode: run2.exitCode,
3023
+ timedOut: run2.timedOut,
3024
+ durationMs: run2.durationMs
3025
+ },
3026
+ "runShell finished"
3027
+ );
3028
+ await markInteraction();
3029
+ ctx.shell.executions.push({
3030
+ command,
3031
+ cwd,
3032
+ approved: true,
3033
+ exitCode: run2.exitCode,
3034
+ output: run2.output,
3035
+ timedOut: run2.timedOut,
3036
+ durationMs: run2.durationMs
3037
+ });
3038
+ return {
3039
+ exitCode: run2.exitCode,
3040
+ timedOut: run2.timedOut,
3041
+ output: run2.output
3042
+ };
2890
3043
  }
2891
-
2892
- // src/lib/tools/verifyImplementation.ts
2893
- function verifyImplementationTool() {
3044
+ function runShellTool(ctx) {
2894
3045
  return tool8({
2895
- 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.",
2896
- inputSchema: z15.object(),
2897
- execute: async () => {
2898
- logger.info("called verifyImplementation tool");
2899
- return runRepoVerificationCheck();
3046
+ 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.",
3047
+ inputSchema: z15.object({
3048
+ command: z15.string().describe(
3049
+ "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
3050
+ ),
3051
+ cwd: z15.string().optional().describe(
3052
+ "Directory to run in, relative to the project root. Defaults to the project root."
3053
+ ),
3054
+ explanation: z15.string().describe(
3055
+ "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."
3056
+ )
3057
+ }),
3058
+ execute: async ({ command, cwd, explanation }) => {
3059
+ if (++ctx.counts.shell > ctx.limits.shell) {
3060
+ return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
3061
+ }
3062
+ const resolved2 = resolveInRoot(ctx, cwd ?? ".");
3063
+ if (!resolved2.ok) return resolved2.error;
3064
+ logger.info({ command, cwd: resolved2.target }, "called runShell tool");
3065
+ return serialize(
3066
+ () => approveAndRun(ctx, command, resolved2.target, explanation)
3067
+ );
2900
3068
  }
2901
3069
  });
2902
3070
  }
@@ -3015,22 +3183,6 @@ function notifyUserTool() {
3015
3183
  });
3016
3184
  }
3017
3185
 
3018
- // src/lib/tools/context.ts
3019
- var DEFAULT_TOOL_LIMITS = {
3020
- list: 10,
3021
- search: 10,
3022
- read: 20,
3023
- match: 100
3024
- };
3025
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
3026
- return {
3027
- root: cwd,
3028
- cwd,
3029
- limits,
3030
- counts: { list: 0, search: 0, read: 0 }
3031
- };
3032
- }
3033
-
3034
3186
  // src/lib/tools/index.ts
3035
3187
  function withLogging(name, def) {
3036
3188
  const execute = def.execute;
@@ -3062,10 +3214,7 @@ function createTools(ctx, { output, tools }) {
3062
3214
  writeCredentialsTool(ctx)
3063
3215
  ),
3064
3216
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3065
- verifyImplementation: withLogging(
3066
- "verifyImplementation",
3067
- verifyImplementationTool()
3068
- ),
3217
+ runShell: withLogging("runShell", runShellTool(ctx)),
3069
3218
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3070
3219
  notifyUser: withLogging("notifyUser", notifyUserTool())
3071
3220
  };
@@ -3100,7 +3249,7 @@ async function runAgent(req) {
3100
3249
  baseURL: PROXY_BASE_URL,
3101
3250
  fetch: proxyFetch
3102
3251
  });
3103
- const toolContext = createToolContext();
3252
+ const toolContext = req.toolContext ?? createToolContext();
3104
3253
  const readTools = ["readFile", "searchFiles", "listFiles"];
3105
3254
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3106
3255
  const instructions = [
@@ -3165,7 +3314,11 @@ async function runAgent(req) {
3165
3314
  "runAgent finished"
3166
3315
  );
3167
3316
  logger.info(
3168
- { counts: toolContext.counts, limits: toolContext.limits },
3317
+ {
3318
+ counts: toolContext.counts,
3319
+ limits: toolContext.limits,
3320
+ commandsRun: toolContext.shell.executions.length
3321
+ },
3169
3322
  "tool usage"
3170
3323
  );
3171
3324
  const toolResults = await stream.toolResults;
@@ -3290,7 +3443,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3290
3443
  // package.json
3291
3444
  var package_default = {
3292
3445
  name: "@algolia/wizard",
3293
- version: "0.12.0-rc.104.151",
3446
+ version: "0.13.0",
3294
3447
  description: "Magically implement Algolia functionality in your codebase",
3295
3448
  type: "module",
3296
3449
  engines: {
@@ -3693,16 +3846,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3693
3846
  import z27 from "zod";
3694
3847
 
3695
3848
  // src/lib/worktree.ts
3696
- import { execFile, spawn as spawn3 } from "node:child_process";
3697
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3698
- import {
3699
- basename as basename2,
3700
- dirname as dirname7,
3701
- isAbsolute as isAbsolute2,
3702
- join as join10,
3703
- relative as relative2,
3704
- resolve as resolve3
3705
- } from "node:path";
3849
+ import { execFile } from "node:child_process";
3850
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3851
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
3706
3852
  var MAX_BUFFER = 32 * 1024 * 1024;
3707
3853
  var MAX_WIZARD_WORKTREES = 3;
3708
3854
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3733,7 +3879,7 @@ async function isWorkingTreeDirty(repoRoot) {
3733
3879
  return out.trim().length > 0;
3734
3880
  }
3735
3881
  async function pruneOldWorktrees(repoRoot) {
3736
- const dir = join10(stateDir(repoRoot), "worktrees");
3882
+ const dir = join9(stateDir(repoRoot), "worktrees");
3737
3883
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3738
3884
  for (const slug of stale) {
3739
3885
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3744,7 +3890,7 @@ async function pruneOldWorktrees(repoRoot) {
3744
3890
  "worktree",
3745
3891
  "remove",
3746
3892
  "--force",
3747
- join10(dir, slug)
3893
+ join9(dir, slug)
3748
3894
  ]);
3749
3895
  await git(["-C", repoRoot, "branch", "-D", branch]);
3750
3896
  } catch (err) {
@@ -3758,113 +3904,13 @@ async function pruneOldWorktrees(repoRoot) {
3758
3904
  async function createWorktree(repoRoot) {
3759
3905
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3760
3906
  const dirSlug = branch.replace(/\//g, "-");
3761
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3907
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3762
3908
  await git(["-C", repoRoot, "worktree", "prune"]);
3763
3909
  await pruneOldWorktrees(repoRoot);
3764
3910
  await mkdir6(dirname7(path), { recursive: true });
3765
3911
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3766
3912
  return { path, branch };
3767
3913
  }
3768
- async function installWorktreeDeps(worktreePath) {
3769
- try {
3770
- await readPackageJson(worktreePath);
3771
- } catch {
3772
- return { ok: true, output: "no package.json; skipped install" };
3773
- }
3774
- const pm = await detectPackageManager(worktreePath);
3775
- return new Promise((resolve4) => {
3776
- let output = "";
3777
- const child = spawn3(pm, ["install"], {
3778
- cwd: worktreePath,
3779
- stdio: ["ignore", "pipe", "pipe"]
3780
- });
3781
- child.stdout?.on("data", (d) => output += d);
3782
- child.stderr?.on("data", (d) => output += d);
3783
- child.on(
3784
- "error",
3785
- (err) => resolve4({
3786
- ok: false,
3787
- output: `Failed to run ${pm} install: ${err.message}`
3788
- })
3789
- );
3790
- child.on(
3791
- "close",
3792
- (code) => resolve4({ ok: code === 0, output: output.trim() })
3793
- );
3794
- });
3795
- }
3796
- var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3797
- function validateIngestEntrypoint(worktreePath, entrypoint) {
3798
- if (!entrypoint || entrypoint.startsWith("-")) {
3799
- return {
3800
- ok: false,
3801
- reason: `entrypoint "${entrypoint}" is not a plain file path`
3802
- };
3803
- }
3804
- const target = resolve3(worktreePath, entrypoint);
3805
- const rel = relative2(worktreePath, target);
3806
- if (rel.startsWith("..") || isAbsolute2(rel)) {
3807
- return {
3808
- ok: false,
3809
- reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3810
- };
3811
- }
3812
- return { ok: true, target };
3813
- }
3814
- async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3815
- if (!INGEST_RUNTIMES.includes(runtime)) {
3816
- return {
3817
- ran: false,
3818
- ok: false,
3819
- output: "",
3820
- reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3821
- };
3822
- }
3823
- const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3824
- if (!validated.ok) {
3825
- return { ran: false, ok: false, output: "", reason: validated.reason };
3826
- }
3827
- try {
3828
- if (!(await stat2(validated.target)).isFile()) {
3829
- return {
3830
- ran: false,
3831
- ok: false,
3832
- output: "",
3833
- reason: `entrypoint "${entrypoint}" is not a file`
3834
- };
3835
- }
3836
- } catch {
3837
- return {
3838
- ran: false,
3839
- ok: false,
3840
- output: "",
3841
- reason: `entrypoint "${entrypoint}" does not exist`
3842
- };
3843
- }
3844
- return new Promise((resolveRun) => {
3845
- let output = "";
3846
- const child = spawn3(runtime, [entrypoint], {
3847
- cwd: worktreePath,
3848
- shell: false,
3849
- stdio: ["ignore", "pipe", "pipe"],
3850
- env: { ...process.env, ...env }
3851
- });
3852
- child.stdout?.on("data", (d) => output += d);
3853
- child.stderr?.on("data", (d) => output += d);
3854
- child.on(
3855
- "error",
3856
- (err) => resolveRun({
3857
- ran: true,
3858
- ok: false,
3859
- output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3860
- })
3861
- );
3862
- child.on(
3863
- "close",
3864
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3865
- );
3866
- });
3867
- }
3868
3914
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3869
3915
  const trimmed = sourcePath.trim();
3870
3916
  if (!trimmed) {
@@ -3878,8 +3924,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3878
3924
  } catch {
3879
3925
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3880
3926
  }
3881
- const relPath = join10(ingestDir, basename2(source));
3882
- const dest = join10(worktreePath, relPath);
3927
+ const relPath = join9(ingestDir, basename2(source));
3928
+ const dest = join9(worktreePath, relPath);
3883
3929
  try {
3884
3930
  await mkdir6(dirname7(dest), { recursive: true });
3885
3931
  await copyFile(source, dest);
@@ -3897,7 +3943,7 @@ function hasEnvVar(content, name) {
3897
3943
  async function readEnvVar(worktreePath, name) {
3898
3944
  let content;
3899
3945
  try {
3900
- content = await readFile8(join10(worktreePath, ".env"), "utf8");
3946
+ content = await readFile7(join9(worktreePath, ".env"), "utf8");
3901
3947
  } catch (err) {
3902
3948
  if (err.code !== "ENOENT") throw err;
3903
3949
  return void 0;
@@ -3912,10 +3958,10 @@ async function readEnvVar(worktreePath, name) {
3912
3958
  return value;
3913
3959
  }
3914
3960
  async function writeSearchEnvValues(worktreePath, vars) {
3915
- const target = join10(worktreePath, ".env");
3961
+ const target = join9(worktreePath, ".env");
3916
3962
  let existing = "";
3917
3963
  try {
3918
- existing = await readFile8(target, "utf8");
3964
+ existing = await readFile7(target, "utf8");
3919
3965
  } catch (err) {
3920
3966
  if (err.code !== "ENOENT") throw err;
3921
3967
  }
@@ -3984,15 +4030,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3984
4030
  }
3985
4031
 
3986
4032
  // src/lib/algoliaDocs.ts
3987
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3988
- import { dirname as dirname8, join as join11 } from "node:path";
4033
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
4034
+ import { dirname as dirname8, join as join10 } from "node:path";
3989
4035
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3990
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
4036
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3991
4037
  function findDocsDir() {
3992
4038
  let dir = dirname8(fileURLToPath2(import.meta.url));
3993
4039
  for (; ; ) {
3994
- const candidate = join11(dir, DOCS_SUBPATH);
3995
- if (existsSync2(candidate)) return candidate;
4040
+ const candidate = join10(dir, DOCS_SUBPATH);
4041
+ if (existsSync(candidate)) return candidate;
3996
4042
  const parent = dirname8(dir);
3997
4043
  if (parent === dir) return void 0;
3998
4044
  dir = parent;
@@ -4014,7 +4060,7 @@ function loadAlgoliaDoc(language) {
4014
4060
  );
4015
4061
  return "";
4016
4062
  }
4017
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
4063
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
4018
4064
  }
4019
4065
  function getNamedDoc(name, language) {
4020
4066
  const docsDir = findDocsDir();
@@ -4022,8 +4068,8 @@ function getNamedDoc(name, language) {
4022
4068
  logger.warn("docs/algolia-sdk not found");
4023
4069
  return "";
4024
4070
  }
4025
- const file = join11(docsDir, `${name}-${language}.md`);
4026
- if (!existsSync2(file)) {
4071
+ const file = join10(docsDir, `${name}-${language}.md`);
4072
+ if (!existsSync(file)) {
4027
4073
  logger.warn({ name, language }, "named SDK reference not found");
4028
4074
  return "";
4029
4075
  }
@@ -4067,11 +4113,7 @@ var implementSchema = z27.object({
4067
4113
  });
4068
4114
  var implementationOutputSchema = z27.object({
4069
4115
  summary: z27.string(),
4070
- // Ingestion only: a structured pair the wizard turns into an argv, never a
4071
- // free-form command string. `runtime` is allowlisted and `entrypoint` is
4072
- // validated worktree-relative, so the agent cannot inject extra commands.
4073
- runtime: z27.enum(INGEST_RUNTIMES).optional(),
4074
- entrypoint: z27.string().optional()
4116
+ ingestCommand: z27.string().optional()
4075
4117
  });
4076
4118
  var verificationOutputSchema = z27.object({
4077
4119
  summary: z27.string(),
@@ -4156,7 +4198,9 @@ function baseInstructions(input) {
4156
4198
  // index-scoped keys then reject with a 403.
4157
4199
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4158
4200
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4159
- "Make minimal, idiomatic changes; do not touch unrelated code."
4201
+ "Make minimal, idiomatic changes; do not touch unrelated code.",
4202
+ `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4203
+ "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."
4160
4204
  ];
4161
4205
  }
4162
4206
  function sourceSpecificInstructions(input) {
@@ -4192,9 +4236,9 @@ function ingestionInstructions(input) {
4192
4236
  "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4193
4237
  "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.",
4194
4238
  getNamedDoc("save-records", "js"),
4195
- 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4239
+ 'Install the Algolia client via runShell and add it to package.json "dependencies" with a valid version range, so the dependency is not just installed ad hoc.',
4240
+ '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.',
4196
4241
  "The summary should be extremely concise.",
4197
- `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.`,
4198
4242
  ...sourceSpecificInstructions(input)
4199
4243
  ] : []
4200
4244
  ];
@@ -4208,6 +4252,7 @@ function searchInstructions(input) {
4208
4252
  doc,
4209
4253
  `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.`,
4210
4254
  `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.`,
4255
+ "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.",
4211
4256
  // The key is provisioned only after verification passes, so the agent never
4212
4257
  // sees one. It must also leave .env alone: the wizard reads that file to
4213
4258
  // decide whether a key already exists, and an agent-invented value there
@@ -4217,8 +4262,7 @@ function searchInstructions(input) {
4217
4262
  // ".env" right after this step, so a renamed prefix would leave the code
4218
4263
  // reading a var the wizard never wrote.
4219
4264
  `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4220
- "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.",
4221
- '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.',
4265
+ 'Install any Algolia/InstantSearch packages you import via runShell, and add them to package.json "dependencies" with a valid version range.',
4222
4266
  "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."
4223
4267
  ];
4224
4268
  }
@@ -4226,11 +4270,12 @@ function verificationInstructions(input) {
4226
4270
  return [
4227
4271
  "Verify the Algolia implementation changes in the current worktree.",
4228
4272
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4229
- "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4230
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4231
- "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.",
4273
+ "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.",
4274
+ "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.",
4275
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4276
+ "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.",
4232
4277
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4233
- `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4278
+ `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4234
4279
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4235
4280
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4236
4281
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4251,14 +4296,15 @@ var IMPLEMENT_CONFIG = {
4251
4296
  }
4252
4297
  };
4253
4298
  var useCaseToolMap = {
4254
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4255
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4256
- verification: [
4299
+ ingestion: [
4257
4300
  ...FS_READ_TOOLS,
4258
4301
  "writeFile",
4259
- "verifyImplementation",
4302
+ "writeCredentials",
4303
+ "runShell",
4260
4304
  "notifyUser"
4261
- ]
4305
+ ],
4306
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4307
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4262
4308
  };
4263
4309
  function toolsForUseCase(useCase, ingestionSource) {
4264
4310
  const tools = useCaseToolMap[useCase];
@@ -4281,15 +4327,51 @@ function formatSummary(useCase, summary) {
4281
4327
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4282
4328
  return `${label}: ${summary}`;
4283
4329
  }
4284
- function buildIngestCommand(worktree, runtime, entrypoint) {
4285
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4286
- }
4287
4330
  function parseIngestRecordCount(output) {
4288
4331
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4289
4332
  if (!match) return void 0;
4290
4333
  const count = Number(match[1]);
4291
4334
  return Number.isFinite(count) ? count : void 0;
4292
4335
  }
4336
+ function ingestOutcome(executions, ingestCommand) {
4337
+ const newestFirst = [...executions].reverse();
4338
+ const withCount = newestFirst.filter(
4339
+ (e) => parseIngestRecordCount(e.output ?? "") != null
4340
+ );
4341
+ const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4342
+ return {
4343
+ run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4344
+ attempt: ingestCommand ? newestFirst.find((e) => e.command === ingestCommand) : void 0,
4345
+ recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4346
+ };
4347
+ }
4348
+ var INGEST_OUTPUT_TAIL_CHARS = 400;
4349
+ function outputTail(output) {
4350
+ const trimmed = output?.trim();
4351
+ if (!trimmed) return void 0;
4352
+ return trimmed.length > INGEST_OUTPUT_TAIL_CHARS ? `\u2026${trimmed.slice(-INGEST_OUTPUT_TAIL_CHARS)}` : trimmed;
4353
+ }
4354
+ function ingestFailure(attempt, executions) {
4355
+ if (!attempt) {
4356
+ return {
4357
+ reason: executions.some((e) => !e.approved) ? "a command it needed was declined" : "no successful run was recorded"
4358
+ };
4359
+ }
4360
+ if (!attempt.approved) return { reason: "you declined to run it" };
4361
+ const detail = outputTail(attempt.output);
4362
+ if (attempt.timedOut) {
4363
+ const seconds = Math.round((attempt.durationMs ?? 0) / 1e3);
4364
+ return { reason: `it timed out after ${seconds}s`, detail };
4365
+ }
4366
+ return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
4367
+ }
4368
+ function makeToolContext(worktree, env = async () => ({})) {
4369
+ return createToolContext(
4370
+ DEFAULT_TOOL_LIMITS,
4371
+ worktree,
4372
+ createShellContext({ env, approve: storeApproval(worktree) })
4373
+ );
4374
+ }
4293
4375
  function verificationRetryInstructions(verification) {
4294
4376
  return [
4295
4377
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4368,9 +4450,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4368
4450
  const confirmed2 = normalized.confirmedEntities;
4369
4451
  const searchLocation = normalized.searchImplementationAnalysis;
4370
4452
  let appId;
4453
+ let ingestAppId;
4371
4454
  if (useCases.includes("search")) {
4372
4455
  appId = (await requireApplication()).id;
4373
4456
  }
4457
+ if (useCases.includes("ingestion")) {
4458
+ ingestAppId = appId ?? (await requireApplication()).id;
4459
+ }
4374
4460
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4375
4461
  try {
4376
4462
  process.chdir(worktree);
@@ -4428,41 +4514,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4428
4514
  }
4429
4515
  let finalSearchEnvVars = input.searchEnvVars;
4430
4516
  let agentRuns = 0;
4431
- let ingestRuntime;
4432
- let ingestEntrypoint;
4517
+ let ingestCommand;
4433
4518
  let ingestScriptRan = false;
4434
4519
  let ingestRecordCount;
4435
4520
  let ingestDurationMs;
4436
- let installFailed = false;
4437
4521
  let ingestOutcomeMessage;
4522
+ const ingestKeyAppId = ingestAppId;
4523
+ const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4524
+ [APP_ID_VAR]: ingestKeyAppId,
4525
+ [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4526
+ [INDEX_NAME_VAR]: targetIndex
4527
+ })) : void 0;
4528
+ const searchTools = makeToolContext(worktree);
4438
4529
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4439
4530
  if (agentRuns > 0) ctx.recordStepExecution();
4440
4531
  agentRuns += 1;
4441
- const result = await runAgent({
4532
+ return runAgent({
4442
4533
  instructions: buildAgentInstructions(
4443
4534
  currentUseCase,
4444
4535
  input,
4445
4536
  extraInstructions
4446
4537
  ),
4447
4538
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4448
- outputSchema: implementationOutputSchema
4449
- });
4450
- ctx.notify({
4451
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4539
+ outputSchema: implementationOutputSchema,
4540
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4452
4541
  });
4453
- const installLogId = ctx.logStart("installWorktreeDeps", {
4454
- useCase: currentUseCase
4455
- });
4456
- const install = await installWorktreeDeps(worktree);
4457
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4458
- if (!install.ok) {
4459
- installFailed = true;
4460
- logger.warn(
4461
- { useCase: currentUseCase, output: install.output },
4462
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4463
- );
4464
- }
4465
- return result;
4466
4542
  }
4467
4543
  async function runVerificationUseCase() {
4468
4544
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4470,111 +4546,75 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4470
4546
  return runAgent({
4471
4547
  instructions: buildAgentInstructions("verification", input),
4472
4548
  tools: toolsForUseCase("verification"),
4473
- outputSchema: verificationOutputSchema
4549
+ outputSchema: verificationOutputSchema,
4550
+ toolContext: searchTools
4474
4551
  });
4475
4552
  }
4476
4553
  if (useCases.includes("ingestion")) {
4477
- const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4478
- summaries.push(formatSummary("ingestion", summary));
4479
- ingestRuntime = runtime;
4480
- ingestEntrypoint = entrypoint;
4481
- if (ingestRuntime && ingestEntrypoint && !installFailed) {
4482
- ctx.clearNotices();
4483
- const runNow = await ctx.requestUserInput({
4484
- prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4485
- promptType: "acceptReject",
4486
- options: ["Yes", "No"],
4487
- messages: []
4488
- }) === true;
4489
- if (runNow) {
4490
- const ingestApp = await requireApplication();
4491
- const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4492
- ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4493
- const scriptLogId = ctx.logStart("runIngestScript", {
4494
- runtime: ingestRuntime,
4495
- entrypoint: ingestEntrypoint
4554
+ let ingestFailureDetail;
4555
+ const result = await runImplementationUseCase("ingestion");
4556
+ summaries.push(formatSummary("ingestion", result.summary));
4557
+ ingestCommand = result.ingestCommand;
4558
+ const executions = (ingestionTools ?? searchTools).shell.executions;
4559
+ const {
4560
+ run: ingestRun,
4561
+ attempt: ingestAttempt,
4562
+ recordCount
4563
+ } = ingestOutcome(executions, ingestCommand);
4564
+ ingestScriptRan = ingestRun != null;
4565
+ ingestRecordCount = recordCount;
4566
+ ingestDurationMs = ingestRun?.durationMs;
4567
+ if (ingestScriptRan) {
4568
+ ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4569
+ if (ingestRecordCount != null) {
4570
+ track("AI Wizard Ingest Successful", {
4571
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4572
+ record_count: ingestRecordCount,
4573
+ duration_ms: ingestDurationMs ?? 0
4496
4574
  });
4497
- const startedAt = Date.now();
4498
- const run2 = await runIngestScript(
4499
- worktree,
4500
- ingestRuntime,
4501
- ingestEntrypoint,
4502
- {
4503
- [APP_ID_VAR]: ingestApp.id,
4504
- [API_KEY_VAR]: writeKey,
4505
- [INDEX_NAME_VAR]: targetIndex
4506
- }
4507
- );
4508
- ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4509
- ingestScriptRan = run2.ran && run2.ok;
4510
- if (ingestScriptRan) {
4511
- ingestDurationMs = Date.now() - startedAt;
4512
- ingestRecordCount = parseIngestRecordCount(run2.output);
4513
- if (ingestRecordCount != null) {
4514
- track("AI Wizard Ingest Successful", {
4515
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4516
- record_count: ingestRecordCount,
4517
- duration_ms: ingestDurationMs
4518
- });
4519
- }
4520
- }
4521
- let summaryLine;
4522
- let outcomeMessage;
4523
- if (!run2.ran) {
4524
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4525
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4526
- logger.warn(
4527
- {
4528
- runtime: ingestRuntime,
4529
- entrypoint: ingestEntrypoint,
4530
- reason: run2.reason
4531
- },
4532
- "implement: refused to auto-run ingestion script"
4533
- );
4534
- track("Error", {
4535
- step: "Push Data",
4536
- error: `ingestion script skipped: ${run2.reason}`,
4537
- product_area: "AI Wizard"
4538
- });
4539
- } else if (run2.ok) {
4540
- const status = "Ingestion run: succeeded.";
4541
- summaryLine = run2.output ? `${status}
4542
- ${run2.output}` : status;
4543
- outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4544
- } else {
4545
- const status = "\u26A0\uFE0F Ingestion run failed:";
4546
- summaryLine = run2.output ? `${status}
4547
- ${run2.output}` : status;
4548
- outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4549
- logger.warn(
4550
- {
4551
- runtime: ingestRuntime,
4552
- entrypoint: ingestEntrypoint,
4553
- output: run2.output
4554
- },
4555
- "implement: ingestion script run failed"
4556
- );
4557
- track("Error", {
4558
- step: "Push Data",
4559
- error: run2.output || "ingestion script exited non-zero",
4560
- product_area: "AI Wizard"
4561
- });
4562
- }
4563
- summaries.push(summaryLine);
4564
- ingestOutcomeMessage = outcomeMessage;
4565
4575
  }
4576
+ } else {
4577
+ const { reason, detail } = ingestFailure(ingestAttempt, executions);
4578
+ ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4579
+ ingestFailureDetail = detail;
4580
+ summaries.push(
4581
+ `\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
4582
+ ${detail}` : ""}`
4583
+ );
4584
+ logger.warn(
4585
+ {
4586
+ ingestCommand,
4587
+ reason,
4588
+ approved: ingestAttempt?.approved,
4589
+ exitCode: ingestAttempt?.exitCode,
4590
+ timedOut: ingestAttempt?.timedOut,
4591
+ commandsRun: executions.length
4592
+ },
4593
+ "implement: ingestion script did not complete successfully"
4594
+ );
4595
+ track("Error", {
4596
+ step: "Push Data",
4597
+ error: `ingestion did not complete: ${reason}`,
4598
+ product_area: "AI Wizard"
4599
+ });
4566
4600
  }
4567
4601
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4568
- if (ingestRuntime && ingestEntrypoint) {
4569
- commandMessages.push(
4570
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4571
- );
4602
+ if (ingestCommand) {
4603
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4572
4604
  }
4573
4605
  await ctx.requestUserInput({
4574
4606
  prompt: "",
4575
4607
  promptType: "enterToContinue",
4576
4608
  options: [],
4577
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4609
+ // The wizard never streams command output, so a failed run's tail is the
4610
+ // only place the developer sees why it failed. One message per line:
4611
+ // the panel's height accounting counts a message as one wrapped line
4612
+ // (see Notices.tsx), so an embedded newline overflows it.
4613
+ messages: [
4614
+ ingestOutcomeMessage,
4615
+ ...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
4616
+ ...commandMessages
4617
+ ]
4578
4618
  });
4579
4619
  }
4580
4620
  if (useCases.includes("search")) {
@@ -4691,22 +4731,13 @@ ${run2.output}` : status;
4691
4731
  "implement: agent reported success but no files changed in the worktree"
4692
4732
  );
4693
4733
  }
4694
- if (installFailed) {
4695
- summaries.push(
4696
- '\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".'
4697
- );
4698
- }
4699
4734
  return {
4700
4735
  ingestionSource,
4701
4736
  filesChanged,
4702
4737
  summary: summaries.join("\n\n"),
4703
4738
  worktreePath: worktree,
4704
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4705
- ingestCommand: buildIngestCommand(
4706
- worktree,
4707
- ingestRuntime,
4708
- ingestEntrypoint
4709
- ),
4739
+ ...useCases.includes("ingestion") && ingestCommand ? {
4740
+ ingestCommand,
4710
4741
  ingestScriptRan,
4711
4742
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4712
4743
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -5038,7 +5069,7 @@ function parseCliArgs(argv) {
5038
5069
 
5039
5070
  // src/lib/resetState.ts
5040
5071
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
5041
- import { join as join12 } from "node:path";
5072
+ import { join as join11 } from "node:path";
5042
5073
  var KEEP = ["wizard.log"];
5043
5074
  async function resetProjectState() {
5044
5075
  const dir = stateDir();
@@ -5052,14 +5083,14 @@ async function resetProjectState() {
5052
5083
  const targets = entries.filter((name) => !KEEP.includes(name));
5053
5084
  await Promise.all(
5054
5085
  targets.map(
5055
- (name) => rm2(join12(dir, name), { recursive: true, force: true })
5086
+ (name) => rm2(join11(dir, name), { recursive: true, force: true })
5056
5087
  )
5057
5088
  );
5058
5089
  return { dir, removed: targets };
5059
5090
  }
5060
5091
 
5061
5092
  // src/main.tsx
5062
- import { jsx as jsx14 } from "react/jsx-runtime";
5093
+ import { jsx as jsx15 } from "react/jsx-runtime";
5063
5094
  async function startup() {
5064
5095
  setProjectRoot(process.cwd());
5065
5096
  let args;
@@ -5109,7 +5140,7 @@ ${formatStepList(workflow)}`);
5109
5140
  }
5110
5141
  async function run(workflow) {
5111
5142
  const store = useWizard.getState();
5112
- const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
5143
+ const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
5113
5144
  await store.waitForStart();
5114
5145
  let user = await getUser();
5115
5146
  if (!user) {