@algolia/wizard 0.9.0-rc.85.78 → 0.9.0-rc.87.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as
|
|
7
|
+
import { Box as Box16, Text as Text16, useApp, useInput as useInput7, useWindowSize as useWindowSize8 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -13,10 +13,60 @@ import { nanoid } from "nanoid";
|
|
|
13
13
|
// src/lib/algoliaCli.ts
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
// src/lib/logger.ts
|
|
18
|
+
import pino from "pino";
|
|
19
|
+
import { join as join2, dirname } from "node:path";
|
|
20
|
+
import { devNull } from "node:os";
|
|
21
|
+
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
22
|
+
|
|
23
|
+
// src/core/constants.ts
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { join, resolve } from "node:path";
|
|
26
|
+
function rootDir() {
|
|
27
|
+
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
28
|
+
}
|
|
29
|
+
var pinnedRoot;
|
|
30
|
+
function setProjectRoot(cwd) {
|
|
31
|
+
pinnedRoot = resolve(cwd);
|
|
32
|
+
}
|
|
33
|
+
function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
|
|
34
|
+
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
35
|
+
}
|
|
36
|
+
function stateDir(cwd = pinnedRoot ?? process.cwd()) {
|
|
37
|
+
return join(rootDir(), projectSlug(cwd));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/lib/logger.ts
|
|
41
|
+
var STDERR_FD = 2;
|
|
42
|
+
function resolveDest() {
|
|
43
|
+
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
46
|
+
closeSync(openSync(target, "a"));
|
|
47
|
+
return target;
|
|
48
|
+
} catch {
|
|
49
|
+
return STDERR_FD;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function logDestination() {
|
|
53
|
+
return pino.destination({ dest: resolveDest(), sync: false });
|
|
54
|
+
}
|
|
55
|
+
var logger = pino(
|
|
56
|
+
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
57
|
+
logDestination()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// src/lib/algoliaCli.ts
|
|
16
61
|
function npxArgs(args) {
|
|
17
62
|
return ["--yes", "@algolia/cli@latest", ...args];
|
|
18
63
|
}
|
|
19
64
|
var shell = process.platform === "win32";
|
|
65
|
+
function childEnv(withoutAdminKey) {
|
|
66
|
+
if (!withoutAdminKey) return void 0;
|
|
67
|
+
const { ALGOLIA_API_KEY: _adminKey, ...rest } = process.env;
|
|
68
|
+
return rest;
|
|
69
|
+
}
|
|
20
70
|
function lineSplitter(emit) {
|
|
21
71
|
let buffer = "";
|
|
22
72
|
return {
|
|
@@ -40,11 +90,14 @@ var stderrSink = (stream, line) => {
|
|
|
40
90
|
if (stream === "stdout") return;
|
|
41
91
|
wizardSink(stream, line);
|
|
42
92
|
};
|
|
43
|
-
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
93
|
+
function runAlgoliaCli(args, { onOutput, withoutAdminKey } = {}) {
|
|
44
94
|
const store = useWizard.getState();
|
|
45
95
|
const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
|
|
46
96
|
return new Promise((resolve4, reject) => {
|
|
47
|
-
const child = spawn("npx", npxArgs(args), {
|
|
97
|
+
const child = spawn("npx", npxArgs(args), {
|
|
98
|
+
shell,
|
|
99
|
+
env: childEnv(withoutAdminKey)
|
|
100
|
+
});
|
|
48
101
|
let stdout = "";
|
|
49
102
|
let stderr = "";
|
|
50
103
|
const splitters = {
|
|
@@ -89,6 +142,7 @@ function runAlgoliaCli(args, { onOutput } = {}) {
|
|
|
89
142
|
},
|
|
90
143
|
(err) => {
|
|
91
144
|
useWizard.getState().logEnd(logId, "error");
|
|
145
|
+
logger.warn({ err, args }, "Algolia CLI command failed");
|
|
92
146
|
throw err;
|
|
93
147
|
}
|
|
94
148
|
);
|
|
@@ -146,45 +200,6 @@ function refreshAuthToken() {
|
|
|
146
200
|
return inFlightRefresh;
|
|
147
201
|
}
|
|
148
202
|
|
|
149
|
-
// src/lib/logger.ts
|
|
150
|
-
import pino from "pino";
|
|
151
|
-
import { join as join2, dirname } from "node:path";
|
|
152
|
-
import { devNull } from "node:os";
|
|
153
|
-
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
154
|
-
|
|
155
|
-
// src/core/constants.ts
|
|
156
|
-
import { homedir } from "node:os";
|
|
157
|
-
import { join, resolve } from "node:path";
|
|
158
|
-
function rootDir() {
|
|
159
|
-
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
160
|
-
}
|
|
161
|
-
function projectSlug(cwd = process.cwd()) {
|
|
162
|
-
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
163
|
-
}
|
|
164
|
-
function stateDir(cwd = process.cwd()) {
|
|
165
|
-
return join(rootDir(), projectSlug(cwd));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// src/lib/logger.ts
|
|
169
|
-
var STDERR_FD = 2;
|
|
170
|
-
function resolveDest() {
|
|
171
|
-
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
172
|
-
try {
|
|
173
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
174
|
-
closeSync(openSync(target, "a"));
|
|
175
|
-
return target;
|
|
176
|
-
} catch {
|
|
177
|
-
return STDERR_FD;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
function logDestination() {
|
|
181
|
-
return pino.destination({ dest: resolveDest(), sync: false });
|
|
182
|
-
}
|
|
183
|
-
var logger = pino(
|
|
184
|
-
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
185
|
-
logDestination()
|
|
186
|
-
);
|
|
187
|
-
|
|
188
203
|
// src/lib/proxyFetch.ts
|
|
189
204
|
var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
|
|
190
205
|
var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
|
|
@@ -614,10 +629,13 @@ function Notices() {
|
|
|
614
629
|
}
|
|
615
630
|
|
|
616
631
|
// src/ui/PromptInput.tsx
|
|
617
|
-
import { Box as
|
|
632
|
+
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
618
633
|
import TextInput from "ink-text-input";
|
|
619
634
|
import { useState as useState5 } from "react";
|
|
620
635
|
|
|
636
|
+
// src/ui/CommandApproval.tsx
|
|
637
|
+
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
638
|
+
|
|
621
639
|
// src/ui/NextAction.tsx
|
|
622
640
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
623
641
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -641,14 +659,69 @@ function NextAction({
|
|
|
641
659
|
] });
|
|
642
660
|
}
|
|
643
661
|
|
|
662
|
+
// src/ui/CommandApproval.tsx
|
|
663
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
664
|
+
function CommandApproval({
|
|
665
|
+
command,
|
|
666
|
+
onDecide
|
|
667
|
+
}) {
|
|
668
|
+
useInput((input, key) => {
|
|
669
|
+
if (key.return) onDecide("approve");
|
|
670
|
+
else if (key.escape) onDecide("reject");
|
|
671
|
+
else if (input.toLowerCase() === "a") onDecide("always");
|
|
672
|
+
});
|
|
673
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
|
|
674
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
|
|
675
|
+
/* @__PURE__ */ jsxs4(
|
|
676
|
+
Box5,
|
|
677
|
+
{
|
|
678
|
+
flexDirection: "column",
|
|
679
|
+
paddingLeft: 2,
|
|
680
|
+
borderStyle: "single",
|
|
681
|
+
borderColor: COLORS.success,
|
|
682
|
+
borderTop: false,
|
|
683
|
+
borderBottom: false,
|
|
684
|
+
borderRight: false,
|
|
685
|
+
gap: 1,
|
|
686
|
+
children: [
|
|
687
|
+
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
688
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
|
|
689
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
|
|
690
|
+
] }),
|
|
691
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
692
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
|
|
693
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
|
|
694
|
+
] }),
|
|
695
|
+
command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
696
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
|
|
697
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
|
|
698
|
+
] })
|
|
699
|
+
]
|
|
700
|
+
}
|
|
701
|
+
),
|
|
702
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
703
|
+
/* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
|
|
704
|
+
/* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" }),
|
|
705
|
+
/* @__PURE__ */ jsx4(
|
|
706
|
+
NextAction,
|
|
707
|
+
{
|
|
708
|
+
action: "approve, and don't ask again for this command",
|
|
709
|
+
keyHint: "a",
|
|
710
|
+
hierarchy: "secondary"
|
|
711
|
+
}
|
|
712
|
+
)
|
|
713
|
+
] })
|
|
714
|
+
] });
|
|
715
|
+
}
|
|
716
|
+
|
|
644
717
|
// src/ui/SelectPrompt.tsx
|
|
645
|
-
import { Box as
|
|
718
|
+
import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
|
|
646
719
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
647
720
|
|
|
648
721
|
// src/ui/ScrollView.tsx
|
|
649
|
-
import { Box as
|
|
722
|
+
import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
650
723
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
651
|
-
import { jsxs as
|
|
724
|
+
import { jsxs as jsxs5 } from "react/jsx-runtime";
|
|
652
725
|
var INDICATOR_ROWS = 2;
|
|
653
726
|
function fittedWidth(node, columns) {
|
|
654
727
|
let left = 0;
|
|
@@ -718,14 +791,14 @@ function useScrollWindow({
|
|
|
718
791
|
};
|
|
719
792
|
}
|
|
720
793
|
function ScrollView({ scroll, children }) {
|
|
721
|
-
return /* @__PURE__ */
|
|
722
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
794
|
+
return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
795
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
723
796
|
"\u2191 ",
|
|
724
797
|
scroll.hiddenAbove,
|
|
725
798
|
" more"
|
|
726
799
|
] }),
|
|
727
800
|
children,
|
|
728
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
801
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
729
802
|
"\u2193 ",
|
|
730
803
|
scroll.hiddenBelow,
|
|
731
804
|
" more"
|
|
@@ -734,7 +807,7 @@ function ScrollView({ scroll, children }) {
|
|
|
734
807
|
}
|
|
735
808
|
|
|
736
809
|
// src/ui/SelectPrompt.tsx
|
|
737
|
-
import { jsx as
|
|
810
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
738
811
|
var CANCEL = "cancel";
|
|
739
812
|
var ARROW_WIDTH = 4;
|
|
740
813
|
var COLUMN_GAP = 2;
|
|
@@ -796,7 +869,7 @@ function SelectPrompt({
|
|
|
796
869
|
revealIndex(index);
|
|
797
870
|
}, [index, revealIndex]);
|
|
798
871
|
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
799
|
-
|
|
872
|
+
useInput2((input, key) => {
|
|
800
873
|
if (rows.length === 0) return;
|
|
801
874
|
if (key.upArrow || input === "k") {
|
|
802
875
|
setIndex((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -819,56 +892,56 @@ function SelectPrompt({
|
|
|
819
892
|
}
|
|
820
893
|
}
|
|
821
894
|
});
|
|
822
|
-
return /* @__PURE__ */
|
|
823
|
-
/* @__PURE__ */
|
|
824
|
-
error && /* @__PURE__ */
|
|
825
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
826
|
-
table && /* @__PURE__ */
|
|
827
|
-
/* @__PURE__ */
|
|
828
|
-
question && /* @__PURE__ */
|
|
829
|
-
helpText && /* @__PURE__ */
|
|
895
|
+
return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
|
|
896
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
897
|
+
error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
|
|
898
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
899
|
+
table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
|
|
900
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
901
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
|
|
902
|
+
helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
|
|
830
903
|
] })
|
|
831
904
|
] }),
|
|
832
|
-
/* @__PURE__ */
|
|
905
|
+
/* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
833
906
|
const i = scroll.offset + visibleIndex;
|
|
834
907
|
const highlighted = i === index;
|
|
835
908
|
const isCancel = i === cancelIndex;
|
|
836
909
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
837
910
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
838
911
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
839
|
-
const label = /* @__PURE__ */
|
|
912
|
+
const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
|
|
840
913
|
highlighted ? "\u276F " : " ",
|
|
841
914
|
bullet,
|
|
842
915
|
option
|
|
843
916
|
] });
|
|
844
917
|
const isText = sec?.kind === "text";
|
|
845
|
-
return /* @__PURE__ */
|
|
846
|
-
|
|
918
|
+
return /* @__PURE__ */ jsxs6(
|
|
919
|
+
Box7,
|
|
847
920
|
{
|
|
848
921
|
width: isText ? "100%" : barWidth,
|
|
849
922
|
paddingX: 1,
|
|
850
923
|
paddingY: 1,
|
|
851
924
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
852
925
|
children: [
|
|
853
|
-
/* @__PURE__ */
|
|
854
|
-
isText && textWidth > 0 && /* @__PURE__ */
|
|
855
|
-
|
|
926
|
+
/* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
927
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
|
|
928
|
+
Text7,
|
|
856
929
|
{
|
|
857
930
|
wrap: "truncate",
|
|
858
931
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
859
932
|
children: sec.value
|
|
860
933
|
}
|
|
861
934
|
) }),
|
|
862
|
-
sec?.kind === "badge" && /* @__PURE__ */
|
|
935
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
863
936
|
]
|
|
864
937
|
},
|
|
865
938
|
`row-${i}`
|
|
866
939
|
);
|
|
867
940
|
}) }),
|
|
868
|
-
/* @__PURE__ */
|
|
941
|
+
/* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
869
942
|
i > 0 ? " " : "",
|
|
870
|
-
/* @__PURE__ */
|
|
871
|
-
/* @__PURE__ */
|
|
943
|
+
/* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
|
|
944
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
|
|
872
945
|
" ",
|
|
873
946
|
label
|
|
874
947
|
] })
|
|
@@ -877,23 +950,23 @@ function SelectPrompt({
|
|
|
877
950
|
}
|
|
878
951
|
|
|
879
952
|
// src/ui/PromptInput.tsx
|
|
880
|
-
import { jsx as
|
|
953
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
881
954
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
882
955
|
function EnterToContinuePrompt({
|
|
883
956
|
question,
|
|
884
957
|
messages,
|
|
885
958
|
onDecide
|
|
886
959
|
}) {
|
|
887
|
-
|
|
960
|
+
useInput3((_input, key) => {
|
|
888
961
|
if (key.return) onDecide(true);
|
|
889
962
|
else if (key.escape) onDecide(false);
|
|
890
963
|
});
|
|
891
|
-
return /* @__PURE__ */
|
|
892
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
893
|
-
question && /* @__PURE__ */
|
|
894
|
-
/* @__PURE__ */
|
|
895
|
-
/* @__PURE__ */
|
|
896
|
-
/* @__PURE__ */
|
|
964
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
|
|
965
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
966
|
+
question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
|
|
967
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
968
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
|
|
969
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
897
970
|
] })
|
|
898
971
|
] });
|
|
899
972
|
}
|
|
@@ -901,11 +974,11 @@ function PromptInput() {
|
|
|
901
974
|
const { phase, inputReq, submitInput } = useWizard();
|
|
902
975
|
const [draft, setDraft] = useState5("");
|
|
903
976
|
if (phase === "done" || phase === "error") {
|
|
904
|
-
return /* @__PURE__ */
|
|
977
|
+
return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
905
978
|
}
|
|
906
979
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
907
980
|
if (inputReq.promptType === "multipleChoice") {
|
|
908
|
-
return /* @__PURE__ */
|
|
981
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
909
982
|
SelectPrompt,
|
|
910
983
|
{
|
|
911
984
|
question: inputReq.prompt,
|
|
@@ -922,7 +995,7 @@ function PromptInput() {
|
|
|
922
995
|
) });
|
|
923
996
|
}
|
|
924
997
|
if (inputReq.promptType === "multiSelect") {
|
|
925
|
-
return /* @__PURE__ */
|
|
998
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
926
999
|
SelectPrompt,
|
|
927
1000
|
{
|
|
928
1001
|
multi: true,
|
|
@@ -937,7 +1010,7 @@ function PromptInput() {
|
|
|
937
1010
|
) });
|
|
938
1011
|
}
|
|
939
1012
|
if (inputReq.promptType === "notice") {
|
|
940
|
-
return /* @__PURE__ */
|
|
1013
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
941
1014
|
SelectPrompt,
|
|
942
1015
|
{
|
|
943
1016
|
question: inputReq.prompt,
|
|
@@ -948,7 +1021,7 @@ function PromptInput() {
|
|
|
948
1021
|
) });
|
|
949
1022
|
}
|
|
950
1023
|
if (inputReq.promptType === "enterToContinue") {
|
|
951
|
-
return /* @__PURE__ */
|
|
1024
|
+
return /* @__PURE__ */ jsx6(
|
|
952
1025
|
EnterToContinuePrompt,
|
|
953
1026
|
{
|
|
954
1027
|
question: inputReq.prompt,
|
|
@@ -957,9 +1030,12 @@ function PromptInput() {
|
|
|
957
1030
|
}
|
|
958
1031
|
);
|
|
959
1032
|
}
|
|
1033
|
+
if (inputReq.promptType === "commandApproval" && inputReq.command) {
|
|
1034
|
+
return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
|
|
1035
|
+
}
|
|
960
1036
|
if (inputReq.promptType === "acceptReject") {
|
|
961
1037
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
962
|
-
return /* @__PURE__ */
|
|
1038
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
963
1039
|
SelectPrompt,
|
|
964
1040
|
{
|
|
965
1041
|
question: inputReq.prompt,
|
|
@@ -970,15 +1046,15 @@ function PromptInput() {
|
|
|
970
1046
|
}
|
|
971
1047
|
) });
|
|
972
1048
|
}
|
|
973
|
-
return /* @__PURE__ */
|
|
974
|
-
inputReq.error && /* @__PURE__ */
|
|
975
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
/* @__PURE__ */
|
|
1049
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1050
|
+
inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
|
|
1051
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
1052
|
+
/* @__PURE__ */ jsxs7(Box8, { children: [
|
|
1053
|
+
/* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
|
|
978
1054
|
inputReq.prompt,
|
|
979
1055
|
" "
|
|
980
1056
|
] }),
|
|
981
|
-
/* @__PURE__ */
|
|
1057
|
+
/* @__PURE__ */ jsx6(
|
|
982
1058
|
TextInput,
|
|
983
1059
|
{
|
|
984
1060
|
value: draft,
|
|
@@ -996,7 +1072,7 @@ function PromptInput() {
|
|
|
996
1072
|
// src/ui/Welcome.tsx
|
|
997
1073
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
998
1074
|
import { fileURLToPath } from "node:url";
|
|
999
|
-
import { Box as
|
|
1075
|
+
import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
1000
1076
|
|
|
1001
1077
|
// src/ui/copy/welcome.ts
|
|
1002
1078
|
var sidebarItems = [
|
|
@@ -1009,12 +1085,12 @@ var sidebarItems = [
|
|
|
1009
1085
|
description: "push 100 records to Algolia in seconds"
|
|
1010
1086
|
},
|
|
1011
1087
|
{
|
|
1012
|
-
title: "detect your
|
|
1013
|
-
description: "
|
|
1088
|
+
title: "detect your stack",
|
|
1089
|
+
description: "whatever language and framework you already use"
|
|
1014
1090
|
},
|
|
1015
1091
|
{
|
|
1016
1092
|
title: "scaffold a search UI",
|
|
1017
|
-
description: "a
|
|
1093
|
+
description: "a search box and results, wired into your app"
|
|
1018
1094
|
},
|
|
1019
1095
|
{
|
|
1020
1096
|
title: "ship it",
|
|
@@ -1024,20 +1100,20 @@ var sidebarItems = [
|
|
|
1024
1100
|
|
|
1025
1101
|
// src/ui/Welcome.tsx
|
|
1026
1102
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
1027
|
-
import { jsx as
|
|
1103
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1028
1104
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
1029
1105
|
function SidebarItem({
|
|
1030
1106
|
title,
|
|
1031
1107
|
description
|
|
1032
1108
|
}) {
|
|
1033
|
-
return /* @__PURE__ */
|
|
1034
|
-
/* @__PURE__ */
|
|
1035
|
-
/* @__PURE__ */
|
|
1036
|
-
/* @__PURE__ */
|
|
1109
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
1110
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
1111
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
|
|
1112
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
|
|
1037
1113
|
] }),
|
|
1038
|
-
/* @__PURE__ */
|
|
1039
|
-
/* @__PURE__ */
|
|
1040
|
-
/* @__PURE__ */
|
|
1114
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
|
|
1115
|
+
/* @__PURE__ */ jsx7(Spacer, {}),
|
|
1116
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
|
|
1041
1117
|
] })
|
|
1042
1118
|
] });
|
|
1043
1119
|
}
|
|
@@ -1045,7 +1121,7 @@ function Welcome() {
|
|
|
1045
1121
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1046
1122
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
1047
1123
|
const { rows } = useWindowSize6();
|
|
1048
|
-
|
|
1124
|
+
useInput4((input, key) => {
|
|
1049
1125
|
if (key.return) confirmStart();
|
|
1050
1126
|
else if (input === "i") openLearnMore();
|
|
1051
1127
|
});
|
|
@@ -1063,16 +1139,16 @@ function Welcome() {
|
|
|
1063
1139
|
if (rows < 30) {
|
|
1064
1140
|
layout = scales["small"];
|
|
1065
1141
|
}
|
|
1066
|
-
return /* @__PURE__ */
|
|
1067
|
-
/* @__PURE__ */
|
|
1068
|
-
|
|
1142
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
1143
|
+
/* @__PURE__ */ jsx7(
|
|
1144
|
+
Box9,
|
|
1069
1145
|
{
|
|
1070
1146
|
paddingY: layout.main.padding.y,
|
|
1071
1147
|
paddingX: layout.main.padding.x,
|
|
1072
1148
|
flexDirection: "column",
|
|
1073
1149
|
justifyContent: "center",
|
|
1074
|
-
children: /* @__PURE__ */
|
|
1075
|
-
/* @__PURE__ */
|
|
1150
|
+
children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
|
|
1151
|
+
/* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
|
|
1076
1152
|
Image,
|
|
1077
1153
|
{
|
|
1078
1154
|
src: IMAGE_PATH,
|
|
@@ -1083,16 +1159,16 @@ function Welcome() {
|
|
|
1083
1159
|
protocol: "halfBlock"
|
|
1084
1160
|
}
|
|
1085
1161
|
) }),
|
|
1086
|
-
/* @__PURE__ */
|
|
1087
|
-
/* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
1089
|
-
/* @__PURE__ */
|
|
1162
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1163
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
|
|
1164
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
1165
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
1090
1166
|
] })
|
|
1091
1167
|
] })
|
|
1092
1168
|
}
|
|
1093
1169
|
),
|
|
1094
|
-
/* @__PURE__ */
|
|
1095
|
-
|
|
1170
|
+
/* @__PURE__ */ jsxs8(
|
|
1171
|
+
Box9,
|
|
1096
1172
|
{
|
|
1097
1173
|
backgroundColor: COLORS.bg.sidebar,
|
|
1098
1174
|
width: 40,
|
|
@@ -1102,8 +1178,8 @@ function Welcome() {
|
|
|
1102
1178
|
flexDirection: "column",
|
|
1103
1179
|
justifyContent: "center",
|
|
1104
1180
|
children: [
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */
|
|
1181
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
1182
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
1107
1183
|
]
|
|
1108
1184
|
}
|
|
1109
1185
|
)
|
|
@@ -1112,7 +1188,7 @@ function Welcome() {
|
|
|
1112
1188
|
|
|
1113
1189
|
// src/ui/LearnMore.tsx
|
|
1114
1190
|
import { Fragment as Fragment2 } from "react";
|
|
1115
|
-
import { Box as
|
|
1191
|
+
import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
|
|
1116
1192
|
|
|
1117
1193
|
// src/ui/copy/learn-more.ts
|
|
1118
1194
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1120,12 +1196,17 @@ var accessItems = [
|
|
|
1120
1196
|
{
|
|
1121
1197
|
tag: "READ",
|
|
1122
1198
|
title: "Project files",
|
|
1123
|
-
description: "reads
|
|
1199
|
+
description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1124
1200
|
},
|
|
1125
1201
|
{
|
|
1126
1202
|
tag: "WRITE",
|
|
1127
1203
|
title: "Code changes",
|
|
1128
|
-
description: "creates & edits files (search UI, config)
|
|
1204
|
+
description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
tag: "EXEC",
|
|
1208
|
+
title: "Setup commands",
|
|
1209
|
+
description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
|
|
1129
1210
|
},
|
|
1130
1211
|
{
|
|
1131
1212
|
tag: "NET",
|
|
@@ -1135,13 +1216,13 @@ var accessItems = [
|
|
|
1135
1216
|
{
|
|
1136
1217
|
tag: "KEY",
|
|
1137
1218
|
title: "Credentials",
|
|
1138
|
-
description: "
|
|
1219
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
|
|
1139
1220
|
}
|
|
1140
1221
|
];
|
|
1141
1222
|
var neverItems = [
|
|
1142
1223
|
"Send your source code to a model or third party",
|
|
1143
1224
|
"Commit or push to git",
|
|
1144
|
-
"
|
|
1225
|
+
"Run a command you haven't approved"
|
|
1145
1226
|
];
|
|
1146
1227
|
var policyLinks = [
|
|
1147
1228
|
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
@@ -1149,10 +1230,11 @@ var policyLinks = [
|
|
|
1149
1230
|
];
|
|
1150
1231
|
|
|
1151
1232
|
// src/ui/LearnMore.tsx
|
|
1152
|
-
import { jsx as
|
|
1233
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1153
1234
|
var TAG_COLORS = {
|
|
1154
1235
|
READ: COLORS.success,
|
|
1155
1236
|
WRITE: COLORS.badge,
|
|
1237
|
+
EXEC: COLORS.danger,
|
|
1156
1238
|
NET: COLORS.accent,
|
|
1157
1239
|
KEY: COLORS.muted
|
|
1158
1240
|
};
|
|
@@ -1165,12 +1247,12 @@ function NeverLine({
|
|
|
1165
1247
|
}) {
|
|
1166
1248
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1167
1249
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1168
|
-
return /* @__PURE__ */
|
|
1169
|
-
/* @__PURE__ */
|
|
1250
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1251
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
|
|
1170
1252
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1171
|
-
segments.map((s, i) => /* @__PURE__ */
|
|
1253
|
+
segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1172
1254
|
" ".repeat(rightPad),
|
|
1173
|
-
/* @__PURE__ */
|
|
1255
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
|
|
1174
1256
|
] });
|
|
1175
1257
|
}
|
|
1176
1258
|
function LearnMore() {
|
|
@@ -1178,12 +1260,12 @@ function LearnMore() {
|
|
|
1178
1260
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1179
1261
|
const { columns } = useWindowSize7();
|
|
1180
1262
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1181
|
-
|
|
1263
|
+
useInput5((_input, key) => {
|
|
1182
1264
|
if (key.escape) backToHome();
|
|
1183
1265
|
else if (key.return) confirmStart();
|
|
1184
1266
|
});
|
|
1185
|
-
return /* @__PURE__ */
|
|
1186
|
-
|
|
1267
|
+
return /* @__PURE__ */ jsxs9(
|
|
1268
|
+
Box10,
|
|
1187
1269
|
{
|
|
1188
1270
|
flexDirection: "column",
|
|
1189
1271
|
paddingX: PADDING_X,
|
|
@@ -1191,31 +1273,31 @@ function LearnMore() {
|
|
|
1191
1273
|
width: "100%",
|
|
1192
1274
|
gap: 1,
|
|
1193
1275
|
children: [
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
/* @__PURE__ */
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
/* @__PURE__ */
|
|
1198
|
-
/* @__PURE__ */
|
|
1199
|
-
/* @__PURE__ */
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
1276
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1277
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
|
|
1278
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
1279
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1280
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1281
|
+
/* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1282
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1283
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1284
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1203
1285
|
] }) })
|
|
1204
1286
|
] })
|
|
1205
1287
|
] }, item.tag)) }),
|
|
1206
|
-
/* @__PURE__ */
|
|
1207
|
-
/* @__PURE__ */
|
|
1208
|
-
/* @__PURE__ */
|
|
1209
|
-
/* @__PURE__ */
|
|
1288
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
1289
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1290
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1291
|
+
/* @__PURE__ */ jsx8(
|
|
1210
1292
|
NeverLine,
|
|
1211
1293
|
{
|
|
1212
1294
|
width: dividerWidth,
|
|
1213
1295
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1214
1296
|
}
|
|
1215
1297
|
),
|
|
1216
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1217
|
-
/* @__PURE__ */
|
|
1218
|
-
/* @__PURE__ */
|
|
1298
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
1299
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1300
|
+
/* @__PURE__ */ jsx8(
|
|
1219
1301
|
NeverLine,
|
|
1220
1302
|
{
|
|
1221
1303
|
width: dividerWidth,
|
|
@@ -1227,24 +1309,24 @@ function LearnMore() {
|
|
|
1227
1309
|
}
|
|
1228
1310
|
)
|
|
1229
1311
|
] }, item)),
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1312
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1313
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1232
1314
|
] }),
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1315
|
+
/* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1316
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1317
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
|
|
1236
1318
|
] }, link.label)) }),
|
|
1237
|
-
/* @__PURE__ */
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1319
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1320
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1321
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
|
|
1322
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
|
|
1323
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
|
|
1242
1324
|
] }),
|
|
1243
|
-
/* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
1245
|
-
/* @__PURE__ */
|
|
1246
|
-
/* @__PURE__ */
|
|
1247
|
-
/* @__PURE__ */
|
|
1325
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1326
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
|
|
1327
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
|
|
1328
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
|
|
1329
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1248
1330
|
] })
|
|
1249
1331
|
] })
|
|
1250
1332
|
]
|
|
@@ -1253,10 +1335,10 @@ function LearnMore() {
|
|
|
1253
1335
|
}
|
|
1254
1336
|
|
|
1255
1337
|
// src/ui/Sidebar.tsx
|
|
1256
|
-
import { Box as
|
|
1338
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1257
1339
|
|
|
1258
1340
|
// src/ui/Steps.tsx
|
|
1259
|
-
import { Box as
|
|
1341
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1260
1342
|
import Spinner from "ink-spinner";
|
|
1261
1343
|
|
|
1262
1344
|
// src/core/persistence.ts
|
|
@@ -1285,12 +1367,12 @@ async function clearWorkflowState(workflowId) {
|
|
|
1285
1367
|
}
|
|
1286
1368
|
|
|
1287
1369
|
// src/ui/Steps.tsx
|
|
1288
|
-
import { jsx as
|
|
1370
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1289
1371
|
function Steps() {
|
|
1290
1372
|
const { steps } = useWizard();
|
|
1291
1373
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1292
|
-
return /* @__PURE__ */
|
|
1293
|
-
s.status === "running" ? /* @__PURE__ */
|
|
1374
|
+
return /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status[s.status], children: [
|
|
1375
|
+
s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1294
1376
|
" ",
|
|
1295
1377
|
s.title
|
|
1296
1378
|
] }) }, s.id)) });
|
|
@@ -1299,27 +1381,27 @@ function CurrentStep() {
|
|
|
1299
1381
|
const { steps } = useWizard();
|
|
1300
1382
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1301
1383
|
if (!currentStep) return null;
|
|
1302
|
-
return /* @__PURE__ */
|
|
1303
|
-
/* @__PURE__ */
|
|
1384
|
+
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
|
|
1385
|
+
/* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
|
|
1304
1386
|
" ",
|
|
1305
1387
|
` ${currentStep.title}`
|
|
1306
1388
|
] });
|
|
1307
1389
|
}
|
|
1308
1390
|
|
|
1309
1391
|
// src/ui/Progress.tsx
|
|
1310
|
-
import { Box as
|
|
1311
|
-
import { jsx as
|
|
1392
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1393
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1312
1394
|
function Progress() {
|
|
1313
1395
|
const { steps, currentStepIndex } = useWizard();
|
|
1314
1396
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1315
1397
|
if (visibleSteps.length === 0) return null;
|
|
1316
1398
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1317
1399
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1318
|
-
return /* @__PURE__ */
|
|
1319
|
-
/* @__PURE__ */
|
|
1320
|
-
/* @__PURE__ */
|
|
1321
|
-
/* @__PURE__ */
|
|
1322
|
-
/* @__PURE__ */
|
|
1400
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1401
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
|
|
1402
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
|
|
1403
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
|
|
1404
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
|
|
1323
1405
|
] });
|
|
1324
1406
|
}
|
|
1325
1407
|
|
|
@@ -1330,10 +1412,10 @@ var sidebarCommands = [
|
|
|
1330
1412
|
];
|
|
1331
1413
|
|
|
1332
1414
|
// src/ui/Sidebar.tsx
|
|
1333
|
-
import { jsx as
|
|
1415
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1334
1416
|
function Sidebar() {
|
|
1335
|
-
return /* @__PURE__ */
|
|
1336
|
-
|
|
1417
|
+
return /* @__PURE__ */ jsxs12(
|
|
1418
|
+
Box13,
|
|
1337
1419
|
{
|
|
1338
1420
|
backgroundColor: "#14171E",
|
|
1339
1421
|
width: 30,
|
|
@@ -1342,16 +1424,16 @@ function Sidebar() {
|
|
|
1342
1424
|
flexDirection: "column",
|
|
1343
1425
|
justifyContent: "space-between",
|
|
1344
1426
|
children: [
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1427
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1428
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1429
|
+
/* @__PURE__ */ jsx11(Steps, {})
|
|
1348
1430
|
] }),
|
|
1349
|
-
/* @__PURE__ */
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1352
|
-
return /* @__PURE__ */
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
/* @__PURE__ */
|
|
1431
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1432
|
+
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1433
|
+
/* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1434
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1435
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1436
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
|
|
1355
1437
|
] });
|
|
1356
1438
|
}) })
|
|
1357
1439
|
] })
|
|
@@ -1361,12 +1443,12 @@ function Sidebar() {
|
|
|
1361
1443
|
}
|
|
1362
1444
|
|
|
1363
1445
|
// src/ui/Ribbon.tsx
|
|
1364
|
-
import { Box as
|
|
1365
|
-
import { jsx as
|
|
1446
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1447
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1366
1448
|
function Ribbon() {
|
|
1367
1449
|
const firstCommand = sidebarCommands[0];
|
|
1368
|
-
return /* @__PURE__ */
|
|
1369
|
-
|
|
1450
|
+
return /* @__PURE__ */ jsxs13(
|
|
1451
|
+
Box14,
|
|
1370
1452
|
{
|
|
1371
1453
|
backgroundColor: "#14171E",
|
|
1372
1454
|
flexDirection: "row",
|
|
@@ -1374,11 +1456,11 @@ function Ribbon() {
|
|
|
1374
1456
|
paddingX: 2,
|
|
1375
1457
|
paddingY: 1,
|
|
1376
1458
|
children: [
|
|
1377
|
-
/* @__PURE__ */
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
/* @__PURE__ */
|
|
1380
|
-
/* @__PURE__ */
|
|
1381
|
-
/* @__PURE__ */
|
|
1459
|
+
/* @__PURE__ */ jsx12(Progress, {}),
|
|
1460
|
+
/* @__PURE__ */ jsx12(CurrentStep, {}),
|
|
1461
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
|
|
1462
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1463
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
|
|
1382
1464
|
] })
|
|
1383
1465
|
]
|
|
1384
1466
|
}
|
|
@@ -1389,8 +1471,8 @@ function Ribbon() {
|
|
|
1389
1471
|
import { useState as useState6 } from "react";
|
|
1390
1472
|
|
|
1391
1473
|
// src/ui/Logs.tsx
|
|
1392
|
-
import { Box as
|
|
1393
|
-
import { jsx as
|
|
1474
|
+
import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
|
|
1475
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1394
1476
|
var KIND_COLOR = {
|
|
1395
1477
|
tool: COLORS.primary,
|
|
1396
1478
|
prompt: COLORS.badge
|
|
@@ -1421,14 +1503,14 @@ function formatTimestamp(ms) {
|
|
|
1421
1503
|
function Logs() {
|
|
1422
1504
|
const logs = useWizard((s) => s.logs);
|
|
1423
1505
|
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1424
|
-
|
|
1506
|
+
useInput6((_input, key) => {
|
|
1425
1507
|
if (key.upArrow) scroll.scrollBy(-1);
|
|
1426
1508
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1427
1509
|
});
|
|
1428
1510
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1429
|
-
return /* @__PURE__ */
|
|
1430
|
-
logs.length === 0 && /* @__PURE__ */
|
|
1431
|
-
/* @__PURE__ */
|
|
1511
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1512
|
+
logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
|
|
1513
|
+
/* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1432
1514
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1433
1515
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1434
1516
|
const rawPreview = rawInputText(entry.input);
|
|
@@ -1438,14 +1520,14 @@ function Logs() {
|
|
|
1438
1520
|
const name = truncate2(entry.name, budget);
|
|
1439
1521
|
budget -= name.length;
|
|
1440
1522
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1441
|
-
return /* @__PURE__ */
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1444
|
-
preview && /* @__PURE__ */
|
|
1445
|
-
durationText && /* @__PURE__ */
|
|
1523
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1524
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
|
|
1525
|
+
/* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1526
|
+
preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1527
|
+
durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
|
|
1446
1528
|
] }, entry.id);
|
|
1447
1529
|
}) }),
|
|
1448
|
-
/* @__PURE__ */
|
|
1530
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1449
1531
|
] });
|
|
1450
1532
|
}
|
|
1451
1533
|
|
|
@@ -1637,7 +1719,7 @@ function track(event, payload) {
|
|
|
1637
1719
|
}
|
|
1638
1720
|
|
|
1639
1721
|
// src/ui/App.tsx
|
|
1640
|
-
import { jsx as
|
|
1722
|
+
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1641
1723
|
function App() {
|
|
1642
1724
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1643
1725
|
const { exit } = useApp();
|
|
@@ -1645,7 +1727,7 @@ function App() {
|
|
|
1645
1727
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1646
1728
|
const finished = phase === "done" || phase === "error";
|
|
1647
1729
|
const currentStep = steps[currentStepIndex];
|
|
1648
|
-
|
|
1730
|
+
useInput7(
|
|
1649
1731
|
(_input, key) => {
|
|
1650
1732
|
if (key.return) {
|
|
1651
1733
|
exit();
|
|
@@ -1653,7 +1735,7 @@ function App() {
|
|
|
1653
1735
|
},
|
|
1654
1736
|
{ isActive: finished }
|
|
1655
1737
|
);
|
|
1656
|
-
|
|
1738
|
+
useInput7((_input, key) => {
|
|
1657
1739
|
if (phase === "idle" || phase === "authenticating") return;
|
|
1658
1740
|
if (key.tab) {
|
|
1659
1741
|
setShowLogs(!showLogs);
|
|
@@ -1664,8 +1746,8 @@ function App() {
|
|
|
1664
1746
|
});
|
|
1665
1747
|
}
|
|
1666
1748
|
});
|
|
1667
|
-
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1668
|
-
|
|
1749
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
1750
|
+
useInput7((_input, key) => {
|
|
1669
1751
|
if (escOwnedElsewhere) return;
|
|
1670
1752
|
if (key.escape) {
|
|
1671
1753
|
track("AI Wizard Interaction", {
|
|
@@ -1684,8 +1766,8 @@ function App() {
|
|
|
1684
1766
|
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1685
1767
|
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1686
1768
|
flicker and leftover rows. */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
|
|
1769
|
+
/* @__PURE__ */ jsxs15(
|
|
1770
|
+
Box16,
|
|
1689
1771
|
{
|
|
1690
1772
|
backgroundColor: COLORS.bg.main,
|
|
1691
1773
|
flexDirection: "row",
|
|
@@ -1693,16 +1775,16 @@ function App() {
|
|
|
1693
1775
|
height: scrollsPastViewport ? void 0 : rows,
|
|
1694
1776
|
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1695
1777
|
children: [
|
|
1696
|
-
mainWindowVisible && /* @__PURE__ */
|
|
1697
|
-
|
|
1778
|
+
mainWindowVisible && /* @__PURE__ */ jsxs15(
|
|
1779
|
+
Box16,
|
|
1698
1780
|
{
|
|
1699
1781
|
flexDirection,
|
|
1700
1782
|
width: "100%",
|
|
1701
1783
|
maxHeight: rows,
|
|
1702
1784
|
justifyContent: "space-between",
|
|
1703
1785
|
children: [
|
|
1704
|
-
showLogs ? /* @__PURE__ */
|
|
1705
|
-
|
|
1786
|
+
showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
|
|
1787
|
+
Box16,
|
|
1706
1788
|
{
|
|
1707
1789
|
flexDirection: "column",
|
|
1708
1790
|
paddingX: 4,
|
|
@@ -1710,26 +1792,26 @@ function App() {
|
|
|
1710
1792
|
width: showSidebar ? 70 : "100%",
|
|
1711
1793
|
flexGrow: 1,
|
|
1712
1794
|
children: [
|
|
1713
|
-
phase === "authenticating" && /* @__PURE__ */
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
1795
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
|
|
1796
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1797
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1716
1798
|
] }),
|
|
1717
|
-
/* @__PURE__ */
|
|
1718
|
-
/* @__PURE__ */
|
|
1719
|
-
/* @__PURE__ */
|
|
1720
|
-
phase === "running" && showSidebar && /* @__PURE__ */
|
|
1721
|
-
phase === "error" && error && /* @__PURE__ */
|
|
1799
|
+
/* @__PURE__ */ jsx14(CliOutput, {}),
|
|
1800
|
+
/* @__PURE__ */ jsx14(Notices, {}),
|
|
1801
|
+
/* @__PURE__ */ jsx14(PromptInput, {}),
|
|
1802
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
|
|
1803
|
+
phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
|
|
1722
1804
|
"\u2716 ",
|
|
1723
1805
|
error
|
|
1724
1806
|
] }) })
|
|
1725
1807
|
]
|
|
1726
1808
|
}
|
|
1727
1809
|
),
|
|
1728
|
-
showSidebar ? /* @__PURE__ */
|
|
1810
|
+
showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
|
|
1729
1811
|
]
|
|
1730
1812
|
}
|
|
1731
1813
|
),
|
|
1732
|
-
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */
|
|
1814
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
|
|
1733
1815
|
]
|
|
1734
1816
|
}
|
|
1735
1817
|
)
|
|
@@ -1743,17 +1825,17 @@ import "zod";
|
|
|
1743
1825
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1744
1826
|
import { join as join5 } from "node:path";
|
|
1745
1827
|
var configFile = () => join5(stateDir(), "config.json");
|
|
1746
|
-
var
|
|
1828
|
+
var defaultConfig = () => ({
|
|
1747
1829
|
version: 1,
|
|
1748
1830
|
aiConsent: false,
|
|
1749
1831
|
workflowsRun: []
|
|
1750
|
-
};
|
|
1832
|
+
});
|
|
1751
1833
|
async function loadConfig() {
|
|
1752
1834
|
try {
|
|
1753
1835
|
const raw = await readFile2(configFile(), "utf8");
|
|
1754
|
-
return { ...
|
|
1836
|
+
return { ...defaultConfig(), ...JSON.parse(raw) };
|
|
1755
1837
|
} catch {
|
|
1756
|
-
return
|
|
1838
|
+
return defaultConfig();
|
|
1757
1839
|
}
|
|
1758
1840
|
}
|
|
1759
1841
|
async function saveConfig(config) {
|
|
@@ -1809,7 +1891,7 @@ async function ensureConsent() {
|
|
|
1809
1891
|
if (config.aiConsent) return;
|
|
1810
1892
|
const store = useWizard.getState();
|
|
1811
1893
|
const answer = await store.requestUserInput({
|
|
1812
|
-
prompt: "Wizard will make AI-authored changes to this repository.",
|
|
1894
|
+
prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
|
|
1813
1895
|
promptType: "enterToContinue",
|
|
1814
1896
|
options: []
|
|
1815
1897
|
});
|
|
@@ -2212,9 +2294,9 @@ function listFilesTool(ctx) {
|
|
|
2212
2294
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
2213
2295
|
return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
|
|
2214
2296
|
}
|
|
2215
|
-
const
|
|
2216
|
-
if (!
|
|
2217
|
-
const entries = await readdir(
|
|
2297
|
+
const resolved2 = resolveInRoot(ctx, ".");
|
|
2298
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2299
|
+
const entries = await readdir(resolved2.target, { withFileTypes: true });
|
|
2218
2300
|
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
2219
2301
|
}
|
|
2220
2302
|
});
|
|
@@ -2232,14 +2314,14 @@ function changeDirectoryTool(ctx) {
|
|
|
2232
2314
|
}),
|
|
2233
2315
|
execute: async ({ path }) => {
|
|
2234
2316
|
logger.info({ path }, "called changeDirectory tool");
|
|
2235
|
-
const
|
|
2236
|
-
if (!
|
|
2317
|
+
const resolved2 = resolveInRoot(ctx, path);
|
|
2318
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2237
2319
|
try {
|
|
2238
|
-
const info = await stat(
|
|
2320
|
+
const info = await stat(resolved2.target);
|
|
2239
2321
|
if (!info.isDirectory()) {
|
|
2240
2322
|
return `Error changing directory to ${path}: not a directory`;
|
|
2241
2323
|
}
|
|
2242
|
-
ctx.cwd =
|
|
2324
|
+
ctx.cwd = resolved2.target;
|
|
2243
2325
|
return `Changed working directory to ${ctx.cwd}`;
|
|
2244
2326
|
} catch (err) {
|
|
2245
2327
|
return `Error changing directory to ${path}: ${err.message}`;
|
|
@@ -2304,11 +2386,11 @@ function readFileTool(ctx) {
|
|
|
2304
2386
|
return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
|
|
2305
2387
|
}
|
|
2306
2388
|
logger.info({ filePath }, "called readFile tool");
|
|
2307
|
-
const
|
|
2308
|
-
if (!
|
|
2389
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2390
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2309
2391
|
try {
|
|
2310
|
-
const content = await readFile3(
|
|
2311
|
-
return isEnvFile(
|
|
2392
|
+
const content = await readFile3(resolved2.target, "utf8");
|
|
2393
|
+
return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
|
|
2312
2394
|
} catch (err) {
|
|
2313
2395
|
return `Error reading ${filePath}: ${err.message}`;
|
|
2314
2396
|
}
|
|
@@ -2330,17 +2412,17 @@ function writeFileTool(ctx) {
|
|
|
2330
2412
|
}),
|
|
2331
2413
|
execute: async ({ filePath, content }) => {
|
|
2332
2414
|
logger.info({ filePath }, "called writeFile tool");
|
|
2333
|
-
const
|
|
2334
|
-
if (
|
|
2335
|
-
if (isSecretEnvFile(
|
|
2415
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2416
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2417
|
+
if (isSecretEnvFile(resolved2.target)) {
|
|
2336
2418
|
return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
|
|
2337
2419
|
}
|
|
2338
2420
|
try {
|
|
2339
|
-
if (await hasSymlinkParent(ctx,
|
|
2340
|
-
return `Refused: ${
|
|
2421
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2422
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2341
2423
|
}
|
|
2342
|
-
await mkdir3(dirname4(
|
|
2343
|
-
await writeFile3(
|
|
2424
|
+
await mkdir3(dirname4(resolved2.target), { recursive: true });
|
|
2425
|
+
await writeFile3(resolved2.target, content, "utf8");
|
|
2344
2426
|
return `Wrote to ${filePath}`;
|
|
2345
2427
|
} catch (err) {
|
|
2346
2428
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -2357,7 +2439,40 @@ import { dirname as dirname5 } from "node:path";
|
|
|
2357
2439
|
|
|
2358
2440
|
// src/lib/algoliaApiKey.ts
|
|
2359
2441
|
import { z as z11 } from "zod";
|
|
2360
|
-
|
|
2442
|
+
|
|
2443
|
+
// src/lib/keychain.ts
|
|
2444
|
+
import { getPassword, setPassword } from "cross-keychain";
|
|
2445
|
+
var SERVICE = "algolia-wizard";
|
|
2446
|
+
function account(kind, index, appId) {
|
|
2447
|
+
return `${kind}:${appId}:${index}`;
|
|
2448
|
+
}
|
|
2449
|
+
async function readStoredKey(kind, index, appId) {
|
|
2450
|
+
try {
|
|
2451
|
+
return await getPassword(SERVICE, account(kind, index, appId));
|
|
2452
|
+
} catch (err) {
|
|
2453
|
+
logger.warn(
|
|
2454
|
+
{ err: err.message, kind, index, appId },
|
|
2455
|
+
"could not read the API key from the keychain"
|
|
2456
|
+
);
|
|
2457
|
+
return null;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
async function storeKey(kind, index, appId, value) {
|
|
2461
|
+
const name = account(kind, index, appId);
|
|
2462
|
+
try {
|
|
2463
|
+
await setPassword(SERVICE, name, value);
|
|
2464
|
+
if (await getPassword(SERVICE, name) !== value) {
|
|
2465
|
+
throw new Error("the keychain did not store the value");
|
|
2466
|
+
}
|
|
2467
|
+
} catch (err) {
|
|
2468
|
+
logger.warn(
|
|
2469
|
+
{ err: err.message, kind, index, appId },
|
|
2470
|
+
"could not store the API key in the keychain; the next run will create another"
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// src/lib/algoliaApiKey.ts
|
|
2361
2476
|
var WRITE_ACLS = [
|
|
2362
2477
|
"addObject",
|
|
2363
2478
|
"deleteObject",
|
|
@@ -2365,83 +2480,88 @@ var WRITE_ACLS = [
|
|
|
2365
2480
|
"editSettings",
|
|
2366
2481
|
"listIndexes"
|
|
2367
2482
|
];
|
|
2368
|
-
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2369
|
-
var apiKeySchema = z11.object({
|
|
2370
|
-
value: z11.string().min(1),
|
|
2371
|
-
acl: z11.array(z11.string()).default([]),
|
|
2372
|
-
indexes: z11.array(z11.string()).default([])
|
|
2373
|
-
});
|
|
2374
|
-
var apiKeyListSchema = z11.object({
|
|
2375
|
-
items: z11.array(apiKeySchema).optional(),
|
|
2376
|
-
keys: z11.array(apiKeySchema).optional()
|
|
2377
|
-
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2378
2483
|
var createdKeySchema = z11.object({
|
|
2379
2484
|
key: z11.string().min(1).optional(),
|
|
2380
2485
|
value: z11.string().min(1).optional()
|
|
2381
|
-
});
|
|
2382
|
-
function
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2486
|
+
}).transform((o) => o.key ?? o.value);
|
|
2487
|
+
async function createKey(index, acls, description) {
|
|
2488
|
+
logger.info({ index, acls }, "creating an API key");
|
|
2489
|
+
const stdout = await runAlgoliaCli(
|
|
2490
|
+
[
|
|
2491
|
+
"apikeys",
|
|
2492
|
+
"create",
|
|
2493
|
+
"--acl",
|
|
2494
|
+
acls.join(","),
|
|
2495
|
+
"--indices",
|
|
2496
|
+
index,
|
|
2497
|
+
"--description",
|
|
2498
|
+
description,
|
|
2499
|
+
"-o",
|
|
2500
|
+
"json"
|
|
2501
|
+
],
|
|
2502
|
+
{ withoutAdminKey: true }
|
|
2503
|
+
);
|
|
2504
|
+
let payload;
|
|
2505
|
+
try {
|
|
2506
|
+
payload = JSON.parse(stdout);
|
|
2507
|
+
} catch {
|
|
2508
|
+
throw new Error("apikeys create returned output that is not valid JSON");
|
|
2509
|
+
}
|
|
2510
|
+
const created = createdKeySchema.parse(payload);
|
|
2400
2511
|
if (!created) throw new Error("apikeys create returned no key value");
|
|
2401
2512
|
return created;
|
|
2402
2513
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2514
|
+
var resolved = /* @__PURE__ */ new Map();
|
|
2515
|
+
function forgetResolvedKeys() {
|
|
2516
|
+
resolved.clear();
|
|
2405
2517
|
}
|
|
2406
|
-
|
|
2407
|
-
const
|
|
2408
|
-
const
|
|
2409
|
-
if (
|
|
2410
|
-
|
|
2411
|
-
|
|
2518
|
+
function resolveKey(kind, index, appId, acls, description) {
|
|
2519
|
+
const cacheKey = `${kind}:${appId}:${index}`;
|
|
2520
|
+
const cached = resolved.get(cacheKey);
|
|
2521
|
+
if (cached) return cached;
|
|
2522
|
+
const pending = provisionKey(kind, index, appId, acls, description).catch(
|
|
2523
|
+
(err) => {
|
|
2524
|
+
resolved.delete(cacheKey);
|
|
2525
|
+
throw err;
|
|
2526
|
+
}
|
|
2527
|
+
);
|
|
2528
|
+
resolved.set(cacheKey, pending);
|
|
2529
|
+
return pending;
|
|
2530
|
+
}
|
|
2531
|
+
async function provisionKey(kind, index, appId, acls, description) {
|
|
2532
|
+
const stored = await readStoredKey(kind, index, appId);
|
|
2533
|
+
if (stored) {
|
|
2534
|
+
logger.info({ kind, index, appId }, "reusing the stored API key");
|
|
2535
|
+
return { key: stored, source: "keychain" };
|
|
2412
2536
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2537
|
+
const key = await createKey(index, acls, description);
|
|
2538
|
+
await storeKey(kind, index, appId, key);
|
|
2539
|
+
return { key, source: "created" };
|
|
2540
|
+
}
|
|
2541
|
+
function resolveWriteKey(index, appId) {
|
|
2542
|
+
return resolveKey(
|
|
2543
|
+
"write",
|
|
2418
2544
|
index,
|
|
2419
|
-
|
|
2420
|
-
WRITE_ACLS
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
2434
|
-
if (existing) {
|
|
2435
|
-
logger.info({ index }, "reusing existing search-only API key");
|
|
2436
|
-
return existing;
|
|
2437
|
-
}
|
|
2438
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
2439
|
-
return createSearchKey(index);
|
|
2545
|
+
appId,
|
|
2546
|
+
WRITE_ACLS,
|
|
2547
|
+
`Algolia Wizard write key for ${index} index`
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2551
|
+
if (envKey) return { key: envKey, source: "env" };
|
|
2552
|
+
return resolveKey(
|
|
2553
|
+
"search",
|
|
2554
|
+
index,
|
|
2555
|
+
appId,
|
|
2556
|
+
["search"],
|
|
2557
|
+
`Algolia Wizard search-only key for ${index} index`
|
|
2558
|
+
);
|
|
2440
2559
|
}
|
|
2441
2560
|
|
|
2442
2561
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2443
2562
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2444
2563
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2564
|
+
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2445
2565
|
function appendEnv(content, entries) {
|
|
2446
2566
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2447
2567
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -2451,9 +2571,16 @@ function appendEnv(content, entries) {
|
|
|
2451
2571
|
function hasEnv(content, name) {
|
|
2452
2572
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
2453
2573
|
}
|
|
2574
|
+
function upsertEnv(content, name, value) {
|
|
2575
|
+
if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
|
|
2576
|
+
return content.replace(
|
|
2577
|
+
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m"),
|
|
2578
|
+
`${name}=${value}`
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2454
2581
|
function writeCredentialsTool(ctx) {
|
|
2455
2582
|
return tool6({
|
|
2456
|
-
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").
|
|
2583
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). Any name the file already defines is left untouched.`,
|
|
2457
2584
|
inputSchema: z12.object({
|
|
2458
2585
|
filePath: z12.string().describe(
|
|
2459
2586
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
@@ -2461,43 +2588,51 @@ function writeCredentialsTool(ctx) {
|
|
|
2461
2588
|
}),
|
|
2462
2589
|
execute: async ({ filePath }) => {
|
|
2463
2590
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2464
|
-
const
|
|
2465
|
-
if (
|
|
2591
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2592
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2466
2593
|
const targetIndex = useWizard.getState().targetIndex;
|
|
2467
2594
|
if (!targetIndex) {
|
|
2468
2595
|
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2469
2596
|
}
|
|
2470
|
-
let
|
|
2471
|
-
let
|
|
2597
|
+
let existing = "";
|
|
2598
|
+
let present;
|
|
2472
2599
|
try {
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
} catch (err) {
|
|
2476
|
-
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2477
|
-
}
|
|
2478
|
-
try {
|
|
2479
|
-
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
2480
|
-
return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
|
|
2600
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2601
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2481
2602
|
}
|
|
2482
|
-
let existing = "";
|
|
2483
2603
|
try {
|
|
2484
|
-
existing = await readFile4(
|
|
2604
|
+
existing = await readFile4(resolved2.target, "utf8");
|
|
2485
2605
|
} catch (err) {
|
|
2486
2606
|
if (err.code !== "ENOENT") throw err;
|
|
2487
2607
|
}
|
|
2488
|
-
|
|
2608
|
+
present = [APP_ID_VAR, API_KEY_VAR].filter(
|
|
2489
2609
|
(name) => hasEnv(existing, name)
|
|
2490
2610
|
);
|
|
2491
|
-
|
|
2492
|
-
|
|
2611
|
+
} catch (err) {
|
|
2612
|
+
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
2613
|
+
}
|
|
2614
|
+
let credentials = [];
|
|
2615
|
+
if (present.length === 0) {
|
|
2616
|
+
try {
|
|
2617
|
+
const appId = (await requireApplication()).id;
|
|
2618
|
+
credentials = [
|
|
2619
|
+
[APP_ID_VAR, appId],
|
|
2620
|
+
[API_KEY_VAR, (await resolveWriteKey(targetIndex, appId)).key]
|
|
2621
|
+
];
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2493
2624
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2625
|
+
}
|
|
2626
|
+
try {
|
|
2627
|
+
const updated = upsertEnv(
|
|
2628
|
+
appendEnv(existing, credentials),
|
|
2629
|
+
INDEX_NAME_VAR,
|
|
2630
|
+
targetIndex
|
|
2631
|
+
);
|
|
2632
|
+
await mkdir4(dirname5(resolved2.target), { recursive: true });
|
|
2633
|
+
await writeFile4(resolved2.target, updated, "utf8");
|
|
2634
|
+
const wrote = `Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}`;
|
|
2635
|
+
return present.length > 0 ? `${wrote}. Skipped ${present.join(" and ")}: already defined there.` : wrote;
|
|
2501
2636
|
} catch (err) {
|
|
2502
2637
|
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
2503
2638
|
}
|
|
@@ -2511,11 +2646,19 @@ import z13 from "zod";
|
|
|
2511
2646
|
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2512
2647
|
import { join as join7 } from "node:path";
|
|
2513
2648
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2649
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2650
|
+
"node_modules",
|
|
2651
|
+
"dist",
|
|
2652
|
+
"build",
|
|
2653
|
+
"vendor",
|
|
2654
|
+
"venv",
|
|
2655
|
+
"__pycache__",
|
|
2656
|
+
"target"
|
|
2657
|
+
]);
|
|
2514
2658
|
async function walkFiles(dir) {
|
|
2515
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2516
2659
|
const out = [];
|
|
2517
2660
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2518
|
-
if (e.name.startsWith(".") ||
|
|
2661
|
+
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
2519
2662
|
const full = join7(dir, e.name);
|
|
2520
2663
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2521
2664
|
else if (e.isFile()) out.push(full);
|
|
@@ -2537,8 +2680,8 @@ function searchFilesTool(ctx) {
|
|
|
2537
2680
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
2538
2681
|
return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
|
|
2539
2682
|
}
|
|
2540
|
-
const
|
|
2541
|
-
if (!
|
|
2683
|
+
const resolved2 = resolveInRoot(ctx, path);
|
|
2684
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2542
2685
|
let re;
|
|
2543
2686
|
try {
|
|
2544
2687
|
re = new RegExp(query);
|
|
@@ -2546,7 +2689,7 @@ function searchFilesTool(ctx) {
|
|
|
2546
2689
|
return `Invalid regex: ${err.message}`;
|
|
2547
2690
|
}
|
|
2548
2691
|
const matches = [];
|
|
2549
|
-
for (const file of await walkFiles(
|
|
2692
|
+
for (const file of await walkFiles(resolved2.target)) {
|
|
2550
2693
|
let content;
|
|
2551
2694
|
try {
|
|
2552
2695
|
content = await readFile5(file, "utf8");
|
|
@@ -2569,92 +2712,194 @@ function searchFilesTool(ctx) {
|
|
|
2569
2712
|
});
|
|
2570
2713
|
}
|
|
2571
2714
|
|
|
2572
|
-
// src/lib/tools/
|
|
2715
|
+
// src/lib/tools/runShell.ts
|
|
2573
2716
|
import { tool as tool8 } from "ai";
|
|
2574
2717
|
import z14 from "zod";
|
|
2718
|
+
import { relative as relative2 } from "node:path";
|
|
2575
2719
|
|
|
2576
|
-
// src/lib/tools/utils/
|
|
2720
|
+
// src/lib/tools/utils/runShell.ts
|
|
2577
2721
|
import { spawn as spawn2 } from "node:child_process";
|
|
2578
|
-
|
|
2722
|
+
|
|
2723
|
+
// src/lib/tools/context.ts
|
|
2724
|
+
var DEFAULT_TOOL_LIMITS = {
|
|
2725
|
+
list: 10,
|
|
2726
|
+
search: 10,
|
|
2727
|
+
read: 20,
|
|
2728
|
+
match: 100,
|
|
2729
|
+
shell: 30
|
|
2730
|
+
};
|
|
2731
|
+
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2732
|
+
async function refuseByDefault() {
|
|
2733
|
+
return "reject";
|
|
2734
|
+
}
|
|
2735
|
+
function createShellContext(overrides = {}) {
|
|
2736
|
+
return {
|
|
2737
|
+
env: async () => ({}),
|
|
2738
|
+
timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
|
|
2739
|
+
approved: /* @__PURE__ */ new Set(),
|
|
2740
|
+
executions: [],
|
|
2741
|
+
approve: refuseByDefault,
|
|
2742
|
+
...overrides
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
|
|
2746
|
+
return {
|
|
2747
|
+
root: cwd,
|
|
2748
|
+
cwd,
|
|
2749
|
+
limits: { ...limits },
|
|
2750
|
+
counts: { list: 0, search: 0, read: 0, shell: 0 },
|
|
2751
|
+
shell: shell2
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/lib/tools/utils/runShell.ts
|
|
2756
|
+
var SIGKILL_DELAY_MS = 5e3;
|
|
2757
|
+
var HEAD_CHARS = 4e3;
|
|
2758
|
+
var TAIL_CHARS = 8e3;
|
|
2759
|
+
function truncateOutput(output) {
|
|
2760
|
+
if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
|
|
2761
|
+
const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
|
|
2762
|
+
return [
|
|
2763
|
+
output.slice(0, HEAD_CHARS),
|
|
2764
|
+
`
|
|
2765
|
+
\u2026 [${omitted} characters omitted] \u2026
|
|
2766
|
+
`,
|
|
2767
|
+
output.slice(-TAIL_CHARS)
|
|
2768
|
+
].join("");
|
|
2769
|
+
}
|
|
2770
|
+
function runShell(command, opts) {
|
|
2771
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
2772
|
+
const startedAt = Date.now();
|
|
2579
2773
|
return new Promise((resolve4) => {
|
|
2580
2774
|
let output = "";
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2775
|
+
let timedOut = false;
|
|
2776
|
+
let settled = false;
|
|
2777
|
+
const child = spawn2(command, {
|
|
2778
|
+
shell: true,
|
|
2779
|
+
cwd: opts.cwd,
|
|
2780
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2781
|
+
env: { ...process.env, ...opts.env }
|
|
2584
2782
|
});
|
|
2783
|
+
const finish = (exitCode) => {
|
|
2784
|
+
if (settled) return;
|
|
2785
|
+
settled = true;
|
|
2786
|
+
clearTimeout(timer);
|
|
2787
|
+
clearTimeout(killTimer);
|
|
2788
|
+
resolve4({
|
|
2789
|
+
exitCode,
|
|
2790
|
+
output: truncateOutput(output.trim()),
|
|
2791
|
+
timedOut,
|
|
2792
|
+
durationMs: Date.now() - startedAt
|
|
2793
|
+
});
|
|
2794
|
+
};
|
|
2795
|
+
let killTimer;
|
|
2796
|
+
const timer = setTimeout(() => {
|
|
2797
|
+
timedOut = true;
|
|
2798
|
+
output += `
|
|
2799
|
+
[timed out after ${timeoutMs}ms]`;
|
|
2800
|
+
child.kill("SIGTERM");
|
|
2801
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
|
|
2802
|
+
}, timeoutMs);
|
|
2585
2803
|
child.stdout?.on("data", (d) => output += d);
|
|
2586
2804
|
child.stderr?.on("data", (d) => output += d);
|
|
2587
|
-
child.on(
|
|
2588
|
-
|
|
2589
|
-
(
|
|
2590
|
-
);
|
|
2591
|
-
child.on("close", (code) =>
|
|
2805
|
+
child.on("error", (err) => {
|
|
2806
|
+
output += `Failed to run ${command}: ${err.message}`;
|
|
2807
|
+
finish(1);
|
|
2808
|
+
});
|
|
2809
|
+
child.on("close", (code) => finish(code ?? 1));
|
|
2592
2810
|
});
|
|
2593
2811
|
}
|
|
2594
2812
|
|
|
2595
|
-
// src/lib/tools/
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
]
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2614
|
-
}
|
|
2615
|
-
async function detectPackageManager(cwd) {
|
|
2616
|
-
try {
|
|
2617
|
-
const pkg = await readPackageJson(cwd);
|
|
2618
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2619
|
-
} catch {
|
|
2620
|
-
}
|
|
2621
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
// src/lib/tools/repoVerification.ts
|
|
2625
|
-
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2626
|
-
async function runRepoVerificationCheck() {
|
|
2627
|
-
let pkg;
|
|
2628
|
-
try {
|
|
2629
|
-
pkg = await readPackageJson();
|
|
2630
|
-
} catch (err) {
|
|
2631
|
-
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2632
|
-
return { ok: false, checks: [], limitation };
|
|
2633
|
-
}
|
|
2634
|
-
const scripts = pkg.scripts ?? {};
|
|
2635
|
-
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2636
|
-
if (present.length === 0) {
|
|
2637
|
-
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2638
|
-
return { ok: false, checks: [], limitation };
|
|
2639
|
-
}
|
|
2640
|
-
const pm = await detectPackageManager(process.cwd());
|
|
2641
|
-
const checks = [];
|
|
2642
|
-
for (const script of present) {
|
|
2643
|
-
const command = `${pm} run ${script}`;
|
|
2644
|
-
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2645
|
-
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2646
|
-
}
|
|
2647
|
-
return { ok: checks.every((c) => c.ok), checks };
|
|
2813
|
+
// src/lib/tools/runShell.ts
|
|
2814
|
+
function approvalKey(cwd, command) {
|
|
2815
|
+
return `${cwd}\0${command}`;
|
|
2816
|
+
}
|
|
2817
|
+
function storeApproval(root) {
|
|
2818
|
+
return async (req) => {
|
|
2819
|
+
const rel = relative2(root, req.cwd);
|
|
2820
|
+
const answer = await useWizard.getState().requestUserInput({
|
|
2821
|
+
prompt: "Run this command?",
|
|
2822
|
+
promptType: "commandApproval",
|
|
2823
|
+
options: [],
|
|
2824
|
+
command: {
|
|
2825
|
+
...req,
|
|
2826
|
+
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
return answer === "approve" || answer === "always" ? answer : "reject";
|
|
2830
|
+
};
|
|
2648
2831
|
}
|
|
2649
|
-
|
|
2650
|
-
// src/lib/tools/verifyImplementation.ts
|
|
2651
|
-
function verifyImplementationTool() {
|
|
2832
|
+
function runShellTool(ctx) {
|
|
2652
2833
|
return tool8({
|
|
2653
|
-
description: "Run the
|
|
2654
|
-
inputSchema: z14.object(
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2834
|
+
description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
2835
|
+
inputSchema: z14.object({
|
|
2836
|
+
command: z14.string().describe(
|
|
2837
|
+
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
2838
|
+
),
|
|
2839
|
+
cwd: z14.string().optional().describe(
|
|
2840
|
+
"Directory to run in, relative to the project root. Defaults to the project root."
|
|
2841
|
+
),
|
|
2842
|
+
explanation: z14.string().describe(
|
|
2843
|
+
"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."
|
|
2844
|
+
)
|
|
2845
|
+
}),
|
|
2846
|
+
execute: async ({ command, cwd, explanation }) => {
|
|
2847
|
+
if (++ctx.counts.shell > ctx.limits.shell) {
|
|
2848
|
+
return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
|
|
2849
|
+
}
|
|
2850
|
+
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
2851
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2852
|
+
logger.info({ command, cwd: resolved2.target }, "called runShell tool");
|
|
2853
|
+
const key = approvalKey(resolved2.target, command);
|
|
2854
|
+
const decision = ctx.shell.approved.has(key) ? "approve" : await ctx.shell.approve({
|
|
2855
|
+
command,
|
|
2856
|
+
cwd: resolved2.target,
|
|
2857
|
+
explanation
|
|
2858
|
+
});
|
|
2859
|
+
if (decision === "reject") {
|
|
2860
|
+
ctx.shell.executions.push({
|
|
2861
|
+
command,
|
|
2862
|
+
cwd: resolved2.target,
|
|
2863
|
+
approved: false
|
|
2864
|
+
});
|
|
2865
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2866
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2867
|
+
}
|
|
2868
|
+
if (decision === "always") ctx.shell.approved.add(key);
|
|
2869
|
+
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
2870
|
+
const env = await ctx.shell.env().catch((err) => {
|
|
2871
|
+
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
2872
|
+
return {};
|
|
2873
|
+
});
|
|
2874
|
+
const run2 = await (ctx.shell.run ?? runShell)(command, {
|
|
2875
|
+
cwd: resolved2.target,
|
|
2876
|
+
env,
|
|
2877
|
+
timeoutMs: ctx.shell.timeoutMs
|
|
2878
|
+
});
|
|
2879
|
+
logger.info(
|
|
2880
|
+
{
|
|
2881
|
+
command,
|
|
2882
|
+
exitCode: run2.exitCode,
|
|
2883
|
+
timedOut: run2.timedOut,
|
|
2884
|
+
durationMs: run2.durationMs
|
|
2885
|
+
},
|
|
2886
|
+
"runShell finished"
|
|
2887
|
+
);
|
|
2888
|
+
await markInteraction();
|
|
2889
|
+
ctx.shell.executions.push({
|
|
2890
|
+
command,
|
|
2891
|
+
cwd: resolved2.target,
|
|
2892
|
+
approved: true,
|
|
2893
|
+
exitCode: run2.exitCode,
|
|
2894
|
+
output: run2.output,
|
|
2895
|
+
timedOut: run2.timedOut,
|
|
2896
|
+
durationMs: run2.durationMs
|
|
2897
|
+
});
|
|
2898
|
+
return {
|
|
2899
|
+
exitCode: run2.exitCode,
|
|
2900
|
+
timedOut: run2.timedOut,
|
|
2901
|
+
output: run2.output
|
|
2902
|
+
};
|
|
2658
2903
|
}
|
|
2659
2904
|
});
|
|
2660
2905
|
}
|
|
@@ -2734,18 +2979,18 @@ function generateRecordTool(ctx) {
|
|
|
2734
2979
|
}));
|
|
2735
2980
|
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2736
2981
|
const relPath = `${DATA_DIR}/${slug}.json`;
|
|
2737
|
-
const
|
|
2738
|
-
if (
|
|
2739
|
-
if (await hasSymlinkParent(ctx,
|
|
2740
|
-
return `Refused: ${
|
|
2982
|
+
const resolved2 = resolveInRoot(ctx, relPath);
|
|
2983
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2984
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2985
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2741
2986
|
}
|
|
2742
|
-
await mkdir5(dirname6(
|
|
2743
|
-
await writeFile5(
|
|
2987
|
+
await mkdir5(dirname6(resolved2.target), { recursive: true });
|
|
2988
|
+
await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
|
|
2744
2989
|
logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
|
|
2745
2990
|
return {
|
|
2746
2991
|
filePath: relPath,
|
|
2747
2992
|
count: records.length,
|
|
2748
|
-
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime
|
|
2993
|
+
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime using your language's standard JSON support \u2014 do not inline the records as literals.`
|
|
2749
2994
|
};
|
|
2750
2995
|
} catch (err) {
|
|
2751
2996
|
return `Error generating records: ${err.message}`;
|
|
@@ -2773,22 +3018,6 @@ function notifyUserTool() {
|
|
|
2773
3018
|
});
|
|
2774
3019
|
}
|
|
2775
3020
|
|
|
2776
|
-
// src/lib/tools/context.ts
|
|
2777
|
-
var DEFAULT_TOOL_LIMITS = {
|
|
2778
|
-
list: 10,
|
|
2779
|
-
search: 10,
|
|
2780
|
-
read: 20,
|
|
2781
|
-
match: 100
|
|
2782
|
-
};
|
|
2783
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
2784
|
-
return {
|
|
2785
|
-
root: cwd,
|
|
2786
|
-
cwd,
|
|
2787
|
-
limits,
|
|
2788
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2789
|
-
};
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
3021
|
// src/lib/tools/index.ts
|
|
2793
3022
|
function withLogging(name, def) {
|
|
2794
3023
|
const execute = def.execute;
|
|
@@ -2820,10 +3049,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2820
3049
|
writeCredentialsTool(ctx)
|
|
2821
3050
|
),
|
|
2822
3051
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2823
|
-
|
|
2824
|
-
"verifyImplementation",
|
|
2825
|
-
verifyImplementationTool()
|
|
2826
|
-
),
|
|
3052
|
+
runShell: withLogging("runShell", runShellTool(ctx)),
|
|
2827
3053
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2828
3054
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
2829
3055
|
};
|
|
@@ -2858,7 +3084,7 @@ async function runAgent(req) {
|
|
|
2858
3084
|
baseURL: PROXY_BASE_URL,
|
|
2859
3085
|
fetch: proxyFetch
|
|
2860
3086
|
});
|
|
2861
|
-
const toolContext = createToolContext();
|
|
3087
|
+
const toolContext = req.toolContext ?? createToolContext();
|
|
2862
3088
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2863
3089
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2864
3090
|
const instructions = [
|
|
@@ -2923,7 +3149,11 @@ async function runAgent(req) {
|
|
|
2923
3149
|
"runAgent finished"
|
|
2924
3150
|
);
|
|
2925
3151
|
logger.info(
|
|
2926
|
-
{
|
|
3152
|
+
{
|
|
3153
|
+
counts: toolContext.counts,
|
|
3154
|
+
limits: toolContext.limits,
|
|
3155
|
+
commandsRun: toolContext.shell.executions.length
|
|
3156
|
+
},
|
|
2927
3157
|
"tool usage"
|
|
2928
3158
|
);
|
|
2929
3159
|
const toolResults = await stream.toolResults;
|
|
@@ -2949,8 +3179,8 @@ var detectLanguageSchema = z19.object({
|
|
|
2949
3179
|
var detectLanguage = () => runAgent({
|
|
2950
3180
|
instructions: [
|
|
2951
3181
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2952
|
-
"If a superset language is found, exclude the subset language.
|
|
2953
|
-
"If a meta-framework is used, exclude the framework. Next
|
|
3182
|
+
"If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
|
|
3183
|
+
"If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
|
|
2954
3184
|
"Return the exact version",
|
|
2955
3185
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2956
3186
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3048,7 +3278,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3048
3278
|
// package.json
|
|
3049
3279
|
var package_default = {
|
|
3050
3280
|
name: "@algolia/wizard",
|
|
3051
|
-
version: "0.9.0-rc.
|
|
3281
|
+
version: "0.9.0-rc.87.86",
|
|
3052
3282
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3053
3283
|
type: "module",
|
|
3054
3284
|
engines: {
|
|
@@ -3099,6 +3329,7 @@ var package_default = {
|
|
|
3099
3329
|
"@hono/node-server": "^2.0.10",
|
|
3100
3330
|
"@segment/analytics-node": "^3.1.0",
|
|
3101
3331
|
ai: "^6.0.190",
|
|
3332
|
+
"cross-keychain": "^1.1.0",
|
|
3102
3333
|
dotenv: "^17.4.2",
|
|
3103
3334
|
hono: "^4.12.27",
|
|
3104
3335
|
ink: "^7.0.5",
|
|
@@ -3196,9 +3427,11 @@ var CURATED_FRAMEWORKS = [
|
|
|
3196
3427
|
"Next.js",
|
|
3197
3428
|
"React",
|
|
3198
3429
|
"Vue",
|
|
3199
|
-
"
|
|
3200
|
-
"
|
|
3201
|
-
"
|
|
3430
|
+
"Vanilla JS",
|
|
3431
|
+
"Django",
|
|
3432
|
+
"Laravel",
|
|
3433
|
+
"Rails",
|
|
3434
|
+
"Symfony"
|
|
3202
3435
|
];
|
|
3203
3436
|
var OTHER_OPTION = "Other";
|
|
3204
3437
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -3211,12 +3444,15 @@ var FRAMEWORK_ALIASES = {
|
|
|
3211
3444
|
vuejs: "vue",
|
|
3212
3445
|
angular: "angular",
|
|
3213
3446
|
angularjs: "angular",
|
|
3214
|
-
svelte: "svelte",
|
|
3215
|
-
sveltekit: "svelte",
|
|
3216
3447
|
vanillajs: "vanillajs",
|
|
3217
3448
|
vanilla: "vanillajs",
|
|
3218
3449
|
javascript: "vanillajs",
|
|
3219
|
-
js: "vanillajs"
|
|
3450
|
+
js: "vanillajs",
|
|
3451
|
+
django: "django",
|
|
3452
|
+
laravel: "laravel",
|
|
3453
|
+
rails: "rails",
|
|
3454
|
+
rubyonrails: "rails",
|
|
3455
|
+
symfony: "symfony"
|
|
3220
3456
|
};
|
|
3221
3457
|
var isSameFramework = (a, b) => {
|
|
3222
3458
|
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
@@ -3450,16 +3686,9 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3450
3686
|
import z26 from "zod";
|
|
3451
3687
|
|
|
3452
3688
|
// src/lib/worktree.ts
|
|
3453
|
-
import { execFile
|
|
3454
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3455
|
-
import {
|
|
3456
|
-
basename as basename2,
|
|
3457
|
-
dirname as dirname7,
|
|
3458
|
-
isAbsolute as isAbsolute2,
|
|
3459
|
-
join as join9,
|
|
3460
|
-
relative as relative2,
|
|
3461
|
-
resolve as resolve3
|
|
3462
|
-
} from "node:path";
|
|
3689
|
+
import { execFile } from "node:child_process";
|
|
3690
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3691
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
|
|
3463
3692
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3464
3693
|
var MAX_WIZARD_WORKTREES = 3;
|
|
3465
3694
|
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
@@ -3490,7 +3719,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3490
3719
|
return out.trim().length > 0;
|
|
3491
3720
|
}
|
|
3492
3721
|
async function pruneOldWorktrees(repoRoot) {
|
|
3493
|
-
const dir =
|
|
3722
|
+
const dir = join8(stateDir(repoRoot), "worktrees");
|
|
3494
3723
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3495
3724
|
for (const slug of stale) {
|
|
3496
3725
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3501,7 +3730,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3501
3730
|
"worktree",
|
|
3502
3731
|
"remove",
|
|
3503
3732
|
"--force",
|
|
3504
|
-
|
|
3733
|
+
join8(dir, slug)
|
|
3505
3734
|
]);
|
|
3506
3735
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3507
3736
|
} catch (err) {
|
|
@@ -3515,113 +3744,13 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3515
3744
|
async function createWorktree(repoRoot) {
|
|
3516
3745
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3517
3746
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3518
|
-
const path =
|
|
3747
|
+
const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3519
3748
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3520
3749
|
await pruneOldWorktrees(repoRoot);
|
|
3521
3750
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3522
3751
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3523
3752
|
return { path, branch };
|
|
3524
3753
|
}
|
|
3525
|
-
async function installWorktreeDeps(worktreePath) {
|
|
3526
|
-
try {
|
|
3527
|
-
await readPackageJson(worktreePath);
|
|
3528
|
-
} catch {
|
|
3529
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3530
|
-
}
|
|
3531
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3532
|
-
return new Promise((resolve4) => {
|
|
3533
|
-
let output = "";
|
|
3534
|
-
const child = spawn3(pm, ["install"], {
|
|
3535
|
-
cwd: worktreePath,
|
|
3536
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3537
|
-
});
|
|
3538
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3539
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3540
|
-
child.on(
|
|
3541
|
-
"error",
|
|
3542
|
-
(err) => resolve4({
|
|
3543
|
-
ok: false,
|
|
3544
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3545
|
-
})
|
|
3546
|
-
);
|
|
3547
|
-
child.on(
|
|
3548
|
-
"close",
|
|
3549
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3550
|
-
);
|
|
3551
|
-
});
|
|
3552
|
-
}
|
|
3553
|
-
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3554
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3555
|
-
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3556
|
-
return {
|
|
3557
|
-
ok: false,
|
|
3558
|
-
reason: `entrypoint "${entrypoint}" is not a plain file path`
|
|
3559
|
-
};
|
|
3560
|
-
}
|
|
3561
|
-
const target = resolve3(worktreePath, entrypoint);
|
|
3562
|
-
const rel = relative2(worktreePath, target);
|
|
3563
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
3564
|
-
return {
|
|
3565
|
-
ok: false,
|
|
3566
|
-
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3567
|
-
};
|
|
3568
|
-
}
|
|
3569
|
-
return { ok: true, target };
|
|
3570
|
-
}
|
|
3571
|
-
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3572
|
-
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
3573
|
-
return {
|
|
3574
|
-
ran: false,
|
|
3575
|
-
ok: false,
|
|
3576
|
-
output: "",
|
|
3577
|
-
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
3581
|
-
if (!validated.ok) {
|
|
3582
|
-
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3583
|
-
}
|
|
3584
|
-
try {
|
|
3585
|
-
if (!(await stat2(validated.target)).isFile()) {
|
|
3586
|
-
return {
|
|
3587
|
-
ran: false,
|
|
3588
|
-
ok: false,
|
|
3589
|
-
output: "",
|
|
3590
|
-
reason: `entrypoint "${entrypoint}" is not a file`
|
|
3591
|
-
};
|
|
3592
|
-
}
|
|
3593
|
-
} catch {
|
|
3594
|
-
return {
|
|
3595
|
-
ran: false,
|
|
3596
|
-
ok: false,
|
|
3597
|
-
output: "",
|
|
3598
|
-
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3599
|
-
};
|
|
3600
|
-
}
|
|
3601
|
-
return new Promise((resolveRun) => {
|
|
3602
|
-
let output = "";
|
|
3603
|
-
const child = spawn3(runtime, [entrypoint], {
|
|
3604
|
-
cwd: worktreePath,
|
|
3605
|
-
shell: false,
|
|
3606
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3607
|
-
env: { ...process.env, ...env }
|
|
3608
|
-
});
|
|
3609
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3610
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3611
|
-
child.on(
|
|
3612
|
-
"error",
|
|
3613
|
-
(err) => resolveRun({
|
|
3614
|
-
ran: true,
|
|
3615
|
-
ok: false,
|
|
3616
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3617
|
-
})
|
|
3618
|
-
);
|
|
3619
|
-
child.on(
|
|
3620
|
-
"close",
|
|
3621
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3622
|
-
);
|
|
3623
|
-
});
|
|
3624
|
-
}
|
|
3625
3754
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3626
3755
|
const trimmed = sourcePath.trim();
|
|
3627
3756
|
if (!trimmed) {
|
|
@@ -3635,8 +3764,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3635
3764
|
} catch {
|
|
3636
3765
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3637
3766
|
}
|
|
3638
|
-
const relPath =
|
|
3639
|
-
const dest =
|
|
3767
|
+
const relPath = join8(ingestDir, basename2(source));
|
|
3768
|
+
const dest = join8(worktreePath, relPath);
|
|
3640
3769
|
try {
|
|
3641
3770
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3642
3771
|
await copyFile(source, dest);
|
|
@@ -3651,11 +3780,28 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3651
3780
|
function hasEnvVar(content, name) {
|
|
3652
3781
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3653
3782
|
}
|
|
3783
|
+
async function readEnvVar(worktreePath, name) {
|
|
3784
|
+
let content;
|
|
3785
|
+
try {
|
|
3786
|
+
content = await readFile6(join8(worktreePath, ".env"), "utf8");
|
|
3787
|
+
} catch (err) {
|
|
3788
|
+
if (err.code !== "ENOENT") throw err;
|
|
3789
|
+
return void 0;
|
|
3790
|
+
}
|
|
3791
|
+
const match = new RegExp(
|
|
3792
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3793
|
+
"m"
|
|
3794
|
+
).exec(content);
|
|
3795
|
+
if (!match) return void 0;
|
|
3796
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3797
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3798
|
+
return value;
|
|
3799
|
+
}
|
|
3654
3800
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3655
|
-
const target =
|
|
3801
|
+
const target = join8(worktreePath, ".env");
|
|
3656
3802
|
let existing = "";
|
|
3657
3803
|
try {
|
|
3658
|
-
existing = await
|
|
3804
|
+
existing = await readFile6(target, "utf8");
|
|
3659
3805
|
} catch (err) {
|
|
3660
3806
|
if (err.code !== "ENOENT") throw err;
|
|
3661
3807
|
}
|
|
@@ -3724,15 +3870,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3724
3870
|
}
|
|
3725
3871
|
|
|
3726
3872
|
// src/lib/algoliaDocs.ts
|
|
3727
|
-
import { readFileSync, readdirSync, existsSync
|
|
3728
|
-
import { dirname as dirname8, join as
|
|
3873
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
3874
|
+
import { dirname as dirname8, join as join9 } from "node:path";
|
|
3729
3875
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3730
|
-
var DOCS_SUBPATH =
|
|
3876
|
+
var DOCS_SUBPATH = join9("docs", "algolia-sdk");
|
|
3731
3877
|
function findDocsDir() {
|
|
3732
3878
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3733
3879
|
for (; ; ) {
|
|
3734
|
-
const candidate =
|
|
3735
|
-
if (
|
|
3880
|
+
const candidate = join9(dir, DOCS_SUBPATH);
|
|
3881
|
+
if (existsSync(candidate)) return candidate;
|
|
3736
3882
|
const parent = dirname8(dir);
|
|
3737
3883
|
if (parent === dir) return void 0;
|
|
3738
3884
|
dir = parent;
|
|
@@ -3754,7 +3900,7 @@ function loadAlgoliaDoc(language) {
|
|
|
3754
3900
|
);
|
|
3755
3901
|
return "";
|
|
3756
3902
|
}
|
|
3757
|
-
return readFileSync(
|
|
3903
|
+
return readFileSync(join9(docsDir, files[0]), "utf8").trim();
|
|
3758
3904
|
}
|
|
3759
3905
|
function getNamedDoc(name, language) {
|
|
3760
3906
|
const docsDir = findDocsDir();
|
|
@@ -3762,14 +3908,15 @@ function getNamedDoc(name, language) {
|
|
|
3762
3908
|
logger.warn("docs/algolia-sdk not found");
|
|
3763
3909
|
return "";
|
|
3764
3910
|
}
|
|
3765
|
-
const file =
|
|
3766
|
-
if (!
|
|
3911
|
+
const file = join9(docsDir, `${name}-${language}.md`);
|
|
3912
|
+
if (!existsSync(file)) {
|
|
3767
3913
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
3768
3914
|
return "";
|
|
3769
3915
|
}
|
|
3770
3916
|
return readFileSync(file, "utf8").trim();
|
|
3771
3917
|
}
|
|
3772
3918
|
function getFrameworkSpecificDoc(frameworks) {
|
|
3919
|
+
if (frameworks.length === 0) return "";
|
|
3773
3920
|
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3774
3921
|
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3775
3922
|
return loadAlgoliaDoc("vue");
|
|
@@ -3777,9 +3924,6 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
3777
3924
|
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3778
3925
|
return loadAlgoliaDoc("react");
|
|
3779
3926
|
}
|
|
3780
|
-
if (fw.includes("angular")) {
|
|
3781
|
-
return loadAlgoliaDoc("angular");
|
|
3782
|
-
}
|
|
3783
3927
|
return loadAlgoliaDoc("js");
|
|
3784
3928
|
}
|
|
3785
3929
|
|
|
@@ -3807,11 +3951,7 @@ var implementSchema = z26.object({
|
|
|
3807
3951
|
});
|
|
3808
3952
|
var implementationOutputSchema = z26.object({
|
|
3809
3953
|
summary: z26.string(),
|
|
3810
|
-
|
|
3811
|
-
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
3812
|
-
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
3813
|
-
runtime: z26.enum(INGEST_RUNTIMES).optional(),
|
|
3814
|
-
entrypoint: z26.string().optional()
|
|
3954
|
+
ingestCommand: z26.string().optional()
|
|
3815
3955
|
});
|
|
3816
3956
|
var verificationOutputSchema = z26.object({
|
|
3817
3957
|
summary: z26.string(),
|
|
@@ -3821,30 +3961,35 @@ var verificationOutputSchema = z26.object({
|
|
|
3821
3961
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3822
3962
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3823
3963
|
var INGEST_DIR = ".algolia-wizard";
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3828
|
-
return "React";
|
|
3829
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3830
|
-
return "JavaScript";
|
|
3831
|
-
}
|
|
3832
|
-
function frameworksForDoc(framework) {
|
|
3833
|
-
switch (framework) {
|
|
3834
|
-
case "React":
|
|
3835
|
-
return ["react"];
|
|
3836
|
-
case "Vue":
|
|
3837
|
-
return ["vue"];
|
|
3838
|
-
case "Angular":
|
|
3839
|
-
return ["angular"];
|
|
3840
|
-
case "JavaScript":
|
|
3841
|
-
return [];
|
|
3842
|
-
}
|
|
3964
|
+
var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
|
|
3965
|
+
function lower(entries) {
|
|
3966
|
+
return entries.map((entry) => entry.name.toLowerCase());
|
|
3843
3967
|
}
|
|
3844
|
-
function
|
|
3845
|
-
|
|
3846
|
-
(
|
|
3968
|
+
function isJsProject(language) {
|
|
3969
|
+
return lower(language.languages).some(
|
|
3970
|
+
(name) => JS_LANGUAGES.some((js) => name.includes(js))
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
var UI_FRAMEWORKS = [
|
|
3974
|
+
{ match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
|
|
3975
|
+
{ match: ["react", "next"], target: "React", doc: "react" },
|
|
3976
|
+
{ match: ["angular"], target: "Angular" }
|
|
3977
|
+
];
|
|
3978
|
+
function matchUiFramework(language) {
|
|
3979
|
+
const names = lower(language.frameworks);
|
|
3980
|
+
return UI_FRAMEWORKS.find(
|
|
3981
|
+
(ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
|
|
3847
3982
|
);
|
|
3983
|
+
}
|
|
3984
|
+
function searchUiTarget(language) {
|
|
3985
|
+
return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
|
|
3986
|
+
}
|
|
3987
|
+
function frameworksForDoc(language) {
|
|
3988
|
+
if (!isJsProject(language)) return [];
|
|
3989
|
+
return [matchUiFramework(language)?.doc ?? "js"];
|
|
3990
|
+
}
|
|
3991
|
+
function publicEnvPrefix(language) {
|
|
3992
|
+
const frameworkNames = lower(language.frameworks);
|
|
3848
3993
|
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3849
3994
|
return "NEXT_PUBLIC_";
|
|
3850
3995
|
}
|
|
@@ -3857,26 +4002,49 @@ function publicEnvPrefix(language) {
|
|
|
3857
4002
|
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3858
4003
|
return "VITE_";
|
|
3859
4004
|
}
|
|
3860
|
-
return "PUBLIC_";
|
|
4005
|
+
return isJsProject(language) ? "PUBLIC_" : "";
|
|
4006
|
+
}
|
|
4007
|
+
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4008
|
+
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4009
|
+
var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
4010
|
+
function appIdVar(language) {
|
|
4011
|
+
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4012
|
+
}
|
|
4013
|
+
function searchKeyVar(language) {
|
|
4014
|
+
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3861
4015
|
}
|
|
3862
|
-
function
|
|
3863
|
-
|
|
4016
|
+
function searchIndexVar(language) {
|
|
4017
|
+
return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
|
|
4018
|
+
}
|
|
4019
|
+
function searchEnvVars(language, index, appId, searchKey) {
|
|
3864
4020
|
return [
|
|
3865
4021
|
{
|
|
3866
|
-
name:
|
|
4022
|
+
name: appIdVar(language),
|
|
3867
4023
|
value: appId ?? "<your-algolia-app-id>"
|
|
3868
4024
|
},
|
|
3869
4025
|
{
|
|
3870
|
-
name:
|
|
4026
|
+
name: searchKeyVar(language),
|
|
3871
4027
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
4028
|
+
},
|
|
4029
|
+
// Wizard-supplied rather than written into the generated code, because an
|
|
4030
|
+
// agent that retypes the name (appending the project name, re-casing it)
|
|
4031
|
+
// leaves the UI querying an index that does not exist.
|
|
4032
|
+
{
|
|
4033
|
+
name: searchIndexVar(language),
|
|
4034
|
+
value: index
|
|
3872
4035
|
}
|
|
3873
4036
|
];
|
|
3874
4037
|
}
|
|
3875
4038
|
function baseInstructions(input) {
|
|
3876
4039
|
return [
|
|
3877
|
-
|
|
4040
|
+
// Agents have renamed this (e.g. appending the project name), which the
|
|
4041
|
+
// index-scoped keys then reject with a 403.
|
|
4042
|
+
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
3878
4043
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
3879
|
-
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
4044
|
+
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
4045
|
+
`Commands run through a shell on ${process.platform}. Write commands that work there.`,
|
|
4046
|
+
"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.",
|
|
4047
|
+
"runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
|
|
3880
4048
|
];
|
|
3881
4049
|
}
|
|
3882
4050
|
function sourceSpecificInstructions(input) {
|
|
@@ -3896,43 +4064,62 @@ function sourceSpecificInstructions(input) {
|
|
|
3896
4064
|
generated: [
|
|
3897
4065
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3898
4066
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
3899
|
-
"In the script, read and parse each returned file path at runtime
|
|
4067
|
+
"In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
|
|
3900
4068
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3901
4069
|
]
|
|
3902
4070
|
};
|
|
3903
4071
|
return byLine[input.ingestionSource];
|
|
3904
4072
|
}
|
|
4073
|
+
function algoliaClientDoc(input) {
|
|
4074
|
+
const doc = getNamedDoc("save-records", "js");
|
|
4075
|
+
if (!doc) return [];
|
|
4076
|
+
if (isJsProject(input.language)) return [doc];
|
|
4077
|
+
return [
|
|
4078
|
+
"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:",
|
|
4079
|
+
doc
|
|
4080
|
+
];
|
|
4081
|
+
}
|
|
3905
4082
|
function ingestionInstructions(input) {
|
|
3906
4083
|
return [
|
|
3907
4084
|
...input.confirmed && input.confirmed.length ? [
|
|
3908
4085
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3909
4086
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3910
4087
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3911
|
-
|
|
4088
|
+
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
4089
|
+
"Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
|
|
3912
4090
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
3913
|
-
|
|
3914
|
-
'
|
|
4091
|
+
...algoliaClientDoc(input),
|
|
4092
|
+
"Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
|
|
4093
|
+
'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
|
|
3915
4094
|
"The summary should be extremely concise.",
|
|
3916
|
-
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
3917
4095
|
...sourceSpecificInstructions(input)
|
|
3918
4096
|
] : []
|
|
3919
4097
|
];
|
|
3920
4098
|
}
|
|
3921
4099
|
function searchInstructions(input) {
|
|
3922
|
-
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.
|
|
4100
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
|
|
3923
4101
|
return [
|
|
3924
4102
|
"Implement an in-app Algolia search experience.",
|
|
3925
|
-
`Build the search UI for ${input.
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
4103
|
+
`Build the search UI for ${input.searchUiTarget}.`,
|
|
4104
|
+
...doc ? [
|
|
4105
|
+
"Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
|
|
4106
|
+
doc
|
|
4107
|
+
] : [
|
|
4108
|
+
"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."
|
|
4109
|
+
],
|
|
4110
|
+
`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.`,
|
|
4111
|
+
`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.`,
|
|
4112
|
+
"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.",
|
|
4113
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4114
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4115
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4116
|
+
// would be reused as if it were real.
|
|
4117
|
+
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
3931
4118
|
// Not the agent's to rename: the wizard writes these exact names into
|
|
3932
4119
|
// ".env" right after this step, so a renamed prefix would leave the code
|
|
3933
4120
|
// reading a var the wizard never wrote.
|
|
3934
|
-
`Use exactly these
|
|
3935
|
-
|
|
4121
|
+
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4122
|
+
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
3936
4123
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3937
4124
|
];
|
|
3938
4125
|
}
|
|
@@ -3940,11 +4127,12 @@ function verificationInstructions(input) {
|
|
|
3940
4127
|
return [
|
|
3941
4128
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3942
4129
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3943
|
-
"
|
|
3944
|
-
"
|
|
3945
|
-
"
|
|
4130
|
+
"Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
|
|
4131
|
+
"This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4132
|
+
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4133
|
+
"Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
3946
4134
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3947
|
-
`Do not modify "${input.ingestDir}/" unless
|
|
4135
|
+
`Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
|
|
3948
4136
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3949
4137
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3950
4138
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3965,14 +4153,15 @@ var IMPLEMENT_CONFIG = {
|
|
|
3965
4153
|
}
|
|
3966
4154
|
};
|
|
3967
4155
|
var useCaseToolMap = {
|
|
3968
|
-
ingestion: [
|
|
3969
|
-
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
3970
|
-
verification: [
|
|
4156
|
+
ingestion: [
|
|
3971
4157
|
...FS_READ_TOOLS,
|
|
3972
4158
|
"writeFile",
|
|
3973
|
-
"
|
|
4159
|
+
"writeCredentials",
|
|
4160
|
+
"runShell",
|
|
3974
4161
|
"notifyUser"
|
|
3975
|
-
]
|
|
4162
|
+
],
|
|
4163
|
+
search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
|
|
4164
|
+
verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
|
|
3976
4165
|
};
|
|
3977
4166
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
3978
4167
|
const tools = useCaseToolMap[useCase];
|
|
@@ -3995,15 +4184,30 @@ function formatSummary(useCase, summary) {
|
|
|
3995
4184
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3996
4185
|
return `${label}: ${summary}`;
|
|
3997
4186
|
}
|
|
3998
|
-
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3999
|
-
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4000
|
-
}
|
|
4001
4187
|
function parseIngestRecordCount(output) {
|
|
4002
4188
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
4003
4189
|
if (!match) return void 0;
|
|
4004
4190
|
const count = Number(match[1]);
|
|
4005
4191
|
return Number.isFinite(count) ? count : void 0;
|
|
4006
4192
|
}
|
|
4193
|
+
function ingestOutcome(executions, ingestCommand) {
|
|
4194
|
+
const newestFirst = [...executions].reverse();
|
|
4195
|
+
const withCount = newestFirst.filter(
|
|
4196
|
+
(e) => parseIngestRecordCount(e.output ?? "") != null
|
|
4197
|
+
);
|
|
4198
|
+
const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
|
|
4199
|
+
return {
|
|
4200
|
+
run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
|
|
4201
|
+
recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
function makeToolContext(worktree, env = async () => ({})) {
|
|
4205
|
+
return createToolContext(
|
|
4206
|
+
DEFAULT_TOOL_LIMITS,
|
|
4207
|
+
worktree,
|
|
4208
|
+
createShellContext({ env, approve: storeApproval(worktree) })
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4007
4211
|
function verificationRetryInstructions(verification) {
|
|
4008
4212
|
return [
|
|
4009
4213
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
@@ -4082,17 +4286,12 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4082
4286
|
const confirmed2 = normalized.confirmedEntities;
|
|
4083
4287
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4084
4288
|
let appId;
|
|
4085
|
-
let
|
|
4289
|
+
let ingestAppId;
|
|
4086
4290
|
if (useCases.includes("search")) {
|
|
4087
4291
|
appId = (await requireApplication()).id;
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
logger.warn(
|
|
4092
|
-
{ err: err.message },
|
|
4093
|
-
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
4094
|
-
);
|
|
4095
|
-
}
|
|
4292
|
+
}
|
|
4293
|
+
if (useCases.includes("ingestion")) {
|
|
4294
|
+
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4096
4295
|
}
|
|
4097
4296
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
4098
4297
|
try {
|
|
@@ -4124,51 +4323,60 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4124
4323
|
targetIndex,
|
|
4125
4324
|
language,
|
|
4126
4325
|
appId,
|
|
4127
|
-
|
|
4128
|
-
|
|
4326
|
+
// Names only: the search-only key is provisioned after verification, so
|
|
4327
|
+
// every value here is still a placeholder when the agent reads them.
|
|
4328
|
+
searchEnvVars: searchEnvVars(language, targetIndex, appId),
|
|
4129
4329
|
ingestDir: INGEST_DIR,
|
|
4130
4330
|
ingestionSource,
|
|
4131
4331
|
uploadFilePath,
|
|
4132
|
-
|
|
4332
|
+
searchUiTarget: searchUiTarget(language)
|
|
4133
4333
|
};
|
|
4134
4334
|
const summaries = [];
|
|
4135
4335
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4336
|
+
let envSearchKey;
|
|
4337
|
+
let envAppIdMismatch = false;
|
|
4338
|
+
if (useCases.includes("search") && appId) {
|
|
4339
|
+
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4340
|
+
if (envAppId === appId) {
|
|
4341
|
+
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4342
|
+
} else if (envAppId) {
|
|
4343
|
+
envAppIdMismatch = true;
|
|
4344
|
+
summaries.push(
|
|
4345
|
+
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
4346
|
+
);
|
|
4347
|
+
logger.warn(
|
|
4348
|
+
{ envAppId, appId },
|
|
4349
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
4136
4354
|
let agentRuns = 0;
|
|
4137
|
-
let
|
|
4138
|
-
let ingestEntrypoint;
|
|
4355
|
+
let ingestCommand;
|
|
4139
4356
|
let ingestScriptRan = false;
|
|
4140
4357
|
let ingestRecordCount;
|
|
4141
4358
|
let ingestDurationMs;
|
|
4142
|
-
let installFailed = false;
|
|
4143
4359
|
let ingestOutcomeMessage;
|
|
4360
|
+
const ingestKeyAppId = ingestAppId;
|
|
4361
|
+
const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
|
|
4362
|
+
[APP_ID_VAR]: ingestKeyAppId,
|
|
4363
|
+
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4364
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
4365
|
+
})) : void 0;
|
|
4366
|
+
const searchTools = makeToolContext(worktree);
|
|
4144
4367
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4145
4368
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4146
4369
|
agentRuns += 1;
|
|
4147
|
-
|
|
4370
|
+
return runAgent({
|
|
4148
4371
|
instructions: buildAgentInstructions(
|
|
4149
4372
|
currentUseCase,
|
|
4150
4373
|
input,
|
|
4151
4374
|
extraInstructions
|
|
4152
4375
|
),
|
|
4153
4376
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4154
|
-
outputSchema: implementationOutputSchema
|
|
4155
|
-
|
|
4156
|
-
ctx.notify({
|
|
4157
|
-
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4377
|
+
outputSchema: implementationOutputSchema,
|
|
4378
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4158
4379
|
});
|
|
4159
|
-
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4160
|
-
useCase: currentUseCase
|
|
4161
|
-
});
|
|
4162
|
-
const install = await installWorktreeDeps(worktree);
|
|
4163
|
-
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4164
|
-
if (!install.ok) {
|
|
4165
|
-
installFailed = true;
|
|
4166
|
-
logger.warn(
|
|
4167
|
-
{ useCase: currentUseCase, output: install.output },
|
|
4168
|
-
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
4169
|
-
);
|
|
4170
|
-
}
|
|
4171
|
-
return result;
|
|
4172
4380
|
}
|
|
4173
4381
|
async function runVerificationUseCase() {
|
|
4174
4382
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4176,110 +4384,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4176
4384
|
return runAgent({
|
|
4177
4385
|
instructions: buildAgentInstructions("verification", input),
|
|
4178
4386
|
tools: toolsForUseCase("verification"),
|
|
4179
|
-
outputSchema: verificationOutputSchema
|
|
4387
|
+
outputSchema: verificationOutputSchema,
|
|
4388
|
+
toolContext: searchTools
|
|
4180
4389
|
});
|
|
4181
4390
|
}
|
|
4182
4391
|
if (useCases.includes("ingestion")) {
|
|
4183
|
-
const
|
|
4184
|
-
summaries.push(formatSummary("ingestion", summary));
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
entrypoint: ingestEntrypoint
|
|
4392
|
+
const result = await runImplementationUseCase("ingestion");
|
|
4393
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
4394
|
+
ingestCommand = result.ingestCommand;
|
|
4395
|
+
const executions = (ingestionTools ?? searchTools).shell.executions;
|
|
4396
|
+
const { run: ingestRun, recordCount } = ingestOutcome(
|
|
4397
|
+
executions,
|
|
4398
|
+
ingestCommand
|
|
4399
|
+
);
|
|
4400
|
+
ingestScriptRan = ingestRun != null;
|
|
4401
|
+
ingestRecordCount = recordCount;
|
|
4402
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
4403
|
+
if (ingestScriptRan) {
|
|
4404
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4405
|
+
if (ingestRecordCount != null) {
|
|
4406
|
+
track("AI Wizard Ingest Successful", {
|
|
4407
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4408
|
+
record_count: ingestRecordCount,
|
|
4409
|
+
duration_ms: ingestDurationMs ?? 0
|
|
4202
4410
|
});
|
|
4203
|
-
const startedAt = Date.now();
|
|
4204
|
-
const run2 = await runIngestScript(
|
|
4205
|
-
worktree,
|
|
4206
|
-
ingestRuntime,
|
|
4207
|
-
ingestEntrypoint,
|
|
4208
|
-
{
|
|
4209
|
-
[APP_ID_VAR]: ingestApp.id,
|
|
4210
|
-
[API_KEY_VAR]: writeKey
|
|
4211
|
-
}
|
|
4212
|
-
);
|
|
4213
|
-
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
4214
|
-
ingestScriptRan = run2.ran && run2.ok;
|
|
4215
|
-
if (ingestScriptRan) {
|
|
4216
|
-
ingestDurationMs = Date.now() - startedAt;
|
|
4217
|
-
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4218
|
-
if (ingestRecordCount != null) {
|
|
4219
|
-
track("AI Wizard Ingest Successful", {
|
|
4220
|
-
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4221
|
-
record_count: ingestRecordCount,
|
|
4222
|
-
duration_ms: ingestDurationMs
|
|
4223
|
-
});
|
|
4224
|
-
}
|
|
4225
|
-
}
|
|
4226
|
-
let summaryLine;
|
|
4227
|
-
let outcomeMessage;
|
|
4228
|
-
if (!run2.ran) {
|
|
4229
|
-
summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
|
|
4230
|
-
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4231
|
-
logger.warn(
|
|
4232
|
-
{
|
|
4233
|
-
runtime: ingestRuntime,
|
|
4234
|
-
entrypoint: ingestEntrypoint,
|
|
4235
|
-
reason: run2.reason
|
|
4236
|
-
},
|
|
4237
|
-
"implement: refused to auto-run ingestion script"
|
|
4238
|
-
);
|
|
4239
|
-
track("Error", {
|
|
4240
|
-
step: "Push Data",
|
|
4241
|
-
error: `ingestion script skipped: ${run2.reason}`,
|
|
4242
|
-
product_area: "AI Wizard"
|
|
4243
|
-
});
|
|
4244
|
-
} else if (run2.ok) {
|
|
4245
|
-
const status = "Ingestion run: succeeded.";
|
|
4246
|
-
summaryLine = run2.output ? `${status}
|
|
4247
|
-
${run2.output}` : status;
|
|
4248
|
-
outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4249
|
-
} else {
|
|
4250
|
-
const status = "\u26A0\uFE0F Ingestion run failed:";
|
|
4251
|
-
summaryLine = run2.output ? `${status}
|
|
4252
|
-
${run2.output}` : status;
|
|
4253
|
-
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4254
|
-
logger.warn(
|
|
4255
|
-
{
|
|
4256
|
-
runtime: ingestRuntime,
|
|
4257
|
-
entrypoint: ingestEntrypoint,
|
|
4258
|
-
output: run2.output
|
|
4259
|
-
},
|
|
4260
|
-
"implement: ingestion script run failed"
|
|
4261
|
-
);
|
|
4262
|
-
track("Error", {
|
|
4263
|
-
step: "Push Data",
|
|
4264
|
-
error: run2.output || "ingestion script exited non-zero",
|
|
4265
|
-
product_area: "AI Wizard"
|
|
4266
|
-
});
|
|
4267
|
-
}
|
|
4268
|
-
summaries.push(summaryLine);
|
|
4269
|
-
ingestOutcomeMessage = outcomeMessage;
|
|
4270
4411
|
}
|
|
4412
|
+
} else {
|
|
4413
|
+
const rejected = executions.some((e) => !e.approved);
|
|
4414
|
+
const reason = rejected ? "you declined to run it" : "no successful run was recorded";
|
|
4415
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4416
|
+
summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
|
|
4417
|
+
logger.warn(
|
|
4418
|
+
{ ingestCommand, rejected, commandsRun: executions.length },
|
|
4419
|
+
"implement: ingestion script did not complete successfully"
|
|
4420
|
+
);
|
|
4421
|
+
track("Error", {
|
|
4422
|
+
step: "Push Data",
|
|
4423
|
+
error: `ingestion did not run: ${reason}`,
|
|
4424
|
+
product_area: "AI Wizard"
|
|
4425
|
+
});
|
|
4271
4426
|
}
|
|
4272
4427
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4273
|
-
if (
|
|
4274
|
-
commandMessages.push(
|
|
4275
|
-
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4276
|
-
);
|
|
4428
|
+
if (ingestCommand) {
|
|
4429
|
+
commandMessages.push(`Ingestion command: ${ingestCommand}`);
|
|
4277
4430
|
}
|
|
4278
4431
|
await ctx.requestUserInput({
|
|
4279
4432
|
prompt: "",
|
|
4280
4433
|
promptType: "enterToContinue",
|
|
4281
4434
|
options: [],
|
|
4282
|
-
messages:
|
|
4435
|
+
messages: [ingestOutcomeMessage, ...commandMessages]
|
|
4283
4436
|
});
|
|
4284
4437
|
}
|
|
4285
4438
|
if (useCases.includes("search")) {
|
|
@@ -4324,7 +4477,34 @@ ${run2.output}` : status;
|
|
|
4324
4477
|
}
|
|
4325
4478
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4326
4479
|
}
|
|
4327
|
-
|
|
4480
|
+
let searchKey;
|
|
4481
|
+
let searchKeyError;
|
|
4482
|
+
if (appId) {
|
|
4483
|
+
try {
|
|
4484
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
4485
|
+
targetIndex,
|
|
4486
|
+
appId,
|
|
4487
|
+
envSearchKey
|
|
4488
|
+
);
|
|
4489
|
+
searchKey = resolved2.key;
|
|
4490
|
+
summaries.push(
|
|
4491
|
+
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4492
|
+
);
|
|
4493
|
+
} catch (err) {
|
|
4494
|
+
searchKeyError = err.message;
|
|
4495
|
+
logger.warn(
|
|
4496
|
+
{ err: searchKeyError },
|
|
4497
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4498
|
+
);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
finalSearchEnvVars = searchEnvVars(
|
|
4502
|
+
language,
|
|
4503
|
+
targetIndex,
|
|
4504
|
+
appId,
|
|
4505
|
+
searchKey
|
|
4506
|
+
);
|
|
4507
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4328
4508
|
(v) => !v.value.startsWith("<")
|
|
4329
4509
|
);
|
|
4330
4510
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4335,13 +4515,29 @@ ${run2.output}` : status;
|
|
|
4335
4515
|
if (written.length > 0) {
|
|
4336
4516
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4337
4517
|
}
|
|
4518
|
+
const stale = [];
|
|
4519
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4520
|
+
if (written.includes(v.name)) continue;
|
|
4521
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4522
|
+
if (current && current !== v.value) stale.push(v);
|
|
4523
|
+
}
|
|
4524
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4525
|
+
summaries.push(
|
|
4526
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4527
|
+
);
|
|
4528
|
+
logger.warn(
|
|
4529
|
+
{ vars: stale.map((v) => v.name) },
|
|
4530
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4338
4533
|
}
|
|
4339
|
-
const unresolvedSearchEnvVars =
|
|
4534
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4340
4535
|
(v) => v.value.startsWith("<")
|
|
4341
4536
|
);
|
|
4342
4537
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4343
4538
|
summaries.push(
|
|
4344
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4539
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
|
|
4540
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4345
4541
|
);
|
|
4346
4542
|
}
|
|
4347
4543
|
} else {
|
|
@@ -4353,27 +4549,18 @@ ${run2.output}` : status;
|
|
|
4353
4549
|
"implement: agent reported success but no files changed in the worktree"
|
|
4354
4550
|
);
|
|
4355
4551
|
}
|
|
4356
|
-
if (installFailed) {
|
|
4357
|
-
summaries.push(
|
|
4358
|
-
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
4359
|
-
);
|
|
4360
|
-
}
|
|
4361
4552
|
return {
|
|
4362
4553
|
ingestionSource,
|
|
4363
4554
|
filesChanged,
|
|
4364
4555
|
summary: summaries.join("\n\n"),
|
|
4365
4556
|
worktreePath: worktree,
|
|
4366
|
-
...useCases.includes("ingestion") &&
|
|
4367
|
-
ingestCommand
|
|
4368
|
-
worktree,
|
|
4369
|
-
ingestRuntime,
|
|
4370
|
-
ingestEntrypoint
|
|
4371
|
-
),
|
|
4557
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4558
|
+
ingestCommand,
|
|
4372
4559
|
ingestScriptRan,
|
|
4373
4560
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4374
4561
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4375
4562
|
} : {},
|
|
4376
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4563
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4377
4564
|
};
|
|
4378
4565
|
} finally {
|
|
4379
4566
|
process.chdir(repoRoot);
|
|
@@ -4696,10 +4883,11 @@ function parseCliArgs(argv) {
|
|
|
4696
4883
|
|
|
4697
4884
|
// src/lib/resetState.ts
|
|
4698
4885
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4699
|
-
import { join as
|
|
4886
|
+
import { join as join10 } from "node:path";
|
|
4700
4887
|
var KEEP = ["wizard.log"];
|
|
4701
4888
|
async function resetProjectState() {
|
|
4702
4889
|
const dir = stateDir();
|
|
4890
|
+
forgetResolvedKeys();
|
|
4703
4891
|
let entries;
|
|
4704
4892
|
try {
|
|
4705
4893
|
entries = await readdir4(dir);
|
|
@@ -4708,14 +4896,17 @@ async function resetProjectState() {
|
|
|
4708
4896
|
}
|
|
4709
4897
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4710
4898
|
await Promise.all(
|
|
4711
|
-
targets.map(
|
|
4899
|
+
targets.map(
|
|
4900
|
+
(name) => rm2(join10(dir, name), { recursive: true, force: true })
|
|
4901
|
+
)
|
|
4712
4902
|
);
|
|
4713
4903
|
return { dir, removed: targets };
|
|
4714
4904
|
}
|
|
4715
4905
|
|
|
4716
4906
|
// src/main.tsx
|
|
4717
|
-
import { jsx as
|
|
4907
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
4718
4908
|
async function startup() {
|
|
4909
|
+
setProjectRoot(process.cwd());
|
|
4719
4910
|
let args;
|
|
4720
4911
|
try {
|
|
4721
4912
|
args = parseCliArgs(process.argv.slice(2));
|
|
@@ -4763,7 +4954,7 @@ ${formatStepList(workflow)}`);
|
|
|
4763
4954
|
}
|
|
4764
4955
|
async function run(workflow) {
|
|
4765
4956
|
const store = useWizard.getState();
|
|
4766
|
-
const instance = render(/* @__PURE__ */
|
|
4957
|
+
const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
|
|
4767
4958
|
await store.waitForStart();
|
|
4768
4959
|
let user = await getUser();
|
|
4769
4960
|
if (!user) {
|