@algolia/wizard 0.6.0-rc.51.28 → 0.6.0-rc.53.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +1039 -1681
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +1 -3
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-dart.md +0 -74
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
- package/docs/algolia-sdk/save-records-swift.md +0 -88
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 Box14, Text as Text14, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -12,20 +12,54 @@ import { nanoid } from "nanoid";
|
|
|
12
12
|
|
|
13
13
|
// src/lib/algoliaCli.ts
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function algoliaCliEntry() {
|
|
18
|
-
return require2.resolve("@algolia/cli/bin/run.js");
|
|
15
|
+
function npxArgs(args) {
|
|
16
|
+
return ["--yes", "@algolia/cli@latest", ...args];
|
|
19
17
|
}
|
|
20
|
-
|
|
18
|
+
var shell = process.platform === "win32";
|
|
19
|
+
function lineSplitter(emit) {
|
|
20
|
+
let buffer = "";
|
|
21
|
+
return {
|
|
22
|
+
push(chunk) {
|
|
23
|
+
buffer += chunk;
|
|
24
|
+
const lines = buffer.split("\n");
|
|
25
|
+
buffer = lines.pop() ?? "";
|
|
26
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
27
|
+
},
|
|
28
|
+
flush() {
|
|
29
|
+
if (buffer) emit(buffer.replace(/\r$/, ""));
|
|
30
|
+
buffer = "";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var wizardSink = (stream, line) => {
|
|
35
|
+
if (!line.trim()) return;
|
|
36
|
+
useWizard.getState().pushCliOutput(stream, line);
|
|
37
|
+
};
|
|
38
|
+
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
39
|
+
const store2 = useWizard.getState();
|
|
40
|
+
const logId = store2.logStart("tool", `algolia ${args.join(" ")}`);
|
|
21
41
|
return new Promise((resolve4, reject) => {
|
|
22
|
-
const child = spawn(
|
|
42
|
+
const child = spawn("npx", npxArgs(args), { shell });
|
|
23
43
|
let stdout = "";
|
|
24
44
|
let stderr = "";
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
const splitters = {
|
|
46
|
+
stdout: lineSplitter((line) => onOutput?.("stdout", line)),
|
|
47
|
+
stderr: lineSplitter((line) => onOutput?.("stderr", line))
|
|
48
|
+
};
|
|
49
|
+
child.stdout.on("data", (chunk) => {
|
|
50
|
+
const text = String(chunk);
|
|
51
|
+
stdout += text;
|
|
52
|
+
splitters.stdout.push(text);
|
|
53
|
+
});
|
|
54
|
+
child.stderr.on("data", (chunk) => {
|
|
55
|
+
const text = String(chunk);
|
|
56
|
+
stderr += text;
|
|
57
|
+
splitters.stderr.push(text);
|
|
58
|
+
});
|
|
27
59
|
child.on("error", reject);
|
|
28
60
|
child.on("close", (code) => {
|
|
61
|
+
splitters.stdout.flush();
|
|
62
|
+
splitters.stderr.flush();
|
|
29
63
|
if (code === 0) {
|
|
30
64
|
resolve4(stdout);
|
|
31
65
|
} else {
|
|
@@ -37,7 +71,16 @@ function runAlgoliaCli(args) {
|
|
|
37
71
|
);
|
|
38
72
|
}
|
|
39
73
|
});
|
|
40
|
-
})
|
|
74
|
+
}).then(
|
|
75
|
+
(out) => {
|
|
76
|
+
useWizard.getState().logEnd(logId, "success");
|
|
77
|
+
return out;
|
|
78
|
+
},
|
|
79
|
+
(err) => {
|
|
80
|
+
useWizard.getState().logEnd(logId, "error");
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
);
|
|
41
84
|
}
|
|
42
85
|
async function getUser() {
|
|
43
86
|
let raw;
|
|
@@ -52,13 +95,21 @@ async function getUser() {
|
|
|
52
95
|
return null;
|
|
53
96
|
}
|
|
54
97
|
}
|
|
98
|
+
function needsInteractiveTerminal(err) {
|
|
99
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
100
|
+
return /non-interactive mode/i.test(message);
|
|
101
|
+
}
|
|
55
102
|
function runAuthLogin() {
|
|
103
|
+
return runAlgoliaCli(["auth", "login", "--default"], {
|
|
104
|
+
onOutput: wizardSink
|
|
105
|
+
}).then(() => void 0);
|
|
106
|
+
}
|
|
107
|
+
function runAuthLoginInTerminal() {
|
|
56
108
|
return new Promise((resolve4, reject) => {
|
|
57
|
-
const child = spawn(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
109
|
+
const child = spawn("npx", npxArgs(["auth", "login", "--default"]), {
|
|
110
|
+
shell,
|
|
111
|
+
stdio: "inherit"
|
|
112
|
+
});
|
|
62
113
|
child.on("error", reject);
|
|
63
114
|
child.on("close", (code) => {
|
|
64
115
|
if (code === 0) resolve4();
|
|
@@ -171,6 +222,7 @@ function describeInputValue(value) {
|
|
|
171
222
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
223
|
}
|
|
173
224
|
var NOTICE_INTERVAL_MS = 2e3;
|
|
225
|
+
var CLI_OUTPUT_LIMIT = 200;
|
|
174
226
|
var useWizard = create((set, get) => ({
|
|
175
227
|
phase: "idle",
|
|
176
228
|
homeScreen: "home",
|
|
@@ -182,10 +234,23 @@ var useWizard = create((set, get) => ({
|
|
|
182
234
|
notices: [],
|
|
183
235
|
_noticeQueue: [],
|
|
184
236
|
_noticeTimer: null,
|
|
237
|
+
cliOutput: [],
|
|
238
|
+
targetIndex: null,
|
|
185
239
|
logs: [],
|
|
186
240
|
error: null,
|
|
187
241
|
inputReq: null,
|
|
188
242
|
_resolve: null,
|
|
243
|
+
// Brackets a CLI subprocess that needs the screen. `endAuth` must land back
|
|
244
|
+
// on exactly 'idle': `confirmStart` is a no-op from any other phase and
|
|
245
|
+
// `waitForStart` resolves on any non-idle phase, so ending anywhere else
|
|
246
|
+
// either skips the welcome screen or ignores its spacebar forever.
|
|
247
|
+
beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
|
|
248
|
+
// Re-enters the auth phase *keeping* the CLI output on screen, for returning
|
|
249
|
+
// from a prompt raised mid-sign-in (`submitInput` leaves the phase at
|
|
250
|
+
// 'running'). Clearing here would blank the login output the user was reading
|
|
251
|
+
// the instant they answered.
|
|
252
|
+
resumeAuth: () => set({ phase: "authenticating" }),
|
|
253
|
+
endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "idle" } : {}),
|
|
189
254
|
// Advances past the welcome screen. Only meaningful from 'idle' — once the
|
|
190
255
|
// workflow is running there's nothing left to confirm.
|
|
191
256
|
// Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
|
|
@@ -220,7 +285,13 @@ var useWizard = create((set, get) => ({
|
|
|
220
285
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
221
286
|
setActiveStep: (index) => {
|
|
222
287
|
get()._clearNoticeQueue();
|
|
223
|
-
set({
|
|
288
|
+
set({
|
|
289
|
+
phase: "running",
|
|
290
|
+
currentStepIndex: index,
|
|
291
|
+
output: "",
|
|
292
|
+
notices: [],
|
|
293
|
+
cliOutput: []
|
|
294
|
+
});
|
|
224
295
|
},
|
|
225
296
|
setUser: (user2) => set({ user: user2 }),
|
|
226
297
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
@@ -261,6 +332,16 @@ var useWizard = create((set, get) => ({
|
|
|
261
332
|
get()._clearNoticeQueue();
|
|
262
333
|
set({ notices: [] });
|
|
263
334
|
},
|
|
335
|
+
// Unthrottled, unlike `pushNotice`: these lines arrive at whatever rate the
|
|
336
|
+
// subprocess emits them, and holding them back would land output after the
|
|
337
|
+
// command it belongs to has already exited.
|
|
338
|
+
pushCliOutput: (stream, text) => set((s) => ({
|
|
339
|
+
cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
|
|
340
|
+
-CLI_OUTPUT_LIMIT
|
|
341
|
+
)
|
|
342
|
+
})),
|
|
343
|
+
clearCliOutput: () => set({ cliOutput: [] }),
|
|
344
|
+
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
264
345
|
logStart: (kind, name, input) => {
|
|
265
346
|
const id = nanoid();
|
|
266
347
|
set((s) => ({
|
|
@@ -305,6 +386,8 @@ var useWizard = create((set, get) => ({
|
|
|
305
386
|
currentStepIndex: 0,
|
|
306
387
|
output: "",
|
|
307
388
|
notices: [],
|
|
389
|
+
cliOutput: [],
|
|
390
|
+
targetIndex: null,
|
|
308
391
|
logs: [],
|
|
309
392
|
error: null,
|
|
310
393
|
inputReq: null,
|
|
@@ -313,16 +396,88 @@ var useWizard = create((set, get) => ({
|
|
|
313
396
|
}
|
|
314
397
|
}));
|
|
315
398
|
|
|
399
|
+
// src/ui/CliOutput.tsx
|
|
400
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
401
|
+
|
|
402
|
+
// src/ui/theme.ts
|
|
403
|
+
var MARKER = {
|
|
404
|
+
pending: "\u25CB",
|
|
405
|
+
running: "\u25D0",
|
|
406
|
+
done: "\u2713",
|
|
407
|
+
error: "\u2716"
|
|
408
|
+
};
|
|
409
|
+
var BRAND = "#003DFF";
|
|
410
|
+
var SECONDARY = "#5468FF";
|
|
411
|
+
var DANGER = "#F86E7E";
|
|
412
|
+
var COLORS = {
|
|
413
|
+
brand: BRAND,
|
|
414
|
+
primary: "#E6EDF3",
|
|
415
|
+
secondary: SECONDARY,
|
|
416
|
+
strong: "#FFFFFF",
|
|
417
|
+
muted: "#8B949E",
|
|
418
|
+
dim: "#484F58",
|
|
419
|
+
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
420
|
+
badge: "#E3B341",
|
|
421
|
+
danger: DANGER,
|
|
422
|
+
success: "#4ADE80",
|
|
423
|
+
bg: {
|
|
424
|
+
main: "#0B0E14",
|
|
425
|
+
sidebar: "#14171E"
|
|
426
|
+
},
|
|
427
|
+
border: "#30363D",
|
|
428
|
+
accent: "#76A0FF",
|
|
429
|
+
status: {
|
|
430
|
+
pending: "gray",
|
|
431
|
+
running: "#76A0FF",
|
|
432
|
+
done: "#4ADE80",
|
|
433
|
+
error: DANGER
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/ui/CliOutput.tsx
|
|
438
|
+
import { jsxs } from "react/jsx-runtime";
|
|
439
|
+
var CLI_MARKER = "\u203A";
|
|
440
|
+
var RESERVED_ROWS = 16;
|
|
441
|
+
var MAX_LINES = 12;
|
|
442
|
+
function CliOutput() {
|
|
443
|
+
const cliOutput = useWizard((s) => s.cliOutput);
|
|
444
|
+
const { rows } = useWindowSize();
|
|
445
|
+
if (!cliOutput.length) return null;
|
|
446
|
+
const budget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_LINES);
|
|
447
|
+
const visible = cliOutput.slice(-budget);
|
|
448
|
+
const hidden = cliOutput.length - visible.length;
|
|
449
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
450
|
+
hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
|
|
451
|
+
"\u2191 ",
|
|
452
|
+
hidden,
|
|
453
|
+
" earlier line(s)"
|
|
454
|
+
] }),
|
|
455
|
+
visible.map((line) => /* @__PURE__ */ jsxs(
|
|
456
|
+
Text,
|
|
457
|
+
{
|
|
458
|
+
color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
|
|
459
|
+
wrap: "truncate",
|
|
460
|
+
children: [
|
|
461
|
+
CLI_MARKER,
|
|
462
|
+
" ",
|
|
463
|
+
line.text
|
|
464
|
+
]
|
|
465
|
+
},
|
|
466
|
+
line.id
|
|
467
|
+
))
|
|
468
|
+
] });
|
|
469
|
+
}
|
|
470
|
+
|
|
316
471
|
// src/ui/Notices.tsx
|
|
317
|
-
import { Box as
|
|
472
|
+
import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
|
|
318
473
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
319
474
|
|
|
320
475
|
// src/ui/Table.tsx
|
|
321
|
-
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
476
|
+
import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
|
|
322
477
|
import { useEffect, useRef, useState } from "react";
|
|
323
478
|
import { jsx } from "react/jsx-runtime";
|
|
324
479
|
function Table({ columns, rows }) {
|
|
325
|
-
const { columns: termCols } =
|
|
480
|
+
const { columns: termCols } = useWindowSize2();
|
|
326
481
|
const ref = useRef(null);
|
|
327
482
|
const [width, setWidth] = useState(0);
|
|
328
483
|
useEffect(() => {
|
|
@@ -330,7 +485,7 @@ function Table({ columns, rows }) {
|
|
|
330
485
|
}, [termCols, columns, rows]);
|
|
331
486
|
if (rows.length === 0) return null;
|
|
332
487
|
const lines = formatTable(columns, rows, width || void 0);
|
|
333
|
-
return /* @__PURE__ */ jsx(
|
|
488
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
334
489
|
}
|
|
335
490
|
function formatTable(columns, rows, width) {
|
|
336
491
|
const natural = columns.map(
|
|
@@ -370,45 +525,10 @@ function resize(widths, budget) {
|
|
|
370
525
|
}
|
|
371
526
|
var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
|
|
372
527
|
|
|
373
|
-
// src/ui/theme.ts
|
|
374
|
-
var MARKER = {
|
|
375
|
-
pending: "\u25CB",
|
|
376
|
-
running: "\u25D0",
|
|
377
|
-
done: "\u2713",
|
|
378
|
-
error: "\u2716"
|
|
379
|
-
};
|
|
380
|
-
var BRAND = "#003DFF";
|
|
381
|
-
var SECONDARY = "#5468FF";
|
|
382
|
-
var DANGER = "#F86E7E";
|
|
383
|
-
var COLORS = {
|
|
384
|
-
brand: BRAND,
|
|
385
|
-
primary: "#E6EDF3",
|
|
386
|
-
secondary: SECONDARY,
|
|
387
|
-
strong: "#FFFFFF",
|
|
388
|
-
muted: "#8B949E",
|
|
389
|
-
dim: "#484F58",
|
|
390
|
-
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
391
|
-
badge: "#E3B341",
|
|
392
|
-
danger: DANGER,
|
|
393
|
-
success: "#4ADE80",
|
|
394
|
-
bg: {
|
|
395
|
-
main: "#0B0E14",
|
|
396
|
-
sidebar: "#14171E"
|
|
397
|
-
},
|
|
398
|
-
border: "#30363D",
|
|
399
|
-
accent: "#76A0FF",
|
|
400
|
-
status: {
|
|
401
|
-
pending: "gray",
|
|
402
|
-
running: "#76A0FF",
|
|
403
|
-
done: "#4ADE80",
|
|
404
|
-
error: DANGER
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
528
|
// src/ui/Notices.tsx
|
|
409
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
529
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
410
530
|
var AGENT_MARKER = "\u2726";
|
|
411
|
-
var
|
|
531
|
+
var RESERVED_ROWS2 = 14;
|
|
412
532
|
var PANEL_TEXT_WIDTH = 45;
|
|
413
533
|
function messageLineCount(text) {
|
|
414
534
|
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
|
|
@@ -422,7 +542,7 @@ function noticeLineCount(notice) {
|
|
|
422
542
|
return messageLines + tableLines;
|
|
423
543
|
}
|
|
424
544
|
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
-
const budget = Math.max(windowRows -
|
|
545
|
+
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
426
546
|
let used = 0;
|
|
427
547
|
let count = 0;
|
|
428
548
|
for (let i = notices.length - 1; i >= 0; i--) {
|
|
@@ -455,7 +575,7 @@ function parseHex(hex) {
|
|
|
455
575
|
}
|
|
456
576
|
function Notices() {
|
|
457
577
|
const notices = useWizard((s) => s.notices);
|
|
458
|
-
const { rows: windowRows } =
|
|
578
|
+
const { rows: windowRows } = useWindowSize3();
|
|
459
579
|
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
580
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
581
|
useEffect2(() => {
|
|
@@ -472,14 +592,14 @@ function Notices() {
|
|
|
472
592
|
}, []);
|
|
473
593
|
if (!visible.length) return null;
|
|
474
594
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
-
return /* @__PURE__ */ jsx2(
|
|
595
|
+
return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
596
|
const isLatest = i === visible.length - 1;
|
|
477
|
-
return /* @__PURE__ */
|
|
597
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
478
598
|
notice.messages?.map((m, j) => {
|
|
479
599
|
const line = typeof m === "string" ? { text: m } : m;
|
|
480
600
|
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
-
return /* @__PURE__ */
|
|
482
|
-
|
|
601
|
+
return /* @__PURE__ */ jsxs2(
|
|
602
|
+
Text3,
|
|
483
603
|
{
|
|
484
604
|
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
605
|
bold: line.bold,
|
|
@@ -497,37 +617,37 @@ function Notices() {
|
|
|
497
617
|
}
|
|
498
618
|
|
|
499
619
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
620
|
+
import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
501
621
|
import TextInput from "ink-text-input";
|
|
502
622
|
import { useState as useState4 } from "react";
|
|
503
623
|
|
|
504
624
|
// src/ui/NextAction.tsx
|
|
505
|
-
import { Box as
|
|
506
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
625
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
626
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
507
627
|
function NextAction({
|
|
508
628
|
action,
|
|
509
629
|
keyHint,
|
|
510
630
|
hierarchy = "primary"
|
|
511
631
|
}) {
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx3(
|
|
514
|
-
hierarchy === "secondary" && /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */ jsx3(
|
|
516
|
-
/* @__PURE__ */ jsx3(
|
|
632
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
633
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
634
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
635
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
636
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
517
637
|
] }),
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */ jsx3(
|
|
520
|
-
/* @__PURE__ */ jsx3(
|
|
521
|
-
/* @__PURE__ */ jsx3(
|
|
522
|
-
/* @__PURE__ */ jsx3(
|
|
638
|
+
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
639
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
|
|
640
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
|
|
641
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
|
|
642
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
|
|
523
643
|
] })
|
|
524
644
|
] });
|
|
525
645
|
}
|
|
526
646
|
|
|
527
647
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
648
|
+
import { Box as Box5, Text as Text5, measureElement as measureElement2, useInput, useWindowSize as useWindowSize4 } from "ink";
|
|
529
649
|
import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
530
|
-
import { jsx as jsx4, jsxs as
|
|
650
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
531
651
|
var CANCEL = "cancel";
|
|
532
652
|
var ARROW_WIDTH = 4;
|
|
533
653
|
var COLUMN_GAP = 2;
|
|
@@ -564,7 +684,7 @@ function SelectPrompt({
|
|
|
564
684
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
685
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
686
|
const containerRef = useRef2(null);
|
|
567
|
-
const { columns } =
|
|
687
|
+
const { columns } = useWindowSize4();
|
|
568
688
|
const [width, setWidth] = useState3(columns);
|
|
569
689
|
useLayoutEffect(() => {
|
|
570
690
|
if (containerRef.current) {
|
|
@@ -609,53 +729,53 @@ function SelectPrompt({
|
|
|
609
729
|
}
|
|
610
730
|
}
|
|
611
731
|
});
|
|
612
|
-
return /* @__PURE__ */ jsx4(
|
|
613
|
-
error && /* @__PURE__ */ jsx4(
|
|
614
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx4(
|
|
732
|
+
return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
|
|
733
|
+
error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
|
|
734
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
615
735
|
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
616
|
-
/* @__PURE__ */
|
|
617
|
-
question && /* @__PURE__ */ jsx4(
|
|
618
|
-
helpText && /* @__PURE__ */ jsx4(
|
|
736
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
737
|
+
question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
|
|
738
|
+
helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
|
|
619
739
|
] }),
|
|
620
|
-
/* @__PURE__ */ jsx4(
|
|
740
|
+
/* @__PURE__ */ jsx4(Box5, { flexDirection: "column", children: rows.map((option, i) => {
|
|
621
741
|
const highlighted = i === index;
|
|
622
742
|
const isCancel = i === cancelIndex;
|
|
623
743
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
744
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
745
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
-
const label = /* @__PURE__ */
|
|
746
|
+
const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
|
|
627
747
|
highlighted ? "\u276F " : " ",
|
|
628
748
|
bullet,
|
|
629
749
|
option
|
|
630
750
|
] });
|
|
631
751
|
const isText = sec?.kind === "text";
|
|
632
|
-
return /* @__PURE__ */
|
|
633
|
-
|
|
752
|
+
return /* @__PURE__ */ jsxs4(
|
|
753
|
+
Box5,
|
|
634
754
|
{
|
|
635
755
|
width: isText ? "100%" : barWidth,
|
|
636
756
|
paddingX: 1,
|
|
637
757
|
paddingY: 1,
|
|
638
758
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
759
|
children: [
|
|
640
|
-
/* @__PURE__ */ jsx4(
|
|
641
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
642
|
-
|
|
760
|
+
/* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
761
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
762
|
+
Text5,
|
|
643
763
|
{
|
|
644
764
|
wrap: "truncate",
|
|
645
765
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
766
|
children: sec.value
|
|
647
767
|
}
|
|
648
768
|
) }),
|
|
649
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
769
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
650
770
|
]
|
|
651
771
|
},
|
|
652
772
|
`row-${i}`
|
|
653
773
|
);
|
|
654
774
|
}) }),
|
|
655
|
-
/* @__PURE__ */ jsx4(
|
|
775
|
+
/* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
656
776
|
i > 0 ? " " : "",
|
|
657
|
-
/* @__PURE__ */ jsx4(
|
|
658
|
-
/* @__PURE__ */
|
|
777
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
|
|
778
|
+
/* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
659
779
|
" ",
|
|
660
780
|
label
|
|
661
781
|
] })
|
|
@@ -664,7 +784,7 @@ function SelectPrompt({
|
|
|
664
784
|
}
|
|
665
785
|
|
|
666
786
|
// src/ui/PromptInput.tsx
|
|
667
|
-
import { jsx as jsx5, jsxs as
|
|
787
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
668
788
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
669
789
|
function SpaceToContinuePrompt({
|
|
670
790
|
question,
|
|
@@ -675,10 +795,10 @@ function SpaceToContinuePrompt({
|
|
|
675
795
|
if (input === " ") onDecide(true);
|
|
676
796
|
else if (key.escape) onDecide(false);
|
|
677
797
|
});
|
|
678
|
-
return /* @__PURE__ */
|
|
679
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
680
|
-
question && /* @__PURE__ */ jsx5(
|
|
681
|
-
/* @__PURE__ */
|
|
798
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
|
|
799
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
800
|
+
question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
|
|
801
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
|
|
682
802
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "space" }),
|
|
683
803
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
684
804
|
] })
|
|
@@ -688,11 +808,11 @@ function PromptInput() {
|
|
|
688
808
|
const { phase, inputReq, submitInput } = useWizard();
|
|
689
809
|
const [draft, setDraft] = useState4("");
|
|
690
810
|
if (phase === "done" || phase === "error") {
|
|
691
|
-
return /* @__PURE__ */ jsx5(
|
|
811
|
+
return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
692
812
|
}
|
|
693
813
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
694
814
|
if (inputReq.promptType === "multipleChoice") {
|
|
695
|
-
return /* @__PURE__ */ jsx5(
|
|
815
|
+
return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
|
|
696
816
|
SelectPrompt,
|
|
697
817
|
{
|
|
698
818
|
question: inputReq.prompt,
|
|
@@ -709,7 +829,7 @@ function PromptInput() {
|
|
|
709
829
|
) });
|
|
710
830
|
}
|
|
711
831
|
if (inputReq.promptType === "multiSelect") {
|
|
712
|
-
return /* @__PURE__ */ jsx5(
|
|
832
|
+
return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
|
|
713
833
|
SelectPrompt,
|
|
714
834
|
{
|
|
715
835
|
multi: true,
|
|
@@ -724,7 +844,7 @@ function PromptInput() {
|
|
|
724
844
|
) });
|
|
725
845
|
}
|
|
726
846
|
if (inputReq.promptType === "notice") {
|
|
727
|
-
return /* @__PURE__ */ jsx5(
|
|
847
|
+
return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
|
|
728
848
|
SelectPrompt,
|
|
729
849
|
{
|
|
730
850
|
question: inputReq.prompt,
|
|
@@ -746,7 +866,7 @@ function PromptInput() {
|
|
|
746
866
|
}
|
|
747
867
|
if (inputReq.promptType === "acceptReject") {
|
|
748
868
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
749
|
-
return /* @__PURE__ */ jsx5(
|
|
869
|
+
return /* @__PURE__ */ jsx5(Box6, { children: /* @__PURE__ */ jsx5(
|
|
750
870
|
SelectPrompt,
|
|
751
871
|
{
|
|
752
872
|
question: inputReq.prompt,
|
|
@@ -757,11 +877,11 @@ function PromptInput() {
|
|
|
757
877
|
}
|
|
758
878
|
) });
|
|
759
879
|
}
|
|
760
|
-
return /* @__PURE__ */
|
|
761
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
762
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
880
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
881
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
|
|
882
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
883
|
+
/* @__PURE__ */ jsxs5(Box6, { children: [
|
|
884
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
|
|
765
885
|
inputReq.prompt,
|
|
766
886
|
" "
|
|
767
887
|
] }),
|
|
@@ -783,7 +903,7 @@ function PromptInput() {
|
|
|
783
903
|
// src/ui/Welcome.tsx
|
|
784
904
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
785
905
|
import { fileURLToPath } from "node:url";
|
|
786
|
-
import { Box as
|
|
906
|
+
import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
787
907
|
|
|
788
908
|
// src/ui/copy/welcome.ts
|
|
789
909
|
var sidebarItems = [
|
|
@@ -796,12 +916,12 @@ var sidebarItems = [
|
|
|
796
916
|
description: "push 100 records to Algolia in seconds"
|
|
797
917
|
},
|
|
798
918
|
{
|
|
799
|
-
title: "detect your
|
|
800
|
-
description: "React, Vue, Angular,
|
|
919
|
+
title: "detect your framework",
|
|
920
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
801
921
|
},
|
|
802
922
|
{
|
|
803
923
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
924
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
805
925
|
},
|
|
806
926
|
{
|
|
807
927
|
title: "ship it",
|
|
@@ -811,27 +931,27 @@ var sidebarItems = [
|
|
|
811
931
|
|
|
812
932
|
// src/ui/Welcome.tsx
|
|
813
933
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
814
|
-
import { jsx as jsx6, jsxs as
|
|
934
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
815
935
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
816
936
|
function SidebarItem({
|
|
817
937
|
title,
|
|
818
938
|
description
|
|
819
939
|
}) {
|
|
820
|
-
return /* @__PURE__ */
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
/* @__PURE__ */ jsx6(
|
|
823
|
-
/* @__PURE__ */ jsx6(
|
|
940
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
941
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
|
|
942
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
|
|
943
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
|
|
824
944
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
945
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
|
|
826
946
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
827
|
-
/* @__PURE__ */ jsx6(
|
|
947
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
|
|
828
948
|
] })
|
|
829
949
|
] });
|
|
830
950
|
}
|
|
831
951
|
function Welcome() {
|
|
832
952
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
833
953
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
834
|
-
const { rows } =
|
|
954
|
+
const { rows } = useWindowSize5();
|
|
835
955
|
useInput3((input) => {
|
|
836
956
|
if (input === " ") confirmStart();
|
|
837
957
|
else if (input === "i") openLearnMore();
|
|
@@ -850,15 +970,15 @@ function Welcome() {
|
|
|
850
970
|
if (rows < 30) {
|
|
851
971
|
layout = scales["small"];
|
|
852
972
|
}
|
|
853
|
-
return /* @__PURE__ */
|
|
973
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
854
974
|
/* @__PURE__ */ jsx6(
|
|
855
|
-
|
|
975
|
+
Box7,
|
|
856
976
|
{
|
|
857
977
|
paddingY: layout.main.padding.y,
|
|
858
978
|
paddingX: layout.main.padding.x,
|
|
859
979
|
flexDirection: "column",
|
|
860
980
|
justifyContent: "center",
|
|
861
|
-
children: /* @__PURE__ */
|
|
981
|
+
children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
|
|
862
982
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
863
983
|
Image,
|
|
864
984
|
{
|
|
@@ -870,16 +990,16 @@ function Welcome() {
|
|
|
870
990
|
protocol: "halfBlock"
|
|
871
991
|
}
|
|
872
992
|
) }),
|
|
873
|
-
/* @__PURE__ */ jsx6(
|
|
874
|
-
/* @__PURE__ */
|
|
993
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
994
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
875
995
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "space" }),
|
|
876
996
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
877
997
|
] })
|
|
878
998
|
] })
|
|
879
999
|
}
|
|
880
1000
|
),
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
|
|
1001
|
+
/* @__PURE__ */ jsxs6(
|
|
1002
|
+
Box7,
|
|
883
1003
|
{
|
|
884
1004
|
backgroundColor: COLORS.bg.sidebar,
|
|
885
1005
|
width: 40,
|
|
@@ -889,7 +1009,7 @@ function Welcome() {
|
|
|
889
1009
|
flexDirection: "column",
|
|
890
1010
|
justifyContent: "center",
|
|
891
1011
|
children: [
|
|
892
|
-
/* @__PURE__ */ jsx6(
|
|
1012
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
893
1013
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
894
1014
|
]
|
|
895
1015
|
}
|
|
@@ -899,7 +1019,7 @@ function Welcome() {
|
|
|
899
1019
|
|
|
900
1020
|
// src/ui/LearnMore.tsx
|
|
901
1021
|
import { Fragment as Fragment2 } from "react";
|
|
902
|
-
import { Box as
|
|
1022
|
+
import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
903
1023
|
|
|
904
1024
|
// src/ui/copy/learn-more.ts
|
|
905
1025
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -907,7 +1027,7 @@ var accessItems = [
|
|
|
907
1027
|
{
|
|
908
1028
|
tag: "READ",
|
|
909
1029
|
title: "Project files",
|
|
910
|
-
description: "reads
|
|
1030
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
1031
|
},
|
|
912
1032
|
{
|
|
913
1033
|
tag: "WRITE",
|
|
@@ -936,7 +1056,7 @@ var policyLinks = [
|
|
|
936
1056
|
];
|
|
937
1057
|
|
|
938
1058
|
// src/ui/LearnMore.tsx
|
|
939
|
-
import { jsx as jsx7, jsxs as
|
|
1059
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
940
1060
|
var TAG_COLORS = {
|
|
941
1061
|
READ: COLORS.success,
|
|
942
1062
|
WRITE: COLORS.badge,
|
|
@@ -952,25 +1072,25 @@ function NeverLine({
|
|
|
952
1072
|
}) {
|
|
953
1073
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
954
1074
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
955
|
-
return /* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */ jsx7(
|
|
1075
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1076
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
|
|
957
1077
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
958
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1078
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
959
1079
|
" ".repeat(rightPad),
|
|
960
|
-
/* @__PURE__ */ jsx7(
|
|
1080
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
|
|
961
1081
|
] });
|
|
962
1082
|
}
|
|
963
1083
|
function LearnMore() {
|
|
964
1084
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
965
1085
|
const backToHome = useWizard((s) => s.backToHome);
|
|
966
|
-
const { columns } =
|
|
1086
|
+
const { columns } = useWindowSize6();
|
|
967
1087
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
968
1088
|
useInput4((input, key) => {
|
|
969
1089
|
if (key.escape) backToHome();
|
|
970
1090
|
else if (input === " ") confirmStart();
|
|
971
1091
|
});
|
|
972
|
-
return /* @__PURE__ */
|
|
973
|
-
|
|
1092
|
+
return /* @__PURE__ */ jsxs7(
|
|
1093
|
+
Box8,
|
|
974
1094
|
{
|
|
975
1095
|
flexDirection: "column",
|
|
976
1096
|
paddingX: PADDING_X,
|
|
@@ -978,20 +1098,20 @@ function LearnMore() {
|
|
|
978
1098
|
width: "100%",
|
|
979
1099
|
gap: 1,
|
|
980
1100
|
children: [
|
|
981
|
-
/* @__PURE__ */ jsx7(
|
|
982
|
-
/* @__PURE__ */ jsx7(
|
|
983
|
-
/* @__PURE__ */ jsx7(
|
|
984
|
-
/* @__PURE__ */ jsx7(
|
|
985
|
-
/* @__PURE__ */
|
|
986
|
-
/* @__PURE__ */ jsx7(
|
|
987
|
-
/* @__PURE__ */ jsx7(
|
|
988
|
-
/* @__PURE__ */ jsx7(
|
|
989
|
-
/* @__PURE__ */ jsx7(
|
|
1101
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1102
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
|
|
1103
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1104
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1105
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1106
|
+
/* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1107
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1108
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1109
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
990
1110
|
] }) })
|
|
991
1111
|
] })
|
|
992
1112
|
] }, item.tag)) }),
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */ jsx7(
|
|
1113
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1114
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
995
1115
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
996
1116
|
/* @__PURE__ */ jsx7(
|
|
997
1117
|
NeverLine,
|
|
@@ -1000,7 +1120,7 @@ function LearnMore() {
|
|
|
1000
1120
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1001
1121
|
}
|
|
1002
1122
|
),
|
|
1003
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1123
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1004
1124
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1005
1125
|
/* @__PURE__ */ jsx7(
|
|
1006
1126
|
NeverLine,
|
|
@@ -1015,23 +1135,23 @@ function LearnMore() {
|
|
|
1015
1135
|
)
|
|
1016
1136
|
] }, item)),
|
|
1017
1137
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1018
|
-
/* @__PURE__ */ jsx7(
|
|
1138
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1019
1139
|
] }),
|
|
1020
|
-
/* @__PURE__ */ jsx7(
|
|
1021
|
-
/* @__PURE__ */ jsx7(
|
|
1022
|
-
/* @__PURE__ */ jsx7(
|
|
1140
|
+
/* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1141
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1142
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
|
|
1023
1143
|
] }, link.label)) }),
|
|
1024
|
-
/* @__PURE__ */
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */ jsx7(
|
|
1027
|
-
/* @__PURE__ */ jsx7(
|
|
1028
|
-
/* @__PURE__ */ jsx7(
|
|
1144
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1145
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1146
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1147
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
|
|
1148
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
|
|
1029
1149
|
] }),
|
|
1030
|
-
/* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */ jsx7(
|
|
1032
|
-
/* @__PURE__ */ jsx7(
|
|
1033
|
-
/* @__PURE__ */ jsx7(
|
|
1034
|
-
/* @__PURE__ */ jsx7(
|
|
1150
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1151
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1152
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "space" }),
|
|
1153
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
|
|
1154
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1035
1155
|
] })
|
|
1036
1156
|
] })
|
|
1037
1157
|
]
|
|
@@ -1040,10 +1160,10 @@ function LearnMore() {
|
|
|
1040
1160
|
}
|
|
1041
1161
|
|
|
1042
1162
|
// src/ui/Sidebar.tsx
|
|
1043
|
-
import { Box as
|
|
1163
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1044
1164
|
|
|
1045
1165
|
// src/ui/Steps.tsx
|
|
1046
|
-
import { Box as
|
|
1166
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1047
1167
|
import Spinner from "ink-spinner";
|
|
1048
1168
|
|
|
1049
1169
|
// src/core/persistence.ts
|
|
@@ -1072,11 +1192,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1072
1192
|
}
|
|
1073
1193
|
|
|
1074
1194
|
// src/ui/Steps.tsx
|
|
1075
|
-
import { jsx as jsx8, jsxs as
|
|
1195
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1076
1196
|
function Steps() {
|
|
1077
1197
|
const { steps } = useWizard();
|
|
1078
1198
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1079
|
-
return /* @__PURE__ */ jsx8(
|
|
1199
|
+
return /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status[s.status], children: [
|
|
1080
1200
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1081
1201
|
" ",
|
|
1082
1202
|
s.title
|
|
@@ -1086,7 +1206,7 @@ function CurrentStep() {
|
|
|
1086
1206
|
const { steps } = useWizard();
|
|
1087
1207
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1088
1208
|
if (!currentStep) return null;
|
|
1089
|
-
return /* @__PURE__ */
|
|
1209
|
+
return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
|
|
1090
1210
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1091
1211
|
" ",
|
|
1092
1212
|
` ${currentStep.title}`
|
|
@@ -1094,19 +1214,19 @@ function CurrentStep() {
|
|
|
1094
1214
|
}
|
|
1095
1215
|
|
|
1096
1216
|
// src/ui/Progress.tsx
|
|
1097
|
-
import { Box as
|
|
1098
|
-
import { jsx as jsx9, jsxs as
|
|
1217
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1218
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1099
1219
|
function Progress() {
|
|
1100
1220
|
const { steps, currentStepIndex } = useWizard();
|
|
1101
1221
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1102
1222
|
if (visibleSteps.length === 0) return null;
|
|
1103
1223
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1104
1224
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1105
|
-
return /* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */ jsx9(
|
|
1107
|
-
/* @__PURE__ */ jsx9(
|
|
1108
|
-
/* @__PURE__ */ jsx9(
|
|
1109
|
-
/* @__PURE__ */ jsx9(
|
|
1225
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1226
|
+
/* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
|
|
1227
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
|
|
1228
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
|
|
1229
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
|
|
1110
1230
|
] });
|
|
1111
1231
|
}
|
|
1112
1232
|
|
|
@@ -1117,10 +1237,10 @@ var sidebarCommands = [
|
|
|
1117
1237
|
];
|
|
1118
1238
|
|
|
1119
1239
|
// src/ui/Sidebar.tsx
|
|
1120
|
-
import { jsx as jsx10, jsxs as
|
|
1240
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1121
1241
|
function Sidebar() {
|
|
1122
|
-
return /* @__PURE__ */
|
|
1123
|
-
|
|
1242
|
+
return /* @__PURE__ */ jsxs10(
|
|
1243
|
+
Box11,
|
|
1124
1244
|
{
|
|
1125
1245
|
backgroundColor: "#14171E",
|
|
1126
1246
|
width: 30,
|
|
@@ -1129,16 +1249,16 @@ function Sidebar() {
|
|
|
1129
1249
|
flexDirection: "column",
|
|
1130
1250
|
justifyContent: "space-between",
|
|
1131
1251
|
children: [
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */ jsx10(
|
|
1252
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1253
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1134
1254
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1135
1255
|
] }),
|
|
1136
|
-
/* @__PURE__ */
|
|
1256
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1137
1257
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1138
|
-
/* @__PURE__ */ jsx10(
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */ jsx10(
|
|
1141
|
-
/* @__PURE__ */ jsx10(
|
|
1258
|
+
/* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1259
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1260
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1261
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
|
|
1142
1262
|
] });
|
|
1143
1263
|
}) })
|
|
1144
1264
|
] })
|
|
@@ -1148,12 +1268,12 @@ function Sidebar() {
|
|
|
1148
1268
|
}
|
|
1149
1269
|
|
|
1150
1270
|
// src/ui/Ribbon.tsx
|
|
1151
|
-
import { Box as
|
|
1152
|
-
import { jsx as jsx11, jsxs as
|
|
1271
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1272
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1153
1273
|
function Ribbon() {
|
|
1154
1274
|
const firstCommand = sidebarCommands[0];
|
|
1155
|
-
return /* @__PURE__ */
|
|
1156
|
-
|
|
1275
|
+
return /* @__PURE__ */ jsxs11(
|
|
1276
|
+
Box12,
|
|
1157
1277
|
{
|
|
1158
1278
|
backgroundColor: "#14171E",
|
|
1159
1279
|
flexDirection: "row",
|
|
@@ -1163,9 +1283,9 @@ function Ribbon() {
|
|
|
1163
1283
|
children: [
|
|
1164
1284
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1165
1285
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1166
|
-
/* @__PURE__ */
|
|
1167
|
-
/* @__PURE__ */ jsx11(
|
|
1168
|
-
/* @__PURE__ */ jsx11(
|
|
1286
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1287
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1288
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
|
|
1169
1289
|
] })
|
|
1170
1290
|
]
|
|
1171
1291
|
}
|
|
@@ -1177,8 +1297,8 @@ import { useState as useState6 } from "react";
|
|
|
1177
1297
|
|
|
1178
1298
|
// src/ui/Logs.tsx
|
|
1179
1299
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
|
|
1180
|
-
import { Box as
|
|
1181
|
-
import { jsx as jsx12, jsxs as
|
|
1300
|
+
import { Box as Box13, Text as Text13, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize7 } from "ink";
|
|
1301
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1182
1302
|
var KIND_COLOR = {
|
|
1183
1303
|
tool: COLORS.primary,
|
|
1184
1304
|
prompt: COLORS.badge
|
|
@@ -1208,7 +1328,7 @@ function formatTimestamp(ms) {
|
|
|
1208
1328
|
}
|
|
1209
1329
|
function Logs() {
|
|
1210
1330
|
const logs = useWizard((s) => s.logs);
|
|
1211
|
-
const { rows, columns } =
|
|
1331
|
+
const { rows, columns } = useWindowSize7();
|
|
1212
1332
|
const viewportRef = useRef3(null);
|
|
1213
1333
|
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1214
1334
|
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
@@ -1245,10 +1365,10 @@ function Logs() {
|
|
|
1245
1365
|
const visible = logs.slice(scrollOffset, scrollOffset + capacity);
|
|
1246
1366
|
const hiddenAbove = scrollOffset;
|
|
1247
1367
|
const hiddenBelow = logs.length - scrollOffset - visible.length;
|
|
1248
|
-
return /* @__PURE__ */
|
|
1249
|
-
logs.length === 0 && /* @__PURE__ */ jsx12(
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
hiddenAbove > 0 && /* @__PURE__ */
|
|
1368
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1369
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
|
|
1370
|
+
/* @__PURE__ */ jsxs12(Box13, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
1371
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
|
|
1252
1372
|
"\u2191 ",
|
|
1253
1373
|
hiddenAbove,
|
|
1254
1374
|
" more"
|
|
@@ -1263,20 +1383,20 @@ function Logs() {
|
|
|
1263
1383
|
const name = truncate2(entry.name, budget);
|
|
1264
1384
|
budget -= name.length;
|
|
1265
1385
|
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1266
|
-
return /* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */ jsx12(
|
|
1268
|
-
/* @__PURE__ */ jsx12(
|
|
1269
|
-
preview && /* @__PURE__ */ jsx12(
|
|
1270
|
-
durationText && /* @__PURE__ */ jsx12(
|
|
1386
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1387
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
|
|
1388
|
+
/* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1389
|
+
preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1390
|
+
durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
|
|
1271
1391
|
] }, entry.id);
|
|
1272
1392
|
}),
|
|
1273
|
-
hiddenBelow > 0 && /* @__PURE__ */
|
|
1393
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs12(Text13, { color: COLORS.dim, children: [
|
|
1274
1394
|
"\u2193 ",
|
|
1275
1395
|
hiddenBelow,
|
|
1276
1396
|
" more"
|
|
1277
1397
|
] })
|
|
1278
1398
|
] }),
|
|
1279
|
-
/* @__PURE__ */ jsx12(
|
|
1399
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1280
1400
|
] });
|
|
1281
1401
|
}
|
|
1282
1402
|
|
|
@@ -1300,11 +1420,11 @@ function track(event, payload) {
|
|
|
1300
1420
|
}
|
|
1301
1421
|
|
|
1302
1422
|
// src/ui/App.tsx
|
|
1303
|
-
import { jsx as jsx13, jsxs as
|
|
1423
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1304
1424
|
function App() {
|
|
1305
1425
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1306
1426
|
const { exit } = useApp();
|
|
1307
|
-
const { columns, rows } =
|
|
1427
|
+
const { columns, rows } = useWindowSize8();
|
|
1308
1428
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1309
1429
|
const finished = phase === "done" || phase === "error";
|
|
1310
1430
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1317,7 +1437,8 @@ function App() {
|
|
|
1317
1437
|
{ isActive: finished }
|
|
1318
1438
|
);
|
|
1319
1439
|
useInput6((_input, key) => {
|
|
1320
|
-
if (phase === "idle" || phase === "preflight")
|
|
1440
|
+
if (phase === "idle" || phase === "preflight" || phase === "authenticating")
|
|
1441
|
+
return;
|
|
1321
1442
|
if (key.tab) {
|
|
1322
1443
|
setShowLogs(!showLogs);
|
|
1323
1444
|
track("AI Wizard Interaction", {
|
|
@@ -1327,7 +1448,7 @@ function App() {
|
|
|
1327
1448
|
});
|
|
1328
1449
|
}
|
|
1329
1450
|
});
|
|
1330
|
-
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
|
|
1451
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
|
|
1331
1452
|
useInput6((_input, key) => {
|
|
1332
1453
|
if (escOwnedElsewhere) return;
|
|
1333
1454
|
if (key.escape) {
|
|
@@ -1340,19 +1461,19 @@ function App() {
|
|
|
1340
1461
|
exit();
|
|
1341
1462
|
}
|
|
1342
1463
|
});
|
|
1343
|
-
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1464
|
+
const mainWindowVisible = phase === "authenticating" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1344
1465
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1345
1466
|
const showSidebar = flexDirection === "row";
|
|
1346
|
-
return /* @__PURE__ */
|
|
1347
|
-
|
|
1467
|
+
return /* @__PURE__ */ jsxs13(
|
|
1468
|
+
Box14,
|
|
1348
1469
|
{
|
|
1349
1470
|
backgroundColor: COLORS.bg.main,
|
|
1350
1471
|
flexDirection: "row",
|
|
1351
1472
|
width: columns,
|
|
1352
1473
|
minHeight: rows,
|
|
1353
1474
|
children: [
|
|
1354
|
-
mainWindowVisible && /* @__PURE__ */
|
|
1355
|
-
|
|
1475
|
+
mainWindowVisible && /* @__PURE__ */ jsxs13(
|
|
1476
|
+
Box14,
|
|
1356
1477
|
{
|
|
1357
1478
|
flexDirection,
|
|
1358
1479
|
width: "100%",
|
|
@@ -1360,8 +1481,8 @@ function App() {
|
|
|
1360
1481
|
children: [
|
|
1361
1482
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1362
1483
|
/* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
|
|
1363
|
-
/* @__PURE__ */
|
|
1364
|
-
|
|
1484
|
+
/* @__PURE__ */ jsxs13(
|
|
1485
|
+
Box14,
|
|
1365
1486
|
{
|
|
1366
1487
|
flexDirection: "column",
|
|
1367
1488
|
paddingX: 4,
|
|
@@ -1369,10 +1490,15 @@ function App() {
|
|
|
1369
1490
|
width: showSidebar ? 70 : "100%",
|
|
1370
1491
|
flexGrow: showSidebar ? 1 : 0,
|
|
1371
1492
|
children: [
|
|
1493
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
|
|
1494
|
+
/* @__PURE__ */ jsx13(Text14, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1495
|
+
/* @__PURE__ */ jsx13(Text14, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1496
|
+
] }),
|
|
1497
|
+
/* @__PURE__ */ jsx13(CliOutput, {}),
|
|
1372
1498
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1373
1499
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1374
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1375
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1500
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1501
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
|
|
1376
1502
|
"\u2716 ",
|
|
1377
1503
|
error
|
|
1378
1504
|
] }) })
|
|
@@ -1784,61 +1910,134 @@ async function runWorkflow(workflow2, appId) {
|
|
|
1784
1910
|
}
|
|
1785
1911
|
}
|
|
1786
1912
|
|
|
1787
|
-
// src/lib/
|
|
1788
|
-
import {
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1913
|
+
// src/lib/algoliaApp.ts
|
|
1914
|
+
import { z as z3 } from "zod";
|
|
1915
|
+
var currentSchema = z3.object({
|
|
1916
|
+
id: z3.string().min(1),
|
|
1917
|
+
name: z3.string().default(""),
|
|
1918
|
+
plan: z3.string().optional()
|
|
1919
|
+
});
|
|
1920
|
+
var listSchema = z3.array(
|
|
1921
|
+
z3.object({
|
|
1922
|
+
id: z3.string().min(1),
|
|
1923
|
+
name: z3.string().default(""),
|
|
1924
|
+
plan_label: z3.string().optional()
|
|
1925
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
1926
|
+
);
|
|
1927
|
+
async function currentApplication() {
|
|
1928
|
+
let raw;
|
|
1800
1929
|
try {
|
|
1801
|
-
|
|
1930
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1802
1931
|
} catch {
|
|
1803
|
-
return
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1934
|
+
const parsed = currentSchema.safeParse(parseJson(raw));
|
|
1935
|
+
return parsed.success ? parsed.data : null;
|
|
1936
|
+
}
|
|
1937
|
+
async function requireApplication() {
|
|
1938
|
+
const app2 = await currentApplication();
|
|
1939
|
+
if (!app2) {
|
|
1940
|
+
throw new Error(
|
|
1941
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
return app2;
|
|
1945
|
+
}
|
|
1946
|
+
async function listApplications() {
|
|
1947
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
1948
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
1949
|
+
if (!parsed.success) {
|
|
1950
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
1951
|
+
}
|
|
1952
|
+
return parsed.data;
|
|
1953
|
+
}
|
|
1954
|
+
function selectableApplications(apps) {
|
|
1955
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
1956
|
+
for (const app2 of apps) {
|
|
1957
|
+
const name = app2.name.trim();
|
|
1958
|
+
if (name) nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
|
|
1804
1959
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
isDefault: t.default === true
|
|
1812
|
-
}));
|
|
1813
|
-
profiles.sort((a, b) => Number(b.isDefault) - Number(a.isDefault));
|
|
1814
|
-
return profiles.map(({ name, appId, apiKey }) => ({ name, appId, apiKey }));
|
|
1815
|
-
}
|
|
1816
|
-
async function loadActiveProfile() {
|
|
1817
|
-
let profiles;
|
|
1960
|
+
return apps.filter((app2) => nameCounts.get(app2.name.trim()) === 1);
|
|
1961
|
+
}
|
|
1962
|
+
async function selectApplication(name) {
|
|
1963
|
+
await runAlgoliaCli(["application", "select", "--app-name", name]);
|
|
1964
|
+
}
|
|
1965
|
+
function parseJson(text) {
|
|
1818
1966
|
try {
|
|
1819
|
-
|
|
1967
|
+
return JSON.parse(text);
|
|
1820
1968
|
} catch {
|
|
1821
|
-
|
|
1969
|
+
return void 0;
|
|
1822
1970
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// src/lib/algoliaAppPicker.ts
|
|
1974
|
+
function secondaryFor(app2) {
|
|
1975
|
+
return app2.plan ? { kind: "badge", value: app2.plan } : void 0;
|
|
1976
|
+
}
|
|
1977
|
+
async function promptForApplication() {
|
|
1978
|
+
const store2 = useWizard.getState();
|
|
1979
|
+
const phaseBefore = store2.phase;
|
|
1980
|
+
const all = await listApplications();
|
|
1981
|
+
const selectable = selectableApplications(all);
|
|
1982
|
+
const skipped = all.length - selectable.length;
|
|
1983
|
+
if (selectable.length === 0) {
|
|
1984
|
+
const why = all.length === 0 ? "this account has no applications" : "none of this account\u2019s applications have a unique name";
|
|
1825
1985
|
throw new Error(
|
|
1826
|
-
|
|
1986
|
+
`No selectable Algolia application was found: the CLI selects an application by name, and ${why}. Name one in the Algolia dashboard, or run \`npx @algolia/cli@latest application select\` directly.`
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
const messages = ["Which Algolia application should the wizard work in?"];
|
|
1990
|
+
if (skipped > 0) {
|
|
1991
|
+
messages.push(
|
|
1992
|
+
`${skipped} application${skipped === 1 ? "" : "s"} not listed: the CLI selects by name, so unnamed and duplicate-named ones can\u2019t be chosen here.`
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
for (; ; ) {
|
|
1996
|
+
const choice = await store2.requestUserInput({
|
|
1997
|
+
prompt: "Select an application",
|
|
1998
|
+
promptType: "multipleChoice",
|
|
1999
|
+
options: selectable.map((app2) => `${app2.name} \u2014 ${app2.id}`),
|
|
2000
|
+
secondary: selectable.map(secondaryFor),
|
|
2001
|
+
messages
|
|
2002
|
+
});
|
|
2003
|
+
const index = selectable.findIndex(
|
|
2004
|
+
(app2) => `${app2.name} \u2014 ${app2.id}` === choice
|
|
1827
2005
|
);
|
|
2006
|
+
const chosen = selectable[index];
|
|
2007
|
+
if (!chosen) {
|
|
2008
|
+
throw new Error("Application picker received an unexpected selection");
|
|
2009
|
+
}
|
|
2010
|
+
try {
|
|
2011
|
+
await selectApplication(chosen.name);
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
logger.warn(
|
|
2014
|
+
{ app: chosen.id, err: err.message },
|
|
2015
|
+
"application select failed; re-prompting"
|
|
2016
|
+
);
|
|
2017
|
+
messages.push(
|
|
2018
|
+
`Could not select \u201C${chosen.name}\u201D. It may have been renamed or removed \u2014 pick another.`
|
|
2019
|
+
);
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
if (phaseBefore === "authenticating") store2.resumeAuth();
|
|
2023
|
+
logger.info({ app: chosen.id }, "selected Algolia application");
|
|
2024
|
+
return chosen;
|
|
1828
2025
|
}
|
|
1829
|
-
|
|
2026
|
+
}
|
|
2027
|
+
async function ensureApplication() {
|
|
2028
|
+
return await currentApplication() ?? await promptForApplication();
|
|
1830
2029
|
}
|
|
1831
2030
|
|
|
1832
2031
|
// src/workflows/default.ts
|
|
1833
|
-
import { z as
|
|
2032
|
+
import { z as z26 } from "zod";
|
|
1834
2033
|
|
|
1835
2034
|
// src/actions/listIndices.ts
|
|
1836
|
-
import { z as
|
|
1837
|
-
var indicesListSchema =
|
|
1838
|
-
items:
|
|
1839
|
-
|
|
1840
|
-
name:
|
|
1841
|
-
entries:
|
|
2035
|
+
import { z as z4 } from "zod";
|
|
2036
|
+
var indicesListSchema = z4.object({
|
|
2037
|
+
items: z4.array(
|
|
2038
|
+
z4.object({
|
|
2039
|
+
name: z4.string(),
|
|
2040
|
+
entries: z4.number().default(0)
|
|
1842
2041
|
})
|
|
1843
2042
|
)
|
|
1844
2043
|
});
|
|
@@ -1909,12 +2108,12 @@ import "zod";
|
|
|
1909
2108
|
|
|
1910
2109
|
// src/lib/tools/listFiles.ts
|
|
1911
2110
|
import { tool } from "ai";
|
|
1912
|
-
import
|
|
2111
|
+
import z5 from "zod";
|
|
1913
2112
|
import { readdir } from "node:fs/promises";
|
|
1914
2113
|
|
|
1915
2114
|
// src/lib/tools/path.ts
|
|
1916
2115
|
import { lstat } from "node:fs/promises";
|
|
1917
|
-
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as
|
|
2116
|
+
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
|
|
1918
2117
|
function resolveInRoot(ctx, path) {
|
|
1919
2118
|
const target = resolve2(ctx.cwd, path);
|
|
1920
2119
|
const rel = relative(ctx.root, target);
|
|
@@ -1930,7 +2129,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1930
2129
|
let current = ctx.root;
|
|
1931
2130
|
const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
|
|
1932
2131
|
for (const part of parts) {
|
|
1933
|
-
current =
|
|
2132
|
+
current = join6(current, part);
|
|
1934
2133
|
try {
|
|
1935
2134
|
if ((await lstat(current)).isSymbolicLink()) return true;
|
|
1936
2135
|
} catch (err) {
|
|
@@ -1945,7 +2144,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1945
2144
|
function listFilesTool(ctx) {
|
|
1946
2145
|
return tool({
|
|
1947
2146
|
description: "List files in the current working directory",
|
|
1948
|
-
inputSchema:
|
|
2147
|
+
inputSchema: z5.object(),
|
|
1949
2148
|
execute: async () => {
|
|
1950
2149
|
logger.info("called listFiles tool");
|
|
1951
2150
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
@@ -1961,13 +2160,13 @@ function listFilesTool(ctx) {
|
|
|
1961
2160
|
|
|
1962
2161
|
// src/lib/tools/changeDirectory.ts
|
|
1963
2162
|
import { tool as tool2 } from "ai";
|
|
1964
|
-
import
|
|
2163
|
+
import z6 from "zod";
|
|
1965
2164
|
import { stat } from "node:fs/promises";
|
|
1966
2165
|
function changeDirectoryTool(ctx) {
|
|
1967
2166
|
return tool2({
|
|
1968
2167
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
1969
|
-
inputSchema:
|
|
1970
|
-
path:
|
|
2168
|
+
inputSchema: z6.object({
|
|
2169
|
+
path: z6.string().describe("Directory to change into")
|
|
1971
2170
|
}),
|
|
1972
2171
|
execute: async ({ path }) => {
|
|
1973
2172
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -1989,13 +2188,13 @@ function changeDirectoryTool(ctx) {
|
|
|
1989
2188
|
|
|
1990
2189
|
// src/lib/tools/reportStatus.ts
|
|
1991
2190
|
import { tool as tool3 } from "ai";
|
|
1992
|
-
import
|
|
2191
|
+
import z7 from "zod";
|
|
1993
2192
|
function reportStatusTool(output) {
|
|
1994
2193
|
return tool3({
|
|
1995
2194
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
1996
|
-
inputSchema:
|
|
1997
|
-
status:
|
|
1998
|
-
reason:
|
|
2195
|
+
inputSchema: z7.object({
|
|
2196
|
+
status: z7.enum(["success", "fail"]),
|
|
2197
|
+
reason: z7.string().optional(),
|
|
1999
2198
|
output
|
|
2000
2199
|
}),
|
|
2001
2200
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2007,8 +2206,8 @@ function reportStatusTool(output) {
|
|
|
2007
2206
|
|
|
2008
2207
|
// src/lib/tools/readFile.ts
|
|
2009
2208
|
import { tool as tool4 } from "ai";
|
|
2010
|
-
import
|
|
2011
|
-
import { readFile as
|
|
2209
|
+
import z8 from "zod";
|
|
2210
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2012
2211
|
|
|
2013
2212
|
// src/lib/tools/env.ts
|
|
2014
2213
|
import { basename } from "node:path";
|
|
@@ -2035,8 +2234,8 @@ function redactEnvValues(content) {
|
|
|
2035
2234
|
function readFileTool(ctx) {
|
|
2036
2235
|
return tool4({
|
|
2037
2236
|
description: "Read the contents of a file at the given path",
|
|
2038
|
-
inputSchema:
|
|
2039
|
-
filePath:
|
|
2237
|
+
inputSchema: z8.object({
|
|
2238
|
+
filePath: z8.string().describe("Path to the file to read")
|
|
2040
2239
|
}),
|
|
2041
2240
|
execute: async ({ filePath }) => {
|
|
2042
2241
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2046,7 +2245,7 @@ function readFileTool(ctx) {
|
|
|
2046
2245
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2047
2246
|
if (!resolved.ok) return resolved.error;
|
|
2048
2247
|
try {
|
|
2049
|
-
const content = await
|
|
2248
|
+
const content = await readFile3(resolved.target, "utf8");
|
|
2050
2249
|
return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
|
|
2051
2250
|
} catch (err) {
|
|
2052
2251
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2057,15 +2256,15 @@ function readFileTool(ctx) {
|
|
|
2057
2256
|
|
|
2058
2257
|
// src/lib/tools/writeFile.ts
|
|
2059
2258
|
import { tool as tool5 } from "ai";
|
|
2060
|
-
import
|
|
2259
|
+
import z9 from "zod";
|
|
2061
2260
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2062
2261
|
import { dirname as dirname4 } from "node:path";
|
|
2063
2262
|
function writeFileTool(ctx) {
|
|
2064
2263
|
return tool5({
|
|
2065
2264
|
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.",
|
|
2066
|
-
inputSchema:
|
|
2067
|
-
filePath:
|
|
2068
|
-
content:
|
|
2265
|
+
inputSchema: z9.object({
|
|
2266
|
+
filePath: z9.string().describe("Path to the file to write"),
|
|
2267
|
+
content: z9.string().describe("Content to write to the file")
|
|
2069
2268
|
}),
|
|
2070
2269
|
execute: async ({ filePath, content }) => {
|
|
2071
2270
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2090,9 +2289,95 @@ function writeFileTool(ctx) {
|
|
|
2090
2289
|
|
|
2091
2290
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2092
2291
|
import { tool as tool6 } from "ai";
|
|
2093
|
-
import
|
|
2094
|
-
import { mkdir as mkdir4, readFile as
|
|
2292
|
+
import z11 from "zod";
|
|
2293
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2095
2294
|
import { dirname as dirname5 } from "node:path";
|
|
2295
|
+
|
|
2296
|
+
// src/lib/algoliaApiKey.ts
|
|
2297
|
+
import { z as z10 } from "zod";
|
|
2298
|
+
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
2299
|
+
var WRITE_ACLS = [
|
|
2300
|
+
"addObject",
|
|
2301
|
+
"deleteObject",
|
|
2302
|
+
"settings",
|
|
2303
|
+
"editSettings",
|
|
2304
|
+
"listIndexes"
|
|
2305
|
+
];
|
|
2306
|
+
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2307
|
+
var apiKeySchema = z10.object({
|
|
2308
|
+
value: z10.string().min(1),
|
|
2309
|
+
acl: z10.array(z10.string()).default([]),
|
|
2310
|
+
indexes: z10.array(z10.string()).default([])
|
|
2311
|
+
});
|
|
2312
|
+
var apiKeyListSchema = z10.object({
|
|
2313
|
+
items: z10.array(apiKeySchema).optional(),
|
|
2314
|
+
keys: z10.array(apiKeySchema).optional()
|
|
2315
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2316
|
+
var createdKeySchema = z10.object({
|
|
2317
|
+
key: z10.string().min(1).optional(),
|
|
2318
|
+
value: z10.string().min(1).optional()
|
|
2319
|
+
});
|
|
2320
|
+
function canReuse(key, index) {
|
|
2321
|
+
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
2322
|
+
}
|
|
2323
|
+
async function createSearchKey(index) {
|
|
2324
|
+
const stdout = await runAlgoliaCli([
|
|
2325
|
+
"apikeys",
|
|
2326
|
+
"create",
|
|
2327
|
+
"--indices",
|
|
2328
|
+
index,
|
|
2329
|
+
"--acl",
|
|
2330
|
+
"search,browse",
|
|
2331
|
+
"--description",
|
|
2332
|
+
`wizard search-only key for ${index}`,
|
|
2333
|
+
"-o",
|
|
2334
|
+
"json"
|
|
2335
|
+
]);
|
|
2336
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
2337
|
+
const created = key ?? value;
|
|
2338
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2339
|
+
return created;
|
|
2340
|
+
}
|
|
2341
|
+
function canReuseForWrites(key, index) {
|
|
2342
|
+
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2343
|
+
}
|
|
2344
|
+
async function resolveWriteKey(index) {
|
|
2345
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2346
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
|
|
2347
|
+
if (existing) {
|
|
2348
|
+
logger.info({ index }, "reusing existing write API key");
|
|
2349
|
+
return existing;
|
|
2350
|
+
}
|
|
2351
|
+
logger.info({ index }, "no reusable write key found; creating one");
|
|
2352
|
+
const created = await runAlgoliaCli([
|
|
2353
|
+
"apikeys",
|
|
2354
|
+
"create",
|
|
2355
|
+
"--indices",
|
|
2356
|
+
index,
|
|
2357
|
+
"--acl",
|
|
2358
|
+
WRITE_ACLS.join(","),
|
|
2359
|
+
"--description",
|
|
2360
|
+
`wizard write key for ${index}`,
|
|
2361
|
+
"-o",
|
|
2362
|
+
"json"
|
|
2363
|
+
]);
|
|
2364
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(created));
|
|
2365
|
+
const writeKey = key ?? value;
|
|
2366
|
+
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2367
|
+
return writeKey;
|
|
2368
|
+
}
|
|
2369
|
+
async function resolveSearchOnlyKey(index) {
|
|
2370
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2371
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
2372
|
+
if (existing) {
|
|
2373
|
+
logger.info({ index }, "reusing existing search-only API key");
|
|
2374
|
+
return existing;
|
|
2375
|
+
}
|
|
2376
|
+
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
2377
|
+
return createSearchKey(index);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2096
2381
|
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2097
2382
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2098
2383
|
function appendEnv(content, entries) {
|
|
@@ -2106,9 +2391,9 @@ function hasEnv(content, name) {
|
|
|
2106
2391
|
}
|
|
2107
2392
|
function writeCredentialsTool(ctx) {
|
|
2108
2393
|
return tool6({
|
|
2109
|
-
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials
|
|
2110
|
-
inputSchema:
|
|
2111
|
-
filePath:
|
|
2394
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file (e.g. ".env"). If the file already defines ${APP_ID_VAR} or ${API_KEY_VAR}, the write is skipped and existing values are left untouched.`,
|
|
2395
|
+
inputSchema: z11.object({
|
|
2396
|
+
filePath: z11.string().describe(
|
|
2112
2397
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2113
2398
|
)
|
|
2114
2399
|
}),
|
|
@@ -2116,11 +2401,17 @@ function writeCredentialsTool(ctx) {
|
|
|
2116
2401
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2117
2402
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2118
2403
|
if (resolved.ok === false) return resolved.error;
|
|
2119
|
-
|
|
2404
|
+
const targetIndex = useWizard.getState().targetIndex;
|
|
2405
|
+
if (!targetIndex) {
|
|
2406
|
+
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2407
|
+
}
|
|
2408
|
+
let appId;
|
|
2409
|
+
let writeKey;
|
|
2120
2410
|
try {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2411
|
+
appId = (await requireApplication()).id;
|
|
2412
|
+
writeKey = await resolveWriteKey(targetIndex);
|
|
2413
|
+
} catch (err) {
|
|
2414
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2124
2415
|
}
|
|
2125
2416
|
try {
|
|
2126
2417
|
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
@@ -2128,7 +2419,7 @@ function writeCredentialsTool(ctx) {
|
|
|
2128
2419
|
}
|
|
2129
2420
|
let existing = "";
|
|
2130
2421
|
try {
|
|
2131
|
-
existing = await
|
|
2422
|
+
existing = await readFile4(resolved.target, "utf8");
|
|
2132
2423
|
} catch (err) {
|
|
2133
2424
|
if (err.code !== "ENOENT") throw err;
|
|
2134
2425
|
}
|
|
@@ -2139,8 +2430,8 @@ function writeCredentialsTool(ctx) {
|
|
|
2139
2430
|
return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
|
|
2140
2431
|
}
|
|
2141
2432
|
const envWithCredentials = appendEnv(existing, [
|
|
2142
|
-
[APP_ID_VAR,
|
|
2143
|
-
[API_KEY_VAR,
|
|
2433
|
+
[APP_ID_VAR, appId],
|
|
2434
|
+
[API_KEY_VAR, writeKey]
|
|
2144
2435
|
]);
|
|
2145
2436
|
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2146
2437
|
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
@@ -2154,731 +2445,27 @@ function writeCredentialsTool(ctx) {
|
|
|
2154
2445
|
|
|
2155
2446
|
// src/lib/tools/searchFiles.ts
|
|
2156
2447
|
import { tool as tool7 } from "ai";
|
|
2157
|
-
import
|
|
2158
|
-
import { readdir as
|
|
2159
|
-
import { join as
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2172
|
-
["yarn.lock", "yarn"],
|
|
2173
|
-
["bun.lockb", "bun"],
|
|
2174
|
-
["bun.lock", "bun"],
|
|
2175
|
-
["package-lock.json", "npm"]
|
|
2176
|
-
];
|
|
2177
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2178
|
-
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2179
|
-
}
|
|
2180
|
-
function packageManagerFrom(pkg) {
|
|
2181
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2182
|
-
}
|
|
2183
|
-
function packageManagerFromLockfile(cwd) {
|
|
2184
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2185
|
-
}
|
|
2186
|
-
async function detectPackageManager(cwd) {
|
|
2187
|
-
try {
|
|
2188
|
-
const pkg = await readPackageJson(cwd);
|
|
2189
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2190
|
-
} catch {
|
|
2191
|
-
}
|
|
2192
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// src/lib/shell.ts
|
|
2196
|
-
function shellQuote(value) {
|
|
2197
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
// src/lib/languages.ts
|
|
2201
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2202
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2203
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2204
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2205
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2206
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2207
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2208
|
-
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2209
|
-
var LANGUAGE_PROFILES = {
|
|
2210
|
-
javascript: {
|
|
2211
|
-
id: "javascript",
|
|
2212
|
-
displayName: "JavaScript/TypeScript",
|
|
2213
|
-
aliases: [
|
|
2214
|
-
"javascript",
|
|
2215
|
-
"js",
|
|
2216
|
-
"typescript",
|
|
2217
|
-
"ts",
|
|
2218
|
-
"node",
|
|
2219
|
-
"nodejs",
|
|
2220
|
-
"node.js",
|
|
2221
|
-
"bun",
|
|
2222
|
-
"deno",
|
|
2223
|
-
"ecmascript",
|
|
2224
|
-
"jsx",
|
|
2225
|
-
"tsx"
|
|
2226
|
-
],
|
|
2227
|
-
manifests: ["package.json"],
|
|
2228
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2229
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2230
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2231
|
-
// binary below.
|
|
2232
|
-
packageManagers: [
|
|
2233
|
-
{
|
|
2234
|
-
id: "npm",
|
|
2235
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2236
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2237
|
-
ingest: {
|
|
2238
|
-
kind: "auto",
|
|
2239
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2240
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
],
|
|
2244
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2245
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2246
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2247
|
-
// repoVerification rather than listed here.
|
|
2248
|
-
verification: [],
|
|
2249
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2250
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2251
|
-
},
|
|
2252
|
-
python: {
|
|
2253
|
-
id: "python",
|
|
2254
|
-
displayName: "Python",
|
|
2255
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2256
|
-
manifests: [
|
|
2257
|
-
"pyproject.toml",
|
|
2258
|
-
"requirements.txt",
|
|
2259
|
-
"setup.py",
|
|
2260
|
-
"setup.cfg",
|
|
2261
|
-
"Pipfile"
|
|
2262
|
-
],
|
|
2263
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2264
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2265
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2266
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2267
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2268
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2269
|
-
// packages (see the optional root-requirements step below).
|
|
2270
|
-
packageManagers: [
|
|
2271
|
-
{
|
|
2272
|
-
id: "pip-venv",
|
|
2273
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2274
|
-
installSteps: [
|
|
2275
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2276
|
-
{
|
|
2277
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2278
|
-
},
|
|
2279
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2280
|
-
// ORMs) when the repo pins them the classic way.
|
|
2281
|
-
{
|
|
2282
|
-
argv: [
|
|
2283
|
-
PY_VENV_PYTHON,
|
|
2284
|
-
"-m",
|
|
2285
|
-
"pip",
|
|
2286
|
-
"install",
|
|
2287
|
-
"-r",
|
|
2288
|
-
"requirements.txt"
|
|
2289
|
-
],
|
|
2290
|
-
requiresFile: "requirements.txt",
|
|
2291
|
-
optional: true
|
|
2292
|
-
}
|
|
2293
|
-
],
|
|
2294
|
-
ingest: {
|
|
2295
|
-
kind: "auto",
|
|
2296
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2297
|
-
entrypointExtensions: [".py"]
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
],
|
|
2301
|
-
sdk: {
|
|
2302
|
-
packageName: "algoliasearch",
|
|
2303
|
-
versionPin: ">=4,<5",
|
|
2304
|
-
docKey: "python"
|
|
2305
|
-
},
|
|
2306
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2307
|
-
verification: [
|
|
2308
|
-
{
|
|
2309
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2310
|
-
// compiles every installed package instead of the generated script.
|
|
2311
|
-
label: "python compileall",
|
|
2312
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2313
|
-
requiresFile: INGEST_DIR
|
|
2314
|
-
}
|
|
2315
|
-
],
|
|
2316
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2317
|
-
skipDirs: [
|
|
2318
|
-
"venv",
|
|
2319
|
-
"__pycache__",
|
|
2320
|
-
"site-packages",
|
|
2321
|
-
"dist",
|
|
2322
|
-
"build",
|
|
2323
|
-
"htmlcov"
|
|
2324
|
-
]
|
|
2325
|
-
},
|
|
2326
|
-
ruby: {
|
|
2327
|
-
id: "ruby",
|
|
2328
|
-
displayName: "Ruby",
|
|
2329
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2330
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2331
|
-
packageManagers: [
|
|
2332
|
-
{
|
|
2333
|
-
id: "bundler",
|
|
2334
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2335
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2336
|
-
ingest: {
|
|
2337
|
-
kind: "auto",
|
|
2338
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2339
|
-
entrypointExtensions: [".rb"]
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
],
|
|
2343
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2344
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2345
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2346
|
-
// time), so verification relies on the agent's own review here.
|
|
2347
|
-
verification: [],
|
|
2348
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2349
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2350
|
-
},
|
|
2351
|
-
php: {
|
|
2352
|
-
id: "php",
|
|
2353
|
-
displayName: "PHP",
|
|
2354
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2355
|
-
manifests: ["composer.json"],
|
|
2356
|
-
packageManagers: [
|
|
2357
|
-
{
|
|
2358
|
-
id: "composer",
|
|
2359
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2360
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2361
|
-
// makes a later `composer install` refuse to run).
|
|
2362
|
-
dependency: { mode: "wizard-installs" },
|
|
2363
|
-
installSteps: [
|
|
2364
|
-
{
|
|
2365
|
-
argv: [
|
|
2366
|
-
"composer",
|
|
2367
|
-
"require",
|
|
2368
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2369
|
-
"--no-interaction",
|
|
2370
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2371
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2372
|
-
"--no-scripts"
|
|
2373
|
-
]
|
|
2374
|
-
}
|
|
2375
|
-
],
|
|
2376
|
-
ingest: {
|
|
2377
|
-
kind: "auto",
|
|
2378
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2379
|
-
entrypointExtensions: [".php"]
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
],
|
|
2383
|
-
sdk: {
|
|
2384
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2385
|
-
versionPin: "^4",
|
|
2386
|
-
docKey: "php"
|
|
2387
|
-
},
|
|
2388
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2389
|
-
verification: [],
|
|
2390
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2391
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2392
|
-
},
|
|
2393
|
-
go: {
|
|
2394
|
-
id: "go",
|
|
2395
|
-
displayName: "Go",
|
|
2396
|
-
aliases: ["go", "golang"],
|
|
2397
|
-
manifests: ["go.mod"],
|
|
2398
|
-
packageManagers: [
|
|
2399
|
-
{
|
|
2400
|
-
id: "gomod",
|
|
2401
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2402
|
-
// resolves and fetches them — which only works because the script lives
|
|
2403
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2404
|
-
dependency: { mode: "code-imports" },
|
|
2405
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2406
|
-
ingest: {
|
|
2407
|
-
kind: "auto",
|
|
2408
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2409
|
-
entrypointExtensions: [".go"]
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
],
|
|
2413
|
-
sdk: {
|
|
2414
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2415
|
-
versionPin: "v4",
|
|
2416
|
-
docKey: "go"
|
|
2417
|
-
},
|
|
2418
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2419
|
-
verification: [
|
|
2420
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2421
|
-
],
|
|
2422
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2423
|
-
skipDirs: ["vendor", "bin"]
|
|
2424
|
-
},
|
|
2425
|
-
java: {
|
|
2426
|
-
id: "java",
|
|
2427
|
-
displayName: "Java",
|
|
2428
|
-
aliases: ["java"],
|
|
2429
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2430
|
-
packageManagers: [
|
|
2431
|
-
{
|
|
2432
|
-
id: "maven",
|
|
2433
|
-
detectFiles: ["pom.xml"],
|
|
2434
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2435
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2436
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2437
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2438
|
-
// because the install step above compiles src/main/java first — which is
|
|
2439
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2440
|
-
ingest: {
|
|
2441
|
-
kind: "auto",
|
|
2442
|
-
argv: [
|
|
2443
|
-
"mvn",
|
|
2444
|
-
"-q",
|
|
2445
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2446
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2447
|
-
],
|
|
2448
|
-
entrypointExtensions: [".java"]
|
|
2449
|
-
}
|
|
2450
|
-
},
|
|
2451
|
-
{
|
|
2452
|
-
id: "gradle",
|
|
2453
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2454
|
-
dependency: {
|
|
2455
|
-
mode: "agent-declares",
|
|
2456
|
-
file: "build.gradle",
|
|
2457
|
-
alternatives: ["build.gradle.kts"]
|
|
2458
|
-
},
|
|
2459
|
-
installSteps: [],
|
|
2460
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2461
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2462
|
-
ingest: {
|
|
2463
|
-
kind: "manual",
|
|
2464
|
-
entrypointExtensions: [".java"],
|
|
2465
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
],
|
|
2469
|
-
sdk: {
|
|
2470
|
-
packageName: "com.algolia:algoliasearch",
|
|
2471
|
-
versionPin: "4.+",
|
|
2472
|
-
docKey: "java",
|
|
2473
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2474
|
-
},
|
|
2475
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2476
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2477
|
-
// fails with "class not found".
|
|
2478
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2479
|
-
verification: [
|
|
2480
|
-
{
|
|
2481
|
-
label: "mvn compile",
|
|
2482
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2483
|
-
requiresFile: "pom.xml"
|
|
2484
|
-
}
|
|
2485
|
-
],
|
|
2486
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2487
|
-
skipDirs: ["target", "build", "out"]
|
|
2488
|
-
},
|
|
2489
|
-
kotlin: {
|
|
2490
|
-
id: "kotlin",
|
|
2491
|
-
displayName: "Kotlin",
|
|
2492
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2493
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2494
|
-
packageManagers: [
|
|
2495
|
-
{
|
|
2496
|
-
id: "gradle",
|
|
2497
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2498
|
-
dependency: {
|
|
2499
|
-
mode: "agent-declares",
|
|
2500
|
-
file: "build.gradle.kts",
|
|
2501
|
-
alternatives: ["build.gradle"]
|
|
2502
|
-
},
|
|
2503
|
-
installSteps: [],
|
|
2504
|
-
ingest: {
|
|
2505
|
-
kind: "manual",
|
|
2506
|
-
entrypointExtensions: [".kt"],
|
|
2507
|
-
runCommand: "./gradlew runAlgoliaIngest"
|
|
2508
|
-
}
|
|
2509
|
-
},
|
|
2510
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2511
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2512
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2513
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2514
|
-
{
|
|
2515
|
-
id: "maven",
|
|
2516
|
-
detectFiles: ["pom.xml"],
|
|
2517
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2518
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2519
|
-
ingest: {
|
|
2520
|
-
kind: "manual",
|
|
2521
|
-
entrypointExtensions: [".kt"],
|
|
2522
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
],
|
|
2526
|
-
sdk: {
|
|
2527
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2528
|
-
versionPin: "3.+",
|
|
2529
|
-
docKey: "kotlin",
|
|
2530
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2531
|
-
// engine the script compiles and then fails at its first request.
|
|
2532
|
-
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2533
|
-
},
|
|
2534
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2535
|
-
verification: [],
|
|
2536
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2537
|
-
skipDirs: ["build", "out"]
|
|
2538
|
-
},
|
|
2539
|
-
scala: {
|
|
2540
|
-
id: "scala",
|
|
2541
|
-
displayName: "Scala",
|
|
2542
|
-
aliases: ["scala", "sbt"],
|
|
2543
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2544
|
-
packageManagers: [
|
|
2545
|
-
{
|
|
2546
|
-
id: "sbt",
|
|
2547
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2548
|
-
installSteps: [],
|
|
2549
|
-
ingest: {
|
|
2550
|
-
kind: "manual",
|
|
2551
|
-
entrypointExtensions: [".scala"],
|
|
2552
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
],
|
|
2556
|
-
sdk: {
|
|
2557
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2558
|
-
versionPin: "2.+",
|
|
2559
|
-
docKey: "scala",
|
|
2560
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2561
|
-
},
|
|
2562
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2563
|
-
verification: [],
|
|
2564
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2565
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2566
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2567
|
-
// is not.
|
|
2568
|
-
skipDirs: ["target"]
|
|
2569
|
-
},
|
|
2570
|
-
csharp: {
|
|
2571
|
-
id: "csharp",
|
|
2572
|
-
displayName: "C#",
|
|
2573
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2574
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2575
|
-
packageManagers: [
|
|
2576
|
-
{
|
|
2577
|
-
id: "dotnet",
|
|
2578
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2579
|
-
// out of the repo's own build graph.
|
|
2580
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2581
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2582
|
-
ingest: {
|
|
2583
|
-
kind: "auto",
|
|
2584
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2585
|
-
entrypointExtensions: [".csproj"]
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
],
|
|
2589
|
-
sdk: {
|
|
2590
|
-
packageName: "Algolia.Search",
|
|
2591
|
-
versionPin: "7.*",
|
|
2592
|
-
docKey: "csharp"
|
|
2593
|
-
},
|
|
2594
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2595
|
-
verification: [
|
|
2596
|
-
{
|
|
2597
|
-
label: "dotnet build",
|
|
2598
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2599
|
-
requiresFile: CSHARP_PROJECT
|
|
2600
|
-
}
|
|
2601
|
-
],
|
|
2602
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2603
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2604
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2605
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2606
|
-
skipDirs: ["bin", "obj"]
|
|
2607
|
-
},
|
|
2608
|
-
swift: {
|
|
2609
|
-
id: "swift",
|
|
2610
|
-
displayName: "Swift",
|
|
2611
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2612
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2613
|
-
packageManagers: [
|
|
2614
|
-
{
|
|
2615
|
-
id: "swiftpm",
|
|
2616
|
-
dependency: {
|
|
2617
|
-
mode: "agent-declares",
|
|
2618
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2619
|
-
},
|
|
2620
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2621
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2622
|
-
// this fails.
|
|
2623
|
-
installSteps: [
|
|
2624
|
-
{
|
|
2625
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2626
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2627
|
-
}
|
|
2628
|
-
],
|
|
2629
|
-
ingest: {
|
|
2630
|
-
kind: "auto",
|
|
2631
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2632
|
-
entrypointExtensions: [".swift"]
|
|
2633
|
-
}
|
|
2634
|
-
}
|
|
2635
|
-
],
|
|
2636
|
-
sdk: {
|
|
2637
|
-
packageName: "algoliasearch-client-swift",
|
|
2638
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2639
|
-
// Package.swift dependency pins the patch.
|
|
2640
|
-
versionPin: 'from: "9.0.0"',
|
|
2641
|
-
docKey: "swift"
|
|
2642
|
-
},
|
|
2643
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2644
|
-
verification: [
|
|
2645
|
-
{
|
|
2646
|
-
label: "swift build",
|
|
2647
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2648
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2649
|
-
}
|
|
2650
|
-
],
|
|
2651
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2652
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2653
|
-
},
|
|
2654
|
-
dart: {
|
|
2655
|
-
id: "dart",
|
|
2656
|
-
displayName: "Dart",
|
|
2657
|
-
aliases: ["dart", "flutter"],
|
|
2658
|
-
manifests: ["pubspec.yaml"],
|
|
2659
|
-
packageManagers: [
|
|
2660
|
-
{
|
|
2661
|
-
id: "flutter-pub",
|
|
2662
|
-
detectFiles: [".metadata"],
|
|
2663
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2664
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2665
|
-
ingest: {
|
|
2666
|
-
kind: "auto",
|
|
2667
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2668
|
-
entrypointExtensions: [".dart"]
|
|
2669
|
-
}
|
|
2670
|
-
},
|
|
2671
|
-
{
|
|
2672
|
-
id: "pub",
|
|
2673
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2674
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2675
|
-
ingest: {
|
|
2676
|
-
kind: "auto",
|
|
2677
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2678
|
-
entrypointExtensions: [".dart"]
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
],
|
|
2682
|
-
sdk: {
|
|
2683
|
-
packageName: "algolia_client_search",
|
|
2684
|
-
versionPin: "^1.0.0",
|
|
2685
|
-
docKey: "dart"
|
|
2686
|
-
},
|
|
2687
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2688
|
-
verification: [
|
|
2689
|
-
{
|
|
2690
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2691
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2692
|
-
// missing path fails the whole verification pass.
|
|
2693
|
-
label: "dart analyze",
|
|
2694
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2695
|
-
requiresFile: INGEST_DIR
|
|
2696
|
-
}
|
|
2697
|
-
],
|
|
2698
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2699
|
-
skipDirs: ["build"]
|
|
2700
|
-
}
|
|
2701
|
-
};
|
|
2702
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2703
|
-
var JAVASCRIPT = "javascript";
|
|
2704
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2705
|
-
LANGUAGE_PROFILES
|
|
2706
|
-
).map((profile2) => profile2.displayName);
|
|
2707
|
-
function isBackendLanguage(profile2) {
|
|
2708
|
-
return profile2.id !== JAVASCRIPT;
|
|
2709
|
-
}
|
|
2710
|
-
function normalizeLanguageName(name) {
|
|
2711
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2712
|
-
}
|
|
2713
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2714
|
-
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2715
|
-
for (const alias of [profile2.id, profile2.displayName, ...profile2.aliases]) {
|
|
2716
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile2.id);
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
function resolveLanguageProfile(name) {
|
|
2720
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2721
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2722
|
-
}
|
|
2723
|
-
function isSameLanguage(a, b) {
|
|
2724
|
-
const x = resolveLanguageProfile(a);
|
|
2725
|
-
const y = resolveLanguageProfile(b);
|
|
2726
|
-
if (x && y) return x.id === y.id;
|
|
2727
|
-
if (x || y) return false;
|
|
2728
|
-
const folded = normalizeLanguageName(a);
|
|
2729
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2730
|
-
}
|
|
2731
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2732
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2733
|
-
...BASE_SKIP_DIRS,
|
|
2734
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2735
|
-
]);
|
|
2736
|
-
var ALLOWED_BINARIES = new Set(
|
|
2737
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile2) => [
|
|
2738
|
-
...profile2.packageManagers.flatMap((pm) => [
|
|
2739
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2740
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2741
|
-
]),
|
|
2742
|
-
...profile2.verification.map((v) => v.argv[0])
|
|
2743
|
-
])
|
|
2744
|
-
);
|
|
2745
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2746
|
-
function isWorktreeRelativeCommand(command) {
|
|
2747
|
-
return command.includes("/");
|
|
2748
|
-
}
|
|
2749
|
-
function withCommand(argv, command) {
|
|
2750
|
-
return [command, ...argv.slice(1)];
|
|
2751
|
-
}
|
|
2752
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2753
|
-
const { dependency } = packageManager;
|
|
2754
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2755
|
-
return packageManager;
|
|
2756
|
-
}
|
|
2757
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2758
|
-
(file) => existsSync2(join9(root, file))
|
|
2759
|
-
);
|
|
2760
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2761
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2762
|
-
}
|
|
2763
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2764
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2765
|
-
if (!listing.entries) {
|
|
2766
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2767
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2768
|
-
}
|
|
2769
|
-
const suffix = manifest.slice(1);
|
|
2770
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2771
|
-
}
|
|
2772
|
-
async function profileManifestPresent(root, profile2, listing) {
|
|
2773
|
-
for (const manifest of profile2.manifests) {
|
|
2774
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2775
|
-
}
|
|
2776
|
-
return false;
|
|
2777
|
-
}
|
|
2778
|
-
async function detectProfilesFromManifests(root) {
|
|
2779
|
-
const listing = {};
|
|
2780
|
-
const found = [];
|
|
2781
|
-
for (const profile2 of Object.values(LANGUAGE_PROFILES)) {
|
|
2782
|
-
if (await profileManifestPresent(root, profile2, listing)) found.push(profile2);
|
|
2783
|
-
}
|
|
2784
|
-
return found;
|
|
2785
|
-
}
|
|
2786
|
-
async function hasProfileManifest(root, profile2) {
|
|
2787
|
-
return profileManifestPresent(root, profile2, {});
|
|
2788
|
-
}
|
|
2789
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2790
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2791
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2792
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2793
|
-
const candidates = [
|
|
2794
|
-
...new Map(
|
|
2795
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2796
|
-
).values()
|
|
2797
|
-
];
|
|
2798
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2799
|
-
}
|
|
2800
|
-
async function resolveToolchain(root, profile2) {
|
|
2801
|
-
const matched = profile2.packageManagers.find(
|
|
2802
|
-
(pm) => [...pm.lockfiles ?? [], ...pm.detectFiles ?? []].some(
|
|
2803
|
-
(f) => existsSync2(join9(root, f))
|
|
2804
|
-
)
|
|
2805
|
-
);
|
|
2806
|
-
const packageManager = resolveDeclaredManifest(
|
|
2807
|
-
root,
|
|
2808
|
-
matched ?? profile2.packageManagers[0]
|
|
2809
|
-
);
|
|
2810
|
-
let { installSteps, ingest } = packageManager;
|
|
2811
|
-
installSteps = installSteps.map(
|
|
2812
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2813
|
-
);
|
|
2814
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2815
|
-
ingest = {
|
|
2816
|
-
...ingest,
|
|
2817
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
if (profile2.id === "javascript") {
|
|
2821
|
-
const pm = await detectPackageManager(root);
|
|
2822
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2823
|
-
installSteps = installSteps.map((step) => ({
|
|
2824
|
-
...step,
|
|
2825
|
-
argv: withCommand(step.argv, pm)
|
|
2826
|
-
}));
|
|
2827
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2828
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
return { profile: profile2, packageManager, installSteps, ingest };
|
|
2833
|
-
}
|
|
2834
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2835
|
-
if (ingest.kind !== "auto") {
|
|
2836
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2837
|
-
}
|
|
2838
|
-
return ingest.argv.map(
|
|
2839
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2840
|
-
);
|
|
2841
|
-
}
|
|
2842
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2843
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2844
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2845
|
-
}
|
|
2846
|
-
function ingestScriptDir(profile2) {
|
|
2847
|
-
const parts = profile2.ingestEntrypointExample.split("/");
|
|
2848
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2849
|
-
}
|
|
2850
|
-
function dependencyInstruction(toolchain) {
|
|
2851
|
-
const { profile: profile2, packageManager } = toolchain;
|
|
2852
|
-
const { packageName, versionPin } = profile2.sdk;
|
|
2853
|
-
const also = profile2.sdk.alsoRequires ? ` ${profile2.sdk.alsoRequires}` : "";
|
|
2854
|
-
switch (packageManager.dependency.mode) {
|
|
2855
|
-
case "wizard-installs":
|
|
2856
|
-
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2857
|
-
case "code-imports":
|
|
2858
|
-
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2859
|
-
case "agent-declares":
|
|
2860
|
-
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
|
|
2864
|
-
// src/lib/tools/searchFiles.ts
|
|
2865
|
-
var MAX_QUERY_LENGTH = 1e3;
|
|
2866
|
-
async function walkFiles(dir) {
|
|
2867
|
-
const out = [];
|
|
2868
|
-
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2869
|
-
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2870
|
-
const full = join10(dir, e.name);
|
|
2871
|
-
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2872
|
-
else if (e.isFile()) out.push(full);
|
|
2873
|
-
}
|
|
2874
|
-
return out;
|
|
2448
|
+
import z12 from "zod";
|
|
2449
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2450
|
+
import { join as join7 } from "node:path";
|
|
2451
|
+
var MAX_QUERY_LENGTH = 1e3;
|
|
2452
|
+
async function walkFiles(dir) {
|
|
2453
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2454
|
+
const out = [];
|
|
2455
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2456
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2457
|
+
const full = join7(dir, e.name);
|
|
2458
|
+
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2459
|
+
else if (e.isFile()) out.push(full);
|
|
2460
|
+
}
|
|
2461
|
+
return out;
|
|
2875
2462
|
}
|
|
2876
2463
|
function searchFilesTool(ctx) {
|
|
2877
2464
|
return tool7({
|
|
2878
2465
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
2879
|
-
inputSchema:
|
|
2880
|
-
query:
|
|
2881
|
-
path:
|
|
2466
|
+
inputSchema: z12.object({
|
|
2467
|
+
query: z12.string().describe("JavaScript RegExp pattern to search for"),
|
|
2468
|
+
path: z12.string().optional().describe("Directory to search in (default: cwd)")
|
|
2882
2469
|
}),
|
|
2883
2470
|
execute: async ({ query, path = "." }) => {
|
|
2884
2471
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2900,7 +2487,7 @@ function searchFilesTool(ctx) {
|
|
|
2900
2487
|
for (const file of await walkFiles(resolved.target)) {
|
|
2901
2488
|
let content;
|
|
2902
2489
|
try {
|
|
2903
|
-
content = await
|
|
2490
|
+
content = await readFile5(file, "utf8");
|
|
2904
2491
|
} catch {
|
|
2905
2492
|
continue;
|
|
2906
2493
|
}
|
|
@@ -2922,148 +2509,90 @@ function searchFilesTool(ctx) {
|
|
|
2922
2509
|
|
|
2923
2510
|
// src/lib/tools/verifyImplementation.ts
|
|
2924
2511
|
import { tool as tool8 } from "ai";
|
|
2925
|
-
import
|
|
2926
|
-
|
|
2927
|
-
// src/lib/tools/repoVerification.ts
|
|
2928
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2929
|
-
import { join as join11 } from "node:path";
|
|
2512
|
+
import z13 from "zod";
|
|
2930
2513
|
|
|
2931
2514
|
// src/lib/tools/utils/runCommand.ts
|
|
2932
2515
|
import { spawn as spawn2 } from "node:child_process";
|
|
2933
|
-
|
|
2934
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2935
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2936
|
-
var KILL_GRACE_MS = 5e3;
|
|
2937
|
-
function runCommand(command, args, options = {}) {
|
|
2938
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2516
|
+
function runCommand(command, args, cwd) {
|
|
2939
2517
|
return new Promise((resolve4) => {
|
|
2940
2518
|
let output = "";
|
|
2941
|
-
let settled = false;
|
|
2942
2519
|
const child = spawn2(command, args, {
|
|
2943
2520
|
cwd,
|
|
2944
|
-
|
|
2945
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2946
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2521
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2947
2522
|
});
|
|
2948
|
-
const settle = (result) => {
|
|
2949
|
-
if (settled) return;
|
|
2950
|
-
settled = true;
|
|
2951
|
-
clearTimeout(timer);
|
|
2952
|
-
resolve4(result);
|
|
2953
|
-
};
|
|
2954
|
-
const timer = setTimeout(() => {
|
|
2955
|
-
child.kill("SIGTERM");
|
|
2956
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2957
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2958
|
-
settle({
|
|
2959
|
-
code: 1,
|
|
2960
|
-
output: `${output}
|
|
2961
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2962
|
-
timedOut: true
|
|
2963
|
-
});
|
|
2964
|
-
}, timeoutMs);
|
|
2965
2523
|
child.stdout?.on("data", (d) => output += d);
|
|
2966
2524
|
child.stderr?.on("data", (d) => output += d);
|
|
2967
2525
|
child.on(
|
|
2968
2526
|
"error",
|
|
2969
|
-
(err) =>
|
|
2970
|
-
code: 1,
|
|
2971
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
2972
|
-
timedOut: false
|
|
2973
|
-
})
|
|
2974
|
-
);
|
|
2975
|
-
child.on(
|
|
2976
|
-
"close",
|
|
2977
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2527
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
2978
2528
|
);
|
|
2529
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2979
2530
|
});
|
|
2980
2531
|
}
|
|
2981
2532
|
|
|
2533
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2534
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2535
|
+
import { existsSync } from "node:fs";
|
|
2536
|
+
import { join as join8 } from "node:path";
|
|
2537
|
+
var LOCKFILES = [
|
|
2538
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2539
|
+
["yarn.lock", "yarn"],
|
|
2540
|
+
["bun.lockb", "bun"],
|
|
2541
|
+
["bun.lock", "bun"],
|
|
2542
|
+
["package-lock.json", "npm"]
|
|
2543
|
+
];
|
|
2544
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2545
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2546
|
+
}
|
|
2547
|
+
function packageManagerFrom(pkg) {
|
|
2548
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2549
|
+
}
|
|
2550
|
+
function packageManagerFromLockfile(cwd) {
|
|
2551
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2552
|
+
}
|
|
2553
|
+
async function detectPackageManager(cwd) {
|
|
2554
|
+
try {
|
|
2555
|
+
const pkg = await readPackageJson(cwd);
|
|
2556
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2557
|
+
} catch {
|
|
2558
|
+
}
|
|
2559
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2982
2562
|
// src/lib/tools/repoVerification.ts
|
|
2983
2563
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2984
|
-
async function
|
|
2985
|
-
const { code, output } = await runCommand(binary, args, {
|
|
2986
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2987
|
-
});
|
|
2988
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2989
|
-
}
|
|
2990
|
-
async function javascriptChecks() {
|
|
2564
|
+
async function runRepoVerificationCheck() {
|
|
2991
2565
|
let pkg;
|
|
2992
2566
|
try {
|
|
2993
2567
|
pkg = await readPackageJson();
|
|
2994
2568
|
} catch (err) {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
};
|
|
2569
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2570
|
+
return { ok: false, checks: [], limitation };
|
|
2998
2571
|
}
|
|
2999
2572
|
const scripts = pkg.scripts ?? {};
|
|
3000
2573
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3001
2574
|
if (present.length === 0) {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
};
|
|
2575
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2576
|
+
return { ok: false, checks: [], limitation };
|
|
3005
2577
|
}
|
|
3006
2578
|
const pm = await detectPackageManager(process.cwd());
|
|
3007
2579
|
const checks = [];
|
|
3008
2580
|
for (const script of present) {
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
);
|
|
3012
|
-
}
|
|
3013
|
-
return { checks };
|
|
3014
|
-
}
|
|
3015
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3016
|
-
const ids = [...new Set(languages)];
|
|
3017
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3018
|
-
const checks = [];
|
|
3019
|
-
const limitations = [];
|
|
3020
|
-
for (const id of ids) {
|
|
3021
|
-
if (id === JAVASCRIPT) {
|
|
3022
|
-
const result = await javascriptChecks();
|
|
3023
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3024
|
-
else limitations.push(result.limitation);
|
|
3025
|
-
continue;
|
|
3026
|
-
}
|
|
3027
|
-
const profile2 = LANGUAGE_PROFILES[id];
|
|
3028
|
-
const runnable = profile2.verification.filter(
|
|
3029
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3030
|
-
);
|
|
3031
|
-
if (runnable.length === 0) {
|
|
3032
|
-
limitations.push(
|
|
3033
|
-
`No mechanical verification available for ${profile2.displayName} in this repo.`
|
|
3034
|
-
);
|
|
3035
|
-
continue;
|
|
3036
|
-
}
|
|
3037
|
-
for (const spec of runnable) {
|
|
3038
|
-
checks.push(
|
|
3039
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [
|
|
3040
|
-
...spec.argv.slice(1)
|
|
3041
|
-
])
|
|
3042
|
-
);
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
if (checks.length === 0) {
|
|
3046
|
-
return {
|
|
3047
|
-
ok: false,
|
|
3048
|
-
checks: [],
|
|
3049
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3050
|
-
};
|
|
2581
|
+
const command = `${pm} run ${script}`;
|
|
2582
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2583
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3051
2584
|
}
|
|
3052
|
-
return {
|
|
3053
|
-
ok: checks.every((c) => c.ok),
|
|
3054
|
-
checks,
|
|
3055
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3056
|
-
};
|
|
2585
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3057
2586
|
}
|
|
3058
2587
|
|
|
3059
2588
|
// src/lib/tools/verifyImplementation.ts
|
|
3060
|
-
function verifyImplementationTool(
|
|
2589
|
+
function verifyImplementationTool() {
|
|
3061
2590
|
return tool8({
|
|
3062
|
-
description: "Run the repo's mechanical verification
|
|
3063
|
-
inputSchema:
|
|
2591
|
+
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.",
|
|
2592
|
+
inputSchema: z13.object(),
|
|
3064
2593
|
execute: async () => {
|
|
3065
|
-
logger.info(
|
|
3066
|
-
return runRepoVerificationCheck(
|
|
2594
|
+
logger.info("called verifyImplementation tool");
|
|
2595
|
+
return runRepoVerificationCheck();
|
|
3067
2596
|
}
|
|
3068
2597
|
});
|
|
3069
2598
|
}
|
|
@@ -3074,7 +2603,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
3074
2603
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3075
2604
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
3076
2605
|
import { dirname as dirname6 } from "node:path";
|
|
3077
|
-
import
|
|
2606
|
+
import z14 from "zod";
|
|
3078
2607
|
var DATA_DIR = ".algolia-wizard/data";
|
|
3079
2608
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
3080
2609
|
var MAX_RECORDS = 100;
|
|
@@ -3086,17 +2615,17 @@ var anthropic = createAnthropic({
|
|
|
3086
2615
|
function generateRecordTool(ctx) {
|
|
3087
2616
|
return tool9({
|
|
3088
2617
|
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.",
|
|
3089
|
-
inputSchema:
|
|
3090
|
-
entityName:
|
|
3091
|
-
attributes:
|
|
3092
|
-
count:
|
|
3093
|
-
hint:
|
|
2618
|
+
inputSchema: z14.object({
|
|
2619
|
+
entityName: z14.string().describe("Name of the entity to generate records for."),
|
|
2620
|
+
attributes: z14.array(z14.string()).describe("Attribute names each record must contain."),
|
|
2621
|
+
count: z14.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2622
|
+
hint: z14.string().optional().describe("Optional context to steer realistic values.")
|
|
3094
2623
|
}),
|
|
3095
2624
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
3096
2625
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
3097
2626
|
try {
|
|
3098
|
-
const value =
|
|
3099
|
-
const recordSchema =
|
|
2627
|
+
const value = z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()]);
|
|
2628
|
+
const recordSchema = z14.object(
|
|
3100
2629
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
3101
2630
|
);
|
|
3102
2631
|
const generateBatch = async (batchCount) => {
|
|
@@ -3106,8 +2635,8 @@ function generateRecordTool(ctx) {
|
|
|
3106
2635
|
const { output } = await generateText({
|
|
3107
2636
|
model: anthropic(RECORD_MODEL),
|
|
3108
2637
|
output: Output.object({
|
|
3109
|
-
schema:
|
|
3110
|
-
records:
|
|
2638
|
+
schema: z14.object({
|
|
2639
|
+
records: z14.array(recordSchema).length(batchCount)
|
|
3111
2640
|
})
|
|
3112
2641
|
}),
|
|
3113
2642
|
prompt: [
|
|
@@ -3165,12 +2694,12 @@ function generateRecordTool(ctx) {
|
|
|
3165
2694
|
|
|
3166
2695
|
// src/lib/tools/notifyUser.ts
|
|
3167
2696
|
import { tool as tool10 } from "ai";
|
|
3168
|
-
import
|
|
2697
|
+
import z15 from "zod";
|
|
3169
2698
|
function notifyUserTool() {
|
|
3170
2699
|
return tool10({
|
|
3171
2700
|
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.`,
|
|
3172
|
-
inputSchema:
|
|
3173
|
-
message:
|
|
2701
|
+
inputSchema: z15.object({
|
|
2702
|
+
message: z15.string().describe(
|
|
3174
2703
|
"Short, plain-language description of what you are doing now."
|
|
3175
2704
|
)
|
|
3176
2705
|
}),
|
|
@@ -3189,17 +2718,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3189
2718
|
read: 20,
|
|
3190
2719
|
match: 100
|
|
3191
2720
|
};
|
|
3192
|
-
function createToolContext({
|
|
3193
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3194
|
-
cwd = process.cwd(),
|
|
3195
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3196
|
-
} = {}) {
|
|
2721
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3197
2722
|
return {
|
|
3198
2723
|
root: cwd,
|
|
3199
2724
|
cwd,
|
|
3200
2725
|
limits,
|
|
3201
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3202
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2726
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3203
2727
|
};
|
|
3204
2728
|
}
|
|
3205
2729
|
|
|
@@ -3236,7 +2760,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3236
2760
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3237
2761
|
verifyImplementation: withLogging(
|
|
3238
2762
|
"verifyImplementation",
|
|
3239
|
-
verifyImplementationTool(
|
|
2763
|
+
verifyImplementationTool()
|
|
3240
2764
|
),
|
|
3241
2765
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3242
2766
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3272,7 +2796,7 @@ async function runAgent(req) {
|
|
|
3272
2796
|
baseURL: PROXY_BASE_URL,
|
|
3273
2797
|
fetch: proxyFetch
|
|
3274
2798
|
});
|
|
3275
|
-
const toolContext = createToolContext(
|
|
2799
|
+
const toolContext = createToolContext();
|
|
3276
2800
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3277
2801
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3278
2802
|
const instructions = [
|
|
@@ -3355,19 +2879,16 @@ async function runAgent(req) {
|
|
|
3355
2879
|
}
|
|
3356
2880
|
|
|
3357
2881
|
// src/actions/detectLanguage.ts
|
|
3358
|
-
import
|
|
3359
|
-
var detectLanguageSchema =
|
|
3360
|
-
languages:
|
|
3361
|
-
frameworks:
|
|
2882
|
+
import z18 from "zod";
|
|
2883
|
+
var detectLanguageSchema = z18.object({
|
|
2884
|
+
languages: z18.array(z18.object({ name: z18.string(), version: z18.string() })),
|
|
2885
|
+
frameworks: z18.array(z18.object({ name: z18.string(), version: z18.string() }))
|
|
3362
2886
|
});
|
|
3363
2887
|
var detectLanguage = () => runAgent({
|
|
3364
2888
|
instructions: [
|
|
3365
2889
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3366
|
-
"
|
|
3367
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3368
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2890
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3369
2891
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3370
|
-
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
3371
2892
|
"Return the exact version",
|
|
3372
2893
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3373
2894
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3379,31 +2900,31 @@ var detectLanguage = () => runAgent({
|
|
|
3379
2900
|
});
|
|
3380
2901
|
|
|
3381
2902
|
// src/actions/analyzeCodebase.ts
|
|
3382
|
-
import
|
|
2903
|
+
import z19 from "zod";
|
|
3383
2904
|
var READONLY_TOOLS = [
|
|
3384
2905
|
"listFiles",
|
|
3385
2906
|
"changeDirectory",
|
|
3386
2907
|
"readFile",
|
|
3387
2908
|
"searchFiles"
|
|
3388
2909
|
];
|
|
3389
|
-
var ingestionAnalysisSchema =
|
|
3390
|
-
ingestionAnalysis:
|
|
3391
|
-
|
|
3392
|
-
name:
|
|
3393
|
-
paths:
|
|
2910
|
+
var ingestionAnalysisSchema = z19.object({
|
|
2911
|
+
ingestionAnalysis: z19.array(
|
|
2912
|
+
z19.object({
|
|
2913
|
+
name: z19.string(),
|
|
2914
|
+
paths: z19.array(z19.string()),
|
|
3394
2915
|
// indexable fields the agent found for this entity
|
|
3395
|
-
attributes:
|
|
2916
|
+
attributes: z19.array(z19.string())
|
|
3396
2917
|
})
|
|
3397
2918
|
)
|
|
3398
2919
|
});
|
|
3399
|
-
var searchImplementationAnalysisSchema =
|
|
3400
|
-
searchImplementationAnalysis:
|
|
2920
|
+
var searchImplementationAnalysisSchema = z19.object({
|
|
2921
|
+
searchImplementationAnalysis: z19.string()
|
|
3401
2922
|
});
|
|
3402
|
-
var verificationSchema =
|
|
3403
|
-
verification:
|
|
2923
|
+
var verificationSchema = z19.object({
|
|
2924
|
+
verification: z19.array(z19.string())
|
|
3404
2925
|
});
|
|
3405
2926
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
3406
|
-
var analyzeCodebaseSchema =
|
|
2927
|
+
var analyzeCodebaseSchema = z19.object({
|
|
3407
2928
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3408
2929
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
3409
2930
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -3416,7 +2937,6 @@ var MODE_CONFIG = {
|
|
|
3416
2937
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3417
2938
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
3418
2939
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3419
|
-
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
3420
2940
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3421
2941
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3422
2942
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3428,9 +2948,8 @@ var MODE_CONFIG = {
|
|
|
3428
2948
|
instructions: [
|
|
3429
2949
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3430
2950
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3431
|
-
"
|
|
3432
|
-
|
|
3433
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2951
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2952
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3434
2953
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3435
2954
|
"When done, call reportStatus"
|
|
3436
2955
|
],
|
|
@@ -3439,8 +2958,8 @@ var MODE_CONFIG = {
|
|
|
3439
2958
|
verification: {
|
|
3440
2959
|
instructions: [
|
|
3441
2960
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3442
|
-
"Look at
|
|
3443
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2961
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2962
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3444
2963
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3445
2964
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3446
2965
|
"When done, call reportStatus"
|
|
@@ -3467,7 +2986,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3467
2986
|
// package.json
|
|
3468
2987
|
var package_default = {
|
|
3469
2988
|
name: "@algolia/wizard",
|
|
3470
|
-
version: "0.6.0-rc.
|
|
2989
|
+
version: "0.6.0-rc.53.32",
|
|
3471
2990
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3472
2991
|
type: "module",
|
|
3473
2992
|
engines: {
|
|
@@ -3515,7 +3034,6 @@ var package_default = {
|
|
|
3515
3034
|
dependencies: {
|
|
3516
3035
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
3517
3036
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
3518
|
-
"@algolia/cli": "^5.11.0",
|
|
3519
3037
|
"@hono/node-server": "^2.0.10",
|
|
3520
3038
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
3521
3039
|
"@segment/analytics-node": "^3.1.0",
|
|
@@ -3530,7 +3048,6 @@ var package_default = {
|
|
|
3530
3048
|
nanoid: "^5.1.15",
|
|
3531
3049
|
pino: "^10.3.1",
|
|
3532
3050
|
react: "^19.2.7",
|
|
3533
|
-
toml: "^4.1.1",
|
|
3534
3051
|
varlock: "^1.5.1",
|
|
3535
3052
|
zod: "^4.4.3",
|
|
3536
3053
|
zustand: "^5.0.14"
|
|
@@ -3570,185 +3087,82 @@ function parseEntries(raw) {
|
|
|
3570
3087
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3571
3088
|
}
|
|
3572
3089
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3573
|
-
|
|
3574
|
-
// src/actions/confirmLanguage.ts
|
|
3575
|
-
import z19 from "zod";
|
|
3576
|
-
var confirmLanguageSchema = z19.object({
|
|
3577
|
-
languages: detectLanguageSchema.shape.languages
|
|
3578
|
-
});
|
|
3579
|
-
var OTHER_OPTION = "Other";
|
|
3580
|
-
function confirmed(languages) {
|
|
3581
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3582
|
-
return { languages };
|
|
3583
|
-
}
|
|
3584
|
-
async function askOtherLanguage(ctx) {
|
|
3585
|
-
let prompt = "enter the language for your ingestion script";
|
|
3090
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3586
3091
|
for (; ; ) {
|
|
3587
3092
|
const answer = await ctx.requestUserInput({
|
|
3588
3093
|
prompt,
|
|
3589
3094
|
promptType: "textInput",
|
|
3590
|
-
options: []
|
|
3095
|
+
options: [],
|
|
3096
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3591
3097
|
});
|
|
3592
3098
|
if (typeof answer !== "string") {
|
|
3593
|
-
throw new Error("
|
|
3099
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3594
3100
|
}
|
|
3595
|
-
const
|
|
3596
|
-
if (
|
|
3597
|
-
prompt = "
|
|
3101
|
+
const entries = parseEntries(answer);
|
|
3102
|
+
if (entries.length || !required) return entries;
|
|
3103
|
+
prompt = "Please enter at least one entry:";
|
|
3598
3104
|
}
|
|
3599
3105
|
}
|
|
3106
|
+
|
|
3107
|
+
// src/actions/confirmLanguage.ts
|
|
3108
|
+
import z21 from "zod";
|
|
3109
|
+
var confirmLanguageSchema = z21.object({
|
|
3110
|
+
languages: detectLanguageSchema.shape.languages
|
|
3111
|
+
});
|
|
3600
3112
|
async function confirmLanguage(ctx) {
|
|
3601
3113
|
const detected = ctx.getStepOutput("project-scan");
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3608
|
-
promptType: "acceptReject",
|
|
3609
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3610
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3611
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3612
|
-
});
|
|
3613
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3614
|
-
}
|
|
3615
|
-
const options = [...CURATED_LANGUAGES];
|
|
3616
|
-
for (const language of detectedLanguages) {
|
|
3617
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3618
|
-
options.push(language.name);
|
|
3619
|
-
}
|
|
3620
|
-
}
|
|
3621
|
-
options.push(OTHER_OPTION);
|
|
3622
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3623
|
-
const secondary = options.map(
|
|
3624
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3625
|
-
);
|
|
3626
|
-
const defaultSelectedIndex = Math.max(
|
|
3627
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3628
|
-
0
|
|
3629
|
-
);
|
|
3630
|
-
const selection = await ctx.requestUserInput({
|
|
3631
|
-
prompt: "select the language for your ingestion script",
|
|
3632
|
-
promptType: "multipleChoice",
|
|
3633
|
-
options,
|
|
3634
|
-
secondary,
|
|
3635
|
-
defaultSelectedIndex
|
|
3114
|
+
const answer = await ctx.requestUserInput({
|
|
3115
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
3116
|
+
promptType: "acceptReject",
|
|
3117
|
+
options: ["Yes", "No"],
|
|
3118
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3636
3119
|
});
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3120
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
3121
|
+
required: true
|
|
3122
|
+
});
|
|
3123
|
+
track("AI Wizard Language Confirmed", {
|
|
3124
|
+
languages
|
|
3125
|
+
});
|
|
3126
|
+
return { languages };
|
|
3643
3127
|
}
|
|
3644
3128
|
|
|
3645
3129
|
// src/actions/confirmFramework.ts
|
|
3646
|
-
import
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
var
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3658
|
-
{
|
|
3659
|
-
name: "Vanilla JS",
|
|
3660
|
-
strategy: "js",
|
|
3661
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3662
|
-
},
|
|
3663
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3664
|
-
// templates get InstantSearch.js from a CDN.
|
|
3665
|
-
{
|
|
3666
|
-
name: "Rails",
|
|
3667
|
-
strategy: "cdn-template",
|
|
3668
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3669
|
-
},
|
|
3670
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3671
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3672
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3673
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3674
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3675
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3676
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3677
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3678
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3130
|
+
import z22 from "zod";
|
|
3131
|
+
var confirmFrameworkSchema = z22.object({
|
|
3132
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3133
|
+
});
|
|
3134
|
+
var CURATED_FRAMEWORKS = [
|
|
3135
|
+
"Next.js",
|
|
3136
|
+
"React",
|
|
3137
|
+
"Vue",
|
|
3138
|
+
"Angular",
|
|
3139
|
+
"Svelte",
|
|
3140
|
+
"Vanilla JS"
|
|
3679
3141
|
];
|
|
3680
|
-
var
|
|
3681
|
-
(f) => f.name
|
|
3682
|
-
);
|
|
3142
|
+
var OTHER_OPTION = "Other";
|
|
3683
3143
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3684
|
-
var
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3144
|
+
var FRAMEWORK_ALIASES = {
|
|
3145
|
+
next: "nextjs",
|
|
3146
|
+
nextjs: "nextjs",
|
|
3147
|
+
react: "react",
|
|
3148
|
+
reactjs: "react",
|
|
3149
|
+
vue: "vue",
|
|
3150
|
+
vuejs: "vue",
|
|
3151
|
+
angular: "angular",
|
|
3152
|
+
angularjs: "angular",
|
|
3153
|
+
svelte: "svelte",
|
|
3154
|
+
sveltekit: "svelte",
|
|
3155
|
+
vanillajs: "vanillajs",
|
|
3156
|
+
vanilla: "vanillajs",
|
|
3157
|
+
javascript: "vanillajs",
|
|
3158
|
+
js: "vanillajs"
|
|
3159
|
+
};
|
|
3160
|
+
var isSameFramework = (a, b) => {
|
|
3161
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
3162
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3697
3163
|
return x !== "" && x === y;
|
|
3698
|
-
}
|
|
3699
|
-
function
|
|
3700
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3701
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3702
|
-
if (strategy) return strategy;
|
|
3703
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3704
|
-
}
|
|
3705
|
-
function searchDocKey(strategy) {
|
|
3706
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3707
|
-
}
|
|
3708
|
-
function bundlesJavaScript(strategy) {
|
|
3709
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3710
|
-
}
|
|
3711
|
-
function canScaffoldSearchUI(strategy) {
|
|
3712
|
-
return strategy !== "none";
|
|
3713
|
-
}
|
|
3714
|
-
var ENV_PREFIXES = [
|
|
3715
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3716
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3717
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3718
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3719
|
-
];
|
|
3720
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3721
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3722
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3723
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3724
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3725
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3726
|
-
}
|
|
3727
|
-
return DEFAULT_ENV_PREFIX;
|
|
3728
|
-
}
|
|
3729
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3730
|
-
switch (strategy) {
|
|
3731
|
-
case "react":
|
|
3732
|
-
return "React (react-instantsearch)";
|
|
3733
|
-
case "vue":
|
|
3734
|
-
return "Vue (vue-instantsearch)";
|
|
3735
|
-
case "angular":
|
|
3736
|
-
return "Angular (angular-instantsearch)";
|
|
3737
|
-
case "js":
|
|
3738
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3739
|
-
case "cdn-template":
|
|
3740
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3741
|
-
case "none":
|
|
3742
|
-
return frameworkName ?? "a native mobile app";
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
|
|
3746
|
-
// src/actions/confirmFramework.ts
|
|
3747
|
-
var confirmFrameworkSchema = z20.object({
|
|
3748
|
-
frameworks: detectLanguageSchema.shape.frameworks
|
|
3749
|
-
});
|
|
3750
|
-
var OTHER_OPTION2 = "Other";
|
|
3751
|
-
function confirmed2(name, version) {
|
|
3164
|
+
};
|
|
3165
|
+
function confirmed(name, version) {
|
|
3752
3166
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3753
3167
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3754
3168
|
return { frameworks };
|
|
@@ -3776,7 +3190,7 @@ async function confirmFramework(ctx) {
|
|
|
3776
3190
|
for (const fw of detectedFrameworks) {
|
|
3777
3191
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3778
3192
|
}
|
|
3779
|
-
options.push(
|
|
3193
|
+
options.push(OTHER_OPTION);
|
|
3780
3194
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3781
3195
|
const primary = detectedFrameworks[0];
|
|
3782
3196
|
if (primary) {
|
|
@@ -3786,7 +3200,7 @@ async function confirmFramework(ctx) {
|
|
|
3786
3200
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3787
3201
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3788
3202
|
});
|
|
3789
|
-
if (accepted === true) return
|
|
3203
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3790
3204
|
}
|
|
3791
3205
|
const secondary = options.map(
|
|
3792
3206
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3796,7 +3210,7 @@ async function confirmFramework(ctx) {
|
|
|
3796
3210
|
0
|
|
3797
3211
|
);
|
|
3798
3212
|
const selection = await ctx.requestUserInput({
|
|
3799
|
-
prompt: "select
|
|
3213
|
+
prompt: "select a framework",
|
|
3800
3214
|
promptType: "multipleChoice",
|
|
3801
3215
|
options,
|
|
3802
3216
|
secondary,
|
|
@@ -3805,10 +3219,10 @@ async function confirmFramework(ctx) {
|
|
|
3805
3219
|
if (typeof selection !== "string") {
|
|
3806
3220
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3807
3221
|
}
|
|
3808
|
-
if (selection ===
|
|
3809
|
-
return
|
|
3222
|
+
if (selection === OTHER_OPTION) {
|
|
3223
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3810
3224
|
}
|
|
3811
|
-
return
|
|
3225
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3812
3226
|
}
|
|
3813
3227
|
|
|
3814
3228
|
// src/actions/promptUser.ts
|
|
@@ -3842,8 +3256,8 @@ async function promptUser(ctx, params) {
|
|
|
3842
3256
|
}
|
|
3843
3257
|
|
|
3844
3258
|
// src/actions/confirmEntities.ts
|
|
3845
|
-
import
|
|
3846
|
-
var confirmEntitiesSchema =
|
|
3259
|
+
import z23 from "zod";
|
|
3260
|
+
var confirmEntitiesSchema = z23.object({
|
|
3847
3261
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3848
3262
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3849
3263
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3901,27 +3315,27 @@ async function confirmEntities(ctx) {
|
|
|
3901
3315
|
onSubmit: () => {
|
|
3902
3316
|
}
|
|
3903
3317
|
});
|
|
3904
|
-
const
|
|
3905
|
-
if (
|
|
3318
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3319
|
+
if (confirmed2.length === 0) {
|
|
3906
3320
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3907
3321
|
}
|
|
3908
|
-
ctx.setUserInput("confirmedEntities",
|
|
3322
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3909
3323
|
track("AI Wizard Entities Confirmed", {
|
|
3910
|
-
entities: toEntitySummary(
|
|
3324
|
+
entities: toEntitySummary(confirmed2)
|
|
3911
3325
|
});
|
|
3912
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3326
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3913
3327
|
}
|
|
3914
3328
|
|
|
3915
3329
|
// src/actions/review.ts
|
|
3916
|
-
import { z as
|
|
3917
|
-
var reviewSchema =
|
|
3330
|
+
import { z as z24 } from "zod";
|
|
3331
|
+
var reviewSchema = z24.object({
|
|
3918
3332
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3919
3333
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3920
3334
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3921
3335
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3922
|
-
summaryPoints:
|
|
3923
|
-
reviewPrompt:
|
|
3924
|
-
nextSteps:
|
|
3336
|
+
summaryPoints: z24.array(z24.string()),
|
|
3337
|
+
reviewPrompt: z24.string(),
|
|
3338
|
+
nextSteps: z24.array(z24.string())
|
|
3925
3339
|
});
|
|
3926
3340
|
function formatCompletedSteps(steps) {
|
|
3927
3341
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3933,7 +3347,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3933
3347
|
}
|
|
3934
3348
|
function formatReviewSummary(result) {
|
|
3935
3349
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3936
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3350
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3937
3351
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3938
3352
|
return {
|
|
3939
3353
|
text: `\u2192 ${step}`,
|
|
@@ -3972,17 +3386,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3972
3386
|
};
|
|
3973
3387
|
|
|
3974
3388
|
// src/actions/implement.ts
|
|
3975
|
-
import
|
|
3389
|
+
import z25 from "zod";
|
|
3976
3390
|
|
|
3977
3391
|
// src/lib/worktree.ts
|
|
3978
|
-
import { execFile } from "node:child_process";
|
|
3979
|
-
import {
|
|
3980
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3392
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3393
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3981
3394
|
import {
|
|
3982
3395
|
basename as basename2,
|
|
3983
3396
|
dirname as dirname7,
|
|
3984
3397
|
isAbsolute as isAbsolute2,
|
|
3985
|
-
join as
|
|
3398
|
+
join as join9,
|
|
3986
3399
|
relative as relative2,
|
|
3987
3400
|
resolve as resolve3
|
|
3988
3401
|
} from "node:path";
|
|
@@ -4016,8 +3429,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4016
3429
|
return out.trim().length > 0;
|
|
4017
3430
|
}
|
|
4018
3431
|
async function pruneOldWorktrees(repoRoot) {
|
|
4019
|
-
const dir =
|
|
4020
|
-
const stale = (await
|
|
3432
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3433
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4021
3434
|
for (const slug of stale) {
|
|
4022
3435
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4023
3436
|
try {
|
|
@@ -4027,7 +3440,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4027
3440
|
"worktree",
|
|
4028
3441
|
"remove",
|
|
4029
3442
|
"--force",
|
|
4030
|
-
|
|
3443
|
+
join9(dir, slug)
|
|
4031
3444
|
]);
|
|
4032
3445
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4033
3446
|
} catch (err) {
|
|
@@ -4041,55 +3454,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4041
3454
|
async function createWorktree(repoRoot) {
|
|
4042
3455
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4043
3456
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4044
|
-
const path =
|
|
3457
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4045
3458
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4046
3459
|
await pruneOldWorktrees(repoRoot);
|
|
4047
3460
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4048
3461
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4049
3462
|
return { path, branch };
|
|
4050
3463
|
}
|
|
4051
|
-
async function
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
return { ok: code === 0, output: output.trim() };
|
|
4057
|
-
}
|
|
4058
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4059
|
-
const { profile: profile2, installSteps, packageManager } = toolchain;
|
|
4060
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4061
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile2) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4062
|
-
if (!haveSomethingToInstall) {
|
|
4063
|
-
return {
|
|
4064
|
-
ok: true,
|
|
4065
|
-
output: `no ${profile2.displayName} manifest; skipped install`
|
|
4066
|
-
};
|
|
4067
|
-
}
|
|
4068
|
-
if (installSteps.length === 0) {
|
|
4069
|
-
return {
|
|
4070
|
-
ok: true,
|
|
4071
|
-
output: `${profile2.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4072
|
-
};
|
|
4073
|
-
}
|
|
4074
|
-
const outputs = [];
|
|
4075
|
-
for (const step of installSteps) {
|
|
4076
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4077
|
-
continue;
|
|
4078
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4079
|
-
if (result.output) outputs.push(result.output);
|
|
4080
|
-
if (result.ok) continue;
|
|
4081
|
-
if (step.optional) {
|
|
4082
|
-
logger.warn(
|
|
4083
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4084
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4085
|
-
);
|
|
4086
|
-
continue;
|
|
4087
|
-
}
|
|
4088
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3464
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3465
|
+
try {
|
|
3466
|
+
await readPackageJson(worktreePath);
|
|
3467
|
+
} catch {
|
|
3468
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4089
3469
|
}
|
|
4090
|
-
|
|
3470
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3471
|
+
return new Promise((resolve4) => {
|
|
3472
|
+
let output = "";
|
|
3473
|
+
const child = spawn3(pm, ["install"], {
|
|
3474
|
+
cwd: worktreePath,
|
|
3475
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3476
|
+
});
|
|
3477
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3478
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3479
|
+
child.on(
|
|
3480
|
+
"error",
|
|
3481
|
+
(err) => resolve4({
|
|
3482
|
+
ok: false,
|
|
3483
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3484
|
+
})
|
|
3485
|
+
);
|
|
3486
|
+
child.on(
|
|
3487
|
+
"close",
|
|
3488
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3489
|
+
);
|
|
3490
|
+
});
|
|
4091
3491
|
}
|
|
4092
|
-
|
|
3492
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3493
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4093
3494
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4094
3495
|
return {
|
|
4095
3496
|
ok: false,
|
|
@@ -4104,29 +3505,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4104
3505
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4105
3506
|
};
|
|
4106
3507
|
}
|
|
4107
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4108
|
-
return {
|
|
4109
|
-
ok: false,
|
|
4110
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4111
|
-
};
|
|
4112
|
-
}
|
|
4113
3508
|
return { ok: true, target };
|
|
4114
3509
|
}
|
|
4115
|
-
async function runIngestScript(worktreePath,
|
|
4116
|
-
|
|
4117
|
-
if (ingest.kind !== "auto") {
|
|
3510
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3511
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4118
3512
|
return {
|
|
4119
3513
|
ran: false,
|
|
4120
3514
|
ok: false,
|
|
4121
3515
|
output: "",
|
|
4122
|
-
reason:
|
|
3516
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4123
3517
|
};
|
|
4124
3518
|
}
|
|
4125
|
-
const validated = validateIngestEntrypoint(
|
|
4126
|
-
worktreePath,
|
|
4127
|
-
entrypoint,
|
|
4128
|
-
ingest.entrypointExtensions
|
|
4129
|
-
);
|
|
3519
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4130
3520
|
if (!validated.ok) {
|
|
4131
3521
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4132
3522
|
}
|
|
@@ -4147,13 +3537,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4147
3537
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4148
3538
|
};
|
|
4149
3539
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
3540
|
+
return new Promise((resolveRun) => {
|
|
3541
|
+
let output = "";
|
|
3542
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3543
|
+
cwd: worktreePath,
|
|
3544
|
+
shell: false,
|
|
3545
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3546
|
+
env: { ...process.env, ...env }
|
|
3547
|
+
});
|
|
3548
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3549
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3550
|
+
child.on(
|
|
3551
|
+
"error",
|
|
3552
|
+
(err) => resolveRun({
|
|
3553
|
+
ran: true,
|
|
3554
|
+
ok: false,
|
|
3555
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3556
|
+
})
|
|
3557
|
+
);
|
|
3558
|
+
child.on(
|
|
3559
|
+
"close",
|
|
3560
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3561
|
+
);
|
|
4155
3562
|
});
|
|
4156
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4157
3563
|
}
|
|
4158
3564
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4159
3565
|
const trimmed = sourcePath.trim();
|
|
@@ -4168,8 +3574,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4168
3574
|
} catch {
|
|
4169
3575
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4170
3576
|
}
|
|
4171
|
-
const relPath =
|
|
4172
|
-
const dest =
|
|
3577
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3578
|
+
const dest = join9(worktreePath, relPath);
|
|
4173
3579
|
try {
|
|
4174
3580
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4175
3581
|
await copyFile(source, dest);
|
|
@@ -4185,10 +3591,10 @@ function hasEnvVar(content, name) {
|
|
|
4185
3591
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4186
3592
|
}
|
|
4187
3593
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4188
|
-
const target =
|
|
3594
|
+
const target = join9(worktreePath, ".env");
|
|
4189
3595
|
let existing = "";
|
|
4190
3596
|
try {
|
|
4191
|
-
existing = await
|
|
3597
|
+
existing = await readFile7(target, "utf8");
|
|
4192
3598
|
} catch (err) {
|
|
4193
3599
|
if (err.code !== "ENOENT") throw err;
|
|
4194
3600
|
}
|
|
@@ -4256,135 +3662,160 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
4256
3662
|
}
|
|
4257
3663
|
}
|
|
4258
3664
|
|
|
4259
|
-
// src/lib/algoliaApiKey.ts
|
|
4260
|
-
import { z as z23 } from "zod";
|
|
4261
|
-
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
4262
|
-
var apiKeySchema = z23.object({
|
|
4263
|
-
value: z23.string().min(1),
|
|
4264
|
-
acl: z23.array(z23.string()).default([]),
|
|
4265
|
-
indexes: z23.array(z23.string()).default([])
|
|
4266
|
-
});
|
|
4267
|
-
var apiKeyListSchema = z23.object({
|
|
4268
|
-
items: z23.array(apiKeySchema).optional(),
|
|
4269
|
-
keys: z23.array(apiKeySchema).optional()
|
|
4270
|
-
}).transform((o) => o.items ?? o.keys ?? []);
|
|
4271
|
-
var createdKeySchema = z23.object({
|
|
4272
|
-
key: z23.string().min(1).optional(),
|
|
4273
|
-
value: z23.string().min(1).optional()
|
|
4274
|
-
});
|
|
4275
|
-
function canReuse(key, index) {
|
|
4276
|
-
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
4277
|
-
}
|
|
4278
|
-
async function createSearchKey(index) {
|
|
4279
|
-
const stdout = await runAlgoliaCli([
|
|
4280
|
-
"apikeys",
|
|
4281
|
-
"create",
|
|
4282
|
-
"--indices",
|
|
4283
|
-
index,
|
|
4284
|
-
"--acl",
|
|
4285
|
-
"search,browse",
|
|
4286
|
-
"--description",
|
|
4287
|
-
`wizard search-only key for ${index}`,
|
|
4288
|
-
"-o",
|
|
4289
|
-
"json"
|
|
4290
|
-
]);
|
|
4291
|
-
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
4292
|
-
const created = key ?? value;
|
|
4293
|
-
if (!created) throw new Error("apikeys create returned no key value");
|
|
4294
|
-
return created;
|
|
4295
|
-
}
|
|
4296
|
-
async function resolveSearchOnlyKey(index) {
|
|
4297
|
-
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
4298
|
-
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
4299
|
-
if (existing) {
|
|
4300
|
-
logger.info({ index }, "reusing existing search-only API key");
|
|
4301
|
-
return existing;
|
|
4302
|
-
}
|
|
4303
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
4304
|
-
return createSearchKey(index);
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4307
3665
|
// src/lib/algoliaDocs.ts
|
|
4308
|
-
import { readFileSync, existsSync as
|
|
4309
|
-
import { dirname as dirname8, join as
|
|
3666
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3667
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
4310
3668
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4311
|
-
var DOCS_SUBPATH =
|
|
3669
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
4312
3670
|
function findDocsDir() {
|
|
4313
3671
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4314
3672
|
for (; ; ) {
|
|
4315
|
-
const candidate =
|
|
4316
|
-
if (
|
|
3673
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
3674
|
+
if (existsSync2(candidate)) return candidate;
|
|
4317
3675
|
const parent = dirname8(dir);
|
|
4318
3676
|
if (parent === dir) return void 0;
|
|
4319
3677
|
dir = parent;
|
|
4320
3678
|
}
|
|
4321
3679
|
}
|
|
4322
|
-
function
|
|
3680
|
+
function loadAlgoliaDoc(language) {
|
|
3681
|
+
const docsDir = findDocsDir();
|
|
3682
|
+
if (!docsDir) {
|
|
3683
|
+
logger.warn(
|
|
3684
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3685
|
+
);
|
|
3686
|
+
return "";
|
|
3687
|
+
}
|
|
3688
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3689
|
+
if (files.length === 0) {
|
|
3690
|
+
logger.warn(
|
|
3691
|
+
{ language },
|
|
3692
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3693
|
+
);
|
|
3694
|
+
return "";
|
|
3695
|
+
}
|
|
3696
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
3697
|
+
}
|
|
3698
|
+
function getNamedDoc(name, language) {
|
|
4323
3699
|
const docsDir = findDocsDir();
|
|
4324
3700
|
if (!docsDir) {
|
|
4325
3701
|
logger.warn("docs/algolia-sdk not found");
|
|
4326
3702
|
return "";
|
|
4327
3703
|
}
|
|
4328
|
-
const file =
|
|
4329
|
-
if (!
|
|
4330
|
-
logger.warn({ name,
|
|
3704
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
3705
|
+
if (!existsSync2(file)) {
|
|
3706
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4331
3707
|
return "";
|
|
4332
3708
|
}
|
|
4333
3709
|
return readFileSync(file, "utf8").trim();
|
|
4334
3710
|
}
|
|
3711
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3712
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3713
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3714
|
+
return loadAlgoliaDoc("vue");
|
|
3715
|
+
}
|
|
3716
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3717
|
+
return loadAlgoliaDoc("react");
|
|
3718
|
+
}
|
|
3719
|
+
if (fw.includes("angular")) {
|
|
3720
|
+
return loadAlgoliaDoc("angular");
|
|
3721
|
+
}
|
|
3722
|
+
return loadAlgoliaDoc("js");
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
// src/lib/shell.ts
|
|
3726
|
+
function shellQuote(value) {
|
|
3727
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3728
|
+
}
|
|
4335
3729
|
|
|
4336
3730
|
// src/actions/implement.ts
|
|
4337
|
-
var implementSchema =
|
|
4338
|
-
filesChanged:
|
|
4339
|
-
summary:
|
|
3731
|
+
var implementSchema = z25.object({
|
|
3732
|
+
filesChanged: z25.array(z25.string()),
|
|
3733
|
+
summary: z25.string(),
|
|
4340
3734
|
// Absolute path to the throwaway worktree holding the generated changes, so
|
|
4341
3735
|
// the user can open it (`cd <worktreePath>`) or inspect the diff
|
|
4342
3736
|
// (`git -C <worktreePath> status/diff`).
|
|
4343
|
-
worktreePath:
|
|
4344
|
-
ingestCommand:
|
|
3737
|
+
worktreePath: z25.string().optional(),
|
|
3738
|
+
ingestCommand: z25.string().optional(),
|
|
4345
3739
|
// True when the user accepted the run-now prompt and the wizard executed the
|
|
4346
3740
|
// ingestion script; downstream steps use this to avoid telling the user to run
|
|
4347
3741
|
// a script that already ran.
|
|
4348
|
-
ingestScriptRan:
|
|
3742
|
+
ingestScriptRan: z25.boolean().optional(),
|
|
4349
3743
|
// Records ingested by the run-now execution, parsed from the script's
|
|
4350
3744
|
// machine-readable count line; absent when the script didn't run or emitted
|
|
4351
3745
|
// no parseable count.
|
|
4352
|
-
ingestRecordCount:
|
|
3746
|
+
ingestRecordCount: z25.number().optional(),
|
|
4353
3747
|
// Wall-clock duration of the run-now ingestion execution, in ms.
|
|
4354
|
-
ingestDurationMs:
|
|
4355
|
-
ingestionSource:
|
|
3748
|
+
ingestDurationMs: z25.number().optional(),
|
|
3749
|
+
ingestionSource: z25.enum(["local", "fileUpload", "generated"]),
|
|
4356
3750
|
// Suggested names/values, built from framework detection. The search agent is
|
|
4357
3751
|
// instructed to rename the prefix if it doesn't match the project's build
|
|
4358
3752
|
// tool, so the names it actually wrote can differ — treat these as hints, not
|
|
4359
3753
|
// ground truth (the agent's summary carries the final names).
|
|
4360
|
-
searchEnvVars:
|
|
4361
|
-
|
|
4362
|
-
name:
|
|
4363
|
-
value:
|
|
3754
|
+
searchEnvVars: z25.array(
|
|
3755
|
+
z25.object({
|
|
3756
|
+
name: z25.string(),
|
|
3757
|
+
value: z25.string()
|
|
4364
3758
|
})
|
|
4365
3759
|
).optional()
|
|
4366
3760
|
});
|
|
4367
|
-
var implementationOutputSchema =
|
|
4368
|
-
summary:
|
|
4369
|
-
// Ingestion only:
|
|
4370
|
-
//
|
|
4371
|
-
//
|
|
4372
|
-
//
|
|
4373
|
-
//
|
|
4374
|
-
|
|
3761
|
+
var implementationOutputSchema = z25.object({
|
|
3762
|
+
summary: z25.string(),
|
|
3763
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3764
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3765
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3766
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3767
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3768
|
+
runtime: z25.enum(INGEST_RUNTIMES).optional(),
|
|
3769
|
+
entrypoint: z25.string().optional()
|
|
4375
3770
|
});
|
|
4376
|
-
var verificationOutputSchema =
|
|
4377
|
-
summary:
|
|
4378
|
-
sufficient:
|
|
4379
|
-
additionalInstructions:
|
|
3771
|
+
var verificationOutputSchema = z25.object({
|
|
3772
|
+
summary: z25.string(),
|
|
3773
|
+
sufficient: z25.boolean(),
|
|
3774
|
+
additionalInstructions: z25.string().optional()
|
|
4380
3775
|
});
|
|
4381
3776
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4382
3777
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
3778
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3779
|
+
function detectUiFramework(language) {
|
|
3780
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3781
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3782
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3783
|
+
return "React";
|
|
3784
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3785
|
+
return "JavaScript";
|
|
3786
|
+
}
|
|
3787
|
+
function frameworksForDoc(framework) {
|
|
3788
|
+
switch (framework) {
|
|
3789
|
+
case "React":
|
|
3790
|
+
return ["react"];
|
|
3791
|
+
case "Vue":
|
|
3792
|
+
return ["vue"];
|
|
3793
|
+
case "Angular":
|
|
3794
|
+
return ["angular"];
|
|
3795
|
+
case "JavaScript":
|
|
3796
|
+
return [];
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
function publicEnvPrefix(language) {
|
|
3800
|
+
const frameworkNames = language.frameworks.map(
|
|
3801
|
+
(framework) => framework.name.toLowerCase()
|
|
4387
3802
|
);
|
|
3803
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3804
|
+
return "NEXT_PUBLIC_";
|
|
3805
|
+
}
|
|
3806
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3807
|
+
return "NUXT_PUBLIC_";
|
|
3808
|
+
}
|
|
3809
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3810
|
+
return "PUBLIC_";
|
|
3811
|
+
}
|
|
3812
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3813
|
+
return "VITE_";
|
|
3814
|
+
}
|
|
3815
|
+
return "PUBLIC_";
|
|
3816
|
+
}
|
|
3817
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3818
|
+
const prefix = publicEnvPrefix(language);
|
|
4388
3819
|
return [
|
|
4389
3820
|
{
|
|
4390
3821
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4396,38 +3827,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4396
3827
|
}
|
|
4397
3828
|
];
|
|
4398
3829
|
}
|
|
4399
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4400
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4401
|
-
repoRoot,
|
|
4402
|
-
language.languages.map((l) => l.name)
|
|
4403
|
-
);
|
|
4404
|
-
if (candidates.length === 0) {
|
|
4405
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4406
|
-
logger.warn(
|
|
4407
|
-
{
|
|
4408
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4409
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4410
|
-
chosen: chosen.id
|
|
4411
|
-
},
|
|
4412
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4413
|
-
);
|
|
4414
|
-
return chosen;
|
|
4415
|
-
}
|
|
4416
|
-
if (candidates.length === 1) return candidates[0];
|
|
4417
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4418
|
-
if (backends.length === 1) return backends[0];
|
|
4419
|
-
if (backends.length === 0) return candidates[0];
|
|
4420
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4421
|
-
const options = backends.map((p) => p.displayName);
|
|
4422
|
-
const selection = await ctx.requestUserInput({
|
|
4423
|
-
prompt: "Which language should the ingestion script use?",
|
|
4424
|
-
promptType: "multipleChoice",
|
|
4425
|
-
options,
|
|
4426
|
-
defaultSelectedIndex: 0
|
|
4427
|
-
});
|
|
4428
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4429
|
-
return picked ?? backends[0];
|
|
4430
|
-
}
|
|
4431
3830
|
function baseInstructions(input) {
|
|
4432
3831
|
return [
|
|
4433
3832
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4455,71 +3854,58 @@ function sourceSpecificInstructions(input) {
|
|
|
4455
3854
|
generated: [
|
|
4456
3855
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4457
3856
|
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
4458
|
-
"In the script, read and parse each returned
|
|
3857
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4459
3858
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4460
3859
|
]
|
|
4461
3860
|
};
|
|
4462
3861
|
return byLine[input.ingestionSource];
|
|
4463
3862
|
}
|
|
4464
3863
|
function ingestionInstructions(input) {
|
|
4465
|
-
const { ingestionProfile: profile2, toolchain } = input;
|
|
4466
|
-
const { ingest } = toolchain;
|
|
4467
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4468
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile2.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile2.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile2.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs.`;
|
|
4469
3864
|
return [
|
|
4470
3865
|
...input.confirmed && input.confirmed.length ? [
|
|
4471
|
-
`
|
|
3866
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4472
3867
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4473
|
-
`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.
|
|
4474
|
-
|
|
3868
|
+
`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.`,
|
|
3869
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4475
3870
|
"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.",
|
|
4476
|
-
getNamedDoc("save-records",
|
|
4477
|
-
|
|
3871
|
+
getNamedDoc("save-records", "js"),
|
|
3872
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4478
3873
|
"The summary should be extremely concise.",
|
|
4479
|
-
|
|
3874
|
+
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4480
3875
|
...sourceSpecificInstructions(input)
|
|
4481
3876
|
] : []
|
|
4482
3877
|
];
|
|
4483
3878
|
}
|
|
4484
3879
|
function searchInstructions(input) {
|
|
4485
|
-
const doc =
|
|
4486
|
-
"instantsearch-setup",
|
|
4487
|
-
searchDocKey(input.searchStrategy)
|
|
4488
|
-
);
|
|
4489
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4490
|
-
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `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.`;
|
|
3880
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4491
3881
|
return [
|
|
4492
3882
|
"Implement an in-app Algolia search experience.",
|
|
4493
|
-
`Build the search UI for ${
|
|
4494
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3883
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3884
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4495
3885
|
doc,
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4500
|
-
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3886
|
+
`Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3887
|
+
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3888
|
+
// appId always resolves (requireApplication throws otherwise); only the
|
|
4501
3889
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4502
3890
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
4503
3891
|
// Names are fixed, not the agent's to rename: the wizard writes the
|
|
4504
3892
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4505
3893
|
// right after this step, so a renamed prefix here would leave the code
|
|
4506
3894
|
// reading a var the wizard never wrote.
|
|
4507
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3895
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3896
|
+
'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.',
|
|
4508
3897
|
"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."
|
|
4509
3898
|
];
|
|
4510
3899
|
}
|
|
4511
3900
|
function verificationInstructions(input) {
|
|
4512
|
-
const protectedDirs = [
|
|
4513
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4514
|
-
];
|
|
4515
3901
|
return [
|
|
4516
3902
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4517
3903
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4518
|
-
|
|
3904
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4519
3905
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4520
3906
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4521
3907
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4522
|
-
`Do not modify ${
|
|
3908
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4523
3909
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4524
3910
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4525
3911
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4528,17 +3914,14 @@ function verificationInstructions(input) {
|
|
|
4528
3914
|
var IMPLEMENT_CONFIG = {
|
|
4529
3915
|
ingestion: {
|
|
4530
3916
|
title: "Algolia ingestion",
|
|
4531
|
-
label: "Ingestion",
|
|
4532
3917
|
buildInstructions: ingestionInstructions
|
|
4533
3918
|
},
|
|
4534
3919
|
search: {
|
|
4535
3920
|
title: "Algolia search",
|
|
4536
|
-
label: "Search",
|
|
4537
3921
|
buildInstructions: searchInstructions
|
|
4538
3922
|
},
|
|
4539
3923
|
verification: {
|
|
4540
3924
|
title: "Algolia verification",
|
|
4541
|
-
label: "Verification",
|
|
4542
3925
|
buildInstructions: verificationInstructions
|
|
4543
3926
|
}
|
|
4544
3927
|
};
|
|
@@ -4570,10 +3953,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4570
3953
|
];
|
|
4571
3954
|
}
|
|
4572
3955
|
function formatSummary(useCase, summary) {
|
|
4573
|
-
|
|
3956
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3957
|
+
return `${label}: ${summary}`;
|
|
4574
3958
|
}
|
|
4575
|
-
function buildIngestCommand(worktree,
|
|
4576
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3959
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3960
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4577
3961
|
}
|
|
4578
3962
|
function parseIngestRecordCount(output) {
|
|
4579
3963
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4650,17 +4034,18 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4650
4034
|
}
|
|
4651
4035
|
}
|
|
4652
4036
|
const targetIndex = selected?.selection;
|
|
4037
|
+
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4653
4038
|
await assertGitRepoWithHead(repoRoot);
|
|
4654
4039
|
if (await isWorkingTreeDirty(repoRoot)) {
|
|
4655
4040
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4656
4041
|
}
|
|
4657
4042
|
const normalized = normalizeFindingPaths(findings);
|
|
4658
|
-
const
|
|
4043
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4659
4044
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4660
4045
|
let appId;
|
|
4661
4046
|
let searchKey;
|
|
4662
4047
|
if (useCases.includes("search")) {
|
|
4663
|
-
appId = (await
|
|
4048
|
+
appId = (await requireApplication()).id;
|
|
4664
4049
|
try {
|
|
4665
4050
|
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
4666
4051
|
} catch (err) {
|
|
@@ -4693,66 +4078,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4693
4078
|
);
|
|
4694
4079
|
}
|
|
4695
4080
|
}
|
|
4696
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4697
|
-
ctx,
|
|
4698
|
-
language,
|
|
4699
|
-
worktree
|
|
4700
|
-
);
|
|
4701
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4702
|
-
const verificationLanguages = [
|
|
4703
|
-
.../* @__PURE__ */ new Set([
|
|
4704
|
-
ingestionProfile.id,
|
|
4705
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4706
|
-
])
|
|
4707
|
-
];
|
|
4708
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4709
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4710
|
-
frameworkName,
|
|
4711
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4712
|
-
);
|
|
4713
|
-
logger.info(
|
|
4714
|
-
{
|
|
4715
|
-
language: ingestionProfile.id,
|
|
4716
|
-
packageManager: toolchain.packageManager.id,
|
|
4717
|
-
ingest: toolchain.ingest.kind,
|
|
4718
|
-
framework: frameworkName,
|
|
4719
|
-
searchStrategy
|
|
4720
|
-
},
|
|
4721
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4722
|
-
);
|
|
4723
4081
|
const input = {
|
|
4724
4082
|
findings: normalized,
|
|
4725
|
-
confirmed:
|
|
4083
|
+
confirmed: confirmed2,
|
|
4726
4084
|
searchLocation,
|
|
4727
4085
|
targetIndex,
|
|
4728
4086
|
language,
|
|
4729
4087
|
appId,
|
|
4730
4088
|
searchKey,
|
|
4731
|
-
searchEnvVars:
|
|
4732
|
-
language,
|
|
4733
|
-
searchStrategy,
|
|
4734
|
-
appId,
|
|
4735
|
-
searchKey
|
|
4736
|
-
),
|
|
4089
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4737
4090
|
ingestDir: INGEST_DIR,
|
|
4738
4091
|
ingestionSource,
|
|
4739
4092
|
uploadFilePath,
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
toolchain,
|
|
4744
|
-
verificationLanguages
|
|
4093
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
4094
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
4095
|
+
uiFramework: detectUiFramework(language)
|
|
4745
4096
|
};
|
|
4746
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4747
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4748
4097
|
const summaries = [];
|
|
4749
4098
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4750
4099
|
let agentRuns = 0;
|
|
4100
|
+
let ingestRuntime;
|
|
4751
4101
|
let ingestEntrypoint;
|
|
4752
4102
|
let ingestScriptRan = false;
|
|
4753
4103
|
let ingestRecordCount;
|
|
4754
4104
|
let ingestDurationMs;
|
|
4755
|
-
|
|
4105
|
+
let installFailed = false;
|
|
4756
4106
|
let ingestOutcomeMessage;
|
|
4757
4107
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4758
4108
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4766,19 +4116,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4766
4116
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4767
4117
|
outputSchema: implementationOutputSchema
|
|
4768
4118
|
});
|
|
4769
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4770
|
-
if (!useCaseToolchain) return result;
|
|
4771
4119
|
ctx.notify({
|
|
4772
4120
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4773
4121
|
});
|
|
4774
4122
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4775
|
-
useCase: currentUseCase
|
|
4776
|
-
language: useCaseToolchain.profile.id
|
|
4123
|
+
useCase: currentUseCase
|
|
4777
4124
|
});
|
|
4778
|
-
const install = await installWorktreeDeps(worktree
|
|
4125
|
+
const install = await installWorktreeDeps(worktree);
|
|
4779
4126
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4780
4127
|
if (!install.ok) {
|
|
4781
|
-
|
|
4128
|
+
installFailed = true;
|
|
4782
4129
|
logger.warn(
|
|
4783
4130
|
{ useCase: currentUseCase, output: install.output },
|
|
4784
4131
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4792,16 +4139,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4792
4139
|
return runAgent({
|
|
4793
4140
|
instructions: buildAgentInstructions("verification", input),
|
|
4794
4141
|
tools: toolsForUseCase("verification"),
|
|
4795
|
-
outputSchema: verificationOutputSchema
|
|
4796
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4797
|
-
languages: input.verificationLanguages
|
|
4142
|
+
outputSchema: verificationOutputSchema
|
|
4798
4143
|
});
|
|
4799
4144
|
}
|
|
4800
4145
|
if (useCases.includes("ingestion")) {
|
|
4801
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4146
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4802
4147
|
summaries.push(formatSummary("ingestion", summary));
|
|
4148
|
+
ingestRuntime = runtime;
|
|
4803
4149
|
ingestEntrypoint = entrypoint;
|
|
4804
|
-
if (
|
|
4150
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4805
4151
|
ctx.clearNotices();
|
|
4806
4152
|
const runNow = await ctx.requestUserInput({
|
|
4807
4153
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4810,20 +4156,21 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4810
4156
|
messages: []
|
|
4811
4157
|
}) === true;
|
|
4812
4158
|
if (runNow) {
|
|
4813
|
-
const
|
|
4159
|
+
const ingestApp = await requireApplication();
|
|
4160
|
+
const writeKey = await resolveWriteKey(targetIndex);
|
|
4814
4161
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4815
4162
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4816
|
-
|
|
4163
|
+
runtime: ingestRuntime,
|
|
4817
4164
|
entrypoint: ingestEntrypoint
|
|
4818
4165
|
});
|
|
4819
4166
|
const startedAt = Date.now();
|
|
4820
4167
|
const run = await runIngestScript(
|
|
4821
4168
|
worktree,
|
|
4822
|
-
|
|
4169
|
+
ingestRuntime,
|
|
4823
4170
|
ingestEntrypoint,
|
|
4824
4171
|
{
|
|
4825
|
-
[APP_ID_VAR]:
|
|
4826
|
-
[API_KEY_VAR]:
|
|
4172
|
+
[APP_ID_VAR]: ingestApp.id,
|
|
4173
|
+
[API_KEY_VAR]: writeKey
|
|
4827
4174
|
}
|
|
4828
4175
|
);
|
|
4829
4176
|
ctx.logEnd(scriptLogId, run.ok ? "success" : "error");
|
|
@@ -4833,7 +4180,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4833
4180
|
ingestRecordCount = parseIngestRecordCount(run.output);
|
|
4834
4181
|
if (ingestRecordCount != null) {
|
|
4835
4182
|
track("AI Wizard Ingest Successful", {
|
|
4836
|
-
entity_name:
|
|
4183
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4837
4184
|
record_count: ingestRecordCount,
|
|
4838
4185
|
duration_ms: ingestDurationMs
|
|
4839
4186
|
});
|
|
@@ -4846,7 +4193,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4846
4193
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
|
|
4847
4194
|
logger.warn(
|
|
4848
4195
|
{
|
|
4849
|
-
|
|
4196
|
+
runtime: ingestRuntime,
|
|
4850
4197
|
entrypoint: ingestEntrypoint,
|
|
4851
4198
|
reason: run.reason
|
|
4852
4199
|
},
|
|
@@ -4869,7 +4216,7 @@ ${run.output}` : status;
|
|
|
4869
4216
|
outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
|
|
4870
4217
|
logger.warn(
|
|
4871
4218
|
{
|
|
4872
|
-
|
|
4219
|
+
runtime: ingestRuntime,
|
|
4873
4220
|
entrypoint: ingestEntrypoint,
|
|
4874
4221
|
output: run.output
|
|
4875
4222
|
},
|
|
@@ -4886,15 +4233,10 @@ ${run.output}` : status;
|
|
|
4886
4233
|
}
|
|
4887
4234
|
}
|
|
4888
4235
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4889
|
-
if (ingestEntrypoint) {
|
|
4236
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4890
4237
|
commandMessages.push(
|
|
4891
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4238
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4892
4239
|
);
|
|
4893
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4894
|
-
commandMessages.push(
|
|
4895
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4896
|
-
);
|
|
4897
|
-
}
|
|
4898
4240
|
}
|
|
4899
4241
|
await ctx.requestUserInput({
|
|
4900
4242
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4905,20 +4247,7 @@ ${run.output}` : status;
|
|
|
4905
4247
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4906
4248
|
});
|
|
4907
4249
|
}
|
|
4908
|
-
|
|
4909
|
-
if (skipSearch) {
|
|
4910
|
-
const target = describeSearchTarget(
|
|
4911
|
-
input.searchStrategy,
|
|
4912
|
-
input.frameworkName
|
|
4913
|
-
);
|
|
4914
|
-
summaries.push(
|
|
4915
|
-
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
4916
|
-
);
|
|
4917
|
-
track("AI Wizard Search UI Skipped", {
|
|
4918
|
-
framework: input.frameworkName ?? "unknown"
|
|
4919
|
-
});
|
|
4920
|
-
}
|
|
4921
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4250
|
+
if (useCases.includes("search")) {
|
|
4922
4251
|
let extraInstructions = [];
|
|
4923
4252
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4924
4253
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4989,9 +4318,9 @@ ${run.output}` : status;
|
|
|
4989
4318
|
"implement: agent reported success but no files changed in the worktree"
|
|
4990
4319
|
);
|
|
4991
4320
|
}
|
|
4992
|
-
if (
|
|
4321
|
+
if (installFailed) {
|
|
4993
4322
|
summaries.push(
|
|
4994
|
-
|
|
4323
|
+
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
4995
4324
|
);
|
|
4996
4325
|
}
|
|
4997
4326
|
return {
|
|
@@ -4999,10 +4328,10 @@ ${run.output}` : status;
|
|
|
4999
4328
|
filesChanged,
|
|
5000
4329
|
summary: summaries.join("\n\n"),
|
|
5001
4330
|
worktreePath: worktree,
|
|
5002
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4331
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5003
4332
|
ingestCommand: buildIngestCommand(
|
|
5004
4333
|
worktree,
|
|
5005
|
-
|
|
4334
|
+
ingestRuntime,
|
|
5006
4335
|
ingestEntrypoint
|
|
5007
4336
|
),
|
|
5008
4337
|
ingestScriptRan,
|
|
@@ -5052,8 +4381,8 @@ var defaultWorkflow = {
|
|
|
5052
4381
|
defineStep({
|
|
5053
4382
|
id: "select-index",
|
|
5054
4383
|
title: "Set up index",
|
|
5055
|
-
outputSchema:
|
|
5056
|
-
selection:
|
|
4384
|
+
outputSchema: z26.object({
|
|
4385
|
+
selection: z26.string()
|
|
5057
4386
|
}),
|
|
5058
4387
|
run: (ctx) => selectIndexStep(ctx)
|
|
5059
4388
|
}),
|
|
@@ -5134,25 +4463,54 @@ var store = useWizard.getState();
|
|
|
5134
4463
|
var instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
5135
4464
|
var user = await getUser();
|
|
5136
4465
|
if (!user) {
|
|
5137
|
-
|
|
5138
|
-
instance.cleanup();
|
|
4466
|
+
store.beginAuth();
|
|
5139
4467
|
try {
|
|
5140
4468
|
await runAuthLogin();
|
|
5141
4469
|
} catch (err) {
|
|
5142
|
-
|
|
5143
|
-
|
|
4470
|
+
if (!needsInteractiveTerminal(err)) {
|
|
4471
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4472
|
+
await instance.waitUntilExit();
|
|
4473
|
+
process.exit(1);
|
|
4474
|
+
}
|
|
4475
|
+
try {
|
|
4476
|
+
await promptForApplication();
|
|
4477
|
+
} catch (pickErr) {
|
|
4478
|
+
logger.warn(
|
|
4479
|
+
{ err: pickErr.message },
|
|
4480
|
+
"in-wizard application selection failed; handing over the terminal"
|
|
4481
|
+
);
|
|
4482
|
+
await instance.waitUntilRenderFlush();
|
|
4483
|
+
instance.cleanup();
|
|
4484
|
+
try {
|
|
4485
|
+
await runAuthLoginInTerminal();
|
|
4486
|
+
} catch (fallbackErr) {
|
|
4487
|
+
console.error(
|
|
4488
|
+
fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
|
|
4489
|
+
);
|
|
4490
|
+
process.exit(1);
|
|
4491
|
+
}
|
|
4492
|
+
instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
4493
|
+
store.clearCliOutput();
|
|
4494
|
+
}
|
|
5144
4495
|
}
|
|
5145
|
-
|
|
4496
|
+
store.endAuth();
|
|
5146
4497
|
user = await getUser();
|
|
5147
4498
|
if (!user) {
|
|
5148
4499
|
store.setError(
|
|
5149
|
-
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
|
|
4500
|
+
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
|
|
5150
4501
|
);
|
|
5151
4502
|
await instance.waitUntilExit();
|
|
5152
4503
|
process.exit(1);
|
|
5153
4504
|
}
|
|
5154
4505
|
}
|
|
5155
4506
|
store.setUser(user);
|
|
5156
|
-
var profile = await loadActiveProfile();
|
|
5157
4507
|
await store.waitForStart();
|
|
5158
|
-
|
|
4508
|
+
var app;
|
|
4509
|
+
try {
|
|
4510
|
+
app = await ensureApplication();
|
|
4511
|
+
} catch (err) {
|
|
4512
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4513
|
+
await instance.waitUntilExit();
|
|
4514
|
+
process.exit(1);
|
|
4515
|
+
}
|
|
4516
|
+
runWorkflow(workflow, app.id);
|