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

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,51 +13,15 @@ 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
  }
60
19
  var shell = process.platform === "win32";
20
+ function childEnv(withoutAdminKey) {
21
+ if (!withoutAdminKey) return void 0;
22
+ const { ALGOLIA_API_KEY: _adminKey, ...rest } = process.env;
23
+ return rest;
24
+ }
61
25
  function lineSplitter(emit) {
62
26
  let buffer = "";
63
27
  return {
@@ -81,11 +45,14 @@ var stderrSink = (stream, line) => {
81
45
  if (stream === "stdout") return;
82
46
  wizardSink(stream, line);
83
47
  };
84
- function runAlgoliaCli(args, { onOutput } = {}) {
48
+ function runAlgoliaCli(args, { onOutput, withoutAdminKey } = {}) {
85
49
  const store = useWizard.getState();
86
50
  const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
87
51
  return new Promise((resolve4, reject) => {
88
- const child = spawn("npx", npxArgs(args), { shell });
52
+ const child = spawn("npx", npxArgs(args), {
53
+ shell,
54
+ env: childEnv(withoutAdminKey)
55
+ });
89
56
  let stdout = "";
90
57
  let stderr = "";
91
58
  const splitters = {
@@ -130,7 +97,6 @@ function runAlgoliaCli(args, { onOutput } = {}) {
130
97
  },
131
98
  (err) => {
132
99
  useWizard.getState().logEnd(logId, "error");
133
- logger.warn({ err, args }, "Algolia CLI command failed");
134
100
  throw err;
135
101
  }
136
102
  );
@@ -188,6 +154,49 @@ function refreshAuthToken() {
188
154
  return inFlightRefresh;
189
155
  }
190
156
 
157
+ // src/lib/logger.ts
158
+ import pino from "pino";
159
+ import { join as join2, dirname } from "node:path";
160
+ import { devNull } from "node:os";
161
+ import { mkdirSync, openSync, closeSync } from "node:fs";
162
+
163
+ // src/core/constants.ts
164
+ import { homedir } from "node:os";
165
+ import { join, resolve } from "node:path";
166
+ function rootDir() {
167
+ return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
168
+ }
169
+ var pinnedRoot;
170
+ function setProjectRoot(cwd) {
171
+ pinnedRoot = resolve(cwd);
172
+ }
173
+ function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
174
+ return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
175
+ }
176
+ function stateDir(cwd = pinnedRoot ?? process.cwd()) {
177
+ return join(rootDir(), projectSlug(cwd));
178
+ }
179
+
180
+ // src/lib/logger.ts
181
+ var STDERR_FD = 2;
182
+ function resolveDest() {
183
+ const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
184
+ try {
185
+ mkdirSync(dirname(target), { recursive: true });
186
+ closeSync(openSync(target, "a"));
187
+ return target;
188
+ } catch {
189
+ return STDERR_FD;
190
+ }
191
+ }
192
+ function logDestination() {
193
+ return pino.destination({ dest: resolveDest(), sync: false });
194
+ }
195
+ var logger = pino(
196
+ { level: process.env.LOG_LEVEL ?? "info" },
197
+ logDestination()
198
+ );
199
+
191
200
  // src/lib/proxyFetch.ts
192
201
  var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
193
202
  var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
@@ -617,13 +626,10 @@ function Notices() {
617
626
  }
618
627
 
619
628
  // src/ui/PromptInput.tsx
620
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
629
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
621
630
  import TextInput from "ink-text-input";
622
631
  import { useState as useState5 } from "react";
623
632
 
624
- // src/ui/CommandApproval.tsx
625
- import { Box as Box5, Text as Text5, useInput } from "ink";
626
-
627
633
  // src/ui/NextAction.tsx
628
634
  import { Box as Box4, Text as Text4 } from "ink";
629
635
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -647,69 +653,14 @@ function NextAction({
647
653
  ] });
648
654
  }
649
655
 
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
656
  // src/ui/SelectPrompt.tsx
706
- import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
657
+ import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
707
658
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
708
659
 
709
660
  // src/ui/ScrollView.tsx
710
- import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
661
+ import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
711
662
  import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
712
- import { jsxs as jsxs5 } from "react/jsx-runtime";
663
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
713
664
  var INDICATOR_ROWS = 2;
714
665
  function fittedWidth(node, columns) {
715
666
  let left = 0;
@@ -779,14 +730,14 @@ function useScrollWindow({
779
730
  };
780
731
  }
781
732
  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: [
733
+ return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
734
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
784
735
  "\u2191 ",
785
736
  scroll.hiddenAbove,
786
737
  " more"
787
738
  ] }),
788
739
  children,
789
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
740
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
790
741
  "\u2193 ",
791
742
  scroll.hiddenBelow,
792
743
  " more"
@@ -795,7 +746,7 @@ function ScrollView({ scroll, children }) {
795
746
  }
796
747
 
797
748
  // src/ui/SelectPrompt.tsx
798
- import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
749
+ import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
799
750
  var CANCEL = "cancel";
800
751
  var ARROW_WIDTH = 4;
801
752
  var COLUMN_GAP = 2;
@@ -857,7 +808,7 @@ function SelectPrompt({
857
808
  revealIndex(index);
858
809
  }, [index, revealIndex]);
859
810
  const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
860
- useInput2((input, key) => {
811
+ useInput((input, key) => {
861
812
  if (rows.length === 0) return;
862
813
  if (key.upArrow || input === "k") {
863
814
  setIndex((i) => (i - 1 + rows.length) % rows.length);
@@ -880,56 +831,56 @@ function SelectPrompt({
880
831
  }
881
832
  }
882
833
  });
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 })
834
+ return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
835
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
836
+ error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
837
+ messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
838
+ table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
839
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
840
+ question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
841
+ helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
891
842
  ] })
892
843
  ] }),
893
- /* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
844
+ /* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
894
845
  const i = scroll.offset + visibleIndex;
895
846
  const highlighted = i === index;
896
847
  const isCancel = i === cancelIndex;
897
848
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
898
849
  const sec = isCancel ? void 0 : secondary?.[i];
899
850
  const labelColor = highlighted ? COLORS.highlight.fg : void 0;
900
- const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
851
+ const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
901
852
  highlighted ? "\u276F " : " ",
902
853
  bullet,
903
854
  option
904
855
  ] });
905
856
  const isText = sec?.kind === "text";
906
- return /* @__PURE__ */ jsxs6(
907
- Box7,
857
+ return /* @__PURE__ */ jsxs5(
858
+ Box6,
908
859
  {
909
860
  width: isText ? "100%" : barWidth,
910
861
  paddingX: 1,
911
862
  paddingY: 1,
912
863
  backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
913
864
  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,
865
+ /* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
866
+ isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
867
+ Text6,
917
868
  {
918
869
  wrap: "truncate",
919
870
  color: highlighted ? COLORS.primary : COLORS.muted,
920
871
  children: sec.value
921
872
  }
922
873
  ) }),
923
- sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
874
+ sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
924
875
  ]
925
876
  },
926
877
  `row-${i}`
927
878
  );
928
879
  }) }),
929
- /* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
880
+ /* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
930
881
  i > 0 ? " " : "",
931
- /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
932
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
882
+ /* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
883
+ /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
933
884
  " ",
934
885
  label
935
886
  ] })
@@ -938,23 +889,23 @@ function SelectPrompt({
938
889
  }
939
890
 
940
891
  // src/ui/PromptInput.tsx
941
- import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
892
+ import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
942
893
  var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
943
894
  function EnterToContinuePrompt({
944
895
  question,
945
896
  messages,
946
897
  onDecide
947
898
  }) {
948
- useInput3((_input, key) => {
899
+ useInput2((_input, key) => {
949
900
  if (key.return) onDecide(true);
950
901
  else if (key.escape) onDecide(false);
951
902
  });
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" })
903
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
904
+ messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
905
+ question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
906
+ /* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
907
+ /* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
908
+ /* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
958
909
  ] })
959
910
  ] });
960
911
  }
@@ -962,11 +913,11 @@ function PromptInput() {
962
913
  const { phase, inputReq, submitInput } = useWizard();
963
914
  const [draft, setDraft] = useState5("");
964
915
  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" }) });
916
+ return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
966
917
  }
967
918
  if (phase !== "awaitingInput" || !inputReq) return null;
968
919
  if (inputReq.promptType === "multipleChoice") {
969
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
920
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
970
921
  SelectPrompt,
971
922
  {
972
923
  question: inputReq.prompt,
@@ -983,7 +934,7 @@ function PromptInput() {
983
934
  ) });
984
935
  }
985
936
  if (inputReq.promptType === "multiSelect") {
986
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
937
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
987
938
  SelectPrompt,
988
939
  {
989
940
  multi: true,
@@ -998,7 +949,7 @@ function PromptInput() {
998
949
  ) });
999
950
  }
1000
951
  if (inputReq.promptType === "notice") {
1001
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
952
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1002
953
  SelectPrompt,
1003
954
  {
1004
955
  question: inputReq.prompt,
@@ -1009,7 +960,7 @@ function PromptInput() {
1009
960
  ) });
1010
961
  }
1011
962
  if (inputReq.promptType === "enterToContinue") {
1012
- return /* @__PURE__ */ jsx6(
963
+ return /* @__PURE__ */ jsx5(
1013
964
  EnterToContinuePrompt,
1014
965
  {
1015
966
  question: inputReq.prompt,
@@ -1018,12 +969,9 @@ function PromptInput() {
1018
969
  }
1019
970
  );
1020
971
  }
1021
- if (inputReq.promptType === "commandApproval" && inputReq.command) {
1022
- return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
1023
- }
1024
972
  if (inputReq.promptType === "acceptReject") {
1025
973
  const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
1026
- return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
974
+ return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
1027
975
  SelectPrompt,
1028
976
  {
1029
977
  question: inputReq.prompt,
@@ -1034,15 +982,15 @@ function PromptInput() {
1034
982
  }
1035
983
  ) });
1036
984
  }
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: [
985
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
986
+ inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
987
+ inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
988
+ /* @__PURE__ */ jsxs6(Box7, { children: [
989
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
1042
990
  inputReq.prompt,
1043
991
  " "
1044
992
  ] }),
1045
- /* @__PURE__ */ jsx6(
993
+ /* @__PURE__ */ jsx5(
1046
994
  TextInput,
1047
995
  {
1048
996
  value: draft,
@@ -1060,7 +1008,7 @@ function PromptInput() {
1060
1008
  // src/ui/Welcome.tsx
1061
1009
  import { dirname as dirname2, join as join3 } from "node:path";
1062
1010
  import { fileURLToPath } from "node:url";
1063
- import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
1011
+ import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
1064
1012
 
1065
1013
  // src/ui/copy/welcome.ts
1066
1014
  var sidebarItems = [
@@ -1073,12 +1021,12 @@ var sidebarItems = [
1073
1021
  description: "push 100 records to Algolia in seconds"
1074
1022
  },
1075
1023
  {
1076
- title: "detect your stack",
1077
- description: "whatever language and framework you already use"
1024
+ title: "detect your framework",
1025
+ description: "React, Vue, Angular, Vanilla JS"
1078
1026
  },
1079
1027
  {
1080
1028
  title: "scaffold a search UI",
1081
- description: "a search box and results, wired into your app"
1029
+ description: "a styled InstantSearch component, wired into your app"
1082
1030
  },
1083
1031
  {
1084
1032
  title: "ship it",
@@ -1088,20 +1036,20 @@ var sidebarItems = [
1088
1036
 
1089
1037
  // src/ui/Welcome.tsx
1090
1038
  import Image, { InkPictureProvider } from "ink-picture";
1091
- import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1039
+ import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
1092
1040
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1093
1041
  function SidebarItem({
1094
1042
  title,
1095
1043
  description
1096
1044
  }) {
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 })
1045
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1046
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
1047
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
1048
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
1101
1049
  ] }),
1102
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
1103
- /* @__PURE__ */ jsx7(Spacer, {}),
1104
- /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
1050
+ /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
1051
+ /* @__PURE__ */ jsx6(Spacer, {}),
1052
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
1105
1053
  ] })
1106
1054
  ] });
1107
1055
  }
@@ -1109,7 +1057,7 @@ function Welcome() {
1109
1057
  const confirmStart = useWizard((s) => s.confirmStart);
1110
1058
  const openLearnMore = useWizard((s) => s.openLearnMore);
1111
1059
  const { rows } = useWindowSize6();
1112
- useInput4((input, key) => {
1060
+ useInput3((input, key) => {
1113
1061
  if (key.return) confirmStart();
1114
1062
  else if (input === "i") openLearnMore();
1115
1063
  });
@@ -1127,16 +1075,16 @@ function Welcome() {
1127
1075
  if (rows < 30) {
1128
1076
  layout = scales["small"];
1129
1077
  }
1130
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1131
- /* @__PURE__ */ jsx7(
1132
- Box9,
1078
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
1079
+ /* @__PURE__ */ jsx6(
1080
+ Box8,
1133
1081
  {
1134
1082
  paddingY: layout.main.padding.y,
1135
1083
  paddingX: layout.main.padding.x,
1136
1084
  flexDirection: "column",
1137
1085
  justifyContent: "center",
1138
- children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1139
- /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1086
+ children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
1087
+ /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
1140
1088
  Image,
1141
1089
  {
1142
1090
  src: IMAGE_PATH,
@@ -1147,16 +1095,16 @@ function Welcome() {
1147
1095
  protocol: "halfBlock"
1148
1096
  }
1149
1097
  ) }),
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" })
1098
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
1099
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
1100
+ /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
1101
+ /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
1154
1102
  ] })
1155
1103
  ] })
1156
1104
  }
1157
1105
  ),
1158
- /* @__PURE__ */ jsxs8(
1159
- Box9,
1106
+ /* @__PURE__ */ jsxs7(
1107
+ Box8,
1160
1108
  {
1161
1109
  backgroundColor: COLORS.bg.sidebar,
1162
1110
  width: 40,
@@ -1166,8 +1114,8 @@ function Welcome() {
1166
1114
  flexDirection: "column",
1167
1115
  justifyContent: "center",
1168
1116
  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))
1117
+ /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
1118
+ sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
1171
1119
  ]
1172
1120
  }
1173
1121
  )
@@ -1176,7 +1124,7 @@ function Welcome() {
1176
1124
 
1177
1125
  // src/ui/LearnMore.tsx
1178
1126
  import { Fragment as Fragment2 } from "react";
1179
- import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
1127
+ import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
1180
1128
 
1181
1129
  // src/ui/copy/learn-more.ts
1182
1130
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -1184,17 +1132,12 @@ var accessItems = [
1184
1132
  {
1185
1133
  tag: "READ",
1186
1134
  title: "Project files",
1187
- description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
1135
+ description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
1188
1136
  },
1189
1137
  {
1190
1138
  tag: "WRITE",
1191
1139
  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."
1140
+ description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
1198
1141
  },
1199
1142
  {
1200
1143
  tag: "NET",
@@ -1204,13 +1147,13 @@ var accessItems = [
1204
1147
  {
1205
1148
  tag: "KEY",
1206
1149
  title: "Credentials",
1207
- description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
1150
+ description: "saves your Admin API key to .env and adds it to .gitignore."
1208
1151
  }
1209
1152
  ];
1210
1153
  var neverItems = [
1211
1154
  "Send your source code to a model or third party",
1212
1155
  "Commit or push to git",
1213
- "Run a command you haven't approved"
1156
+ "Touch files outside your project directory"
1214
1157
  ];
1215
1158
  var policyLinks = [
1216
1159
  { label: "Terms", url: "https://www.algolia.com/policies/terms" },
@@ -1218,11 +1161,10 @@ var policyLinks = [
1218
1161
  ];
1219
1162
 
1220
1163
  // src/ui/LearnMore.tsx
1221
- import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1164
+ import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1222
1165
  var TAG_COLORS = {
1223
1166
  READ: COLORS.success,
1224
1167
  WRITE: COLORS.badge,
1225
- EXEC: COLORS.danger,
1226
1168
  NET: COLORS.accent,
1227
1169
  KEY: COLORS.muted
1228
1170
  };
@@ -1235,12 +1177,12 @@ function NeverLine({
1235
1177
  }) {
1236
1178
  const used = segments.reduce((n, s) => n + s.text.length, 0);
1237
1179
  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" }),
1180
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
1181
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
1240
1182
  " ".repeat(NEVER_BOX_PAD_X),
1241
- segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
1183
+ segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
1242
1184
  " ".repeat(rightPad),
1243
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
1185
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
1244
1186
  ] });
1245
1187
  }
1246
1188
  function LearnMore() {
@@ -1248,12 +1190,12 @@ function LearnMore() {
1248
1190
  const backToHome = useWizard((s) => s.backToHome);
1249
1191
  const { columns } = useWindowSize7();
1250
1192
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
1251
- useInput5((_input, key) => {
1193
+ useInput4((_input, key) => {
1252
1194
  if (key.escape) backToHome();
1253
1195
  else if (key.return) confirmStart();
1254
1196
  });
1255
- return /* @__PURE__ */ jsxs9(
1256
- Box10,
1197
+ return /* @__PURE__ */ jsxs8(
1198
+ Box9,
1257
1199
  {
1258
1200
  flexDirection: "column",
1259
1201
  paddingX: PADDING_X,
@@ -1261,31 +1203,31 @@ function LearnMore() {
1261
1203
  width: "100%",
1262
1204
  gap: 1,
1263
1205
  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}` })
1206
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
1207
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
1208
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
1209
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
1210
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
1211
+ /* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
1212
+ /* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
1213
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
1214
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
1273
1215
  ] }) })
1274
1216
  ] })
1275
1217
  ] }, 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(
1218
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
1219
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
1220
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1221
+ /* @__PURE__ */ jsx7(
1280
1222
  NeverLine,
1281
1223
  {
1282
1224
  width: dividerWidth,
1283
1225
  segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
1284
1226
  }
1285
1227
  ),
1286
- neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
1287
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1288
- /* @__PURE__ */ jsx8(
1228
+ neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
1229
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1230
+ /* @__PURE__ */ jsx7(
1289
1231
  NeverLine,
1290
1232
  {
1291
1233
  width: dividerWidth,
@@ -1297,24 +1239,24 @@ function LearnMore() {
1297
1239
  }
1298
1240
  )
1299
1241
  ] }, item)),
1300
- /* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
1301
- /* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1242
+ /* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
1243
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1302
1244
  ] }),
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 })
1245
+ /* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1246
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1247
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
1306
1248
  ] }, 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" })
1249
+ /* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1250
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1251
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1252
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
1253
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
1312
1254
  ] }),
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" })
1255
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1256
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
1257
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
1258
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
1259
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
1318
1260
  ] })
1319
1261
  ] })
1320
1262
  ]
@@ -1323,10 +1265,10 @@ function LearnMore() {
1323
1265
  }
1324
1266
 
1325
1267
  // src/ui/Sidebar.tsx
1326
- import { Box as Box13, Text as Text13 } from "ink";
1268
+ import { Box as Box12, Text as Text12 } from "ink";
1327
1269
 
1328
1270
  // src/ui/Steps.tsx
1329
- import { Box as Box11, Text as Text11 } from "ink";
1271
+ import { Box as Box10, Text as Text10 } from "ink";
1330
1272
  import Spinner from "ink-spinner";
1331
1273
 
1332
1274
  // src/core/persistence.ts
@@ -1355,12 +1297,12 @@ async function clearWorkflowState(workflowId) {
1355
1297
  }
1356
1298
 
1357
1299
  // src/ui/Steps.tsx
1358
- import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1300
+ import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
1359
1301
  function Steps() {
1360
1302
  const { steps } = useWizard();
1361
1303
  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],
1304
+ return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
1305
+ s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
1364
1306
  " ",
1365
1307
  s.title
1366
1308
  ] }) }, s.id)) });
@@ -1369,27 +1311,27 @@ function CurrentStep() {
1369
1311
  const { steps } = useWizard();
1370
1312
  const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
1371
1313
  if (!currentStep) return null;
1372
- return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
1373
- /* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
1314
+ return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
1315
+ /* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
1374
1316
  " ",
1375
1317
  ` ${currentStep.title}`
1376
1318
  ] });
1377
1319
  }
1378
1320
 
1379
1321
  // 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";
1322
+ import { Box as Box11, Text as Text11 } from "ink";
1323
+ import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
1382
1324
  function Progress() {
1383
1325
  const { steps, currentStepIndex } = useWizard();
1384
1326
  const visibleSteps = steps.filter(isStepVisible);
1385
1327
  if (visibleSteps.length === 0) return null;
1386
1328
  const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
1387
1329
  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 })
1330
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1331
+ /* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
1332
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
1333
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
1334
+ /* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
1393
1335
  ] });
1394
1336
  }
1395
1337
 
@@ -1400,10 +1342,10 @@ var sidebarCommands = [
1400
1342
  ];
1401
1343
 
1402
1344
  // src/ui/Sidebar.tsx
1403
- import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1345
+ import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
1404
1346
  function Sidebar() {
1405
- return /* @__PURE__ */ jsxs12(
1406
- Box13,
1347
+ return /* @__PURE__ */ jsxs11(
1348
+ Box12,
1407
1349
  {
1408
1350
  backgroundColor: "#14171E",
1409
1351
  width: 30,
@@ -1412,16 +1354,16 @@ function Sidebar() {
1412
1354
  flexDirection: "column",
1413
1355
  justifyContent: "space-between",
1414
1356
  children: [
1415
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
1416
- /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
1417
- /* @__PURE__ */ jsx11(Steps, {})
1357
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1358
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
1359
+ /* @__PURE__ */ jsx10(Steps, {})
1418
1360
  ] }),
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 })
1361
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
1362
+ /* @__PURE__ */ jsx10(Progress, {}),
1363
+ /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
1364
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
1365
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1366
+ /* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
1425
1367
  ] });
1426
1368
  }) })
1427
1369
  ] })
@@ -1431,12 +1373,12 @@ function Sidebar() {
1431
1373
  }
1432
1374
 
1433
1375
  // 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";
1376
+ import { Box as Box13, Text as Text13 } from "ink";
1377
+ import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
1436
1378
  function Ribbon() {
1437
1379
  const firstCommand = sidebarCommands[0];
1438
- return /* @__PURE__ */ jsxs13(
1439
- Box14,
1380
+ return /* @__PURE__ */ jsxs12(
1381
+ Box13,
1440
1382
  {
1441
1383
  backgroundColor: "#14171E",
1442
1384
  flexDirection: "row",
@@ -1444,11 +1386,11 @@ function Ribbon() {
1444
1386
  paddingX: 2,
1445
1387
  paddingY: 1,
1446
1388
  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 })
1389
+ /* @__PURE__ */ jsx11(Progress, {}),
1390
+ /* @__PURE__ */ jsx11(CurrentStep, {}),
1391
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
1392
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1393
+ /* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
1452
1394
  ] })
1453
1395
  ]
1454
1396
  }
@@ -1459,8 +1401,8 @@ function Ribbon() {
1459
1401
  import { useState as useState6 } from "react";
1460
1402
 
1461
1403
  // 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";
1404
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1405
+ import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
1464
1406
  var KIND_COLOR = {
1465
1407
  tool: COLORS.primary,
1466
1408
  prompt: COLORS.badge
@@ -1491,14 +1433,14 @@ function formatTimestamp(ms) {
1491
1433
  function Logs() {
1492
1434
  const logs = useWizard((s) => s.logs);
1493
1435
  const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1494
- useInput6((_input, key) => {
1436
+ useInput5((_input, key) => {
1495
1437
  if (key.upArrow) scroll.scrollBy(-1);
1496
1438
  else if (key.downArrow) scroll.scrollBy(1);
1497
1439
  });
1498
1440
  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) => {
1441
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1442
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
1443
+ /* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
1502
1444
  const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1503
1445
  const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1504
1446
  const rawPreview = rawInputText(entry.input);
@@ -1508,14 +1450,14 @@ function Logs() {
1508
1450
  const name = truncate2(entry.name, budget);
1509
1451
  budget -= name.length;
1510
1452
  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 })
1453
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
1454
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
1455
+ /* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
1456
+ preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
1457
+ durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
1516
1458
  ] }, entry.id);
1517
1459
  }) }),
1518
- /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1460
+ /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1519
1461
  ] });
1520
1462
  }
1521
1463
 
@@ -1707,7 +1649,7 @@ function track(event, payload) {
1707
1649
  }
1708
1650
 
1709
1651
  // src/ui/App.tsx
1710
- import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
1652
+ import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
1711
1653
  function App() {
1712
1654
  const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1713
1655
  const { exit } = useApp();
@@ -1715,7 +1657,7 @@ function App() {
1715
1657
  const [showLogs, setShowLogs] = useState6(false);
1716
1658
  const finished = phase === "done" || phase === "error";
1717
1659
  const currentStep = steps[currentStepIndex];
1718
- useInput7(
1660
+ useInput6(
1719
1661
  (_input, key) => {
1720
1662
  if (key.return) {
1721
1663
  exit();
@@ -1723,7 +1665,7 @@ function App() {
1723
1665
  },
1724
1666
  { isActive: finished }
1725
1667
  );
1726
- useInput7((_input, key) => {
1668
+ useInput6((_input, key) => {
1727
1669
  if (phase === "idle" || phase === "authenticating") return;
1728
1670
  if (key.tab) {
1729
1671
  setShowLogs(!showLogs);
@@ -1734,8 +1676,8 @@ function App() {
1734
1676
  });
1735
1677
  }
1736
1678
  });
1737
- const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
1738
- useInput7((_input, key) => {
1679
+ const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1680
+ useInput6((_input, key) => {
1739
1681
  if (escOwnedElsewhere) return;
1740
1682
  if (key.escape) {
1741
1683
  track("AI Wizard Interaction", {
@@ -1754,8 +1696,8 @@ function App() {
1754
1696
  /* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
1755
1697
  the whole screen, and the scrolling throws off its cursor arithmetic —
1756
1698
  flicker and leftover rows. */
1757
- /* @__PURE__ */ jsxs15(
1758
- Box16,
1699
+ /* @__PURE__ */ jsxs14(
1700
+ Box15,
1759
1701
  {
1760
1702
  backgroundColor: COLORS.bg.main,
1761
1703
  flexDirection: "row",
@@ -1763,16 +1705,16 @@ function App() {
1763
1705
  height: scrollsPastViewport ? void 0 : rows,
1764
1706
  overflow: scrollsPastViewport ? "visible" : "hidden",
1765
1707
  children: [
1766
- mainWindowVisible && /* @__PURE__ */ jsxs15(
1767
- Box16,
1708
+ mainWindowVisible && /* @__PURE__ */ jsxs14(
1709
+ Box15,
1768
1710
  {
1769
1711
  flexDirection,
1770
1712
  width: "100%",
1771
1713
  maxHeight: rows,
1772
1714
  justifyContent: "space-between",
1773
1715
  children: [
1774
- showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
1775
- Box16,
1716
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
1717
+ Box15,
1776
1718
  {
1777
1719
  flexDirection: "column",
1778
1720
  paddingX: 4,
@@ -1780,26 +1722,26 @@ function App() {
1780
1722
  width: showSidebar ? 70 : "100%",
1781
1723
  flexGrow: 1,
1782
1724
  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." })
1725
+ phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
1726
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
1727
+ /* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
1786
1728
  ] }),
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: [
1729
+ /* @__PURE__ */ jsx13(CliOutput, {}),
1730
+ /* @__PURE__ */ jsx13(Notices, {}),
1731
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1732
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1733
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
1792
1734
  "\u2716 ",
1793
1735
  error
1794
1736
  ] }) })
1795
1737
  ]
1796
1738
  }
1797
1739
  ),
1798
- showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
1740
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1799
1741
  ]
1800
1742
  }
1801
1743
  ),
1802
- phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
1744
+ phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1803
1745
  ]
1804
1746
  }
1805
1747
  )
@@ -1813,18 +1755,17 @@ import "zod";
1813
1755
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1814
1756
  import { join as join5 } from "node:path";
1815
1757
  var configFile = () => join5(stateDir(), "config.json");
1816
- var DEFAULT_CONFIG = {
1758
+ var defaultConfig = () => ({
1817
1759
  version: 1,
1818
1760
  aiConsent: false,
1819
- workflowsRun: [],
1820
- searchApiKeys: {}
1821
- };
1761
+ workflowsRun: []
1762
+ });
1822
1763
  async function loadConfig() {
1823
1764
  try {
1824
1765
  const raw = await readFile2(configFile(), "utf8");
1825
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1766
+ return { ...defaultConfig(), ...JSON.parse(raw) };
1826
1767
  } catch {
1827
- return { ...DEFAULT_CONFIG };
1768
+ return defaultConfig();
1828
1769
  }
1829
1770
  }
1830
1771
  async function saveConfig(config) {
@@ -1836,38 +1777,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
1836
1777
  config.workflowsRun.push({ workflowId, completedAt });
1837
1778
  await saveConfig(config);
1838
1779
  }
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
1780
 
1872
1781
  // src/core/orchestrator.ts
1873
1782
  function defineStep(step) {
@@ -1912,7 +1821,7 @@ async function ensureConsent() {
1912
1821
  if (config.aiConsent) return;
1913
1822
  const store = useWizard.getState();
1914
1823
  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.",
1824
+ prompt: "Wizard will make AI-authored changes to this repository.",
1916
1825
  promptType: "enterToContinue",
1917
1826
  options: []
1918
1827
  });
@@ -2315,9 +2224,9 @@ function listFilesTool(ctx) {
2315
2224
  if (++ctx.counts.list > ctx.limits.list) {
2316
2225
  return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
2317
2226
  }
2318
- const resolved = resolveInRoot(ctx, ".");
2319
- if (!resolved.ok) return resolved.error;
2320
- const entries = await readdir(resolved.target, { withFileTypes: true });
2227
+ const resolved2 = resolveInRoot(ctx, ".");
2228
+ if (!resolved2.ok) return resolved2.error;
2229
+ const entries = await readdir(resolved2.target, { withFileTypes: true });
2321
2230
  return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
2322
2231
  }
2323
2232
  });
@@ -2335,14 +2244,14 @@ function changeDirectoryTool(ctx) {
2335
2244
  }),
2336
2245
  execute: async ({ path }) => {
2337
2246
  logger.info({ path }, "called changeDirectory tool");
2338
- const resolved = resolveInRoot(ctx, path);
2339
- if (!resolved.ok) return resolved.error;
2247
+ const resolved2 = resolveInRoot(ctx, path);
2248
+ if (!resolved2.ok) return resolved2.error;
2340
2249
  try {
2341
- const info = await stat(resolved.target);
2250
+ const info = await stat(resolved2.target);
2342
2251
  if (!info.isDirectory()) {
2343
2252
  return `Error changing directory to ${path}: not a directory`;
2344
2253
  }
2345
- ctx.cwd = resolved.target;
2254
+ ctx.cwd = resolved2.target;
2346
2255
  return `Changed working directory to ${ctx.cwd}`;
2347
2256
  } catch (err) {
2348
2257
  return `Error changing directory to ${path}: ${err.message}`;
@@ -2407,11 +2316,11 @@ function readFileTool(ctx) {
2407
2316
  return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
2408
2317
  }
2409
2318
  logger.info({ filePath }, "called readFile tool");
2410
- const resolved = resolveInRoot(ctx, filePath);
2411
- if (!resolved.ok) return resolved.error;
2319
+ const resolved2 = resolveInRoot(ctx, filePath);
2320
+ if (!resolved2.ok) return resolved2.error;
2412
2321
  try {
2413
- const content = await readFile3(resolved.target, "utf8");
2414
- return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
2322
+ const content = await readFile3(resolved2.target, "utf8");
2323
+ return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
2415
2324
  } catch (err) {
2416
2325
  return `Error reading ${filePath}: ${err.message}`;
2417
2326
  }
@@ -2433,17 +2342,17 @@ function writeFileTool(ctx) {
2433
2342
  }),
2434
2343
  execute: async ({ filePath, content }) => {
2435
2344
  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)) {
2345
+ const resolved2 = resolveInRoot(ctx, filePath);
2346
+ if (resolved2.ok === false) return resolved2.error;
2347
+ if (isSecretEnvFile(resolved2.target)) {
2439
2348
  return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
2440
2349
  }
2441
2350
  try {
2442
- if (await hasSymlinkParent(ctx, resolved.target)) {
2443
- return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2351
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2352
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2444
2353
  }
2445
- await mkdir3(dirname4(resolved.target), { recursive: true });
2446
- await writeFile3(resolved.target, content, "utf8");
2354
+ await mkdir3(dirname4(resolved2.target), { recursive: true });
2355
+ await writeFile3(resolved2.target, content, "utf8");
2447
2356
  return `Wrote to ${filePath}`;
2448
2357
  } catch (err) {
2449
2358
  return `Error writing ${filePath}: ${err.message}`;
@@ -2460,6 +2369,40 @@ import { dirname as dirname5 } from "node:path";
2460
2369
 
2461
2370
  // src/lib/algoliaApiKey.ts
2462
2371
  import { z as z11 } from "zod";
2372
+
2373
+ // src/lib/keychain.ts
2374
+ import { getPassword, setPassword } from "cross-keychain";
2375
+ var SERVICE = "algolia-wizard";
2376
+ function account(kind, index, appId) {
2377
+ return `${kind}:${appId}:${index}`;
2378
+ }
2379
+ async function readStoredKey(kind, index, appId) {
2380
+ try {
2381
+ return await getPassword(SERVICE, account(kind, index, appId));
2382
+ } catch (err) {
2383
+ logger.warn(
2384
+ { err: err.message, kind, index, appId },
2385
+ "could not read the API key from the keychain"
2386
+ );
2387
+ return null;
2388
+ }
2389
+ }
2390
+ async function storeKey(kind, index, appId, value) {
2391
+ const name = account(kind, index, appId);
2392
+ try {
2393
+ await setPassword(SERVICE, name, value);
2394
+ if (await getPassword(SERVICE, name) !== value) {
2395
+ throw new Error("the keychain did not store the value");
2396
+ }
2397
+ } catch (err) {
2398
+ logger.warn(
2399
+ { err: err.message, kind, index, appId },
2400
+ "could not store the API key in the keychain; the next run will create another"
2401
+ );
2402
+ }
2403
+ }
2404
+
2405
+ // src/lib/algoliaApiKey.ts
2463
2406
  var WRITE_ACLS = [
2464
2407
  "addObject",
2465
2408
  "deleteObject",
@@ -2467,81 +2410,27 @@ var WRITE_ACLS = [
2467
2410
  "editSettings",
2468
2411
  "listIndexes"
2469
2412
  ];
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
2413
  var createdKeySchema = z11.object({
2499
2414
  key: z11.string().min(1).optional(),
2500
2415
  value: z11.string().min(1).optional()
2501
2416
  }).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");
2533
- const stdout = await runAlgoliaCli([
2534
- "apikeys",
2535
- "create",
2536
- "--acl",
2537
- "search",
2538
- "--indices",
2539
- index,
2540
- "--description",
2541
- `Algolia Wizard search-only key for ${index}`,
2542
- "-o",
2543
- "json"
2544
- ]);
2417
+ async function createKey(index, acls, description) {
2418
+ logger.info({ index, acls }, "creating an API key");
2419
+ const stdout = await runAlgoliaCli(
2420
+ [
2421
+ "apikeys",
2422
+ "create",
2423
+ "--acl",
2424
+ acls.join(","),
2425
+ "--indices",
2426
+ index,
2427
+ "--description",
2428
+ description,
2429
+ "-o",
2430
+ "json"
2431
+ ],
2432
+ { withoutAdminKey: true }
2433
+ );
2545
2434
  let payload;
2546
2435
  try {
2547
2436
  payload = JSON.parse(stdout);
@@ -2552,49 +2441,57 @@ async function createSearchOnlyKey(index) {
2552
2441
  if (!created) throw new Error("apikeys create returned no key value");
2553
2442
  return created;
2554
2443
  }
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
- }
2444
+ var resolved = /* @__PURE__ */ new Map();
2445
+ function forgetResolvedKeys() {
2446
+ resolved.clear();
2562
2447
  }
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" };
2448
+ function resolveKey(kind, index, appId, acls, description) {
2449
+ const cacheKey = `${kind}:${appId}:${index}`;
2450
+ const cached = resolved.get(cacheKey);
2451
+ if (cached) return cached;
2452
+ const pending = provisionKey(kind, index, appId, acls, description).catch(
2453
+ (err) => {
2454
+ resolved.delete(cacheKey);
2455
+ throw err;
2573
2456
  }
2574
- logger.warn(
2575
- { index, appId },
2576
- "the stored search-only API key no longer exists; creating a replacement"
2577
- );
2578
- await forgetSearchKey(index);
2457
+ );
2458
+ resolved.set(cacheKey, pending);
2459
+ return pending;
2460
+ }
2461
+ async function provisionKey(kind, index, appId, acls, description) {
2462
+ const stored = await readStoredKey(kind, index, appId);
2463
+ if (stored) {
2464
+ logger.info({ kind, index, appId }, "reusing the stored API key");
2465
+ return { key: stored, source: "keychain" };
2579
2466
  }
2580
- const key = await createSearchOnlyKey(index);
2581
- await recordSearchKey(index, appId, key);
2467
+ const key = await createKey(index, acls, description);
2468
+ await storeKey(kind, index, appId, key);
2582
2469
  return { key, source: "created" };
2583
2470
  }
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
- }
2471
+ function resolveWriteKey(index, appId) {
2472
+ return resolveKey(
2473
+ "write",
2474
+ index,
2475
+ appId,
2476
+ WRITE_ACLS,
2477
+ `Algolia Wizard write key for ${index} index`
2478
+ );
2479
+ }
2480
+ async function resolveSearchOnlyKey(index, appId, envKey) {
2481
+ if (envKey) return { key: envKey, source: "env" };
2482
+ return resolveKey(
2483
+ "search",
2484
+ index,
2485
+ appId,
2486
+ ["search"],
2487
+ `Algolia Wizard search-only key for ${index} index`
2488
+ );
2593
2489
  }
2594
2490
 
2595
2491
  // src/lib/tools/writeAlgoliaCredentials.ts
2596
2492
  var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
2597
2493
  var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
2494
+ var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
2598
2495
  function appendEnv(content, entries) {
2599
2496
  const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
2600
2497
  const lines = entries.map(([name, value]) => `${name}=${value}
@@ -2604,9 +2501,16 @@ function appendEnv(content, entries) {
2604
2501
  function hasEnv(content, name) {
2605
2502
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
2606
2503
  }
2504
+ function upsertEnv(content, name, value) {
2505
+ if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
2506
+ return content.replace(
2507
+ new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m"),
2508
+ `${name}=${value}`
2509
+ );
2510
+ }
2607
2511
  function writeCredentialsTool(ctx) {
2608
2512
  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.`,
2513
+ 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.`,
2610
2514
  inputSchema: z12.object({
2611
2515
  filePath: z12.string().describe(
2612
2516
  'Path to the env file to write credentials into (e.g. ".env")'
@@ -2614,43 +2518,51 @@ function writeCredentialsTool(ctx) {
2614
2518
  }),
2615
2519
  execute: async ({ filePath }) => {
2616
2520
  logger.info({ filePath }, "called writeCredentials tool");
2617
- const resolved = resolveInRoot(ctx, filePath);
2618
- if (resolved.ok === false) return resolved.error;
2521
+ const resolved2 = resolveInRoot(ctx, filePath);
2522
+ if (resolved2.ok === false) return resolved2.error;
2619
2523
  const targetIndex = useWizard.getState().targetIndex;
2620
2524
  if (!targetIndex) {
2621
2525
  return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
2622
2526
  }
2623
- let appId;
2624
- let writeKey;
2625
- 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
- }
2527
+ let existing = "";
2528
+ let present;
2631
2529
  try {
2632
- if (await hasSymlinkParent(ctx, resolved.target)) {
2633
- return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
2530
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2531
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2634
2532
  }
2635
- let existing = "";
2636
2533
  try {
2637
- existing = await readFile4(resolved.target, "utf8");
2534
+ existing = await readFile4(resolved2.target, "utf8");
2638
2535
  } catch (err) {
2639
2536
  if (err.code !== "ENOENT") throw err;
2640
2537
  }
2641
- const present = [APP_ID_VAR, API_KEY_VAR].filter(
2538
+ present = [APP_ID_VAR, API_KEY_VAR].filter(
2642
2539
  (name) => hasEnv(existing, name)
2643
2540
  );
2644
- if (present.length > 0) {
2645
- return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2541
+ } catch (err) {
2542
+ return `Error writing credentials to ${filePath}: ${err.message}`;
2543
+ }
2544
+ let credentials = [];
2545
+ if (present.length === 0) {
2546
+ try {
2547
+ const appId = (await requireApplication()).id;
2548
+ credentials = [
2549
+ [APP_ID_VAR, appId],
2550
+ [API_KEY_VAR, (await resolveWriteKey(targetIndex, appId)).key]
2551
+ ];
2552
+ } catch (err) {
2553
+ return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
2646
2554
  }
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}`;
2555
+ }
2556
+ try {
2557
+ const updated = upsertEnv(
2558
+ appendEnv(existing, credentials),
2559
+ INDEX_NAME_VAR,
2560
+ targetIndex
2561
+ );
2562
+ await mkdir4(dirname5(resolved2.target), { recursive: true });
2563
+ await writeFile4(resolved2.target, updated, "utf8");
2564
+ const wrote = `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}`;
2565
+ return present.length > 0 ? `${wrote}. Skipped ${present.join(" and ")}: already defined there.` : wrote;
2654
2566
  } catch (err) {
2655
2567
  return `Error writing credentials to ${filePath}: ${err.message}`;
2656
2568
  }
@@ -2664,19 +2576,11 @@ import z13 from "zod";
2664
2576
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
2665
2577
  import { join as join7 } from "node:path";
2666
2578
  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
2579
  async function walkFiles(dir) {
2580
+ const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
2677
2581
  const out = [];
2678
2582
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2679
- if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2583
+ if (e.name.startsWith(".") || skip.has(e.name)) continue;
2680
2584
  const full = join7(dir, e.name);
2681
2585
  if (e.isDirectory()) out.push(...await walkFiles(full));
2682
2586
  else if (e.isFile()) out.push(full);
@@ -2698,8 +2602,8 @@ function searchFilesTool(ctx) {
2698
2602
  if (query.length > MAX_QUERY_LENGTH) {
2699
2603
  return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
2700
2604
  }
2701
- const resolved = resolveInRoot(ctx, path);
2702
- if (!resolved.ok) return resolved.error;
2605
+ const resolved2 = resolveInRoot(ctx, path);
2606
+ if (!resolved2.ok) return resolved2.error;
2703
2607
  let re;
2704
2608
  try {
2705
2609
  re = new RegExp(query);
@@ -2707,7 +2611,7 @@ function searchFilesTool(ctx) {
2707
2611
  return `Invalid regex: ${err.message}`;
2708
2612
  }
2709
2613
  const matches = [];
2710
- for (const file of await walkFiles(resolved.target)) {
2614
+ for (const file of await walkFiles(resolved2.target)) {
2711
2615
  let content;
2712
2616
  try {
2713
2617
  content = await readFile5(file, "utf8");
@@ -2730,194 +2634,92 @@ function searchFilesTool(ctx) {
2730
2634
  });
2731
2635
  }
2732
2636
 
2733
- // src/lib/tools/runShell.ts
2637
+ // src/lib/tools/verifyImplementation.ts
2734
2638
  import { tool as tool8 } from "ai";
2735
2639
  import z14 from "zod";
2736
- import { relative as relative2 } from "node:path";
2737
2640
 
2738
- // src/lib/tools/utils/runShell.ts
2641
+ // src/lib/tools/utils/runCommand.ts
2739
2642
  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();
2643
+ function runCommand(command, args, cwd) {
2791
2644
  return new Promise((resolve4) => {
2792
2645
  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 }
2646
+ const child = spawn2(command, args, {
2647
+ cwd,
2648
+ stdio: ["ignore", "pipe", "pipe"]
2800
2649
  });
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
2650
  child.stdout?.on("data", (d) => output += d);
2822
2651
  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));
2652
+ child.on(
2653
+ "error",
2654
+ (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2655
+ );
2656
+ child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2828
2657
  });
2829
2658
  }
2830
2659
 
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
- };
2660
+ // src/lib/tools/utils/packageManager.ts
2661
+ import { readFile as readFile6 } from "node:fs/promises";
2662
+ import { existsSync } from "node:fs";
2663
+ import { join as join8 } from "node:path";
2664
+ var LOCKFILES = [
2665
+ ["pnpm-lock.yaml", "pnpm"],
2666
+ ["yarn.lock", "yarn"],
2667
+ ["bun.lockb", "bun"],
2668
+ ["bun.lock", "bun"],
2669
+ ["package-lock.json", "npm"]
2670
+ ];
2671
+ async function readPackageJson(cwd = process.cwd()) {
2672
+ return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
2673
+ }
2674
+ function packageManagerFrom(pkg) {
2675
+ return pkg.packageManager?.split("@")[0] ?? "npm";
2676
+ }
2677
+ function packageManagerFromLockfile(cwd) {
2678
+ return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
2679
+ }
2680
+ async function detectPackageManager(cwd) {
2681
+ try {
2682
+ const pkg = await readPackageJson(cwd);
2683
+ if (pkg.packageManager) return packageManagerFrom(pkg);
2684
+ } catch {
2685
+ }
2686
+ return packageManagerFromLockfile(cwd) ?? "npm";
2687
+ }
2688
+
2689
+ // src/lib/tools/repoVerification.ts
2690
+ var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2691
+ async function runRepoVerificationCheck() {
2692
+ let pkg;
2693
+ try {
2694
+ pkg = await readPackageJson();
2695
+ } catch (err) {
2696
+ const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2697
+ return { ok: false, checks: [], limitation };
2698
+ }
2699
+ const scripts = pkg.scripts ?? {};
2700
+ const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2701
+ if (present.length === 0) {
2702
+ const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2703
+ return { ok: false, checks: [], limitation };
2704
+ }
2705
+ const pm = await detectPackageManager(process.cwd());
2706
+ const checks = [];
2707
+ for (const script of present) {
2708
+ const command = `${pm} run ${script}`;
2709
+ const { code, output } = await runCommand(pm, ["run", script]);
2710
+ checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2711
+ }
2712
+ return { ok: checks.every((c) => c.ok), checks };
2849
2713
  }
2850
- function runShellTool(ctx) {
2714
+
2715
+ // src/lib/tools/verifyImplementation.ts
2716
+ function verifyImplementationTool() {
2851
2717
  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
- };
2718
+ description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
2719
+ inputSchema: z14.object(),
2720
+ execute: async () => {
2721
+ logger.info("called verifyImplementation tool");
2722
+ return runRepoVerificationCheck();
2921
2723
  }
2922
2724
  });
2923
2725
  }
@@ -2997,18 +2799,18 @@ function generateRecordTool(ctx) {
2997
2799
  }));
2998
2800
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2999
2801
  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}).`;
2802
+ const resolved2 = resolveInRoot(ctx, relPath);
2803
+ if (resolved2.ok === false) return resolved2.error;
2804
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2805
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
3004
2806
  }
3005
- await mkdir5(dirname6(resolved.target), { recursive: true });
3006
- await writeFile5(resolved.target, JSON.stringify(records, null, 2), "utf8");
2807
+ await mkdir5(dirname6(resolved2.target), { recursive: true });
2808
+ await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
3007
2809
  logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
3008
2810
  return {
3009
2811
  filePath: relPath,
3010
2812
  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.`
2813
+ message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) \u2014 do not inline the records as literals.`
3012
2814
  };
3013
2815
  } catch (err) {
3014
2816
  return `Error generating records: ${err.message}`;
@@ -3036,6 +2838,22 @@ function notifyUserTool() {
3036
2838
  });
3037
2839
  }
3038
2840
 
2841
+ // src/lib/tools/context.ts
2842
+ var DEFAULT_TOOL_LIMITS = {
2843
+ list: 10,
2844
+ search: 10,
2845
+ read: 20,
2846
+ match: 100
2847
+ };
2848
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2849
+ return {
2850
+ root: cwd,
2851
+ cwd,
2852
+ limits,
2853
+ counts: { list: 0, search: 0, read: 0 }
2854
+ };
2855
+ }
2856
+
3039
2857
  // src/lib/tools/index.ts
3040
2858
  function withLogging(name, def) {
3041
2859
  const execute = def.execute;
@@ -3067,7 +2885,10 @@ function createTools(ctx, { output, tools }) {
3067
2885
  writeCredentialsTool(ctx)
3068
2886
  ),
3069
2887
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3070
- runShell: withLogging("runShell", runShellTool(ctx)),
2888
+ verifyImplementation: withLogging(
2889
+ "verifyImplementation",
2890
+ verifyImplementationTool()
2891
+ ),
3071
2892
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3072
2893
  notifyUser: withLogging("notifyUser", notifyUserTool())
3073
2894
  };
@@ -3102,7 +2923,7 @@ async function runAgent(req) {
3102
2923
  baseURL: PROXY_BASE_URL,
3103
2924
  fetch: proxyFetch
3104
2925
  });
3105
- const toolContext = req.toolContext ?? createToolContext();
2926
+ const toolContext = createToolContext();
3106
2927
  const readTools = ["readFile", "searchFiles", "listFiles"];
3107
2928
  const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
3108
2929
  const instructions = [
@@ -3167,11 +2988,7 @@ async function runAgent(req) {
3167
2988
  "runAgent finished"
3168
2989
  );
3169
2990
  logger.info(
3170
- {
3171
- counts: toolContext.counts,
3172
- limits: toolContext.limits,
3173
- commandsRun: toolContext.shell.executions.length
3174
- },
2991
+ { counts: toolContext.counts, limits: toolContext.limits },
3175
2992
  "tool usage"
3176
2993
  );
3177
2994
  const toolResults = await stream.toolResults;
@@ -3197,8 +3014,8 @@ var detectLanguageSchema = z19.object({
3197
3014
  var detectLanguage = () => runAgent({
3198
3015
  instructions: [
3199
3016
  "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).",
3017
+ "If a superset language is found, exclude the subset language. TS-over-JS.",
3018
+ "If a meta-framework is used, exclude the framework. Next-over-React.",
3202
3019
  "Return the exact version",
3203
3020
  "Exclude things like CSS frameworks, build tools, or testing frameworks",
3204
3021
  'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
@@ -3296,7 +3113,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3296
3113
  // package.json
3297
3114
  var package_default = {
3298
3115
  name: "@algolia/wizard",
3299
- version: "0.9.0-rc.87.83",
3116
+ version: "0.9.0-rc.88.85",
3300
3117
  description: "Magically implement Algolia functionality in your codebase",
3301
3118
  type: "module",
3302
3119
  engines: {
@@ -3347,6 +3164,7 @@ var package_default = {
3347
3164
  "@hono/node-server": "^2.0.10",
3348
3165
  "@segment/analytics-node": "^3.1.0",
3349
3166
  ai: "^6.0.190",
3167
+ "cross-keychain": "^1.1.0",
3350
3168
  dotenv: "^17.4.2",
3351
3169
  hono: "^4.12.27",
3352
3170
  ink: "^7.0.5",
@@ -3444,11 +3262,9 @@ var CURATED_FRAMEWORKS = [
3444
3262
  "Next.js",
3445
3263
  "React",
3446
3264
  "Vue",
3447
- "Vanilla JS",
3448
- "Django",
3449
- "Laravel",
3450
- "Rails",
3451
- "Symfony"
3265
+ "Angular",
3266
+ "Svelte",
3267
+ "Vanilla JS"
3452
3268
  ];
3453
3269
  var OTHER_OPTION = "Other";
3454
3270
  var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -3461,15 +3277,12 @@ var FRAMEWORK_ALIASES = {
3461
3277
  vuejs: "vue",
3462
3278
  angular: "angular",
3463
3279
  angularjs: "angular",
3280
+ svelte: "svelte",
3281
+ sveltekit: "svelte",
3464
3282
  vanillajs: "vanillajs",
3465
3283
  vanilla: "vanillajs",
3466
3284
  javascript: "vanillajs",
3467
- js: "vanillajs",
3468
- django: "django",
3469
- laravel: "laravel",
3470
- rails: "rails",
3471
- rubyonrails: "rails",
3472
- symfony: "symfony"
3285
+ js: "vanillajs"
3473
3286
  };
3474
3287
  var isSameFramework = (a, b) => {
3475
3288
  const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
@@ -3703,9 +3516,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3703
3516
  import z26 from "zod";
3704
3517
 
3705
3518
  // 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";
3519
+ import { execFile, spawn as spawn3 } from "node:child_process";
3520
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3521
+ import {
3522
+ basename as basename2,
3523
+ dirname as dirname7,
3524
+ isAbsolute as isAbsolute2,
3525
+ join as join9,
3526
+ relative as relative2,
3527
+ resolve as resolve3
3528
+ } from "node:path";
3709
3529
  var MAX_BUFFER = 32 * 1024 * 1024;
3710
3530
  var MAX_WIZARD_WORKTREES = 3;
3711
3531
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3736,7 +3556,7 @@ async function isWorkingTreeDirty(repoRoot) {
3736
3556
  return out.trim().length > 0;
3737
3557
  }
3738
3558
  async function pruneOldWorktrees(repoRoot) {
3739
- const dir = join8(stateDir(repoRoot), "worktrees");
3559
+ const dir = join9(stateDir(repoRoot), "worktrees");
3740
3560
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3741
3561
  for (const slug of stale) {
3742
3562
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3747,7 +3567,7 @@ async function pruneOldWorktrees(repoRoot) {
3747
3567
  "worktree",
3748
3568
  "remove",
3749
3569
  "--force",
3750
- join8(dir, slug)
3570
+ join9(dir, slug)
3751
3571
  ]);
3752
3572
  await git(["-C", repoRoot, "branch", "-D", branch]);
3753
3573
  } catch (err) {
@@ -3761,13 +3581,113 @@ async function pruneOldWorktrees(repoRoot) {
3761
3581
  async function createWorktree(repoRoot) {
3762
3582
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3763
3583
  const dirSlug = branch.replace(/\//g, "-");
3764
- const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
3584
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3765
3585
  await git(["-C", repoRoot, "worktree", "prune"]);
3766
3586
  await pruneOldWorktrees(repoRoot);
3767
3587
  await mkdir6(dirname7(path), { recursive: true });
3768
3588
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3769
3589
  return { path, branch };
3770
3590
  }
3591
+ async function installWorktreeDeps(worktreePath) {
3592
+ try {
3593
+ await readPackageJson(worktreePath);
3594
+ } catch {
3595
+ return { ok: true, output: "no package.json; skipped install" };
3596
+ }
3597
+ const pm = await detectPackageManager(worktreePath);
3598
+ return new Promise((resolve4) => {
3599
+ let output = "";
3600
+ const child = spawn3(pm, ["install"], {
3601
+ cwd: worktreePath,
3602
+ stdio: ["ignore", "pipe", "pipe"]
3603
+ });
3604
+ child.stdout?.on("data", (d) => output += d);
3605
+ child.stderr?.on("data", (d) => output += d);
3606
+ child.on(
3607
+ "error",
3608
+ (err) => resolve4({
3609
+ ok: false,
3610
+ output: `Failed to run ${pm} install: ${err.message}`
3611
+ })
3612
+ );
3613
+ child.on(
3614
+ "close",
3615
+ (code) => resolve4({ ok: code === 0, output: output.trim() })
3616
+ );
3617
+ });
3618
+ }
3619
+ var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
3620
+ function validateIngestEntrypoint(worktreePath, entrypoint) {
3621
+ if (!entrypoint || entrypoint.startsWith("-")) {
3622
+ return {
3623
+ ok: false,
3624
+ reason: `entrypoint "${entrypoint}" is not a plain file path`
3625
+ };
3626
+ }
3627
+ const target = resolve3(worktreePath, entrypoint);
3628
+ const rel = relative2(worktreePath, target);
3629
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
3630
+ return {
3631
+ ok: false,
3632
+ reason: `entrypoint "${entrypoint}" resolves outside the worktree`
3633
+ };
3634
+ }
3635
+ return { ok: true, target };
3636
+ }
3637
+ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
3638
+ if (!INGEST_RUNTIMES.includes(runtime)) {
3639
+ return {
3640
+ ran: false,
3641
+ ok: false,
3642
+ output: "",
3643
+ reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
3644
+ };
3645
+ }
3646
+ const validated = validateIngestEntrypoint(worktreePath, entrypoint);
3647
+ if (!validated.ok) {
3648
+ return { ran: false, ok: false, output: "", reason: validated.reason };
3649
+ }
3650
+ try {
3651
+ if (!(await stat2(validated.target)).isFile()) {
3652
+ return {
3653
+ ran: false,
3654
+ ok: false,
3655
+ output: "",
3656
+ reason: `entrypoint "${entrypoint}" is not a file`
3657
+ };
3658
+ }
3659
+ } catch {
3660
+ return {
3661
+ ran: false,
3662
+ ok: false,
3663
+ output: "",
3664
+ reason: `entrypoint "${entrypoint}" does not exist`
3665
+ };
3666
+ }
3667
+ return new Promise((resolveRun) => {
3668
+ let output = "";
3669
+ const child = spawn3(runtime, [entrypoint], {
3670
+ cwd: worktreePath,
3671
+ shell: false,
3672
+ stdio: ["ignore", "pipe", "pipe"],
3673
+ env: { ...process.env, ...env }
3674
+ });
3675
+ child.stdout?.on("data", (d) => output += d);
3676
+ child.stderr?.on("data", (d) => output += d);
3677
+ child.on(
3678
+ "error",
3679
+ (err) => resolveRun({
3680
+ ran: true,
3681
+ ok: false,
3682
+ output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
3683
+ })
3684
+ );
3685
+ child.on(
3686
+ "close",
3687
+ (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
3688
+ );
3689
+ });
3690
+ }
3771
3691
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
3772
3692
  const trimmed = sourcePath.trim();
3773
3693
  if (!trimmed) {
@@ -3781,8 +3701,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3781
3701
  } catch {
3782
3702
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3783
3703
  }
3784
- const relPath = join8(ingestDir, basename2(source));
3785
- const dest = join8(worktreePath, relPath);
3704
+ const relPath = join9(ingestDir, basename2(source));
3705
+ const dest = join9(worktreePath, relPath);
3786
3706
  try {
3787
3707
  await mkdir6(dirname7(dest), { recursive: true });
3788
3708
  await copyFile(source, dest);
@@ -3800,7 +3720,7 @@ function hasEnvVar(content, name) {
3800
3720
  async function readEnvVar(worktreePath, name) {
3801
3721
  let content;
3802
3722
  try {
3803
- content = await readFile6(join8(worktreePath, ".env"), "utf8");
3723
+ content = await readFile7(join9(worktreePath, ".env"), "utf8");
3804
3724
  } catch (err) {
3805
3725
  if (err.code !== "ENOENT") throw err;
3806
3726
  return void 0;
@@ -3815,10 +3735,10 @@ async function readEnvVar(worktreePath, name) {
3815
3735
  return value;
3816
3736
  }
3817
3737
  async function writeSearchEnvValues(worktreePath, vars) {
3818
- const target = join8(worktreePath, ".env");
3738
+ const target = join9(worktreePath, ".env");
3819
3739
  let existing = "";
3820
3740
  try {
3821
- existing = await readFile6(target, "utf8");
3741
+ existing = await readFile7(target, "utf8");
3822
3742
  } catch (err) {
3823
3743
  if (err.code !== "ENOENT") throw err;
3824
3744
  }
@@ -3887,15 +3807,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3887
3807
  }
3888
3808
 
3889
3809
  // src/lib/algoliaDocs.ts
3890
- import { readFileSync, readdirSync, existsSync } from "node:fs";
3891
- import { dirname as dirname8, join as join9 } from "node:path";
3810
+ import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
3811
+ import { dirname as dirname8, join as join10 } from "node:path";
3892
3812
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3893
- var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3813
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3894
3814
  function findDocsDir() {
3895
3815
  let dir = dirname8(fileURLToPath2(import.meta.url));
3896
3816
  for (; ; ) {
3897
- const candidate = join9(dir, DOCS_SUBPATH);
3898
- if (existsSync(candidate)) return candidate;
3817
+ const candidate = join10(dir, DOCS_SUBPATH);
3818
+ if (existsSync2(candidate)) return candidate;
3899
3819
  const parent = dirname8(dir);
3900
3820
  if (parent === dir) return void 0;
3901
3821
  dir = parent;
@@ -3917,7 +3837,7 @@ function loadAlgoliaDoc(language) {
3917
3837
  );
3918
3838
  return "";
3919
3839
  }
3920
- return readFileSync(join9(docsDir, files[0]), "utf8").trim();
3840
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3921
3841
  }
3922
3842
  function getNamedDoc(name, language) {
3923
3843
  const docsDir = findDocsDir();
@@ -3925,15 +3845,14 @@ function getNamedDoc(name, language) {
3925
3845
  logger.warn("docs/algolia-sdk not found");
3926
3846
  return "";
3927
3847
  }
3928
- const file = join9(docsDir, `${name}-${language}.md`);
3929
- if (!existsSync(file)) {
3848
+ const file = join10(docsDir, `${name}-${language}.md`);
3849
+ if (!existsSync2(file)) {
3930
3850
  logger.warn({ name, language }, "named SDK reference not found");
3931
3851
  return "";
3932
3852
  }
3933
3853
  return readFileSync(file, "utf8").trim();
3934
3854
  }
3935
3855
  function getFrameworkSpecificDoc(frameworks) {
3936
- if (frameworks.length === 0) return "";
3937
3856
  const fw = frameworks.map((f) => f.toLowerCase());
3938
3857
  if (fw.includes("vue") || fw.includes("nuxt")) {
3939
3858
  return loadAlgoliaDoc("vue");
@@ -3941,6 +3860,9 @@ function getFrameworkSpecificDoc(frameworks) {
3941
3860
  if (fw.includes("react") || fw.includes("next.js")) {
3942
3861
  return loadAlgoliaDoc("react");
3943
3862
  }
3863
+ if (fw.includes("angular")) {
3864
+ return loadAlgoliaDoc("angular");
3865
+ }
3944
3866
  return loadAlgoliaDoc("js");
3945
3867
  }
3946
3868
 
@@ -3968,7 +3890,11 @@ var implementSchema = z26.object({
3968
3890
  });
3969
3891
  var implementationOutputSchema = z26.object({
3970
3892
  summary: z26.string(),
3971
- ingestCommand: z26.string().optional()
3893
+ // Ingestion only: a structured pair the wizard turns into an argv, never a
3894
+ // free-form command string. `runtime` is allowlisted and `entrypoint` is
3895
+ // validated worktree-relative, so the agent cannot inject extra commands.
3896
+ runtime: z26.enum(INGEST_RUNTIMES).optional(),
3897
+ entrypoint: z26.string().optional()
3972
3898
  });
3973
3899
  var verificationOutputSchema = z26.object({
3974
3900
  summary: z26.string(),
@@ -3978,35 +3904,30 @@ var verificationOutputSchema = z26.object({
3978
3904
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
3979
3905
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
3980
3906
  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"];
3907
+ function detectUiFramework(language) {
3908
+ const names = language.frameworks.map((f) => f.name.toLowerCase());
3909
+ if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
3910
+ if (names.some((n) => n.includes("react") || n.includes("next")))
3911
+ return "React";
3912
+ if (names.some((n) => n.includes("angular"))) return "Angular";
3913
+ return "JavaScript";
3914
+ }
3915
+ function frameworksForDoc(framework) {
3916
+ switch (framework) {
3917
+ case "React":
3918
+ return ["react"];
3919
+ case "Vue":
3920
+ return ["vue"];
3921
+ case "Angular":
3922
+ return ["angular"];
3923
+ case "JavaScript":
3924
+ return [];
3925
+ }
4007
3926
  }
4008
3927
  function publicEnvPrefix(language) {
4009
- const frameworkNames = lower(language.frameworks);
3928
+ const frameworkNames = language.frameworks.map(
3929
+ (framework) => framework.name.toLowerCase()
3930
+ );
4010
3931
  if (frameworkNames.some((name) => name.includes("next"))) {
4011
3932
  return "NEXT_PUBLIC_";
4012
3933
  }
@@ -4019,17 +3940,21 @@ function publicEnvPrefix(language) {
4019
3940
  if (frameworkNames.some((name) => name.includes("vite"))) {
4020
3941
  return "VITE_";
4021
3942
  }
4022
- return isJsProject(language) ? "PUBLIC_" : "";
3943
+ return "PUBLIC_";
4023
3944
  }
4024
3945
  var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
4025
3946
  var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
3947
+ var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
4026
3948
  function appIdVar(language) {
4027
3949
  return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
4028
3950
  }
4029
3951
  function searchKeyVar(language) {
4030
3952
  return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
4031
3953
  }
4032
- function searchEnvVars(language, appId, searchKey) {
3954
+ function searchIndexVar(language) {
3955
+ return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
3956
+ }
3957
+ function searchEnvVars(language, index, appId, searchKey) {
4033
3958
  return [
4034
3959
  {
4035
3960
  name: appIdVar(language),
@@ -4038,17 +3963,23 @@ function searchEnvVars(language, appId, searchKey) {
4038
3963
  {
4039
3964
  name: searchKeyVar(language),
4040
3965
  value: searchKey ?? "<your-algolia-search-only-api-key>"
3966
+ },
3967
+ // Wizard-supplied rather than written into the generated code, because an
3968
+ // agent that retypes the name (appending the project name, re-casing it)
3969
+ // leaves the UI querying an index that does not exist.
3970
+ {
3971
+ name: searchIndexVar(language),
3972
+ value: index
4041
3973
  }
4042
3974
  ];
4043
3975
  }
4044
3976
  function baseInstructions(input) {
4045
3977
  return [
4046
- `Target Algolia index: ${input.targetIndex}`,
3978
+ // Agents have renamed this (e.g. appending the project name), which the
3979
+ // index-scoped keys then reject with a 403.
3980
+ `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4047
3981
  `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."
3982
+ "Make minimal, idiomatic changes; do not touch unrelated code."
4052
3983
  ];
4053
3984
  }
4054
3985
  function sourceSpecificInstructions(input) {
@@ -4068,50 +3999,38 @@ function sourceSpecificInstructions(input) {
4068
3999
  generated: [
4069
4000
  "No real data source exists; use sample records for each confirmed entity.",
4070
4001
  "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.",
4002
+ "In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
4072
4003
  "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
4004
  ]
4074
4005
  };
4075
4006
  return byLine[input.ingestionSource];
4076
4007
  }
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
4008
  function ingestionInstructions(input) {
4087
4009
  return [
4088
4010
  ...input.confirmed && input.confirmed.length ? [
4089
4011
  `Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
4090
4012
  `Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
4091
4013
  `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.",
4014
+ `Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
4015
+ "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4093
4016
  "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.',
4017
+ getNamedDoc("save-records", "js"),
4018
+ 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4097
4019
  "The summary should be extremely concise.",
4020
+ `Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
4098
4021
  ...sourceSpecificInstructions(input)
4099
4022
  ] : []
4100
4023
  ];
4101
4024
  }
4102
4025
  function searchInstructions(input) {
4103
- const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
4026
+ const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
4104
4027
  return [
4105
4028
  "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.",
4029
+ `Build the search UI for ${input.uiFramework}.`,
4030
+ "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
4031
+ doc,
4032
+ `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
4033
+ `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
4034
  // The key is provisioned only after verification passes, so the agent never
4116
4035
  // sees one. It must also leave .env alone: the wizard reads that file to
4117
4036
  // decide whether a key already exists, and an agent-invented value there
@@ -4120,8 +4039,9 @@ function searchInstructions(input) {
4120
4039
  // Not the agent's to rename: the wizard writes these exact names into
4121
4040
  // ".env" right after this step, so a renamed prefix would leave the code
4122
4041
  // 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.",
4042
+ `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4043
+ "Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
4044
+ 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4125
4045
  "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
4046
  ];
4127
4047
  }
@@ -4129,12 +4049,11 @@ function verificationInstructions(input) {
4129
4049
  return [
4130
4050
  "Verify the Algolia implementation changes in the current worktree.",
4131
4051
  `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.",
4052
+ "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4053
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4054
+ "Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4136
4055
  "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.`,
4056
+ `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4138
4057
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4139
4058
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4140
4059
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4155,15 +4074,14 @@ var IMPLEMENT_CONFIG = {
4155
4074
  }
4156
4075
  };
4157
4076
  var useCaseToolMap = {
4158
- ingestion: [
4077
+ ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4078
+ search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4079
+ verification: [
4159
4080
  ...FS_READ_TOOLS,
4160
4081
  "writeFile",
4161
- "writeCredentials",
4162
- "runShell",
4082
+ "verifyImplementation",
4163
4083
  "notifyUser"
4164
- ],
4165
- search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4166
- verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4084
+ ]
4167
4085
  };
4168
4086
  function toolsForUseCase(useCase, ingestionSource) {
4169
4087
  const tools = useCaseToolMap[useCase];
@@ -4186,31 +4104,15 @@ function formatSummary(useCase, summary) {
4186
4104
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4187
4105
  return `${label}: ${summary}`;
4188
4106
  }
4107
+ function buildIngestCommand(worktree, runtime, entrypoint) {
4108
+ return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4109
+ }
4189
4110
  function parseIngestRecordCount(output) {
4190
4111
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4191
4112
  if (!match) return void 0;
4192
4113
  const count = Number(match[1]);
4193
4114
  return Number.isFinite(count) ? count : void 0;
4194
4115
  }
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
4116
  function verificationRetryInstructions(verification) {
4215
4117
  return [
4216
4118
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4289,13 +4191,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4289
4191
  const confirmed2 = normalized.confirmedEntities;
4290
4192
  const searchLocation = normalized.searchImplementationAnalysis;
4291
4193
  let appId;
4292
- let ingestAppId;
4293
4194
  if (useCases.includes("search")) {
4294
4195
  appId = (await requireApplication()).id;
4295
4196
  }
4296
- if (useCases.includes("ingestion")) {
4297
- ingestAppId = appId ?? (await requireApplication()).id;
4298
- }
4299
4197
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4300
4198
  try {
4301
4199
  process.chdir(worktree);
@@ -4328,11 +4226,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4328
4226
  appId,
4329
4227
  // Names only: the search-only key is provisioned after verification, so
4330
4228
  // every value here is still a placeholder when the agent reads them.
4331
- searchEnvVars: searchEnvVars(language, appId),
4229
+ searchEnvVars: searchEnvVars(language, targetIndex, appId),
4332
4230
  ingestDir: INGEST_DIR,
4333
4231
  ingestionSource,
4334
4232
  uploadFilePath,
4335
- searchUiTarget: searchUiTarget(language)
4233
+ uiFramework: detectUiFramework(language)
4336
4234
  };
4337
4235
  const summaries = [];
4338
4236
  if (uploadWarning) summaries.push(uploadWarning);
@@ -4355,29 +4253,41 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4355
4253
  }
4356
4254
  let finalSearchEnvVars = input.searchEnvVars;
4357
4255
  let agentRuns = 0;
4358
- let ingestCommand;
4256
+ let ingestRuntime;
4257
+ let ingestEntrypoint;
4359
4258
  let ingestScriptRan = false;
4360
4259
  let ingestRecordCount;
4361
4260
  let ingestDurationMs;
4261
+ let installFailed = false;
4362
4262
  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
4263
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4369
4264
  if (agentRuns > 0) ctx.recordStepExecution();
4370
4265
  agentRuns += 1;
4371
- return runAgent({
4266
+ const result = await runAgent({
4372
4267
  instructions: buildAgentInstructions(
4373
4268
  currentUseCase,
4374
4269
  input,
4375
4270
  extraInstructions
4376
4271
  ),
4377
4272
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4378
- outputSchema: implementationOutputSchema,
4379
- toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4273
+ outputSchema: implementationOutputSchema
4274
+ });
4275
+ ctx.notify({
4276
+ messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4277
+ });
4278
+ const installLogId = ctx.logStart("installWorktreeDeps", {
4279
+ useCase: currentUseCase
4380
4280
  });
4281
+ const install = await installWorktreeDeps(worktree);
4282
+ ctx.logEnd(installLogId, install.ok ? "success" : "error");
4283
+ if (!install.ok) {
4284
+ installFailed = true;
4285
+ logger.warn(
4286
+ { useCase: currentUseCase, output: install.output },
4287
+ "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4288
+ );
4289
+ }
4290
+ return result;
4381
4291
  }
4382
4292
  async function runVerificationUseCase() {
4383
4293
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4385,55 +4295,111 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4385
4295
  return runAgent({
4386
4296
  instructions: buildAgentInstructions("verification", input),
4387
4297
  tools: toolsForUseCase("verification"),
4388
- outputSchema: verificationOutputSchema,
4389
- toolContext: searchTools
4298
+ outputSchema: verificationOutputSchema
4390
4299
  });
4391
4300
  }
4392
4301
  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
4302
+ const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4303
+ summaries.push(formatSummary("ingestion", summary));
4304
+ ingestRuntime = runtime;
4305
+ ingestEntrypoint = entrypoint;
4306
+ if (ingestRuntime && ingestEntrypoint && !installFailed) {
4307
+ ctx.clearNotices();
4308
+ const runNow = await ctx.requestUserInput({
4309
+ prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4310
+ promptType: "acceptReject",
4311
+ options: ["Yes", "No"],
4312
+ messages: []
4313
+ }) === true;
4314
+ if (runNow) {
4315
+ const ingestApp = await requireApplication();
4316
+ const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4317
+ ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4318
+ const scriptLogId = ctx.logStart("runIngestScript", {
4319
+ runtime: ingestRuntime,
4320
+ entrypoint: ingestEntrypoint
4411
4321
  });
4322
+ const startedAt = Date.now();
4323
+ const run2 = await runIngestScript(
4324
+ worktree,
4325
+ ingestRuntime,
4326
+ ingestEntrypoint,
4327
+ {
4328
+ [APP_ID_VAR]: ingestApp.id,
4329
+ [API_KEY_VAR]: writeKey,
4330
+ [INDEX_NAME_VAR]: targetIndex
4331
+ }
4332
+ );
4333
+ ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4334
+ ingestScriptRan = run2.ran && run2.ok;
4335
+ if (ingestScriptRan) {
4336
+ ingestDurationMs = Date.now() - startedAt;
4337
+ ingestRecordCount = parseIngestRecordCount(run2.output);
4338
+ if (ingestRecordCount != null) {
4339
+ track("AI Wizard Ingest Successful", {
4340
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4341
+ record_count: ingestRecordCount,
4342
+ duration_ms: ingestDurationMs
4343
+ });
4344
+ }
4345
+ }
4346
+ let summaryLine;
4347
+ let outcomeMessage;
4348
+ if (!run2.ran) {
4349
+ summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4350
+ outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4351
+ logger.warn(
4352
+ {
4353
+ runtime: ingestRuntime,
4354
+ entrypoint: ingestEntrypoint,
4355
+ reason: run2.reason
4356
+ },
4357
+ "implement: refused to auto-run ingestion script"
4358
+ );
4359
+ track("Error", {
4360
+ step: "Push Data",
4361
+ error: `ingestion script skipped: ${run2.reason}`,
4362
+ product_area: "AI Wizard"
4363
+ });
4364
+ } else if (run2.ok) {
4365
+ const status = "Ingestion run: succeeded.";
4366
+ summaryLine = run2.output ? `${status}
4367
+ ${run2.output}` : status;
4368
+ outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4369
+ } else {
4370
+ const status = "\u26A0\uFE0F Ingestion run failed:";
4371
+ summaryLine = run2.output ? `${status}
4372
+ ${run2.output}` : status;
4373
+ outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4374
+ logger.warn(
4375
+ {
4376
+ runtime: ingestRuntime,
4377
+ entrypoint: ingestEntrypoint,
4378
+ output: run2.output
4379
+ },
4380
+ "implement: ingestion script run failed"
4381
+ );
4382
+ track("Error", {
4383
+ step: "Push Data",
4384
+ error: run2.output || "ingestion script exited non-zero",
4385
+ product_area: "AI Wizard"
4386
+ });
4387
+ }
4388
+ summaries.push(summaryLine);
4389
+ ingestOutcomeMessage = outcomeMessage;
4412
4390
  }
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
4391
  }
4428
4392
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4429
- if (ingestCommand) {
4430
- commandMessages.push(`Ingestion command: ${ingestCommand}`);
4393
+ if (ingestRuntime && ingestEntrypoint) {
4394
+ commandMessages.push(
4395
+ `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4396
+ );
4431
4397
  }
4432
4398
  await ctx.requestUserInput({
4433
4399
  prompt: "",
4434
4400
  promptType: "enterToContinue",
4435
4401
  options: [],
4436
- messages: [ingestOutcomeMessage, ...commandMessages]
4402
+ messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4437
4403
  });
4438
4404
  }
4439
4405
  if (useCases.includes("search")) {
@@ -4482,14 +4448,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4482
4448
  let searchKeyError;
4483
4449
  if (appId) {
4484
4450
  try {
4485
- const resolved = await resolveSearchOnlyKey(
4451
+ const resolved2 = await resolveSearchOnlyKey(
4486
4452
  targetIndex,
4487
4453
  appId,
4488
4454
  envSearchKey
4489
4455
  );
4490
- searchKey = resolved.key;
4456
+ searchKey = resolved2.key;
4491
4457
  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}.`
4458
+ 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
4459
  );
4494
4460
  } catch (err) {
4495
4461
  searchKeyError = err.message;
@@ -4499,7 +4465,12 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4499
4465
  );
4500
4466
  }
4501
4467
  }
4502
- finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
4468
+ finalSearchEnvVars = searchEnvVars(
4469
+ language,
4470
+ targetIndex,
4471
+ appId,
4472
+ searchKey
4473
+ );
4503
4474
  const resolvedSearchEnvVars = finalSearchEnvVars.filter(
4504
4475
  (v) => !v.value.startsWith("<")
4505
4476
  );
@@ -4545,13 +4516,22 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4545
4516
  "implement: agent reported success but no files changed in the worktree"
4546
4517
  );
4547
4518
  }
4519
+ if (installFailed) {
4520
+ summaries.push(
4521
+ '\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
4522
+ );
4523
+ }
4548
4524
  return {
4549
4525
  ingestionSource,
4550
4526
  filesChanged,
4551
4527
  summary: summaries.join("\n\n"),
4552
4528
  worktreePath: worktree,
4553
- ...useCases.includes("ingestion") && ingestCommand ? {
4554
- ingestCommand,
4529
+ ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4530
+ ingestCommand: buildIngestCommand(
4531
+ worktree,
4532
+ ingestRuntime,
4533
+ ingestEntrypoint
4534
+ ),
4555
4535
  ingestScriptRan,
4556
4536
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4557
4537
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4879,10 +4859,11 @@ function parseCliArgs(argv) {
4879
4859
 
4880
4860
  // src/lib/resetState.ts
4881
4861
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4882
- import { join as join10 } from "node:path";
4862
+ import { join as join11 } from "node:path";
4883
4863
  var KEEP = ["wizard.log"];
4884
4864
  async function resetProjectState() {
4885
4865
  const dir = stateDir();
4866
+ forgetResolvedKeys();
4886
4867
  let entries;
4887
4868
  try {
4888
4869
  entries = await readdir4(dir);
@@ -4891,14 +4872,17 @@ async function resetProjectState() {
4891
4872
  }
4892
4873
  const targets = entries.filter((name) => !KEEP.includes(name));
4893
4874
  await Promise.all(
4894
- targets.map((name) => rm2(join10(dir, name), { recursive: true, force: true }))
4875
+ targets.map(
4876
+ (name) => rm2(join11(dir, name), { recursive: true, force: true })
4877
+ )
4895
4878
  );
4896
4879
  return { dir, removed: targets };
4897
4880
  }
4898
4881
 
4899
4882
  // src/main.tsx
4900
- import { jsx as jsx15 } from "react/jsx-runtime";
4883
+ import { jsx as jsx14 } from "react/jsx-runtime";
4901
4884
  async function startup() {
4885
+ setProjectRoot(process.cwd());
4902
4886
  let args;
4903
4887
  try {
4904
4888
  args = parseCliArgs(process.argv.slice(2));
@@ -4946,7 +4930,7 @@ ${formatStepList(workflow)}`);
4946
4930
  }
4947
4931
  async function run(workflow) {
4948
4932
  const store = useWizard.getState();
4949
- const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
4933
+ const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4950
4934
  await store.waitForStart();
4951
4935
  let user = await getUser();
4952
4936
  if (!user) {