@algolia/wizard 0.9.0-rc.84.75 → 0.9.0-rc.87.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/main.js +1428 -1025
- package/docs/algolia-sdk/README.md +4 -4
- package/docs/algolia-sdk/instantsearch-setup-js.md +11 -0
- package/package.json +1 -3
- package/docs/algolia-sdk/instantsearch-setup-angular.md +0 -80
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";
|
|
@@ -12,32 +12,128 @@ import { nanoid } from "nanoid";
|
|
|
12
12
|
|
|
13
13
|
// src/lib/algoliaCli.ts
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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");
|
|
19
28
|
}
|
|
20
|
-
function
|
|
29
|
+
function projectSlug(cwd = process.cwd()) {
|
|
30
|
+
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
31
|
+
}
|
|
32
|
+
function stateDir(cwd = process.cwd()) {
|
|
33
|
+
return join(rootDir(), projectSlug(cwd));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/lib/logger.ts
|
|
37
|
+
var STDERR_FD = 2;
|
|
38
|
+
function resolveDest() {
|
|
39
|
+
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
42
|
+
closeSync(openSync(target, "a"));
|
|
43
|
+
return target;
|
|
44
|
+
} catch {
|
|
45
|
+
return STDERR_FD;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function logDestination() {
|
|
49
|
+
return pino.destination({ dest: resolveDest(), sync: false });
|
|
50
|
+
}
|
|
51
|
+
var logger = pino(
|
|
52
|
+
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
53
|
+
logDestination()
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// src/lib/algoliaCli.ts
|
|
57
|
+
function npxArgs(args) {
|
|
58
|
+
return ["--yes", "@algolia/cli@latest", ...args];
|
|
59
|
+
}
|
|
60
|
+
var shell = process.platform === "win32";
|
|
61
|
+
function lineSplitter(emit) {
|
|
62
|
+
let buffer = "";
|
|
63
|
+
return {
|
|
64
|
+
push(chunk) {
|
|
65
|
+
buffer += chunk;
|
|
66
|
+
const lines = buffer.split("\n");
|
|
67
|
+
buffer = lines.pop() ?? "";
|
|
68
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
69
|
+
},
|
|
70
|
+
flush() {
|
|
71
|
+
if (buffer) emit(buffer.replace(/\r$/, ""));
|
|
72
|
+
buffer = "";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
var wizardSink = (stream, line) => {
|
|
77
|
+
if (!line.trim()) return;
|
|
78
|
+
useWizard.getState().pushCliOutput(stream, line);
|
|
79
|
+
};
|
|
80
|
+
var stderrSink = (stream, line) => {
|
|
81
|
+
if (stream === "stdout") return;
|
|
82
|
+
wizardSink(stream, line);
|
|
83
|
+
};
|
|
84
|
+
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
85
|
+
const store = useWizard.getState();
|
|
86
|
+
const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
|
|
21
87
|
return new Promise((resolve4, reject) => {
|
|
22
|
-
const child = spawn(
|
|
88
|
+
const child = spawn("npx", npxArgs(args), { shell });
|
|
23
89
|
let stdout = "";
|
|
24
90
|
let stderr = "";
|
|
25
|
-
|
|
26
|
-
|
|
91
|
+
const splitters = {
|
|
92
|
+
stdout: lineSplitter((line) => onOutput?.("stdout", line)),
|
|
93
|
+
stderr: lineSplitter((line) => onOutput?.("stderr", line))
|
|
94
|
+
};
|
|
95
|
+
child.stdout.on("data", (chunk) => {
|
|
96
|
+
const text = String(chunk);
|
|
97
|
+
stdout += text;
|
|
98
|
+
splitters.stdout.push(text);
|
|
99
|
+
});
|
|
100
|
+
child.stderr.on("data", (chunk) => {
|
|
101
|
+
const text = String(chunk);
|
|
102
|
+
stderr += text;
|
|
103
|
+
splitters.stderr.push(text);
|
|
104
|
+
});
|
|
27
105
|
child.on("error", reject);
|
|
28
106
|
child.on("close", (code) => {
|
|
107
|
+
splitters.stdout.flush();
|
|
108
|
+
splitters.stderr.flush();
|
|
29
109
|
if (code === 0) {
|
|
30
110
|
resolve4(stdout);
|
|
31
111
|
} else {
|
|
32
|
-
const
|
|
112
|
+
const failed = stderr.trim();
|
|
113
|
+
let detail = "";
|
|
114
|
+
if (failed) {
|
|
115
|
+
detail = `: ${failed}`;
|
|
116
|
+
} else if (stdout.trim()) {
|
|
117
|
+
detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
|
|
118
|
+
}
|
|
33
119
|
reject(
|
|
34
120
|
new Error(
|
|
35
|
-
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail
|
|
121
|
+
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
|
|
36
122
|
)
|
|
37
123
|
);
|
|
38
124
|
}
|
|
39
125
|
});
|
|
40
|
-
})
|
|
126
|
+
}).then(
|
|
127
|
+
(out) => {
|
|
128
|
+
useWizard.getState().logEnd(logId, "success");
|
|
129
|
+
return out;
|
|
130
|
+
},
|
|
131
|
+
(err) => {
|
|
132
|
+
useWizard.getState().logEnd(logId, "error");
|
|
133
|
+
logger.warn({ err, args }, "Algolia CLI command failed");
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
41
137
|
}
|
|
42
138
|
async function getUser() {
|
|
43
139
|
let raw;
|
|
@@ -52,19 +148,23 @@ async function getUser() {
|
|
|
52
148
|
return null;
|
|
53
149
|
}
|
|
54
150
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
child.on("error", reject);
|
|
63
|
-
child.on("close", (code) => {
|
|
64
|
-
if (code === 0) resolve4();
|
|
65
|
-
else reject(new Error(`Algolia authentication failed (exit ${code}).`));
|
|
66
|
-
});
|
|
151
|
+
var loginResultSchema = z.object({
|
|
152
|
+
success: z.boolean(),
|
|
153
|
+
email: z.string().optional()
|
|
154
|
+
});
|
|
155
|
+
async function runAuthLogin() {
|
|
156
|
+
const raw = await runAlgoliaCli(["auth", "login", "--non-interactive"], {
|
|
157
|
+
onOutput: stderrSink
|
|
67
158
|
});
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = loginResultSchema.safeParse(JSON.parse(raw));
|
|
162
|
+
} catch {
|
|
163
|
+
parsed = void 0;
|
|
164
|
+
}
|
|
165
|
+
if (parsed?.success && !parsed.data.success) {
|
|
166
|
+
throw new Error("Algolia sign-in did not report success.");
|
|
167
|
+
}
|
|
68
168
|
}
|
|
69
169
|
|
|
70
170
|
// src/lib/auth.ts
|
|
@@ -88,45 +188,6 @@ function refreshAuthToken() {
|
|
|
88
188
|
return inFlightRefresh;
|
|
89
189
|
}
|
|
90
190
|
|
|
91
|
-
// src/lib/logger.ts
|
|
92
|
-
import pino from "pino";
|
|
93
|
-
import { join as join2, dirname } from "node:path";
|
|
94
|
-
import { devNull } from "node:os";
|
|
95
|
-
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
96
|
-
|
|
97
|
-
// src/core/constants.ts
|
|
98
|
-
import { homedir } from "node:os";
|
|
99
|
-
import { join, resolve } from "node:path";
|
|
100
|
-
function rootDir() {
|
|
101
|
-
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
102
|
-
}
|
|
103
|
-
function projectSlug(cwd = process.cwd()) {
|
|
104
|
-
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
105
|
-
}
|
|
106
|
-
function stateDir(cwd = process.cwd()) {
|
|
107
|
-
return join(rootDir(), projectSlug(cwd));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// src/lib/logger.ts
|
|
111
|
-
var STDERR_FD = 2;
|
|
112
|
-
function resolveDest() {
|
|
113
|
-
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
114
|
-
try {
|
|
115
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
116
|
-
closeSync(openSync(target, "a"));
|
|
117
|
-
return target;
|
|
118
|
-
} catch {
|
|
119
|
-
return STDERR_FD;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function logDestination() {
|
|
123
|
-
return pino.destination({ dest: resolveDest(), sync: false });
|
|
124
|
-
}
|
|
125
|
-
var logger = pino(
|
|
126
|
-
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
127
|
-
logDestination()
|
|
128
|
-
);
|
|
129
|
-
|
|
130
191
|
// src/lib/proxyFetch.ts
|
|
131
192
|
var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
|
|
132
193
|
var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
|
|
@@ -171,6 +232,7 @@ function describeInputValue(value) {
|
|
|
171
232
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
233
|
}
|
|
173
234
|
var NOTICE_INTERVAL_MS = 2e3;
|
|
235
|
+
var CLI_OUTPUT_LIMIT = 200;
|
|
174
236
|
var useWizard = create((set, get) => ({
|
|
175
237
|
phase: "idle",
|
|
176
238
|
homeScreen: "home",
|
|
@@ -182,22 +244,21 @@ var useWizard = create((set, get) => ({
|
|
|
182
244
|
notices: [],
|
|
183
245
|
_noticeQueue: [],
|
|
184
246
|
_noticeTimer: null,
|
|
247
|
+
cliOutput: [],
|
|
248
|
+
targetIndex: null,
|
|
185
249
|
logs: [],
|
|
186
250
|
error: null,
|
|
187
251
|
inputReq: null,
|
|
188
252
|
_resolve: null,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
253
|
+
// `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
|
|
254
|
+
// welcome screen, so going back would gate the run a second time.
|
|
255
|
+
beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
|
|
256
|
+
endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
|
|
192
257
|
confirmStart: () => set(
|
|
193
258
|
(s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
|
|
194
259
|
),
|
|
195
|
-
// Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
|
|
196
260
|
openLearnMore: () => set({ homeScreen: "learnMore" }),
|
|
197
261
|
backToHome: () => set({ homeScreen: "home" }),
|
|
198
|
-
// Resolves once the phase leaves 'idle', whether that happens before or
|
|
199
|
-
// after this is called (the welcome screen's enter handler is what
|
|
200
|
-
// drives the transition via `confirmStart`).
|
|
201
262
|
waitForStart: () => new Promise((resolve4) => {
|
|
202
263
|
if (get().phase !== "idle") {
|
|
203
264
|
resolve4();
|
|
@@ -220,15 +281,19 @@ var useWizard = create((set, get) => ({
|
|
|
220
281
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
221
282
|
setActiveStep: (index) => {
|
|
222
283
|
get()._clearNoticeQueue();
|
|
223
|
-
set({
|
|
284
|
+
set({
|
|
285
|
+
phase: "running",
|
|
286
|
+
currentStepIndex: index,
|
|
287
|
+
output: "",
|
|
288
|
+
notices: [],
|
|
289
|
+
cliOutput: []
|
|
290
|
+
});
|
|
224
291
|
},
|
|
225
292
|
setUser: (user) => set({ user }),
|
|
226
293
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
227
294
|
clearOutput: () => set({ output: "" }),
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// the timer stays armed through an empty drain so the cooldown always
|
|
231
|
-
// covers the time since the last render, even across bursts.
|
|
295
|
+
// The timer stays armed through an empty drain, so the spacing covers the
|
|
296
|
+
// time since the last render even across bursts.
|
|
232
297
|
pushNotice: (notice) => {
|
|
233
298
|
const { notices, _noticeQueue, _noticeTimer } = get();
|
|
234
299
|
if (_noticeTimer === null) {
|
|
@@ -261,6 +326,13 @@ var useWizard = create((set, get) => ({
|
|
|
261
326
|
get()._clearNoticeQueue();
|
|
262
327
|
set({ notices: [] });
|
|
263
328
|
},
|
|
329
|
+
pushCliOutput: (stream, text) => set((s) => ({
|
|
330
|
+
cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
|
|
331
|
+
-CLI_OUTPUT_LIMIT
|
|
332
|
+
)
|
|
333
|
+
})),
|
|
334
|
+
clearCliOutput: () => set({ cliOutput: [] }),
|
|
335
|
+
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
264
336
|
logStart: (kind, name, input) => {
|
|
265
337
|
const id = nanoid();
|
|
266
338
|
set((s) => ({
|
|
@@ -283,9 +355,6 @@ var useWizard = create((set, get) => ({
|
|
|
283
355
|
_resolve: resolve4
|
|
284
356
|
});
|
|
285
357
|
}),
|
|
286
|
-
// Logs what the user picked — not the prompt text that was shown, which
|
|
287
|
-
// may repeat or duplicate on-screen content and isn't the useful signal
|
|
288
|
-
// here.
|
|
289
358
|
submitInput: async (value) => {
|
|
290
359
|
await markInteraction();
|
|
291
360
|
get()._resolve?.(value);
|
|
@@ -305,6 +374,8 @@ var useWizard = create((set, get) => ({
|
|
|
305
374
|
currentStepIndex: 0,
|
|
306
375
|
output: "",
|
|
307
376
|
notices: [],
|
|
377
|
+
cliOutput: [],
|
|
378
|
+
targetIndex: null,
|
|
308
379
|
logs: [],
|
|
309
380
|
error: null,
|
|
310
381
|
inputReq: null,
|
|
@@ -313,16 +384,100 @@ var useWizard = create((set, get) => ({
|
|
|
313
384
|
}
|
|
314
385
|
}));
|
|
315
386
|
|
|
387
|
+
// src/ui/CliOutput.tsx
|
|
388
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
389
|
+
|
|
390
|
+
// src/ui/theme.ts
|
|
391
|
+
var MARKER = {
|
|
392
|
+
pending: "\u25CB",
|
|
393
|
+
running: "\u25D0",
|
|
394
|
+
done: "\u2713",
|
|
395
|
+
error: "\u2716"
|
|
396
|
+
};
|
|
397
|
+
var BRAND = "#003DFF";
|
|
398
|
+
var SECONDARY = "#5468FF";
|
|
399
|
+
var DANGER = "#F86E7E";
|
|
400
|
+
var COLORS = {
|
|
401
|
+
brand: BRAND,
|
|
402
|
+
primary: "#E6EDF3",
|
|
403
|
+
secondary: SECONDARY,
|
|
404
|
+
strong: "#FFFFFF",
|
|
405
|
+
muted: "#8B949E",
|
|
406
|
+
dim: "#484F58",
|
|
407
|
+
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
408
|
+
badge: "#E3B341",
|
|
409
|
+
danger: DANGER,
|
|
410
|
+
success: "#4ADE80",
|
|
411
|
+
bg: {
|
|
412
|
+
main: "#0B0E14",
|
|
413
|
+
sidebar: "#14171E"
|
|
414
|
+
},
|
|
415
|
+
border: "#30363D",
|
|
416
|
+
accent: "#76A0FF",
|
|
417
|
+
status: {
|
|
418
|
+
pending: "gray",
|
|
419
|
+
running: "#76A0FF",
|
|
420
|
+
done: "#4ADE80",
|
|
421
|
+
error: DANGER
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/ui/CliOutput.tsx
|
|
426
|
+
import { jsxs } from "react/jsx-runtime";
|
|
427
|
+
var CLI_MARKER = "\u203A";
|
|
428
|
+
var RESERVED_ROWS = 16;
|
|
429
|
+
var MAX_ROWS = 12;
|
|
430
|
+
var PANEL_TEXT_WIDTH = 45;
|
|
431
|
+
var URL_PATTERN = /https?:\/\//;
|
|
432
|
+
function rowCost(text) {
|
|
433
|
+
return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
|
|
434
|
+
}
|
|
435
|
+
function CliOutput() {
|
|
436
|
+
const cliOutput = useWizard((s) => s.cliOutput);
|
|
437
|
+
const { rows } = useWindowSize();
|
|
438
|
+
if (!cliOutput.length) return null;
|
|
439
|
+
const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
|
|
440
|
+
const visible = [];
|
|
441
|
+
let usedRows = 0;
|
|
442
|
+
for (let i = cliOutput.length - 1; i >= 0; i--) {
|
|
443
|
+
const cost = rowCost(cliOutput[i].text);
|
|
444
|
+
if (usedRows + cost > rowBudget && visible.length > 0) break;
|
|
445
|
+
visible.unshift(cliOutput[i]);
|
|
446
|
+
usedRows += cost;
|
|
447
|
+
}
|
|
448
|
+
const hidden = cliOutput.length - visible.length;
|
|
449
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
450
|
+
hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
|
|
451
|
+
"\u2191 ",
|
|
452
|
+
hidden,
|
|
453
|
+
" earlier line(s)"
|
|
454
|
+
] }),
|
|
455
|
+
visible.map((line) => /* @__PURE__ */ jsxs(
|
|
456
|
+
Text,
|
|
457
|
+
{
|
|
458
|
+
color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
|
|
459
|
+
wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
|
|
460
|
+
children: [
|
|
461
|
+
CLI_MARKER,
|
|
462
|
+
" ",
|
|
463
|
+
line.text
|
|
464
|
+
]
|
|
465
|
+
},
|
|
466
|
+
line.id
|
|
467
|
+
))
|
|
468
|
+
] });
|
|
469
|
+
}
|
|
470
|
+
|
|
316
471
|
// src/ui/Notices.tsx
|
|
317
|
-
import { Box as
|
|
472
|
+
import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
|
|
318
473
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
319
474
|
|
|
320
475
|
// src/ui/Table.tsx
|
|
321
|
-
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
476
|
+
import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
|
|
322
477
|
import { useEffect, useRef, useState } from "react";
|
|
323
478
|
import { jsx } from "react/jsx-runtime";
|
|
324
479
|
function Table({ columns, rows }) {
|
|
325
|
-
const { columns: termCols } =
|
|
480
|
+
const { columns: termCols } = useWindowSize2();
|
|
326
481
|
const ref = useRef(null);
|
|
327
482
|
const [width, setWidth] = useState(0);
|
|
328
483
|
useEffect(() => {
|
|
@@ -330,7 +485,7 @@ function Table({ columns, rows }) {
|
|
|
330
485
|
}, [termCols, columns, rows]);
|
|
331
486
|
if (rows.length === 0) return null;
|
|
332
487
|
const lines = formatTable(columns, rows, width || void 0);
|
|
333
|
-
return /* @__PURE__ */ jsx(
|
|
488
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
334
489
|
}
|
|
335
490
|
function formatTable(columns, rows, width) {
|
|
336
491
|
const natural = columns.map(
|
|
@@ -370,48 +525,13 @@ function resize(widths, budget) {
|
|
|
370
525
|
}
|
|
371
526
|
var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
|
|
372
527
|
|
|
373
|
-
// src/ui/theme.ts
|
|
374
|
-
var MARKER = {
|
|
375
|
-
pending: "\u25CB",
|
|
376
|
-
running: "\u25D0",
|
|
377
|
-
done: "\u2713",
|
|
378
|
-
error: "\u2716"
|
|
379
|
-
};
|
|
380
|
-
var BRAND = "#003DFF";
|
|
381
|
-
var SECONDARY = "#5468FF";
|
|
382
|
-
var DANGER = "#F86E7E";
|
|
383
|
-
var COLORS = {
|
|
384
|
-
brand: BRAND,
|
|
385
|
-
primary: "#E6EDF3",
|
|
386
|
-
secondary: SECONDARY,
|
|
387
|
-
strong: "#FFFFFF",
|
|
388
|
-
muted: "#8B949E",
|
|
389
|
-
dim: "#484F58",
|
|
390
|
-
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
391
|
-
badge: "#E3B341",
|
|
392
|
-
danger: DANGER,
|
|
393
|
-
success: "#4ADE80",
|
|
394
|
-
bg: {
|
|
395
|
-
main: "#0B0E14",
|
|
396
|
-
sidebar: "#14171E"
|
|
397
|
-
},
|
|
398
|
-
border: "#30363D",
|
|
399
|
-
accent: "#76A0FF",
|
|
400
|
-
status: {
|
|
401
|
-
pending: "gray",
|
|
402
|
-
running: "#76A0FF",
|
|
403
|
-
done: "#4ADE80",
|
|
404
|
-
error: DANGER
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
528
|
// src/ui/Notices.tsx
|
|
409
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
529
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
410
530
|
var AGENT_MARKER = "\u2726";
|
|
411
|
-
var
|
|
412
|
-
var
|
|
531
|
+
var RESERVED_ROWS2 = 14;
|
|
532
|
+
var PANEL_TEXT_WIDTH2 = 45;
|
|
413
533
|
function messageLineCount(text) {
|
|
414
|
-
return Math.max(1, Math.ceil(text.length /
|
|
534
|
+
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
|
|
415
535
|
}
|
|
416
536
|
function noticeLineCount(notice) {
|
|
417
537
|
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
@@ -422,7 +542,7 @@ function noticeLineCount(notice) {
|
|
|
422
542
|
return messageLines + tableLines;
|
|
423
543
|
}
|
|
424
544
|
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
-
const budget = Math.max(windowRows -
|
|
545
|
+
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
426
546
|
let used = 0;
|
|
427
547
|
let count = 0;
|
|
428
548
|
for (let i = notices.length - 1; i >= 0; i--) {
|
|
@@ -455,7 +575,7 @@ function parseHex(hex) {
|
|
|
455
575
|
}
|
|
456
576
|
function Notices() {
|
|
457
577
|
const notices = useWizard((s) => s.notices);
|
|
458
|
-
const { rows: windowRows } =
|
|
578
|
+
const { rows: windowRows } = useWindowSize3();
|
|
459
579
|
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
580
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
581
|
useEffect2(() => {
|
|
@@ -472,14 +592,14 @@ function Notices() {
|
|
|
472
592
|
}, []);
|
|
473
593
|
if (!visible.length) return null;
|
|
474
594
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
-
return /* @__PURE__ */ jsx2(
|
|
595
|
+
return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
596
|
const isLatest = i === visible.length - 1;
|
|
477
|
-
return /* @__PURE__ */
|
|
597
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
478
598
|
notice.messages?.map((m, j) => {
|
|
479
599
|
const line = typeof m === "string" ? { text: m } : m;
|
|
480
600
|
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
-
return /* @__PURE__ */
|
|
482
|
-
|
|
601
|
+
return /* @__PURE__ */ jsxs2(
|
|
602
|
+
Text3,
|
|
483
603
|
{
|
|
484
604
|
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
605
|
bold: line.bold,
|
|
@@ -497,41 +617,99 @@ function Notices() {
|
|
|
497
617
|
}
|
|
498
618
|
|
|
499
619
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
620
|
+
import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
|
|
501
621
|
import TextInput from "ink-text-input";
|
|
502
622
|
import { useState as useState5 } from "react";
|
|
503
623
|
|
|
624
|
+
// src/ui/CommandApproval.tsx
|
|
625
|
+
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
626
|
+
|
|
504
627
|
// src/ui/NextAction.tsx
|
|
505
|
-
import { Box as
|
|
506
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
628
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
629
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
507
630
|
function NextAction({
|
|
508
631
|
action,
|
|
509
632
|
keyHint,
|
|
510
633
|
hierarchy = "primary"
|
|
511
634
|
}) {
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx3(
|
|
514
|
-
hierarchy === "secondary" && /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */ jsx3(
|
|
516
|
-
/* @__PURE__ */ jsx3(
|
|
635
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
636
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
637
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
638
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
639
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
517
640
|
] }),
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */ jsx3(
|
|
520
|
-
/* @__PURE__ */ jsx3(
|
|
521
|
-
/* @__PURE__ */ jsx3(
|
|
522
|
-
/* @__PURE__ */ jsx3(
|
|
641
|
+
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
642
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
|
|
643
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
|
|
644
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
|
|
645
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
|
|
646
|
+
] })
|
|
647
|
+
] });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/ui/CommandApproval.tsx
|
|
651
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
652
|
+
function CommandApproval({
|
|
653
|
+
command,
|
|
654
|
+
onDecide
|
|
655
|
+
}) {
|
|
656
|
+
useInput((input, key) => {
|
|
657
|
+
if (key.return) onDecide("approve");
|
|
658
|
+
else if (key.escape) onDecide("reject");
|
|
659
|
+
else if (input.toLowerCase() === "a") onDecide("always");
|
|
660
|
+
});
|
|
661
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
|
|
662
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, bold: true, children: "Run this command?" }),
|
|
663
|
+
/* @__PURE__ */ jsxs4(
|
|
664
|
+
Box5,
|
|
665
|
+
{
|
|
666
|
+
flexDirection: "column",
|
|
667
|
+
paddingLeft: 2,
|
|
668
|
+
borderStyle: "single",
|
|
669
|
+
borderColor: COLORS.success,
|
|
670
|
+
borderTop: false,
|
|
671
|
+
borderBottom: false,
|
|
672
|
+
borderRight: false,
|
|
673
|
+
gap: 1,
|
|
674
|
+
children: [
|
|
675
|
+
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
676
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "$ " }),
|
|
677
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.strong, wrap: "wrap", children: command.command })
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
680
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "in:" }),
|
|
681
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, wrap: "wrap", children: command.cwd })
|
|
682
|
+
] }),
|
|
683
|
+
command.explanation && /* @__PURE__ */ jsxs4(Box5, { gap: 1, children: [
|
|
684
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: "why:" }),
|
|
685
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.accent, wrap: "wrap", children: command.explanation })
|
|
686
|
+
] })
|
|
687
|
+
]
|
|
688
|
+
}
|
|
689
|
+
),
|
|
690
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
691
|
+
/* @__PURE__ */ jsx4(NextAction, { action: "approve", keyHint: "enter" }),
|
|
692
|
+
/* @__PURE__ */ jsx4(NextAction, { action: "reject", keyHint: "esc", hierarchy: "secondary" }),
|
|
693
|
+
/* @__PURE__ */ jsx4(
|
|
694
|
+
NextAction,
|
|
695
|
+
{
|
|
696
|
+
action: "approve, and don't ask again for this command",
|
|
697
|
+
keyHint: "a",
|
|
698
|
+
hierarchy: "secondary"
|
|
699
|
+
}
|
|
700
|
+
)
|
|
523
701
|
] })
|
|
524
702
|
] });
|
|
525
703
|
}
|
|
526
704
|
|
|
527
705
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
706
|
+
import { Box as Box7, Text as Text7, useInput as useInput2, useWindowSize as useWindowSize5 } from "ink";
|
|
529
707
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
530
708
|
|
|
531
709
|
// src/ui/ScrollView.tsx
|
|
532
|
-
import { Box as
|
|
710
|
+
import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
533
711
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
534
|
-
import { jsxs as
|
|
712
|
+
import { jsxs as jsxs5 } from "react/jsx-runtime";
|
|
535
713
|
var INDICATOR_ROWS = 2;
|
|
536
714
|
function fittedWidth(node, columns) {
|
|
537
715
|
let left = 0;
|
|
@@ -546,7 +724,7 @@ function useScrollWindow({
|
|
|
546
724
|
followBottom = false
|
|
547
725
|
}) {
|
|
548
726
|
const viewportRef = useRef2(null);
|
|
549
|
-
const { columns } =
|
|
727
|
+
const { columns } = useWindowSize4();
|
|
550
728
|
const [size, setSize] = useState3(
|
|
551
729
|
null
|
|
552
730
|
);
|
|
@@ -601,14 +779,14 @@ function useScrollWindow({
|
|
|
601
779
|
};
|
|
602
780
|
}
|
|
603
781
|
function ScrollView({ scroll, children }) {
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
782
|
+
return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
783
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
606
784
|
"\u2191 ",
|
|
607
785
|
scroll.hiddenAbove,
|
|
608
786
|
" more"
|
|
609
787
|
] }),
|
|
610
788
|
children,
|
|
611
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
789
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
612
790
|
"\u2193 ",
|
|
613
791
|
scroll.hiddenBelow,
|
|
614
792
|
" more"
|
|
@@ -617,7 +795,7 @@ function ScrollView({ scroll, children }) {
|
|
|
617
795
|
}
|
|
618
796
|
|
|
619
797
|
// src/ui/SelectPrompt.tsx
|
|
620
|
-
import { jsx as
|
|
798
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
621
799
|
var CANCEL = "cancel";
|
|
622
800
|
var ARROW_WIDTH = 4;
|
|
623
801
|
var COLUMN_GAP = 2;
|
|
@@ -648,7 +826,7 @@ function SelectPrompt({
|
|
|
648
826
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
649
827
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
650
828
|
const containerRef = useRef3(null);
|
|
651
|
-
const { columns } =
|
|
829
|
+
const { columns } = useWindowSize5();
|
|
652
830
|
const [width, setWidth] = useState4(columns);
|
|
653
831
|
useLayoutEffect2(() => {
|
|
654
832
|
if (!containerRef.current) return;
|
|
@@ -679,7 +857,7 @@ function SelectPrompt({
|
|
|
679
857
|
revealIndex(index);
|
|
680
858
|
}, [index, revealIndex]);
|
|
681
859
|
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
682
|
-
|
|
860
|
+
useInput2((input, key) => {
|
|
683
861
|
if (rows.length === 0) return;
|
|
684
862
|
if (key.upArrow || input === "k") {
|
|
685
863
|
setIndex((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -702,56 +880,56 @@ function SelectPrompt({
|
|
|
702
880
|
}
|
|
703
881
|
}
|
|
704
882
|
});
|
|
705
|
-
return /* @__PURE__ */
|
|
706
|
-
/* @__PURE__ */
|
|
707
|
-
error && /* @__PURE__ */
|
|
708
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
709
|
-
table && /* @__PURE__ */
|
|
710
|
-
/* @__PURE__ */
|
|
711
|
-
question && /* @__PURE__ */
|
|
712
|
-
helpText && /* @__PURE__ */
|
|
883
|
+
return /* @__PURE__ */ jsx5(Box7, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, width, children: [
|
|
884
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
885
|
+
error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: error }),
|
|
886
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
887
|
+
table && /* @__PURE__ */ jsx5(Table, { columns: table.columns, rows: table.rows }),
|
|
888
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
889
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: question }),
|
|
890
|
+
helpText && /* @__PURE__ */ jsx5(Text7, { color: COLORS.dim, children: helpText })
|
|
713
891
|
] })
|
|
714
892
|
] }),
|
|
715
|
-
/* @__PURE__ */
|
|
893
|
+
/* @__PURE__ */ jsx5(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
716
894
|
const i = scroll.offset + visibleIndex;
|
|
717
895
|
const highlighted = i === index;
|
|
718
896
|
const isCancel = i === cancelIndex;
|
|
719
897
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
720
898
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
721
899
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
722
|
-
const label = /* @__PURE__ */
|
|
900
|
+
const label = /* @__PURE__ */ jsxs6(Text7, { color: labelColor, wrap: "truncate", children: [
|
|
723
901
|
highlighted ? "\u276F " : " ",
|
|
724
902
|
bullet,
|
|
725
903
|
option
|
|
726
904
|
] });
|
|
727
905
|
const isText = sec?.kind === "text";
|
|
728
|
-
return /* @__PURE__ */
|
|
729
|
-
|
|
906
|
+
return /* @__PURE__ */ jsxs6(
|
|
907
|
+
Box7,
|
|
730
908
|
{
|
|
731
909
|
width: isText ? "100%" : barWidth,
|
|
732
910
|
paddingX: 1,
|
|
733
911
|
paddingY: 1,
|
|
734
912
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
735
913
|
children: [
|
|
736
|
-
/* @__PURE__ */
|
|
737
|
-
isText && textWidth > 0 && /* @__PURE__ */
|
|
738
|
-
|
|
914
|
+
/* @__PURE__ */ jsx5(Box7, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
915
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx5(Box7, { width: textWidth, children: /* @__PURE__ */ jsx5(
|
|
916
|
+
Text7,
|
|
739
917
|
{
|
|
740
918
|
wrap: "truncate",
|
|
741
919
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
742
920
|
children: sec.value
|
|
743
921
|
}
|
|
744
922
|
) }),
|
|
745
|
-
sec?.kind === "badge" && /* @__PURE__ */
|
|
923
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx5(Box7, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text7, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
746
924
|
]
|
|
747
925
|
},
|
|
748
926
|
`row-${i}`
|
|
749
927
|
);
|
|
750
928
|
}) }),
|
|
751
|
-
/* @__PURE__ */
|
|
929
|
+
/* @__PURE__ */ jsx5(Box7, { flexShrink: 0, children: /* @__PURE__ */ jsx5(Text7, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
752
930
|
i > 0 ? " " : "",
|
|
753
|
-
/* @__PURE__ */
|
|
754
|
-
/* @__PURE__ */
|
|
931
|
+
/* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: key }),
|
|
932
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.dim, children: [
|
|
755
933
|
" ",
|
|
756
934
|
label
|
|
757
935
|
] })
|
|
@@ -760,23 +938,23 @@ function SelectPrompt({
|
|
|
760
938
|
}
|
|
761
939
|
|
|
762
940
|
// src/ui/PromptInput.tsx
|
|
763
|
-
import { jsx as
|
|
941
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
764
942
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
765
943
|
function EnterToContinuePrompt({
|
|
766
944
|
question,
|
|
767
945
|
messages,
|
|
768
946
|
onDecide
|
|
769
947
|
}) {
|
|
770
|
-
|
|
948
|
+
useInput3((_input, key) => {
|
|
771
949
|
if (key.return) onDecide(true);
|
|
772
950
|
else if (key.escape) onDecide(false);
|
|
773
951
|
});
|
|
774
|
-
return /* @__PURE__ */
|
|
775
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
776
|
-
question && /* @__PURE__ */
|
|
777
|
-
/* @__PURE__ */
|
|
778
|
-
/* @__PURE__ */
|
|
779
|
-
/* @__PURE__ */
|
|
952
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
|
|
953
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
954
|
+
question && /* @__PURE__ */ jsx6(Text8, { color: COLORS.primary, children: question }),
|
|
955
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
956
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "continue", keyHint: "enter" }),
|
|
957
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
780
958
|
] })
|
|
781
959
|
] });
|
|
782
960
|
}
|
|
@@ -784,11 +962,11 @@ function PromptInput() {
|
|
|
784
962
|
const { phase, inputReq, submitInput } = useWizard();
|
|
785
963
|
const [draft, setDraft] = useState5("");
|
|
786
964
|
if (phase === "done" || phase === "error") {
|
|
787
|
-
return /* @__PURE__ */
|
|
965
|
+
return /* @__PURE__ */ jsx6(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text8, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
788
966
|
}
|
|
789
967
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
790
968
|
if (inputReq.promptType === "multipleChoice") {
|
|
791
|
-
return /* @__PURE__ */
|
|
969
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
792
970
|
SelectPrompt,
|
|
793
971
|
{
|
|
794
972
|
question: inputReq.prompt,
|
|
@@ -805,7 +983,7 @@ function PromptInput() {
|
|
|
805
983
|
) });
|
|
806
984
|
}
|
|
807
985
|
if (inputReq.promptType === "multiSelect") {
|
|
808
|
-
return /* @__PURE__ */
|
|
986
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
809
987
|
SelectPrompt,
|
|
810
988
|
{
|
|
811
989
|
multi: true,
|
|
@@ -820,7 +998,7 @@ function PromptInput() {
|
|
|
820
998
|
) });
|
|
821
999
|
}
|
|
822
1000
|
if (inputReq.promptType === "notice") {
|
|
823
|
-
return /* @__PURE__ */
|
|
1001
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
824
1002
|
SelectPrompt,
|
|
825
1003
|
{
|
|
826
1004
|
question: inputReq.prompt,
|
|
@@ -831,7 +1009,7 @@ function PromptInput() {
|
|
|
831
1009
|
) });
|
|
832
1010
|
}
|
|
833
1011
|
if (inputReq.promptType === "enterToContinue") {
|
|
834
|
-
return /* @__PURE__ */
|
|
1012
|
+
return /* @__PURE__ */ jsx6(
|
|
835
1013
|
EnterToContinuePrompt,
|
|
836
1014
|
{
|
|
837
1015
|
question: inputReq.prompt,
|
|
@@ -840,9 +1018,12 @@ function PromptInput() {
|
|
|
840
1018
|
}
|
|
841
1019
|
);
|
|
842
1020
|
}
|
|
1021
|
+
if (inputReq.promptType === "commandApproval" && inputReq.command) {
|
|
1022
|
+
return /* @__PURE__ */ jsx6(CommandApproval, { command: inputReq.command, onDecide: submitInput });
|
|
1023
|
+
}
|
|
843
1024
|
if (inputReq.promptType === "acceptReject") {
|
|
844
1025
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
845
|
-
return /* @__PURE__ */
|
|
1026
|
+
return /* @__PURE__ */ jsx6(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx6(
|
|
846
1027
|
SelectPrompt,
|
|
847
1028
|
{
|
|
848
1029
|
question: inputReq.prompt,
|
|
@@ -853,15 +1034,15 @@ function PromptInput() {
|
|
|
853
1034
|
}
|
|
854
1035
|
) });
|
|
855
1036
|
}
|
|
856
|
-
return /* @__PURE__ */
|
|
857
|
-
inputReq.error && /* @__PURE__ */
|
|
858
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */
|
|
859
|
-
/* @__PURE__ */
|
|
860
|
-
/* @__PURE__ */
|
|
1037
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1038
|
+
inputReq.error && /* @__PURE__ */ jsx6(Text8, { color: COLORS.danger, children: inputReq.error }),
|
|
1039
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
1040
|
+
/* @__PURE__ */ jsxs7(Box8, { children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs7(Text8, { color: COLORS.primary, children: [
|
|
861
1042
|
inputReq.prompt,
|
|
862
1043
|
" "
|
|
863
1044
|
] }),
|
|
864
|
-
/* @__PURE__ */
|
|
1045
|
+
/* @__PURE__ */ jsx6(
|
|
865
1046
|
TextInput,
|
|
866
1047
|
{
|
|
867
1048
|
value: draft,
|
|
@@ -879,7 +1060,7 @@ function PromptInput() {
|
|
|
879
1060
|
// src/ui/Welcome.tsx
|
|
880
1061
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
881
1062
|
import { fileURLToPath } from "node:url";
|
|
882
|
-
import { Box as
|
|
1063
|
+
import { Box as Box9, Spacer, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
883
1064
|
|
|
884
1065
|
// src/ui/copy/welcome.ts
|
|
885
1066
|
var sidebarItems = [
|
|
@@ -892,12 +1073,12 @@ var sidebarItems = [
|
|
|
892
1073
|
description: "push 100 records to Algolia in seconds"
|
|
893
1074
|
},
|
|
894
1075
|
{
|
|
895
|
-
title: "detect your
|
|
896
|
-
description: "
|
|
1076
|
+
title: "detect your stack",
|
|
1077
|
+
description: "whatever language and framework you already use"
|
|
897
1078
|
},
|
|
898
1079
|
{
|
|
899
1080
|
title: "scaffold a search UI",
|
|
900
|
-
description: "a
|
|
1081
|
+
description: "a search box and results, wired into your app"
|
|
901
1082
|
},
|
|
902
1083
|
{
|
|
903
1084
|
title: "ship it",
|
|
@@ -907,28 +1088,28 @@ var sidebarItems = [
|
|
|
907
1088
|
|
|
908
1089
|
// src/ui/Welcome.tsx
|
|
909
1090
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
910
|
-
import { jsx as
|
|
1091
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
911
1092
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
912
1093
|
function SidebarItem({
|
|
913
1094
|
title,
|
|
914
1095
|
description
|
|
915
1096
|
}) {
|
|
916
|
-
return /* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */
|
|
919
|
-
/* @__PURE__ */
|
|
1097
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
1098
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, children: [
|
|
1099
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, children: "\u2192" }),
|
|
1100
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: title })
|
|
920
1101
|
] }),
|
|
921
|
-
/* @__PURE__ */
|
|
922
|
-
/* @__PURE__ */
|
|
923
|
-
/* @__PURE__ */
|
|
1102
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 2, children: [
|
|
1103
|
+
/* @__PURE__ */ jsx7(Spacer, {}),
|
|
1104
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: description })
|
|
924
1105
|
] })
|
|
925
1106
|
] });
|
|
926
1107
|
}
|
|
927
1108
|
function Welcome() {
|
|
928
1109
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
929
1110
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
930
|
-
const { rows } =
|
|
931
|
-
|
|
1111
|
+
const { rows } = useWindowSize6();
|
|
1112
|
+
useInput4((input, key) => {
|
|
932
1113
|
if (key.return) confirmStart();
|
|
933
1114
|
else if (input === "i") openLearnMore();
|
|
934
1115
|
});
|
|
@@ -946,16 +1127,16 @@ function Welcome() {
|
|
|
946
1127
|
if (rows < 30) {
|
|
947
1128
|
layout = scales["small"];
|
|
948
1129
|
}
|
|
949
|
-
return /* @__PURE__ */
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
|
|
1130
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx7(
|
|
1132
|
+
Box9,
|
|
952
1133
|
{
|
|
953
1134
|
paddingY: layout.main.padding.y,
|
|
954
1135
|
paddingX: layout.main.padding.x,
|
|
955
1136
|
flexDirection: "column",
|
|
956
1137
|
justifyContent: "center",
|
|
957
|
-
children: /* @__PURE__ */
|
|
958
|
-
/* @__PURE__ */
|
|
1138
|
+
children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
|
|
1139
|
+
/* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
|
|
959
1140
|
Image,
|
|
960
1141
|
{
|
|
961
1142
|
src: IMAGE_PATH,
|
|
@@ -966,16 +1147,16 @@ function Welcome() {
|
|
|
966
1147
|
protocol: "halfBlock"
|
|
967
1148
|
}
|
|
968
1149
|
) }),
|
|
969
|
-
/* @__PURE__ */
|
|
970
|
-
/* @__PURE__ */
|
|
971
|
-
/* @__PURE__ */
|
|
972
|
-
/* @__PURE__ */
|
|
1150
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1151
|
+
/* @__PURE__ */ jsxs8(Box9, { gap: 1, flexDirection: "column", children: [
|
|
1152
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
1153
|
+
/* @__PURE__ */ jsx7(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
973
1154
|
] })
|
|
974
1155
|
] })
|
|
975
1156
|
}
|
|
976
1157
|
),
|
|
977
|
-
/* @__PURE__ */
|
|
978
|
-
|
|
1158
|
+
/* @__PURE__ */ jsxs8(
|
|
1159
|
+
Box9,
|
|
979
1160
|
{
|
|
980
1161
|
backgroundColor: COLORS.bg.sidebar,
|
|
981
1162
|
width: 40,
|
|
@@ -985,8 +1166,8 @@ function Welcome() {
|
|
|
985
1166
|
flexDirection: "column",
|
|
986
1167
|
justifyContent: "center",
|
|
987
1168
|
children: [
|
|
988
|
-
/* @__PURE__ */
|
|
989
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */
|
|
1169
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
1170
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx7(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
990
1171
|
]
|
|
991
1172
|
}
|
|
992
1173
|
)
|
|
@@ -995,7 +1176,7 @@ function Welcome() {
|
|
|
995
1176
|
|
|
996
1177
|
// src/ui/LearnMore.tsx
|
|
997
1178
|
import { Fragment as Fragment2 } from "react";
|
|
998
|
-
import { Box as
|
|
1179
|
+
import { Box as Box10, Text as Text10, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
|
|
999
1180
|
|
|
1000
1181
|
// src/ui/copy/learn-more.ts
|
|
1001
1182
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1003,12 +1184,17 @@ var accessItems = [
|
|
|
1003
1184
|
{
|
|
1004
1185
|
tag: "READ",
|
|
1005
1186
|
title: "Project files",
|
|
1006
|
-
description: "reads
|
|
1187
|
+
description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1007
1188
|
},
|
|
1008
1189
|
{
|
|
1009
1190
|
tag: "WRITE",
|
|
1010
1191
|
title: "Code changes",
|
|
1011
|
-
description: "creates & edits files (search UI, config)
|
|
1192
|
+
description: "creates & edits files (search UI, config) in a throwaway git worktree \u2014 your checkout is never touched."
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
tag: "EXEC",
|
|
1196
|
+
title: "Setup commands",
|
|
1197
|
+
description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
|
|
1012
1198
|
},
|
|
1013
1199
|
{
|
|
1014
1200
|
tag: "NET",
|
|
@@ -1018,13 +1204,13 @@ var accessItems = [
|
|
|
1018
1204
|
{
|
|
1019
1205
|
tag: "KEY",
|
|
1020
1206
|
title: "Credentials",
|
|
1021
|
-
description: "
|
|
1207
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in the worktree."
|
|
1022
1208
|
}
|
|
1023
1209
|
];
|
|
1024
1210
|
var neverItems = [
|
|
1025
1211
|
"Send your source code to a model or third party",
|
|
1026
1212
|
"Commit or push to git",
|
|
1027
|
-
"
|
|
1213
|
+
"Run a command you haven't approved"
|
|
1028
1214
|
];
|
|
1029
1215
|
var policyLinks = [
|
|
1030
1216
|
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
@@ -1032,10 +1218,11 @@ var policyLinks = [
|
|
|
1032
1218
|
];
|
|
1033
1219
|
|
|
1034
1220
|
// src/ui/LearnMore.tsx
|
|
1035
|
-
import { jsx as
|
|
1221
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1036
1222
|
var TAG_COLORS = {
|
|
1037
1223
|
READ: COLORS.success,
|
|
1038
1224
|
WRITE: COLORS.badge,
|
|
1225
|
+
EXEC: COLORS.danger,
|
|
1039
1226
|
NET: COLORS.accent,
|
|
1040
1227
|
KEY: COLORS.muted
|
|
1041
1228
|
};
|
|
@@ -1048,25 +1235,25 @@ function NeverLine({
|
|
|
1048
1235
|
}) {
|
|
1049
1236
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1050
1237
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1051
|
-
return /* @__PURE__ */
|
|
1052
|
-
/* @__PURE__ */
|
|
1238
|
+
return /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1239
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" }),
|
|
1053
1240
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1054
|
-
segments.map((s, i) => /* @__PURE__ */
|
|
1241
|
+
segments.map((s, i) => /* @__PURE__ */ jsx8(Text10, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1055
1242
|
" ".repeat(rightPad),
|
|
1056
|
-
/* @__PURE__ */
|
|
1243
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: "\u2502" })
|
|
1057
1244
|
] });
|
|
1058
1245
|
}
|
|
1059
1246
|
function LearnMore() {
|
|
1060
1247
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1061
1248
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1062
|
-
const { columns } =
|
|
1249
|
+
const { columns } = useWindowSize7();
|
|
1063
1250
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1064
|
-
|
|
1251
|
+
useInput5((_input, key) => {
|
|
1065
1252
|
if (key.escape) backToHome();
|
|
1066
1253
|
else if (key.return) confirmStart();
|
|
1067
1254
|
});
|
|
1068
|
-
return /* @__PURE__ */
|
|
1069
|
-
|
|
1255
|
+
return /* @__PURE__ */ jsxs9(
|
|
1256
|
+
Box10,
|
|
1070
1257
|
{
|
|
1071
1258
|
flexDirection: "column",
|
|
1072
1259
|
paddingX: PADDING_X,
|
|
@@ -1074,31 +1261,31 @@ function LearnMore() {
|
|
|
1074
1261
|
width: "100%",
|
|
1075
1262
|
gap: 1,
|
|
1076
1263
|
children: [
|
|
1077
|
-
/* @__PURE__ */
|
|
1078
|
-
/* @__PURE__ */
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
/* @__PURE__ */
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */
|
|
1083
|
-
/* @__PURE__ */
|
|
1084
|
-
/* @__PURE__ */
|
|
1085
|
-
/* @__PURE__ */
|
|
1264
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1265
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: accessIntro }),
|
|
1266
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
1267
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1268
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1269
|
+
/* @__PURE__ */ jsx8(Box10, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text10, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1270
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1271
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1272
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1086
1273
|
] }) })
|
|
1087
1274
|
] })
|
|
1088
1275
|
] }, item.tag)) }),
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
/* @__PURE__ */
|
|
1091
|
-
/* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1276
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
1277
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1278
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1279
|
+
/* @__PURE__ */ jsx8(
|
|
1093
1280
|
NeverLine,
|
|
1094
1281
|
{
|
|
1095
1282
|
width: dividerWidth,
|
|
1096
1283
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1097
1284
|
}
|
|
1098
1285
|
),
|
|
1099
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1100
|
-
/* @__PURE__ */
|
|
1101
|
-
/* @__PURE__ */
|
|
1286
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
1287
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1288
|
+
/* @__PURE__ */ jsx8(
|
|
1102
1289
|
NeverLine,
|
|
1103
1290
|
{
|
|
1104
1291
|
width: dividerWidth,
|
|
@@ -1110,24 +1297,24 @@ function LearnMore() {
|
|
|
1110
1297
|
}
|
|
1111
1298
|
)
|
|
1112
1299
|
] }, item)),
|
|
1113
|
-
/* @__PURE__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1300
|
+
/* @__PURE__ */ jsx8(NeverLine, { width: dividerWidth }),
|
|
1301
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1115
1302
|
] }),
|
|
1116
|
-
/* @__PURE__ */
|
|
1117
|
-
/* @__PURE__ */
|
|
1118
|
-
/* @__PURE__ */
|
|
1303
|
+
/* @__PURE__ */ jsx8(Box10, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1304
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1305
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.accent, children: link.url })
|
|
1119
1306
|
] }, link.label)) }),
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1123
|
-
/* @__PURE__ */
|
|
1124
|
-
/* @__PURE__ */
|
|
1307
|
+
/* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1308
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1309
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
|
|
1310
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "esc" }),
|
|
1311
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "] back" })
|
|
1125
1312
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */
|
|
1128
|
-
/* @__PURE__ */
|
|
1129
|
-
/* @__PURE__ */
|
|
1130
|
-
/* @__PURE__ */
|
|
1313
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1314
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "[" }),
|
|
1315
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "enter" }),
|
|
1316
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "]" }),
|
|
1317
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1131
1318
|
] })
|
|
1132
1319
|
] })
|
|
1133
1320
|
]
|
|
@@ -1136,10 +1323,10 @@ function LearnMore() {
|
|
|
1136
1323
|
}
|
|
1137
1324
|
|
|
1138
1325
|
// src/ui/Sidebar.tsx
|
|
1139
|
-
import { Box as
|
|
1326
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1140
1327
|
|
|
1141
1328
|
// src/ui/Steps.tsx
|
|
1142
|
-
import { Box as
|
|
1329
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1143
1330
|
import Spinner from "ink-spinner";
|
|
1144
1331
|
|
|
1145
1332
|
// src/core/persistence.ts
|
|
@@ -1168,12 +1355,12 @@ async function clearWorkflowState(workflowId) {
|
|
|
1168
1355
|
}
|
|
1169
1356
|
|
|
1170
1357
|
// src/ui/Steps.tsx
|
|
1171
|
-
import { jsx as
|
|
1358
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1172
1359
|
function Steps() {
|
|
1173
1360
|
const { steps } = useWizard();
|
|
1174
1361
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1175
|
-
return /* @__PURE__ */
|
|
1176
|
-
s.status === "running" ? /* @__PURE__ */
|
|
1362
|
+
return /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status[s.status], children: [
|
|
1363
|
+
s.status === "running" ? /* @__PURE__ */ jsx9(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1177
1364
|
" ",
|
|
1178
1365
|
s.title
|
|
1179
1366
|
] }) }, s.id)) });
|
|
@@ -1182,27 +1369,27 @@ function CurrentStep() {
|
|
|
1182
1369
|
const { steps } = useWizard();
|
|
1183
1370
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1184
1371
|
if (!currentStep) return null;
|
|
1185
|
-
return /* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1372
|
+
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.status.running, children: [
|
|
1373
|
+
/* @__PURE__ */ jsx9(Spinner, { type: "dots" }),
|
|
1187
1374
|
" ",
|
|
1188
1375
|
` ${currentStep.title}`
|
|
1189
1376
|
] });
|
|
1190
1377
|
}
|
|
1191
1378
|
|
|
1192
1379
|
// src/ui/Progress.tsx
|
|
1193
|
-
import { Box as
|
|
1194
|
-
import { jsx as
|
|
1380
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1381
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1195
1382
|
function Progress() {
|
|
1196
1383
|
const { steps, currentStepIndex } = useWizard();
|
|
1197
1384
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1198
1385
|
if (visibleSteps.length === 0) return null;
|
|
1199
1386
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1200
1387
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1201
|
-
return /* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */
|
|
1203
|
-
/* @__PURE__ */
|
|
1204
|
-
/* @__PURE__ */
|
|
1205
|
-
/* @__PURE__ */
|
|
1388
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1389
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "STEP" }),
|
|
1390
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: activeStepNumber }),
|
|
1391
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: "/" }),
|
|
1392
|
+
/* @__PURE__ */ jsx10(Text12, { bold: true, children: visibleSteps.length })
|
|
1206
1393
|
] });
|
|
1207
1394
|
}
|
|
1208
1395
|
|
|
@@ -1213,10 +1400,10 @@ var sidebarCommands = [
|
|
|
1213
1400
|
];
|
|
1214
1401
|
|
|
1215
1402
|
// src/ui/Sidebar.tsx
|
|
1216
|
-
import { jsx as
|
|
1403
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1217
1404
|
function Sidebar() {
|
|
1218
|
-
return /* @__PURE__ */
|
|
1219
|
-
|
|
1405
|
+
return /* @__PURE__ */ jsxs12(
|
|
1406
|
+
Box13,
|
|
1220
1407
|
{
|
|
1221
1408
|
backgroundColor: "#14171E",
|
|
1222
1409
|
width: 30,
|
|
@@ -1225,16 +1412,16 @@ function Sidebar() {
|
|
|
1225
1412
|
flexDirection: "column",
|
|
1226
1413
|
justifyContent: "space-between",
|
|
1227
1414
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1415
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1416
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1417
|
+
/* @__PURE__ */ jsx11(Steps, {})
|
|
1231
1418
|
] }),
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1419
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
1420
|
+
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1421
|
+
/* @__PURE__ */ jsx11(Box13, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1422
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1423
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1424
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: c.description })
|
|
1238
1425
|
] });
|
|
1239
1426
|
}) })
|
|
1240
1427
|
] })
|
|
@@ -1244,12 +1431,12 @@ function Sidebar() {
|
|
|
1244
1431
|
}
|
|
1245
1432
|
|
|
1246
1433
|
// src/ui/Ribbon.tsx
|
|
1247
|
-
import { Box as
|
|
1248
|
-
import { jsx as
|
|
1434
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1435
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1249
1436
|
function Ribbon() {
|
|
1250
1437
|
const firstCommand = sidebarCommands[0];
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
|
|
1438
|
+
return /* @__PURE__ */ jsxs13(
|
|
1439
|
+
Box14,
|
|
1253
1440
|
{
|
|
1254
1441
|
backgroundColor: "#14171E",
|
|
1255
1442
|
flexDirection: "row",
|
|
@@ -1257,11 +1444,11 @@ function Ribbon() {
|
|
|
1257
1444
|
paddingX: 2,
|
|
1258
1445
|
paddingY: 1,
|
|
1259
1446
|
children: [
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
/* @__PURE__ */
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */
|
|
1264
|
-
/* @__PURE__ */
|
|
1447
|
+
/* @__PURE__ */ jsx12(Progress, {}),
|
|
1448
|
+
/* @__PURE__ */ jsx12(CurrentStep, {}),
|
|
1449
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
|
|
1450
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1451
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: firstCommand.description })
|
|
1265
1452
|
] })
|
|
1266
1453
|
]
|
|
1267
1454
|
}
|
|
@@ -1272,8 +1459,8 @@ function Ribbon() {
|
|
|
1272
1459
|
import { useState as useState6 } from "react";
|
|
1273
1460
|
|
|
1274
1461
|
// src/ui/Logs.tsx
|
|
1275
|
-
import { Box as
|
|
1276
|
-
import { jsx as
|
|
1462
|
+
import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
|
|
1463
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1277
1464
|
var KIND_COLOR = {
|
|
1278
1465
|
tool: COLORS.primary,
|
|
1279
1466
|
prompt: COLORS.badge
|
|
@@ -1304,14 +1491,14 @@ function formatTimestamp(ms) {
|
|
|
1304
1491
|
function Logs() {
|
|
1305
1492
|
const logs = useWizard((s) => s.logs);
|
|
1306
1493
|
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1307
|
-
|
|
1494
|
+
useInput6((_input, key) => {
|
|
1308
1495
|
if (key.upArrow) scroll.scrollBy(-1);
|
|
1309
1496
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1310
1497
|
});
|
|
1311
1498
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1312
|
-
return /* @__PURE__ */
|
|
1313
|
-
logs.length === 0 && /* @__PURE__ */
|
|
1314
|
-
/* @__PURE__ */
|
|
1499
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1500
|
+
logs.length === 0 && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "No logs yet." }),
|
|
1501
|
+
/* @__PURE__ */ jsx13(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1315
1502
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1316
1503
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1317
1504
|
const rawPreview = rawInputText(entry.input);
|
|
@@ -1321,14 +1508,14 @@ function Logs() {
|
|
|
1321
1508
|
const name = truncate2(entry.name, budget);
|
|
1322
1509
|
budget -= name.length;
|
|
1323
1510
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1324
|
-
return /* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */
|
|
1326
|
-
/* @__PURE__ */
|
|
1327
|
-
preview && /* @__PURE__ */
|
|
1328
|
-
durationText && /* @__PURE__ */
|
|
1511
|
+
return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1512
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: timestamp }),
|
|
1513
|
+
/* @__PURE__ */ jsx13(Text15, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1514
|
+
preview && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1515
|
+
durationText && /* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: durationText })
|
|
1329
1516
|
] }, entry.id);
|
|
1330
1517
|
}) }),
|
|
1331
|
-
/* @__PURE__ */
|
|
1518
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1332
1519
|
] });
|
|
1333
1520
|
}
|
|
1334
1521
|
|
|
@@ -1520,15 +1707,15 @@ function track(event, payload) {
|
|
|
1520
1707
|
}
|
|
1521
1708
|
|
|
1522
1709
|
// src/ui/App.tsx
|
|
1523
|
-
import { jsx as
|
|
1710
|
+
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1524
1711
|
function App() {
|
|
1525
1712
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1526
1713
|
const { exit } = useApp();
|
|
1527
|
-
const { columns, rows } =
|
|
1714
|
+
const { columns, rows } = useWindowSize8();
|
|
1528
1715
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1529
1716
|
const finished = phase === "done" || phase === "error";
|
|
1530
1717
|
const currentStep = steps[currentStepIndex];
|
|
1531
|
-
|
|
1718
|
+
useInput7(
|
|
1532
1719
|
(_input, key) => {
|
|
1533
1720
|
if (key.return) {
|
|
1534
1721
|
exit();
|
|
@@ -1536,8 +1723,8 @@ function App() {
|
|
|
1536
1723
|
},
|
|
1537
1724
|
{ isActive: finished }
|
|
1538
1725
|
);
|
|
1539
|
-
|
|
1540
|
-
if (phase === "idle" || phase === "
|
|
1726
|
+
useInput7((_input, key) => {
|
|
1727
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
1541
1728
|
if (key.tab) {
|
|
1542
1729
|
setShowLogs(!showLogs);
|
|
1543
1730
|
track("AI Wizard Interaction", {
|
|
@@ -1547,49 +1734,45 @@ function App() {
|
|
|
1547
1734
|
});
|
|
1548
1735
|
}
|
|
1549
1736
|
});
|
|
1550
|
-
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1551
|
-
|
|
1737
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
1738
|
+
useInput7((_input, key) => {
|
|
1552
1739
|
if (escOwnedElsewhere) return;
|
|
1553
1740
|
if (key.escape) {
|
|
1554
1741
|
track("AI Wizard Interaction", {
|
|
1555
1742
|
context: "global",
|
|
1556
1743
|
key: "esc",
|
|
1557
|
-
// No step is active until `startWorkflow` — report the phase instead.
|
|
1558
1744
|
currentStep: currentStep?.id ?? phase
|
|
1559
1745
|
});
|
|
1560
1746
|
exit();
|
|
1561
1747
|
}
|
|
1562
1748
|
});
|
|
1563
|
-
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1749
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1564
1750
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1565
1751
|
const showSidebar = flexDirection === "row";
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
against (see SelectPrompt). */
|
|
1591
|
-
/* @__PURE__ */ jsxs13(
|
|
1592
|
-
Box14,
|
|
1752
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
1753
|
+
return (
|
|
1754
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1755
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1756
|
+
flicker and leftover rows. */
|
|
1757
|
+
/* @__PURE__ */ jsxs15(
|
|
1758
|
+
Box16,
|
|
1759
|
+
{
|
|
1760
|
+
backgroundColor: COLORS.bg.main,
|
|
1761
|
+
flexDirection: "row",
|
|
1762
|
+
width: columns,
|
|
1763
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
1764
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1765
|
+
children: [
|
|
1766
|
+
mainWindowVisible && /* @__PURE__ */ jsxs15(
|
|
1767
|
+
Box16,
|
|
1768
|
+
{
|
|
1769
|
+
flexDirection,
|
|
1770
|
+
width: "100%",
|
|
1771
|
+
maxHeight: rows,
|
|
1772
|
+
justifyContent: "space-between",
|
|
1773
|
+
children: [
|
|
1774
|
+
showLogs ? /* @__PURE__ */ jsx14(Logs, {}) : /* @__PURE__ */ jsxs15(
|
|
1775
|
+
Box16,
|
|
1593
1776
|
{
|
|
1594
1777
|
flexDirection: "column",
|
|
1595
1778
|
paddingX: 4,
|
|
@@ -1597,24 +1780,29 @@ function App() {
|
|
|
1597
1780
|
width: showSidebar ? 70 : "100%",
|
|
1598
1781
|
flexGrow: 1,
|
|
1599
1782
|
children: [
|
|
1600
|
-
/* @__PURE__ */
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1783
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
|
|
1784
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1785
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ jsx14(CliOutput, {}),
|
|
1788
|
+
/* @__PURE__ */ jsx14(Notices, {}),
|
|
1789
|
+
/* @__PURE__ */ jsx14(PromptInput, {}),
|
|
1790
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx14(CurrentStep, {}) }),
|
|
1791
|
+
phase === "error" && error && /* @__PURE__ */ jsx14(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: COLORS.status.error, children: [
|
|
1604
1792
|
"\u2716 ",
|
|
1605
1793
|
error
|
|
1606
1794
|
] }) })
|
|
1607
1795
|
]
|
|
1608
1796
|
}
|
|
1609
|
-
)
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1797
|
+
),
|
|
1798
|
+
showSidebar ? /* @__PURE__ */ jsx14(Sidebar, {}) : /* @__PURE__ */ jsx14(Ribbon, {})
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
),
|
|
1802
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx14(LearnMore, {}) : /* @__PURE__ */ jsx14(Welcome, {}))
|
|
1803
|
+
]
|
|
1804
|
+
}
|
|
1805
|
+
)
|
|
1618
1806
|
);
|
|
1619
1807
|
}
|
|
1620
1808
|
|
|
@@ -1628,7 +1816,8 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1628
1816
|
var DEFAULT_CONFIG = {
|
|
1629
1817
|
version: 1,
|
|
1630
1818
|
aiConsent: false,
|
|
1631
|
-
workflowsRun: []
|
|
1819
|
+
workflowsRun: [],
|
|
1820
|
+
searchApiKeys: {}
|
|
1632
1821
|
};
|
|
1633
1822
|
async function loadConfig() {
|
|
1634
1823
|
try {
|
|
@@ -1647,6 +1836,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1647
1836
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1648
1837
|
await saveConfig(config);
|
|
1649
1838
|
}
|
|
1839
|
+
function isStoredSearchKey(value) {
|
|
1840
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1841
|
+
const { appId, key } = value;
|
|
1842
|
+
return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
|
|
1843
|
+
}
|
|
1844
|
+
function storedSearchKeys(config) {
|
|
1845
|
+
const stored = config.searchApiKeys;
|
|
1846
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1847
|
+
return {};
|
|
1848
|
+
}
|
|
1849
|
+
return stored;
|
|
1850
|
+
}
|
|
1851
|
+
async function getStoredSearchKey(index, appId) {
|
|
1852
|
+
const entry = storedSearchKeys(await loadConfig())[index];
|
|
1853
|
+
if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
|
|
1854
|
+
return entry.key;
|
|
1855
|
+
}
|
|
1856
|
+
async function storeSearchKey(index, appId, key) {
|
|
1857
|
+
const config = await loadConfig();
|
|
1858
|
+
config.searchApiKeys = {
|
|
1859
|
+
...storedSearchKeys(config),
|
|
1860
|
+
[index]: { appId, key }
|
|
1861
|
+
};
|
|
1862
|
+
await saveConfig(config);
|
|
1863
|
+
}
|
|
1864
|
+
async function forgetSearchKey(index) {
|
|
1865
|
+
const config = await loadConfig();
|
|
1866
|
+
const remaining = { ...storedSearchKeys(config) };
|
|
1867
|
+
delete remaining[index];
|
|
1868
|
+
config.searchApiKeys = remaining;
|
|
1869
|
+
await saveConfig(config);
|
|
1870
|
+
}
|
|
1650
1871
|
|
|
1651
1872
|
// src/core/orchestrator.ts
|
|
1652
1873
|
function defineStep(step) {
|
|
@@ -1691,7 +1912,7 @@ async function ensureConsent() {
|
|
|
1691
1912
|
if (config.aiConsent) return;
|
|
1692
1913
|
const store = useWizard.getState();
|
|
1693
1914
|
const answer = await store.requestUserInput({
|
|
1694
|
-
prompt: "Wizard will make AI-authored changes to this repository.",
|
|
1915
|
+
prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
|
|
1695
1916
|
promptType: "enterToContinue",
|
|
1696
1917
|
options: []
|
|
1697
1918
|
});
|
|
@@ -1850,61 +2071,138 @@ async function runWorkflow(workflow, appId) {
|
|
|
1850
2071
|
}
|
|
1851
2072
|
}
|
|
1852
2073
|
|
|
1853
|
-
// src/lib/
|
|
1854
|
-
import {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2074
|
+
// src/lib/algoliaApp.ts
|
|
2075
|
+
import { z as z4 } from "zod";
|
|
2076
|
+
var applicationSchema = z4.object({
|
|
2077
|
+
id: z4.string().min(1),
|
|
2078
|
+
name: z4.string().default(""),
|
|
2079
|
+
plan: z4.string().optional()
|
|
2080
|
+
});
|
|
2081
|
+
var listSchema = z4.array(
|
|
2082
|
+
z4.object({
|
|
2083
|
+
id: z4.string().min(1),
|
|
2084
|
+
name: z4.string().default(""),
|
|
2085
|
+
plan_label: z4.string().optional()
|
|
2086
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
2087
|
+
);
|
|
2088
|
+
async function currentApplication() {
|
|
2089
|
+
let raw;
|
|
1866
2090
|
try {
|
|
1867
|
-
|
|
2091
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1868
2092
|
} catch {
|
|
1869
|
-
return
|
|
2093
|
+
return null;
|
|
2094
|
+
}
|
|
2095
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2096
|
+
return parsed.success ? parsed.data : null;
|
|
2097
|
+
}
|
|
2098
|
+
async function requireApplication() {
|
|
2099
|
+
const app = await currentApplication();
|
|
2100
|
+
if (!app) {
|
|
2101
|
+
throw new Error(
|
|
2102
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
2103
|
+
);
|
|
1870
2104
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2105
|
+
return app;
|
|
2106
|
+
}
|
|
2107
|
+
async function listApplications() {
|
|
2108
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
2109
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
2110
|
+
if (!parsed.success) {
|
|
2111
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
2112
|
+
}
|
|
2113
|
+
return parsed.data;
|
|
2114
|
+
}
|
|
2115
|
+
async function selectApplication(id) {
|
|
2116
|
+
const raw = await runAlgoliaCli(
|
|
2117
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
2118
|
+
{ onOutput: stderrSink }
|
|
2119
|
+
);
|
|
2120
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2121
|
+
if (!parsed.success) {
|
|
2122
|
+
throw new Error(
|
|
2123
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
return parsed.data;
|
|
2127
|
+
}
|
|
2128
|
+
function parseJson(text) {
|
|
1884
2129
|
try {
|
|
1885
|
-
|
|
2130
|
+
return JSON.parse(text);
|
|
1886
2131
|
} catch {
|
|
1887
|
-
|
|
2132
|
+
return void 0;
|
|
1888
2133
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// src/lib/algoliaAppPicker.ts
|
|
2137
|
+
function secondaryFor(app) {
|
|
2138
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
2139
|
+
}
|
|
2140
|
+
function labelFor(app) {
|
|
2141
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
2142
|
+
}
|
|
2143
|
+
function selectAndReport(app) {
|
|
2144
|
+
useWizard.getState().pushCliOutput(
|
|
2145
|
+
"stdout",
|
|
2146
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
2147
|
+
);
|
|
2148
|
+
return selectApplication(app.id);
|
|
2149
|
+
}
|
|
2150
|
+
async function promptForApplication() {
|
|
2151
|
+
const store = useWizard.getState();
|
|
2152
|
+
const apps = await listApplications();
|
|
2153
|
+
if (apps.length === 0) {
|
|
1891
2154
|
throw new Error(
|
|
1892
|
-
"
|
|
2155
|
+
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
1893
2156
|
);
|
|
1894
2157
|
}
|
|
1895
|
-
|
|
2158
|
+
if (apps.length === 1) {
|
|
2159
|
+
const only = apps[0];
|
|
2160
|
+
logger.info(
|
|
2161
|
+
{ app: only.id },
|
|
2162
|
+
"single application on the account; selecting it"
|
|
2163
|
+
);
|
|
2164
|
+
return selectAndReport(only);
|
|
2165
|
+
}
|
|
2166
|
+
const messages = ["Which Algolia application should the wizard work in?"];
|
|
2167
|
+
for (; ; ) {
|
|
2168
|
+
const choice = await store.requestUserInput({
|
|
2169
|
+
prompt: "Select an application",
|
|
2170
|
+
promptType: "multipleChoice",
|
|
2171
|
+
options: apps.map(labelFor),
|
|
2172
|
+
secondary: apps.map(secondaryFor),
|
|
2173
|
+
messages
|
|
2174
|
+
});
|
|
2175
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
2176
|
+
if (!chosen) {
|
|
2177
|
+
throw new Error("Application picker received an unexpected selection");
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
return await selectAndReport(chosen);
|
|
2181
|
+
} catch (err) {
|
|
2182
|
+
logger.warn(
|
|
2183
|
+
{ app: chosen.id, err: err.message },
|
|
2184
|
+
"application select failed; re-prompting"
|
|
2185
|
+
);
|
|
2186
|
+
messages.push(
|
|
2187
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function ensureApplication() {
|
|
2193
|
+
return await currentApplication() ?? await promptForApplication();
|
|
1896
2194
|
}
|
|
1897
2195
|
|
|
1898
2196
|
// src/workflows/default.ts
|
|
1899
|
-
import { z as
|
|
2197
|
+
import { z as z27 } from "zod";
|
|
1900
2198
|
|
|
1901
2199
|
// src/actions/listIndices.ts
|
|
1902
|
-
import { z as
|
|
1903
|
-
var indicesListSchema =
|
|
1904
|
-
items:
|
|
1905
|
-
|
|
1906
|
-
name:
|
|
1907
|
-
entries:
|
|
2200
|
+
import { z as z5 } from "zod";
|
|
2201
|
+
var indicesListSchema = z5.object({
|
|
2202
|
+
items: z5.array(
|
|
2203
|
+
z5.object({
|
|
2204
|
+
name: z5.string(),
|
|
2205
|
+
entries: z5.number().default(0)
|
|
1908
2206
|
})
|
|
1909
2207
|
)
|
|
1910
2208
|
});
|
|
@@ -1975,12 +2273,12 @@ import "zod";
|
|
|
1975
2273
|
|
|
1976
2274
|
// src/lib/tools/listFiles.ts
|
|
1977
2275
|
import { tool } from "ai";
|
|
1978
|
-
import
|
|
2276
|
+
import z6 from "zod";
|
|
1979
2277
|
import { readdir } from "node:fs/promises";
|
|
1980
2278
|
|
|
1981
2279
|
// src/lib/tools/path.ts
|
|
1982
2280
|
import { lstat } from "node:fs/promises";
|
|
1983
|
-
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as
|
|
2281
|
+
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
|
|
1984
2282
|
function resolveInRoot(ctx, path) {
|
|
1985
2283
|
const target = resolve2(ctx.cwd, path);
|
|
1986
2284
|
const rel = relative(ctx.root, target);
|
|
@@ -1996,7 +2294,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1996
2294
|
let current = ctx.root;
|
|
1997
2295
|
const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
|
|
1998
2296
|
for (const part of parts) {
|
|
1999
|
-
current =
|
|
2297
|
+
current = join6(current, part);
|
|
2000
2298
|
try {
|
|
2001
2299
|
if ((await lstat(current)).isSymbolicLink()) return true;
|
|
2002
2300
|
} catch (err) {
|
|
@@ -2011,7 +2309,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
2011
2309
|
function listFilesTool(ctx) {
|
|
2012
2310
|
return tool({
|
|
2013
2311
|
description: "List files in the current working directory",
|
|
2014
|
-
inputSchema:
|
|
2312
|
+
inputSchema: z6.object(),
|
|
2015
2313
|
execute: async () => {
|
|
2016
2314
|
logger.info("called listFiles tool");
|
|
2017
2315
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
@@ -2027,13 +2325,13 @@ function listFilesTool(ctx) {
|
|
|
2027
2325
|
|
|
2028
2326
|
// src/lib/tools/changeDirectory.ts
|
|
2029
2327
|
import { tool as tool2 } from "ai";
|
|
2030
|
-
import
|
|
2328
|
+
import z7 from "zod";
|
|
2031
2329
|
import { stat } from "node:fs/promises";
|
|
2032
2330
|
function changeDirectoryTool(ctx) {
|
|
2033
2331
|
return tool2({
|
|
2034
2332
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
2035
|
-
inputSchema:
|
|
2036
|
-
path:
|
|
2333
|
+
inputSchema: z7.object({
|
|
2334
|
+
path: z7.string().describe("Directory to change into")
|
|
2037
2335
|
}),
|
|
2038
2336
|
execute: async ({ path }) => {
|
|
2039
2337
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -2055,13 +2353,13 @@ function changeDirectoryTool(ctx) {
|
|
|
2055
2353
|
|
|
2056
2354
|
// src/lib/tools/reportStatus.ts
|
|
2057
2355
|
import { tool as tool3 } from "ai";
|
|
2058
|
-
import
|
|
2356
|
+
import z8 from "zod";
|
|
2059
2357
|
function reportStatusTool(output) {
|
|
2060
2358
|
return tool3({
|
|
2061
2359
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2062
|
-
inputSchema:
|
|
2063
|
-
status:
|
|
2064
|
-
reason:
|
|
2360
|
+
inputSchema: z8.object({
|
|
2361
|
+
status: z8.enum(["success", "fail"]),
|
|
2362
|
+
reason: z8.string().optional(),
|
|
2065
2363
|
output
|
|
2066
2364
|
}),
|
|
2067
2365
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2073,8 +2371,8 @@ function reportStatusTool(output) {
|
|
|
2073
2371
|
|
|
2074
2372
|
// src/lib/tools/readFile.ts
|
|
2075
2373
|
import { tool as tool4 } from "ai";
|
|
2076
|
-
import
|
|
2077
|
-
import { readFile as
|
|
2374
|
+
import z9 from "zod";
|
|
2375
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2078
2376
|
|
|
2079
2377
|
// src/lib/tools/env.ts
|
|
2080
2378
|
import { basename } from "node:path";
|
|
@@ -2101,8 +2399,8 @@ function redactEnvValues(content) {
|
|
|
2101
2399
|
function readFileTool(ctx) {
|
|
2102
2400
|
return tool4({
|
|
2103
2401
|
description: "Read the contents of a file at the given path",
|
|
2104
|
-
inputSchema:
|
|
2105
|
-
filePath:
|
|
2402
|
+
inputSchema: z9.object({
|
|
2403
|
+
filePath: z9.string().describe("Path to the file to read")
|
|
2106
2404
|
}),
|
|
2107
2405
|
execute: async ({ filePath }) => {
|
|
2108
2406
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2112,7 +2410,7 @@ function readFileTool(ctx) {
|
|
|
2112
2410
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2113
2411
|
if (!resolved.ok) return resolved.error;
|
|
2114
2412
|
try {
|
|
2115
|
-
const content = await
|
|
2413
|
+
const content = await readFile3(resolved.target, "utf8");
|
|
2116
2414
|
return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
|
|
2117
2415
|
} catch (err) {
|
|
2118
2416
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2123,15 +2421,15 @@ function readFileTool(ctx) {
|
|
|
2123
2421
|
|
|
2124
2422
|
// src/lib/tools/writeFile.ts
|
|
2125
2423
|
import { tool as tool5 } from "ai";
|
|
2126
|
-
import
|
|
2424
|
+
import z10 from "zod";
|
|
2127
2425
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2128
2426
|
import { dirname as dirname4 } from "node:path";
|
|
2129
2427
|
function writeFileTool(ctx) {
|
|
2130
2428
|
return tool5({
|
|
2131
2429
|
description: "Write content to a file at the given path, overwriting it. To set Algolia credentials in an env file, use writeCredentials instead of this tool.",
|
|
2132
|
-
inputSchema:
|
|
2133
|
-
filePath:
|
|
2134
|
-
content:
|
|
2430
|
+
inputSchema: z10.object({
|
|
2431
|
+
filePath: z10.string().describe("Path to the file to write"),
|
|
2432
|
+
content: z10.string().describe("Content to write to the file")
|
|
2135
2433
|
}),
|
|
2136
2434
|
execute: async ({ filePath, content }) => {
|
|
2137
2435
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2156,9 +2454,145 @@ function writeFileTool(ctx) {
|
|
|
2156
2454
|
|
|
2157
2455
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2158
2456
|
import { tool as tool6 } from "ai";
|
|
2159
|
-
import
|
|
2160
|
-
import { mkdir as mkdir4, readFile as
|
|
2457
|
+
import z12 from "zod";
|
|
2458
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2161
2459
|
import { dirname as dirname5 } from "node:path";
|
|
2460
|
+
|
|
2461
|
+
// src/lib/algoliaApiKey.ts
|
|
2462
|
+
import { z as z11 } from "zod";
|
|
2463
|
+
var WRITE_ACLS = [
|
|
2464
|
+
"addObject",
|
|
2465
|
+
"deleteObject",
|
|
2466
|
+
"settings",
|
|
2467
|
+
"editSettings",
|
|
2468
|
+
"listIndexes"
|
|
2469
|
+
];
|
|
2470
|
+
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2471
|
+
var apiKeySchema = z11.object({
|
|
2472
|
+
value: z11.string().min(1),
|
|
2473
|
+
acl: z11.array(z11.string()).default([]),
|
|
2474
|
+
indexes: z11.array(z11.string()).default([])
|
|
2475
|
+
});
|
|
2476
|
+
var apiKeyListSchema = z11.union([
|
|
2477
|
+
z11.array(apiKeySchema),
|
|
2478
|
+
z11.object({
|
|
2479
|
+
items: z11.array(apiKeySchema).optional(),
|
|
2480
|
+
keys: z11.array(apiKeySchema).optional()
|
|
2481
|
+
})
|
|
2482
|
+
]).transform((o) => Array.isArray(o) ? o : o.items ?? o.keys ?? []);
|
|
2483
|
+
function parseCliJson(schema, stdout, command) {
|
|
2484
|
+
let raw;
|
|
2485
|
+
try {
|
|
2486
|
+
raw = JSON.parse(stdout);
|
|
2487
|
+
} catch {
|
|
2488
|
+
throw new Error(`Algolia CLI \`${command}\` returned unreadable JSON.`);
|
|
2489
|
+
}
|
|
2490
|
+
const parsed = schema.safeParse(raw);
|
|
2491
|
+
if (!parsed.success) {
|
|
2492
|
+
throw new Error(
|
|
2493
|
+
`Algolia CLI \`${command}\` returned JSON in an unexpected shape.`
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
return parsed.data;
|
|
2497
|
+
}
|
|
2498
|
+
var createdKeySchema = z11.object({
|
|
2499
|
+
key: z11.string().min(1).optional(),
|
|
2500
|
+
value: z11.string().min(1).optional()
|
|
2501
|
+
}).transform((o) => o.key ?? o.value);
|
|
2502
|
+
function canReuseForWrites(key, index) {
|
|
2503
|
+
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2504
|
+
}
|
|
2505
|
+
async function resolveWriteKey(index) {
|
|
2506
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2507
|
+
const existing = parseCliJson(apiKeyListSchema, stdout, "apikeys list").find(
|
|
2508
|
+
(key) => canReuseForWrites(key, index)
|
|
2509
|
+
)?.value;
|
|
2510
|
+
if (existing) {
|
|
2511
|
+
logger.info({ index }, "reusing existing write API key");
|
|
2512
|
+
return existing;
|
|
2513
|
+
}
|
|
2514
|
+
logger.info({ index }, "no reusable write key found; creating one");
|
|
2515
|
+
const created = await runAlgoliaCli([
|
|
2516
|
+
"apikeys",
|
|
2517
|
+
"create",
|
|
2518
|
+
"--indices",
|
|
2519
|
+
index,
|
|
2520
|
+
"--acl",
|
|
2521
|
+
WRITE_ACLS.join(","),
|
|
2522
|
+
"--description",
|
|
2523
|
+
`wizard write key for ${index}`,
|
|
2524
|
+
"-o",
|
|
2525
|
+
"json"
|
|
2526
|
+
]);
|
|
2527
|
+
const writeKey = parseCliJson(createdKeySchema, created, "apikeys create");
|
|
2528
|
+
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2529
|
+
return writeKey;
|
|
2530
|
+
}
|
|
2531
|
+
async function createSearchOnlyKey(index) {
|
|
2532
|
+
logger.info({ index }, "creating a search-only API key");
|
|
2533
|
+
const stdout = await runAlgoliaCli([
|
|
2534
|
+
"apikeys",
|
|
2535
|
+
"create",
|
|
2536
|
+
"--acl",
|
|
2537
|
+
"search",
|
|
2538
|
+
"--indices",
|
|
2539
|
+
index,
|
|
2540
|
+
"--description",
|
|
2541
|
+
`Algolia Wizard search-only key for ${index}`,
|
|
2542
|
+
"-o",
|
|
2543
|
+
"json"
|
|
2544
|
+
]);
|
|
2545
|
+
let payload;
|
|
2546
|
+
try {
|
|
2547
|
+
payload = JSON.parse(stdout);
|
|
2548
|
+
} catch {
|
|
2549
|
+
throw new Error("apikeys create returned output that is not valid JSON");
|
|
2550
|
+
}
|
|
2551
|
+
const created = createdKeySchema.parse(payload);
|
|
2552
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2553
|
+
return created;
|
|
2554
|
+
}
|
|
2555
|
+
async function apiKeyExists(key) {
|
|
2556
|
+
try {
|
|
2557
|
+
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
|
|
2558
|
+
return true;
|
|
2559
|
+
} catch (err) {
|
|
2560
|
+
return !/does not exist|not found|404/i.test(err.message);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2564
|
+
if (envKey) {
|
|
2565
|
+
await recordSearchKey(index, appId, envKey);
|
|
2566
|
+
return { key: envKey, source: "env" };
|
|
2567
|
+
}
|
|
2568
|
+
const stored = await getStoredSearchKey(index, appId);
|
|
2569
|
+
if (stored) {
|
|
2570
|
+
if (await apiKeyExists(stored)) {
|
|
2571
|
+
logger.info({ index, appId }, "reusing the stored search-only API key");
|
|
2572
|
+
return { key: stored, source: "config" };
|
|
2573
|
+
}
|
|
2574
|
+
logger.warn(
|
|
2575
|
+
{ index, appId },
|
|
2576
|
+
"the stored search-only API key no longer exists; creating a replacement"
|
|
2577
|
+
);
|
|
2578
|
+
await forgetSearchKey(index);
|
|
2579
|
+
}
|
|
2580
|
+
const key = await createSearchOnlyKey(index);
|
|
2581
|
+
await recordSearchKey(index, appId, key);
|
|
2582
|
+
return { key, source: "created" };
|
|
2583
|
+
}
|
|
2584
|
+
async function recordSearchKey(index, appId, key) {
|
|
2585
|
+
try {
|
|
2586
|
+
await storeSearchKey(index, appId, key);
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
logger.warn(
|
|
2589
|
+
{ err: err.message, index },
|
|
2590
|
+
"could not record the search-only API key; a later run may create another"
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2162
2596
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2163
2597
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2164
2598
|
function appendEnv(content, entries) {
|
|
@@ -2172,9 +2606,9 @@ function hasEnv(content, name) {
|
|
|
2172
2606
|
}
|
|
2173
2607
|
function writeCredentialsTool(ctx) {
|
|
2174
2608
|
return tool6({
|
|
2175
|
-
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials
|
|
2176
|
-
inputSchema:
|
|
2177
|
-
filePath:
|
|
2609
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
|
|
2610
|
+
inputSchema: z12.object({
|
|
2611
|
+
filePath: z12.string().describe(
|
|
2178
2612
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2179
2613
|
)
|
|
2180
2614
|
}),
|
|
@@ -2182,11 +2616,17 @@ function writeCredentialsTool(ctx) {
|
|
|
2182
2616
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2183
2617
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2184
2618
|
if (resolved.ok === false) return resolved.error;
|
|
2185
|
-
|
|
2619
|
+
const targetIndex = useWizard.getState().targetIndex;
|
|
2620
|
+
if (!targetIndex) {
|
|
2621
|
+
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2622
|
+
}
|
|
2623
|
+
let appId;
|
|
2624
|
+
let writeKey;
|
|
2186
2625
|
try {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2626
|
+
appId = (await requireApplication()).id;
|
|
2627
|
+
writeKey = await resolveWriteKey(targetIndex);
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2190
2630
|
}
|
|
2191
2631
|
try {
|
|
2192
2632
|
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
@@ -2194,7 +2634,7 @@ function writeCredentialsTool(ctx) {
|
|
|
2194
2634
|
}
|
|
2195
2635
|
let existing = "";
|
|
2196
2636
|
try {
|
|
2197
|
-
existing = await
|
|
2637
|
+
existing = await readFile4(resolved.target, "utf8");
|
|
2198
2638
|
} catch (err) {
|
|
2199
2639
|
if (err.code !== "ENOENT") throw err;
|
|
2200
2640
|
}
|
|
@@ -2205,8 +2645,8 @@ function writeCredentialsTool(ctx) {
|
|
|
2205
2645
|
return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
|
|
2206
2646
|
}
|
|
2207
2647
|
const envWithCredentials = appendEnv(existing, [
|
|
2208
|
-
[APP_ID_VAR,
|
|
2209
|
-
[API_KEY_VAR,
|
|
2648
|
+
[APP_ID_VAR, appId],
|
|
2649
|
+
[API_KEY_VAR, writeKey]
|
|
2210
2650
|
]);
|
|
2211
2651
|
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2212
2652
|
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
@@ -2220,16 +2660,24 @@ function writeCredentialsTool(ctx) {
|
|
|
2220
2660
|
|
|
2221
2661
|
// src/lib/tools/searchFiles.ts
|
|
2222
2662
|
import { tool as tool7 } from "ai";
|
|
2223
|
-
import
|
|
2224
|
-
import { readdir as readdir2, readFile as
|
|
2225
|
-
import { join as
|
|
2663
|
+
import z13 from "zod";
|
|
2664
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2665
|
+
import { join as join7 } from "node:path";
|
|
2226
2666
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2667
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2668
|
+
"node_modules",
|
|
2669
|
+
"dist",
|
|
2670
|
+
"build",
|
|
2671
|
+
"vendor",
|
|
2672
|
+
"venv",
|
|
2673
|
+
"__pycache__",
|
|
2674
|
+
"target"
|
|
2675
|
+
]);
|
|
2227
2676
|
async function walkFiles(dir) {
|
|
2228
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2229
2677
|
const out = [];
|
|
2230
2678
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2231
|
-
if (e.name.startsWith(".") ||
|
|
2232
|
-
const full =
|
|
2679
|
+
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
2680
|
+
const full = join7(dir, e.name);
|
|
2233
2681
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2234
2682
|
else if (e.isFile()) out.push(full);
|
|
2235
2683
|
}
|
|
@@ -2238,9 +2686,9 @@ async function walkFiles(dir) {
|
|
|
2238
2686
|
function searchFilesTool(ctx) {
|
|
2239
2687
|
return tool7({
|
|
2240
2688
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
2241
|
-
inputSchema:
|
|
2242
|
-
query:
|
|
2243
|
-
path:
|
|
2689
|
+
inputSchema: z13.object({
|
|
2690
|
+
query: z13.string().describe("JavaScript RegExp pattern to search for"),
|
|
2691
|
+
path: z13.string().optional().describe("Directory to search in (default: cwd)")
|
|
2244
2692
|
}),
|
|
2245
2693
|
execute: async ({ query, path = "." }) => {
|
|
2246
2694
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2262,7 +2710,7 @@ function searchFilesTool(ctx) {
|
|
|
2262
2710
|
for (const file of await walkFiles(resolved.target)) {
|
|
2263
2711
|
let content;
|
|
2264
2712
|
try {
|
|
2265
|
-
content = await
|
|
2713
|
+
content = await readFile5(file, "utf8");
|
|
2266
2714
|
} catch {
|
|
2267
2715
|
continue;
|
|
2268
2716
|
}
|
|
@@ -2282,92 +2730,194 @@ function searchFilesTool(ctx) {
|
|
|
2282
2730
|
});
|
|
2283
2731
|
}
|
|
2284
2732
|
|
|
2285
|
-
// src/lib/tools/
|
|
2733
|
+
// src/lib/tools/runShell.ts
|
|
2286
2734
|
import { tool as tool8 } from "ai";
|
|
2287
|
-
import
|
|
2735
|
+
import z14 from "zod";
|
|
2736
|
+
import { relative as relative2 } from "node:path";
|
|
2288
2737
|
|
|
2289
|
-
// src/lib/tools/utils/
|
|
2738
|
+
// src/lib/tools/utils/runShell.ts
|
|
2290
2739
|
import { spawn as spawn2 } from "node:child_process";
|
|
2291
|
-
|
|
2740
|
+
|
|
2741
|
+
// src/lib/tools/context.ts
|
|
2742
|
+
var DEFAULT_TOOL_LIMITS = {
|
|
2743
|
+
list: 10,
|
|
2744
|
+
search: 10,
|
|
2745
|
+
read: 20,
|
|
2746
|
+
match: 100,
|
|
2747
|
+
shell: 30
|
|
2748
|
+
};
|
|
2749
|
+
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2750
|
+
async function refuseByDefault() {
|
|
2751
|
+
return "reject";
|
|
2752
|
+
}
|
|
2753
|
+
function createShellContext(overrides = {}) {
|
|
2754
|
+
return {
|
|
2755
|
+
env: async () => ({}),
|
|
2756
|
+
timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
|
|
2757
|
+
approved: /* @__PURE__ */ new Set(),
|
|
2758
|
+
executions: [],
|
|
2759
|
+
approve: refuseByDefault,
|
|
2760
|
+
...overrides
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
|
|
2764
|
+
return {
|
|
2765
|
+
root: cwd,
|
|
2766
|
+
cwd,
|
|
2767
|
+
limits: { ...limits },
|
|
2768
|
+
counts: { list: 0, search: 0, read: 0, shell: 0 },
|
|
2769
|
+
shell: shell2
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
// src/lib/tools/utils/runShell.ts
|
|
2774
|
+
var SIGKILL_DELAY_MS = 5e3;
|
|
2775
|
+
var HEAD_CHARS = 4e3;
|
|
2776
|
+
var TAIL_CHARS = 8e3;
|
|
2777
|
+
function truncateOutput(output) {
|
|
2778
|
+
if (output.length <= HEAD_CHARS + TAIL_CHARS) return output;
|
|
2779
|
+
const omitted = output.length - HEAD_CHARS - TAIL_CHARS;
|
|
2780
|
+
return [
|
|
2781
|
+
output.slice(0, HEAD_CHARS),
|
|
2782
|
+
`
|
|
2783
|
+
\u2026 [${omitted} characters omitted] \u2026
|
|
2784
|
+
`,
|
|
2785
|
+
output.slice(-TAIL_CHARS)
|
|
2786
|
+
].join("");
|
|
2787
|
+
}
|
|
2788
|
+
function runShell(command, opts) {
|
|
2789
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
2790
|
+
const startedAt = Date.now();
|
|
2292
2791
|
return new Promise((resolve4) => {
|
|
2293
2792
|
let output = "";
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2793
|
+
let timedOut = false;
|
|
2794
|
+
let settled = false;
|
|
2795
|
+
const child = spawn2(command, {
|
|
2796
|
+
shell: true,
|
|
2797
|
+
cwd: opts.cwd,
|
|
2798
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2799
|
+
env: { ...process.env, ...opts.env }
|
|
2297
2800
|
});
|
|
2801
|
+
const finish = (exitCode) => {
|
|
2802
|
+
if (settled) return;
|
|
2803
|
+
settled = true;
|
|
2804
|
+
clearTimeout(timer);
|
|
2805
|
+
clearTimeout(killTimer);
|
|
2806
|
+
resolve4({
|
|
2807
|
+
exitCode,
|
|
2808
|
+
output: truncateOutput(output.trim()),
|
|
2809
|
+
timedOut,
|
|
2810
|
+
durationMs: Date.now() - startedAt
|
|
2811
|
+
});
|
|
2812
|
+
};
|
|
2813
|
+
let killTimer;
|
|
2814
|
+
const timer = setTimeout(() => {
|
|
2815
|
+
timedOut = true;
|
|
2816
|
+
output += `
|
|
2817
|
+
[timed out after ${timeoutMs}ms]`;
|
|
2818
|
+
child.kill("SIGTERM");
|
|
2819
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), SIGKILL_DELAY_MS);
|
|
2820
|
+
}, timeoutMs);
|
|
2298
2821
|
child.stdout?.on("data", (d) => output += d);
|
|
2299
2822
|
child.stderr?.on("data", (d) => output += d);
|
|
2300
|
-
child.on(
|
|
2301
|
-
|
|
2302
|
-
(
|
|
2303
|
-
);
|
|
2304
|
-
child.on("close", (code) =>
|
|
2823
|
+
child.on("error", (err) => {
|
|
2824
|
+
output += `Failed to run ${command}: ${err.message}`;
|
|
2825
|
+
finish(1);
|
|
2826
|
+
});
|
|
2827
|
+
child.on("close", (code) => finish(code ?? 1));
|
|
2305
2828
|
});
|
|
2306
2829
|
}
|
|
2307
2830
|
|
|
2308
|
-
// src/lib/tools/
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
]
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2327
|
-
}
|
|
2328
|
-
async function detectPackageManager(cwd) {
|
|
2329
|
-
try {
|
|
2330
|
-
const pkg = await readPackageJson(cwd);
|
|
2331
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2332
|
-
} catch {
|
|
2333
|
-
}
|
|
2334
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// src/lib/tools/repoVerification.ts
|
|
2338
|
-
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2339
|
-
async function runRepoVerificationCheck() {
|
|
2340
|
-
let pkg;
|
|
2341
|
-
try {
|
|
2342
|
-
pkg = await readPackageJson();
|
|
2343
|
-
} catch (err) {
|
|
2344
|
-
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2345
|
-
return { ok: false, checks: [], limitation };
|
|
2346
|
-
}
|
|
2347
|
-
const scripts = pkg.scripts ?? {};
|
|
2348
|
-
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2349
|
-
if (present.length === 0) {
|
|
2350
|
-
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2351
|
-
return { ok: false, checks: [], limitation };
|
|
2352
|
-
}
|
|
2353
|
-
const pm = await detectPackageManager(process.cwd());
|
|
2354
|
-
const checks = [];
|
|
2355
|
-
for (const script of present) {
|
|
2356
|
-
const command = `${pm} run ${script}`;
|
|
2357
|
-
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2358
|
-
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
2359
|
-
}
|
|
2360
|
-
return { ok: checks.every((c) => c.ok), checks };
|
|
2831
|
+
// src/lib/tools/runShell.ts
|
|
2832
|
+
function approvalKey(cwd, command) {
|
|
2833
|
+
return `${cwd}\0${command}`;
|
|
2834
|
+
}
|
|
2835
|
+
function storeApproval(root) {
|
|
2836
|
+
return async (req) => {
|
|
2837
|
+
const rel = relative2(root, req.cwd);
|
|
2838
|
+
const answer = await useWizard.getState().requestUserInput({
|
|
2839
|
+
prompt: "Run this command?",
|
|
2840
|
+
promptType: "commandApproval",
|
|
2841
|
+
options: [],
|
|
2842
|
+
command: {
|
|
2843
|
+
...req,
|
|
2844
|
+
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
2845
|
+
}
|
|
2846
|
+
});
|
|
2847
|
+
return answer === "approve" || answer === "always" ? answer : "reject";
|
|
2848
|
+
};
|
|
2361
2849
|
}
|
|
2362
|
-
|
|
2363
|
-
// src/lib/tools/verifyImplementation.ts
|
|
2364
|
-
function verifyImplementationTool() {
|
|
2850
|
+
function runShellTool(ctx) {
|
|
2365
2851
|
return tool8({
|
|
2366
|
-
description: "Run the
|
|
2367
|
-
inputSchema:
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2852
|
+
description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
2853
|
+
inputSchema: z14.object({
|
|
2854
|
+
command: z14.string().describe(
|
|
2855
|
+
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
2856
|
+
),
|
|
2857
|
+
cwd: z14.string().optional().describe(
|
|
2858
|
+
"Directory to run in, relative to the project root. Defaults to the project root."
|
|
2859
|
+
),
|
|
2860
|
+
explanation: z14.string().describe(
|
|
2861
|
+
"One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
|
|
2862
|
+
)
|
|
2863
|
+
}),
|
|
2864
|
+
execute: async ({ command, cwd, explanation }) => {
|
|
2865
|
+
if (++ctx.counts.shell > ctx.limits.shell) {
|
|
2866
|
+
return `Refused: command limit (${ctx.limits.shell}) reached. Stop running commands and report what you have.`;
|
|
2867
|
+
}
|
|
2868
|
+
const resolved = resolveInRoot(ctx, cwd ?? ".");
|
|
2869
|
+
if (!resolved.ok) return resolved.error;
|
|
2870
|
+
logger.info({ command, cwd: resolved.target }, "called runShell tool");
|
|
2871
|
+
const key = approvalKey(resolved.target, command);
|
|
2872
|
+
const decision = ctx.shell.approved.has(key) ? "approve" : await ctx.shell.approve({
|
|
2873
|
+
command,
|
|
2874
|
+
cwd: resolved.target,
|
|
2875
|
+
explanation
|
|
2876
|
+
});
|
|
2877
|
+
if (decision === "reject") {
|
|
2878
|
+
ctx.shell.executions.push({
|
|
2879
|
+
command,
|
|
2880
|
+
cwd: resolved.target,
|
|
2881
|
+
approved: false
|
|
2882
|
+
});
|
|
2883
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2884
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2885
|
+
}
|
|
2886
|
+
if (decision === "always") ctx.shell.approved.add(key);
|
|
2887
|
+
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
2888
|
+
const env = await ctx.shell.env().catch((err) => {
|
|
2889
|
+
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
2890
|
+
return {};
|
|
2891
|
+
});
|
|
2892
|
+
const run2 = await (ctx.shell.run ?? runShell)(command, {
|
|
2893
|
+
cwd: resolved.target,
|
|
2894
|
+
env,
|
|
2895
|
+
timeoutMs: ctx.shell.timeoutMs
|
|
2896
|
+
});
|
|
2897
|
+
logger.info(
|
|
2898
|
+
{
|
|
2899
|
+
command,
|
|
2900
|
+
exitCode: run2.exitCode,
|
|
2901
|
+
timedOut: run2.timedOut,
|
|
2902
|
+
durationMs: run2.durationMs
|
|
2903
|
+
},
|
|
2904
|
+
"runShell finished"
|
|
2905
|
+
);
|
|
2906
|
+
await markInteraction();
|
|
2907
|
+
ctx.shell.executions.push({
|
|
2908
|
+
command,
|
|
2909
|
+
cwd: resolved.target,
|
|
2910
|
+
approved: true,
|
|
2911
|
+
exitCode: run2.exitCode,
|
|
2912
|
+
output: run2.output,
|
|
2913
|
+
timedOut: run2.timedOut,
|
|
2914
|
+
durationMs: run2.durationMs
|
|
2915
|
+
});
|
|
2916
|
+
return {
|
|
2917
|
+
exitCode: run2.exitCode,
|
|
2918
|
+
timedOut: run2.timedOut,
|
|
2919
|
+
output: run2.output
|
|
2920
|
+
};
|
|
2371
2921
|
}
|
|
2372
2922
|
});
|
|
2373
2923
|
}
|
|
@@ -2378,7 +2928,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
2378
2928
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2379
2929
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2380
2930
|
import { dirname as dirname6 } from "node:path";
|
|
2381
|
-
import
|
|
2931
|
+
import z15 from "zod";
|
|
2382
2932
|
var DATA_DIR = ".algolia-wizard/data";
|
|
2383
2933
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
2384
2934
|
var MAX_RECORDS = 100;
|
|
@@ -2390,17 +2940,17 @@ var anthropic = createAnthropic({
|
|
|
2390
2940
|
function generateRecordTool(ctx) {
|
|
2391
2941
|
return tool9({
|
|
2392
2942
|
description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
|
|
2393
|
-
inputSchema:
|
|
2394
|
-
entityName:
|
|
2395
|
-
attributes:
|
|
2396
|
-
count:
|
|
2397
|
-
hint:
|
|
2943
|
+
inputSchema: z15.object({
|
|
2944
|
+
entityName: z15.string().describe("Name of the entity to generate records for."),
|
|
2945
|
+
attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
|
|
2946
|
+
count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2947
|
+
hint: z15.string().optional().describe("Optional context to steer realistic values.")
|
|
2398
2948
|
}),
|
|
2399
2949
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
2400
2950
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
2401
2951
|
try {
|
|
2402
|
-
const value =
|
|
2403
|
-
const recordSchema =
|
|
2952
|
+
const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
|
|
2953
|
+
const recordSchema = z15.object(
|
|
2404
2954
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
2405
2955
|
);
|
|
2406
2956
|
const generateBatch = async (batchCount) => {
|
|
@@ -2410,8 +2960,8 @@ function generateRecordTool(ctx) {
|
|
|
2410
2960
|
const { output } = await generateText({
|
|
2411
2961
|
model: anthropic(RECORD_MODEL),
|
|
2412
2962
|
output: Output.object({
|
|
2413
|
-
schema:
|
|
2414
|
-
records:
|
|
2963
|
+
schema: z15.object({
|
|
2964
|
+
records: z15.array(recordSchema).length(batchCount)
|
|
2415
2965
|
})
|
|
2416
2966
|
}),
|
|
2417
2967
|
prompt: [
|
|
@@ -2458,7 +3008,7 @@ function generateRecordTool(ctx) {
|
|
|
2458
3008
|
return {
|
|
2459
3009
|
filePath: relPath,
|
|
2460
3010
|
count: records.length,
|
|
2461
|
-
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime
|
|
3011
|
+
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime using your language's standard JSON support \u2014 do not inline the records as literals.`
|
|
2462
3012
|
};
|
|
2463
3013
|
} catch (err) {
|
|
2464
3014
|
return `Error generating records: ${err.message}`;
|
|
@@ -2469,12 +3019,12 @@ function generateRecordTool(ctx) {
|
|
|
2469
3019
|
|
|
2470
3020
|
// src/lib/tools/notifyUser.ts
|
|
2471
3021
|
import { tool as tool10 } from "ai";
|
|
2472
|
-
import
|
|
3022
|
+
import z16 from "zod";
|
|
2473
3023
|
function notifyUserTool() {
|
|
2474
3024
|
return tool10({
|
|
2475
3025
|
description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
|
|
2476
|
-
inputSchema:
|
|
2477
|
-
message:
|
|
3026
|
+
inputSchema: z16.object({
|
|
3027
|
+
message: z16.string().describe(
|
|
2478
3028
|
"Short, plain-language description of what you are doing now."
|
|
2479
3029
|
)
|
|
2480
3030
|
}),
|
|
@@ -2486,22 +3036,6 @@ function notifyUserTool() {
|
|
|
2486
3036
|
});
|
|
2487
3037
|
}
|
|
2488
3038
|
|
|
2489
|
-
// src/lib/tools/context.ts
|
|
2490
|
-
var DEFAULT_TOOL_LIMITS = {
|
|
2491
|
-
list: 10,
|
|
2492
|
-
search: 10,
|
|
2493
|
-
read: 20,
|
|
2494
|
-
match: 100
|
|
2495
|
-
};
|
|
2496
|
-
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
2497
|
-
return {
|
|
2498
|
-
root: cwd,
|
|
2499
|
-
cwd,
|
|
2500
|
-
limits,
|
|
2501
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2502
|
-
};
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
3039
|
// src/lib/tools/index.ts
|
|
2506
3040
|
function withLogging(name, def) {
|
|
2507
3041
|
const execute = def.execute;
|
|
@@ -2533,10 +3067,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2533
3067
|
writeCredentialsTool(ctx)
|
|
2534
3068
|
),
|
|
2535
3069
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2536
|
-
|
|
2537
|
-
"verifyImplementation",
|
|
2538
|
-
verifyImplementationTool()
|
|
2539
|
-
),
|
|
3070
|
+
runShell: withLogging("runShell", runShellTool(ctx)),
|
|
2540
3071
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2541
3072
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
2542
3073
|
};
|
|
@@ -2571,7 +3102,7 @@ async function runAgent(req) {
|
|
|
2571
3102
|
baseURL: PROXY_BASE_URL,
|
|
2572
3103
|
fetch: proxyFetch
|
|
2573
3104
|
});
|
|
2574
|
-
const toolContext = createToolContext();
|
|
3105
|
+
const toolContext = req.toolContext ?? createToolContext();
|
|
2575
3106
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2576
3107
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2577
3108
|
const instructions = [
|
|
@@ -2636,7 +3167,11 @@ async function runAgent(req) {
|
|
|
2636
3167
|
"runAgent finished"
|
|
2637
3168
|
);
|
|
2638
3169
|
logger.info(
|
|
2639
|
-
{
|
|
3170
|
+
{
|
|
3171
|
+
counts: toolContext.counts,
|
|
3172
|
+
limits: toolContext.limits,
|
|
3173
|
+
commandsRun: toolContext.shell.executions.length
|
|
3174
|
+
},
|
|
2640
3175
|
"tool usage"
|
|
2641
3176
|
);
|
|
2642
3177
|
const toolResults = await stream.toolResults;
|
|
@@ -2654,16 +3189,16 @@ async function runAgent(req) {
|
|
|
2654
3189
|
}
|
|
2655
3190
|
|
|
2656
3191
|
// src/actions/detectLanguage.ts
|
|
2657
|
-
import
|
|
2658
|
-
var detectLanguageSchema =
|
|
2659
|
-
languages:
|
|
2660
|
-
frameworks:
|
|
3192
|
+
import z19 from "zod";
|
|
3193
|
+
var detectLanguageSchema = z19.object({
|
|
3194
|
+
languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
|
|
3195
|
+
frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
|
|
2661
3196
|
});
|
|
2662
3197
|
var detectLanguage = () => runAgent({
|
|
2663
3198
|
instructions: [
|
|
2664
3199
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
2665
|
-
"If a superset language is found, exclude the subset language.
|
|
2666
|
-
"If a meta-framework is used, exclude the framework. Next
|
|
3200
|
+
"If a superset language is found, exclude the subset language (e.g. TypeScript over JavaScript).",
|
|
3201
|
+
"If a meta-framework is used, exclude the framework it builds on (e.g. Next.js over React, Rails over Rack).",
|
|
2667
3202
|
"Return the exact version",
|
|
2668
3203
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2669
3204
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2675,31 +3210,31 @@ var detectLanguage = () => runAgent({
|
|
|
2675
3210
|
});
|
|
2676
3211
|
|
|
2677
3212
|
// src/actions/analyzeCodebase.ts
|
|
2678
|
-
import
|
|
3213
|
+
import z20 from "zod";
|
|
2679
3214
|
var READONLY_TOOLS = [
|
|
2680
3215
|
"listFiles",
|
|
2681
3216
|
"changeDirectory",
|
|
2682
3217
|
"readFile",
|
|
2683
3218
|
"searchFiles"
|
|
2684
3219
|
];
|
|
2685
|
-
var ingestionAnalysisSchema =
|
|
2686
|
-
ingestionAnalysis:
|
|
2687
|
-
|
|
2688
|
-
name:
|
|
2689
|
-
paths:
|
|
3220
|
+
var ingestionAnalysisSchema = z20.object({
|
|
3221
|
+
ingestionAnalysis: z20.array(
|
|
3222
|
+
z20.object({
|
|
3223
|
+
name: z20.string(),
|
|
3224
|
+
paths: z20.array(z20.string()),
|
|
2690
3225
|
// indexable fields the agent found for this entity
|
|
2691
|
-
attributes:
|
|
3226
|
+
attributes: z20.array(z20.string())
|
|
2692
3227
|
})
|
|
2693
3228
|
)
|
|
2694
3229
|
});
|
|
2695
|
-
var searchImplementationAnalysisSchema =
|
|
2696
|
-
searchImplementationAnalysis:
|
|
3230
|
+
var searchImplementationAnalysisSchema = z20.object({
|
|
3231
|
+
searchImplementationAnalysis: z20.string()
|
|
2697
3232
|
});
|
|
2698
|
-
var verificationSchema =
|
|
2699
|
-
verification:
|
|
3233
|
+
var verificationSchema = z20.object({
|
|
3234
|
+
verification: z20.array(z20.string())
|
|
2700
3235
|
});
|
|
2701
3236
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
2702
|
-
var analyzeCodebaseSchema =
|
|
3237
|
+
var analyzeCodebaseSchema = z20.object({
|
|
2703
3238
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
2704
3239
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
2705
3240
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -2761,7 +3296,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2761
3296
|
// package.json
|
|
2762
3297
|
var package_default = {
|
|
2763
3298
|
name: "@algolia/wizard",
|
|
2764
|
-
version: "0.9.0-rc.
|
|
3299
|
+
version: "0.9.0-rc.87.83",
|
|
2765
3300
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2766
3301
|
type: "module",
|
|
2767
3302
|
engines: {
|
|
@@ -2809,7 +3344,6 @@ var package_default = {
|
|
|
2809
3344
|
dependencies: {
|
|
2810
3345
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2811
3346
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2812
|
-
"@algolia/cli": "^5.11.0",
|
|
2813
3347
|
"@hono/node-server": "^2.0.10",
|
|
2814
3348
|
"@segment/analytics-node": "^3.1.0",
|
|
2815
3349
|
ai: "^6.0.190",
|
|
@@ -2823,7 +3357,6 @@ var package_default = {
|
|
|
2823
3357
|
nanoid: "^5.1.15",
|
|
2824
3358
|
pino: "^10.3.1",
|
|
2825
3359
|
react: "^19.2.7",
|
|
2826
|
-
toml: "^4.1.1",
|
|
2827
3360
|
varlock: "^1.5.1",
|
|
2828
3361
|
zod: "^4.4.3",
|
|
2829
3362
|
zustand: "^5.0.14"
|
|
@@ -2881,8 +3414,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
2881
3414
|
}
|
|
2882
3415
|
|
|
2883
3416
|
// src/actions/confirmLanguage.ts
|
|
2884
|
-
import
|
|
2885
|
-
var confirmLanguageSchema =
|
|
3417
|
+
import z22 from "zod";
|
|
3418
|
+
var confirmLanguageSchema = z22.object({
|
|
2886
3419
|
languages: detectLanguageSchema.shape.languages
|
|
2887
3420
|
});
|
|
2888
3421
|
async function confirmLanguage(ctx) {
|
|
@@ -2903,17 +3436,19 @@ async function confirmLanguage(ctx) {
|
|
|
2903
3436
|
}
|
|
2904
3437
|
|
|
2905
3438
|
// src/actions/confirmFramework.ts
|
|
2906
|
-
import
|
|
2907
|
-
var confirmFrameworkSchema =
|
|
3439
|
+
import z23 from "zod";
|
|
3440
|
+
var confirmFrameworkSchema = z23.object({
|
|
2908
3441
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
2909
3442
|
});
|
|
2910
3443
|
var CURATED_FRAMEWORKS = [
|
|
2911
3444
|
"Next.js",
|
|
2912
3445
|
"React",
|
|
2913
3446
|
"Vue",
|
|
2914
|
-
"
|
|
2915
|
-
"
|
|
2916
|
-
"
|
|
3447
|
+
"Vanilla JS",
|
|
3448
|
+
"Django",
|
|
3449
|
+
"Laravel",
|
|
3450
|
+
"Rails",
|
|
3451
|
+
"Symfony"
|
|
2917
3452
|
];
|
|
2918
3453
|
var OTHER_OPTION = "Other";
|
|
2919
3454
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
@@ -2926,12 +3461,15 @@ var FRAMEWORK_ALIASES = {
|
|
|
2926
3461
|
vuejs: "vue",
|
|
2927
3462
|
angular: "angular",
|
|
2928
3463
|
angularjs: "angular",
|
|
2929
|
-
svelte: "svelte",
|
|
2930
|
-
sveltekit: "svelte",
|
|
2931
3464
|
vanillajs: "vanillajs",
|
|
2932
3465
|
vanilla: "vanillajs",
|
|
2933
3466
|
javascript: "vanillajs",
|
|
2934
|
-
js: "vanillajs"
|
|
3467
|
+
js: "vanillajs",
|
|
3468
|
+
django: "django",
|
|
3469
|
+
laravel: "laravel",
|
|
3470
|
+
rails: "rails",
|
|
3471
|
+
rubyonrails: "rails",
|
|
3472
|
+
symfony: "symfony"
|
|
2935
3473
|
};
|
|
2936
3474
|
var isSameFramework = (a, b) => {
|
|
2937
3475
|
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
@@ -3032,8 +3570,8 @@ async function promptUser(ctx, params) {
|
|
|
3032
3570
|
}
|
|
3033
3571
|
|
|
3034
3572
|
// src/actions/confirmEntities.ts
|
|
3035
|
-
import
|
|
3036
|
-
var confirmEntitiesSchema =
|
|
3573
|
+
import z24 from "zod";
|
|
3574
|
+
var confirmEntitiesSchema = z24.object({
|
|
3037
3575
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3038
3576
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3039
3577
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3103,15 +3641,15 @@ async function confirmEntities(ctx) {
|
|
|
3103
3641
|
}
|
|
3104
3642
|
|
|
3105
3643
|
// src/actions/review.ts
|
|
3106
|
-
import { z as
|
|
3107
|
-
var reviewSchema =
|
|
3644
|
+
import { z as z25 } from "zod";
|
|
3645
|
+
var reviewSchema = z25.object({
|
|
3108
3646
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3109
3647
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3110
3648
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3111
3649
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3112
|
-
summaryPoints:
|
|
3113
|
-
reviewPrompt:
|
|
3114
|
-
nextSteps:
|
|
3650
|
+
summaryPoints: z25.array(z25.string()),
|
|
3651
|
+
reviewPrompt: z25.string(),
|
|
3652
|
+
nextSteps: z25.array(z25.string())
|
|
3115
3653
|
});
|
|
3116
3654
|
function formatCompletedSteps(steps) {
|
|
3117
3655
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3162,19 +3700,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3162
3700
|
};
|
|
3163
3701
|
|
|
3164
3702
|
// src/actions/implement.ts
|
|
3165
|
-
import
|
|
3703
|
+
import z26 from "zod";
|
|
3166
3704
|
|
|
3167
3705
|
// src/lib/worktree.ts
|
|
3168
|
-
import { execFile
|
|
3169
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3170
|
-
import {
|
|
3171
|
-
basename as basename2,
|
|
3172
|
-
dirname as dirname7,
|
|
3173
|
-
isAbsolute as isAbsolute2,
|
|
3174
|
-
join as join10,
|
|
3175
|
-
relative as relative2,
|
|
3176
|
-
resolve as resolve3
|
|
3177
|
-
} from "node:path";
|
|
3706
|
+
import { execFile } from "node:child_process";
|
|
3707
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile6, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3708
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
|
|
3178
3709
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
3179
3710
|
var MAX_WIZARD_WORKTREES = 3;
|
|
3180
3711
|
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
@@ -3205,7 +3736,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3205
3736
|
return out.trim().length > 0;
|
|
3206
3737
|
}
|
|
3207
3738
|
async function pruneOldWorktrees(repoRoot) {
|
|
3208
|
-
const dir =
|
|
3739
|
+
const dir = join8(stateDir(repoRoot), "worktrees");
|
|
3209
3740
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3210
3741
|
for (const slug of stale) {
|
|
3211
3742
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3216,7 +3747,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3216
3747
|
"worktree",
|
|
3217
3748
|
"remove",
|
|
3218
3749
|
"--force",
|
|
3219
|
-
|
|
3750
|
+
join8(dir, slug)
|
|
3220
3751
|
]);
|
|
3221
3752
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3222
3753
|
} catch (err) {
|
|
@@ -3230,113 +3761,13 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3230
3761
|
async function createWorktree(repoRoot) {
|
|
3231
3762
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3232
3763
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3233
|
-
const path =
|
|
3764
|
+
const path = join8(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3234
3765
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3235
3766
|
await pruneOldWorktrees(repoRoot);
|
|
3236
3767
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3237
3768
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3238
3769
|
return { path, branch };
|
|
3239
3770
|
}
|
|
3240
|
-
async function installWorktreeDeps(worktreePath) {
|
|
3241
|
-
try {
|
|
3242
|
-
await readPackageJson(worktreePath);
|
|
3243
|
-
} catch {
|
|
3244
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3245
|
-
}
|
|
3246
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3247
|
-
return new Promise((resolve4) => {
|
|
3248
|
-
let output = "";
|
|
3249
|
-
const child = spawn3(pm, ["install"], {
|
|
3250
|
-
cwd: worktreePath,
|
|
3251
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3252
|
-
});
|
|
3253
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3254
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3255
|
-
child.on(
|
|
3256
|
-
"error",
|
|
3257
|
-
(err) => resolve4({
|
|
3258
|
-
ok: false,
|
|
3259
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3260
|
-
})
|
|
3261
|
-
);
|
|
3262
|
-
child.on(
|
|
3263
|
-
"close",
|
|
3264
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3265
|
-
);
|
|
3266
|
-
});
|
|
3267
|
-
}
|
|
3268
|
-
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3269
|
-
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
3270
|
-
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3271
|
-
return {
|
|
3272
|
-
ok: false,
|
|
3273
|
-
reason: `entrypoint "${entrypoint}" is not a plain file path`
|
|
3274
|
-
};
|
|
3275
|
-
}
|
|
3276
|
-
const target = resolve3(worktreePath, entrypoint);
|
|
3277
|
-
const rel = relative2(worktreePath, target);
|
|
3278
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
3279
|
-
return {
|
|
3280
|
-
ok: false,
|
|
3281
|
-
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3282
|
-
};
|
|
3283
|
-
}
|
|
3284
|
-
return { ok: true, target };
|
|
3285
|
-
}
|
|
3286
|
-
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3287
|
-
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
3288
|
-
return {
|
|
3289
|
-
ran: false,
|
|
3290
|
-
ok: false,
|
|
3291
|
-
output: "",
|
|
3292
|
-
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
3293
|
-
};
|
|
3294
|
-
}
|
|
3295
|
-
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
3296
|
-
if (!validated.ok) {
|
|
3297
|
-
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3298
|
-
}
|
|
3299
|
-
try {
|
|
3300
|
-
if (!(await stat2(validated.target)).isFile()) {
|
|
3301
|
-
return {
|
|
3302
|
-
ran: false,
|
|
3303
|
-
ok: false,
|
|
3304
|
-
output: "",
|
|
3305
|
-
reason: `entrypoint "${entrypoint}" is not a file`
|
|
3306
|
-
};
|
|
3307
|
-
}
|
|
3308
|
-
} catch {
|
|
3309
|
-
return {
|
|
3310
|
-
ran: false,
|
|
3311
|
-
ok: false,
|
|
3312
|
-
output: "",
|
|
3313
|
-
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3314
|
-
};
|
|
3315
|
-
}
|
|
3316
|
-
return new Promise((resolveRun) => {
|
|
3317
|
-
let output = "";
|
|
3318
|
-
const child = spawn3(runtime, [entrypoint], {
|
|
3319
|
-
cwd: worktreePath,
|
|
3320
|
-
shell: false,
|
|
3321
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3322
|
-
env: { ...process.env, ...env }
|
|
3323
|
-
});
|
|
3324
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3325
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3326
|
-
child.on(
|
|
3327
|
-
"error",
|
|
3328
|
-
(err) => resolveRun({
|
|
3329
|
-
ran: true,
|
|
3330
|
-
ok: false,
|
|
3331
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3332
|
-
})
|
|
3333
|
-
);
|
|
3334
|
-
child.on(
|
|
3335
|
-
"close",
|
|
3336
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3337
|
-
);
|
|
3338
|
-
});
|
|
3339
|
-
}
|
|
3340
3771
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3341
3772
|
const trimmed = sourcePath.trim();
|
|
3342
3773
|
if (!trimmed) {
|
|
@@ -3350,8 +3781,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3350
3781
|
} catch {
|
|
3351
3782
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3352
3783
|
}
|
|
3353
|
-
const relPath =
|
|
3354
|
-
const dest =
|
|
3784
|
+
const relPath = join8(ingestDir, basename2(source));
|
|
3785
|
+
const dest = join8(worktreePath, relPath);
|
|
3355
3786
|
try {
|
|
3356
3787
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3357
3788
|
await copyFile(source, dest);
|
|
@@ -3366,11 +3797,28 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3366
3797
|
function hasEnvVar(content, name) {
|
|
3367
3798
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3368
3799
|
}
|
|
3800
|
+
async function readEnvVar(worktreePath, name) {
|
|
3801
|
+
let content;
|
|
3802
|
+
try {
|
|
3803
|
+
content = await readFile6(join8(worktreePath, ".env"), "utf8");
|
|
3804
|
+
} catch (err) {
|
|
3805
|
+
if (err.code !== "ENOENT") throw err;
|
|
3806
|
+
return void 0;
|
|
3807
|
+
}
|
|
3808
|
+
const match = new RegExp(
|
|
3809
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3810
|
+
"m"
|
|
3811
|
+
).exec(content);
|
|
3812
|
+
if (!match) return void 0;
|
|
3813
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3814
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3815
|
+
return value;
|
|
3816
|
+
}
|
|
3369
3817
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3370
|
-
const target =
|
|
3818
|
+
const target = join8(worktreePath, ".env");
|
|
3371
3819
|
let existing = "";
|
|
3372
3820
|
try {
|
|
3373
|
-
existing = await
|
|
3821
|
+
existing = await readFile6(target, "utf8");
|
|
3374
3822
|
} catch (err) {
|
|
3375
3823
|
if (err.code !== "ENOENT") throw err;
|
|
3376
3824
|
}
|
|
@@ -3438,64 +3886,16 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3438
3886
|
}
|
|
3439
3887
|
}
|
|
3440
3888
|
|
|
3441
|
-
// src/lib/algoliaApiKey.ts
|
|
3442
|
-
import { z as z23 } from "zod";
|
|
3443
|
-
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
3444
|
-
var apiKeySchema = z23.object({
|
|
3445
|
-
value: z23.string().min(1),
|
|
3446
|
-
acl: z23.array(z23.string()).default([]),
|
|
3447
|
-
indexes: z23.array(z23.string()).default([])
|
|
3448
|
-
});
|
|
3449
|
-
var apiKeyListSchema = z23.object({
|
|
3450
|
-
items: z23.array(apiKeySchema).optional(),
|
|
3451
|
-
keys: z23.array(apiKeySchema).optional()
|
|
3452
|
-
}).transform((o) => o.items ?? o.keys ?? []);
|
|
3453
|
-
var createdKeySchema = z23.object({
|
|
3454
|
-
key: z23.string().min(1).optional(),
|
|
3455
|
-
value: z23.string().min(1).optional()
|
|
3456
|
-
});
|
|
3457
|
-
function canReuse(key, index) {
|
|
3458
|
-
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
3459
|
-
}
|
|
3460
|
-
async function createSearchKey(index) {
|
|
3461
|
-
const stdout = await runAlgoliaCli([
|
|
3462
|
-
"apikeys",
|
|
3463
|
-
"create",
|
|
3464
|
-
"--indices",
|
|
3465
|
-
index,
|
|
3466
|
-
"--acl",
|
|
3467
|
-
"search,browse",
|
|
3468
|
-
"--description",
|
|
3469
|
-
`wizard search-only key for ${index}`,
|
|
3470
|
-
"-o",
|
|
3471
|
-
"json"
|
|
3472
|
-
]);
|
|
3473
|
-
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
3474
|
-
const created = key ?? value;
|
|
3475
|
-
if (!created) throw new Error("apikeys create returned no key value");
|
|
3476
|
-
return created;
|
|
3477
|
-
}
|
|
3478
|
-
async function resolveSearchOnlyKey(index) {
|
|
3479
|
-
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
3480
|
-
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
3481
|
-
if (existing) {
|
|
3482
|
-
logger.info({ index }, "reusing existing search-only API key");
|
|
3483
|
-
return existing;
|
|
3484
|
-
}
|
|
3485
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
3486
|
-
return createSearchKey(index);
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
3889
|
// src/lib/algoliaDocs.ts
|
|
3490
|
-
import { readFileSync, readdirSync, existsSync
|
|
3491
|
-
import { dirname as dirname8, join as
|
|
3890
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
3891
|
+
import { dirname as dirname8, join as join9 } from "node:path";
|
|
3492
3892
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3493
|
-
var DOCS_SUBPATH =
|
|
3893
|
+
var DOCS_SUBPATH = join9("docs", "algolia-sdk");
|
|
3494
3894
|
function findDocsDir() {
|
|
3495
3895
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3496
3896
|
for (; ; ) {
|
|
3497
|
-
const candidate =
|
|
3498
|
-
if (
|
|
3897
|
+
const candidate = join9(dir, DOCS_SUBPATH);
|
|
3898
|
+
if (existsSync(candidate)) return candidate;
|
|
3499
3899
|
const parent = dirname8(dir);
|
|
3500
3900
|
if (parent === dir) return void 0;
|
|
3501
3901
|
dir = parent;
|
|
@@ -3517,7 +3917,7 @@ function loadAlgoliaDoc(language) {
|
|
|
3517
3917
|
);
|
|
3518
3918
|
return "";
|
|
3519
3919
|
}
|
|
3520
|
-
return readFileSync(
|
|
3920
|
+
return readFileSync(join9(docsDir, files[0]), "utf8").trim();
|
|
3521
3921
|
}
|
|
3522
3922
|
function getNamedDoc(name, language) {
|
|
3523
3923
|
const docsDir = findDocsDir();
|
|
@@ -3525,14 +3925,15 @@ function getNamedDoc(name, language) {
|
|
|
3525
3925
|
logger.warn("docs/algolia-sdk not found");
|
|
3526
3926
|
return "";
|
|
3527
3927
|
}
|
|
3528
|
-
const file =
|
|
3529
|
-
if (!
|
|
3928
|
+
const file = join9(docsDir, `${name}-${language}.md`);
|
|
3929
|
+
if (!existsSync(file)) {
|
|
3530
3930
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
3531
3931
|
return "";
|
|
3532
3932
|
}
|
|
3533
3933
|
return readFileSync(file, "utf8").trim();
|
|
3534
3934
|
}
|
|
3535
3935
|
function getFrameworkSpecificDoc(frameworks) {
|
|
3936
|
+
if (frameworks.length === 0) return "";
|
|
3536
3937
|
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3537
3938
|
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3538
3939
|
return loadAlgoliaDoc("vue");
|
|
@@ -3540,9 +3941,6 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
3540
3941
|
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3541
3942
|
return loadAlgoliaDoc("react");
|
|
3542
3943
|
}
|
|
3543
|
-
if (fw.includes("angular")) {
|
|
3544
|
-
return loadAlgoliaDoc("angular");
|
|
3545
|
-
}
|
|
3546
3944
|
return loadAlgoliaDoc("js");
|
|
3547
3945
|
}
|
|
3548
3946
|
|
|
@@ -3552,78 +3950,63 @@ function shellQuote(value) {
|
|
|
3552
3950
|
}
|
|
3553
3951
|
|
|
3554
3952
|
// src/actions/implement.ts
|
|
3555
|
-
var implementSchema =
|
|
3556
|
-
filesChanged:
|
|
3557
|
-
summary:
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
// machine-readable count line; absent when the script didn't run or emitted
|
|
3569
|
-
// no parseable count.
|
|
3570
|
-
ingestRecordCount: z24.number().optional(),
|
|
3571
|
-
// Wall-clock duration of the run-now ingestion execution, in ms.
|
|
3572
|
-
ingestDurationMs: z24.number().optional(),
|
|
3573
|
-
ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
|
|
3574
|
-
// Suggested names/values, built from framework detection. The search agent is
|
|
3575
|
-
// instructed to rename the prefix if it doesn't match the project's build
|
|
3576
|
-
// tool, so the names it actually wrote can differ — treat these as hints, not
|
|
3577
|
-
// ground truth (the agent's summary carries the final names).
|
|
3578
|
-
searchEnvVars: z24.array(
|
|
3579
|
-
z24.object({
|
|
3580
|
-
name: z24.string(),
|
|
3581
|
-
value: z24.string()
|
|
3953
|
+
var implementSchema = z26.object({
|
|
3954
|
+
filesChanged: z26.array(z26.string()),
|
|
3955
|
+
summary: z26.string(),
|
|
3956
|
+
worktreePath: z26.string().optional(),
|
|
3957
|
+
ingestCommand: z26.string().optional(),
|
|
3958
|
+
ingestScriptRan: z26.boolean().optional(),
|
|
3959
|
+
ingestRecordCount: z26.number().optional(),
|
|
3960
|
+
ingestDurationMs: z26.number().optional(),
|
|
3961
|
+
ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
|
|
3962
|
+
searchEnvVars: z26.array(
|
|
3963
|
+
z26.object({
|
|
3964
|
+
name: z26.string(),
|
|
3965
|
+
value: z26.string()
|
|
3582
3966
|
})
|
|
3583
3967
|
).optional()
|
|
3584
3968
|
});
|
|
3585
|
-
var implementationOutputSchema =
|
|
3586
|
-
summary:
|
|
3587
|
-
|
|
3588
|
-
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3589
|
-
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3590
|
-
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3591
|
-
// the agent cannot inject extra commands or swap the interpreter.
|
|
3592
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3593
|
-
entrypoint: z24.string().optional()
|
|
3969
|
+
var implementationOutputSchema = z26.object({
|
|
3970
|
+
summary: z26.string(),
|
|
3971
|
+
ingestCommand: z26.string().optional()
|
|
3594
3972
|
});
|
|
3595
|
-
var verificationOutputSchema =
|
|
3596
|
-
summary:
|
|
3597
|
-
sufficient:
|
|
3598
|
-
additionalInstructions:
|
|
3973
|
+
var verificationOutputSchema = z26.object({
|
|
3974
|
+
summary: z26.string(),
|
|
3975
|
+
sufficient: z26.boolean(),
|
|
3976
|
+
additionalInstructions: z26.string().optional()
|
|
3599
3977
|
});
|
|
3600
3978
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3601
3979
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3602
3980
|
var INGEST_DIR = ".algolia-wizard";
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3607
|
-
return "React";
|
|
3608
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3609
|
-
return "JavaScript";
|
|
3610
|
-
}
|
|
3611
|
-
function frameworksForDoc(framework) {
|
|
3612
|
-
switch (framework) {
|
|
3613
|
-
case "React":
|
|
3614
|
-
return ["react"];
|
|
3615
|
-
case "Vue":
|
|
3616
|
-
return ["vue"];
|
|
3617
|
-
case "Angular":
|
|
3618
|
-
return ["angular"];
|
|
3619
|
-
case "JavaScript":
|
|
3620
|
-
return [];
|
|
3621
|
-
}
|
|
3981
|
+
var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
|
|
3982
|
+
function lower(entries) {
|
|
3983
|
+
return entries.map((entry) => entry.name.toLowerCase());
|
|
3622
3984
|
}
|
|
3623
|
-
function
|
|
3624
|
-
|
|
3625
|
-
(
|
|
3985
|
+
function isJsProject(language) {
|
|
3986
|
+
return lower(language.languages).some(
|
|
3987
|
+
(name) => JS_LANGUAGES.some((js) => name.includes(js))
|
|
3988
|
+
);
|
|
3989
|
+
}
|
|
3990
|
+
var UI_FRAMEWORKS = [
|
|
3991
|
+
{ match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
|
|
3992
|
+
{ match: ["react", "next"], target: "React", doc: "react" },
|
|
3993
|
+
{ match: ["angular"], target: "Angular" }
|
|
3994
|
+
];
|
|
3995
|
+
function matchUiFramework(language) {
|
|
3996
|
+
const names = lower(language.frameworks);
|
|
3997
|
+
return UI_FRAMEWORKS.find(
|
|
3998
|
+
(ui) => ui.match.some((needle) => names.some((name) => name.includes(needle)))
|
|
3626
3999
|
);
|
|
4000
|
+
}
|
|
4001
|
+
function searchUiTarget(language) {
|
|
4002
|
+
return matchUiFramework(language)?.target ?? language.frameworks[0]?.name ?? (isJsProject(language) ? "JavaScript" : "this project");
|
|
4003
|
+
}
|
|
4004
|
+
function frameworksForDoc(language) {
|
|
4005
|
+
if (!isJsProject(language)) return [];
|
|
4006
|
+
return [matchUiFramework(language)?.doc ?? "js"];
|
|
4007
|
+
}
|
|
4008
|
+
function publicEnvPrefix(language) {
|
|
4009
|
+
const frameworkNames = lower(language.frameworks);
|
|
3627
4010
|
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3628
4011
|
return "NEXT_PUBLIC_";
|
|
3629
4012
|
}
|
|
@@ -3636,17 +4019,24 @@ function publicEnvPrefix(language) {
|
|
|
3636
4019
|
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3637
4020
|
return "VITE_";
|
|
3638
4021
|
}
|
|
3639
|
-
return "PUBLIC_";
|
|
4022
|
+
return isJsProject(language) ? "PUBLIC_" : "";
|
|
4023
|
+
}
|
|
4024
|
+
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4025
|
+
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4026
|
+
function appIdVar(language) {
|
|
4027
|
+
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4028
|
+
}
|
|
4029
|
+
function searchKeyVar(language) {
|
|
4030
|
+
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3640
4031
|
}
|
|
3641
4032
|
function searchEnvVars(language, appId, searchKey) {
|
|
3642
|
-
const prefix = publicEnvPrefix(language);
|
|
3643
4033
|
return [
|
|
3644
4034
|
{
|
|
3645
|
-
name:
|
|
4035
|
+
name: appIdVar(language),
|
|
3646
4036
|
value: appId ?? "<your-algolia-app-id>"
|
|
3647
4037
|
},
|
|
3648
4038
|
{
|
|
3649
|
-
name:
|
|
4039
|
+
name: searchKeyVar(language),
|
|
3650
4040
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3651
4041
|
}
|
|
3652
4042
|
];
|
|
@@ -3655,7 +4045,10 @@ function baseInstructions(input) {
|
|
|
3655
4045
|
return [
|
|
3656
4046
|
`Target Algolia index: ${input.targetIndex}`,
|
|
3657
4047
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
3658
|
-
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
4048
|
+
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
4049
|
+
`Commands run through a shell on ${process.platform}. Write commands that work there.`,
|
|
4050
|
+
"Use the project's own tooling for every command \u2014 its package manager, task runner, and test/lint commands. Do not assume a JavaScript toolchain.",
|
|
4051
|
+
"runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
|
|
3659
4052
|
];
|
|
3660
4053
|
}
|
|
3661
4054
|
function sourceSpecificInstructions(input) {
|
|
@@ -3667,9 +4060,6 @@ function sourceSpecificInstructions(input) {
|
|
|
3667
4060
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
3668
4061
|
],
|
|
3669
4062
|
fileUpload: [
|
|
3670
|
-
// The wizard already copied the developer's file into the worktree at this
|
|
3671
|
-
// exact path, so the agent must read it directly — never search for or
|
|
3672
|
-
// substitute another file.
|
|
3673
4063
|
`Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
3674
4064
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
3675
4065
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
@@ -3678,46 +4068,60 @@ function sourceSpecificInstructions(input) {
|
|
|
3678
4068
|
generated: [
|
|
3679
4069
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3680
4070
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
3681
|
-
"In the script, read and parse each returned file path at runtime
|
|
4071
|
+
"In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
|
|
3682
4072
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3683
4073
|
]
|
|
3684
4074
|
};
|
|
3685
4075
|
return byLine[input.ingestionSource];
|
|
3686
4076
|
}
|
|
4077
|
+
function algoliaClientDoc(input) {
|
|
4078
|
+
const doc = getNamedDoc("save-records", "js");
|
|
4079
|
+
if (!doc) return [];
|
|
4080
|
+
if (isJsProject(input.language)) return [doc];
|
|
4081
|
+
return [
|
|
4082
|
+
"The reference below is written in JavaScript. Use it for the method names, arguments, and record shape, then translate to this project's language and its official Algolia client:",
|
|
4083
|
+
doc
|
|
4084
|
+
];
|
|
4085
|
+
}
|
|
3687
4086
|
function ingestionInstructions(input) {
|
|
3688
4087
|
return [
|
|
3689
4088
|
...input.confirmed && input.confirmed.length ? [
|
|
3690
4089
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3691
4090
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3692
4091
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3693
|
-
"
|
|
4092
|
+
"Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
|
|
3694
4093
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
3695
|
-
|
|
3696
|
-
'
|
|
4094
|
+
...algoliaClientDoc(input),
|
|
4095
|
+
"Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
|
|
4096
|
+
'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
|
|
3697
4097
|
"The summary should be extremely concise.",
|
|
3698
|
-
`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.`,
|
|
3699
4098
|
...sourceSpecificInstructions(input)
|
|
3700
4099
|
] : []
|
|
3701
4100
|
];
|
|
3702
4101
|
}
|
|
3703
4102
|
function searchInstructions(input) {
|
|
3704
|
-
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.
|
|
4103
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
|
|
3705
4104
|
return [
|
|
3706
4105
|
"Implement an in-app Algolia search experience.",
|
|
3707
|
-
`Build the search UI for ${input.
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
`
|
|
3715
|
-
|
|
3716
|
-
//
|
|
3717
|
-
//
|
|
4106
|
+
`Build the search UI for ${input.searchUiTarget}.`,
|
|
4107
|
+
...doc ? [
|
|
4108
|
+
"Follow the Algolia SDK reference below for client setup and search UI wiring; prefer it over prior knowledge:",
|
|
4109
|
+
doc
|
|
4110
|
+
] : [
|
|
4111
|
+
"No bundled Algolia SDK reference exists for this stack, so rely on the project's own conventions and Algolia's official client for its language. Do not invent APIs \u2014 keep to the documented search endpoint and its parameters."
|
|
4112
|
+
],
|
|
4113
|
+
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working search input and results list against the "${input.targetIndex}" index.`,
|
|
4114
|
+
"Read the App ID and a search-only API key from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4115
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4116
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4117
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4118
|
+
// would be reused as if it were real.
|
|
4119
|
+
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
4120
|
+
// Not the agent's to rename: the wizard writes these exact names into
|
|
4121
|
+
// ".env" right after this step, so a renamed prefix would leave the code
|
|
3718
4122
|
// reading a var the wizard never wrote.
|
|
3719
|
-
`Use exactly these
|
|
3720
|
-
|
|
4123
|
+
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4124
|
+
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
3721
4125
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3722
4126
|
];
|
|
3723
4127
|
}
|
|
@@ -3725,11 +4129,12 @@ function verificationInstructions(input) {
|
|
|
3725
4129
|
return [
|
|
3726
4130
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3727
4131
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3728
|
-
"
|
|
3729
|
-
"
|
|
3730
|
-
"
|
|
4132
|
+
"Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
|
|
4133
|
+
"This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4134
|
+
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4135
|
+
"Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
3731
4136
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3732
|
-
`Do not modify "${input.ingestDir}/" unless
|
|
4137
|
+
`Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
|
|
3733
4138
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3734
4139
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3735
4140
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3750,14 +4155,15 @@ var IMPLEMENT_CONFIG = {
|
|
|
3750
4155
|
}
|
|
3751
4156
|
};
|
|
3752
4157
|
var useCaseToolMap = {
|
|
3753
|
-
ingestion: [
|
|
3754
|
-
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
3755
|
-
verification: [
|
|
4158
|
+
ingestion: [
|
|
3756
4159
|
...FS_READ_TOOLS,
|
|
3757
4160
|
"writeFile",
|
|
3758
|
-
"
|
|
4161
|
+
"writeCredentials",
|
|
4162
|
+
"runShell",
|
|
3759
4163
|
"notifyUser"
|
|
3760
|
-
]
|
|
4164
|
+
],
|
|
4165
|
+
search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
|
|
4166
|
+
verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
|
|
3761
4167
|
};
|
|
3762
4168
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
3763
4169
|
const tools = useCaseToolMap[useCase];
|
|
@@ -3780,15 +4186,31 @@ function formatSummary(useCase, summary) {
|
|
|
3780
4186
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3781
4187
|
return `${label}: ${summary}`;
|
|
3782
4188
|
}
|
|
3783
|
-
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3784
|
-
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
3785
|
-
}
|
|
3786
4189
|
function parseIngestRecordCount(output) {
|
|
3787
4190
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
3788
4191
|
if (!match) return void 0;
|
|
3789
4192
|
const count = Number(match[1]);
|
|
3790
4193
|
return Number.isFinite(count) ? count : void 0;
|
|
3791
4194
|
}
|
|
4195
|
+
function ingestOutcome(executions, ingestCommand) {
|
|
4196
|
+
const newestFirst = [...executions].reverse();
|
|
4197
|
+
const withCount = newestFirst.filter(
|
|
4198
|
+
(e) => parseIngestRecordCount(e.output ?? "") != null
|
|
4199
|
+
);
|
|
4200
|
+
const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
|
|
4201
|
+
return {
|
|
4202
|
+
run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
|
|
4203
|
+
recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
var PLACEHOLDER_WRITE_KEY = "ALGOLIA_WRITE_KEY_PLACEHOLDER";
|
|
4207
|
+
function makeToolContext(worktree, env = async () => ({})) {
|
|
4208
|
+
return createToolContext(
|
|
4209
|
+
DEFAULT_TOOL_LIMITS,
|
|
4210
|
+
worktree,
|
|
4211
|
+
createShellContext({ env, approve: storeApproval(worktree) })
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
3792
4214
|
function verificationRetryInstructions(verification) {
|
|
3793
4215
|
return [
|
|
3794
4216
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
@@ -3858,6 +4280,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3858
4280
|
}
|
|
3859
4281
|
}
|
|
3860
4282
|
const targetIndex = selected?.selection;
|
|
4283
|
+
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
3861
4284
|
await assertGitRepoWithHead(repoRoot);
|
|
3862
4285
|
if (await isWorkingTreeDirty(repoRoot)) {
|
|
3863
4286
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
@@ -3866,17 +4289,12 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3866
4289
|
const confirmed2 = normalized.confirmedEntities;
|
|
3867
4290
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3868
4291
|
let appId;
|
|
3869
|
-
let
|
|
4292
|
+
let ingestAppId;
|
|
3870
4293
|
if (useCases.includes("search")) {
|
|
3871
|
-
appId = (await
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
logger.warn(
|
|
3876
|
-
{ err: err.message },
|
|
3877
|
-
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
3878
|
-
);
|
|
3879
|
-
}
|
|
4294
|
+
appId = (await requireApplication()).id;
|
|
4295
|
+
}
|
|
4296
|
+
if (useCases.includes("ingestion")) {
|
|
4297
|
+
ingestAppId = appId ?? (await requireApplication()).id;
|
|
3880
4298
|
}
|
|
3881
4299
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3882
4300
|
try {
|
|
@@ -3908,53 +4326,58 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3908
4326
|
targetIndex,
|
|
3909
4327
|
language,
|
|
3910
4328
|
appId,
|
|
3911
|
-
|
|
3912
|
-
|
|
4329
|
+
// Names only: the search-only key is provisioned after verification, so
|
|
4330
|
+
// every value here is still a placeholder when the agent reads them.
|
|
4331
|
+
searchEnvVars: searchEnvVars(language, appId),
|
|
3913
4332
|
ingestDir: INGEST_DIR,
|
|
3914
4333
|
ingestionSource,
|
|
3915
4334
|
uploadFilePath,
|
|
3916
|
-
|
|
3917
|
-
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3918
|
-
uiFramework: detectUiFramework(language)
|
|
4335
|
+
searchUiTarget: searchUiTarget(language)
|
|
3919
4336
|
};
|
|
3920
4337
|
const summaries = [];
|
|
3921
4338
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4339
|
+
let envSearchKey;
|
|
4340
|
+
let envAppIdMismatch = false;
|
|
4341
|
+
if (useCases.includes("search") && appId) {
|
|
4342
|
+
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4343
|
+
if (envAppId === appId) {
|
|
4344
|
+
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4345
|
+
} else if (envAppId) {
|
|
4346
|
+
envAppIdMismatch = true;
|
|
4347
|
+
summaries.push(
|
|
4348
|
+
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
4349
|
+
);
|
|
4350
|
+
logger.warn(
|
|
4351
|
+
{ envAppId, appId },
|
|
4352
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
3922
4357
|
let agentRuns = 0;
|
|
3923
|
-
let
|
|
3924
|
-
let ingestEntrypoint;
|
|
4358
|
+
let ingestCommand;
|
|
3925
4359
|
let ingestScriptRan = false;
|
|
3926
4360
|
let ingestRecordCount;
|
|
3927
4361
|
let ingestDurationMs;
|
|
3928
|
-
let installFailed = false;
|
|
3929
4362
|
let ingestOutcomeMessage;
|
|
4363
|
+
const ingestionTools = ingestAppId ? makeToolContext(worktree, async () => ({
|
|
4364
|
+
[APP_ID_VAR]: ingestAppId,
|
|
4365
|
+
[API_KEY_VAR]: PLACEHOLDER_WRITE_KEY
|
|
4366
|
+
})) : void 0;
|
|
4367
|
+
const searchTools = makeToolContext(worktree);
|
|
3930
4368
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
3931
4369
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
3932
4370
|
agentRuns += 1;
|
|
3933
|
-
|
|
4371
|
+
return runAgent({
|
|
3934
4372
|
instructions: buildAgentInstructions(
|
|
3935
4373
|
currentUseCase,
|
|
3936
4374
|
input,
|
|
3937
4375
|
extraInstructions
|
|
3938
4376
|
),
|
|
3939
4377
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3940
|
-
outputSchema: implementationOutputSchema
|
|
3941
|
-
|
|
3942
|
-
ctx.notify({
|
|
3943
|
-
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4378
|
+
outputSchema: implementationOutputSchema,
|
|
4379
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
3944
4380
|
});
|
|
3945
|
-
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3946
|
-
useCase: currentUseCase
|
|
3947
|
-
});
|
|
3948
|
-
const install = await installWorktreeDeps(worktree);
|
|
3949
|
-
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3950
|
-
if (!install.ok) {
|
|
3951
|
-
installFailed = true;
|
|
3952
|
-
logger.warn(
|
|
3953
|
-
{ useCase: currentUseCase, output: install.output },
|
|
3954
|
-
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
3955
|
-
);
|
|
3956
|
-
}
|
|
3957
|
-
return result;
|
|
3958
4381
|
}
|
|
3959
4382
|
async function runVerificationUseCase() {
|
|
3960
4383
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -3962,111 +4385,55 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3962
4385
|
return runAgent({
|
|
3963
4386
|
instructions: buildAgentInstructions("verification", input),
|
|
3964
4387
|
tools: toolsForUseCase("verification"),
|
|
3965
|
-
outputSchema: verificationOutputSchema
|
|
4388
|
+
outputSchema: verificationOutputSchema,
|
|
4389
|
+
toolContext: searchTools
|
|
3966
4390
|
});
|
|
3967
4391
|
}
|
|
3968
4392
|
if (useCases.includes("ingestion")) {
|
|
3969
|
-
const
|
|
3970
|
-
summaries.push(formatSummary("ingestion", summary));
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
4393
|
+
const result = await runImplementationUseCase("ingestion");
|
|
4394
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
4395
|
+
ingestCommand = result.ingestCommand;
|
|
4396
|
+
const executions = (ingestionTools ?? searchTools).shell.executions;
|
|
4397
|
+
const { run: ingestRun, recordCount } = ingestOutcome(
|
|
4398
|
+
executions,
|
|
4399
|
+
ingestCommand
|
|
4400
|
+
);
|
|
4401
|
+
ingestScriptRan = ingestRun != null;
|
|
4402
|
+
ingestRecordCount = recordCount;
|
|
4403
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
4404
|
+
if (ingestScriptRan) {
|
|
4405
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4406
|
+
if (ingestRecordCount != null) {
|
|
4407
|
+
track("AI Wizard Ingest Successful", {
|
|
4408
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4409
|
+
record_count: ingestRecordCount,
|
|
4410
|
+
duration_ms: ingestDurationMs ?? 0
|
|
3987
4411
|
});
|
|
3988
|
-
const startedAt = Date.now();
|
|
3989
|
-
const run2 = await runIngestScript(
|
|
3990
|
-
worktree,
|
|
3991
|
-
ingestRuntime,
|
|
3992
|
-
ingestEntrypoint,
|
|
3993
|
-
{
|
|
3994
|
-
[APP_ID_VAR]: profile.appId,
|
|
3995
|
-
[API_KEY_VAR]: profile.apiKey
|
|
3996
|
-
}
|
|
3997
|
-
);
|
|
3998
|
-
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
3999
|
-
ingestScriptRan = run2.ran && run2.ok;
|
|
4000
|
-
if (ingestScriptRan) {
|
|
4001
|
-
ingestDurationMs = Date.now() - startedAt;
|
|
4002
|
-
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4003
|
-
if (ingestRecordCount != null) {
|
|
4004
|
-
track("AI Wizard Ingest Successful", {
|
|
4005
|
-
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4006
|
-
record_count: ingestRecordCount,
|
|
4007
|
-
duration_ms: ingestDurationMs
|
|
4008
|
-
});
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
let summaryLine;
|
|
4012
|
-
let outcomeMessage;
|
|
4013
|
-
if (!run2.ran) {
|
|
4014
|
-
summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
|
|
4015
|
-
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4016
|
-
logger.warn(
|
|
4017
|
-
{
|
|
4018
|
-
runtime: ingestRuntime,
|
|
4019
|
-
entrypoint: ingestEntrypoint,
|
|
4020
|
-
reason: run2.reason
|
|
4021
|
-
},
|
|
4022
|
-
"implement: refused to auto-run ingestion script"
|
|
4023
|
-
);
|
|
4024
|
-
track("Error", {
|
|
4025
|
-
step: "Push Data",
|
|
4026
|
-
error: `ingestion script skipped: ${run2.reason}`,
|
|
4027
|
-
product_area: "AI Wizard"
|
|
4028
|
-
});
|
|
4029
|
-
} else if (run2.ok) {
|
|
4030
|
-
const status = "Ingestion run: succeeded.";
|
|
4031
|
-
summaryLine = run2.output ? `${status}
|
|
4032
|
-
${run2.output}` : status;
|
|
4033
|
-
outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
4034
|
-
} else {
|
|
4035
|
-
const status = "\u26A0\uFE0F Ingestion run failed:";
|
|
4036
|
-
summaryLine = run2.output ? `${status}
|
|
4037
|
-
${run2.output}` : status;
|
|
4038
|
-
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4039
|
-
logger.warn(
|
|
4040
|
-
{
|
|
4041
|
-
runtime: ingestRuntime,
|
|
4042
|
-
entrypoint: ingestEntrypoint,
|
|
4043
|
-
output: run2.output
|
|
4044
|
-
},
|
|
4045
|
-
"implement: ingestion script run failed"
|
|
4046
|
-
);
|
|
4047
|
-
track("Error", {
|
|
4048
|
-
step: "Push Data",
|
|
4049
|
-
error: run2.output || "ingestion script exited non-zero",
|
|
4050
|
-
product_area: "AI Wizard"
|
|
4051
|
-
});
|
|
4052
|
-
}
|
|
4053
|
-
summaries.push(summaryLine);
|
|
4054
|
-
ingestOutcomeMessage = outcomeMessage;
|
|
4055
4412
|
}
|
|
4413
|
+
} else {
|
|
4414
|
+
const rejected = executions.some((e) => !e.approved);
|
|
4415
|
+
const reason = rejected ? "you declined to run it" : "no successful run was recorded";
|
|
4416
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4417
|
+
summaries.push(`\u26A0\uFE0F The ingestion script did not run: ${reason}.`);
|
|
4418
|
+
logger.warn(
|
|
4419
|
+
{ ingestCommand, rejected, commandsRun: executions.length },
|
|
4420
|
+
"implement: ingestion script did not complete successfully"
|
|
4421
|
+
);
|
|
4422
|
+
track("Error", {
|
|
4423
|
+
step: "Push Data",
|
|
4424
|
+
error: `ingestion did not run: ${reason}`,
|
|
4425
|
+
product_area: "AI Wizard"
|
|
4426
|
+
});
|
|
4056
4427
|
}
|
|
4057
4428
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4058
|
-
if (
|
|
4059
|
-
commandMessages.push(
|
|
4060
|
-
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4061
|
-
);
|
|
4429
|
+
if (ingestCommand) {
|
|
4430
|
+
commandMessages.push(`Ingestion command: ${ingestCommand}`);
|
|
4062
4431
|
}
|
|
4063
4432
|
await ctx.requestUserInput({
|
|
4064
|
-
// No question being asked here, just an acknowledgement — the
|
|
4065
|
-
// continue/decline hints below already say "continue".
|
|
4066
4433
|
prompt: "",
|
|
4067
4434
|
promptType: "enterToContinue",
|
|
4068
4435
|
options: [],
|
|
4069
|
-
messages:
|
|
4436
|
+
messages: [ingestOutcomeMessage, ...commandMessages]
|
|
4070
4437
|
});
|
|
4071
4438
|
}
|
|
4072
4439
|
if (useCases.includes("search")) {
|
|
@@ -4111,7 +4478,29 @@ ${run2.output}` : status;
|
|
|
4111
4478
|
}
|
|
4112
4479
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4113
4480
|
}
|
|
4114
|
-
|
|
4481
|
+
let searchKey;
|
|
4482
|
+
let searchKeyError;
|
|
4483
|
+
if (appId) {
|
|
4484
|
+
try {
|
|
4485
|
+
const resolved = await resolveSearchOnlyKey(
|
|
4486
|
+
targetIndex,
|
|
4487
|
+
appId,
|
|
4488
|
+
envSearchKey
|
|
4489
|
+
);
|
|
4490
|
+
searchKey = resolved.key;
|
|
4491
|
+
summaries.push(
|
|
4492
|
+
resolved.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4493
|
+
);
|
|
4494
|
+
} catch (err) {
|
|
4495
|
+
searchKeyError = err.message;
|
|
4496
|
+
logger.warn(
|
|
4497
|
+
{ err: searchKeyError },
|
|
4498
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4499
|
+
);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
|
|
4503
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4115
4504
|
(v) => !v.value.startsWith("<")
|
|
4116
4505
|
);
|
|
4117
4506
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4122,13 +4511,29 @@ ${run2.output}` : status;
|
|
|
4122
4511
|
if (written.length > 0) {
|
|
4123
4512
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4124
4513
|
}
|
|
4514
|
+
const stale = [];
|
|
4515
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4516
|
+
if (written.includes(v.name)) continue;
|
|
4517
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4518
|
+
if (current && current !== v.value) stale.push(v);
|
|
4519
|
+
}
|
|
4520
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4521
|
+
summaries.push(
|
|
4522
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4523
|
+
);
|
|
4524
|
+
logger.warn(
|
|
4525
|
+
{ vars: stale.map((v) => v.name) },
|
|
4526
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4527
|
+
);
|
|
4528
|
+
}
|
|
4125
4529
|
}
|
|
4126
|
-
const unresolvedSearchEnvVars =
|
|
4530
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4127
4531
|
(v) => v.value.startsWith("<")
|
|
4128
4532
|
);
|
|
4129
4533
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4130
4534
|
summaries.push(
|
|
4131
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4535
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // Without the reason the line is a dead end.
|
|
4536
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4132
4537
|
);
|
|
4133
4538
|
}
|
|
4134
4539
|
} else {
|
|
@@ -4140,27 +4545,18 @@ ${run2.output}` : status;
|
|
|
4140
4545
|
"implement: agent reported success but no files changed in the worktree"
|
|
4141
4546
|
);
|
|
4142
4547
|
}
|
|
4143
|
-
if (installFailed) {
|
|
4144
|
-
summaries.push(
|
|
4145
|
-
'\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".'
|
|
4146
|
-
);
|
|
4147
|
-
}
|
|
4148
4548
|
return {
|
|
4149
4549
|
ingestionSource,
|
|
4150
4550
|
filesChanged,
|
|
4151
4551
|
summary: summaries.join("\n\n"),
|
|
4152
4552
|
worktreePath: worktree,
|
|
4153
|
-
...useCases.includes("ingestion") &&
|
|
4154
|
-
ingestCommand
|
|
4155
|
-
worktree,
|
|
4156
|
-
ingestRuntime,
|
|
4157
|
-
ingestEntrypoint
|
|
4158
|
-
),
|
|
4553
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4554
|
+
ingestCommand,
|
|
4159
4555
|
ingestScriptRan,
|
|
4160
4556
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4161
4557
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4162
4558
|
} : {},
|
|
4163
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4559
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4164
4560
|
};
|
|
4165
4561
|
} finally {
|
|
4166
4562
|
process.chdir(repoRoot);
|
|
@@ -4203,8 +4599,8 @@ var defaultWorkflow = {
|
|
|
4203
4599
|
defineStep({
|
|
4204
4600
|
id: "select-index",
|
|
4205
4601
|
title: "Set up index",
|
|
4206
|
-
outputSchema:
|
|
4207
|
-
selection:
|
|
4602
|
+
outputSchema: z27.object({
|
|
4603
|
+
selection: z27.string()
|
|
4208
4604
|
}),
|
|
4209
4605
|
run: (ctx) => selectIndexStep(ctx)
|
|
4210
4606
|
}),
|
|
@@ -4483,7 +4879,7 @@ function parseCliArgs(argv) {
|
|
|
4483
4879
|
|
|
4484
4880
|
// src/lib/resetState.ts
|
|
4485
4881
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4486
|
-
import { join as
|
|
4882
|
+
import { join as join10 } from "node:path";
|
|
4487
4883
|
var KEEP = ["wizard.log"];
|
|
4488
4884
|
async function resetProjectState() {
|
|
4489
4885
|
const dir = stateDir();
|
|
@@ -4495,13 +4891,13 @@ async function resetProjectState() {
|
|
|
4495
4891
|
}
|
|
4496
4892
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4497
4893
|
await Promise.all(
|
|
4498
|
-
targets.map((name) => rm2(
|
|
4894
|
+
targets.map((name) => rm2(join10(dir, name), { recursive: true, force: true }))
|
|
4499
4895
|
);
|
|
4500
4896
|
return { dir, removed: targets };
|
|
4501
4897
|
}
|
|
4502
4898
|
|
|
4503
4899
|
// src/main.tsx
|
|
4504
|
-
import { jsx as
|
|
4900
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
4505
4901
|
async function startup() {
|
|
4506
4902
|
let args;
|
|
4507
4903
|
try {
|
|
@@ -4550,31 +4946,38 @@ ${formatStepList(workflow)}`);
|
|
|
4550
4946
|
}
|
|
4551
4947
|
async function run(workflow) {
|
|
4552
4948
|
const store = useWizard.getState();
|
|
4553
|
-
|
|
4949
|
+
const instance = render(/* @__PURE__ */ jsx15(App, {}), { incrementalRendering: true });
|
|
4950
|
+
await store.waitForStart();
|
|
4554
4951
|
let user = await getUser();
|
|
4555
4952
|
if (!user) {
|
|
4556
|
-
|
|
4557
|
-
instance.cleanup();
|
|
4953
|
+
store.beginAuth();
|
|
4558
4954
|
try {
|
|
4559
4955
|
await runAuthLogin();
|
|
4560
4956
|
} catch (err) {
|
|
4561
|
-
|
|
4957
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4958
|
+
await instance.waitUntilExit();
|
|
4562
4959
|
process.exit(1);
|
|
4563
4960
|
}
|
|
4564
|
-
|
|
4961
|
+
store.endAuth();
|
|
4565
4962
|
user = await getUser();
|
|
4566
4963
|
if (!user) {
|
|
4567
4964
|
store.setError(
|
|
4568
|
-
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
|
|
4965
|
+
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
|
|
4569
4966
|
);
|
|
4570
4967
|
await instance.waitUntilExit();
|
|
4571
4968
|
process.exit(1);
|
|
4572
4969
|
}
|
|
4573
4970
|
}
|
|
4574
4971
|
store.setUser(user);
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4972
|
+
let app;
|
|
4973
|
+
try {
|
|
4974
|
+
app = await ensureApplication();
|
|
4975
|
+
} catch (err) {
|
|
4976
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4977
|
+
await instance.waitUntilExit();
|
|
4978
|
+
process.exit(1);
|
|
4979
|
+
}
|
|
4980
|
+
runWorkflow(workflow, app.id);
|
|
4578
4981
|
}
|
|
4579
4982
|
var started = await startup();
|
|
4580
4983
|
if (typeof started === "number") {
|