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