@algolia/wizard 0.9.0-rc.87.83 → 0.9.0-rc.88.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/ui/App.tsx
7
- import { Box as Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
7
+ import { Box as Box15, Text as Text15, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
@@ -13,47 +13,6 @@ import { nanoid } from "nanoid";
13
13
  // src/lib/algoliaCli.ts
14
14
  import { spawn } from "node:child_process";
15
15
  import { z } from "zod";
16
-
17
- // src/lib/logger.ts
18
- import pino from "pino";
19
- import { join as join2, dirname } from "node:path";
20
- import { devNull } from "node:os";
21
- import { mkdirSync, openSync, closeSync } from "node:fs";
22
-
23
- // src/core/constants.ts
24
- import { homedir } from "node:os";
25
- import { join, resolve } from "node:path";
26
- function rootDir() {
27
- return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
28
- }
29
- function projectSlug(cwd = process.cwd()) {
30
- return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
31
- }
32
- function stateDir(cwd = process.cwd()) {
33
- return join(rootDir(), projectSlug(cwd));
34
- }
35
-
36
- // src/lib/logger.ts
37
- var STDERR_FD = 2;
38
- function resolveDest() {
39
- const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
40
- try {
41
- mkdirSync(dirname(target), { recursive: true });
42
- closeSync(openSync(target, "a"));
43
- return target;
44
- } catch {
45
- return STDERR_FD;
46
- }
47
- }
48
- function logDestination() {
49
- return pino.destination({ dest: resolveDest(), sync: false });
50
- }
51
- var logger = pino(
52
- { level: process.env.LOG_LEVEL ?? "info" },
53
- logDestination()
54
- );
55
-
56
- // src/lib/algoliaCli.ts
57
16
  function npxArgs(args) {
58
17
  return ["--yes", "@algolia/cli@latest", ...args];
59
18
  }
@@ -130,7 +89,6 @@ function runAlgoliaCli(args, { onOutput } = {}) {
130
89
  },
131
90
  (err) => {
132
91
  useWizard.getState().logEnd(logId, "error");
133
- logger.warn({ err, args }, "Algolia CLI command failed");
134
92
  throw err;
135
93
  }
136
94
  );
@@ -188,6 +146,49 @@ function refreshAuthToken() {
188
146
  return inFlightRefresh;
189
147
  }
190
148
 
149
+ // src/lib/logger.ts
150
+ import pino from "pino";
151
+ import { join as join2, dirname } from "node:path";
152
+ import { devNull } from "node:os";
153
+ import { mkdirSync, openSync, closeSync } from "node:fs";
154
+
155
+ // src/core/constants.ts
156
+ import { homedir } from "node:os";
157
+ import { join, resolve } from "node:path";
158
+ function rootDir() {
159
+ return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
160
+ }
161
+ var pinnedRoot;
162
+ function setProjectRoot(cwd) {
163
+ pinnedRoot = resolve(cwd);
164
+ }
165
+ function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
166
+ return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
167
+ }
168
+ function stateDir(cwd = pinnedRoot ?? process.cwd()) {
169
+ return join(rootDir(), projectSlug(cwd));
170
+ }
171
+
172
+ // src/lib/logger.ts
173
+ var STDERR_FD = 2;
174
+ function resolveDest() {
175
+ const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
176
+ try {
177
+ mkdirSync(dirname(target), { recursive: true });
178
+ closeSync(openSync(target, "a"));
179
+ return target;
180
+ } catch {
181
+ return STDERR_FD;
182
+ }
183
+ }
184
+ function logDestination() {
185
+ return pino.destination({ dest: resolveDest(), sync: false });
186
+ }
187
+ var logger = pino(
188
+ { level: process.env.LOG_LEVEL ?? "info" },
189
+ logDestination()
190
+ );
191
+
191
192
  // src/lib/proxyFetch.ts
192
193
  var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
193
194
  var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
@@ -617,13 +618,10 @@ function Notices() {
617
618
  }
618
619
 
619
620
  // src/ui/PromptInput.tsx
620
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
621
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
621
622
  import TextInput from "ink-text-input";
622
623
  import { useState as useState5 } from "react";
623
624
 
624
- // src/ui/CommandApproval.tsx
625
- import { Box as Box5, Text as Text5, useInput } from "ink";
626
-
627
625
  // src/ui/NextAction.tsx
628
626
  import { Box as Box4, Text as Text4 } from "ink";
629
627
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -647,69 +645,14 @@ function NextAction({
647
645
  ] });
648
646
  }
649
647
 
650
- // src/ui/CommandApproval.tsx
651
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
652
- function CommandApproval({
653
- command,
654
- onDecide
655
- }) {
656
- useInput((input, key) => {
657
- if (key.return) onDecide("approve");
658
- else if (key.escape) onDecide("reject");
659
- else if (input.toLowerCase() === "a") onDecide("always");
660
- });
661
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
662
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
663
- /* @__PURE__ */ jsxs4(
664
- Box5,
665
- {
666
- flexDirection: "column",
667
- paddingLeft: 2,
668
- borderStyle: "single",
669
- borderColor: COLORS.success,
670
- borderTop: false,
671
- borderBottom: false,
672
- borderRight: false,
673
- gap: 1,
674
- children: [
675
- /* @__PURE__ */ jsxs4(Box5, { children: [
676
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
677
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
678
- ] }),
679
- /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
680
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
681
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
682
- ] }),
683
- command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
684
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
685
- /* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
686
- ] })
687
- ]
688
- }
689
- ),
690
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
691
- /* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
692
- /* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" }),
693
- /* @__PURE__ */ jsx4(
694
- NextAction,
695
- {
696
- action: "approve, and don't ask again for this command",
697
- keyHint: "a",
698
- hierarchy: "secondary"
699
- }
700
- )
701
- ] })
702
- ] });
703
- }
704
-
705
648
  // src/ui/SelectPrompt.tsx
706
- import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
649
+ import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
707
650
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
708
651
 
709
652
  // src/ui/ScrollView.tsx
710
- import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
653
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
711
654
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
712
- import { jsxs as jsxs5 } from "react/jsx-runtime";
655
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
713
656
  var INDICATOR_ROWS = 2;
714
657
  function fittedWidth(node, columns) {
715
658
  let left = 0;
@@ -779,14 +722,14 @@ function useScrollWindow({
779
722
  };
780
723
  }
781
724
  function ScrollView({ scroll, children }) {
782
- return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
783
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
725
+ return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
726
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
784
727
  "\u2191 ",
785
728
  scroll.hiddenAbove,
786
729
  " more"
787
730
  ] }),
788
731
  children,
789
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
732
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
790
733
  "\u2193 ",
791
734
  scroll.hiddenBelow,
792
735
  " more"
@@ -795,7 +738,7 @@ function ScrollView({ scroll, children }) {
795
738
  }
796
739
 
797
740
  // src/ui/SelectPrompt.tsx
798
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
741
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
799
742
  var CANCEL = "cancel";
800
743
  var ARROW_WIDTH = 4;
801
744
  var COLUMN_GAP = 2;
@@ -857,7 +800,7 @@ function SelectPrompt({
857
800
  revealIndex(index);
858
801
  }, [index, revealIndex]);
859
802
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
860
- useInput2((input, key) => {
803
+ useInput((input, key) => {
861
804
  if (rows.length === 0) return;
862
805
  if (key.upArrow || input === "k") {
863
806
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -880,56 +823,56 @@ function SelectPrompt({
880
823
  }
881
824
  }
882
825
  });
883
- return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
884
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
885
- error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
886
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
887
- table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
888
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
889
- question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
890
- helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
826
+ return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
827
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
828
+ error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
829
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
830
+ table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
831
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
832
+ question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
833
+ helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
891
834
  ] })
892
835
  ] }),
893
- /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
836
+ /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
894
837
  const i = scroll.offset + visibleIndex;
895
838
  const highlighted = i === index;
896
839
  const isCancel = i === cancelIndex;
897
840
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
898
841
  const sec = isCancel ? void 0 : secondary?.[i];
899
842
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
900
- const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
843
+ const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
901
844
  highlighted ? "\u276F " : " ",
902
845
  bullet,
903
846
  option
904
847
  ] });
905
848
  const isText = sec?.kind === "text";
906
- return /* @__PURE__ */ jsxs6(
907
- Box7,
849
+ return /* @__PURE__ */ jsxs5(
850
+ Box6,
908
851
  {
909
852
  width: isText ? "100%" : barWidth,
910
853
  paddingX: 1,
911
854
  paddingY: 1,
912
855
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
913
856
  children: [
914
- /* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
915
- isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
916
- Text7,
857
+ /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
858
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
859
+ Text6,
917
860
  {
918
861
  wrap: "truncate",
919
862
  color: highlighted ? COLORS.primary : COLORS.muted,
920
863
  children: sec.value
921
864
  }
922
865
  ) }),
923
- sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
866
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
924
867
  ]
925
868
  },
926
869
  `row-${i}`
927
870
  );
928
871
  }) }),
929
- /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
872
+ /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
930
873
  i > 0 ? " " : "",
931
- /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
932
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
874
+ /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
875
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
933
876
  " ",
934
877
  label
935
878
  ] })
@@ -938,23 +881,23 @@ function SelectPrompt({
938
881
  }
939
882
 
940
883
  // src/ui/PromptInput.tsx
941
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
884
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
942
885
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
943
886
  function EnterToContinuePrompt({
944
887
  question,
945
888
  messages,
946
889
  onDecide
947
890
  }) {
948
- useInput3((_input, key) => {
891
+ useInput2((_input, key) => {
949
892
  if (key.return) onDecide(true);
950
893
  else if (key.escape) onDecide(false);
951
894
  });
952
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
953
- messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
954
- question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
955
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
956
- /* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
957
- /* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
895
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
896
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
897
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
898
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
899
+ /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
900
+ /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
958
901
  ] })
959
902
  ] });
960
903
  }
@@ -962,11 +905,11 @@ function PromptInput() {
962
905
  const { phase, inputReq, submitInput } = useWizard();
963
906
  const [draft, setDraft] = useState5("");
964
907
  if (phase === "done" || phase === "error") {
965
- return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
908
+ return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
966
909
  }
967
910
  if (phase !== "awaitingInput" || !inputReq) return null;
968
911
  if (inputReq.promptType === "multipleChoice") {
969
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
912
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
970
913
  SelectPrompt,
971
914
  {
972
915
  question: inputReq.prompt,
@@ -983,7 +926,7 @@ function PromptInput() {
983
926
  ) });
984
927
  }
985
928
  if (inputReq.promptType === "multiSelect") {
986
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
929
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
987
930
  SelectPrompt,
988
931
  {
989
932
  multi: true,
@@ -998,7 +941,7 @@ function PromptInput() {
998
941
  ) });
999
942
  }
1000
943
  if (inputReq.promptType === "notice") {
1001
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
944
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1002
945
  SelectPrompt,
1003
946
  {
1004
947
  question: inputReq.prompt,
@@ -1009,7 +952,7 @@ function PromptInput() {
1009
952
  ) });
1010
953
  }
1011
954
  if (inputReq.promptType === "enterToContinue") {
1012
- return /* @__PURE__ */ jsx6(
955
+ return /* @__PURE__ */ jsx5(
1013
956
  EnterToContinuePrompt,
1014
957
  {
1015
958
  question: inputReq.prompt,
@@ -1018,12 +961,9 @@ function PromptInput() {
1018
961
  }
1019
962
  );
1020
963
  }
1021
- if (inputReq.promptType === "commandApproval" && inputReq.command) {
1022
- return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1023
- }
1024
964
  if (inputReq.promptType === "acceptReject") {
1025
965
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
1026
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
966
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1027
967
  SelectPrompt,
1028
968
  {
1029
969
  question: inputReq.prompt,
@@ -1034,15 +974,15 @@ function PromptInput() {
1034
974
  }
1035
975
  ) });
1036
976
  }
1037
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1038
- inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
1039
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
1040
- /* @__PURE__ */ jsxs7(Box8, { children: [
1041
- /* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
977
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
978
+ inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
979
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
980
+ /* @__PURE__ */ jsxs6(Box7, { children: [
981
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
1042
982
  inputReq.prompt,
1043
983
  " "
1044
984
  ] }),
1045
- /* @__PURE__ */ jsx6(
985
+ /* @__PURE__ */ jsx5(
1046
986
  TextInput,
1047
987
  {
1048
988
  value: draft,
@@ -1060,7 +1000,7 @@ function PromptInput() {
1060
1000
  // src/ui/Welcome.tsx
1061
1001
  import { dirname as dirname2, join as join3 } from "node:path";
1062
1002
  import { fileURLToPath } from "node:url";
1063
- import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1003
+ import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1064
1004
 
1065
1005
  // src/ui/copy/welcome.ts
1066
1006
  var sidebarItems = [
@@ -1073,12 +1013,12 @@ var sidebarItems = [
1073
1013
  description: "push 100 records to Algolia in seconds"
1074
1014
  },
1075
1015
  {
1076
- title: "detect your stack",
1077
- description: "whatever language and framework you already use"
1016
+ title: "detect your framework",
1017
+ description: "React, Vue, Angular, Vanilla JS"
1078
1018
  },
1079
1019
  {
1080
1020
  title: "scaffold a search UI",
1081
- description: "a search box and results, wired into your app"
1021
+ description: "a styled InstantSearch component, wired into your app"
1082
1022
  },
1083
1023
  {
1084
1024
  title: "ship it",
@@ -1088,20 +1028,20 @@ var sidebarItems = [
1088
1028
 
1089
1029
  // src/ui/Welcome.tsx
1090
1030
  import Image, { InkPictureProvider } from "ink-picture";
1091
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1031
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1092
1032
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1093
1033
  function SidebarItem({
1094
1034
  title,
1095
1035
  description
1096
1036
  }) {
1097
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
1098
- /* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
1099
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
1100
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
1037
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1038
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1039
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1040
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
1101
1041
  ] }),
1102
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1103
- /* @__PURE__ */ jsx7(Spacer, {}),
1104
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1042
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1043
+ /* @__PURE__ */ jsx6(Spacer, {}),
1044
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1105
1045
  ] })
1106
1046
  ] });
1107
1047
  }
@@ -1109,7 +1049,7 @@ function Welcome() {
1109
1049
  const confirmStart = useWizard((s) => s.confirmStart);
1110
1050
  const openLearnMore = useWizard((s) => s.openLearnMore);
1111
1051
  const { rows } = useWindowSize6();
1112
- useInput4((input, key) => {
1052
+ useInput3((input, key) => {
1113
1053
  if (key.return) confirmStart();
1114
1054
  else if (input === "i") openLearnMore();
1115
1055
  });
@@ -1127,16 +1067,16 @@ function Welcome() {
1127
1067
  if (rows < 30) {
1128
1068
  layout = scales["small"];
1129
1069
  }
1130
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1131
- /* @__PURE__ */ jsx7(
1132
- Box9,
1070
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1071
+ /* @__PURE__ */ jsx6(
1072
+ Box8,
1133
1073
  {
1134
1074
  paddingY: layout.main.padding.y,
1135
1075
  paddingX: layout.main.padding.x,
1136
1076
  flexDirection: "column",
1137
1077
  justifyContent: "center",
1138
- children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1139
- /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1078
+ children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1079
+ /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1140
1080
  Image,
1141
1081
  {
1142
1082
  src: IMAGE_PATH,
@@ -1147,16 +1087,16 @@ function Welcome() {
1147
1087
  protocol: "halfBlock"
1148
1088
  }
1149
1089
  ) }),
1150
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1151
- /* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
1152
- /* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
1153
- /* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1090
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1091
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1092
+ /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1093
+ /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1154
1094
  ] })
1155
1095
  ] })
1156
1096
  }
1157
1097
  ),
1158
- /* @__PURE__ */ jsxs8(
1159
- Box9,
1098
+ /* @__PURE__ */ jsxs7(
1099
+ Box8,
1160
1100
  {
1161
1101
  backgroundColor: COLORS.bg.sidebar,
1162
1102
  width: 40,
@@ -1166,8 +1106,8 @@ function Welcome() {
1166
1106
  flexDirection: "column",
1167
1107
  justifyContent: "center",
1168
1108
  children: [
1169
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1170
- sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
1109
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1110
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
1171
1111
  ]
1172
1112
  }
1173
1113
  )
@@ -1176,7 +1116,7 @@ function Welcome() {
1176
1116
 
1177
1117
  // src/ui/LearnMore.tsx
1178
1118
  import { Fragment as Fragment2 } from "react";
1179
- import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1119
+ import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1180
1120
 
1181
1121
  // src/ui/copy/learn-more.ts
1182
1122
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1184,17 +1124,12 @@ var accessItems = [
1184
1124
  {
1185
1125
  tag: "READ",
1186
1126
  title: "Project files",
1187
- description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1127
+ description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1188
1128
  },
1189
1129
  {
1190
1130
  tag: "WRITE",
1191
1131
  title: "Code changes",
1192
- description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
1193
- },
1194
- {
1195
- tag: "EXEC",
1196
- title: "Setup commands",
1197
- description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
1132
+ description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1198
1133
  },
1199
1134
  {
1200
1135
  tag: "NET",
@@ -1204,13 +1139,13 @@ var accessItems = [
1204
1139
  {
1205
1140
  tag: "KEY",
1206
1141
  title: "Credentials",
1207
- description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1142
+ description: "saves your Admin API key to .env and adds it to .gitignore."
1208
1143
  }
1209
1144
  ];
1210
1145
  var neverItems = [
1211
1146
  "Send your source code to a model or third party",
1212
1147
  "Commit or push to git",
1213
- "Run a command you haven't approved"
1148
+ "Touch files outside your project directory"
1214
1149
  ];
1215
1150
  var policyLinks = [
1216
1151
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1218,11 +1153,10 @@ var policyLinks = [
1218
1153
  ];
1219
1154
 
1220
1155
  // src/ui/LearnMore.tsx
1221
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1156
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1222
1157
  var TAG_COLORS = {
1223
1158
  READ: COLORS.success,
1224
1159
  WRITE: COLORS.badge,
1225
- EXEC: COLORS.danger,
1226
1160
  NET: COLORS.accent,
1227
1161
  KEY: COLORS.muted
1228
1162
  };
@@ -1235,12 +1169,12 @@ function NeverLine({
1235
1169
  }) {
1236
1170
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1237
1171
  const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
1238
- return /* @__PURE__ */ jsxs9(Text10, { children: [
1239
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
1172
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
1173
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1240
1174
  " ".repeat(NEVER_BOX_PAD_X),
1241
- segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1175
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1242
1176
  " ".repeat(rightPad),
1243
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1177
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1244
1178
  ] });
1245
1179
  }
1246
1180
  function LearnMore() {
@@ -1248,12 +1182,12 @@ function LearnMore() {
1248
1182
  const backToHome = useWizard((s) => s.backToHome);
1249
1183
  const { columns } = useWindowSize7();
1250
1184
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1251
- useInput5((_input, key) => {
1185
+ useInput4((_input, key) => {
1252
1186
  if (key.escape) backToHome();
1253
1187
  else if (key.return) confirmStart();
1254
1188
  });
1255
- return /* @__PURE__ */ jsxs9(
1256
- Box10,
1189
+ return /* @__PURE__ */ jsxs8(
1190
+ Box9,
1257
1191
  {
1258
1192
  flexDirection: "column",
1259
1193
  paddingX: PADDING_X,
@@ -1261,31 +1195,31 @@ function LearnMore() {
1261
1195
  width: "100%",
1262
1196
  gap: 1,
1263
1197
  children: [
1264
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1265
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
1266
- /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
1267
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1268
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1269
- /* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1270
- /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
1271
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
1272
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1198
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1199
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1200
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1201
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1202
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1203
+ /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1204
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1205
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1206
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1273
1207
  ] }) })
1274
1208
  ] })
1275
1209
  ] }, item.tag)) }),
1276
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
1277
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1278
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1279
- /* @__PURE__ */ jsx8(
1210
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1211
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1212
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1213
+ /* @__PURE__ */ jsx7(
1280
1214
  NeverLine,
1281
1215
  {
1282
1216
  width: dividerWidth,
1283
1217
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1284
1218
  }
1285
1219
  ),
1286
- neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1287
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1288
- /* @__PURE__ */ jsx8(
1220
+ neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1221
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1222
+ /* @__PURE__ */ jsx7(
1289
1223
  NeverLine,
1290
1224
  {
1291
1225
  width: dividerWidth,
@@ -1297,24 +1231,24 @@ function LearnMore() {
1297
1231
  }
1298
1232
  )
1299
1233
  ] }, item)),
1300
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1301
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1234
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1235
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1302
1236
  ] }),
1303
- /* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1304
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1305
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
1237
+ /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1238
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1239
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1306
1240
  ] }, link.label)) }),
1307
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1308
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1309
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1310
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
1311
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
1241
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1242
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1243
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1244
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1245
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1312
1246
  ] }),
1313
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1314
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
1315
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
1316
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
1317
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
1247
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1248
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1249
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1250
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1251
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1318
1252
  ] })
1319
1253
  ] })
1320
1254
  ]
@@ -1323,10 +1257,10 @@ function LearnMore() {
1323
1257
  }
1324
1258
 
1325
1259
  // src/ui/Sidebar.tsx
1326
- import { Box as Box13, Text as Text13 } from "ink";
1260
+ import { Box as Box12, Text as Text12 } from "ink";
1327
1261
 
1328
1262
  // src/ui/Steps.tsx
1329
- import { Box as Box11, Text as Text11 } from "ink";
1263
+ import { Box as Box10, Text as Text10 } from "ink";
1330
1264
  import Spinner from "ink-spinner";
1331
1265
 
1332
1266
  // src/core/persistence.ts
@@ -1355,12 +1289,12 @@ async function clearWorkflowState(workflowId) {
1355
1289
  }
1356
1290
 
1357
1291
  // src/ui/Steps.tsx
1358
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1292
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1359
1293
  function Steps() {
1360
1294
  const { steps } = useWizard();
1361
1295
  const visibleSteps = steps.filter(isStepVisible);
1362
- return /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status[s.status], children: [
1363
- s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
1296
+ return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
1297
+ s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1364
1298
  " ",
1365
1299
  s.title
1366
1300
  ] }) }, s.id)) });
@@ -1369,27 +1303,27 @@ function CurrentStep() {
1369
1303
  const { steps } = useWizard();
1370
1304
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1371
1305
  if (!currentStep) return null;
1372
- return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1373
- /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1306
+ return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1307
+ /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1374
1308
  " ",
1375
1309
  ` ${currentStep.title}`
1376
1310
  ] });
1377
1311
  }
1378
1312
 
1379
1313
  // src/ui/Progress.tsx
1380
- import { Box as Box12, Text as Text12 } from "ink";
1381
- import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1314
+ import { Box as Box11, Text as Text11 } from "ink";
1315
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1382
1316
  function Progress() {
1383
1317
  const { steps, currentStepIndex } = useWizard();
1384
1318
  const visibleSteps = steps.filter(isStepVisible);
1385
1319
  if (visibleSteps.length === 0) return null;
1386
1320
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1387
1321
  const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
1388
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1389
- /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
1390
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
1391
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
1392
- /* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
1322
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1323
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1324
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1325
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1326
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1393
1327
  ] });
1394
1328
  }
1395
1329
 
@@ -1400,10 +1334,10 @@ var sidebarCommands = [
1400
1334
  ];
1401
1335
 
1402
1336
  // src/ui/Sidebar.tsx
1403
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1337
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1404
1338
  function Sidebar() {
1405
- return /* @__PURE__ */ jsxs12(
1406
- Box13,
1339
+ return /* @__PURE__ */ jsxs11(
1340
+ Box12,
1407
1341
  {
1408
1342
  backgroundColor: "#14171E",
1409
1343
  width: 30,
@@ -1412,16 +1346,16 @@ function Sidebar() {
1412
1346
  flexDirection: "column",
1413
1347
  justifyContent: "space-between",
1414
1348
  children: [
1415
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1416
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1417
- /* @__PURE__ */ jsx11(Steps, {})
1349
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1350
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1351
+ /* @__PURE__ */ jsx10(Steps, {})
1418
1352
  ] }),
1419
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1420
- /* @__PURE__ */ jsx11(Progress, {}),
1421
- /* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
1422
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1423
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1424
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
1353
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1354
+ /* @__PURE__ */ jsx10(Progress, {}),
1355
+ /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1356
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1357
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1358
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1425
1359
  ] });
1426
1360
  }) })
1427
1361
  ] })
@@ -1431,12 +1365,12 @@ function Sidebar() {
1431
1365
  }
1432
1366
 
1433
1367
  // src/ui/Ribbon.tsx
1434
- import { Box as Box14, Text as Text14 } from "ink";
1435
- import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1368
+ import { Box as Box13, Text as Text13 } from "ink";
1369
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1436
1370
  function Ribbon() {
1437
1371
  const firstCommand = sidebarCommands[0];
1438
- return /* @__PURE__ */ jsxs13(
1439
- Box14,
1372
+ return /* @__PURE__ */ jsxs12(
1373
+ Box13,
1440
1374
  {
1441
1375
  backgroundColor: "#14171E",
1442
1376
  flexDirection: "row",
@@ -1444,11 +1378,11 @@ function Ribbon() {
1444
1378
  paddingX: 2,
1445
1379
  paddingY: 1,
1446
1380
  children: [
1447
- /* @__PURE__ */ jsx12(Progress, {}),
1448
- /* @__PURE__ */ jsx12(CurrentStep, {}),
1449
- /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
1450
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1451
- /* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
1381
+ /* @__PURE__ */ jsx11(Progress, {}),
1382
+ /* @__PURE__ */ jsx11(CurrentStep, {}),
1383
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1384
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1385
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1452
1386
  ] })
1453
1387
  ]
1454
1388
  }
@@ -1459,8 +1393,8 @@ function Ribbon() {
1459
1393
  import { useState as useState6 } from "react";
1460
1394
 
1461
1395
  // src/ui/Logs.tsx
1462
- import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1463
- import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1396
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1397
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1464
1398
  var KIND_COLOR = {
1465
1399
  tool: COLORS.primary,
1466
1400
  prompt: COLORS.badge
@@ -1491,14 +1425,14 @@ function formatTimestamp(ms) {
1491
1425
  function Logs() {
1492
1426
  const logs = useWizard((s) => s.logs);
1493
1427
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1494
- useInput6((_input, key) => {
1428
+ useInput5((_input, key) => {
1495
1429
  if (key.upArrow) scroll.scrollBy(-1);
1496
1430
  else if (key.downArrow) scroll.scrollBy(1);
1497
1431
  });
1498
1432
  const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
1499
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1500
- logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
1501
- /* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
1433
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1434
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1435
+ /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1502
1436
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1503
1437
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1504
1438
  const rawPreview = rawInputText(entry.input);
@@ -1508,14 +1442,14 @@ function Logs() {
1508
1442
  const name = truncate2(entry.name, budget);
1509
1443
  budget -= name.length;
1510
1444
  const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1511
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
1512
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
1513
- /* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
1514
- preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
1515
- durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
1445
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1446
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1447
+ /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1448
+ preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1449
+ durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1516
1450
  ] }, entry.id);
1517
1451
  }) }),
1518
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1452
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1519
1453
  ] });
1520
1454
  }
1521
1455
 
@@ -1707,7 +1641,7 @@ function track(event, payload) {
1707
1641
  }
1708
1642
 
1709
1643
  // src/ui/App.tsx
1710
- import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1644
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1711
1645
  function App() {
1712
1646
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1713
1647
  const { exit } = useApp();
@@ -1715,7 +1649,7 @@ function App() {
1715
1649
  const [showLogs, setShowLogs] = useState6(false);
1716
1650
  const finished = phase === "done" || phase === "error";
1717
1651
  const currentStep = steps[currentStepIndex];
1718
- useInput7(
1652
+ useInput6(
1719
1653
  (_input, key) => {
1720
1654
  if (key.return) {
1721
1655
  exit();
@@ -1723,7 +1657,7 @@ function App() {
1723
1657
  },
1724
1658
  { isActive: finished }
1725
1659
  );
1726
- useInput7((_input, key) => {
1660
+ useInput6((_input, key) => {
1727
1661
  if (phase === "idle" || phase === "authenticating") return;
1728
1662
  if (key.tab) {
1729
1663
  setShowLogs(!showLogs);
@@ -1734,8 +1668,8 @@ function App() {
1734
1668
  });
1735
1669
  }
1736
1670
  });
1737
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1738
- useInput7((_input, key) => {
1671
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1672
+ useInput6((_input, key) => {
1739
1673
  if (escOwnedElsewhere) return;
1740
1674
  if (key.escape) {
1741
1675
  track("AI Wizard Interaction", {
@@ -1754,8 +1688,8 @@ function App() {
1754
1688
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1755
1689
  the whole screen, and the scrolling throws off its cursor arithmetic —
1756
1690
  flicker and leftover rows. */
1757
- /* @__PURE__ */ jsxs15(
1758
- Box16,
1691
+ /* @__PURE__ */ jsxs14(
1692
+ Box15,
1759
1693
  {
1760
1694
  backgroundColor: COLORS.bg.main,
1761
1695
  flexDirection: "row",
@@ -1763,16 +1697,16 @@ function App() {
1763
1697
  height: scrollsPastViewport ? void 0 : rows,
1764
1698
  overflow: scrollsPastViewport ? "visible" : "hidden",
1765
1699
  children: [
1766
- mainWindowVisible && /* @__PURE__ */ jsxs15(
1767
- Box16,
1700
+ mainWindowVisible && /* @__PURE__ */ jsxs14(
1701
+ Box15,
1768
1702
  {
1769
1703
  flexDirection,
1770
1704
  width: "100%",
1771
1705
  maxHeight: rows,
1772
1706
  justifyContent: "space-between",
1773
1707
  children: [
1774
- showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1775
- Box16,
1708
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1709
+ Box15,
1776
1710
  {
1777
1711
  flexDirection: "column",
1778
1712
  paddingX: 4,
@@ -1780,26 +1714,26 @@ function App() {
1780
1714
  width: showSidebar ? 70 : "100%",
1781
1715
  flexGrow: 1,
1782
1716
  children: [
1783
- phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
1784
- /* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1785
- /* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1717
+ phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1718
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1719
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1786
1720
  ] }),
1787
- /* @__PURE__ */ jsx14(CliOutput, {}),
1788
- /* @__PURE__ */ jsx14(Notices, {}),
1789
- /* @__PURE__ */ jsx14(PromptInput, {}),
1790
- phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
1791
- phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
1721
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1722
+ /* @__PURE__ */ jsx13(Notices, {}),
1723
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1724
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1725
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1792
1726
  "\u2716 ",
1793
1727
  error
1794
1728
  ] }) })
1795
1729
  ]
1796
1730
  }
1797
1731
  ),
1798
- showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1732
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1799
1733
  ]
1800
1734
  }
1801
1735
  ),
1802
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1736
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1803
1737
  ]
1804
1738
  }
1805
1739
  )
@@ -1813,18 +1747,17 @@ import "zod";
1813
1747
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1814
1748
  import { join as join5 } from "node:path";
1815
1749
  var configFile = () => join5(stateDir(), "config.json");
1816
- var DEFAULT_CONFIG = {
1750
+ var defaultConfig = () => ({
1817
1751
  version: 1,
1818
1752
  aiConsent: false,
1819
- workflowsRun: [],
1820
- searchApiKeys: {}
1821
- };
1753
+ workflowsRun: []
1754
+ });
1822
1755
  async function loadConfig() {
1823
1756
  try {
1824
1757
  const raw = await readFile2(configFile(), "utf8");
1825
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1758
+ return { ...defaultConfig(), ...JSON.parse(raw) };
1826
1759
  } catch {
1827
- return { ...DEFAULT_CONFIG };
1760
+ return defaultConfig();
1828
1761
  }
1829
1762
  }
1830
1763
  async function saveConfig(config) {
@@ -1836,38 +1769,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
1836
1769
  config.workflowsRun.push({ workflowId, completedAt });
1837
1770
  await saveConfig(config);
1838
1771
  }
1839
- function isStoredSearchKey(value) {
1840
- if (typeof value !== "object" || value === null) return false;
1841
- const { appId, key } = value;
1842
- return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
1843
- }
1844
- function storedSearchKeys(config) {
1845
- const stored = config.searchApiKeys;
1846
- if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
1847
- return {};
1848
- }
1849
- return stored;
1850
- }
1851
- async function getStoredSearchKey(index, appId) {
1852
- const entry = storedSearchKeys(await loadConfig())[index];
1853
- if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
1854
- return entry.key;
1855
- }
1856
- async function storeSearchKey(index, appId, key) {
1857
- const config = await loadConfig();
1858
- config.searchApiKeys = {
1859
- ...storedSearchKeys(config),
1860
- [index]: { appId, key }
1861
- };
1862
- await saveConfig(config);
1863
- }
1864
- async function forgetSearchKey(index) {
1865
- const config = await loadConfig();
1866
- const remaining = { ...storedSearchKeys(config) };
1867
- delete remaining[index];
1868
- config.searchApiKeys = remaining;
1869
- await saveConfig(config);
1870
- }
1871
1772
 
1872
1773
  // src/core/orchestrator.ts
1873
1774
  function defineStep(step) {
@@ -1912,7 +1813,7 @@ async function ensureConsent() {
1912
1813
  if (config.aiConsent) return;
1913
1814
  const store = useWizard.getState();
1914
1815
  const answer = await store.requestUserInput({
1915
- prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
1816
+ prompt: "Wizard will make AI-authored changes to this repository.",
1916
1817
  promptType: "enterToContinue",
1917
1818
  options: []
1918
1819
  });
@@ -2194,7 +2095,7 @@ async function ensureApplication() {
2194
2095
  }
2195
2096
 
2196
2097
  // src/workflows/default.ts
2197
- import { z as z27 } from "zod";
2098
+ import { z as z28 } from "zod";
2198
2099
 
2199
2100
  // src/actions/listIndices.ts
2200
2101
  import { z as z5 } from "zod";
@@ -2315,9 +2216,9 @@ function listFilesTool(ctx) {
2315
2216
  if (++ctx.counts.list > ctx.limits.list) {
2316
2217
  return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
2317
2218
  }
2318
- const resolved = resolveInRoot(ctx, ".");
2319
- if (!resolved.ok) return resolved.error;
2320
- const entries = await readdir(resolved.target, { withFileTypes: true });
2219
+ const resolved2 = resolveInRoot(ctx, ".");
2220
+ if (!resolved2.ok) return resolved2.error;
2221
+ const entries = await readdir(resolved2.target, { withFileTypes: true });
2321
2222
  return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
2322
2223
  }
2323
2224
  });
@@ -2335,14 +2236,14 @@ function changeDirectoryTool(ctx) {
2335
2236
  }),
2336
2237
  execute: async ({ path }) => {
2337
2238
  logger.info({ path }, "called changeDirectory tool");
2338
- const resolved = resolveInRoot(ctx, path);
2339
- if (!resolved.ok) return resolved.error;
2239
+ const resolved2 = resolveInRoot(ctx, path);
2240
+ if (!resolved2.ok) return resolved2.error;
2340
2241
  try {
2341
- const info = await stat(resolved.target);
2242
+ const info = await stat(resolved2.target);
2342
2243
  if (!info.isDirectory()) {
2343
2244
  return `Error changing directory to ${path}: not a directory`;
2344
2245
  }
2345
- ctx.cwd = resolved.target;
2246
+ ctx.cwd = resolved2.target;
2346
2247
  return `Changed working directory to ${ctx.cwd}`;
2347
2248
  } catch (err) {
2348
2249
  return `Error changing directory to ${path}: ${err.message}`;
@@ -2407,11 +2308,11 @@ function readFileTool(ctx) {
2407
2308
  return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
2408
2309
  }
2409
2310
  logger.info({ filePath }, "called readFile tool");
2410
- const resolved = resolveInRoot(ctx, filePath);
2411
- if (!resolved.ok) return resolved.error;
2311
+ const resolved2 = resolveInRoot(ctx, filePath);
2312
+ if (!resolved2.ok) return resolved2.error;
2412
2313
  try {
2413
- const content = await readFile3(resolved.target, "utf8");
2414
- return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2314
+ const content = await readFile3(resolved2.target, "utf8");
2315
+ return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
2415
2316
  } catch (err) {
2416
2317
  return `Error reading ${filePath}: ${err.message}`;
2417
2318
  }
@@ -2433,17 +2334,17 @@ function writeFileTool(ctx) {
2433
2334
  }),
2434
2335
  execute: async ({ filePath, content }) => {
2435
2336
  logger.info({ filePath }, "called writeFile tool");
2436
- const resolved = resolveInRoot(ctx, filePath);
2437
- if (resolved.ok === false) return resolved.error;
2438
- if (isSecretEnvFile(resolved.target)) {
2337
+ const resolved2 = resolveInRoot(ctx, filePath);
2338
+ if (resolved2.ok === false) return resolved2.error;
2339
+ if (isSecretEnvFile(resolved2.target)) {
2439
2340
  return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
2440
2341
  }
2441
2342
  try {
2442
- if (await hasSymlinkParent(ctx, resolved.target)) {
2443
- return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2343
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2344
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2444
2345
  }
2445
- await mkdir3(dirname4(resolved.target), { recursive: true });
2446
- await writeFile3(resolved.target, content, "utf8");
2346
+ await mkdir3(dirname4(resolved2.target), { recursive: true });
2347
+ await writeFile3(resolved2.target, content, "utf8");
2447
2348
  return `Wrote to ${filePath}`;
2448
2349
  } catch (err) {
2449
2350
  return `Error writing ${filePath}: ${err.message}`;
@@ -2454,12 +2355,93 @@ function writeFileTool(ctx) {
2454
2355
 
2455
2356
  // src/lib/tools/writeAlgoliaCredentials.ts
2456
2357
  import { tool as tool6 } from "ai";
2457
- import z12 from "zod";
2358
+ import z13 from "zod";
2458
2359
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2459
2360
  import { dirname as dirname5 } from "node:path";
2460
2361
 
2461
2362
  // src/lib/algoliaApiKey.ts
2363
+ import { z as z12 } from "zod";
2364
+
2365
+ // src/lib/keychain.ts
2366
+ import { deletePassword, getPassword, setPassword } from "cross-keychain";
2462
2367
  import { z as z11 } from "zod";
2368
+ var SERVICE = "algolia-wizard";
2369
+ var ACCOUNT = "api-keys";
2370
+ var storedKeysSchema = z11.record(z11.string(), z11.string());
2371
+ function entryId(kind, index, appId) {
2372
+ return `${kind}:${appId}:${index}`;
2373
+ }
2374
+ async function loadKeys() {
2375
+ const raw = await getPassword(SERVICE, ACCOUNT);
2376
+ if (!raw) return {};
2377
+ let payload;
2378
+ try {
2379
+ payload = JSON.parse(raw);
2380
+ } catch {
2381
+ payload = null;
2382
+ }
2383
+ const keys = storedKeysSchema.safeParse(payload);
2384
+ if (!keys.success) {
2385
+ logger.warn("the stored API keys are unreadable; treating them as empty");
2386
+ return {};
2387
+ }
2388
+ return keys.data;
2389
+ }
2390
+ var queue = Promise.resolve();
2391
+ function serialized(op) {
2392
+ const next = queue.then(op);
2393
+ queue = next.catch(() => {
2394
+ });
2395
+ return next;
2396
+ }
2397
+ async function readStoredKey(kind, index, appId) {
2398
+ try {
2399
+ return (await loadKeys())[entryId(kind, index, appId)] ?? null;
2400
+ } catch (err) {
2401
+ logger.warn(
2402
+ { err: err.message, kind, index, appId },
2403
+ "could not read the API key from the keychain"
2404
+ );
2405
+ return null;
2406
+ }
2407
+ }
2408
+ function storeKey(kind, index, appId, value) {
2409
+ return serialized(async () => {
2410
+ const id = entryId(kind, index, appId);
2411
+ try {
2412
+ const keys = await loadKeys();
2413
+ await setPassword(
2414
+ SERVICE,
2415
+ ACCOUNT,
2416
+ JSON.stringify({ ...keys, [id]: value })
2417
+ );
2418
+ if ((await loadKeys())[id] !== value) {
2419
+ throw new Error("the keychain did not store the value");
2420
+ }
2421
+ } catch (err) {
2422
+ logger.warn(
2423
+ { err: err.message, kind, index, appId },
2424
+ "could not store the API key in the keychain; the next run will create another"
2425
+ );
2426
+ }
2427
+ });
2428
+ }
2429
+ function deleteStoredKeys() {
2430
+ return serialized(async () => {
2431
+ try {
2432
+ await deletePassword(SERVICE, ACCOUNT);
2433
+ } catch (err) {
2434
+ const message = err.message;
2435
+ if (/not found/i.test(message)) return;
2436
+ logger.warn(
2437
+ { err: message },
2438
+ "could not delete the API keys from the keychain"
2439
+ );
2440
+ }
2441
+ });
2442
+ }
2443
+
2444
+ // src/lib/algoliaApiKey.ts
2463
2445
  var WRITE_ACLS = [
2464
2446
  "addObject",
2465
2447
  "deleteObject",
@@ -2467,78 +2449,21 @@ var WRITE_ACLS = [
2467
2449
  "editSettings",
2468
2450
  "listIndexes"
2469
2451
  ];
2470
- var WRITE_ACL_SET = new Set(WRITE_ACLS);
2471
- var apiKeySchema = z11.object({
2472
- value: z11.string().min(1),
2473
- acl: z11.array(z11.string()).default([]),
2474
- indexes: z11.array(z11.string()).default([])
2475
- });
2476
- var apiKeyListSchema = z11.union([
2477
- z11.array(apiKeySchema),
2478
- z11.object({
2479
- items: z11.array(apiKeySchema).optional(),
2480
- keys: z11.array(apiKeySchema).optional()
2481
- })
2482
- ]).transform((o) => Array.isArray(o) ? o : o.items ?? o.keys ?? []);
2483
- function parseCliJson(schema, stdout, command) {
2484
- let raw;
2485
- try {
2486
- raw = JSON.parse(stdout);
2487
- } catch {
2488
- throw new Error(`Algolia CLI \`${command}\` returned unreadable JSON.`);
2489
- }
2490
- const parsed = schema.safeParse(raw);
2491
- if (!parsed.success) {
2492
- throw new Error(
2493
- `Algolia CLI \`${command}\` returned JSON in an unexpected shape.`
2494
- );
2495
- }
2496
- return parsed.data;
2497
- }
2498
- var createdKeySchema = z11.object({
2499
- key: z11.string().min(1).optional(),
2500
- value: z11.string().min(1).optional()
2452
+ var createdKeySchema = z12.object({
2453
+ key: z12.string().min(1).optional(),
2454
+ value: z12.string().min(1).optional()
2501
2455
  }).transform((o) => o.key ?? o.value);
2502
- function canReuseForWrites(key, index) {
2503
- return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
2504
- }
2505
- async function resolveWriteKey(index) {
2506
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
2507
- const existing = parseCliJson(apiKeyListSchema, stdout, "apikeys list").find(
2508
- (key) => canReuseForWrites(key, index)
2509
- )?.value;
2510
- if (existing) {
2511
- logger.info({ index }, "reusing existing write API key");
2512
- return existing;
2513
- }
2514
- logger.info({ index }, "no reusable write key found; creating one");
2515
- const created = await runAlgoliaCli([
2516
- "apikeys",
2517
- "create",
2518
- "--indices",
2519
- index,
2520
- "--acl",
2521
- WRITE_ACLS.join(","),
2522
- "--description",
2523
- `wizard write key for ${index}`,
2524
- "-o",
2525
- "json"
2526
- ]);
2527
- const writeKey = parseCliJson(createdKeySchema, created, "apikeys create");
2528
- if (!writeKey) throw new Error("apikeys create returned no key value");
2529
- return writeKey;
2530
- }
2531
- async function createSearchOnlyKey(index) {
2532
- logger.info({ index }, "creating a search-only API key");
2456
+ async function createKey(index, acls, description) {
2457
+ logger.info({ index, acls }, "creating an API key");
2533
2458
  const stdout = await runAlgoliaCli([
2534
2459
  "apikeys",
2535
2460
  "create",
2536
2461
  "--acl",
2537
- "search",
2462
+ acls.join(","),
2538
2463
  "--indices",
2539
2464
  index,
2540
2465
  "--description",
2541
- `Algolia Wizard search-only key for ${index}`,
2466
+ description,
2542
2467
  "-o",
2543
2468
  "json"
2544
2469
  ]);
@@ -2552,49 +2477,58 @@ async function createSearchOnlyKey(index) {
2552
2477
  if (!created) throw new Error("apikeys create returned no key value");
2553
2478
  return created;
2554
2479
  }
2555
- async function apiKeyExists(key) {
2556
- try {
2557
- await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
2558
- return true;
2559
- } catch (err) {
2560
- return !/does not exist|not found|404/i.test(err.message);
2561
- }
2480
+ var resolved = /* @__PURE__ */ new Map();
2481
+ async function forgetResolvedKeys() {
2482
+ resolved.clear();
2483
+ await deleteStoredKeys();
2562
2484
  }
2563
- async function resolveSearchOnlyKey(index, appId, envKey) {
2564
- if (envKey) {
2565
- await recordSearchKey(index, appId, envKey);
2566
- return { key: envKey, source: "env" };
2567
- }
2568
- const stored = await getStoredSearchKey(index, appId);
2569
- if (stored) {
2570
- if (await apiKeyExists(stored)) {
2571
- logger.info({ index, appId }, "reusing the stored search-only API key");
2572
- return { key: stored, source: "config" };
2485
+ function resolveKey(kind, index, appId, acls, description) {
2486
+ const cacheKey = `${kind}:${appId}:${index}`;
2487
+ const cached = resolved.get(cacheKey);
2488
+ if (cached) return cached;
2489
+ const pending = provisionKey(kind, index, appId, acls, description).catch(
2490
+ (err) => {
2491
+ resolved.delete(cacheKey);
2492
+ throw err;
2573
2493
  }
2574
- logger.warn(
2575
- { index, appId },
2576
- "the stored search-only API key no longer exists; creating a replacement"
2577
- );
2578
- await forgetSearchKey(index);
2494
+ );
2495
+ resolved.set(cacheKey, pending);
2496
+ return pending;
2497
+ }
2498
+ async function provisionKey(kind, index, appId, acls, description) {
2499
+ const stored = await readStoredKey(kind, index, appId);
2500
+ if (stored) {
2501
+ logger.info({ kind, index, appId }, "reusing the stored API key");
2502
+ return { key: stored, source: "keychain" };
2579
2503
  }
2580
- const key = await createSearchOnlyKey(index);
2581
- await recordSearchKey(index, appId, key);
2504
+ const key = await createKey(index, acls, description);
2505
+ await storeKey(kind, index, appId, key);
2582
2506
  return { key, source: "created" };
2583
2507
  }
2584
- async function recordSearchKey(index, appId, key) {
2585
- try {
2586
- await storeSearchKey(index, appId, key);
2587
- } catch (err) {
2588
- logger.warn(
2589
- { err: err.message, index },
2590
- "could not record the search-only API key; a later run may create another"
2591
- );
2592
- }
2508
+ function resolveWriteKey(index, appId) {
2509
+ return resolveKey(
2510
+ "write",
2511
+ index,
2512
+ appId,
2513
+ WRITE_ACLS,
2514
+ `Algolia Wizard write key for ${index} index`
2515
+ );
2516
+ }
2517
+ async function resolveSearchOnlyKey(index, appId, envKey) {
2518
+ if (envKey) return { key: envKey, source: "env" };
2519
+ return resolveKey(
2520
+ "search",
2521
+ index,
2522
+ appId,
2523
+ ["search"],
2524
+ `Algolia Wizard search-only key for ${index} index`
2525
+ );
2593
2526
  }
2594
2527
 
2595
2528
  // src/lib/tools/writeAlgoliaCredentials.ts
2596
2529
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2597
2530
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2531
+ var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
2598
2532
  function appendEnv(content, entries) {
2599
2533
  const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
2600
2534
  const lines = entries.map(([name, value]) => `${name}=${value}
@@ -2604,53 +2538,68 @@ function appendEnv(content, entries) {
2604
2538
  function hasEnv(content, name) {
2605
2539
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
2606
2540
  }
2541
+ function upsertEnv(content, name, value) {
2542
+ if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
2543
+ return content.replace(
2544
+ new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m"),
2545
+ `${name}=${value}`
2546
+ );
2547
+ }
2607
2548
  function writeCredentialsTool(ctx) {
2608
2549
  return tool6({
2609
- description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
2610
- inputSchema: z12.object({
2611
- filePath: z12.string().describe(
2550
+ description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). Any name the file already defines is left untouched.`,
2551
+ inputSchema: z13.object({
2552
+ filePath: z13.string().describe(
2612
2553
  'Path to the env file to write credentials into (e.g. ".env")'
2613
2554
  )
2614
2555
  }),
2615
2556
  execute: async ({ filePath }) => {
2616
2557
  logger.info({ filePath }, "called writeCredentials tool");
2617
- const resolved = resolveInRoot(ctx, filePath);
2618
- if (resolved.ok === false) return resolved.error;
2558
+ const resolved2 = resolveInRoot(ctx, filePath);
2559
+ if (resolved2.ok === false) return resolved2.error;
2619
2560
  const targetIndex = useWizard.getState().targetIndex;
2620
2561
  if (!targetIndex) {
2621
2562
  return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2622
2563
  }
2623
- let appId;
2624
- let writeKey;
2564
+ let existing = "";
2565
+ let present;
2625
2566
  try {
2626
- appId = (await requireApplication()).id;
2627
- writeKey = await resolveWriteKey(targetIndex);
2628
- } catch (err) {
2629
- return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2630
- }
2631
- try {
2632
- if (await hasSymlinkParent(ctx, resolved.target)) {
2633
- return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2567
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2568
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2634
2569
  }
2635
- let existing = "";
2636
2570
  try {
2637
- existing = await readFile4(resolved.target, "utf8");
2571
+ existing = await readFile4(resolved2.target, "utf8");
2638
2572
  } catch (err) {
2639
2573
  if (err.code !== "ENOENT") throw err;
2640
2574
  }
2641
- const present = [APP_ID_VAR, API_KEY_VAR].filter(
2575
+ present = [APP_ID_VAR, API_KEY_VAR].filter(
2642
2576
  (name) => hasEnv(existing, name)
2643
2577
  );
2644
- if (present.length > 0) {
2645
- return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2578
+ } catch (err) {
2579
+ return `Error writing credentials to ${filePath}: ${err.message}`;
2580
+ }
2581
+ let credentials = [];
2582
+ if (present.length === 0) {
2583
+ try {
2584
+ const appId = (await requireApplication()).id;
2585
+ credentials = [
2586
+ [APP_ID_VAR, appId],
2587
+ [API_KEY_VAR, (await resolveWriteKey(targetIndex, appId)).key]
2588
+ ];
2589
+ } catch (err) {
2590
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2646
2591
  }
2647
- const envWithCredentials = appendEnv(existing, [
2648
- [APP_ID_VAR, appId],
2649
- [API_KEY_VAR, writeKey]
2650
- ]);
2651
- await mkdir4(dirname5(resolved.target), { recursive: true });
2652
- await writeFile4(resolved.target, envWithCredentials, "utf8");
2653
- return `Wrote Algolia credentials to ${filePath}`;
2592
+ }
2593
+ try {
2594
+ const updated = upsertEnv(
2595
+ appendEnv(existing, credentials),
2596
+ INDEX_NAME_VAR,
2597
+ targetIndex
2598
+ );
2599
+ await mkdir4(dirname5(resolved2.target), { recursive: true });
2600
+ await writeFile4(resolved2.target, updated, "utf8");
2601
+ const wrote = `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}`;
2602
+ return present.length > 0 ? `${wrote}. Skipped ${present.join(" and ")}: already defined there.` : wrote;
2654
2603
  } catch (err) {
2655
2604
  return `Error writing credentials to ${filePath}: ${err.message}`;
2656
2605
  }
@@ -2660,23 +2609,15 @@ function writeCredentialsTool(ctx) {
2660
2609
 
2661
2610
  // src/lib/tools/searchFiles.ts
2662
2611
  import { tool as tool7 } from "ai";
2663
- import z13 from "zod";
2612
+ import z14 from "zod";
2664
2613
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2665
2614
  import { join as join7 } from "node:path";
2666
2615
  var MAX_QUERY_LENGTH = 1e3;
2667
- var SKIP_DIRS = /* @__PURE__ */ new Set([
2668
- "node_modules",
2669
- "dist",
2670
- "build",
2671
- "vendor",
2672
- "venv",
2673
- "__pycache__",
2674
- "target"
2675
- ]);
2676
2616
  async function walkFiles(dir) {
2617
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2677
2618
  const out = [];
2678
2619
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2679
- if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2620
+ if (e.name.startsWith(".") || skip.has(e.name)) continue;
2680
2621
  const full = join7(dir, e.name);
2681
2622
  if (e.isDirectory()) out.push(...await walkFiles(full));
2682
2623
  else if (e.isFile()) out.push(full);
@@ -2686,9 +2627,9 @@ async function walkFiles(dir) {
2686
2627
  function searchFilesTool(ctx) {
2687
2628
  return tool7({
2688
2629
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
2689
- inputSchema: z13.object({
2690
- query: z13.string().describe("JavaScript RegExp pattern to search for"),
2691
- path: z13.string().optional().describe("Directory to search in (default: cwd)")
2630
+ inputSchema: z14.object({
2631
+ query: z14.string().describe("JavaScript RegExp pattern to search for"),
2632
+ path: z14.string().optional().describe("Directory to search in (default: cwd)")
2692
2633
  }),
2693
2634
  execute: async ({ query, path = "." }) => {
2694
2635
  logger.info({ query, path }, "called searchFiles tool");
@@ -2698,8 +2639,8 @@ function searchFilesTool(ctx) {
2698
2639
  if (query.length > MAX_QUERY_LENGTH) {
2699
2640
  return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
2700
2641
  }
2701
- const resolved = resolveInRoot(ctx, path);
2702
- if (!resolved.ok) return resolved.error;
2642
+ const resolved2 = resolveInRoot(ctx, path);
2643
+ if (!resolved2.ok) return resolved2.error;
2703
2644
  let re;
2704
2645
  try {
2705
2646
  re = new RegExp(query);
@@ -2707,7 +2648,7 @@ function searchFilesTool(ctx) {
2707
2648
  return `Invalid regex: ${err.message}`;
2708
2649
  }
2709
2650
  const matches = [];
2710
- for (const file of await walkFiles(resolved.target)) {
2651
+ for (const file of await walkFiles(resolved2.target)) {
2711
2652
  let content;
2712
2653
  try {
2713
2654
  content = await readFile5(file, "utf8");
@@ -2730,194 +2671,92 @@ function searchFilesTool(ctx) {
2730
2671
  });
2731
2672
  }
2732
2673
 
2733
- // src/lib/tools/runShell.ts
2674
+ // src/lib/tools/verifyImplementation.ts
2734
2675
  import { tool as tool8 } from "ai";
2735
- import z14 from "zod";
2736
- import { relative as relative2 } from "node:path";
2676
+ import z15 from "zod";
2737
2677
 
2738
- // src/lib/tools/utils/runShell.ts
2678
+ // src/lib/tools/utils/runCommand.ts
2739
2679
  import { spawn as spawn2 } from "node:child_process";
2740
-
2741
- // src/lib/tools/context.ts
2742
- var DEFAULT_TOOL_LIMITS = {
2743
- list: 10,
2744
- search: 10,
2745
- read: 20,
2746
- match: 100,
2747
- shell: 30
2748
- };
2749
- var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2750
- async function refuseByDefault() {
2751
- return "reject";
2752
- }
2753
- function createShellContext(overrides = {}) {
2754
- return {
2755
- env: async () => ({}),
2756
- timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2757
- approved: /* @__PURE__ */ new Set(),
2758
- executions: [],
2759
- approve: refuseByDefault,
2760
- ...overrides
2761
- };
2762
- }
2763
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2764
- return {
2765
- root: cwd,
2766
- cwd,
2767
- limits: { ...limits },
2768
- counts: { list: 0, search: 0, read: 0, shell: 0 },
2769
- shell: shell2
2770
- };
2771
- }
2772
-
2773
- // src/lib/tools/utils/runShell.ts
2774
- var SIGKILL_DELAY_MS = 5e3;
2775
- var HEAD_CHARS = 4e3;
2776
- var TAIL_CHARS = 8e3;
2777
- function truncateOutput(output) {
2778
- if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
2779
- const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
2780
- return [
2781
- output.slice(0, HEAD_CHARS),
2782
- `
2783
- \u2026 [${omitted} characters omitted] \u2026
2784
- `,
2785
- output.slice(-TAIL_CHARS)
2786
- ].join("");
2787
- }
2788
- function runShell(command, opts) {
2789
- const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2790
- const startedAt = Date.now();
2680
+ function runCommand(command, args, cwd) {
2791
2681
  return new Promise((resolve4) => {
2792
2682
  let output = "";
2793
- let timedOut = false;
2794
- let settled = false;
2795
- const child = spawn2(command, {
2796
- shell: true,
2797
- cwd: opts.cwd,
2798
- stdio: ["ignore", "pipe", "pipe"],
2799
- env: { ...process.env, ...opts.env }
2683
+ const child = spawn2(command, args, {
2684
+ cwd,
2685
+ stdio: ["ignore", "pipe", "pipe"]
2800
2686
  });
2801
- const finish = (exitCode) => {
2802
- if (settled) return;
2803
- settled = true;
2804
- clearTimeout(timer);
2805
- clearTimeout(killTimer);
2806
- resolve4({
2807
- exitCode,
2808
- output: truncateOutput(output.trim()),
2809
- timedOut,
2810
- durationMs: Date.now() - startedAt
2811
- });
2812
- };
2813
- let killTimer;
2814
- const timer = setTimeout(() => {
2815
- timedOut = true;
2816
- output += `
2817
- [timed out after ${timeoutMs}ms]`;
2818
- child.kill("SIGTERM");
2819
- killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
2820
- }, timeoutMs);
2821
2687
  child.stdout?.on("data", (d) => output += d);
2822
2688
  child.stderr?.on("data", (d) => output += d);
2823
- child.on("error", (err) => {
2824
- output += `Failed to run ${command}: ${err.message}`;
2825
- finish(1);
2826
- });
2827
- child.on("close", (code) => finish(code ?? 1));
2689
+ child.on(
2690
+ "error",
2691
+ (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2692
+ );
2693
+ child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2828
2694
  });
2829
2695
  }
2830
2696
 
2831
- // src/lib/tools/runShell.ts
2832
- function approvalKey(cwd, command) {
2833
- return `${cwd}\0${command}`;
2834
- }
2835
- function storeApproval(root) {
2836
- return async (req) => {
2837
- const rel = relative2(root, req.cwd);
2838
- const answer = await useWizard.getState().requestUserInput({
2839
- prompt: "Run this command?",
2840
- promptType: "commandApproval",
2841
- options: [],
2842
- command: {
2843
- ...req,
2844
- cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2845
- }
2846
- });
2847
- return answer === "approve" || answer === "always" ? answer : "reject";
2848
- };
2697
+ // src/lib/tools/utils/packageManager.ts
2698
+ import { readFile as readFile6 } from "node:fs/promises";
2699
+ import { existsSync } from "node:fs";
2700
+ import { join as join8 } from "node:path";
2701
+ var LOCKFILES = [
2702
+ ["pnpm-lock.yaml", "pnpm"],
2703
+ ["yarn.lock", "yarn"],
2704
+ ["bun.lockb", "bun"],
2705
+ ["bun.lock", "bun"],
2706
+ ["package-lock.json", "npm"]
2707
+ ];
2708
+ async function readPackageJson(cwd = process.cwd()) {
2709
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2710
+ }
2711
+ function packageManagerFrom(pkg) {
2712
+ return pkg.packageManager?.split("@")[0] ?? "npm";
2713
+ }
2714
+ function packageManagerFromLockfile(cwd) {
2715
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2716
+ }
2717
+ async function detectPackageManager(cwd) {
2718
+ try {
2719
+ const pkg = await readPackageJson(cwd);
2720
+ if (pkg.packageManager) return packageManagerFrom(pkg);
2721
+ } catch {
2722
+ }
2723
+ return packageManagerFromLockfile(cwd) ?? "npm";
2724
+ }
2725
+
2726
+ // src/lib/tools/repoVerification.ts
2727
+ var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2728
+ async function runRepoVerificationCheck() {
2729
+ let pkg;
2730
+ try {
2731
+ pkg = await readPackageJson();
2732
+ } catch (err) {
2733
+ const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2734
+ return { ok: false, checks: [], limitation };
2735
+ }
2736
+ const scripts = pkg.scripts ?? {};
2737
+ const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2738
+ if (present.length === 0) {
2739
+ const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2740
+ return { ok: false, checks: [], limitation };
2741
+ }
2742
+ const pm = await detectPackageManager(process.cwd());
2743
+ const checks = [];
2744
+ for (const script of present) {
2745
+ const command = `${pm} run ${script}`;
2746
+ const { code, output } = await runCommand(pm, ["run", script]);
2747
+ checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2748
+ }
2749
+ return { ok: checks.every((c) => c.ok), checks };
2849
2750
  }
2850
- function runShellTool(ctx) {
2751
+
2752
+ // src/lib/tools/verifyImplementation.ts
2753
+ function verifyImplementationTool() {
2851
2754
  return tool8({
2852
- description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach.",
2853
- inputSchema: z14.object({
2854
- command: z14.string().describe(
2855
- "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
2856
- ),
2857
- cwd: z14.string().optional().describe(
2858
- "Directory to run in, relative to the project root. Defaults to the project root."
2859
- ),
2860
- explanation: z14.string().describe(
2861
- "One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
2862
- )
2863
- }),
2864
- execute: async ({ command, cwd, explanation }) => {
2865
- if (++ctx.counts.shell > ctx.limits.shell) {
2866
- return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
2867
- }
2868
- const resolved = resolveInRoot(ctx, cwd ?? ".");
2869
- if (!resolved.ok) return resolved.error;
2870
- logger.info({ command, cwd: resolved.target }, "called runShell tool");
2871
- const key = approvalKey(resolved.target, command);
2872
- const decision = ctx.shell.approved.has(key) ? "approve" : await ctx.shell.approve({
2873
- command,
2874
- cwd: resolved.target,
2875
- explanation
2876
- });
2877
- if (decision === "reject") {
2878
- ctx.shell.executions.push({
2879
- command,
2880
- cwd: resolved.target,
2881
- approved: false
2882
- });
2883
- logger.info({ command }, "runShell: user rejected the command");
2884
- return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
2885
- }
2886
- if (decision === "always") ctx.shell.approved.add(key);
2887
- useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
2888
- const env = await ctx.shell.env().catch((err) => {
2889
- logger.warn({ err, command }, "runShell: could not resolve command env");
2890
- return {};
2891
- });
2892
- const run2 = await (ctx.shell.run ?? runShell)(command, {
2893
- cwd: resolved.target,
2894
- env,
2895
- timeoutMs: ctx.shell.timeoutMs
2896
- });
2897
- logger.info(
2898
- {
2899
- command,
2900
- exitCode: run2.exitCode,
2901
- timedOut: run2.timedOut,
2902
- durationMs: run2.durationMs
2903
- },
2904
- "runShell finished"
2905
- );
2906
- await markInteraction();
2907
- ctx.shell.executions.push({
2908
- command,
2909
- cwd: resolved.target,
2910
- approved: true,
2911
- exitCode: run2.exitCode,
2912
- output: run2.output,
2913
- timedOut: run2.timedOut,
2914
- durationMs: run2.durationMs
2915
- });
2916
- return {
2917
- exitCode: run2.exitCode,
2918
- timedOut: run2.timedOut,
2919
- output: run2.output
2920
- };
2755
+ description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
2756
+ inputSchema: z15.object(),
2757
+ execute: async () => {
2758
+ logger.info("called verifyImplementation tool");
2759
+ return runRepoVerificationCheck();
2921
2760
  }
2922
2761
  });
2923
2762
  }
@@ -2928,7 +2767,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
2928
2767
  import { nanoid as nanoid2 } from "nanoid";
2929
2768
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2930
2769
  import { dirname as dirname6 } from "node:path";
2931
- import z15 from "zod";
2770
+ import z16 from "zod";
2932
2771
  var DATA_DIR = ".algolia-wizard/data";
2933
2772
  var RECORD_MODEL = "claude-haiku-4-5";
2934
2773
  var MAX_RECORDS = 100;
@@ -2940,17 +2779,17 @@ var anthropic = createAnthropic({
2940
2779
  function generateRecordTool(ctx) {
2941
2780
  return tool9({
2942
2781
  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.",
2943
- inputSchema: z15.object({
2944
- entityName: z15.string().describe("Name of the entity to generate records for."),
2945
- attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
2946
- count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2947
- hint: z15.string().optional().describe("Optional context to steer realistic values.")
2782
+ inputSchema: z16.object({
2783
+ entityName: z16.string().describe("Name of the entity to generate records for."),
2784
+ attributes: z16.array(z16.string()).describe("Attribute names each record must contain."),
2785
+ count: z16.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
2786
+ hint: z16.string().optional().describe("Optional context to steer realistic values.")
2948
2787
  }),
2949
2788
  execute: async ({ entityName, attributes, count, hint }) => {
2950
2789
  logger.info({ entityName, count }, "called generateRecord tool");
2951
2790
  try {
2952
- const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
2953
- const recordSchema = z15.object(
2791
+ const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
2792
+ const recordSchema = z16.object(
2954
2793
  Object.fromEntries(attributes.map((attr) => [attr, value]))
2955
2794
  );
2956
2795
  const generateBatch = async (batchCount) => {
@@ -2960,8 +2799,8 @@ function generateRecordTool(ctx) {
2960
2799
  const { output } = await generateText({
2961
2800
  model: anthropic(RECORD_MODEL),
2962
2801
  output: Output.object({
2963
- schema: z15.object({
2964
- records: z15.array(recordSchema).length(batchCount)
2802
+ schema: z16.object({
2803
+ records: z16.array(recordSchema).length(batchCount)
2965
2804
  })
2966
2805
  }),
2967
2806
  prompt: [
@@ -2997,18 +2836,18 @@ function generateRecordTool(ctx) {
2997
2836
  }));
2998
2837
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2999
2838
  const relPath = `${DATA_DIR}/${slug}.json`;
3000
- const resolved = resolveInRoot(ctx, relPath);
3001
- if (resolved.ok === false) return resolved.error;
3002
- if (await hasSymlinkParent(ctx, resolved.target)) {
3003
- return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2839
+ const resolved2 = resolveInRoot(ctx, relPath);
2840
+ if (resolved2.ok === false) return resolved2.error;
2841
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2842
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
3004
2843
  }
3005
- await mkdir5(dirname6(resolved.target), { recursive: true });
3006
- await writeFile5(resolved.target, JSON.stringify(records, null, 2), "utf8");
2844
+ await mkdir5(dirname6(resolved2.target), { recursive: true });
2845
+ await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
3007
2846
  logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
3008
2847
  return {
3009
2848
  filePath: relPath,
3010
2849
  count: records.length,
3011
- message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime using your language's standard JSON support \u2014 do not inline the records as literals.`
2850
+ message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) \u2014 do not inline the records as literals.`
3012
2851
  };
3013
2852
  } catch (err) {
3014
2853
  return `Error generating records: ${err.message}`;
@@ -3019,12 +2858,12 @@ function generateRecordTool(ctx) {
3019
2858
 
3020
2859
  // src/lib/tools/notifyUser.ts
3021
2860
  import { tool as tool10 } from "ai";
3022
- import z16 from "zod";
2861
+ import z17 from "zod";
3023
2862
  function notifyUserTool() {
3024
2863
  return tool10({
3025
2864
  description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
3026
- inputSchema: z16.object({
3027
- message: z16.string().describe(
2865
+ inputSchema: z17.object({
2866
+ message: z17.string().describe(
3028
2867
  "Short, plain-language description of what you are doing now."
3029
2868
  )
3030
2869
  }),
@@ -3036,6 +2875,22 @@ function notifyUserTool() {
3036
2875
  });
3037
2876
  }
3038
2877
 
2878
+ // src/lib/tools/context.ts
2879
+ var DEFAULT_TOOL_LIMITS = {
2880
+ list: 10,
2881
+ search: 10,
2882
+ read: 20,
2883
+ match: 100
2884
+ };
2885
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2886
+ return {
2887
+ root: cwd,
2888
+ cwd,
2889
+ limits,
2890
+ counts: { list: 0, search: 0, read: 0 }
2891
+ };
2892
+ }
2893
+
3039
2894
  // src/lib/tools/index.ts
3040
2895
  function withLogging(name, def) {
3041
2896
  const execute = def.execute;
@@ -3067,7 +2922,10 @@ function createTools(ctx, { output, tools }) {
3067
2922
  writeCredentialsTool(ctx)
3068
2923
  ),
3069
2924
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3070
- runShell: withLogging("runShell", runShellTool(ctx)),
2925
+ verifyImplementation: withLogging(
2926
+ "verifyImplementation",
2927
+ verifyImplementationTool()
2928
+ ),
3071
2929
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3072
2930
  notifyUser: withLogging("notifyUser", notifyUserTool())
3073
2931
  };
@@ -3102,7 +2960,7 @@ async function runAgent(req) {
3102
2960
  baseURL: PROXY_BASE_URL,
3103
2961
  fetch: proxyFetch
3104
2962
  });
3105
- const toolContext = req.toolContext ?? createToolContext();
2963
+ const toolContext = createToolContext();
3106
2964
  const readTools = ["readFile", "searchFiles", "listFiles"];
3107
2965
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3108
2966
  const instructions = [
@@ -3167,11 +3025,7 @@ async function runAgent(req) {
3167
3025
  "runAgent finished"
3168
3026
  );
3169
3027
  logger.info(
3170
- {
3171
- counts: toolContext.counts,
3172
- limits: toolContext.limits,
3173
- commandsRun: toolContext.shell.executions.length
3174
- },
3028
+ { counts: toolContext.counts, limits: toolContext.limits },
3175
3029
  "tool usage"
3176
3030
  );
3177
3031
  const toolResults = await stream.toolResults;
@@ -3189,16 +3043,16 @@ async function runAgent(req) {
3189
3043
  }
3190
3044
 
3191
3045
  // src/actions/detectLanguage.ts
3192
- import z19 from "zod";
3193
- var detectLanguageSchema = z19.object({
3194
- languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
3195
- frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
3046
+ import z20 from "zod";
3047
+ var detectLanguageSchema = z20.object({
3048
+ languages: z20.array(z20.object({ name: z20.string(), version: z20.string() })),
3049
+ frameworks: z20.array(z20.object({ name: z20.string(), version: z20.string() }))
3196
3050
  });
3197
3051
  var detectLanguage = () => runAgent({
3198
3052
  instructions: [
3199
3053
  "Analyze the codebase and determine the programming languages and frameworks used",
3200
- "If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
3201
- "If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
3054
+ "If a superset language is found, exclude the subset language. TS-over-JS.",
3055
+ "If a meta-framework is used, exclude the framework. Next-over-React.",
3202
3056
  "Return the exact version",
3203
3057
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3204
3058
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3210,31 +3064,31 @@ var detectLanguage = () => runAgent({
3210
3064
  });
3211
3065
 
3212
3066
  // src/actions/analyzeCodebase.ts
3213
- import z20 from "zod";
3067
+ import z21 from "zod";
3214
3068
  var READONLY_TOOLS = [
3215
3069
  "listFiles",
3216
3070
  "changeDirectory",
3217
3071
  "readFile",
3218
3072
  "searchFiles"
3219
3073
  ];
3220
- var ingestionAnalysisSchema = z20.object({
3221
- ingestionAnalysis: z20.array(
3222
- z20.object({
3223
- name: z20.string(),
3224
- paths: z20.array(z20.string()),
3074
+ var ingestionAnalysisSchema = z21.object({
3075
+ ingestionAnalysis: z21.array(
3076
+ z21.object({
3077
+ name: z21.string(),
3078
+ paths: z21.array(z21.string()),
3225
3079
  // indexable fields the agent found for this entity
3226
- attributes: z20.array(z20.string())
3080
+ attributes: z21.array(z21.string())
3227
3081
  })
3228
3082
  )
3229
3083
  });
3230
- var searchImplementationAnalysisSchema = z20.object({
3231
- searchImplementationAnalysis: z20.string()
3084
+ var searchImplementationAnalysisSchema = z21.object({
3085
+ searchImplementationAnalysis: z21.string()
3232
3086
  });
3233
- var verificationSchema = z20.object({
3234
- verification: z20.array(z20.string())
3087
+ var verificationSchema = z21.object({
3088
+ verification: z21.array(z21.string())
3235
3089
  });
3236
3090
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3237
- var analyzeCodebaseSchema = z20.object({
3091
+ var analyzeCodebaseSchema = z21.object({
3238
3092
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3239
3093
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3240
3094
  verification: verificationSchema.shape.verification.optional(),
@@ -3296,7 +3150,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3296
3150
  // package.json
3297
3151
  var package_default = {
3298
3152
  name: "@algolia/wizard",
3299
- version: "0.9.0-rc.87.83",
3153
+ version: "0.9.0-rc.88.102",
3300
3154
  description: "Magically implement Algolia functionality in your codebase",
3301
3155
  type: "module",
3302
3156
  engines: {
@@ -3347,6 +3201,7 @@ var package_default = {
3347
3201
  "@hono/node-server": "^2.0.10",
3348
3202
  "@segment/analytics-node": "^3.1.0",
3349
3203
  ai: "^6.0.190",
3204
+ "cross-keychain": "^1.1.0",
3350
3205
  dotenv: "^17.4.2",
3351
3206
  hono: "^4.12.27",
3352
3207
  ink: "^7.0.5",
@@ -3414,8 +3269,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
3414
3269
  }
3415
3270
 
3416
3271
  // src/actions/confirmLanguage.ts
3417
- import z22 from "zod";
3418
- var confirmLanguageSchema = z22.object({
3272
+ import z23 from "zod";
3273
+ var confirmLanguageSchema = z23.object({
3419
3274
  languages: detectLanguageSchema.shape.languages
3420
3275
  });
3421
3276
  async function confirmLanguage(ctx) {
@@ -3436,19 +3291,17 @@ async function confirmLanguage(ctx) {
3436
3291
  }
3437
3292
 
3438
3293
  // src/actions/confirmFramework.ts
3439
- import z23 from "zod";
3440
- var confirmFrameworkSchema = z23.object({
3294
+ import z24 from "zod";
3295
+ var confirmFrameworkSchema = z24.object({
3441
3296
  frameworks: detectLanguageSchema.shape.frameworks
3442
3297
  });
3443
3298
  var CURATED_FRAMEWORKS = [
3444
3299
  "Next.js",
3445
3300
  "React",
3446
3301
  "Vue",
3447
- "Vanilla JS",
3448
- "Django",
3449
- "Laravel",
3450
- "Rails",
3451
- "Symfony"
3302
+ "Angular",
3303
+ "Svelte",
3304
+ "Vanilla JS"
3452
3305
  ];
3453
3306
  var OTHER_OPTION = "Other";
3454
3307
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3461,15 +3314,12 @@ var FRAMEWORK_ALIASES = {
3461
3314
  vuejs: "vue",
3462
3315
  angular: "angular",
3463
3316
  angularjs: "angular",
3317
+ svelte: "svelte",
3318
+ sveltekit: "svelte",
3464
3319
  vanillajs: "vanillajs",
3465
3320
  vanilla: "vanillajs",
3466
3321
  javascript: "vanillajs",
3467
- js: "vanillajs",
3468
- django: "django",
3469
- laravel: "laravel",
3470
- rails: "rails",
3471
- rubyonrails: "rails",
3472
- symfony: "symfony"
3322
+ js: "vanillajs"
3473
3323
  };
3474
3324
  var isSameFramework = (a, b) => {
3475
3325
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3570,8 +3420,8 @@ async function promptUser(ctx, params) {
3570
3420
  }
3571
3421
 
3572
3422
  // src/actions/confirmEntities.ts
3573
- import z24 from "zod";
3574
- var confirmEntitiesSchema = z24.object({
3423
+ import z25 from "zod";
3424
+ var confirmEntitiesSchema = z25.object({
3575
3425
  // Final detection — the focused re-run may supersede project-scan's.
3576
3426
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3577
3427
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3641,15 +3491,15 @@ async function confirmEntities(ctx) {
3641
3491
  }
3642
3492
 
3643
3493
  // src/actions/review.ts
3644
- import { z as z25 } from "zod";
3645
- var reviewSchema = z25.object({
3494
+ import { z as z26 } from "zod";
3495
+ var reviewSchema = z26.object({
3646
3496
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3647
3497
  // not one entry per workflow step — a step's raw output can be a long,
3648
3498
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3649
3499
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3650
- summaryPoints: z25.array(z25.string()),
3651
- reviewPrompt: z25.string(),
3652
- nextSteps: z25.array(z25.string())
3500
+ summaryPoints: z26.array(z26.string()),
3501
+ reviewPrompt: z26.string(),
3502
+ nextSteps: z26.array(z26.string())
3653
3503
  });
3654
3504
  function formatCompletedSteps(steps) {
3655
3505
  if (!steps.length) return "(no prior steps completed)";
@@ -3700,12 +3550,19 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3700
3550
  };
3701
3551
 
3702
3552
  // src/actions/implement.ts
3703
- import z26 from "zod";
3553
+ import z27 from "zod";
3704
3554
 
3705
3555
  // src/lib/worktree.ts
3706
- import { execFile } from "node:child_process";
3707
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3708
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3556
+ import { execFile, spawn as spawn3 } from "node:child_process";
3557
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3558
+ import {
3559
+ basename as basename2,
3560
+ dirname as dirname7,
3561
+ isAbsolute as isAbsolute2,
3562
+ join as join9,
3563
+ relative as relative2,
3564
+ resolve as resolve3
3565
+ } from "node:path";
3709
3566
  var MAX_BUFFER = 32 * 1024 * 1024;
3710
3567
  var MAX_WIZARD_WORKTREES = 3;
3711
3568
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3736,7 +3593,7 @@ async function isWorkingTreeDirty(repoRoot) {
3736
3593
  return out.trim().length > 0;
3737
3594
  }
3738
3595
  async function pruneOldWorktrees(repoRoot) {
3739
- const dir = join8(stateDir(repoRoot), "worktrees");
3596
+ const dir = join9(stateDir(repoRoot), "worktrees");
3740
3597
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3741
3598
  for (const slug of stale) {
3742
3599
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3747,7 +3604,7 @@ async function pruneOldWorktrees(repoRoot) {
3747
3604
  "worktree",
3748
3605
  "remove",
3749
3606
  "--force",
3750
- join8(dir, slug)
3607
+ join9(dir, slug)
3751
3608
  ]);
3752
3609
  await git(["-C", repoRoot, "branch", "-D", branch]);
3753
3610
  } catch (err) {
@@ -3761,13 +3618,113 @@ async function pruneOldWorktrees(repoRoot) {
3761
3618
  async function createWorktree(repoRoot) {
3762
3619
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3763
3620
  const dirSlug = branch.replace(/\//g, "-");
3764
- const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3621
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3765
3622
  await git(["-C", repoRoot, "worktree", "prune"]);
3766
3623
  await pruneOldWorktrees(repoRoot);
3767
3624
  await mkdir6(dirname7(path), { recursive: true });
3768
3625
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3769
3626
  return { path, branch };
3770
3627
  }
3628
+ async function installWorktreeDeps(worktreePath) {
3629
+ try {
3630
+ await readPackageJson(worktreePath);
3631
+ } catch {
3632
+ return { ok: true, output: "no package.json; skipped install" };
3633
+ }
3634
+ const pm = await detectPackageManager(worktreePath);
3635
+ return new Promise((resolve4) => {
3636
+ let output = "";
3637
+ const child = spawn3(pm, ["install"], {
3638
+ cwd: worktreePath,
3639
+ stdio: ["ignore", "pipe", "pipe"]
3640
+ });
3641
+ child.stdout?.on("data", (d) => output += d);
3642
+ child.stderr?.on("data", (d) => output += d);
3643
+ child.on(
3644
+ "error",
3645
+ (err) => resolve4({
3646
+ ok: false,
3647
+ output: `Failed to run ${pm} install: ${err.message}`
3648
+ })
3649
+ );
3650
+ child.on(
3651
+ "close",
3652
+ (code) => resolve4({ ok: code === 0, output: output.trim() })
3653
+ );
3654
+ });
3655
+ }
3656
+ var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3657
+ function validateIngestEntrypoint(worktreePath, entrypoint) {
3658
+ if (!entrypoint || entrypoint.startsWith("-")) {
3659
+ return {
3660
+ ok: false,
3661
+ reason: `entrypoint "${entrypoint}" is not a plain file path`
3662
+ };
3663
+ }
3664
+ const target = resolve3(worktreePath, entrypoint);
3665
+ const rel = relative2(worktreePath, target);
3666
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
3667
+ return {
3668
+ ok: false,
3669
+ reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3670
+ };
3671
+ }
3672
+ return { ok: true, target };
3673
+ }
3674
+ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3675
+ if (!INGEST_RUNTIMES.includes(runtime)) {
3676
+ return {
3677
+ ran: false,
3678
+ ok: false,
3679
+ output: "",
3680
+ reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3681
+ };
3682
+ }
3683
+ const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3684
+ if (!validated.ok) {
3685
+ return { ran: false, ok: false, output: "", reason: validated.reason };
3686
+ }
3687
+ try {
3688
+ if (!(await stat2(validated.target)).isFile()) {
3689
+ return {
3690
+ ran: false,
3691
+ ok: false,
3692
+ output: "",
3693
+ reason: `entrypoint "${entrypoint}" is not a file`
3694
+ };
3695
+ }
3696
+ } catch {
3697
+ return {
3698
+ ran: false,
3699
+ ok: false,
3700
+ output: "",
3701
+ reason: `entrypoint "${entrypoint}" does not exist`
3702
+ };
3703
+ }
3704
+ return new Promise((resolveRun) => {
3705
+ let output = "";
3706
+ const child = spawn3(runtime, [entrypoint], {
3707
+ cwd: worktreePath,
3708
+ shell: false,
3709
+ stdio: ["ignore", "pipe", "pipe"],
3710
+ env: { ...process.env, ...env }
3711
+ });
3712
+ child.stdout?.on("data", (d) => output += d);
3713
+ child.stderr?.on("data", (d) => output += d);
3714
+ child.on(
3715
+ "error",
3716
+ (err) => resolveRun({
3717
+ ran: true,
3718
+ ok: false,
3719
+ output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3720
+ })
3721
+ );
3722
+ child.on(
3723
+ "close",
3724
+ (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3725
+ );
3726
+ });
3727
+ }
3771
3728
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3772
3729
  const trimmed = sourcePath.trim();
3773
3730
  if (!trimmed) {
@@ -3781,8 +3738,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3781
3738
  } catch {
3782
3739
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3783
3740
  }
3784
- const relPath = join8(ingestDir, basename2(source));
3785
- const dest = join8(worktreePath, relPath);
3741
+ const relPath = join9(ingestDir, basename2(source));
3742
+ const dest = join9(worktreePath, relPath);
3786
3743
  try {
3787
3744
  await mkdir6(dirname7(dest), { recursive: true });
3788
3745
  await copyFile(source, dest);
@@ -3800,7 +3757,7 @@ function hasEnvVar(content, name) {
3800
3757
  async function readEnvVar(worktreePath, name) {
3801
3758
  let content;
3802
3759
  try {
3803
- content = await readFile6(join8(worktreePath, ".env"), "utf8");
3760
+ content = await readFile7(join9(worktreePath, ".env"), "utf8");
3804
3761
  } catch (err) {
3805
3762
  if (err.code !== "ENOENT") throw err;
3806
3763
  return void 0;
@@ -3815,10 +3772,10 @@ async function readEnvVar(worktreePath, name) {
3815
3772
  return value;
3816
3773
  }
3817
3774
  async function writeSearchEnvValues(worktreePath, vars) {
3818
- const target = join8(worktreePath, ".env");
3775
+ const target = join9(worktreePath, ".env");
3819
3776
  let existing = "";
3820
3777
  try {
3821
- existing = await readFile6(target, "utf8");
3778
+ existing = await readFile7(target, "utf8");
3822
3779
  } catch (err) {
3823
3780
  if (err.code !== "ENOENT") throw err;
3824
3781
  }
@@ -3887,15 +3844,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3887
3844
  }
3888
3845
 
3889
3846
  // src/lib/algoliaDocs.ts
3890
- import { readFileSync, readdirSync, existsSync } from "node:fs";
3891
- import { dirname as dirname8, join as join9 } from "node:path";
3847
+ import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3848
+ import { dirname as dirname8, join as join10 } from "node:path";
3892
3849
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3893
- var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3850
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3894
3851
  function findDocsDir() {
3895
3852
  let dir = dirname8(fileURLToPath2(import.meta.url));
3896
3853
  for (; ; ) {
3897
- const candidate = join9(dir, DOCS_SUBPATH);
3898
- if (existsSync(candidate)) return candidate;
3854
+ const candidate = join10(dir, DOCS_SUBPATH);
3855
+ if (existsSync2(candidate)) return candidate;
3899
3856
  const parent = dirname8(dir);
3900
3857
  if (parent === dir) return void 0;
3901
3858
  dir = parent;
@@ -3917,7 +3874,7 @@ function loadAlgoliaDoc(language) {
3917
3874
  );
3918
3875
  return "";
3919
3876
  }
3920
- return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3877
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3921
3878
  }
3922
3879
  function getNamedDoc(name, language) {
3923
3880
  const docsDir = findDocsDir();
@@ -3925,15 +3882,14 @@ function getNamedDoc(name, language) {
3925
3882
  logger.warn("docs/algolia-sdk not found");
3926
3883
  return "";
3927
3884
  }
3928
- const file = join9(docsDir, `${name}-${language}.md`);
3929
- if (!existsSync(file)) {
3885
+ const file = join10(docsDir, `${name}-${language}.md`);
3886
+ if (!existsSync2(file)) {
3930
3887
  logger.warn({ name, language }, "named SDK reference not found");
3931
3888
  return "";
3932
3889
  }
3933
3890
  return readFileSync(file, "utf8").trim();
3934
3891
  }
3935
3892
  function getFrameworkSpecificDoc(frameworks) {
3936
- if (frameworks.length === 0) return "";
3937
3893
  const fw = frameworks.map((f) => f.toLowerCase());
3938
3894
  if (fw.includes("vue") || fw.includes("nuxt")) {
3939
3895
  return loadAlgoliaDoc("vue");
@@ -3941,6 +3897,9 @@ function getFrameworkSpecificDoc(frameworks) {
3941
3897
  if (fw.includes("react") || fw.includes("next.js")) {
3942
3898
  return loadAlgoliaDoc("react");
3943
3899
  }
3900
+ if (fw.includes("angular")) {
3901
+ return loadAlgoliaDoc("angular");
3902
+ }
3944
3903
  return loadAlgoliaDoc("js");
3945
3904
  }
3946
3905
 
@@ -3950,63 +3909,62 @@ function shellQuote(value) {
3950
3909
  }
3951
3910
 
3952
3911
  // src/actions/implement.ts
3953
- var implementSchema = z26.object({
3954
- filesChanged: z26.array(z26.string()),
3955
- summary: z26.string(),
3956
- worktreePath: z26.string().optional(),
3957
- ingestCommand: z26.string().optional(),
3958
- ingestScriptRan: z26.boolean().optional(),
3959
- ingestRecordCount: z26.number().optional(),
3960
- ingestDurationMs: z26.number().optional(),
3961
- ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
3962
- searchEnvVars: z26.array(
3963
- z26.object({
3964
- name: z26.string(),
3965
- value: z26.string()
3912
+ var implementSchema = z27.object({
3913
+ filesChanged: z27.array(z27.string()),
3914
+ summary: z27.string(),
3915
+ worktreePath: z27.string().optional(),
3916
+ ingestCommand: z27.string().optional(),
3917
+ ingestScriptRan: z27.boolean().optional(),
3918
+ ingestRecordCount: z27.number().optional(),
3919
+ ingestDurationMs: z27.number().optional(),
3920
+ ingestionSource: z27.enum(["local", "fileUpload", "generated"]),
3921
+ searchEnvVars: z27.array(
3922
+ z27.object({
3923
+ name: z27.string(),
3924
+ value: z27.string()
3966
3925
  })
3967
3926
  ).optional()
3968
3927
  });
3969
- var implementationOutputSchema = z26.object({
3970
- summary: z26.string(),
3971
- ingestCommand: z26.string().optional()
3928
+ var implementationOutputSchema = z27.object({
3929
+ summary: z27.string(),
3930
+ // Ingestion only: a structured pair the wizard turns into an argv, never a
3931
+ // free-form command string. `runtime` is allowlisted and `entrypoint` is
3932
+ // validated worktree-relative, so the agent cannot inject extra commands.
3933
+ runtime: z27.enum(INGEST_RUNTIMES).optional(),
3934
+ entrypoint: z27.string().optional()
3972
3935
  });
3973
- var verificationOutputSchema = z26.object({
3974
- summary: z26.string(),
3975
- sufficient: z26.boolean(),
3976
- additionalInstructions: z26.string().optional()
3936
+ var verificationOutputSchema = z27.object({
3937
+ summary: z27.string(),
3938
+ sufficient: z27.boolean(),
3939
+ additionalInstructions: z27.string().optional()
3977
3940
  });
3978
3941
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3979
3942
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3980
3943
  var INGEST_DIR = ".algolia-wizard";
3981
- var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
3982
- function lower(entries) {
3983
- return entries.map((entry) => entry.name.toLowerCase());
3984
- }
3985
- function isJsProject(language) {
3986
- return lower(language.languages).some(
3987
- (name) => JS_LANGUAGES.some((js) => name.includes(js))
3988
- );
3989
- }
3990
- var UI_FRAMEWORKS = [
3991
- { match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
3992
- { match: ["react", "next"], target: "React", doc: "react" },
3993
- { match: ["angular"], target: "Angular" }
3994
- ];
3995
- function matchUiFramework(language) {
3996
- const names = lower(language.frameworks);
3997
- return UI_FRAMEWORKS.find(
3998
- (ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
3999
- );
4000
- }
4001
- function searchUiTarget(language) {
4002
- return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
4003
- }
4004
- function frameworksForDoc(language) {
4005
- if (!isJsProject(language)) return [];
4006
- return [matchUiFramework(language)?.doc ?? "js"];
3944
+ function detectUiFramework(language) {
3945
+ const names = language.frameworks.map((f) => f.name.toLowerCase());
3946
+ if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3947
+ if (names.some((n) => n.includes("react") || n.includes("next")))
3948
+ return "React";
3949
+ if (names.some((n) => n.includes("angular"))) return "Angular";
3950
+ return "JavaScript";
3951
+ }
3952
+ function frameworksForDoc(framework) {
3953
+ switch (framework) {
3954
+ case "React":
3955
+ return ["react"];
3956
+ case "Vue":
3957
+ return ["vue"];
3958
+ case "Angular":
3959
+ return ["angular"];
3960
+ case "JavaScript":
3961
+ return [];
3962
+ }
4007
3963
  }
4008
3964
  function publicEnvPrefix(language) {
4009
- const frameworkNames = lower(language.frameworks);
3965
+ const frameworkNames = language.frameworks.map(
3966
+ (framework) => framework.name.toLowerCase()
3967
+ );
4010
3968
  if (frameworkNames.some((name) => name.includes("next"))) {
4011
3969
  return "NEXT_PUBLIC_";
4012
3970
  }
@@ -4019,17 +3977,21 @@ function publicEnvPrefix(language) {
4019
3977
  if (frameworkNames.some((name) => name.includes("vite"))) {
4020
3978
  return "VITE_";
4021
3979
  }
4022
- return isJsProject(language) ? "PUBLIC_" : "";
3980
+ return "PUBLIC_";
4023
3981
  }
4024
3982
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
4025
3983
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
3984
+ var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
4026
3985
  function appIdVar(language) {
4027
3986
  return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
4028
3987
  }
4029
3988
  function searchKeyVar(language) {
4030
3989
  return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
4031
3990
  }
4032
- function searchEnvVars(language, appId, searchKey) {
3991
+ function searchIndexVar(language) {
3992
+ return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
3993
+ }
3994
+ function searchEnvVars(language, index, appId, searchKey) {
4033
3995
  return [
4034
3996
  {
4035
3997
  name: appIdVar(language),
@@ -4038,17 +4000,23 @@ function searchEnvVars(language, appId, searchKey) {
4038
4000
  {
4039
4001
  name: searchKeyVar(language),
4040
4002
  value: searchKey ?? "<your-algolia-search-only-api-key>"
4003
+ },
4004
+ // Wizard-supplied rather than written into the generated code, because an
4005
+ // agent that retypes the name (appending the project name, re-casing it)
4006
+ // leaves the UI querying an index that does not exist.
4007
+ {
4008
+ name: searchIndexVar(language),
4009
+ value: index
4041
4010
  }
4042
4011
  ];
4043
4012
  }
4044
4013
  function baseInstructions(input) {
4045
4014
  return [
4046
- `Target Algolia index: ${input.targetIndex}`,
4015
+ // Agents have renamed this (e.g. appending the project name), which the
4016
+ // index-scoped keys then reject with a 403.
4017
+ `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4047
4018
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4048
- "Make minimal, idiomatic changes; do not touch unrelated code.",
4049
- `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4050
- "Use the project's own tooling for every command \u2014 its package manager, task runner, and test/lint commands. Do not assume a JavaScript toolchain.",
4051
- "runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
4019
+ "Make minimal, idiomatic changes; do not touch unrelated code."
4052
4020
  ];
4053
4021
  }
4054
4022
  function sourceSpecificInstructions(input) {
@@ -4068,50 +4036,38 @@ function sourceSpecificInstructions(input) {
4068
4036
  generated: [
4069
4037
  "No real data source exists; use sample records for each confirmed entity.",
4070
4038
  "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.",
4071
- "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.",
4039
+ "In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
4072
4040
  "Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
4073
4041
  ]
4074
4042
  };
4075
4043
  return byLine[input.ingestionSource];
4076
4044
  }
4077
- function algoliaClientDoc(input) {
4078
- const doc = getNamedDoc("save-records", "js");
4079
- if (!doc) return [];
4080
- if (isJsProject(input.language)) return [doc];
4081
- return [
4082
- "The reference below is written in JavaScript. Use it for the method names, arguments, and record shape, then translate to this project's language and its official Algolia client:",
4083
- doc
4084
- ];
4085
- }
4086
4045
  function ingestionInstructions(input) {
4087
4046
  return [
4088
4047
  ...input.confirmed && input.confirmed.length ? [
4089
4048
  `Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
4090
4049
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4091
4050
  `Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
4092
- "Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
4051
+ `Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
4052
+ "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4093
4053
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
4094
- ...algoliaClientDoc(input),
4095
- "Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
4096
- 'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
4054
+ getNamedDoc("save-records", "js"),
4055
+ 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4097
4056
  "The summary should be extremely concise.",
4057
+ `Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
4098
4058
  ...sourceSpecificInstructions(input)
4099
4059
  ] : []
4100
4060
  ];
4101
4061
  }
4102
4062
  function searchInstructions(input) {
4103
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4063
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4104
4064
  return [
4105
4065
  "Implement an in-app Algolia search experience.",
4106
- `Build the search UI for ${input.searchUiTarget}.`,
4107
- ...doc ? [
4108
- "Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
4109
- doc
4110
- ] : [
4111
- "No bundled Algolia SDK reference exists for this stack, so rely on the project's own conventions and Algolia's official client for its language. Do not invent APIs \u2014 keep to the documented search endpoint and its parameters."
4112
- ],
4113
- `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working search input and results list against the "${input.targetIndex}" index.`,
4114
- "Read the App ID and a search-only API key from env vars; never hardcode them. A search-only key is safe to expose client-side.",
4066
+ `Build the search UI for ${input.uiFramework}.`,
4067
+ "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4068
+ doc,
4069
+ `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
4070
+ `Read the index name from the ${searchIndexVar(input.language)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
4115
4071
  // The key is provisioned only after verification passes, so the agent never
4116
4072
  // sees one. It must also leave .env alone: the wizard reads that file to
4117
4073
  // decide whether a key already exists, and an agent-invented value there
@@ -4120,8 +4076,9 @@ function searchInstructions(input) {
4120
4076
  // Not the agent's to rename: the wizard writes these exact names into
4121
4077
  // ".env" right after this step, so a renamed prefix would leave the code
4122
4078
  // reading a var the wizard never wrote.
4123
- `Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4124
- "Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
4079
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4080
+ "Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
4081
+ 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4125
4082
  "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
4126
4083
  ];
4127
4084
  }
@@ -4129,12 +4086,11 @@ function verificationInstructions(input) {
4129
4086
  return [
4130
4087
  "Verify the Algolia implementation changes in the current worktree.",
4131
4088
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4132
- "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.",
4133
- "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.",
4134
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4135
- "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.",
4089
+ "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4090
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4091
+ "Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4136
4092
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4137
- `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4093
+ `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4138
4094
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4139
4095
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4140
4096
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4155,15 +4111,14 @@ var IMPLEMENT_CONFIG = {
4155
4111
  }
4156
4112
  };
4157
4113
  var useCaseToolMap = {
4158
- ingestion: [
4114
+ ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4115
+ search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4116
+ verification: [
4159
4117
  ...FS_READ_TOOLS,
4160
4118
  "writeFile",
4161
- "writeCredentials",
4162
- "runShell",
4119
+ "verifyImplementation",
4163
4120
  "notifyUser"
4164
- ],
4165
- search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4166
- verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4121
+ ]
4167
4122
  };
4168
4123
  function toolsForUseCase(useCase, ingestionSource) {
4169
4124
  const tools = useCaseToolMap[useCase];
@@ -4186,31 +4141,15 @@ function formatSummary(useCase, summary) {
4186
4141
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4187
4142
  return `${label}: ${summary}`;
4188
4143
  }
4144
+ function buildIngestCommand(worktree, runtime, entrypoint) {
4145
+ return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4146
+ }
4189
4147
  function parseIngestRecordCount(output) {
4190
4148
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4191
4149
  if (!match) return void 0;
4192
4150
  const count = Number(match[1]);
4193
4151
  return Number.isFinite(count) ? count : void 0;
4194
4152
  }
4195
- function ingestOutcome(executions, ingestCommand) {
4196
- const newestFirst = [...executions].reverse();
4197
- const withCount = newestFirst.filter(
4198
- (e) => parseIngestRecordCount(e.output ?? "") != null
4199
- );
4200
- const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4201
- return {
4202
- run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4203
- recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4204
- };
4205
- }
4206
- var PLACEHOLDER_WRITE_KEY = "ALGOLIA_WRITE_KEY_PLACEHOLDER";
4207
- function makeToolContext(worktree, env = async () => ({})) {
4208
- return createToolContext(
4209
- DEFAULT_TOOL_LIMITS,
4210
- worktree,
4211
- createShellContext({ env, approve: storeApproval(worktree) })
4212
- );
4213
- }
4214
4153
  function verificationRetryInstructions(verification) {
4215
4154
  return [
4216
4155
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4289,13 +4228,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4289
4228
  const confirmed2 = normalized.confirmedEntities;
4290
4229
  const searchLocation = normalized.searchImplementationAnalysis;
4291
4230
  let appId;
4292
- let ingestAppId;
4293
4231
  if (useCases.includes("search")) {
4294
4232
  appId = (await requireApplication()).id;
4295
4233
  }
4296
- if (useCases.includes("ingestion")) {
4297
- ingestAppId = appId ?? (await requireApplication()).id;
4298
- }
4299
4234
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4300
4235
  try {
4301
4236
  process.chdir(worktree);
@@ -4328,11 +4263,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4328
4263
  appId,
4329
4264
  // Names only: the search-only key is provisioned after verification, so
4330
4265
  // every value here is still a placeholder when the agent reads them.
4331
- searchEnvVars: searchEnvVars(language, appId),
4266
+ searchEnvVars: searchEnvVars(language, targetIndex, appId),
4332
4267
  ingestDir: INGEST_DIR,
4333
4268
  ingestionSource,
4334
4269
  uploadFilePath,
4335
- searchUiTarget: searchUiTarget(language)
4270
+ uiFramework: detectUiFramework(language)
4336
4271
  };
4337
4272
  const summaries = [];
4338
4273
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4355,29 +4290,41 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4355
4290
  }
4356
4291
  let finalSearchEnvVars = input.searchEnvVars;
4357
4292
  let agentRuns = 0;
4358
- let ingestCommand;
4293
+ let ingestRuntime;
4294
+ let ingestEntrypoint;
4359
4295
  let ingestScriptRan = false;
4360
4296
  let ingestRecordCount;
4361
4297
  let ingestDurationMs;
4298
+ let installFailed = false;
4362
4299
  let ingestOutcomeMessage;
4363
- const ingestionTools = ingestAppId ? makeToolContext(worktree, async () => ({
4364
- [APP_ID_VAR]: ingestAppId,
4365
- [API_KEY_VAR]: PLACEHOLDER_WRITE_KEY
4366
- })) : void 0;
4367
- const searchTools = makeToolContext(worktree);
4368
4300
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4369
4301
  if (agentRuns > 0) ctx.recordStepExecution();
4370
4302
  agentRuns += 1;
4371
- return runAgent({
4303
+ const result = await runAgent({
4372
4304
  instructions: buildAgentInstructions(
4373
4305
  currentUseCase,
4374
4306
  input,
4375
4307
  extraInstructions
4376
4308
  ),
4377
4309
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4378
- outputSchema: implementationOutputSchema,
4379
- toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4310
+ outputSchema: implementationOutputSchema
4311
+ });
4312
+ ctx.notify({
4313
+ messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4314
+ });
4315
+ const installLogId = ctx.logStart("installWorktreeDeps", {
4316
+ useCase: currentUseCase
4380
4317
  });
4318
+ const install = await installWorktreeDeps(worktree);
4319
+ ctx.logEnd(installLogId, install.ok ? "success" : "error");
4320
+ if (!install.ok) {
4321
+ installFailed = true;
4322
+ logger.warn(
4323
+ { useCase: currentUseCase, output: install.output },
4324
+ "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4325
+ );
4326
+ }
4327
+ return result;
4381
4328
  }
4382
4329
  async function runVerificationUseCase() {
4383
4330
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4385,55 +4332,111 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4385
4332
  return runAgent({
4386
4333
  instructions: buildAgentInstructions("verification", input),
4387
4334
  tools: toolsForUseCase("verification"),
4388
- outputSchema: verificationOutputSchema,
4389
- toolContext: searchTools
4335
+ outputSchema: verificationOutputSchema
4390
4336
  });
4391
4337
  }
4392
4338
  if (useCases.includes("ingestion")) {
4393
- const result = await runImplementationUseCase("ingestion");
4394
- summaries.push(formatSummary("ingestion", result.summary));
4395
- ingestCommand = result.ingestCommand;
4396
- const executions = (ingestionTools ?? searchTools).shell.executions;
4397
- const { run: ingestRun, recordCount } = ingestOutcome(
4398
- executions,
4399
- ingestCommand
4400
- );
4401
- ingestScriptRan = ingestRun != null;
4402
- ingestRecordCount = recordCount;
4403
- ingestDurationMs = ingestRun?.durationMs;
4404
- if (ingestScriptRan) {
4405
- ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4406
- if (ingestRecordCount != null) {
4407
- track("AI Wizard Ingest Successful", {
4408
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4409
- record_count: ingestRecordCount,
4410
- duration_ms: ingestDurationMs ?? 0
4339
+ const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4340
+ summaries.push(formatSummary("ingestion", summary));
4341
+ ingestRuntime = runtime;
4342
+ ingestEntrypoint = entrypoint;
4343
+ if (ingestRuntime && ingestEntrypoint && !installFailed) {
4344
+ ctx.clearNotices();
4345
+ const runNow = await ctx.requestUserInput({
4346
+ prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4347
+ promptType: "acceptReject",
4348
+ options: ["Yes", "No"],
4349
+ messages: []
4350
+ }) === true;
4351
+ if (runNow) {
4352
+ const ingestApp = await requireApplication();
4353
+ const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4354
+ ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4355
+ const scriptLogId = ctx.logStart("runIngestScript", {
4356
+ runtime: ingestRuntime,
4357
+ entrypoint: ingestEntrypoint
4411
4358
  });
4359
+ const startedAt = Date.now();
4360
+ const run2 = await runIngestScript(
4361
+ worktree,
4362
+ ingestRuntime,
4363
+ ingestEntrypoint,
4364
+ {
4365
+ [APP_ID_VAR]: ingestApp.id,
4366
+ [API_KEY_VAR]: writeKey,
4367
+ [INDEX_NAME_VAR]: targetIndex
4368
+ }
4369
+ );
4370
+ ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4371
+ ingestScriptRan = run2.ran && run2.ok;
4372
+ if (ingestScriptRan) {
4373
+ ingestDurationMs = Date.now() - startedAt;
4374
+ ingestRecordCount = parseIngestRecordCount(run2.output);
4375
+ if (ingestRecordCount != null) {
4376
+ track("AI Wizard Ingest Successful", {
4377
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4378
+ record_count: ingestRecordCount,
4379
+ duration_ms: ingestDurationMs
4380
+ });
4381
+ }
4382
+ }
4383
+ let summaryLine;
4384
+ let outcomeMessage;
4385
+ if (!run2.ran) {
4386
+ summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4387
+ outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4388
+ logger.warn(
4389
+ {
4390
+ runtime: ingestRuntime,
4391
+ entrypoint: ingestEntrypoint,
4392
+ reason: run2.reason
4393
+ },
4394
+ "implement: refused to auto-run ingestion script"
4395
+ );
4396
+ track("Error", {
4397
+ step: "Push Data",
4398
+ error: `ingestion script skipped: ${run2.reason}`,
4399
+ product_area: "AI Wizard"
4400
+ });
4401
+ } else if (run2.ok) {
4402
+ const status = "Ingestion run: succeeded.";
4403
+ summaryLine = run2.output ? `${status}
4404
+ ${run2.output}` : status;
4405
+ outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4406
+ } else {
4407
+ const status = "\u26A0\uFE0F Ingestion run failed:";
4408
+ summaryLine = run2.output ? `${status}
4409
+ ${run2.output}` : status;
4410
+ outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4411
+ logger.warn(
4412
+ {
4413
+ runtime: ingestRuntime,
4414
+ entrypoint: ingestEntrypoint,
4415
+ output: run2.output
4416
+ },
4417
+ "implement: ingestion script run failed"
4418
+ );
4419
+ track("Error", {
4420
+ step: "Push Data",
4421
+ error: run2.output || "ingestion script exited non-zero",
4422
+ product_area: "AI Wizard"
4423
+ });
4424
+ }
4425
+ summaries.push(summaryLine);
4426
+ ingestOutcomeMessage = outcomeMessage;
4412
4427
  }
4413
- } else {
4414
- const rejected = executions.some((e) => !e.approved);
4415
- const reason = rejected ? "you declined to run it" : "no successful run was recorded";
4416
- ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4417
- summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
4418
- logger.warn(
4419
- { ingestCommand, rejected, commandsRun: executions.length },
4420
- "implement: ingestion script did not complete successfully"
4421
- );
4422
- track("Error", {
4423
- step: "Push Data",
4424
- error: `ingestion did not run: ${reason}`,
4425
- product_area: "AI Wizard"
4426
- });
4427
4428
  }
4428
4429
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4429
- if (ingestCommand) {
4430
- commandMessages.push(`Ingestion command: ${ingestCommand}`);
4430
+ if (ingestRuntime && ingestEntrypoint) {
4431
+ commandMessages.push(
4432
+ `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4433
+ );
4431
4434
  }
4432
4435
  await ctx.requestUserInput({
4433
4436
  prompt: "",
4434
4437
  promptType: "enterToContinue",
4435
4438
  options: [],
4436
- messages: [ingestOutcomeMessage, ...commandMessages]
4439
+ messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4437
4440
  });
4438
4441
  }
4439
4442
  if (useCases.includes("search")) {
@@ -4482,14 +4485,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4482
4485
  let searchKeyError;
4483
4486
  if (appId) {
4484
4487
  try {
4485
- const resolved = await resolveSearchOnlyKey(
4488
+ const resolved2 = await resolveSearchOnlyKey(
4486
4489
  targetIndex,
4487
4490
  appId,
4488
4491
  envSearchKey
4489
4492
  );
4490
- searchKey = resolved.key;
4493
+ searchKey = resolved2.key;
4491
4494
  summaries.push(
4492
- resolved.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}.`
4495
+ 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}.`
4493
4496
  );
4494
4497
  } catch (err) {
4495
4498
  searchKeyError = err.message;
@@ -4499,7 +4502,12 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4499
4502
  );
4500
4503
  }
4501
4504
  }
4502
- finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
4505
+ finalSearchEnvVars = searchEnvVars(
4506
+ language,
4507
+ targetIndex,
4508
+ appId,
4509
+ searchKey
4510
+ );
4503
4511
  const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4504
4512
  (v) => !v.value.startsWith("<")
4505
4513
  );
@@ -4545,13 +4553,22 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4545
4553
  "implement: agent reported success but no files changed in the worktree"
4546
4554
  );
4547
4555
  }
4556
+ if (installFailed) {
4557
+ summaries.push(
4558
+ '\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
4559
+ );
4560
+ }
4548
4561
  return {
4549
4562
  ingestionSource,
4550
4563
  filesChanged,
4551
4564
  summary: summaries.join("\n\n"),
4552
4565
  worktreePath: worktree,
4553
- ...useCases.includes("ingestion") && ingestCommand ? {
4554
- ingestCommand,
4566
+ ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4567
+ ingestCommand: buildIngestCommand(
4568
+ worktree,
4569
+ ingestRuntime,
4570
+ ingestEntrypoint
4571
+ ),
4555
4572
  ingestScriptRan,
4556
4573
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4557
4574
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4599,8 +4616,8 @@ var defaultWorkflow = {
4599
4616
  defineStep({
4600
4617
  id: "select-index",
4601
4618
  title: "Set up index",
4602
- outputSchema: z27.object({
4603
- selection: z27.string()
4619
+ outputSchema: z28.object({
4620
+ selection: z28.string()
4604
4621
  }),
4605
4622
  run: (ctx) => selectIndexStep(ctx)
4606
4623
  }),
@@ -4842,7 +4859,10 @@ Options:
4842
4859
  --no-telemetry Send no telemetry or analytics for this run.
4843
4860
  --reset-on-run Wipe this project's wizard state (run state, AI consent,
4844
4861
  worktrees) before starting, so the run behaves like a
4845
- first-ever run. Algolia credentials are not touched.
4862
+ first-ever run. Also drops every API key the wizard has
4863
+ stored in your keychain, for this project and any other, so
4864
+ later runs create new ones. Your Algolia login is not
4865
+ touched.
4846
4866
  -h, --help Print this message.`;
4847
4867
  function parseCliArgs(argv) {
4848
4868
  const positionals = [];
@@ -4879,10 +4899,11 @@ function parseCliArgs(argv) {
4879
4899
 
4880
4900
  // src/lib/resetState.ts
4881
4901
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4882
- import { join as join10 } from "node:path";
4902
+ import { join as join11 } from "node:path";
4883
4903
  var KEEP = ["wizard.log"];
4884
4904
  async function resetProjectState() {
4885
4905
  const dir = stateDir();
4906
+ await forgetResolvedKeys();
4886
4907
  let entries;
4887
4908
  try {
4888
4909
  entries = await readdir4(dir);
@@ -4891,14 +4912,17 @@ async function resetProjectState() {
4891
4912
  }
4892
4913
  const targets = entries.filter((name) => !KEEP.includes(name));
4893
4914
  await Promise.all(
4894
- targets.map((name) => rm2(join10(dir, name), { recursive: true, force: true }))
4915
+ targets.map(
4916
+ (name) => rm2(join11(dir, name), { recursive: true, force: true })
4917
+ )
4895
4918
  );
4896
4919
  return { dir, removed: targets };
4897
4920
  }
4898
4921
 
4899
4922
  // src/main.tsx
4900
- import { jsx as jsx15 } from "react/jsx-runtime";
4923
+ import { jsx as jsx14 } from "react/jsx-runtime";
4901
4924
  async function startup() {
4925
+ setProjectRoot(process.cwd());
4902
4926
  let args;
4903
4927
  try {
4904
4928
  args = parseCliArgs(process.argv.slice(2));
@@ -4946,7 +4970,7 @@ ${formatStepList(workflow)}`);
4946
4970
  }
4947
4971
  async function run(workflow) {
4948
4972
  const store = useWizard.getState();
4949
- const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4973
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4950
4974
  await store.waitForStart();
4951
4975
  let user = await getUser();
4952
4976
  if (!user) {