@algolia/wizard 0.22.0 → 0.24.0-rc.111.205

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 (3) hide show
  1. package/README.md +0 -14
  2. package/dist/main.js +557 -610
  3. 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 Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
7
+ import { Box as Box17, Text as Text17, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -251,6 +251,7 @@ var useWizard = create((set, get) => ({
251
251
  _noticeTimer: null,
252
252
  cliOutput: [],
253
253
  targetIndex: null,
254
+ writtenFiles: [],
254
255
  logs: [],
255
256
  error: null,
256
257
  inputReq: null,
@@ -344,6 +345,8 @@ var useWizard = create((set, get) => ({
344
345
  })),
345
346
  clearCliOutput: () => set({ cliOutput: [] }),
346
347
  setTargetIndex: (index) => set({ targetIndex: index }),
348
+ recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
349
+ clearWrittenFiles: () => set({ writtenFiles: [] }),
347
350
  logStart: (kind, name, input) => {
348
351
  const id = nanoid();
349
352
  set((s) => ({
@@ -391,6 +394,7 @@ var useWizard = create((set, get) => ({
391
394
  notices: [],
392
395
  cliOutput: [],
393
396
  targetIndex: null,
397
+ writtenFiles: [],
394
398
  logs: [],
395
399
  error: null,
396
400
  inputReq: null,
@@ -632,7 +636,7 @@ function Notices() {
632
636
  }
633
637
 
634
638
  // src/ui/PromptInput.tsx
635
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
639
+ import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
636
640
  import TextInput from "ink-text-input";
637
641
  import { useState as useState5 } from "react";
638
642
 
@@ -709,7 +713,7 @@ function CommandApproval({
709
713
  }
710
714
 
711
715
  // src/ui/SelectPrompt.tsx
712
- import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
716
+ import { Box as Box8, Text as Text8, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
713
717
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
714
718
 
715
719
  // src/ui/ScrollView.tsx
@@ -800,8 +804,47 @@ function ScrollView({ scroll, children }) {
800
804
  ] });
801
805
  }
802
806
 
803
- // src/ui/SelectPrompt.tsx
807
+ // src/ui/SelectRow.tsx
808
+ import { Box as Box7, Text as Text7 } from "ink";
804
809
  import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
810
+ function SelectRow({
811
+ highlighted,
812
+ label,
813
+ width,
814
+ labelWidth,
815
+ highlightBackground = true,
816
+ usePadding = false,
817
+ children
818
+ }) {
819
+ const labelColor = highlighted ? highlightBackground ? COLORS.highlight.fg : COLORS.success : void 0;
820
+ return /* @__PURE__ */ jsxs6(
821
+ Box7,
822
+ {
823
+ width,
824
+ paddingX: usePadding ? 1 : 0,
825
+ paddingY: usePadding ? 1 : 0,
826
+ backgroundColor: highlighted && highlightBackground ? COLORS.highlight.bg : void 0,
827
+ children: [
828
+ /* @__PURE__ */ jsx5(Box7, { width: labelWidth, children: /* @__PURE__ */ jsxs6(
829
+ Text7,
830
+ {
831
+ color: labelColor,
832
+ bold: highlighted && !highlightBackground,
833
+ wrap: "truncate",
834
+ children: [
835
+ highlighted ? "\u276F " : " ",
836
+ label
837
+ ]
838
+ }
839
+ ) }),
840
+ children
841
+ ]
842
+ }
843
+ );
844
+ }
845
+
846
+ // src/ui/SelectPrompt.tsx
847
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
805
848
  var CANCEL = "cancel";
806
849
  var ARROW_WIDTH = 4;
807
850
  var COLUMN_GAP = 2;
@@ -886,56 +929,50 @@ function SelectPrompt({
886
929
  }
887
930
  }
888
931
  });
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 })
932
+ return /* @__PURE__ */ jsx6(Box8, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, width, children: [
933
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
934
+ error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: error }),
935
+ messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
936
+ table && /* @__PURE__ */ jsx6(Table, { columns: table.columns, rows: table.rows }),
937
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
938
+ question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: question }),
939
+ helpText && /* @__PURE__ */ jsx6(Text8, { color: COLORS.dim, children: helpText })
897
940
  ] })
898
941
  ] }),
899
- /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
942
+ /* @__PURE__ */ jsx6(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
900
943
  const i = scroll.offset + visibleIndex;
901
944
  const highlighted = i === index;
902
945
  const isCancel = i === cancelIndex;
903
946
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
904
947
  const sec = isCancel ? void 0 : secondary?.[i];
905
- const labelColor = highlighted ? COLORS.highlight.fg : void 0;
906
- const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
907
- highlighted ? "\u276F " : " ",
908
- bullet,
909
- option
910
- ] });
911
948
  const isText = sec?.kind === "text";
912
- return /* @__PURE__ */ jsxs6(
913
- Box7,
949
+ return /* @__PURE__ */ jsxs7(
950
+ SelectRow,
914
951
  {
952
+ highlighted,
915
953
  width: isText ? "100%" : barWidth,
916
- paddingX: 1,
917
- paddingY: 1,
918
- backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
954
+ labelWidth: isText ? labelWidth : barLabelWidth,
955
+ label: `${bullet}${option}`,
956
+ usePadding: true,
919
957
  children: [
920
- /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
921
- isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
922
- Text7,
958
+ isText && textWidth > 0 && /* @__PURE__ */ jsx6(Box8, { width: textWidth, children: /* @__PURE__ */ jsx6(
959
+ Text8,
923
960
  {
924
961
  wrap: "truncate",
925
962
  color: highlighted ? COLORS.primary : COLORS.muted,
926
963
  children: sec.value
927
964
  }
928
965
  ) }),
929
- sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
966
+ sec?.kind === "badge" && /* @__PURE__ */ jsx6(Box8, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx6(Text8, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
930
967
  ]
931
968
  },
932
969
  `row-${i}`
933
970
  );
934
971
  }) }),
935
- /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
972
+ /* @__PURE__ */ jsx6(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx6(Text8, { color: COLORS.dim, children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs7(Text8, { color: COLORS.dim, children: [
936
973
  i > 0 ? " " : "",
937
- /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
938
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
974
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: key }),
975
+ /* @__PURE__ */ jsxs7(Text8, { color: COLORS.dim, children: [
939
976
  " ",
940
977
  label
941
978
  ] })
@@ -944,7 +981,7 @@ function SelectPrompt({
944
981
  }
945
982
 
946
983
  // src/ui/PromptInput.tsx
947
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
984
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
948
985
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
949
986
  function EnterToContinuePrompt({
950
987
  question,
@@ -955,12 +992,12 @@ function EnterToContinuePrompt({
955
992
  if (key.return) onDecide(true);
956
993
  else if (key.escape) onDecide(false);
957
994
  });
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" })
995
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 1, children: [
996
+ messages?.map((m, i) => /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: m }, `msg-${i}`)),
997
+ question && /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: question }),
998
+ /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
999
+ /* @__PURE__ */ jsx7(NextAction, { action: "continue", keyHint: "enter" }),
1000
+ /* @__PURE__ */ jsx7(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
964
1001
  ] })
965
1002
  ] });
966
1003
  }
@@ -968,11 +1005,11 @@ function PromptInput() {
968
1005
  const { phase, inputReq, submitInput } = useWizard();
969
1006
  const [draft, setDraft] = useState5("");
970
1007
  if (phase === "done" || phase === "error") {
971
- return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
1008
+ return /* @__PURE__ */ jsx7(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text9, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
972
1009
  }
973
1010
  if (phase !== "awaitingInput" || !inputReq) return null;
974
1011
  if (inputReq.promptType === "multipleChoice") {
975
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
1012
+ return /* @__PURE__ */ jsx7(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx7(
976
1013
  SelectPrompt,
977
1014
  {
978
1015
  question: inputReq.prompt,
@@ -989,7 +1026,7 @@ function PromptInput() {
989
1026
  ) });
990
1027
  }
991
1028
  if (inputReq.promptType === "multiSelect") {
992
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
1029
+ return /* @__PURE__ */ jsx7(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx7(
993
1030
  SelectPrompt,
994
1031
  {
995
1032
  multi: true,
@@ -1004,7 +1041,7 @@ function PromptInput() {
1004
1041
  ) });
1005
1042
  }
1006
1043
  if (inputReq.promptType === "notice") {
1007
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
1044
+ return /* @__PURE__ */ jsx7(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx7(
1008
1045
  SelectPrompt,
1009
1046
  {
1010
1047
  question: inputReq.prompt,
@@ -1015,7 +1052,7 @@ function PromptInput() {
1015
1052
  ) });
1016
1053
  }
1017
1054
  if (inputReq.promptType === "enterToContinue") {
1018
- return /* @__PURE__ */ jsx6(
1055
+ return /* @__PURE__ */ jsx7(
1019
1056
  EnterToContinuePrompt,
1020
1057
  {
1021
1058
  question: inputReq.prompt,
@@ -1025,11 +1062,11 @@ function PromptInput() {
1025
1062
  );
1026
1063
  }
1027
1064
  if (inputReq.promptType === "commandApproval" && inputReq.command) {
1028
- return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1065
+ return /* @__PURE__ */ jsx7(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1029
1066
  }
1030
1067
  if (inputReq.promptType === "acceptReject") {
1031
1068
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
1032
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
1069
+ return /* @__PURE__ */ jsx7(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx7(
1033
1070
  SelectPrompt,
1034
1071
  {
1035
1072
  question: inputReq.prompt,
@@ -1040,15 +1077,15 @@ function PromptInput() {
1040
1077
  }
1041
1078
  ) });
1042
1079
  }
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: [
1080
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1081
+ inputReq.error && /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: inputReq.error }),
1082
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: m }, `msg-${i}`)),
1083
+ /* @__PURE__ */ jsxs8(Box9, { children: [
1084
+ /* @__PURE__ */ jsxs8(Text9, { color: COLORS.primary, children: [
1048
1085
  inputReq.prompt,
1049
1086
  " "
1050
1087
  ] }),
1051
- /* @__PURE__ */ jsx6(
1088
+ /* @__PURE__ */ jsx7(
1052
1089
  TextInput,
1053
1090
  {
1054
1091
  value: draft,
@@ -1066,7 +1103,8 @@ function PromptInput() {
1066
1103
  // src/ui/Welcome.tsx
1067
1104
  import { dirname as dirname2, join as join3 } from "node:path";
1068
1105
  import { fileURLToPath } from "node:url";
1069
- import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1106
+ import { useState as useState6 } from "react";
1107
+ import { Box as Box10, Spacer, Text as Text10, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1070
1108
 
1071
1109
  // src/ui/copy/welcome.ts
1072
1110
  var sidebarItems = [
@@ -1094,7 +1132,7 @@ var sidebarItems = [
1094
1132
 
1095
1133
  // src/ui/Welcome.tsx
1096
1134
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
1097
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1135
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1098
1136
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1099
1137
  var TERMINAL_INFO = {
1100
1138
  ...defaultTerminalInfo,
@@ -1105,14 +1143,14 @@ function SidebarItem({
1105
1143
  title,
1106
1144
  description
1107
1145
  }) {
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 })
1146
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
1147
+ /* @__PURE__ */ jsxs9(Box10, { gap: 1, children: [
1148
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, children: "\u2192" }),
1149
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: title })
1112
1150
  ] }),
1113
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1114
- /* @__PURE__ */ jsx7(Spacer, {}),
1115
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1151
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 2, children: [
1152
+ /* @__PURE__ */ jsx8(Spacer, {}),
1153
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: description })
1116
1154
  ] })
1117
1155
  ] });
1118
1156
  }
@@ -1120,9 +1158,19 @@ function Welcome() {
1120
1158
  const confirmStart = useWizard((s) => s.confirmStart);
1121
1159
  const openLearnMore = useWizard((s) => s.openLearnMore);
1122
1160
  const { rows } = useWindowSize6();
1161
+ const actions = [
1162
+ { label: "start wizard", run: confirmStart },
1163
+ { label: "learn more", run: openLearnMore }
1164
+ ];
1165
+ const [index, setIndex] = useState6(0);
1123
1166
  useInput4((input, key) => {
1124
- if (key.return) confirmStart();
1125
- else if (input === "i") openLearnMore();
1167
+ if (key.upArrow || input === "k") {
1168
+ setIndex((i) => (i - 1 + actions.length) % actions.length);
1169
+ } else if (key.downArrow || input === "j") {
1170
+ setIndex((i) => (i + 1) % actions.length);
1171
+ } else if (key.return) {
1172
+ actions[index].run();
1173
+ }
1126
1174
  });
1127
1175
  const scales = {
1128
1176
  large: {
@@ -1138,16 +1186,16 @@ function Welcome() {
1138
1186
  if (rows < 30) {
1139
1187
  layout = scales["small"];
1140
1188
  }
1141
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1142
- /* @__PURE__ */ jsx7(
1143
- Box9,
1189
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1190
+ /* @__PURE__ */ jsx8(
1191
+ Box10,
1144
1192
  {
1145
1193
  paddingY: layout.main.padding.y,
1146
1194
  paddingX: layout.main.padding.x,
1147
1195
  flexDirection: "column",
1148
1196
  justifyContent: "center",
1149
- children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1150
- /* @__PURE__ */ jsx7(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx7(
1197
+ children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 2, children: [
1198
+ /* @__PURE__ */ jsx8(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx8(
1151
1199
  Image,
1152
1200
  {
1153
1201
  src: IMAGE_PATH,
@@ -1158,16 +1206,31 @@ function Welcome() {
1158
1206
  protocol: "halfBlock"
1159
1207
  }
1160
1208
  ) }),
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" })
1209
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1210
+ /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: actions.map((action, i) => /* @__PURE__ */ jsx8(
1211
+ SelectRow,
1212
+ {
1213
+ highlighted: i === index,
1214
+ highlightBackground: false,
1215
+ label: action.label
1216
+ },
1217
+ action.label
1218
+ )) }),
1219
+ /* @__PURE__ */ jsxs9(Box10, { gap: 2, children: [
1220
+ /* @__PURE__ */ jsxs9(Text10, { children: [
1221
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[\u2191] [\u2193]" }),
1222
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " move" })
1223
+ ] }),
1224
+ /* @__PURE__ */ jsxs9(Text10, { children: [
1225
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[enter]" }),
1226
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " confirm" })
1227
+ ] })
1165
1228
  ] })
1166
1229
  ] })
1167
1230
  }
1168
1231
  ),
1169
- /* @__PURE__ */ jsxs8(
1170
- Box9,
1232
+ /* @__PURE__ */ jsxs9(
1233
+ Box10,
1171
1234
  {
1172
1235
  backgroundColor: COLORS.bg.sidebar,
1173
1236
  width: 40,
@@ -1177,8 +1240,8 @@ function Welcome() {
1177
1240
  flexDirection: "column",
1178
1241
  justifyContent: "center",
1179
1242
  children: [
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))
1243
+ /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1244
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx8(SidebarItem, { title: i.title, description: i.description }, idx))
1182
1245
  ]
1183
1246
  }
1184
1247
  )
@@ -1187,7 +1250,7 @@ function Welcome() {
1187
1250
 
1188
1251
  // src/ui/LearnMore.tsx
1189
1252
  import { Fragment as Fragment2 } from "react";
1190
- import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1253
+ import { Box as Box11, Text as Text11, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1191
1254
 
1192
1255
  // src/ui/copy/learn-more.ts
1193
1256
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1200,7 +1263,7 @@ var accessItems = [
1200
1263
  {
1201
1264
  tag: "WRITE",
1202
1265
  title: "Code changes",
1203
- description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1266
+ description: "creates & edits files (search UI, config) directly in your branch."
1204
1267
  },
1205
1268
  {
1206
1269
  tag: "EXEC",
@@ -1215,7 +1278,7 @@ var accessItems = [
1215
1278
  {
1216
1279
  tag: "KEY",
1217
1280
  title: "Credentials",
1218
- description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1281
+ description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
1219
1282
  }
1220
1283
  ];
1221
1284
  var neverItems = [
@@ -1229,7 +1292,7 @@ var policyLinks = [
1229
1292
  ];
1230
1293
 
1231
1294
  // src/ui/LearnMore.tsx
1232
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1295
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1233
1296
  var TAG_COLORS = {
1234
1297
  READ: COLORS.success,
1235
1298
  WRITE: COLORS.badge,
@@ -1246,12 +1309,12 @@ function NeverLine({
1246
1309
  }) {
1247
1310
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1248
1311
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1249
- return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.primary, children: [
1250
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1312
+ return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
1313
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" }),
1251
1314
  " ".repeat(NEVER_BOX_PAD_X),
1252
- segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1315
+ segments.map((s, i) => /* @__PURE__ */ jsx9(Text11, { color: s.color, bold: s.bold, children: s.text }, i)),
1253
1316
  " ".repeat(rightPad),
1254
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1317
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" })
1255
1318
  ] });
1256
1319
  }
1257
1320
  function LearnMore() {
@@ -1263,8 +1326,8 @@ function LearnMore() {
1263
1326
  if (key.escape) backToHome();
1264
1327
  else if (key.return) confirmStart();
1265
1328
  });
1266
- return /* @__PURE__ */ jsxs9(
1267
- Box10,
1329
+ return /* @__PURE__ */ jsxs10(
1330
+ Box11,
1268
1331
  {
1269
1332
  flexDirection: "column",
1270
1333
  paddingX: PADDING_X,
@@ -1272,31 +1335,31 @@ function LearnMore() {
1272
1335
  width: "100%",
1273
1336
  gap: 1,
1274
1337
  children: [
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, { color: COLORS.primary, children: [
1282
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1283
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1338
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1339
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: accessIntro }),
1340
+ /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
1341
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1342
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1343
+ /* @__PURE__ */ jsx9(Box11, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text11, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1344
+ /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
1345
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: item.title }),
1346
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1284
1347
  ] }) })
1285
1348
  ] })
1286
1349
  ] }, item.tag)) }),
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(
1350
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
1351
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1352
+ /* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
1353
+ /* @__PURE__ */ jsx9(
1291
1354
  NeverLine,
1292
1355
  {
1293
1356
  width: dividerWidth,
1294
1357
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1295
1358
  }
1296
1359
  ),
1297
- neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1298
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1299
- /* @__PURE__ */ jsx8(
1360
+ neverItems.map((item) => /* @__PURE__ */ jsxs10(Fragment2, { children: [
1361
+ /* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
1362
+ /* @__PURE__ */ jsx9(
1300
1363
  NeverLine,
1301
1364
  {
1302
1365
  width: dividerWidth,
@@ -1308,24 +1371,24 @@ function LearnMore() {
1308
1371
  }
1309
1372
  )
1310
1373
  ] }, item)),
1311
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1312
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1374
+ /* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
1375
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1313
1376
  ] }),
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 })
1377
+ /* @__PURE__ */ jsx9(Box11, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1378
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1379
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.accent, children: link.url })
1317
1380
  ] }, link.label)) }),
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" })
1381
+ /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1382
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1383
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
1384
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "esc" }),
1385
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "] back" })
1323
1386
  ] }),
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" })
1387
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1388
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
1389
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "enter" }),
1390
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "]" }),
1391
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.success, bold: true, children: "start wizard" })
1329
1392
  ] })
1330
1393
  ] })
1331
1394
  ]
@@ -1334,10 +1397,10 @@ function LearnMore() {
1334
1397
  }
1335
1398
 
1336
1399
  // src/ui/Sidebar.tsx
1337
- import { Box as Box13, Text as Text13 } from "ink";
1400
+ import { Box as Box14, Text as Text14 } from "ink";
1338
1401
 
1339
1402
  // src/ui/Steps.tsx
1340
- import { Box as Box11, Text as Text11 } from "ink";
1403
+ import { Box as Box12, Text as Text12 } from "ink";
1341
1404
  import Spinner from "ink-spinner";
1342
1405
 
1343
1406
  // src/core/persistence.ts
@@ -1366,12 +1429,12 @@ async function clearWorkflowState(workflowId) {
1366
1429
  }
1367
1430
 
1368
1431
  // src/ui/Steps.tsx
1369
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1432
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1370
1433
  function Steps() {
1371
1434
  const { steps } = useWizard();
1372
1435
  const visibleSteps = steps.filter(isStepVisible);
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],
1436
+ return /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status[s.status], children: [
1437
+ s.status === "running" ? /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) : MARKER[s.status],
1375
1438
  " ",
1376
1439
  s.title
1377
1440
  ] }) }, s.id)) });
@@ -1380,27 +1443,27 @@ function CurrentStep() {
1380
1443
  const { steps } = useWizard();
1381
1444
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1382
1445
  if (!currentStep) return null;
1383
- return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1384
- /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1446
+ return /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status.running, children: [
1447
+ /* @__PURE__ */ jsx10(Spinner, { type: "dots" }),
1385
1448
  " ",
1386
1449
  ` ${currentStep.title}`
1387
1450
  ] });
1388
1451
  }
1389
1452
 
1390
1453
  // src/ui/Progress.tsx
1391
- import { Box as Box12, Text as Text12 } from "ink";
1392
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1454
+ import { Box as Box13, Text as Text13 } from "ink";
1455
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1393
1456
  function Progress() {
1394
1457
  const { steps, currentStepIndex } = useWizard();
1395
1458
  const visibleSteps = steps.filter(isStepVisible);
1396
1459
  if (visibleSteps.length === 0) return null;
1397
1460
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1398
1461
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1399
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1400
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1401
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: activeStepNumber }),
1402
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: "/" }),
1403
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.strong, bold: true, children: visibleSteps.length })
1462
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1463
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "STEP" }),
1464
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: activeStepNumber }),
1465
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: "/" }),
1466
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: visibleSteps.length })
1404
1467
  ] });
1405
1468
  }
1406
1469
 
@@ -1411,10 +1474,10 @@ var sidebarCommands = [
1411
1474
  ];
1412
1475
 
1413
1476
  // src/ui/Sidebar.tsx
1414
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1477
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1415
1478
  function Sidebar() {
1416
- return /* @__PURE__ */ jsxs12(
1417
- Box13,
1479
+ return /* @__PURE__ */ jsxs13(
1480
+ Box14,
1418
1481
  {
1419
1482
  backgroundColor: "#14171E",
1420
1483
  width: 30,
@@ -1423,16 +1486,16 @@ function Sidebar() {
1423
1486
  flexDirection: "column",
1424
1487
  justifyContent: "space-between",
1425
1488
  children: [
1426
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1427
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1428
- /* @__PURE__ */ jsx11(Steps, {})
1489
+ /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
1490
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: "PROGRESS" }),
1491
+ /* @__PURE__ */ jsx12(Steps, {})
1429
1492
  ] }),
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 })
1493
+ /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
1494
+ /* @__PURE__ */ jsx12(Progress, {}),
1495
+ /* @__PURE__ */ jsx12(Box14, { flexDirection: "column", children: sidebarCommands.map((c) => {
1496
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1497
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1498
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: c.description })
1436
1499
  ] });
1437
1500
  }) })
1438
1501
  ] })
@@ -1442,12 +1505,12 @@ function Sidebar() {
1442
1505
  }
1443
1506
 
1444
1507
  // src/ui/Ribbon.tsx
1445
- import { Box as Box14, Text as Text14 } from "ink";
1446
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1508
+ import { Box as Box15, Text as Text15 } from "ink";
1509
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1447
1510
  function Ribbon() {
1448
1511
  const firstCommand = sidebarCommands[0];
1449
- return /* @__PURE__ */ jsxs13(
1450
- Box14,
1512
+ return /* @__PURE__ */ jsxs14(
1513
+ Box15,
1451
1514
  {
1452
1515
  backgroundColor: "#14171E",
1453
1516
  flexDirection: "row",
@@ -1455,11 +1518,11 @@ function Ribbon() {
1455
1518
  paddingX: 2,
1456
1519
  paddingY: 1,
1457
1520
  children: [
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 })
1521
+ /* @__PURE__ */ jsx13(Progress, {}),
1522
+ /* @__PURE__ */ jsx13(CurrentStep, {}),
1523
+ /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: 1, children: [
1524
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1525
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: firstCommand.description })
1463
1526
  ] })
1464
1527
  ]
1465
1528
  }
@@ -1467,11 +1530,11 @@ function Ribbon() {
1467
1530
  }
1468
1531
 
1469
1532
  // src/ui/App.tsx
1470
- import { useState as useState6 } from "react";
1533
+ import { useState as useState7 } from "react";
1471
1534
 
1472
1535
  // src/ui/Logs.tsx
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";
1536
+ import { Box as Box16, Text as Text16, useInput as useInput6 } from "ink";
1537
+ import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1475
1538
  var KIND_COLOR = {
1476
1539
  tool: COLORS.primary,
1477
1540
  prompt: COLORS.badge
@@ -1507,9 +1570,9 @@ function Logs() {
1507
1570
  else if (key.downArrow) scroll.scrollBy(1);
1508
1571
  });
1509
1572
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
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) => {
1573
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1574
+ logs.length === 0 && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "No logs yet." }),
1575
+ /* @__PURE__ */ jsx14(ScrollView, { scroll, children: visible.map((entry) => {
1513
1576
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1514
1577
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1515
1578
  const rawPreview = rawInputText(entry.input);
@@ -1519,14 +1582,14 @@ function Logs() {
1519
1582
  const name = truncate2(entry.name, budget);
1520
1583
  budget -= name.length;
1521
1584
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
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 })
1585
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "row", gap: ROW_GAP, children: [
1586
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: timestamp }),
1587
+ /* @__PURE__ */ jsx14(Text16, { color: logNameColor(entry), wrap: "truncate", children: name }),
1588
+ preview && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, wrap: "truncate", children: preview }),
1589
+ durationText && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: durationText })
1527
1590
  ] }, entry.id);
1528
1591
  }) }),
1529
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1592
+ /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1530
1593
  ] });
1531
1594
  }
1532
1595
 
@@ -1718,12 +1781,12 @@ function track(event, payload) {
1718
1781
  }
1719
1782
 
1720
1783
  // src/ui/App.tsx
1721
- import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1784
+ import { jsx as jsx15, jsxs as jsxs16 } from "react/jsx-runtime";
1722
1785
  function App() {
1723
1786
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1724
1787
  const { exit } = useApp();
1725
1788
  const { columns, rows } = useWindowSize8();
1726
- const [showLogs, setShowLogs] = useState6(false);
1789
+ const [showLogs, setShowLogs] = useState7(false);
1727
1790
  const finished = phase === "done" || phase === "error";
1728
1791
  const currentStep = steps[currentStepIndex];
1729
1792
  useInput7(
@@ -1765,8 +1828,8 @@ function App() {
1765
1828
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1766
1829
  the whole screen, and the scrolling throws off its cursor arithmetic —
1767
1830
  flicker and leftover rows. */
1768
- /* @__PURE__ */ jsxs15(
1769
- Box16,
1831
+ /* @__PURE__ */ jsxs16(
1832
+ Box17,
1770
1833
  {
1771
1834
  backgroundColor: COLORS.bg.main,
1772
1835
  flexDirection: "row",
@@ -1774,16 +1837,16 @@ function App() {
1774
1837
  height: scrollsPastViewport ? void 0 : rows,
1775
1838
  overflow: scrollsPastViewport ? "visible" : "hidden",
1776
1839
  children: [
1777
- mainWindowVisible && /* @__PURE__ */ jsxs15(
1778
- Box16,
1840
+ mainWindowVisible && /* @__PURE__ */ jsxs16(
1841
+ Box17,
1779
1842
  {
1780
1843
  flexDirection,
1781
1844
  width: "100%",
1782
1845
  maxHeight: rows,
1783
1846
  justifyContent: "space-between",
1784
1847
  children: [
1785
- showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1786
- Box16,
1848
+ showLogs ? /* @__PURE__ */ jsx15(Logs, {}) : /* @__PURE__ */ jsxs16(
1849
+ Box17,
1787
1850
  {
1788
1851
  flexDirection: "column",
1789
1852
  paddingX: 4,
@@ -1791,25 +1854,25 @@ function App() {
1791
1854
  width: showSidebar ? 70 : "100%",
1792
1855
  flexGrow: 1,
1793
1856
  children: [
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." })
1857
+ phase === "authenticating" && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
1858
+ /* @__PURE__ */ jsx15(Text17, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1859
+ /* @__PURE__ */ jsx15(Text17, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1797
1860
  ] }),
1798
- /* @__PURE__ */ jsx14(CliOutput, {}),
1799
- /* @__PURE__ */ jsx14(Notices, {}),
1800
- /* @__PURE__ */ jsx14(PromptInput, {}),
1801
- phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1861
+ /* @__PURE__ */ jsx15(CliOutput, {}),
1862
+ /* @__PURE__ */ jsx15(Notices, {}),
1863
+ /* @__PURE__ */ jsx15(PromptInput, {}),
1864
+ phase === "error" && error && /* @__PURE__ */ jsx15(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: COLORS.status.error, children: [
1802
1865
  "\u2716 ",
1803
1866
  error
1804
1867
  ] }) })
1805
1868
  ]
1806
1869
  }
1807
1870
  ),
1808
- showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1871
+ showSidebar ? /* @__PURE__ */ jsx15(Sidebar, {}) : /* @__PURE__ */ jsx15(Ribbon, {})
1809
1872
  ]
1810
1873
  }
1811
1874
  ),
1812
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1875
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx15(LearnMore, {}) : /* @__PURE__ */ jsx15(Welcome, {}))
1813
1876
  ]
1814
1877
  }
1815
1878
  )
@@ -2495,6 +2558,7 @@ function writeFileTool(ctx) {
2495
2558
  }
2496
2559
  await mkdir3(dirname4(resolved2.target), { recursive: true });
2497
2560
  await writeFile3(resolved2.target, content, "utf8");
2561
+ useWizard.getState().recordWrittenFile(resolved2.target);
2498
2562
  return `Wrote to ${filePath}`;
2499
2563
  } catch (err) {
2500
2564
  return `Error writing ${filePath}: ${err.message}`;
@@ -3275,7 +3339,7 @@ function defaultCreateModel() {
3275
3339
  }
3276
3340
  function generateRecordTool(ctx, createModel = defaultCreateModel) {
3277
3341
  return tool10({
3278
- description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
3342
+ description: "Generate realistic sample records for an entity and write them to a JSON file. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
3279
3343
  inputSchema: z17.object({
3280
3344
  entityName: z17.string().describe("Name of the entity to generate records for."),
3281
3345
  attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
@@ -3641,7 +3705,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3641
3705
  // package.json
3642
3706
  var package_default = {
3643
3707
  name: "@algolia/wizard",
3644
- version: "0.22.0",
3708
+ version: "0.24.0-rc.111.205",
3645
3709
  description: "Magically implement Algolia functionality in your codebase",
3646
3710
  type: "module",
3647
3711
  engines: {
@@ -4008,11 +4072,10 @@ ${JSON.stringify(s.output, null, 2)}`
4008
4072
  function formatReviewSummary(result) {
4009
4073
  const nextStepLines = result.nextSteps.map((step) => {
4010
4074
  const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
4011
- const isWorktreeCommand = step.includes("/worktrees/");
4012
4075
  return {
4013
4076
  text: `\u2192 ${step}`,
4014
- color: isIngestCommand ? COLORS.brand : isWorktreeCommand ? COLORS.secondary : void 0,
4015
- bold: isIngestCommand || isWorktreeCommand
4077
+ color: isIngestCommand ? COLORS.brand : void 0,
4078
+ bold: isIngestCommand
4016
4079
  };
4017
4080
  });
4018
4081
  return [
@@ -4047,15 +4110,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
4047
4110
 
4048
4111
  // src/actions/implement.ts
4049
4112
  import z29 from "zod";
4050
- import { join as join12 } from "node:path";
4113
+ import { join as join12, relative as relative6 } from "node:path";
4051
4114
 
4052
- // src/lib/worktree.ts
4115
+ // src/lib/git.ts
4053
4116
  import { execFile as execFile2 } from "node:child_process";
4054
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
4117
+ import { copyFile, mkdir as mkdir6, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
4055
4118
  import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
4056
4119
  var MAX_BUFFER = 32 * 1024 * 1024;
4057
- var MAX_WIZARD_WORKTREES = 3;
4058
- var WIZARD_BRANCH_PREFIX = "wizard/implement-";
4059
4120
  function git(args) {
4060
4121
  return new Promise((resolve4, reject) => {
4061
4122
  execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
@@ -4078,44 +4139,7 @@ async function assertGitRepoWithHead(repoRoot) {
4078
4139
  );
4079
4140
  }
4080
4141
  }
4081
- async function isWorkingTreeDirty(repoRoot) {
4082
- const out = await git(["-C", repoRoot, "status", "--porcelain"]);
4083
- return out.trim().length > 0;
4084
- }
4085
- async function pruneOldWorktrees(repoRoot) {
4086
- const dir = join10(stateDir(repoRoot), "worktrees");
4087
- const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
4088
- for (const slug of stale) {
4089
- const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
4090
- try {
4091
- await git([
4092
- "-C",
4093
- repoRoot,
4094
- "worktree",
4095
- "remove",
4096
- "--force",
4097
- join10(dir, slug)
4098
- ]);
4099
- await git(["-C", repoRoot, "branch", "-D", branch]);
4100
- } catch (err) {
4101
- logger.warn(
4102
- { branch, err: err.message },
4103
- "createWorktree: failed to prune a stale wizard worktree; continuing"
4104
- );
4105
- }
4106
- }
4107
- }
4108
- async function createWorktree(repoRoot) {
4109
- const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
4110
- const dirSlug = branch.replace(/\//g, "-");
4111
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
4112
- await git(["-C", repoRoot, "worktree", "prune"]);
4113
- await pruneOldWorktrees(repoRoot);
4114
- await mkdir6(dirname7(path), { recursive: true });
4115
- await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
4116
- return { path, branch };
4117
- }
4118
- async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
4142
+ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
4119
4143
  const trimmed = sourcePath.trim();
4120
4144
  if (!trimmed) {
4121
4145
  return { ok: false, reason: "no file path was provided" };
@@ -4129,7 +4153,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
4129
4153
  return { ok: false, reason: `"${sourcePath}" does not exist` };
4130
4154
  }
4131
4155
  const relPath = join10(ingestDir, basename2(source));
4132
- const dest = join10(worktreePath, relPath);
4156
+ const dest = join10(repoRoot, relPath);
4157
+ if (resolve3(source) === resolve3(dest)) {
4158
+ return { ok: true, relPath };
4159
+ }
4133
4160
  try {
4134
4161
  await mkdir6(dirname7(dest), { recursive: true });
4135
4162
  await copyFile(source, dest);
@@ -4144,10 +4171,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
4144
4171
  function hasEnvVar(content, name) {
4145
4172
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
4146
4173
  }
4147
- async function readEnvVar(worktreePath, name) {
4174
+ async function readEnvVar(repoRoot, name) {
4148
4175
  let content;
4149
4176
  try {
4150
- content = await readFile8(join10(worktreePath, ".env"), "utf8");
4177
+ content = await readFile8(join10(repoRoot, ".env"), "utf8");
4151
4178
  } catch (err) {
4152
4179
  if (err.code !== "ENOENT") throw err;
4153
4180
  return void 0;
@@ -4161,8 +4188,8 @@ async function readEnvVar(worktreePath, name) {
4161
4188
  if (!value || value.startsWith("<")) return void 0;
4162
4189
  return value;
4163
4190
  }
4164
- async function writeSearchEnvValues(worktreePath, vars) {
4165
- const target = join10(worktreePath, ".env");
4191
+ async function writeSearchEnvValues(repoRoot, vars) {
4192
+ const target = join10(repoRoot, ".env");
4166
4193
  let existing = "";
4167
4194
  try {
4168
4195
  existing = await readFile8(target, "utf8");
@@ -4177,61 +4204,27 @@ async function writeSearchEnvValues(worktreePath, vars) {
4177
4204
  await writeFile7(target, existing + prefix + lines, "utf8");
4178
4205
  return missing.map((v) => v.name);
4179
4206
  }
4180
- async function listChangedFiles(worktreePath) {
4181
- const raw = await git(["-C", worktreePath, "status", "--porcelain", "-z"]);
4182
- const entries = raw.split("\0");
4183
- const files = [];
4184
- for (let i = 0; i < entries.length; i += 1) {
4185
- const entry = entries[i];
4186
- if (!entry) continue;
4187
- files.push(entry.slice(3));
4188
- if (["R", "C"].includes(entry[0]) || ["R", "C"].includes(entry[1])) i += 1;
4189
- }
4190
- return files;
4191
- }
4192
4207
  function normalizeFindingPaths(findings) {
4193
4208
  return {
4194
4209
  ...findings,
4195
4210
  ingestionAnalysis: findings.ingestionAnalysis?.map((e) => ({
4196
4211
  ...e,
4197
- paths: e.paths.map(toWorktreeRelative)
4212
+ paths: e.paths.map(toRootRelative)
4198
4213
  })),
4199
4214
  searchImplementationAnalysis: findings.searchImplementationAnalysis ? normalizeSearchLocation(findings.searchImplementationAnalysis) : void 0,
4200
4215
  confirmedEntities: findings.confirmedEntities?.map((e) => ({
4201
4216
  ...e,
4202
- paths: e.paths.map(toWorktreeRelative)
4217
+ paths: e.paths.map(toRootRelative)
4203
4218
  }))
4204
4219
  };
4205
4220
  }
4206
4221
  function normalizeSearchLocation(path) {
4207
- const normalized = path ? toWorktreeRelative(path).trim() : "";
4222
+ const normalized = path ? toRootRelative(path).trim() : "";
4208
4223
  return normalized && normalized.toLowerCase() !== "unknown" ? normalized : void 0;
4209
4224
  }
4210
- function toWorktreeRelative(p) {
4225
+ function toRootRelative(p) {
4211
4226
  return p.replace(/^\/+/, "");
4212
4227
  }
4213
- async function confirmDirtyWorkingTree(ctx, repoRoot) {
4214
- const MAX_LISTED_DIRTY_FILES = 10;
4215
- const dirty = await listChangedFiles(repoRoot);
4216
- const shown = dirty.slice(0, MAX_LISTED_DIRTY_FILES);
4217
- const overflow = dirty.length - shown.length;
4218
- const answer = await ctx.requestUserInput({
4219
- prompt: "Proceed using HEAD only? Uncommitted changes will NOT be included in the generated implementation.",
4220
- promptType: "acceptReject",
4221
- options: [],
4222
- messages: [
4223
- `${dirty.length} uncommitted change(s) detected. The wizard builds an isolated worktree from HEAD, so these are ignored:`,
4224
- ...shown.map((file) => ` \u2022 ${file}`),
4225
- ...overflow > 0 ? [` \u2022 \u2026and ${overflow} more`] : [],
4226
- "Commit or stash them first to include them in the implementation."
4227
- ]
4228
- });
4229
- if (answer !== true) {
4230
- throw new Error(
4231
- "implement aborted: commit or stash your changes so they are built into the worktree, then re-run the wizard."
4232
- );
4233
- }
4234
- }
4235
4228
 
4236
4229
  // src/lib/algoliaDocs.ts
4237
4230
  import { readFileSync, readdirSync, existsSync } from "node:fs";
@@ -4291,11 +4284,6 @@ function getFrameworkSpecificDoc(frameworks) {
4291
4284
  return loadAlgoliaDoc("js");
4292
4285
  }
4293
4286
 
4294
- // src/lib/shell.ts
4295
- function shellQuote(value) {
4296
- return "'" + value.replace(/'/g, "'\\''") + "'";
4297
- }
4298
-
4299
4287
  // src/actions/resolveEnvVarPrefix.ts
4300
4288
  import z28 from "zod";
4301
4289
  var resolveEnvVarPrefixSchema = z28.object({
@@ -4315,9 +4303,7 @@ var resolveEnvVarPrefix = (frameworkName) => runAgent({
4315
4303
 
4316
4304
  // src/actions/implement.ts
4317
4305
  var implementSchema = z29.object({
4318
- filesChanged: z29.array(z29.string()),
4319
4306
  summary: z29.string(),
4320
- worktreePath: z29.string().optional(),
4321
4307
  ingestCommand: z29.string().optional(),
4322
4308
  ingestScriptRan: z29.boolean().optional(),
4323
4309
  ingestRecordCount: z29.number().optional(),
@@ -4370,8 +4356,6 @@ function frameworksForDoc(language) {
4370
4356
  }
4371
4357
  function baseInstructions(input) {
4372
4358
  return [
4373
- // Agents have renamed this (e.g. appending the project name), which the
4374
- // index-scoped keys then reject with a 403.
4375
4359
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4376
4360
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4377
4361
  "Make minimal, idiomatic changes; do not touch unrelated code.",
@@ -4389,14 +4373,14 @@ function sourceSpecificInstructions(input) {
4389
4373
  "Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
4390
4374
  ],
4391
4375
  fileUpload: [
4392
- `Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
4376
+ `Records come from the developer's file, already copied into the project at "${input.uploadFilePath}". Read and parse that exact file.`,
4393
4377
  "Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
4394
4378
  "Map parsed columns/fields to the confirmed entity attributes.",
4395
4379
  "Never fabricate, hardcode, or substitute a different file."
4396
4380
  ],
4397
4381
  generated: [
4398
4382
  "No real data source exists; use sample records for each confirmed entity.",
4399
- "Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
4383
+ "Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file, returning the file path. Do not write records or objectIDs yourself.",
4400
4384
  "In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
4401
4385
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4402
4386
  ]
@@ -4445,14 +4429,11 @@ function searchInstructions(input) {
4445
4429
  "If a search box already exists, replace it with yours.",
4446
4430
  `Read the index name from the ${publicIndexNameVar(input.publicEnvVarPrefix)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
4447
4431
  "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.",
4448
- // The key is provisioned only after verification passes, so the agent never
4449
- // sees one. It must also leave .env alone: the wizard reads that file to
4450
- // decide whether a key already exists, and an agent-invented value there
4451
- // would be reused as if it were real.
4432
+ // The key is provisioned only after verification passes, and the wizard
4433
+ // reads .env to decide whether a key already exists an agent-invented
4434
+ // value there would be reused as if it were real.
4452
4435
  `Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
4453
- // Not the agent's to rename: the wizard writes these exact names into
4454
- // ".env" right after this step, so a renamed prefix would leave the code
4455
- // reading a var the wizard never wrote.
4436
+ // The wizard writes these exact names into .env right after this step.
4456
4437
  `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4457
4438
  "Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
4458
4439
  "Match the styles of the application as closely as possible.",
@@ -4461,10 +4442,10 @@ function searchInstructions(input) {
4461
4442
  }
4462
4443
  function verificationInstructions(input) {
4463
4444
  return [
4464
- "Verify the Algolia implementation changes in the current worktree.",
4445
+ "Verify the Algolia implementation changes.",
4465
4446
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4466
4447
  "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.",
4467
- "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.",
4448
+ "If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
4468
4449
  "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4469
4450
  "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.",
4470
4451
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
@@ -4559,11 +4540,11 @@ function ingestFailure(attempt, executions) {
4559
4540
  }
4560
4541
  return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
4561
4542
  }
4562
- function makeToolContext(worktree, env = async () => ({})) {
4543
+ function makeToolContext(root, env = async () => ({})) {
4563
4544
  return createToolContext(
4564
4545
  DEFAULT_TOOL_LIMITS,
4565
- worktree,
4566
- createShellContext({ env, approve: storeApproval(worktree) })
4546
+ root,
4547
+ createShellContext({ env, approve: storeApproval(root) })
4567
4548
  );
4568
4549
  }
4569
4550
  function verificationRetryInstructions(verification) {
@@ -4571,7 +4552,7 @@ function verificationRetryInstructions(verification) {
4571
4552
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
4572
4553
  ];
4573
4554
  }
4574
- async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWorktreePath) {
4555
+ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4575
4556
  const repoRoot = process.cwd();
4576
4557
  const scan = ctx.getStepOutput("project-scan");
4577
4558
  const entities = ctx.getStepOutput(
@@ -4652,9 +4633,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4652
4633
  const targetIndex = selected?.selection;
4653
4634
  useWizard.getState().setTargetIndex(targetIndex ?? null);
4654
4635
  await assertGitRepoWithHead(repoRoot);
4655
- if (await isWorkingTreeDirty(repoRoot)) {
4656
- await confirmDirtyWorkingTree(ctx, repoRoot);
4657
- }
4658
4636
  const normalized = normalizeFindingPaths(findings);
4659
4637
  const confirmed2 = normalized.confirmedEntities;
4660
4638
  const searchLocation = normalized.searchImplementationAnalysis;
@@ -4666,328 +4644,303 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4666
4644
  if (useCases.includes("ingestion")) {
4667
4645
  ingestAppId = appId ?? (await requireApplication()).id;
4668
4646
  }
4669
- const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4670
- try {
4671
- process.chdir(worktree);
4672
- let uploadFilePath;
4673
- let uploadWarning;
4674
- if (ingestionSource === "fileUpload") {
4675
- const copied = await copyUploadIntoWorktree(
4647
+ let uploadFilePath;
4648
+ let uploadWarning;
4649
+ if (ingestionSource === "fileUpload") {
4650
+ const copied = await copyUploadIntoProject(
4651
+ repoRoot,
4652
+ INGEST_DIR,
4653
+ uploadSourcePath ?? ""
4654
+ );
4655
+ if (copied.ok) {
4656
+ uploadFilePath = copied.relPath;
4657
+ } else {
4658
+ ingestionSource = "generated";
4659
+ uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
4660
+ logger.warn(
4661
+ { reason: copied.reason },
4662
+ "implement: file upload unavailable; falling back to generated sample records"
4663
+ );
4664
+ }
4665
+ }
4666
+ const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
4667
+ const input = {
4668
+ findings: normalized,
4669
+ confirmed: confirmed2,
4670
+ searchLocation,
4671
+ targetIndex,
4672
+ language,
4673
+ publicEnvVarPrefix,
4674
+ appId,
4675
+ searchEnvVars: publicSearchEnvVars(publicEnvVarPrefix, targetIndex, appId),
4676
+ ingestDir: INGEST_DIR,
4677
+ ingestionSource,
4678
+ uploadFilePath,
4679
+ searchUiTarget: searchUiTarget(language)
4680
+ };
4681
+ const summaries = [];
4682
+ if (uploadWarning) summaries.push(uploadWarning);
4683
+ let envSearchKey;
4684
+ let envAppIdMismatch = false;
4685
+ if (useCases.includes("search") && appId) {
4686
+ const envAppId = await readEnvVar(
4687
+ repoRoot,
4688
+ publicAppIdVar(publicEnvVarPrefix)
4689
+ );
4690
+ if (envAppId === appId) {
4691
+ envSearchKey = await readEnvVar(
4676
4692
  repoRoot,
4677
- worktree,
4678
- INGEST_DIR,
4679
- uploadSourcePath ?? ""
4693
+ publicSearchKeyVar(publicEnvVarPrefix)
4694
+ );
4695
+ } else if (envAppId) {
4696
+ envAppIdMismatch = true;
4697
+ const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
4698
+ const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
4699
+ summaries.push(
4700
+ `\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
4701
+ );
4702
+ logger.warn(
4703
+ { envAppId, appId },
4704
+ "implement: .env holds credentials for a different Algolia application; not reusing its search key"
4680
4705
  );
4681
- if (copied.ok) {
4682
- uploadFilePath = copied.relPath;
4683
- } else {
4684
- ingestionSource = "generated";
4685
- uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
4686
- logger.warn(
4687
- { reason: copied.reason },
4688
- "implement: file upload unavailable; falling back to generated sample records"
4689
- );
4690
- }
4691
4706
  }
4692
- const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
4693
- const input = {
4694
- findings: normalized,
4695
- confirmed: confirmed2,
4696
- searchLocation,
4697
- targetIndex,
4698
- language,
4699
- publicEnvVarPrefix,
4700
- appId,
4701
- searchEnvVars: publicSearchEnvVars(
4702
- publicEnvVarPrefix,
4703
- targetIndex,
4704
- appId
4707
+ }
4708
+ let finalSearchEnvVars = input.searchEnvVars;
4709
+ let agentRuns = 0;
4710
+ let ingestCommand;
4711
+ let ingestScriptRan = false;
4712
+ let ingestRecordCount;
4713
+ let ingestDurationMs;
4714
+ let ingestOutcomeMessage;
4715
+ const ingestKeyAppId = ingestAppId;
4716
+ const ingestionTools = ingestKeyAppId ? makeToolContext(repoRoot, async () => ({
4717
+ [APP_ID_VAR]: ingestKeyAppId,
4718
+ [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4719
+ [INDEX_NAME_VAR]: targetIndex
4720
+ })) : void 0;
4721
+ const searchTools = makeToolContext(repoRoot);
4722
+ async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4723
+ if (agentRuns > 0) ctx.recordStepExecution();
4724
+ agentRuns += 1;
4725
+ return runAgent({
4726
+ instructions: buildAgentInstructions(
4727
+ currentUseCase,
4728
+ input,
4729
+ extraInstructions
4705
4730
  ),
4706
- ingestDir: INGEST_DIR,
4707
- ingestionSource,
4708
- uploadFilePath,
4709
- searchUiTarget: searchUiTarget(language)
4710
- };
4711
- const summaries = [];
4712
- if (uploadWarning) summaries.push(uploadWarning);
4713
- let envSearchKey;
4714
- let envAppIdMismatch = false;
4715
- if (useCases.includes("search") && appId) {
4716
- const envAppId = await readEnvVar(
4717
- worktree,
4718
- publicAppIdVar(publicEnvVarPrefix)
4731
+ tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4732
+ outputSchema: implementationOutputSchema,
4733
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4734
+ });
4735
+ }
4736
+ async function runVerificationUseCase() {
4737
+ if (agentRuns > 0) ctx.recordStepExecution();
4738
+ agentRuns += 1;
4739
+ return runAgent({
4740
+ instructions: buildAgentInstructions("verification", input),
4741
+ tools: toolsForUseCase("verification"),
4742
+ outputSchema: verificationOutputSchema,
4743
+ toolContext: searchTools
4744
+ });
4745
+ }
4746
+ if (useCases.includes("ingestion")) {
4747
+ let ingestFailureDetail;
4748
+ const result = await runImplementationUseCase("ingestion");
4749
+ summaries.push(formatSummary("ingestion", result.summary));
4750
+ ingestCommand = result.ingestCommand;
4751
+ const ingestionContext = ingestionTools ?? searchTools;
4752
+ const executions = ingestionContext.shell.executions;
4753
+ const {
4754
+ run: ingestRun,
4755
+ attempt: ingestAttempt,
4756
+ recordCount
4757
+ } = ingestOutcome(executions, ingestCommand);
4758
+ ingestScriptRan = ingestRun != null;
4759
+ ingestRecordCount = recordCount;
4760
+ ingestDurationMs = ingestRun?.durationMs;
4761
+ if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
4762
+ summaries.push(
4763
+ "\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in your project before trusting the index contents."
4764
+ );
4765
+ logger.warn(
4766
+ { ingestCommand },
4767
+ "implement: ingestion ran without a reviewScript call"
4719
4768
  );
4720
- if (envAppId === appId) {
4721
- envSearchKey = await readEnvVar(
4722
- worktree,
4723
- publicSearchKeyVar(publicEnvVarPrefix)
4724
- );
4725
- } else if (envAppId) {
4726
- envAppIdMismatch = true;
4727
- const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
4728
- const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
4729
- summaries.push(
4730
- `\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
4731
- );
4732
- logger.warn(
4733
- { envAppId, appId },
4734
- "implement: .env holds credentials for a different Algolia application; not reusing its search key"
4735
- );
4736
- }
4737
- }
4738
- let finalSearchEnvVars = input.searchEnvVars;
4739
- let agentRuns = 0;
4740
- let ingestCommand;
4741
- let ingestScriptRan = false;
4742
- let ingestRecordCount;
4743
- let ingestDurationMs;
4744
- let ingestOutcomeMessage;
4745
- const ingestKeyAppId = ingestAppId;
4746
- const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4747
- [APP_ID_VAR]: ingestKeyAppId,
4748
- [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4749
- [INDEX_NAME_VAR]: targetIndex
4750
- })) : void 0;
4751
- const searchTools = makeToolContext(worktree);
4752
- async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4753
- if (agentRuns > 0) ctx.recordStepExecution();
4754
- agentRuns += 1;
4755
- return runAgent({
4756
- instructions: buildAgentInstructions(
4757
- currentUseCase,
4758
- input,
4759
- extraInstructions
4760
- ),
4761
- tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4762
- outputSchema: implementationOutputSchema,
4763
- toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4764
- });
4765
- }
4766
- async function runVerificationUseCase() {
4767
- if (agentRuns > 0) ctx.recordStepExecution();
4768
- agentRuns += 1;
4769
- return runAgent({
4770
- instructions: buildAgentInstructions("verification", input),
4771
- tools: toolsForUseCase("verification"),
4772
- outputSchema: verificationOutputSchema,
4773
- toolContext: searchTools
4774
- });
4775
4769
  }
4776
- if (useCases.includes("ingestion")) {
4777
- let ingestFailureDetail;
4778
- const result = await runImplementationUseCase("ingestion");
4779
- summaries.push(formatSummary("ingestion", result.summary));
4780
- ingestCommand = result.ingestCommand;
4781
- const ingestionContext = ingestionTools ?? searchTools;
4782
- const executions = ingestionContext.shell.executions;
4783
- const {
4784
- run: ingestRun,
4785
- attempt: ingestAttempt,
4786
- recordCount
4787
- } = ingestOutcome(executions, ingestCommand);
4788
- ingestScriptRan = ingestRun != null;
4789
- ingestRecordCount = recordCount;
4790
- ingestDurationMs = ingestRun?.durationMs;
4791
- if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
4792
- summaries.push(
4793
- "\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in the worktree before trusting the index contents."
4794
- );
4795
- logger.warn(
4796
- { ingestCommand },
4797
- "implement: ingestion ran without a reviewScript call"
4798
- );
4770
+ if (ingestScriptRan) {
4771
+ ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4772
+ if (ingestRecordCount != null) {
4773
+ track("AI Wizard Ingest Successful", {
4774
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4775
+ record_count: ingestRecordCount,
4776
+ duration_ms: ingestDurationMs ?? 0
4777
+ });
4799
4778
  }
4800
- if (ingestScriptRan) {
4801
- ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4802
- if (ingestRecordCount != null) {
4803
- track("AI Wizard Ingest Successful", {
4804
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4805
- record_count: ingestRecordCount,
4806
- duration_ms: ingestDurationMs ?? 0
4807
- });
4808
- }
4809
- } else {
4810
- const { reason, detail } = ingestFailure(ingestAttempt, executions);
4811
- ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4812
- ingestFailureDetail = detail;
4813
- summaries.push(
4814
- `\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
4779
+ } else {
4780
+ const { reason, detail } = ingestFailure(ingestAttempt, executions);
4781
+ ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4782
+ ingestFailureDetail = detail;
4783
+ summaries.push(
4784
+ `\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
4815
4785
  ${detail}` : ""}`
4816
- );
4817
- logger.warn(
4786
+ );
4787
+ logger.warn(
4788
+ {
4789
+ ingestCommand,
4790
+ reason,
4791
+ approved: ingestAttempt?.approved,
4792
+ exitCode: ingestAttempt?.exitCode,
4793
+ timedOut: ingestAttempt?.timedOut,
4794
+ commandsRun: executions.length
4795
+ },
4796
+ "implement: ingestion script did not complete successfully"
4797
+ );
4798
+ track("Error", {
4799
+ step: "Push Data",
4800
+ error: `ingestion did not complete: ${reason}`,
4801
+ product_area: "AI Wizard"
4802
+ });
4803
+ }
4804
+ const commandMessages = ingestCommand ? [`Ingestion command: ${ingestCommand}`] : [];
4805
+ await ctx.requestUserInput({
4806
+ prompt: "",
4807
+ promptType: "enterToContinue",
4808
+ options: [],
4809
+ // One message per line: Notices.tsx counts a message as one wrapped
4810
+ // line, so an embedded newline overflows the panel's height accounting.
4811
+ messages: [
4812
+ ingestOutcomeMessage,
4813
+ ...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
4814
+ ...commandMessages
4815
+ ]
4816
+ });
4817
+ }
4818
+ if (useCases.includes("search")) {
4819
+ let extraInstructions = [];
4820
+ useWizard.getState().clearWrittenFiles();
4821
+ for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
4822
+ if (attempt > 1) {
4823
+ logger.info(
4818
4824
  {
4819
- ingestCommand,
4820
- reason,
4821
- approved: ingestAttempt?.approved,
4822
- exitCode: ingestAttempt?.exitCode,
4823
- timedOut: ingestAttempt?.timedOut,
4824
- commandsRun: executions.length
4825
+ attempt,
4826
+ maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
4827
+ extraInstructions
4825
4828
  },
4826
- "implement: ingestion script did not complete successfully"
4829
+ "implement: retrying search implementation after failed verification"
4827
4830
  );
4828
- track("Error", {
4829
- step: "Push Data",
4830
- error: `ingestion did not complete: ${reason}`,
4831
- product_area: "AI Wizard"
4831
+ }
4832
+ const { summary } = await runImplementationUseCase(
4833
+ "search",
4834
+ extraInstructions
4835
+ );
4836
+ summaries.push(formatSummary("search", summary));
4837
+ const verification = await runVerificationUseCase();
4838
+ summaries.push(formatSummary("verification", verification.summary));
4839
+ if (verification.sufficient) {
4840
+ ctx.setUserInput("implementation", "success");
4841
+ const searchFilesChanged = [
4842
+ ...new Set(useWizard.getState().writtenFiles)
4843
+ ].map((file) => relative6(repoRoot, file));
4844
+ track("AI Wizard Frontend Component Generated", {
4845
+ filePaths: searchFilesChanged
4832
4846
  });
4847
+ track("AI Wizard Wired to UI", {
4848
+ location_heuristic: searchLocation ?? "unknown"
4849
+ });
4850
+ break;
4833
4851
  }
4834
- const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4835
- if (ingestCommand) {
4836
- commandMessages.push(`Ingestion command: ${ingestCommand}`);
4852
+ if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
4853
+ ctx.setUserInput("implementation", "fail");
4854
+ throw new Error(
4855
+ `Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
4856
+ );
4837
4857
  }
4838
- await ctx.requestUserInput({
4839
- prompt: "",
4840
- promptType: "enterToContinue",
4841
- options: [],
4842
- // The wizard never streams command output, so a failed run's tail is the
4843
- // only place the developer sees why it failed. One message per line:
4844
- // the panel's height accounting counts a message as one wrapped line
4845
- // (see Notices.tsx), so an embedded newline overflows it.
4846
- messages: [
4847
- ingestOutcomeMessage,
4848
- ...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
4849
- ...commandMessages
4850
- ]
4851
- });
4858
+ extraInstructions = verificationRetryInstructions(verification);
4852
4859
  }
4853
- if (useCases.includes("search")) {
4854
- let extraInstructions = [];
4855
- const preSearchFiles = new Set(await listChangedFiles(worktree));
4856
- for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
4857
- if (attempt > 1) {
4858
- logger.info(
4859
- {
4860
- attempt,
4861
- maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
4862
- extraInstructions
4863
- },
4864
- "implement: retrying search implementation after failed verification"
4865
- );
4866
- }
4867
- const { summary } = await runImplementationUseCase(
4868
- "search",
4869
- extraInstructions
4860
+ let searchKey;
4861
+ let searchKeyError;
4862
+ if (appId) {
4863
+ try {
4864
+ const resolved2 = await resolveSearchOnlyKey(
4865
+ targetIndex,
4866
+ appId,
4867
+ envSearchKey
4868
+ );
4869
+ searchKey = resolved2.key;
4870
+ summaries.push(
4871
+ resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4872
+ );
4873
+ } catch (err) {
4874
+ searchKeyError = err.message;
4875
+ logger.warn(
4876
+ { err: searchKeyError },
4877
+ "implement: could not provision a search-only API key; the .env value stays a placeholder"
4870
4878
  );
4871
- summaries.push(formatSummary("search", summary));
4872
- const verification = await runVerificationUseCase();
4873
- summaries.push(formatSummary("verification", verification.summary));
4874
- if (verification.sufficient) {
4875
- ctx.setUserInput("implementation", "success");
4876
- const searchFilesChanged = (await listChangedFiles(worktree)).filter(
4877
- (file) => !preSearchFiles.has(file)
4878
- );
4879
- track("AI Wizard Frontend Component Generated", {
4880
- filePaths: searchFilesChanged
4881
- });
4882
- track("AI Wizard Wired to UI", {
4883
- location_heuristic: searchLocation ?? "unknown"
4884
- });
4885
- break;
4886
- }
4887
- if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
4888
- ctx.setUserInput("implementation", "fail");
4889
- throw new Error(
4890
- `Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
4891
- );
4892
- }
4893
- extraInstructions = verificationRetryInstructions(verification);
4894
- }
4895
- let searchKey;
4896
- let searchKeyError;
4897
- if (appId) {
4898
- try {
4899
- const resolved2 = await resolveSearchOnlyKey(
4900
- targetIndex,
4901
- appId,
4902
- envSearchKey
4903
- );
4904
- searchKey = resolved2.key;
4905
- summaries.push(
4906
- resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
4907
- );
4908
- } catch (err) {
4909
- searchKeyError = err.message;
4910
- logger.warn(
4911
- { err: searchKeyError },
4912
- "implement: could not provision a search-only API key; the .env value stays a placeholder"
4913
- );
4914
- }
4915
4879
  }
4916
- finalSearchEnvVars = publicSearchEnvVars(
4917
- publicEnvVarPrefix,
4918
- targetIndex,
4919
- appId,
4920
- searchKey
4921
- );
4922
- const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4923
- (v) => !v.value.startsWith("<")
4880
+ }
4881
+ finalSearchEnvVars = publicSearchEnvVars(
4882
+ publicEnvVarPrefix,
4883
+ targetIndex,
4884
+ appId,
4885
+ searchKey
4886
+ );
4887
+ const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4888
+ (v) => !v.value.startsWith("<")
4889
+ );
4890
+ if (resolvedSearchEnvVars.length > 0) {
4891
+ const written = await writeSearchEnvValues(
4892
+ repoRoot,
4893
+ resolvedSearchEnvVars
4924
4894
  );
4925
- if (resolvedSearchEnvVars.length > 0) {
4926
- const written = await writeSearchEnvValues(
4927
- worktree,
4928
- resolvedSearchEnvVars
4895
+ if (written.length > 0) {
4896
+ summaries.push(`Wrote ${written.join(", ")} to .env.`);
4897
+ }
4898
+ const ignored = await ensureGitIgnored(repoRoot, join12(repoRoot, ".env"));
4899
+ if (ignored === "added") {
4900
+ summaries.push("Added .env to .gitignore.");
4901
+ } else if (ignored === "tracked") {
4902
+ summaries.push(
4903
+ '\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
4929
4904
  );
4930
- if (written.length > 0) {
4931
- summaries.push(`Wrote ${written.join(", ")} to .env.`);
4932
- }
4933
- const ignored = await ensureGitIgnored(worktree, join12(worktree, ".env"));
4934
- if (ignored === "added") {
4935
- summaries.push("Added .env to .gitignore.");
4936
- } else if (ignored === "tracked") {
4937
- summaries.push(
4938
- '\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
4939
- );
4940
- }
4941
- const stale = [];
4942
- for (const v of resolvedSearchEnvVars) {
4943
- if (written.includes(v.name)) continue;
4944
- const current = await readEnvVar(worktree, v.name);
4945
- if (current && current !== v.value) stale.push(v);
4946
- }
4947
- if (stale.length > 0 && !envAppIdMismatch) {
4948
- summaries.push(
4949
- `\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
4950
- );
4951
- logger.warn(
4952
- { vars: stale.map((v) => v.name) },
4953
- "implement: .env holds different values for the resolved search credentials; not overwriting them"
4954
- );
4955
- }
4956
4905
  }
4957
- const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
4958
- (v) => v.value.startsWith("<")
4959
- );
4960
- if (unresolvedSearchEnvVars.length > 0) {
4906
+ const stale = [];
4907
+ for (const v of resolvedSearchEnvVars) {
4908
+ if (written.includes(v.name)) continue;
4909
+ const current = await readEnvVar(repoRoot, v.name);
4910
+ if (current && current !== v.value) stale.push(v);
4911
+ }
4912
+ if (stale.length > 0 && !envAppIdMismatch) {
4961
4913
  summaries.push(
4962
- `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
4963
- (searchKeyError ? ` Reason: ${searchKeyError}` : "")
4914
+ `\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
4915
+ );
4916
+ logger.warn(
4917
+ { vars: stale.map((v) => v.name) },
4918
+ "implement: .env holds different values for the resolved search credentials; not overwriting them"
4964
4919
  );
4965
4920
  }
4966
- } else {
4967
- ctx.setUserInput("implementation", "success");
4968
4921
  }
4969
- const filesChanged = await listChangedFiles(worktree);
4970
- if (filesChanged.length === 0) {
4971
- logger.warn(
4972
- "implement: agent reported success but no files changed in the worktree"
4922
+ const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
4923
+ (v) => v.value.startsWith("<")
4924
+ );
4925
+ if (unresolvedSearchEnvVars.length > 0) {
4926
+ summaries.push(
4927
+ `Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
4973
4928
  );
4974
4929
  }
4975
- return {
4976
- ingestionSource,
4977
- filesChanged,
4978
- summary: summaries.join("\n\n"),
4979
- worktreePath: worktree,
4980
- ...useCases.includes("ingestion") && ingestCommand ? {
4981
- ingestCommand,
4982
- ingestScriptRan,
4983
- ...ingestRecordCount != null ? { ingestRecordCount } : {},
4984
- ...ingestDurationMs != null ? { ingestDurationMs } : {}
4985
- } : {},
4986
- ...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
4987
- };
4988
- } finally {
4989
- process.chdir(repoRoot);
4930
+ } else {
4931
+ ctx.setUserInput("implementation", "success");
4990
4932
  }
4933
+ return {
4934
+ ingestionSource,
4935
+ summary: summaries.join("\n\n"),
4936
+ ...useCases.includes("ingestion") && ingestCommand ? {
4937
+ ingestCommand,
4938
+ ingestScriptRan,
4939
+ ...ingestRecordCount != null ? { ingestRecordCount } : {},
4940
+ ...ingestDurationMs != null ? { ingestDurationMs } : {}
4941
+ } : {},
4942
+ ...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
4943
+ };
4991
4944
  }
4992
4945
 
4993
4946
  // src/workflows/default.ts
@@ -5059,10 +5012,7 @@ var defaultWorkflow = {
5059
5012
  ctx.notify({
5060
5013
  messages: ["Building your Algolia search experience\u2026"]
5061
5014
  });
5062
- const ingestion2 = ctx.getStepOutput(
5063
- "ingestion"
5064
- );
5065
- return implement(ctx, ["search"], ingestion2?.worktreePath);
5015
+ return implement(ctx, ["search"]);
5066
5016
  }
5067
5017
  }),
5068
5018
  defineStep({
@@ -5077,9 +5027,8 @@ var defaultWorkflow = {
5077
5027
  "ingestion"
5078
5028
  );
5079
5029
  return reviewStep(ctx, {
5080
- // The ingestion step already showed the user the exact `ingestCommand`
5081
- // and worktree path as a notice, so nextSteps must not restate it —
5082
- // an LLM-paraphrased command risks being wrong.
5030
+ // ingestCommand was already shown verbatim as a notice; an
5031
+ // LLM-paraphrased restatement in nextSteps risks being wrong.
5083
5032
  nextStepsGuidance: ingestion2?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
5084
5033
  });
5085
5034
  }
@@ -5126,7 +5075,6 @@ var selectIndex = {
5126
5075
  selection: "wizard_seed_products"
5127
5076
  };
5128
5077
  var ingestion = {
5129
- filesChanged: ["algolia/ingest.mjs", "algolia/records.json", "package.json"],
5130
5078
  summary: "Generated sample Product records and an ingestion script that pushes them to the target index with algoliasearch.",
5131
5079
  ingestCommand: "node algolia/ingest.mjs",
5132
5080
  ingestScriptRan: true,
@@ -5138,7 +5086,6 @@ var confirmFramework2 = {
5138
5086
  frameworks: projectScan2.frameworks
5139
5087
  };
5140
5088
  var search = {
5141
- filesChanged: ["src/components/Search.tsx", "src/components/Header.tsx", ".env"],
5142
5089
  summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
5143
5090
  ingestionSource: "generated",
5144
5091
  searchEnvVars: [
@@ -5151,7 +5098,7 @@ var review = {
5151
5098
  "Ingested 25 generated Product records into wizard_seed_products.",
5152
5099
  "Added an InstantSearch search experience to the shared header."
5153
5100
  ],
5154
- reviewPrompt: "Review the Algolia ingestion and search changes in this worktree.",
5101
+ reviewPrompt: "Review the Algolia ingestion and search changes.",
5155
5102
  nextSteps: ["Point the ingestion script at your real product data."]
5156
5103
  };
5157
5104
  var SEEDS = {
@@ -5268,9 +5215,9 @@ Options:
5268
5215
  steps pre-filled with test data. Pass with no value to print
5269
5216
  the step ids. See CONTRIBUTING.md.
5270
5217
  --no-telemetry Send no telemetry or analytics for this run.
5271
- --reset-on-run Wipe this project's wizard state (run state, AI consent,
5272
- worktrees) before starting, so the run behaves like a
5273
- first-ever run. Also drops every API key the wizard has
5218
+ --reset-on-run Wipe this project's wizard state (run state, AI consent)
5219
+ before starting, so the run behaves like a first-ever
5220
+ run. Also drops every API key the wizard has
5274
5221
  stored in your keychain (or, where the platform has none,
5275
5222
  the encrypted file it falls back to \u2014 see CONTRIBUTING.md),
5276
5223
  for this project and any other, so later runs create new
@@ -5310,7 +5257,7 @@ function parseCliArgs(argv) {
5310
5257
  }
5311
5258
 
5312
5259
  // src/lib/resetState.ts
5313
- import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
5260
+ import { readdir as readdir3, rm as rm2 } from "node:fs/promises";
5314
5261
  import { join as join13 } from "node:path";
5315
5262
  var KEEP = ["wizard.log"];
5316
5263
  async function resetProjectState() {
@@ -5318,7 +5265,7 @@ async function resetProjectState() {
5318
5265
  await forgetResolvedKeys();
5319
5266
  let entries;
5320
5267
  try {
5321
- entries = await readdir4(dir);
5268
+ entries = await readdir3(dir);
5322
5269
  } catch {
5323
5270
  return { dir, removed: [] };
5324
5271
  }
@@ -5371,7 +5318,7 @@ function delay(ms) {
5371
5318
  }
5372
5319
 
5373
5320
  // src/main.tsx
5374
- import { jsx as jsx15 } from "react/jsx-runtime";
5321
+ import { jsx as jsx16 } from "react/jsx-runtime";
5375
5322
  async function startup() {
5376
5323
  setProjectRoot(process.cwd());
5377
5324
  let args;
@@ -5421,7 +5368,7 @@ ${formatStepList(workflow)}`);
5421
5368
  }
5422
5369
  async function run(workflow) {
5423
5370
  const store = useWizard.getState();
5424
- const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
5371
+ const instance = render(/* @__PURE__ */ jsx16(App, {}), { incrementalRendering: true });
5425
5372
  await store.waitForStart();
5426
5373
  let user = await getUser();
5427
5374
  if (!user) {