@algolia/wizard 0.2.0 → 0.3.0-rc.43.6
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 +3 -49
- package/dist/main.js +1175 -600
- package/package.json +4 -4
package/dist/main.js
CHANGED
|
@@ -4,27 +4,197 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import {
|
|
8
|
-
import { Box as Box10, Text as Text10, useApp, useInput as useInput3, useWindowSize as useWindowSize2 } from "ink";
|
|
7
|
+
import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize5 } from "ink";
|
|
9
8
|
|
|
10
9
|
// src/core/store.ts
|
|
11
10
|
import { create } from "zustand";
|
|
11
|
+
import { nanoid } from "nanoid";
|
|
12
|
+
|
|
13
|
+
// src/lib/algoliaCli.ts
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
var require2 = createRequire(import.meta.url);
|
|
17
|
+
function algoliaCliEntry() {
|
|
18
|
+
return require2.resolve("@algolia/cli/bin/run.js");
|
|
19
|
+
}
|
|
20
|
+
function runAlgoliaCli(args) {
|
|
21
|
+
return new Promise((resolve4, reject) => {
|
|
22
|
+
const child = spawn(process.execPath, [algoliaCliEntry(), ...args]);
|
|
23
|
+
let stdout = "";
|
|
24
|
+
let stderr = "";
|
|
25
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
26
|
+
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
27
|
+
child.on("error", reject);
|
|
28
|
+
child.on("close", (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
resolve4(stdout);
|
|
31
|
+
} else {
|
|
32
|
+
const detail = stderr.trim() || stdout.trim();
|
|
33
|
+
reject(
|
|
34
|
+
new Error(
|
|
35
|
+
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail ? `: ${detail}` : ""}`
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function getUser() {
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = await runAlgoliaCli(["auth", "get", "--with-access-token"]);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return toUserInfo(JSON.parse(raw));
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function runAuthLogin() {
|
|
56
|
+
return new Promise((resolve4, reject) => {
|
|
57
|
+
const child = spawn(
|
|
58
|
+
process.execPath,
|
|
59
|
+
[algoliaCliEntry(), "auth", "login", "--default"],
|
|
60
|
+
{ stdio: "inherit" }
|
|
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
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/auth.ts
|
|
71
|
+
function getAuthToken() {
|
|
72
|
+
return useWizard.getState().user?.token || null;
|
|
73
|
+
}
|
|
74
|
+
var inFlightRefresh = null;
|
|
75
|
+
function refreshAuthToken() {
|
|
76
|
+
inFlightRefresh ??= (async () => {
|
|
77
|
+
try {
|
|
78
|
+
const raw = await runAlgoliaCli(["auth", "get", "--with-access-token"]);
|
|
79
|
+
const user2 = toUserInfo(JSON.parse(raw));
|
|
80
|
+
useWizard.getState().setUser(user2);
|
|
81
|
+
return user2.token;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
} finally {
|
|
85
|
+
inFlightRefresh = null;
|
|
86
|
+
}
|
|
87
|
+
})();
|
|
88
|
+
return inFlightRefresh;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/lib/logger.ts
|
|
92
|
+
import pino from "pino";
|
|
93
|
+
import { join as join2, dirname } from "node:path";
|
|
94
|
+
import { devNull } from "node:os";
|
|
95
|
+
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
96
|
+
|
|
97
|
+
// src/core/constants.ts
|
|
98
|
+
import { homedir } from "node:os";
|
|
99
|
+
import { join, resolve } from "node:path";
|
|
100
|
+
function rootDir() {
|
|
101
|
+
return process.env.WIZARD_HOME ?? join(homedir(), ".algolia");
|
|
102
|
+
}
|
|
103
|
+
function projectSlug(cwd = process.cwd()) {
|
|
104
|
+
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
105
|
+
}
|
|
106
|
+
function stateDir(cwd = process.cwd()) {
|
|
107
|
+
return join(rootDir(), projectSlug(cwd));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/lib/logger.ts
|
|
111
|
+
var STDERR_FD = 2;
|
|
112
|
+
function resolveDest() {
|
|
113
|
+
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
|
|
114
|
+
try {
|
|
115
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
116
|
+
closeSync(openSync(target, "a"));
|
|
117
|
+
return target;
|
|
118
|
+
} catch {
|
|
119
|
+
return STDERR_FD;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function logDestination() {
|
|
123
|
+
return pino.destination({ dest: resolveDest(), sync: false });
|
|
124
|
+
}
|
|
125
|
+
var logger = pino(
|
|
126
|
+
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
127
|
+
logDestination()
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// src/lib/proxyFetch.ts
|
|
131
|
+
var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
|
|
132
|
+
var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
|
|
133
|
+
var proxyFetch = async (input, init) => {
|
|
134
|
+
const req = new Request(input, init);
|
|
135
|
+
const retry = req.clone();
|
|
136
|
+
const res = await fetch(req);
|
|
137
|
+
if (res.status !== 401) return res;
|
|
138
|
+
if (res.headers.get(PROXY_AUTH_REJECTED_HEADER) !== "rejected") return res;
|
|
139
|
+
const fresh = await refreshAuthToken();
|
|
140
|
+
if (!fresh) return res;
|
|
141
|
+
const headers = new Headers(retry.headers);
|
|
142
|
+
if (headers.has("authorization")) {
|
|
143
|
+
headers.set("authorization", `Bearer ${fresh}`);
|
|
144
|
+
} else {
|
|
145
|
+
headers.set("x-api-key", fresh);
|
|
146
|
+
}
|
|
147
|
+
return fetch(new Request(retry, { headers }));
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/lib/interaction.ts
|
|
151
|
+
async function markInteraction() {
|
|
152
|
+
const token = getAuthToken();
|
|
153
|
+
if (!token) return;
|
|
154
|
+
try {
|
|
155
|
+
const res = await proxyFetch(`${PROXY_BASE_URL}/interaction`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: { authorization: `Bearer ${token}` }
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) throw new Error(`proxy interaction ${res.status}`);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.warn({ err }, "failed to mark wizard interaction");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/core/store.ts
|
|
12
166
|
function toUserInfo({ user_id, ...rest }) {
|
|
13
167
|
return { userId: user_id, ...rest };
|
|
14
168
|
}
|
|
169
|
+
function describeInputValue(value) {
|
|
170
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
171
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
172
|
+
}
|
|
173
|
+
var NOTICE_INTERVAL_MS = 2e3;
|
|
15
174
|
var useWizard = create((set, get) => ({
|
|
16
175
|
phase: "idle",
|
|
176
|
+
homeScreen: "home",
|
|
17
177
|
user: null,
|
|
18
178
|
workflow: null,
|
|
19
179
|
steps: [],
|
|
20
180
|
currentStepIndex: 0,
|
|
21
181
|
output: "",
|
|
182
|
+
notices: [],
|
|
183
|
+
_noticeQueue: [],
|
|
184
|
+
_noticeTimer: null,
|
|
185
|
+
logs: [],
|
|
22
186
|
error: null,
|
|
23
187
|
inputReq: null,
|
|
24
188
|
_resolve: null,
|
|
25
189
|
// Advances past the welcome screen. Only meaningful from 'idle' — once the
|
|
26
190
|
// workflow is running there's nothing left to confirm.
|
|
27
|
-
|
|
191
|
+
// Reset `homeScreen` so preflight shows Welcome, not the Learn more sub-view.
|
|
192
|
+
confirmStart: () => set(
|
|
193
|
+
(s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
|
|
194
|
+
),
|
|
195
|
+
// Welcome sub-view navigation; leaves `phase` untouched so the workflow stays paused.
|
|
196
|
+
openLearnMore: () => set({ homeScreen: "learnMore" }),
|
|
197
|
+
backToHome: () => set({ homeScreen: "home" }),
|
|
28
198
|
// Resolves once the phase leaves 'idle', whether that happens before or
|
|
29
199
|
// after this is called (the welcome screen's spacebar handler is what
|
|
30
200
|
// drives the transition via `confirmStart`).
|
|
@@ -48,10 +218,64 @@ var useWizard = create((set, get) => ({
|
|
|
48
218
|
error: null
|
|
49
219
|
}),
|
|
50
220
|
syncSteps: (steps, currentStepIndex) => set({ steps, currentStepIndex }),
|
|
51
|
-
setActiveStep: (index) =>
|
|
221
|
+
setActiveStep: (index) => {
|
|
222
|
+
get()._clearNoticeQueue();
|
|
223
|
+
set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
|
|
224
|
+
},
|
|
52
225
|
setUser: (user2) => set({ user: user2 }),
|
|
53
226
|
appendToken: (text) => set((s) => ({ output: s.output + text })),
|
|
54
227
|
clearOutput: () => set({ output: "" }),
|
|
228
|
+
// Renders the first notice of a burst immediately, then holds later
|
|
229
|
+
// arrivals in `_noticeQueue` and drains one per `NOTICE_INTERVAL_MS` —
|
|
230
|
+
// the timer stays armed through an empty drain so the cooldown always
|
|
231
|
+
// covers the time since the last render, even across bursts.
|
|
232
|
+
pushNotice: (notice) => {
|
|
233
|
+
const { notices, _noticeQueue, _noticeTimer } = get();
|
|
234
|
+
if (_noticeTimer === null) {
|
|
235
|
+
set({
|
|
236
|
+
notices: [...notices, notice],
|
|
237
|
+
_noticeTimer: setTimeout(() => get()._drainNoticeQueue(), NOTICE_INTERVAL_MS)
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
set({ _noticeQueue: [..._noticeQueue, notice] });
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
_drainNoticeQueue: () => {
|
|
244
|
+
const [next, ...rest] = get()._noticeQueue;
|
|
245
|
+
if (next) {
|
|
246
|
+
set((s) => ({
|
|
247
|
+
notices: [...s.notices, next],
|
|
248
|
+
_noticeQueue: rest,
|
|
249
|
+
_noticeTimer: setTimeout(() => get()._drainNoticeQueue(), NOTICE_INTERVAL_MS)
|
|
250
|
+
}));
|
|
251
|
+
} else {
|
|
252
|
+
set({ _noticeTimer: null });
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
_clearNoticeQueue: () => {
|
|
256
|
+
const timer = get()._noticeTimer;
|
|
257
|
+
if (timer) clearTimeout(timer);
|
|
258
|
+
set({ _noticeQueue: [], _noticeTimer: null });
|
|
259
|
+
},
|
|
260
|
+
clearNotices: () => {
|
|
261
|
+
get()._clearNoticeQueue();
|
|
262
|
+
set({ notices: [] });
|
|
263
|
+
},
|
|
264
|
+
logStart: (kind, name, input) => {
|
|
265
|
+
const id = nanoid();
|
|
266
|
+
set((s) => ({
|
|
267
|
+
logs: [
|
|
268
|
+
...s.logs,
|
|
269
|
+
{ id, kind, name, input, status: "running", startedAt: Date.now() }
|
|
270
|
+
]
|
|
271
|
+
}));
|
|
272
|
+
return id;
|
|
273
|
+
},
|
|
274
|
+
logEnd: (id, status) => set((s) => ({
|
|
275
|
+
logs: s.logs.map(
|
|
276
|
+
(t) => t.id === id ? { ...t, status, durationMs: Date.now() - t.startedAt } : t
|
|
277
|
+
)
|
|
278
|
+
})),
|
|
55
279
|
requestUserInput: (req) => new Promise((resolve4) => {
|
|
56
280
|
set({
|
|
57
281
|
phase: "awaitingInput",
|
|
@@ -59,43 +283,39 @@ var useWizard = create((set, get) => ({
|
|
|
59
283
|
_resolve: resolve4
|
|
60
284
|
});
|
|
61
285
|
}),
|
|
62
|
-
|
|
286
|
+
// Logs what the user picked — not the prompt text that was shown, which
|
|
287
|
+
// may repeat or duplicate on-screen content and isn't the useful signal
|
|
288
|
+
// here.
|
|
289
|
+
submitInput: async (value) => {
|
|
290
|
+
await markInteraction();
|
|
63
291
|
get()._resolve?.(value);
|
|
64
292
|
set({ inputReq: null, _resolve: null, phase: "running" });
|
|
293
|
+
const id = get().logStart("prompt", `User input: ${describeInputValue(value)}`);
|
|
294
|
+
get().logEnd(id, "success");
|
|
65
295
|
},
|
|
66
296
|
setDone: () => set({ phase: "done" }),
|
|
67
297
|
setError: (message) => set({ phase: "error", error: message }),
|
|
68
|
-
reset: () =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
298
|
+
reset: () => {
|
|
299
|
+
get()._clearNoticeQueue();
|
|
300
|
+
set({
|
|
301
|
+
phase: "idle",
|
|
302
|
+
homeScreen: "home",
|
|
303
|
+
workflow: null,
|
|
304
|
+
steps: [],
|
|
305
|
+
currentStepIndex: 0,
|
|
306
|
+
output: "",
|
|
307
|
+
notices: [],
|
|
308
|
+
logs: [],
|
|
309
|
+
error: null,
|
|
310
|
+
inputReq: null,
|
|
311
|
+
_resolve: null
|
|
312
|
+
});
|
|
313
|
+
}
|
|
78
314
|
}));
|
|
79
315
|
|
|
80
|
-
// src/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
stream.write(`\x1B]52;c;${encoded}\x07`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/lib/shell.ts
|
|
87
|
-
function shellQuote(value) {
|
|
88
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// src/ui/PromptInput.tsx
|
|
92
|
-
import { Box as Box3, Text as Text3 } from "ink";
|
|
93
|
-
import TextInput from "ink-text-input";
|
|
94
|
-
import { useState as useState3 } from "react";
|
|
95
|
-
|
|
96
|
-
// src/ui/SelectPrompt.tsx
|
|
97
|
-
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
98
|
-
import { useState as useState2 } from "react";
|
|
316
|
+
// src/ui/Notices.tsx
|
|
317
|
+
import { Box as Box2, Text as Text2, useWindowSize as useWindowSize2 } from "ink";
|
|
318
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
99
319
|
|
|
100
320
|
// src/ui/Table.tsx
|
|
101
321
|
import { Box, Text, measureElement, useWindowSize } from "ink";
|
|
@@ -159,6 +379,7 @@ var MARKER = {
|
|
|
159
379
|
};
|
|
160
380
|
var BRAND = "#003DFF";
|
|
161
381
|
var SECONDARY = "#5468FF";
|
|
382
|
+
var DANGER = "#F86E7E";
|
|
162
383
|
var COLORS = {
|
|
163
384
|
brand: BRAND,
|
|
164
385
|
primary: "#E6EDF3",
|
|
@@ -168,23 +389,145 @@ var COLORS = {
|
|
|
168
389
|
dim: "#484F58",
|
|
169
390
|
highlight: { bg: "#12331C", fg: "#4ADE80" },
|
|
170
391
|
badge: "#E3B341",
|
|
171
|
-
|
|
392
|
+
danger: DANGER,
|
|
172
393
|
success: "#4ADE80",
|
|
173
394
|
bg: {
|
|
174
395
|
main: "#0B0E14",
|
|
175
396
|
sidebar: "#14171E"
|
|
176
397
|
},
|
|
398
|
+
border: "#30363D",
|
|
177
399
|
accent: "#76A0FF",
|
|
178
400
|
status: {
|
|
179
401
|
pending: "gray",
|
|
180
402
|
running: "#76A0FF",
|
|
181
403
|
done: "#4ADE80",
|
|
182
|
-
error:
|
|
404
|
+
error: DANGER
|
|
183
405
|
}
|
|
184
406
|
};
|
|
185
407
|
|
|
186
|
-
// src/ui/
|
|
408
|
+
// src/ui/Notices.tsx
|
|
187
409
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
410
|
+
var AGENT_MARKER = "\u2726";
|
|
411
|
+
var RESERVED_ROWS = 14;
|
|
412
|
+
var PANEL_TEXT_WIDTH = 45;
|
|
413
|
+
function messageLineCount(text) {
|
|
414
|
+
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH));
|
|
415
|
+
}
|
|
416
|
+
function noticeLineCount(notice) {
|
|
417
|
+
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
418
|
+
const text = typeof m === "string" ? m : m.text;
|
|
419
|
+
return sum + messageLineCount(text);
|
|
420
|
+
}, 0);
|
|
421
|
+
const tableLines = notice.table ? notice.table.rows.length + 4 : 0;
|
|
422
|
+
return messageLines + tableLines;
|
|
423
|
+
}
|
|
424
|
+
function fitVisibleNotices(notices, windowRows) {
|
|
425
|
+
const budget = Math.max(windowRows - RESERVED_ROWS, 3);
|
|
426
|
+
let used = 0;
|
|
427
|
+
let count = 0;
|
|
428
|
+
for (let i = notices.length - 1; i >= 0; i--) {
|
|
429
|
+
const height = noticeLineCount(notices[i]) + (count > 0 ? 1 : 0);
|
|
430
|
+
if (count > 0 && used + height > budget) break;
|
|
431
|
+
used += height;
|
|
432
|
+
count++;
|
|
433
|
+
}
|
|
434
|
+
return notices.slice(notices.length - count);
|
|
435
|
+
}
|
|
436
|
+
var PULSE_STEPS = 12;
|
|
437
|
+
var PULSE_STEP_MS = 150;
|
|
438
|
+
var PULSE_COLORS = Array.from(
|
|
439
|
+
{ length: PULSE_STEPS },
|
|
440
|
+
(_, i) => mixHex(COLORS.accent, COLORS.strong, i / (PULSE_STEPS - 1))
|
|
441
|
+
);
|
|
442
|
+
function mixHex(from, to, t) {
|
|
443
|
+
const a = parseHex(from);
|
|
444
|
+
const b = parseHex(to);
|
|
445
|
+
const channel = (k) => Math.round(a[k] + (b[k] - a[k]) * t).toString(16).padStart(2, "0");
|
|
446
|
+
return `#${channel("r")}${channel("g")}${channel("b")}`;
|
|
447
|
+
}
|
|
448
|
+
function parseHex(hex) {
|
|
449
|
+
const n = hex.replace("#", "");
|
|
450
|
+
return {
|
|
451
|
+
r: parseInt(n.slice(0, 2), 16),
|
|
452
|
+
g: parseInt(n.slice(2, 4), 16),
|
|
453
|
+
b: parseInt(n.slice(4, 6), 16)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function Notices() {
|
|
457
|
+
const notices = useWizard((s) => s.notices);
|
|
458
|
+
const { rows: windowRows } = useWindowSize2();
|
|
459
|
+
const visible = fitVisibleNotices(notices, windowRows);
|
|
460
|
+
const [pulseStep, setPulseStep] = useState2(0);
|
|
461
|
+
useEffect2(() => {
|
|
462
|
+
let direction = 1;
|
|
463
|
+
const id = setInterval(() => {
|
|
464
|
+
setPulseStep((step) => {
|
|
465
|
+
const next = step + direction;
|
|
466
|
+
if (next >= PULSE_COLORS.length - 1) direction = -1;
|
|
467
|
+
else if (next <= 0) direction = 1;
|
|
468
|
+
return Math.min(Math.max(next, 0), PULSE_COLORS.length - 1);
|
|
469
|
+
});
|
|
470
|
+
}, PULSE_STEP_MS);
|
|
471
|
+
return () => clearInterval(id);
|
|
472
|
+
}, []);
|
|
473
|
+
if (!visible.length) return null;
|
|
474
|
+
const pulseColor = PULSE_COLORS[pulseStep];
|
|
475
|
+
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap: 1, marginBottom: 1, children: visible.map((notice, i) => {
|
|
476
|
+
const isLatest = i === visible.length - 1;
|
|
477
|
+
return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", children: [
|
|
478
|
+
notice.messages?.map((m, j) => {
|
|
479
|
+
const line = typeof m === "string" ? { text: m } : m;
|
|
480
|
+
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
481
|
+
return /* @__PURE__ */ jsxs(
|
|
482
|
+
Text2,
|
|
483
|
+
{
|
|
484
|
+
color: isLatest && !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
485
|
+
bold: line.bold,
|
|
486
|
+
children: [
|
|
487
|
+
prefix,
|
|
488
|
+
line.text
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
`notice-${i}-${j}`
|
|
492
|
+
);
|
|
493
|
+
}),
|
|
494
|
+
notice.table && /* @__PURE__ */ jsx2(Table, { columns: notice.table.columns, rows: notice.table.rows })
|
|
495
|
+
] }, `notice-${i}`);
|
|
496
|
+
}) });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/ui/PromptInput.tsx
|
|
500
|
+
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
501
|
+
import TextInput from "ink-text-input";
|
|
502
|
+
import { useState as useState4 } from "react";
|
|
503
|
+
|
|
504
|
+
// src/ui/NextAction.tsx
|
|
505
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
506
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
507
|
+
function NextAction({
|
|
508
|
+
action,
|
|
509
|
+
keyHint,
|
|
510
|
+
hierarchy = "primary"
|
|
511
|
+
}) {
|
|
512
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "row", gap: 1, children: [
|
|
513
|
+
hierarchy === "primary" && /* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
514
|
+
hierarchy === "secondary" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
515
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.success, bold: true, children: `>` }),
|
|
516
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.primary, bold: true, children: action })
|
|
517
|
+
] }),
|
|
518
|
+
/* @__PURE__ */ jsxs2(Box3, { children: [
|
|
519
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: "press " }),
|
|
520
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: `[` }),
|
|
521
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.primary, children: keyHint }),
|
|
522
|
+
/* @__PURE__ */ jsx3(Text3, { color: COLORS.muted, children: `]` })
|
|
523
|
+
] })
|
|
524
|
+
] });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/ui/SelectPrompt.tsx
|
|
528
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
529
|
+
import { useState as useState3 } from "react";
|
|
530
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
188
531
|
var CANCEL = "cancel";
|
|
189
532
|
function SelectPrompt({
|
|
190
533
|
options,
|
|
@@ -199,10 +542,10 @@ function SelectPrompt({
|
|
|
199
542
|
secondary,
|
|
200
543
|
defaultSelectedIndex = 0
|
|
201
544
|
}) {
|
|
202
|
-
const [index, setIndex] =
|
|
545
|
+
const [index, setIndex] = useState3(
|
|
203
546
|
() => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
|
|
204
547
|
);
|
|
205
|
-
const [checked, setChecked] =
|
|
548
|
+
const [checked, setChecked] = useState3(() => /* @__PURE__ */ new Set());
|
|
206
549
|
const hasCancel = Boolean(multi || cancelable);
|
|
207
550
|
const rows = hasCancel ? [...options, "Cancel"] : options;
|
|
208
551
|
const cancelIndex = hasCancel ? options.length : -1;
|
|
@@ -239,46 +582,46 @@ function SelectPrompt({
|
|
|
239
582
|
}
|
|
240
583
|
}
|
|
241
584
|
});
|
|
242
|
-
return /* @__PURE__ */
|
|
243
|
-
error && /* @__PURE__ */
|
|
244
|
-
messages?.map((m, i) => /* @__PURE__ */
|
|
245
|
-
table && /* @__PURE__ */
|
|
246
|
-
/* @__PURE__ */
|
|
247
|
-
question && /* @__PURE__ */
|
|
248
|
-
helpText && /* @__PURE__ */
|
|
585
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, children: [
|
|
586
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
587
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
588
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
589
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
590
|
+
question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
|
|
591
|
+
helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
|
|
249
592
|
] }),
|
|
250
|
-
/* @__PURE__ */
|
|
593
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
|
|
251
594
|
const highlighted = i === index;
|
|
252
595
|
const isCancel = i === cancelIndex;
|
|
253
596
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
254
597
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
255
598
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
256
|
-
const label = /* @__PURE__ */
|
|
599
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, children: [
|
|
257
600
|
highlighted ? "\u276F " : " ",
|
|
258
601
|
bullet,
|
|
259
602
|
option
|
|
260
603
|
] });
|
|
261
|
-
return /* @__PURE__ */
|
|
262
|
-
|
|
604
|
+
return /* @__PURE__ */ jsxs3(
|
|
605
|
+
Box4,
|
|
263
606
|
{
|
|
264
607
|
width: rowWidth,
|
|
265
608
|
paddingX: 1,
|
|
266
609
|
paddingY: 1,
|
|
267
610
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
268
611
|
children: [
|
|
269
|
-
sec?.kind === "text" ? /* @__PURE__ */
|
|
270
|
-
sec?.kind === "text" && /* @__PURE__ */
|
|
271
|
-
/* @__PURE__ */
|
|
272
|
-
sec?.kind === "badge" && /* @__PURE__ */
|
|
612
|
+
sec?.kind === "text" ? /* @__PURE__ */ jsx4(Box4, { width: labelColWidth, flexShrink: 0, children: label }) : label,
|
|
613
|
+
sec?.kind === "text" && /* @__PURE__ */ jsx4(Text4, { color: highlighted ? COLORS.primary : COLORS.muted, children: sec.value }),
|
|
614
|
+
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1 }),
|
|
615
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, children: sec.value })
|
|
273
616
|
]
|
|
274
617
|
},
|
|
275
618
|
`row-${i}`
|
|
276
619
|
);
|
|
277
620
|
}) }),
|
|
278
|
-
/* @__PURE__ */
|
|
621
|
+
/* @__PURE__ */ jsx4(Box4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
279
622
|
i > 0 ? " " : "",
|
|
280
|
-
/* @__PURE__ */
|
|
281
|
-
/* @__PURE__ */
|
|
623
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
624
|
+
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
282
625
|
" ",
|
|
283
626
|
label
|
|
284
627
|
] })
|
|
@@ -287,17 +630,35 @@ function SelectPrompt({
|
|
|
287
630
|
}
|
|
288
631
|
|
|
289
632
|
// src/ui/PromptInput.tsx
|
|
290
|
-
import { jsx as
|
|
633
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
291
634
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
635
|
+
function SpaceToContinuePrompt({
|
|
636
|
+
question,
|
|
637
|
+
messages,
|
|
638
|
+
onDecide
|
|
639
|
+
}) {
|
|
640
|
+
useInput2((input, key) => {
|
|
641
|
+
if (input === " ") onDecide(true);
|
|
642
|
+
else if (key.escape) onDecide(false);
|
|
643
|
+
});
|
|
644
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
|
|
645
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
646
|
+
question && /* @__PURE__ */ jsx5(Text5, { color: COLORS.primary, children: question }),
|
|
647
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 1, flexDirection: "column", children: [
|
|
648
|
+
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "space" }),
|
|
649
|
+
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
650
|
+
] })
|
|
651
|
+
] });
|
|
652
|
+
}
|
|
292
653
|
function PromptInput() {
|
|
293
654
|
const { phase, inputReq, submitInput } = useWizard();
|
|
294
|
-
const [draft, setDraft] =
|
|
655
|
+
const [draft, setDraft] = useState4("");
|
|
295
656
|
if (phase === "done" || phase === "error") {
|
|
296
|
-
return /* @__PURE__ */
|
|
657
|
+
return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
297
658
|
}
|
|
298
659
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
299
660
|
if (inputReq.promptType === "multipleChoice") {
|
|
300
|
-
return /* @__PURE__ */
|
|
661
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
301
662
|
SelectPrompt,
|
|
302
663
|
{
|
|
303
664
|
question: inputReq.prompt,
|
|
@@ -314,7 +675,7 @@ function PromptInput() {
|
|
|
314
675
|
) });
|
|
315
676
|
}
|
|
316
677
|
if (inputReq.promptType === "multiSelect") {
|
|
317
|
-
return /* @__PURE__ */
|
|
678
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
318
679
|
SelectPrompt,
|
|
319
680
|
{
|
|
320
681
|
multi: true,
|
|
@@ -329,7 +690,7 @@ function PromptInput() {
|
|
|
329
690
|
) });
|
|
330
691
|
}
|
|
331
692
|
if (inputReq.promptType === "notice") {
|
|
332
|
-
return /* @__PURE__ */
|
|
693
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
333
694
|
SelectPrompt,
|
|
334
695
|
{
|
|
335
696
|
question: inputReq.prompt,
|
|
@@ -339,9 +700,19 @@ function PromptInput() {
|
|
|
339
700
|
}
|
|
340
701
|
) });
|
|
341
702
|
}
|
|
703
|
+
if (inputReq.promptType === "spaceToContinue") {
|
|
704
|
+
return /* @__PURE__ */ jsx5(
|
|
705
|
+
SpaceToContinuePrompt,
|
|
706
|
+
{
|
|
707
|
+
question: inputReq.prompt,
|
|
708
|
+
messages: inputReq.messages,
|
|
709
|
+
onDecide: submitInput
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
}
|
|
342
713
|
if (inputReq.promptType === "acceptReject") {
|
|
343
714
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
344
|
-
return /* @__PURE__ */
|
|
715
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
345
716
|
SelectPrompt,
|
|
346
717
|
{
|
|
347
718
|
question: inputReq.prompt,
|
|
@@ -352,15 +723,15 @@ function PromptInput() {
|
|
|
352
723
|
}
|
|
353
724
|
) });
|
|
354
725
|
}
|
|
355
|
-
return /* @__PURE__ */
|
|
356
|
-
inputReq.error && /* @__PURE__ */
|
|
357
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */
|
|
358
|
-
/* @__PURE__ */
|
|
359
|
-
/* @__PURE__ */
|
|
726
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
727
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text5, { color: COLORS.danger, children: inputReq.error }),
|
|
728
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
729
|
+
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
730
|
+
/* @__PURE__ */ jsxs4(Text5, { color: COLORS.primary, children: [
|
|
360
731
|
inputReq.prompt,
|
|
361
732
|
" "
|
|
362
733
|
] }),
|
|
363
|
-
/* @__PURE__ */
|
|
734
|
+
/* @__PURE__ */ jsx5(
|
|
364
735
|
TextInput,
|
|
365
736
|
{
|
|
366
737
|
value: draft,
|
|
@@ -376,32 +747,9 @@ function PromptInput() {
|
|
|
376
747
|
}
|
|
377
748
|
|
|
378
749
|
// src/ui/Welcome.tsx
|
|
379
|
-
import { dirname, join } from "node:path";
|
|
750
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
380
751
|
import { fileURLToPath } from "node:url";
|
|
381
|
-
import { Box as
|
|
382
|
-
|
|
383
|
-
// src/ui/NextAction.tsx
|
|
384
|
-
import { Box as Box4, Text as Text4 } from "ink";
|
|
385
|
-
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
386
|
-
function NextAction({
|
|
387
|
-
action,
|
|
388
|
-
keyHint,
|
|
389
|
-
hierarchy = "primary"
|
|
390
|
-
}) {
|
|
391
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "row", gap: 1, children: [
|
|
392
|
-
hierarchy === "primary" && /* @__PURE__ */ jsx4(Text4, { color: COLORS.success, bold: true, children: `> ${action}` }),
|
|
393
|
-
hierarchy === "secondary" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
394
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.success, bold: true, children: `>` }),
|
|
395
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, bold: true, children: action })
|
|
396
|
-
] }),
|
|
397
|
-
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
398
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: "press " }),
|
|
399
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: `[` }),
|
|
400
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: keyHint }),
|
|
401
|
-
/* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: `]` })
|
|
402
|
-
] })
|
|
403
|
-
] });
|
|
404
|
-
}
|
|
752
|
+
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3 } from "ink";
|
|
405
753
|
|
|
406
754
|
// src/ui/copy/welcome.ts
|
|
407
755
|
var sidebarItems = [
|
|
@@ -429,31 +777,33 @@ var sidebarItems = [
|
|
|
429
777
|
|
|
430
778
|
// src/ui/Welcome.tsx
|
|
431
779
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
432
|
-
import { jsx as
|
|
433
|
-
var IMAGE_PATH =
|
|
780
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
781
|
+
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
434
782
|
function SidebarItem({
|
|
435
783
|
title,
|
|
436
784
|
description
|
|
437
785
|
}) {
|
|
438
|
-
return /* @__PURE__ */
|
|
439
|
-
/* @__PURE__ */
|
|
440
|
-
/* @__PURE__ */
|
|
441
|
-
/* @__PURE__ */
|
|
786
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
787
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
|
|
788
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.success, children: "\u2192" }),
|
|
789
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.strong, bold: true, children: title })
|
|
442
790
|
] }),
|
|
443
|
-
/* @__PURE__ */
|
|
444
|
-
/* @__PURE__ */
|
|
445
|
-
/* @__PURE__ */
|
|
791
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
|
|
792
|
+
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
793
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
|
|
446
794
|
] })
|
|
447
795
|
] });
|
|
448
796
|
}
|
|
449
797
|
function Welcome() {
|
|
450
798
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
451
|
-
|
|
799
|
+
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
800
|
+
useInput3((input) => {
|
|
452
801
|
if (input === " ") confirmStart();
|
|
802
|
+
else if (input === "i") openLearnMore();
|
|
453
803
|
});
|
|
454
|
-
return /* @__PURE__ */
|
|
455
|
-
/* @__PURE__ */
|
|
456
|
-
/* @__PURE__ */
|
|
804
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
805
|
+
/* @__PURE__ */ jsx6(Box6, { padding: 8, flexDirection: "column", justifyContent: "center", children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 4, children: [
|
|
806
|
+
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
457
807
|
Image,
|
|
458
808
|
{
|
|
459
809
|
src: IMAGE_PATH,
|
|
@@ -464,14 +814,14 @@ function Welcome() {
|
|
|
464
814
|
protocol: "halfBlock"
|
|
465
815
|
}
|
|
466
816
|
) }),
|
|
467
|
-
/* @__PURE__ */
|
|
468
|
-
/* @__PURE__ */
|
|
469
|
-
/* @__PURE__ */
|
|
470
|
-
/* @__PURE__ */
|
|
817
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
818
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
|
|
819
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "space" }),
|
|
820
|
+
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
471
821
|
] })
|
|
472
822
|
] }) }),
|
|
473
|
-
/* @__PURE__ */
|
|
474
|
-
|
|
823
|
+
/* @__PURE__ */ jsxs5(
|
|
824
|
+
Box6,
|
|
475
825
|
{
|
|
476
826
|
backgroundColor: COLORS.bg.sidebar,
|
|
477
827
|
width: 40,
|
|
@@ -480,41 +830,168 @@ function Welcome() {
|
|
|
480
830
|
flexDirection: "column",
|
|
481
831
|
justifyContent: "center",
|
|
482
832
|
children: [
|
|
483
|
-
/* @__PURE__ */
|
|
484
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */
|
|
833
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
834
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
485
835
|
]
|
|
486
836
|
}
|
|
487
837
|
)
|
|
488
838
|
] });
|
|
489
839
|
}
|
|
490
840
|
|
|
841
|
+
// src/ui/LearnMore.tsx
|
|
842
|
+
import { Fragment as Fragment2 } from "react";
|
|
843
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize3 } from "ink";
|
|
844
|
+
|
|
845
|
+
// src/ui/copy/learn-more.ts
|
|
846
|
+
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
847
|
+
var accessItems = [
|
|
848
|
+
{
|
|
849
|
+
tag: "READ",
|
|
850
|
+
title: "Project files",
|
|
851
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
tag: "WRITE",
|
|
855
|
+
title: "Code changes",
|
|
856
|
+
description: "creates & edits files (search UI, config). Shown as a diff first \u2014 nothing lands without your approval."
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
tag: "NET",
|
|
860
|
+
title: "Algolia API",
|
|
861
|
+
description: "sends index settings & the records you pick to your Algolia app over HTTPS."
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
tag: "KEY",
|
|
865
|
+
title: "Credentials",
|
|
866
|
+
description: "saves your Admin API key to .env and adds it to .gitignore."
|
|
867
|
+
}
|
|
868
|
+
];
|
|
869
|
+
var neverItems = [
|
|
870
|
+
"Send your source code to a model or third party",
|
|
871
|
+
"Commit or push to git",
|
|
872
|
+
"Touch files outside your project directory"
|
|
873
|
+
];
|
|
874
|
+
var policyLinks = [
|
|
875
|
+
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
876
|
+
{ label: "Privacy Policy", url: "https://www.algolia.com/policies/privacy" }
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
// src/ui/LearnMore.tsx
|
|
880
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
881
|
+
var TAG_COLORS = {
|
|
882
|
+
READ: COLORS.success,
|
|
883
|
+
WRITE: COLORS.badge,
|
|
884
|
+
NET: COLORS.accent,
|
|
885
|
+
KEY: COLORS.muted
|
|
886
|
+
};
|
|
887
|
+
var TAG_COLUMN_WIDTH = 10;
|
|
888
|
+
var PADDING_X = 6;
|
|
889
|
+
var NEVER_BOX_PAD_X = 2;
|
|
890
|
+
function NeverLine({
|
|
891
|
+
width,
|
|
892
|
+
segments = []
|
|
893
|
+
}) {
|
|
894
|
+
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
895
|
+
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
896
|
+
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
897
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" }),
|
|
898
|
+
" ".repeat(NEVER_BOX_PAD_X),
|
|
899
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
900
|
+
" ".repeat(rightPad),
|
|
901
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" })
|
|
902
|
+
] });
|
|
903
|
+
}
|
|
904
|
+
function LearnMore() {
|
|
905
|
+
const confirmStart = useWizard((s) => s.confirmStart);
|
|
906
|
+
const backToHome = useWizard((s) => s.backToHome);
|
|
907
|
+
const { columns } = useWindowSize3();
|
|
908
|
+
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
909
|
+
useInput4((input, key) => {
|
|
910
|
+
if (key.escape) backToHome();
|
|
911
|
+
else if (input === " ") confirmStart();
|
|
912
|
+
});
|
|
913
|
+
return /* @__PURE__ */ jsxs6(
|
|
914
|
+
Box7,
|
|
915
|
+
{
|
|
916
|
+
flexDirection: "column",
|
|
917
|
+
paddingX: PADDING_X,
|
|
918
|
+
paddingY: 2,
|
|
919
|
+
width: "100%",
|
|
920
|
+
gap: 1,
|
|
921
|
+
children: [
|
|
922
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
923
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: accessIntro }),
|
|
924
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
925
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
926
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
927
|
+
/* @__PURE__ */ jsx7(Box7, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text7, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
928
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
929
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: item.title }),
|
|
930
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
931
|
+
] }) })
|
|
932
|
+
] })
|
|
933
|
+
] }, item.tag)) }),
|
|
934
|
+
/* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
935
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
936
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
937
|
+
/* @__PURE__ */ jsx7(
|
|
938
|
+
NeverLine,
|
|
939
|
+
{
|
|
940
|
+
width: dividerWidth,
|
|
941
|
+
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
942
|
+
}
|
|
943
|
+
),
|
|
944
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
945
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
946
|
+
/* @__PURE__ */ jsx7(
|
|
947
|
+
NeverLine,
|
|
948
|
+
{
|
|
949
|
+
width: dividerWidth,
|
|
950
|
+
segments: [
|
|
951
|
+
{ text: "\u2715", color: COLORS.danger },
|
|
952
|
+
{ text: " " },
|
|
953
|
+
{ text: item, color: COLORS.primary }
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
)
|
|
957
|
+
] }, item)),
|
|
958
|
+
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
959
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
960
|
+
] }),
|
|
961
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
962
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
963
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.accent, children: link.url })
|
|
964
|
+
] }, link.label)) }),
|
|
965
|
+
/* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
966
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
967
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
|
|
968
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "esc" }),
|
|
969
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "] back" })
|
|
970
|
+
] }),
|
|
971
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
972
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
|
|
973
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "space" }),
|
|
974
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "]" }),
|
|
975
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
976
|
+
] })
|
|
977
|
+
] })
|
|
978
|
+
]
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
|
|
491
983
|
// src/ui/Sidebar.tsx
|
|
492
|
-
import { Box as
|
|
984
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
493
985
|
|
|
494
986
|
// src/ui/Steps.tsx
|
|
495
|
-
import { Box as
|
|
987
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
496
988
|
import Spinner from "ink-spinner";
|
|
497
989
|
|
|
498
990
|
// src/core/persistence.ts
|
|
499
991
|
import { mkdir, readFile, writeFile, rm } from "node:fs/promises";
|
|
500
|
-
import { join as
|
|
501
|
-
|
|
502
|
-
// src/core/constants.ts
|
|
503
|
-
import { homedir } from "node:os";
|
|
504
|
-
import { join as join2, resolve } from "node:path";
|
|
505
|
-
function rootDir() {
|
|
506
|
-
return process.env.WIZARD_HOME ?? join2(homedir(), ".algolia");
|
|
507
|
-
}
|
|
508
|
-
function projectSlug(cwd = process.cwd()) {
|
|
509
|
-
return resolve(cwd).replace(/[/\\:]+/g, "-").replace(/^-+/, "") || "root";
|
|
510
|
-
}
|
|
511
|
-
function stateDir(cwd = process.cwd()) {
|
|
512
|
-
return join2(rootDir(), projectSlug(cwd));
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// src/core/persistence.ts
|
|
992
|
+
import { join as join4 } from "node:path";
|
|
516
993
|
var isStepVisible = (s) => s.visible !== false;
|
|
517
|
-
var stateFile = (workflowId) =>
|
|
994
|
+
var stateFile = (workflowId) => join4(stateDir(), `state-${workflowId}.json`);
|
|
518
995
|
async function loadWorkflowState(workflowId) {
|
|
519
996
|
try {
|
|
520
997
|
const raw = await readFile(stateFile(workflowId), "utf8");
|
|
@@ -536,154 +1013,55 @@ async function clearWorkflowState(workflowId) {
|
|
|
536
1013
|
}
|
|
537
1014
|
|
|
538
1015
|
// src/ui/Steps.tsx
|
|
539
|
-
import { jsx as
|
|
540
|
-
function introspectDetail(output) {
|
|
541
|
-
if (!output || typeof output !== "object") return null;
|
|
542
|
-
const o = output;
|
|
543
|
-
if (!o.recordShape || typeof o.recordShape !== "object" || !Array.isArray(o.facets)) {
|
|
544
|
-
return null;
|
|
545
|
-
}
|
|
546
|
-
const fields = Object.entries(o.recordShape).map(
|
|
547
|
-
([name, type]) => ({ name, type: String(type) })
|
|
548
|
-
);
|
|
549
|
-
return { fields, facets: o.facets.map(String) };
|
|
550
|
-
}
|
|
551
|
-
function reviewDetail(output) {
|
|
552
|
-
if (!output || typeof output !== "object") return null;
|
|
553
|
-
const o = output;
|
|
554
|
-
if (!Array.isArray(o.stepSummaries) || typeof o.reviewPrompt !== "string" || !Array.isArray(o.nextSteps)) {
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
const stepSummaries = o.stepSummaries.filter((entry) => {
|
|
558
|
-
if (!entry || typeof entry !== "object") return false;
|
|
559
|
-
const s = entry;
|
|
560
|
-
return typeof s.stepId === "string" && typeof s.stepTitle === "string" && typeof s.summary === "string";
|
|
561
|
-
}).map((s) => ({
|
|
562
|
-
stepId: s.stepId,
|
|
563
|
-
stepTitle: s.stepTitle,
|
|
564
|
-
summary: s.summary
|
|
565
|
-
}));
|
|
566
|
-
return {
|
|
567
|
-
stepSummaries,
|
|
568
|
-
reviewPrompt: o.reviewPrompt,
|
|
569
|
-
nextSteps: o.nextSteps.map(String)
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function plural(count, noun) {
|
|
573
|
-
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
574
|
-
}
|
|
575
|
-
function IntrospectSummary({ detail }) {
|
|
576
|
-
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
577
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
578
|
-
" ",
|
|
579
|
-
"Inferred ",
|
|
580
|
-
plural(detail.fields.length, "field"),
|
|
581
|
-
",",
|
|
582
|
-
" ",
|
|
583
|
-
plural(detail.facets.length, "facet")
|
|
584
|
-
] }),
|
|
585
|
-
detail.fields.map((f) => /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
586
|
-
" ",
|
|
587
|
-
f.name,
|
|
588
|
-
": ",
|
|
589
|
-
f.type
|
|
590
|
-
] }, f.name)),
|
|
591
|
-
/* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
592
|
-
" ",
|
|
593
|
-
"Facets:",
|
|
594
|
-
" ",
|
|
595
|
-
detail.facets.length ? detail.facets.join(", ") : "none"
|
|
596
|
-
] })
|
|
597
|
-
] });
|
|
598
|
-
}
|
|
599
|
-
function ReviewSummary({ detail }) {
|
|
600
|
-
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
601
|
-
detail.stepSummaries.map((s) => /* @__PURE__ */ jsxs5(Text6, { color: "gray", children: [
|
|
602
|
-
" ",
|
|
603
|
-
s.stepTitle,
|
|
604
|
-
": ",
|
|
605
|
-
s.summary
|
|
606
|
-
] }, s.stepId)),
|
|
607
|
-
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.brand, children: [
|
|
608
|
-
" ",
|
|
609
|
-
detail.reviewPrompt
|
|
610
|
-
] }),
|
|
611
|
-
detail.nextSteps.map((step, i) => {
|
|
612
|
-
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
613
|
-
const isWorktreeCommand = step.includes("/worktrees/");
|
|
614
|
-
const color = isIngestCommand ? COLORS.brand : isWorktreeCommand ? COLORS.secondary : "gray";
|
|
615
|
-
return /* @__PURE__ */ jsxs5(
|
|
616
|
-
Text6,
|
|
617
|
-
{
|
|
618
|
-
color,
|
|
619
|
-
bold: isIngestCommand || isWorktreeCommand,
|
|
620
|
-
children: [
|
|
621
|
-
" ",
|
|
622
|
-
"\u2192 ",
|
|
623
|
-
step
|
|
624
|
-
]
|
|
625
|
-
},
|
|
626
|
-
`next-${i}`
|
|
627
|
-
);
|
|
628
|
-
})
|
|
629
|
-
] });
|
|
630
|
-
}
|
|
1016
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
631
1017
|
function Steps() {
|
|
632
1018
|
const { steps } = useWizard();
|
|
633
1019
|
const visibleSteps = steps.filter(isStepVisible);
|
|
634
|
-
return /* @__PURE__ */
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
s.status === "running" ? /* @__PURE__ */ jsx6(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
640
|
-
" ",
|
|
641
|
-
s.title
|
|
642
|
-
] }),
|
|
643
|
-
introspect && /* @__PURE__ */ jsx6(IntrospectSummary, { detail: introspect }),
|
|
644
|
-
review && /* @__PURE__ */ jsx6(ReviewSummary, { detail: review })
|
|
645
|
-
] }, s.id);
|
|
646
|
-
}) });
|
|
1020
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status[s.status], children: [
|
|
1021
|
+
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1022
|
+
" ",
|
|
1023
|
+
s.title
|
|
1024
|
+
] }) }, s.id)) });
|
|
647
1025
|
}
|
|
648
1026
|
function CurrentStep() {
|
|
649
1027
|
const { steps } = useWizard();
|
|
650
1028
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
651
1029
|
if (!currentStep) return null;
|
|
652
|
-
return /* @__PURE__ */
|
|
653
|
-
/* @__PURE__ */
|
|
1030
|
+
return /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
|
|
1031
|
+
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
654
1032
|
" ",
|
|
655
1033
|
` ${currentStep.title}`
|
|
656
1034
|
] });
|
|
657
1035
|
}
|
|
658
1036
|
|
|
659
1037
|
// src/ui/Progress.tsx
|
|
660
|
-
import { Box as
|
|
661
|
-
import { jsx as
|
|
1038
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1039
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
662
1040
|
function Progress() {
|
|
663
1041
|
const { steps, currentStepIndex } = useWizard();
|
|
664
1042
|
const visibleSteps = steps.filter(isStepVisible);
|
|
665
1043
|
if (visibleSteps.length === 0) return null;
|
|
666
1044
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
667
1045
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
668
|
-
return /* @__PURE__ */
|
|
669
|
-
/* @__PURE__ */
|
|
670
|
-
/* @__PURE__ */
|
|
671
|
-
/* @__PURE__ */
|
|
672
|
-
/* @__PURE__ */
|
|
1046
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1047
|
+
/* @__PURE__ */ jsx9(Text9, { color: COLORS.muted, children: "STEP" }),
|
|
1048
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: activeStepNumber }),
|
|
1049
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "/" }),
|
|
1050
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: visibleSteps.length })
|
|
673
1051
|
] });
|
|
674
1052
|
}
|
|
675
1053
|
|
|
676
1054
|
// src/ui/copy/sidebar-commands.ts
|
|
677
1055
|
var sidebarCommands = [
|
|
678
1056
|
{ keyHint: "tab", description: "toggle logs" },
|
|
679
|
-
{ keyHint: "esc", description: "
|
|
1057
|
+
{ keyHint: "esc", description: "exit wizard" }
|
|
680
1058
|
];
|
|
681
1059
|
|
|
682
1060
|
// src/ui/Sidebar.tsx
|
|
683
|
-
import { jsx as
|
|
1061
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
684
1062
|
function Sidebar() {
|
|
685
|
-
return /* @__PURE__ */
|
|
686
|
-
|
|
1063
|
+
return /* @__PURE__ */ jsxs9(
|
|
1064
|
+
Box10,
|
|
687
1065
|
{
|
|
688
1066
|
backgroundColor: "#14171E",
|
|
689
1067
|
width: 30,
|
|
@@ -692,16 +1070,16 @@ function Sidebar() {
|
|
|
692
1070
|
flexDirection: "column",
|
|
693
1071
|
justifyContent: "space-between",
|
|
694
1072
|
children: [
|
|
695
|
-
/* @__PURE__ */
|
|
696
|
-
/* @__PURE__ */
|
|
697
|
-
/* @__PURE__ */
|
|
1073
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
|
|
1074
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1075
|
+
/* @__PURE__ */ jsx10(Steps, {})
|
|
698
1076
|
] }),
|
|
699
|
-
/* @__PURE__ */
|
|
700
|
-
/* @__PURE__ */
|
|
701
|
-
/* @__PURE__ */
|
|
702
|
-
return /* @__PURE__ */
|
|
703
|
-
/* @__PURE__ */
|
|
704
|
-
/* @__PURE__ */
|
|
1077
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
|
|
1078
|
+
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1079
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1080
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1081
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1082
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: c.description })
|
|
705
1083
|
] });
|
|
706
1084
|
}) })
|
|
707
1085
|
] })
|
|
@@ -711,12 +1089,12 @@ function Sidebar() {
|
|
|
711
1089
|
}
|
|
712
1090
|
|
|
713
1091
|
// src/ui/Ribbon.tsx
|
|
714
|
-
import { Box as
|
|
715
|
-
import { jsx as
|
|
1092
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1093
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
716
1094
|
function Ribbon() {
|
|
717
1095
|
const firstCommand = sidebarCommands[0];
|
|
718
|
-
return /* @__PURE__ */
|
|
719
|
-
|
|
1096
|
+
return /* @__PURE__ */ jsxs10(
|
|
1097
|
+
Box11,
|
|
720
1098
|
{
|
|
721
1099
|
backgroundColor: "#14171E",
|
|
722
1100
|
flexDirection: "row",
|
|
@@ -724,11 +1102,11 @@ function Ribbon() {
|
|
|
724
1102
|
paddingX: 2,
|
|
725
1103
|
paddingY: 1,
|
|
726
1104
|
children: [
|
|
727
|
-
/* @__PURE__ */
|
|
728
|
-
/* @__PURE__ */
|
|
729
|
-
/* @__PURE__ */
|
|
730
|
-
/* @__PURE__ */
|
|
731
|
-
/* @__PURE__ */
|
|
1105
|
+
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1106
|
+
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1107
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1108
|
+
/* @__PURE__ */ jsx11(Text11, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1109
|
+
/* @__PURE__ */ jsx11(Text11, { color: COLORS.muted, children: firstCommand.description })
|
|
732
1110
|
] })
|
|
733
1111
|
]
|
|
734
1112
|
}
|
|
@@ -736,75 +1114,205 @@ function Ribbon() {
|
|
|
736
1114
|
}
|
|
737
1115
|
|
|
738
1116
|
// src/ui/App.tsx
|
|
739
|
-
import {
|
|
1117
|
+
import { useState as useState6 } from "react";
|
|
1118
|
+
|
|
1119
|
+
// src/ui/Logs.tsx
|
|
1120
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
|
|
1121
|
+
import { Box as Box12, Text as Text12, measureElement as measureElement2, useInput as useInput5, useWindowSize as useWindowSize4 } from "ink";
|
|
1122
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1123
|
+
var KIND_COLOR = {
|
|
1124
|
+
tool: COLORS.primary,
|
|
1125
|
+
prompt: COLORS.badge
|
|
1126
|
+
};
|
|
1127
|
+
var STATUS_COLOR = {
|
|
1128
|
+
running: COLORS.status.running,
|
|
1129
|
+
error: COLORS.danger
|
|
1130
|
+
};
|
|
1131
|
+
function logNameColor(entry) {
|
|
1132
|
+
return STATUS_COLOR[entry.status] ?? KIND_COLOR[entry.kind];
|
|
1133
|
+
}
|
|
1134
|
+
var ROW_GAP = 1;
|
|
1135
|
+
function truncate2(str, maxWidth) {
|
|
1136
|
+
if (maxWidth <= 0) return "";
|
|
1137
|
+
return str.length > maxWidth ? `${str.slice(0, maxWidth - 1)}\u2026` : str;
|
|
1138
|
+
}
|
|
1139
|
+
function rawInputText(input) {
|
|
1140
|
+
if (input === void 0) return "";
|
|
1141
|
+
const str = typeof input === "string" ? input : JSON.stringify(input);
|
|
1142
|
+
if (!str || str === "{}") return "";
|
|
1143
|
+
return str.replace(/\s+/g, " ").trim();
|
|
1144
|
+
}
|
|
1145
|
+
function formatTimestamp(ms) {
|
|
1146
|
+
const d = new Date(ms);
|
|
1147
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1148
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1149
|
+
}
|
|
1150
|
+
function Logs() {
|
|
1151
|
+
const logs = useWizard((s) => s.logs);
|
|
1152
|
+
const { rows, columns } = useWindowSize4();
|
|
1153
|
+
const viewportRef = useRef2(null);
|
|
1154
|
+
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1155
|
+
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1156
|
+
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1157
|
+
const prevMaxOffsetRef = useRef2(0);
|
|
1158
|
+
useLayoutEffect(() => {
|
|
1159
|
+
if (!viewportRef.current) return;
|
|
1160
|
+
const { width, height } = measureElement2(viewportRef.current);
|
|
1161
|
+
setViewportHeight(height);
|
|
1162
|
+
setViewportWidth(width);
|
|
1163
|
+
}, [rows, columns, logs.length === 0]);
|
|
1164
|
+
let capacity = viewportHeight;
|
|
1165
|
+
for (let i = 0; i < 2; i++) {
|
|
1166
|
+
const hasAbove = scrollOffset > 0;
|
|
1167
|
+
const hasBelow = scrollOffset + capacity < logs.length;
|
|
1168
|
+
capacity = Math.max(
|
|
1169
|
+
viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
|
|
1170
|
+
0
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1174
|
+
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1175
|
+
useLayoutEffect(() => {
|
|
1176
|
+
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1177
|
+
prevMaxOffsetRef.current = maxOffset;
|
|
1178
|
+
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
1179
|
+
}, [maxOffset]);
|
|
1180
|
+
useInput5((_input, key) => {
|
|
1181
|
+
if (!key.upArrow && !key.downArrow) return;
|
|
1182
|
+
setScrollOffset(
|
|
1183
|
+
(o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
|
|
1184
|
+
);
|
|
1185
|
+
});
|
|
1186
|
+
const visible = logs.slice(scrollOffset, scrollOffset + capacity);
|
|
1187
|
+
const hiddenAbove = scrollOffset;
|
|
1188
|
+
const hiddenBelow = logs.length - scrollOffset - visible.length;
|
|
1189
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1190
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
|
|
1191
|
+
/* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
1192
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1193
|
+
"\u2191 ",
|
|
1194
|
+
hiddenAbove,
|
|
1195
|
+
" more"
|
|
1196
|
+
] }),
|
|
1197
|
+
visible.map((entry) => {
|
|
1198
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1199
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1200
|
+
const rawPreview = rawInputText(entry.input);
|
|
1201
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1202
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
1203
|
+
let budget = viewportWidth - timestamp.length - durationText.length - gaps;
|
|
1204
|
+
const name = truncate2(entry.name, budget);
|
|
1205
|
+
budget -= name.length;
|
|
1206
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1207
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1208
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
|
|
1209
|
+
/* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1210
|
+
preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1211
|
+
durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
|
|
1212
|
+
] }, entry.id);
|
|
1213
|
+
}),
|
|
1214
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1215
|
+
"\u2193 ",
|
|
1216
|
+
hiddenBelow,
|
|
1217
|
+
" more"
|
|
1218
|
+
] })
|
|
1219
|
+
] }),
|
|
1220
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1221
|
+
] });
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/lib/events.ts
|
|
1225
|
+
import "zod";
|
|
1226
|
+
function track(event, payload) {
|
|
1227
|
+
const token = getAuthToken();
|
|
1228
|
+
if (!token) return;
|
|
1229
|
+
const userId = useWizard.getState().user?.userId;
|
|
1230
|
+
if (!userId) return;
|
|
1231
|
+
void proxyFetch(`${PROXY_BASE_URL}/events`, {
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
headers: {
|
|
1234
|
+
"content-type": "application/json",
|
|
1235
|
+
authorization: `Bearer ${token}`
|
|
1236
|
+
},
|
|
1237
|
+
body: JSON.stringify({ userId, event, properties: payload })
|
|
1238
|
+
}).catch((err) => {
|
|
1239
|
+
logger.warn({ err, event }, "failed to send analytics event");
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// src/ui/App.tsx
|
|
1244
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
740
1245
|
function App() {
|
|
741
|
-
const { phase, error } = useWizard();
|
|
742
|
-
const implementOutput = useWizard((s) => {
|
|
743
|
-
return s.steps.find((st) => st.id === "ingestion")?.output;
|
|
744
|
-
});
|
|
745
|
-
const ingestCommand = implementOutput?.ingestCommand;
|
|
746
|
-
const openWorktreeCommand = implementOutput?.worktreePath ? `cd ${shellQuote(implementOutput.worktreePath)}` : void 0;
|
|
1246
|
+
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
747
1247
|
const { exit } = useApp();
|
|
748
|
-
const { columns, rows } =
|
|
749
|
-
const [
|
|
1248
|
+
const { columns, rows } = useWindowSize5();
|
|
1249
|
+
const [showLogs, setShowLogs] = useState6(false);
|
|
750
1250
|
const finished = phase === "done" || phase === "error";
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}, [copied]);
|
|
756
|
-
useInput3(
|
|
757
|
-
(input, key) => {
|
|
758
|
-
if (key.return || key.escape) {
|
|
1251
|
+
const currentStep = steps[currentStepIndex];
|
|
1252
|
+
useInput6(
|
|
1253
|
+
(_input, key) => {
|
|
1254
|
+
if (key.return) {
|
|
759
1255
|
exit();
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
if (ingestCommand && (input === "c" || input === "C")) {
|
|
763
|
-
copyToClipboard(ingestCommand);
|
|
764
|
-
setCopied("ingest");
|
|
765
|
-
}
|
|
766
|
-
if (openWorktreeCommand && (input === "d" || input === "D")) {
|
|
767
|
-
copyToClipboard(openWorktreeCommand);
|
|
768
|
-
setCopied("worktree");
|
|
769
1256
|
}
|
|
770
1257
|
},
|
|
771
1258
|
{ isActive: finished }
|
|
772
1259
|
);
|
|
1260
|
+
useInput6((_input, key) => {
|
|
1261
|
+
if (phase === "idle" || phase === "preflight") return;
|
|
1262
|
+
if (key.tab) {
|
|
1263
|
+
setShowLogs(!showLogs);
|
|
1264
|
+
track("AI Wizard Interaction", {
|
|
1265
|
+
context: "global",
|
|
1266
|
+
key: "tab",
|
|
1267
|
+
currentStep: currentStep?.id
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
|
|
1272
|
+
useInput6((_input, key) => {
|
|
1273
|
+
if (escOwnedElsewhere) return;
|
|
1274
|
+
if (key.escape) {
|
|
1275
|
+
track("AI Wizard Interaction", {
|
|
1276
|
+
context: "global",
|
|
1277
|
+
key: "esc",
|
|
1278
|
+
// No step is active until `startWorkflow` — report the phase instead.
|
|
1279
|
+
currentStep: currentStep?.id ?? phase
|
|
1280
|
+
});
|
|
1281
|
+
exit();
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
773
1284
|
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
774
1285
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
775
1286
|
const showSidebar = flexDirection === "row";
|
|
776
|
-
return /* @__PURE__ */
|
|
777
|
-
|
|
1287
|
+
return /* @__PURE__ */ jsxs12(
|
|
1288
|
+
Box13,
|
|
778
1289
|
{
|
|
779
1290
|
backgroundColor: COLORS.bg.main,
|
|
780
1291
|
flexDirection: "row",
|
|
781
1292
|
width: columns,
|
|
782
1293
|
minHeight: rows,
|
|
783
1294
|
children: [
|
|
784
|
-
mainWindowVisible && /* @__PURE__ */
|
|
785
|
-
|
|
1295
|
+
mainWindowVisible && /* @__PURE__ */ jsxs12(
|
|
1296
|
+
Box13,
|
|
786
1297
|
{
|
|
787
1298
|
flexDirection,
|
|
788
1299
|
width: "100%",
|
|
789
1300
|
justifyContent: "space-between",
|
|
790
1301
|
children: [
|
|
791
|
-
/* @__PURE__ */
|
|
792
|
-
/* @__PURE__ */
|
|
793
|
-
|
|
1302
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
|
|
1303
|
+
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1304
|
+
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1305
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1306
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
794
1307
|
"\u2716 ",
|
|
795
1308
|
error
|
|
796
|
-
] }) })
|
|
797
|
-
finished && (ingestCommand || openWorktreeCommand) && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
798
|
-
ingestCommand && /* @__PURE__ */ jsx10(Text10, { color: COLORS.brand, children: copied === "ingest" ? "\u2713 Ingestion command copied to clipboard" : 'Press "c" to copy the ingestion command' }),
|
|
799
|
-
openWorktreeCommand && /* @__PURE__ */ jsx10(Text10, { color: COLORS.secondary, children: copied === "worktree" ? "\u2713 Worktree command copied to clipboard" : 'Press "d" to copy the command to open the worktree' })
|
|
800
|
-
] })
|
|
1309
|
+
] }) })
|
|
801
1310
|
] }),
|
|
802
|
-
/* @__PURE__ */
|
|
803
|
-
showSidebar ? /* @__PURE__ */ jsx10(Sidebar, {}) : /* @__PURE__ */ jsx10(Ribbon, {})
|
|
1311
|
+
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
804
1312
|
]
|
|
805
1313
|
}
|
|
806
1314
|
),
|
|
807
|
-
(phase === "idle" || phase === "preflight") && /* @__PURE__ */
|
|
1315
|
+
(phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
|
|
808
1316
|
]
|
|
809
1317
|
}
|
|
810
1318
|
);
|
|
@@ -815,8 +1323,8 @@ import "zod";
|
|
|
815
1323
|
|
|
816
1324
|
// src/core/config.ts
|
|
817
1325
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
818
|
-
import { join as
|
|
819
|
-
var configFile = () =>
|
|
1326
|
+
import { join as join5 } from "node:path";
|
|
1327
|
+
var configFile = () => join5(stateDir(), "config.json");
|
|
820
1328
|
var DEFAULT_CONFIG = {
|
|
821
1329
|
version: 1,
|
|
822
1330
|
aiConsent: false,
|
|
@@ -840,128 +1348,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
840
1348
|
await saveConfig(config);
|
|
841
1349
|
}
|
|
842
1350
|
|
|
843
|
-
// src/lib/algoliaCli.ts
|
|
844
|
-
import { spawn } from "node:child_process";
|
|
845
|
-
import { createRequire } from "node:module";
|
|
846
|
-
var require2 = createRequire(import.meta.url);
|
|
847
|
-
function algoliaCliEntry() {
|
|
848
|
-
return require2.resolve("@algolia/cli/bin/run.js");
|
|
849
|
-
}
|
|
850
|
-
function runAlgoliaCli(args) {
|
|
851
|
-
return new Promise((resolve4, reject) => {
|
|
852
|
-
const child = spawn(process.execPath, [algoliaCliEntry(), ...args]);
|
|
853
|
-
let stdout = "";
|
|
854
|
-
let stderr = "";
|
|
855
|
-
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
856
|
-
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
857
|
-
child.on("error", reject);
|
|
858
|
-
child.on("close", (code) => {
|
|
859
|
-
if (code === 0) {
|
|
860
|
-
resolve4(stdout);
|
|
861
|
-
} else {
|
|
862
|
-
const detail = stderr.trim() || stdout.trim();
|
|
863
|
-
reject(
|
|
864
|
-
new Error(
|
|
865
|
-
`Algolia CLI \`${args.join(" ")}\` failed (exit ${code})${detail ? `: ${detail}` : ""}`
|
|
866
|
-
)
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
async function getUser() {
|
|
873
|
-
let raw;
|
|
874
|
-
try {
|
|
875
|
-
raw = await runAlgoliaCli(["auth", "get", "--with-access-token"]);
|
|
876
|
-
} catch {
|
|
877
|
-
return null;
|
|
878
|
-
}
|
|
879
|
-
try {
|
|
880
|
-
return toUserInfo(JSON.parse(raw));
|
|
881
|
-
} catch {
|
|
882
|
-
return null;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
function runAuthLogin() {
|
|
886
|
-
return new Promise((resolve4, reject) => {
|
|
887
|
-
const child = spawn(
|
|
888
|
-
process.execPath,
|
|
889
|
-
[algoliaCliEntry(), "auth", "login", "--default"],
|
|
890
|
-
{ stdio: "inherit" }
|
|
891
|
-
);
|
|
892
|
-
child.on("error", reject);
|
|
893
|
-
child.on("close", (code) => {
|
|
894
|
-
if (code === 0) resolve4();
|
|
895
|
-
else reject(new Error(`Algolia authentication failed (exit ${code}).`));
|
|
896
|
-
});
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// src/lib/auth.ts
|
|
901
|
-
function getAuthToken() {
|
|
902
|
-
return useWizard.getState().user?.token || null;
|
|
903
|
-
}
|
|
904
|
-
var inFlightRefresh = null;
|
|
905
|
-
function refreshAuthToken() {
|
|
906
|
-
inFlightRefresh ??= (async () => {
|
|
907
|
-
try {
|
|
908
|
-
const raw = await runAlgoliaCli(["auth", "get", "--with-access-token"]);
|
|
909
|
-
const user2 = toUserInfo(JSON.parse(raw));
|
|
910
|
-
useWizard.getState().setUser(user2);
|
|
911
|
-
return user2.token;
|
|
912
|
-
} catch {
|
|
913
|
-
return null;
|
|
914
|
-
} finally {
|
|
915
|
-
inFlightRefresh = null;
|
|
916
|
-
}
|
|
917
|
-
})();
|
|
918
|
-
return inFlightRefresh;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// src/lib/logger.ts
|
|
922
|
-
import pino from "pino";
|
|
923
|
-
import { join as join5, dirname as dirname2 } from "node:path";
|
|
924
|
-
import { devNull } from "node:os";
|
|
925
|
-
import { mkdirSync, openSync, closeSync } from "node:fs";
|
|
926
|
-
var STDERR_FD = 2;
|
|
927
|
-
function resolveDest() {
|
|
928
|
-
const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join5(stateDir(), "wizard.log");
|
|
929
|
-
try {
|
|
930
|
-
mkdirSync(dirname2(target), { recursive: true });
|
|
931
|
-
closeSync(openSync(target, "a"));
|
|
932
|
-
return target;
|
|
933
|
-
} catch {
|
|
934
|
-
return STDERR_FD;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
function logDestination() {
|
|
938
|
-
return pino.destination({ dest: resolveDest(), sync: false });
|
|
939
|
-
}
|
|
940
|
-
var logger = pino(
|
|
941
|
-
{ level: process.env.LOG_LEVEL ?? "info" },
|
|
942
|
-
logDestination()
|
|
943
|
-
);
|
|
944
|
-
|
|
945
|
-
// src/lib/proxyFetch.ts
|
|
946
|
-
var PROXY_BASE_URL = process.env.PROXY_BASE_URL ?? "https://proxy-624203421261.us-east4.run.app";
|
|
947
|
-
var PROXY_AUTH_REJECTED_HEADER = "x-wizard-proxy-auth";
|
|
948
|
-
var proxyFetch = async (input, init) => {
|
|
949
|
-
const req = new Request(input, init);
|
|
950
|
-
const retry = req.clone();
|
|
951
|
-
const res = await fetch(req);
|
|
952
|
-
if (res.status !== 401) return res;
|
|
953
|
-
if (res.headers.get(PROXY_AUTH_REJECTED_HEADER) !== "rejected") return res;
|
|
954
|
-
const fresh = await refreshAuthToken();
|
|
955
|
-
if (!fresh) return res;
|
|
956
|
-
const headers = new Headers(retry.headers);
|
|
957
|
-
if (headers.has("authorization")) {
|
|
958
|
-
headers.set("authorization", `Bearer ${fresh}`);
|
|
959
|
-
} else {
|
|
960
|
-
headers.set("x-api-key", fresh);
|
|
961
|
-
}
|
|
962
|
-
return fetch(new Request(retry, { headers }));
|
|
963
|
-
};
|
|
964
|
-
|
|
965
1351
|
// src/lib/telemetry.ts
|
|
966
1352
|
function isTelemetryEnabled() {
|
|
967
1353
|
return Boolean(getAuthToken()) && !process.env.VITEST && process.env.WIZARD_TELEMETRY !== "false";
|
|
@@ -1124,25 +1510,6 @@ function trackWorkflowError(ctx) {
|
|
|
1124
1510
|
);
|
|
1125
1511
|
}
|
|
1126
1512
|
|
|
1127
|
-
// src/lib/events.ts
|
|
1128
|
-
import "zod";
|
|
1129
|
-
function track(event, payload) {
|
|
1130
|
-
const token = getAuthToken();
|
|
1131
|
-
if (!token) return;
|
|
1132
|
-
const userId = useWizard.getState().user?.userId;
|
|
1133
|
-
if (!userId) return;
|
|
1134
|
-
void proxyFetch(`${PROXY_BASE_URL}/events`, {
|
|
1135
|
-
method: "POST",
|
|
1136
|
-
headers: {
|
|
1137
|
-
"content-type": "application/json",
|
|
1138
|
-
authorization: `Bearer ${token}`
|
|
1139
|
-
},
|
|
1140
|
-
body: JSON.stringify({ userId, event, properties: payload })
|
|
1141
|
-
}).catch((err) => {
|
|
1142
|
-
logger.warn({ err, event }, "failed to send analytics event");
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
1513
|
// src/core/orchestrator.ts
|
|
1147
1514
|
function defineStep(step) {
|
|
1148
1515
|
return { visible: true, ...step };
|
|
@@ -1186,12 +1553,14 @@ async function ensureConsent() {
|
|
|
1186
1553
|
if (config.aiConsent) return;
|
|
1187
1554
|
const store2 = useWizard.getState();
|
|
1188
1555
|
const answer = await store2.requestUserInput({
|
|
1189
|
-
prompt:
|
|
1190
|
-
promptType: "
|
|
1556
|
+
prompt: "Wizard will make AI-authored changes to this repository.",
|
|
1557
|
+
promptType: "spaceToContinue",
|
|
1191
1558
|
options: []
|
|
1192
1559
|
});
|
|
1193
|
-
if (
|
|
1194
|
-
throw new Error(
|
|
1560
|
+
if (answer !== true) {
|
|
1561
|
+
throw new Error(
|
|
1562
|
+
"AI consent declined \u2014 cannot proceed. If you change your mind, just run the Wizard again!"
|
|
1563
|
+
);
|
|
1195
1564
|
}
|
|
1196
1565
|
config.aiConsent = true;
|
|
1197
1566
|
await saveConfig(config);
|
|
@@ -1212,6 +1581,10 @@ async function makeContext(state) {
|
|
|
1212
1581
|
completedSteps,
|
|
1213
1582
|
getStepOutput: (stepId) => outputs[stepId],
|
|
1214
1583
|
requestUserInput: (prompt) => useWizard.getState().requestUserInput(prompt),
|
|
1584
|
+
notify: (notice) => useWizard.getState().pushNotice(notice),
|
|
1585
|
+
clearNotices: () => useWizard.getState().clearNotices(),
|
|
1586
|
+
logStart: (name, input) => useWizard.getState().logStart("tool", name, input),
|
|
1587
|
+
logEnd: (id, status) => useWizard.getState().logEnd(id, status),
|
|
1215
1588
|
updateAlgoliaState: (key, value) => {
|
|
1216
1589
|
state.algoliaState[key] = value;
|
|
1217
1590
|
},
|
|
@@ -1243,6 +1616,7 @@ async function runStep(state, index, step, appId) {
|
|
|
1243
1616
|
store2.setActiveStep(index);
|
|
1244
1617
|
store2.syncSteps([...state.steps], index);
|
|
1245
1618
|
await saveWorkflowState(state);
|
|
1619
|
+
await markInteraction();
|
|
1246
1620
|
const ctx = await makeContext(state);
|
|
1247
1621
|
const raw = await step.run(ctx);
|
|
1248
1622
|
const output = step.outputSchema.parse(raw);
|
|
@@ -1263,7 +1637,6 @@ async function runStep(state, index, step, appId) {
|
|
|
1263
1637
|
async function runWorkflow(workflow2, appId) {
|
|
1264
1638
|
const store2 = useWizard.getState();
|
|
1265
1639
|
try {
|
|
1266
|
-
await ensureConsent();
|
|
1267
1640
|
const persisted = await loadWorkflowState(workflow2.id);
|
|
1268
1641
|
const state = (persisted && reconcileWorkflowState(persisted, workflow2)) ?? initWorkflowState(workflow2, nowIso());
|
|
1269
1642
|
ensureExecutedStepCount(state);
|
|
@@ -1275,6 +1648,7 @@ async function runWorkflow(workflow2, appId) {
|
|
|
1275
1648
|
},
|
|
1276
1649
|
[...state.steps]
|
|
1277
1650
|
);
|
|
1651
|
+
await ensureConsent();
|
|
1278
1652
|
trackWorkflowStart({ workflowId: workflow2.id, appId });
|
|
1279
1653
|
for (let i = state.currentStepIndex; i < workflow2.steps.length; i++) {
|
|
1280
1654
|
await runStep(state, i, workflow2.steps[i], appId);
|
|
@@ -1384,7 +1758,7 @@ async function loadActiveProfile() {
|
|
|
1384
1758
|
}
|
|
1385
1759
|
|
|
1386
1760
|
// src/workflows/default.ts
|
|
1387
|
-
import { z as
|
|
1761
|
+
import { z as z25 } from "zod";
|
|
1388
1762
|
|
|
1389
1763
|
// src/actions/listIndices.ts
|
|
1390
1764
|
import { z as z3 } from "zod";
|
|
@@ -1863,8 +2237,11 @@ function verifyImplementationTool() {
|
|
|
1863
2237
|
// src/lib/tools/generateRecord.ts
|
|
1864
2238
|
import { tool as tool9, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
1865
2239
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
1866
|
-
import { nanoid } from "nanoid";
|
|
2240
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
2241
|
+
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2242
|
+
import { dirname as dirname6 } from "node:path";
|
|
1867
2243
|
import z12 from "zod";
|
|
2244
|
+
var DATA_DIR = ".algolia-wizard/data";
|
|
1868
2245
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
1869
2246
|
var MAX_RECORDS = 100;
|
|
1870
2247
|
var BATCH_SIZE = 10;
|
|
@@ -1872,9 +2249,9 @@ var MAX_BATCH_ATTEMPTS = 3;
|
|
|
1872
2249
|
var anthropic = createAnthropic({
|
|
1873
2250
|
apiKey: process.env.PROVIDER_API_KEY ?? ""
|
|
1874
2251
|
});
|
|
1875
|
-
function generateRecordTool() {
|
|
2252
|
+
function generateRecordTool(ctx) {
|
|
1876
2253
|
return tool9({
|
|
1877
|
-
description: "Generate realistic sample records for an entity. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values and returns
|
|
2254
|
+
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.",
|
|
1878
2255
|
inputSchema: z12.object({
|
|
1879
2256
|
entityName: z12.string().describe("Name of the entity to generate records for."),
|
|
1880
2257
|
attributes: z12.array(z12.string()).describe("Attribute names each record must contain."),
|
|
@@ -1906,7 +2283,7 @@ function generateRecordTool() {
|
|
|
1906
2283
|
// would otherwise drift to the same high-probability values and
|
|
1907
2284
|
// collide across batches. This per-batch seed pushes each call
|
|
1908
2285
|
// into a different region of the output space.
|
|
1909
|
-
`Variety seed: ${
|
|
2286
|
+
`Variety seed: ${nanoid2()}. Use it to diversify values.`
|
|
1910
2287
|
].filter(Boolean).join("\n")
|
|
1911
2288
|
});
|
|
1912
2289
|
return output.records;
|
|
@@ -1928,10 +2305,23 @@ function generateRecordTool() {
|
|
|
1928
2305
|
const batches = await Promise.all(batchSizes.map(generateBatch));
|
|
1929
2306
|
const records = batches.flat().map((record) => ({
|
|
1930
2307
|
...record,
|
|
1931
|
-
objectID:
|
|
2308
|
+
objectID: nanoid2()
|
|
1932
2309
|
}));
|
|
1933
|
-
|
|
1934
|
-
|
|
2310
|
+
const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2311
|
+
const relPath = `${DATA_DIR}/${slug}.json`;
|
|
2312
|
+
const resolved = resolveInRoot(ctx, relPath);
|
|
2313
|
+
if (resolved.ok === false) return resolved.error;
|
|
2314
|
+
if (await hasSymlinkParent(ctx, resolved.target)) {
|
|
2315
|
+
return `Refused: ${resolved.target} is outside the repo root (${ctx.root}).`;
|
|
2316
|
+
}
|
|
2317
|
+
await mkdir5(dirname6(resolved.target), { recursive: true });
|
|
2318
|
+
await writeFile5(resolved.target, JSON.stringify(records, null, 2), "utf8");
|
|
2319
|
+
logger.info({ entityName, count: records.length, relPath }, "generateRecord wrote records to disk");
|
|
2320
|
+
return {
|
|
2321
|
+
filePath: relPath,
|
|
2322
|
+
count: records.length,
|
|
2323
|
+
message: `Wrote ${records.length} records to ${relPath}. Read and parse this file in the script at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) \u2014 do not inline the records as literals.`
|
|
2324
|
+
};
|
|
1935
2325
|
} catch (err) {
|
|
1936
2326
|
return `Error generating records: ${err.message}`;
|
|
1937
2327
|
}
|
|
@@ -1939,6 +2329,25 @@ function generateRecordTool() {
|
|
|
1939
2329
|
});
|
|
1940
2330
|
}
|
|
1941
2331
|
|
|
2332
|
+
// src/lib/tools/notifyUser.ts
|
|
2333
|
+
import { tool as tool10 } from "ai";
|
|
2334
|
+
import z13 from "zod";
|
|
2335
|
+
function notifyUserTool() {
|
|
2336
|
+
return tool10({
|
|
2337
|
+
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.`,
|
|
2338
|
+
inputSchema: z13.object({
|
|
2339
|
+
message: z13.string().describe(
|
|
2340
|
+
"Short, plain-language description of what you are doing now."
|
|
2341
|
+
)
|
|
2342
|
+
}),
|
|
2343
|
+
execute: async ({ message }) => {
|
|
2344
|
+
logger.info({ message }, "called notifyUser tool");
|
|
2345
|
+
useWizard.getState().pushNotice({ messages: [message] });
|
|
2346
|
+
return "ok";
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
1942
2351
|
// src/lib/tools/context.ts
|
|
1943
2352
|
var DEFAULT_TOOL_LIMITS = {
|
|
1944
2353
|
list: 10,
|
|
@@ -1956,20 +2365,45 @@ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
|
1956
2365
|
}
|
|
1957
2366
|
|
|
1958
2367
|
// src/lib/tools/index.ts
|
|
2368
|
+
function withLogging(name, def) {
|
|
2369
|
+
const execute = def.execute;
|
|
2370
|
+
if (!execute) return def;
|
|
2371
|
+
return {
|
|
2372
|
+
...def,
|
|
2373
|
+
execute: async (input, options) => {
|
|
2374
|
+
const id = useWizard.getState().logStart("tool", name, input);
|
|
2375
|
+
try {
|
|
2376
|
+
const output = await execute(input, options);
|
|
2377
|
+
useWizard.getState().logEnd(id, "success");
|
|
2378
|
+
return output;
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
useWizard.getState().logEnd(id, "error");
|
|
2381
|
+
throw err;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
1959
2386
|
function createTools(ctx, { output, tools }) {
|
|
1960
2387
|
const all = {
|
|
1961
|
-
listFiles: listFilesTool(ctx),
|
|
1962
|
-
changeDirectory: changeDirectoryTool(ctx),
|
|
1963
|
-
reportStatus: reportStatusTool(output),
|
|
1964
|
-
readFile: readFileTool(ctx),
|
|
1965
|
-
writeFile: writeFileTool(ctx),
|
|
1966
|
-
writeCredentials:
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2388
|
+
listFiles: withLogging("listFiles", listFilesTool(ctx)),
|
|
2389
|
+
changeDirectory: withLogging("changeDirectory", changeDirectoryTool(ctx)),
|
|
2390
|
+
reportStatus: withLogging("reportStatus", reportStatusTool(output)),
|
|
2391
|
+
readFile: withLogging("readFile", readFileTool(ctx)),
|
|
2392
|
+
writeFile: withLogging("writeFile", writeFileTool(ctx)),
|
|
2393
|
+
writeCredentials: withLogging(
|
|
2394
|
+
"writeCredentials",
|
|
2395
|
+
writeCredentialsTool(ctx)
|
|
2396
|
+
),
|
|
2397
|
+
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2398
|
+
verifyImplementation: withLogging(
|
|
2399
|
+
"verifyImplementation",
|
|
2400
|
+
verifyImplementationTool()
|
|
2401
|
+
),
|
|
2402
|
+
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2403
|
+
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
1970
2404
|
};
|
|
1971
2405
|
if (!tools) return all;
|
|
1972
|
-
const selection = /* @__PURE__ */ new Set([...tools, "reportStatus"]);
|
|
2406
|
+
const selection = /* @__PURE__ */ new Set([...tools, "reportStatus", "notifyUser"]);
|
|
1973
2407
|
return Object.fromEntries(
|
|
1974
2408
|
Object.entries(all).filter(([name]) => selection.has(name))
|
|
1975
2409
|
);
|
|
@@ -2002,16 +2436,19 @@ async function runAgent(req) {
|
|
|
2002
2436
|
const toolContext = createToolContext();
|
|
2003
2437
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2004
2438
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2005
|
-
const instructions =
|
|
2439
|
+
const instructions = [
|
|
2006
2440
|
...req.instructions ?? [],
|
|
2007
|
-
|
|
2008
|
-
|
|
2441
|
+
...hasReadTools ? [
|
|
2442
|
+
"When you need to read or search multiple files, issue those tool calls together in one step rather than one at a time."
|
|
2443
|
+
] : [],
|
|
2444
|
+
"Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use."
|
|
2445
|
+
];
|
|
2009
2446
|
const agent = new ToolLoopAgent({
|
|
2010
2447
|
model: anthropic2(MODEL_BY_SIZE[req.modelSize ?? "medium"]),
|
|
2011
2448
|
// Cache tools + system on the last system block. Tools render before
|
|
2012
2449
|
// system, so one breakpoint here caches both, reused on every loop turn
|
|
2013
2450
|
// after the first.
|
|
2014
|
-
instructions: instructions
|
|
2451
|
+
instructions: instructions.map((i, idx, arr) => {
|
|
2015
2452
|
return {
|
|
2016
2453
|
role: "system",
|
|
2017
2454
|
content: i,
|
|
@@ -2079,10 +2516,10 @@ async function runAgent(req) {
|
|
|
2079
2516
|
}
|
|
2080
2517
|
|
|
2081
2518
|
// src/actions/detectLanguage.ts
|
|
2082
|
-
import
|
|
2083
|
-
var detectLanguageSchema =
|
|
2084
|
-
languages:
|
|
2085
|
-
frameworks:
|
|
2519
|
+
import z16 from "zod";
|
|
2520
|
+
var detectLanguageSchema = z16.object({
|
|
2521
|
+
languages: z16.array(z16.object({ name: z16.string(), version: z16.string() })),
|
|
2522
|
+
frameworks: z16.array(z16.object({ name: z16.string(), version: z16.string() }))
|
|
2086
2523
|
});
|
|
2087
2524
|
var detectLanguage = () => runAgent({
|
|
2088
2525
|
instructions: [
|
|
@@ -2100,31 +2537,31 @@ var detectLanguage = () => runAgent({
|
|
|
2100
2537
|
});
|
|
2101
2538
|
|
|
2102
2539
|
// src/actions/analyzeCodebase.ts
|
|
2103
|
-
import
|
|
2540
|
+
import z17 from "zod";
|
|
2104
2541
|
var READONLY_TOOLS = [
|
|
2105
2542
|
"listFiles",
|
|
2106
2543
|
"changeDirectory",
|
|
2107
2544
|
"readFile",
|
|
2108
2545
|
"searchFiles"
|
|
2109
2546
|
];
|
|
2110
|
-
var ingestionAnalysisSchema =
|
|
2111
|
-
ingestionAnalysis:
|
|
2112
|
-
|
|
2113
|
-
name:
|
|
2114
|
-
paths:
|
|
2547
|
+
var ingestionAnalysisSchema = z17.object({
|
|
2548
|
+
ingestionAnalysis: z17.array(
|
|
2549
|
+
z17.object({
|
|
2550
|
+
name: z17.string(),
|
|
2551
|
+
paths: z17.array(z17.string()),
|
|
2115
2552
|
// indexable fields the agent found for this entity
|
|
2116
|
-
attributes:
|
|
2553
|
+
attributes: z17.array(z17.string())
|
|
2117
2554
|
})
|
|
2118
2555
|
)
|
|
2119
2556
|
});
|
|
2120
|
-
var searchImplementationAnalysisSchema =
|
|
2121
|
-
searchImplementationAnalysis:
|
|
2557
|
+
var searchImplementationAnalysisSchema = z17.object({
|
|
2558
|
+
searchImplementationAnalysis: z17.string()
|
|
2122
2559
|
});
|
|
2123
|
-
var verificationSchema =
|
|
2124
|
-
verification:
|
|
2560
|
+
var verificationSchema = z17.object({
|
|
2561
|
+
verification: z17.array(z17.string())
|
|
2125
2562
|
});
|
|
2126
2563
|
var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
|
|
2127
|
-
var analyzeCodebaseSchema =
|
|
2564
|
+
var analyzeCodebaseSchema = z17.object({
|
|
2128
2565
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
2129
2566
|
searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
|
|
2130
2567
|
verification: verificationSchema.shape.verification.optional(),
|
|
@@ -2186,7 +2623,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2186
2623
|
// package.json
|
|
2187
2624
|
var package_default = {
|
|
2188
2625
|
name: "@algolia/wizard",
|
|
2189
|
-
version: "0.
|
|
2626
|
+
version: "0.3.0-rc.43.6",
|
|
2190
2627
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2191
2628
|
type: "module",
|
|
2192
2629
|
engines: {
|
|
@@ -2200,9 +2637,9 @@ var package_default = {
|
|
|
2200
2637
|
"docs"
|
|
2201
2638
|
],
|
|
2202
2639
|
scripts: {
|
|
2203
|
-
"build:proxy":
|
|
2204
|
-
build: "
|
|
2205
|
-
"dev:proxy": "NODE_OPTIONS=--use-system-ca tsx watch src/proxy/index.ts",
|
|
2640
|
+
"build:proxy": "node scripts/build.mjs proxy",
|
|
2641
|
+
build: "node scripts/build.mjs",
|
|
2642
|
+
"dev:proxy": "touch .env && NODE_OPTIONS=--use-system-ca tsx watch --env-file=.env src/proxy/index.ts",
|
|
2206
2643
|
dev: "touch .env && tsx --env-file=.env ./src/main.tsx",
|
|
2207
2644
|
"env:load": "pnpm exec -- varlock load",
|
|
2208
2645
|
prepare: "husky",
|
|
@@ -2307,8 +2744,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
|
|
|
2307
2744
|
}
|
|
2308
2745
|
|
|
2309
2746
|
// src/actions/confirmLanguage.ts
|
|
2310
|
-
import
|
|
2311
|
-
var confirmLanguageSchema =
|
|
2747
|
+
import z19 from "zod";
|
|
2748
|
+
var confirmLanguageSchema = z19.object({
|
|
2312
2749
|
languages: detectLanguageSchema.shape.languages
|
|
2313
2750
|
});
|
|
2314
2751
|
async function confirmLanguage(ctx) {
|
|
@@ -2329,8 +2766,8 @@ async function confirmLanguage(ctx) {
|
|
|
2329
2766
|
}
|
|
2330
2767
|
|
|
2331
2768
|
// src/actions/confirmFramework.ts
|
|
2332
|
-
import
|
|
2333
|
-
var confirmFrameworkSchema =
|
|
2769
|
+
import z20 from "zod";
|
|
2770
|
+
var confirmFrameworkSchema = z20.object({
|
|
2334
2771
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
2335
2772
|
});
|
|
2336
2773
|
var CURATED_FRAMEWORKS = [
|
|
@@ -2458,8 +2895,8 @@ async function promptUser(ctx, params) {
|
|
|
2458
2895
|
}
|
|
2459
2896
|
|
|
2460
2897
|
// src/actions/confirmEntities.ts
|
|
2461
|
-
import
|
|
2462
|
-
var confirmEntitiesSchema =
|
|
2898
|
+
import z21 from "zod";
|
|
2899
|
+
var confirmEntitiesSchema = z21.object({
|
|
2463
2900
|
// Final detection — the focused re-run may supersede project-scan's.
|
|
2464
2901
|
ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
|
|
2465
2902
|
confirmedEntities: confirmedEntitiesFieldSchema
|
|
@@ -2529,17 +2966,15 @@ async function confirmEntities(ctx) {
|
|
|
2529
2966
|
}
|
|
2530
2967
|
|
|
2531
2968
|
// src/actions/review.ts
|
|
2532
|
-
import { z as
|
|
2533
|
-
var reviewSchema =
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
)
|
|
2541
|
-
reviewPrompt: z21.string(),
|
|
2542
|
-
nextSteps: z21.array(z21.string())
|
|
2969
|
+
import { z as z22 } from "zod";
|
|
2970
|
+
var reviewSchema = z22.object({
|
|
2971
|
+
// Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
|
|
2972
|
+
// not one entry per workflow step — a step's raw output can be a long,
|
|
2973
|
+
// multi-paragraph blob (see implement.ts's summaries.join), and mirroring
|
|
2974
|
+
// that 1:1 is what made the old per-step summary an unreadable wall of text.
|
|
2975
|
+
summaryPoints: z22.array(z22.string()),
|
|
2976
|
+
reviewPrompt: z22.string(),
|
|
2977
|
+
nextSteps: z22.array(z22.string())
|
|
2543
2978
|
});
|
|
2544
2979
|
function formatCompletedSteps(steps) {
|
|
2545
2980
|
if (!steps.length) return "(no prior steps completed)";
|
|
@@ -2549,32 +2984,55 @@ Output:
|
|
|
2549
2984
|
${JSON.stringify(s.output, null, 2)}`
|
|
2550
2985
|
).join("\n\n");
|
|
2551
2986
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2987
|
+
function formatReviewSummary(result) {
|
|
2988
|
+
const nextStepLines = result.nextSteps.map((step) => {
|
|
2989
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
2990
|
+
const isWorktreeCommand = step.includes("/worktrees/");
|
|
2991
|
+
return {
|
|
2992
|
+
text: `\u2192 ${step}`,
|
|
2993
|
+
color: isIngestCommand ? COLORS.brand : isWorktreeCommand ? COLORS.secondary : void 0,
|
|
2994
|
+
bold: isIngestCommand || isWorktreeCommand
|
|
2995
|
+
};
|
|
2996
|
+
});
|
|
2997
|
+
return [
|
|
2998
|
+
// Plain lines, same as nextSteps' un-highlighted entries — the summary is
|
|
2999
|
+
// an overview, not a call to action, so it gets no arrow/color/bold.
|
|
3000
|
+
...result.summaryPoints,
|
|
3001
|
+
{ text: result.reviewPrompt, color: COLORS.brand },
|
|
3002
|
+
...nextStepLines
|
|
3003
|
+
];
|
|
3004
|
+
}
|
|
3005
|
+
var reviewStep = async (ctx, options) => {
|
|
3006
|
+
const result = await runAgent({
|
|
3007
|
+
instructions: [
|
|
3008
|
+
"Summarize what was accomplished in the workflow, leaving out verbose details.",
|
|
3009
|
+
"Base your summary only on the step outputs provided \u2014 do not read the repository.",
|
|
3010
|
+
"Group summaryPoints into a few broad, high-level takeaways (e.g. what was ingested, what search UI was built) rather than one entry per step \u2014 do not mirror the step outputs 1:1 or restate every detail.",
|
|
3011
|
+
"Each summaryPoint should be a short, standalone statement.",
|
|
3012
|
+
'Set reviewPrompt to a short message that asks the user to review the changes \u2014 it must include wording like "Please review the generated code".',
|
|
3013
|
+
"Set nextSteps to concrete actions the user should take to use what this workflow set up \u2014 do not suggest open-ended or generic tasks (e.g. deploying, writing tests) unless they are directly required.",
|
|
3014
|
+
options.nextStepsGuidance,
|
|
3015
|
+
`Completed steps:
|
|
2561
3016
|
${formatCompletedSteps(ctx.completedSteps)}`,
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
});
|
|
3017
|
+
"When done, call reportStatus"
|
|
3018
|
+
],
|
|
3019
|
+
tools: [],
|
|
3020
|
+
outputSchema: reviewSchema,
|
|
3021
|
+
modelSize: "small"
|
|
3022
|
+
});
|
|
3023
|
+
ctx.notify({ messages: formatReviewSummary(result) });
|
|
3024
|
+
return result;
|
|
3025
|
+
};
|
|
2568
3026
|
|
|
2569
3027
|
// src/actions/implement.ts
|
|
2570
|
-
import
|
|
3028
|
+
import z24 from "zod";
|
|
2571
3029
|
|
|
2572
3030
|
// src/lib/worktree.ts
|
|
2573
3031
|
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
2574
|
-
import { copyFile, mkdir as
|
|
3032
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
2575
3033
|
import {
|
|
2576
3034
|
basename as basename2,
|
|
2577
|
-
dirname as
|
|
3035
|
+
dirname as dirname7,
|
|
2578
3036
|
isAbsolute as isAbsolute2,
|
|
2579
3037
|
join as join10,
|
|
2580
3038
|
relative as relative2,
|
|
@@ -2638,7 +3096,7 @@ async function createWorktree(repoRoot) {
|
|
|
2638
3096
|
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
2639
3097
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
2640
3098
|
await pruneOldWorktrees(repoRoot);
|
|
2641
|
-
await
|
|
3099
|
+
await mkdir6(dirname7(path), { recursive: true });
|
|
2642
3100
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
2643
3101
|
return { path, branch };
|
|
2644
3102
|
}
|
|
@@ -2758,7 +3216,7 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
2758
3216
|
const relPath = join10(ingestDir, basename2(source));
|
|
2759
3217
|
const dest = join10(worktreePath, relPath);
|
|
2760
3218
|
try {
|
|
2761
|
-
await
|
|
3219
|
+
await mkdir6(dirname7(dest), { recursive: true });
|
|
2762
3220
|
await copyFile(source, dest);
|
|
2763
3221
|
} catch (err) {
|
|
2764
3222
|
return {
|
|
@@ -2768,6 +3226,25 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
2768
3226
|
}
|
|
2769
3227
|
return { ok: true, relPath };
|
|
2770
3228
|
}
|
|
3229
|
+
function hasEnvVar(content, name) {
|
|
3230
|
+
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3231
|
+
}
|
|
3232
|
+
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3233
|
+
const target = join10(worktreePath, ".env");
|
|
3234
|
+
let existing = "";
|
|
3235
|
+
try {
|
|
3236
|
+
existing = await readFile8(target, "utf8");
|
|
3237
|
+
} catch (err) {
|
|
3238
|
+
if (err.code !== "ENOENT") throw err;
|
|
3239
|
+
}
|
|
3240
|
+
const missing = vars.filter((v) => !hasEnvVar(existing, v.name));
|
|
3241
|
+
if (missing.length === 0) return [];
|
|
3242
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3243
|
+
const lines = missing.map(({ name, value }) => `${name}=${value}
|
|
3244
|
+
`).join("");
|
|
3245
|
+
await writeFile6(target, existing + prefix + lines, "utf8");
|
|
3246
|
+
return missing.map((v) => v.name);
|
|
3247
|
+
}
|
|
2771
3248
|
async function listChangedFiles(worktreePath) {
|
|
2772
3249
|
const raw = await git(["-C", worktreePath, "status", "--porcelain", "-z"]);
|
|
2773
3250
|
const entries = raw.split("\0");
|
|
@@ -2825,20 +3302,20 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
2825
3302
|
}
|
|
2826
3303
|
|
|
2827
3304
|
// src/lib/algoliaApiKey.ts
|
|
2828
|
-
import { z as
|
|
3305
|
+
import { z as z23 } from "zod";
|
|
2829
3306
|
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
2830
|
-
var apiKeySchema =
|
|
2831
|
-
value:
|
|
2832
|
-
acl:
|
|
2833
|
-
indexes:
|
|
3307
|
+
var apiKeySchema = z23.object({
|
|
3308
|
+
value: z23.string().min(1),
|
|
3309
|
+
acl: z23.array(z23.string()).default([]),
|
|
3310
|
+
indexes: z23.array(z23.string()).default([])
|
|
2834
3311
|
});
|
|
2835
|
-
var apiKeyListSchema =
|
|
2836
|
-
items:
|
|
2837
|
-
keys:
|
|
3312
|
+
var apiKeyListSchema = z23.object({
|
|
3313
|
+
items: z23.array(apiKeySchema).optional(),
|
|
3314
|
+
keys: z23.array(apiKeySchema).optional()
|
|
2838
3315
|
}).transform((o) => o.items ?? o.keys ?? []);
|
|
2839
|
-
var createdKeySchema =
|
|
2840
|
-
key:
|
|
2841
|
-
value:
|
|
3316
|
+
var createdKeySchema = z23.object({
|
|
3317
|
+
key: z23.string().min(1).optional(),
|
|
3318
|
+
value: z23.string().min(1).optional()
|
|
2842
3319
|
});
|
|
2843
3320
|
function canReuse(key, index) {
|
|
2844
3321
|
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
@@ -2874,15 +3351,15 @@ async function resolveSearchOnlyKey(index) {
|
|
|
2874
3351
|
|
|
2875
3352
|
// src/lib/algoliaDocs.ts
|
|
2876
3353
|
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
2877
|
-
import { dirname as
|
|
3354
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
2878
3355
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2879
3356
|
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
2880
3357
|
function findDocsDir() {
|
|
2881
|
-
let dir =
|
|
3358
|
+
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
2882
3359
|
for (; ; ) {
|
|
2883
3360
|
const candidate = join11(dir, DOCS_SUBPATH);
|
|
2884
3361
|
if (existsSync2(candidate)) return candidate;
|
|
2885
|
-
const parent =
|
|
3362
|
+
const parent = dirname8(dir);
|
|
2886
3363
|
if (parent === dir) return void 0;
|
|
2887
3364
|
dir = parent;
|
|
2888
3365
|
}
|
|
@@ -2932,51 +3409,56 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
2932
3409
|
return loadAlgoliaDoc("js");
|
|
2933
3410
|
}
|
|
2934
3411
|
|
|
3412
|
+
// src/lib/shell.ts
|
|
3413
|
+
function shellQuote(value) {
|
|
3414
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3415
|
+
}
|
|
3416
|
+
|
|
2935
3417
|
// src/actions/implement.ts
|
|
2936
|
-
var implementSchema =
|
|
2937
|
-
filesChanged:
|
|
2938
|
-
summary:
|
|
3418
|
+
var implementSchema = z24.object({
|
|
3419
|
+
filesChanged: z24.array(z24.string()),
|
|
3420
|
+
summary: z24.string(),
|
|
2939
3421
|
// Absolute path to the throwaway worktree holding the generated changes, so
|
|
2940
3422
|
// the user can open it (`cd <worktreePath>`) or inspect the diff
|
|
2941
3423
|
// (`git -C <worktreePath> status/diff`).
|
|
2942
|
-
worktreePath:
|
|
2943
|
-
ingestCommand:
|
|
3424
|
+
worktreePath: z24.string().optional(),
|
|
3425
|
+
ingestCommand: z24.string().optional(),
|
|
2944
3426
|
// True when the user accepted the run-now prompt and the wizard executed the
|
|
2945
3427
|
// ingestion script; downstream steps use this to avoid telling the user to run
|
|
2946
3428
|
// a script that already ran.
|
|
2947
|
-
ingestScriptRan:
|
|
3429
|
+
ingestScriptRan: z24.boolean().optional(),
|
|
2948
3430
|
// Records ingested by the run-now execution, parsed from the script's
|
|
2949
3431
|
// machine-readable count line; absent when the script didn't run or emitted
|
|
2950
3432
|
// no parseable count.
|
|
2951
|
-
ingestRecordCount:
|
|
3433
|
+
ingestRecordCount: z24.number().optional(),
|
|
2952
3434
|
// Wall-clock duration of the run-now ingestion execution, in ms.
|
|
2953
|
-
ingestDurationMs:
|
|
2954
|
-
ingestionSource:
|
|
3435
|
+
ingestDurationMs: z24.number().optional(),
|
|
3436
|
+
ingestionSource: z24.enum(["local", "fileUpload", "generated"]),
|
|
2955
3437
|
// Suggested names/values, built from framework detection. The search agent is
|
|
2956
3438
|
// instructed to rename the prefix if it doesn't match the project's build
|
|
2957
3439
|
// tool, so the names it actually wrote can differ — treat these as hints, not
|
|
2958
3440
|
// ground truth (the agent's summary carries the final names).
|
|
2959
|
-
searchEnvVars:
|
|
2960
|
-
|
|
2961
|
-
name:
|
|
2962
|
-
value:
|
|
3441
|
+
searchEnvVars: z24.array(
|
|
3442
|
+
z24.object({
|
|
3443
|
+
name: z24.string(),
|
|
3444
|
+
value: z24.string()
|
|
2963
3445
|
})
|
|
2964
3446
|
).optional()
|
|
2965
3447
|
});
|
|
2966
|
-
var implementationOutputSchema =
|
|
2967
|
-
summary:
|
|
3448
|
+
var implementationOutputSchema = z24.object({
|
|
3449
|
+
summary: z24.string(),
|
|
2968
3450
|
// Ingestion only: how to run the generated script, as a structured pair the
|
|
2969
3451
|
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
2970
3452
|
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
2971
3453
|
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
2972
3454
|
// the agent cannot inject extra commands or swap the interpreter.
|
|
2973
|
-
runtime:
|
|
2974
|
-
entrypoint:
|
|
3455
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
3456
|
+
entrypoint: z24.string().optional()
|
|
2975
3457
|
});
|
|
2976
|
-
var verificationOutputSchema =
|
|
2977
|
-
summary:
|
|
2978
|
-
sufficient:
|
|
2979
|
-
additionalInstructions:
|
|
3458
|
+
var verificationOutputSchema = z24.object({
|
|
3459
|
+
summary: z24.string(),
|
|
3460
|
+
sufficient: z24.boolean(),
|
|
3461
|
+
additionalInstructions: z24.string().optional()
|
|
2980
3462
|
});
|
|
2981
3463
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
2982
3464
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -3058,9 +3540,9 @@ function sourceSpecificInstructions(input) {
|
|
|
3058
3540
|
],
|
|
3059
3541
|
generated: [
|
|
3060
3542
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3061
|
-
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50)
|
|
3062
|
-
"
|
|
3063
|
-
"Add a prominent TODO where the developer swaps the generated records for their real record source."
|
|
3543
|
+
"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.",
|
|
3544
|
+
"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.",
|
|
3545
|
+
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3064
3546
|
]
|
|
3065
3547
|
};
|
|
3066
3548
|
return byLine[input.ingestionSource];
|
|
@@ -3090,12 +3572,16 @@ function searchInstructions(input) {
|
|
|
3090
3572
|
doc,
|
|
3091
3573
|
`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.`,
|
|
3092
3574
|
"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.",
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
`
|
|
3575
|
+
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3576
|
+
// search-only key is best-effort and can fall back to a placeholder.
|
|
3577
|
+
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3578
|
+
// Names are fixed, not the agent's to rename: the wizard writes the
|
|
3579
|
+
// resolved app id / search-only key into ".env" under these exact names
|
|
3580
|
+
// right after this step, so a renamed prefix here would leave the code
|
|
3581
|
+
// reading a var the wizard never wrote.
|
|
3582
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3096
3583
|
'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.',
|
|
3097
|
-
|
|
3098
|
-
"In your summary, tell the developer to fill in the concrete env values after reviewing the code."
|
|
3584
|
+
"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."
|
|
3099
3585
|
];
|
|
3100
3586
|
}
|
|
3101
3587
|
function verificationInstructions(input) {
|
|
@@ -3127,9 +3613,14 @@ var IMPLEMENT_CONFIG = {
|
|
|
3127
3613
|
}
|
|
3128
3614
|
};
|
|
3129
3615
|
var useCaseToolMap = {
|
|
3130
|
-
ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials"],
|
|
3131
|
-
search: [...FS_READ_TOOLS, "writeFile"],
|
|
3132
|
-
verification: [
|
|
3616
|
+
ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
|
|
3617
|
+
search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
|
|
3618
|
+
verification: [
|
|
3619
|
+
...FS_READ_TOOLS,
|
|
3620
|
+
"writeFile",
|
|
3621
|
+
"verifyImplementation",
|
|
3622
|
+
"notifyUser"
|
|
3623
|
+
]
|
|
3133
3624
|
};
|
|
3134
3625
|
function toolsForUseCase(useCase, ingestionSource) {
|
|
3135
3626
|
const tools = useCaseToolMap[useCase];
|
|
@@ -3152,6 +3643,9 @@ function formatSummary(useCase, summary) {
|
|
|
3152
3643
|
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3153
3644
|
return `${label}: ${summary}`;
|
|
3154
3645
|
}
|
|
3646
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3647
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
3648
|
+
}
|
|
3155
3649
|
function parseIngestRecordCount(output) {
|
|
3156
3650
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
3157
3651
|
if (!match) return void 0;
|
|
@@ -3295,6 +3789,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3295
3789
|
let ingestRecordCount;
|
|
3296
3790
|
let ingestDurationMs;
|
|
3297
3791
|
let installFailed = false;
|
|
3792
|
+
let ingestOutcomeMessage;
|
|
3298
3793
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
3299
3794
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
3300
3795
|
agentRuns += 1;
|
|
@@ -3307,7 +3802,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3307
3802
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3308
3803
|
outputSchema: implementationOutputSchema
|
|
3309
3804
|
});
|
|
3805
|
+
ctx.notify({
|
|
3806
|
+
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3807
|
+
});
|
|
3808
|
+
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3809
|
+
useCase: currentUseCase
|
|
3810
|
+
});
|
|
3310
3811
|
const install = await installWorktreeDeps(worktree);
|
|
3812
|
+
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3311
3813
|
if (!install.ok) {
|
|
3312
3814
|
installFailed = true;
|
|
3313
3815
|
logger.warn(
|
|
@@ -3332,6 +3834,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3332
3834
|
ingestRuntime = runtime;
|
|
3333
3835
|
ingestEntrypoint = entrypoint;
|
|
3334
3836
|
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
3837
|
+
ctx.clearNotices();
|
|
3335
3838
|
const runNow = await ctx.requestUserInput({
|
|
3336
3839
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
3337
3840
|
promptType: "acceptReject",
|
|
@@ -3340,6 +3843,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3340
3843
|
}) === true;
|
|
3341
3844
|
if (runNow) {
|
|
3342
3845
|
const profile2 = await loadActiveProfile();
|
|
3846
|
+
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3847
|
+
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3848
|
+
runtime: ingestRuntime,
|
|
3849
|
+
entrypoint: ingestEntrypoint
|
|
3850
|
+
});
|
|
3343
3851
|
const startedAt = Date.now();
|
|
3344
3852
|
const run = await runIngestScript(
|
|
3345
3853
|
worktree,
|
|
@@ -3350,6 +3858,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3350
3858
|
[API_KEY_VAR]: profile2.apiKey
|
|
3351
3859
|
}
|
|
3352
3860
|
);
|
|
3861
|
+
ctx.logEnd(scriptLogId, run.ok ? "success" : "error");
|
|
3353
3862
|
ingestScriptRan = run.ran && run.ok;
|
|
3354
3863
|
if (ingestScriptRan) {
|
|
3355
3864
|
ingestDurationMs = Date.now() - startedAt;
|
|
@@ -3405,19 +3914,38 @@ ${run.output}` : status;
|
|
|
3405
3914
|
});
|
|
3406
3915
|
}
|
|
3407
3916
|
summaries.push(summaryLine);
|
|
3408
|
-
|
|
3409
|
-
prompt: "Continue",
|
|
3410
|
-
promptType: "notice",
|
|
3411
|
-
options: [],
|
|
3412
|
-
messages: [outcomeMessage]
|
|
3413
|
-
});
|
|
3917
|
+
ingestOutcomeMessage = outcomeMessage;
|
|
3414
3918
|
}
|
|
3415
3919
|
}
|
|
3920
|
+
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
3921
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
3922
|
+
commandMessages.push(
|
|
3923
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
await ctx.requestUserInput({
|
|
3927
|
+
// No question being asked here, just an acknowledgement — the
|
|
3928
|
+
// continue/decline hints below already say "continue".
|
|
3929
|
+
prompt: "",
|
|
3930
|
+
promptType: "spaceToContinue",
|
|
3931
|
+
options: [],
|
|
3932
|
+
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
3933
|
+
});
|
|
3416
3934
|
}
|
|
3417
3935
|
if (useCases.includes("search")) {
|
|
3418
3936
|
let extraInstructions = [];
|
|
3419
3937
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
3420
3938
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
3939
|
+
if (attempt > 1) {
|
|
3940
|
+
logger.info(
|
|
3941
|
+
{
|
|
3942
|
+
attempt,
|
|
3943
|
+
maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
|
|
3944
|
+
extraInstructions
|
|
3945
|
+
},
|
|
3946
|
+
"implement: retrying search implementation after failed verification"
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3421
3949
|
const { summary } = await runImplementationUseCase(
|
|
3422
3950
|
"search",
|
|
3423
3951
|
extraInstructions
|
|
@@ -3446,6 +3974,26 @@ ${run.output}` : status;
|
|
|
3446
3974
|
}
|
|
3447
3975
|
extraInstructions = verificationRetryInstructions(verification);
|
|
3448
3976
|
}
|
|
3977
|
+
const resolvedSearchEnvVars = input.searchEnvVars.filter(
|
|
3978
|
+
(v) => !v.value.startsWith("<")
|
|
3979
|
+
);
|
|
3980
|
+
if (resolvedSearchEnvVars.length > 0) {
|
|
3981
|
+
const written = await writeSearchEnvValues(
|
|
3982
|
+
worktree,
|
|
3983
|
+
resolvedSearchEnvVars
|
|
3984
|
+
);
|
|
3985
|
+
if (written.length > 0) {
|
|
3986
|
+
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
const unresolvedSearchEnvVars = input.searchEnvVars.filter(
|
|
3990
|
+
(v) => v.value.startsWith("<")
|
|
3991
|
+
);
|
|
3992
|
+
if (unresolvedSearchEnvVars.length > 0) {
|
|
3993
|
+
summaries.push(
|
|
3994
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3449
3997
|
} else {
|
|
3450
3998
|
ctx.setUserInput("implementation", "success");
|
|
3451
3999
|
}
|
|
@@ -3466,7 +4014,11 @@ ${run.output}` : status;
|
|
|
3466
4014
|
summary: summaries.join("\n\n"),
|
|
3467
4015
|
worktreePath: worktree,
|
|
3468
4016
|
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
3469
|
-
ingestCommand:
|
|
4017
|
+
ingestCommand: buildIngestCommand(
|
|
4018
|
+
worktree,
|
|
4019
|
+
ingestRuntime,
|
|
4020
|
+
ingestEntrypoint
|
|
4021
|
+
),
|
|
3470
4022
|
ingestScriptRan,
|
|
3471
4023
|
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
3472
4024
|
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
@@ -3488,7 +4040,14 @@ var defaultWorkflow = {
|
|
|
3488
4040
|
id: "project-scan",
|
|
3489
4041
|
title: "project scan",
|
|
3490
4042
|
outputSchema: projectScanSchema,
|
|
3491
|
-
run: (ctx) =>
|
|
4043
|
+
run: (ctx) => {
|
|
4044
|
+
ctx.notify({
|
|
4045
|
+
messages: [
|
|
4046
|
+
"Scanning your project for languages, frameworks, and Algolia integration points\u2026"
|
|
4047
|
+
]
|
|
4048
|
+
});
|
|
4049
|
+
return projectScan(ctx);
|
|
4050
|
+
}
|
|
3492
4051
|
}),
|
|
3493
4052
|
defineStep({
|
|
3494
4053
|
id: "confirm-language",
|
|
@@ -3507,8 +4066,8 @@ var defaultWorkflow = {
|
|
|
3507
4066
|
defineStep({
|
|
3508
4067
|
id: "select-index",
|
|
3509
4068
|
title: "Set up index",
|
|
3510
|
-
outputSchema:
|
|
3511
|
-
selection:
|
|
4069
|
+
outputSchema: z25.object({
|
|
4070
|
+
selection: z25.string()
|
|
3512
4071
|
}),
|
|
3513
4072
|
run: (ctx) => selectIndexStep(ctx)
|
|
3514
4073
|
}),
|
|
@@ -3516,7 +4075,14 @@ var defaultWorkflow = {
|
|
|
3516
4075
|
id: "ingestion",
|
|
3517
4076
|
title: "ingest records",
|
|
3518
4077
|
outputSchema: implementSchema,
|
|
3519
|
-
run: (ctx) =>
|
|
4078
|
+
run: (ctx) => {
|
|
4079
|
+
ctx.notify({
|
|
4080
|
+
messages: [
|
|
4081
|
+
"Setting up an Algolia ingestion pipeline in your project\u2026"
|
|
4082
|
+
]
|
|
4083
|
+
});
|
|
4084
|
+
return implement(ctx, ["ingestion"]);
|
|
4085
|
+
}
|
|
3520
4086
|
}),
|
|
3521
4087
|
defineStep({
|
|
3522
4088
|
id: "confirm-framework",
|
|
@@ -3530,6 +4096,9 @@ var defaultWorkflow = {
|
|
|
3530
4096
|
title: "create search ui",
|
|
3531
4097
|
outputSchema: implementSchema,
|
|
3532
4098
|
run: (ctx) => {
|
|
4099
|
+
ctx.notify({
|
|
4100
|
+
messages: ["Building your Algolia search experience\u2026"]
|
|
4101
|
+
});
|
|
3533
4102
|
const ingestion = ctx.getStepOutput(
|
|
3534
4103
|
"ingestion"
|
|
3535
4104
|
);
|
|
@@ -3541,11 +4110,17 @@ var defaultWorkflow = {
|
|
|
3541
4110
|
title: "done",
|
|
3542
4111
|
outputSchema: reviewSchema,
|
|
3543
4112
|
run: (ctx) => {
|
|
4113
|
+
ctx.notify({
|
|
4114
|
+
messages: ["Summarizing what we did\u2026"]
|
|
4115
|
+
});
|
|
3544
4116
|
const ingestion = ctx.getStepOutput(
|
|
3545
4117
|
"ingestion"
|
|
3546
4118
|
);
|
|
3547
4119
|
return reviewStep(ctx, {
|
|
3548
|
-
|
|
4120
|
+
// The ingestion step already showed the user the exact `ingestCommand`
|
|
4121
|
+
// and worktree path as a notice, so nextSteps must not restate it —
|
|
4122
|
+
// an LLM-paraphrased command risks being wrong.
|
|
4123
|
+
nextStepsGuidance: ingestion?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
|
|
3549
4124
|
});
|
|
3550
4125
|
}
|
|
3551
4126
|
})
|
|
@@ -3561,7 +4136,7 @@ function getWorkflow(id) {
|
|
|
3561
4136
|
}
|
|
3562
4137
|
|
|
3563
4138
|
// src/main.tsx
|
|
3564
|
-
import { jsx as
|
|
4139
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
3565
4140
|
var requestedId = process.argv[2] ?? defaultWorkflow.id;
|
|
3566
4141
|
var workflow = getWorkflow(requestedId);
|
|
3567
4142
|
if (!workflow) {
|
|
@@ -3570,7 +4145,7 @@ if (!workflow) {
|
|
|
3570
4145
|
process.exit(1);
|
|
3571
4146
|
}
|
|
3572
4147
|
var store = useWizard.getState();
|
|
3573
|
-
var instance = render(/* @__PURE__ */
|
|
4148
|
+
var instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
3574
4149
|
var user = await getUser();
|
|
3575
4150
|
if (!user) {
|
|
3576
4151
|
await instance.waitUntilRenderFlush();
|
|
@@ -3581,7 +4156,7 @@ if (!user) {
|
|
|
3581
4156
|
console.error(err instanceof Error ? err.message : String(err));
|
|
3582
4157
|
process.exit(1);
|
|
3583
4158
|
}
|
|
3584
|
-
instance = render(/* @__PURE__ */
|
|
4159
|
+
instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
|
|
3585
4160
|
user = await getUser();
|
|
3586
4161
|
if (!user) {
|
|
3587
4162
|
store.setError(
|