@algolia/wizard 0.9.0-rc.85.78 → 0.9.0-rc.87.83
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
|
|
7
|
+
import { Box as Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -13,6 +13,47 @@ 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
|
|
16
57
|
function npxArgs(args) {
|
|
17
58
|
return ["--yes", "@algolia/cli@latest", ...args];
|
|
18
59
|
}
|
|
@@ -89,6 +130,7 @@ function runAlgoliaCli(args, { onOutput } = {}) {
|
|
|
89
130
|
},
|
|
90
131
|
(err) => {
|
|
91
132
|
useWizard.getState().logEnd(logId, "error");
|
|
133
|
+
logger.warn({ err, args }, "Algolia CLI command failed");
|
|
92
134
|
throw err;
|
|
93
135
|
}
|
|
94
136
|
);
|
|
@@ -146,45 +188,6 @@ function refreshAuthToken() {
|
|
|
146
188
|
return inFlightRefresh;
|
|
147
189
|
}
|
|
148
190
|
|
|
149
|
-
// src/lib/logger.ts
|
|
150
|
-
import pino from "pino";
|
|
151
|
-
import { join as join2, dirname } from "node:path";
|
|
152
|
-
import { devNull } from "node:os";
|
|
153
|
-
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
154
|
-
|
|
155
|
-
// src/core/constants.ts
|
|
156
|
-
import { homedir } from "node:os";
|
|
157
|
-
import { join, resolve } from "node:path";
|
|
158
|
-
function rootDir() {
|
|
159
|
-
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
160
|
-
}
|
|
161
|
-
function projectSlug(cwd = process.cwd()) {
|
|
162
|
-
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
163
|
-
}
|
|
164
|
-
function stateDir(cwd = process.cwd()) {
|
|
165
|
-
return join(rootDir(), projectSlug(cwd));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// src/lib/logger.ts
|
|
169
|
-
var STDERR_FD = 2;
|
|
170
|
-
function resolveDest() {
|
|
171
|
-
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
172
|
-
try {
|
|
173
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
174
|
-
closeSync(openSync(target, "a"));
|
|
175
|
-
return target;
|
|
176
|
-
} catch {
|
|
177
|
-
return STDERR_FD;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
function logDestination() {
|
|
181
|
-
return pino.destination({ dest: resolveDest(), sync: false });
|
|
182
|
-
}
|
|
183
|
-
var logger = pino(
|
|
184
|
-
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
185
|
-
logDestination()
|
|
186
|
-
);
|
|
187
|
-
|
|
188
191
|
// src/lib/proxyFetch.ts
|
|
189
192
|
var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
|
|
190
193
|
var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
|
|
@@ -614,10 +617,13 @@ function Notices() {
|
|
|
614
617
|
}
|
|
615
618
|
|
|
616
619
|
// src/ui/PromptInput.tsx
|
|
617
|
-
import { Box as
|
|
620
|
+
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
618
621
|
import TextInput from "ink-text-input";
|
|
619
622
|
import { useState as useState5 } from "react";
|
|
620
623
|
|
|
624
|
+
// src/ui/CommandApproval.tsx
|
|
625
|
+
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
626
|
+
|
|
621
627
|
// src/ui/NextAction.tsx
|
|
622
628
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
623
629
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -641,14 +647,69 @@ function NextAction({
|
|
|
641
647
|
] });
|
|
642
648
|
}
|
|
643
649
|
|
|
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
|
+
|
|
644
705
|
// src/ui/SelectPrompt.tsx
|
|
645
|
-
import { Box as
|
|
706
|
+
import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
|
|
646
707
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
647
708
|
|
|
648
709
|
// src/ui/ScrollView.tsx
|
|
649
|
-
import { Box as
|
|
710
|
+
import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
650
711
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
651
|
-
import { jsxs as
|
|
712
|
+
import { jsxs as jsxs5 } from "react/jsx-runtime";
|
|
652
713
|
var INDICATOR_ROWS = 2;
|
|
653
714
|
function fittedWidth(node, columns) {
|
|
654
715
|
let left = 0;
|
|
@@ -718,14 +779,14 @@ function useScrollWindow({
|
|
|
718
779
|
};
|
|
719
780
|
}
|
|
720
781
|
function ScrollView({ scroll, children }) {
|
|
721
|
-
return /* @__PURE__ */
|
|
722
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
782
|
+
return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
783
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
723
784
|
"\u2191 ",
|
|
724
785
|
scroll.hiddenAbove,
|
|
725
786
|
" more"
|
|
726
787
|
] }),
|
|
727
788
|
children,
|
|
728
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
789
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
729
790
|
"\u2193 ",
|
|
730
791
|
scroll.hiddenBelow,
|
|
731
792
|
" more"
|
|
@@ -734,7 +795,7 @@ function ScrollView({ scroll, children }) {
|
|
|
734
795
|
}
|
|
735
796
|
|
|
736
797
|
// src/ui/SelectPrompt.tsx
|
|
737
|
-
import { jsx as
|
|
798
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
738
799
|
var CANCEL = "cancel";
|
|
739
800
|
var ARROW_WIDTH = 4;
|
|
740
801
|
var COLUMN_GAP = 2;
|
|
@@ -796,7 +857,7 @@ function SelectPrompt({
|
|
|
796
857
|
revealIndex(index);
|
|
797
858
|
}, [index, revealIndex]);
|
|
798
859
|
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
799
|
-
|
|
860
|
+
useInput2((input, key) => {
|
|
800
861
|
if (rows.length === 0) return;
|
|
801
862
|
if (key.upArrow || input === "k") {
|
|
802
863
|
setIndex((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -819,56 +880,56 @@ function SelectPrompt({
|
|
|
819
880
|
}
|
|
820
881
|
}
|
|
821
882
|
});
|
|
822
|
-
return /* @__PURE__ */
|
|
823
|
-
/* @__PURE__ */
|
|
824
|
-
error && /* @__PURE__ */
|
|
825
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
826
|
-
table && /* @__PURE__ */
|
|
827
|
-
/* @__PURE__ */
|
|
828
|
-
question && /* @__PURE__ */
|
|
829
|
-
helpText && /* @__PURE__ */
|
|
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 })
|
|
830
891
|
] })
|
|
831
892
|
] }),
|
|
832
|
-
/* @__PURE__ */
|
|
893
|
+
/* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
833
894
|
const i = scroll.offset + visibleIndex;
|
|
834
895
|
const highlighted = i === index;
|
|
835
896
|
const isCancel = i === cancelIndex;
|
|
836
897
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
837
898
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
838
899
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
839
|
-
const label = /* @__PURE__ */
|
|
900
|
+
const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
|
|
840
901
|
highlighted ? "\u276F " : " ",
|
|
841
902
|
bullet,
|
|
842
903
|
option
|
|
843
904
|
] });
|
|
844
905
|
const isText = sec?.kind === "text";
|
|
845
|
-
return /* @__PURE__ */
|
|
846
|
-
|
|
906
|
+
return /* @__PURE__ */ jsxs6(
|
|
907
|
+
Box7,
|
|
847
908
|
{
|
|
848
909
|
width: isText ? "100%" : barWidth,
|
|
849
910
|
paddingX: 1,
|
|
850
911
|
paddingY: 1,
|
|
851
912
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
852
913
|
children: [
|
|
853
|
-
/* @__PURE__ */
|
|
854
|
-
isText && textWidth > 0 && /* @__PURE__ */
|
|
855
|
-
|
|
914
|
+
/* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
915
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
|
|
916
|
+
Text7,
|
|
856
917
|
{
|
|
857
918
|
wrap: "truncate",
|
|
858
919
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
859
920
|
children: sec.value
|
|
860
921
|
}
|
|
861
922
|
) }),
|
|
862
|
-
sec?.kind === "badge" && /* @__PURE__ */
|
|
923
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
863
924
|
]
|
|
864
925
|
},
|
|
865
926
|
`row-${i}`
|
|
866
927
|
);
|
|
867
928
|
}) }),
|
|
868
|
-
/* @__PURE__ */
|
|
929
|
+
/* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
869
930
|
i > 0 ? " " : "",
|
|
870
|
-
/* @__PURE__ */
|
|
871
|
-
/* @__PURE__ */
|
|
931
|
+
/* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
|
|
932
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
|
|
872
933
|
" ",
|
|
873
934
|
label
|
|
874
935
|
] })
|
|
@@ -877,23 +938,23 @@ function SelectPrompt({
|
|
|
877
938
|
}
|
|
878
939
|
|
|
879
940
|
// src/ui/PromptInput.tsx
|
|
880
|
-
import { jsx as
|
|
941
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
881
942
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
882
943
|
function EnterToContinuePrompt({
|
|
883
944
|
question,
|
|
884
945
|
messages,
|
|
885
946
|
onDecide
|
|
886
947
|
}) {
|
|
887
|
-
|
|
948
|
+
useInput3((_input, key) => {
|
|
888
949
|
if (key.return) onDecide(true);
|
|
889
950
|
else if (key.escape) onDecide(false);
|
|
890
951
|
});
|
|
891
|
-
return /* @__PURE__ */
|
|
892
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
893
|
-
question && /* @__PURE__ */
|
|
894
|
-
/* @__PURE__ */
|
|
895
|
-
/* @__PURE__ */
|
|
896
|
-
/* @__PURE__ */
|
|
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" })
|
|
897
958
|
] })
|
|
898
959
|
] });
|
|
899
960
|
}
|
|
@@ -901,11 +962,11 @@ function PromptInput() {
|
|
|
901
962
|
const { phase, inputReq, submitInput } = useWizard();
|
|
902
963
|
const [draft, setDraft] = useState5("");
|
|
903
964
|
if (phase === "done" || phase === "error") {
|
|
904
|
-
return /* @__PURE__ */
|
|
965
|
+
return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
905
966
|
}
|
|
906
967
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
907
968
|
if (inputReq.promptType === "multipleChoice") {
|
|
908
|
-
return /* @__PURE__ */
|
|
969
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
909
970
|
SelectPrompt,
|
|
910
971
|
{
|
|
911
972
|
question: inputReq.prompt,
|
|
@@ -922,7 +983,7 @@ function PromptInput() {
|
|
|
922
983
|
) });
|
|
923
984
|
}
|
|
924
985
|
if (inputReq.promptType === "multiSelect") {
|
|
925
|
-
return /* @__PURE__ */
|
|
986
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
926
987
|
SelectPrompt,
|
|
927
988
|
{
|
|
928
989
|
multi: true,
|
|
@@ -937,7 +998,7 @@ function PromptInput() {
|
|
|
937
998
|
) });
|
|
938
999
|
}
|
|
939
1000
|
if (inputReq.promptType === "notice") {
|
|
940
|
-
return /* @__PURE__ */
|
|
1001
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
941
1002
|
SelectPrompt,
|
|
942
1003
|
{
|
|
943
1004
|
question: inputReq.prompt,
|
|
@@ -948,7 +1009,7 @@ function PromptInput() {
|
|
|
948
1009
|
) });
|
|
949
1010
|
}
|
|
950
1011
|
if (inputReq.promptType === "enterToContinue") {
|
|
951
|
-
return /* @__PURE__ */
|
|
1012
|
+
return /* @__PURE__ */ jsx6(
|
|
952
1013
|
EnterToContinuePrompt,
|
|
953
1014
|
{
|
|
954
1015
|
question: inputReq.prompt,
|
|
@@ -957,9 +1018,12 @@ function PromptInput() {
|
|
|
957
1018
|
}
|
|
958
1019
|
);
|
|
959
1020
|
}
|
|
1021
|
+
if (inputReq.promptType === "commandApproval" && inputReq.command) {
|
|
1022
|
+
return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
|
|
1023
|
+
}
|
|
960
1024
|
if (inputReq.promptType === "acceptReject") {
|
|
961
1025
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
962
|
-
return /* @__PURE__ */
|
|
1026
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
963
1027
|
SelectPrompt,
|
|
964
1028
|
{
|
|
965
1029
|
question: inputReq.prompt,
|
|
@@ -970,15 +1034,15 @@ function PromptInput() {
|
|
|
970
1034
|
}
|
|
971
1035
|
) });
|
|
972
1036
|
}
|
|
973
|
-
return /* @__PURE__ */
|
|
974
|
-
inputReq.error && /* @__PURE__ */
|
|
975
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
/* @__PURE__ */
|
|
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: [
|
|
978
1042
|
inputReq.prompt,
|
|
979
1043
|
" "
|
|
980
1044
|
] }),
|
|
981
|
-
/* @__PURE__ */
|
|
1045
|
+
/* @__PURE__ */ jsx6(
|
|
982
1046
|
TextInput,
|
|
983
1047
|
{
|
|
984
1048
|
value: draft,
|
|
@@ -996,7 +1060,7 @@ function PromptInput() {
|
|
|
996
1060
|
// src/ui/Welcome.tsx
|
|
997
1061
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
998
1062
|
import { fileURLToPath } from "node:url";
|
|
999
|
-
import { Box as
|
|
1063
|
+
import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
1000
1064
|
|
|
1001
1065
|
// src/ui/copy/welcome.ts
|
|
1002
1066
|
var sidebarItems = [
|
|
@@ -1009,12 +1073,12 @@ var sidebarItems = [
|
|
|
1009
1073
|
description: "push 100 records to Algolia in seconds"
|
|
1010
1074
|
},
|
|
1011
1075
|
{
|
|
1012
|
-
title: "detect your
|
|
1013
|
-
description: "
|
|
1076
|
+
title: "detect your stack",
|
|
1077
|
+
description: "whatever language and framework you already use"
|
|
1014
1078
|
},
|
|
1015
1079
|
{
|
|
1016
1080
|
title: "scaffold a search UI",
|
|
1017
|
-
description: "a
|
|
1081
|
+
description: "a search box and results, wired into your app"
|
|
1018
1082
|
},
|
|
1019
1083
|
{
|
|
1020
1084
|
title: "ship it",
|
|
@@ -1024,20 +1088,20 @@ var sidebarItems = [
|
|
|
1024
1088
|
|
|
1025
1089
|
// src/ui/Welcome.tsx
|
|
1026
1090
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
1027
|
-
import { jsx as
|
|
1091
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1028
1092
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
1029
1093
|
function SidebarItem({
|
|
1030
1094
|
title,
|
|
1031
1095
|
description
|
|
1032
1096
|
}) {
|
|
1033
|
-
return /* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
/* @__PURE__ */
|
|
1036
|
-
/* @__PURE__ */
|
|
1097
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
1098
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
1099
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
|
|
1100
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
|
|
1037
1101
|
] }),
|
|
1038
|
-
/* @__PURE__ */
|
|
1039
|
-
/* @__PURE__ */
|
|
1040
|
-
/* @__PURE__ */
|
|
1102
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
|
|
1103
|
+
/* @__PURE__ */ jsx7(Spacer, {}),
|
|
1104
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
|
|
1041
1105
|
] })
|
|
1042
1106
|
] });
|
|
1043
1107
|
}
|
|
@@ -1045,7 +1109,7 @@ function Welcome() {
|
|
|
1045
1109
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1046
1110
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
1047
1111
|
const { rows } = useWindowSize6();
|
|
1048
|
-
|
|
1112
|
+
useInput4((input, key) => {
|
|
1049
1113
|
if (key.return) confirmStart();
|
|
1050
1114
|
else if (input === "i") openLearnMore();
|
|
1051
1115
|
});
|
|
@@ -1063,16 +1127,16 @@ function Welcome() {
|
|
|
1063
1127
|
if (rows < 30) {
|
|
1064
1128
|
layout = scales["small"];
|
|
1065
1129
|
}
|
|
1066
|
-
return /* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
1068
|
-
|
|
1130
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx7(
|
|
1132
|
+
Box9,
|
|
1069
1133
|
{
|
|
1070
1134
|
paddingY: layout.main.padding.y,
|
|
1071
1135
|
paddingX: layout.main.padding.x,
|
|
1072
1136
|
flexDirection: "column",
|
|
1073
1137
|
justifyContent: "center",
|
|
1074
|
-
children: /* @__PURE__ */
|
|
1075
|
-
/* @__PURE__ */
|
|
1138
|
+
children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
|
|
1139
|
+
/* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
|
|
1076
1140
|
Image,
|
|
1077
1141
|
{
|
|
1078
1142
|
src: IMAGE_PATH,
|
|
@@ -1083,16 +1147,16 @@ function Welcome() {
|
|
|
1083
1147
|
protocol: "halfBlock"
|
|
1084
1148
|
}
|
|
1085
1149
|
) }),
|
|
1086
|
-
/* @__PURE__ */
|
|
1087
|
-
/* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
1089
|
-
/* @__PURE__ */
|
|
1150
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1151
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
|
|
1152
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
1153
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
1090
1154
|
] })
|
|
1091
1155
|
] })
|
|
1092
1156
|
}
|
|
1093
1157
|
),
|
|
1094
|
-
/* @__PURE__ */
|
|
1095
|
-
|
|
1158
|
+
/* @__PURE__ */ jsxs8(
|
|
1159
|
+
Box9,
|
|
1096
1160
|
{
|
|
1097
1161
|
backgroundColor: COLORS.bg.sidebar,
|
|
1098
1162
|
width: 40,
|
|
@@ -1102,8 +1166,8 @@ function Welcome() {
|
|
|
1102
1166
|
flexDirection: "column",
|
|
1103
1167
|
justifyContent: "center",
|
|
1104
1168
|
children: [
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */
|
|
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))
|
|
1107
1171
|
]
|
|
1108
1172
|
}
|
|
1109
1173
|
)
|
|
@@ -1112,7 +1176,7 @@ function Welcome() {
|
|
|
1112
1176
|
|
|
1113
1177
|
// src/ui/LearnMore.tsx
|
|
1114
1178
|
import { Fragment as Fragment2 } from "react";
|
|
1115
|
-
import { Box as
|
|
1179
|
+
import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
|
|
1116
1180
|
|
|
1117
1181
|
// src/ui/copy/learn-more.ts
|
|
1118
1182
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1120,12 +1184,17 @@ var accessItems = [
|
|
|
1120
1184
|
{
|
|
1121
1185
|
tag: "READ",
|
|
1122
1186
|
title: "Project files",
|
|
1123
|
-
description: "reads
|
|
1187
|
+
description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1124
1188
|
},
|
|
1125
1189
|
{
|
|
1126
1190
|
tag: "WRITE",
|
|
1127
1191
|
title: "Code changes",
|
|
1128
|
-
description: "creates & edits files (search UI, config)
|
|
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."
|
|
1129
1198
|
},
|
|
1130
1199
|
{
|
|
1131
1200
|
tag: "NET",
|
|
@@ -1135,13 +1204,13 @@ var accessItems = [
|
|
|
1135
1204
|
{
|
|
1136
1205
|
tag: "KEY",
|
|
1137
1206
|
title: "Credentials",
|
|
1138
|
-
description: "
|
|
1207
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
|
|
1139
1208
|
}
|
|
1140
1209
|
];
|
|
1141
1210
|
var neverItems = [
|
|
1142
1211
|
"Send your source code to a model or third party",
|
|
1143
1212
|
"Commit or push to git",
|
|
1144
|
-
"
|
|
1213
|
+
"Run a command you haven't approved"
|
|
1145
1214
|
];
|
|
1146
1215
|
var policyLinks = [
|
|
1147
1216
|
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
@@ -1149,10 +1218,11 @@ var policyLinks = [
|
|
|
1149
1218
|
];
|
|
1150
1219
|
|
|
1151
1220
|
// src/ui/LearnMore.tsx
|
|
1152
|
-
import { jsx as
|
|
1221
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1153
1222
|
var TAG_COLORS = {
|
|
1154
1223
|
READ: COLORS.success,
|
|
1155
1224
|
WRITE: COLORS.badge,
|
|
1225
|
+
EXEC: COLORS.danger,
|
|
1156
1226
|
NET: COLORS.accent,
|
|
1157
1227
|
KEY: COLORS.muted
|
|
1158
1228
|
};
|
|
@@ -1165,12 +1235,12 @@ function NeverLine({
|
|
|
1165
1235
|
}) {
|
|
1166
1236
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1167
1237
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1168
|
-
return /* @__PURE__ */
|
|
1169
|
-
/* @__PURE__ */
|
|
1238
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1239
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
|
|
1170
1240
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1171
|
-
segments.map((s, i) => /* @__PURE__ */
|
|
1241
|
+
segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1172
1242
|
" ".repeat(rightPad),
|
|
1173
|
-
/* @__PURE__ */
|
|
1243
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
|
|
1174
1244
|
] });
|
|
1175
1245
|
}
|
|
1176
1246
|
function LearnMore() {
|
|
@@ -1178,12 +1248,12 @@ function LearnMore() {
|
|
|
1178
1248
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1179
1249
|
const { columns } = useWindowSize7();
|
|
1180
1250
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1181
|
-
|
|
1251
|
+
useInput5((_input, key) => {
|
|
1182
1252
|
if (key.escape) backToHome();
|
|
1183
1253
|
else if (key.return) confirmStart();
|
|
1184
1254
|
});
|
|
1185
|
-
return /* @__PURE__ */
|
|
1186
|
-
|
|
1255
|
+
return /* @__PURE__ */ jsxs9(
|
|
1256
|
+
Box10,
|
|
1187
1257
|
{
|
|
1188
1258
|
flexDirection: "column",
|
|
1189
1259
|
paddingX: PADDING_X,
|
|
@@ -1191,31 +1261,31 @@ function LearnMore() {
|
|
|
1191
1261
|
width: "100%",
|
|
1192
1262
|
gap: 1,
|
|
1193
1263
|
children: [
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
/* @__PURE__ */
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
/* @__PURE__ */
|
|
1198
|
-
/* @__PURE__ */
|
|
1199
|
-
/* @__PURE__ */
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
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}` })
|
|
1203
1273
|
] }) })
|
|
1204
1274
|
] })
|
|
1205
1275
|
] }, item.tag)) }),
|
|
1206
|
-
/* @__PURE__ */
|
|
1207
|
-
/* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1276
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
1277
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1278
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1279
|
+
/* @__PURE__ */ jsx8(
|
|
1210
1280
|
NeverLine,
|
|
1211
1281
|
{
|
|
1212
1282
|
width: dividerWidth,
|
|
1213
1283
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1214
1284
|
}
|
|
1215
1285
|
),
|
|
1216
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1217
|
-
/* @__PURE__ */
|
|
1218
|
-
/* @__PURE__ */
|
|
1286
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
1287
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1288
|
+
/* @__PURE__ */ jsx8(
|
|
1219
1289
|
NeverLine,
|
|
1220
1290
|
{
|
|
1221
1291
|
width: dividerWidth,
|
|
@@ -1227,24 +1297,24 @@ function LearnMore() {
|
|
|
1227
1297
|
}
|
|
1228
1298
|
)
|
|
1229
1299
|
] }, item)),
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1300
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1301
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1232
1302
|
] }),
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
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 })
|
|
1236
1306
|
] }, link.label)) }),
|
|
1237
|
-
/* @__PURE__ */
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
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" })
|
|
1242
1312
|
] }),
|
|
1243
|
-
/* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
1245
|
-
/* @__PURE__ */
|
|
1246
|
-
/* @__PURE__ */
|
|
1247
|
-
/* @__PURE__ */
|
|
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" })
|
|
1248
1318
|
] })
|
|
1249
1319
|
] })
|
|
1250
1320
|
]
|
|
@@ -1253,10 +1323,10 @@ function LearnMore() {
|
|
|
1253
1323
|
}
|
|
1254
1324
|
|
|
1255
1325
|
// src/ui/Sidebar.tsx
|
|
1256
|
-
import { Box as
|
|
1326
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1257
1327
|
|
|
1258
1328
|
// src/ui/Steps.tsx
|
|
1259
|
-
import { Box as
|
|
1329
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1260
1330
|
import Spinner from "ink-spinner";
|
|
1261
1331
|
|
|
1262
1332
|
// src/core/persistence.ts
|
|
@@ -1285,12 +1355,12 @@ async function clearWorkflowState(workflowId) {
|
|
|
1285
1355
|
}
|
|
1286
1356
|
|
|
1287
1357
|
// src/ui/Steps.tsx
|
|
1288
|
-
import { jsx as
|
|
1358
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1289
1359
|
function Steps() {
|
|
1290
1360
|
const { steps } = useWizard();
|
|
1291
1361
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1292
|
-
return /* @__PURE__ */
|
|
1293
|
-
s.status === "running" ? /* @__PURE__ */
|
|
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],
|
|
1294
1364
|
" ",
|
|
1295
1365
|
s.title
|
|
1296
1366
|
] }) }, s.id)) });
|
|
@@ -1299,27 +1369,27 @@ function CurrentStep() {
|
|
|
1299
1369
|
const { steps } = useWizard();
|
|
1300
1370
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1301
1371
|
if (!currentStep) return null;
|
|
1302
|
-
return /* @__PURE__ */
|
|
1303
|
-
/* @__PURE__ */
|
|
1372
|
+
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
|
|
1373
|
+
/* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
|
|
1304
1374
|
" ",
|
|
1305
1375
|
` ${currentStep.title}`
|
|
1306
1376
|
] });
|
|
1307
1377
|
}
|
|
1308
1378
|
|
|
1309
1379
|
// src/ui/Progress.tsx
|
|
1310
|
-
import { Box as
|
|
1311
|
-
import { jsx as
|
|
1380
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1381
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1312
1382
|
function Progress() {
|
|
1313
1383
|
const { steps, currentStepIndex } = useWizard();
|
|
1314
1384
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1315
1385
|
if (visibleSteps.length === 0) return null;
|
|
1316
1386
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1317
1387
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1318
|
-
return /* @__PURE__ */
|
|
1319
|
-
/* @__PURE__ */
|
|
1320
|
-
/* @__PURE__ */
|
|
1321
|
-
/* @__PURE__ */
|
|
1322
|
-
/* @__PURE__ */
|
|
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 })
|
|
1323
1393
|
] });
|
|
1324
1394
|
}
|
|
1325
1395
|
|
|
@@ -1330,10 +1400,10 @@ var sidebarCommands = [
|
|
|
1330
1400
|
];
|
|
1331
1401
|
|
|
1332
1402
|
// src/ui/Sidebar.tsx
|
|
1333
|
-
import { jsx as
|
|
1403
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1334
1404
|
function Sidebar() {
|
|
1335
|
-
return /* @__PURE__ */
|
|
1336
|
-
|
|
1405
|
+
return /* @__PURE__ */ jsxs12(
|
|
1406
|
+
Box13,
|
|
1337
1407
|
{
|
|
1338
1408
|
backgroundColor: "#14171E",
|
|
1339
1409
|
width: 30,
|
|
@@ -1342,16 +1412,16 @@ function Sidebar() {
|
|
|
1342
1412
|
flexDirection: "column",
|
|
1343
1413
|
justifyContent: "space-between",
|
|
1344
1414
|
children: [
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1415
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1416
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1417
|
+
/* @__PURE__ */ jsx11(Steps, {})
|
|
1348
1418
|
] }),
|
|
1349
|
-
/* @__PURE__ */
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1352
|
-
return /* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
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 })
|
|
1355
1425
|
] });
|
|
1356
1426
|
}) })
|
|
1357
1427
|
] })
|
|
@@ -1361,12 +1431,12 @@ function Sidebar() {
|
|
|
1361
1431
|
}
|
|
1362
1432
|
|
|
1363
1433
|
// src/ui/Ribbon.tsx
|
|
1364
|
-
import { Box as
|
|
1365
|
-
import { jsx as
|
|
1434
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1435
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1366
1436
|
function Ribbon() {
|
|
1367
1437
|
const firstCommand = sidebarCommands[0];
|
|
1368
|
-
return /* @__PURE__ */
|
|
1369
|
-
|
|
1438
|
+
return /* @__PURE__ */ jsxs13(
|
|
1439
|
+
Box14,
|
|
1370
1440
|
{
|
|
1371
1441
|
backgroundColor: "#14171E",
|
|
1372
1442
|
flexDirection: "row",
|
|
@@ -1374,11 +1444,11 @@ function Ribbon() {
|
|
|
1374
1444
|
paddingX: 2,
|
|
1375
1445
|
paddingY: 1,
|
|
1376
1446
|
children: [
|
|
1377
|
-
/* @__PURE__ */
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
/* @__PURE__ */
|
|
1380
|
-
/* @__PURE__ */
|
|
1381
|
-
/* @__PURE__ */
|
|
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 })
|
|
1382
1452
|
] })
|
|
1383
1453
|
]
|
|
1384
1454
|
}
|
|
@@ -1389,8 +1459,8 @@ function Ribbon() {
|
|
|
1389
1459
|
import { useState as useState6 } from "react";
|
|
1390
1460
|
|
|
1391
1461
|
// src/ui/Logs.tsx
|
|
1392
|
-
import { Box as
|
|
1393
|
-
import { jsx as
|
|
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";
|
|
1394
1464
|
var KIND_COLOR = {
|
|
1395
1465
|
tool: COLORS.primary,
|
|
1396
1466
|
prompt: COLORS.badge
|
|
@@ -1421,14 +1491,14 @@ function formatTimestamp(ms) {
|
|
|
1421
1491
|
function Logs() {
|
|
1422
1492
|
const logs = useWizard((s) => s.logs);
|
|
1423
1493
|
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1424
|
-
|
|
1494
|
+
useInput6((_input, key) => {
|
|
1425
1495
|
if (key.upArrow) scroll.scrollBy(-1);
|
|
1426
1496
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1427
1497
|
});
|
|
1428
1498
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1429
|
-
return /* @__PURE__ */
|
|
1430
|
-
logs.length === 0 && /* @__PURE__ */
|
|
1431
|
-
/* @__PURE__ */
|
|
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) => {
|
|
1432
1502
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1433
1503
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1434
1504
|
const rawPreview = rawInputText(entry.input);
|
|
@@ -1438,14 +1508,14 @@ function Logs() {
|
|
|
1438
1508
|
const name = truncate2(entry.name, budget);
|
|
1439
1509
|
budget -= name.length;
|
|
1440
1510
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1441
|
-
return /* @__PURE__ */
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1444
|
-
preview && /* @__PURE__ */
|
|
1445
|
-
durationText && /* @__PURE__ */
|
|
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 })
|
|
1446
1516
|
] }, entry.id);
|
|
1447
1517
|
}) }),
|
|
1448
|
-
/* @__PURE__ */
|
|
1518
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1449
1519
|
] });
|
|
1450
1520
|
}
|
|
1451
1521
|
|
|
@@ -1637,7 +1707,7 @@ function track(event, payload) {
|
|
|
1637
1707
|
}
|
|
1638
1708
|
|
|
1639
1709
|
// src/ui/App.tsx
|
|
1640
|
-
import { jsx as
|
|
1710
|
+
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1641
1711
|
function App() {
|
|
1642
1712
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1643
1713
|
const { exit } = useApp();
|
|
@@ -1645,7 +1715,7 @@ function App() {
|
|
|
1645
1715
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1646
1716
|
const finished = phase === "done" || phase === "error";
|
|
1647
1717
|
const currentStep = steps[currentStepIndex];
|
|
1648
|
-
|
|
1718
|
+
useInput7(
|
|
1649
1719
|
(_input, key) => {
|
|
1650
1720
|
if (key.return) {
|
|
1651
1721
|
exit();
|
|
@@ -1653,7 +1723,7 @@ function App() {
|
|
|
1653
1723
|
},
|
|
1654
1724
|
{ isActive: finished }
|
|
1655
1725
|
);
|
|
1656
|
-
|
|
1726
|
+
useInput7((_input, key) => {
|
|
1657
1727
|
if (phase === "idle" || phase === "authenticating") return;
|
|
1658
1728
|
if (key.tab) {
|
|
1659
1729
|
setShowLogs(!showLogs);
|
|
@@ -1664,8 +1734,8 @@ function App() {
|
|
|
1664
1734
|
});
|
|
1665
1735
|
}
|
|
1666
1736
|
});
|
|
1667
|
-
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1668
|
-
|
|
1737
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
1738
|
+
useInput7((_input, key) => {
|
|
1669
1739
|
if (escOwnedElsewhere) return;
|
|
1670
1740
|
if (key.escape) {
|
|
1671
1741
|
track("AI Wizard Interaction", {
|
|
@@ -1684,8 +1754,8 @@ function App() {
|
|
|
1684
1754
|
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1685
1755
|
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1686
1756
|
flicker and leftover rows. */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
|
|
1757
|
+
/* @__PURE__ */ jsxs15(
|
|
1758
|
+
Box16,
|
|
1689
1759
|
{
|
|
1690
1760
|
backgroundColor: COLORS.bg.main,
|
|
1691
1761
|
flexDirection: "row",
|
|
@@ -1693,16 +1763,16 @@ function App() {
|
|
|
1693
1763
|
height: scrollsPastViewport ? void 0 : rows,
|
|
1694
1764
|
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1695
1765
|
children: [
|
|
1696
|
-
mainWindowVisible && /* @__PURE__ */
|
|
1697
|
-
|
|
1766
|
+
mainWindowVisible && /* @__PURE__ */ jsxs15(
|
|
1767
|
+
Box16,
|
|
1698
1768
|
{
|
|
1699
1769
|
flexDirection,
|
|
1700
1770
|
width: "100%",
|
|
1701
1771
|
maxHeight: rows,
|
|
1702
1772
|
justifyContent: "space-between",
|
|
1703
1773
|
children: [
|
|
1704
|
-
showLogs ? /* @__PURE__ */
|
|
1705
|
-
|
|
1774
|
+
showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
|
|
1775
|
+
Box16,
|
|
1706
1776
|
{
|
|
1707
1777
|
flexDirection: "column",
|
|
1708
1778
|
paddingX: 4,
|
|
@@ -1710,26 +1780,26 @@ function App() {
|
|
|
1710
1780
|
width: showSidebar ? 70 : "100%",
|
|
1711
1781
|
flexGrow: 1,
|
|
1712
1782
|
children: [
|
|
1713
|
-
phase === "authenticating" && /* @__PURE__ */
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
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." })
|
|
1716
1786
|
] }),
|
|
1717
|
-
/* @__PURE__ */
|
|
1718
|
-
/* @__PURE__ */
|
|
1719
|
-
/* @__PURE__ */
|
|
1720
|
-
phase === "running" && showSidebar && /* @__PURE__ */
|
|
1721
|
-
phase === "error" && error && /* @__PURE__ */
|
|
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: [
|
|
1722
1792
|
"\u2716 ",
|
|
1723
1793
|
error
|
|
1724
1794
|
] }) })
|
|
1725
1795
|
]
|
|
1726
1796
|
}
|
|
1727
1797
|
),
|
|
1728
|
-
showSidebar ? /* @__PURE__ */
|
|
1798
|
+
showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
|
|
1729
1799
|
]
|
|
1730
1800
|
}
|
|
1731
1801
|
),
|
|
1732
|
-
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */
|
|
1802
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
|
|
1733
1803
|
]
|
|
1734
1804
|
}
|
|
1735
1805
|
)
|
|
@@ -1746,7 +1816,8 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1746
1816
|
var DEFAULT_CONFIG = {
|
|
1747
1817
|
version: 1,
|
|
1748
1818
|
aiConsent: false,
|
|
1749
|
-
workflowsRun: []
|
|
1819
|
+
workflowsRun: [],
|
|
1820
|
+
searchApiKeys: {}
|
|
1750
1821
|
};
|
|
1751
1822
|
async function loadConfig() {
|
|
1752
1823
|
try {
|
|
@@ -1765,6 +1836,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1765
1836
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1766
1837
|
await saveConfig(config);
|
|
1767
1838
|
}
|
|
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
|
+
}
|
|
1768
1871
|
|
|
1769
1872
|
// src/core/orchestrator.ts
|
|
1770
1873
|
function defineStep(step) {
|
|
@@ -1809,7 +1912,7 @@ async function ensureConsent() {
|
|
|
1809
1912
|
if (config.aiConsent) return;
|
|
1810
1913
|
const store = useWizard.getState();
|
|
1811
1914
|
const answer = await store.requestUserInput({
|
|
1812
|
-
prompt: "Wizard will make AI-authored changes to this repository.",
|
|
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.",
|
|
1813
1916
|
promptType: "enterToContinue",
|
|
1814
1917
|
options: []
|
|
1815
1918
|
});
|
|
@@ -2357,7 +2460,6 @@ import { dirname as dirname5 } from "node:path";
|
|
|
2357
2460
|
|
|
2358
2461
|
// src/lib/algoliaApiKey.ts
|
|
2359
2462
|
import { z as z11 } from "zod";
|
|
2360
|
-
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
2361
2463
|
var WRITE_ACLS = [
|
|
2362
2464
|
"addObject",
|
|
2363
2465
|
"deleteObject",
|
|
@@ -2371,41 +2473,40 @@ var apiKeySchema = z11.object({
|
|
|
2371
2473
|
acl: z11.array(z11.string()).default([]),
|
|
2372
2474
|
indexes: z11.array(z11.string()).default([])
|
|
2373
2475
|
});
|
|
2374
|
-
var apiKeyListSchema = z11.
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
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
|
+
}
|
|
2378
2498
|
var createdKeySchema = z11.object({
|
|
2379
2499
|
key: z11.string().min(1).optional(),
|
|
2380
2500
|
value: z11.string().min(1).optional()
|
|
2381
|
-
});
|
|
2382
|
-
function canReuse(key, index) {
|
|
2383
|
-
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
2384
|
-
}
|
|
2385
|
-
async function createSearchKey(index) {
|
|
2386
|
-
const stdout = await runAlgoliaCli([
|
|
2387
|
-
"apikeys",
|
|
2388
|
-
"create",
|
|
2389
|
-
"--indices",
|
|
2390
|
-
index,
|
|
2391
|
-
"--acl",
|
|
2392
|
-
"search,browse",
|
|
2393
|
-
"--description",
|
|
2394
|
-
`wizard search-only key for ${index}`,
|
|
2395
|
-
"-o",
|
|
2396
|
-
"json"
|
|
2397
|
-
]);
|
|
2398
|
-
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
2399
|
-
const created = key ?? value;
|
|
2400
|
-
if (!created) throw new Error("apikeys create returned no key value");
|
|
2401
|
-
return created;
|
|
2402
|
-
}
|
|
2501
|
+
}).transform((o) => o.key ?? o.value);
|
|
2403
2502
|
function canReuseForWrites(key, index) {
|
|
2404
2503
|
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2405
2504
|
}
|
|
2406
2505
|
async function resolveWriteKey(index) {
|
|
2407
2506
|
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2408
|
-
const existing = apiKeyListSchema
|
|
2507
|
+
const existing = parseCliJson(apiKeyListSchema, stdout, "apikeys list").find(
|
|
2508
|
+
(key) => canReuseForWrites(key, index)
|
|
2509
|
+
)?.value;
|
|
2409
2510
|
if (existing) {
|
|
2410
2511
|
logger.info({ index }, "reusing existing write API key");
|
|
2411
2512
|
return existing;
|
|
@@ -2423,20 +2524,72 @@ async function resolveWriteKey(index) {
|
|
|
2423
2524
|
"-o",
|
|
2424
2525
|
"json"
|
|
2425
2526
|
]);
|
|
2426
|
-
const
|
|
2427
|
-
const writeKey = key ?? value;
|
|
2527
|
+
const writeKey = parseCliJson(createdKeySchema, created, "apikeys create");
|
|
2428
2528
|
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2429
2529
|
return writeKey;
|
|
2430
2530
|
}
|
|
2431
|
-
async function
|
|
2432
|
-
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
+
]);
|
|
2545
|
+
let payload;
|
|
2546
|
+
try {
|
|
2547
|
+
payload = JSON.parse(stdout);
|
|
2548
|
+
} catch {
|
|
2549
|
+
throw new Error("apikeys create returned output that is not valid JSON");
|
|
2550
|
+
}
|
|
2551
|
+
const created = createdKeySchema.parse(payload);
|
|
2552
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2553
|
+
return created;
|
|
2554
|
+
}
|
|
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
|
+
}
|
|
2562
|
+
}
|
|
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" };
|
|
2573
|
+
}
|
|
2574
|
+
logger.warn(
|
|
2575
|
+
{ index, appId },
|
|
2576
|
+
"the stored search-only API key no longer exists; creating a replacement"
|
|
2577
|
+
);
|
|
2578
|
+
await forgetSearchKey(index);
|
|
2579
|
+
}
|
|
2580
|
+
const key = await createSearchOnlyKey(index);
|
|
2581
|
+
await recordSearchKey(index, appId, key);
|
|
2582
|
+
return { key, source: "created" };
|
|
2583
|
+
}
|
|
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
|
+
);
|
|
2437
2592
|
}
|
|
2438
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
2439
|
-
return createSearchKey(index);
|
|
2440
2593
|
}
|
|
2441
2594
|
|
|
2442
2595
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
@@ -2511,11 +2664,19 @@ import z13 from "zod";
|
|
|
2511
2664
|
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2512
2665
|
import { join as join7 } from "node:path";
|
|
2513
2666
|
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
|
+
]);
|
|
2514
2676
|
async function walkFiles(dir) {
|
|
2515
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2516
2677
|
const out = [];
|
|
2517
2678
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2518
|
-
if (e.name.startsWith(".") ||
|
|
2679
|
+
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
2519
2680
|
const full = join7(dir, e.name);
|
|
2520
2681
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2521
2682
|
else if (e.isFile()) out.push(full);
|
|
@@ -2569,92 +2730,194 @@ function searchFilesTool(ctx) {
|
|
|
2569
2730
|
});
|
|
2570
2731
|
}
|
|
2571
2732
|
|
|
2572
|
-
// src/lib/tools/
|
|
2733
|
+
// src/lib/tools/runShell.ts
|
|
2573
2734
|
import { tool as tool8 } from "ai";
|
|
2574
2735
|
import z14 from "zod";
|
|
2736
|
+
import { relative as relative2 } from "node:path";
|
|
2575
2737
|
|
|
2576
|
-
// src/lib/tools/utils/
|
|
2738
|
+
// src/lib/tools/utils/runShell.ts
|
|
2577
2739
|
import { spawn as spawn2 } from "node:child_process";
|
|
2578
|
-
|
|
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();
|
|
2579
2791
|
return new Promise((resolve4) => {
|
|
2580
2792
|
let output = "";
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
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 }
|
|
2584
2800
|
});
|
|
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);
|
|
2585
2821
|
child.stdout?.on("data", (d) => output += d);
|
|
2586
2822
|
child.stderr?.on("data", (d) => output += d);
|
|
2587
|
-
child.on(
|
|
2588
|
-
|
|
2589
|
-
(
|
|
2590
|
-
);
|
|
2591
|
-
child.on("close", (code) =>
|
|
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));
|
|
2592
2828
|
});
|
|
2593
2829
|
}
|
|
2594
2830
|
|
|
2595
|
-
// src/lib/tools/
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
]
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2614
|
-
}
|
|
2615
|
-
async function detectPackageManager(cwd) {
|
|
2616
|
-
try {
|
|
2617
|
-
const pkg = await readPackageJson(cwd);
|
|
2618
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2619
|
-
} catch {
|
|
2620
|
-
}
|
|
2621
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
// src/lib/tools/repoVerification.ts
|
|
2625
|
-
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2626
|
-
async function runRepoVerificationCheck() {
|
|
2627
|
-
let pkg;
|
|
2628
|
-
try {
|
|
2629
|
-
pkg = await readPackageJson();
|
|
2630
|
-
} catch (err) {
|
|
2631
|
-
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2632
|
-
return { ok: false, checks: [], limitation };
|
|
2633
|
-
}
|
|
2634
|
-
const scripts = pkg.scripts ?? {};
|
|
2635
|
-
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2636
|
-
if (present.length === 0) {
|
|
2637
|
-
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2638
|
-
return { ok: false, checks: [], limitation };
|
|
2639
|
-
}
|
|
2640
|
-
const pm = await detectPackageManager(process.cwd());
|
|
2641
|
-
const checks = [];
|
|
2642
|
-
for (const script of present) {
|
|
2643
|
-
const command = `${pm} run ${script}`;
|
|
2644
|
-
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2645
|
-
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2646
|
-
}
|
|
2647
|
-
return { ok: checks.every((c) => c.ok), checks };
|
|
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
|
+
};
|
|
2648
2849
|
}
|
|
2649
|
-
|
|
2650
|
-
// src/lib/tools/verifyImplementation.ts
|
|
2651
|
-
function verifyImplementationTool() {
|
|
2850
|
+
function runShellTool(ctx) {
|
|
2652
2851
|
return tool8({
|
|
2653
|
-
description: "Run the
|
|
2654
|
-
inputSchema: z14.object(
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
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
|
+
};
|
|
2658
2921
|
}
|
|
2659
2922
|
});
|
|
2660
2923
|
}
|
|
@@ -2745,7 +3008,7 @@ function generateRecordTool(ctx) {
|
|
|
2745
3008
|
return {
|
|
2746
3009
|
filePath: relPath,
|
|
2747
3010
|
count: records.length,
|
|
2748
|
-
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime
|
|
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.`
|
|
2749
3012
|
};
|
|
2750
3013
|
} catch (err) {
|
|
2751
3014
|
return `Error generating records: ${err.message}`;
|
|
@@ -2773,22 +3036,6 @@ function notifyUserTool() {
|
|
|
2773
3036
|
});
|
|
2774
3037
|
}
|
|
2775
3038
|
|
|
2776
|
-
// src/lib/tools/context.ts
|
|
2777
|
-
var DEFAULT_TOOL_LIMITS = {
|
|
2778
|
-
list: 10,
|
|
2779
|
-
search: 10,
|
|
2780
|
-
read: 20,
|
|
2781
|
-
match: 100
|
|
2782
|
-
};
|
|
2783
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
2784
|
-
return {
|
|
2785
|
-
root: cwd,
|
|
2786
|
-
cwd,
|
|
2787
|
-
limits,
|
|
2788
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2789
|
-
};
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
3039
|
// src/lib/tools/index.ts
|
|
2793
3040
|
function withLogging(name, def) {
|
|
2794
3041
|
const execute = def.execute;
|
|
@@ -2820,10 +3067,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2820
3067
|
writeCredentialsTool(ctx)
|
|
2821
3068
|
),
|
|
2822
3069
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2823
|
-
|
|
2824
|
-
"verifyImplementation",
|
|
2825
|
-
verifyImplementationTool()
|
|
2826
|
-
),
|
|
3070
|
+
runShell: withLogging("runShell", runShellTool(ctx)),
|
|
2827
3071
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2828
3072
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
2829
3073
|
};
|
|
@@ -2858,7 +3102,7 @@ async function runAgent(req) {
|
|
|
2858
3102
|
baseURL: PROXY_BASE_URL,
|
|
2859
3103
|
fetch: proxyFetch
|
|
2860
3104
|
});
|
|
2861
|
-
const toolContext = createToolContext();
|
|
3105
|
+
const toolContext = req.toolContext ?? createToolContext();
|
|
2862
3106
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2863
3107
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2864
3108
|
const instructions = [
|
|
@@ -2923,7 +3167,11 @@ async function runAgent(req) {
|
|
|
2923
3167
|
"runAgent finished"
|
|
2924
3168
|
);
|
|
2925
3169
|
logger.info(
|
|
2926
|
-
{
|
|
3170
|
+
{
|
|
3171
|
+
counts: toolContext.counts,
|
|
3172
|
+
limits: toolContext.limits,
|
|
3173
|
+
commandsRun: toolContext.shell.executions.length
|
|
3174
|
+
},
|
|
2927
3175
|
"tool usage"
|
|
2928
3176
|
);
|
|
2929
3177
|
const toolResults = await stream.toolResults;
|
|
@@ -2949,8 +3197,8 @@ var detectLanguageSchema = z19.object({
|
|
|
2949
3197
|
var detectLanguage = () => runAgent({
|
|
2950
3198
|
instructions: [
|
|
2951
3199
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2952
|
-
"If a superset language is found, exclude the subset language.
|
|
2953
|
-
"If a meta-framework is used, exclude the framework. Next
|
|
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).",
|
|
2954
3202
|
"Return the exact version",
|
|
2955
3203
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2956
3204
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3048,7 +3296,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3048
3296
|
// package.json
|
|
3049
3297
|
var package_default = {
|
|
3050
3298
|
name: "@algolia/wizard",
|
|
3051
|
-
version: "0.9.0-rc.
|
|
3299
|
+
version: "0.9.0-rc.87.83",
|
|
3052
3300
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3053
3301
|
type: "module",
|
|
3054
3302
|
engines: {
|
|
@@ -3196,9 +3444,11 @@ var CURATED_FRAMEWORKS = [
|
|
|
3196
3444
|
"Next.js",
|
|
3197
3445
|
"React",
|
|
3198
3446
|
"Vue",
|
|
3199
|
-
"
|
|
3200
|
-
"
|
|
3201
|
-
"
|
|
3447
|
+
"Vanilla JS",
|
|
3448
|
+
"Django",
|
|
3449
|
+
"Laravel",
|
|
3450
|
+
"Rails",
|
|
3451
|
+
"Symfony"
|
|
3202
3452
|
];
|
|
3203
3453
|
var OTHER_OPTION = "Other";
|
|
3204
3454
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -3211,12 +3461,15 @@ var FRAMEWORK_ALIASES = {
|
|
|
3211
3461
|
vuejs: "vue",
|
|
3212
3462
|
angular: "angular",
|
|
3213
3463
|
angularjs: "angular",
|
|
3214
|
-
svelte: "svelte",
|
|
3215
|
-
sveltekit: "svelte",
|
|
3216
3464
|
vanillajs: "vanillajs",
|
|
3217
3465
|
vanilla: "vanillajs",
|
|
3218
3466
|
javascript: "vanillajs",
|
|
3219
|
-
js: "vanillajs"
|
|
3467
|
+
js: "vanillajs",
|
|
3468
|
+
django: "django",
|
|
3469
|
+
laravel: "laravel",
|
|
3470
|
+
rails: "rails",
|
|
3471
|
+
rubyonrails: "rails",
|
|
3472
|
+
symfony: "symfony"
|
|
3220
3473
|
};
|
|
3221
3474
|
var isSameFramework = (a, b) => {
|
|
3222
3475
|
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
@@ -3450,16 +3703,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3450
3703
|
import z26 from "zod";
|
|
3451
3704
|
|
|
3452
3705
|
// src/lib/worktree.ts
|
|
3453
|
-
import { execFile
|
|
3454
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3455
|
-
import {
|
|
3456
|
-
basename as basename2,
|
|
3457
|
-
dirname as dirname7,
|
|
3458
|
-
isAbsolute as isAbsolute2,
|
|
3459
|
-
join as join9,
|
|
3460
|
-
relative as relative2,
|
|
3461
|
-
resolve as resolve3
|
|
3462
|
-
} from "node:path";
|
|
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";
|
|
3463
3709
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3464
3710
|
var MAX_WIZARD_WORKTREES = 3;
|
|
3465
3711
|
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
@@ -3490,7 +3736,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3490
3736
|
return out.trim().length > 0;
|
|
3491
3737
|
}
|
|
3492
3738
|
async function pruneOldWorktrees(repoRoot) {
|
|
3493
|
-
const dir =
|
|
3739
|
+
const dir = join8(stateDir(repoRoot), "worktrees");
|
|
3494
3740
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3495
3741
|
for (const slug of stale) {
|
|
3496
3742
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3501,7 +3747,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3501
3747
|
"worktree",
|
|
3502
3748
|
"remove",
|
|
3503
3749
|
"--force",
|
|
3504
|
-
|
|
3750
|
+
join8(dir, slug)
|
|
3505
3751
|
]);
|
|
3506
3752
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3507
3753
|
} catch (err) {
|
|
@@ -3515,113 +3761,13 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3515
3761
|
async function createWorktree(repoRoot) {
|
|
3516
3762
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3517
3763
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3518
|
-
const path =
|
|
3764
|
+
const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3519
3765
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3520
3766
|
await pruneOldWorktrees(repoRoot);
|
|
3521
3767
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3522
3768
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3523
3769
|
return { path, branch };
|
|
3524
3770
|
}
|
|
3525
|
-
async function installWorktreeDeps(worktreePath) {
|
|
3526
|
-
try {
|
|
3527
|
-
await readPackageJson(worktreePath);
|
|
3528
|
-
} catch {
|
|
3529
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3530
|
-
}
|
|
3531
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3532
|
-
return new Promise((resolve4) => {
|
|
3533
|
-
let output = "";
|
|
3534
|
-
const child = spawn3(pm, ["install"], {
|
|
3535
|
-
cwd: worktreePath,
|
|
3536
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3537
|
-
});
|
|
3538
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3539
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3540
|
-
child.on(
|
|
3541
|
-
"error",
|
|
3542
|
-
(err) => resolve4({
|
|
3543
|
-
ok: false,
|
|
3544
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3545
|
-
})
|
|
3546
|
-
);
|
|
3547
|
-
child.on(
|
|
3548
|
-
"close",
|
|
3549
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3550
|
-
);
|
|
3551
|
-
});
|
|
3552
|
-
}
|
|
3553
|
-
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3554
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3555
|
-
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3556
|
-
return {
|
|
3557
|
-
ok: false,
|
|
3558
|
-
reason: `entrypoint "${entrypoint}" is not a plain file path`
|
|
3559
|
-
};
|
|
3560
|
-
}
|
|
3561
|
-
const target = resolve3(worktreePath, entrypoint);
|
|
3562
|
-
const rel = relative2(worktreePath, target);
|
|
3563
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
3564
|
-
return {
|
|
3565
|
-
ok: false,
|
|
3566
|
-
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3567
|
-
};
|
|
3568
|
-
}
|
|
3569
|
-
return { ok: true, target };
|
|
3570
|
-
}
|
|
3571
|
-
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3572
|
-
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
3573
|
-
return {
|
|
3574
|
-
ran: false,
|
|
3575
|
-
ok: false,
|
|
3576
|
-
output: "",
|
|
3577
|
-
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
3581
|
-
if (!validated.ok) {
|
|
3582
|
-
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3583
|
-
}
|
|
3584
|
-
try {
|
|
3585
|
-
if (!(await stat2(validated.target)).isFile()) {
|
|
3586
|
-
return {
|
|
3587
|
-
ran: false,
|
|
3588
|
-
ok: false,
|
|
3589
|
-
output: "",
|
|
3590
|
-
reason: `entrypoint "${entrypoint}" is not a file`
|
|
3591
|
-
};
|
|
3592
|
-
}
|
|
3593
|
-
} catch {
|
|
3594
|
-
return {
|
|
3595
|
-
ran: false,
|
|
3596
|
-
ok: false,
|
|
3597
|
-
output: "",
|
|
3598
|
-
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3599
|
-
};
|
|
3600
|
-
}
|
|
3601
|
-
return new Promise((resolveRun) => {
|
|
3602
|
-
let output = "";
|
|
3603
|
-
const child = spawn3(runtime, [entrypoint], {
|
|
3604
|
-
cwd: worktreePath,
|
|
3605
|
-
shell: false,
|
|
3606
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3607
|
-
env: { ...process.env, ...env }
|
|
3608
|
-
});
|
|
3609
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3610
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3611
|
-
child.on(
|
|
3612
|
-
"error",
|
|
3613
|
-
(err) => resolveRun({
|
|
3614
|
-
ran: true,
|
|
3615
|
-
ok: false,
|
|
3616
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3617
|
-
})
|
|
3618
|
-
);
|
|
3619
|
-
child.on(
|
|
3620
|
-
"close",
|
|
3621
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3622
|
-
);
|
|
3623
|
-
});
|
|
3624
|
-
}
|
|
3625
3771
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3626
3772
|
const trimmed = sourcePath.trim();
|
|
3627
3773
|
if (!trimmed) {
|
|
@@ -3635,8 +3781,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3635
3781
|
} catch {
|
|
3636
3782
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3637
3783
|
}
|
|
3638
|
-
const relPath =
|
|
3639
|
-
const dest =
|
|
3784
|
+
const relPath = join8(ingestDir, basename2(source));
|
|
3785
|
+
const dest = join8(worktreePath, relPath);
|
|
3640
3786
|
try {
|
|
3641
3787
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3642
3788
|
await copyFile(source, dest);
|
|
@@ -3651,11 +3797,28 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3651
3797
|
function hasEnvVar(content, name) {
|
|
3652
3798
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3653
3799
|
}
|
|
3800
|
+
async function readEnvVar(worktreePath, name) {
|
|
3801
|
+
let content;
|
|
3802
|
+
try {
|
|
3803
|
+
content = await readFile6(join8(worktreePath, ".env"), "utf8");
|
|
3804
|
+
} catch (err) {
|
|
3805
|
+
if (err.code !== "ENOENT") throw err;
|
|
3806
|
+
return void 0;
|
|
3807
|
+
}
|
|
3808
|
+
const match = new RegExp(
|
|
3809
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3810
|
+
"m"
|
|
3811
|
+
).exec(content);
|
|
3812
|
+
if (!match) return void 0;
|
|
3813
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3814
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3815
|
+
return value;
|
|
3816
|
+
}
|
|
3654
3817
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3655
|
-
const target =
|
|
3818
|
+
const target = join8(worktreePath, ".env");
|
|
3656
3819
|
let existing = "";
|
|
3657
3820
|
try {
|
|
3658
|
-
existing = await
|
|
3821
|
+
existing = await readFile6(target, "utf8");
|
|
3659
3822
|
} catch (err) {
|
|
3660
3823
|
if (err.code !== "ENOENT") throw err;
|
|
3661
3824
|
}
|
|
@@ -3724,15 +3887,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3724
3887
|
}
|
|
3725
3888
|
|
|
3726
3889
|
// src/lib/algoliaDocs.ts
|
|
3727
|
-
import { readFileSync, readdirSync, existsSync
|
|
3728
|
-
import { dirname as dirname8, join as
|
|
3890
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
3891
|
+
import { dirname as dirname8, join as join9 } from "node:path";
|
|
3729
3892
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3730
|
-
var DOCS_SUBPATH =
|
|
3893
|
+
var DOCS_SUBPATH = join9("docs", "algolia-sdk");
|
|
3731
3894
|
function findDocsDir() {
|
|
3732
3895
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3733
3896
|
for (; ; ) {
|
|
3734
|
-
const candidate =
|
|
3735
|
-
if (
|
|
3897
|
+
const candidate = join9(dir, DOCS_SUBPATH);
|
|
3898
|
+
if (existsSync(candidate)) return candidate;
|
|
3736
3899
|
const parent = dirname8(dir);
|
|
3737
3900
|
if (parent === dir) return void 0;
|
|
3738
3901
|
dir = parent;
|
|
@@ -3754,7 +3917,7 @@ function loadAlgoliaDoc(language) {
|
|
|
3754
3917
|
);
|
|
3755
3918
|
return "";
|
|
3756
3919
|
}
|
|
3757
|
-
return readFileSync(
|
|
3920
|
+
return readFileSync(join9(docsDir, files[0]), "utf8").trim();
|
|
3758
3921
|
}
|
|
3759
3922
|
function getNamedDoc(name, language) {
|
|
3760
3923
|
const docsDir = findDocsDir();
|
|
@@ -3762,14 +3925,15 @@ function getNamedDoc(name, language) {
|
|
|
3762
3925
|
logger.warn("docs/algolia-sdk not found");
|
|
3763
3926
|
return "";
|
|
3764
3927
|
}
|
|
3765
|
-
const file =
|
|
3766
|
-
if (!
|
|
3928
|
+
const file = join9(docsDir, `${name}-${language}.md`);
|
|
3929
|
+
if (!existsSync(file)) {
|
|
3767
3930
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
3768
3931
|
return "";
|
|
3769
3932
|
}
|
|
3770
3933
|
return readFileSync(file, "utf8").trim();
|
|
3771
3934
|
}
|
|
3772
3935
|
function getFrameworkSpecificDoc(frameworks) {
|
|
3936
|
+
if (frameworks.length === 0) return "";
|
|
3773
3937
|
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3774
3938
|
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3775
3939
|
return loadAlgoliaDoc("vue");
|
|
@@ -3777,9 +3941,6 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
3777
3941
|
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3778
3942
|
return loadAlgoliaDoc("react");
|
|
3779
3943
|
}
|
|
3780
|
-
if (fw.includes("angular")) {
|
|
3781
|
-
return loadAlgoliaDoc("angular");
|
|
3782
|
-
}
|
|
3783
3944
|
return loadAlgoliaDoc("js");
|
|
3784
3945
|
}
|
|
3785
3946
|
|
|
@@ -3807,11 +3968,7 @@ var implementSchema = z26.object({
|
|
|
3807
3968
|
});
|
|
3808
3969
|
var implementationOutputSchema = z26.object({
|
|
3809
3970
|
summary: z26.string(),
|
|
3810
|
-
|
|
3811
|
-
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
3812
|
-
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
3813
|
-
runtime: z26.enum(INGEST_RUNTIMES).optional(),
|
|
3814
|
-
entrypoint: z26.string().optional()
|
|
3971
|
+
ingestCommand: z26.string().optional()
|
|
3815
3972
|
});
|
|
3816
3973
|
var verificationOutputSchema = z26.object({
|
|
3817
3974
|
summary: z26.string(),
|
|
@@ -3821,30 +3978,35 @@ var verificationOutputSchema = z26.object({
|
|
|
3821
3978
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3822
3979
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3823
3980
|
var INGEST_DIR = ".algolia-wizard";
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3828
|
-
return "React";
|
|
3829
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3830
|
-
return "JavaScript";
|
|
3831
|
-
}
|
|
3832
|
-
function frameworksForDoc(framework) {
|
|
3833
|
-
switch (framework) {
|
|
3834
|
-
case "React":
|
|
3835
|
-
return ["react"];
|
|
3836
|
-
case "Vue":
|
|
3837
|
-
return ["vue"];
|
|
3838
|
-
case "Angular":
|
|
3839
|
-
return ["angular"];
|
|
3840
|
-
case "JavaScript":
|
|
3841
|
-
return [];
|
|
3842
|
-
}
|
|
3981
|
+
var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
|
|
3982
|
+
function lower(entries) {
|
|
3983
|
+
return entries.map((entry) => entry.name.toLowerCase());
|
|
3843
3984
|
}
|
|
3844
|
-
function
|
|
3845
|
-
|
|
3846
|
-
(
|
|
3985
|
+
function isJsProject(language) {
|
|
3986
|
+
return lower(language.languages).some(
|
|
3987
|
+
(name) => JS_LANGUAGES.some((js) => name.includes(js))
|
|
3847
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"];
|
|
4007
|
+
}
|
|
4008
|
+
function publicEnvPrefix(language) {
|
|
4009
|
+
const frameworkNames = lower(language.frameworks);
|
|
3848
4010
|
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3849
4011
|
return "NEXT_PUBLIC_";
|
|
3850
4012
|
}
|
|
@@ -3857,17 +4019,24 @@ function publicEnvPrefix(language) {
|
|
|
3857
4019
|
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3858
4020
|
return "VITE_";
|
|
3859
4021
|
}
|
|
3860
|
-
return "PUBLIC_";
|
|
4022
|
+
return isJsProject(language) ? "PUBLIC_" : "";
|
|
4023
|
+
}
|
|
4024
|
+
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4025
|
+
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4026
|
+
function appIdVar(language) {
|
|
4027
|
+
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4028
|
+
}
|
|
4029
|
+
function searchKeyVar(language) {
|
|
4030
|
+
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3861
4031
|
}
|
|
3862
4032
|
function searchEnvVars(language, appId, searchKey) {
|
|
3863
|
-
const prefix = publicEnvPrefix(language);
|
|
3864
4033
|
return [
|
|
3865
4034
|
{
|
|
3866
|
-
name:
|
|
4035
|
+
name: appIdVar(language),
|
|
3867
4036
|
value: appId ?? "<your-algolia-app-id>"
|
|
3868
4037
|
},
|
|
3869
4038
|
{
|
|
3870
|
-
name:
|
|
4039
|
+
name: searchKeyVar(language),
|
|
3871
4040
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3872
4041
|
}
|
|
3873
4042
|
];
|
|
@@ -3876,7 +4045,10 @@ function baseInstructions(input) {
|
|
|
3876
4045
|
return [
|
|
3877
4046
|
`Target Algolia index: ${input.targetIndex}`,
|
|
3878
4047
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
3879
|
-
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
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."
|
|
3880
4052
|
];
|
|
3881
4053
|
}
|
|
3882
4054
|
function sourceSpecificInstructions(input) {
|
|
@@ -3896,43 +4068,60 @@ function sourceSpecificInstructions(input) {
|
|
|
3896
4068
|
generated: [
|
|
3897
4069
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3898
4070
|
"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.",
|
|
3899
|
-
"In the script, read and parse each returned file path at runtime
|
|
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.",
|
|
3900
4072
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3901
4073
|
]
|
|
3902
4074
|
};
|
|
3903
4075
|
return byLine[input.ingestionSource];
|
|
3904
4076
|
}
|
|
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
|
+
}
|
|
3905
4086
|
function ingestionInstructions(input) {
|
|
3906
4087
|
return [
|
|
3907
4088
|
...input.confirmed && input.confirmed.length ? [
|
|
3908
4089
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3909
4090
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3910
4091
|
`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.`,
|
|
3911
|
-
"
|
|
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.",
|
|
3912
4093
|
"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.",
|
|
3913
|
-
|
|
3914
|
-
'
|
|
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.',
|
|
3915
4097
|
"The summary should be extremely concise.",
|
|
3916
|
-
`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.`,
|
|
3917
4098
|
...sourceSpecificInstructions(input)
|
|
3918
4099
|
] : []
|
|
3919
4100
|
];
|
|
3920
4101
|
}
|
|
3921
4102
|
function searchInstructions(input) {
|
|
3922
|
-
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.
|
|
4103
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
|
|
3923
4104
|
return [
|
|
3924
4105
|
"Implement an in-app Algolia search experience.",
|
|
3925
|
-
`Build the search UI for ${input.
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
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.",
|
|
4115
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4116
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4117
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4118
|
+
// would be reused as if it were real.
|
|
4119
|
+
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
3931
4120
|
// Not the agent's to rename: the wizard writes these exact names into
|
|
3932
4121
|
// ".env" right after this step, so a renamed prefix would leave the code
|
|
3933
4122
|
// reading a var the wizard never wrote.
|
|
3934
|
-
`Use exactly these
|
|
3935
|
-
|
|
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.",
|
|
3936
4125
|
"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."
|
|
3937
4126
|
];
|
|
3938
4127
|
}
|
|
@@ -3940,11 +4129,12 @@ function verificationInstructions(input) {
|
|
|
3940
4129
|
return [
|
|
3941
4130
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3942
4131
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3943
|
-
"
|
|
3944
|
-
"
|
|
3945
|
-
"
|
|
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.",
|
|
3946
4136
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3947
|
-
`Do not modify "${input.ingestDir}/" unless
|
|
4137
|
+
`Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
|
|
3948
4138
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3949
4139
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3950
4140
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3965,14 +4155,15 @@ var IMPLEMENT_CONFIG = {
|
|
|
3965
4155
|
}
|
|
3966
4156
|
};
|
|
3967
4157
|
var useCaseToolMap = {
|
|
3968
|
-
ingestion: [
|
|
3969
|
-
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
3970
|
-
verification: [
|
|
4158
|
+
ingestion: [
|
|
3971
4159
|
...FS_READ_TOOLS,
|
|
3972
4160
|
"writeFile",
|
|
3973
|
-
"
|
|
4161
|
+
"writeCredentials",
|
|
4162
|
+
"runShell",
|
|
3974
4163
|
"notifyUser"
|
|
3975
|
-
]
|
|
4164
|
+
],
|
|
4165
|
+
search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
|
|
4166
|
+
verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
|
|
3976
4167
|
};
|
|
3977
4168
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
3978
4169
|
const tools = useCaseToolMap[useCase];
|
|
@@ -3995,15 +4186,31 @@ function formatSummary(useCase, summary) {
|
|
|
3995
4186
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3996
4187
|
return `${label}: ${summary}`;
|
|
3997
4188
|
}
|
|
3998
|
-
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3999
|
-
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4000
|
-
}
|
|
4001
4189
|
function parseIngestRecordCount(output) {
|
|
4002
4190
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
4003
4191
|
if (!match) return void 0;
|
|
4004
4192
|
const count = Number(match[1]);
|
|
4005
4193
|
return Number.isFinite(count) ? count : void 0;
|
|
4006
4194
|
}
|
|
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
|
+
}
|
|
4007
4214
|
function verificationRetryInstructions(verification) {
|
|
4008
4215
|
return [
|
|
4009
4216
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
@@ -4082,17 +4289,12 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4082
4289
|
const confirmed2 = normalized.confirmedEntities;
|
|
4083
4290
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4084
4291
|
let appId;
|
|
4085
|
-
let
|
|
4292
|
+
let ingestAppId;
|
|
4086
4293
|
if (useCases.includes("search")) {
|
|
4087
4294
|
appId = (await requireApplication()).id;
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
logger.warn(
|
|
4092
|
-
{ err: err.message },
|
|
4093
|
-
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
4094
|
-
);
|
|
4095
|
-
}
|
|
4295
|
+
}
|
|
4296
|
+
if (useCases.includes("ingestion")) {
|
|
4297
|
+
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4096
4298
|
}
|
|
4097
4299
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
4098
4300
|
try {
|
|
@@ -4124,51 +4326,58 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4124
4326
|
targetIndex,
|
|
4125
4327
|
language,
|
|
4126
4328
|
appId,
|
|
4127
|
-
|
|
4128
|
-
|
|
4329
|
+
// Names only: the search-only key is provisioned after verification, so
|
|
4330
|
+
// every value here is still a placeholder when the agent reads them.
|
|
4331
|
+
searchEnvVars: searchEnvVars(language, appId),
|
|
4129
4332
|
ingestDir: INGEST_DIR,
|
|
4130
4333
|
ingestionSource,
|
|
4131
4334
|
uploadFilePath,
|
|
4132
|
-
|
|
4335
|
+
searchUiTarget: searchUiTarget(language)
|
|
4133
4336
|
};
|
|
4134
4337
|
const summaries = [];
|
|
4135
4338
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4339
|
+
let envSearchKey;
|
|
4340
|
+
let envAppIdMismatch = false;
|
|
4341
|
+
if (useCases.includes("search") && appId) {
|
|
4342
|
+
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4343
|
+
if (envAppId === appId) {
|
|
4344
|
+
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4345
|
+
} else if (envAppId) {
|
|
4346
|
+
envAppIdMismatch = true;
|
|
4347
|
+
summaries.push(
|
|
4348
|
+
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
4349
|
+
);
|
|
4350
|
+
logger.warn(
|
|
4351
|
+
{ envAppId, appId },
|
|
4352
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
4136
4357
|
let agentRuns = 0;
|
|
4137
|
-
let
|
|
4138
|
-
let ingestEntrypoint;
|
|
4358
|
+
let ingestCommand;
|
|
4139
4359
|
let ingestScriptRan = false;
|
|
4140
4360
|
let ingestRecordCount;
|
|
4141
4361
|
let ingestDurationMs;
|
|
4142
|
-
let installFailed = false;
|
|
4143
4362
|
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);
|
|
4144
4368
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4145
4369
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4146
4370
|
agentRuns += 1;
|
|
4147
|
-
|
|
4371
|
+
return runAgent({
|
|
4148
4372
|
instructions: buildAgentInstructions(
|
|
4149
4373
|
currentUseCase,
|
|
4150
4374
|
input,
|
|
4151
4375
|
extraInstructions
|
|
4152
4376
|
),
|
|
4153
4377
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4154
|
-
outputSchema: implementationOutputSchema
|
|
4378
|
+
outputSchema: implementationOutputSchema,
|
|
4379
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4155
4380
|
});
|
|
4156
|
-
ctx.notify({
|
|
4157
|
-
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4158
|
-
});
|
|
4159
|
-
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4160
|
-
useCase: currentUseCase
|
|
4161
|
-
});
|
|
4162
|
-
const install = await installWorktreeDeps(worktree);
|
|
4163
|
-
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4164
|
-
if (!install.ok) {
|
|
4165
|
-
installFailed = true;
|
|
4166
|
-
logger.warn(
|
|
4167
|
-
{ useCase: currentUseCase, output: install.output },
|
|
4168
|
-
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
4169
|
-
);
|
|
4170
|
-
}
|
|
4171
|
-
return result;
|
|
4172
4381
|
}
|
|
4173
4382
|
async function runVerificationUseCase() {
|
|
4174
4383
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4176,110 +4385,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4176
4385
|
return runAgent({
|
|
4177
4386
|
instructions: buildAgentInstructions("verification", input),
|
|
4178
4387
|
tools: toolsForUseCase("verification"),
|
|
4179
|
-
outputSchema: verificationOutputSchema
|
|
4388
|
+
outputSchema: verificationOutputSchema,
|
|
4389
|
+
toolContext: searchTools
|
|
4180
4390
|
});
|
|
4181
4391
|
}
|
|
4182
4392
|
if (useCases.includes("ingestion")) {
|
|
4183
|
-
const
|
|
4184
|
-
summaries.push(formatSummary("ingestion", summary));
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
entrypoint: ingestEntrypoint
|
|
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
|
|
4202
4411
|
});
|
|
4203
|
-
const startedAt = Date.now();
|
|
4204
|
-
const run2 = await runIngestScript(
|
|
4205
|
-
worktree,
|
|
4206
|
-
ingestRuntime,
|
|
4207
|
-
ingestEntrypoint,
|
|
4208
|
-
{
|
|
4209
|
-
[APP_ID_VAR]: ingestApp.id,
|
|
4210
|
-
[API_KEY_VAR]: writeKey
|
|
4211
|
-
}
|
|
4212
|
-
);
|
|
4213
|
-
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
4214
|
-
ingestScriptRan = run2.ran && run2.ok;
|
|
4215
|
-
if (ingestScriptRan) {
|
|
4216
|
-
ingestDurationMs = Date.now() - startedAt;
|
|
4217
|
-
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4218
|
-
if (ingestRecordCount != null) {
|
|
4219
|
-
track("AI Wizard Ingest Successful", {
|
|
4220
|
-
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4221
|
-
record_count: ingestRecordCount,
|
|
4222
|
-
duration_ms: ingestDurationMs
|
|
4223
|
-
});
|
|
4224
|
-
}
|
|
4225
|
-
}
|
|
4226
|
-
let summaryLine;
|
|
4227
|
-
let outcomeMessage;
|
|
4228
|
-
if (!run2.ran) {
|
|
4229
|
-
summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
|
|
4230
|
-
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4231
|
-
logger.warn(
|
|
4232
|
-
{
|
|
4233
|
-
runtime: ingestRuntime,
|
|
4234
|
-
entrypoint: ingestEntrypoint,
|
|
4235
|
-
reason: run2.reason
|
|
4236
|
-
},
|
|
4237
|
-
"implement: refused to auto-run ingestion script"
|
|
4238
|
-
);
|
|
4239
|
-
track("Error", {
|
|
4240
|
-
step: "Push Data",
|
|
4241
|
-
error: `ingestion script skipped: ${run2.reason}`,
|
|
4242
|
-
product_area: "AI Wizard"
|
|
4243
|
-
});
|
|
4244
|
-
} else if (run2.ok) {
|
|
4245
|
-
const status = "Ingestion run: succeeded.";
|
|
4246
|
-
summaryLine = run2.output ? `${status}
|
|
4247
|
-
${run2.output}` : status;
|
|
4248
|
-
outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4249
|
-
} else {
|
|
4250
|
-
const status = "\u26A0\uFE0F Ingestion run failed:";
|
|
4251
|
-
summaryLine = run2.output ? `${status}
|
|
4252
|
-
${run2.output}` : status;
|
|
4253
|
-
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4254
|
-
logger.warn(
|
|
4255
|
-
{
|
|
4256
|
-
runtime: ingestRuntime,
|
|
4257
|
-
entrypoint: ingestEntrypoint,
|
|
4258
|
-
output: run2.output
|
|
4259
|
-
},
|
|
4260
|
-
"implement: ingestion script run failed"
|
|
4261
|
-
);
|
|
4262
|
-
track("Error", {
|
|
4263
|
-
step: "Push Data",
|
|
4264
|
-
error: run2.output || "ingestion script exited non-zero",
|
|
4265
|
-
product_area: "AI Wizard"
|
|
4266
|
-
});
|
|
4267
|
-
}
|
|
4268
|
-
summaries.push(summaryLine);
|
|
4269
|
-
ingestOutcomeMessage = outcomeMessage;
|
|
4270
4412
|
}
|
|
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
|
+
});
|
|
4271
4427
|
}
|
|
4272
4428
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4273
|
-
if (
|
|
4274
|
-
commandMessages.push(
|
|
4275
|
-
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4276
|
-
);
|
|
4429
|
+
if (ingestCommand) {
|
|
4430
|
+
commandMessages.push(`Ingestion command: ${ingestCommand}`);
|
|
4277
4431
|
}
|
|
4278
4432
|
await ctx.requestUserInput({
|
|
4279
4433
|
prompt: "",
|
|
4280
4434
|
promptType: "enterToContinue",
|
|
4281
4435
|
options: [],
|
|
4282
|
-
messages:
|
|
4436
|
+
messages: [ingestOutcomeMessage, ...commandMessages]
|
|
4283
4437
|
});
|
|
4284
4438
|
}
|
|
4285
4439
|
if (useCases.includes("search")) {
|
|
@@ -4324,7 +4478,29 @@ ${run2.output}` : status;
|
|
|
4324
4478
|
}
|
|
4325
4479
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4326
4480
|
}
|
|
4327
|
-
|
|
4481
|
+
let searchKey;
|
|
4482
|
+
let searchKeyError;
|
|
4483
|
+
if (appId) {
|
|
4484
|
+
try {
|
|
4485
|
+
const resolved = await resolveSearchOnlyKey(
|
|
4486
|
+
targetIndex,
|
|
4487
|
+
appId,
|
|
4488
|
+
envSearchKey
|
|
4489
|
+
);
|
|
4490
|
+
searchKey = resolved.key;
|
|
4491
|
+
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}.`
|
|
4493
|
+
);
|
|
4494
|
+
} catch (err) {
|
|
4495
|
+
searchKeyError = err.message;
|
|
4496
|
+
logger.warn(
|
|
4497
|
+
{ err: searchKeyError },
|
|
4498
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4499
|
+
);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
|
|
4503
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4328
4504
|
(v) => !v.value.startsWith("<")
|
|
4329
4505
|
);
|
|
4330
4506
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4335,13 +4511,29 @@ ${run2.output}` : status;
|
|
|
4335
4511
|
if (written.length > 0) {
|
|
4336
4512
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4337
4513
|
}
|
|
4514
|
+
const stale = [];
|
|
4515
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4516
|
+
if (written.includes(v.name)) continue;
|
|
4517
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4518
|
+
if (current && current !== v.value) stale.push(v);
|
|
4519
|
+
}
|
|
4520
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4521
|
+
summaries.push(
|
|
4522
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4523
|
+
);
|
|
4524
|
+
logger.warn(
|
|
4525
|
+
{ vars: stale.map((v) => v.name) },
|
|
4526
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4527
|
+
);
|
|
4528
|
+
}
|
|
4338
4529
|
}
|
|
4339
|
-
const unresolvedSearchEnvVars =
|
|
4530
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4340
4531
|
(v) => v.value.startsWith("<")
|
|
4341
4532
|
);
|
|
4342
4533
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4343
4534
|
summaries.push(
|
|
4344
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4535
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
|
|
4536
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4345
4537
|
);
|
|
4346
4538
|
}
|
|
4347
4539
|
} else {
|
|
@@ -4353,27 +4545,18 @@ ${run2.output}` : status;
|
|
|
4353
4545
|
"implement: agent reported success but no files changed in the worktree"
|
|
4354
4546
|
);
|
|
4355
4547
|
}
|
|
4356
|
-
if (installFailed) {
|
|
4357
|
-
summaries.push(
|
|
4358
|
-
'\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".'
|
|
4359
|
-
);
|
|
4360
|
-
}
|
|
4361
4548
|
return {
|
|
4362
4549
|
ingestionSource,
|
|
4363
4550
|
filesChanged,
|
|
4364
4551
|
summary: summaries.join("\n\n"),
|
|
4365
4552
|
worktreePath: worktree,
|
|
4366
|
-
...useCases.includes("ingestion") &&
|
|
4367
|
-
ingestCommand
|
|
4368
|
-
worktree,
|
|
4369
|
-
ingestRuntime,
|
|
4370
|
-
ingestEntrypoint
|
|
4371
|
-
),
|
|
4553
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4554
|
+
ingestCommand,
|
|
4372
4555
|
ingestScriptRan,
|
|
4373
4556
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4374
4557
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4375
4558
|
} : {},
|
|
4376
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4559
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4377
4560
|
};
|
|
4378
4561
|
} finally {
|
|
4379
4562
|
process.chdir(repoRoot);
|
|
@@ -4696,7 +4879,7 @@ function parseCliArgs(argv) {
|
|
|
4696
4879
|
|
|
4697
4880
|
// src/lib/resetState.ts
|
|
4698
4881
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4699
|
-
import { join as
|
|
4882
|
+
import { join as join10 } from "node:path";
|
|
4700
4883
|
var KEEP = ["wizard.log"];
|
|
4701
4884
|
async function resetProjectState() {
|
|
4702
4885
|
const dir = stateDir();
|
|
@@ -4708,13 +4891,13 @@ async function resetProjectState() {
|
|
|
4708
4891
|
}
|
|
4709
4892
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4710
4893
|
await Promise.all(
|
|
4711
|
-
targets.map((name) => rm2(
|
|
4894
|
+
targets.map((name) => rm2(join10(dir, name), { recursive: true, force: true }))
|
|
4712
4895
|
);
|
|
4713
4896
|
return { dir, removed: targets };
|
|
4714
4897
|
}
|
|
4715
4898
|
|
|
4716
4899
|
// src/main.tsx
|
|
4717
|
-
import { jsx as
|
|
4900
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
4718
4901
|
async function startup() {
|
|
4719
4902
|
let args;
|
|
4720
4903
|
try {
|
|
@@ -4763,7 +4946,7 @@ ${formatStepList(workflow)}`);
|
|
|
4763
4946
|
}
|
|
4764
4947
|
async function run(workflow) {
|
|
4765
4948
|
const store = useWizard.getState();
|
|
4766
|
-
const instance = render(/* @__PURE__ */
|
|
4949
|
+
const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
|
|
4767
4950
|
await store.waitForStart();
|
|
4768
4951
|
let user = await getUser();
|
|
4769
4952
|
if (!user) {
|