@algolia/wizard 0.9.0 → 0.11.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 +1143 -582
- package/package.json +2 -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,90 @@ 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 mask(text, secret) {
|
|
21
|
+
return secret ? text.replaceAll(secret, "***") : text;
|
|
22
|
+
}
|
|
23
|
+
function lineSplitter(emit) {
|
|
24
|
+
let buffer = "";
|
|
25
|
+
return {
|
|
26
|
+
push(chunk) {
|
|
27
|
+
buffer += chunk;
|
|
28
|
+
const lines = buffer.split("\n");
|
|
29
|
+
buffer = lines.pop() ?? "";
|
|
30
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
31
|
+
},
|
|
32
|
+
flush() {
|
|
33
|
+
if (buffer) emit(buffer.replace(/\r$/, ""));
|
|
34
|
+
buffer = "";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
var wizardSink = (stream, line) => {
|
|
39
|
+
if (!line.trim()) return;
|
|
40
|
+
useWizard.getState().pushCliOutput(stream, line);
|
|
41
|
+
};
|
|
42
|
+
var stderrSink = (stream, line) => {
|
|
43
|
+
if (stream === "stdout") return;
|
|
44
|
+
wizardSink(stream, line);
|
|
45
|
+
};
|
|
46
|
+
function runAlgoliaCli(args, { onOutput, redact } = {}) {
|
|
47
|
+
const store = useWizard.getState();
|
|
48
|
+
const command = mask(args.join(" "), redact);
|
|
49
|
+
const logId = store.logStart("tool", `algolia ${command}`);
|
|
21
50
|
return new Promise((resolve4, reject) => {
|
|
22
|
-
const child = spawn(
|
|
51
|
+
const child = spawn("npx", npxArgs(args), { shell });
|
|
23
52
|
let stdout = "";
|
|
24
53
|
let stderr = "";
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
const splitters = {
|
|
55
|
+
stdout: lineSplitter((line) => onOutput?.("stdout", line)),
|
|
56
|
+
stderr: lineSplitter((line) => onOutput?.("stderr", line))
|
|
57
|
+
};
|
|
58
|
+
child.stdout.on("data", (chunk) => {
|
|
59
|
+
const text = String(chunk);
|
|
60
|
+
stdout += text;
|
|
61
|
+
splitters.stdout.push(text);
|
|
62
|
+
});
|
|
63
|
+
child.stderr.on("data", (chunk) => {
|
|
64
|
+
const text = String(chunk);
|
|
65
|
+
stderr += text;
|
|
66
|
+
splitters.stderr.push(text);
|
|
67
|
+
});
|
|
27
68
|
child.on("error", reject);
|
|
28
69
|
child.on("close", (code) => {
|
|
70
|
+
splitters.stdout.flush();
|
|
71
|
+
splitters.stderr.flush();
|
|
29
72
|
if (code === 0) {
|
|
30
73
|
resolve4(stdout);
|
|
31
74
|
} else {
|
|
32
|
-
const
|
|
75
|
+
const failed = stderr.trim();
|
|
76
|
+
let detail = "";
|
|
77
|
+
if (failed) {
|
|
78
|
+
detail = `: ${mask(failed, redact)}`;
|
|
79
|
+
} else if (stdout.trim()) {
|
|
80
|
+
detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
|
|
81
|
+
}
|
|
33
82
|
reject(
|
|
34
83
|
new Error(
|
|
35
|
-
`Algolia CLI \`${
|
|
84
|
+
`Algolia CLI \`${command}\` failed (exit ${code})${detail}`
|
|
36
85
|
)
|
|
37
86
|
);
|
|
38
87
|
}
|
|
39
88
|
});
|
|
40
|
-
})
|
|
89
|
+
}).then(
|
|
90
|
+
(out) => {
|
|
91
|
+
useWizard.getState().logEnd(logId, "success");
|
|
92
|
+
return out;
|
|
93
|
+
},
|
|
94
|
+
(err) => {
|
|
95
|
+
useWizard.getState().logEnd(logId, "error");
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
);
|
|
41
99
|
}
|
|
42
100
|
async function getUser() {
|
|
43
101
|
let raw;
|
|
@@ -52,19 +110,23 @@ async function getUser() {
|
|
|
52
110
|
return null;
|
|
53
111
|
}
|
|
54
112
|
}
|
|
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
|
-
});
|
|
113
|
+
var loginResultSchema = z.object({
|
|
114
|
+
success: z.boolean(),
|
|
115
|
+
email: z.string().optional()
|
|
116
|
+
});
|
|
117
|
+
async function runAuthLogin() {
|
|
118
|
+
const raw = await runAlgoliaCli(["auth", "login", "--non-interactive"], {
|
|
119
|
+
onOutput: stderrSink
|
|
67
120
|
});
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = loginResultSchema.safeParse(JSON.parse(raw));
|
|
124
|
+
} catch {
|
|
125
|
+
parsed = void 0;
|
|
126
|
+
}
|
|
127
|
+
if (parsed?.success && !parsed.data.success) {
|
|
128
|
+
throw new Error("Algolia sign-in did not report success.");
|
|
129
|
+
}
|
|
68
130
|
}
|
|
69
131
|
|
|
70
132
|
// src/lib/auth.ts
|
|
@@ -100,10 +162,14 @@ import { join, resolve } from "node:path";
|
|
|
100
162
|
function rootDir() {
|
|
101
163
|
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
102
164
|
}
|
|
103
|
-
|
|
165
|
+
var pinnedRoot;
|
|
166
|
+
function setProjectRoot(cwd) {
|
|
167
|
+
pinnedRoot = resolve(cwd);
|
|
168
|
+
}
|
|
169
|
+
function projectSlug(cwd = pinnedRoot ?? process.cwd()) {
|
|
104
170
|
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
105
171
|
}
|
|
106
|
-
function stateDir(cwd = process.cwd()) {
|
|
172
|
+
function stateDir(cwd = pinnedRoot ?? process.cwd()) {
|
|
107
173
|
return join(rootDir(), projectSlug(cwd));
|
|
108
174
|
}
|
|
109
175
|
|
|
@@ -171,6 +237,7 @@ function describeInputValue(value) {
|
|
|
171
237
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
238
|
}
|
|
173
239
|
var NOTICE_INTERVAL_MS = 2e3;
|
|
240
|
+
var CLI_OUTPUT_LIMIT = 200;
|
|
174
241
|
var useWizard = create((set, get) => ({
|
|
175
242
|
phase: "idle",
|
|
176
243
|
homeScreen: "home",
|
|
@@ -182,22 +249,21 @@ var useWizard = create((set, get) => ({
|
|
|
182
249
|
notices: [],
|
|
183
250
|
_noticeQueue: [],
|
|
184
251
|
_noticeTimer: null,
|
|
252
|
+
cliOutput: [],
|
|
253
|
+
targetIndex: null,
|
|
185
254
|
logs: [],
|
|
186
255
|
error: null,
|
|
187
256
|
inputReq: null,
|
|
188
257
|
_resolve: null,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
258
|
+
// `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
|
|
259
|
+
// welcome screen, so going back would gate the run a second time.
|
|
260
|
+
beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
|
|
261
|
+
endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
|
|
192
262
|
confirmStart: () => set(
|
|
193
263
|
(s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
|
|
194
264
|
),
|
|
195
|
-
// Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
|
|
196
265
|
openLearnMore: () => set({ homeScreen: "learnMore" }),
|
|
197
266
|
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
267
|
waitForStart: () => new Promise((resolve4) => {
|
|
202
268
|
if (get().phase !== "idle") {
|
|
203
269
|
resolve4();
|
|
@@ -220,15 +286,19 @@ var useWizard = create((set, get) => ({
|
|
|
220
286
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
221
287
|
setActiveStep: (index) => {
|
|
222
288
|
get()._clearNoticeQueue();
|
|
223
|
-
set({
|
|
289
|
+
set({
|
|
290
|
+
phase: "running",
|
|
291
|
+
currentStepIndex: index,
|
|
292
|
+
output: "",
|
|
293
|
+
notices: [],
|
|
294
|
+
cliOutput: []
|
|
295
|
+
});
|
|
224
296
|
},
|
|
225
297
|
setUser: (user) => set({ user }),
|
|
226
298
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
227
299
|
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.
|
|
300
|
+
// The timer stays armed through an empty drain, so the spacing covers the
|
|
301
|
+
// time since the last render even across bursts.
|
|
232
302
|
pushNotice: (notice) => {
|
|
233
303
|
const { notices, _noticeQueue, _noticeTimer } = get();
|
|
234
304
|
if (_noticeTimer === null) {
|
|
@@ -261,6 +331,13 @@ var useWizard = create((set, get) => ({
|
|
|
261
331
|
get()._clearNoticeQueue();
|
|
262
332
|
set({ notices: [] });
|
|
263
333
|
},
|
|
334
|
+
pushCliOutput: (stream, text) => set((s) => ({
|
|
335
|
+
cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
|
|
336
|
+
-CLI_OUTPUT_LIMIT
|
|
337
|
+
)
|
|
338
|
+
})),
|
|
339
|
+
clearCliOutput: () => set({ cliOutput: [] }),
|
|
340
|
+
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
264
341
|
logStart: (kind, name, input) => {
|
|
265
342
|
const id = nanoid();
|
|
266
343
|
set((s) => ({
|
|
@@ -283,9 +360,6 @@ var useWizard = create((set, get) => ({
|
|
|
283
360
|
_resolve: resolve4
|
|
284
361
|
});
|
|
285
362
|
}),
|
|
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
363
|
submitInput: async (value) => {
|
|
290
364
|
await markInteraction();
|
|
291
365
|
get()._resolve?.(value);
|
|
@@ -305,6 +379,8 @@ var useWizard = create((set, get) => ({
|
|
|
305
379
|
currentStepIndex: 0,
|
|
306
380
|
output: "",
|
|
307
381
|
notices: [],
|
|
382
|
+
cliOutput: [],
|
|
383
|
+
targetIndex: null,
|
|
308
384
|
logs: [],
|
|
309
385
|
error: null,
|
|
310
386
|
inputReq: null,
|
|
@@ -313,16 +389,100 @@ var useWizard = create((set, get) => ({
|
|
|
313
389
|
}
|
|
314
390
|
}));
|
|
315
391
|
|
|
392
|
+
// src/ui/CliOutput.tsx
|
|
393
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
394
|
+
|
|
395
|
+
// src/ui/theme.ts
|
|
396
|
+
var MARKER = {
|
|
397
|
+
pending: "\u25CB",
|
|
398
|
+
running: "\u25D0",
|
|
399
|
+
done: "\u2713",
|
|
400
|
+
error: "\u2716"
|
|
401
|
+
};
|
|
402
|
+
var BRAND = "#003DFF";
|
|
403
|
+
var SECONDARY = "#5468FF";
|
|
404
|
+
var DANGER = "#F86E7E";
|
|
405
|
+
var COLORS = {
|
|
406
|
+
brand: BRAND,
|
|
407
|
+
primary: "#E6EDF3",
|
|
408
|
+
secondary: SECONDARY,
|
|
409
|
+
strong: "#FFFFFF",
|
|
410
|
+
muted: "#8B949E",
|
|
411
|
+
dim: "#484F58",
|
|
412
|
+
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
413
|
+
badge: "#E3B341",
|
|
414
|
+
danger: DANGER,
|
|
415
|
+
success: "#4ADE80",
|
|
416
|
+
bg: {
|
|
417
|
+
main: "#0B0E14",
|
|
418
|
+
sidebar: "#14171E"
|
|
419
|
+
},
|
|
420
|
+
border: "#30363D",
|
|
421
|
+
accent: "#76A0FF",
|
|
422
|
+
status: {
|
|
423
|
+
pending: "gray",
|
|
424
|
+
running: "#76A0FF",
|
|
425
|
+
done: "#4ADE80",
|
|
426
|
+
error: DANGER
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/ui/CliOutput.tsx
|
|
431
|
+
import { jsxs } from "react/jsx-runtime";
|
|
432
|
+
var CLI_MARKER = "\u203A";
|
|
433
|
+
var RESERVED_ROWS = 16;
|
|
434
|
+
var MAX_ROWS = 12;
|
|
435
|
+
var PANEL_TEXT_WIDTH = 45;
|
|
436
|
+
var URL_PATTERN = /https?:\/\//;
|
|
437
|
+
function rowCost(text) {
|
|
438
|
+
return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
|
|
439
|
+
}
|
|
440
|
+
function CliOutput() {
|
|
441
|
+
const cliOutput = useWizard((s) => s.cliOutput);
|
|
442
|
+
const { rows } = useWindowSize();
|
|
443
|
+
if (!cliOutput.length) return null;
|
|
444
|
+
const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
|
|
445
|
+
const visible = [];
|
|
446
|
+
let usedRows = 0;
|
|
447
|
+
for (let i = cliOutput.length - 1; i >= 0; i--) {
|
|
448
|
+
const cost = rowCost(cliOutput[i].text);
|
|
449
|
+
if (usedRows + cost > rowBudget && visible.length > 0) break;
|
|
450
|
+
visible.unshift(cliOutput[i]);
|
|
451
|
+
usedRows += cost;
|
|
452
|
+
}
|
|
453
|
+
const hidden = cliOutput.length - visible.length;
|
|
454
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
455
|
+
hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
|
|
456
|
+
"\u2191 ",
|
|
457
|
+
hidden,
|
|
458
|
+
" earlier line(s)"
|
|
459
|
+
] }),
|
|
460
|
+
visible.map((line) => /* @__PURE__ */ jsxs(
|
|
461
|
+
Text,
|
|
462
|
+
{
|
|
463
|
+
color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
|
|
464
|
+
wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
|
|
465
|
+
children: [
|
|
466
|
+
CLI_MARKER,
|
|
467
|
+
" ",
|
|
468
|
+
line.text
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
line.id
|
|
472
|
+
))
|
|
473
|
+
] });
|
|
474
|
+
}
|
|
475
|
+
|
|
316
476
|
// src/ui/Notices.tsx
|
|
317
|
-
import { Box as
|
|
477
|
+
import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
|
|
318
478
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
319
479
|
|
|
320
480
|
// src/ui/Table.tsx
|
|
321
|
-
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
481
|
+
import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
|
|
322
482
|
import { useEffect, useRef, useState } from "react";
|
|
323
483
|
import { jsx } from "react/jsx-runtime";
|
|
324
484
|
function Table({ columns, rows }) {
|
|
325
|
-
const { columns: termCols } =
|
|
485
|
+
const { columns: termCols } = useWindowSize2();
|
|
326
486
|
const ref = useRef(null);
|
|
327
487
|
const [width, setWidth] = useState(0);
|
|
328
488
|
useEffect(() => {
|
|
@@ -330,7 +490,7 @@ function Table({ columns, rows }) {
|
|
|
330
490
|
}, [termCols, columns, rows]);
|
|
331
491
|
if (rows.length === 0) return null;
|
|
332
492
|
const lines = formatTable(columns, rows, width || void 0);
|
|
333
|
-
return /* @__PURE__ */ jsx(
|
|
493
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
334
494
|
}
|
|
335
495
|
function formatTable(columns, rows, width) {
|
|
336
496
|
const natural = columns.map(
|
|
@@ -370,48 +530,13 @@ function resize(widths, budget) {
|
|
|
370
530
|
}
|
|
371
531
|
var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
|
|
372
532
|
|
|
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
533
|
// src/ui/Notices.tsx
|
|
409
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
534
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
410
535
|
var AGENT_MARKER = "\u2726";
|
|
411
|
-
var
|
|
412
|
-
var
|
|
536
|
+
var RESERVED_ROWS2 = 14;
|
|
537
|
+
var PANEL_TEXT_WIDTH2 = 45;
|
|
413
538
|
function messageLineCount(text) {
|
|
414
|
-
return Math.max(1, Math.ceil(text.length /
|
|
539
|
+
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
|
|
415
540
|
}
|
|
416
541
|
function noticeLineCount(notice) {
|
|
417
542
|
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
@@ -422,7 +547,7 @@ function noticeLineCount(notice) {
|
|
|
422
547
|
return messageLines + tableLines;
|
|
423
548
|
}
|
|
424
549
|
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
-
const budget = Math.max(windowRows -
|
|
550
|
+
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
426
551
|
let used = 0;
|
|
427
552
|
let count = 0;
|
|
428
553
|
for (let i = notices.length - 1; i >= 0; i--) {
|
|
@@ -455,7 +580,7 @@ function parseHex(hex) {
|
|
|
455
580
|
}
|
|
456
581
|
function Notices() {
|
|
457
582
|
const notices = useWizard((s) => s.notices);
|
|
458
|
-
const { rows: windowRows } =
|
|
583
|
+
const { rows: windowRows } = useWindowSize3();
|
|
459
584
|
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
585
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
586
|
useEffect2(() => {
|
|
@@ -472,14 +597,14 @@ function Notices() {
|
|
|
472
597
|
}, []);
|
|
473
598
|
if (!visible.length) return null;
|
|
474
599
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
-
return /* @__PURE__ */ jsx2(
|
|
600
|
+
return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
601
|
const isLatest = i === visible.length - 1;
|
|
477
|
-
return /* @__PURE__ */
|
|
602
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
478
603
|
notice.messages?.map((m, j) => {
|
|
479
604
|
const line = typeof m === "string" ? { text: m } : m;
|
|
480
605
|
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
-
return /* @__PURE__ */
|
|
482
|
-
|
|
606
|
+
return /* @__PURE__ */ jsxs2(
|
|
607
|
+
Text3,
|
|
483
608
|
{
|
|
484
609
|
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
610
|
bold: line.bold,
|
|
@@ -497,41 +622,41 @@ function Notices() {
|
|
|
497
622
|
}
|
|
498
623
|
|
|
499
624
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
625
|
+
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
501
626
|
import TextInput from "ink-text-input";
|
|
502
627
|
import { useState as useState5 } from "react";
|
|
503
628
|
|
|
504
629
|
// src/ui/NextAction.tsx
|
|
505
|
-
import { Box as
|
|
506
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
630
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
631
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
507
632
|
function NextAction({
|
|
508
633
|
action,
|
|
509
634
|
keyHint,
|
|
510
635
|
hierarchy = "primary"
|
|
511
636
|
}) {
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx3(
|
|
514
|
-
hierarchy === "secondary" && /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */ jsx3(
|
|
516
|
-
/* @__PURE__ */ jsx3(
|
|
637
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
638
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
639
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
640
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
641
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
517
642
|
] }),
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */ jsx3(
|
|
520
|
-
/* @__PURE__ */ jsx3(
|
|
521
|
-
/* @__PURE__ */ jsx3(
|
|
522
|
-
/* @__PURE__ */ jsx3(
|
|
643
|
+
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
644
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
|
|
645
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
|
|
646
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
|
|
647
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
|
|
523
648
|
] })
|
|
524
649
|
] });
|
|
525
650
|
}
|
|
526
651
|
|
|
527
652
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
653
|
+
import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
|
|
529
654
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
530
655
|
|
|
531
656
|
// src/ui/ScrollView.tsx
|
|
532
|
-
import { Box as
|
|
657
|
+
import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
533
658
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
534
|
-
import { jsxs as
|
|
659
|
+
import { jsxs as jsxs4 } from "react/jsx-runtime";
|
|
535
660
|
var INDICATOR_ROWS = 2;
|
|
536
661
|
function fittedWidth(node, columns) {
|
|
537
662
|
let left = 0;
|
|
@@ -546,7 +671,7 @@ function useScrollWindow({
|
|
|
546
671
|
followBottom = false
|
|
547
672
|
}) {
|
|
548
673
|
const viewportRef = useRef2(null);
|
|
549
|
-
const { columns } =
|
|
674
|
+
const { columns } = useWindowSize4();
|
|
550
675
|
const [size, setSize] = useState3(
|
|
551
676
|
null
|
|
552
677
|
);
|
|
@@ -601,14 +726,14 @@ function useScrollWindow({
|
|
|
601
726
|
};
|
|
602
727
|
}
|
|
603
728
|
function ScrollView({ scroll, children }) {
|
|
604
|
-
return /* @__PURE__ */
|
|
605
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */
|
|
729
|
+
return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
730
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
606
731
|
"\u2191 ",
|
|
607
732
|
scroll.hiddenAbove,
|
|
608
733
|
" more"
|
|
609
734
|
] }),
|
|
610
735
|
children,
|
|
611
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */
|
|
736
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
612
737
|
"\u2193 ",
|
|
613
738
|
scroll.hiddenBelow,
|
|
614
739
|
" more"
|
|
@@ -617,7 +742,7 @@ function ScrollView({ scroll, children }) {
|
|
|
617
742
|
}
|
|
618
743
|
|
|
619
744
|
// src/ui/SelectPrompt.tsx
|
|
620
|
-
import { jsx as jsx4, jsxs as
|
|
745
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
621
746
|
var CANCEL = "cancel";
|
|
622
747
|
var ARROW_WIDTH = 4;
|
|
623
748
|
var COLUMN_GAP = 2;
|
|
@@ -648,7 +773,7 @@ function SelectPrompt({
|
|
|
648
773
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
649
774
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
650
775
|
const containerRef = useRef3(null);
|
|
651
|
-
const { columns } =
|
|
776
|
+
const { columns } = useWindowSize5();
|
|
652
777
|
const [width, setWidth] = useState4(columns);
|
|
653
778
|
useLayoutEffect2(() => {
|
|
654
779
|
if (!containerRef.current) return;
|
|
@@ -702,14 +827,14 @@ function SelectPrompt({
|
|
|
702
827
|
}
|
|
703
828
|
}
|
|
704
829
|
});
|
|
705
|
-
return /* @__PURE__ */ jsx4(
|
|
706
|
-
/* @__PURE__ */
|
|
707
|
-
error && /* @__PURE__ */ jsx4(
|
|
708
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx4(
|
|
830
|
+
return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
|
|
831
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
832
|
+
error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
|
|
833
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
709
834
|
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
710
|
-
/* @__PURE__ */
|
|
711
|
-
question && /* @__PURE__ */ jsx4(
|
|
712
|
-
helpText && /* @__PURE__ */ jsx4(
|
|
835
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
836
|
+
question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
|
|
837
|
+
helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
|
|
713
838
|
] })
|
|
714
839
|
] }),
|
|
715
840
|
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
@@ -719,39 +844,39 @@ function SelectPrompt({
|
|
|
719
844
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
720
845
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
721
846
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
722
|
-
const label = /* @__PURE__ */
|
|
847
|
+
const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
|
|
723
848
|
highlighted ? "\u276F " : " ",
|
|
724
849
|
bullet,
|
|
725
850
|
option
|
|
726
851
|
] });
|
|
727
852
|
const isText = sec?.kind === "text";
|
|
728
|
-
return /* @__PURE__ */
|
|
729
|
-
|
|
853
|
+
return /* @__PURE__ */ jsxs5(
|
|
854
|
+
Box6,
|
|
730
855
|
{
|
|
731
856
|
width: isText ? "100%" : barWidth,
|
|
732
857
|
paddingX: 1,
|
|
733
858
|
paddingY: 1,
|
|
734
859
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
735
860
|
children: [
|
|
736
|
-
/* @__PURE__ */ jsx4(
|
|
737
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
738
|
-
|
|
861
|
+
/* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
862
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
863
|
+
Text6,
|
|
739
864
|
{
|
|
740
865
|
wrap: "truncate",
|
|
741
866
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
742
867
|
children: sec.value
|
|
743
868
|
}
|
|
744
869
|
) }),
|
|
745
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
870
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
746
871
|
]
|
|
747
872
|
},
|
|
748
873
|
`row-${i}`
|
|
749
874
|
);
|
|
750
875
|
}) }),
|
|
751
|
-
/* @__PURE__ */ jsx4(
|
|
876
|
+
/* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
752
877
|
i > 0 ? " " : "",
|
|
753
|
-
/* @__PURE__ */ jsx4(
|
|
754
|
-
/* @__PURE__ */
|
|
878
|
+
/* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
|
|
879
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
755
880
|
" ",
|
|
756
881
|
label
|
|
757
882
|
] })
|
|
@@ -760,7 +885,7 @@ function SelectPrompt({
|
|
|
760
885
|
}
|
|
761
886
|
|
|
762
887
|
// src/ui/PromptInput.tsx
|
|
763
|
-
import { jsx as jsx5, jsxs as
|
|
888
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
764
889
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
765
890
|
function EnterToContinuePrompt({
|
|
766
891
|
question,
|
|
@@ -771,10 +896,10 @@ function EnterToContinuePrompt({
|
|
|
771
896
|
if (key.return) onDecide(true);
|
|
772
897
|
else if (key.escape) onDecide(false);
|
|
773
898
|
});
|
|
774
|
-
return /* @__PURE__ */
|
|
775
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
776
|
-
question && /* @__PURE__ */ jsx5(
|
|
777
|
-
/* @__PURE__ */
|
|
899
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
|
|
900
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
901
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
|
|
902
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
778
903
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
779
904
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
780
905
|
] })
|
|
@@ -784,11 +909,11 @@ function PromptInput() {
|
|
|
784
909
|
const { phase, inputReq, submitInput } = useWizard();
|
|
785
910
|
const [draft, setDraft] = useState5("");
|
|
786
911
|
if (phase === "done" || phase === "error") {
|
|
787
|
-
return /* @__PURE__ */ jsx5(
|
|
912
|
+
return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
788
913
|
}
|
|
789
914
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
790
915
|
if (inputReq.promptType === "multipleChoice") {
|
|
791
|
-
return /* @__PURE__ */ jsx5(
|
|
916
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
792
917
|
SelectPrompt,
|
|
793
918
|
{
|
|
794
919
|
question: inputReq.prompt,
|
|
@@ -805,7 +930,7 @@ function PromptInput() {
|
|
|
805
930
|
) });
|
|
806
931
|
}
|
|
807
932
|
if (inputReq.promptType === "multiSelect") {
|
|
808
|
-
return /* @__PURE__ */ jsx5(
|
|
933
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
809
934
|
SelectPrompt,
|
|
810
935
|
{
|
|
811
936
|
multi: true,
|
|
@@ -820,7 +945,7 @@ function PromptInput() {
|
|
|
820
945
|
) });
|
|
821
946
|
}
|
|
822
947
|
if (inputReq.promptType === "notice") {
|
|
823
|
-
return /* @__PURE__ */ jsx5(
|
|
948
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
824
949
|
SelectPrompt,
|
|
825
950
|
{
|
|
826
951
|
question: inputReq.prompt,
|
|
@@ -842,7 +967,7 @@ function PromptInput() {
|
|
|
842
967
|
}
|
|
843
968
|
if (inputReq.promptType === "acceptReject") {
|
|
844
969
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
845
|
-
return /* @__PURE__ */ jsx5(
|
|
970
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
846
971
|
SelectPrompt,
|
|
847
972
|
{
|
|
848
973
|
question: inputReq.prompt,
|
|
@@ -853,11 +978,11 @@ function PromptInput() {
|
|
|
853
978
|
}
|
|
854
979
|
) });
|
|
855
980
|
}
|
|
856
|
-
return /* @__PURE__ */
|
|
857
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
858
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
859
|
-
/* @__PURE__ */
|
|
860
|
-
/* @__PURE__ */
|
|
981
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
982
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
|
|
983
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
984
|
+
/* @__PURE__ */ jsxs6(Box7, { children: [
|
|
985
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
|
|
861
986
|
inputReq.prompt,
|
|
862
987
|
" "
|
|
863
988
|
] }),
|
|
@@ -879,7 +1004,7 @@ function PromptInput() {
|
|
|
879
1004
|
// src/ui/Welcome.tsx
|
|
880
1005
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
881
1006
|
import { fileURLToPath } from "node:url";
|
|
882
|
-
import { Box as
|
|
1007
|
+
import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
|
|
883
1008
|
|
|
884
1009
|
// src/ui/copy/welcome.ts
|
|
885
1010
|
var sidebarItems = [
|
|
@@ -907,27 +1032,27 @@ var sidebarItems = [
|
|
|
907
1032
|
|
|
908
1033
|
// src/ui/Welcome.tsx
|
|
909
1034
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
910
|
-
import { jsx as jsx6, jsxs as
|
|
1035
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
911
1036
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
912
1037
|
function SidebarItem({
|
|
913
1038
|
title,
|
|
914
1039
|
description
|
|
915
1040
|
}) {
|
|
916
|
-
return /* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */ jsx6(
|
|
919
|
-
/* @__PURE__ */ jsx6(
|
|
1041
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
|
|
1043
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
|
|
1044
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
|
|
920
1045
|
] }),
|
|
921
|
-
/* @__PURE__ */
|
|
1046
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
|
|
922
1047
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
923
|
-
/* @__PURE__ */ jsx6(
|
|
1048
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
|
|
924
1049
|
] })
|
|
925
1050
|
] });
|
|
926
1051
|
}
|
|
927
1052
|
function Welcome() {
|
|
928
1053
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
929
1054
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
930
|
-
const { rows } =
|
|
1055
|
+
const { rows } = useWindowSize6();
|
|
931
1056
|
useInput3((input, key) => {
|
|
932
1057
|
if (key.return) confirmStart();
|
|
933
1058
|
else if (input === "i") openLearnMore();
|
|
@@ -946,15 +1071,15 @@ function Welcome() {
|
|
|
946
1071
|
if (rows < 30) {
|
|
947
1072
|
layout = scales["small"];
|
|
948
1073
|
}
|
|
949
|
-
return /* @__PURE__ */
|
|
1074
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
950
1075
|
/* @__PURE__ */ jsx6(
|
|
951
|
-
|
|
1076
|
+
Box8,
|
|
952
1077
|
{
|
|
953
1078
|
paddingY: layout.main.padding.y,
|
|
954
1079
|
paddingX: layout.main.padding.x,
|
|
955
1080
|
flexDirection: "column",
|
|
956
1081
|
justifyContent: "center",
|
|
957
|
-
children: /* @__PURE__ */
|
|
1082
|
+
children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
|
|
958
1083
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
959
1084
|
Image,
|
|
960
1085
|
{
|
|
@@ -966,16 +1091,16 @@ function Welcome() {
|
|
|
966
1091
|
protocol: "halfBlock"
|
|
967
1092
|
}
|
|
968
1093
|
) }),
|
|
969
|
-
/* @__PURE__ */ jsx6(
|
|
970
|
-
/* @__PURE__ */
|
|
1094
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1095
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
971
1096
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
972
1097
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
973
1098
|
] })
|
|
974
1099
|
] })
|
|
975
1100
|
}
|
|
976
1101
|
),
|
|
977
|
-
/* @__PURE__ */
|
|
978
|
-
|
|
1102
|
+
/* @__PURE__ */ jsxs7(
|
|
1103
|
+
Box8,
|
|
979
1104
|
{
|
|
980
1105
|
backgroundColor: COLORS.bg.sidebar,
|
|
981
1106
|
width: 40,
|
|
@@ -985,7 +1110,7 @@ function Welcome() {
|
|
|
985
1110
|
flexDirection: "column",
|
|
986
1111
|
justifyContent: "center",
|
|
987
1112
|
children: [
|
|
988
|
-
/* @__PURE__ */ jsx6(
|
|
1113
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
989
1114
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
990
1115
|
]
|
|
991
1116
|
}
|
|
@@ -995,7 +1120,7 @@ function Welcome() {
|
|
|
995
1120
|
|
|
996
1121
|
// src/ui/LearnMore.tsx
|
|
997
1122
|
import { Fragment as Fragment2 } from "react";
|
|
998
|
-
import { Box as
|
|
1123
|
+
import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
|
|
999
1124
|
|
|
1000
1125
|
// src/ui/copy/learn-more.ts
|
|
1001
1126
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1032,7 +1157,7 @@ var policyLinks = [
|
|
|
1032
1157
|
];
|
|
1033
1158
|
|
|
1034
1159
|
// src/ui/LearnMore.tsx
|
|
1035
|
-
import { jsx as jsx7, jsxs as
|
|
1160
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1036
1161
|
var TAG_COLORS = {
|
|
1037
1162
|
READ: COLORS.success,
|
|
1038
1163
|
WRITE: COLORS.badge,
|
|
@@ -1048,25 +1173,25 @@ function NeverLine({
|
|
|
1048
1173
|
}) {
|
|
1049
1174
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1050
1175
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1051
|
-
return /* @__PURE__ */
|
|
1052
|
-
/* @__PURE__ */ jsx7(
|
|
1176
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1177
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
|
|
1053
1178
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1054
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1179
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1055
1180
|
" ".repeat(rightPad),
|
|
1056
|
-
/* @__PURE__ */ jsx7(
|
|
1181
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
|
|
1057
1182
|
] });
|
|
1058
1183
|
}
|
|
1059
1184
|
function LearnMore() {
|
|
1060
1185
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1061
1186
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1062
|
-
const { columns } =
|
|
1187
|
+
const { columns } = useWindowSize7();
|
|
1063
1188
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1064
1189
|
useInput4((_input, key) => {
|
|
1065
1190
|
if (key.escape) backToHome();
|
|
1066
1191
|
else if (key.return) confirmStart();
|
|
1067
1192
|
});
|
|
1068
|
-
return /* @__PURE__ */
|
|
1069
|
-
|
|
1193
|
+
return /* @__PURE__ */ jsxs8(
|
|
1194
|
+
Box9,
|
|
1070
1195
|
{
|
|
1071
1196
|
flexDirection: "column",
|
|
1072
1197
|
paddingX: PADDING_X,
|
|
@@ -1074,20 +1199,20 @@ function LearnMore() {
|
|
|
1074
1199
|
width: "100%",
|
|
1075
1200
|
gap: 1,
|
|
1076
1201
|
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(
|
|
1202
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1203
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
|
|
1204
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
1205
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1206
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1207
|
+
/* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1208
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1209
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1210
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1086
1211
|
] }) })
|
|
1087
1212
|
] })
|
|
1088
1213
|
] }, item.tag)) }),
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
/* @__PURE__ */ jsx7(
|
|
1214
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1215
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1091
1216
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1092
1217
|
/* @__PURE__ */ jsx7(
|
|
1093
1218
|
NeverLine,
|
|
@@ -1096,7 +1221,7 @@ function LearnMore() {
|
|
|
1096
1221
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1097
1222
|
}
|
|
1098
1223
|
),
|
|
1099
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1224
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1100
1225
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1101
1226
|
/* @__PURE__ */ jsx7(
|
|
1102
1227
|
NeverLine,
|
|
@@ -1111,23 +1236,23 @@ function LearnMore() {
|
|
|
1111
1236
|
)
|
|
1112
1237
|
] }, item)),
|
|
1113
1238
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1114
|
-
/* @__PURE__ */ jsx7(
|
|
1239
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1115
1240
|
] }),
|
|
1116
|
-
/* @__PURE__ */ jsx7(
|
|
1117
|
-
/* @__PURE__ */ jsx7(
|
|
1118
|
-
/* @__PURE__ */ jsx7(
|
|
1241
|
+
/* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1242
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1243
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
|
|
1119
1244
|
] }, link.label)) }),
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */ jsx7(
|
|
1123
|
-
/* @__PURE__ */ jsx7(
|
|
1124
|
-
/* @__PURE__ */ jsx7(
|
|
1245
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1246
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1247
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1248
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
|
|
1249
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
|
|
1125
1250
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */ jsx7(
|
|
1128
|
-
/* @__PURE__ */ jsx7(
|
|
1129
|
-
/* @__PURE__ */ jsx7(
|
|
1130
|
-
/* @__PURE__ */ jsx7(
|
|
1251
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1252
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1253
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
|
|
1254
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
|
|
1255
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1131
1256
|
] })
|
|
1132
1257
|
] })
|
|
1133
1258
|
]
|
|
@@ -1136,10 +1261,10 @@ function LearnMore() {
|
|
|
1136
1261
|
}
|
|
1137
1262
|
|
|
1138
1263
|
// src/ui/Sidebar.tsx
|
|
1139
|
-
import { Box as
|
|
1264
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1140
1265
|
|
|
1141
1266
|
// src/ui/Steps.tsx
|
|
1142
|
-
import { Box as
|
|
1267
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1143
1268
|
import Spinner from "ink-spinner";
|
|
1144
1269
|
|
|
1145
1270
|
// src/core/persistence.ts
|
|
@@ -1168,11 +1293,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1168
1293
|
}
|
|
1169
1294
|
|
|
1170
1295
|
// src/ui/Steps.tsx
|
|
1171
|
-
import { jsx as jsx8, jsxs as
|
|
1296
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1172
1297
|
function Steps() {
|
|
1173
1298
|
const { steps } = useWizard();
|
|
1174
1299
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1175
|
-
return /* @__PURE__ */ jsx8(
|
|
1300
|
+
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
1301
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1177
1302
|
" ",
|
|
1178
1303
|
s.title
|
|
@@ -1182,7 +1307,7 @@ function CurrentStep() {
|
|
|
1182
1307
|
const { steps } = useWizard();
|
|
1183
1308
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1184
1309
|
if (!currentStep) return null;
|
|
1185
|
-
return /* @__PURE__ */
|
|
1310
|
+
return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
|
|
1186
1311
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1187
1312
|
" ",
|
|
1188
1313
|
` ${currentStep.title}`
|
|
@@ -1190,19 +1315,19 @@ function CurrentStep() {
|
|
|
1190
1315
|
}
|
|
1191
1316
|
|
|
1192
1317
|
// src/ui/Progress.tsx
|
|
1193
|
-
import { Box as
|
|
1194
|
-
import { jsx as jsx9, jsxs as
|
|
1318
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1319
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1195
1320
|
function Progress() {
|
|
1196
1321
|
const { steps, currentStepIndex } = useWizard();
|
|
1197
1322
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1198
1323
|
if (visibleSteps.length === 0) return null;
|
|
1199
1324
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1200
1325
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1201
|
-
return /* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */ jsx9(
|
|
1203
|
-
/* @__PURE__ */ jsx9(
|
|
1204
|
-
/* @__PURE__ */ jsx9(
|
|
1205
|
-
/* @__PURE__ */ jsx9(
|
|
1326
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1327
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
|
|
1328
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
|
|
1329
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
|
|
1330
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
|
|
1206
1331
|
] });
|
|
1207
1332
|
}
|
|
1208
1333
|
|
|
@@ -1213,10 +1338,10 @@ var sidebarCommands = [
|
|
|
1213
1338
|
];
|
|
1214
1339
|
|
|
1215
1340
|
// src/ui/Sidebar.tsx
|
|
1216
|
-
import { jsx as jsx10, jsxs as
|
|
1341
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1217
1342
|
function Sidebar() {
|
|
1218
|
-
return /* @__PURE__ */
|
|
1219
|
-
|
|
1343
|
+
return /* @__PURE__ */ jsxs11(
|
|
1344
|
+
Box12,
|
|
1220
1345
|
{
|
|
1221
1346
|
backgroundColor: "#14171E",
|
|
1222
1347
|
width: 30,
|
|
@@ -1225,16 +1350,16 @@ function Sidebar() {
|
|
|
1225
1350
|
flexDirection: "column",
|
|
1226
1351
|
justifyContent: "space-between",
|
|
1227
1352
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */ jsx10(
|
|
1353
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1354
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1230
1355
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1231
1356
|
] }),
|
|
1232
|
-
/* @__PURE__ */
|
|
1357
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1233
1358
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1234
|
-
/* @__PURE__ */ jsx10(
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */ jsx10(
|
|
1237
|
-
/* @__PURE__ */ jsx10(
|
|
1359
|
+
/* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1360
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1361
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1362
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
|
|
1238
1363
|
] });
|
|
1239
1364
|
}) })
|
|
1240
1365
|
] })
|
|
@@ -1244,12 +1369,12 @@ function Sidebar() {
|
|
|
1244
1369
|
}
|
|
1245
1370
|
|
|
1246
1371
|
// src/ui/Ribbon.tsx
|
|
1247
|
-
import { Box as
|
|
1248
|
-
import { jsx as jsx11, jsxs as
|
|
1372
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1373
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1249
1374
|
function Ribbon() {
|
|
1250
1375
|
const firstCommand = sidebarCommands[0];
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
|
|
1376
|
+
return /* @__PURE__ */ jsxs12(
|
|
1377
|
+
Box13,
|
|
1253
1378
|
{
|
|
1254
1379
|
backgroundColor: "#14171E",
|
|
1255
1380
|
flexDirection: "row",
|
|
@@ -1259,9 +1384,9 @@ function Ribbon() {
|
|
|
1259
1384
|
children: [
|
|
1260
1385
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1261
1386
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */ jsx11(
|
|
1264
|
-
/* @__PURE__ */ jsx11(
|
|
1387
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1388
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1389
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
|
|
1265
1390
|
] })
|
|
1266
1391
|
]
|
|
1267
1392
|
}
|
|
@@ -1272,8 +1397,8 @@ function Ribbon() {
|
|
|
1272
1397
|
import { useState as useState6 } from "react";
|
|
1273
1398
|
|
|
1274
1399
|
// src/ui/Logs.tsx
|
|
1275
|
-
import { Box as
|
|
1276
|
-
import { jsx as jsx12, jsxs as
|
|
1400
|
+
import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
|
|
1401
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1277
1402
|
var KIND_COLOR = {
|
|
1278
1403
|
tool: COLORS.primary,
|
|
1279
1404
|
prompt: COLORS.badge
|
|
@@ -1309,8 +1434,8 @@ function Logs() {
|
|
|
1309
1434
|
else if (key.downArrow) scroll.scrollBy(1);
|
|
1310
1435
|
});
|
|
1311
1436
|
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1312
|
-
return /* @__PURE__ */
|
|
1313
|
-
logs.length === 0 && /* @__PURE__ */ jsx12(
|
|
1437
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1438
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
|
|
1314
1439
|
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1315
1440
|
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1316
1441
|
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
@@ -1321,14 +1446,14 @@ function Logs() {
|
|
|
1321
1446
|
const name = truncate2(entry.name, budget);
|
|
1322
1447
|
budget -= name.length;
|
|
1323
1448
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1324
|
-
return /* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */ jsx12(
|
|
1326
|
-
/* @__PURE__ */ jsx12(
|
|
1327
|
-
preview && /* @__PURE__ */ jsx12(
|
|
1328
|
-
durationText && /* @__PURE__ */ jsx12(
|
|
1449
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1450
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
|
|
1451
|
+
/* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1452
|
+
preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1453
|
+
durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
|
|
1329
1454
|
] }, entry.id);
|
|
1330
1455
|
}) }),
|
|
1331
|
-
/* @__PURE__ */ jsx12(
|
|
1456
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1332
1457
|
] });
|
|
1333
1458
|
}
|
|
1334
1459
|
|
|
@@ -1520,11 +1645,11 @@ function track(event, payload) {
|
|
|
1520
1645
|
}
|
|
1521
1646
|
|
|
1522
1647
|
// src/ui/App.tsx
|
|
1523
|
-
import { jsx as jsx13, jsxs as
|
|
1648
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1524
1649
|
function App() {
|
|
1525
1650
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1526
1651
|
const { exit } = useApp();
|
|
1527
|
-
const { columns, rows } =
|
|
1652
|
+
const { columns, rows } = useWindowSize8();
|
|
1528
1653
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1529
1654
|
const finished = phase === "done" || phase === "error";
|
|
1530
1655
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1537,7 +1662,7 @@ function App() {
|
|
|
1537
1662
|
{ isActive: finished }
|
|
1538
1663
|
);
|
|
1539
1664
|
useInput6((_input, key) => {
|
|
1540
|
-
if (phase === "idle" || phase === "
|
|
1665
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
1541
1666
|
if (key.tab) {
|
|
1542
1667
|
setShowLogs(!showLogs);
|
|
1543
1668
|
track("AI Wizard Interaction", {
|
|
@@ -1547,49 +1672,45 @@ function App() {
|
|
|
1547
1672
|
});
|
|
1548
1673
|
}
|
|
1549
1674
|
});
|
|
1550
|
-
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1675
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1551
1676
|
useInput6((_input, key) => {
|
|
1552
1677
|
if (escOwnedElsewhere) return;
|
|
1553
1678
|
if (key.escape) {
|
|
1554
1679
|
track("AI Wizard Interaction", {
|
|
1555
1680
|
context: "global",
|
|
1556
1681
|
key: "esc",
|
|
1557
|
-
// No step is active until `startWorkflow` — report the phase instead.
|
|
1558
1682
|
currentStep: currentStep?.id ?? phase
|
|
1559
1683
|
});
|
|
1560
1684
|
exit();
|
|
1561
1685
|
}
|
|
1562
1686
|
});
|
|
1563
|
-
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1687
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1564
1688
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1565
1689
|
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,
|
|
1690
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
1691
|
+
return (
|
|
1692
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1693
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1694
|
+
flicker and leftover rows. */
|
|
1695
|
+
/* @__PURE__ */ jsxs14(
|
|
1696
|
+
Box15,
|
|
1697
|
+
{
|
|
1698
|
+
backgroundColor: COLORS.bg.main,
|
|
1699
|
+
flexDirection: "row",
|
|
1700
|
+
width: columns,
|
|
1701
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
1702
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1703
|
+
children: [
|
|
1704
|
+
mainWindowVisible && /* @__PURE__ */ jsxs14(
|
|
1705
|
+
Box15,
|
|
1706
|
+
{
|
|
1707
|
+
flexDirection,
|
|
1708
|
+
width: "100%",
|
|
1709
|
+
maxHeight: rows,
|
|
1710
|
+
justifyContent: "space-between",
|
|
1711
|
+
children: [
|
|
1712
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs14(
|
|
1713
|
+
Box15,
|
|
1593
1714
|
{
|
|
1594
1715
|
flexDirection: "column",
|
|
1595
1716
|
paddingX: 4,
|
|
@@ -1597,24 +1718,29 @@ function App() {
|
|
|
1597
1718
|
width: showSidebar ? 70 : "100%",
|
|
1598
1719
|
flexGrow: 1,
|
|
1599
1720
|
children: [
|
|
1721
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
|
|
1722
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1723
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1724
|
+
] }),
|
|
1725
|
+
/* @__PURE__ */ jsx13(CliOutput, {}),
|
|
1600
1726
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1601
1727
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1602
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1603
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1728
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1729
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
|
|
1604
1730
|
"\u2716 ",
|
|
1605
1731
|
error
|
|
1606
1732
|
] }) })
|
|
1607
1733
|
]
|
|
1608
1734
|
}
|
|
1609
|
-
)
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1735
|
+
),
|
|
1736
|
+
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1737
|
+
]
|
|
1738
|
+
}
|
|
1739
|
+
),
|
|
1740
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
|
|
1741
|
+
]
|
|
1742
|
+
}
|
|
1743
|
+
)
|
|
1618
1744
|
);
|
|
1619
1745
|
}
|
|
1620
1746
|
|
|
@@ -1625,17 +1751,17 @@ import "zod";
|
|
|
1625
1751
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1626
1752
|
import { join as join5 } from "node:path";
|
|
1627
1753
|
var configFile = () => join5(stateDir(), "config.json");
|
|
1628
|
-
var
|
|
1754
|
+
var defaultConfig = () => ({
|
|
1629
1755
|
version: 1,
|
|
1630
1756
|
aiConsent: false,
|
|
1631
1757
|
workflowsRun: []
|
|
1632
|
-
};
|
|
1758
|
+
});
|
|
1633
1759
|
async function loadConfig() {
|
|
1634
1760
|
try {
|
|
1635
1761
|
const raw = await readFile2(configFile(), "utf8");
|
|
1636
|
-
return { ...
|
|
1762
|
+
return { ...defaultConfig(), ...JSON.parse(raw) };
|
|
1637
1763
|
} catch {
|
|
1638
|
-
return
|
|
1764
|
+
return defaultConfig();
|
|
1639
1765
|
}
|
|
1640
1766
|
}
|
|
1641
1767
|
async function saveConfig(config) {
|
|
@@ -1850,61 +1976,212 @@ async function runWorkflow(workflow, appId) {
|
|
|
1850
1976
|
}
|
|
1851
1977
|
}
|
|
1852
1978
|
|
|
1853
|
-
// src/lib/
|
|
1854
|
-
import {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1979
|
+
// src/lib/algoliaApp.ts
|
|
1980
|
+
import { z as z4 } from "zod";
|
|
1981
|
+
var applicationSchema = z4.object({
|
|
1982
|
+
id: z4.string().min(1),
|
|
1983
|
+
name: z4.string().default(""),
|
|
1984
|
+
plan: z4.string().optional()
|
|
1985
|
+
});
|
|
1986
|
+
var listSchema = z4.array(
|
|
1987
|
+
z4.object({
|
|
1988
|
+
id: z4.string().min(1),
|
|
1989
|
+
name: z4.string().default(""),
|
|
1990
|
+
plan_label: z4.string().optional()
|
|
1991
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
1992
|
+
);
|
|
1993
|
+
async function currentApplication() {
|
|
1994
|
+
let raw;
|
|
1866
1995
|
try {
|
|
1867
|
-
|
|
1996
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1868
1997
|
} catch {
|
|
1869
|
-
return
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2001
|
+
return parsed.success ? parsed.data : null;
|
|
2002
|
+
}
|
|
2003
|
+
async function requireApplication() {
|
|
2004
|
+
const app = await currentApplication();
|
|
2005
|
+
if (!app) {
|
|
2006
|
+
throw new Error(
|
|
2007
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
return app;
|
|
2011
|
+
}
|
|
2012
|
+
async function listApplications() {
|
|
2013
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
2014
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
2015
|
+
if (!parsed.success) {
|
|
2016
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
2017
|
+
}
|
|
2018
|
+
return parsed.data;
|
|
2019
|
+
}
|
|
2020
|
+
async function selectApplication(id) {
|
|
2021
|
+
const raw = await runAlgoliaCli(
|
|
2022
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
2023
|
+
{ onOutput: stderrSink }
|
|
2024
|
+
);
|
|
2025
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2026
|
+
if (!parsed.success) {
|
|
2027
|
+
throw new Error(
|
|
2028
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
2029
|
+
);
|
|
1870
2030
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
name,
|
|
1875
|
-
appId: t.application_id,
|
|
1876
|
-
apiKey: t.api_key,
|
|
1877
|
-
isDefault: t.default === true
|
|
1878
|
-
}));
|
|
1879
|
-
profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
|
|
1880
|
-
return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
|
|
1881
|
-
}
|
|
1882
|
-
async function loadActiveProfile() {
|
|
1883
|
-
let profiles;
|
|
2031
|
+
return parsed.data;
|
|
2032
|
+
}
|
|
2033
|
+
function parseJson(text) {
|
|
1884
2034
|
try {
|
|
1885
|
-
|
|
2035
|
+
return JSON.parse(text);
|
|
1886
2036
|
} catch {
|
|
1887
|
-
|
|
2037
|
+
return void 0;
|
|
1888
2038
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/lib/envAppId.ts
|
|
2042
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2043
|
+
import { join as join6 } from "node:path";
|
|
2044
|
+
var ENV_FILES = [".env", ".env.local"];
|
|
2045
|
+
var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
|
|
2046
|
+
async function findEnvApplicationId(root = process.cwd()) {
|
|
2047
|
+
for (const file of ENV_FILES) {
|
|
2048
|
+
let content;
|
|
2049
|
+
try {
|
|
2050
|
+
content = await readFile3(join6(root, file), "utf8");
|
|
2051
|
+
} catch (err) {
|
|
2052
|
+
if (err.code !== "ENOENT") {
|
|
2053
|
+
logger.warn(
|
|
2054
|
+
{ file, err },
|
|
2055
|
+
"could not read env file for an application id"
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
|
|
2061
|
+
const id = readValue(raw);
|
|
2062
|
+
if (id) {
|
|
2063
|
+
logger.info({ file, name, app: id }, "found an application id in env");
|
|
2064
|
+
return { id, name, file };
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return null;
|
|
2069
|
+
}
|
|
2070
|
+
function readValue(raw) {
|
|
2071
|
+
const trimmed = raw.trim();
|
|
2072
|
+
const quoted = trimmed.match(/^(['"])(.*)\1/);
|
|
2073
|
+
const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
|
|
2074
|
+
return value.length > 0 && !value.startsWith("<") ? value : null;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/lib/algoliaAppPicker.ts
|
|
2078
|
+
function secondaryFor(app) {
|
|
2079
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
2080
|
+
}
|
|
2081
|
+
function labelFor(app) {
|
|
2082
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
2083
|
+
}
|
|
2084
|
+
function selectAndReport(app) {
|
|
2085
|
+
useWizard.getState().pushCliOutput(
|
|
2086
|
+
"stdout",
|
|
2087
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
2088
|
+
);
|
|
2089
|
+
return selectApplication(app.id);
|
|
2090
|
+
}
|
|
2091
|
+
async function promptForApplication(leadIn = []) {
|
|
2092
|
+
const store = useWizard.getState();
|
|
2093
|
+
const apps = await listApplications();
|
|
2094
|
+
if (apps.length === 0) {
|
|
1891
2095
|
throw new Error(
|
|
1892
|
-
"
|
|
2096
|
+
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
if (apps.length === 1) {
|
|
2100
|
+
const only = apps[0];
|
|
2101
|
+
logger.info(
|
|
2102
|
+
{ app: only.id },
|
|
2103
|
+
"single application on the account; selecting it"
|
|
2104
|
+
);
|
|
2105
|
+
for (const line of leadIn) store.pushCliOutput("stdout", line);
|
|
2106
|
+
return selectAndReport(only);
|
|
2107
|
+
}
|
|
2108
|
+
const messages = [
|
|
2109
|
+
...leadIn,
|
|
2110
|
+
"Which Algolia application should the wizard work in?"
|
|
2111
|
+
];
|
|
2112
|
+
for (; ; ) {
|
|
2113
|
+
const choice = await store.requestUserInput({
|
|
2114
|
+
prompt: "Select an application",
|
|
2115
|
+
promptType: "multipleChoice",
|
|
2116
|
+
options: apps.map(labelFor),
|
|
2117
|
+
secondary: apps.map(secondaryFor),
|
|
2118
|
+
messages
|
|
2119
|
+
});
|
|
2120
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
2121
|
+
if (!chosen) {
|
|
2122
|
+
throw new Error("Application picker received an unexpected selection");
|
|
2123
|
+
}
|
|
2124
|
+
try {
|
|
2125
|
+
return await selectAndReport(chosen);
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
logger.warn(
|
|
2128
|
+
{ app: chosen.id, err: err.message },
|
|
2129
|
+
"application select failed; re-prompting"
|
|
2130
|
+
);
|
|
2131
|
+
messages.push(
|
|
2132
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
async function confirmEnvApplication(env, current) {
|
|
2138
|
+
const useEnv = `Use ${env.id} (from ${env.file})`;
|
|
2139
|
+
const choice = await useWizard.getState().requestUserInput({
|
|
2140
|
+
prompt: "Select an application",
|
|
2141
|
+
promptType: "multipleChoice",
|
|
2142
|
+
options: [
|
|
2143
|
+
useEnv,
|
|
2144
|
+
current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
|
|
2145
|
+
],
|
|
2146
|
+
messages: [
|
|
2147
|
+
`${env.file} already sets ${env.name}=${env.id}.`,
|
|
2148
|
+
"Which Algolia application should the wizard work in?"
|
|
2149
|
+
]
|
|
2150
|
+
});
|
|
2151
|
+
return choice === useEnv;
|
|
2152
|
+
}
|
|
2153
|
+
async function selectEnvApplication(env) {
|
|
2154
|
+
try {
|
|
2155
|
+
return await selectAndReport({ id: env.id, name: "" });
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
logger.warn(
|
|
2158
|
+
{ app: env.id, err: err.message },
|
|
2159
|
+
"could not select the application named in env; falling back to the picker"
|
|
1893
2160
|
);
|
|
2161
|
+
return promptForApplication([
|
|
2162
|
+
`Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
|
|
2163
|
+
]);
|
|
1894
2164
|
}
|
|
1895
|
-
|
|
2165
|
+
}
|
|
2166
|
+
async function ensureApplication() {
|
|
2167
|
+
const current = await currentApplication();
|
|
2168
|
+
const env = await findEnvApplicationId();
|
|
2169
|
+
if (env && env.id !== current?.id && await confirmEnvApplication(env, current)) {
|
|
2170
|
+
return selectEnvApplication(env);
|
|
2171
|
+
}
|
|
2172
|
+
return current ?? await promptForApplication();
|
|
1896
2173
|
}
|
|
1897
2174
|
|
|
1898
2175
|
// src/workflows/default.ts
|
|
1899
|
-
import { z as
|
|
2176
|
+
import { z as z28 } from "zod";
|
|
1900
2177
|
|
|
1901
2178
|
// src/actions/listIndices.ts
|
|
1902
|
-
import { z as
|
|
1903
|
-
var indicesListSchema =
|
|
1904
|
-
items:
|
|
1905
|
-
|
|
1906
|
-
name:
|
|
1907
|
-
entries:
|
|
2179
|
+
import { z as z5 } from "zod";
|
|
2180
|
+
var indicesListSchema = z5.object({
|
|
2181
|
+
items: z5.array(
|
|
2182
|
+
z5.object({
|
|
2183
|
+
name: z5.string(),
|
|
2184
|
+
entries: z5.number().default(0)
|
|
1908
2185
|
})
|
|
1909
2186
|
)
|
|
1910
2187
|
});
|
|
@@ -1975,7 +2252,7 @@ import "zod";
|
|
|
1975
2252
|
|
|
1976
2253
|
// src/lib/tools/listFiles.ts
|
|
1977
2254
|
import { tool } from "ai";
|
|
1978
|
-
import
|
|
2255
|
+
import z6 from "zod";
|
|
1979
2256
|
import { readdir } from "node:fs/promises";
|
|
1980
2257
|
|
|
1981
2258
|
// src/lib/tools/path.ts
|
|
@@ -2011,15 +2288,15 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
2011
2288
|
function listFilesTool(ctx) {
|
|
2012
2289
|
return tool({
|
|
2013
2290
|
description: "List files in the current working directory",
|
|
2014
|
-
inputSchema:
|
|
2291
|
+
inputSchema: z6.object(),
|
|
2015
2292
|
execute: async () => {
|
|
2016
2293
|
logger.info("called listFiles tool");
|
|
2017
2294
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
2018
2295
|
return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
|
|
2019
2296
|
}
|
|
2020
|
-
const
|
|
2021
|
-
if (!
|
|
2022
|
-
const entries = await readdir(
|
|
2297
|
+
const resolved2 = resolveInRoot(ctx, ".");
|
|
2298
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2299
|
+
const entries = await readdir(resolved2.target, { withFileTypes: true });
|
|
2023
2300
|
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
2024
2301
|
}
|
|
2025
2302
|
});
|
|
@@ -2027,24 +2304,24 @@ function listFilesTool(ctx) {
|
|
|
2027
2304
|
|
|
2028
2305
|
// src/lib/tools/changeDirectory.ts
|
|
2029
2306
|
import { tool as tool2 } from "ai";
|
|
2030
|
-
import
|
|
2307
|
+
import z7 from "zod";
|
|
2031
2308
|
import { stat } from "node:fs/promises";
|
|
2032
2309
|
function changeDirectoryTool(ctx) {
|
|
2033
2310
|
return tool2({
|
|
2034
2311
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
2035
|
-
inputSchema:
|
|
2036
|
-
path:
|
|
2312
|
+
inputSchema: z7.object({
|
|
2313
|
+
path: z7.string().describe("Directory to change into")
|
|
2037
2314
|
}),
|
|
2038
2315
|
execute: async ({ path }) => {
|
|
2039
2316
|
logger.info({ path }, "called changeDirectory tool");
|
|
2040
|
-
const
|
|
2041
|
-
if (!
|
|
2317
|
+
const resolved2 = resolveInRoot(ctx, path);
|
|
2318
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2042
2319
|
try {
|
|
2043
|
-
const info = await stat(
|
|
2320
|
+
const info = await stat(resolved2.target);
|
|
2044
2321
|
if (!info.isDirectory()) {
|
|
2045
2322
|
return `Error changing directory to ${path}: not a directory`;
|
|
2046
2323
|
}
|
|
2047
|
-
ctx.cwd =
|
|
2324
|
+
ctx.cwd = resolved2.target;
|
|
2048
2325
|
return `Changed working directory to ${ctx.cwd}`;
|
|
2049
2326
|
} catch (err) {
|
|
2050
2327
|
return `Error changing directory to ${path}: ${err.message}`;
|
|
@@ -2055,13 +2332,13 @@ function changeDirectoryTool(ctx) {
|
|
|
2055
2332
|
|
|
2056
2333
|
// src/lib/tools/reportStatus.ts
|
|
2057
2334
|
import { tool as tool3 } from "ai";
|
|
2058
|
-
import
|
|
2335
|
+
import z8 from "zod";
|
|
2059
2336
|
function reportStatusTool(output) {
|
|
2060
2337
|
return tool3({
|
|
2061
2338
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2062
|
-
inputSchema:
|
|
2063
|
-
status:
|
|
2064
|
-
reason:
|
|
2339
|
+
inputSchema: z8.object({
|
|
2340
|
+
status: z8.enum(["success", "fail"]),
|
|
2341
|
+
reason: z8.string().optional(),
|
|
2065
2342
|
output
|
|
2066
2343
|
}),
|
|
2067
2344
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2073,7 +2350,7 @@ function reportStatusTool(output) {
|
|
|
2073
2350
|
|
|
2074
2351
|
// src/lib/tools/readFile.ts
|
|
2075
2352
|
import { tool as tool4 } from "ai";
|
|
2076
|
-
import
|
|
2353
|
+
import z9 from "zod";
|
|
2077
2354
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
2078
2355
|
|
|
2079
2356
|
// src/lib/tools/env.ts
|
|
@@ -2101,19 +2378,19 @@ function redactEnvValues(content) {
|
|
|
2101
2378
|
function readFileTool(ctx) {
|
|
2102
2379
|
return tool4({
|
|
2103
2380
|
description: "Read the contents of a file at the given path",
|
|
2104
|
-
inputSchema:
|
|
2105
|
-
filePath:
|
|
2381
|
+
inputSchema: z9.object({
|
|
2382
|
+
filePath: z9.string().describe("Path to the file to read")
|
|
2106
2383
|
}),
|
|
2107
2384
|
execute: async ({ filePath }) => {
|
|
2108
2385
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
2109
2386
|
return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
|
|
2110
2387
|
}
|
|
2111
2388
|
logger.info({ filePath }, "called readFile tool");
|
|
2112
|
-
const
|
|
2113
|
-
if (!
|
|
2389
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2390
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2114
2391
|
try {
|
|
2115
|
-
const content = await readFile4(
|
|
2116
|
-
return isEnvFile(
|
|
2392
|
+
const content = await readFile4(resolved2.target, "utf8");
|
|
2393
|
+
return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
|
|
2117
2394
|
} catch (err) {
|
|
2118
2395
|
return `Error reading ${filePath}: ${err.message}`;
|
|
2119
2396
|
}
|
|
@@ -2123,29 +2400,29 @@ function readFileTool(ctx) {
|
|
|
2123
2400
|
|
|
2124
2401
|
// src/lib/tools/writeFile.ts
|
|
2125
2402
|
import { tool as tool5 } from "ai";
|
|
2126
|
-
import
|
|
2403
|
+
import z10 from "zod";
|
|
2127
2404
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2128
2405
|
import { dirname as dirname4 } from "node:path";
|
|
2129
2406
|
function writeFileTool(ctx) {
|
|
2130
2407
|
return tool5({
|
|
2131
2408
|
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:
|
|
2409
|
+
inputSchema: z10.object({
|
|
2410
|
+
filePath: z10.string().describe("Path to the file to write"),
|
|
2411
|
+
content: z10.string().describe("Content to write to the file")
|
|
2135
2412
|
}),
|
|
2136
2413
|
execute: async ({ filePath, content }) => {
|
|
2137
2414
|
logger.info({ filePath }, "called writeFile tool");
|
|
2138
|
-
const
|
|
2139
|
-
if (
|
|
2140
|
-
if (isSecretEnvFile(
|
|
2415
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2416
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2417
|
+
if (isSecretEnvFile(resolved2.target)) {
|
|
2141
2418
|
return `Refused: ${filePath} holds secrets. Use the writeCredentials tool to set Algolia environment variables, passing this file path.`;
|
|
2142
2419
|
}
|
|
2143
2420
|
try {
|
|
2144
|
-
if (await hasSymlinkParent(ctx,
|
|
2145
|
-
return `Refused: ${
|
|
2421
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2422
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2146
2423
|
}
|
|
2147
|
-
await mkdir3(dirname4(
|
|
2148
|
-
await writeFile3(
|
|
2424
|
+
await mkdir3(dirname4(resolved2.target), { recursive: true });
|
|
2425
|
+
await writeFile3(resolved2.target, content, "utf8");
|
|
2149
2426
|
return `Wrote to ${filePath}`;
|
|
2150
2427
|
} catch (err) {
|
|
2151
2428
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -2156,11 +2433,194 @@ function writeFileTool(ctx) {
|
|
|
2156
2433
|
|
|
2157
2434
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2158
2435
|
import { tool as tool6 } from "ai";
|
|
2159
|
-
import
|
|
2436
|
+
import z13 from "zod";
|
|
2160
2437
|
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
2161
2438
|
import { dirname as dirname5 } from "node:path";
|
|
2439
|
+
|
|
2440
|
+
// src/lib/algoliaApiKey.ts
|
|
2441
|
+
import { z as z12 } from "zod";
|
|
2442
|
+
|
|
2443
|
+
// src/lib/keychain.ts
|
|
2444
|
+
import { deletePassword, getPassword, setPassword } from "cross-keychain";
|
|
2445
|
+
import { z as z11 } from "zod";
|
|
2446
|
+
var SERVICE = "algolia-wizard";
|
|
2447
|
+
var ACCOUNT = "api-keys";
|
|
2448
|
+
var storedKeysSchema = z11.record(z11.string(), z11.string());
|
|
2449
|
+
function entryId(kind, index, appId) {
|
|
2450
|
+
return `${kind}:${appId}:${index}`;
|
|
2451
|
+
}
|
|
2452
|
+
async function loadKeys() {
|
|
2453
|
+
const raw = await getPassword(SERVICE, ACCOUNT);
|
|
2454
|
+
if (!raw) return {};
|
|
2455
|
+
let payload;
|
|
2456
|
+
try {
|
|
2457
|
+
payload = JSON.parse(raw);
|
|
2458
|
+
} catch {
|
|
2459
|
+
payload = null;
|
|
2460
|
+
}
|
|
2461
|
+
const keys = storedKeysSchema.safeParse(payload);
|
|
2462
|
+
if (!keys.success) {
|
|
2463
|
+
logger.warn("the stored API keys are unreadable; treating them as empty");
|
|
2464
|
+
return {};
|
|
2465
|
+
}
|
|
2466
|
+
return keys.data;
|
|
2467
|
+
}
|
|
2468
|
+
var queue = Promise.resolve();
|
|
2469
|
+
function serialized(op) {
|
|
2470
|
+
const next = queue.then(op);
|
|
2471
|
+
queue = next.catch(() => {
|
|
2472
|
+
});
|
|
2473
|
+
return next;
|
|
2474
|
+
}
|
|
2475
|
+
async function readStoredKey(kind, index, appId) {
|
|
2476
|
+
try {
|
|
2477
|
+
return (await loadKeys())[entryId(kind, index, appId)] ?? null;
|
|
2478
|
+
} catch (err) {
|
|
2479
|
+
logger.warn(
|
|
2480
|
+
{ err: err.message, kind, index, appId },
|
|
2481
|
+
"could not read the API key from the keychain"
|
|
2482
|
+
);
|
|
2483
|
+
return null;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
function storeKey(kind, index, appId, value) {
|
|
2487
|
+
return serialized(async () => {
|
|
2488
|
+
const id = entryId(kind, index, appId);
|
|
2489
|
+
try {
|
|
2490
|
+
const keys = await loadKeys();
|
|
2491
|
+
await setPassword(
|
|
2492
|
+
SERVICE,
|
|
2493
|
+
ACCOUNT,
|
|
2494
|
+
JSON.stringify({ ...keys, [id]: value })
|
|
2495
|
+
);
|
|
2496
|
+
if ((await loadKeys())[id] !== value) {
|
|
2497
|
+
throw new Error("the keychain did not store the value");
|
|
2498
|
+
}
|
|
2499
|
+
} catch (err) {
|
|
2500
|
+
logger.warn(
|
|
2501
|
+
{ err: err.message, kind, index, appId },
|
|
2502
|
+
"could not store the API key in the keychain; the next run will create another"
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
function deleteStoredKeys() {
|
|
2508
|
+
return serialized(async () => {
|
|
2509
|
+
try {
|
|
2510
|
+
await deletePassword(SERVICE, ACCOUNT);
|
|
2511
|
+
} catch (err) {
|
|
2512
|
+
const message = err.message;
|
|
2513
|
+
if (/not found/i.test(message)) return;
|
|
2514
|
+
logger.warn(
|
|
2515
|
+
{ err: message },
|
|
2516
|
+
"could not delete the API keys from the keychain"
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
// src/lib/algoliaApiKey.ts
|
|
2523
|
+
var WRITE_ACLS = [
|
|
2524
|
+
"addObject",
|
|
2525
|
+
"deleteObject",
|
|
2526
|
+
"settings",
|
|
2527
|
+
"editSettings",
|
|
2528
|
+
"listIndexes"
|
|
2529
|
+
];
|
|
2530
|
+
var createdKeySchema = z12.object({
|
|
2531
|
+
key: z12.string().min(1).optional(),
|
|
2532
|
+
value: z12.string().min(1).optional()
|
|
2533
|
+
}).transform((o) => o.key ?? o.value);
|
|
2534
|
+
async function createKey(index, acls, description) {
|
|
2535
|
+
logger.info({ index, acls }, "creating an API key");
|
|
2536
|
+
const stdout = await runAlgoliaCli([
|
|
2537
|
+
"apikeys",
|
|
2538
|
+
"create",
|
|
2539
|
+
"--acl",
|
|
2540
|
+
acls.join(","),
|
|
2541
|
+
"--indices",
|
|
2542
|
+
index,
|
|
2543
|
+
"--description",
|
|
2544
|
+
description,
|
|
2545
|
+
"-o",
|
|
2546
|
+
"json"
|
|
2547
|
+
]);
|
|
2548
|
+
let payload;
|
|
2549
|
+
try {
|
|
2550
|
+
payload = JSON.parse(stdout);
|
|
2551
|
+
} catch {
|
|
2552
|
+
throw new Error("apikeys create returned output that is not valid JSON");
|
|
2553
|
+
}
|
|
2554
|
+
const created = createdKeySchema.parse(payload);
|
|
2555
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2556
|
+
return created;
|
|
2557
|
+
}
|
|
2558
|
+
async function keyExists(key) {
|
|
2559
|
+
try {
|
|
2560
|
+
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
|
|
2561
|
+
return true;
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
return !/does not exist/i.test(err.message);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
var resolved = /* @__PURE__ */ new Map();
|
|
2567
|
+
async function forgetResolvedKeys() {
|
|
2568
|
+
resolved.clear();
|
|
2569
|
+
await deleteStoredKeys();
|
|
2570
|
+
}
|
|
2571
|
+
function resolveKey(kind, index, appId, acls, description) {
|
|
2572
|
+
const cacheKey = `${kind}:${appId}:${index}`;
|
|
2573
|
+
const cached = resolved.get(cacheKey);
|
|
2574
|
+
if (cached) return cached;
|
|
2575
|
+
const pending = provisionKey(kind, index, appId, acls, description).catch(
|
|
2576
|
+
(err) => {
|
|
2577
|
+
resolved.delete(cacheKey);
|
|
2578
|
+
throw err;
|
|
2579
|
+
}
|
|
2580
|
+
);
|
|
2581
|
+
resolved.set(cacheKey, pending);
|
|
2582
|
+
return pending;
|
|
2583
|
+
}
|
|
2584
|
+
async function provisionKey(kind, index, appId, acls, description) {
|
|
2585
|
+
const stored = await readStoredKey(kind, index, appId);
|
|
2586
|
+
if (stored) {
|
|
2587
|
+
if (await keyExists(stored)) {
|
|
2588
|
+
logger.info({ kind, index, appId }, "reusing the stored API key");
|
|
2589
|
+
return { key: stored, source: "keychain" };
|
|
2590
|
+
}
|
|
2591
|
+
logger.info(
|
|
2592
|
+
{ kind, index, appId },
|
|
2593
|
+
"the stored API key no longer exists; creating another"
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2596
|
+
const key = await createKey(index, acls, description);
|
|
2597
|
+
await storeKey(kind, index, appId, key);
|
|
2598
|
+
return { key, source: "created" };
|
|
2599
|
+
}
|
|
2600
|
+
function resolveWriteKey(index, appId) {
|
|
2601
|
+
return resolveKey(
|
|
2602
|
+
"write",
|
|
2603
|
+
index,
|
|
2604
|
+
appId,
|
|
2605
|
+
WRITE_ACLS,
|
|
2606
|
+
`Algolia Wizard write key for ${index} index`
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2610
|
+
if (envKey) return { key: envKey, source: "env" };
|
|
2611
|
+
return resolveKey(
|
|
2612
|
+
"search",
|
|
2613
|
+
index,
|
|
2614
|
+
appId,
|
|
2615
|
+
["search"],
|
|
2616
|
+
`Algolia Wizard search-only key for ${index} index`
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2162
2621
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2163
2622
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2623
|
+
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2164
2624
|
function appendEnv(content, entries) {
|
|
2165
2625
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
2166
2626
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -2170,47 +2630,111 @@ function appendEnv(content, entries) {
|
|
|
2170
2630
|
function hasEnv(content, name) {
|
|
2171
2631
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
2172
2632
|
}
|
|
2633
|
+
function readEnv(content, name) {
|
|
2634
|
+
const found = content.match(
|
|
2635
|
+
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*(.*)$`, "m")
|
|
2636
|
+
);
|
|
2637
|
+
if (!found) return null;
|
|
2638
|
+
const raw = found[1].trim();
|
|
2639
|
+
const quoted = raw.match(/^(['"])(.*)\1/);
|
|
2640
|
+
const value = quoted ? quoted[2] : raw.replace(/\s+#.*$/, "");
|
|
2641
|
+
return value.length > 0 ? value : null;
|
|
2642
|
+
}
|
|
2643
|
+
function upsertEnv(content, name, value) {
|
|
2644
|
+
if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
|
|
2645
|
+
return content.replace(
|
|
2646
|
+
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "gm"),
|
|
2647
|
+
() => `${name}=${value}`
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
2173
2650
|
function writeCredentialsTool(ctx) {
|
|
2174
2651
|
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:
|
|
2652
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application.`,
|
|
2653
|
+
inputSchema: z13.object({
|
|
2654
|
+
filePath: z13.string().describe(
|
|
2178
2655
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2179
2656
|
)
|
|
2180
2657
|
}),
|
|
2181
2658
|
execute: async ({ filePath }) => {
|
|
2182
2659
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2183
|
-
const
|
|
2184
|
-
if (
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
} catch {
|
|
2189
|
-
return "Error: no Algolia profile is configured, so credentials cannot be written. Ask the user to authenticate with the Algolia CLI first.";
|
|
2660
|
+
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2661
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2662
|
+
const targetIndex = useWizard.getState().targetIndex;
|
|
2663
|
+
if (!targetIndex) {
|
|
2664
|
+
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2190
2665
|
}
|
|
2666
|
+
let existing = "";
|
|
2667
|
+
let present;
|
|
2191
2668
|
try {
|
|
2192
|
-
if (await hasSymlinkParent(ctx,
|
|
2193
|
-
return `Refused: ${
|
|
2669
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2670
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2194
2671
|
}
|
|
2195
|
-
let existing = "";
|
|
2196
2672
|
try {
|
|
2197
|
-
existing = await readFile5(
|
|
2673
|
+
existing = await readFile5(resolved2.target, "utf8");
|
|
2198
2674
|
} catch (err) {
|
|
2199
2675
|
if (err.code !== "ENOENT") throw err;
|
|
2200
2676
|
}
|
|
2201
|
-
|
|
2202
|
-
(name) =>
|
|
2677
|
+
present = [APP_ID_VAR, API_KEY_VAR].filter(
|
|
2678
|
+
(name) => readEnv(existing, name) !== null
|
|
2679
|
+
);
|
|
2680
|
+
} catch (err) {
|
|
2681
|
+
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
2682
|
+
}
|
|
2683
|
+
const credentials = [];
|
|
2684
|
+
const notes = [];
|
|
2685
|
+
const fileAppId = readEnv(existing, APP_ID_VAR);
|
|
2686
|
+
const fileKey = readEnv(existing, API_KEY_VAR);
|
|
2687
|
+
const fileIndex = readEnv(existing, INDEX_NAME_VAR);
|
|
2688
|
+
if (fileAppId === null || fileKey === null) {
|
|
2689
|
+
try {
|
|
2690
|
+
const selected = (await requireApplication()).id;
|
|
2691
|
+
if (fileAppId === null) credentials.push([APP_ID_VAR, selected]);
|
|
2692
|
+
if (fileKey === null) {
|
|
2693
|
+
if (fileAppId !== null && fileAppId !== selected) {
|
|
2694
|
+
notes.push(
|
|
2695
|
+
`No ${API_KEY_VAR} was provisioned: ${filePath} names application ${fileAppId}, but ${selected} is selected. Tell the user to remove ${APP_ID_VAR} from ${filePath} and re-run so a matching pair can be written, or to fill in a ${API_KEY_VAR} for ${fileAppId} by hand.`
|
|
2696
|
+
);
|
|
2697
|
+
} else {
|
|
2698
|
+
credentials.push([
|
|
2699
|
+
API_KEY_VAR,
|
|
2700
|
+
(await resolveWriteKey(targetIndex, selected)).key
|
|
2701
|
+
]);
|
|
2702
|
+
}
|
|
2703
|
+
} else {
|
|
2704
|
+
notes.push(
|
|
2705
|
+
`The existing ${API_KEY_VAR} must belong to application ${selected} or writes will fail.`
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
} catch (err) {
|
|
2709
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
try {
|
|
2713
|
+
const updated = [
|
|
2714
|
+
...credentials,
|
|
2715
|
+
// Unconditional, unlike the credentials: the write key is scoped to
|
|
2716
|
+
// the run's index, so a stale name left in the file earns a 403.
|
|
2717
|
+
[INDEX_NAME_VAR, targetIndex]
|
|
2718
|
+
].reduce(
|
|
2719
|
+
(content, [name, value]) => upsertEnv(content, name, value),
|
|
2720
|
+
existing
|
|
2203
2721
|
);
|
|
2722
|
+
await mkdir4(dirname5(resolved2.target), { recursive: true });
|
|
2723
|
+
await writeFile4(resolved2.target, updated, "utf8");
|
|
2724
|
+
const sentences = [
|
|
2725
|
+
`Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
|
|
2726
|
+
];
|
|
2727
|
+
if (fileIndex !== null && fileIndex !== targetIndex) {
|
|
2728
|
+
sentences.push(
|
|
2729
|
+
`That replaced the ${INDEX_NAME_VAR} already there ("${fileIndex}"), which this run does not target.`
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2204
2732
|
if (present.length > 0) {
|
|
2205
|
-
|
|
2733
|
+
sentences.push(
|
|
2734
|
+
`Skipped ${present.join(" and ")}: already defined there.`
|
|
2735
|
+
);
|
|
2206
2736
|
}
|
|
2207
|
-
|
|
2208
|
-
[APP_ID_VAR, profile.appId],
|
|
2209
|
-
[API_KEY_VAR, profile.apiKey]
|
|
2210
|
-
]);
|
|
2211
|
-
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2212
|
-
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
2213
|
-
return `Wrote Algolia credentials to ${filePath}`;
|
|
2737
|
+
return [...sentences, ...notes].join(" ");
|
|
2214
2738
|
} catch (err) {
|
|
2215
2739
|
return `Error writing credentials to ${filePath}: ${err.message}`;
|
|
2216
2740
|
}
|
|
@@ -2220,7 +2744,7 @@ function writeCredentialsTool(ctx) {
|
|
|
2220
2744
|
|
|
2221
2745
|
// src/lib/tools/searchFiles.ts
|
|
2222
2746
|
import { tool as tool7 } from "ai";
|
|
2223
|
-
import
|
|
2747
|
+
import z14 from "zod";
|
|
2224
2748
|
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2225
2749
|
import { join as join8 } from "node:path";
|
|
2226
2750
|
var MAX_QUERY_LENGTH = 1e3;
|
|
@@ -2238,9 +2762,9 @@ async function walkFiles(dir) {
|
|
|
2238
2762
|
function searchFilesTool(ctx) {
|
|
2239
2763
|
return tool7({
|
|
2240
2764
|
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:
|
|
2765
|
+
inputSchema: z14.object({
|
|
2766
|
+
query: z14.string().describe("JavaScript RegExp pattern to search for"),
|
|
2767
|
+
path: z14.string().optional().describe("Directory to search in (default: cwd)")
|
|
2244
2768
|
}),
|
|
2245
2769
|
execute: async ({ query, path = "." }) => {
|
|
2246
2770
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2250,8 +2774,8 @@ function searchFilesTool(ctx) {
|
|
|
2250
2774
|
if (query.length > MAX_QUERY_LENGTH) {
|
|
2251
2775
|
return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
|
|
2252
2776
|
}
|
|
2253
|
-
const
|
|
2254
|
-
if (!
|
|
2777
|
+
const resolved2 = resolveInRoot(ctx, path);
|
|
2778
|
+
if (!resolved2.ok) return resolved2.error;
|
|
2255
2779
|
let re;
|
|
2256
2780
|
try {
|
|
2257
2781
|
re = new RegExp(query);
|
|
@@ -2259,7 +2783,7 @@ function searchFilesTool(ctx) {
|
|
|
2259
2783
|
return `Invalid regex: ${err.message}`;
|
|
2260
2784
|
}
|
|
2261
2785
|
const matches = [];
|
|
2262
|
-
for (const file of await walkFiles(
|
|
2786
|
+
for (const file of await walkFiles(resolved2.target)) {
|
|
2263
2787
|
let content;
|
|
2264
2788
|
try {
|
|
2265
2789
|
content = await readFile6(file, "utf8");
|
|
@@ -2284,7 +2808,7 @@ function searchFilesTool(ctx) {
|
|
|
2284
2808
|
|
|
2285
2809
|
// src/lib/tools/verifyImplementation.ts
|
|
2286
2810
|
import { tool as tool8 } from "ai";
|
|
2287
|
-
import
|
|
2811
|
+
import z15 from "zod";
|
|
2288
2812
|
|
|
2289
2813
|
// src/lib/tools/utils/runCommand.ts
|
|
2290
2814
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -2364,7 +2888,7 @@ async function runRepoVerificationCheck() {
|
|
|
2364
2888
|
function verifyImplementationTool() {
|
|
2365
2889
|
return tool8({
|
|
2366
2890
|
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:
|
|
2891
|
+
inputSchema: z15.object(),
|
|
2368
2892
|
execute: async () => {
|
|
2369
2893
|
logger.info("called verifyImplementation tool");
|
|
2370
2894
|
return runRepoVerificationCheck();
|
|
@@ -2378,7 +2902,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
2378
2902
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2379
2903
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2380
2904
|
import { dirname as dirname6 } from "node:path";
|
|
2381
|
-
import
|
|
2905
|
+
import z16 from "zod";
|
|
2382
2906
|
var DATA_DIR = ".algolia-wizard/data";
|
|
2383
2907
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
2384
2908
|
var MAX_RECORDS = 100;
|
|
@@ -2390,17 +2914,17 @@ var anthropic = createAnthropic({
|
|
|
2390
2914
|
function generateRecordTool(ctx) {
|
|
2391
2915
|
return tool9({
|
|
2392
2916
|
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:
|
|
2917
|
+
inputSchema: z16.object({
|
|
2918
|
+
entityName: z16.string().describe("Name of the entity to generate records for."),
|
|
2919
|
+
attributes: z16.array(z16.string()).describe("Attribute names each record must contain."),
|
|
2920
|
+
count: z16.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2921
|
+
hint: z16.string().optional().describe("Optional context to steer realistic values.")
|
|
2398
2922
|
}),
|
|
2399
2923
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
2400
2924
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
2401
2925
|
try {
|
|
2402
|
-
const value =
|
|
2403
|
-
const recordSchema =
|
|
2926
|
+
const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
|
|
2927
|
+
const recordSchema = z16.object(
|
|
2404
2928
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
2405
2929
|
);
|
|
2406
2930
|
const generateBatch = async (batchCount) => {
|
|
@@ -2410,8 +2934,8 @@ function generateRecordTool(ctx) {
|
|
|
2410
2934
|
const { output } = await generateText({
|
|
2411
2935
|
model: anthropic(RECORD_MODEL),
|
|
2412
2936
|
output: Output.object({
|
|
2413
|
-
schema:
|
|
2414
|
-
records:
|
|
2937
|
+
schema: z16.object({
|
|
2938
|
+
records: z16.array(recordSchema).length(batchCount)
|
|
2415
2939
|
})
|
|
2416
2940
|
}),
|
|
2417
2941
|
prompt: [
|
|
@@ -2447,13 +2971,13 @@ function generateRecordTool(ctx) {
|
|
|
2447
2971
|
}));
|
|
2448
2972
|
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2449
2973
|
const relPath = `${DATA_DIR}/${slug}.json`;
|
|
2450
|
-
const
|
|
2451
|
-
if (
|
|
2452
|
-
if (await hasSymlinkParent(ctx,
|
|
2453
|
-
return `Refused: ${
|
|
2974
|
+
const resolved2 = resolveInRoot(ctx, relPath);
|
|
2975
|
+
if (resolved2.ok === false) return resolved2.error;
|
|
2976
|
+
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2977
|
+
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2454
2978
|
}
|
|
2455
|
-
await mkdir5(dirname6(
|
|
2456
|
-
await writeFile5(
|
|
2979
|
+
await mkdir5(dirname6(resolved2.target), { recursive: true });
|
|
2980
|
+
await writeFile5(resolved2.target, JSON.stringify(records, null, 2), "utf8");
|
|
2457
2981
|
logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
|
|
2458
2982
|
return {
|
|
2459
2983
|
filePath: relPath,
|
|
@@ -2469,12 +2993,12 @@ function generateRecordTool(ctx) {
|
|
|
2469
2993
|
|
|
2470
2994
|
// src/lib/tools/notifyUser.ts
|
|
2471
2995
|
import { tool as tool10 } from "ai";
|
|
2472
|
-
import
|
|
2996
|
+
import z17 from "zod";
|
|
2473
2997
|
function notifyUserTool() {
|
|
2474
2998
|
return tool10({
|
|
2475
2999
|
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:
|
|
3000
|
+
inputSchema: z17.object({
|
|
3001
|
+
message: z17.string().describe(
|
|
2478
3002
|
"Short, plain-language description of what you are doing now."
|
|
2479
3003
|
)
|
|
2480
3004
|
}),
|
|
@@ -2654,10 +3178,10 @@ async function runAgent(req) {
|
|
|
2654
3178
|
}
|
|
2655
3179
|
|
|
2656
3180
|
// src/actions/detectLanguage.ts
|
|
2657
|
-
import
|
|
2658
|
-
var detectLanguageSchema =
|
|
2659
|
-
languages:
|
|
2660
|
-
frameworks:
|
|
3181
|
+
import z20 from "zod";
|
|
3182
|
+
var detectLanguageSchema = z20.object({
|
|
3183
|
+
languages: z20.array(z20.object({ name: z20.string(), version: z20.string() })),
|
|
3184
|
+
frameworks: z20.array(z20.object({ name: z20.string(), version: z20.string() }))
|
|
2661
3185
|
});
|
|
2662
3186
|
var detectLanguage = () => runAgent({
|
|
2663
3187
|
instructions: [
|
|
@@ -2675,31 +3199,31 @@ var detectLanguage = () => runAgent({
|
|
|
2675
3199
|
});
|
|
2676
3200
|
|
|
2677
3201
|
// src/actions/analyzeCodebase.ts
|
|
2678
|
-
import
|
|
3202
|
+
import z21 from "zod";
|
|
2679
3203
|
var READONLY_TOOLS = [
|
|
2680
3204
|
"listFiles",
|
|
2681
3205
|
"changeDirectory",
|
|
2682
3206
|
"readFile",
|
|
2683
3207
|
"searchFiles"
|
|
2684
3208
|
];
|
|
2685
|
-
var ingestionAnalysisSchema =
|
|
2686
|
-
ingestionAnalysis:
|
|
2687
|
-
|
|
2688
|
-
name:
|
|
2689
|
-
paths:
|
|
3209
|
+
var ingestionAnalysisSchema = z21.object({
|
|
3210
|
+
ingestionAnalysis: z21.array(
|
|
3211
|
+
z21.object({
|
|
3212
|
+
name: z21.string(),
|
|
3213
|
+
paths: z21.array(z21.string()),
|
|
2690
3214
|
// indexable fields the agent found for this entity
|
|
2691
|
-
attributes:
|
|
3215
|
+
attributes: z21.array(z21.string())
|
|
2692
3216
|
})
|
|
2693
3217
|
)
|
|
2694
3218
|
});
|
|
2695
|
-
var searchImplementationAnalysisSchema =
|
|
2696
|
-
searchImplementationAnalysis:
|
|
3219
|
+
var searchImplementationAnalysisSchema = z21.object({
|
|
3220
|
+
searchImplementationAnalysis: z21.string()
|
|
2697
3221
|
});
|
|
2698
|
-
var verificationSchema =
|
|
2699
|
-
verification:
|
|
3222
|
+
var verificationSchema = z21.object({
|
|
3223
|
+
verification: z21.array(z21.string())
|
|
2700
3224
|
});
|
|
2701
3225
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
2702
|
-
var analyzeCodebaseSchema =
|
|
3226
|
+
var analyzeCodebaseSchema = z21.object({
|
|
2703
3227
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
2704
3228
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
2705
3229
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -2761,7 +3285,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2761
3285
|
// package.json
|
|
2762
3286
|
var package_default = {
|
|
2763
3287
|
name: "@algolia/wizard",
|
|
2764
|
-
version: "0.
|
|
3288
|
+
version: "0.11.0",
|
|
2765
3289
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2766
3290
|
type: "module",
|
|
2767
3291
|
engines: {
|
|
@@ -2809,10 +3333,10 @@ var package_default = {
|
|
|
2809
3333
|
dependencies: {
|
|
2810
3334
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2811
3335
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2812
|
-
"@algolia/cli": "^5.11.0",
|
|
2813
3336
|
"@hono/node-server": "^2.0.10",
|
|
2814
3337
|
"@segment/analytics-node": "^3.1.0",
|
|
2815
3338
|
ai: "^6.0.190",
|
|
3339
|
+
"cross-keychain": "^1.1.0",
|
|
2816
3340
|
dotenv: "^17.4.2",
|
|
2817
3341
|
hono: "^4.12.27",
|
|
2818
3342
|
ink: "^7.0.5",
|
|
@@ -2823,7 +3347,6 @@ var package_default = {
|
|
|
2823
3347
|
nanoid: "^5.1.15",
|
|
2824
3348
|
pino: "^10.3.1",
|
|
2825
3349
|
react: "^19.2.7",
|
|
2826
|
-
toml: "^4.1.1",
|
|
2827
3350
|
varlock: "^1.5.1",
|
|
2828
3351
|
zod: "^4.4.3",
|
|
2829
3352
|
zustand: "^5.0.14"
|
|
@@ -2881,8 +3404,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
2881
3404
|
}
|
|
2882
3405
|
|
|
2883
3406
|
// src/actions/confirmLanguage.ts
|
|
2884
|
-
import
|
|
2885
|
-
var confirmLanguageSchema =
|
|
3407
|
+
import z23 from "zod";
|
|
3408
|
+
var confirmLanguageSchema = z23.object({
|
|
2886
3409
|
languages: detectLanguageSchema.shape.languages
|
|
2887
3410
|
});
|
|
2888
3411
|
async function confirmLanguage(ctx) {
|
|
@@ -2903,8 +3426,8 @@ async function confirmLanguage(ctx) {
|
|
|
2903
3426
|
}
|
|
2904
3427
|
|
|
2905
3428
|
// src/actions/confirmFramework.ts
|
|
2906
|
-
import
|
|
2907
|
-
var confirmFrameworkSchema =
|
|
3429
|
+
import z24 from "zod";
|
|
3430
|
+
var confirmFrameworkSchema = z24.object({
|
|
2908
3431
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
2909
3432
|
});
|
|
2910
3433
|
var CURATED_FRAMEWORKS = [
|
|
@@ -3032,8 +3555,8 @@ async function promptUser(ctx, params) {
|
|
|
3032
3555
|
}
|
|
3033
3556
|
|
|
3034
3557
|
// src/actions/confirmEntities.ts
|
|
3035
|
-
import
|
|
3036
|
-
var confirmEntitiesSchema =
|
|
3558
|
+
import z25 from "zod";
|
|
3559
|
+
var confirmEntitiesSchema = z25.object({
|
|
3037
3560
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3038
3561
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3039
3562
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3103,15 +3626,15 @@ async function confirmEntities(ctx) {
|
|
|
3103
3626
|
}
|
|
3104
3627
|
|
|
3105
3628
|
// src/actions/review.ts
|
|
3106
|
-
import { z as
|
|
3107
|
-
var reviewSchema =
|
|
3629
|
+
import { z as z26 } from "zod";
|
|
3630
|
+
var reviewSchema = z26.object({
|
|
3108
3631
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3109
3632
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3110
3633
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3111
3634
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3112
|
-
summaryPoints:
|
|
3113
|
-
reviewPrompt:
|
|
3114
|
-
nextSteps:
|
|
3635
|
+
summaryPoints: z26.array(z26.string()),
|
|
3636
|
+
reviewPrompt: z26.string(),
|
|
3637
|
+
nextSteps: z26.array(z26.string())
|
|
3115
3638
|
});
|
|
3116
3639
|
function formatCompletedSteps(steps) {
|
|
3117
3640
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3162,7 +3685,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3162
3685
|
};
|
|
3163
3686
|
|
|
3164
3687
|
// src/actions/implement.ts
|
|
3165
|
-
import
|
|
3688
|
+
import z27 from "zod";
|
|
3166
3689
|
|
|
3167
3690
|
// src/lib/worktree.ts
|
|
3168
3691
|
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
@@ -3366,6 +3889,23 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3366
3889
|
function hasEnvVar(content, name) {
|
|
3367
3890
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3368
3891
|
}
|
|
3892
|
+
async function readEnvVar(worktreePath, name) {
|
|
3893
|
+
let content;
|
|
3894
|
+
try {
|
|
3895
|
+
content = await readFile8(join10(worktreePath, ".env"), "utf8");
|
|
3896
|
+
} catch (err) {
|
|
3897
|
+
if (err.code !== "ENOENT") throw err;
|
|
3898
|
+
return void 0;
|
|
3899
|
+
}
|
|
3900
|
+
const match = new RegExp(
|
|
3901
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3902
|
+
"m"
|
|
3903
|
+
).exec(content);
|
|
3904
|
+
if (!match) return void 0;
|
|
3905
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3906
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3907
|
+
return value;
|
|
3908
|
+
}
|
|
3369
3909
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3370
3910
|
const target = join10(worktreePath, ".env");
|
|
3371
3911
|
let existing = "";
|
|
@@ -3438,54 +3978,6 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3438
3978
|
}
|
|
3439
3979
|
}
|
|
3440
3980
|
|
|
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
3981
|
// src/lib/algoliaDocs.ts
|
|
3490
3982
|
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3491
3983
|
import { dirname as dirname8, join as join11 } from "node:path";
|
|
@@ -3552,50 +4044,34 @@ function shellQuote(value) {
|
|
|
3552
4044
|
}
|
|
3553
4045
|
|
|
3554
4046
|
// 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()
|
|
4047
|
+
var implementSchema = z27.object({
|
|
4048
|
+
filesChanged: z27.array(z27.string()),
|
|
4049
|
+
summary: z27.string(),
|
|
4050
|
+
worktreePath: z27.string().optional(),
|
|
4051
|
+
ingestCommand: z27.string().optional(),
|
|
4052
|
+
ingestScriptRan: z27.boolean().optional(),
|
|
4053
|
+
ingestRecordCount: z27.number().optional(),
|
|
4054
|
+
ingestDurationMs: z27.number().optional(),
|
|
4055
|
+
ingestionSource: z27.enum(["local", "fileUpload", "generated"]),
|
|
4056
|
+
searchEnvVars: z27.array(
|
|
4057
|
+
z27.object({
|
|
4058
|
+
name: z27.string(),
|
|
4059
|
+
value: z27.string()
|
|
3582
4060
|
})
|
|
3583
4061
|
).optional()
|
|
3584
4062
|
});
|
|
3585
|
-
var implementationOutputSchema =
|
|
3586
|
-
summary:
|
|
3587
|
-
// Ingestion only:
|
|
3588
|
-
//
|
|
3589
|
-
//
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3593
|
-
entrypoint: z24.string().optional()
|
|
4063
|
+
var implementationOutputSchema = z27.object({
|
|
4064
|
+
summary: z27.string(),
|
|
4065
|
+
// Ingestion only: a structured pair the wizard turns into an argv, never a
|
|
4066
|
+
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
4067
|
+
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
4068
|
+
runtime: z27.enum(INGEST_RUNTIMES).optional(),
|
|
4069
|
+
entrypoint: z27.string().optional()
|
|
3594
4070
|
});
|
|
3595
|
-
var verificationOutputSchema =
|
|
3596
|
-
summary:
|
|
3597
|
-
sufficient:
|
|
3598
|
-
additionalInstructions:
|
|
4071
|
+
var verificationOutputSchema = z27.object({
|
|
4072
|
+
summary: z27.string(),
|
|
4073
|
+
sufficient: z27.boolean(),
|
|
4074
|
+
additionalInstructions: z27.string().optional()
|
|
3599
4075
|
});
|
|
3600
4076
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3601
4077
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -3638,22 +4114,42 @@ function publicEnvPrefix(language) {
|
|
|
3638
4114
|
}
|
|
3639
4115
|
return "PUBLIC_";
|
|
3640
4116
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
4117
|
+
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
4118
|
+
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
4119
|
+
var INDEX_VAR_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
4120
|
+
function appIdVar(language) {
|
|
4121
|
+
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
4122
|
+
}
|
|
4123
|
+
function searchKeyVar(language) {
|
|
4124
|
+
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
4125
|
+
}
|
|
4126
|
+
function searchIndexVar(language) {
|
|
4127
|
+
return `${publicEnvPrefix(language)}${INDEX_VAR_SUFFIX}`;
|
|
4128
|
+
}
|
|
4129
|
+
function searchEnvVars(language, index, appId, searchKey) {
|
|
3643
4130
|
return [
|
|
3644
4131
|
{
|
|
3645
|
-
name:
|
|
4132
|
+
name: appIdVar(language),
|
|
3646
4133
|
value: appId ?? "<your-algolia-app-id>"
|
|
3647
4134
|
},
|
|
3648
4135
|
{
|
|
3649
|
-
name:
|
|
4136
|
+
name: searchKeyVar(language),
|
|
3650
4137
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
4138
|
+
},
|
|
4139
|
+
// Wizard-supplied rather than written into the generated code, because an
|
|
4140
|
+
// agent that retypes the name (appending the project name, re-casing it)
|
|
4141
|
+
// leaves the UI querying an index that does not exist.
|
|
4142
|
+
{
|
|
4143
|
+
name: searchIndexVar(language),
|
|
4144
|
+
value: index
|
|
3651
4145
|
}
|
|
3652
4146
|
];
|
|
3653
4147
|
}
|
|
3654
4148
|
function baseInstructions(input) {
|
|
3655
4149
|
return [
|
|
3656
|
-
|
|
4150
|
+
// Agents have renamed this (e.g. appending the project name), which the
|
|
4151
|
+
// index-scoped keys then reject with a 403.
|
|
4152
|
+
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
3657
4153
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
3658
4154
|
"Make minimal, idiomatic changes; do not touch unrelated code."
|
|
3659
4155
|
];
|
|
@@ -3667,9 +4163,6 @@ function sourceSpecificInstructions(input) {
|
|
|
3667
4163
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
3668
4164
|
],
|
|
3669
4165
|
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
4166
|
`Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
3674
4167
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
3675
4168
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
@@ -3690,6 +4183,7 @@ function ingestionInstructions(input) {
|
|
|
3690
4183
|
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3691
4184
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3692
4185
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
4186
|
+
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
3693
4187
|
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
3694
4188
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
3695
4189
|
getNamedDoc("save-records", "js"),
|
|
@@ -3707,16 +4201,18 @@ function searchInstructions(input) {
|
|
|
3707
4201
|
`Build the search UI for ${input.uiFramework}.`,
|
|
3708
4202
|
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3709
4203
|
doc,
|
|
3710
|
-
`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
|
|
3711
|
-
|
|
3712
|
-
//
|
|
3713
|
-
//
|
|
3714
|
-
|
|
3715
|
-
//
|
|
3716
|
-
|
|
3717
|
-
//
|
|
4204
|
+
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
|
|
4205
|
+
`Read the index name from the ${searchIndexVar(input.language)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
|
|
4206
|
+
// The key is provisioned only after verification passes, so the agent never
|
|
4207
|
+
// sees one. It must also leave .env alone: the wizard reads that file to
|
|
4208
|
+
// decide whether a key already exists, and an agent-invented value there
|
|
4209
|
+
// would be reused as if it were real.
|
|
4210
|
+
`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.`,
|
|
4211
|
+
// Not the agent's to rename: the wizard writes these exact names into
|
|
4212
|
+
// ".env" right after this step, so a renamed prefix would leave the code
|
|
3718
4213
|
// reading a var the wizard never wrote.
|
|
3719
4214
|
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4215
|
+
"Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3720
4216
|
'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
4217
|
"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
4218
|
];
|
|
@@ -3858,6 +4354,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3858
4354
|
}
|
|
3859
4355
|
}
|
|
3860
4356
|
const targetIndex = selected?.selection;
|
|
4357
|
+
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
3861
4358
|
await assertGitRepoWithHead(repoRoot);
|
|
3862
4359
|
if (await isWorkingTreeDirty(repoRoot)) {
|
|
3863
4360
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
@@ -3866,17 +4363,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3866
4363
|
const confirmed2 = normalized.confirmedEntities;
|
|
3867
4364
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3868
4365
|
let appId;
|
|
3869
|
-
let searchKey;
|
|
3870
4366
|
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
|
-
}
|
|
4367
|
+
appId = (await requireApplication()).id;
|
|
3880
4368
|
}
|
|
3881
4369
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3882
4370
|
try {
|
|
@@ -3908,17 +4396,32 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3908
4396
|
targetIndex,
|
|
3909
4397
|
language,
|
|
3910
4398
|
appId,
|
|
3911
|
-
|
|
3912
|
-
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4399
|
+
searchEnvVars: searchEnvVars(language, targetIndex, appId),
|
|
3913
4400
|
ingestDir: INGEST_DIR,
|
|
3914
4401
|
ingestionSource,
|
|
3915
4402
|
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
4403
|
uiFramework: detectUiFramework(language)
|
|
3919
4404
|
};
|
|
3920
4405
|
const summaries = [];
|
|
3921
4406
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4407
|
+
let envSearchKey;
|
|
4408
|
+
let envAppIdMismatch = false;
|
|
4409
|
+
if (useCases.includes("search") && appId) {
|
|
4410
|
+
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
4411
|
+
if (envAppId === appId) {
|
|
4412
|
+
envSearchKey = await readEnvVar(worktree, searchKeyVar(language));
|
|
4413
|
+
} else if (envAppId) {
|
|
4414
|
+
envAppIdMismatch = true;
|
|
4415
|
+
summaries.push(
|
|
4416
|
+
`\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.`
|
|
4417
|
+
);
|
|
4418
|
+
logger.warn(
|
|
4419
|
+
{ envAppId, appId },
|
|
4420
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
3922
4425
|
let agentRuns = 0;
|
|
3923
4426
|
let ingestRuntime;
|
|
3924
4427
|
let ingestEntrypoint;
|
|
@@ -3979,7 +4482,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3979
4482
|
messages: []
|
|
3980
4483
|
}) === true;
|
|
3981
4484
|
if (runNow) {
|
|
3982
|
-
const
|
|
4485
|
+
const ingestApp = await requireApplication();
|
|
4486
|
+
const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
|
|
3983
4487
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3984
4488
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3985
4489
|
runtime: ingestRuntime,
|
|
@@ -3991,8 +4495,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3991
4495
|
ingestRuntime,
|
|
3992
4496
|
ingestEntrypoint,
|
|
3993
4497
|
{
|
|
3994
|
-
[APP_ID_VAR]:
|
|
3995
|
-
[API_KEY_VAR]:
|
|
4498
|
+
[APP_ID_VAR]: ingestApp.id,
|
|
4499
|
+
[API_KEY_VAR]: writeKey,
|
|
4500
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
3996
4501
|
}
|
|
3997
4502
|
);
|
|
3998
4503
|
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
@@ -4061,8 +4566,6 @@ ${run2.output}` : status;
|
|
|
4061
4566
|
);
|
|
4062
4567
|
}
|
|
4063
4568
|
await ctx.requestUserInput({
|
|
4064
|
-
// No question being asked here, just an acknowledgement — the
|
|
4065
|
-
// continue/decline hints below already say "continue".
|
|
4066
4569
|
prompt: "",
|
|
4067
4570
|
promptType: "enterToContinue",
|
|
4068
4571
|
options: [],
|
|
@@ -4111,7 +4614,34 @@ ${run2.output}` : status;
|
|
|
4111
4614
|
}
|
|
4112
4615
|
extraInstructions = verificationRetryInstructions(verification);
|
|
4113
4616
|
}
|
|
4114
|
-
|
|
4617
|
+
let searchKey;
|
|
4618
|
+
let searchKeyError;
|
|
4619
|
+
if (appId) {
|
|
4620
|
+
try {
|
|
4621
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
4622
|
+
targetIndex,
|
|
4623
|
+
appId,
|
|
4624
|
+
envSearchKey
|
|
4625
|
+
);
|
|
4626
|
+
searchKey = resolved2.key;
|
|
4627
|
+
summaries.push(
|
|
4628
|
+
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4629
|
+
);
|
|
4630
|
+
} catch (err) {
|
|
4631
|
+
searchKeyError = err.message;
|
|
4632
|
+
logger.warn(
|
|
4633
|
+
{ err: searchKeyError },
|
|
4634
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4635
|
+
);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
finalSearchEnvVars = searchEnvVars(
|
|
4639
|
+
language,
|
|
4640
|
+
targetIndex,
|
|
4641
|
+
appId,
|
|
4642
|
+
searchKey
|
|
4643
|
+
);
|
|
4644
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4115
4645
|
(v) => !v.value.startsWith("<")
|
|
4116
4646
|
);
|
|
4117
4647
|
if (resolvedSearchEnvVars.length > 0) {
|
|
@@ -4122,13 +4652,29 @@ ${run2.output}` : status;
|
|
|
4122
4652
|
if (written.length > 0) {
|
|
4123
4653
|
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4124
4654
|
}
|
|
4655
|
+
const stale = [];
|
|
4656
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4657
|
+
if (written.includes(v.name)) continue;
|
|
4658
|
+
const current = await readEnvVar(worktree, v.name);
|
|
4659
|
+
if (current && current !== v.value) stale.push(v);
|
|
4660
|
+
}
|
|
4661
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4662
|
+
summaries.push(
|
|
4663
|
+
`\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.`
|
|
4664
|
+
);
|
|
4665
|
+
logger.warn(
|
|
4666
|
+
{ vars: stale.map((v) => v.name) },
|
|
4667
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4668
|
+
);
|
|
4669
|
+
}
|
|
4125
4670
|
}
|
|
4126
|
-
const unresolvedSearchEnvVars =
|
|
4671
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4127
4672
|
(v) => v.value.startsWith("<")
|
|
4128
4673
|
);
|
|
4129
4674
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4130
4675
|
summaries.push(
|
|
4131
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4676
|
+
`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.
|
|
4677
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4132
4678
|
);
|
|
4133
4679
|
}
|
|
4134
4680
|
} else {
|
|
@@ -4160,7 +4706,7 @@ ${run2.output}` : status;
|
|
|
4160
4706
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4161
4707
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4162
4708
|
} : {},
|
|
4163
|
-
...useCases.includes("search") ? { searchEnvVars:
|
|
4709
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4164
4710
|
};
|
|
4165
4711
|
} finally {
|
|
4166
4712
|
process.chdir(repoRoot);
|
|
@@ -4203,8 +4749,8 @@ var defaultWorkflow = {
|
|
|
4203
4749
|
defineStep({
|
|
4204
4750
|
id: "select-index",
|
|
4205
4751
|
title: "Set up index",
|
|
4206
|
-
outputSchema:
|
|
4207
|
-
selection:
|
|
4752
|
+
outputSchema: z28.object({
|
|
4753
|
+
selection: z28.string()
|
|
4208
4754
|
}),
|
|
4209
4755
|
run: (ctx) => selectIndexStep(ctx)
|
|
4210
4756
|
}),
|
|
@@ -4446,7 +4992,11 @@ Options:
|
|
|
4446
4992
|
--no-telemetry Send no telemetry or analytics for this run.
|
|
4447
4993
|
--reset-on-run Wipe this project's wizard state (run state, AI consent,
|
|
4448
4994
|
worktrees) before starting, so the run behaves like a
|
|
4449
|
-
first-ever run.
|
|
4995
|
+
first-ever run. Also drops every API key the wizard has
|
|
4996
|
+
stored in your keychain (or, where the platform has none,
|
|
4997
|
+
the encrypted file it falls back to \u2014 see CONTRIBUTING.md),
|
|
4998
|
+
for this project and any other, so later runs create new
|
|
4999
|
+
ones. Your Algolia login is not touched.
|
|
4450
5000
|
-h, --help Print this message.`;
|
|
4451
5001
|
function parseCliArgs(argv) {
|
|
4452
5002
|
const positionals = [];
|
|
@@ -4487,6 +5037,7 @@ import { join as join12 } from "node:path";
|
|
|
4487
5037
|
var KEEP = ["wizard.log"];
|
|
4488
5038
|
async function resetProjectState() {
|
|
4489
5039
|
const dir = stateDir();
|
|
5040
|
+
await forgetResolvedKeys();
|
|
4490
5041
|
let entries;
|
|
4491
5042
|
try {
|
|
4492
5043
|
entries = await readdir4(dir);
|
|
@@ -4495,7 +5046,9 @@ async function resetProjectState() {
|
|
|
4495
5046
|
}
|
|
4496
5047
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4497
5048
|
await Promise.all(
|
|
4498
|
-
targets.map(
|
|
5049
|
+
targets.map(
|
|
5050
|
+
(name) => rm2(join12(dir, name), { recursive: true, force: true })
|
|
5051
|
+
)
|
|
4499
5052
|
);
|
|
4500
5053
|
return { dir, removed: targets };
|
|
4501
5054
|
}
|
|
@@ -4503,6 +5056,7 @@ async function resetProjectState() {
|
|
|
4503
5056
|
// src/main.tsx
|
|
4504
5057
|
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
4505
5058
|
async function startup() {
|
|
5059
|
+
setProjectRoot(process.cwd());
|
|
4506
5060
|
let args;
|
|
4507
5061
|
try {
|
|
4508
5062
|
args = parseCliArgs(process.argv.slice(2));
|
|
@@ -4550,31 +5104,38 @@ ${formatStepList(workflow)}`);
|
|
|
4550
5104
|
}
|
|
4551
5105
|
async function run(workflow) {
|
|
4552
5106
|
const store = useWizard.getState();
|
|
4553
|
-
|
|
5107
|
+
const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
5108
|
+
await store.waitForStart();
|
|
4554
5109
|
let user = await getUser();
|
|
4555
5110
|
if (!user) {
|
|
4556
|
-
|
|
4557
|
-
instance.cleanup();
|
|
5111
|
+
store.beginAuth();
|
|
4558
5112
|
try {
|
|
4559
5113
|
await runAuthLogin();
|
|
4560
5114
|
} catch (err) {
|
|
4561
|
-
|
|
5115
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
5116
|
+
await instance.waitUntilExit();
|
|
4562
5117
|
process.exit(1);
|
|
4563
5118
|
}
|
|
4564
|
-
|
|
5119
|
+
store.endAuth();
|
|
4565
5120
|
user = await getUser();
|
|
4566
5121
|
if (!user) {
|
|
4567
5122
|
store.setError(
|
|
4568
|
-
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
|
|
5123
|
+
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
|
|
4569
5124
|
);
|
|
4570
5125
|
await instance.waitUntilExit();
|
|
4571
5126
|
process.exit(1);
|
|
4572
5127
|
}
|
|
4573
5128
|
}
|
|
4574
5129
|
store.setUser(user);
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
5130
|
+
let app;
|
|
5131
|
+
try {
|
|
5132
|
+
app = await ensureApplication();
|
|
5133
|
+
} catch (err) {
|
|
5134
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
5135
|
+
await instance.waitUntilExit();
|
|
5136
|
+
process.exit(1);
|
|
5137
|
+
}
|
|
5138
|
+
runWorkflow(workflow, app.id);
|
|
4578
5139
|
}
|
|
4579
5140
|
var started = await startup();
|
|
4580
5141
|
if (typeof started === "number") {
|