@algolia/wizard 0.8.0-rc.67.55 → 0.9.0-rc.53.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/main.js +1230 -1877
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +2 -4
- 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 Box15, Text as Text15, useApp, useInput as useInput6, useWindowSize as useWindowSize8 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -12,32 +12,86 @@ import { nanoid } from "nanoid";
|
|
|
12
12
|
|
|
13
13
|
// src/lib/algoliaCli.ts
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return require2.resolve("@algolia/cli/bin/run.js");
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
function npxArgs(args) {
|
|
17
|
+
return ["--yes", "@algolia/cli@latest", ...args];
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
var shell = process.platform === "win32";
|
|
20
|
+
function lineSplitter(emit) {
|
|
21
|
+
let buffer = "";
|
|
22
|
+
return {
|
|
23
|
+
push(chunk) {
|
|
24
|
+
buffer += chunk;
|
|
25
|
+
const lines = buffer.split("\n");
|
|
26
|
+
buffer = lines.pop() ?? "";
|
|
27
|
+
for (const line of lines) emit(line.replace(/\r$/, ""));
|
|
28
|
+
},
|
|
29
|
+
flush() {
|
|
30
|
+
if (buffer) emit(buffer.replace(/\r$/, ""));
|
|
31
|
+
buffer = "";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var wizardSink = (stream, line) => {
|
|
36
|
+
if (!line.trim()) return;
|
|
37
|
+
useWizard.getState().pushCliOutput(stream, line);
|
|
38
|
+
};
|
|
39
|
+
var stderrSink = (stream, line) => {
|
|
40
|
+
if (stream === "stdout") return;
|
|
41
|
+
wizardSink(stream, line);
|
|
42
|
+
};
|
|
43
|
+
function runAlgoliaCli(args, { onOutput } = {}) {
|
|
44
|
+
const store = useWizard.getState();
|
|
45
|
+
const logId = store.logStart("tool", `algolia ${args.join(" ")}`);
|
|
21
46
|
return new Promise((resolve4, reject) => {
|
|
22
|
-
const child = spawn(
|
|
47
|
+
const child = spawn("npx", npxArgs(args), { shell });
|
|
23
48
|
let stdout = "";
|
|
24
49
|
let stderr = "";
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
const splitters = {
|
|
51
|
+
stdout: lineSplitter((line) => onOutput?.("stdout", line)),
|
|
52
|
+
stderr: lineSplitter((line) => onOutput?.("stderr", line))
|
|
53
|
+
};
|
|
54
|
+
child.stdout.on("data", (chunk) => {
|
|
55
|
+
const text = String(chunk);
|
|
56
|
+
stdout += text;
|
|
57
|
+
splitters.stdout.push(text);
|
|
58
|
+
});
|
|
59
|
+
child.stderr.on("data", (chunk) => {
|
|
60
|
+
const text = String(chunk);
|
|
61
|
+
stderr += text;
|
|
62
|
+
splitters.stderr.push(text);
|
|
63
|
+
});
|
|
27
64
|
child.on("error", reject);
|
|
28
65
|
child.on("close", (code) => {
|
|
66
|
+
splitters.stdout.flush();
|
|
67
|
+
splitters.stderr.flush();
|
|
29
68
|
if (code === 0) {
|
|
30
69
|
resolve4(stdout);
|
|
31
70
|
} else {
|
|
32
|
-
const
|
|
71
|
+
const failed = stderr.trim();
|
|
72
|
+
let detail = "";
|
|
73
|
+
if (failed) {
|
|
74
|
+
detail = `: ${failed}`;
|
|
75
|
+
} else if (stdout.trim()) {
|
|
76
|
+
detail = " (no stderr; stdout withheld \u2014 it may contain credentials)";
|
|
77
|
+
}
|
|
33
78
|
reject(
|
|
34
79
|
new Error(
|
|
35
|
-
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail
|
|
80
|
+
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail}`
|
|
36
81
|
)
|
|
37
82
|
);
|
|
38
83
|
}
|
|
39
84
|
});
|
|
40
|
-
})
|
|
85
|
+
}).then(
|
|
86
|
+
(out) => {
|
|
87
|
+
useWizard.getState().logEnd(logId, "success");
|
|
88
|
+
return out;
|
|
89
|
+
},
|
|
90
|
+
(err) => {
|
|
91
|
+
useWizard.getState().logEnd(logId, "error");
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
);
|
|
41
95
|
}
|
|
42
96
|
async function getUser() {
|
|
43
97
|
let raw;
|
|
@@ -52,19 +106,23 @@ async function getUser() {
|
|
|
52
106
|
return null;
|
|
53
107
|
}
|
|
54
108
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
child.on("error", reject);
|
|
63
|
-
child.on("close", (code) => {
|
|
64
|
-
if (code === 0) resolve4();
|
|
65
|
-
else reject(new Error(`Algolia authentication failed (exit ${code}).`));
|
|
66
|
-
});
|
|
109
|
+
var loginResultSchema = z.object({
|
|
110
|
+
success: z.boolean(),
|
|
111
|
+
email: z.string().optional()
|
|
112
|
+
});
|
|
113
|
+
async function runAuthLogin() {
|
|
114
|
+
const raw = await runAlgoliaCli(["auth", "login", "--non-interactive"], {
|
|
115
|
+
onOutput: stderrSink
|
|
67
116
|
});
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = loginResultSchema.safeParse(JSON.parse(raw));
|
|
120
|
+
} catch {
|
|
121
|
+
parsed = void 0;
|
|
122
|
+
}
|
|
123
|
+
if (parsed?.success && !parsed.data.success) {
|
|
124
|
+
throw new Error("Algolia sign-in did not report success.");
|
|
125
|
+
}
|
|
68
126
|
}
|
|
69
127
|
|
|
70
128
|
// src/lib/auth.ts
|
|
@@ -171,6 +229,7 @@ function describeInputValue(value) {
|
|
|
171
229
|
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
230
|
}
|
|
173
231
|
var NOTICE_INTERVAL_MS = 2e3;
|
|
232
|
+
var CLI_OUTPUT_LIMIT = 200;
|
|
174
233
|
var useWizard = create((set, get) => ({
|
|
175
234
|
phase: "idle",
|
|
176
235
|
homeScreen: "home",
|
|
@@ -182,22 +241,26 @@ var useWizard = create((set, get) => ({
|
|
|
182
241
|
notices: [],
|
|
183
242
|
_noticeQueue: [],
|
|
184
243
|
_noticeTimer: null,
|
|
244
|
+
cliOutput: [],
|
|
245
|
+
targetIndex: null,
|
|
185
246
|
logs: [],
|
|
186
247
|
error: null,
|
|
187
248
|
inputReq: null,
|
|
188
249
|
_resolve: null,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
250
|
+
// Sign-in happens after the welcome screen's enter, so `endAuth` lands on
|
|
251
|
+
// 'preflight': returning to 'idle' would put the welcome screen back up and
|
|
252
|
+
// ask the user to confirm the run a second time.
|
|
253
|
+
beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
|
|
254
|
+
endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
|
|
255
|
+
// Only meaningful from 'idle' — once the workflow is running there's nothing
|
|
256
|
+
// left to confirm. `homeScreen` resets so preflight shows Welcome rather than
|
|
257
|
+
// the Learn more sub-view.
|
|
192
258
|
confirmStart: () => set(
|
|
193
259
|
(s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
|
|
194
260
|
),
|
|
195
|
-
// Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
|
|
196
261
|
openLearnMore: () => set({ homeScreen: "learnMore" }),
|
|
197
262
|
backToHome: () => set({ homeScreen: "home" }),
|
|
198
|
-
// Resolves
|
|
199
|
-
// after this is called (the welcome screen's enter handler is what
|
|
200
|
-
// drives the transition via `confirmStart`).
|
|
263
|
+
// Resolves whether the phase left 'idle' before or after this is called.
|
|
201
264
|
waitForStart: () => new Promise((resolve4) => {
|
|
202
265
|
if (get().phase !== "idle") {
|
|
203
266
|
resolve4();
|
|
@@ -220,15 +283,20 @@ var useWizard = create((set, get) => ({
|
|
|
220
283
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
221
284
|
setActiveStep: (index) => {
|
|
222
285
|
get()._clearNoticeQueue();
|
|
223
|
-
set({
|
|
286
|
+
set({
|
|
287
|
+
phase: "running",
|
|
288
|
+
currentStepIndex: index,
|
|
289
|
+
output: "",
|
|
290
|
+
notices: [],
|
|
291
|
+
cliOutput: []
|
|
292
|
+
});
|
|
224
293
|
},
|
|
225
294
|
setUser: (user) => set({ user }),
|
|
226
295
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
227
296
|
clearOutput: () => set({ output: "" }),
|
|
228
|
-
// Renders the first notice of a burst immediately, then
|
|
229
|
-
//
|
|
230
|
-
// the
|
|
231
|
-
// covers the time since the last render, even across bursts.
|
|
297
|
+
// Renders the first notice of a burst immediately, then drains later arrivals
|
|
298
|
+
// one per `NOTICE_INTERVAL_MS`. The timer stays armed through an empty drain
|
|
299
|
+
// so the cooldown covers the time since the last render, across bursts.
|
|
232
300
|
pushNotice: (notice) => {
|
|
233
301
|
const { notices, _noticeQueue, _noticeTimer } = get();
|
|
234
302
|
if (_noticeTimer === null) {
|
|
@@ -261,6 +329,15 @@ var useWizard = create((set, get) => ({
|
|
|
261
329
|
get()._clearNoticeQueue();
|
|
262
330
|
set({ notices: [] });
|
|
263
331
|
},
|
|
332
|
+
// Unthrottled, unlike `pushNotice`: holding these back would land output
|
|
333
|
+
// after the command it belongs to has already exited.
|
|
334
|
+
pushCliOutput: (stream, text) => set((s) => ({
|
|
335
|
+
cliOutput: [...s.cliOutput, { id: nanoid(), stream, text }].slice(
|
|
336
|
+
-CLI_OUTPUT_LIMIT
|
|
337
|
+
)
|
|
338
|
+
})),
|
|
339
|
+
clearCliOutput: () => set({ cliOutput: [] }),
|
|
340
|
+
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
264
341
|
logStart: (kind, name, input) => {
|
|
265
342
|
const id = nanoid();
|
|
266
343
|
set((s) => ({
|
|
@@ -283,9 +360,8 @@ var useWizard = create((set, get) => ({
|
|
|
283
360
|
_resolve: resolve4
|
|
284
361
|
});
|
|
285
362
|
}),
|
|
286
|
-
// Logs what the user picked
|
|
287
|
-
//
|
|
288
|
-
// here.
|
|
363
|
+
// Logs what the user picked, not the prompt text — that just duplicates
|
|
364
|
+
// on-screen content.
|
|
289
365
|
submitInput: async (value) => {
|
|
290
366
|
await markInteraction();
|
|
291
367
|
get()._resolve?.(value);
|
|
@@ -305,6 +381,8 @@ var useWizard = create((set, get) => ({
|
|
|
305
381
|
currentStepIndex: 0,
|
|
306
382
|
output: "",
|
|
307
383
|
notices: [],
|
|
384
|
+
cliOutput: [],
|
|
385
|
+
targetIndex: null,
|
|
308
386
|
logs: [],
|
|
309
387
|
error: null,
|
|
310
388
|
inputReq: null,
|
|
@@ -313,16 +391,100 @@ var useWizard = create((set, get) => ({
|
|
|
313
391
|
}
|
|
314
392
|
}));
|
|
315
393
|
|
|
394
|
+
// src/ui/CliOutput.tsx
|
|
395
|
+
import { Box, Text, useWindowSize } from "ink";
|
|
396
|
+
|
|
397
|
+
// src/ui/theme.ts
|
|
398
|
+
var MARKER = {
|
|
399
|
+
pending: "\u25CB",
|
|
400
|
+
running: "\u25D0",
|
|
401
|
+
done: "\u2713",
|
|
402
|
+
error: "\u2716"
|
|
403
|
+
};
|
|
404
|
+
var BRAND = "#003DFF";
|
|
405
|
+
var SECONDARY = "#5468FF";
|
|
406
|
+
var DANGER = "#F86E7E";
|
|
407
|
+
var COLORS = {
|
|
408
|
+
brand: BRAND,
|
|
409
|
+
primary: "#E6EDF3",
|
|
410
|
+
secondary: SECONDARY,
|
|
411
|
+
strong: "#FFFFFF",
|
|
412
|
+
muted: "#8B949E",
|
|
413
|
+
dim: "#484F58",
|
|
414
|
+
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
415
|
+
badge: "#E3B341",
|
|
416
|
+
danger: DANGER,
|
|
417
|
+
success: "#4ADE80",
|
|
418
|
+
bg: {
|
|
419
|
+
main: "#0B0E14",
|
|
420
|
+
sidebar: "#14171E"
|
|
421
|
+
},
|
|
422
|
+
border: "#30363D",
|
|
423
|
+
accent: "#76A0FF",
|
|
424
|
+
status: {
|
|
425
|
+
pending: "gray",
|
|
426
|
+
running: "#76A0FF",
|
|
427
|
+
done: "#4ADE80",
|
|
428
|
+
error: DANGER
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// src/ui/CliOutput.tsx
|
|
433
|
+
import { jsxs } from "react/jsx-runtime";
|
|
434
|
+
var CLI_MARKER = "\u203A";
|
|
435
|
+
var RESERVED_ROWS = 16;
|
|
436
|
+
var MAX_ROWS = 12;
|
|
437
|
+
var PANEL_TEXT_WIDTH = 45;
|
|
438
|
+
var URL_PATTERN = /https?:\/\//;
|
|
439
|
+
function rowCost(text) {
|
|
440
|
+
return URL_PATTERN.test(text) ? Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH)) : 1;
|
|
441
|
+
}
|
|
442
|
+
function CliOutput() {
|
|
443
|
+
const cliOutput = useWizard((s) => s.cliOutput);
|
|
444
|
+
const { rows } = useWindowSize();
|
|
445
|
+
if (!cliOutput.length) return null;
|
|
446
|
+
const rowBudget = Math.min(Math.max(rows - RESERVED_ROWS, 3), MAX_ROWS);
|
|
447
|
+
const visible = [];
|
|
448
|
+
let usedRows = 0;
|
|
449
|
+
for (let i = cliOutput.length - 1; i >= 0; i--) {
|
|
450
|
+
const cost = rowCost(cliOutput[i].text);
|
|
451
|
+
if (usedRows + cost > rowBudget && visible.length > 0) break;
|
|
452
|
+
visible.unshift(cliOutput[i]);
|
|
453
|
+
usedRows += cost;
|
|
454
|
+
}
|
|
455
|
+
const hidden = cliOutput.length - visible.length;
|
|
456
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
457
|
+
hidden > 0 && /* @__PURE__ */ jsxs(Text, { color: COLORS.dim, children: [
|
|
458
|
+
"\u2191 ",
|
|
459
|
+
hidden,
|
|
460
|
+
" earlier line(s)"
|
|
461
|
+
] }),
|
|
462
|
+
visible.map((line) => /* @__PURE__ */ jsxs(
|
|
463
|
+
Text,
|
|
464
|
+
{
|
|
465
|
+
color: line.stream === "stderr" ? COLORS.muted : COLORS.dim,
|
|
466
|
+
wrap: URL_PATTERN.test(line.text) ? "wrap" : "truncate",
|
|
467
|
+
children: [
|
|
468
|
+
CLI_MARKER,
|
|
469
|
+
" ",
|
|
470
|
+
line.text
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
line.id
|
|
474
|
+
))
|
|
475
|
+
] });
|
|
476
|
+
}
|
|
477
|
+
|
|
316
478
|
// src/ui/Notices.tsx
|
|
317
|
-
import { Box as
|
|
479
|
+
import { Box as Box3, Text as Text3, useWindowSize as useWindowSize3 } from "ink";
|
|
318
480
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
319
481
|
|
|
320
482
|
// src/ui/Table.tsx
|
|
321
|
-
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
483
|
+
import { Box as Box2, Text as Text2, measureElement, useWindowSize as useWindowSize2 } from "ink";
|
|
322
484
|
import { useEffect, useRef, useState } from "react";
|
|
323
485
|
import { jsx } from "react/jsx-runtime";
|
|
324
486
|
function Table({ columns, rows }) {
|
|
325
|
-
const { columns: termCols } =
|
|
487
|
+
const { columns: termCols } = useWindowSize2();
|
|
326
488
|
const ref = useRef(null);
|
|
327
489
|
const [width, setWidth] = useState(0);
|
|
328
490
|
useEffect(() => {
|
|
@@ -330,7 +492,7 @@ function Table({ columns, rows }) {
|
|
|
330
492
|
}, [termCols, columns, rows]);
|
|
331
493
|
if (rows.length === 0) return null;
|
|
332
494
|
const lines = formatTable(columns, rows, width || void 0);
|
|
333
|
-
return /* @__PURE__ */ jsx(
|
|
495
|
+
return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", ref, children: lines.map((line, i) => /* @__PURE__ */ jsx(Text2, { wrap: "truncate", children: line }, `tbl-${i}`)) });
|
|
334
496
|
}
|
|
335
497
|
function formatTable(columns, rows, width) {
|
|
336
498
|
const natural = columns.map(
|
|
@@ -370,48 +532,13 @@ function resize(widths, budget) {
|
|
|
370
532
|
}
|
|
371
533
|
var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, width) : `${s.slice(0, width - 1)}\u2026`;
|
|
372
534
|
|
|
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
535
|
// src/ui/Notices.tsx
|
|
409
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
536
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
410
537
|
var AGENT_MARKER = "\u2726";
|
|
411
|
-
var
|
|
412
|
-
var
|
|
538
|
+
var RESERVED_ROWS2 = 14;
|
|
539
|
+
var PANEL_TEXT_WIDTH2 = 45;
|
|
413
540
|
function messageLineCount(text) {
|
|
414
|
-
return Math.max(1, Math.ceil(text.length /
|
|
541
|
+
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
|
|
415
542
|
}
|
|
416
543
|
function noticeLineCount(notice) {
|
|
417
544
|
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
@@ -422,7 +549,7 @@ function noticeLineCount(notice) {
|
|
|
422
549
|
return messageLines + tableLines;
|
|
423
550
|
}
|
|
424
551
|
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
-
const budget = Math.max(windowRows -
|
|
552
|
+
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
426
553
|
let used = 0;
|
|
427
554
|
let count = 0;
|
|
428
555
|
for (let i = notices.length - 1; i >= 0; i--) {
|
|
@@ -455,7 +582,7 @@ function parseHex(hex) {
|
|
|
455
582
|
}
|
|
456
583
|
function Notices() {
|
|
457
584
|
const notices = useWizard((s) => s.notices);
|
|
458
|
-
const { rows: windowRows } =
|
|
585
|
+
const { rows: windowRows } = useWindowSize3();
|
|
459
586
|
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
587
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
588
|
useEffect2(() => {
|
|
@@ -472,14 +599,14 @@ function Notices() {
|
|
|
472
599
|
}, []);
|
|
473
600
|
if (!visible.length) return null;
|
|
474
601
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
-
return /* @__PURE__ */ jsx2(
|
|
602
|
+
return /* @__PURE__ */ jsx2(Box3, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
603
|
const isLatest = i === visible.length - 1;
|
|
477
|
-
return /* @__PURE__ */
|
|
604
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
478
605
|
notice.messages?.map((m, j) => {
|
|
479
606
|
const line = typeof m === "string" ? { text: m } : m;
|
|
480
607
|
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
-
return /* @__PURE__ */
|
|
482
|
-
|
|
608
|
+
return /* @__PURE__ */ jsxs2(
|
|
609
|
+
Text3,
|
|
483
610
|
{
|
|
484
611
|
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
612
|
bold: line.bold,
|
|
@@ -497,41 +624,42 @@ function Notices() {
|
|
|
497
624
|
}
|
|
498
625
|
|
|
499
626
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
627
|
+
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
501
628
|
import TextInput from "ink-text-input";
|
|
502
|
-
import { useState as
|
|
629
|
+
import { useState as useState5 } from "react";
|
|
503
630
|
|
|
504
631
|
// src/ui/NextAction.tsx
|
|
505
|
-
import { Box as
|
|
506
|
-
import { Fragment, jsx as jsx3, jsxs as
|
|
632
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
633
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
507
634
|
function NextAction({
|
|
508
635
|
action,
|
|
509
636
|
keyHint,
|
|
510
637
|
hierarchy = "primary"
|
|
511
638
|
}) {
|
|
512
|
-
return /* @__PURE__ */
|
|
513
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx3(
|
|
514
|
-
hierarchy === "secondary" && /* @__PURE__ */
|
|
515
|
-
/* @__PURE__ */ jsx3(
|
|
516
|
-
/* @__PURE__ */ jsx3(
|
|
639
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
640
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
641
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
642
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
643
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
517
644
|
] }),
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */ jsx3(
|
|
520
|
-
/* @__PURE__ */ jsx3(
|
|
521
|
-
/* @__PURE__ */ jsx3(
|
|
522
|
-
/* @__PURE__ */ jsx3(
|
|
645
|
+
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
646
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: "press " }),
|
|
647
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `[` }),
|
|
648
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.primary, children: keyHint }),
|
|
649
|
+
/* @__PURE__ */ jsx3(Text4, { color: COLORS.muted, children: `]` })
|
|
523
650
|
] })
|
|
524
651
|
] });
|
|
525
652
|
}
|
|
526
653
|
|
|
527
654
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
529
|
-
import { useLayoutEffect, useRef as
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
655
|
+
import { Box as Box6, Text as Text6, useInput, useWindowSize as useWindowSize5 } from "ink";
|
|
656
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
657
|
+
|
|
658
|
+
// src/ui/ScrollView.tsx
|
|
659
|
+
import { Box as Box5, Text as Text5, measureElement as measureElement2, useWindowSize as useWindowSize4 } from "ink";
|
|
660
|
+
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
661
|
+
import { jsxs as jsxs4 } from "react/jsx-runtime";
|
|
662
|
+
var INDICATOR_ROWS = 2;
|
|
535
663
|
function fittedWidth(node, columns) {
|
|
536
664
|
let left = 0;
|
|
537
665
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -539,6 +667,89 @@ function fittedWidth(node, columns) {
|
|
|
539
667
|
}
|
|
540
668
|
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
541
669
|
}
|
|
670
|
+
function useScrollWindow({
|
|
671
|
+
itemCount,
|
|
672
|
+
rowHeight = 1,
|
|
673
|
+
followBottom = false
|
|
674
|
+
}) {
|
|
675
|
+
const viewportRef = useRef2(null);
|
|
676
|
+
const { columns } = useWindowSize4();
|
|
677
|
+
const [size, setSize] = useState3(
|
|
678
|
+
null
|
|
679
|
+
);
|
|
680
|
+
useLayoutEffect(() => {
|
|
681
|
+
if (!viewportRef.current) return;
|
|
682
|
+
const width = fittedWidth(viewportRef.current, columns);
|
|
683
|
+
const { height } = measureElement2(viewportRef.current);
|
|
684
|
+
setSize(
|
|
685
|
+
(prev) => prev?.width === width && prev.height === height ? prev : { width, height }
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
const capacity = size === null || itemCount * rowHeight <= size.height ? itemCount : Math.max(Math.floor((size.height - INDICATOR_ROWS) / rowHeight), 1);
|
|
689
|
+
const maxOffset = Math.max(itemCount - capacity, 0);
|
|
690
|
+
const [offset, setOffset] = useState3(0);
|
|
691
|
+
const prevMaxOffsetRef = useRef2(0);
|
|
692
|
+
useLayoutEffect(() => {
|
|
693
|
+
const wasAtBottom = offset >= prevMaxOffsetRef.current;
|
|
694
|
+
prevMaxOffsetRef.current = maxOffset;
|
|
695
|
+
setOffset(
|
|
696
|
+
(o) => followBottom && wasAtBottom ? maxOffset : Math.min(o, maxOffset)
|
|
697
|
+
);
|
|
698
|
+
}, [maxOffset, followBottom]);
|
|
699
|
+
const scrollBy = useCallback(
|
|
700
|
+
(delta) => {
|
|
701
|
+
setOffset((o) => Math.min(Math.max(o + delta, 0), maxOffset));
|
|
702
|
+
},
|
|
703
|
+
[maxOffset]
|
|
704
|
+
);
|
|
705
|
+
const revealIndex = useCallback(
|
|
706
|
+
(index) => {
|
|
707
|
+
setOffset((o) => {
|
|
708
|
+
if (index < o) return index;
|
|
709
|
+
if (index >= o + capacity) {
|
|
710
|
+
return Math.min(index - capacity + 1, maxOffset);
|
|
711
|
+
}
|
|
712
|
+
return o;
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
[capacity, maxOffset]
|
|
716
|
+
);
|
|
717
|
+
const visibleCount = Math.min(capacity, Math.max(itemCount - offset, 0));
|
|
718
|
+
return {
|
|
719
|
+
viewportRef,
|
|
720
|
+
width: size?.width ?? columns,
|
|
721
|
+
offset,
|
|
722
|
+
capacity,
|
|
723
|
+
maxOffset,
|
|
724
|
+
hiddenAbove: Math.min(offset, itemCount),
|
|
725
|
+
hiddenBelow: Math.max(itemCount - offset - visibleCount, 0),
|
|
726
|
+
scrollBy,
|
|
727
|
+
revealIndex
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function ScrollView({ scroll, children }) {
|
|
731
|
+
return /* @__PURE__ */ jsxs4(Box5, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
732
|
+
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
733
|
+
"\u2191 ",
|
|
734
|
+
scroll.hiddenAbove,
|
|
735
|
+
" more"
|
|
736
|
+
] }),
|
|
737
|
+
children,
|
|
738
|
+
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
739
|
+
"\u2193 ",
|
|
740
|
+
scroll.hiddenBelow,
|
|
741
|
+
" more"
|
|
742
|
+
] })
|
|
743
|
+
] });
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/ui/SelectPrompt.tsx
|
|
747
|
+
import { jsx as jsx4, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
748
|
+
var CANCEL = "cancel";
|
|
749
|
+
var ARROW_WIDTH = 4;
|
|
750
|
+
var COLUMN_GAP = 2;
|
|
751
|
+
var BAR_PADDING = 2;
|
|
752
|
+
var ROW_HEIGHT = 3;
|
|
542
753
|
function SelectPrompt({
|
|
543
754
|
options,
|
|
544
755
|
onSelect,
|
|
@@ -552,10 +763,10 @@ function SelectPrompt({
|
|
|
552
763
|
secondary,
|
|
553
764
|
defaultSelectedIndex = 0
|
|
554
765
|
}) {
|
|
555
|
-
const [index, setIndex] =
|
|
766
|
+
const [index, setIndex] = useState4(
|
|
556
767
|
() => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
|
|
557
768
|
);
|
|
558
|
-
const [checked, setChecked] =
|
|
769
|
+
const [checked, setChecked] = useState4(() => /* @__PURE__ */ new Set());
|
|
559
770
|
const hasCancel = Boolean(multi || cancelable);
|
|
560
771
|
const rows = hasCancel ? [...options, "Cancel"] : options;
|
|
561
772
|
const cancelIndex = hasCancel ? options.length : -1;
|
|
@@ -563,14 +774,14 @@ function SelectPrompt({
|
|
|
563
774
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
564
775
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
776
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
|
-
const containerRef =
|
|
567
|
-
const { columns } =
|
|
568
|
-
const [width, setWidth] =
|
|
569
|
-
|
|
570
|
-
if (containerRef.current)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
777
|
+
const containerRef = useRef3(null);
|
|
778
|
+
const { columns } = useWindowSize5();
|
|
779
|
+
const [width, setWidth] = useState4(columns);
|
|
780
|
+
useLayoutEffect2(() => {
|
|
781
|
+
if (!containerRef.current) return;
|
|
782
|
+
const measured = fittedWidth(containerRef.current, columns);
|
|
783
|
+
setWidth((prev) => prev === measured ? prev : measured);
|
|
784
|
+
});
|
|
574
785
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
786
|
const labelWidth = Math.min(
|
|
576
787
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -586,6 +797,15 @@ function SelectPrompt({
|
|
|
586
797
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
587
798
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
799
|
const textWidth = inner - labelWidth;
|
|
800
|
+
const scroll = useScrollWindow({
|
|
801
|
+
itemCount: rows.length,
|
|
802
|
+
rowHeight: ROW_HEIGHT
|
|
803
|
+
});
|
|
804
|
+
const { revealIndex } = scroll;
|
|
805
|
+
useLayoutEffect2(() => {
|
|
806
|
+
revealIndex(index);
|
|
807
|
+
}, [index, revealIndex]);
|
|
808
|
+
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
589
809
|
useInput((input, key) => {
|
|
590
810
|
if (rows.length === 0) return;
|
|
591
811
|
if (key.upArrow || input === "k") {
|
|
@@ -609,62 +829,65 @@ function SelectPrompt({
|
|
|
609
829
|
}
|
|
610
830
|
}
|
|
611
831
|
});
|
|
612
|
-
return /* @__PURE__ */ jsx4(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
832
|
+
return /* @__PURE__ */ jsx4(Box6, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, width, children: [
|
|
833
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
834
|
+
error && /* @__PURE__ */ jsx4(Text6, { color: COLORS.danger, children: error }),
|
|
835
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
836
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
837
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
838
|
+
question && /* @__PURE__ */ jsx4(Text6, { color: COLORS.muted, children: question }),
|
|
839
|
+
helpText && /* @__PURE__ */ jsx4(Text6, { color: COLORS.dim, children: helpText })
|
|
840
|
+
] })
|
|
619
841
|
] }),
|
|
620
|
-
/* @__PURE__ */ jsx4(
|
|
842
|
+
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
843
|
+
const i = scroll.offset + visibleIndex;
|
|
621
844
|
const highlighted = i === index;
|
|
622
845
|
const isCancel = i === cancelIndex;
|
|
623
846
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
847
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
848
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
-
const label = /* @__PURE__ */
|
|
849
|
+
const label = /* @__PURE__ */ jsxs5(Text6, { color: labelColor, wrap: "truncate", children: [
|
|
627
850
|
highlighted ? "\u276F " : " ",
|
|
628
851
|
bullet,
|
|
629
852
|
option
|
|
630
853
|
] });
|
|
631
854
|
const isText = sec?.kind === "text";
|
|
632
|
-
return /* @__PURE__ */
|
|
633
|
-
|
|
855
|
+
return /* @__PURE__ */ jsxs5(
|
|
856
|
+
Box6,
|
|
634
857
|
{
|
|
635
858
|
width: isText ? "100%" : barWidth,
|
|
636
859
|
paddingX: 1,
|
|
637
860
|
paddingY: 1,
|
|
638
861
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
862
|
children: [
|
|
640
|
-
/* @__PURE__ */ jsx4(
|
|
641
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
642
|
-
|
|
863
|
+
/* @__PURE__ */ jsx4(Box6, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
864
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box6, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
865
|
+
Text6,
|
|
643
866
|
{
|
|
644
867
|
wrap: "truncate",
|
|
645
868
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
869
|
children: sec.value
|
|
647
870
|
}
|
|
648
871
|
) }),
|
|
649
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
872
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box6, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text6, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
650
873
|
]
|
|
651
874
|
},
|
|
652
875
|
`row-${i}`
|
|
653
876
|
);
|
|
654
877
|
}) }),
|
|
655
|
-
/* @__PURE__ */ jsx4(
|
|
878
|
+
/* @__PURE__ */ jsx4(Box6, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text6, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
656
879
|
i > 0 ? " " : "",
|
|
657
|
-
/* @__PURE__ */ jsx4(
|
|
658
|
-
/* @__PURE__ */
|
|
880
|
+
/* @__PURE__ */ jsx4(Text6, { color: COLORS.primary, children: key }),
|
|
881
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
|
|
659
882
|
" ",
|
|
660
883
|
label
|
|
661
884
|
] })
|
|
662
|
-
] }, label)) })
|
|
885
|
+
] }, label)) }) })
|
|
663
886
|
] }) });
|
|
664
887
|
}
|
|
665
888
|
|
|
666
889
|
// src/ui/PromptInput.tsx
|
|
667
|
-
import { jsx as jsx5, jsxs as
|
|
890
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
668
891
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
669
892
|
function EnterToContinuePrompt({
|
|
670
893
|
question,
|
|
@@ -675,10 +898,10 @@ function EnterToContinuePrompt({
|
|
|
675
898
|
if (key.return) onDecide(true);
|
|
676
899
|
else if (key.escape) onDecide(false);
|
|
677
900
|
});
|
|
678
|
-
return /* @__PURE__ */
|
|
679
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
680
|
-
question && /* @__PURE__ */ jsx5(
|
|
681
|
-
/* @__PURE__ */
|
|
901
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
|
|
902
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
903
|
+
question && /* @__PURE__ */ jsx5(Text7, { color: COLORS.primary, children: question }),
|
|
904
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
682
905
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
683
906
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
684
907
|
] })
|
|
@@ -686,13 +909,13 @@ function EnterToContinuePrompt({
|
|
|
686
909
|
}
|
|
687
910
|
function PromptInput() {
|
|
688
911
|
const { phase, inputReq, submitInput } = useWizard();
|
|
689
|
-
const [draft, setDraft] =
|
|
912
|
+
const [draft, setDraft] = useState5("");
|
|
690
913
|
if (phase === "done" || phase === "error") {
|
|
691
|
-
return /* @__PURE__ */ jsx5(
|
|
914
|
+
return /* @__PURE__ */ jsx5(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text7, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
692
915
|
}
|
|
693
916
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
694
917
|
if (inputReq.promptType === "multipleChoice") {
|
|
695
|
-
return /* @__PURE__ */ jsx5(
|
|
918
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
696
919
|
SelectPrompt,
|
|
697
920
|
{
|
|
698
921
|
question: inputReq.prompt,
|
|
@@ -709,7 +932,7 @@ function PromptInput() {
|
|
|
709
932
|
) });
|
|
710
933
|
}
|
|
711
934
|
if (inputReq.promptType === "multiSelect") {
|
|
712
|
-
return /* @__PURE__ */ jsx5(
|
|
935
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
713
936
|
SelectPrompt,
|
|
714
937
|
{
|
|
715
938
|
multi: true,
|
|
@@ -724,7 +947,7 @@ function PromptInput() {
|
|
|
724
947
|
) });
|
|
725
948
|
}
|
|
726
949
|
if (inputReq.promptType === "notice") {
|
|
727
|
-
return /* @__PURE__ */ jsx5(
|
|
950
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
728
951
|
SelectPrompt,
|
|
729
952
|
{
|
|
730
953
|
question: inputReq.prompt,
|
|
@@ -746,7 +969,7 @@ function PromptInput() {
|
|
|
746
969
|
}
|
|
747
970
|
if (inputReq.promptType === "acceptReject") {
|
|
748
971
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
749
|
-
return /* @__PURE__ */ jsx5(
|
|
972
|
+
return /* @__PURE__ */ jsx5(Box7, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
750
973
|
SelectPrompt,
|
|
751
974
|
{
|
|
752
975
|
question: inputReq.prompt,
|
|
@@ -757,11 +980,11 @@ function PromptInput() {
|
|
|
757
980
|
}
|
|
758
981
|
) });
|
|
759
982
|
}
|
|
760
|
-
return /* @__PURE__ */
|
|
761
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
762
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
983
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
984
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text7, { color: COLORS.danger, children: inputReq.error }),
|
|
985
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text7, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
986
|
+
/* @__PURE__ */ jsxs6(Box7, { children: [
|
|
987
|
+
/* @__PURE__ */ jsxs6(Text7, { color: COLORS.primary, children: [
|
|
765
988
|
inputReq.prompt,
|
|
766
989
|
" "
|
|
767
990
|
] }),
|
|
@@ -783,7 +1006,7 @@ function PromptInput() {
|
|
|
783
1006
|
// src/ui/Welcome.tsx
|
|
784
1007
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
785
1008
|
import { fileURLToPath } from "node:url";
|
|
786
|
-
import { Box as
|
|
1009
|
+
import { Box as Box8, Spacer, Text as Text8, useInput as useInput3, useWindowSize as useWindowSize6 } from "ink";
|
|
787
1010
|
|
|
788
1011
|
// src/ui/copy/welcome.ts
|
|
789
1012
|
var sidebarItems = [
|
|
@@ -796,12 +1019,12 @@ var sidebarItems = [
|
|
|
796
1019
|
description: "push 100 records to Algolia in seconds"
|
|
797
1020
|
},
|
|
798
1021
|
{
|
|
799
|
-
title: "detect your
|
|
800
|
-
description: "React, Vue, Angular,
|
|
1022
|
+
title: "detect your framework",
|
|
1023
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
801
1024
|
},
|
|
802
1025
|
{
|
|
803
1026
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
1027
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
805
1028
|
},
|
|
806
1029
|
{
|
|
807
1030
|
title: "ship it",
|
|
@@ -811,27 +1034,27 @@ var sidebarItems = [
|
|
|
811
1034
|
|
|
812
1035
|
// src/ui/Welcome.tsx
|
|
813
1036
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
814
|
-
import { jsx as jsx6, jsxs as
|
|
1037
|
+
import { jsx as jsx6, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
815
1038
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
816
1039
|
function SidebarItem({
|
|
817
1040
|
title,
|
|
818
1041
|
description
|
|
819
1042
|
}) {
|
|
820
|
-
return /* @__PURE__ */
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
/* @__PURE__ */ jsx6(
|
|
823
|
-
/* @__PURE__ */ jsx6(
|
|
1043
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
1044
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
|
|
1045
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.success, children: "\u2192" }),
|
|
1046
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.strong, bold: true, children: title })
|
|
824
1047
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
1048
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 2, children: [
|
|
826
1049
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
827
|
-
/* @__PURE__ */ jsx6(
|
|
1050
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: description })
|
|
828
1051
|
] })
|
|
829
1052
|
] });
|
|
830
1053
|
}
|
|
831
1054
|
function Welcome() {
|
|
832
1055
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
833
1056
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
834
|
-
const { rows } =
|
|
1057
|
+
const { rows } = useWindowSize6();
|
|
835
1058
|
useInput3((input, key) => {
|
|
836
1059
|
if (key.return) confirmStart();
|
|
837
1060
|
else if (input === "i") openLearnMore();
|
|
@@ -850,15 +1073,15 @@ function Welcome() {
|
|
|
850
1073
|
if (rows < 30) {
|
|
851
1074
|
layout = scales["small"];
|
|
852
1075
|
}
|
|
853
|
-
return /* @__PURE__ */
|
|
1076
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
854
1077
|
/* @__PURE__ */ jsx6(
|
|
855
|
-
|
|
1078
|
+
Box8,
|
|
856
1079
|
{
|
|
857
1080
|
paddingY: layout.main.padding.y,
|
|
858
1081
|
paddingX: layout.main.padding.x,
|
|
859
1082
|
flexDirection: "column",
|
|
860
1083
|
justifyContent: "center",
|
|
861
|
-
children: /* @__PURE__ */
|
|
1084
|
+
children: /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 2, children: [
|
|
862
1085
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
863
1086
|
Image,
|
|
864
1087
|
{
|
|
@@ -870,16 +1093,16 @@ function Welcome() {
|
|
|
870
1093
|
protocol: "halfBlock"
|
|
871
1094
|
}
|
|
872
1095
|
) }),
|
|
873
|
-
/* @__PURE__ */ jsx6(
|
|
874
|
-
/* @__PURE__ */
|
|
1096
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1097
|
+
/* @__PURE__ */ jsxs7(Box8, { gap: 1, flexDirection: "column", children: [
|
|
875
1098
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
876
1099
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
877
1100
|
] })
|
|
878
1101
|
] })
|
|
879
1102
|
}
|
|
880
1103
|
),
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
|
|
1104
|
+
/* @__PURE__ */ jsxs7(
|
|
1105
|
+
Box8,
|
|
883
1106
|
{
|
|
884
1107
|
backgroundColor: COLORS.bg.sidebar,
|
|
885
1108
|
width: 40,
|
|
@@ -889,7 +1112,7 @@ function Welcome() {
|
|
|
889
1112
|
flexDirection: "column",
|
|
890
1113
|
justifyContent: "center",
|
|
891
1114
|
children: [
|
|
892
|
-
/* @__PURE__ */ jsx6(
|
|
1115
|
+
/* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
893
1116
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
894
1117
|
]
|
|
895
1118
|
}
|
|
@@ -899,7 +1122,7 @@ function Welcome() {
|
|
|
899
1122
|
|
|
900
1123
|
// src/ui/LearnMore.tsx
|
|
901
1124
|
import { Fragment as Fragment2 } from "react";
|
|
902
|
-
import { Box as
|
|
1125
|
+
import { Box as Box9, Text as Text9, useInput as useInput4, useWindowSize as useWindowSize7 } from "ink";
|
|
903
1126
|
|
|
904
1127
|
// src/ui/copy/learn-more.ts
|
|
905
1128
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -907,7 +1130,7 @@ var accessItems = [
|
|
|
907
1130
|
{
|
|
908
1131
|
tag: "READ",
|
|
909
1132
|
title: "Project files",
|
|
910
|
-
description: "reads
|
|
1133
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
1134
|
},
|
|
912
1135
|
{
|
|
913
1136
|
tag: "WRITE",
|
|
@@ -936,7 +1159,7 @@ var policyLinks = [
|
|
|
936
1159
|
];
|
|
937
1160
|
|
|
938
1161
|
// src/ui/LearnMore.tsx
|
|
939
|
-
import { jsx as jsx7, jsxs as
|
|
1162
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
940
1163
|
var TAG_COLORS = {
|
|
941
1164
|
READ: COLORS.success,
|
|
942
1165
|
WRITE: COLORS.badge,
|
|
@@ -952,25 +1175,25 @@ function NeverLine({
|
|
|
952
1175
|
}) {
|
|
953
1176
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
954
1177
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
955
|
-
return /* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */ jsx7(
|
|
1178
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1179
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" }),
|
|
957
1180
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
958
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1181
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text9, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
959
1182
|
" ".repeat(rightPad),
|
|
960
|
-
/* @__PURE__ */ jsx7(
|
|
1183
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: "\u2502" })
|
|
961
1184
|
] });
|
|
962
1185
|
}
|
|
963
1186
|
function LearnMore() {
|
|
964
1187
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
965
1188
|
const backToHome = useWizard((s) => s.backToHome);
|
|
966
|
-
const { columns } =
|
|
1189
|
+
const { columns } = useWindowSize7();
|
|
967
1190
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
968
1191
|
useInput4((_input, key) => {
|
|
969
1192
|
if (key.escape) backToHome();
|
|
970
1193
|
else if (key.return) confirmStart();
|
|
971
1194
|
});
|
|
972
|
-
return /* @__PURE__ */
|
|
973
|
-
|
|
1195
|
+
return /* @__PURE__ */ jsxs8(
|
|
1196
|
+
Box9,
|
|
974
1197
|
{
|
|
975
1198
|
flexDirection: "column",
|
|
976
1199
|
paddingX: PADDING_X,
|
|
@@ -978,20 +1201,20 @@ function LearnMore() {
|
|
|
978
1201
|
width: "100%",
|
|
979
1202
|
gap: 1,
|
|
980
1203
|
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(
|
|
1204
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1205
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: accessIntro }),
|
|
1206
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
1207
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1208
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1209
|
+
/* @__PURE__ */ jsx7(Box9, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text9, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1210
|
+
/* @__PURE__ */ jsx7(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1211
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1212
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
990
1213
|
] }) })
|
|
991
1214
|
] })
|
|
992
1215
|
] }, item.tag)) }),
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */ jsx7(
|
|
1216
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1217
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
995
1218
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
996
1219
|
/* @__PURE__ */ jsx7(
|
|
997
1220
|
NeverLine,
|
|
@@ -1000,7 +1223,7 @@ function LearnMore() {
|
|
|
1000
1223
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1001
1224
|
}
|
|
1002
1225
|
),
|
|
1003
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1226
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1004
1227
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1005
1228
|
/* @__PURE__ */ jsx7(
|
|
1006
1229
|
NeverLine,
|
|
@@ -1015,23 +1238,23 @@ function LearnMore() {
|
|
|
1015
1238
|
)
|
|
1016
1239
|
] }, item)),
|
|
1017
1240
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1018
|
-
/* @__PURE__ */ jsx7(
|
|
1241
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1019
1242
|
] }),
|
|
1020
|
-
/* @__PURE__ */ jsx7(
|
|
1021
|
-
/* @__PURE__ */ jsx7(
|
|
1022
|
-
/* @__PURE__ */ jsx7(
|
|
1243
|
+
/* @__PURE__ */ jsx7(Box9, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1244
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1245
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.accent, children: link.url })
|
|
1023
1246
|
] }, link.label)) }),
|
|
1024
|
-
/* @__PURE__ */
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */ jsx7(
|
|
1027
|
-
/* @__PURE__ */ jsx7(
|
|
1028
|
-
/* @__PURE__ */ jsx7(
|
|
1247
|
+
/* @__PURE__ */ jsxs8(Box9, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1248
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1249
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1250
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "esc" }),
|
|
1251
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "] back" })
|
|
1029
1252
|
] }),
|
|
1030
|
-
/* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */ jsx7(
|
|
1032
|
-
/* @__PURE__ */ jsx7(
|
|
1033
|
-
/* @__PURE__ */ jsx7(
|
|
1034
|
-
/* @__PURE__ */ jsx7(
|
|
1253
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1254
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "[" }),
|
|
1255
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: "enter" }),
|
|
1256
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.muted, children: "]" }),
|
|
1257
|
+
/* @__PURE__ */ jsx7(Text9, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1035
1258
|
] })
|
|
1036
1259
|
] })
|
|
1037
1260
|
]
|
|
@@ -1040,10 +1263,10 @@ function LearnMore() {
|
|
|
1040
1263
|
}
|
|
1041
1264
|
|
|
1042
1265
|
// src/ui/Sidebar.tsx
|
|
1043
|
-
import { Box as
|
|
1266
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1044
1267
|
|
|
1045
1268
|
// src/ui/Steps.tsx
|
|
1046
|
-
import { Box as
|
|
1269
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1047
1270
|
import Spinner from "ink-spinner";
|
|
1048
1271
|
|
|
1049
1272
|
// src/core/persistence.ts
|
|
@@ -1072,11 +1295,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1072
1295
|
}
|
|
1073
1296
|
|
|
1074
1297
|
// src/ui/Steps.tsx
|
|
1075
|
-
import { jsx as jsx8, jsxs as
|
|
1298
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1076
1299
|
function Steps() {
|
|
1077
1300
|
const { steps } = useWizard();
|
|
1078
1301
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1079
|
-
return /* @__PURE__ */ jsx8(
|
|
1302
|
+
return /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status[s.status], children: [
|
|
1080
1303
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1081
1304
|
" ",
|
|
1082
1305
|
s.title
|
|
@@ -1086,7 +1309,7 @@ function CurrentStep() {
|
|
|
1086
1309
|
const { steps } = useWizard();
|
|
1087
1310
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1088
1311
|
if (!currentStep) return null;
|
|
1089
|
-
return /* @__PURE__ */
|
|
1312
|
+
return /* @__PURE__ */ jsxs9(Text10, { color: COLORS.status.running, children: [
|
|
1090
1313
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1091
1314
|
" ",
|
|
1092
1315
|
` ${currentStep.title}`
|
|
@@ -1094,19 +1317,19 @@ function CurrentStep() {
|
|
|
1094
1317
|
}
|
|
1095
1318
|
|
|
1096
1319
|
// src/ui/Progress.tsx
|
|
1097
|
-
import { Box as
|
|
1098
|
-
import { jsx as jsx9, jsxs as
|
|
1320
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1321
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1099
1322
|
function Progress() {
|
|
1100
1323
|
const { steps, currentStepIndex } = useWizard();
|
|
1101
1324
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1102
1325
|
if (visibleSteps.length === 0) return null;
|
|
1103
1326
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1104
1327
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1105
|
-
return /* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */ jsx9(
|
|
1107
|
-
/* @__PURE__ */ jsx9(
|
|
1108
|
-
/* @__PURE__ */ jsx9(
|
|
1109
|
-
/* @__PURE__ */ jsx9(
|
|
1328
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1329
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "STEP" }),
|
|
1330
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: activeStepNumber }),
|
|
1331
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: "/" }),
|
|
1332
|
+
/* @__PURE__ */ jsx9(Text11, { bold: true, children: visibleSteps.length })
|
|
1110
1333
|
] });
|
|
1111
1334
|
}
|
|
1112
1335
|
|
|
@@ -1117,10 +1340,10 @@ var sidebarCommands = [
|
|
|
1117
1340
|
];
|
|
1118
1341
|
|
|
1119
1342
|
// src/ui/Sidebar.tsx
|
|
1120
|
-
import { jsx as jsx10, jsxs as
|
|
1343
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1121
1344
|
function Sidebar() {
|
|
1122
|
-
return /* @__PURE__ */
|
|
1123
|
-
|
|
1345
|
+
return /* @__PURE__ */ jsxs11(
|
|
1346
|
+
Box12,
|
|
1124
1347
|
{
|
|
1125
1348
|
backgroundColor: "#14171E",
|
|
1126
1349
|
width: 30,
|
|
@@ -1129,16 +1352,16 @@ function Sidebar() {
|
|
|
1129
1352
|
flexDirection: "column",
|
|
1130
1353
|
justifyContent: "space-between",
|
|
1131
1354
|
children: [
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */ jsx10(
|
|
1355
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1356
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1134
1357
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1135
1358
|
] }),
|
|
1136
|
-
/* @__PURE__ */
|
|
1359
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
1137
1360
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1138
|
-
/* @__PURE__ */ jsx10(
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */ jsx10(
|
|
1141
|
-
/* @__PURE__ */ jsx10(
|
|
1361
|
+
/* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1362
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1363
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1364
|
+
/* @__PURE__ */ jsx10(Text12, { color: COLORS.muted, children: c.description })
|
|
1142
1365
|
] });
|
|
1143
1366
|
}) })
|
|
1144
1367
|
] })
|
|
@@ -1148,12 +1371,12 @@ function Sidebar() {
|
|
|
1148
1371
|
}
|
|
1149
1372
|
|
|
1150
1373
|
// src/ui/Ribbon.tsx
|
|
1151
|
-
import { Box as
|
|
1152
|
-
import { jsx as jsx11, jsxs as
|
|
1374
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1375
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1153
1376
|
function Ribbon() {
|
|
1154
1377
|
const firstCommand = sidebarCommands[0];
|
|
1155
|
-
return /* @__PURE__ */
|
|
1156
|
-
|
|
1378
|
+
return /* @__PURE__ */ jsxs12(
|
|
1379
|
+
Box13,
|
|
1157
1380
|
{
|
|
1158
1381
|
backgroundColor: "#14171E",
|
|
1159
1382
|
flexDirection: "row",
|
|
@@ -1163,9 +1386,9 @@ function Ribbon() {
|
|
|
1163
1386
|
children: [
|
|
1164
1387
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1165
1388
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1166
|
-
/* @__PURE__ */
|
|
1167
|
-
/* @__PURE__ */ jsx11(
|
|
1168
|
-
/* @__PURE__ */ jsx11(
|
|
1389
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1390
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1391
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: firstCommand.description })
|
|
1169
1392
|
] })
|
|
1170
1393
|
]
|
|
1171
1394
|
}
|
|
@@ -1176,9 +1399,8 @@ function Ribbon() {
|
|
|
1176
1399
|
import { useState as useState6 } from "react";
|
|
1177
1400
|
|
|
1178
1401
|
// src/ui/Logs.tsx
|
|
1179
|
-
import {
|
|
1180
|
-
import {
|
|
1181
|
-
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1402
|
+
import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
|
|
1403
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1182
1404
|
var KIND_COLOR = {
|
|
1183
1405
|
tool: COLORS.primary,
|
|
1184
1406
|
prompt: COLORS.badge
|
|
@@ -1208,75 +1430,32 @@ function formatTimestamp(ms) {
|
|
|
1208
1430
|
}
|
|
1209
1431
|
function Logs() {
|
|
1210
1432
|
const logs = useWizard((s) => s.logs);
|
|
1211
|
-
const {
|
|
1212
|
-
const viewportRef = useRef3(null);
|
|
1213
|
-
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1214
|
-
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1215
|
-
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1216
|
-
const prevMaxOffsetRef = useRef3(0);
|
|
1217
|
-
useLayoutEffect2(() => {
|
|
1218
|
-
if (!viewportRef.current) return;
|
|
1219
|
-
const { width, height } = measureElement3(viewportRef.current);
|
|
1220
|
-
setViewportHeight(height);
|
|
1221
|
-
setViewportWidth(width);
|
|
1222
|
-
}, [rows, columns, logs.length === 0]);
|
|
1223
|
-
let capacity = viewportHeight;
|
|
1224
|
-
for (let i = 0; i < 2; i++) {
|
|
1225
|
-
const hasAbove = scrollOffset > 0;
|
|
1226
|
-
const hasBelow = scrollOffset + capacity < logs.length;
|
|
1227
|
-
capacity = Math.max(
|
|
1228
|
-
viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
|
|
1229
|
-
0
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1233
|
-
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1234
|
-
useLayoutEffect2(() => {
|
|
1235
|
-
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1236
|
-
prevMaxOffsetRef.current = maxOffset;
|
|
1237
|
-
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
1238
|
-
}, [maxOffset]);
|
|
1433
|
+
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1239
1434
|
useInput5((_input, key) => {
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
(o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
|
|
1243
|
-
);
|
|
1435
|
+
if (key.upArrow) scroll.scrollBy(-1);
|
|
1436
|
+
else if (key.downArrow) scroll.scrollBy(1);
|
|
1244
1437
|
});
|
|
1245
|
-
const visible = logs.slice(
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1267
|
-
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
|
|
1268
|
-
/* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1269
|
-
preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1270
|
-
durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
|
|
1271
|
-
] }, entry.id);
|
|
1272
|
-
}),
|
|
1273
|
-
hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1274
|
-
"\u2193 ",
|
|
1275
|
-
hiddenBelow,
|
|
1276
|
-
" more"
|
|
1277
|
-
] })
|
|
1278
|
-
] }),
|
|
1279
|
-
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1438
|
+
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1439
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1440
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "No logs yet." }),
|
|
1441
|
+
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1442
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1443
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1444
|
+
const rawPreview = rawInputText(entry.input);
|
|
1445
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1446
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
1447
|
+
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
1448
|
+
const name = truncate2(entry.name, budget);
|
|
1449
|
+
budget -= name.length;
|
|
1450
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1451
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1452
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: timestamp }),
|
|
1453
|
+
/* @__PURE__ */ jsx12(Text14, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1454
|
+
preview && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1455
|
+
durationText && /* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: durationText })
|
|
1456
|
+
] }, entry.id);
|
|
1457
|
+
}) }),
|
|
1458
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1280
1459
|
] });
|
|
1281
1460
|
}
|
|
1282
1461
|
|
|
@@ -1468,11 +1647,11 @@ function track(event, payload) {
|
|
|
1468
1647
|
}
|
|
1469
1648
|
|
|
1470
1649
|
// src/ui/App.tsx
|
|
1471
|
-
import { jsx as jsx13, jsxs as
|
|
1650
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1472
1651
|
function App() {
|
|
1473
1652
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1474
1653
|
const { exit } = useApp();
|
|
1475
|
-
const { columns, rows } =
|
|
1654
|
+
const { columns, rows } = useWindowSize8();
|
|
1476
1655
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1477
1656
|
const finished = phase === "done" || phase === "error";
|
|
1478
1657
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1485,7 +1664,7 @@ function App() {
|
|
|
1485
1664
|
{ isActive: finished }
|
|
1486
1665
|
);
|
|
1487
1666
|
useInput6((_input, key) => {
|
|
1488
|
-
if (phase === "idle" || phase === "
|
|
1667
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
1489
1668
|
if (key.tab) {
|
|
1490
1669
|
setShowLogs(!showLogs);
|
|
1491
1670
|
track("AI Wizard Interaction", {
|
|
@@ -1495,7 +1674,7 @@ function App() {
|
|
|
1495
1674
|
});
|
|
1496
1675
|
}
|
|
1497
1676
|
});
|
|
1498
|
-
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1677
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
|
|
1499
1678
|
useInput6((_input, key) => {
|
|
1500
1679
|
if (escOwnedElsewhere) return;
|
|
1501
1680
|
if (key.escape) {
|
|
@@ -1508,53 +1687,71 @@ function App() {
|
|
|
1508
1687
|
exit();
|
|
1509
1688
|
}
|
|
1510
1689
|
});
|
|
1511
|
-
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1690
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1512
1691
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1513
1692
|
const showSidebar = flexDirection === "row";
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1693
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
1694
|
+
return (
|
|
1695
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1696
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1697
|
+
flicker and leftover rows. */
|
|
1698
|
+
/* @__PURE__ */ jsxs14(
|
|
1699
|
+
Box15,
|
|
1700
|
+
{
|
|
1701
|
+
backgroundColor: COLORS.bg.main,
|
|
1702
|
+
flexDirection: "row",
|
|
1703
|
+
width: columns,
|
|
1704
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
1705
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1706
|
+
children: [
|
|
1707
|
+
mainWindowVisible && // Without a cap the scrolling lists in here grow to their content
|
|
1708
|
+
// instead of windowing (see `useScrollWindow`).
|
|
1709
|
+
/* @__PURE__ */ jsxs14(
|
|
1710
|
+
Box15,
|
|
1711
|
+
{
|
|
1712
|
+
flexDirection,
|
|
1713
|
+
width: "100%",
|
|
1714
|
+
maxHeight: rows,
|
|
1715
|
+
justifyContent: "space-between",
|
|
1716
|
+
children: [
|
|
1717
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1718
|
+
/* Fill the space the sidebar/ribbon leaves — width beside the
|
|
1719
|
+
sidebar, height above the ribbon. The height matters even
|
|
1720
|
+
stacked: it is what the prompt's scrolling list measures itself
|
|
1721
|
+
against (see SelectPrompt). */
|
|
1722
|
+
/* @__PURE__ */ jsxs14(
|
|
1723
|
+
Box15,
|
|
1724
|
+
{
|
|
1725
|
+
flexDirection: "column",
|
|
1726
|
+
paddingX: 4,
|
|
1727
|
+
paddingY: 2,
|
|
1728
|
+
width: showSidebar ? 70 : "100%",
|
|
1729
|
+
flexGrow: 1,
|
|
1730
|
+
children: [
|
|
1731
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginBottom: 1, children: [
|
|
1732
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
1733
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
1734
|
+
] }),
|
|
1735
|
+
/* @__PURE__ */ jsx13(CliOutput, {}),
|
|
1736
|
+
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1737
|
+
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1738
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1739
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: COLORS.status.error, children: [
|
|
1740
|
+
"\u2716 ",
|
|
1741
|
+
error
|
|
1742
|
+
] }) })
|
|
1743
|
+
]
|
|
1744
|
+
}
|
|
1745
|
+
)
|
|
1746
|
+
),
|
|
1747
|
+
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1748
|
+
]
|
|
1749
|
+
}
|
|
1750
|
+
),
|
|
1751
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
|
|
1752
|
+
]
|
|
1753
|
+
}
|
|
1754
|
+
)
|
|
1558
1755
|
);
|
|
1559
1756
|
}
|
|
1560
1757
|
|
|
@@ -1790,61 +1987,138 @@ async function runWorkflow(workflow, appId) {
|
|
|
1790
1987
|
}
|
|
1791
1988
|
}
|
|
1792
1989
|
|
|
1793
|
-
// src/lib/
|
|
1794
|
-
import {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1990
|
+
// src/lib/algoliaApp.ts
|
|
1991
|
+
import { z as z4 } from "zod";
|
|
1992
|
+
var applicationSchema = z4.object({
|
|
1993
|
+
id: z4.string().min(1),
|
|
1994
|
+
name: z4.string().default(""),
|
|
1995
|
+
plan: z4.string().optional()
|
|
1996
|
+
});
|
|
1997
|
+
var listSchema = z4.array(
|
|
1998
|
+
z4.object({
|
|
1999
|
+
id: z4.string().min(1),
|
|
2000
|
+
name: z4.string().default(""),
|
|
2001
|
+
plan_label: z4.string().optional()
|
|
2002
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
2003
|
+
);
|
|
2004
|
+
async function currentApplication() {
|
|
2005
|
+
let raw;
|
|
1806
2006
|
try {
|
|
1807
|
-
|
|
2007
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1808
2008
|
} catch {
|
|
1809
|
-
return
|
|
2009
|
+
return null;
|
|
2010
|
+
}
|
|
2011
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2012
|
+
return parsed.success ? parsed.data : null;
|
|
2013
|
+
}
|
|
2014
|
+
async function requireApplication() {
|
|
2015
|
+
const app = await currentApplication();
|
|
2016
|
+
if (!app) {
|
|
2017
|
+
throw new Error(
|
|
2018
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
return app;
|
|
2022
|
+
}
|
|
2023
|
+
async function listApplications() {
|
|
2024
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
2025
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
2026
|
+
if (!parsed.success) {
|
|
2027
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
1810
2028
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
|
|
2029
|
+
return parsed.data;
|
|
2030
|
+
}
|
|
2031
|
+
async function selectApplication(id) {
|
|
2032
|
+
const raw = await runAlgoliaCli(
|
|
2033
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
2034
|
+
{ onOutput: stderrSink }
|
|
2035
|
+
);
|
|
2036
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2037
|
+
if (!parsed.success) {
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
return parsed.data;
|
|
2043
|
+
}
|
|
2044
|
+
function parseJson(text) {
|
|
1824
2045
|
try {
|
|
1825
|
-
|
|
2046
|
+
return JSON.parse(text);
|
|
1826
2047
|
} catch {
|
|
1827
|
-
|
|
2048
|
+
return void 0;
|
|
1828
2049
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// src/lib/algoliaAppPicker.ts
|
|
2053
|
+
function secondaryFor(app) {
|
|
2054
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
2055
|
+
}
|
|
2056
|
+
function labelFor(app) {
|
|
2057
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
2058
|
+
}
|
|
2059
|
+
function selectAndReport(app) {
|
|
2060
|
+
useWizard.getState().pushCliOutput(
|
|
2061
|
+
"stdout",
|
|
2062
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
2063
|
+
);
|
|
2064
|
+
return selectApplication(app.id);
|
|
2065
|
+
}
|
|
2066
|
+
async function promptForApplication() {
|
|
2067
|
+
const store = useWizard.getState();
|
|
2068
|
+
const apps = await listApplications();
|
|
2069
|
+
if (apps.length === 0) {
|
|
1831
2070
|
throw new Error(
|
|
1832
|
-
"
|
|
2071
|
+
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
if (apps.length === 1) {
|
|
2075
|
+
const only = apps[0];
|
|
2076
|
+
logger.info(
|
|
2077
|
+
{ app: only.id },
|
|
2078
|
+
"single application on the account; selecting it"
|
|
1833
2079
|
);
|
|
2080
|
+
return selectAndReport(only);
|
|
2081
|
+
}
|
|
2082
|
+
const messages = ["Which Algolia application should the wizard work in?"];
|
|
2083
|
+
for (; ; ) {
|
|
2084
|
+
const choice = await store.requestUserInput({
|
|
2085
|
+
prompt: "Select an application",
|
|
2086
|
+
promptType: "multipleChoice",
|
|
2087
|
+
options: apps.map(labelFor),
|
|
2088
|
+
secondary: apps.map(secondaryFor),
|
|
2089
|
+
messages
|
|
2090
|
+
});
|
|
2091
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
2092
|
+
if (!chosen) {
|
|
2093
|
+
throw new Error("Application picker received an unexpected selection");
|
|
2094
|
+
}
|
|
2095
|
+
try {
|
|
2096
|
+
return await selectAndReport(chosen);
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
logger.warn(
|
|
2099
|
+
{ app: chosen.id, err: err.message },
|
|
2100
|
+
"application select failed; re-prompting"
|
|
2101
|
+
);
|
|
2102
|
+
messages.push(
|
|
2103
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
1834
2106
|
}
|
|
1835
|
-
|
|
2107
|
+
}
|
|
2108
|
+
async function ensureApplication() {
|
|
2109
|
+
return await currentApplication() ?? await promptForApplication();
|
|
1836
2110
|
}
|
|
1837
2111
|
|
|
1838
2112
|
// src/workflows/default.ts
|
|
1839
|
-
import { z as
|
|
2113
|
+
import { z as z27 } from "zod";
|
|
1840
2114
|
|
|
1841
2115
|
// src/actions/listIndices.ts
|
|
1842
|
-
import { z as
|
|
1843
|
-
var indicesListSchema =
|
|
1844
|
-
items:
|
|
1845
|
-
|
|
1846
|
-
name:
|
|
1847
|
-
entries:
|
|
2116
|
+
import { z as z5 } from "zod";
|
|
2117
|
+
var indicesListSchema = z5.object({
|
|
2118
|
+
items: z5.array(
|
|
2119
|
+
z5.object({
|
|
2120
|
+
name: z5.string(),
|
|
2121
|
+
entries: z5.number().default(0)
|
|
1848
2122
|
})
|
|
1849
2123
|
)
|
|
1850
2124
|
});
|
|
@@ -1915,12 +2189,12 @@ import "zod";
|
|
|
1915
2189
|
|
|
1916
2190
|
// src/lib/tools/listFiles.ts
|
|
1917
2191
|
import { tool } from "ai";
|
|
1918
|
-
import
|
|
2192
|
+
import z6 from "zod";
|
|
1919
2193
|
import { readdir } from "node:fs/promises";
|
|
1920
2194
|
|
|
1921
2195
|
// src/lib/tools/path.ts
|
|
1922
2196
|
import { lstat } from "node:fs/promises";
|
|
1923
|
-
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as
|
|
2197
|
+
import { resolve as resolve2, relative, isAbsolute, dirname as dirname3, join as join6, sep } from "node:path";
|
|
1924
2198
|
function resolveInRoot(ctx, path) {
|
|
1925
2199
|
const target = resolve2(ctx.cwd, path);
|
|
1926
2200
|
const rel = relative(ctx.root, target);
|
|
@@ -1936,7 +2210,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1936
2210
|
let current = ctx.root;
|
|
1937
2211
|
const parts = relative(ctx.root, dirname3(target)).split(sep).filter(Boolean);
|
|
1938
2212
|
for (const part of parts) {
|
|
1939
|
-
current =
|
|
2213
|
+
current = join6(current, part);
|
|
1940
2214
|
try {
|
|
1941
2215
|
if ((await lstat(current)).isSymbolicLink()) return true;
|
|
1942
2216
|
} catch (err) {
|
|
@@ -1951,7 +2225,7 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
1951
2225
|
function listFilesTool(ctx) {
|
|
1952
2226
|
return tool({
|
|
1953
2227
|
description: "List files in the current working directory",
|
|
1954
|
-
inputSchema:
|
|
2228
|
+
inputSchema: z6.object(),
|
|
1955
2229
|
execute: async () => {
|
|
1956
2230
|
logger.info("called listFiles tool");
|
|
1957
2231
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
@@ -1967,13 +2241,13 @@ function listFilesTool(ctx) {
|
|
|
1967
2241
|
|
|
1968
2242
|
// src/lib/tools/changeDirectory.ts
|
|
1969
2243
|
import { tool as tool2 } from "ai";
|
|
1970
|
-
import
|
|
2244
|
+
import z7 from "zod";
|
|
1971
2245
|
import { stat } from "node:fs/promises";
|
|
1972
2246
|
function changeDirectoryTool(ctx) {
|
|
1973
2247
|
return tool2({
|
|
1974
2248
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
1975
|
-
inputSchema:
|
|
1976
|
-
path:
|
|
2249
|
+
inputSchema: z7.object({
|
|
2250
|
+
path: z7.string().describe("Directory to change into")
|
|
1977
2251
|
}),
|
|
1978
2252
|
execute: async ({ path }) => {
|
|
1979
2253
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -1995,13 +2269,13 @@ function changeDirectoryTool(ctx) {
|
|
|
1995
2269
|
|
|
1996
2270
|
// src/lib/tools/reportStatus.ts
|
|
1997
2271
|
import { tool as tool3 } from "ai";
|
|
1998
|
-
import
|
|
2272
|
+
import z8 from "zod";
|
|
1999
2273
|
function reportStatusTool(output) {
|
|
2000
2274
|
return tool3({
|
|
2001
2275
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2002
|
-
inputSchema:
|
|
2003
|
-
status:
|
|
2004
|
-
reason:
|
|
2276
|
+
inputSchema: z8.object({
|
|
2277
|
+
status: z8.enum(["success", "fail"]),
|
|
2278
|
+
reason: z8.string().optional(),
|
|
2005
2279
|
output
|
|
2006
2280
|
}),
|
|
2007
2281
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2013,8 +2287,8 @@ function reportStatusTool(output) {
|
|
|
2013
2287
|
|
|
2014
2288
|
// src/lib/tools/readFile.ts
|
|
2015
2289
|
import { tool as tool4 } from "ai";
|
|
2016
|
-
import
|
|
2017
|
-
import { readFile as
|
|
2290
|
+
import z9 from "zod";
|
|
2291
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2018
2292
|
|
|
2019
2293
|
// src/lib/tools/env.ts
|
|
2020
2294
|
import { basename } from "node:path";
|
|
@@ -2041,8 +2315,8 @@ function redactEnvValues(content) {
|
|
|
2041
2315
|
function readFileTool(ctx) {
|
|
2042
2316
|
return tool4({
|
|
2043
2317
|
description: "Read the contents of a file at the given path",
|
|
2044
|
-
inputSchema:
|
|
2045
|
-
filePath:
|
|
2318
|
+
inputSchema: z9.object({
|
|
2319
|
+
filePath: z9.string().describe("Path to the file to read")
|
|
2046
2320
|
}),
|
|
2047
2321
|
execute: async ({ filePath }) => {
|
|
2048
2322
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2052,7 +2326,7 @@ function readFileTool(ctx) {
|
|
|
2052
2326
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2053
2327
|
if (!resolved.ok) return resolved.error;
|
|
2054
2328
|
try {
|
|
2055
|
-
const content = await
|
|
2329
|
+
const content = await readFile3(resolved.target, "utf8");
|
|
2056
2330
|
return isEnvFile(resolved.target) ? redactEnvValues(content) : content;
|
|
2057
2331
|
} catch (err) {
|
|
2058
2332
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2063,15 +2337,15 @@ function readFileTool(ctx) {
|
|
|
2063
2337
|
|
|
2064
2338
|
// src/lib/tools/writeFile.ts
|
|
2065
2339
|
import { tool as tool5 } from "ai";
|
|
2066
|
-
import
|
|
2340
|
+
import z10 from "zod";
|
|
2067
2341
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2068
2342
|
import { dirname as dirname4 } from "node:path";
|
|
2069
2343
|
function writeFileTool(ctx) {
|
|
2070
2344
|
return tool5({
|
|
2071
2345
|
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.",
|
|
2072
|
-
inputSchema:
|
|
2073
|
-
filePath:
|
|
2074
|
-
content:
|
|
2346
|
+
inputSchema: z10.object({
|
|
2347
|
+
filePath: z10.string().describe("Path to the file to write"),
|
|
2348
|
+
content: z10.string().describe("Content to write to the file")
|
|
2075
2349
|
}),
|
|
2076
2350
|
execute: async ({ filePath, content }) => {
|
|
2077
2351
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2096,10 +2370,96 @@ function writeFileTool(ctx) {
|
|
|
2096
2370
|
|
|
2097
2371
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2098
2372
|
import { tool as tool6 } from "ai";
|
|
2099
|
-
import
|
|
2100
|
-
import { mkdir as mkdir4, readFile as
|
|
2373
|
+
import z12 from "zod";
|
|
2374
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2101
2375
|
import { dirname as dirname5 } from "node:path";
|
|
2102
|
-
|
|
2376
|
+
|
|
2377
|
+
// src/lib/algoliaApiKey.ts
|
|
2378
|
+
import { z as z11 } from "zod";
|
|
2379
|
+
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
2380
|
+
var WRITE_ACLS = [
|
|
2381
|
+
"addObject",
|
|
2382
|
+
"deleteObject",
|
|
2383
|
+
"settings",
|
|
2384
|
+
"editSettings",
|
|
2385
|
+
"listIndexes"
|
|
2386
|
+
];
|
|
2387
|
+
var WRITE_ACL_SET = new Set(WRITE_ACLS);
|
|
2388
|
+
var apiKeySchema = z11.object({
|
|
2389
|
+
value: z11.string().min(1),
|
|
2390
|
+
acl: z11.array(z11.string()).default([]),
|
|
2391
|
+
indexes: z11.array(z11.string()).default([])
|
|
2392
|
+
});
|
|
2393
|
+
var apiKeyListSchema = z11.object({
|
|
2394
|
+
items: z11.array(apiKeySchema).optional(),
|
|
2395
|
+
keys: z11.array(apiKeySchema).optional()
|
|
2396
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2397
|
+
var createdKeySchema = z11.object({
|
|
2398
|
+
key: z11.string().min(1).optional(),
|
|
2399
|
+
value: z11.string().min(1).optional()
|
|
2400
|
+
});
|
|
2401
|
+
function canReuse(key, index) {
|
|
2402
|
+
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
2403
|
+
}
|
|
2404
|
+
async function createSearchKey(index) {
|
|
2405
|
+
const stdout = await runAlgoliaCli([
|
|
2406
|
+
"apikeys",
|
|
2407
|
+
"create",
|
|
2408
|
+
"--indices",
|
|
2409
|
+
index,
|
|
2410
|
+
"--acl",
|
|
2411
|
+
"search,browse",
|
|
2412
|
+
"--description",
|
|
2413
|
+
`wizard search-only key for ${index}`,
|
|
2414
|
+
"-o",
|
|
2415
|
+
"json"
|
|
2416
|
+
]);
|
|
2417
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
2418
|
+
const created = key ?? value;
|
|
2419
|
+
if (!created) throw new Error("apikeys create returned no key value");
|
|
2420
|
+
return created;
|
|
2421
|
+
}
|
|
2422
|
+
function canReuseForWrites(key, index) {
|
|
2423
|
+
return WRITE_ACLS.every((acl) => key.acl.includes(acl)) && key.acl.every((acl) => WRITE_ACL_SET.has(acl)) && key.indexes.includes(index);
|
|
2424
|
+
}
|
|
2425
|
+
async function resolveWriteKey(index) {
|
|
2426
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2427
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key2) => canReuseForWrites(key2, index))?.value;
|
|
2428
|
+
if (existing) {
|
|
2429
|
+
logger.info({ index }, "reusing existing write API key");
|
|
2430
|
+
return existing;
|
|
2431
|
+
}
|
|
2432
|
+
logger.info({ index }, "no reusable write key found; creating one");
|
|
2433
|
+
const created = await runAlgoliaCli([
|
|
2434
|
+
"apikeys",
|
|
2435
|
+
"create",
|
|
2436
|
+
"--indices",
|
|
2437
|
+
index,
|
|
2438
|
+
"--acl",
|
|
2439
|
+
WRITE_ACLS.join(","),
|
|
2440
|
+
"--description",
|
|
2441
|
+
`wizard write key for ${index}`,
|
|
2442
|
+
"-o",
|
|
2443
|
+
"json"
|
|
2444
|
+
]);
|
|
2445
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(created));
|
|
2446
|
+
const writeKey = key ?? value;
|
|
2447
|
+
if (!writeKey) throw new Error("apikeys create returned no key value");
|
|
2448
|
+
return writeKey;
|
|
2449
|
+
}
|
|
2450
|
+
async function resolveSearchOnlyKey(index) {
|
|
2451
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
2452
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
2453
|
+
if (existing) {
|
|
2454
|
+
logger.info({ index }, "reusing existing search-only API key");
|
|
2455
|
+
return existing;
|
|
2456
|
+
}
|
|
2457
|
+
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
2458
|
+
return createSearchKey(index);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2462
|
+
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2103
2463
|
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
2104
2464
|
function appendEnv(content, entries) {
|
|
2105
2465
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
@@ -2112,9 +2472,9 @@ function hasEnv(content, name) {
|
|
|
2112
2472
|
}
|
|
2113
2473
|
function writeCredentialsTool(ctx) {
|
|
2114
2474
|
return tool6({
|
|
2115
|
-
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) into the given env file. The credentials
|
|
2116
|
-
inputSchema:
|
|
2117
|
-
filePath:
|
|
2475
|
+
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.`,
|
|
2476
|
+
inputSchema: z12.object({
|
|
2477
|
+
filePath: z12.string().describe(
|
|
2118
2478
|
'Path to the env file to write credentials into (e.g. ".env")'
|
|
2119
2479
|
)
|
|
2120
2480
|
}),
|
|
@@ -2122,11 +2482,17 @@ function writeCredentialsTool(ctx) {
|
|
|
2122
2482
|
logger.info({ filePath }, "called writeCredentials tool");
|
|
2123
2483
|
const resolved = resolveInRoot(ctx, filePath);
|
|
2124
2484
|
if (resolved.ok === false) return resolved.error;
|
|
2125
|
-
|
|
2485
|
+
const targetIndex = useWizard.getState().targetIndex;
|
|
2486
|
+
if (!targetIndex) {
|
|
2487
|
+
return "Error: no target index is set for this run, so a scoped write key cannot be provisioned.";
|
|
2488
|
+
}
|
|
2489
|
+
let appId;
|
|
2490
|
+
let writeKey;
|
|
2126
2491
|
try {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2492
|
+
appId = (await requireApplication()).id;
|
|
2493
|
+
writeKey = await resolveWriteKey(targetIndex);
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
|
|
2130
2496
|
}
|
|
2131
2497
|
try {
|
|
2132
2498
|
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
@@ -2134,7 +2500,7 @@ function writeCredentialsTool(ctx) {
|
|
|
2134
2500
|
}
|
|
2135
2501
|
let existing = "";
|
|
2136
2502
|
try {
|
|
2137
|
-
existing = await
|
|
2503
|
+
existing = await readFile4(resolved.target, "utf8");
|
|
2138
2504
|
} catch (err) {
|
|
2139
2505
|
if (err.code !== "ENOENT") throw err;
|
|
2140
2506
|
}
|
|
@@ -2145,8 +2511,8 @@ function writeCredentialsTool(ctx) {
|
|
|
2145
2511
|
return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
|
|
2146
2512
|
}
|
|
2147
2513
|
const envWithCredentials = appendEnv(existing, [
|
|
2148
|
-
[APP_ID_VAR,
|
|
2149
|
-
[API_KEY_VAR,
|
|
2514
|
+
[APP_ID_VAR, appId],
|
|
2515
|
+
[API_KEY_VAR, writeKey]
|
|
2150
2516
|
]);
|
|
2151
2517
|
await mkdir4(dirname5(resolved.target), { recursive: true });
|
|
2152
2518
|
await writeFile4(resolved.target, envWithCredentials, "utf8");
|
|
@@ -2160,746 +2526,16 @@ function writeCredentialsTool(ctx) {
|
|
|
2160
2526
|
|
|
2161
2527
|
// src/lib/tools/searchFiles.ts
|
|
2162
2528
|
import { tool as tool7 } from "ai";
|
|
2163
|
-
import
|
|
2164
|
-
import { readdir as
|
|
2165
|
-
import { join as
|
|
2166
|
-
|
|
2167
|
-
// src/lib/languages.ts
|
|
2168
|
-
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2169
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2170
|
-
import { join as join9 } from "node:path";
|
|
2171
|
-
|
|
2172
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2173
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
2174
|
-
import { existsSync } from "node:fs";
|
|
2175
|
-
import { join as join8 } from "node:path";
|
|
2176
|
-
var LOCKFILES = [
|
|
2177
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2178
|
-
["yarn.lock", "yarn"],
|
|
2179
|
-
["bun.lockb", "bun"],
|
|
2180
|
-
["bun.lock", "bun"],
|
|
2181
|
-
["package-lock.json", "npm"]
|
|
2182
|
-
];
|
|
2183
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2184
|
-
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2185
|
-
}
|
|
2186
|
-
function packageManagerFrom(pkg) {
|
|
2187
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2188
|
-
}
|
|
2189
|
-
function packageManagerFromLockfile(cwd) {
|
|
2190
|
-
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2191
|
-
}
|
|
2192
|
-
async function detectPackageManager(cwd) {
|
|
2193
|
-
try {
|
|
2194
|
-
const pkg = await readPackageJson(cwd);
|
|
2195
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2196
|
-
} catch {
|
|
2197
|
-
}
|
|
2198
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
// src/lib/shell.ts
|
|
2202
|
-
function shellQuote(value) {
|
|
2203
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
// src/lib/languages.ts
|
|
2207
|
-
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2208
|
-
var INGEST_DIR = ".algolia-wizard";
|
|
2209
|
-
var SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2210
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2211
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2212
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2213
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2214
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2215
|
-
var LANGUAGE_PROFILES = {
|
|
2216
|
-
javascript: {
|
|
2217
|
-
id: "javascript",
|
|
2218
|
-
displayName: "JavaScript/TypeScript",
|
|
2219
|
-
aliases: [
|
|
2220
|
-
"javascript",
|
|
2221
|
-
"js",
|
|
2222
|
-
"typescript",
|
|
2223
|
-
"ts",
|
|
2224
|
-
"node",
|
|
2225
|
-
"nodejs",
|
|
2226
|
-
"node.js",
|
|
2227
|
-
"bun",
|
|
2228
|
-
"deno",
|
|
2229
|
-
"ecmascript",
|
|
2230
|
-
"jsx",
|
|
2231
|
-
"tsx"
|
|
2232
|
-
],
|
|
2233
|
-
manifests: ["package.json"],
|
|
2234
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2235
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2236
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2237
|
-
// binary below.
|
|
2238
|
-
packageManagers: [
|
|
2239
|
-
{
|
|
2240
|
-
id: "npm",
|
|
2241
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2242
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2243
|
-
ingest: {
|
|
2244
|
-
kind: "auto",
|
|
2245
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2246
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
],
|
|
2250
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2251
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2252
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2253
|
-
// repoVerification rather than listed here.
|
|
2254
|
-
verification: [],
|
|
2255
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2256
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2257
|
-
},
|
|
2258
|
-
python: {
|
|
2259
|
-
id: "python",
|
|
2260
|
-
displayName: "Python",
|
|
2261
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2262
|
-
manifests: [
|
|
2263
|
-
"pyproject.toml",
|
|
2264
|
-
"requirements.txt",
|
|
2265
|
-
"setup.py",
|
|
2266
|
-
"setup.cfg",
|
|
2267
|
-
"Pipfile"
|
|
2268
|
-
],
|
|
2269
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2270
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2271
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2272
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2273
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2274
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2275
|
-
// packages (see the optional root-requirements step below).
|
|
2276
|
-
packageManagers: [
|
|
2277
|
-
{
|
|
2278
|
-
id: "pip-venv",
|
|
2279
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2280
|
-
installSteps: [
|
|
2281
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2282
|
-
{
|
|
2283
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2284
|
-
},
|
|
2285
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2286
|
-
// ORMs) when the repo pins them the classic way.
|
|
2287
|
-
{
|
|
2288
|
-
argv: [
|
|
2289
|
-
PY_VENV_PYTHON,
|
|
2290
|
-
"-m",
|
|
2291
|
-
"pip",
|
|
2292
|
-
"install",
|
|
2293
|
-
"-r",
|
|
2294
|
-
"requirements.txt"
|
|
2295
|
-
],
|
|
2296
|
-
requiresFile: "requirements.txt",
|
|
2297
|
-
optional: true
|
|
2298
|
-
}
|
|
2299
|
-
],
|
|
2300
|
-
ingest: {
|
|
2301
|
-
kind: "auto",
|
|
2302
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2303
|
-
entrypointExtensions: [".py"]
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
],
|
|
2307
|
-
sdk: {
|
|
2308
|
-
packageName: "algoliasearch",
|
|
2309
|
-
versionPin: ">=4,<5",
|
|
2310
|
-
docKey: "python"
|
|
2311
|
-
},
|
|
2312
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2313
|
-
localSourceCaveat: {
|
|
2314
|
-
unless: "requirements.txt",
|
|
2315
|
-
message: "The ingestion script runs in its own environment under .algolia-wizard/, so it can install the Algolia client but not this project's packages (no requirements.txt to install from). If the script needs your database driver or ORM, add those packages to .algolia-wizard/requirements.txt and re-run the install."
|
|
2316
|
-
},
|
|
2317
|
-
verification: [
|
|
2318
|
-
{
|
|
2319
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2320
|
-
// compiles every installed package instead of the generated script.
|
|
2321
|
-
label: "python compileall",
|
|
2322
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2323
|
-
requiresFile: INGEST_DIR
|
|
2324
|
-
}
|
|
2325
|
-
],
|
|
2326
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2327
|
-
skipDirs: [
|
|
2328
|
-
"venv",
|
|
2329
|
-
"__pycache__",
|
|
2330
|
-
"site-packages",
|
|
2331
|
-
"dist",
|
|
2332
|
-
"build",
|
|
2333
|
-
"htmlcov"
|
|
2334
|
-
]
|
|
2335
|
-
},
|
|
2336
|
-
ruby: {
|
|
2337
|
-
id: "ruby",
|
|
2338
|
-
displayName: "Ruby",
|
|
2339
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2340
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2341
|
-
packageManagers: [
|
|
2342
|
-
{
|
|
2343
|
-
id: "bundler",
|
|
2344
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2345
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2346
|
-
ingest: {
|
|
2347
|
-
kind: "auto",
|
|
2348
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2349
|
-
entrypointExtensions: [".rb"]
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
],
|
|
2353
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2354
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2355
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2356
|
-
// time), so verification relies on the agent's own review here.
|
|
2357
|
-
verification: [],
|
|
2358
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2359
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2360
|
-
},
|
|
2361
|
-
php: {
|
|
2362
|
-
id: "php",
|
|
2363
|
-
displayName: "PHP",
|
|
2364
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2365
|
-
manifests: ["composer.json"],
|
|
2366
|
-
packageManagers: [
|
|
2367
|
-
{
|
|
2368
|
-
id: "composer",
|
|
2369
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2370
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2371
|
-
// makes a later `composer install` refuse to run).
|
|
2372
|
-
dependency: { mode: "wizard-installs" },
|
|
2373
|
-
installSteps: [
|
|
2374
|
-
{
|
|
2375
|
-
argv: [
|
|
2376
|
-
"composer",
|
|
2377
|
-
"require",
|
|
2378
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2379
|
-
"--no-interaction",
|
|
2380
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2381
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2382
|
-
"--no-scripts"
|
|
2383
|
-
]
|
|
2384
|
-
}
|
|
2385
|
-
],
|
|
2386
|
-
ingest: {
|
|
2387
|
-
kind: "auto",
|
|
2388
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2389
|
-
entrypointExtensions: [".php"]
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
],
|
|
2393
|
-
sdk: {
|
|
2394
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2395
|
-
versionPin: "^4",
|
|
2396
|
-
docKey: "php"
|
|
2397
|
-
},
|
|
2398
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2399
|
-
verification: [],
|
|
2400
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2401
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2402
|
-
},
|
|
2403
|
-
go: {
|
|
2404
|
-
id: "go",
|
|
2405
|
-
displayName: "Go",
|
|
2406
|
-
aliases: ["go", "golang"],
|
|
2407
|
-
manifests: ["go.mod"],
|
|
2408
|
-
packageManagers: [
|
|
2409
|
-
{
|
|
2410
|
-
id: "gomod",
|
|
2411
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2412
|
-
// resolves and fetches them — which only works because the script lives
|
|
2413
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2414
|
-
dependency: { mode: "code-imports" },
|
|
2415
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2416
|
-
ingest: {
|
|
2417
|
-
kind: "auto",
|
|
2418
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2419
|
-
entrypointExtensions: [".go"]
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
],
|
|
2423
|
-
sdk: {
|
|
2424
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2425
|
-
versionPin: "v4",
|
|
2426
|
-
docKey: "go"
|
|
2427
|
-
},
|
|
2428
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2429
|
-
verification: [
|
|
2430
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2431
|
-
],
|
|
2432
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2433
|
-
skipDirs: ["vendor", "bin"]
|
|
2434
|
-
},
|
|
2435
|
-
java: {
|
|
2436
|
-
id: "java",
|
|
2437
|
-
displayName: "Java",
|
|
2438
|
-
aliases: ["java"],
|
|
2439
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2440
|
-
packageManagers: [
|
|
2441
|
-
{
|
|
2442
|
-
id: "maven",
|
|
2443
|
-
detectFiles: ["pom.xml"],
|
|
2444
|
-
sdkVersionPin: "[4,5)",
|
|
2445
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2446
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2447
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2448
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2449
|
-
// because the install step above compiles src/main/java first — which is
|
|
2450
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2451
|
-
ingest: {
|
|
2452
|
-
kind: "auto",
|
|
2453
|
-
argv: [
|
|
2454
|
-
"mvn",
|
|
2455
|
-
"-q",
|
|
2456
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2457
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2458
|
-
],
|
|
2459
|
-
entrypointExtensions: [".java"]
|
|
2460
|
-
}
|
|
2461
|
-
},
|
|
2462
|
-
{
|
|
2463
|
-
id: "gradle",
|
|
2464
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2465
|
-
dependency: {
|
|
2466
|
-
mode: "agent-declares",
|
|
2467
|
-
file: "build.gradle",
|
|
2468
|
-
alternatives: ["build.gradle.kts"]
|
|
2469
|
-
},
|
|
2470
|
-
installSteps: [],
|
|
2471
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2472
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2473
|
-
ingest: {
|
|
2474
|
-
kind: "manual",
|
|
2475
|
-
entrypointExtensions: [".java"],
|
|
2476
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2477
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
],
|
|
2481
|
-
sdk: {
|
|
2482
|
-
packageName: "com.algolia:algoliasearch",
|
|
2483
|
-
versionPin: "4.+",
|
|
2484
|
-
docKey: "java",
|
|
2485
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2486
|
-
},
|
|
2487
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2488
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2489
|
-
// fails with "class not found".
|
|
2490
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2491
|
-
verification: [
|
|
2492
|
-
{
|
|
2493
|
-
label: "mvn compile",
|
|
2494
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2495
|
-
requiresFile: "pom.xml"
|
|
2496
|
-
}
|
|
2497
|
-
],
|
|
2498
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2499
|
-
skipDirs: ["target", "build", "out"]
|
|
2500
|
-
},
|
|
2501
|
-
kotlin: {
|
|
2502
|
-
id: "kotlin",
|
|
2503
|
-
displayName: "Kotlin",
|
|
2504
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2505
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2506
|
-
packageManagers: [
|
|
2507
|
-
{
|
|
2508
|
-
id: "gradle",
|
|
2509
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2510
|
-
dependency: {
|
|
2511
|
-
mode: "agent-declares",
|
|
2512
|
-
file: "build.gradle.kts",
|
|
2513
|
-
alternatives: ["build.gradle"]
|
|
2514
|
-
},
|
|
2515
|
-
installSteps: [],
|
|
2516
|
-
ingest: {
|
|
2517
|
-
kind: "manual",
|
|
2518
|
-
entrypointExtensions: [".kt"],
|
|
2519
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2520
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2521
|
-
}
|
|
2522
|
-
},
|
|
2523
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2524
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2525
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2526
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2527
|
-
{
|
|
2528
|
-
id: "maven",
|
|
2529
|
-
detectFiles: ["pom.xml"],
|
|
2530
|
-
sdkVersionPin: "[3,4)",
|
|
2531
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2532
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2533
|
-
ingest: {
|
|
2534
|
-
kind: "manual",
|
|
2535
|
-
entrypointExtensions: [".kt"],
|
|
2536
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
],
|
|
2540
|
-
sdk: {
|
|
2541
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2542
|
-
versionPin: "3.+",
|
|
2543
|
-
docKey: "kotlin",
|
|
2544
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2545
|
-
// engine the script compiles and then fails at its first request.
|
|
2546
|
-
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."
|
|
2547
|
-
},
|
|
2548
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2549
|
-
verification: [],
|
|
2550
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2551
|
-
skipDirs: ["build", "out"]
|
|
2552
|
-
},
|
|
2553
|
-
scala: {
|
|
2554
|
-
id: "scala",
|
|
2555
|
-
displayName: "Scala",
|
|
2556
|
-
aliases: ["scala", "sbt"],
|
|
2557
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2558
|
-
packageManagers: [
|
|
2559
|
-
{
|
|
2560
|
-
id: "sbt",
|
|
2561
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2562
|
-
installSteps: [],
|
|
2563
|
-
ingest: {
|
|
2564
|
-
kind: "manual",
|
|
2565
|
-
entrypointExtensions: [".scala"],
|
|
2566
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
],
|
|
2570
|
-
sdk: {
|
|
2571
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2572
|
-
versionPin: "2.+",
|
|
2573
|
-
docKey: "scala",
|
|
2574
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2575
|
-
},
|
|
2576
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2577
|
-
verification: [],
|
|
2578
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2579
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2580
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2581
|
-
// is not.
|
|
2582
|
-
skipDirs: ["target"]
|
|
2583
|
-
},
|
|
2584
|
-
csharp: {
|
|
2585
|
-
id: "csharp",
|
|
2586
|
-
displayName: "C#",
|
|
2587
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2588
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2589
|
-
packageManagers: [
|
|
2590
|
-
{
|
|
2591
|
-
id: "dotnet",
|
|
2592
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2593
|
-
// out of the repo's own build graph.
|
|
2594
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2595
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2596
|
-
ingest: {
|
|
2597
|
-
kind: "auto",
|
|
2598
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2599
|
-
entrypointExtensions: [".csproj"]
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
],
|
|
2603
|
-
sdk: {
|
|
2604
|
-
packageName: "Algolia.Search",
|
|
2605
|
-
versionPin: "7.*",
|
|
2606
|
-
docKey: "csharp"
|
|
2607
|
-
},
|
|
2608
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2609
|
-
verification: [
|
|
2610
|
-
{
|
|
2611
|
-
label: "dotnet build",
|
|
2612
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2613
|
-
requiresFile: CSHARP_PROJECT
|
|
2614
|
-
}
|
|
2615
|
-
],
|
|
2616
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2617
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2618
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2619
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2620
|
-
skipDirs: ["bin", "obj"]
|
|
2621
|
-
},
|
|
2622
|
-
swift: {
|
|
2623
|
-
id: "swift",
|
|
2624
|
-
displayName: "Swift",
|
|
2625
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2626
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2627
|
-
packageManagers: [
|
|
2628
|
-
{
|
|
2629
|
-
id: "swiftpm",
|
|
2630
|
-
dependency: {
|
|
2631
|
-
mode: "agent-declares",
|
|
2632
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2633
|
-
},
|
|
2634
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2635
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2636
|
-
// this fails.
|
|
2637
|
-
installSteps: [
|
|
2638
|
-
{
|
|
2639
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2640
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2641
|
-
}
|
|
2642
|
-
],
|
|
2643
|
-
ingest: {
|
|
2644
|
-
kind: "auto",
|
|
2645
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2646
|
-
entrypointExtensions: [".swift"]
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
],
|
|
2650
|
-
sdk: {
|
|
2651
|
-
packageName: "algoliasearch-client-swift",
|
|
2652
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2653
|
-
// Package.swift dependency pins the patch.
|
|
2654
|
-
versionPin: 'from: "9.0.0"',
|
|
2655
|
-
docKey: "swift"
|
|
2656
|
-
},
|
|
2657
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2658
|
-
verification: [
|
|
2659
|
-
{
|
|
2660
|
-
label: "swift build",
|
|
2661
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2662
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2663
|
-
}
|
|
2664
|
-
],
|
|
2665
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2666
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2667
|
-
},
|
|
2668
|
-
dart: {
|
|
2669
|
-
id: "dart",
|
|
2670
|
-
displayName: "Dart",
|
|
2671
|
-
aliases: ["dart", "flutter"],
|
|
2672
|
-
manifests: ["pubspec.yaml"],
|
|
2673
|
-
packageManagers: [
|
|
2674
|
-
{
|
|
2675
|
-
id: "flutter-pub",
|
|
2676
|
-
detectFiles: [".metadata"],
|
|
2677
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2678
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2679
|
-
ingest: {
|
|
2680
|
-
kind: "auto",
|
|
2681
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2682
|
-
entrypointExtensions: [".dart"]
|
|
2683
|
-
}
|
|
2684
|
-
},
|
|
2685
|
-
{
|
|
2686
|
-
id: "pub",
|
|
2687
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2688
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2689
|
-
ingest: {
|
|
2690
|
-
kind: "auto",
|
|
2691
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2692
|
-
entrypointExtensions: [".dart"]
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
],
|
|
2696
|
-
sdk: {
|
|
2697
|
-
packageName: "algolia_client_search",
|
|
2698
|
-
versionPin: "^1.0.0",
|
|
2699
|
-
docKey: "dart"
|
|
2700
|
-
},
|
|
2701
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2702
|
-
verification: [
|
|
2703
|
-
{
|
|
2704
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2705
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2706
|
-
// missing path fails the whole verification pass.
|
|
2707
|
-
label: "dart analyze",
|
|
2708
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2709
|
-
requiresFile: INGEST_DIR
|
|
2710
|
-
}
|
|
2711
|
-
],
|
|
2712
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2713
|
-
skipDirs: ["build"]
|
|
2714
|
-
}
|
|
2715
|
-
};
|
|
2716
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2717
|
-
var JAVASCRIPT = "javascript";
|
|
2718
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2719
|
-
LANGUAGE_PROFILES
|
|
2720
|
-
).map((profile) => profile.displayName);
|
|
2721
|
-
function isBackendLanguage(profile) {
|
|
2722
|
-
return profile.id !== JAVASCRIPT;
|
|
2723
|
-
}
|
|
2724
|
-
function normalizeLanguageName(name) {
|
|
2725
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2726
|
-
}
|
|
2727
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2728
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2729
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2730
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
function resolveLanguageProfile(name) {
|
|
2734
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2735
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2736
|
-
}
|
|
2737
|
-
function isSameLanguage(a, b) {
|
|
2738
|
-
const x = resolveLanguageProfile(a);
|
|
2739
|
-
const y = resolveLanguageProfile(b);
|
|
2740
|
-
if (x && y) return x.id === y.id;
|
|
2741
|
-
if (x || y) return false;
|
|
2742
|
-
const folded = normalizeLanguageName(a);
|
|
2743
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2744
|
-
}
|
|
2745
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2746
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2747
|
-
...BASE_SKIP_DIRS,
|
|
2748
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2749
|
-
]);
|
|
2750
|
-
var ALLOWED_BINARIES = new Set(
|
|
2751
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2752
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
2753
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2754
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2755
|
-
]),
|
|
2756
|
-
...profile.verification.map((v) => v.argv[0])
|
|
2757
|
-
])
|
|
2758
|
-
);
|
|
2759
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2760
|
-
function isWorktreeRelativeCommand(command) {
|
|
2761
|
-
return command.includes("/");
|
|
2762
|
-
}
|
|
2763
|
-
function withCommand(argv, command) {
|
|
2764
|
-
return [command, ...argv.slice(1)];
|
|
2765
|
-
}
|
|
2766
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2767
|
-
const { dependency } = packageManager;
|
|
2768
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2769
|
-
return packageManager;
|
|
2770
|
-
}
|
|
2771
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2772
|
-
(file) => existsSync2(join9(root, file))
|
|
2773
|
-
);
|
|
2774
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2775
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2776
|
-
}
|
|
2777
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2778
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2779
|
-
if (!listing.entries) {
|
|
2780
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2781
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2782
|
-
}
|
|
2783
|
-
const suffix = manifest.slice(1);
|
|
2784
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2785
|
-
}
|
|
2786
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
2787
|
-
for (const manifest of profile.manifests) {
|
|
2788
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2789
|
-
}
|
|
2790
|
-
return false;
|
|
2791
|
-
}
|
|
2792
|
-
async function detectProfilesFromManifests(root) {
|
|
2793
|
-
const listing = {};
|
|
2794
|
-
const found = [];
|
|
2795
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2796
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2797
|
-
}
|
|
2798
|
-
return found;
|
|
2799
|
-
}
|
|
2800
|
-
async function hasProfileManifest(root, profile) {
|
|
2801
|
-
return profileManifestPresent(root, profile, {});
|
|
2802
|
-
}
|
|
2803
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2804
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2805
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2806
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2807
|
-
const candidates = [
|
|
2808
|
-
...new Map(
|
|
2809
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2810
|
-
).values()
|
|
2811
|
-
];
|
|
2812
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2813
|
-
}
|
|
2814
|
-
async function resolveToolchain(root, profile) {
|
|
2815
|
-
const signals = (pm) => [
|
|
2816
|
-
...pm.lockfiles ?? [],
|
|
2817
|
-
...pm.detectFiles ?? []
|
|
2818
|
-
];
|
|
2819
|
-
const matched = profile.packageManagers.find(
|
|
2820
|
-
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2821
|
-
);
|
|
2822
|
-
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2823
|
-
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2824
|
-
let { installSteps, ingest } = packageManager;
|
|
2825
|
-
installSteps = installSteps.map(
|
|
2826
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2827
|
-
);
|
|
2828
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2829
|
-
ingest = {
|
|
2830
|
-
...ingest,
|
|
2831
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2832
|
-
};
|
|
2833
|
-
}
|
|
2834
|
-
if (profile.id === "javascript") {
|
|
2835
|
-
const pm = await detectPackageManager(root);
|
|
2836
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2837
|
-
installSteps = installSteps.map((step) => ({
|
|
2838
|
-
...step,
|
|
2839
|
-
argv: withCommand(step.argv, pm)
|
|
2840
|
-
}));
|
|
2841
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2842
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
return { profile, packageManager, installSteps, ingest };
|
|
2847
|
-
}
|
|
2848
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2849
|
-
if (ingest.kind !== "auto") {
|
|
2850
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2851
|
-
}
|
|
2852
|
-
return ingest.argv.map(
|
|
2853
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2854
|
-
);
|
|
2855
|
-
}
|
|
2856
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2857
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2858
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2859
|
-
}
|
|
2860
|
-
function ingestScriptDir(profile) {
|
|
2861
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
2862
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2863
|
-
}
|
|
2864
|
-
function localSourceLimitation(root, profile) {
|
|
2865
|
-
const caveat = profile.localSourceCaveat;
|
|
2866
|
-
if (!caveat) return void 0;
|
|
2867
|
-
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2868
|
-
}
|
|
2869
|
-
async function missingBuildTask(root, toolchain) {
|
|
2870
|
-
const { ingest, packageManager } = toolchain;
|
|
2871
|
-
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2872
|
-
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2873
|
-
const buildFile = join9(root, packageManager.dependency.file);
|
|
2874
|
-
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2875
|
-
if (contents === void 0) return void 0;
|
|
2876
|
-
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2877
|
-
}
|
|
2878
|
-
function sdkVersionPin(profile, packageManager) {
|
|
2879
|
-
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2880
|
-
}
|
|
2881
|
-
function dependencyInstruction(toolchain) {
|
|
2882
|
-
const { profile, packageManager } = toolchain;
|
|
2883
|
-
const { packageName } = profile.sdk;
|
|
2884
|
-
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2885
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2886
|
-
switch (packageManager.dependency.mode) {
|
|
2887
|
-
case "wizard-installs":
|
|
2888
|
-
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}`;
|
|
2889
|
-
case "code-imports":
|
|
2890
|
-
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}`;
|
|
2891
|
-
case "agent-declares":
|
|
2892
|
-
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}`;
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// src/lib/tools/searchFiles.ts
|
|
2529
|
+
import z13 from "zod";
|
|
2530
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
2531
|
+
import { join as join7 } from "node:path";
|
|
2897
2532
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2898
2533
|
async function walkFiles(dir) {
|
|
2534
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2899
2535
|
const out = [];
|
|
2900
|
-
for (const e of await
|
|
2901
|
-
if (e.name.startsWith(".") ||
|
|
2902
|
-
const full =
|
|
2536
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2537
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2538
|
+
const full = join7(dir, e.name);
|
|
2903
2539
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2904
2540
|
else if (e.isFile()) out.push(full);
|
|
2905
2541
|
}
|
|
@@ -2908,9 +2544,9 @@ async function walkFiles(dir) {
|
|
|
2908
2544
|
function searchFilesTool(ctx) {
|
|
2909
2545
|
return tool7({
|
|
2910
2546
|
description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
|
|
2911
|
-
inputSchema:
|
|
2912
|
-
query:
|
|
2913
|
-
path:
|
|
2547
|
+
inputSchema: z13.object({
|
|
2548
|
+
query: z13.string().describe("JavaScript RegExp pattern to search for"),
|
|
2549
|
+
path: z13.string().optional().describe("Directory to search in (default: cwd)")
|
|
2914
2550
|
}),
|
|
2915
2551
|
execute: async ({ query, path = "." }) => {
|
|
2916
2552
|
logger.info({ query, path }, "called searchFiles tool");
|
|
@@ -2932,7 +2568,7 @@ function searchFilesTool(ctx) {
|
|
|
2932
2568
|
for (const file of await walkFiles(resolved.target)) {
|
|
2933
2569
|
let content;
|
|
2934
2570
|
try {
|
|
2935
|
-
content = await
|
|
2571
|
+
content = await readFile5(file, "utf8");
|
|
2936
2572
|
} catch {
|
|
2937
2573
|
continue;
|
|
2938
2574
|
}
|
|
@@ -2954,146 +2590,90 @@ function searchFilesTool(ctx) {
|
|
|
2954
2590
|
|
|
2955
2591
|
// src/lib/tools/verifyImplementation.ts
|
|
2956
2592
|
import { tool as tool8 } from "ai";
|
|
2957
|
-
import
|
|
2958
|
-
|
|
2959
|
-
// src/lib/tools/repoVerification.ts
|
|
2960
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2961
|
-
import { join as join11 } from "node:path";
|
|
2593
|
+
import z14 from "zod";
|
|
2962
2594
|
|
|
2963
2595
|
// src/lib/tools/utils/runCommand.ts
|
|
2964
2596
|
import { spawn as spawn2 } from "node:child_process";
|
|
2965
|
-
|
|
2966
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2967
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2968
|
-
var KILL_GRACE_MS = 5e3;
|
|
2969
|
-
function runCommand(command, args, options = {}) {
|
|
2970
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2597
|
+
function runCommand(command, args, cwd) {
|
|
2971
2598
|
return new Promise((resolve4) => {
|
|
2972
2599
|
let output = "";
|
|
2973
|
-
let settled = false;
|
|
2974
2600
|
const child = spawn2(command, args, {
|
|
2975
2601
|
cwd,
|
|
2976
|
-
|
|
2977
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2978
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2602
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2979
2603
|
});
|
|
2980
|
-
const settle = (result) => {
|
|
2981
|
-
if (settled) return;
|
|
2982
|
-
settled = true;
|
|
2983
|
-
clearTimeout(timer);
|
|
2984
|
-
resolve4(result);
|
|
2985
|
-
};
|
|
2986
|
-
const timer = setTimeout(() => {
|
|
2987
|
-
child.kill("SIGTERM");
|
|
2988
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2989
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2990
|
-
settle({
|
|
2991
|
-
code: 1,
|
|
2992
|
-
output: `${output}
|
|
2993
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2994
|
-
timedOut: true
|
|
2995
|
-
});
|
|
2996
|
-
}, timeoutMs);
|
|
2997
2604
|
child.stdout?.on("data", (d) => output += d);
|
|
2998
2605
|
child.stderr?.on("data", (d) => output += d);
|
|
2999
2606
|
child.on(
|
|
3000
2607
|
"error",
|
|
3001
|
-
(err) =>
|
|
3002
|
-
code: 1,
|
|
3003
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
3004
|
-
timedOut: false
|
|
3005
|
-
})
|
|
3006
|
-
);
|
|
3007
|
-
child.on(
|
|
3008
|
-
"close",
|
|
3009
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2608
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
3010
2609
|
);
|
|
2610
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
3011
2611
|
});
|
|
3012
2612
|
}
|
|
3013
2613
|
|
|
2614
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2615
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2616
|
+
import { existsSync } from "node:fs";
|
|
2617
|
+
import { join as join8 } from "node:path";
|
|
2618
|
+
var LOCKFILES = [
|
|
2619
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2620
|
+
["yarn.lock", "yarn"],
|
|
2621
|
+
["bun.lockb", "bun"],
|
|
2622
|
+
["bun.lock", "bun"],
|
|
2623
|
+
["package-lock.json", "npm"]
|
|
2624
|
+
];
|
|
2625
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2626
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2627
|
+
}
|
|
2628
|
+
function packageManagerFrom(pkg) {
|
|
2629
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2630
|
+
}
|
|
2631
|
+
function packageManagerFromLockfile(cwd) {
|
|
2632
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2633
|
+
}
|
|
2634
|
+
async function detectPackageManager(cwd) {
|
|
2635
|
+
try {
|
|
2636
|
+
const pkg = await readPackageJson(cwd);
|
|
2637
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2638
|
+
} catch {
|
|
2639
|
+
}
|
|
2640
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2641
|
+
}
|
|
2642
|
+
|
|
3014
2643
|
// src/lib/tools/repoVerification.ts
|
|
3015
2644
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
3016
|
-
async function
|
|
3017
|
-
const { code, output } = await runCommand(binary, args, {
|
|
3018
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
3019
|
-
});
|
|
3020
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
3021
|
-
}
|
|
3022
|
-
async function javascriptChecks() {
|
|
2645
|
+
async function runRepoVerificationCheck() {
|
|
3023
2646
|
let pkg;
|
|
3024
2647
|
try {
|
|
3025
2648
|
pkg = await readPackageJson();
|
|
3026
|
-
} catch (err) {
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
};
|
|
2649
|
+
} catch (err) {
|
|
2650
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2651
|
+
return { ok: false, checks: [], limitation };
|
|
3030
2652
|
}
|
|
3031
2653
|
const scripts = pkg.scripts ?? {};
|
|
3032
2654
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3033
2655
|
if (present.length === 0) {
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
};
|
|
2656
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2657
|
+
return { ok: false, checks: [], limitation };
|
|
3037
2658
|
}
|
|
3038
2659
|
const pm = await detectPackageManager(process.cwd());
|
|
3039
2660
|
const checks = [];
|
|
3040
2661
|
for (const script of present) {
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
);
|
|
3044
|
-
}
|
|
3045
|
-
return { checks };
|
|
3046
|
-
}
|
|
3047
|
-
async function registryChecks(id) {
|
|
3048
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
3049
|
-
const runnable = profile.verification.filter(
|
|
3050
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3051
|
-
);
|
|
3052
|
-
if (runnable.length === 0) {
|
|
3053
|
-
return {
|
|
3054
|
-
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
3055
|
-
};
|
|
3056
|
-
}
|
|
3057
|
-
const checks = [];
|
|
3058
|
-
for (const spec of runnable) {
|
|
3059
|
-
checks.push(
|
|
3060
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
3061
|
-
);
|
|
3062
|
-
}
|
|
3063
|
-
return { checks };
|
|
3064
|
-
}
|
|
3065
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3066
|
-
const ids = [...new Set(languages)];
|
|
3067
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3068
|
-
const checks = [];
|
|
3069
|
-
const limitations = [];
|
|
3070
|
-
for (const id of ids) {
|
|
3071
|
-
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
3072
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3073
|
-
else limitations.push(result.limitation);
|
|
3074
|
-
}
|
|
3075
|
-
if (checks.length === 0) {
|
|
3076
|
-
return {
|
|
3077
|
-
ok: false,
|
|
3078
|
-
checks: [],
|
|
3079
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3080
|
-
};
|
|
2662
|
+
const command = `${pm} run ${script}`;
|
|
2663
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2664
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3081
2665
|
}
|
|
3082
|
-
return {
|
|
3083
|
-
ok: checks.every((c) => c.ok),
|
|
3084
|
-
checks,
|
|
3085
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3086
|
-
};
|
|
2666
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3087
2667
|
}
|
|
3088
2668
|
|
|
3089
2669
|
// src/lib/tools/verifyImplementation.ts
|
|
3090
|
-
function verifyImplementationTool(
|
|
2670
|
+
function verifyImplementationTool() {
|
|
3091
2671
|
return tool8({
|
|
3092
|
-
description: "Run the repo's mechanical verification
|
|
3093
|
-
inputSchema:
|
|
2672
|
+
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.",
|
|
2673
|
+
inputSchema: z14.object(),
|
|
3094
2674
|
execute: async () => {
|
|
3095
|
-
logger.info(
|
|
3096
|
-
return runRepoVerificationCheck(
|
|
2675
|
+
logger.info("called verifyImplementation tool");
|
|
2676
|
+
return runRepoVerificationCheck();
|
|
3097
2677
|
}
|
|
3098
2678
|
});
|
|
3099
2679
|
}
|
|
@@ -3104,7 +2684,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
3104
2684
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3105
2685
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
3106
2686
|
import { dirname as dirname6 } from "node:path";
|
|
3107
|
-
import
|
|
2687
|
+
import z15 from "zod";
|
|
3108
2688
|
var DATA_DIR = ".algolia-wizard/data";
|
|
3109
2689
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
3110
2690
|
var MAX_RECORDS = 100;
|
|
@@ -3116,17 +2696,17 @@ var anthropic = createAnthropic({
|
|
|
3116
2696
|
function generateRecordTool(ctx) {
|
|
3117
2697
|
return tool9({
|
|
3118
2698
|
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.",
|
|
3119
|
-
inputSchema:
|
|
3120
|
-
entityName:
|
|
3121
|
-
attributes:
|
|
3122
|
-
count:
|
|
3123
|
-
hint:
|
|
2699
|
+
inputSchema: z15.object({
|
|
2700
|
+
entityName: z15.string().describe("Name of the entity to generate records for."),
|
|
2701
|
+
attributes: z15.array(z15.string()).describe("Attribute names each record must contain."),
|
|
2702
|
+
count: z15.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
|
|
2703
|
+
hint: z15.string().optional().describe("Optional context to steer realistic values.")
|
|
3124
2704
|
}),
|
|
3125
2705
|
execute: async ({ entityName, attributes, count, hint }) => {
|
|
3126
2706
|
logger.info({ entityName, count }, "called generateRecord tool");
|
|
3127
2707
|
try {
|
|
3128
|
-
const value =
|
|
3129
|
-
const recordSchema =
|
|
2708
|
+
const value = z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]);
|
|
2709
|
+
const recordSchema = z15.object(
|
|
3130
2710
|
Object.fromEntries(attributes.map((attr) => [attr, value]))
|
|
3131
2711
|
);
|
|
3132
2712
|
const generateBatch = async (batchCount) => {
|
|
@@ -3136,8 +2716,8 @@ function generateRecordTool(ctx) {
|
|
|
3136
2716
|
const { output } = await generateText({
|
|
3137
2717
|
model: anthropic(RECORD_MODEL),
|
|
3138
2718
|
output: Output.object({
|
|
3139
|
-
schema:
|
|
3140
|
-
records:
|
|
2719
|
+
schema: z15.object({
|
|
2720
|
+
records: z15.array(recordSchema).length(batchCount)
|
|
3141
2721
|
})
|
|
3142
2722
|
}),
|
|
3143
2723
|
prompt: [
|
|
@@ -3195,12 +2775,12 @@ function generateRecordTool(ctx) {
|
|
|
3195
2775
|
|
|
3196
2776
|
// src/lib/tools/notifyUser.ts
|
|
3197
2777
|
import { tool as tool10 } from "ai";
|
|
3198
|
-
import
|
|
2778
|
+
import z16 from "zod";
|
|
3199
2779
|
function notifyUserTool() {
|
|
3200
2780
|
return tool10({
|
|
3201
2781
|
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.`,
|
|
3202
|
-
inputSchema:
|
|
3203
|
-
message:
|
|
2782
|
+
inputSchema: z16.object({
|
|
2783
|
+
message: z16.string().describe(
|
|
3204
2784
|
"Short, plain-language description of what you are doing now."
|
|
3205
2785
|
)
|
|
3206
2786
|
}),
|
|
@@ -3219,17 +2799,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3219
2799
|
read: 20,
|
|
3220
2800
|
match: 100
|
|
3221
2801
|
};
|
|
3222
|
-
function createToolContext({
|
|
3223
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3224
|
-
cwd = process.cwd(),
|
|
3225
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3226
|
-
} = {}) {
|
|
2802
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3227
2803
|
return {
|
|
3228
2804
|
root: cwd,
|
|
3229
2805
|
cwd,
|
|
3230
2806
|
limits,
|
|
3231
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3232
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2807
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3233
2808
|
};
|
|
3234
2809
|
}
|
|
3235
2810
|
|
|
@@ -3266,7 +2841,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3266
2841
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3267
2842
|
verifyImplementation: withLogging(
|
|
3268
2843
|
"verifyImplementation",
|
|
3269
|
-
verifyImplementationTool(
|
|
2844
|
+
verifyImplementationTool()
|
|
3270
2845
|
),
|
|
3271
2846
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3272
2847
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3302,7 +2877,7 @@ async function runAgent(req) {
|
|
|
3302
2877
|
baseURL: PROXY_BASE_URL,
|
|
3303
2878
|
fetch: proxyFetch
|
|
3304
2879
|
});
|
|
3305
|
-
const toolContext = createToolContext(
|
|
2880
|
+
const toolContext = createToolContext();
|
|
3306
2881
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3307
2882
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3308
2883
|
const instructions = [
|
|
@@ -3385,19 +2960,16 @@ async function runAgent(req) {
|
|
|
3385
2960
|
}
|
|
3386
2961
|
|
|
3387
2962
|
// src/actions/detectLanguage.ts
|
|
3388
|
-
import
|
|
3389
|
-
var detectLanguageSchema =
|
|
3390
|
-
languages:
|
|
3391
|
-
frameworks:
|
|
2963
|
+
import z19 from "zod";
|
|
2964
|
+
var detectLanguageSchema = z19.object({
|
|
2965
|
+
languages: z19.array(z19.object({ name: z19.string(), version: z19.string() })),
|
|
2966
|
+
frameworks: z19.array(z19.object({ name: z19.string(), version: z19.string() }))
|
|
3392
2967
|
});
|
|
3393
2968
|
var detectLanguage = () => runAgent({
|
|
3394
2969
|
instructions: [
|
|
3395
2970
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3396
|
-
"
|
|
3397
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3398
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2971
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3399
2972
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3400
|
-
"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).",
|
|
3401
2973
|
"Return the exact version",
|
|
3402
2974
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3403
2975
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3409,31 +2981,31 @@ var detectLanguage = () => runAgent({
|
|
|
3409
2981
|
});
|
|
3410
2982
|
|
|
3411
2983
|
// src/actions/analyzeCodebase.ts
|
|
3412
|
-
import
|
|
2984
|
+
import z20 from "zod";
|
|
3413
2985
|
var READONLY_TOOLS = [
|
|
3414
2986
|
"listFiles",
|
|
3415
2987
|
"changeDirectory",
|
|
3416
2988
|
"readFile",
|
|
3417
2989
|
"searchFiles"
|
|
3418
2990
|
];
|
|
3419
|
-
var ingestionAnalysisSchema =
|
|
3420
|
-
ingestionAnalysis:
|
|
3421
|
-
|
|
3422
|
-
name:
|
|
3423
|
-
paths:
|
|
2991
|
+
var ingestionAnalysisSchema = z20.object({
|
|
2992
|
+
ingestionAnalysis: z20.array(
|
|
2993
|
+
z20.object({
|
|
2994
|
+
name: z20.string(),
|
|
2995
|
+
paths: z20.array(z20.string()),
|
|
3424
2996
|
// indexable fields the agent found for this entity
|
|
3425
|
-
attributes:
|
|
2997
|
+
attributes: z20.array(z20.string())
|
|
3426
2998
|
})
|
|
3427
2999
|
)
|
|
3428
3000
|
});
|
|
3429
|
-
var searchImplementationAnalysisSchema =
|
|
3430
|
-
searchImplementationAnalysis:
|
|
3001
|
+
var searchImplementationAnalysisSchema = z20.object({
|
|
3002
|
+
searchImplementationAnalysis: z20.string()
|
|
3431
3003
|
});
|
|
3432
|
-
var verificationSchema =
|
|
3433
|
-
verification:
|
|
3004
|
+
var verificationSchema = z20.object({
|
|
3005
|
+
verification: z20.array(z20.string())
|
|
3434
3006
|
});
|
|
3435
3007
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
3436
|
-
var analyzeCodebaseSchema =
|
|
3008
|
+
var analyzeCodebaseSchema = z20.object({
|
|
3437
3009
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3438
3010
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
3439
3011
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -3446,7 +3018,6 @@ var MODE_CONFIG = {
|
|
|
3446
3018
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3447
3019
|
"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).",
|
|
3448
3020
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3449
|
-
"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.",
|
|
3450
3021
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3451
3022
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3452
3023
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3458,9 +3029,8 @@ var MODE_CONFIG = {
|
|
|
3458
3029
|
instructions: [
|
|
3459
3030
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3460
3031
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3461
|
-
"
|
|
3462
|
-
|
|
3463
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
3032
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
3033
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3464
3034
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3465
3035
|
"When done, call reportStatus"
|
|
3466
3036
|
],
|
|
@@ -3469,8 +3039,8 @@ var MODE_CONFIG = {
|
|
|
3469
3039
|
verification: {
|
|
3470
3040
|
instructions: [
|
|
3471
3041
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3472
|
-
"Look at
|
|
3473
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
3042
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
3043
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3474
3044
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3475
3045
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3476
3046
|
"When done, call reportStatus"
|
|
@@ -3497,7 +3067,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3497
3067
|
// package.json
|
|
3498
3068
|
var package_default = {
|
|
3499
3069
|
name: "@algolia/wizard",
|
|
3500
|
-
version: "0.
|
|
3070
|
+
version: "0.9.0-rc.53.57",
|
|
3501
3071
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3502
3072
|
type: "module",
|
|
3503
3073
|
engines: {
|
|
@@ -3519,7 +3089,7 @@ var package_default = {
|
|
|
3519
3089
|
prepare: "husky",
|
|
3520
3090
|
prepublishOnly: "pnpm build",
|
|
3521
3091
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3522
|
-
"test:
|
|
3092
|
+
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3523
3093
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3524
3094
|
test: "vitest",
|
|
3525
3095
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -3545,7 +3115,6 @@ var package_default = {
|
|
|
3545
3115
|
dependencies: {
|
|
3546
3116
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
3547
3117
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
3548
|
-
"@algolia/cli": "^5.11.0",
|
|
3549
3118
|
"@hono/node-server": "^2.0.10",
|
|
3550
3119
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
3551
3120
|
"@segment/analytics-node": "^3.1.0",
|
|
@@ -3560,7 +3129,6 @@ var package_default = {
|
|
|
3560
3129
|
nanoid: "^5.1.15",
|
|
3561
3130
|
pino: "^10.3.1",
|
|
3562
3131
|
react: "^19.2.7",
|
|
3563
|
-
toml: "^4.1.1",
|
|
3564
3132
|
varlock: "^1.5.1",
|
|
3565
3133
|
zod: "^4.4.3",
|
|
3566
3134
|
zustand: "^5.0.14"
|
|
@@ -3600,185 +3168,82 @@ function parseEntries(raw) {
|
|
|
3600
3168
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3601
3169
|
}
|
|
3602
3170
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3603
|
-
|
|
3604
|
-
// src/actions/confirmLanguage.ts
|
|
3605
|
-
import z19 from "zod";
|
|
3606
|
-
var confirmLanguageSchema = z19.object({
|
|
3607
|
-
languages: detectLanguageSchema.shape.languages
|
|
3608
|
-
});
|
|
3609
|
-
var OTHER_OPTION = "Other";
|
|
3610
|
-
function confirmed(languages) {
|
|
3611
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3612
|
-
return { languages };
|
|
3613
|
-
}
|
|
3614
|
-
async function askOtherLanguage(ctx) {
|
|
3615
|
-
let prompt = "enter the language for your ingestion script";
|
|
3171
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3616
3172
|
for (; ; ) {
|
|
3617
3173
|
const answer = await ctx.requestUserInput({
|
|
3618
3174
|
prompt,
|
|
3619
3175
|
promptType: "textInput",
|
|
3620
|
-
options: []
|
|
3176
|
+
options: [],
|
|
3177
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3621
3178
|
});
|
|
3622
3179
|
if (typeof answer !== "string") {
|
|
3623
|
-
throw new Error("
|
|
3180
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3624
3181
|
}
|
|
3625
|
-
const
|
|
3626
|
-
if (
|
|
3627
|
-
prompt = "
|
|
3182
|
+
const entries = parseEntries(answer);
|
|
3183
|
+
if (entries.length || !required) return entries;
|
|
3184
|
+
prompt = "Please enter at least one entry:";
|
|
3628
3185
|
}
|
|
3629
3186
|
}
|
|
3187
|
+
|
|
3188
|
+
// src/actions/confirmLanguage.ts
|
|
3189
|
+
import z22 from "zod";
|
|
3190
|
+
var confirmLanguageSchema = z22.object({
|
|
3191
|
+
languages: detectLanguageSchema.shape.languages
|
|
3192
|
+
});
|
|
3630
3193
|
async function confirmLanguage(ctx) {
|
|
3631
3194
|
const detected = ctx.getStepOutput("project-scan");
|
|
3632
|
-
const
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3638
|
-
promptType: "acceptReject",
|
|
3639
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3640
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3641
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3642
|
-
});
|
|
3643
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3644
|
-
}
|
|
3645
|
-
const options = [...CURATED_LANGUAGES];
|
|
3646
|
-
for (const language of detectedLanguages) {
|
|
3647
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3648
|
-
options.push(language.name);
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
options.push(OTHER_OPTION);
|
|
3652
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3653
|
-
const secondary = options.map(
|
|
3654
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3655
|
-
);
|
|
3656
|
-
const defaultSelectedIndex = Math.max(
|
|
3657
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3658
|
-
0
|
|
3659
|
-
);
|
|
3660
|
-
const selection = await ctx.requestUserInput({
|
|
3661
|
-
prompt: "select the language for your ingestion script",
|
|
3662
|
-
promptType: "multipleChoice",
|
|
3663
|
-
options,
|
|
3664
|
-
secondary,
|
|
3665
|
-
defaultSelectedIndex
|
|
3195
|
+
const answer = await ctx.requestUserInput({
|
|
3196
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
3197
|
+
promptType: "acceptReject",
|
|
3198
|
+
options: ["Yes", "No"],
|
|
3199
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3666
3200
|
});
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3201
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
3202
|
+
required: true
|
|
3203
|
+
});
|
|
3204
|
+
track("AI Wizard Language Confirmed", {
|
|
3205
|
+
languages
|
|
3206
|
+
});
|
|
3207
|
+
return { languages };
|
|
3673
3208
|
}
|
|
3674
3209
|
|
|
3675
3210
|
// src/actions/confirmFramework.ts
|
|
3676
|
-
import
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
var
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3688
|
-
{
|
|
3689
|
-
name: "Vanilla JS",
|
|
3690
|
-
strategy: "js",
|
|
3691
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3692
|
-
},
|
|
3693
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3694
|
-
// templates get InstantSearch.js from a CDN.
|
|
3695
|
-
{
|
|
3696
|
-
name: "Rails",
|
|
3697
|
-
strategy: "cdn-template",
|
|
3698
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3699
|
-
},
|
|
3700
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3701
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3702
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3703
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3704
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3705
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3706
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3707
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3708
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3211
|
+
import z23 from "zod";
|
|
3212
|
+
var confirmFrameworkSchema = z23.object({
|
|
3213
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3214
|
+
});
|
|
3215
|
+
var CURATED_FRAMEWORKS = [
|
|
3216
|
+
"Next.js",
|
|
3217
|
+
"React",
|
|
3218
|
+
"Vue",
|
|
3219
|
+
"Angular",
|
|
3220
|
+
"Svelte",
|
|
3221
|
+
"Vanilla JS"
|
|
3709
3222
|
];
|
|
3710
|
-
var
|
|
3711
|
-
(f) => f.name
|
|
3712
|
-
);
|
|
3223
|
+
var OTHER_OPTION = "Other";
|
|
3713
3224
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3714
|
-
var
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3225
|
+
var FRAMEWORK_ALIASES = {
|
|
3226
|
+
next: "nextjs",
|
|
3227
|
+
nextjs: "nextjs",
|
|
3228
|
+
react: "react",
|
|
3229
|
+
reactjs: "react",
|
|
3230
|
+
vue: "vue",
|
|
3231
|
+
vuejs: "vue",
|
|
3232
|
+
angular: "angular",
|
|
3233
|
+
angularjs: "angular",
|
|
3234
|
+
svelte: "svelte",
|
|
3235
|
+
sveltekit: "svelte",
|
|
3236
|
+
vanillajs: "vanillajs",
|
|
3237
|
+
vanilla: "vanillajs",
|
|
3238
|
+
javascript: "vanillajs",
|
|
3239
|
+
js: "vanillajs"
|
|
3240
|
+
};
|
|
3241
|
+
var isSameFramework = (a, b) => {
|
|
3242
|
+
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
3243
|
+
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3727
3244
|
return x !== "" && x === y;
|
|
3728
|
-
}
|
|
3729
|
-
function
|
|
3730
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3731
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3732
|
-
if (strategy) return strategy;
|
|
3733
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3734
|
-
}
|
|
3735
|
-
function searchDocKey(strategy) {
|
|
3736
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3737
|
-
}
|
|
3738
|
-
function bundlesJavaScript(strategy) {
|
|
3739
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3740
|
-
}
|
|
3741
|
-
function canScaffoldSearchUI(strategy) {
|
|
3742
|
-
return strategy !== "none";
|
|
3743
|
-
}
|
|
3744
|
-
var ENV_PREFIXES = [
|
|
3745
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3746
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3747
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3748
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3749
|
-
];
|
|
3750
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3751
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3752
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3753
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3754
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3755
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3756
|
-
}
|
|
3757
|
-
return DEFAULT_ENV_PREFIX;
|
|
3758
|
-
}
|
|
3759
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3760
|
-
switch (strategy) {
|
|
3761
|
-
case "react":
|
|
3762
|
-
return "React (react-instantsearch)";
|
|
3763
|
-
case "vue":
|
|
3764
|
-
return "Vue (vue-instantsearch)";
|
|
3765
|
-
case "angular":
|
|
3766
|
-
return "Angular (angular-instantsearch)";
|
|
3767
|
-
case "js":
|
|
3768
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3769
|
-
case "cdn-template":
|
|
3770
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3771
|
-
case "none":
|
|
3772
|
-
return frameworkName ?? "a native mobile app";
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
|
|
3776
|
-
// src/actions/confirmFramework.ts
|
|
3777
|
-
var confirmFrameworkSchema = z20.object({
|
|
3778
|
-
frameworks: detectLanguageSchema.shape.frameworks
|
|
3779
|
-
});
|
|
3780
|
-
var OTHER_OPTION2 = "Other";
|
|
3781
|
-
function confirmed2(name, version) {
|
|
3245
|
+
};
|
|
3246
|
+
function confirmed(name, version) {
|
|
3782
3247
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3783
3248
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3784
3249
|
return { frameworks };
|
|
@@ -3806,7 +3271,7 @@ async function confirmFramework(ctx) {
|
|
|
3806
3271
|
for (const fw of detectedFrameworks) {
|
|
3807
3272
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3808
3273
|
}
|
|
3809
|
-
options.push(
|
|
3274
|
+
options.push(OTHER_OPTION);
|
|
3810
3275
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3811
3276
|
const primary = detectedFrameworks[0];
|
|
3812
3277
|
if (primary) {
|
|
@@ -3816,7 +3281,7 @@ async function confirmFramework(ctx) {
|
|
|
3816
3281
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3817
3282
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3818
3283
|
});
|
|
3819
|
-
if (accepted === true) return
|
|
3284
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3820
3285
|
}
|
|
3821
3286
|
const secondary = options.map(
|
|
3822
3287
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3826,7 +3291,7 @@ async function confirmFramework(ctx) {
|
|
|
3826
3291
|
0
|
|
3827
3292
|
);
|
|
3828
3293
|
const selection = await ctx.requestUserInput({
|
|
3829
|
-
prompt: "select
|
|
3294
|
+
prompt: "select a framework",
|
|
3830
3295
|
promptType: "multipleChoice",
|
|
3831
3296
|
options,
|
|
3832
3297
|
secondary,
|
|
@@ -3835,10 +3300,10 @@ async function confirmFramework(ctx) {
|
|
|
3835
3300
|
if (typeof selection !== "string") {
|
|
3836
3301
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3837
3302
|
}
|
|
3838
|
-
if (selection ===
|
|
3839
|
-
return
|
|
3303
|
+
if (selection === OTHER_OPTION) {
|
|
3304
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3840
3305
|
}
|
|
3841
|
-
return
|
|
3306
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3842
3307
|
}
|
|
3843
3308
|
|
|
3844
3309
|
// src/actions/promptUser.ts
|
|
@@ -3872,8 +3337,8 @@ async function promptUser(ctx, params) {
|
|
|
3872
3337
|
}
|
|
3873
3338
|
|
|
3874
3339
|
// src/actions/confirmEntities.ts
|
|
3875
|
-
import
|
|
3876
|
-
var confirmEntitiesSchema =
|
|
3340
|
+
import z24 from "zod";
|
|
3341
|
+
var confirmEntitiesSchema = z24.object({
|
|
3877
3342
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
3878
3343
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
3879
3344
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -3931,27 +3396,27 @@ async function confirmEntities(ctx) {
|
|
|
3931
3396
|
onSubmit: () => {
|
|
3932
3397
|
}
|
|
3933
3398
|
});
|
|
3934
|
-
const
|
|
3935
|
-
if (
|
|
3399
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3400
|
+
if (confirmed2.length === 0) {
|
|
3936
3401
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3937
3402
|
}
|
|
3938
|
-
ctx.setUserInput("confirmedEntities",
|
|
3403
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3939
3404
|
track("AI Wizard Entities Confirmed", {
|
|
3940
|
-
entities: toEntitySummary(
|
|
3405
|
+
entities: toEntitySummary(confirmed2)
|
|
3941
3406
|
});
|
|
3942
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3407
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3943
3408
|
}
|
|
3944
3409
|
|
|
3945
3410
|
// src/actions/review.ts
|
|
3946
|
-
import { z as
|
|
3947
|
-
var reviewSchema =
|
|
3411
|
+
import { z as z25 } from "zod";
|
|
3412
|
+
var reviewSchema = z25.object({
|
|
3948
3413
|
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
3949
3414
|
// not one entry per workflow step — a step's raw output can be a long,
|
|
3950
3415
|
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
3951
3416
|
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
3952
|
-
summaryPoints:
|
|
3953
|
-
reviewPrompt:
|
|
3954
|
-
nextSteps:
|
|
3417
|
+
summaryPoints: z25.array(z25.string()),
|
|
3418
|
+
reviewPrompt: z25.string(),
|
|
3419
|
+
nextSteps: z25.array(z25.string())
|
|
3955
3420
|
});
|
|
3956
3421
|
function formatCompletedSteps(steps) {
|
|
3957
3422
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -3963,7 +3428,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3963
3428
|
}
|
|
3964
3429
|
function formatReviewSummary(result) {
|
|
3965
3430
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3966
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3431
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3967
3432
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3968
3433
|
return {
|
|
3969
3434
|
text: `\u2192 ${step}`,
|
|
@@ -4002,17 +3467,16 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4002
3467
|
};
|
|
4003
3468
|
|
|
4004
3469
|
// src/actions/implement.ts
|
|
4005
|
-
import
|
|
3470
|
+
import z26 from "zod";
|
|
4006
3471
|
|
|
4007
3472
|
// src/lib/worktree.ts
|
|
4008
|
-
import { execFile } from "node:child_process";
|
|
4009
|
-
import {
|
|
4010
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3473
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3474
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
4011
3475
|
import {
|
|
4012
3476
|
basename as basename2,
|
|
4013
3477
|
dirname as dirname7,
|
|
4014
3478
|
isAbsolute as isAbsolute2,
|
|
4015
|
-
join as
|
|
3479
|
+
join as join9,
|
|
4016
3480
|
relative as relative2,
|
|
4017
3481
|
resolve as resolve3
|
|
4018
3482
|
} from "node:path";
|
|
@@ -4046,8 +3510,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4046
3510
|
return out.trim().length > 0;
|
|
4047
3511
|
}
|
|
4048
3512
|
async function pruneOldWorktrees(repoRoot) {
|
|
4049
|
-
const dir =
|
|
4050
|
-
const stale = (await
|
|
3513
|
+
const dir = join9(stateDir(repoRoot), "worktrees");
|
|
3514
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4051
3515
|
for (const slug of stale) {
|
|
4052
3516
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4053
3517
|
try {
|
|
@@ -4057,7 +3521,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4057
3521
|
"worktree",
|
|
4058
3522
|
"remove",
|
|
4059
3523
|
"--force",
|
|
4060
|
-
|
|
3524
|
+
join9(dir, slug)
|
|
4061
3525
|
]);
|
|
4062
3526
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4063
3527
|
} catch (err) {
|
|
@@ -4071,55 +3535,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4071
3535
|
async function createWorktree(repoRoot) {
|
|
4072
3536
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4073
3537
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4074
|
-
const path =
|
|
3538
|
+
const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4075
3539
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4076
3540
|
await pruneOldWorktrees(repoRoot);
|
|
4077
3541
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4078
3542
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4079
3543
|
return { path, branch };
|
|
4080
3544
|
}
|
|
4081
|
-
async function
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
return { ok: code === 0, output: output.trim() };
|
|
4087
|
-
}
|
|
4088
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4089
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
4090
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4091
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4092
|
-
if (!haveSomethingToInstall) {
|
|
4093
|
-
return {
|
|
4094
|
-
ok: true,
|
|
4095
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4096
|
-
};
|
|
4097
|
-
}
|
|
4098
|
-
if (installSteps.length === 0) {
|
|
4099
|
-
return {
|
|
4100
|
-
ok: true,
|
|
4101
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
const outputs = [];
|
|
4105
|
-
for (const step of installSteps) {
|
|
4106
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4107
|
-
continue;
|
|
4108
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4109
|
-
if (result.output) outputs.push(result.output);
|
|
4110
|
-
if (result.ok) continue;
|
|
4111
|
-
if (step.optional) {
|
|
4112
|
-
logger.warn(
|
|
4113
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4114
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4115
|
-
);
|
|
4116
|
-
continue;
|
|
4117
|
-
}
|
|
4118
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3545
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3546
|
+
try {
|
|
3547
|
+
await readPackageJson(worktreePath);
|
|
3548
|
+
} catch {
|
|
3549
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4119
3550
|
}
|
|
4120
|
-
|
|
3551
|
+
const pm = await detectPackageManager(worktreePath);
|
|
3552
|
+
return new Promise((resolve4) => {
|
|
3553
|
+
let output = "";
|
|
3554
|
+
const child = spawn3(pm, ["install"], {
|
|
3555
|
+
cwd: worktreePath,
|
|
3556
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3557
|
+
});
|
|
3558
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3559
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3560
|
+
child.on(
|
|
3561
|
+
"error",
|
|
3562
|
+
(err) => resolve4({
|
|
3563
|
+
ok: false,
|
|
3564
|
+
output: `Failed to run ${pm} install: ${err.message}`
|
|
3565
|
+
})
|
|
3566
|
+
);
|
|
3567
|
+
child.on(
|
|
3568
|
+
"close",
|
|
3569
|
+
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3570
|
+
);
|
|
3571
|
+
});
|
|
4121
3572
|
}
|
|
4122
|
-
|
|
3573
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3574
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4123
3575
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4124
3576
|
return {
|
|
4125
3577
|
ok: false,
|
|
@@ -4134,29 +3586,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4134
3586
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4135
3587
|
};
|
|
4136
3588
|
}
|
|
4137
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4138
|
-
return {
|
|
4139
|
-
ok: false,
|
|
4140
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4141
|
-
};
|
|
4142
|
-
}
|
|
4143
3589
|
return { ok: true, target };
|
|
4144
3590
|
}
|
|
4145
|
-
async function runIngestScript(worktreePath,
|
|
4146
|
-
|
|
4147
|
-
if (ingest.kind !== "auto") {
|
|
3591
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3592
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4148
3593
|
return {
|
|
4149
3594
|
ran: false,
|
|
4150
3595
|
ok: false,
|
|
4151
3596
|
output: "",
|
|
4152
|
-
reason:
|
|
3597
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4153
3598
|
};
|
|
4154
3599
|
}
|
|
4155
|
-
const validated = validateIngestEntrypoint(
|
|
4156
|
-
worktreePath,
|
|
4157
|
-
entrypoint,
|
|
4158
|
-
ingest.entrypointExtensions
|
|
4159
|
-
);
|
|
3600
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4160
3601
|
if (!validated.ok) {
|
|
4161
3602
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4162
3603
|
}
|
|
@@ -4177,13 +3618,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4177
3618
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4178
3619
|
};
|
|
4179
3620
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
3621
|
+
return new Promise((resolveRun) => {
|
|
3622
|
+
let output = "";
|
|
3623
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3624
|
+
cwd: worktreePath,
|
|
3625
|
+
shell: false,
|
|
3626
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3627
|
+
env: { ...process.env, ...env }
|
|
3628
|
+
});
|
|
3629
|
+
child.stdout?.on("data", (d) => output += d);
|
|
3630
|
+
child.stderr?.on("data", (d) => output += d);
|
|
3631
|
+
child.on(
|
|
3632
|
+
"error",
|
|
3633
|
+
(err) => resolveRun({
|
|
3634
|
+
ran: true,
|
|
3635
|
+
ok: false,
|
|
3636
|
+
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3637
|
+
})
|
|
3638
|
+
);
|
|
3639
|
+
child.on(
|
|
3640
|
+
"close",
|
|
3641
|
+
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3642
|
+
);
|
|
4185
3643
|
});
|
|
4186
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4187
3644
|
}
|
|
4188
3645
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4189
3646
|
const trimmed = sourcePath.trim();
|
|
@@ -4198,8 +3655,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4198
3655
|
} catch {
|
|
4199
3656
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4200
3657
|
}
|
|
4201
|
-
const relPath =
|
|
4202
|
-
const dest =
|
|
3658
|
+
const relPath = join9(ingestDir, basename2(source));
|
|
3659
|
+
const dest = join9(worktreePath, relPath);
|
|
4203
3660
|
try {
|
|
4204
3661
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4205
3662
|
await copyFile(source, dest);
|
|
@@ -4215,10 +3672,10 @@ function hasEnvVar(content, name) {
|
|
|
4215
3672
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4216
3673
|
}
|
|
4217
3674
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4218
|
-
const target =
|
|
3675
|
+
const target = join9(worktreePath, ".env");
|
|
4219
3676
|
let existing = "";
|
|
4220
3677
|
try {
|
|
4221
|
-
existing = await
|
|
3678
|
+
existing = await readFile7(target, "utf8");
|
|
4222
3679
|
} catch (err) {
|
|
4223
3680
|
if (err.code !== "ENOENT") throw err;
|
|
4224
3681
|
}
|
|
@@ -4286,135 +3743,146 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
4286
3743
|
}
|
|
4287
3744
|
}
|
|
4288
3745
|
|
|
4289
|
-
// src/lib/algoliaApiKey.ts
|
|
4290
|
-
import { z as z23 } from "zod";
|
|
4291
|
-
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
4292
|
-
var apiKeySchema = z23.object({
|
|
4293
|
-
value: z23.string().min(1),
|
|
4294
|
-
acl: z23.array(z23.string()).default([]),
|
|
4295
|
-
indexes: z23.array(z23.string()).default([])
|
|
4296
|
-
});
|
|
4297
|
-
var apiKeyListSchema = z23.object({
|
|
4298
|
-
items: z23.array(apiKeySchema).optional(),
|
|
4299
|
-
keys: z23.array(apiKeySchema).optional()
|
|
4300
|
-
}).transform((o) => o.items ?? o.keys ?? []);
|
|
4301
|
-
var createdKeySchema = z23.object({
|
|
4302
|
-
key: z23.string().min(1).optional(),
|
|
4303
|
-
value: z23.string().min(1).optional()
|
|
4304
|
-
});
|
|
4305
|
-
function canReuse(key, index) {
|
|
4306
|
-
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
4307
|
-
}
|
|
4308
|
-
async function createSearchKey(index) {
|
|
4309
|
-
const stdout = await runAlgoliaCli([
|
|
4310
|
-
"apikeys",
|
|
4311
|
-
"create",
|
|
4312
|
-
"--indices",
|
|
4313
|
-
index,
|
|
4314
|
-
"--acl",
|
|
4315
|
-
"search,browse",
|
|
4316
|
-
"--description",
|
|
4317
|
-
`wizard search-only key for ${index}`,
|
|
4318
|
-
"-o",
|
|
4319
|
-
"json"
|
|
4320
|
-
]);
|
|
4321
|
-
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
4322
|
-
const created = key ?? value;
|
|
4323
|
-
if (!created) throw new Error("apikeys create returned no key value");
|
|
4324
|
-
return created;
|
|
4325
|
-
}
|
|
4326
|
-
async function resolveSearchOnlyKey(index) {
|
|
4327
|
-
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
4328
|
-
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
4329
|
-
if (existing) {
|
|
4330
|
-
logger.info({ index }, "reusing existing search-only API key");
|
|
4331
|
-
return existing;
|
|
4332
|
-
}
|
|
4333
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
4334
|
-
return createSearchKey(index);
|
|
4335
|
-
}
|
|
4336
|
-
|
|
4337
3746
|
// src/lib/algoliaDocs.ts
|
|
4338
|
-
import { readFileSync, existsSync as
|
|
4339
|
-
import { dirname as dirname8, join as
|
|
3747
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3748
|
+
import { dirname as dirname8, join as join10 } from "node:path";
|
|
4340
3749
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4341
|
-
var DOCS_SUBPATH =
|
|
3750
|
+
var DOCS_SUBPATH = join10("docs", "algolia-sdk");
|
|
4342
3751
|
function findDocsDir() {
|
|
4343
3752
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4344
3753
|
for (; ; ) {
|
|
4345
|
-
const candidate =
|
|
4346
|
-
if (
|
|
3754
|
+
const candidate = join10(dir, DOCS_SUBPATH);
|
|
3755
|
+
if (existsSync2(candidate)) return candidate;
|
|
4347
3756
|
const parent = dirname8(dir);
|
|
4348
3757
|
if (parent === dir) return void 0;
|
|
4349
3758
|
dir = parent;
|
|
4350
3759
|
}
|
|
4351
3760
|
}
|
|
4352
|
-
function
|
|
3761
|
+
function loadAlgoliaDoc(language) {
|
|
3762
|
+
const docsDir = findDocsDir();
|
|
3763
|
+
if (!docsDir) {
|
|
3764
|
+
logger.warn(
|
|
3765
|
+
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3766
|
+
);
|
|
3767
|
+
return "";
|
|
3768
|
+
}
|
|
3769
|
+
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3770
|
+
if (files.length === 0) {
|
|
3771
|
+
logger.warn(
|
|
3772
|
+
{ language },
|
|
3773
|
+
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3774
|
+
);
|
|
3775
|
+
return "";
|
|
3776
|
+
}
|
|
3777
|
+
return readFileSync(join10(docsDir, files[0]), "utf8").trim();
|
|
3778
|
+
}
|
|
3779
|
+
function getNamedDoc(name, language) {
|
|
4353
3780
|
const docsDir = findDocsDir();
|
|
4354
3781
|
if (!docsDir) {
|
|
4355
3782
|
logger.warn("docs/algolia-sdk not found");
|
|
4356
3783
|
return "";
|
|
4357
3784
|
}
|
|
4358
|
-
const file =
|
|
4359
|
-
if (!
|
|
4360
|
-
logger.warn({ name,
|
|
3785
|
+
const file = join10(docsDir, `${name}-${language}.md`);
|
|
3786
|
+
if (!existsSync2(file)) {
|
|
3787
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4361
3788
|
return "";
|
|
4362
3789
|
}
|
|
4363
3790
|
return readFileSync(file, "utf8").trim();
|
|
4364
3791
|
}
|
|
3792
|
+
function getFrameworkSpecificDoc(frameworks) {
|
|
3793
|
+
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3794
|
+
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3795
|
+
return loadAlgoliaDoc("vue");
|
|
3796
|
+
}
|
|
3797
|
+
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3798
|
+
return loadAlgoliaDoc("react");
|
|
3799
|
+
}
|
|
3800
|
+
if (fw.includes("angular")) {
|
|
3801
|
+
return loadAlgoliaDoc("angular");
|
|
3802
|
+
}
|
|
3803
|
+
return loadAlgoliaDoc("js");
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
// src/lib/shell.ts
|
|
3807
|
+
function shellQuote(value) {
|
|
3808
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3809
|
+
}
|
|
4365
3810
|
|
|
4366
3811
|
// src/actions/implement.ts
|
|
4367
|
-
var implementSchema =
|
|
4368
|
-
filesChanged:
|
|
4369
|
-
summary:
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
//
|
|
4377
|
-
//
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
ingestRecordCount: z24.number().optional(),
|
|
4383
|
-
// Wall-clock duration of the run-now ingestion execution, in ms.
|
|
4384
|
-
ingestDurationMs: z24.number().optional(),
|
|
4385
|
-
ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
|
|
4386
|
-
// Suggested names/values, built from framework detection. The search agent is
|
|
4387
|
-
// instructed to rename the prefix if it doesn't match the project's build
|
|
4388
|
-
// tool, so the names it actually wrote can differ — treat these as hints, not
|
|
4389
|
-
// ground truth (the agent's summary carries the final names).
|
|
4390
|
-
searchEnvVars: z24.array(
|
|
4391
|
-
z24.object({
|
|
4392
|
-
name: z24.string(),
|
|
4393
|
-
value: z24.string()
|
|
3812
|
+
var implementSchema = z26.object({
|
|
3813
|
+
filesChanged: z26.array(z26.string()),
|
|
3814
|
+
summary: z26.string(),
|
|
3815
|
+
worktreePath: z26.string().optional(),
|
|
3816
|
+
ingestCommand: z26.string().optional(),
|
|
3817
|
+
ingestScriptRan: z26.boolean().optional(),
|
|
3818
|
+
ingestRecordCount: z26.number().optional(),
|
|
3819
|
+
ingestDurationMs: z26.number().optional(),
|
|
3820
|
+
ingestionSource: z26.enum(["local", "fileUpload", "generated"]),
|
|
3821
|
+
// Hints, not ground truth: the search agent may rename the prefix to match
|
|
3822
|
+
// the project's build tool, and its summary carries the final names.
|
|
3823
|
+
searchEnvVars: z26.array(
|
|
3824
|
+
z26.object({
|
|
3825
|
+
name: z26.string(),
|
|
3826
|
+
value: z26.string()
|
|
4394
3827
|
})
|
|
4395
3828
|
).optional()
|
|
4396
3829
|
});
|
|
4397
|
-
var implementationOutputSchema =
|
|
4398
|
-
summary:
|
|
4399
|
-
// Ingestion only:
|
|
4400
|
-
// command string
|
|
4401
|
-
//
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
entrypoint: z24.string().optional()
|
|
3830
|
+
var implementationOutputSchema = z26.object({
|
|
3831
|
+
summary: z26.string(),
|
|
3832
|
+
// Ingestion only: a structured pair the wizard turns into an argv, never a
|
|
3833
|
+
// free-form command string. `runtime` is allowlisted and `entrypoint` is
|
|
3834
|
+
// validated worktree-relative, so the agent cannot inject extra commands.
|
|
3835
|
+
runtime: z26.enum(INGEST_RUNTIMES).optional(),
|
|
3836
|
+
entrypoint: z26.string().optional()
|
|
4405
3837
|
});
|
|
4406
|
-
var verificationOutputSchema =
|
|
4407
|
-
summary:
|
|
4408
|
-
sufficient:
|
|
4409
|
-
additionalInstructions:
|
|
3838
|
+
var verificationOutputSchema = z26.object({
|
|
3839
|
+
summary: z26.string(),
|
|
3840
|
+
sufficient: z26.boolean(),
|
|
3841
|
+
additionalInstructions: z26.string().optional()
|
|
4410
3842
|
});
|
|
4411
3843
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4412
3844
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
3845
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3846
|
+
function detectUiFramework(language) {
|
|
3847
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3848
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
3849
|
+
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3850
|
+
return "React";
|
|
3851
|
+
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3852
|
+
return "JavaScript";
|
|
3853
|
+
}
|
|
3854
|
+
function frameworksForDoc(framework) {
|
|
3855
|
+
switch (framework) {
|
|
3856
|
+
case "React":
|
|
3857
|
+
return ["react"];
|
|
3858
|
+
case "Vue":
|
|
3859
|
+
return ["vue"];
|
|
3860
|
+
case "Angular":
|
|
3861
|
+
return ["angular"];
|
|
3862
|
+
case "JavaScript":
|
|
3863
|
+
return [];
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
function publicEnvPrefix(language) {
|
|
3867
|
+
const frameworkNames = language.frameworks.map(
|
|
3868
|
+
(framework) => framework.name.toLowerCase()
|
|
4417
3869
|
);
|
|
3870
|
+
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3871
|
+
return "NEXT_PUBLIC_";
|
|
3872
|
+
}
|
|
3873
|
+
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3874
|
+
return "NUXT_PUBLIC_";
|
|
3875
|
+
}
|
|
3876
|
+
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3877
|
+
return "PUBLIC_";
|
|
3878
|
+
}
|
|
3879
|
+
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3880
|
+
return "VITE_";
|
|
3881
|
+
}
|
|
3882
|
+
return "PUBLIC_";
|
|
3883
|
+
}
|
|
3884
|
+
function searchEnvVars(language, appId, searchKey) {
|
|
3885
|
+
const prefix = publicEnvPrefix(language);
|
|
4418
3886
|
return [
|
|
4419
3887
|
{
|
|
4420
3888
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4426,38 +3894,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4426
3894
|
}
|
|
4427
3895
|
];
|
|
4428
3896
|
}
|
|
4429
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4430
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4431
|
-
repoRoot,
|
|
4432
|
-
language.languages.map((l) => l.name)
|
|
4433
|
-
);
|
|
4434
|
-
if (candidates.length === 0) {
|
|
4435
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4436
|
-
logger.warn(
|
|
4437
|
-
{
|
|
4438
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4439
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4440
|
-
chosen: chosen.id
|
|
4441
|
-
},
|
|
4442
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4443
|
-
);
|
|
4444
|
-
return chosen;
|
|
4445
|
-
}
|
|
4446
|
-
if (candidates.length === 1) return candidates[0];
|
|
4447
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4448
|
-
if (backends.length === 1) return backends[0];
|
|
4449
|
-
if (backends.length === 0) return candidates[0];
|
|
4450
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4451
|
-
const options = backends.map((p) => p.displayName);
|
|
4452
|
-
const selection = await ctx.requestUserInput({
|
|
4453
|
-
prompt: "Which language should the ingestion script use?",
|
|
4454
|
-
promptType: "multipleChoice",
|
|
4455
|
-
options,
|
|
4456
|
-
defaultSelectedIndex: 0
|
|
4457
|
-
});
|
|
4458
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4459
|
-
return picked ?? backends[0];
|
|
4460
|
-
}
|
|
4461
3897
|
function baseInstructions(input) {
|
|
4462
3898
|
return [
|
|
4463
3899
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4474,9 +3910,6 @@ function sourceSpecificInstructions(input) {
|
|
|
4474
3910
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
4475
3911
|
],
|
|
4476
3912
|
fileUpload: [
|
|
4477
|
-
// The wizard already copied the developer's file into the worktree at this
|
|
4478
|
-
// exact path, so the agent must read it directly — never search for or
|
|
4479
|
-
// substitute another file.
|
|
4480
3913
|
`Records come from the developer's file, already copied into the worktree at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
4481
3914
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
4482
3915
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
@@ -4485,71 +3918,57 @@ function sourceSpecificInstructions(input) {
|
|
|
4485
3918
|
generated: [
|
|
4486
3919
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4487
3920
|
"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.",
|
|
4488
|
-
"In the script, read and parse each returned
|
|
3921
|
+
"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.",
|
|
4489
3922
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4490
3923
|
]
|
|
4491
3924
|
};
|
|
4492
3925
|
return byLine[input.ingestionSource];
|
|
4493
3926
|
}
|
|
4494
3927
|
function ingestionInstructions(input) {
|
|
4495
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4496
|
-
const { ingest } = toolchain;
|
|
4497
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4498
|
-
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.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. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
4499
3928
|
return [
|
|
4500
3929
|
...input.confirmed && input.confirmed.length ? [
|
|
4501
|
-
`
|
|
3930
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4502
3931
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4503
|
-
`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.
|
|
4504
|
-
|
|
3932
|
+
`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.`,
|
|
3933
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4505
3934
|
"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.",
|
|
4506
|
-
getNamedDoc("save-records",
|
|
4507
|
-
|
|
3935
|
+
getNamedDoc("save-records", "js"),
|
|
3936
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4508
3937
|
"The summary should be extremely concise.",
|
|
4509
|
-
|
|
3938
|
+
`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.`,
|
|
4510
3939
|
...sourceSpecificInstructions(input)
|
|
4511
3940
|
] : []
|
|
4512
3941
|
];
|
|
4513
3942
|
}
|
|
4514
3943
|
function searchInstructions(input) {
|
|
4515
|
-
const doc =
|
|
4516
|
-
"instantsearch-setup",
|
|
4517
|
-
searchDocKey(input.searchStrategy)
|
|
4518
|
-
);
|
|
4519
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4520
|
-
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.`;
|
|
3944
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4521
3945
|
return [
|
|
4522
3946
|
"Implement an in-app Algolia search experience.",
|
|
4523
|
-
`Build the search UI for ${
|
|
4524
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3947
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3948
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4525
3949
|
doc,
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4531
|
-
// search-only key is best-effort and can fall back to a placeholder.
|
|
3950
|
+
`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.`,
|
|
3951
|
+
"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.",
|
|
3952
|
+
// appId always resolves (requireApplication throws otherwise); only the key
|
|
3953
|
+
// can fall back to a placeholder.
|
|
4532
3954
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
4533
|
-
//
|
|
4534
|
-
//
|
|
4535
|
-
// right after this step, so a renamed prefix here would leave the code
|
|
3955
|
+
// Not the agent's to rename: the wizard writes these exact names into
|
|
3956
|
+
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4536
3957
|
// reading a var the wizard never wrote.
|
|
4537
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3958
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3959
|
+
'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.',
|
|
4538
3960
|
"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."
|
|
4539
3961
|
];
|
|
4540
3962
|
}
|
|
4541
3963
|
function verificationInstructions(input) {
|
|
4542
|
-
const protectedDirs = [
|
|
4543
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4544
|
-
];
|
|
4545
3964
|
return [
|
|
4546
3965
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4547
3966
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4548
|
-
|
|
3967
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4549
3968
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4550
3969
|
"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.",
|
|
4551
3970
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4552
|
-
`Do not modify ${
|
|
3971
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4553
3972
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4554
3973
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4555
3974
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4558,17 +3977,14 @@ function verificationInstructions(input) {
|
|
|
4558
3977
|
var IMPLEMENT_CONFIG = {
|
|
4559
3978
|
ingestion: {
|
|
4560
3979
|
title: "Algolia ingestion",
|
|
4561
|
-
label: "Ingestion",
|
|
4562
3980
|
buildInstructions: ingestionInstructions
|
|
4563
3981
|
},
|
|
4564
3982
|
search: {
|
|
4565
3983
|
title: "Algolia search",
|
|
4566
|
-
label: "Search",
|
|
4567
3984
|
buildInstructions: searchInstructions
|
|
4568
3985
|
},
|
|
4569
3986
|
verification: {
|
|
4570
3987
|
title: "Algolia verification",
|
|
4571
|
-
label: "Verification",
|
|
4572
3988
|
buildInstructions: verificationInstructions
|
|
4573
3989
|
}
|
|
4574
3990
|
};
|
|
@@ -4600,10 +4016,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4600
4016
|
];
|
|
4601
4017
|
}
|
|
4602
4018
|
function formatSummary(useCase, summary) {
|
|
4603
|
-
|
|
4019
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
4020
|
+
return `${label}: ${summary}`;
|
|
4604
4021
|
}
|
|
4605
|
-
function buildIngestCommand(worktree,
|
|
4606
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4022
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
4023
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4607
4024
|
}
|
|
4608
4025
|
function parseIngestRecordCount(output) {
|
|
4609
4026
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4680,17 +4097,18 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4680
4097
|
}
|
|
4681
4098
|
}
|
|
4682
4099
|
const targetIndex = selected?.selection;
|
|
4100
|
+
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4683
4101
|
await assertGitRepoWithHead(repoRoot);
|
|
4684
4102
|
if (await isWorkingTreeDirty(repoRoot)) {
|
|
4685
4103
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4686
4104
|
}
|
|
4687
4105
|
const normalized = normalizeFindingPaths(findings);
|
|
4688
|
-
const
|
|
4106
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4689
4107
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4690
4108
|
let appId;
|
|
4691
4109
|
let searchKey;
|
|
4692
4110
|
if (useCases.includes("search")) {
|
|
4693
|
-
appId = (await
|
|
4111
|
+
appId = (await requireApplication()).id;
|
|
4694
4112
|
try {
|
|
4695
4113
|
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
4696
4114
|
} catch (err) {
|
|
@@ -4723,66 +4141,29 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4723
4141
|
);
|
|
4724
4142
|
}
|
|
4725
4143
|
}
|
|
4726
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4727
|
-
ctx,
|
|
4728
|
-
language,
|
|
4729
|
-
worktree
|
|
4730
|
-
);
|
|
4731
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4732
|
-
const verificationLanguages = [
|
|
4733
|
-
.../* @__PURE__ */ new Set([
|
|
4734
|
-
ingestionProfile.id,
|
|
4735
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4736
|
-
])
|
|
4737
|
-
];
|
|
4738
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4739
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4740
|
-
frameworkName,
|
|
4741
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4742
|
-
);
|
|
4743
|
-
logger.info(
|
|
4744
|
-
{
|
|
4745
|
-
language: ingestionProfile.id,
|
|
4746
|
-
packageManager: toolchain.packageManager.id,
|
|
4747
|
-
ingest: toolchain.ingest.kind,
|
|
4748
|
-
framework: frameworkName,
|
|
4749
|
-
searchStrategy
|
|
4750
|
-
},
|
|
4751
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4752
|
-
);
|
|
4753
4144
|
const input = {
|
|
4754
4145
|
findings: normalized,
|
|
4755
|
-
confirmed:
|
|
4146
|
+
confirmed: confirmed2,
|
|
4756
4147
|
searchLocation,
|
|
4757
4148
|
targetIndex,
|
|
4758
4149
|
language,
|
|
4759
4150
|
appId,
|
|
4760
4151
|
searchKey,
|
|
4761
|
-
searchEnvVars:
|
|
4762
|
-
language,
|
|
4763
|
-
searchStrategy,
|
|
4764
|
-
appId,
|
|
4765
|
-
searchKey
|
|
4766
|
-
),
|
|
4152
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4767
4153
|
ingestDir: INGEST_DIR,
|
|
4768
4154
|
ingestionSource,
|
|
4769
4155
|
uploadFilePath,
|
|
4770
|
-
|
|
4771
|
-
frameworkName,
|
|
4772
|
-
ingestionProfile,
|
|
4773
|
-
toolchain,
|
|
4774
|
-
verificationLanguages
|
|
4156
|
+
uiFramework: detectUiFramework(language)
|
|
4775
4157
|
};
|
|
4776
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4777
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4778
4158
|
const summaries = [];
|
|
4779
4159
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4780
4160
|
let agentRuns = 0;
|
|
4161
|
+
let ingestRuntime;
|
|
4781
4162
|
let ingestEntrypoint;
|
|
4782
4163
|
let ingestScriptRan = false;
|
|
4783
4164
|
let ingestRecordCount;
|
|
4784
4165
|
let ingestDurationMs;
|
|
4785
|
-
|
|
4166
|
+
let installFailed = false;
|
|
4786
4167
|
let ingestOutcomeMessage;
|
|
4787
4168
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4788
4169
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4796,19 +4177,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4796
4177
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4797
4178
|
outputSchema: implementationOutputSchema
|
|
4798
4179
|
});
|
|
4799
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4800
|
-
if (!useCaseToolchain) return result;
|
|
4801
4180
|
ctx.notify({
|
|
4802
4181
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4803
4182
|
});
|
|
4804
4183
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4805
|
-
useCase: currentUseCase
|
|
4806
|
-
language: useCaseToolchain.profile.id
|
|
4184
|
+
useCase: currentUseCase
|
|
4807
4185
|
});
|
|
4808
|
-
const install = await installWorktreeDeps(worktree
|
|
4186
|
+
const install = await installWorktreeDeps(worktree);
|
|
4809
4187
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4810
4188
|
if (!install.ok) {
|
|
4811
|
-
|
|
4189
|
+
installFailed = true;
|
|
4812
4190
|
logger.warn(
|
|
4813
4191
|
{ useCase: currentUseCase, output: install.output },
|
|
4814
4192
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4822,16 +4200,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4822
4200
|
return runAgent({
|
|
4823
4201
|
instructions: buildAgentInstructions("verification", input),
|
|
4824
4202
|
tools: toolsForUseCase("verification"),
|
|
4825
|
-
outputSchema: verificationOutputSchema
|
|
4826
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4827
|
-
languages: input.verificationLanguages
|
|
4203
|
+
outputSchema: verificationOutputSchema
|
|
4828
4204
|
});
|
|
4829
4205
|
}
|
|
4830
4206
|
if (useCases.includes("ingestion")) {
|
|
4831
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4207
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4832
4208
|
summaries.push(formatSummary("ingestion", summary));
|
|
4209
|
+
ingestRuntime = runtime;
|
|
4833
4210
|
ingestEntrypoint = entrypoint;
|
|
4834
|
-
if (
|
|
4211
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4835
4212
|
ctx.clearNotices();
|
|
4836
4213
|
const runNow = await ctx.requestUserInput({
|
|
4837
4214
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4840,20 +4217,21 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4840
4217
|
messages: []
|
|
4841
4218
|
}) === true;
|
|
4842
4219
|
if (runNow) {
|
|
4843
|
-
const
|
|
4220
|
+
const ingestApp = await requireApplication();
|
|
4221
|
+
const writeKey = await resolveWriteKey(targetIndex);
|
|
4844
4222
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4845
4223
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4846
|
-
|
|
4224
|
+
runtime: ingestRuntime,
|
|
4847
4225
|
entrypoint: ingestEntrypoint
|
|
4848
4226
|
});
|
|
4849
4227
|
const startedAt = Date.now();
|
|
4850
4228
|
const run2 = await runIngestScript(
|
|
4851
4229
|
worktree,
|
|
4852
|
-
|
|
4230
|
+
ingestRuntime,
|
|
4853
4231
|
ingestEntrypoint,
|
|
4854
4232
|
{
|
|
4855
|
-
[APP_ID_VAR]:
|
|
4856
|
-
[API_KEY_VAR]:
|
|
4233
|
+
[APP_ID_VAR]: ingestApp.id,
|
|
4234
|
+
[API_KEY_VAR]: writeKey
|
|
4857
4235
|
}
|
|
4858
4236
|
);
|
|
4859
4237
|
ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
|
|
@@ -4863,7 +4241,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4863
4241
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4864
4242
|
if (ingestRecordCount != null) {
|
|
4865
4243
|
track("AI Wizard Ingest Successful", {
|
|
4866
|
-
entity_name:
|
|
4244
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4867
4245
|
record_count: ingestRecordCount,
|
|
4868
4246
|
duration_ms: ingestDurationMs
|
|
4869
4247
|
});
|
|
@@ -4876,7 +4254,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4876
4254
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4877
4255
|
logger.warn(
|
|
4878
4256
|
{
|
|
4879
|
-
|
|
4257
|
+
runtime: ingestRuntime,
|
|
4880
4258
|
entrypoint: ingestEntrypoint,
|
|
4881
4259
|
reason: run2.reason
|
|
4882
4260
|
},
|
|
@@ -4899,7 +4277,7 @@ ${run2.output}` : status;
|
|
|
4899
4277
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4900
4278
|
logger.warn(
|
|
4901
4279
|
{
|
|
4902
|
-
|
|
4280
|
+
runtime: ingestRuntime,
|
|
4903
4281
|
entrypoint: ingestEntrypoint,
|
|
4904
4282
|
output: run2.output
|
|
4905
4283
|
},
|
|
@@ -4916,52 +4294,20 @@ ${run2.output}` : status;
|
|
|
4916
4294
|
}
|
|
4917
4295
|
}
|
|
4918
4296
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4919
|
-
if (ingestEntrypoint) {
|
|
4297
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4920
4298
|
commandMessages.push(
|
|
4921
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4299
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4922
4300
|
);
|
|
4923
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4924
|
-
commandMessages.push(
|
|
4925
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4926
|
-
);
|
|
4927
|
-
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4928
|
-
if (missingTask) {
|
|
4929
|
-
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4930
|
-
commandMessages.push(warning);
|
|
4931
|
-
summaries.push(warning);
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
if (ingestionSource === "local") {
|
|
4936
|
-
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4937
|
-
if (limitation) {
|
|
4938
|
-
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4939
|
-
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4940
|
-
}
|
|
4941
4301
|
}
|
|
4942
4302
|
await ctx.requestUserInput({
|
|
4943
|
-
//
|
|
4944
|
-
// continue/decline hints below already say "continue".
|
|
4303
|
+
// Nothing to ask — the continue/decline hints carry the whole prompt.
|
|
4945
4304
|
prompt: "",
|
|
4946
4305
|
promptType: "enterToContinue",
|
|
4947
4306
|
options: [],
|
|
4948
4307
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4949
4308
|
});
|
|
4950
4309
|
}
|
|
4951
|
-
|
|
4952
|
-
if (skipSearch) {
|
|
4953
|
-
const target = describeSearchTarget(
|
|
4954
|
-
input.searchStrategy,
|
|
4955
|
-
input.frameworkName
|
|
4956
|
-
);
|
|
4957
|
-
summaries.push(
|
|
4958
|
-
`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).`
|
|
4959
|
-
);
|
|
4960
|
-
track("AI Wizard Search UI Skipped", {
|
|
4961
|
-
framework: input.frameworkName ?? "unknown"
|
|
4962
|
-
});
|
|
4963
|
-
}
|
|
4964
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4310
|
+
if (useCases.includes("search")) {
|
|
4965
4311
|
let extraInstructions = [];
|
|
4966
4312
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4967
4313
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -5032,9 +4378,9 @@ ${run2.output}` : status;
|
|
|
5032
4378
|
"implement: agent reported success but no files changed in the worktree"
|
|
5033
4379
|
);
|
|
5034
4380
|
}
|
|
5035
|
-
if (
|
|
4381
|
+
if (installFailed) {
|
|
5036
4382
|
summaries.push(
|
|
5037
|
-
|
|
4383
|
+
'\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".'
|
|
5038
4384
|
);
|
|
5039
4385
|
}
|
|
5040
4386
|
return {
|
|
@@ -5042,10 +4388,10 @@ ${run2.output}` : status;
|
|
|
5042
4388
|
filesChanged,
|
|
5043
4389
|
summary: summaries.join("\n\n"),
|
|
5044
4390
|
worktreePath: worktree,
|
|
5045
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4391
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5046
4392
|
ingestCommand: buildIngestCommand(
|
|
5047
4393
|
worktree,
|
|
5048
|
-
|
|
4394
|
+
ingestRuntime,
|
|
5049
4395
|
ingestEntrypoint
|
|
5050
4396
|
),
|
|
5051
4397
|
ingestScriptRan,
|
|
@@ -5095,8 +4441,8 @@ var defaultWorkflow = {
|
|
|
5095
4441
|
defineStep({
|
|
5096
4442
|
id: "select-index",
|
|
5097
4443
|
title: "Set up index",
|
|
5098
|
-
outputSchema:
|
|
5099
|
-
selection:
|
|
4444
|
+
outputSchema: z27.object({
|
|
4445
|
+
selection: z27.string()
|
|
5100
4446
|
}),
|
|
5101
4447
|
run: (ctx) => selectIndexStep(ctx)
|
|
5102
4448
|
}),
|
|
@@ -5374,20 +4720,20 @@ function parseCliArgs(argv) {
|
|
|
5374
4720
|
}
|
|
5375
4721
|
|
|
5376
4722
|
// src/lib/resetState.ts
|
|
5377
|
-
import { readdir as
|
|
5378
|
-
import { join as
|
|
4723
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4724
|
+
import { join as join11 } from "node:path";
|
|
5379
4725
|
var KEEP = ["wizard.log"];
|
|
5380
4726
|
async function resetProjectState() {
|
|
5381
4727
|
const dir = stateDir();
|
|
5382
4728
|
let entries;
|
|
5383
4729
|
try {
|
|
5384
|
-
entries = await
|
|
4730
|
+
entries = await readdir4(dir);
|
|
5385
4731
|
} catch {
|
|
5386
4732
|
return { dir, removed: [] };
|
|
5387
4733
|
}
|
|
5388
4734
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5389
4735
|
await Promise.all(
|
|
5390
|
-
targets.map((name) => rm2(
|
|
4736
|
+
targets.map((name) => rm2(join11(dir, name), { recursive: true, force: true }))
|
|
5391
4737
|
);
|
|
5392
4738
|
return { dir, removed: targets };
|
|
5393
4739
|
}
|
|
@@ -5442,31 +4788,38 @@ ${formatStepList(workflow)}`);
|
|
|
5442
4788
|
}
|
|
5443
4789
|
async function run(workflow) {
|
|
5444
4790
|
const store = useWizard.getState();
|
|
5445
|
-
|
|
4791
|
+
const instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
4792
|
+
await store.waitForStart();
|
|
5446
4793
|
let user = await getUser();
|
|
5447
4794
|
if (!user) {
|
|
5448
|
-
|
|
5449
|
-
instance.cleanup();
|
|
4795
|
+
store.beginAuth();
|
|
5450
4796
|
try {
|
|
5451
4797
|
await runAuthLogin();
|
|
5452
4798
|
} catch (err) {
|
|
5453
|
-
|
|
4799
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4800
|
+
await instance.waitUntilExit();
|
|
5454
4801
|
process.exit(1);
|
|
5455
4802
|
}
|
|
5456
|
-
|
|
4803
|
+
store.endAuth();
|
|
5457
4804
|
user = await getUser();
|
|
5458
4805
|
if (!user) {
|
|
5459
4806
|
store.setError(
|
|
5460
|
-
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
|
|
4807
|
+
"Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli@latest auth login` directly."
|
|
5461
4808
|
);
|
|
5462
4809
|
await instance.waitUntilExit();
|
|
5463
4810
|
process.exit(1);
|
|
5464
4811
|
}
|
|
5465
4812
|
}
|
|
5466
4813
|
store.setUser(user);
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
4814
|
+
let app;
|
|
4815
|
+
try {
|
|
4816
|
+
app = await ensureApplication();
|
|
4817
|
+
} catch (err) {
|
|
4818
|
+
store.setError(err instanceof Error ? err.message : String(err));
|
|
4819
|
+
await instance.waitUntilExit();
|
|
4820
|
+
process.exit(1);
|
|
4821
|
+
}
|
|
4822
|
+
runWorkflow(workflow, app.id);
|
|
5470
4823
|
}
|
|
5471
4824
|
var started = await startup();
|
|
5472
4825
|
if (typeof started === "number") {
|