@algolia/wizard 0.9.0-rc.96.109 → 0.10.0
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";
|
|
@@ -17,9 +17,6 @@ function npxArgs(args) {
|
|
|
17
17
|
return ["--yes", "@algolia/cli@latest", ...args];
|
|
18
18
|
}
|
|
19
19
|
var shell = process.platform === "win32";
|
|
20
|
-
function mask(text, secret) {
|
|
21
|
-
return secret ? text.replaceAll(secret, "***") : text;
|
|
22
|
-
}
|
|
23
20
|
function lineSplitter(emit) {
|
|
24
21
|
let buffer = "";
|
|
25
22
|
return {
|
|
@@ -43,10 +40,9 @@ var stderrSink = (stream, line) => {
|
|
|
43
40
|
if (stream === "stdout") return;
|
|
44
41
|
wizardSink(stream, line);
|
|
45
42
|
};
|
|
46
|
-
function runAlgoliaCli(args, { onOutput
|
|
43
|
+
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
47
44
|
const store = useWizard.getState();
|
|
48
|
-
const
|
|
49
|
-
const logId = store.logStart("tool", `algolia ${command}`);
|
|
45
|
+
const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
|
|
50
46
|
return new Promise((resolve4, reject) => {
|
|
51
47
|
const child = spawn("npx", npxArgs(args), { shell });
|
|
52
48
|
let stdout = "";
|
|
@@ -75,13 +71,13 @@ function runAlgoliaCli(args, { onOutput, redact } = {}) {
|
|
|
75
71
|
const failed = stderr.trim();
|
|
76
72
|
let detail = "";
|
|
77
73
|
if (failed) {
|
|
78
|
-
detail = `: ${
|
|
74
|
+
detail = `: ${failed}`;
|
|
79
75
|
} else if (stdout.trim()) {
|
|
80
76
|
detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
|
|
81
77
|
}
|
|
82
78
|
reject(
|
|
83
79
|
new Error(
|
|
84
|
-
`Algolia CLI \`${
|
|
80
|
+
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
|
|
85
81
|
)
|
|
86
82
|
);
|
|
87
83
|
}
|
|
@@ -162,14 +158,10 @@ import { join, resolve } from "node:path";
|
|
|
162
158
|
function rootDir() {
|
|
163
159
|
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
164
160
|
}
|
|
165
|
-
|
|
166
|
-
function setProjectRoot(cwd) {
|
|
167
|
-
pinnedRoot = resolve(cwd);
|
|
168
|
-
}
|
|
169
|
-
function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
|
|
161
|
+
function projectSlug(cwd = process.cwd()) {
|
|
170
162
|
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
171
163
|
}
|
|
172
|
-
function stateDir(cwd =
|
|
164
|
+
function stateDir(cwd = process.cwd()) {
|
|
173
165
|
return join(rootDir(), projectSlug(cwd));
|
|
174
166
|
}
|
|
175
167
|
|
|
@@ -622,13 +614,10 @@ function Notices() {
|
|
|
622
614
|
}
|
|
623
615
|
|
|
624
616
|
// src/ui/PromptInput.tsx
|
|
625
|
-
import { Box as
|
|
617
|
+
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
626
618
|
import TextInput from "ink-text-input";
|
|
627
619
|
import { useState as useState5 } from "react";
|
|
628
620
|
|
|
629
|
-
// src/ui/CommandApproval.tsx
|
|
630
|
-
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
631
|
-
|
|
632
621
|
// src/ui/NextAction.tsx
|
|
633
622
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
634
623
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -652,60 +641,14 @@ function NextAction({
|
|
|
652
641
|
] });
|
|
653
642
|
}
|
|
654
643
|
|
|
655
|
-
// src/ui/CommandApproval.tsx
|
|
656
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
657
|
-
function CommandApproval({
|
|
658
|
-
command,
|
|
659
|
-
onDecide
|
|
660
|
-
}) {
|
|
661
|
-
useInput((_input, key) => {
|
|
662
|
-
if (key.return) onDecide("approve");
|
|
663
|
-
else if (key.escape) onDecide("reject");
|
|
664
|
-
});
|
|
665
|
-
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
|
|
666
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
|
|
667
|
-
/* @__PURE__ */ jsxs4(
|
|
668
|
-
Box5,
|
|
669
|
-
{
|
|
670
|
-
flexDirection: "column",
|
|
671
|
-
paddingLeft: 2,
|
|
672
|
-
borderStyle: "single",
|
|
673
|
-
borderColor: COLORS.success,
|
|
674
|
-
borderTop: false,
|
|
675
|
-
borderBottom: false,
|
|
676
|
-
borderRight: false,
|
|
677
|
-
gap: 1,
|
|
678
|
-
children: [
|
|
679
|
-
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
680
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
|
|
681
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
|
|
682
|
-
] }),
|
|
683
|
-
/* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
684
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
|
|
685
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
|
|
686
|
-
] }),
|
|
687
|
-
command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
688
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
|
|
689
|
-
/* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
|
|
690
|
-
] })
|
|
691
|
-
]
|
|
692
|
-
}
|
|
693
|
-
),
|
|
694
|
-
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
695
|
-
/* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
|
|
696
|
-
/* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" })
|
|
697
|
-
] })
|
|
698
|
-
] });
|
|
699
|
-
}
|
|
700
|
-
|
|
701
644
|
// src/ui/SelectPrompt.tsx
|
|
702
|
-
import { Box as
|
|
645
|
+
import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
|
|
703
646
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
704
647
|
|
|
705
648
|
// src/ui/ScrollView.tsx
|
|
706
|
-
import { Box as
|
|
649
|
+
import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
707
650
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
708
|
-
import { jsxs as
|
|
651
|
+
import { jsxs as jsxs4 } from "react/jsx-runtime";
|
|
709
652
|
var INDICATOR_ROWS = 2;
|
|
710
653
|
function fittedWidth(node, columns) {
|
|
711
654
|
let left = 0;
|
|
@@ -775,14 +718,14 @@ function useScrollWindow({
|
|
|
775
718
|
};
|
|
776
719
|
}
|
|
777
720
|
function ScrollView({ scroll, children }) {
|
|
778
|
-
return /* @__PURE__ */
|
|
779
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
721
|
+
return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
722
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
780
723
|
"\u2191 ",
|
|
781
724
|
scroll.hiddenAbove,
|
|
782
725
|
" more"
|
|
783
726
|
] }),
|
|
784
727
|
children,
|
|
785
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
728
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
786
729
|
"\u2193 ",
|
|
787
730
|
scroll.hiddenBelow,
|
|
788
731
|
" more"
|
|
@@ -791,7 +734,7 @@ function ScrollView({ scroll, children }) {
|
|
|
791
734
|
}
|
|
792
735
|
|
|
793
736
|
// src/ui/SelectPrompt.tsx
|
|
794
|
-
import { jsx as
|
|
737
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
795
738
|
var CANCEL = "cancel";
|
|
796
739
|
var ARROW_WIDTH = 4;
|
|
797
740
|
var COLUMN_GAP = 2;
|
|
@@ -853,7 +796,7 @@ function SelectPrompt({
|
|
|
853
796
|
revealIndex(index);
|
|
854
797
|
}, [index, revealIndex]);
|
|
855
798
|
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
856
|
-
|
|
799
|
+
useInput((input, key) => {
|
|
857
800
|
if (rows.length === 0) return;
|
|
858
801
|
if (key.upArrow || input === "k") {
|
|
859
802
|
setIndex((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -876,56 +819,56 @@ function SelectPrompt({
|
|
|
876
819
|
}
|
|
877
820
|
}
|
|
878
821
|
});
|
|
879
|
-
return /* @__PURE__ */
|
|
880
|
-
/* @__PURE__ */
|
|
881
|
-
error && /* @__PURE__ */
|
|
882
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
883
|
-
table && /* @__PURE__ */
|
|
884
|
-
/* @__PURE__ */
|
|
885
|
-
question && /* @__PURE__ */
|
|
886
|
-
helpText && /* @__PURE__ */
|
|
822
|
+
return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
|
|
823
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
824
|
+
error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
|
|
825
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
826
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
827
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
828
|
+
question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
|
|
829
|
+
helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
|
|
887
830
|
] })
|
|
888
831
|
] }),
|
|
889
|
-
/* @__PURE__ */
|
|
832
|
+
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
890
833
|
const i = scroll.offset + visibleIndex;
|
|
891
834
|
const highlighted = i === index;
|
|
892
835
|
const isCancel = i === cancelIndex;
|
|
893
836
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
894
837
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
895
838
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
896
|
-
const label = /* @__PURE__ */
|
|
839
|
+
const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
|
|
897
840
|
highlighted ? "\u276F " : " ",
|
|
898
841
|
bullet,
|
|
899
842
|
option
|
|
900
843
|
] });
|
|
901
844
|
const isText = sec?.kind === "text";
|
|
902
|
-
return /* @__PURE__ */
|
|
903
|
-
|
|
845
|
+
return /* @__PURE__ */ jsxs5(
|
|
846
|
+
Box6,
|
|
904
847
|
{
|
|
905
848
|
width: isText ? "100%" : barWidth,
|
|
906
849
|
paddingX: 1,
|
|
907
850
|
paddingY: 1,
|
|
908
851
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
909
852
|
children: [
|
|
910
|
-
/* @__PURE__ */
|
|
911
|
-
isText && textWidth > 0 && /* @__PURE__ */
|
|
912
|
-
|
|
853
|
+
/* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
854
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
855
|
+
Text6,
|
|
913
856
|
{
|
|
914
857
|
wrap: "truncate",
|
|
915
858
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
916
859
|
children: sec.value
|
|
917
860
|
}
|
|
918
861
|
) }),
|
|
919
|
-
sec?.kind === "badge" && /* @__PURE__ */
|
|
862
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
920
863
|
]
|
|
921
864
|
},
|
|
922
865
|
`row-${i}`
|
|
923
866
|
);
|
|
924
867
|
}) }),
|
|
925
|
-
/* @__PURE__ */
|
|
868
|
+
/* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
926
869
|
i > 0 ? " " : "",
|
|
927
|
-
/* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
870
|
+
/* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
|
|
871
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
929
872
|
" ",
|
|
930
873
|
label
|
|
931
874
|
] })
|
|
@@ -934,23 +877,23 @@ function SelectPrompt({
|
|
|
934
877
|
}
|
|
935
878
|
|
|
936
879
|
// src/ui/PromptInput.tsx
|
|
937
|
-
import { jsx as
|
|
880
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
938
881
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
939
882
|
function EnterToContinuePrompt({
|
|
940
883
|
question,
|
|
941
884
|
messages,
|
|
942
885
|
onDecide
|
|
943
886
|
}) {
|
|
944
|
-
|
|
887
|
+
useInput2((_input, key) => {
|
|
945
888
|
if (key.return) onDecide(true);
|
|
946
889
|
else if (key.escape) onDecide(false);
|
|
947
890
|
});
|
|
948
|
-
return /* @__PURE__ */
|
|
949
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
950
|
-
question && /* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
952
|
-
/* @__PURE__ */
|
|
953
|
-
/* @__PURE__ */
|
|
891
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
|
|
892
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
893
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
|
|
894
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
895
|
+
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
896
|
+
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
954
897
|
] })
|
|
955
898
|
] });
|
|
956
899
|
}
|
|
@@ -958,11 +901,11 @@ function PromptInput() {
|
|
|
958
901
|
const { phase, inputReq, submitInput } = useWizard();
|
|
959
902
|
const [draft, setDraft] = useState5("");
|
|
960
903
|
if (phase === "done" || phase === "error") {
|
|
961
|
-
return /* @__PURE__ */
|
|
904
|
+
return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
962
905
|
}
|
|
963
906
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
964
907
|
if (inputReq.promptType === "multipleChoice") {
|
|
965
|
-
return /* @__PURE__ */
|
|
908
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
966
909
|
SelectPrompt,
|
|
967
910
|
{
|
|
968
911
|
question: inputReq.prompt,
|
|
@@ -979,7 +922,7 @@ function PromptInput() {
|
|
|
979
922
|
) });
|
|
980
923
|
}
|
|
981
924
|
if (inputReq.promptType === "multiSelect") {
|
|
982
|
-
return /* @__PURE__ */
|
|
925
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
983
926
|
SelectPrompt,
|
|
984
927
|
{
|
|
985
928
|
multi: true,
|
|
@@ -994,7 +937,7 @@ function PromptInput() {
|
|
|
994
937
|
) });
|
|
995
938
|
}
|
|
996
939
|
if (inputReq.promptType === "notice") {
|
|
997
|
-
return /* @__PURE__ */
|
|
940
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
998
941
|
SelectPrompt,
|
|
999
942
|
{
|
|
1000
943
|
question: inputReq.prompt,
|
|
@@ -1005,7 +948,7 @@ function PromptInput() {
|
|
|
1005
948
|
) });
|
|
1006
949
|
}
|
|
1007
950
|
if (inputReq.promptType === "enterToContinue") {
|
|
1008
|
-
return /* @__PURE__ */
|
|
951
|
+
return /* @__PURE__ */ jsx5(
|
|
1009
952
|
EnterToContinuePrompt,
|
|
1010
953
|
{
|
|
1011
954
|
question: inputReq.prompt,
|
|
@@ -1014,12 +957,9 @@ function PromptInput() {
|
|
|
1014
957
|
}
|
|
1015
958
|
);
|
|
1016
959
|
}
|
|
1017
|
-
if (inputReq.promptType === "commandApproval" && inputReq.command) {
|
|
1018
|
-
return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
|
|
1019
|
-
}
|
|
1020
960
|
if (inputReq.promptType === "acceptReject") {
|
|
1021
961
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
1022
|
-
return /* @__PURE__ */
|
|
962
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
1023
963
|
SelectPrompt,
|
|
1024
964
|
{
|
|
1025
965
|
question: inputReq.prompt,
|
|
@@ -1030,15 +970,15 @@ function PromptInput() {
|
|
|
1030
970
|
}
|
|
1031
971
|
) });
|
|
1032
972
|
}
|
|
1033
|
-
return /* @__PURE__ */
|
|
1034
|
-
inputReq.error && /* @__PURE__ */
|
|
1035
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */
|
|
1036
|
-
/* @__PURE__ */
|
|
1037
|
-
/* @__PURE__ */
|
|
973
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
974
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
|
|
975
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
976
|
+
/* @__PURE__ */ jsxs6(Box7, { children: [
|
|
977
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
|
|
1038
978
|
inputReq.prompt,
|
|
1039
979
|
" "
|
|
1040
980
|
] }),
|
|
1041
|
-
/* @__PURE__ */
|
|
981
|
+
/* @__PURE__ */ jsx5(
|
|
1042
982
|
TextInput,
|
|
1043
983
|
{
|
|
1044
984
|
value: draft,
|
|
@@ -1056,7 +996,7 @@ function PromptInput() {
|
|
|
1056
996
|
// src/ui/Welcome.tsx
|
|
1057
997
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
1058
998
|
import { fileURLToPath } from "node:url";
|
|
1059
|
-
import { Box as
|
|
999
|
+
import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
|
|
1060
1000
|
|
|
1061
1001
|
// src/ui/copy/welcome.ts
|
|
1062
1002
|
var sidebarItems = [
|
|
@@ -1069,12 +1009,12 @@ var sidebarItems = [
|
|
|
1069
1009
|
description: "push 100 records to Algolia in seconds"
|
|
1070
1010
|
},
|
|
1071
1011
|
{
|
|
1072
|
-
title: "detect your
|
|
1073
|
-
description: "
|
|
1012
|
+
title: "detect your framework",
|
|
1013
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
1074
1014
|
},
|
|
1075
1015
|
{
|
|
1076
1016
|
title: "scaffold a search UI",
|
|
1077
|
-
description: "a
|
|
1017
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
1078
1018
|
},
|
|
1079
1019
|
{
|
|
1080
1020
|
title: "ship it",
|
|
@@ -1084,20 +1024,20 @@ var sidebarItems = [
|
|
|
1084
1024
|
|
|
1085
1025
|
// src/ui/Welcome.tsx
|
|
1086
1026
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
1087
|
-
import { jsx as
|
|
1027
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1088
1028
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
1089
1029
|
function SidebarItem({
|
|
1090
1030
|
title,
|
|
1091
1031
|
description
|
|
1092
1032
|
}) {
|
|
1093
|
-
return /* @__PURE__ */
|
|
1094
|
-
/* @__PURE__ */
|
|
1095
|
-
/* @__PURE__ */
|
|
1096
|
-
/* @__PURE__ */
|
|
1033
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1034
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
|
|
1035
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
|
|
1036
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
|
|
1097
1037
|
] }),
|
|
1098
|
-
/* @__PURE__ */
|
|
1099
|
-
/* @__PURE__ */
|
|
1100
|
-
/* @__PURE__ */
|
|
1038
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
|
|
1039
|
+
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
1040
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
|
|
1101
1041
|
] })
|
|
1102
1042
|
] });
|
|
1103
1043
|
}
|
|
@@ -1105,7 +1045,7 @@ function Welcome() {
|
|
|
1105
1045
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1106
1046
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
1107
1047
|
const { rows } = useWindowSize6();
|
|
1108
|
-
|
|
1048
|
+
useInput3((input, key) => {
|
|
1109
1049
|
if (key.return) confirmStart();
|
|
1110
1050
|
else if (input === "i") openLearnMore();
|
|
1111
1051
|
});
|
|
@@ -1123,16 +1063,16 @@ function Welcome() {
|
|
|
1123
1063
|
if (rows < 30) {
|
|
1124
1064
|
layout = scales["small"];
|
|
1125
1065
|
}
|
|
1126
|
-
return /* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */
|
|
1128
|
-
|
|
1066
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
1067
|
+
/* @__PURE__ */ jsx6(
|
|
1068
|
+
Box8,
|
|
1129
1069
|
{
|
|
1130
1070
|
paddingY: layout.main.padding.y,
|
|
1131
1071
|
paddingX: layout.main.padding.x,
|
|
1132
1072
|
flexDirection: "column",
|
|
1133
1073
|
justifyContent: "center",
|
|
1134
|
-
children: /* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
1074
|
+
children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
|
|
1075
|
+
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
1136
1076
|
Image,
|
|
1137
1077
|
{
|
|
1138
1078
|
src: IMAGE_PATH,
|
|
@@ -1143,16 +1083,16 @@ function Welcome() {
|
|
|
1143
1083
|
protocol: "halfBlock"
|
|
1144
1084
|
}
|
|
1145
1085
|
) }),
|
|
1146
|
-
/* @__PURE__ */
|
|
1147
|
-
/* @__PURE__ */
|
|
1148
|
-
/* @__PURE__ */
|
|
1149
|
-
/* @__PURE__ */
|
|
1086
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1087
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
1088
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
1089
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
1150
1090
|
] })
|
|
1151
1091
|
] })
|
|
1152
1092
|
}
|
|
1153
1093
|
),
|
|
1154
|
-
/* @__PURE__ */
|
|
1155
|
-
|
|
1094
|
+
/* @__PURE__ */ jsxs7(
|
|
1095
|
+
Box8,
|
|
1156
1096
|
{
|
|
1157
1097
|
backgroundColor: COLORS.bg.sidebar,
|
|
1158
1098
|
width: 40,
|
|
@@ -1162,8 +1102,8 @@ function Welcome() {
|
|
|
1162
1102
|
flexDirection: "column",
|
|
1163
1103
|
justifyContent: "center",
|
|
1164
1104
|
children: [
|
|
1165
|
-
/* @__PURE__ */
|
|
1166
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */
|
|
1105
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
1106
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
1167
1107
|
]
|
|
1168
1108
|
}
|
|
1169
1109
|
)
|
|
@@ -1172,7 +1112,7 @@ function Welcome() {
|
|
|
1172
1112
|
|
|
1173
1113
|
// src/ui/LearnMore.tsx
|
|
1174
1114
|
import { Fragment as Fragment2 } from "react";
|
|
1175
|
-
import { Box as
|
|
1115
|
+
import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
|
|
1176
1116
|
|
|
1177
1117
|
// src/ui/copy/learn-more.ts
|
|
1178
1118
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1180,17 +1120,12 @@ var accessItems = [
|
|
|
1180
1120
|
{
|
|
1181
1121
|
tag: "READ",
|
|
1182
1122
|
title: "Project files",
|
|
1183
|
-
description: "reads
|
|
1123
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1184
1124
|
},
|
|
1185
1125
|
{
|
|
1186
1126
|
tag: "WRITE",
|
|
1187
1127
|
title: "Code changes",
|
|
1188
|
-
description: "creates & edits files (search UI, config)
|
|
1189
|
-
},
|
|
1190
|
-
{
|
|
1191
|
-
tag: "EXEC",
|
|
1192
|
-
title: "Setup commands",
|
|
1193
|
-
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."
|
|
1128
|
+
description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
|
|
1194
1129
|
},
|
|
1195
1130
|
{
|
|
1196
1131
|
tag: "NET",
|
|
@@ -1200,13 +1135,13 @@ var accessItems = [
|
|
|
1200
1135
|
{
|
|
1201
1136
|
tag: "KEY",
|
|
1202
1137
|
title: "Credentials",
|
|
1203
|
-
description: "
|
|
1138
|
+
description: "saves your Admin API key to .env and adds it to .gitignore."
|
|
1204
1139
|
}
|
|
1205
1140
|
];
|
|
1206
1141
|
var neverItems = [
|
|
1207
1142
|
"Send your source code to a model or third party",
|
|
1208
1143
|
"Commit or push to git",
|
|
1209
|
-
"
|
|
1144
|
+
"Touch files outside your project directory"
|
|
1210
1145
|
];
|
|
1211
1146
|
var policyLinks = [
|
|
1212
1147
|
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
@@ -1214,11 +1149,10 @@ var policyLinks = [
|
|
|
1214
1149
|
];
|
|
1215
1150
|
|
|
1216
1151
|
// src/ui/LearnMore.tsx
|
|
1217
|
-
import { jsx as
|
|
1152
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1218
1153
|
var TAG_COLORS = {
|
|
1219
1154
|
READ: COLORS.success,
|
|
1220
1155
|
WRITE: COLORS.badge,
|
|
1221
|
-
EXEC: COLORS.danger,
|
|
1222
1156
|
NET: COLORS.accent,
|
|
1223
1157
|
KEY: COLORS.muted
|
|
1224
1158
|
};
|
|
@@ -1231,12 +1165,12 @@ function NeverLine({
|
|
|
1231
1165
|
}) {
|
|
1232
1166
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1233
1167
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1234
|
-
return /* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1168
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1169
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
|
|
1236
1170
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1237
|
-
segments.map((s, i) => /* @__PURE__ */
|
|
1171
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1238
1172
|
" ".repeat(rightPad),
|
|
1239
|
-
/* @__PURE__ */
|
|
1173
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
|
|
1240
1174
|
] });
|
|
1241
1175
|
}
|
|
1242
1176
|
function LearnMore() {
|
|
@@ -1244,12 +1178,12 @@ function LearnMore() {
|
|
|
1244
1178
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1245
1179
|
const { columns } = useWindowSize7();
|
|
1246
1180
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1247
|
-
|
|
1181
|
+
useInput4((_input, key) => {
|
|
1248
1182
|
if (key.escape) backToHome();
|
|
1249
1183
|
else if (key.return) confirmStart();
|
|
1250
1184
|
});
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
|
|
1185
|
+
return /* @__PURE__ */ jsxs8(
|
|
1186
|
+
Box9,
|
|
1253
1187
|
{
|
|
1254
1188
|
flexDirection: "column",
|
|
1255
1189
|
paddingX: PADDING_X,
|
|
@@ -1257,31 +1191,31 @@ function LearnMore() {
|
|
|
1257
1191
|
width: "100%",
|
|
1258
1192
|
gap: 1,
|
|
1259
1193
|
children: [
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
/* @__PURE__ */
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */
|
|
1264
|
-
/* @__PURE__ */
|
|
1265
|
-
/* @__PURE__ */
|
|
1266
|
-
/* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
/* @__PURE__ */
|
|
1194
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1195
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
|
|
1196
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
1197
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1198
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1199
|
+
/* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1200
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1201
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1202
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1269
1203
|
] }) })
|
|
1270
1204
|
] })
|
|
1271
1205
|
] }, item.tag)) }),
|
|
1272
|
-
/* @__PURE__ */
|
|
1273
|
-
/* @__PURE__ */
|
|
1274
|
-
/* @__PURE__ */
|
|
1275
|
-
/* @__PURE__ */
|
|
1206
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1207
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1208
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1209
|
+
/* @__PURE__ */ jsx7(
|
|
1276
1210
|
NeverLine,
|
|
1277
1211
|
{
|
|
1278
1212
|
width: dividerWidth,
|
|
1279
1213
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1280
1214
|
}
|
|
1281
1215
|
),
|
|
1282
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1283
|
-
/* @__PURE__ */
|
|
1284
|
-
/* @__PURE__ */
|
|
1216
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1217
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1218
|
+
/* @__PURE__ */ jsx7(
|
|
1285
1219
|
NeverLine,
|
|
1286
1220
|
{
|
|
1287
1221
|
width: dividerWidth,
|
|
@@ -1293,24 +1227,24 @@ function LearnMore() {
|
|
|
1293
1227
|
}
|
|
1294
1228
|
)
|
|
1295
1229
|
] }, item)),
|
|
1296
|
-
/* @__PURE__ */
|
|
1297
|
-
/* @__PURE__ */
|
|
1230
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1231
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1298
1232
|
] }),
|
|
1299
|
-
/* @__PURE__ */
|
|
1300
|
-
/* @__PURE__ */
|
|
1301
|
-
/* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1234
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1235
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
|
|
1302
1236
|
] }, link.label)) }),
|
|
1303
|
-
/* @__PURE__ */
|
|
1304
|
-
/* @__PURE__ */
|
|
1305
|
-
/* @__PURE__ */
|
|
1306
|
-
/* @__PURE__ */
|
|
1307
|
-
/* @__PURE__ */
|
|
1237
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1238
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1239
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1240
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
|
|
1241
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
|
|
1308
1242
|
] }),
|
|
1309
|
-
/* @__PURE__ */
|
|
1310
|
-
/* @__PURE__ */
|
|
1311
|
-
/* @__PURE__ */
|
|
1312
|
-
/* @__PURE__ */
|
|
1313
|
-
/* @__PURE__ */
|
|
1243
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1244
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1245
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
|
|
1246
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
|
|
1247
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1314
1248
|
] })
|
|
1315
1249
|
] })
|
|
1316
1250
|
]
|
|
@@ -1319,10 +1253,10 @@ function LearnMore() {
|
|
|
1319
1253
|
}
|
|
1320
1254
|
|
|
1321
1255
|
// src/ui/Sidebar.tsx
|
|
1322
|
-
import { Box as
|
|
1256
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1323
1257
|
|
|
1324
1258
|
// src/ui/Steps.tsx
|
|
1325
|
-
import { Box as
|
|
1259
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1326
1260
|
import Spinner from "ink-spinner";
|
|
1327
1261
|
|
|
1328
1262
|
// src/core/persistence.ts
|
|
@@ -1351,12 +1285,12 @@ async function clearWorkflowState(workflowId) {
|
|
|
1351
1285
|
}
|
|
1352
1286
|
|
|
1353
1287
|
// src/ui/Steps.tsx
|
|
1354
|
-
import { jsx as
|
|
1288
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1355
1289
|
function Steps() {
|
|
1356
1290
|
const { steps } = useWizard();
|
|
1357
1291
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1358
|
-
return /* @__PURE__ */
|
|
1359
|
-
s.status === "running" ? /* @__PURE__ */
|
|
1292
|
+
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: [
|
|
1293
|
+
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1360
1294
|
" ",
|
|
1361
1295
|
s.title
|
|
1362
1296
|
] }) }, s.id)) });
|
|
@@ -1365,27 +1299,27 @@ function CurrentStep() {
|
|
|
1365
1299
|
const { steps } = useWizard();
|
|
1366
1300
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1367
1301
|
if (!currentStep) return null;
|
|
1368
|
-
return /* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1302
|
+
return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
|
|
1303
|
+
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1370
1304
|
" ",
|
|
1371
1305
|
` ${currentStep.title}`
|
|
1372
1306
|
] });
|
|
1373
1307
|
}
|
|
1374
1308
|
|
|
1375
1309
|
// src/ui/Progress.tsx
|
|
1376
|
-
import { Box as
|
|
1377
|
-
import { jsx as
|
|
1310
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1311
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1378
1312
|
function Progress() {
|
|
1379
1313
|
const { steps, currentStepIndex } = useWizard();
|
|
1380
1314
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1381
1315
|
if (visibleSteps.length === 0) return null;
|
|
1382
1316
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1383
1317
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1384
|
-
return /* @__PURE__ */
|
|
1385
|
-
/* @__PURE__ */
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1388
|
-
/* @__PURE__ */
|
|
1318
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1319
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
|
|
1320
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
|
|
1321
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
|
|
1322
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
|
|
1389
1323
|
] });
|
|
1390
1324
|
}
|
|
1391
1325
|
|
|
@@ -1396,10 +1330,10 @@ var sidebarCommands = [
|
|
|
1396
1330
|
];
|
|
1397
1331
|
|
|
1398
1332
|
// src/ui/Sidebar.tsx
|
|
1399
|
-
import { jsx as
|
|
1333
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1400
1334
|
function Sidebar() {
|
|
1401
|
-
return /* @__PURE__ */
|
|
1402
|
-
|
|
1335
|
+
return /* @__PURE__ */ jsxs11(
|
|
1336
|
+
Box12,
|
|
1403
1337
|
{
|
|
1404
1338
|
backgroundColor: "#14171E",
|
|
1405
1339
|
width: 30,
|
|
@@ -1408,16 +1342,16 @@ function Sidebar() {
|
|
|
1408
1342
|
flexDirection: "column",
|
|
1409
1343
|
justifyContent: "space-between",
|
|
1410
1344
|
children: [
|
|
1411
|
-
/* @__PURE__ */
|
|
1412
|
-
/* @__PURE__ */
|
|
1413
|
-
/* @__PURE__ */
|
|
1345
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1346
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1347
|
+
/* @__PURE__ */ jsx10(Steps, {})
|
|
1414
1348
|
] }),
|
|
1415
|
-
/* @__PURE__ */
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1418
|
-
return /* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1420
|
-
/* @__PURE__ */
|
|
1349
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1350
|
+
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1351
|
+
/* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1352
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1353
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1354
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
|
|
1421
1355
|
] });
|
|
1422
1356
|
}) })
|
|
1423
1357
|
] })
|
|
@@ -1427,12 +1361,12 @@ function Sidebar() {
|
|
|
1427
1361
|
}
|
|
1428
1362
|
|
|
1429
1363
|
// src/ui/Ribbon.tsx
|
|
1430
|
-
import { Box as
|
|
1431
|
-
import { jsx as
|
|
1364
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1365
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1432
1366
|
function Ribbon() {
|
|
1433
1367
|
const firstCommand = sidebarCommands[0];
|
|
1434
|
-
return /* @__PURE__ */
|
|
1435
|
-
|
|
1368
|
+
return /* @__PURE__ */ jsxs12(
|
|
1369
|
+
Box13,
|
|
1436
1370
|
{
|
|
1437
1371
|
backgroundColor: "#14171E",
|
|
1438
1372
|
flexDirection: "row",
|
|
@@ -1440,11 +1374,11 @@ function Ribbon() {
|
|
|
1440
1374
|
paddingX: 2,
|
|
1441
1375
|
paddingY: 1,
|
|
1442
1376
|
children: [
|
|
1443
|
-
/* @__PURE__ */
|
|
1444
|
-
/* @__PURE__ */
|
|
1445
|
-
/* @__PURE__ */
|
|
1446
|
-
/* @__PURE__ */
|
|
1447
|
-
/* @__PURE__ */
|
|
1377
|
+
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1378
|
+
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1379
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1380
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1381
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
|
|
1448
1382
|
] })
|
|
1449
1383
|
]
|
|
1450
1384
|
}
|
|
@@ -1455,8 +1389,8 @@ function Ribbon() {
|
|
|
1455
1389
|
import { useState as useState6 } from "react";
|
|
1456
1390
|
|
|
1457
1391
|
// src/ui/Logs.tsx
|
|
1458
|
-
import { Box as
|
|
1459
|
-
import { jsx as
|
|
1392
|
+
import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
|
|
1393
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1460
1394
|
var KIND_COLOR = {
|
|
1461
1395
|
tool: COLORS.primary,
|
|
1462
1396
|
prompt: COLORS.badge
|
|
@@ -1487,14 +1421,14 @@ function formatTimestamp(ms) {
|
|
|
1487
1421
|
function Logs() {
|
|
1488
1422
|
const logs = useWizard((s) => s.logs);
|
|
1489
1423
|
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1490
|
-
|
|
1424
|
+
useInput5((_input, key) => {
|
|
1491
1425
|
if (key.upArrow) scroll.scrollBy(-1);
|
|
1492
1426
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1493
1427
|
});
|
|
1494
1428
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1495
|
-
return /* @__PURE__ */
|
|
1496
|
-
logs.length === 0 && /* @__PURE__ */
|
|
1497
|
-
/* @__PURE__ */
|
|
1429
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1430
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
|
|
1431
|
+
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1498
1432
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1499
1433
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1500
1434
|
const rawPreview = rawInputText(entry.input);
|
|
@@ -1504,14 +1438,14 @@ function Logs() {
|
|
|
1504
1438
|
const name = truncate2(entry.name, budget);
|
|
1505
1439
|
budget -= name.length;
|
|
1506
1440
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1507
|
-
return /* @__PURE__ */
|
|
1508
|
-
/* @__PURE__ */
|
|
1509
|
-
/* @__PURE__ */
|
|
1510
|
-
preview && /* @__PURE__ */
|
|
1511
|
-
durationText && /* @__PURE__ */
|
|
1441
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1442
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
|
|
1443
|
+
/* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1444
|
+
preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1445
|
+
durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
|
|
1512
1446
|
] }, entry.id);
|
|
1513
1447
|
}) }),
|
|
1514
|
-
/* @__PURE__ */
|
|
1448
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1515
1449
|
] });
|
|
1516
1450
|
}
|
|
1517
1451
|
|
|
@@ -1703,7 +1637,7 @@ function track(event, payload) {
|
|
|
1703
1637
|
}
|
|
1704
1638
|
|
|
1705
1639
|
// src/ui/App.tsx
|
|
1706
|
-
import { jsx as
|
|
1640
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1707
1641
|
function App() {
|
|
1708
1642
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1709
1643
|
const { exit } = useApp();
|
|
@@ -1711,7 +1645,7 @@ function App() {
|
|
|
1711
1645
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1712
1646
|
const finished = phase === "done" || phase === "error";
|
|
1713
1647
|
const currentStep = steps[currentStepIndex];
|
|
1714
|
-
|
|
1648
|
+
useInput6(
|
|
1715
1649
|
(_input, key) => {
|
|
1716
1650
|
if (key.return) {
|
|
1717
1651
|
exit();
|
|
@@ -1719,7 +1653,7 @@ function App() {
|
|
|
1719
1653
|
},
|
|
1720
1654
|
{ isActive: finished }
|
|
1721
1655
|
);
|
|
1722
|
-
|
|
1656
|
+
useInput6((_input, key) => {
|
|
1723
1657
|
if (phase === "idle" || phase === "authenticating") return;
|
|
1724
1658
|
if (key.tab) {
|
|
1725
1659
|
setShowLogs(!showLogs);
|
|
@@ -1730,8 +1664,8 @@ function App() {
|
|
|
1730
1664
|
});
|
|
1731
1665
|
}
|
|
1732
1666
|
});
|
|
1733
|
-
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" &&
|
|
1734
|
-
|
|
1667
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1668
|
+
useInput6((_input, key) => {
|
|
1735
1669
|
if (escOwnedElsewhere) return;
|
|
1736
1670
|
if (key.escape) {
|
|
1737
1671
|
track("AI Wizard Interaction", {
|
|
@@ -1750,8 +1684,8 @@ function App() {
|
|
|
1750
1684
|
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1751
1685
|
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1752
1686
|
flicker and leftover rows. */
|
|
1753
|
-
/* @__PURE__ */
|
|
1754
|
-
|
|
1687
|
+
/* @__PURE__ */ jsxs14(
|
|
1688
|
+
Box15,
|
|
1755
1689
|
{
|
|
1756
1690
|
backgroundColor: COLORS.bg.main,
|
|
1757
1691
|
flexDirection: "row",
|
|
@@ -1759,16 +1693,16 @@ function App() {
|
|
|
1759
1693
|
height: scrollsPastViewport ? void 0 : rows,
|
|
1760
1694
|
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1761
1695
|
children: [
|
|
1762
|
-
mainWindowVisible && /* @__PURE__ */
|
|
1763
|
-
|
|
1696
|
+
mainWindowVisible && /* @__PURE__ */ jsxs14(
|
|
1697
|
+
Box15,
|
|
1764
1698
|
{
|
|
1765
1699
|
flexDirection,
|
|
1766
1700
|
width: "100%",
|
|
1767
1701
|
maxHeight: rows,
|
|
1768
1702
|
justifyContent: "space-between",
|
|
1769
1703
|
children: [
|
|
1770
|
-
showLogs ? /* @__PURE__ */
|
|
1771
|
-
|
|
1704
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
|
|
1705
|
+
Box15,
|
|
1772
1706
|
{
|
|
1773
1707
|
flexDirection: "column",
|
|
1774
1708
|
paddingX: 4,
|
|
@@ -1776,26 +1710,26 @@ function App() {
|
|
|
1776
1710
|
width: showSidebar ? 70 : "100%",
|
|
1777
1711
|
flexGrow: 1,
|
|
1778
1712
|
children: [
|
|
1779
|
-
phase === "authenticating" && /* @__PURE__ */
|
|
1780
|
-
/* @__PURE__ */
|
|
1781
|
-
/* @__PURE__ */
|
|
1713
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
|
|
1714
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1715
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1782
1716
|
] }),
|
|
1783
|
-
/* @__PURE__ */
|
|
1784
|
-
/* @__PURE__ */
|
|
1785
|
-
/* @__PURE__ */
|
|
1786
|
-
phase === "running" && showSidebar && /* @__PURE__ */
|
|
1787
|
-
phase === "error" && error && /* @__PURE__ */
|
|
1717
|
+
/* @__PURE__ */ jsx13(CliOutput, {}),
|
|
1718
|
+
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1719
|
+
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1720
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1721
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
|
|
1788
1722
|
"\u2716 ",
|
|
1789
1723
|
error
|
|
1790
1724
|
] }) })
|
|
1791
1725
|
]
|
|
1792
1726
|
}
|
|
1793
1727
|
),
|
|
1794
|
-
showSidebar ? /* @__PURE__ */
|
|
1728
|
+
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1795
1729
|
]
|
|
1796
1730
|
}
|
|
1797
1731
|
),
|
|
1798
|
-
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */
|
|
1732
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
|
|
1799
1733
|
]
|
|
1800
1734
|
}
|
|
1801
1735
|
)
|
|
@@ -1809,17 +1743,18 @@ import "zod";
|
|
|
1809
1743
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1810
1744
|
import { join as join5 } from "node:path";
|
|
1811
1745
|
var configFile = () => join5(stateDir(), "config.json");
|
|
1812
|
-
var
|
|
1746
|
+
var DEFAULT_CONFIG = {
|
|
1813
1747
|
version: 1,
|
|
1814
1748
|
aiConsent: false,
|
|
1815
|
-
workflowsRun: []
|
|
1816
|
-
}
|
|
1749
|
+
workflowsRun: [],
|
|
1750
|
+
searchApiKeys: {}
|
|
1751
|
+
};
|
|
1817
1752
|
async function loadConfig() {
|
|
1818
1753
|
try {
|
|
1819
1754
|
const raw = await readFile2(configFile(), "utf8");
|
|
1820
|
-
return { ...
|
|
1755
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1821
1756
|
} catch {
|
|
1822
|
-
return
|
|
1757
|
+
return { ...DEFAULT_CONFIG };
|
|
1823
1758
|
}
|
|
1824
1759
|
}
|
|
1825
1760
|
async function saveConfig(config) {
|
|
@@ -1831,6 +1766,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1831
1766
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1832
1767
|
await saveConfig(config);
|
|
1833
1768
|
}
|
|
1769
|
+
function isStoredSearchKey(value) {
|
|
1770
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1771
|
+
const { appId, key } = value;
|
|
1772
|
+
return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
|
|
1773
|
+
}
|
|
1774
|
+
function storedSearchKeys(config) {
|
|
1775
|
+
const stored = config.searchApiKeys;
|
|
1776
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1777
|
+
return {};
|
|
1778
|
+
}
|
|
1779
|
+
return stored;
|
|
1780
|
+
}
|
|
1781
|
+
async function getStoredSearchKey(index, appId) {
|
|
1782
|
+
const entry = storedSearchKeys(await loadConfig())[index];
|
|
1783
|
+
if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
|
|
1784
|
+
return entry.key;
|
|
1785
|
+
}
|
|
1786
|
+
async function storeSearchKey(index, appId, key) {
|
|
1787
|
+
const config = await loadConfig();
|
|
1788
|
+
config.searchApiKeys = {
|
|
1789
|
+
...storedSearchKeys(config),
|
|
1790
|
+
[index]: { appId, key }
|
|
1791
|
+
};
|
|
1792
|
+
await saveConfig(config);
|
|
1793
|
+
}
|
|
1794
|
+
async function forgetSearchKey(index) {
|
|
1795
|
+
const config = await loadConfig();
|
|
1796
|
+
const remaining = { ...storedSearchKeys(config) };
|
|
1797
|
+
delete remaining[index];
|
|
1798
|
+
config.searchApiKeys = remaining;
|
|
1799
|
+
await saveConfig(config);
|
|
1800
|
+
}
|
|
1834
1801
|
|
|
1835
1802
|
// src/core/orchestrator.ts
|
|
1836
1803
|
function defineStep(step) {
|
|
@@ -1875,7 +1842,7 @@ async function ensureConsent() {
|
|
|
1875
1842
|
if (config.aiConsent) return;
|
|
1876
1843
|
const store = useWizard.getState();
|
|
1877
1844
|
const answer = await store.requestUserInput({
|
|
1878
|
-
prompt: "Wizard will make AI-authored changes to this repository
|
|
1845
|
+
prompt: "Wizard will make AI-authored changes to this repository.",
|
|
1879
1846
|
promptType: "enterToContinue",
|
|
1880
1847
|
options: []
|
|
1881
1848
|
});
|
|
@@ -2157,7 +2124,7 @@ async function ensureApplication() {
|
|
|
2157
2124
|
}
|
|
2158
2125
|
|
|
2159
2126
|
// src/workflows/default.ts
|
|
2160
|
-
import { z as
|
|
2127
|
+
import { z as z27 } from "zod";
|
|
2161
2128
|
|
|
2162
2129
|
// src/actions/listIndices.ts
|
|
2163
2130
|
import { z as z5 } from "zod";
|
|
@@ -2278,9 +2245,9 @@ function listFilesTool(ctx) {
|
|
|
2278
2245
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
2279
2246
|
return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
|
|
2280
2247
|
}
|
|
2281
|
-
const
|
|
2282
|
-
if (!
|
|
2283
|
-
const entries = await readdir(
|
|
2248
|
+
const resolved = resolveInRoot(ctx, ".");
|
|
2249
|
+
if (!resolved.ok) return resolved.error;
|
|
2250
|
+
const entries = await readdir(resolved.target, { withFileTypes: true });
|
|
2284
2251
|
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
2285
2252
|
}
|
|
2286
2253
|
});
|
|
@@ -2298,14 +2265,14 @@ function changeDirectoryTool(ctx) {
|
|
|
2298
2265
|
}),
|
|
2299
2266
|
execute: async ({ path }) => {
|
|
2300
2267
|
logger.info({ path }, "called changeDirectory tool");
|
|
2301
|
-
const
|
|
2302
|
-
if (!
|
|
2268
|
+
const resolved = resolveInRoot(ctx, path);
|
|
2269
|
+
if (!resolved.ok) return resolved.error;
|
|
2303
2270
|
try {
|
|
2304
|
-
const info = await stat(
|
|
2271
|
+
const info = await stat(resolved.target);
|
|
2305
2272
|
if (!info.isDirectory()) {
|
|
2306
2273
|
return `Error changing directory to ${path}: not a directory`;
|
|
2307
2274
|
}
|
|
2308
|
-
ctx.cwd =
|
|
2275
|
+
ctx.cwd = resolved.target;
|
|
2309
2276
|
return `Changed working directory to ${ctx.cwd}`;
|
|
2310
2277
|
} catch (err) {
|
|
2311
2278
|
return `Error changing directory to ${path}: ${err.message}`;
|
|
@@ -2370,11 +2337,11 @@ function readFileTool(ctx) {
|
|
|
2370
2337
|
return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
|
|
2371
2338
|
}
|
|
2372
2339
|
logger.info({ filePath }, "called readFile tool");
|
|
2373
|
-
const
|
|
2374
|
-
if (!
|
|
2340
|
+
const resolved = resolveInRoot(ctx, filePath);
|
|
2341
|
+
if (!resolved.ok) return resolved.error;
|
|
2375
2342
|
try {
|
|
2376
|
-
const content = await readFile3(
|
|
2377
|
-
return isEnvFile(
|
|
2343
|
+
const content = await readFile3(resolved.target, "utf8");
|
|
2344
|
+
return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
|
|
2378
2345
|
} catch (err) {
|
|
2379
2346
|
return `Error reading ${filePath}: ${err.message}`;
|
|
2380
2347
|
}
|
|
@@ -2396,17 +2363,17 @@ function writeFileTool(ctx) {
|
|
|
2396
2363
|
}),
|
|
2397
2364
|
execute: async ({ filePath, content }) => {
|
|
2398
2365
|
logger.info({ filePath }, "called writeFile tool");
|
|
2399
|
-
const
|
|
2400
|
-
if (
|
|
2401
|
-
if (isSecretEnvFile(
|
|
2366
|
+
const resolved = resolveInRoot(ctx, filePath);
|
|
2367
|
+
if (resolved.ok === false) return resolved.error;
|
|
2368
|
+
if (isSecretEnvFile(resolved.target)) {
|
|
2402
2369
|
return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
|
|
2403
2370
|
}
|
|
2404
2371
|
try {
|
|
2405
|
-
if (await hasSymlinkParent(ctx,
|
|
2406
|
-
return `Refused: ${
|
|
2372
|
+
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
2373
|
+
return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
|
|
2407
2374
|
}
|
|
2408
|
-
await mkdir3(dirname4(
|
|
2409
|
-
await writeFile3(
|
|
2375
|
+
await mkdir3(dirname4(resolved.target), { recursive: true });
|
|
2376
|
+
await writeFile3(resolved.target, content, "utf8");
|
|
2410
2377
|
return `Wrote to ${filePath}`;
|
|
2411
2378
|
} catch (err) {
|
|
2412
2379
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -2417,93 +2384,12 @@ function writeFileTool(ctx) {
|
|
|
2417
2384
|
|
|
2418
2385
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2419
2386
|
import { tool as tool6 } from "ai";
|
|
2420
|
-
import
|
|
2387
|
+
import z12 from "zod";
|
|
2421
2388
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2422
2389
|
import { dirname as dirname5 } from "node:path";
|
|
2423
2390
|
|
|
2424
2391
|
// src/lib/algoliaApiKey.ts
|
|
2425
|
-
import { z as z12 } from "zod";
|
|
2426
|
-
|
|
2427
|
-
// src/lib/keychain.ts
|
|
2428
|
-
import { deletePassword, getPassword, setPassword } from "cross-keychain";
|
|
2429
2392
|
import { z as z11 } from "zod";
|
|
2430
|
-
var SERVICE = "algolia-wizard";
|
|
2431
|
-
var ACCOUNT = "api-keys";
|
|
2432
|
-
var storedKeysSchema = z11.record(z11.string(), z11.string());
|
|
2433
|
-
function entryId(kind, index, appId) {
|
|
2434
|
-
return `${kind}:${appId}:${index}`;
|
|
2435
|
-
}
|
|
2436
|
-
async function loadKeys() {
|
|
2437
|
-
const raw = await getPassword(SERVICE, ACCOUNT);
|
|
2438
|
-
if (!raw) return {};
|
|
2439
|
-
let payload;
|
|
2440
|
-
try {
|
|
2441
|
-
payload = JSON.parse(raw);
|
|
2442
|
-
} catch {
|
|
2443
|
-
payload = null;
|
|
2444
|
-
}
|
|
2445
|
-
const keys = storedKeysSchema.safeParse(payload);
|
|
2446
|
-
if (!keys.success) {
|
|
2447
|
-
logger.warn("the stored API keys are unreadable; treating them as empty");
|
|
2448
|
-
return {};
|
|
2449
|
-
}
|
|
2450
|
-
return keys.data;
|
|
2451
|
-
}
|
|
2452
|
-
var queue = Promise.resolve();
|
|
2453
|
-
function serialized(op) {
|
|
2454
|
-
const next = queue.then(op);
|
|
2455
|
-
queue = next.catch(() => {
|
|
2456
|
-
});
|
|
2457
|
-
return next;
|
|
2458
|
-
}
|
|
2459
|
-
async function readStoredKey(kind, index, appId) {
|
|
2460
|
-
try {
|
|
2461
|
-
return (await loadKeys())[entryId(kind, index, appId)] ?? null;
|
|
2462
|
-
} catch (err) {
|
|
2463
|
-
logger.warn(
|
|
2464
|
-
{ err: err.message, kind, index, appId },
|
|
2465
|
-
"could not read the API key from the keychain"
|
|
2466
|
-
);
|
|
2467
|
-
return null;
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
function storeKey(kind, index, appId, value) {
|
|
2471
|
-
return serialized(async () => {
|
|
2472
|
-
const id = entryId(kind, index, appId);
|
|
2473
|
-
try {
|
|
2474
|
-
const keys = await loadKeys();
|
|
2475
|
-
await setPassword(
|
|
2476
|
-
SERVICE,
|
|
2477
|
-
ACCOUNT,
|
|
2478
|
-
JSON.stringify({ ...keys, [id]: value })
|
|
2479
|
-
);
|
|
2480
|
-
if ((await loadKeys())[id] !== value) {
|
|
2481
|
-
throw new Error("the keychain did not store the value");
|
|
2482
|
-
}
|
|
2483
|
-
} catch (err) {
|
|
2484
|
-
logger.warn(
|
|
2485
|
-
{ err: err.message, kind, index, appId },
|
|
2486
|
-
"could not store the API key in the keychain; the next run will create another"
|
|
2487
|
-
);
|
|
2488
|
-
}
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
function deleteStoredKeys() {
|
|
2492
|
-
return serialized(async () => {
|
|
2493
|
-
try {
|
|
2494
|
-
await deletePassword(SERVICE, ACCOUNT);
|
|
2495
|
-
} catch (err) {
|
|
2496
|
-
const message = err.message;
|
|
2497
|
-
if (/not found/i.test(message)) return;
|
|
2498
|
-
logger.warn(
|
|
2499
|
-
{ err: message },
|
|
2500
|
-
"could not delete the API keys from the keychain"
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
});
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
// src/lib/algoliaApiKey.ts
|
|
2507
2393
|
var WRITE_ACLS = [
|
|
2508
2394
|
"addObject",
|
|
2509
2395
|
"deleteObject",
|
|
@@ -2511,21 +2397,58 @@ var WRITE_ACLS = [
|
|
|
2511
2397
|
"editSettings",
|
|
2512
2398
|
"listIndexes"
|
|
2513
2399
|
];
|
|
2514
|
-
var
|
|
2515
|
-
|
|
2516
|
-
value:
|
|
2400
|
+
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2401
|
+
var apiKeySchema = z11.object({
|
|
2402
|
+
value: z11.string().min(1),
|
|
2403
|
+
acl: z11.array(z11.string()).default([]),
|
|
2404
|
+
indexes: z11.array(z11.string()).default([])
|
|
2405
|
+
});
|
|
2406
|
+
var apiKeyListSchema = z11.object({
|
|
2407
|
+
items: z11.array(apiKeySchema).optional(),
|
|
2408
|
+
keys: z11.array(apiKeySchema).optional()
|
|
2409
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2410
|
+
var createdKeySchema = z11.object({
|
|
2411
|
+
key: z11.string().min(1).optional(),
|
|
2412
|
+
value: z11.string().min(1).optional()
|
|
2517
2413
|
}).transform((o) => o.key ?? o.value);
|
|
2518
|
-
|
|
2519
|
-
|
|
2414
|
+
function canReuseForWrites(key, index) {
|
|
2415
|
+
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2416
|
+
}
|
|
2417
|
+
async function resolveWriteKey(index) {
|
|
2418
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2419
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuseForWrites(key, index))?.value;
|
|
2420
|
+
if (existing) {
|
|
2421
|
+
logger.info({ index }, "reusing existing write API key");
|
|
2422
|
+
return existing;
|
|
2423
|
+
}
|
|
2424
|
+
logger.info({ index }, "no reusable write key found; creating one");
|
|
2425
|
+
const created = await runAlgoliaCli([
|
|
2426
|
+
"apikeys",
|
|
2427
|
+
"create",
|
|
2428
|
+
"--indices",
|
|
2429
|
+
index,
|
|
2430
|
+
"--acl",
|
|
2431
|
+
WRITE_ACLS.join(","),
|
|
2432
|
+
"--description",
|
|
2433
|
+
`wizard write key for ${index}`,
|
|
2434
|
+
"-o",
|
|
2435
|
+
"json"
|
|
2436
|
+
]);
|
|
2437
|
+
const writeKey = createdKeySchema.parse(JSON.parse(created));
|
|
2438
|
+
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2439
|
+
return writeKey;
|
|
2440
|
+
}
|
|
2441
|
+
async function createSearchOnlyKey(index) {
|
|
2442
|
+
logger.info({ index }, "creating a search-only API key");
|
|
2520
2443
|
const stdout = await runAlgoliaCli([
|
|
2521
2444
|
"apikeys",
|
|
2522
2445
|
"create",
|
|
2523
2446
|
"--acl",
|
|
2524
|
-
|
|
2447
|
+
"search",
|
|
2525
2448
|
"--indices",
|
|
2526
2449
|
index,
|
|
2527
2450
|
"--description",
|
|
2528
|
-
|
|
2451
|
+
`Algolia Wizard search-only key for ${index}`,
|
|
2529
2452
|
"-o",
|
|
2530
2453
|
"json"
|
|
2531
2454
|
]);
|
|
@@ -2539,72 +2462,49 @@ async function createKey(index, acls, description) {
|
|
|
2539
2462
|
if (!created) throw new Error("apikeys create returned no key value");
|
|
2540
2463
|
return created;
|
|
2541
2464
|
}
|
|
2542
|
-
async function
|
|
2465
|
+
async function apiKeyExists(key) {
|
|
2543
2466
|
try {
|
|
2544
|
-
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]
|
|
2467
|
+
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
|
|
2545
2468
|
return true;
|
|
2546
2469
|
} catch (err) {
|
|
2547
|
-
return !/does not exist/i.test(err.message);
|
|
2470
|
+
return !/does not exist|not found|404/i.test(err.message);
|
|
2548
2471
|
}
|
|
2549
2472
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
const cacheKey = `${kind}:${appId}:${index}`;
|
|
2557
|
-
const cached = resolved.get(cacheKey);
|
|
2558
|
-
if (cached) return cached;
|
|
2559
|
-
const pending = provisionKey(kind, index, appId, acls, description).catch(
|
|
2560
|
-
(err) => {
|
|
2561
|
-
resolved.delete(cacheKey);
|
|
2562
|
-
throw err;
|
|
2563
|
-
}
|
|
2564
|
-
);
|
|
2565
|
-
resolved.set(cacheKey, pending);
|
|
2566
|
-
return pending;
|
|
2567
|
-
}
|
|
2568
|
-
async function provisionKey(kind, index, appId, acls, description) {
|
|
2569
|
-
const stored = await readStoredKey(kind, index, appId);
|
|
2473
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2474
|
+
if (envKey) {
|
|
2475
|
+
await recordSearchKey(index, appId, envKey);
|
|
2476
|
+
return { key: envKey, source: "env" };
|
|
2477
|
+
}
|
|
2478
|
+
const stored = await getStoredSearchKey(index, appId);
|
|
2570
2479
|
if (stored) {
|
|
2571
|
-
if (await
|
|
2572
|
-
logger.info({
|
|
2573
|
-
return { key: stored, source: "
|
|
2480
|
+
if (await apiKeyExists(stored)) {
|
|
2481
|
+
logger.info({ index, appId }, "reusing the stored search-only API key");
|
|
2482
|
+
return { key: stored, source: "config" };
|
|
2574
2483
|
}
|
|
2575
|
-
logger.
|
|
2576
|
-
{
|
|
2577
|
-
"the stored API key no longer exists; creating
|
|
2484
|
+
logger.warn(
|
|
2485
|
+
{ index, appId },
|
|
2486
|
+
"the stored search-only API key no longer exists; creating a replacement"
|
|
2578
2487
|
);
|
|
2488
|
+
await forgetSearchKey(index);
|
|
2579
2489
|
}
|
|
2580
|
-
const key = await
|
|
2581
|
-
await
|
|
2490
|
+
const key = await createSearchOnlyKey(index);
|
|
2491
|
+
await recordSearchKey(index, appId, key);
|
|
2582
2492
|
return { key, source: "created" };
|
|
2583
2493
|
}
|
|
2584
|
-
function
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
}
|
|
2593
|
-
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2594
|
-
if (envKey) return { key: envKey, source: "env" };
|
|
2595
|
-
return resolveKey(
|
|
2596
|
-
"search",
|
|
2597
|
-
index,
|
|
2598
|
-
appId,
|
|
2599
|
-
["search"],
|
|
2600
|
-
`Algolia Wizard search-only key for ${index} index`
|
|
2601
|
-
);
|
|
2494
|
+
async function recordSearchKey(index, appId, key) {
|
|
2495
|
+
try {
|
|
2496
|
+
await storeSearchKey(index, appId, key);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
logger.warn(
|
|
2499
|
+
{ err: err.message, index },
|
|
2500
|
+
"could not record the search-only API key; a later run may create another"
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2602
2503
|
}
|
|
2603
2504
|
|
|
2604
2505
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2605
2506
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2606
2507
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2607
|
-
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2608
2508
|
function appendEnv(content, entries) {
|
|
2609
2509
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2610
2510
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -2614,68 +2514,53 @@ function appendEnv(content, entries) {
|
|
|
2614
2514
|
function hasEnv(content, name) {
|
|
2615
2515
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
2616
2516
|
}
|
|
2617
|
-
function upsertEnv(content, name, value) {
|
|
2618
|
-
if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
|
|
2619
|
-
return content.replace(
|
|
2620
|
-
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m"),
|
|
2621
|
-
`${name}=${value}`
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
2517
|
function writeCredentialsTool(ctx) {
|
|
2625
2518
|
return tool6({
|
|
2626
|
-
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR})
|
|
2627
|
-
inputSchema:
|
|
2628
|
-
filePath:
|
|
2519
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
|
|
2520
|
+
inputSchema: z12.object({
|
|
2521
|
+
filePath: z12.string().describe(
|
|
2629
2522
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2630
2523
|
)
|
|
2631
2524
|
}),
|
|
2632
2525
|
execute: async ({ filePath }) => {
|
|
2633
2526
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2634
|
-
const
|
|
2635
|
-
if (
|
|
2527
|
+
const resolved = resolveInRoot(ctx, filePath);
|
|
2528
|
+
if (resolved.ok === false) return resolved.error;
|
|
2636
2529
|
const targetIndex = useWizard.getState().targetIndex;
|
|
2637
2530
|
if (!targetIndex) {
|
|
2638
2531
|
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2639
2532
|
}
|
|
2640
|
-
let
|
|
2641
|
-
let
|
|
2533
|
+
let appId;
|
|
2534
|
+
let writeKey;
|
|
2535
|
+
try {
|
|
2536
|
+
appId = (await requireApplication()).id;
|
|
2537
|
+
writeKey = await resolveWriteKey(targetIndex);
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2540
|
+
}
|
|
2642
2541
|
try {
|
|
2643
|
-
if (await hasSymlinkParent(ctx,
|
|
2644
|
-
return `Refused: ${
|
|
2542
|
+
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
2543
|
+
return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
|
|
2645
2544
|
}
|
|
2545
|
+
let existing = "";
|
|
2646
2546
|
try {
|
|
2647
|
-
existing = await readFile4(
|
|
2547
|
+
existing = await readFile4(resolved.target, "utf8");
|
|
2648
2548
|
} catch (err) {
|
|
2649
2549
|
if (err.code !== "ENOENT") throw err;
|
|
2650
2550
|
}
|
|
2651
|
-
present = [APP_ID_VAR, API_KEY_VAR].filter(
|
|
2551
|
+
const present = [APP_ID_VAR, API_KEY_VAR].filter(
|
|
2652
2552
|
(name) => hasEnv(existing, name)
|
|
2653
2553
|
);
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
}
|
|
2657
|
-
let credentials = [];
|
|
2658
|
-
if (present.length === 0) {
|
|
2659
|
-
try {
|
|
2660
|
-
const appId = (await requireApplication()).id;
|
|
2661
|
-
credentials = [
|
|
2662
|
-
[APP_ID_VAR, appId],
|
|
2663
|
-
[API_KEY_VAR, (await resolveWriteKey(targetIndex, appId)).key]
|
|
2664
|
-
];
|
|
2665
|
-
} catch (err) {
|
|
2666
|
-
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2554
|
+
if (present.length > 0) {
|
|
2555
|
+
return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
|
|
2667
2556
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
await mkdir4(dirname5(resolved2.target), { recursive: true });
|
|
2676
|
-
await writeFile4(resolved2.target, updated, "utf8");
|
|
2677
|
-
const wrote = `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}`;
|
|
2678
|
-
return present.length > 0 ? `${wrote}. Skipped ${present.join(" and ")}: already defined there.` : wrote;
|
|
2557
|
+
const envWithCredentials = appendEnv(existing, [
|
|
2558
|
+
[APP_ID_VAR, appId],
|
|
2559
|
+
[API_KEY_VAR, writeKey]
|
|
2560
|
+
]);
|
|
2561
|
+
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2562
|
+
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
2563
|
+
return `Wrote Algolia credentials to ${filePath}`;
|
|
2679
2564
|
} catch (err) {
|
|
2680
2565
|
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
2681
2566
|
}
|
|
@@ -2685,23 +2570,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2685
2570
|
|
|
2686
2571
|
// src/lib/tools/searchFiles.ts
|
|
2687
2572
|
import { tool as tool7 } from "ai";
|
|
2688
|
-
import
|
|
2573
|
+
import z13 from "zod";
|
|
2689
2574
|
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2690
2575
|
import { join as join7 } from "node:path";
|
|
2691
2576
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2692
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2693
|
-
"node_modules",
|
|
2694
|
-
"dist",
|
|
2695
|
-
"build",
|
|
2696
|
-
"vendor",
|
|
2697
|
-
"venv",
|
|
2698
|
-
"__pycache__",
|
|
2699
|
-
"target"
|
|
2700
|
-
]);
|
|
2701
2577
|
async function walkFiles(dir) {
|
|
2578
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2702
2579
|
const out = [];
|
|
2703
2580
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2704
|
-
if (e.name.startsWith(".") ||
|
|
2581
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2705
2582
|
const full = join7(dir, e.name);
|
|
2706
2583
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2707
2584
|
else if (e.isFile()) out.push(full);
|
|
@@ -2711,9 +2588,9 @@ async function walkFiles(dir) {
|
|
|
2711
2588
|
function searchFilesTool(ctx) {
|
|
2712
2589
|
return tool7({
|
|
2713
2590
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
2714
|
-
inputSchema:
|
|
2715
|
-
query:
|
|
2716
|
-
path:
|
|
2591
|
+
inputSchema: z13.object({
|
|
2592
|
+
query: z13.string().describe("JavaScript RegExp pattern to search for"),
|
|
2593
|
+
path: z13.string().optional().describe("Directory to search in (default: cwd)")
|
|
2717
2594
|
}),
|
|
2718
2595
|
execute: async ({ query, path = "." }) => {
|
|
2719
2596
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2723,8 +2600,8 @@ function searchFilesTool(ctx) {
|
|
|
2723
2600
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
2724
2601
|
return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
|
|
2725
2602
|
}
|
|
2726
|
-
const
|
|
2727
|
-
if (!
|
|
2603
|
+
const resolved = resolveInRoot(ctx, path);
|
|
2604
|
+
if (!resolved.ok) return resolved.error;
|
|
2728
2605
|
let re;
|
|
2729
2606
|
try {
|
|
2730
2607
|
re = new RegExp(query);
|
|
@@ -2732,7 +2609,7 @@ function searchFilesTool(ctx) {
|
|
|
2732
2609
|
return `Invalid regex: ${err.message}`;
|
|
2733
2610
|
}
|
|
2734
2611
|
const matches = [];
|
|
2735
|
-
for (const file of await walkFiles(
|
|
2612
|
+
for (const file of await walkFiles(resolved.target)) {
|
|
2736
2613
|
let content;
|
|
2737
2614
|
try {
|
|
2738
2615
|
content = await readFile5(file, "utf8");
|
|
@@ -2755,188 +2632,92 @@ function searchFilesTool(ctx) {
|
|
|
2755
2632
|
});
|
|
2756
2633
|
}
|
|
2757
2634
|
|
|
2758
|
-
// src/lib/tools/
|
|
2635
|
+
// src/lib/tools/verifyImplementation.ts
|
|
2759
2636
|
import { tool as tool8 } from "ai";
|
|
2760
|
-
import
|
|
2761
|
-
import { relative as relative2 } from "node:path";
|
|
2637
|
+
import z14 from "zod";
|
|
2762
2638
|
|
|
2763
|
-
// src/lib/tools/utils/
|
|
2639
|
+
// src/lib/tools/utils/runCommand.ts
|
|
2764
2640
|
import { spawn as spawn2 } from "node:child_process";
|
|
2765
|
-
|
|
2766
|
-
// src/lib/tools/context.ts
|
|
2767
|
-
var DEFAULT_TOOL_LIMITS = {
|
|
2768
|
-
list: 10,
|
|
2769
|
-
search: 10,
|
|
2770
|
-
read: 20,
|
|
2771
|
-
match: 100,
|
|
2772
|
-
shell: 30
|
|
2773
|
-
};
|
|
2774
|
-
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2775
|
-
async function refuseByDefault() {
|
|
2776
|
-
return "reject";
|
|
2777
|
-
}
|
|
2778
|
-
function createShellContext(overrides = {}) {
|
|
2779
|
-
return {
|
|
2780
|
-
env: async () => ({}),
|
|
2781
|
-
timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
|
|
2782
|
-
executions: [],
|
|
2783
|
-
approve: refuseByDefault,
|
|
2784
|
-
...overrides
|
|
2785
|
-
};
|
|
2786
|
-
}
|
|
2787
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
|
|
2788
|
-
return {
|
|
2789
|
-
root: cwd,
|
|
2790
|
-
cwd,
|
|
2791
|
-
limits: { ...limits },
|
|
2792
|
-
counts: { list: 0, search: 0, read: 0, shell: 0 },
|
|
2793
|
-
shell: shell2
|
|
2794
|
-
};
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
// src/lib/tools/utils/runShell.ts
|
|
2798
|
-
var SIGKILL_DELAY_MS = 5e3;
|
|
2799
|
-
var HEAD_CHARS = 4e3;
|
|
2800
|
-
var TAIL_CHARS = 8e3;
|
|
2801
|
-
function truncateOutput(output) {
|
|
2802
|
-
if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
|
|
2803
|
-
const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
|
|
2804
|
-
return [
|
|
2805
|
-
output.slice(0, HEAD_CHARS),
|
|
2806
|
-
`
|
|
2807
|
-
\u2026 [${omitted} characters omitted] \u2026
|
|
2808
|
-
`,
|
|
2809
|
-
output.slice(-TAIL_CHARS)
|
|
2810
|
-
].join("");
|
|
2811
|
-
}
|
|
2812
|
-
function runShell(command, opts) {
|
|
2813
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
2814
|
-
const startedAt = Date.now();
|
|
2641
|
+
function runCommand(command, args, cwd) {
|
|
2815
2642
|
return new Promise((resolve4) => {
|
|
2816
2643
|
let output = "";
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
shell: true,
|
|
2821
|
-
cwd: opts.cwd,
|
|
2822
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2823
|
-
env: { ...process.env, ...opts.env }
|
|
2644
|
+
const child = spawn2(command, args, {
|
|
2645
|
+
cwd,
|
|
2646
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2824
2647
|
});
|
|
2825
|
-
const finish = (exitCode) => {
|
|
2826
|
-
if (settled) return;
|
|
2827
|
-
settled = true;
|
|
2828
|
-
clearTimeout(timer);
|
|
2829
|
-
clearTimeout(killTimer);
|
|
2830
|
-
resolve4({
|
|
2831
|
-
exitCode,
|
|
2832
|
-
output: truncateOutput(output.trim()),
|
|
2833
|
-
timedOut,
|
|
2834
|
-
durationMs: Date.now() - startedAt
|
|
2835
|
-
});
|
|
2836
|
-
};
|
|
2837
|
-
let killTimer;
|
|
2838
|
-
const timer = setTimeout(() => {
|
|
2839
|
-
timedOut = true;
|
|
2840
|
-
output += `
|
|
2841
|
-
[timed out after ${timeoutMs}ms]`;
|
|
2842
|
-
child.kill("SIGTERM");
|
|
2843
|
-
killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
|
|
2844
|
-
}, timeoutMs);
|
|
2845
2648
|
child.stdout?.on("data", (d) => output += d);
|
|
2846
2649
|
child.stderr?.on("data", (d) => output += d);
|
|
2847
|
-
child.on(
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
child.on("close", (code) =>
|
|
2650
|
+
child.on(
|
|
2651
|
+
"error",
|
|
2652
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2653
|
+
);
|
|
2654
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2852
2655
|
});
|
|
2853
2656
|
}
|
|
2854
2657
|
|
|
2855
|
-
// src/lib/tools/
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2658
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2659
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2660
|
+
import { existsSync } from "node:fs";
|
|
2661
|
+
import { join as join8 } from "node:path";
|
|
2662
|
+
var LOCKFILES = [
|
|
2663
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2664
|
+
["yarn.lock", "yarn"],
|
|
2665
|
+
["bun.lockb", "bun"],
|
|
2666
|
+
["bun.lock", "bun"],
|
|
2667
|
+
["package-lock.json", "npm"]
|
|
2668
|
+
];
|
|
2669
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2670
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2671
|
+
}
|
|
2672
|
+
function packageManagerFrom(pkg) {
|
|
2673
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2870
2674
|
}
|
|
2871
|
-
function
|
|
2675
|
+
function packageManagerFromLockfile(cwd) {
|
|
2676
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2677
|
+
}
|
|
2678
|
+
async function detectPackageManager(cwd) {
|
|
2679
|
+
try {
|
|
2680
|
+
const pkg = await readPackageJson(cwd);
|
|
2681
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2682
|
+
} catch {
|
|
2683
|
+
}
|
|
2684
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// src/lib/tools/repoVerification.ts
|
|
2688
|
+
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2689
|
+
async function runRepoVerificationCheck() {
|
|
2690
|
+
let pkg;
|
|
2691
|
+
try {
|
|
2692
|
+
pkg = await readPackageJson();
|
|
2693
|
+
} catch (err) {
|
|
2694
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2695
|
+
return { ok: false, checks: [], limitation };
|
|
2696
|
+
}
|
|
2697
|
+
const scripts = pkg.scripts ?? {};
|
|
2698
|
+
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2699
|
+
if (present.length === 0) {
|
|
2700
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2701
|
+
return { ok: false, checks: [], limitation };
|
|
2702
|
+
}
|
|
2703
|
+
const pm = await detectPackageManager(process.cwd());
|
|
2704
|
+
const checks = [];
|
|
2705
|
+
for (const script of present) {
|
|
2706
|
+
const command = `${pm} run ${script}`;
|
|
2707
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2708
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2709
|
+
}
|
|
2710
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/lib/tools/verifyImplementation.ts
|
|
2714
|
+
function verifyImplementationTool() {
|
|
2872
2715
|
return tool8({
|
|
2873
|
-
description: "Run
|
|
2874
|
-
inputSchema:
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
)
|
|
2878
|
-
cwd: z15.string().optional().describe(
|
|
2879
|
-
"Directory to run in, relative to the project root. Defaults to the project root."
|
|
2880
|
-
),
|
|
2881
|
-
explanation: z15.string().describe(
|
|
2882
|
-
"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."
|
|
2883
|
-
)
|
|
2884
|
-
}),
|
|
2885
|
-
execute: async ({ command, cwd, explanation }) => {
|
|
2886
|
-
if (++ctx.counts.shell > ctx.limits.shell) {
|
|
2887
|
-
return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
|
|
2888
|
-
}
|
|
2889
|
-
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
2890
|
-
if (!resolved2.ok) return resolved2.error;
|
|
2891
|
-
logger.info({ command, cwd: resolved2.target }, "called runShell tool");
|
|
2892
|
-
const decision = await ctx.shell.approve({
|
|
2893
|
-
command,
|
|
2894
|
-
cwd: resolved2.target,
|
|
2895
|
-
explanation
|
|
2896
|
-
});
|
|
2897
|
-
if (decision === "reject") {
|
|
2898
|
-
ctx.shell.executions.push({
|
|
2899
|
-
command,
|
|
2900
|
-
cwd: resolved2.target,
|
|
2901
|
-
approved: false
|
|
2902
|
-
});
|
|
2903
|
-
logger.info({ command }, "runShell: user rejected the command");
|
|
2904
|
-
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2905
|
-
}
|
|
2906
|
-
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
2907
|
-
const env = await ctx.shell.env().catch((err) => {
|
|
2908
|
-
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
2909
|
-
return {};
|
|
2910
|
-
});
|
|
2911
|
-
const run2 = await (ctx.shell.run ?? runShell)(command, {
|
|
2912
|
-
cwd: resolved2.target,
|
|
2913
|
-
env,
|
|
2914
|
-
timeoutMs: ctx.shell.timeoutMs
|
|
2915
|
-
});
|
|
2916
|
-
logger.info(
|
|
2917
|
-
{
|
|
2918
|
-
command,
|
|
2919
|
-
exitCode: run2.exitCode,
|
|
2920
|
-
timedOut: run2.timedOut,
|
|
2921
|
-
durationMs: run2.durationMs
|
|
2922
|
-
},
|
|
2923
|
-
"runShell finished"
|
|
2924
|
-
);
|
|
2925
|
-
await markInteraction();
|
|
2926
|
-
ctx.shell.executions.push({
|
|
2927
|
-
command,
|
|
2928
|
-
cwd: resolved2.target,
|
|
2929
|
-
approved: true,
|
|
2930
|
-
exitCode: run2.exitCode,
|
|
2931
|
-
output: run2.output,
|
|
2932
|
-
timedOut: run2.timedOut,
|
|
2933
|
-
durationMs: run2.durationMs
|
|
2934
|
-
});
|
|
2935
|
-
return {
|
|
2936
|
-
exitCode: run2.exitCode,
|
|
2937
|
-
timedOut: run2.timedOut,
|
|
2938
|
-
output: run2.output
|
|
2939
|
-
};
|
|
2716
|
+
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.",
|
|
2717
|
+
inputSchema: z14.object(),
|
|
2718
|
+
execute: async () => {
|
|
2719
|
+
logger.info("called verifyImplementation tool");
|
|
2720
|
+
return runRepoVerificationCheck();
|
|
2940
2721
|
}
|
|
2941
2722
|
});
|
|
2942
2723
|
}
|
|
@@ -2947,7 +2728,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
2947
2728
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2948
2729
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2949
2730
|
import { dirname as dirname6 } from "node:path";
|
|
2950
|
-
import
|
|
2731
|
+
import z15 from "zod";
|
|
2951
2732
|
var DATA_DIR = ".algolia-wizard/data";
|
|
2952
2733
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
2953
2734
|
var MAX_RECORDS = 100;
|
|
@@ -2959,17 +2740,17 @@ var anthropic = createAnthropic({
|
|
|
2959
2740
|
function generateRecordTool(ctx) {
|
|
2960
2741
|
return tool9({
|
|
2961
2742
|
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.",
|
|
2962
|
-
inputSchema:
|
|
2963
|
-
entityName:
|
|
2964
|
-
attributes:
|
|
2965
|
-
count:
|
|
2966
|
-
hint:
|
|
2743
|
+
inputSchema: z15.object({
|
|
2744
|
+
entityName: z15.string().describe("Name of the entity to generate records for."),
|
|
2745
|
+
attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
|
|
2746
|
+
count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2747
|
+
hint: z15.string().optional().describe("Optional context to steer realistic values.")
|
|
2967
2748
|
}),
|
|
2968
2749
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
2969
2750
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
2970
2751
|
try {
|
|
2971
|
-
const value =
|
|
2972
|
-
const recordSchema =
|
|
2752
|
+
const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
|
|
2753
|
+
const recordSchema = z15.object(
|
|
2973
2754
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
2974
2755
|
);
|
|
2975
2756
|
const generateBatch = async (batchCount) => {
|
|
@@ -2979,8 +2760,8 @@ function generateRecordTool(ctx) {
|
|
|
2979
2760
|
const { output } = await generateText({
|
|
2980
2761
|
model: anthropic(RECORD_MODEL),
|
|
2981
2762
|
output: Output.object({
|
|
2982
|
-
schema:
|
|
2983
|
-
records:
|
|
2763
|
+
schema: z15.object({
|
|
2764
|
+
records: z15.array(recordSchema).length(batchCount)
|
|
2984
2765
|
})
|
|
2985
2766
|
}),
|
|
2986
2767
|
prompt: [
|
|
@@ -3016,18 +2797,18 @@ function generateRecordTool(ctx) {
|
|
|
3016
2797
|
}));
|
|
3017
2798
|
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
3018
2799
|
const relPath = `${DATA_DIR}/${slug}.json`;
|
|
3019
|
-
const
|
|
3020
|
-
if (
|
|
3021
|
-
if (await hasSymlinkParent(ctx,
|
|
3022
|
-
return `Refused: ${
|
|
2800
|
+
const resolved = resolveInRoot(ctx, relPath);
|
|
2801
|
+
if (resolved.ok === false) return resolved.error;
|
|
2802
|
+
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
2803
|
+
return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
|
|
3023
2804
|
}
|
|
3024
|
-
await mkdir5(dirname6(
|
|
3025
|
-
await writeFile5(
|
|
2805
|
+
await mkdir5(dirname6(resolved.target), { recursive: true });
|
|
2806
|
+
await writeFile5(resolved.target, JSON.stringify(records, null, 2), "utf8");
|
|
3026
2807
|
logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
|
|
3027
2808
|
return {
|
|
3028
2809
|
filePath: relPath,
|
|
3029
2810
|
count: records.length,
|
|
3030
|
-
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime
|
|
2811
|
+
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.`
|
|
3031
2812
|
};
|
|
3032
2813
|
} catch (err) {
|
|
3033
2814
|
return `Error generating records: ${err.message}`;
|
|
@@ -3038,12 +2819,12 @@ function generateRecordTool(ctx) {
|
|
|
3038
2819
|
|
|
3039
2820
|
// src/lib/tools/notifyUser.ts
|
|
3040
2821
|
import { tool as tool10 } from "ai";
|
|
3041
|
-
import
|
|
2822
|
+
import z16 from "zod";
|
|
3042
2823
|
function notifyUserTool() {
|
|
3043
2824
|
return tool10({
|
|
3044
2825
|
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.`,
|
|
3045
|
-
inputSchema:
|
|
3046
|
-
message:
|
|
2826
|
+
inputSchema: z16.object({
|
|
2827
|
+
message: z16.string().describe(
|
|
3047
2828
|
"Short, plain-language description of what you are doing now."
|
|
3048
2829
|
)
|
|
3049
2830
|
}),
|
|
@@ -3055,6 +2836,22 @@ function notifyUserTool() {
|
|
|
3055
2836
|
});
|
|
3056
2837
|
}
|
|
3057
2838
|
|
|
2839
|
+
// src/lib/tools/context.ts
|
|
2840
|
+
var DEFAULT_TOOL_LIMITS = {
|
|
2841
|
+
list: 10,
|
|
2842
|
+
search: 10,
|
|
2843
|
+
read: 20,
|
|
2844
|
+
match: 100
|
|
2845
|
+
};
|
|
2846
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
2847
|
+
return {
|
|
2848
|
+
root: cwd,
|
|
2849
|
+
cwd,
|
|
2850
|
+
limits,
|
|
2851
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
|
|
3058
2855
|
// src/lib/tools/index.ts
|
|
3059
2856
|
function withLogging(name, def) {
|
|
3060
2857
|
const execute = def.execute;
|
|
@@ -3086,7 +2883,10 @@ function createTools(ctx, { output, tools }) {
|
|
|
3086
2883
|
writeCredentialsTool(ctx)
|
|
3087
2884
|
),
|
|
3088
2885
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3089
|
-
|
|
2886
|
+
verifyImplementation: withLogging(
|
|
2887
|
+
"verifyImplementation",
|
|
2888
|
+
verifyImplementationTool()
|
|
2889
|
+
),
|
|
3090
2890
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3091
2891
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
3092
2892
|
};
|
|
@@ -3121,7 +2921,7 @@ async function runAgent(req) {
|
|
|
3121
2921
|
baseURL: PROXY_BASE_URL,
|
|
3122
2922
|
fetch: proxyFetch
|
|
3123
2923
|
});
|
|
3124
|
-
const toolContext =
|
|
2924
|
+
const toolContext = createToolContext();
|
|
3125
2925
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3126
2926
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3127
2927
|
const instructions = [
|
|
@@ -3186,11 +2986,7 @@ async function runAgent(req) {
|
|
|
3186
2986
|
"runAgent finished"
|
|
3187
2987
|
);
|
|
3188
2988
|
logger.info(
|
|
3189
|
-
{
|
|
3190
|
-
counts: toolContext.counts,
|
|
3191
|
-
limits: toolContext.limits,
|
|
3192
|
-
commandsRun: toolContext.shell.executions.length
|
|
3193
|
-
},
|
|
2989
|
+
{ counts: toolContext.counts, limits: toolContext.limits },
|
|
3194
2990
|
"tool usage"
|
|
3195
2991
|
);
|
|
3196
2992
|
const toolResults = await stream.toolResults;
|
|
@@ -3208,16 +3004,16 @@ async function runAgent(req) {
|
|
|
3208
3004
|
}
|
|
3209
3005
|
|
|
3210
3006
|
// src/actions/detectLanguage.ts
|
|
3211
|
-
import
|
|
3212
|
-
var detectLanguageSchema =
|
|
3213
|
-
languages:
|
|
3214
|
-
frameworks:
|
|
3007
|
+
import z19 from "zod";
|
|
3008
|
+
var detectLanguageSchema = z19.object({
|
|
3009
|
+
languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
|
|
3010
|
+
frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
|
|
3215
3011
|
});
|
|
3216
3012
|
var detectLanguage = () => runAgent({
|
|
3217
3013
|
instructions: [
|
|
3218
3014
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3219
|
-
"If a superset language is found, exclude the subset language
|
|
3220
|
-
"If a meta-framework is used, exclude the framework
|
|
3015
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3016
|
+
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3221
3017
|
"Return the exact version",
|
|
3222
3018
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3223
3019
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3229,31 +3025,31 @@ var detectLanguage = () => runAgent({
|
|
|
3229
3025
|
});
|
|
3230
3026
|
|
|
3231
3027
|
// src/actions/analyzeCodebase.ts
|
|
3232
|
-
import
|
|
3028
|
+
import z20 from "zod";
|
|
3233
3029
|
var READONLY_TOOLS = [
|
|
3234
3030
|
"listFiles",
|
|
3235
3031
|
"changeDirectory",
|
|
3236
3032
|
"readFile",
|
|
3237
3033
|
"searchFiles"
|
|
3238
3034
|
];
|
|
3239
|
-
var ingestionAnalysisSchema =
|
|
3240
|
-
ingestionAnalysis:
|
|
3241
|
-
|
|
3242
|
-
name:
|
|
3243
|
-
paths:
|
|
3035
|
+
var ingestionAnalysisSchema = z20.object({
|
|
3036
|
+
ingestionAnalysis: z20.array(
|
|
3037
|
+
z20.object({
|
|
3038
|
+
name: z20.string(),
|
|
3039
|
+
paths: z20.array(z20.string()),
|
|
3244
3040
|
// indexable fields the agent found for this entity
|
|
3245
|
-
attributes:
|
|
3041
|
+
attributes: z20.array(z20.string())
|
|
3246
3042
|
})
|
|
3247
3043
|
)
|
|
3248
3044
|
});
|
|
3249
|
-
var searchImplementationAnalysisSchema =
|
|
3250
|
-
searchImplementationAnalysis:
|
|
3045
|
+
var searchImplementationAnalysisSchema = z20.object({
|
|
3046
|
+
searchImplementationAnalysis: z20.string()
|
|
3251
3047
|
});
|
|
3252
|
-
var verificationSchema =
|
|
3253
|
-
verification:
|
|
3048
|
+
var verificationSchema = z20.object({
|
|
3049
|
+
verification: z20.array(z20.string())
|
|
3254
3050
|
});
|
|
3255
3051
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
3256
|
-
var analyzeCodebaseSchema =
|
|
3052
|
+
var analyzeCodebaseSchema = z20.object({
|
|
3257
3053
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3258
3054
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
3259
3055
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -3315,7 +3111,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3315
3111
|
// package.json
|
|
3316
3112
|
var package_default = {
|
|
3317
3113
|
name: "@algolia/wizard",
|
|
3318
|
-
version: "0.
|
|
3114
|
+
version: "0.10.0",
|
|
3319
3115
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3320
3116
|
type: "module",
|
|
3321
3117
|
engines: {
|
|
@@ -3366,7 +3162,6 @@ var package_default = {
|
|
|
3366
3162
|
"@hono/node-server": "^2.0.10",
|
|
3367
3163
|
"@segment/analytics-node": "^3.1.0",
|
|
3368
3164
|
ai: "^6.0.190",
|
|
3369
|
-
"cross-keychain": "^1.1.0",
|
|
3370
3165
|
dotenv: "^17.4.2",
|
|
3371
3166
|
hono: "^4.12.27",
|
|
3372
3167
|
ink: "^7.0.5",
|
|
@@ -3434,8 +3229,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
3434
3229
|
}
|
|
3435
3230
|
|
|
3436
3231
|
// src/actions/confirmLanguage.ts
|
|
3437
|
-
import
|
|
3438
|
-
var confirmLanguageSchema =
|
|
3232
|
+
import z22 from "zod";
|
|
3233
|
+
var confirmLanguageSchema = z22.object({
|
|
3439
3234
|
languages: detectLanguageSchema.shape.languages
|
|
3440
3235
|
});
|
|
3441
3236
|
async function confirmLanguage(ctx) {
|
|
@@ -3456,19 +3251,17 @@ async function confirmLanguage(ctx) {
|
|
|
3456
3251
|
}
|
|
3457
3252
|
|
|
3458
3253
|
// src/actions/confirmFramework.ts
|
|
3459
|
-
import
|
|
3460
|
-
var confirmFrameworkSchema =
|
|
3254
|
+
import z23 from "zod";
|
|
3255
|
+
var confirmFrameworkSchema = z23.object({
|
|
3461
3256
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3462
3257
|
});
|
|
3463
3258
|
var CURATED_FRAMEWORKS = [
|
|
3464
3259
|
"Next.js",
|
|
3465
3260
|
"React",
|
|
3466
3261
|
"Vue",
|
|
3467
|
-
"
|
|
3468
|
-
"
|
|
3469
|
-
"
|
|
3470
|
-
"Rails",
|
|
3471
|
-
"Symfony"
|
|
3262
|
+
"Angular",
|
|
3263
|
+
"Svelte",
|
|
3264
|
+
"Vanilla JS"
|
|
3472
3265
|
];
|
|
3473
3266
|
var OTHER_OPTION = "Other";
|
|
3474
3267
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -3481,15 +3274,12 @@ var FRAMEWORK_ALIASES = {
|
|
|
3481
3274
|
vuejs: "vue",
|
|
3482
3275
|
angular: "angular",
|
|
3483
3276
|
angularjs: "angular",
|
|
3277
|
+
svelte: "svelte",
|
|
3278
|
+
sveltekit: "svelte",
|
|
3484
3279
|
vanillajs: "vanillajs",
|
|
3485
3280
|
vanilla: "vanillajs",
|
|
3486
3281
|
javascript: "vanillajs",
|
|
3487
|
-
js: "vanillajs"
|
|
3488
|
-
django: "django",
|
|
3489
|
-
laravel: "laravel",
|
|
3490
|
-
rails: "rails",
|
|
3491
|
-
rubyonrails: "rails",
|
|
3492
|
-
symfony: "symfony"
|
|
3282
|
+
js: "vanillajs"
|
|
3493
3283
|
};
|
|
3494
3284
|
var isSameFramework = (a, b) => {
|
|
3495
3285
|
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
@@ -3590,8 +3380,8 @@ async function promptUser(ctx, params) {
|
|
|
3590
3380
|
}
|
|
3591
3381
|
|
|
3592
3382
|
// src/actions/confirmEntities.ts
|
|
3593
|
-
import
|
|
3594
|
-
var confirmEntitiesSchema =
|
|
3383
|
+
import z24 from "zod";
|
|
3384
|
+
var confirmEntitiesSchema = z24.object({
|
|
3595
3385
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3596
3386
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3597
3387
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3661,15 +3451,15 @@ async function confirmEntities(ctx) {
|
|
|
3661
3451
|
}
|
|
3662
3452
|
|
|
3663
3453
|
// src/actions/review.ts
|
|
3664
|
-
import { z as
|
|
3665
|
-
var reviewSchema =
|
|
3454
|
+
import { z as z25 } from "zod";
|
|
3455
|
+
var reviewSchema = z25.object({
|
|
3666
3456
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3667
3457
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3668
3458
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3669
3459
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3670
|
-
summaryPoints:
|
|
3671
|
-
reviewPrompt:
|
|
3672
|
-
nextSteps:
|
|
3460
|
+
summaryPoints: z25.array(z25.string()),
|
|
3461
|
+
reviewPrompt: z25.string(),
|
|
3462
|
+
nextSteps: z25.array(z25.string())
|
|
3673
3463
|
});
|
|
3674
3464
|
function formatCompletedSteps(steps) {
|
|
3675
3465
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3720,12 +3510,19 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3720
3510
|
};
|
|
3721
3511
|
|
|
3722
3512
|
// src/actions/implement.ts
|
|
3723
|
-
import
|
|
3513
|
+
import z26 from "zod";
|
|
3724
3514
|
|
|
3725
3515
|
// src/lib/worktree.ts
|
|
3726
|
-
import { execFile } from "node:child_process";
|
|
3727
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3728
|
-
import {
|
|
3516
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3517
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3518
|
+
import {
|
|
3519
|
+
basename as basename2,
|
|
3520
|
+
dirname as dirname7,
|
|
3521
|
+
isAbsolute as isAbsolute2,
|
|
3522
|
+
join as join9,
|
|
3523
|
+
relative as relative2,
|
|
3524
|
+
resolve as resolve3
|
|
3525
|
+
} from "node:path";
|
|
3729
3526
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3730
3527
|
var MAX_WIZARD_WORKTREES = 3;
|
|
3731
3528
|
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
@@ -3756,7 +3553,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3756
3553
|
return out.trim().length > 0;
|
|
3757
3554
|
}
|
|
3758
3555
|
async function pruneOldWorktrees(repoRoot) {
|
|
3759
|
-
const dir =
|
|
3556
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3760
3557
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3761
3558
|
for (const slug of stale) {
|
|
3762
3559
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3767,7 +3564,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3767
3564
|
"worktree",
|
|
3768
3565
|
"remove",
|
|
3769
3566
|
"--force",
|
|
3770
|
-
|
|
3567
|
+
join9(dir, slug)
|
|
3771
3568
|
]);
|
|
3772
3569
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3773
3570
|
} catch (err) {
|
|
@@ -3781,13 +3578,113 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3781
3578
|
async function createWorktree(repoRoot) {
|
|
3782
3579
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3783
3580
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3784
|
-
const path =
|
|
3581
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3785
3582
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3786
3583
|
await pruneOldWorktrees(repoRoot);
|
|
3787
3584
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3788
3585
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3789
3586
|
return { path, branch };
|
|
3790
3587
|
}
|
|
3588
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3589
|
+
try {
|
|
3590
|
+
await readPackageJson(worktreePath);
|
|
3591
|
+
} catch {
|
|
3592
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
3593
|
+
}
|
|
3594
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3595
|
+
return new Promise((resolve4) => {
|
|
3596
|
+
let output = "";
|
|
3597
|
+
const child = spawn3(pm, ["install"], {
|
|
3598
|
+
cwd: worktreePath,
|
|
3599
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3600
|
+
});
|
|
3601
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3602
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3603
|
+
child.on(
|
|
3604
|
+
"error",
|
|
3605
|
+
(err) => resolve4({
|
|
3606
|
+
ok: false,
|
|
3607
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3608
|
+
})
|
|
3609
|
+
);
|
|
3610
|
+
child.on(
|
|
3611
|
+
"close",
|
|
3612
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3613
|
+
);
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3617
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3618
|
+
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3619
|
+
return {
|
|
3620
|
+
ok: false,
|
|
3621
|
+
reason: `entrypoint "${entrypoint}" is not a plain file path`
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
const target = resolve3(worktreePath, entrypoint);
|
|
3625
|
+
const rel = relative2(worktreePath, target);
|
|
3626
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
3627
|
+
return {
|
|
3628
|
+
ok: false,
|
|
3629
|
+
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
return { ok: true, target };
|
|
3633
|
+
}
|
|
3634
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3635
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
3636
|
+
return {
|
|
3637
|
+
ran: false,
|
|
3638
|
+
ok: false,
|
|
3639
|
+
output: "",
|
|
3640
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
3644
|
+
if (!validated.ok) {
|
|
3645
|
+
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3646
|
+
}
|
|
3647
|
+
try {
|
|
3648
|
+
if (!(await stat2(validated.target)).isFile()) {
|
|
3649
|
+
return {
|
|
3650
|
+
ran: false,
|
|
3651
|
+
ok: false,
|
|
3652
|
+
output: "",
|
|
3653
|
+
reason: `entrypoint "${entrypoint}" is not a file`
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
} catch {
|
|
3657
|
+
return {
|
|
3658
|
+
ran: false,
|
|
3659
|
+
ok: false,
|
|
3660
|
+
output: "",
|
|
3661
|
+
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
return new Promise((resolveRun) => {
|
|
3665
|
+
let output = "";
|
|
3666
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3667
|
+
cwd: worktreePath,
|
|
3668
|
+
shell: false,
|
|
3669
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3670
|
+
env: { ...process.env, ...env }
|
|
3671
|
+
});
|
|
3672
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3673
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3674
|
+
child.on(
|
|
3675
|
+
"error",
|
|
3676
|
+
(err) => resolveRun({
|
|
3677
|
+
ran: true,
|
|
3678
|
+
ok: false,
|
|
3679
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3680
|
+
})
|
|
3681
|
+
);
|
|
3682
|
+
child.on(
|
|
3683
|
+
"close",
|
|
3684
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3685
|
+
);
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3791
3688
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3792
3689
|
const trimmed = sourcePath.trim();
|
|
3793
3690
|
if (!trimmed) {
|
|
@@ -3801,8 +3698,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3801
3698
|
} catch {
|
|
3802
3699
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3803
3700
|
}
|
|
3804
|
-
const relPath =
|
|
3805
|
-
const dest =
|
|
3701
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3702
|
+
const dest = join9(worktreePath, relPath);
|
|
3806
3703
|
try {
|
|
3807
3704
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3808
3705
|
await copyFile(source, dest);
|
|
@@ -3820,7 +3717,7 @@ function hasEnvVar(content, name) {
|
|
|
3820
3717
|
async function readEnvVar(worktreePath, name) {
|
|
3821
3718
|
let content;
|
|
3822
3719
|
try {
|
|
3823
|
-
content = await
|
|
3720
|
+
content = await readFile7(join9(worktreePath, ".env"), "utf8");
|
|
3824
3721
|
} catch (err) {
|
|
3825
3722
|
if (err.code !== "ENOENT") throw err;
|
|
3826
3723
|
return void 0;
|
|
@@ -3835,10 +3732,10 @@ async function readEnvVar(worktreePath, name) {
|
|
|
3835
3732
|
return value;
|
|
3836
3733
|
}
|
|
3837
3734
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3838
|
-
const target =
|
|
3735
|
+
const target = join9(worktreePath, ".env");
|
|
3839
3736
|
let existing = "";
|
|
3840
3737
|
try {
|
|
3841
|
-
existing = await
|
|
3738
|
+
existing = await readFile7(target, "utf8");
|
|
3842
3739
|
} catch (err) {
|
|
3843
3740
|
if (err.code !== "ENOENT") throw err;
|
|
3844
3741
|
}
|
|
@@ -3907,15 +3804,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3907
3804
|
}
|
|
3908
3805
|
|
|
3909
3806
|
// src/lib/algoliaDocs.ts
|
|
3910
|
-
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
3911
|
-
import { dirname as dirname8, join as
|
|
3807
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3808
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
3912
3809
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3913
|
-
var DOCS_SUBPATH =
|
|
3810
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
3914
3811
|
function findDocsDir() {
|
|
3915
3812
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3916
3813
|
for (; ; ) {
|
|
3917
|
-
const candidate =
|
|
3918
|
-
if (
|
|
3814
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
3815
|
+
if (existsSync2(candidate)) return candidate;
|
|
3919
3816
|
const parent = dirname8(dir);
|
|
3920
3817
|
if (parent === dir) return void 0;
|
|
3921
3818
|
dir = parent;
|
|
@@ -3937,7 +3834,7 @@ function loadAlgoliaDoc(language) {
|
|
|
3937
3834
|
);
|
|
3938
3835
|
return "";
|
|
3939
3836
|
}
|
|
3940
|
-
return readFileSync(
|
|
3837
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
3941
3838
|
}
|
|
3942
3839
|
function getNamedDoc(name, language) {
|
|
3943
3840
|
const docsDir = findDocsDir();
|
|
@@ -3945,15 +3842,14 @@ function getNamedDoc(name, language) {
|
|
|
3945
3842
|
logger.warn("docs/algolia-sdk not found");
|
|
3946
3843
|
return "";
|
|
3947
3844
|
}
|
|
3948
|
-
const file =
|
|
3949
|
-
if (!
|
|
3845
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
3846
|
+
if (!existsSync2(file)) {
|
|
3950
3847
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
3951
3848
|
return "";
|
|
3952
3849
|
}
|
|
3953
3850
|
return readFileSync(file, "utf8").trim();
|
|
3954
3851
|
}
|
|
3955
3852
|
function getFrameworkSpecificDoc(frameworks) {
|
|
3956
|
-
if (frameworks.length === 0) return "";
|
|
3957
3853
|
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3958
3854
|
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3959
3855
|
return loadAlgoliaDoc("vue");
|
|
@@ -3961,6 +3857,9 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
3961
3857
|
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3962
3858
|
return loadAlgoliaDoc("react");
|
|
3963
3859
|
}
|
|
3860
|
+
if (fw.includes("angular")) {
|
|
3861
|
+
return loadAlgoliaDoc("angular");
|
|
3862
|
+
}
|
|
3964
3863
|
return loadAlgoliaDoc("js");
|
|
3965
3864
|
}
|
|
3966
3865
|
|
|
@@ -3970,63 +3869,62 @@ function shellQuote(value) {
|
|
|
3970
3869
|
}
|
|
3971
3870
|
|
|
3972
3871
|
// src/actions/implement.ts
|
|
3973
|
-
var implementSchema =
|
|
3974
|
-
filesChanged:
|
|
3975
|
-
summary:
|
|
3976
|
-
worktreePath:
|
|
3977
|
-
ingestCommand:
|
|
3978
|
-
ingestScriptRan:
|
|
3979
|
-
ingestRecordCount:
|
|
3980
|
-
ingestDurationMs:
|
|
3981
|
-
ingestionSource:
|
|
3982
|
-
searchEnvVars:
|
|
3983
|
-
|
|
3984
|
-
name:
|
|
3985
|
-
value:
|
|
3872
|
+
var implementSchema = z26.object({
|
|
3873
|
+
filesChanged: z26.array(z26.string()),
|
|
3874
|
+
summary: z26.string(),
|
|
3875
|
+
worktreePath: z26.string().optional(),
|
|
3876
|
+
ingestCommand: z26.string().optional(),
|
|
3877
|
+
ingestScriptRan: z26.boolean().optional(),
|
|
3878
|
+
ingestRecordCount: z26.number().optional(),
|
|
3879
|
+
ingestDurationMs: z26.number().optional(),
|
|
3880
|
+
ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
|
|
3881
|
+
searchEnvVars: z26.array(
|
|
3882
|
+
z26.object({
|
|
3883
|
+
name: z26.string(),
|
|
3884
|
+
value: z26.string()
|
|
3986
3885
|
})
|
|
3987
3886
|
).optional()
|
|
3988
3887
|
});
|
|
3989
|
-
var implementationOutputSchema =
|
|
3990
|
-
summary:
|
|
3991
|
-
|
|
3888
|
+
var implementationOutputSchema = z26.object({
|
|
3889
|
+
summary: z26.string(),
|
|
3890
|
+
// Ingestion only: a structured pair the wizard turns into an argv, never a
|
|
3891
|
+
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
3892
|
+
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
3893
|
+
runtime: z26.enum(INGEST_RUNTIMES).optional(),
|
|
3894
|
+
entrypoint: z26.string().optional()
|
|
3992
3895
|
});
|
|
3993
|
-
var verificationOutputSchema =
|
|
3994
|
-
summary:
|
|
3995
|
-
sufficient:
|
|
3996
|
-
additionalInstructions:
|
|
3896
|
+
var verificationOutputSchema = z26.object({
|
|
3897
|
+
summary: z26.string(),
|
|
3898
|
+
sufficient: z26.boolean(),
|
|
3899
|
+
additionalInstructions: z26.string().optional()
|
|
3997
3900
|
});
|
|
3998
3901
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3999
3902
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4000
3903
|
var INGEST_DIR = ".algolia-wizard";
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
];
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
}
|
|
4021
|
-
function searchUiTarget(language) {
|
|
4022
|
-
return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
|
|
4023
|
-
}
|
|
4024
|
-
function frameworksForDoc(language) {
|
|
4025
|
-
if (!isJsProject(language)) return [];
|
|
4026
|
-
return [matchUiFramework(language)?.doc ?? "js"];
|
|
3904
|
+
function detectUiFramework(language) {
|
|
3905
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3906
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3907
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3908
|
+
return "React";
|
|
3909
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3910
|
+
return "JavaScript";
|
|
3911
|
+
}
|
|
3912
|
+
function frameworksForDoc(framework) {
|
|
3913
|
+
switch (framework) {
|
|
3914
|
+
case "React":
|
|
3915
|
+
return ["react"];
|
|
3916
|
+
case "Vue":
|
|
3917
|
+
return ["vue"];
|
|
3918
|
+
case "Angular":
|
|
3919
|
+
return ["angular"];
|
|
3920
|
+
case "JavaScript":
|
|
3921
|
+
return [];
|
|
3922
|
+
}
|
|
4027
3923
|
}
|
|
4028
3924
|
function publicEnvPrefix(language) {
|
|
4029
|
-
const frameworkNames =
|
|
3925
|
+
const frameworkNames = language.frameworks.map(
|
|
3926
|
+
(framework) => framework.name.toLowerCase()
|
|
3927
|
+
);
|
|
4030
3928
|
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
4031
3929
|
return "NEXT_PUBLIC_";
|
|
4032
3930
|
}
|
|
@@ -4039,21 +3937,17 @@ function publicEnvPrefix(language) {
|
|
|
4039
3937
|
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
4040
3938
|
return "VITE_";
|
|
4041
3939
|
}
|
|
4042
|
-
return
|
|
3940
|
+
return "PUBLIC_";
|
|
4043
3941
|
}
|
|
4044
3942
|
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4045
3943
|
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4046
|
-
var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
4047
3944
|
function appIdVar(language) {
|
|
4048
3945
|
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4049
3946
|
}
|
|
4050
3947
|
function searchKeyVar(language) {
|
|
4051
3948
|
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
4052
3949
|
}
|
|
4053
|
-
function
|
|
4054
|
-
return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
|
|
4055
|
-
}
|
|
4056
|
-
function searchEnvVars(language, index, appId, searchKey) {
|
|
3950
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
4057
3951
|
return [
|
|
4058
3952
|
{
|
|
4059
3953
|
name: appIdVar(language),
|
|
@@ -4062,26 +3956,14 @@ function searchEnvVars(language, index, appId, searchKey) {
|
|
|
4062
3956
|
{
|
|
4063
3957
|
name: searchKeyVar(language),
|
|
4064
3958
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
4065
|
-
},
|
|
4066
|
-
// Wizard-supplied rather than written into the generated code, because an
|
|
4067
|
-
// agent that retypes the name (appending the project name, re-casing it)
|
|
4068
|
-
// leaves the UI querying an index that does not exist.
|
|
4069
|
-
{
|
|
4070
|
-
name: searchIndexVar(language),
|
|
4071
|
-
value: index
|
|
4072
3959
|
}
|
|
4073
3960
|
];
|
|
4074
3961
|
}
|
|
4075
3962
|
function baseInstructions(input) {
|
|
4076
3963
|
return [
|
|
4077
|
-
|
|
4078
|
-
// index-scoped keys then reject with a 403.
|
|
4079
|
-
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
3964
|
+
`Target Algolia index: ${input.targetIndex}`,
|
|
4080
3965
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
4081
|
-
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
4082
|
-
`Commands run through a shell on ${process.platform}. Write commands that work there.`,
|
|
4083
|
-
"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.",
|
|
4084
|
-
"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."
|
|
3966
|
+
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
4085
3967
|
];
|
|
4086
3968
|
}
|
|
4087
3969
|
function sourceSpecificInstructions(input) {
|
|
@@ -4101,52 +3983,36 @@ function sourceSpecificInstructions(input) {
|
|
|
4101
3983
|
generated: [
|
|
4102
3984
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4103
3985
|
"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.",
|
|
4104
|
-
"In the script, read and parse each returned file path at runtime
|
|
3986
|
+
"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.",
|
|
4105
3987
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4106
3988
|
]
|
|
4107
3989
|
};
|
|
4108
3990
|
return byLine[input.ingestionSource];
|
|
4109
3991
|
}
|
|
4110
|
-
function algoliaClientDoc(input) {
|
|
4111
|
-
const doc = getNamedDoc("save-records", "js");
|
|
4112
|
-
if (!doc) return [];
|
|
4113
|
-
if (isJsProject(input.language)) return [doc];
|
|
4114
|
-
return [
|
|
4115
|
-
"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:",
|
|
4116
|
-
doc
|
|
4117
|
-
];
|
|
4118
|
-
}
|
|
4119
3992
|
function ingestionInstructions(input) {
|
|
4120
3993
|
return [
|
|
4121
3994
|
...input.confirmed && input.confirmed.length ? [
|
|
4122
3995
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4123
3996
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4124
3997
|
`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.`,
|
|
4125
|
-
|
|
4126
|
-
"Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
|
|
3998
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4127
3999
|
"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.",
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
'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.',
|
|
4000
|
+
getNamedDoc("save-records", "js"),
|
|
4001
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4131
4002
|
"The summary should be extremely concise.",
|
|
4003
|
+
`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.`,
|
|
4132
4004
|
...sourceSpecificInstructions(input)
|
|
4133
4005
|
] : []
|
|
4134
4006
|
];
|
|
4135
4007
|
}
|
|
4136
4008
|
function searchInstructions(input) {
|
|
4137
|
-
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.
|
|
4009
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4138
4010
|
return [
|
|
4139
4011
|
"Implement an in-app Algolia search experience.",
|
|
4140
|
-
`Build the search UI for ${input.
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
] : [
|
|
4145
|
-
"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."
|
|
4146
|
-
],
|
|
4147
|
-
`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 target index.`,
|
|
4148
|
-
`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.`,
|
|
4149
|
-
"Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4012
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
4013
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4014
|
+
doc,
|
|
4015
|
+
`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 "${input.targetIndex}" index.`,
|
|
4150
4016
|
// The key is provisioned only after verification passes, so the agent never
|
|
4151
4017
|
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4152
4018
|
// decide whether a key already exists, and an agent-invented value there
|
|
@@ -4155,8 +4021,9 @@ function searchInstructions(input) {
|
|
|
4155
4021
|
// Not the agent's to rename: the wizard writes these exact names into
|
|
4156
4022
|
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4157
4023
|
// reading a var the wizard never wrote.
|
|
4158
|
-
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4159
|
-
"
|
|
4024
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4025
|
+
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4026
|
+
'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.',
|
|
4160
4027
|
"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."
|
|
4161
4028
|
];
|
|
4162
4029
|
}
|
|
@@ -4164,12 +4031,11 @@ function verificationInstructions(input) {
|
|
|
4164
4031
|
return [
|
|
4165
4032
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4166
4033
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4167
|
-
"
|
|
4168
|
-
"
|
|
4169
|
-
"
|
|
4170
|
-
"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.",
|
|
4034
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4035
|
+
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4036
|
+
"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.",
|
|
4171
4037
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4172
|
-
`Do not modify "${input.ingestDir}/" unless
|
|
4038
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4173
4039
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4174
4040
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4175
4041
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4190,15 +4056,14 @@ var IMPLEMENT_CONFIG = {
|
|
|
4190
4056
|
}
|
|
4191
4057
|
};
|
|
4192
4058
|
var useCaseToolMap = {
|
|
4193
|
-
ingestion: [
|
|
4059
|
+
ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
|
|
4060
|
+
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
4061
|
+
verification: [
|
|
4194
4062
|
...FS_READ_TOOLS,
|
|
4195
4063
|
"writeFile",
|
|
4196
|
-
"
|
|
4197
|
-
"runShell",
|
|
4064
|
+
"verifyImplementation",
|
|
4198
4065
|
"notifyUser"
|
|
4199
|
-
]
|
|
4200
|
-
search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
|
|
4201
|
-
verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
|
|
4066
|
+
]
|
|
4202
4067
|
};
|
|
4203
4068
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
4204
4069
|
const tools = useCaseToolMap[useCase];
|
|
@@ -4221,30 +4086,15 @@ function formatSummary(useCase, summary) {
|
|
|
4221
4086
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
4222
4087
|
return `${label}: ${summary}`;
|
|
4223
4088
|
}
|
|
4089
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
4090
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4091
|
+
}
|
|
4224
4092
|
function parseIngestRecordCount(output) {
|
|
4225
4093
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
4226
4094
|
if (!match) return void 0;
|
|
4227
4095
|
const count = Number(match[1]);
|
|
4228
4096
|
return Number.isFinite(count) ? count : void 0;
|
|
4229
4097
|
}
|
|
4230
|
-
function ingestOutcome(executions, ingestCommand) {
|
|
4231
|
-
const newestFirst = [...executions].reverse();
|
|
4232
|
-
const withCount = newestFirst.filter(
|
|
4233
|
-
(e) => parseIngestRecordCount(e.output ?? "") != null
|
|
4234
|
-
);
|
|
4235
|
-
const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
|
|
4236
|
-
return {
|
|
4237
|
-
run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
|
|
4238
|
-
recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
|
|
4239
|
-
};
|
|
4240
|
-
}
|
|
4241
|
-
function makeToolContext(worktree, env = async () => ({})) {
|
|
4242
|
-
return createToolContext(
|
|
4243
|
-
DEFAULT_TOOL_LIMITS,
|
|
4244
|
-
worktree,
|
|
4245
|
-
createShellContext({ env, approve: storeApproval(worktree) })
|
|
4246
|
-
);
|
|
4247
|
-
}
|
|
4248
4098
|
function verificationRetryInstructions(verification) {
|
|
4249
4099
|
return [
|
|
4250
4100
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
@@ -4323,13 +4173,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4323
4173
|
const confirmed2 = normalized.confirmedEntities;
|
|
4324
4174
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4325
4175
|
let appId;
|
|
4326
|
-
let ingestAppId;
|
|
4327
4176
|
if (useCases.includes("search")) {
|
|
4328
4177
|
appId = (await requireApplication()).id;
|
|
4329
4178
|
}
|
|
4330
|
-
if (useCases.includes("ingestion")) {
|
|
4331
|
-
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4332
|
-
}
|
|
4333
4179
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
4334
4180
|
try {
|
|
4335
4181
|
process.chdir(worktree);
|
|
@@ -4362,11 +4208,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4362
4208
|
appId,
|
|
4363
4209
|
// Names only: the search-only key is provisioned after verification, so
|
|
4364
4210
|
// every value here is still a placeholder when the agent reads them.
|
|
4365
|
-
searchEnvVars: searchEnvVars(language,
|
|
4211
|
+
searchEnvVars: searchEnvVars(language, appId),
|
|
4366
4212
|
ingestDir: INGEST_DIR,
|
|
4367
4213
|
ingestionSource,
|
|
4368
4214
|
uploadFilePath,
|
|
4369
|
-
|
|
4215
|
+
uiFramework: detectUiFramework(language)
|
|
4370
4216
|
};
|
|
4371
4217
|
const summaries = [];
|
|
4372
4218
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
@@ -4389,31 +4235,41 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4389
4235
|
}
|
|
4390
4236
|
let finalSearchEnvVars = input.searchEnvVars;
|
|
4391
4237
|
let agentRuns = 0;
|
|
4392
|
-
let
|
|
4238
|
+
let ingestRuntime;
|
|
4239
|
+
let ingestEntrypoint;
|
|
4393
4240
|
let ingestScriptRan = false;
|
|
4394
4241
|
let ingestRecordCount;
|
|
4395
4242
|
let ingestDurationMs;
|
|
4243
|
+
let installFailed = false;
|
|
4396
4244
|
let ingestOutcomeMessage;
|
|
4397
|
-
const ingestKeyAppId = ingestAppId;
|
|
4398
|
-
const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
|
|
4399
|
-
[APP_ID_VAR]: ingestKeyAppId,
|
|
4400
|
-
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4401
|
-
[INDEX_NAME_VAR]: targetIndex
|
|
4402
|
-
})) : void 0;
|
|
4403
|
-
const searchTools = makeToolContext(worktree);
|
|
4404
4245
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4405
4246
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4406
4247
|
agentRuns += 1;
|
|
4407
|
-
|
|
4248
|
+
const result = await runAgent({
|
|
4408
4249
|
instructions: buildAgentInstructions(
|
|
4409
4250
|
currentUseCase,
|
|
4410
4251
|
input,
|
|
4411
4252
|
extraInstructions
|
|
4412
4253
|
),
|
|
4413
4254
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4414
|
-
outputSchema: implementationOutputSchema
|
|
4415
|
-
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4255
|
+
outputSchema: implementationOutputSchema
|
|
4416
4256
|
});
|
|
4257
|
+
ctx.notify({
|
|
4258
|
+
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4259
|
+
});
|
|
4260
|
+
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4261
|
+
useCase: currentUseCase
|
|
4262
|
+
});
|
|
4263
|
+
const install = await installWorktreeDeps(worktree);
|
|
4264
|
+
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4265
|
+
if (!install.ok) {
|
|
4266
|
+
installFailed = true;
|
|
4267
|
+
logger.warn(
|
|
4268
|
+
{ useCase: currentUseCase, output: install.output },
|
|
4269
|
+
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
return result;
|
|
4417
4273
|
}
|
|
4418
4274
|
async function runVerificationUseCase() {
|
|
4419
4275
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4421,55 +4277,110 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4421
4277
|
return runAgent({
|
|
4422
4278
|
instructions: buildAgentInstructions("verification", input),
|
|
4423
4279
|
tools: toolsForUseCase("verification"),
|
|
4424
|
-
outputSchema: verificationOutputSchema
|
|
4425
|
-
toolContext: searchTools
|
|
4280
|
+
outputSchema: verificationOutputSchema
|
|
4426
4281
|
});
|
|
4427
4282
|
}
|
|
4428
4283
|
if (useCases.includes("ingestion")) {
|
|
4429
|
-
const
|
|
4430
|
-
summaries.push(formatSummary("ingestion",
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4284
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4285
|
+
summaries.push(formatSummary("ingestion", summary));
|
|
4286
|
+
ingestRuntime = runtime;
|
|
4287
|
+
ingestEntrypoint = entrypoint;
|
|
4288
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4289
|
+
ctx.clearNotices();
|
|
4290
|
+
const runNow = await ctx.requestUserInput({
|
|
4291
|
+
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
4292
|
+
promptType: "acceptReject",
|
|
4293
|
+
options: ["Yes", "No"],
|
|
4294
|
+
messages: []
|
|
4295
|
+
}) === true;
|
|
4296
|
+
if (runNow) {
|
|
4297
|
+
const ingestApp = await requireApplication();
|
|
4298
|
+
const writeKey = await resolveWriteKey(targetIndex);
|
|
4299
|
+
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4300
|
+
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4301
|
+
runtime: ingestRuntime,
|
|
4302
|
+
entrypoint: ingestEntrypoint
|
|
4447
4303
|
});
|
|
4304
|
+
const startedAt = Date.now();
|
|
4305
|
+
const run2 = await runIngestScript(
|
|
4306
|
+
worktree,
|
|
4307
|
+
ingestRuntime,
|
|
4308
|
+
ingestEntrypoint,
|
|
4309
|
+
{
|
|
4310
|
+
[APP_ID_VAR]: ingestApp.id,
|
|
4311
|
+
[API_KEY_VAR]: writeKey
|
|
4312
|
+
}
|
|
4313
|
+
);
|
|
4314
|
+
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
4315
|
+
ingestScriptRan = run2.ran && run2.ok;
|
|
4316
|
+
if (ingestScriptRan) {
|
|
4317
|
+
ingestDurationMs = Date.now() - startedAt;
|
|
4318
|
+
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4319
|
+
if (ingestRecordCount != null) {
|
|
4320
|
+
track("AI Wizard Ingest Successful", {
|
|
4321
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4322
|
+
record_count: ingestRecordCount,
|
|
4323
|
+
duration_ms: ingestDurationMs
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
let summaryLine;
|
|
4328
|
+
let outcomeMessage;
|
|
4329
|
+
if (!run2.ran) {
|
|
4330
|
+
summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
|
|
4331
|
+
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4332
|
+
logger.warn(
|
|
4333
|
+
{
|
|
4334
|
+
runtime: ingestRuntime,
|
|
4335
|
+
entrypoint: ingestEntrypoint,
|
|
4336
|
+
reason: run2.reason
|
|
4337
|
+
},
|
|
4338
|
+
"implement: refused to auto-run ingestion script"
|
|
4339
|
+
);
|
|
4340
|
+
track("Error", {
|
|
4341
|
+
step: "Push Data",
|
|
4342
|
+
error: `ingestion script skipped: ${run2.reason}`,
|
|
4343
|
+
product_area: "AI Wizard"
|
|
4344
|
+
});
|
|
4345
|
+
} else if (run2.ok) {
|
|
4346
|
+
const status = "Ingestion run: succeeded.";
|
|
4347
|
+
summaryLine = run2.output ? `${status}
|
|
4348
|
+
${run2.output}` : status;
|
|
4349
|
+
outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4350
|
+
} else {
|
|
4351
|
+
const status = "\u26A0\uFE0F Ingestion run failed:";
|
|
4352
|
+
summaryLine = run2.output ? `${status}
|
|
4353
|
+
${run2.output}` : status;
|
|
4354
|
+
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4355
|
+
logger.warn(
|
|
4356
|
+
{
|
|
4357
|
+
runtime: ingestRuntime,
|
|
4358
|
+
entrypoint: ingestEntrypoint,
|
|
4359
|
+
output: run2.output
|
|
4360
|
+
},
|
|
4361
|
+
"implement: ingestion script run failed"
|
|
4362
|
+
);
|
|
4363
|
+
track("Error", {
|
|
4364
|
+
step: "Push Data",
|
|
4365
|
+
error: run2.output || "ingestion script exited non-zero",
|
|
4366
|
+
product_area: "AI Wizard"
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
summaries.push(summaryLine);
|
|
4370
|
+
ingestOutcomeMessage = outcomeMessage;
|
|
4448
4371
|
}
|
|
4449
|
-
} else {
|
|
4450
|
-
const rejected = executions.some((e) => !e.approved);
|
|
4451
|
-
const reason = rejected ? "you declined to run it" : "no successful run was recorded";
|
|
4452
|
-
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4453
|
-
summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
|
|
4454
|
-
logger.warn(
|
|
4455
|
-
{ ingestCommand, rejected, commandsRun: executions.length },
|
|
4456
|
-
"implement: ingestion script did not complete successfully"
|
|
4457
|
-
);
|
|
4458
|
-
track("Error", {
|
|
4459
|
-
step: "Push Data",
|
|
4460
|
-
error: `ingestion did not run: ${reason}`,
|
|
4461
|
-
product_area: "AI Wizard"
|
|
4462
|
-
});
|
|
4463
4372
|
}
|
|
4464
4373
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4465
|
-
if (
|
|
4466
|
-
commandMessages.push(
|
|
4374
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4375
|
+
commandMessages.push(
|
|
4376
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4377
|
+
);
|
|
4467
4378
|
}
|
|
4468
4379
|
await ctx.requestUserInput({
|
|
4469
4380
|
prompt: "",
|
|
4470
4381
|
promptType: "enterToContinue",
|
|
4471
4382
|
options: [],
|
|
4472
|
-
messages: [ingestOutcomeMessage, ...commandMessages]
|
|
4383
|
+
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4473
4384
|
});
|
|
4474
4385
|
}
|
|
4475
4386
|
if (useCases.includes("search")) {
|
|
@@ -4518,14 +4429,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4518
4429
|
let searchKeyError;
|
|
4519
4430
|
if (appId) {
|
|
4520
4431
|
try {
|
|
4521
|
-
const
|
|
4432
|
+
const resolved = await resolveSearchOnlyKey(
|
|
4522
4433
|
targetIndex,
|
|
4523
4434
|
appId,
|
|
4524
4435
|
envSearchKey
|
|
4525
4436
|
);
|
|
4526
|
-
searchKey =
|
|
4437
|
+
searchKey = resolved.key;
|
|
4527
4438
|
summaries.push(
|
|
4528
|
-
|
|
4439
|
+
resolved.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4529
4440
|
);
|
|
4530
4441
|
} catch (err) {
|
|
4531
4442
|
searchKeyError = err.message;
|
|
@@ -4535,12 +4446,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4535
4446
|
);
|
|
4536
4447
|
}
|
|
4537
4448
|
}
|
|
4538
|
-
finalSearchEnvVars = searchEnvVars(
|
|
4539
|
-
language,
|
|
4540
|
-
targetIndex,
|
|
4541
|
-
appId,
|
|
4542
|
-
searchKey
|
|
4543
|
-
);
|
|
4449
|
+
finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
|
|
4544
4450
|
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4545
4451
|
(v) => !v.value.startsWith("<")
|
|
4546
4452
|
);
|
|
@@ -4586,13 +4492,22 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4586
4492
|
"implement: agent reported success but no files changed in the worktree"
|
|
4587
4493
|
);
|
|
4588
4494
|
}
|
|
4495
|
+
if (installFailed) {
|
|
4496
|
+
summaries.push(
|
|
4497
|
+
'\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".'
|
|
4498
|
+
);
|
|
4499
|
+
}
|
|
4589
4500
|
return {
|
|
4590
4501
|
ingestionSource,
|
|
4591
4502
|
filesChanged,
|
|
4592
4503
|
summary: summaries.join("\n\n"),
|
|
4593
4504
|
worktreePath: worktree,
|
|
4594
|
-
...useCases.includes("ingestion") &&
|
|
4595
|
-
ingestCommand
|
|
4505
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
4506
|
+
ingestCommand: buildIngestCommand(
|
|
4507
|
+
worktree,
|
|
4508
|
+
ingestRuntime,
|
|
4509
|
+
ingestEntrypoint
|
|
4510
|
+
),
|
|
4596
4511
|
ingestScriptRan,
|
|
4597
4512
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4598
4513
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
@@ -4640,8 +4555,8 @@ var defaultWorkflow = {
|
|
|
4640
4555
|
defineStep({
|
|
4641
4556
|
id: "select-index",
|
|
4642
4557
|
title: "Set up index",
|
|
4643
|
-
outputSchema:
|
|
4644
|
-
selection:
|
|
4558
|
+
outputSchema: z27.object({
|
|
4559
|
+
selection: z27.string()
|
|
4645
4560
|
}),
|
|
4646
4561
|
run: (ctx) => selectIndexStep(ctx)
|
|
4647
4562
|
}),
|
|
@@ -4883,10 +4798,7 @@ Options:
|
|
|
4883
4798
|
--no-telemetry Send no telemetry or analytics for this run.
|
|
4884
4799
|
--reset-on-run Wipe this project's wizard state (run state, AI consent,
|
|
4885
4800
|
worktrees) before starting, so the run behaves like a
|
|
4886
|
-
first-ever run.
|
|
4887
|
-
stored in your keychain, for this project and any other, so
|
|
4888
|
-
later runs create new ones. Your Algolia login is not
|
|
4889
|
-
touched.
|
|
4801
|
+
first-ever run. Algolia credentials are not touched.
|
|
4890
4802
|
-h, --help Print this message.`;
|
|
4891
4803
|
function parseCliArgs(argv) {
|
|
4892
4804
|
const positionals = [];
|
|
@@ -4923,11 +4835,10 @@ function parseCliArgs(argv) {
|
|
|
4923
4835
|
|
|
4924
4836
|
// src/lib/resetState.ts
|
|
4925
4837
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4926
|
-
import { join as
|
|
4838
|
+
import { join as join11 } from "node:path";
|
|
4927
4839
|
var KEEP = ["wizard.log"];
|
|
4928
4840
|
async function resetProjectState() {
|
|
4929
4841
|
const dir = stateDir();
|
|
4930
|
-
await forgetResolvedKeys();
|
|
4931
4842
|
let entries;
|
|
4932
4843
|
try {
|
|
4933
4844
|
entries = await readdir4(dir);
|
|
@@ -4936,17 +4847,14 @@ async function resetProjectState() {
|
|
|
4936
4847
|
}
|
|
4937
4848
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4938
4849
|
await Promise.all(
|
|
4939
|
-
targets.map(
|
|
4940
|
-
(name) => rm2(join10(dir, name), { recursive: true, force: true })
|
|
4941
|
-
)
|
|
4850
|
+
targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
|
|
4942
4851
|
);
|
|
4943
4852
|
return { dir, removed: targets };
|
|
4944
4853
|
}
|
|
4945
4854
|
|
|
4946
4855
|
// src/main.tsx
|
|
4947
|
-
import { jsx as
|
|
4856
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
4948
4857
|
async function startup() {
|
|
4949
|
-
setProjectRoot(process.cwd());
|
|
4950
4858
|
let args;
|
|
4951
4859
|
try {
|
|
4952
4860
|
args = parseCliArgs(process.argv.slice(2));
|
|
@@ -4994,7 +4902,7 @@ ${formatStepList(workflow)}`);
|
|
|
4994
4902
|
}
|
|
4995
4903
|
async function run(workflow) {
|
|
4996
4904
|
const store = useWizard.getState();
|
|
4997
|
-
const instance = render(/* @__PURE__ */
|
|
4905
|
+
const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
4998
4906
|
await store.waitForStart();
|
|
4999
4907
|
let user = await getUser();
|
|
5000
4908
|
if (!user) {
|