@algolia/wizard 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/main.js +919 -560
- package/package.json +1 -3
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as
|
|
7
|
+
import { Box as Box15, Text as Text15, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -12,32 +12,86 @@ 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
|
-
return require2.resolve("@algolia/cli/bin/run.js");
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
function npxArgs(args) {
|
|
17
|
+
return ["--yes", "@algolia/cli@latest", ...args];
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
var shell = process.platform === "win32";
|
|
20
|
+
function lineSplitter(emit) {
|
|
21
|
+
let buffer = "";
|
|
22
|
+
return {
|
|
23
|
+
push(chunk) {
|
|
24
|
+
buffer += chunk;
|
|
25
|
+
const lines = buffer.split("\n");
|
|
26
|
+
buffer = lines.pop() ?? "";
|
|
27
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
28
|
+
},
|
|
29
|
+
flush() {
|
|
30
|
+
if (buffer) emit(buffer.replace(/\r$/, ""));
|
|
31
|
+
buffer = "";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var wizardSink = (stream, line) => {
|
|
36
|
+
if (!line.trim()) return;
|
|
37
|
+
useWizard.getState().pushCliOutput(stream, line);
|
|
38
|
+
};
|
|
39
|
+
var stderrSink = (stream, line) => {
|
|
40
|
+
if (stream === "stdout") return;
|
|
41
|
+
wizardSink(stream, line);
|
|
42
|
+
};
|
|
43
|
+
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
44
|
+
const store = useWizard.getState();
|
|
45
|
+
const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
|
|
21
46
|
return new Promise((resolve4, reject) => {
|
|
22
|
-
const child = spawn(
|
|
47
|
+
const child = spawn("npx", npxArgs(args), { shell });
|
|
23
48
|
let stdout = "";
|
|
24
49
|
let stderr = "";
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
const splitters = {
|
|
51
|
+
stdout: lineSplitter((line) => onOutput?.("stdout", line)),
|
|
52
|
+
stderr: lineSplitter((line) => onOutput?.("stderr", line))
|
|
53
|
+
};
|
|
54
|
+
child.stdout.on("data", (chunk) => {
|
|
55
|
+
const text = String(chunk);
|
|
56
|
+
stdout += text;
|
|
57
|
+
splitters.stdout.push(text);
|
|
58
|
+
});
|
|
59
|
+
child.stderr.on("data", (chunk) => {
|
|
60
|
+
const text = String(chunk);
|
|
61
|
+
stderr += text;
|
|
62
|
+
splitters.stderr.push(text);
|
|
63
|
+
});
|
|
27
64
|
child.on("error", reject);
|
|
28
65
|
child.on("close", (code) => {
|
|
66
|
+
splitters.stdout.flush();
|
|
67
|
+
splitters.stderr.flush();
|
|
29
68
|
if (code === 0) {
|
|
30
69
|
resolve4(stdout);
|
|
31
70
|
} else {
|
|
32
|
-
const
|
|
71
|
+
const failed = stderr.trim();
|
|
72
|
+
let detail = "";
|
|
73
|
+
if (failed) {
|
|
74
|
+
detail = `: ${failed}`;
|
|
75
|
+
} else if (stdout.trim()) {
|
|
76
|
+
detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
|
|
77
|
+
}
|
|
33
78
|
reject(
|
|
34
79
|
new Error(
|
|
35
|
-
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail
|
|
80
|
+
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
|
|
36
81
|
)
|
|
37
82
|
);
|
|
38
83
|
}
|
|
39
84
|
});
|
|
40
|
-
})
|
|
85
|
+
}).then(
|
|
86
|
+
(out) => {
|
|
87
|
+
useWizard.getState().logEnd(logId, "success");
|
|
88
|
+
return out;
|
|
89
|
+
},
|
|
90
|
+
(err) => {
|
|
91
|
+
useWizard.getState().logEnd(logId, "error");
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
);
|
|
41
95
|
}
|
|
42
96
|
async function getUser() {
|
|
43
97
|
let raw;
|
|
@@ -52,19 +106,23 @@ async function getUser() {
|
|
|
52
106
|
return null;
|
|
53
107
|
}
|
|
54
108
|
}
|
|
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
|
-
});
|
|
109
|
+
var loginResultSchema = z.object({
|
|
110
|
+
success: z.boolean(),
|
|
111
|
+
email: z.string().optional()
|
|
112
|
+
});
|
|
113
|
+
async function runAuthLogin() {
|
|
114
|
+
const raw = await runAlgoliaCli(["auth", "login", "--non-interactive"], {
|
|
115
|
+
onOutput: stderrSink
|
|
67
116
|
});
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = loginResultSchema.safeParse(JSON.parse(raw));
|
|
120
|
+
} catch {
|
|
121
|
+
parsed = void 0;
|
|
122
|
+
}
|
|
123
|
+
if (parsed?.success && !parsed.data.success) {
|
|
124
|
+
throw new Error("Algolia sign-in did not report success.");
|
|
125
|
+
}
|
|
68
126
|
}
|
|
69
127
|
|
|
70
128
|
// src/lib/auth.ts
|
|
@@ -171,6 +229,7 @@ function describeInputValue(value) {
|
|
|
171
229
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
230
|
}
|
|
173
231
|
var NOTICE_INTERVAL_MS = 2e3;
|
|
232
|
+
var CLI_OUTPUT_LIMIT = 200;
|
|
174
233
|
var useWizard = create((set, get) => ({
|
|
175
234
|
phase: "idle",
|
|
176
235
|
homeScreen: "home",
|
|
@@ -182,22 +241,21 @@ var useWizard = create((set, get) => ({
|
|
|
182
241
|
notices: [],
|
|
183
242
|
_noticeQueue: [],
|
|
184
243
|
_noticeTimer: null,
|
|
244
|
+
cliOutput: [],
|
|
245
|
+
targetIndex: null,
|
|
185
246
|
logs: [],
|
|
186
247
|
error: null,
|
|
187
248
|
inputReq: null,
|
|
188
249
|
_resolve: null,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
250
|
+
// `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
|
|
251
|
+
// welcome screen, so going back would gate the run a second time.
|
|
252
|
+
beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
|
|
253
|
+
endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
|
|
192
254
|
confirmStart: () => set(
|
|
193
255
|
(s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
|
|
194
256
|
),
|
|
195
|
-
// Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
|
|
196
257
|
openLearnMore: () => set({ homeScreen: "learnMore" }),
|
|
197
258
|
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
259
|
waitForStart: () => new Promise((resolve4) => {
|
|
202
260
|
if (get().phase !== "idle") {
|
|
203
261
|
resolve4();
|
|
@@ -220,15 +278,19 @@ var useWizard = create((set, get) => ({
|
|
|
220
278
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
221
279
|
setActiveStep: (index) => {
|
|
222
280
|
get()._clearNoticeQueue();
|
|
223
|
-
set({
|
|
281
|
+
set({
|
|
282
|
+
phase: "running",
|
|
283
|
+
currentStepIndex: index,
|
|
284
|
+
output: "",
|
|
285
|
+
notices: [],
|
|
286
|
+
cliOutput: []
|
|
287
|
+
});
|
|
224
288
|
},
|
|
225
289
|
setUser: (user) => set({ user }),
|
|
226
290
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
227
291
|
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.
|
|
292
|
+
// The timer stays armed through an empty drain, so the spacing covers the
|
|
293
|
+
// time since the last render even across bursts.
|
|
232
294
|
pushNotice: (notice) => {
|
|
233
295
|
const { notices, _noticeQueue, _noticeTimer } = get();
|
|
234
296
|
if (_noticeTimer === null) {
|
|
@@ -261,6 +323,13 @@ var useWizard = create((set, get) => ({
|
|
|
261
323
|
get()._clearNoticeQueue();
|
|
262
324
|
set({ notices: [] });
|
|
263
325
|
},
|
|
326
|
+
pushCliOutput: (stream, text) => set((s) => ({
|
|
327
|
+
cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
|
|
328
|
+
-CLI_OUTPUT_LIMIT
|
|
329
|
+
)
|
|
330
|
+
})),
|
|
331
|
+
clearCliOutput: () => set({ cliOutput: [] }),
|
|
332
|
+
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
264
333
|
logStart: (kind, name, input) => {
|
|
265
334
|
const id = nanoid();
|
|
266
335
|
set((s) => ({
|
|
@@ -283,9 +352,6 @@ var useWizard = create((set, get) => ({
|
|
|
283
352
|
_resolve: resolve4
|
|
284
353
|
});
|
|
285
354
|
}),
|
|
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
355
|
submitInput: async (value) => {
|
|
290
356
|
await markInteraction();
|
|
291
357
|
get()._resolve?.(value);
|
|
@@ -305,6 +371,8 @@ var useWizard = create((set, get) => ({
|
|
|
305
371
|
currentStepIndex: 0,
|
|
306
372
|
output: "",
|
|
307
373
|
notices: [],
|
|
374
|
+
cliOutput: [],
|
|
375
|
+
targetIndex: null,
|
|
308
376
|
logs: [],
|
|
309
377
|
error: null,
|
|
310
378
|
inputReq: null,
|
|
@@ -313,16 +381,100 @@ var useWizard = create((set, get) => ({
|
|
|
313
381
|
}
|
|
314
382
|
}));
|
|
315
383
|
|
|
384
|
+
// src/ui/CliOutput.tsx
|
|
385
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
386
|
+
|
|
387
|
+
// src/ui/theme.ts
|
|
388
|
+
var MARKER = {
|
|
389
|
+
pending: "\u25CB",
|
|
390
|
+
running: "\u25D0",
|
|
391
|
+
done: "\u2713",
|
|
392
|
+
error: "\u2716"
|
|
393
|
+
};
|
|
394
|
+
var BRAND = "#003DFF";
|
|
395
|
+
var SECONDARY = "#5468FF";
|
|
396
|
+
var DANGER = "#F86E7E";
|
|
397
|
+
var COLORS = {
|
|
398
|
+
brand: BRAND,
|
|
399
|
+
primary: "#E6EDF3",
|
|
400
|
+
secondary: SECONDARY,
|
|
401
|
+
strong: "#FFFFFF",
|
|
402
|
+
muted: "#8B949E",
|
|
403
|
+
dim: "#484F58",
|
|
404
|
+
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
405
|
+
badge: "#E3B341",
|
|
406
|
+
danger: DANGER,
|
|
407
|
+
success: "#4ADE80",
|
|
408
|
+
bg: {
|
|
409
|
+
main: "#0B0E14",
|
|
410
|
+
sidebar: "#14171E"
|
|
411
|
+
},
|
|
412
|
+
border: "#30363D",
|
|
413
|
+
accent: "#76A0FF",
|
|
414
|
+
status: {
|
|
415
|
+
pending: "gray",
|
|
416
|
+
running: "#76A0FF",
|
|
417
|
+
done: "#4ADE80",
|
|
418
|
+
error: DANGER
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/ui/CliOutput.tsx
|
|
423
|
+
import { jsxs } from "react/jsx-runtime";
|
|
424
|
+
var CLI_MARKER = "\u203A";
|
|
425
|
+
var RESERVED_ROWS = 16;
|
|
426
|
+
var MAX_ROWS = 12;
|
|
427
|
+
var PANEL_TEXT_WIDTH = 45;
|
|
428
|
+
var URL_PATTERN = /https?:\/\//;
|
|
429
|
+
function rowCost(text) {
|
|
430
|
+
return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
|
|
431
|
+
}
|
|
432
|
+
function CliOutput() {
|
|
433
|
+
const cliOutput = useWizard((s) => s.cliOutput);
|
|
434
|
+
const { rows } = useWindowSize();
|
|
435
|
+
if (!cliOutput.length) return null;
|
|
436
|
+
const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
|
|
437
|
+
const visible = [];
|
|
438
|
+
let usedRows = 0;
|
|
439
|
+
for (let i = cliOutput.length - 1; i >= 0; i--) {
|
|
440
|
+
const cost = rowCost(cliOutput[i].text);
|
|
441
|
+
if (usedRows + cost > rowBudget && visible.length > 0) break;
|
|
442
|
+
visible.unshift(cliOutput[i]);
|
|
443
|
+
usedRows += cost;
|
|
444
|
+
}
|
|
445
|
+
const hidden = cliOutput.length - visible.length;
|
|
446
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
447
|
+
hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
|
|
448
|
+
"\u2191 ",
|
|
449
|
+
hidden,
|
|
450
|
+
" earlier line(s)"
|
|
451
|
+
] }),
|
|
452
|
+
visible.map((line) => /* @__PURE__ */ jsxs(
|
|
453
|
+
Text,
|
|
454
|
+
{
|
|
455
|
+
color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
|
|
456
|
+
wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
|
|
457
|
+
children: [
|
|
458
|
+
CLI_MARKER,
|
|
459
|
+
" ",
|
|
460
|
+
line.text
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
line.id
|
|
464
|
+
))
|
|
465
|
+
] });
|
|
466
|
+
}
|
|
467
|
+
|
|
316
468
|
// src/ui/Notices.tsx
|
|
317
|
-
import { Box as
|
|
469
|
+
import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
|
|
318
470
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
319
471
|
|
|
320
472
|
// src/ui/Table.tsx
|
|
321
|
-
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
473
|
+
import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
|
|
322
474
|
import { useEffect, useRef, useState } from "react";
|
|
323
475
|
import { jsx } from "react/jsx-runtime";
|
|
324
476
|
function Table({ columns, rows }) {
|
|
325
|
-
const { columns: termCols } =
|
|
477
|
+
const { columns: termCols } = useWindowSize2();
|
|
326
478
|
const ref = useRef(null);
|
|
327
479
|
const [width, setWidth] = useState(0);
|
|
328
480
|
useEffect(() => {
|
|
@@ -330,7 +482,7 @@ function Table({ columns, rows }) {
|
|
|
330
482
|
}, [termCols, columns, rows]);
|
|
331
483
|
if (rows.length === 0) return null;
|
|
332
484
|
const lines = formatTable(columns, rows, width || void 0);
|
|
333
|
-
return /* @__PURE__ */ jsx(
|
|
485
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
334
486
|
}
|
|
335
487
|
function formatTable(columns, rows, width) {
|
|
336
488
|
const natural = columns.map(
|
|
@@ -370,48 +522,13 @@ function resize(widths, budget) {
|
|
|
370
522
|
}
|
|
371
523
|
var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
|
|
372
524
|
|
|
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
525
|
// src/ui/Notices.tsx
|
|
409
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
526
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
410
527
|
var AGENT_MARKER = "\u2726";
|
|
411
|
-
var
|
|
412
|
-
var
|
|
528
|
+
var RESERVED_ROWS2 = 14;
|
|
529
|
+
var PANEL_TEXT_WIDTH2 = 45;
|
|
413
530
|
function messageLineCount(text) {
|
|
414
|
-
return Math.max(1, Math.ceil(text.length /
|
|
531
|
+
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
|
|
415
532
|
}
|
|
416
533
|
function noticeLineCount(notice) {
|
|
417
534
|
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
@@ -422,7 +539,7 @@ function noticeLineCount(notice) {
|
|
|
422
539
|
return messageLines + tableLines;
|
|
423
540
|
}
|
|
424
541
|
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
-
const budget = Math.max(windowRows -
|
|
542
|
+
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
426
543
|
let used = 0;
|
|
427
544
|
let count = 0;
|
|
428
545
|
for (let i = notices.length - 1; i >= 0; i--) {
|
|
@@ -455,7 +572,7 @@ function parseHex(hex) {
|
|
|
455
572
|
}
|
|
456
573
|
function Notices() {
|
|
457
574
|
const notices = useWizard((s) => s.notices);
|
|
458
|
-
const { rows: windowRows } =
|
|
575
|
+
const { rows: windowRows } = useWindowSize3();
|
|
459
576
|
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
577
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
578
|
useEffect2(() => {
|
|
@@ -472,14 +589,14 @@ function Notices() {
|
|
|
472
589
|
}, []);
|
|
473
590
|
if (!visible.length) return null;
|
|
474
591
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
-
return /* @__PURE__ */ jsx2(
|
|
592
|
+
return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
593
|
const isLatest = i === visible.length - 1;
|
|
477
|
-
return /* @__PURE__ */
|
|
594
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
478
595
|
notice.messages?.map((m, j) => {
|
|
479
596
|
const line = typeof m === "string" ? { text: m } : m;
|
|
480
597
|
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
-
return /* @__PURE__ */
|
|
482
|
-
|
|
598
|
+
return /* @__PURE__ */ jsxs2(
|
|
599
|
+
Text3,
|
|
483
600
|
{
|
|
484
601
|
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
602
|
bold: line.bold,
|
|
@@ -497,41 +614,41 @@ function Notices() {
|
|
|
497
614
|
}
|
|
498
615
|
|
|
499
616
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
617
|
+
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
501
618
|
import TextInput from "ink-text-input";
|
|
502
619
|
import { useState as useState5 } from "react";
|
|
503
620
|
|
|
504
621
|
// src/ui/NextAction.tsx
|
|
505
|
-
import { Box as
|
|
506
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
622
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
623
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
507
624
|
function NextAction({
|
|
508
625
|
action,
|
|
509
626
|
keyHint,
|
|
510
627
|
hierarchy = "primary"
|
|
511
628
|
}) {
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx3(
|
|
514
|
-
hierarchy === "secondary" && /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */ jsx3(
|
|
516
|
-
/* @__PURE__ */ jsx3(
|
|
629
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
630
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
631
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
632
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
633
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
517
634
|
] }),
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */ jsx3(
|
|
520
|
-
/* @__PURE__ */ jsx3(
|
|
521
|
-
/* @__PURE__ */ jsx3(
|
|
522
|
-
/* @__PURE__ */ jsx3(
|
|
635
|
+
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
636
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
|
|
637
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
|
|
638
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
|
|
639
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
|
|
523
640
|
] })
|
|
524
641
|
] });
|
|
525
642
|
}
|
|
526
643
|
|
|
527
644
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
645
|
+
import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
|
|
529
646
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
530
647
|
|
|
531
648
|
// src/ui/ScrollView.tsx
|
|
532
|
-
import { Box as
|
|
649
|
+
import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
533
650
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
534
|
-
import { jsxs as
|
|
651
|
+
import { jsxs as jsxs4 } from "react/jsx-runtime";
|
|
535
652
|
var INDICATOR_ROWS = 2;
|
|
536
653
|
function fittedWidth(node, columns) {
|
|
537
654
|
let left = 0;
|
|
@@ -546,7 +663,7 @@ function useScrollWindow({
|
|
|
546
663
|
followBottom = false
|
|
547
664
|
}) {
|
|
548
665
|
const viewportRef = useRef2(null);
|
|
549
|
-
const { columns } =
|
|
666
|
+
const { columns } = useWindowSize4();
|
|
550
667
|
const [size, setSize] = useState3(
|
|
551
668
|
null
|
|
552
669
|
);
|
|
@@ -601,14 +718,14 @@ function useScrollWindow({
|
|
|
601
718
|
};
|
|
602
719
|
}
|
|
603
720
|
function ScrollView({ scroll, children }) {
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
721
|
+
return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
722
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
606
723
|
"\u2191 ",
|
|
607
724
|
scroll.hiddenAbove,
|
|
608
725
|
" more"
|
|
609
726
|
] }),
|
|
610
727
|
children,
|
|
611
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
728
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
612
729
|
"\u2193 ",
|
|
613
730
|
scroll.hiddenBelow,
|
|
614
731
|
" more"
|
|
@@ -617,7 +734,7 @@ function ScrollView({ scroll, children }) {
|
|
|
617
734
|
}
|
|
618
735
|
|
|
619
736
|
// src/ui/SelectPrompt.tsx
|
|
620
|
-
import { jsx as jsx4, jsxs as
|
|
737
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
621
738
|
var CANCEL = "cancel";
|
|
622
739
|
var ARROW_WIDTH = 4;
|
|
623
740
|
var COLUMN_GAP = 2;
|
|
@@ -648,7 +765,7 @@ function SelectPrompt({
|
|
|
648
765
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
649
766
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
650
767
|
const containerRef = useRef3(null);
|
|
651
|
-
const { columns } =
|
|
768
|
+
const { columns } = useWindowSize5();
|
|
652
769
|
const [width, setWidth] = useState4(columns);
|
|
653
770
|
useLayoutEffect2(() => {
|
|
654
771
|
if (!containerRef.current) return;
|
|
@@ -702,14 +819,14 @@ function SelectPrompt({
|
|
|
702
819
|
}
|
|
703
820
|
}
|
|
704
821
|
});
|
|
705
|
-
return /* @__PURE__ */ jsx4(
|
|
706
|
-
/* @__PURE__ */
|
|
707
|
-
error && /* @__PURE__ */ jsx4(
|
|
708
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx4(
|
|
822
|
+
return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
|
|
823
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
824
|
+
error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
|
|
825
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
709
826
|
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
710
|
-
/* @__PURE__ */
|
|
711
|
-
question && /* @__PURE__ */ jsx4(
|
|
712
|
-
helpText && /* @__PURE__ */ jsx4(
|
|
827
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
828
|
+
question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
|
|
829
|
+
helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
|
|
713
830
|
] })
|
|
714
831
|
] }),
|
|
715
832
|
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
@@ -719,39 +836,39 @@ function SelectPrompt({
|
|
|
719
836
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
720
837
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
721
838
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
722
|
-
const label = /* @__PURE__ */
|
|
839
|
+
const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
|
|
723
840
|
highlighted ? "\u276F " : " ",
|
|
724
841
|
bullet,
|
|
725
842
|
option
|
|
726
843
|
] });
|
|
727
844
|
const isText = sec?.kind === "text";
|
|
728
|
-
return /* @__PURE__ */
|
|
729
|
-
|
|
845
|
+
return /* @__PURE__ */ jsxs5(
|
|
846
|
+
Box6,
|
|
730
847
|
{
|
|
731
848
|
width: isText ? "100%" : barWidth,
|
|
732
849
|
paddingX: 1,
|
|
733
850
|
paddingY: 1,
|
|
734
851
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
735
852
|
children: [
|
|
736
|
-
/* @__PURE__ */ jsx4(
|
|
737
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
738
|
-
|
|
853
|
+
/* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
854
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
855
|
+
Text6,
|
|
739
856
|
{
|
|
740
857
|
wrap: "truncate",
|
|
741
858
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
742
859
|
children: sec.value
|
|
743
860
|
}
|
|
744
861
|
) }),
|
|
745
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
862
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
746
863
|
]
|
|
747
864
|
},
|
|
748
865
|
`row-${i}`
|
|
749
866
|
);
|
|
750
867
|
}) }),
|
|
751
|
-
/* @__PURE__ */ jsx4(
|
|
868
|
+
/* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
752
869
|
i > 0 ? " " : "",
|
|
753
|
-
/* @__PURE__ */ jsx4(
|
|
754
|
-
/* @__PURE__ */
|
|
870
|
+
/* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
|
|
871
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
755
872
|
" ",
|
|
756
873
|
label
|
|
757
874
|
] })
|
|
@@ -760,7 +877,7 @@ function SelectPrompt({
|
|
|
760
877
|
}
|
|
761
878
|
|
|
762
879
|
// src/ui/PromptInput.tsx
|
|
763
|
-
import { jsx as jsx5, jsxs as
|
|
880
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
764
881
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
765
882
|
function EnterToContinuePrompt({
|
|
766
883
|
question,
|
|
@@ -771,10 +888,10 @@ function EnterToContinuePrompt({
|
|
|
771
888
|
if (key.return) onDecide(true);
|
|
772
889
|
else if (key.escape) onDecide(false);
|
|
773
890
|
});
|
|
774
|
-
return /* @__PURE__ */
|
|
775
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
776
|
-
question && /* @__PURE__ */ jsx5(
|
|
777
|
-
/* @__PURE__ */
|
|
891
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
|
|
892
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
893
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
|
|
894
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
778
895
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
779
896
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
780
897
|
] })
|
|
@@ -784,11 +901,11 @@ function PromptInput() {
|
|
|
784
901
|
const { phase, inputReq, submitInput } = useWizard();
|
|
785
902
|
const [draft, setDraft] = useState5("");
|
|
786
903
|
if (phase === "done" || phase === "error") {
|
|
787
|
-
return /* @__PURE__ */ jsx5(
|
|
904
|
+
return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
788
905
|
}
|
|
789
906
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
790
907
|
if (inputReq.promptType === "multipleChoice") {
|
|
791
|
-
return /* @__PURE__ */ jsx5(
|
|
908
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
792
909
|
SelectPrompt,
|
|
793
910
|
{
|
|
794
911
|
question: inputReq.prompt,
|
|
@@ -805,7 +922,7 @@ function PromptInput() {
|
|
|
805
922
|
) });
|
|
806
923
|
}
|
|
807
924
|
if (inputReq.promptType === "multiSelect") {
|
|
808
|
-
return /* @__PURE__ */ jsx5(
|
|
925
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
809
926
|
SelectPrompt,
|
|
810
927
|
{
|
|
811
928
|
multi: true,
|
|
@@ -820,7 +937,7 @@ function PromptInput() {
|
|
|
820
937
|
) });
|
|
821
938
|
}
|
|
822
939
|
if (inputReq.promptType === "notice") {
|
|
823
|
-
return /* @__PURE__ */ jsx5(
|
|
940
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
824
941
|
SelectPrompt,
|
|
825
942
|
{
|
|
826
943
|
question: inputReq.prompt,
|
|
@@ -842,7 +959,7 @@ function PromptInput() {
|
|
|
842
959
|
}
|
|
843
960
|
if (inputReq.promptType === "acceptReject") {
|
|
844
961
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
845
|
-
return /* @__PURE__ */ jsx5(
|
|
962
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
846
963
|
SelectPrompt,
|
|
847
964
|
{
|
|
848
965
|
question: inputReq.prompt,
|
|
@@ -853,11 +970,11 @@ function PromptInput() {
|
|
|
853
970
|
}
|
|
854
971
|
) });
|
|
855
972
|
}
|
|
856
|
-
return /* @__PURE__ */
|
|
857
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
858
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
859
|
-
/* @__PURE__ */
|
|
860
|
-
/* @__PURE__ */
|
|
973
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
974
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
|
|
975
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
976
|
+
/* @__PURE__ */ jsxs6(Box7, { children: [
|
|
977
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
|
|
861
978
|
inputReq.prompt,
|
|
862
979
|
" "
|
|
863
980
|
] }),
|
|
@@ -879,7 +996,7 @@ function PromptInput() {
|
|
|
879
996
|
// src/ui/Welcome.tsx
|
|
880
997
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
881
998
|
import { fileURLToPath } from "node:url";
|
|
882
|
-
import { Box as
|
|
999
|
+
import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
|
|
883
1000
|
|
|
884
1001
|
// src/ui/copy/welcome.ts
|
|
885
1002
|
var sidebarItems = [
|
|
@@ -907,27 +1024,27 @@ var sidebarItems = [
|
|
|
907
1024
|
|
|
908
1025
|
// src/ui/Welcome.tsx
|
|
909
1026
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
910
|
-
import { jsx as jsx6, jsxs as
|
|
1027
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
911
1028
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
912
1029
|
function SidebarItem({
|
|
913
1030
|
title,
|
|
914
1031
|
description
|
|
915
1032
|
}) {
|
|
916
|
-
return /* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */ jsx6(
|
|
919
|
-
/* @__PURE__ */ jsx6(
|
|
1033
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1034
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
|
|
1035
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
|
|
1036
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
|
|
920
1037
|
] }),
|
|
921
|
-
/* @__PURE__ */
|
|
1038
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
|
|
922
1039
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
923
|
-
/* @__PURE__ */ jsx6(
|
|
1040
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
|
|
924
1041
|
] })
|
|
925
1042
|
] });
|
|
926
1043
|
}
|
|
927
1044
|
function Welcome() {
|
|
928
1045
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
929
1046
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
930
|
-
const { rows } =
|
|
1047
|
+
const { rows } = useWindowSize6();
|
|
931
1048
|
useInput3((input, key) => {
|
|
932
1049
|
if (key.return) confirmStart();
|
|
933
1050
|
else if (input === "i") openLearnMore();
|
|
@@ -946,15 +1063,15 @@ function Welcome() {
|
|
|
946
1063
|
if (rows < 30) {
|
|
947
1064
|
layout = scales["small"];
|
|
948
1065
|
}
|
|
949
|
-
return /* @__PURE__ */
|
|
1066
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
950
1067
|
/* @__PURE__ */ jsx6(
|
|
951
|
-
|
|
1068
|
+
Box8,
|
|
952
1069
|
{
|
|
953
1070
|
paddingY: layout.main.padding.y,
|
|
954
1071
|
paddingX: layout.main.padding.x,
|
|
955
1072
|
flexDirection: "column",
|
|
956
1073
|
justifyContent: "center",
|
|
957
|
-
children: /* @__PURE__ */
|
|
1074
|
+
children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
|
|
958
1075
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
959
1076
|
Image,
|
|
960
1077
|
{
|
|
@@ -966,16 +1083,16 @@ function Welcome() {
|
|
|
966
1083
|
protocol: "halfBlock"
|
|
967
1084
|
}
|
|
968
1085
|
) }),
|
|
969
|
-
/* @__PURE__ */ jsx6(
|
|
970
|
-
/* @__PURE__ */
|
|
1086
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1087
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
971
1088
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
972
1089
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
973
1090
|
] })
|
|
974
1091
|
] })
|
|
975
1092
|
}
|
|
976
1093
|
),
|
|
977
|
-
/* @__PURE__ */
|
|
978
|
-
|
|
1094
|
+
/* @__PURE__ */ jsxs7(
|
|
1095
|
+
Box8,
|
|
979
1096
|
{
|
|
980
1097
|
backgroundColor: COLORS.bg.sidebar,
|
|
981
1098
|
width: 40,
|
|
@@ -985,7 +1102,7 @@ function Welcome() {
|
|
|
985
1102
|
flexDirection: "column",
|
|
986
1103
|
justifyContent: "center",
|
|
987
1104
|
children: [
|
|
988
|
-
/* @__PURE__ */ jsx6(
|
|
1105
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
989
1106
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
990
1107
|
]
|
|
991
1108
|
}
|
|
@@ -995,7 +1112,7 @@ function Welcome() {
|
|
|
995
1112
|
|
|
996
1113
|
// src/ui/LearnMore.tsx
|
|
997
1114
|
import { Fragment as Fragment2 } from "react";
|
|
998
|
-
import { Box as
|
|
1115
|
+
import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
|
|
999
1116
|
|
|
1000
1117
|
// src/ui/copy/learn-more.ts
|
|
1001
1118
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1032,7 +1149,7 @@ var policyLinks = [
|
|
|
1032
1149
|
];
|
|
1033
1150
|
|
|
1034
1151
|
// src/ui/LearnMore.tsx
|
|
1035
|
-
import { jsx as jsx7, jsxs as
|
|
1152
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1036
1153
|
var TAG_COLORS = {
|
|
1037
1154
|
READ: COLORS.success,
|
|
1038
1155
|
WRITE: COLORS.badge,
|
|
@@ -1048,25 +1165,25 @@ function NeverLine({
|
|
|
1048
1165
|
}) {
|
|
1049
1166
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1050
1167
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1051
|
-
return /* @__PURE__ */
|
|
1052
|
-
/* @__PURE__ */ jsx7(
|
|
1168
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1169
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
|
|
1053
1170
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1054
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1171
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1055
1172
|
" ".repeat(rightPad),
|
|
1056
|
-
/* @__PURE__ */ jsx7(
|
|
1173
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
|
|
1057
1174
|
] });
|
|
1058
1175
|
}
|
|
1059
1176
|
function LearnMore() {
|
|
1060
1177
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1061
1178
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1062
|
-
const { columns } =
|
|
1179
|
+
const { columns } = useWindowSize7();
|
|
1063
1180
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1064
1181
|
useInput4((_input, key) => {
|
|
1065
1182
|
if (key.escape) backToHome();
|
|
1066
1183
|
else if (key.return) confirmStart();
|
|
1067
1184
|
});
|
|
1068
|
-
return /* @__PURE__ */
|
|
1069
|
-
|
|
1185
|
+
return /* @__PURE__ */ jsxs8(
|
|
1186
|
+
Box9,
|
|
1070
1187
|
{
|
|
1071
1188
|
flexDirection: "column",
|
|
1072
1189
|
paddingX: PADDING_X,
|
|
@@ -1074,20 +1191,20 @@ function LearnMore() {
|
|
|
1074
1191
|
width: "100%",
|
|
1075
1192
|
gap: 1,
|
|
1076
1193
|
children: [
|
|
1077
|
-
/* @__PURE__ */ jsx7(
|
|
1078
|
-
/* @__PURE__ */ jsx7(
|
|
1079
|
-
/* @__PURE__ */ jsx7(
|
|
1080
|
-
/* @__PURE__ */ jsx7(
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */ jsx7(
|
|
1083
|
-
/* @__PURE__ */ jsx7(
|
|
1084
|
-
/* @__PURE__ */ jsx7(
|
|
1085
|
-
/* @__PURE__ */ jsx7(
|
|
1194
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1195
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
|
|
1196
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
1197
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1198
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1199
|
+
/* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1200
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1201
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1202
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1086
1203
|
] }) })
|
|
1087
1204
|
] })
|
|
1088
1205
|
] }, item.tag)) }),
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
/* @__PURE__ */ jsx7(
|
|
1206
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1207
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1091
1208
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1092
1209
|
/* @__PURE__ */ jsx7(
|
|
1093
1210
|
NeverLine,
|
|
@@ -1096,7 +1213,7 @@ function LearnMore() {
|
|
|
1096
1213
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1097
1214
|
}
|
|
1098
1215
|
),
|
|
1099
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1216
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1100
1217
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1101
1218
|
/* @__PURE__ */ jsx7(
|
|
1102
1219
|
NeverLine,
|
|
@@ -1111,23 +1228,23 @@ function LearnMore() {
|
|
|
1111
1228
|
)
|
|
1112
1229
|
] }, item)),
|
|
1113
1230
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1114
|
-
/* @__PURE__ */ jsx7(
|
|
1231
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1115
1232
|
] }),
|
|
1116
|
-
/* @__PURE__ */ jsx7(
|
|
1117
|
-
/* @__PURE__ */ jsx7(
|
|
1118
|
-
/* @__PURE__ */ jsx7(
|
|
1233
|
+
/* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1234
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1235
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
|
|
1119
1236
|
] }, link.label)) }),
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */ jsx7(
|
|
1123
|
-
/* @__PURE__ */ jsx7(
|
|
1124
|
-
/* @__PURE__ */ jsx7(
|
|
1237
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1238
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1239
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1240
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
|
|
1241
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
|
|
1125
1242
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */ jsx7(
|
|
1128
|
-
/* @__PURE__ */ jsx7(
|
|
1129
|
-
/* @__PURE__ */ jsx7(
|
|
1130
|
-
/* @__PURE__ */ jsx7(
|
|
1243
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1244
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1245
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
|
|
1246
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
|
|
1247
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1131
1248
|
] })
|
|
1132
1249
|
] })
|
|
1133
1250
|
]
|
|
@@ -1136,10 +1253,10 @@ function LearnMore() {
|
|
|
1136
1253
|
}
|
|
1137
1254
|
|
|
1138
1255
|
// src/ui/Sidebar.tsx
|
|
1139
|
-
import { Box as
|
|
1256
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1140
1257
|
|
|
1141
1258
|
// src/ui/Steps.tsx
|
|
1142
|
-
import { Box as
|
|
1259
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1143
1260
|
import Spinner from "ink-spinner";
|
|
1144
1261
|
|
|
1145
1262
|
// src/core/persistence.ts
|
|
@@ -1168,11 +1285,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1168
1285
|
}
|
|
1169
1286
|
|
|
1170
1287
|
// src/ui/Steps.tsx
|
|
1171
|
-
import { jsx as jsx8, jsxs as
|
|
1288
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1172
1289
|
function Steps() {
|
|
1173
1290
|
const { steps } = useWizard();
|
|
1174
1291
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1175
|
-
return /* @__PURE__ */ jsx8(
|
|
1292
|
+
return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
|
|
1176
1293
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1177
1294
|
" ",
|
|
1178
1295
|
s.title
|
|
@@ -1182,7 +1299,7 @@ function CurrentStep() {
|
|
|
1182
1299
|
const { steps } = useWizard();
|
|
1183
1300
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1184
1301
|
if (!currentStep) return null;
|
|
1185
|
-
return /* @__PURE__ */
|
|
1302
|
+
return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
|
|
1186
1303
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1187
1304
|
" ",
|
|
1188
1305
|
` ${currentStep.title}`
|
|
@@ -1190,19 +1307,19 @@ function CurrentStep() {
|
|
|
1190
1307
|
}
|
|
1191
1308
|
|
|
1192
1309
|
// src/ui/Progress.tsx
|
|
1193
|
-
import { Box as
|
|
1194
|
-
import { jsx as jsx9, jsxs as
|
|
1310
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1311
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1195
1312
|
function Progress() {
|
|
1196
1313
|
const { steps, currentStepIndex } = useWizard();
|
|
1197
1314
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1198
1315
|
if (visibleSteps.length === 0) return null;
|
|
1199
1316
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1200
1317
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1201
|
-
return /* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */ jsx9(
|
|
1203
|
-
/* @__PURE__ */ jsx9(
|
|
1204
|
-
/* @__PURE__ */ jsx9(
|
|
1205
|
-
/* @__PURE__ */ jsx9(
|
|
1318
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1319
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
|
|
1320
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
|
|
1321
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
|
|
1322
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
|
|
1206
1323
|
] });
|
|
1207
1324
|
}
|
|
1208
1325
|
|
|
@@ -1213,10 +1330,10 @@ var sidebarCommands = [
|
|
|
1213
1330
|
];
|
|
1214
1331
|
|
|
1215
1332
|
// src/ui/Sidebar.tsx
|
|
1216
|
-
import { jsx as jsx10, jsxs as
|
|
1333
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1217
1334
|
function Sidebar() {
|
|
1218
|
-
return /* @__PURE__ */
|
|
1219
|
-
|
|
1335
|
+
return /* @__PURE__ */ jsxs11(
|
|
1336
|
+
Box12,
|
|
1220
1337
|
{
|
|
1221
1338
|
backgroundColor: "#14171E",
|
|
1222
1339
|
width: 30,
|
|
@@ -1225,16 +1342,16 @@ function Sidebar() {
|
|
|
1225
1342
|
flexDirection: "column",
|
|
1226
1343
|
justifyContent: "space-between",
|
|
1227
1344
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */ jsx10(
|
|
1345
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1346
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1230
1347
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1231
1348
|
] }),
|
|
1232
|
-
/* @__PURE__ */
|
|
1349
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1233
1350
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1234
|
-
/* @__PURE__ */ jsx10(
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */ jsx10(
|
|
1237
|
-
/* @__PURE__ */ jsx10(
|
|
1351
|
+
/* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1352
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1353
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1354
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
|
|
1238
1355
|
] });
|
|
1239
1356
|
}) })
|
|
1240
1357
|
] })
|
|
@@ -1244,12 +1361,12 @@ function Sidebar() {
|
|
|
1244
1361
|
}
|
|
1245
1362
|
|
|
1246
1363
|
// src/ui/Ribbon.tsx
|
|
1247
|
-
import { Box as
|
|
1248
|
-
import { jsx as jsx11, jsxs as
|
|
1364
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1365
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1249
1366
|
function Ribbon() {
|
|
1250
1367
|
const firstCommand = sidebarCommands[0];
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
|
|
1368
|
+
return /* @__PURE__ */ jsxs12(
|
|
1369
|
+
Box13,
|
|
1253
1370
|
{
|
|
1254
1371
|
backgroundColor: "#14171E",
|
|
1255
1372
|
flexDirection: "row",
|
|
@@ -1259,9 +1376,9 @@ function Ribbon() {
|
|
|
1259
1376
|
children: [
|
|
1260
1377
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1261
1378
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */ jsx11(
|
|
1264
|
-
/* @__PURE__ */ jsx11(
|
|
1379
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1380
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1381
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
|
|
1265
1382
|
] })
|
|
1266
1383
|
]
|
|
1267
1384
|
}
|
|
@@ -1272,8 +1389,8 @@ function Ribbon() {
|
|
|
1272
1389
|
import { useState as useState6 } from "react";
|
|
1273
1390
|
|
|
1274
1391
|
// src/ui/Logs.tsx
|
|
1275
|
-
import { Box as
|
|
1276
|
-
import { jsx as jsx12, jsxs as
|
|
1392
|
+
import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
|
|
1393
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1277
1394
|
var KIND_COLOR = {
|
|
1278
1395
|
tool: COLORS.primary,
|
|
1279
1396
|
prompt: COLORS.badge
|
|
@@ -1309,8 +1426,8 @@ function Logs() {
|
|
|
1309
1426
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1310
1427
|
});
|
|
1311
1428
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1312
|
-
return /* @__PURE__ */
|
|
1313
|
-
logs.length === 0 && /* @__PURE__ */ jsx12(
|
|
1429
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1430
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
|
|
1314
1431
|
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1315
1432
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1316
1433
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
@@ -1321,14 +1438,14 @@ function Logs() {
|
|
|
1321
1438
|
const name = truncate2(entry.name, budget);
|
|
1322
1439
|
budget -= name.length;
|
|
1323
1440
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1324
|
-
return /* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */ jsx12(
|
|
1326
|
-
/* @__PURE__ */ jsx12(
|
|
1327
|
-
preview && /* @__PURE__ */ jsx12(
|
|
1328
|
-
durationText && /* @__PURE__ */ jsx12(
|
|
1441
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1442
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
|
|
1443
|
+
/* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1444
|
+
preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1445
|
+
durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
|
|
1329
1446
|
] }, entry.id);
|
|
1330
1447
|
}) }),
|
|
1331
|
-
/* @__PURE__ */ jsx12(
|
|
1448
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1332
1449
|
] });
|
|
1333
1450
|
}
|
|
1334
1451
|
|
|
@@ -1520,11 +1637,11 @@ function track(event, payload) {
|
|
|
1520
1637
|
}
|
|
1521
1638
|
|
|
1522
1639
|
// src/ui/App.tsx
|
|
1523
|
-
import { jsx as jsx13, jsxs as
|
|
1640
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1524
1641
|
function App() {
|
|
1525
1642
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1526
1643
|
const { exit } = useApp();
|
|
1527
|
-
const { columns, rows } =
|
|
1644
|
+
const { columns, rows } = useWindowSize8();
|
|
1528
1645
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1529
1646
|
const finished = phase === "done" || phase === "error";
|
|
1530
1647
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1537,7 +1654,7 @@ function App() {
|
|
|
1537
1654
|
{ isActive: finished }
|
|
1538
1655
|
);
|
|
1539
1656
|
useInput6((_input, key) => {
|
|
1540
|
-
if (phase === "idle" || phase === "
|
|
1657
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
1541
1658
|
if (key.tab) {
|
|
1542
1659
|
setShowLogs(!showLogs);
|
|
1543
1660
|
track("AI Wizard Interaction", {
|
|
@@ -1547,49 +1664,45 @@ function App() {
|
|
|
1547
1664
|
});
|
|
1548
1665
|
}
|
|
1549
1666
|
});
|
|
1550
|
-
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1667
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1551
1668
|
useInput6((_input, key) => {
|
|
1552
1669
|
if (escOwnedElsewhere) return;
|
|
1553
1670
|
if (key.escape) {
|
|
1554
1671
|
track("AI Wizard Interaction", {
|
|
1555
1672
|
context: "global",
|
|
1556
1673
|
key: "esc",
|
|
1557
|
-
// No step is active until `startWorkflow` — report the phase instead.
|
|
1558
1674
|
currentStep: currentStep?.id ?? phase
|
|
1559
1675
|
});
|
|
1560
1676
|
exit();
|
|
1561
1677
|
}
|
|
1562
1678
|
});
|
|
1563
|
-
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1679
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1564
1680
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1565
1681
|
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,
|
|
1682
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
1683
|
+
return (
|
|
1684
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1685
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1686
|
+
flicker and leftover rows. */
|
|
1687
|
+
/* @__PURE__ */ jsxs14(
|
|
1688
|
+
Box15,
|
|
1689
|
+
{
|
|
1690
|
+
backgroundColor: COLORS.bg.main,
|
|
1691
|
+
flexDirection: "row",
|
|
1692
|
+
width: columns,
|
|
1693
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
1694
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1695
|
+
children: [
|
|
1696
|
+
mainWindowVisible && /* @__PURE__ */ jsxs14(
|
|
1697
|
+
Box15,
|
|
1698
|
+
{
|
|
1699
|
+
flexDirection,
|
|
1700
|
+
width: "100%",
|
|
1701
|
+
maxHeight: rows,
|
|
1702
|
+
justifyContent: "space-between",
|
|
1703
|
+
children: [
|
|
1704
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
|
|
1705
|
+
Box15,
|
|
1593
1706
|
{
|
|
1594
1707
|
flexDirection: "column",
|
|
1595
1708
|
paddingX: 4,
|
|
@@ -1597,24 +1710,29 @@ function App() {
|
|
|
1597
1710
|
width: showSidebar ? 70 : "100%",
|
|
1598
1711
|
flexGrow: 1,
|
|
1599
1712
|
children: [
|
|
1713
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
|
|
1714
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1715
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1716
|
+
] }),
|
|
1717
|
+
/* @__PURE__ */ jsx13(CliOutput, {}),
|
|
1600
1718
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1601
1719
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1602
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1603
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1720
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1721
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
|
|
1604
1722
|
"\u2716 ",
|
|
1605
1723
|
error
|
|
1606
1724
|
] }) })
|
|
1607
1725
|
]
|
|
1608
1726
|
}
|
|
1609
|
-
)
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1727
|
+
),
|
|
1728
|
+
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1729
|
+
]
|
|
1730
|
+
}
|
|
1731
|
+
),
|
|
1732
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
|
|
1733
|
+
]
|
|
1734
|
+
}
|
|
1735
|
+
)
|
|
1618
1736
|
);
|
|
1619
1737
|
}
|
|
1620
1738
|
|
|
@@ -1628,7 +1746,8 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1628
1746
|
var DEFAULT_CONFIG = {
|
|
1629
1747
|
version: 1,
|
|
1630
1748
|
aiConsent: false,
|
|
1631
|
-
workflowsRun: []
|
|
1749
|
+
workflowsRun: [],
|
|
1750
|
+
searchApiKeys: {}
|
|
1632
1751
|
};
|
|
1633
1752
|
async function loadConfig() {
|
|
1634
1753
|
try {
|
|
@@ -1647,6 +1766,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1647
1766
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1648
1767
|
await saveConfig(config);
|
|
1649
1768
|
}
|
|
1769
|
+
function isStoredSearchKey(value) {
|
|
1770
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1771
|
+
const { appId, key } = value;
|
|
1772
|
+
return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
|
|
1773
|
+
}
|
|
1774
|
+
function storedSearchKeys(config) {
|
|
1775
|
+
const stored = config.searchApiKeys;
|
|
1776
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1777
|
+
return {};
|
|
1778
|
+
}
|
|
1779
|
+
return stored;
|
|
1780
|
+
}
|
|
1781
|
+
async function getStoredSearchKey(index, appId) {
|
|
1782
|
+
const entry = storedSearchKeys(await loadConfig())[index];
|
|
1783
|
+
if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
|
|
1784
|
+
return entry.key;
|
|
1785
|
+
}
|
|
1786
|
+
async function storeSearchKey(index, appId, key) {
|
|
1787
|
+
const config = await loadConfig();
|
|
1788
|
+
config.searchApiKeys = {
|
|
1789
|
+
...storedSearchKeys(config),
|
|
1790
|
+
[index]: { appId, key }
|
|
1791
|
+
};
|
|
1792
|
+
await saveConfig(config);
|
|
1793
|
+
}
|
|
1794
|
+
async function forgetSearchKey(index) {
|
|
1795
|
+
const config = await loadConfig();
|
|
1796
|
+
const remaining = { ...storedSearchKeys(config) };
|
|
1797
|
+
delete remaining[index];
|
|
1798
|
+
config.searchApiKeys = remaining;
|
|
1799
|
+
await saveConfig(config);
|
|
1800
|
+
}
|
|
1650
1801
|
|
|
1651
1802
|
// src/core/orchestrator.ts
|
|
1652
1803
|
function defineStep(step) {
|
|
@@ -1850,61 +2001,138 @@ async function runWorkflow(workflow, appId) {
|
|
|
1850
2001
|
}
|
|
1851
2002
|
}
|
|
1852
2003
|
|
|
1853
|
-
// src/lib/
|
|
1854
|
-
import {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2004
|
+
// src/lib/algoliaApp.ts
|
|
2005
|
+
import { z as z4 } from "zod";
|
|
2006
|
+
var applicationSchema = z4.object({
|
|
2007
|
+
id: z4.string().min(1),
|
|
2008
|
+
name: z4.string().default(""),
|
|
2009
|
+
plan: z4.string().optional()
|
|
2010
|
+
});
|
|
2011
|
+
var listSchema = z4.array(
|
|
2012
|
+
z4.object({
|
|
2013
|
+
id: z4.string().min(1),
|
|
2014
|
+
name: z4.string().default(""),
|
|
2015
|
+
plan_label: z4.string().optional()
|
|
2016
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
2017
|
+
);
|
|
2018
|
+
async function currentApplication() {
|
|
2019
|
+
let raw;
|
|
1866
2020
|
try {
|
|
1867
|
-
|
|
2021
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1868
2022
|
} catch {
|
|
1869
|
-
return
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2026
|
+
return parsed.success ? parsed.data : null;
|
|
2027
|
+
}
|
|
2028
|
+
async function requireApplication() {
|
|
2029
|
+
const app = await currentApplication();
|
|
2030
|
+
if (!app) {
|
|
2031
|
+
throw new Error(
|
|
2032
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
return app;
|
|
2036
|
+
}
|
|
2037
|
+
async function listApplications() {
|
|
2038
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
2039
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
2040
|
+
if (!parsed.success) {
|
|
2041
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
1870
2042
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
|
|
2043
|
+
return parsed.data;
|
|
2044
|
+
}
|
|
2045
|
+
async function selectApplication(id) {
|
|
2046
|
+
const raw = await runAlgoliaCli(
|
|
2047
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
2048
|
+
{ onOutput: stderrSink }
|
|
2049
|
+
);
|
|
2050
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2051
|
+
if (!parsed.success) {
|
|
2052
|
+
throw new Error(
|
|
2053
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
return parsed.data;
|
|
2057
|
+
}
|
|
2058
|
+
function parseJson(text) {
|
|
1884
2059
|
try {
|
|
1885
|
-
|
|
2060
|
+
return JSON.parse(text);
|
|
1886
2061
|
} catch {
|
|
1887
|
-
|
|
2062
|
+
return void 0;
|
|
1888
2063
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// src/lib/algoliaAppPicker.ts
|
|
2067
|
+
function secondaryFor(app) {
|
|
2068
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
2069
|
+
}
|
|
2070
|
+
function labelFor(app) {
|
|
2071
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
2072
|
+
}
|
|
2073
|
+
function selectAndReport(app) {
|
|
2074
|
+
useWizard.getState().pushCliOutput(
|
|
2075
|
+
"stdout",
|
|
2076
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
2077
|
+
);
|
|
2078
|
+
return selectApplication(app.id);
|
|
2079
|
+
}
|
|
2080
|
+
async function promptForApplication() {
|
|
2081
|
+
const store = useWizard.getState();
|
|
2082
|
+
const apps = await listApplications();
|
|
2083
|
+
if (apps.length === 0) {
|
|
1891
2084
|
throw new Error(
|
|
1892
|
-
"
|
|
2085
|
+
"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
2086
|
);
|
|
1894
2087
|
}
|
|
1895
|
-
|
|
2088
|
+
if (apps.length === 1) {
|
|
2089
|
+
const only = apps[0];
|
|
2090
|
+
logger.info(
|
|
2091
|
+
{ app: only.id },
|
|
2092
|
+
"single application on the account; selecting it"
|
|
2093
|
+
);
|
|
2094
|
+
return selectAndReport(only);
|
|
2095
|
+
}
|
|
2096
|
+
const messages = ["Which Algolia application should the wizard work in?"];
|
|
2097
|
+
for (; ; ) {
|
|
2098
|
+
const choice = await store.requestUserInput({
|
|
2099
|
+
prompt: "Select an application",
|
|
2100
|
+
promptType: "multipleChoice",
|
|
2101
|
+
options: apps.map(labelFor),
|
|
2102
|
+
secondary: apps.map(secondaryFor),
|
|
2103
|
+
messages
|
|
2104
|
+
});
|
|
2105
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
2106
|
+
if (!chosen) {
|
|
2107
|
+
throw new Error("Application picker received an unexpected selection");
|
|
2108
|
+
}
|
|
2109
|
+
try {
|
|
2110
|
+
return await selectAndReport(chosen);
|
|
2111
|
+
} catch (err) {
|
|
2112
|
+
logger.warn(
|
|
2113
|
+
{ app: chosen.id, err: err.message },
|
|
2114
|
+
"application select failed; re-prompting"
|
|
2115
|
+
);
|
|
2116
|
+
messages.push(
|
|
2117
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
async function ensureApplication() {
|
|
2123
|
+
return await currentApplication() ?? await promptForApplication();
|
|
1896
2124
|
}
|
|
1897
2125
|
|
|
1898
2126
|
// src/workflows/default.ts
|
|
1899
|
-
import { z as
|
|
2127
|
+
import { z as z27 } from "zod";
|
|
1900
2128
|
|
|
1901
2129
|
// src/actions/listIndices.ts
|
|
1902
|
-
import { z as
|
|
1903
|
-
var indicesListSchema =
|
|
1904
|
-
items:
|
|
1905
|
-
|
|
1906
|
-
name:
|
|
1907
|
-
entries:
|
|
2130
|
+
import { z as z5 } from "zod";
|
|
2131
|
+
var indicesListSchema = z5.object({
|
|
2132
|
+
items: z5.array(
|
|
2133
|
+
z5.object({
|
|
2134
|
+
name: z5.string(),
|
|
2135
|
+
entries: z5.number().default(0)
|
|
1908
2136
|
})
|
|
1909
2137
|
)
|
|
1910
2138
|
});
|
|
@@ -1975,12 +2203,12 @@ import "zod";
|
|
|
1975
2203
|
|
|
1976
2204
|
// src/lib/tools/listFiles.ts
|
|
1977
2205
|
import { tool } from "ai";
|
|
1978
|
-
import
|
|
2206
|
+
import z6 from "zod";
|
|
1979
2207
|
import { readdir } from "node:fs/promises";
|
|
1980
2208
|
|
|
1981
2209
|
// src/lib/tools/path.ts
|
|
1982
2210
|
import { lstat } from "node:fs/promises";
|
|
1983
|
-
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as
|
|
2211
|
+
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
|
|
1984
2212
|
function resolveInRoot(ctx, path) {
|
|
1985
2213
|
const target = resolve2(ctx.cwd, path);
|
|
1986
2214
|
const rel = relative(ctx.root, target);
|
|
@@ -1996,7 +2224,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1996
2224
|
let current = ctx.root;
|
|
1997
2225
|
const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
|
|
1998
2226
|
for (const part of parts) {
|
|
1999
|
-
current =
|
|
2227
|
+
current = join6(current, part);
|
|
2000
2228
|
try {
|
|
2001
2229
|
if ((await lstat(current)).isSymbolicLink()) return true;
|
|
2002
2230
|
} catch (err) {
|
|
@@ -2011,7 +2239,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
2011
2239
|
function listFilesTool(ctx) {
|
|
2012
2240
|
return tool({
|
|
2013
2241
|
description: "List files in the current working directory",
|
|
2014
|
-
inputSchema:
|
|
2242
|
+
inputSchema: z6.object(),
|
|
2015
2243
|
execute: async () => {
|
|
2016
2244
|
logger.info("called listFiles tool");
|
|
2017
2245
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
@@ -2027,13 +2255,13 @@ function listFilesTool(ctx) {
|
|
|
2027
2255
|
|
|
2028
2256
|
// src/lib/tools/changeDirectory.ts
|
|
2029
2257
|
import { tool as tool2 } from "ai";
|
|
2030
|
-
import
|
|
2258
|
+
import z7 from "zod";
|
|
2031
2259
|
import { stat } from "node:fs/promises";
|
|
2032
2260
|
function changeDirectoryTool(ctx) {
|
|
2033
2261
|
return tool2({
|
|
2034
2262
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
2035
|
-
inputSchema:
|
|
2036
|
-
path:
|
|
2263
|
+
inputSchema: z7.object({
|
|
2264
|
+
path: z7.string().describe("Directory to change into")
|
|
2037
2265
|
}),
|
|
2038
2266
|
execute: async ({ path }) => {
|
|
2039
2267
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -2055,13 +2283,13 @@ function changeDirectoryTool(ctx) {
|
|
|
2055
2283
|
|
|
2056
2284
|
// src/lib/tools/reportStatus.ts
|
|
2057
2285
|
import { tool as tool3 } from "ai";
|
|
2058
|
-
import
|
|
2286
|
+
import z8 from "zod";
|
|
2059
2287
|
function reportStatusTool(output) {
|
|
2060
2288
|
return tool3({
|
|
2061
2289
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2062
|
-
inputSchema:
|
|
2063
|
-
status:
|
|
2064
|
-
reason:
|
|
2290
|
+
inputSchema: z8.object({
|
|
2291
|
+
status: z8.enum(["success", "fail"]),
|
|
2292
|
+
reason: z8.string().optional(),
|
|
2065
2293
|
output
|
|
2066
2294
|
}),
|
|
2067
2295
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2073,8 +2301,8 @@ function reportStatusTool(output) {
|
|
|
2073
2301
|
|
|
2074
2302
|
// src/lib/tools/readFile.ts
|
|
2075
2303
|
import { tool as tool4 } from "ai";
|
|
2076
|
-
import
|
|
2077
|
-
import { readFile as
|
|
2304
|
+
import z9 from "zod";
|
|
2305
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2078
2306
|
|
|
2079
2307
|
// src/lib/tools/env.ts
|
|
2080
2308
|
import { basename } from "node:path";
|
|
@@ -2101,8 +2329,8 @@ function redactEnvValues(content) {
|
|
|
2101
2329
|
function readFileTool(ctx) {
|
|
2102
2330
|
return tool4({
|
|
2103
2331
|
description: "Read the contents of a file at the given path",
|
|
2104
|
-
inputSchema:
|
|
2105
|
-
filePath:
|
|
2332
|
+
inputSchema: z9.object({
|
|
2333
|
+
filePath: z9.string().describe("Path to the file to read")
|
|
2106
2334
|
}),
|
|
2107
2335
|
execute: async ({ filePath }) => {
|
|
2108
2336
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2112,7 +2340,7 @@ function readFileTool(ctx) {
|
|
|
2112
2340
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2113
2341
|
if (!resolved.ok) return resolved.error;
|
|
2114
2342
|
try {
|
|
2115
|
-
const content = await
|
|
2343
|
+
const content = await readFile3(resolved.target, "utf8");
|
|
2116
2344
|
return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
|
|
2117
2345
|
} catch (err) {
|
|
2118
2346
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2123,15 +2351,15 @@ function readFileTool(ctx) {
|
|
|
2123
2351
|
|
|
2124
2352
|
// src/lib/tools/writeFile.ts
|
|
2125
2353
|
import { tool as tool5 } from "ai";
|
|
2126
|
-
import
|
|
2354
|
+
import z10 from "zod";
|
|
2127
2355
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2128
2356
|
import { dirname as dirname4 } from "node:path";
|
|
2129
2357
|
function writeFileTool(ctx) {
|
|
2130
2358
|
return tool5({
|
|
2131
2359
|
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:
|
|
2360
|
+
inputSchema: z10.object({
|
|
2361
|
+
filePath: z10.string().describe("Path to the file to write"),
|
|
2362
|
+
content: z10.string().describe("Content to write to the file")
|
|
2135
2363
|
}),
|
|
2136
2364
|
execute: async ({ filePath, content }) => {
|
|
2137
2365
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2156,9 +2384,125 @@ function writeFileTool(ctx) {
|
|
|
2156
2384
|
|
|
2157
2385
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2158
2386
|
import { tool as tool6 } from "ai";
|
|
2159
|
-
import
|
|
2160
|
-
import { mkdir as mkdir4, readFile as
|
|
2387
|
+
import z12 from "zod";
|
|
2388
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2161
2389
|
import { dirname as dirname5 } from "node:path";
|
|
2390
|
+
|
|
2391
|
+
// src/lib/algoliaApiKey.ts
|
|
2392
|
+
import { z as z11 } from "zod";
|
|
2393
|
+
var WRITE_ACLS = [
|
|
2394
|
+
"addObject",
|
|
2395
|
+
"deleteObject",
|
|
2396
|
+
"settings",
|
|
2397
|
+
"editSettings",
|
|
2398
|
+
"listIndexes"
|
|
2399
|
+
];
|
|
2400
|
+
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2401
|
+
var apiKeySchema = z11.object({
|
|
2402
|
+
value: z11.string().min(1),
|
|
2403
|
+
acl: z11.array(z11.string()).default([]),
|
|
2404
|
+
indexes: z11.array(z11.string()).default([])
|
|
2405
|
+
});
|
|
2406
|
+
var apiKeyListSchema = z11.object({
|
|
2407
|
+
items: z11.array(apiKeySchema).optional(),
|
|
2408
|
+
keys: z11.array(apiKeySchema).optional()
|
|
2409
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2410
|
+
var createdKeySchema = z11.object({
|
|
2411
|
+
key: z11.string().min(1).optional(),
|
|
2412
|
+
value: z11.string().min(1).optional()
|
|
2413
|
+
}).transform((o) => o.key ?? o.value);
|
|
2414
|
+
function canReuseForWrites(key, index) {
|
|
2415
|
+
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2416
|
+
}
|
|
2417
|
+
async function resolveWriteKey(index) {
|
|
2418
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2419
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuseForWrites(key, index))?.value;
|
|
2420
|
+
if (existing) {
|
|
2421
|
+
logger.info({ index }, "reusing existing write API key");
|
|
2422
|
+
return existing;
|
|
2423
|
+
}
|
|
2424
|
+
logger.info({ index }, "no reusable write key found; creating one");
|
|
2425
|
+
const created = await runAlgoliaCli([
|
|
2426
|
+
"apikeys",
|
|
2427
|
+
"create",
|
|
2428
|
+
"--indices",
|
|
2429
|
+
index,
|
|
2430
|
+
"--acl",
|
|
2431
|
+
WRITE_ACLS.join(","),
|
|
2432
|
+
"--description",
|
|
2433
|
+
`wizard write key for ${index}`,
|
|
2434
|
+
"-o",
|
|
2435
|
+
"json"
|
|
2436
|
+
]);
|
|
2437
|
+
const writeKey = createdKeySchema.parse(JSON.parse(created));
|
|
2438
|
+
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2439
|
+
return writeKey;
|
|
2440
|
+
}
|
|
2441
|
+
async function createSearchOnlyKey(index) {
|
|
2442
|
+
logger.info({ index }, "creating a search-only API key");
|
|
2443
|
+
const stdout = await runAlgoliaCli([
|
|
2444
|
+
"apikeys",
|
|
2445
|
+
"create",
|
|
2446
|
+
"--acl",
|
|
2447
|
+
"search",
|
|
2448
|
+
"--indices",
|
|
2449
|
+
index,
|
|
2450
|
+
"--description",
|
|
2451
|
+
`Algolia Wizard search-only key for ${index}`,
|
|
2452
|
+
"-o",
|
|
2453
|
+
"json"
|
|
2454
|
+
]);
|
|
2455
|
+
let payload;
|
|
2456
|
+
try {
|
|
2457
|
+
payload = JSON.parse(stdout);
|
|
2458
|
+
} catch {
|
|
2459
|
+
throw new Error("apikeys create returned output that is not valid JSON");
|
|
2460
|
+
}
|
|
2461
|
+
const created = createdKeySchema.parse(payload);
|
|
2462
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2463
|
+
return created;
|
|
2464
|
+
}
|
|
2465
|
+
async function apiKeyExists(key) {
|
|
2466
|
+
try {
|
|
2467
|
+
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
|
|
2468
|
+
return true;
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
return !/does not exist|not found|404/i.test(err.message);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2474
|
+
if (envKey) {
|
|
2475
|
+
await recordSearchKey(index, appId, envKey);
|
|
2476
|
+
return { key: envKey, source: "env" };
|
|
2477
|
+
}
|
|
2478
|
+
const stored = await getStoredSearchKey(index, appId);
|
|
2479
|
+
if (stored) {
|
|
2480
|
+
if (await apiKeyExists(stored)) {
|
|
2481
|
+
logger.info({ index, appId }, "reusing the stored search-only API key");
|
|
2482
|
+
return { key: stored, source: "config" };
|
|
2483
|
+
}
|
|
2484
|
+
logger.warn(
|
|
2485
|
+
{ index, appId },
|
|
2486
|
+
"the stored search-only API key no longer exists; creating a replacement"
|
|
2487
|
+
);
|
|
2488
|
+
await forgetSearchKey(index);
|
|
2489
|
+
}
|
|
2490
|
+
const key = await createSearchOnlyKey(index);
|
|
2491
|
+
await recordSearchKey(index, appId, key);
|
|
2492
|
+
return { key, source: "created" };
|
|
2493
|
+
}
|
|
2494
|
+
async function recordSearchKey(index, appId, key) {
|
|
2495
|
+
try {
|
|
2496
|
+
await storeSearchKey(index, appId, key);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
logger.warn(
|
|
2499
|
+
{ err: err.message, index },
|
|
2500
|
+
"could not record the search-only API key; a later run may create another"
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2162
2506
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2163
2507
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2164
2508
|
function appendEnv(content, entries) {
|
|
@@ -2172,9 +2516,9 @@ function hasEnv(content, name) {
|
|
|
2172
2516
|
}
|
|
2173
2517
|
function writeCredentialsTool(ctx) {
|
|
2174
2518
|
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:
|
|
2519
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
|
|
2520
|
+
inputSchema: z12.object({
|
|
2521
|
+
filePath: z12.string().describe(
|
|
2178
2522
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2179
2523
|
)
|
|
2180
2524
|
}),
|
|
@@ -2182,11 +2526,17 @@ function writeCredentialsTool(ctx) {
|
|
|
2182
2526
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2183
2527
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2184
2528
|
if (resolved.ok === false) return resolved.error;
|
|
2185
|
-
|
|
2529
|
+
const targetIndex = useWizard.getState().targetIndex;
|
|
2530
|
+
if (!targetIndex) {
|
|
2531
|
+
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2532
|
+
}
|
|
2533
|
+
let appId;
|
|
2534
|
+
let writeKey;
|
|
2186
2535
|
try {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2536
|
+
appId = (await requireApplication()).id;
|
|
2537
|
+
writeKey = await resolveWriteKey(targetIndex);
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2190
2540
|
}
|
|
2191
2541
|
try {
|
|
2192
2542
|
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
@@ -2194,7 +2544,7 @@ function writeCredentialsTool(ctx) {
|
|
|
2194
2544
|
}
|
|
2195
2545
|
let existing = "";
|
|
2196
2546
|
try {
|
|
2197
|
-
existing = await
|
|
2547
|
+
existing = await readFile4(resolved.target, "utf8");
|
|
2198
2548
|
} catch (err) {
|
|
2199
2549
|
if (err.code !== "ENOENT") throw err;
|
|
2200
2550
|
}
|
|
@@ -2205,8 +2555,8 @@ function writeCredentialsTool(ctx) {
|
|
|
2205
2555
|
return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
|
|
2206
2556
|
}
|
|
2207
2557
|
const envWithCredentials = appendEnv(existing, [
|
|
2208
|
-
[APP_ID_VAR,
|
|
2209
|
-
[API_KEY_VAR,
|
|
2558
|
+
[APP_ID_VAR, appId],
|
|
2559
|
+
[API_KEY_VAR, writeKey]
|
|
2210
2560
|
]);
|
|
2211
2561
|
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2212
2562
|
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
@@ -2220,16 +2570,16 @@ function writeCredentialsTool(ctx) {
|
|
|
2220
2570
|
|
|
2221
2571
|
// src/lib/tools/searchFiles.ts
|
|
2222
2572
|
import { tool as tool7 } from "ai";
|
|
2223
|
-
import
|
|
2224
|
-
import { readdir as readdir2, readFile as
|
|
2225
|
-
import { join as
|
|
2573
|
+
import z13 from "zod";
|
|
2574
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2575
|
+
import { join as join7 } from "node:path";
|
|
2226
2576
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2227
2577
|
async function walkFiles(dir) {
|
|
2228
2578
|
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2229
2579
|
const out = [];
|
|
2230
2580
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2231
2581
|
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2232
|
-
const full =
|
|
2582
|
+
const full = join7(dir, e.name);
|
|
2233
2583
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2234
2584
|
else if (e.isFile()) out.push(full);
|
|
2235
2585
|
}
|
|
@@ -2238,9 +2588,9 @@ async function walkFiles(dir) {
|
|
|
2238
2588
|
function searchFilesTool(ctx) {
|
|
2239
2589
|
return tool7({
|
|
2240
2590
|
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:
|
|
2591
|
+
inputSchema: z13.object({
|
|
2592
|
+
query: z13.string().describe("JavaScript RegExp pattern to search for"),
|
|
2593
|
+
path: z13.string().optional().describe("Directory to search in (default: cwd)")
|
|
2244
2594
|
}),
|
|
2245
2595
|
execute: async ({ query, path = "." }) => {
|
|
2246
2596
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2262,7 +2612,7 @@ function searchFilesTool(ctx) {
|
|
|
2262
2612
|
for (const file of await walkFiles(resolved.target)) {
|
|
2263
2613
|
let content;
|
|
2264
2614
|
try {
|
|
2265
|
-
content = await
|
|
2615
|
+
content = await readFile5(file, "utf8");
|
|
2266
2616
|
} catch {
|
|
2267
2617
|
continue;
|
|
2268
2618
|
}
|
|
@@ -2284,7 +2634,7 @@ function searchFilesTool(ctx) {
|
|
|
2284
2634
|
|
|
2285
2635
|
// src/lib/tools/verifyImplementation.ts
|
|
2286
2636
|
import { tool as tool8 } from "ai";
|
|
2287
|
-
import
|
|
2637
|
+
import z14 from "zod";
|
|
2288
2638
|
|
|
2289
2639
|
// src/lib/tools/utils/runCommand.ts
|
|
2290
2640
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -2306,9 +2656,9 @@ function runCommand(command, args, cwd) {
|
|
|
2306
2656
|
}
|
|
2307
2657
|
|
|
2308
2658
|
// src/lib/tools/utils/packageManager.ts
|
|
2309
|
-
import { readFile as
|
|
2659
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2310
2660
|
import { existsSync } from "node:fs";
|
|
2311
|
-
import { join as
|
|
2661
|
+
import { join as join8 } from "node:path";
|
|
2312
2662
|
var LOCKFILES = [
|
|
2313
2663
|
["pnpm-lock.yaml", "pnpm"],
|
|
2314
2664
|
["yarn.lock", "yarn"],
|
|
@@ -2317,13 +2667,13 @@ var LOCKFILES = [
|
|
|
2317
2667
|
["package-lock.json", "npm"]
|
|
2318
2668
|
];
|
|
2319
2669
|
async function readPackageJson(cwd = process.cwd()) {
|
|
2320
|
-
return JSON.parse(await
|
|
2670
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2321
2671
|
}
|
|
2322
2672
|
function packageManagerFrom(pkg) {
|
|
2323
2673
|
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2324
2674
|
}
|
|
2325
2675
|
function packageManagerFromLockfile(cwd) {
|
|
2326
|
-
return LOCKFILES.find(([file]) => existsSync(
|
|
2676
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2327
2677
|
}
|
|
2328
2678
|
async function detectPackageManager(cwd) {
|
|
2329
2679
|
try {
|
|
@@ -2364,7 +2714,7 @@ async function runRepoVerificationCheck() {
|
|
|
2364
2714
|
function verifyImplementationTool() {
|
|
2365
2715
|
return tool8({
|
|
2366
2716
|
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2367
|
-
inputSchema:
|
|
2717
|
+
inputSchema: z14.object(),
|
|
2368
2718
|
execute: async () => {
|
|
2369
2719
|
logger.info("called verifyImplementation tool");
|
|
2370
2720
|
return runRepoVerificationCheck();
|
|
@@ -2378,7 +2728,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
2378
2728
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2379
2729
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2380
2730
|
import { dirname as dirname6 } from "node:path";
|
|
2381
|
-
import
|
|
2731
|
+
import z15 from "zod";
|
|
2382
2732
|
var DATA_DIR = ".algolia-wizard/data";
|
|
2383
2733
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
2384
2734
|
var MAX_RECORDS = 100;
|
|
@@ -2390,17 +2740,17 @@ var anthropic = createAnthropic({
|
|
|
2390
2740
|
function generateRecordTool(ctx) {
|
|
2391
2741
|
return tool9({
|
|
2392
2742
|
description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
|
|
2393
|
-
inputSchema:
|
|
2394
|
-
entityName:
|
|
2395
|
-
attributes:
|
|
2396
|
-
count:
|
|
2397
|
-
hint:
|
|
2743
|
+
inputSchema: z15.object({
|
|
2744
|
+
entityName: z15.string().describe("Name of the entity to generate records for."),
|
|
2745
|
+
attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
|
|
2746
|
+
count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2747
|
+
hint: z15.string().optional().describe("Optional context to steer realistic values.")
|
|
2398
2748
|
}),
|
|
2399
2749
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
2400
2750
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
2401
2751
|
try {
|
|
2402
|
-
const value =
|
|
2403
|
-
const recordSchema =
|
|
2752
|
+
const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
|
|
2753
|
+
const recordSchema = z15.object(
|
|
2404
2754
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
2405
2755
|
);
|
|
2406
2756
|
const generateBatch = async (batchCount) => {
|
|
@@ -2410,8 +2760,8 @@ function generateRecordTool(ctx) {
|
|
|
2410
2760
|
const { output } = await generateText({
|
|
2411
2761
|
model: anthropic(RECORD_MODEL),
|
|
2412
2762
|
output: Output.object({
|
|
2413
|
-
schema:
|
|
2414
|
-
records:
|
|
2763
|
+
schema: z15.object({
|
|
2764
|
+
records: z15.array(recordSchema).length(batchCount)
|
|
2415
2765
|
})
|
|
2416
2766
|
}),
|
|
2417
2767
|
prompt: [
|
|
@@ -2469,12 +2819,12 @@ function generateRecordTool(ctx) {
|
|
|
2469
2819
|
|
|
2470
2820
|
// src/lib/tools/notifyUser.ts
|
|
2471
2821
|
import { tool as tool10 } from "ai";
|
|
2472
|
-
import
|
|
2822
|
+
import z16 from "zod";
|
|
2473
2823
|
function notifyUserTool() {
|
|
2474
2824
|
return tool10({
|
|
2475
2825
|
description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
|
|
2476
|
-
inputSchema:
|
|
2477
|
-
message:
|
|
2826
|
+
inputSchema: z16.object({
|
|
2827
|
+
message: z16.string().describe(
|
|
2478
2828
|
"Short, plain-language description of what you are doing now."
|
|
2479
2829
|
)
|
|
2480
2830
|
}),
|
|
@@ -2654,10 +3004,10 @@ async function runAgent(req) {
|
|
|
2654
3004
|
}
|
|
2655
3005
|
|
|
2656
3006
|
// src/actions/detectLanguage.ts
|
|
2657
|
-
import
|
|
2658
|
-
var detectLanguageSchema =
|
|
2659
|
-
languages:
|
|
2660
|
-
frameworks:
|
|
3007
|
+
import z19 from "zod";
|
|
3008
|
+
var detectLanguageSchema = z19.object({
|
|
3009
|
+
languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
|
|
3010
|
+
frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
|
|
2661
3011
|
});
|
|
2662
3012
|
var detectLanguage = () => runAgent({
|
|
2663
3013
|
instructions: [
|
|
@@ -2675,31 +3025,31 @@ var detectLanguage = () => runAgent({
|
|
|
2675
3025
|
});
|
|
2676
3026
|
|
|
2677
3027
|
// src/actions/analyzeCodebase.ts
|
|
2678
|
-
import
|
|
3028
|
+
import z20 from "zod";
|
|
2679
3029
|
var READONLY_TOOLS = [
|
|
2680
3030
|
"listFiles",
|
|
2681
3031
|
"changeDirectory",
|
|
2682
3032
|
"readFile",
|
|
2683
3033
|
"searchFiles"
|
|
2684
3034
|
];
|
|
2685
|
-
var ingestionAnalysisSchema =
|
|
2686
|
-
ingestionAnalysis:
|
|
2687
|
-
|
|
2688
|
-
name:
|
|
2689
|
-
paths:
|
|
3035
|
+
var ingestionAnalysisSchema = z20.object({
|
|
3036
|
+
ingestionAnalysis: z20.array(
|
|
3037
|
+
z20.object({
|
|
3038
|
+
name: z20.string(),
|
|
3039
|
+
paths: z20.array(z20.string()),
|
|
2690
3040
|
// indexable fields the agent found for this entity
|
|
2691
|
-
attributes:
|
|
3041
|
+
attributes: z20.array(z20.string())
|
|
2692
3042
|
})
|
|
2693
3043
|
)
|
|
2694
3044
|
});
|
|
2695
|
-
var searchImplementationAnalysisSchema =
|
|
2696
|
-
searchImplementationAnalysis:
|
|
3045
|
+
var searchImplementationAnalysisSchema = z20.object({
|
|
3046
|
+
searchImplementationAnalysis: z20.string()
|
|
2697
3047
|
});
|
|
2698
|
-
var verificationSchema =
|
|
2699
|
-
verification:
|
|
3048
|
+
var verificationSchema = z20.object({
|
|
3049
|
+
verification: z20.array(z20.string())
|
|
2700
3050
|
});
|
|
2701
3051
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
2702
|
-
var analyzeCodebaseSchema =
|
|
3052
|
+
var analyzeCodebaseSchema = z20.object({
|
|
2703
3053
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
2704
3054
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
2705
3055
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -2761,7 +3111,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2761
3111
|
// package.json
|
|
2762
3112
|
var package_default = {
|
|
2763
3113
|
name: "@algolia/wizard",
|
|
2764
|
-
version: "0.
|
|
3114
|
+
version: "0.10.0",
|
|
2765
3115
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2766
3116
|
type: "module",
|
|
2767
3117
|
engines: {
|
|
@@ -2809,7 +3159,6 @@ var package_default = {
|
|
|
2809
3159
|
dependencies: {
|
|
2810
3160
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2811
3161
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2812
|
-
"@algolia/cli": "^5.11.0",
|
|
2813
3162
|
"@hono/node-server": "^2.0.10",
|
|
2814
3163
|
"@segment/analytics-node": "^3.1.0",
|
|
2815
3164
|
ai: "^6.0.190",
|
|
@@ -2823,7 +3172,6 @@ var package_default = {
|
|
|
2823
3172
|
nanoid: "^5.1.15",
|
|
2824
3173
|
pino: "^10.3.1",
|
|
2825
3174
|
react: "^19.2.7",
|
|
2826
|
-
toml: "^4.1.1",
|
|
2827
3175
|
varlock: "^1.5.1",
|
|
2828
3176
|
zod: "^4.4.3",
|
|
2829
3177
|
zustand: "^5.0.14"
|
|
@@ -2881,8 +3229,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
2881
3229
|
}
|
|
2882
3230
|
|
|
2883
3231
|
// src/actions/confirmLanguage.ts
|
|
2884
|
-
import
|
|
2885
|
-
var confirmLanguageSchema =
|
|
3232
|
+
import z22 from "zod";
|
|
3233
|
+
var confirmLanguageSchema = z22.object({
|
|
2886
3234
|
languages: detectLanguageSchema.shape.languages
|
|
2887
3235
|
});
|
|
2888
3236
|
async function confirmLanguage(ctx) {
|
|
@@ -2903,8 +3251,8 @@ async function confirmLanguage(ctx) {
|
|
|
2903
3251
|
}
|
|
2904
3252
|
|
|
2905
3253
|
// src/actions/confirmFramework.ts
|
|
2906
|
-
import
|
|
2907
|
-
var confirmFrameworkSchema =
|
|
3254
|
+
import z23 from "zod";
|
|
3255
|
+
var confirmFrameworkSchema = z23.object({
|
|
2908
3256
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
2909
3257
|
});
|
|
2910
3258
|
var CURATED_FRAMEWORKS = [
|
|
@@ -3032,8 +3380,8 @@ async function promptUser(ctx, params) {
|
|
|
3032
3380
|
}
|
|
3033
3381
|
|
|
3034
3382
|
// src/actions/confirmEntities.ts
|
|
3035
|
-
import
|
|
3036
|
-
var confirmEntitiesSchema =
|
|
3383
|
+
import z24 from "zod";
|
|
3384
|
+
var confirmEntitiesSchema = z24.object({
|
|
3037
3385
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3038
3386
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3039
3387
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3103,15 +3451,15 @@ async function confirmEntities(ctx) {
|
|
|
3103
3451
|
}
|
|
3104
3452
|
|
|
3105
3453
|
// src/actions/review.ts
|
|
3106
|
-
import { z as
|
|
3107
|
-
var reviewSchema =
|
|
3454
|
+
import { z as z25 } from "zod";
|
|
3455
|
+
var reviewSchema = z25.object({
|
|
3108
3456
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3109
3457
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3110
3458
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3111
3459
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3112
|
-
summaryPoints:
|
|
3113
|
-
reviewPrompt:
|
|
3114
|
-
nextSteps:
|
|
3460
|
+
summaryPoints: z25.array(z25.string()),
|
|
3461
|
+
reviewPrompt: z25.string(),
|
|
3462
|
+
nextSteps: z25.array(z25.string())
|
|
3115
3463
|
});
|
|
3116
3464
|
function formatCompletedSteps(steps) {
|
|
3117
3465
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3162,16 +3510,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3162
3510
|
};
|
|
3163
3511
|
|
|
3164
3512
|
// src/actions/implement.ts
|
|
3165
|
-
import
|
|
3513
|
+
import z26 from "zod";
|
|
3166
3514
|
|
|
3167
3515
|
// src/lib/worktree.ts
|
|
3168
3516
|
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3169
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as
|
|
3517
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3170
3518
|
import {
|
|
3171
3519
|
basename as basename2,
|
|
3172
3520
|
dirname as dirname7,
|
|
3173
3521
|
isAbsolute as isAbsolute2,
|
|
3174
|
-
join as
|
|
3522
|
+
join as join9,
|
|
3175
3523
|
relative as relative2,
|
|
3176
3524
|
resolve as resolve3
|
|
3177
3525
|
} from "node:path";
|
|
@@ -3205,7 +3553,7 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3205
3553
|
return out.trim().length > 0;
|
|
3206
3554
|
}
|
|
3207
3555
|
async function pruneOldWorktrees(repoRoot) {
|
|
3208
|
-
const dir =
|
|
3556
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3209
3557
|
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3210
3558
|
for (const slug of stale) {
|
|
3211
3559
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
@@ -3216,7 +3564,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3216
3564
|
"worktree",
|
|
3217
3565
|
"remove",
|
|
3218
3566
|
"--force",
|
|
3219
|
-
|
|
3567
|
+
join9(dir, slug)
|
|
3220
3568
|
]);
|
|
3221
3569
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3222
3570
|
} catch (err) {
|
|
@@ -3230,7 +3578,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3230
3578
|
async function createWorktree(repoRoot) {
|
|
3231
3579
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3232
3580
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3233
|
-
const path =
|
|
3581
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3234
3582
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3235
3583
|
await pruneOldWorktrees(repoRoot);
|
|
3236
3584
|
await mkdir6(dirname7(path), { recursive: true });
|
|
@@ -3350,8 +3698,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3350
3698
|
} catch {
|
|
3351
3699
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3352
3700
|
}
|
|
3353
|
-
const relPath =
|
|
3354
|
-
const dest =
|
|
3701
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3702
|
+
const dest = join9(worktreePath, relPath);
|
|
3355
3703
|
try {
|
|
3356
3704
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3357
3705
|
await copyFile(source, dest);
|
|
@@ -3366,11 +3714,28 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3366
3714
|
function hasEnvVar(content, name) {
|
|
3367
3715
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3368
3716
|
}
|
|
3717
|
+
async function readEnvVar(worktreePath, name) {
|
|
3718
|
+
let content;
|
|
3719
|
+
try {
|
|
3720
|
+
content = await readFile7(join9(worktreePath, ".env"), "utf8");
|
|
3721
|
+
} catch (err) {
|
|
3722
|
+
if (err.code !== "ENOENT") throw err;
|
|
3723
|
+
return void 0;
|
|
3724
|
+
}
|
|
3725
|
+
const match = new RegExp(
|
|
3726
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3727
|
+
"m"
|
|
3728
|
+
).exec(content);
|
|
3729
|
+
if (!match) return void 0;
|
|
3730
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3731
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3732
|
+
return value;
|
|
3733
|
+
}
|
|
3369
3734
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3370
|
-
const target =
|
|
3735
|
+
const target = join9(worktreePath, ".env");
|
|
3371
3736
|
let existing = "";
|
|
3372
3737
|
try {
|
|
3373
|
-
existing = await
|
|
3738
|
+
existing = await readFile7(target, "utf8");
|
|
3374
3739
|
} catch (err) {
|
|
3375
3740
|
if (err.code !== "ENOENT") throw err;
|
|
3376
3741
|
}
|
|
@@ -3438,63 +3803,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3438
3803
|
}
|
|
3439
3804
|
}
|
|
3440
3805
|
|
|
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
3806
|
// src/lib/algoliaDocs.ts
|
|
3490
3807
|
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3491
|
-
import { dirname as dirname8, join as
|
|
3808
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
3492
3809
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3493
|
-
var DOCS_SUBPATH =
|
|
3810
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
3494
3811
|
function findDocsDir() {
|
|
3495
3812
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3496
3813
|
for (; ; ) {
|
|
3497
|
-
const candidate =
|
|
3814
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
3498
3815
|
if (existsSync2(candidate)) return candidate;
|
|
3499
3816
|
const parent = dirname8(dir);
|
|
3500
3817
|
if (parent === dir) return void 0;
|
|
@@ -3517,7 +3834,7 @@ function loadAlgoliaDoc(language) {
|
|
|
3517
3834
|
);
|
|
3518
3835
|
return "";
|
|
3519
3836
|
}
|
|
3520
|
-
return readFileSync(
|
|
3837
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
3521
3838
|
}
|
|
3522
3839
|
function getNamedDoc(name, language) {
|
|
3523
3840
|
const docsDir = findDocsDir();
|
|
@@ -3525,7 +3842,7 @@ function getNamedDoc(name, language) {
|
|
|
3525
3842
|
logger.warn("docs/algolia-sdk not found");
|
|
3526
3843
|
return "";
|
|
3527
3844
|
}
|
|
3528
|
-
const file =
|
|
3845
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
3529
3846
|
if (!existsSync2(file)) {
|
|
3530
3847
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
3531
3848
|
return "";
|
|
@@ -3552,50 +3869,34 @@ function shellQuote(value) {
|
|
|
3552
3869
|
}
|
|
3553
3870
|
|
|
3554
3871
|
// 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()
|
|
3872
|
+
var implementSchema = z26.object({
|
|
3873
|
+
filesChanged: z26.array(z26.string()),
|
|
3874
|
+
summary: z26.string(),
|
|
3875
|
+
worktreePath: z26.string().optional(),
|
|
3876
|
+
ingestCommand: z26.string().optional(),
|
|
3877
|
+
ingestScriptRan: z26.boolean().optional(),
|
|
3878
|
+
ingestRecordCount: z26.number().optional(),
|
|
3879
|
+
ingestDurationMs: z26.number().optional(),
|
|
3880
|
+
ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
|
|
3881
|
+
searchEnvVars: z26.array(
|
|
3882
|
+
z26.object({
|
|
3883
|
+
name: z26.string(),
|
|
3884
|
+
value: z26.string()
|
|
3582
3885
|
})
|
|
3583
3886
|
).optional()
|
|
3584
3887
|
});
|
|
3585
|
-
var implementationOutputSchema =
|
|
3586
|
-
summary:
|
|
3587
|
-
// Ingestion only:
|
|
3588
|
-
//
|
|
3589
|
-
//
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3593
|
-
entrypoint: z24.string().optional()
|
|
3888
|
+
var implementationOutputSchema = z26.object({
|
|
3889
|
+
summary: z26.string(),
|
|
3890
|
+
// Ingestion only: a structured pair the wizard turns into an argv, never a
|
|
3891
|
+
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
3892
|
+
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
3893
|
+
runtime: z26.enum(INGEST_RUNTIMES).optional(),
|
|
3894
|
+
entrypoint: z26.string().optional()
|
|
3594
3895
|
});
|
|
3595
|
-
var verificationOutputSchema =
|
|
3596
|
-
summary:
|
|
3597
|
-
sufficient:
|
|
3598
|
-
additionalInstructions:
|
|
3896
|
+
var verificationOutputSchema = z26.object({
|
|
3897
|
+
summary: z26.string(),
|
|
3898
|
+
sufficient: z26.boolean(),
|
|
3899
|
+
additionalInstructions: z26.string().optional()
|
|
3599
3900
|
});
|
|
3600
3901
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3601
3902
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -3638,15 +3939,22 @@ function publicEnvPrefix(language) {
|
|
|
3638
3939
|
}
|
|
3639
3940
|
return "PUBLIC_";
|
|
3640
3941
|
}
|
|
3942
|
+
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
3943
|
+
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
3944
|
+
function appIdVar(language) {
|
|
3945
|
+
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
3946
|
+
}
|
|
3947
|
+
function searchKeyVar(language) {
|
|
3948
|
+
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3949
|
+
}
|
|
3641
3950
|
function searchEnvVars(language, appId, searchKey) {
|
|
3642
|
-
const prefix = publicEnvPrefix(language);
|
|
3643
3951
|
return [
|
|
3644
3952
|
{
|
|
3645
|
-
name:
|
|
3953
|
+
name: appIdVar(language),
|
|
3646
3954
|
value: appId ?? "<your-algolia-app-id>"
|
|
3647
3955
|
},
|
|
3648
3956
|
{
|
|
3649
|
-
name:
|
|
3957
|
+
name: searchKeyVar(language),
|
|
3650
3958
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3651
3959
|
}
|
|
3652
3960
|
];
|
|
@@ -3667,9 +3975,6 @@ function sourceSpecificInstructions(input) {
|
|
|
3667
3975
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
3668
3976
|
],
|
|
3669
3977
|
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
3978
|
`Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
3674
3979
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
3675
3980
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
@@ -3708,15 +4013,16 @@ function searchInstructions(input) {
|
|
|
3708
4013
|
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3709
4014
|
doc,
|
|
3710
4015
|
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3711
|
-
|
|
3712
|
-
//
|
|
3713
|
-
//
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
//
|
|
3717
|
-
// right after this step, so a renamed prefix
|
|
4016
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4017
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4018
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4019
|
+
// would be reused as if it were real.
|
|
4020
|
+
`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.`,
|
|
4021
|
+
// Not the agent's to rename: the wizard writes these exact names into
|
|
4022
|
+
// ".env" right after this step, so a renamed prefix would leave the code
|
|
3718
4023
|
// reading a var the wizard never wrote.
|
|
3719
4024
|
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4025
|
+
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3720
4026
|
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
3721
4027
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3722
4028
|
];
|
|
@@ -3858,6 +4164,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3858
4164
|
}
|
|
3859
4165
|
}
|
|
3860
4166
|
const targetIndex = selected?.selection;
|
|
4167
|
+
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
3861
4168
|
await assertGitRepoWithHead(repoRoot);
|
|
3862
4169
|
if (await isWorkingTreeDirty(repoRoot)) {
|
|
3863
4170
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
@@ -3866,17 +4173,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3866
4173
|
const confirmed2 = normalized.confirmedEntities;
|
|
3867
4174
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3868
4175
|
let appId;
|
|
3869
|
-
let searchKey;
|
|
3870
4176
|
if (useCases.includes("search")) {
|
|
3871
|
-
appId = (await
|
|
3872
|
-
try {
|
|
3873
|
-
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
3874
|
-
} catch (err) {
|
|
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
|
-
}
|
|
4177
|
+
appId = (await requireApplication()).id;
|
|
3880
4178
|
}
|
|
3881
4179
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3882
4180
|
try {
|
|
@@ -3908,17 +4206,34 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3908
4206
|
targetIndex,
|
|
3909
4207
|
language,
|
|
3910
4208
|
appId,
|
|
3911
|
-
|
|
3912
|
-
|
|
4209
|
+
// Names only: the search-only key is provisioned after verification, so
|
|
4210
|
+
// every value here is still a placeholder when the agent reads them.
|
|
4211
|
+
searchEnvVars: searchEnvVars(language, appId),
|
|
3913
4212
|
ingestDir: INGEST_DIR,
|
|
3914
4213
|
ingestionSource,
|
|
3915
4214
|
uploadFilePath,
|
|
3916
|
-
// language.frameworks already prefers the confirm-framework step output,
|
|
3917
|
-
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3918
4215
|
uiFramework: detectUiFramework(language)
|
|
3919
4216
|
};
|
|
3920
4217
|
const summaries = [];
|
|
3921
4218
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4219
|
+
let envSearchKey;
|
|
4220
|
+
let envAppIdMismatch = false;
|
|
4221
|
+
if (useCases.includes("search") && appId) {
|
|
4222
|
+
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4223
|
+
if (envAppId === appId) {
|
|
4224
|
+
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4225
|
+
} else if (envAppId) {
|
|
4226
|
+
envAppIdMismatch = true;
|
|
4227
|
+
summaries.push(
|
|
4228
|
+
`\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.`
|
|
4229
|
+
);
|
|
4230
|
+
logger.warn(
|
|
4231
|
+
{ envAppId, appId },
|
|
4232
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
3922
4237
|
let agentRuns = 0;
|
|
3923
4238
|
let ingestRuntime;
|
|
3924
4239
|
let ingestEntrypoint;
|
|
@@ -3979,7 +4294,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3979
4294
|
messages: []
|
|
3980
4295
|
}) === true;
|
|
3981
4296
|
if (runNow) {
|
|
3982
|
-
const
|
|
4297
|
+
const ingestApp = await requireApplication();
|
|
4298
|
+
const writeKey = await resolveWriteKey(targetIndex);
|
|
3983
4299
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3984
4300
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3985
4301
|
runtime: ingestRuntime,
|
|
@@ -3991,8 +4307,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3991
4307
|
ingestRuntime,
|
|
3992
4308
|
ingestEntrypoint,
|
|
3993
4309
|
{
|
|
3994
|
-
[APP_ID_VAR]:
|
|
3995
|
-
[API_KEY_VAR]:
|
|
4310
|
+
[APP_ID_VAR]: ingestApp.id,
|
|
4311
|
+
[API_KEY_VAR]: writeKey
|
|
3996
4312
|
}
|
|
3997
4313
|
);
|
|
3998
4314
|
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
@@ -4061,8 +4377,6 @@ ${run2.output}` : status;
|
|
|
4061
4377
|
);
|
|
4062
4378
|
}
|
|
4063
4379
|
await ctx.requestUserInput({
|
|
4064
|
-
// No question being asked here, just an acknowledgement — the
|
|
4065
|
-
// continue/decline hints below already say "continue".
|
|
4066
4380
|
prompt: "",
|
|
4067
4381
|
promptType: "enterToContinue",
|
|
4068
4382
|
options: [],
|
|
@@ -4111,7 +4425,29 @@ ${run2.output}` : status;
|
|
|
4111
4425
|
}
|
|
4112
4426
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4113
4427
|
}
|
|
4114
|
-
|
|
4428
|
+
let searchKey;
|
|
4429
|
+
let searchKeyError;
|
|
4430
|
+
if (appId) {
|
|
4431
|
+
try {
|
|
4432
|
+
const resolved = await resolveSearchOnlyKey(
|
|
4433
|
+
targetIndex,
|
|
4434
|
+
appId,
|
|
4435
|
+
envSearchKey
|
|
4436
|
+
);
|
|
4437
|
+
searchKey = resolved.key;
|
|
4438
|
+
summaries.push(
|
|
4439
|
+
resolved.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4440
|
+
);
|
|
4441
|
+
} catch (err) {
|
|
4442
|
+
searchKeyError = err.message;
|
|
4443
|
+
logger.warn(
|
|
4444
|
+
{ err: searchKeyError },
|
|
4445
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
finalSearchEnvVars = searchEnvVars(language, appId, searchKey);
|
|
4450
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4115
4451
|
(v) => !v.value.startsWith("<")
|
|
4116
4452
|
);
|
|
4117
4453
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4122,13 +4458,29 @@ ${run2.output}` : status;
|
|
|
4122
4458
|
if (written.length > 0) {
|
|
4123
4459
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4124
4460
|
}
|
|
4461
|
+
const stale = [];
|
|
4462
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4463
|
+
if (written.includes(v.name)) continue;
|
|
4464
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4465
|
+
if (current && current !== v.value) stale.push(v);
|
|
4466
|
+
}
|
|
4467
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4468
|
+
summaries.push(
|
|
4469
|
+
`\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.`
|
|
4470
|
+
);
|
|
4471
|
+
logger.warn(
|
|
4472
|
+
{ vars: stale.map((v) => v.name) },
|
|
4473
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4125
4476
|
}
|
|
4126
|
-
const unresolvedSearchEnvVars =
|
|
4477
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4127
4478
|
(v) => v.value.startsWith("<")
|
|
4128
4479
|
);
|
|
4129
4480
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4130
4481
|
summaries.push(
|
|
4131
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4482
|
+
`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.
|
|
4483
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4132
4484
|
);
|
|
4133
4485
|
}
|
|
4134
4486
|
} else {
|
|
@@ -4160,7 +4512,7 @@ ${run2.output}` : status;
|
|
|
4160
4512
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4161
4513
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4162
4514
|
} : {},
|
|
4163
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4515
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4164
4516
|
};
|
|
4165
4517
|
} finally {
|
|
4166
4518
|
process.chdir(repoRoot);
|
|
@@ -4203,8 +4555,8 @@ var defaultWorkflow = {
|
|
|
4203
4555
|
defineStep({
|
|
4204
4556
|
id: "select-index",
|
|
4205
4557
|
title: "Set up index",
|
|
4206
|
-
outputSchema:
|
|
4207
|
-
selection:
|
|
4558
|
+
outputSchema: z27.object({
|
|
4559
|
+
selection: z27.string()
|
|
4208
4560
|
}),
|
|
4209
4561
|
run: (ctx) => selectIndexStep(ctx)
|
|
4210
4562
|
}),
|
|
@@ -4483,7 +4835,7 @@ function parseCliArgs(argv) {
|
|
|
4483
4835
|
|
|
4484
4836
|
// src/lib/resetState.ts
|
|
4485
4837
|
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4486
|
-
import { join as
|
|
4838
|
+
import { join as join11 } from "node:path";
|
|
4487
4839
|
var KEEP = ["wizard.log"];
|
|
4488
4840
|
async function resetProjectState() {
|
|
4489
4841
|
const dir = stateDir();
|
|
@@ -4495,7 +4847,7 @@ async function resetProjectState() {
|
|
|
4495
4847
|
}
|
|
4496
4848
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4497
4849
|
await Promise.all(
|
|
4498
|
-
targets.map((name) => rm2(
|
|
4850
|
+
targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
|
|
4499
4851
|
);
|
|
4500
4852
|
return { dir, removed: targets };
|
|
4501
4853
|
}
|
|
@@ -4550,31 +4902,38 @@ ${formatStepList(workflow)}`);
|
|
|
4550
4902
|
}
|
|
4551
4903
|
async function run(workflow) {
|
|
4552
4904
|
const store = useWizard.getState();
|
|
4553
|
-
|
|
4905
|
+
const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
4906
|
+
await store.waitForStart();
|
|
4554
4907
|
let user = await getUser();
|
|
4555
4908
|
if (!user) {
|
|
4556
|
-
|
|
4557
|
-
instance.cleanup();
|
|
4909
|
+
store.beginAuth();
|
|
4558
4910
|
try {
|
|
4559
4911
|
await runAuthLogin();
|
|
4560
4912
|
} catch (err) {
|
|
4561
|
-
|
|
4913
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4914
|
+
await instance.waitUntilExit();
|
|
4562
4915
|
process.exit(1);
|
|
4563
4916
|
}
|
|
4564
|
-
|
|
4917
|
+
store.endAuth();
|
|
4565
4918
|
user = await getUser();
|
|
4566
4919
|
if (!user) {
|
|
4567
4920
|
store.setError(
|
|
4568
|
-
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
|
|
4921
|
+
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
|
|
4569
4922
|
);
|
|
4570
4923
|
await instance.waitUntilExit();
|
|
4571
4924
|
process.exit(1);
|
|
4572
4925
|
}
|
|
4573
4926
|
}
|
|
4574
4927
|
store.setUser(user);
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4928
|
+
let app;
|
|
4929
|
+
try {
|
|
4930
|
+
app = await ensureApplication();
|
|
4931
|
+
} catch (err) {
|
|
4932
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4933
|
+
await instance.waitUntilExit();
|
|
4934
|
+
process.exit(1);
|
|
4935
|
+
}
|
|
4936
|
+
runWorkflow(workflow, app.id);
|
|
4578
4937
|
}
|
|
4579
4938
|
var started = await startup();
|
|
4580
4939
|
if (typeof started === "number") {
|