@algolia/wizard 0.34.0-rc.125.245 → 0.35.0-rc.126.249
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js
CHANGED
|
@@ -253,6 +253,7 @@ var useWizard = create((set, get) => ({
|
|
|
253
253
|
cliOutput: [],
|
|
254
254
|
targetIndex: null,
|
|
255
255
|
writtenFiles: [],
|
|
256
|
+
approvedCommands: /* @__PURE__ */ new Set(),
|
|
256
257
|
logs: [],
|
|
257
258
|
error: null,
|
|
258
259
|
inputReq: null,
|
|
@@ -348,6 +349,10 @@ var useWizard = create((set, get) => ({
|
|
|
348
349
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
349
350
|
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
350
351
|
clearWrittenFiles: () => set({ writtenFiles: [] }),
|
|
352
|
+
isCommandApproved: (command, cwd) => get().approvedCommands.has(`${cwd}\0${command}`),
|
|
353
|
+
approveCommand: (command, cwd) => set((s) => ({
|
|
354
|
+
approvedCommands: new Set(s.approvedCommands).add(`${cwd}\0${command}`)
|
|
355
|
+
})),
|
|
351
356
|
logStart: (kind, name, input) => {
|
|
352
357
|
const id = nanoid();
|
|
353
358
|
set((s) => ({
|
|
@@ -396,6 +401,7 @@ var useWizard = create((set, get) => ({
|
|
|
396
401
|
cliOutput: [],
|
|
397
402
|
targetIndex: null,
|
|
398
403
|
writtenFiles: [],
|
|
404
|
+
approvedCommands: /* @__PURE__ */ new Set(),
|
|
399
405
|
logs: [],
|
|
400
406
|
error: null,
|
|
401
407
|
inputReq: null,
|
|
@@ -1121,576 +1127,138 @@ function PromptInput() {
|
|
|
1121
1127
|
] });
|
|
1122
1128
|
}
|
|
1123
1129
|
|
|
1124
|
-
// src/
|
|
1125
|
-
import {
|
|
1126
|
-
import { fileURLToPath } from "node:url";
|
|
1127
|
-
import { useState as useState7 } from "react";
|
|
1128
|
-
import { Box as Box10, Spacer, Text as Text10, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
1130
|
+
// src/workflows/default.ts
|
|
1131
|
+
import { z as z30 } from "zod";
|
|
1129
1132
|
|
|
1130
|
-
// src/
|
|
1131
|
-
|
|
1132
|
-
{
|
|
1133
|
-
title: "scan your project",
|
|
1134
|
-
description: "detect models, schemas, and data worth indexing"
|
|
1135
|
-
},
|
|
1136
|
-
{
|
|
1137
|
-
title: "select and index",
|
|
1138
|
-
description: "push 100 records to Algolia in seconds"
|
|
1139
|
-
},
|
|
1140
|
-
{
|
|
1141
|
-
title: "detect your stack",
|
|
1142
|
-
description: "whatever language and framework you already use"
|
|
1143
|
-
},
|
|
1144
|
-
{
|
|
1145
|
-
title: "scaffold a search UI",
|
|
1146
|
-
description: "a search box and results, wired into your app"
|
|
1147
|
-
},
|
|
1148
|
-
{
|
|
1149
|
-
title: "ship it",
|
|
1150
|
-
description: "build check passes, search is live with your real data"
|
|
1151
|
-
}
|
|
1152
|
-
];
|
|
1133
|
+
// src/core/orchestrator.ts
|
|
1134
|
+
import "zod";
|
|
1153
1135
|
|
|
1154
|
-
// src/
|
|
1155
|
-
import
|
|
1156
|
-
import {
|
|
1157
|
-
var
|
|
1158
|
-
var
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
};
|
|
1163
|
-
function
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.success, children: "\u2192" }),
|
|
1170
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: title })
|
|
1171
|
-
] }),
|
|
1172
|
-
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 2, children: [
|
|
1173
|
-
/* @__PURE__ */ jsx8(Spacer, {}),
|
|
1174
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: description })
|
|
1175
|
-
] })
|
|
1176
|
-
] });
|
|
1177
|
-
}
|
|
1178
|
-
function Welcome() {
|
|
1179
|
-
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1180
|
-
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
1181
|
-
const { rows } = useWindowSize5();
|
|
1182
|
-
const actions = [
|
|
1183
|
-
{ label: "start wizard", run: confirmStart },
|
|
1184
|
-
{ label: "learn more", run: openLearnMore }
|
|
1185
|
-
];
|
|
1186
|
-
const [index, setIndex] = useState7(0);
|
|
1187
|
-
useInput3((input, key) => {
|
|
1188
|
-
if (key.upArrow || input === "k") {
|
|
1189
|
-
setIndex((i) => (i - 1 + actions.length) % actions.length);
|
|
1190
|
-
} else if (key.downArrow || input === "j") {
|
|
1191
|
-
setIndex((i) => (i + 1) % actions.length);
|
|
1192
|
-
} else if (key.return) {
|
|
1193
|
-
actions[index].run();
|
|
1194
|
-
}
|
|
1195
|
-
});
|
|
1196
|
-
const scales = {
|
|
1197
|
-
large: {
|
|
1198
|
-
sidebar: { padding: { x: 4, y: 2 }, gap: 2 },
|
|
1199
|
-
main: { padding: { x: 8, y: 4 } }
|
|
1200
|
-
},
|
|
1201
|
-
small: {
|
|
1202
|
-
sidebar: { padding: { x: 2, y: 1 }, gap: 1 },
|
|
1203
|
-
main: { padding: { x: 4, y: 2 } }
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
let layout = scales["large"];
|
|
1207
|
-
if (rows < 30) {
|
|
1208
|
-
layout = scales["small"];
|
|
1136
|
+
// src/core/config.ts
|
|
1137
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1138
|
+
import { join as join3 } from "node:path";
|
|
1139
|
+
var configFile = () => join3(stateDir(), "config.json");
|
|
1140
|
+
var defaultConfig = () => ({
|
|
1141
|
+
version: 1,
|
|
1142
|
+
aiConsent: false,
|
|
1143
|
+
workflowsRun: []
|
|
1144
|
+
});
|
|
1145
|
+
async function loadConfig() {
|
|
1146
|
+
try {
|
|
1147
|
+
const raw = await readFile(configFile(), "utf8");
|
|
1148
|
+
return { ...defaultConfig(), ...JSON.parse(raw) };
|
|
1149
|
+
} catch {
|
|
1150
|
+
return defaultConfig();
|
|
1209
1151
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
/* @__PURE__ */ jsx8(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx8(
|
|
1220
|
-
Image,
|
|
1221
|
-
{
|
|
1222
|
-
src: IMAGE_PATH,
|
|
1223
|
-
objectFit: "contain",
|
|
1224
|
-
alt: "Algolia",
|
|
1225
|
-
width: 20,
|
|
1226
|
-
height: 10,
|
|
1227
|
-
protocol: "halfBlock"
|
|
1228
|
-
}
|
|
1229
|
-
) }),
|
|
1230
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
1231
|
-
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: actions.map((action, i) => /* @__PURE__ */ jsx8(
|
|
1232
|
-
SelectRow,
|
|
1233
|
-
{
|
|
1234
|
-
highlighted: i === index,
|
|
1235
|
-
highlightBackground: false,
|
|
1236
|
-
label: action.label
|
|
1237
|
-
},
|
|
1238
|
-
action.label
|
|
1239
|
-
)) }),
|
|
1240
|
-
/* @__PURE__ */ jsxs9(Box10, { gap: 2, children: [
|
|
1241
|
-
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1242
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[\u2191] [\u2193]" }),
|
|
1243
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " move" })
|
|
1244
|
-
] }),
|
|
1245
|
-
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1246
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[enter]" }),
|
|
1247
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " confirm" })
|
|
1248
|
-
] })
|
|
1249
|
-
] })
|
|
1250
|
-
] })
|
|
1251
|
-
}
|
|
1252
|
-
),
|
|
1253
|
-
/* @__PURE__ */ jsxs9(
|
|
1254
|
-
Box10,
|
|
1255
|
-
{
|
|
1256
|
-
backgroundColor: COLORS.bg.sidebar,
|
|
1257
|
-
width: 40,
|
|
1258
|
-
paddingY: layout.sidebar.padding.y,
|
|
1259
|
-
paddingX: layout.sidebar.padding.x,
|
|
1260
|
-
gap: layout.sidebar.gap,
|
|
1261
|
-
flexDirection: "column",
|
|
1262
|
-
justifyContent: "center",
|
|
1263
|
-
children: [
|
|
1264
|
-
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
1265
|
-
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx8(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
1266
|
-
]
|
|
1267
|
-
}
|
|
1268
|
-
)
|
|
1269
|
-
] });
|
|
1152
|
+
}
|
|
1153
|
+
async function saveConfig(config) {
|
|
1154
|
+
await mkdir(stateDir(), { recursive: true });
|
|
1155
|
+
await writeFile(configFile(), JSON.stringify(config, null, 2), "utf8");
|
|
1156
|
+
}
|
|
1157
|
+
async function recordWorkflowRun(workflowId, completedAt) {
|
|
1158
|
+
const config = await loadConfig();
|
|
1159
|
+
config.workflowsRun.push({ workflowId, completedAt });
|
|
1160
|
+
await saveConfig(config);
|
|
1270
1161
|
}
|
|
1271
1162
|
|
|
1272
|
-
// src/
|
|
1273
|
-
import {
|
|
1274
|
-
import {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
},
|
|
1284
|
-
{
|
|
1285
|
-
tag: "WRITE",
|
|
1286
|
-
title: "Code changes",
|
|
1287
|
-
description: "creates & edits files (search UI, config) directly in your branch."
|
|
1288
|
-
},
|
|
1289
|
-
{
|
|
1290
|
-
tag: "EXEC",
|
|
1291
|
-
title: "Setup commands",
|
|
1292
|
-
description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
|
|
1293
|
-
},
|
|
1294
|
-
{
|
|
1295
|
-
tag: "NET",
|
|
1296
|
-
title: "Algolia API",
|
|
1297
|
-
description: "sends index settings & the records you pick to your Algolia app over HTTPS."
|
|
1298
|
-
},
|
|
1299
|
-
{
|
|
1300
|
-
tag: "KEY",
|
|
1301
|
-
title: "Credentials",
|
|
1302
|
-
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
|
|
1163
|
+
// src/core/persistence.ts
|
|
1164
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, rm } from "node:fs/promises";
|
|
1165
|
+
import { join as join4 } from "node:path";
|
|
1166
|
+
var isStepVisible = (s) => s.visible !== false;
|
|
1167
|
+
var stateFile = (workflowId) => join4(stateDir(), `state-${workflowId}.json`);
|
|
1168
|
+
async function loadWorkflowState(workflowId) {
|
|
1169
|
+
try {
|
|
1170
|
+
const raw = await readFile2(stateFile(workflowId), "utf8");
|
|
1171
|
+
return JSON.parse(raw);
|
|
1172
|
+
} catch {
|
|
1173
|
+
return null;
|
|
1303
1174
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1175
|
+
}
|
|
1176
|
+
async function saveWorkflowState(state) {
|
|
1177
|
+
await mkdir2(stateDir(), { recursive: true });
|
|
1178
|
+
await writeFile2(
|
|
1179
|
+
stateFile(state.workflowId),
|
|
1180
|
+
JSON.stringify(state, null, 2),
|
|
1181
|
+
"utf8"
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
async function clearWorkflowState(workflowId) {
|
|
1185
|
+
await rm(stateFile(workflowId), { force: true });
|
|
1186
|
+
}
|
|
1314
1187
|
|
|
1315
|
-
// src/
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
READ: COLORS.success,
|
|
1319
|
-
WRITE: COLORS.badge,
|
|
1320
|
-
EXEC: COLORS.danger,
|
|
1321
|
-
NET: COLORS.accent,
|
|
1322
|
-
KEY: COLORS.muted
|
|
1323
|
-
};
|
|
1324
|
-
var TAG_COLUMN_WIDTH = 10;
|
|
1325
|
-
var PADDING_X = 6;
|
|
1326
|
-
var NEVER_BOX_PAD_X = 2;
|
|
1327
|
-
function NeverLine({
|
|
1328
|
-
width,
|
|
1329
|
-
segments = []
|
|
1330
|
-
}) {
|
|
1331
|
-
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1332
|
-
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1333
|
-
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
1334
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" }),
|
|
1335
|
-
" ".repeat(NEVER_BOX_PAD_X),
|
|
1336
|
-
segments.map((s, i) => /* @__PURE__ */ jsx9(Text11, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1337
|
-
" ".repeat(rightPad),
|
|
1338
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" })
|
|
1339
|
-
] });
|
|
1188
|
+
// src/lib/telemetry.ts
|
|
1189
|
+
function isTelemetryOptedOut() {
|
|
1190
|
+
return process.env.WIZARD_TELEMETRY === "false";
|
|
1340
1191
|
}
|
|
1341
|
-
function
|
|
1342
|
-
|
|
1343
|
-
const backToHome = useWizard((s) => s.backToHome);
|
|
1344
|
-
const { columns } = useWindowSize6();
|
|
1345
|
-
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1346
|
-
useInput4((_input, key) => {
|
|
1347
|
-
if (key.escape) backToHome();
|
|
1348
|
-
else if (key.return) confirmStart();
|
|
1349
|
-
});
|
|
1350
|
-
return /* @__PURE__ */ jsxs10(
|
|
1351
|
-
Box11,
|
|
1352
|
-
{
|
|
1353
|
-
flexDirection: "column",
|
|
1354
|
-
paddingX: PADDING_X,
|
|
1355
|
-
paddingY: 2,
|
|
1356
|
-
width: "100%",
|
|
1357
|
-
gap: 1,
|
|
1358
|
-
children: [
|
|
1359
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1360
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: accessIntro }),
|
|
1361
|
-
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
1362
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1363
|
-
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1364
|
-
/* @__PURE__ */ jsx9(Box11, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text11, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1365
|
-
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
1366
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1367
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1368
|
-
] }) })
|
|
1369
|
-
] })
|
|
1370
|
-
] }, item.tag)) }),
|
|
1371
|
-
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
|
|
1372
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1373
|
-
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
1374
|
-
/* @__PURE__ */ jsx9(
|
|
1375
|
-
NeverLine,
|
|
1376
|
-
{
|
|
1377
|
-
width: dividerWidth,
|
|
1378
|
-
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1379
|
-
}
|
|
1380
|
-
),
|
|
1381
|
-
neverItems.map((item) => /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
1382
|
-
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
1383
|
-
/* @__PURE__ */ jsx9(
|
|
1384
|
-
NeverLine,
|
|
1385
|
-
{
|
|
1386
|
-
width: dividerWidth,
|
|
1387
|
-
segments: [
|
|
1388
|
-
{ text: "\u2715", color: COLORS.danger },
|
|
1389
|
-
{ text: " " },
|
|
1390
|
-
{ text: item, color: COLORS.primary }
|
|
1391
|
-
]
|
|
1392
|
-
}
|
|
1393
|
-
)
|
|
1394
|
-
] }, item)),
|
|
1395
|
-
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
1396
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1397
|
-
] }),
|
|
1398
|
-
/* @__PURE__ */ jsx9(Box11, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1399
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1400
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.accent, children: link.url })
|
|
1401
|
-
] }, link.label)) }),
|
|
1402
|
-
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1403
|
-
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1404
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
1405
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "esc" }),
|
|
1406
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "] back" })
|
|
1407
|
-
] }),
|
|
1408
|
-
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1409
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
1410
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "enter" }),
|
|
1411
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "]" }),
|
|
1412
|
-
/* @__PURE__ */ jsx9(Text11, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1413
|
-
] })
|
|
1414
|
-
] })
|
|
1415
|
-
]
|
|
1416
|
-
}
|
|
1417
|
-
);
|
|
1192
|
+
function isTelemetryEnabled() {
|
|
1193
|
+
return Boolean(getAuthToken()) && !process.env.VITEST && !isTelemetryOptedOut();
|
|
1418
1194
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
// src/ui/Steps.tsx
|
|
1424
|
-
import { Box as Box12, Text as Text12 } from "ink";
|
|
1425
|
-
import Spinner from "ink-spinner";
|
|
1426
|
-
|
|
1427
|
-
// src/core/persistence.ts
|
|
1428
|
-
import { mkdir, readFile, writeFile, rm } from "node:fs/promises";
|
|
1429
|
-
import { join as join4 } from "node:path";
|
|
1430
|
-
var isStepVisible = (s) => s.visible !== false;
|
|
1431
|
-
var stateFile = (workflowId) => join4(stateDir(), `state-${workflowId}.json`);
|
|
1432
|
-
async function loadWorkflowState(workflowId) {
|
|
1433
|
-
try {
|
|
1434
|
-
const raw = await readFile(stateFile(workflowId), "utf8");
|
|
1435
|
-
return JSON.parse(raw);
|
|
1436
|
-
} catch {
|
|
1437
|
-
return null;
|
|
1438
|
-
}
|
|
1195
|
+
function fireAndForget(promise) {
|
|
1196
|
+
void promise.catch(() => {
|
|
1197
|
+
});
|
|
1439
1198
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
"
|
|
1199
|
+
function sendTelemetry(payload) {
|
|
1200
|
+
if (!isTelemetryEnabled()) return;
|
|
1201
|
+
const token = getAuthToken();
|
|
1202
|
+
if (!token) return;
|
|
1203
|
+
const body = {
|
|
1204
|
+
env: process.env.DD_ENV ?? "development",
|
|
1205
|
+
...payload
|
|
1206
|
+
};
|
|
1207
|
+
fireAndForget(
|
|
1208
|
+
proxyFetch(`${PROXY_BASE_URL}/telemetry`, {
|
|
1209
|
+
method: "POST",
|
|
1210
|
+
headers: {
|
|
1211
|
+
"Content-Type": "application/json",
|
|
1212
|
+
authorization: `Bearer ${token}`
|
|
1213
|
+
},
|
|
1214
|
+
body: JSON.stringify(body)
|
|
1215
|
+
}).then((res) => {
|
|
1216
|
+
if (!res.ok) {
|
|
1217
|
+
throw new Error(`Proxy telemetry ${res.status}`);
|
|
1218
|
+
}
|
|
1219
|
+
})
|
|
1446
1220
|
);
|
|
1447
1221
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1222
|
+
function sendLog(status, message, attributes = {}, tags = []) {
|
|
1223
|
+
sendTelemetry({
|
|
1224
|
+
logs: [{ status, message, attributes, tags }]
|
|
1225
|
+
});
|
|
1450
1226
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
const { steps } = useWizard();
|
|
1456
|
-
const visibleSteps = steps.filter(isStepVisible);
|
|
1457
|
-
return /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status[s.status], children: [
|
|
1458
|
-
s.status === "running" ? /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1459
|
-
" ",
|
|
1460
|
-
s.title
|
|
1461
|
-
] }) }, s.id)) });
|
|
1227
|
+
function sendMetric(name, value, type, tags = []) {
|
|
1228
|
+
sendTelemetry({
|
|
1229
|
+
metrics: [{ name, value, type, tags }]
|
|
1230
|
+
});
|
|
1462
1231
|
}
|
|
1463
|
-
function
|
|
1464
|
-
|
|
1465
|
-
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1466
|
-
if (!currentStep) return null;
|
|
1467
|
-
return /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status.running, children: [
|
|
1468
|
-
/* @__PURE__ */ jsx10(Spinner, { type: "dots" }),
|
|
1469
|
-
" ",
|
|
1470
|
-
` ${currentStep.title}`
|
|
1471
|
-
] });
|
|
1232
|
+
function metricTags(workflowId, actionId) {
|
|
1233
|
+
return actionId ? [`workflow:${workflowId}`, `action:${actionId}`] : [`workflow:${workflowId}`];
|
|
1472
1234
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
const { steps, currentStepIndex } = useWizard();
|
|
1479
|
-
const visibleSteps = steps.filter(isStepVisible);
|
|
1480
|
-
if (visibleSteps.length === 0) return null;
|
|
1481
|
-
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1482
|
-
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1483
|
-
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
1484
|
-
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "STEP" }),
|
|
1485
|
-
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: activeStepNumber }),
|
|
1486
|
-
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: "/" }),
|
|
1487
|
-
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: visibleSteps.length })
|
|
1488
|
-
] });
|
|
1235
|
+
function logTags(workflowId, actionId, appId) {
|
|
1236
|
+
return [
|
|
1237
|
+
...metricTags(workflowId, actionId),
|
|
1238
|
+
...appId ? [`app_id:${appId}`] : []
|
|
1239
|
+
];
|
|
1489
1240
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
// src/ui/Sidebar.tsx
|
|
1498
|
-
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1499
|
-
function Sidebar() {
|
|
1500
|
-
return /* @__PURE__ */ jsxs13(
|
|
1501
|
-
Box14,
|
|
1502
|
-
{
|
|
1503
|
-
backgroundColor: "#14171E",
|
|
1504
|
-
width: 30,
|
|
1505
|
-
paddingX: 4,
|
|
1506
|
-
paddingY: 2,
|
|
1507
|
-
flexDirection: "column",
|
|
1508
|
-
justifyContent: "space-between",
|
|
1509
|
-
children: [
|
|
1510
|
-
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
1511
|
-
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1512
|
-
/* @__PURE__ */ jsx12(Steps, {})
|
|
1513
|
-
] }),
|
|
1514
|
-
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
1515
|
-
/* @__PURE__ */ jsx12(Progress, {}),
|
|
1516
|
-
/* @__PURE__ */ jsx12(Box14, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1517
|
-
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
|
|
1518
|
-
/* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1519
|
-
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: c.description })
|
|
1520
|
-
] });
|
|
1521
|
-
}) })
|
|
1522
|
-
] })
|
|
1523
|
-
]
|
|
1524
|
-
}
|
|
1525
|
-
);
|
|
1241
|
+
function emitTelemetryLog(level, message, attributes, tags) {
|
|
1242
|
+
if (level === "info") {
|
|
1243
|
+
logger.info(attributes, message);
|
|
1244
|
+
} else {
|
|
1245
|
+
logger.error(attributes, message);
|
|
1246
|
+
}
|
|
1247
|
+
sendLog(level, message, attributes, tags);
|
|
1526
1248
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
justifyContent: "space-between",
|
|
1539
|
-
paddingX: 2,
|
|
1540
|
-
paddingY: 1,
|
|
1541
|
-
children: [
|
|
1542
|
-
/* @__PURE__ */ jsx13(Progress, {}),
|
|
1543
|
-
/* @__PURE__ */ jsx13(CurrentStep, {}),
|
|
1544
|
-
/* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: 1, children: [
|
|
1545
|
-
/* @__PURE__ */ jsx13(Text15, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1546
|
-
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: firstCommand.description })
|
|
1547
|
-
] })
|
|
1548
|
-
]
|
|
1549
|
-
}
|
|
1249
|
+
function trackWorkflowStart(ctx) {
|
|
1250
|
+
const attributes = {
|
|
1251
|
+
event: "wizard.workflow.start",
|
|
1252
|
+
workflow_id: ctx.workflowId,
|
|
1253
|
+
app_id: ctx.appId
|
|
1254
|
+
};
|
|
1255
|
+
emitTelemetryLog(
|
|
1256
|
+
"info",
|
|
1257
|
+
"wizard workflow started",
|
|
1258
|
+
attributes,
|
|
1259
|
+
logTags(ctx.workflowId, void 0, ctx.appId)
|
|
1550
1260
|
);
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
// src/ui/App.tsx
|
|
1554
|
-
import { useState as useState9 } from "react";
|
|
1555
|
-
|
|
1556
|
-
// src/ui/Logs.tsx
|
|
1557
|
-
import { Box as Box16, Text as Text16, useInput as useInput5 } from "ink";
|
|
1558
|
-
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1559
|
-
var KIND_COLOR = {
|
|
1560
|
-
tool: COLORS.primary,
|
|
1561
|
-
prompt: COLORS.badge
|
|
1562
|
-
};
|
|
1563
|
-
var STATUS_COLOR = {
|
|
1564
|
-
running: COLORS.status.running,
|
|
1565
|
-
error: COLORS.danger
|
|
1566
|
-
};
|
|
1567
|
-
function logNameColor(entry) {
|
|
1568
|
-
return STATUS_COLOR[entry.status] ?? KIND_COLOR[entry.kind];
|
|
1569
|
-
}
|
|
1570
|
-
var ROW_GAP = 1;
|
|
1571
|
-
function truncate2(str, maxWidth) {
|
|
1572
|
-
if (maxWidth <= 0) return "";
|
|
1573
|
-
return str.length > maxWidth ? `${str.slice(0, maxWidth - 1)}\u2026` : str;
|
|
1574
|
-
}
|
|
1575
|
-
function rawInputText(input) {
|
|
1576
|
-
if (input === void 0) return "";
|
|
1577
|
-
const str = typeof input === "string" ? input : JSON.stringify(input);
|
|
1578
|
-
if (!str || str === "{}") return "";
|
|
1579
|
-
return str.replace(/\s+/g, " ").trim();
|
|
1580
|
-
}
|
|
1581
|
-
function formatTimestamp(ms) {
|
|
1582
|
-
const d = new Date(ms);
|
|
1583
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
1584
|
-
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1585
|
-
}
|
|
1586
|
-
function Logs() {
|
|
1587
|
-
const logs = useWizard((s) => s.logs);
|
|
1588
|
-
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1589
|
-
useInput5((_input, key) => {
|
|
1590
|
-
if (key.upArrow) scroll.scrollBy(-1);
|
|
1591
|
-
else if (key.downArrow) scroll.scrollBy(1);
|
|
1592
|
-
});
|
|
1593
|
-
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1594
|
-
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1595
|
-
logs.length === 0 && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "No logs yet." }),
|
|
1596
|
-
/* @__PURE__ */ jsx14(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1597
|
-
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1598
|
-
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1599
|
-
const rawPreview = rawInputText(entry.input);
|
|
1600
|
-
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1601
|
-
const gaps = (partCount - 1) * ROW_GAP;
|
|
1602
|
-
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
1603
|
-
const name = truncate2(entry.name, budget);
|
|
1604
|
-
budget -= name.length;
|
|
1605
|
-
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1606
|
-
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1607
|
-
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: timestamp }),
|
|
1608
|
-
/* @__PURE__ */ jsx14(Text16, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1609
|
-
preview && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1610
|
-
durationText && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: durationText })
|
|
1611
|
-
] }, entry.id);
|
|
1612
|
-
}) }),
|
|
1613
|
-
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1614
|
-
] });
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
// src/lib/events.ts
|
|
1618
|
-
import "zod";
|
|
1619
|
-
|
|
1620
|
-
// src/lib/telemetry.ts
|
|
1621
|
-
function isTelemetryOptedOut() {
|
|
1622
|
-
return process.env.WIZARD_TELEMETRY === "false";
|
|
1623
|
-
}
|
|
1624
|
-
function isTelemetryEnabled() {
|
|
1625
|
-
return Boolean(getAuthToken()) && !process.env.VITEST && !isTelemetryOptedOut();
|
|
1626
|
-
}
|
|
1627
|
-
function fireAndForget(promise) {
|
|
1628
|
-
void promise.catch(() => {
|
|
1629
|
-
});
|
|
1630
|
-
}
|
|
1631
|
-
function sendTelemetry(payload) {
|
|
1632
|
-
if (!isTelemetryEnabled()) return;
|
|
1633
|
-
const token = getAuthToken();
|
|
1634
|
-
if (!token) return;
|
|
1635
|
-
const body = {
|
|
1636
|
-
env: process.env.DD_ENV ?? "development",
|
|
1637
|
-
...payload
|
|
1638
|
-
};
|
|
1639
|
-
fireAndForget(
|
|
1640
|
-
proxyFetch(`${PROXY_BASE_URL}/telemetry`, {
|
|
1641
|
-
method: "POST",
|
|
1642
|
-
headers: {
|
|
1643
|
-
"Content-Type": "application/json",
|
|
1644
|
-
authorization: `Bearer ${token}`
|
|
1645
|
-
},
|
|
1646
|
-
body: JSON.stringify(body)
|
|
1647
|
-
}).then((res) => {
|
|
1648
|
-
if (!res.ok) {
|
|
1649
|
-
throw new Error(`Proxy telemetry ${res.status}`);
|
|
1650
|
-
}
|
|
1651
|
-
})
|
|
1652
|
-
);
|
|
1653
|
-
}
|
|
1654
|
-
function sendLog(status, message, attributes = {}, tags = []) {
|
|
1655
|
-
sendTelemetry({
|
|
1656
|
-
logs: [{ status, message, attributes, tags }]
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
function sendMetric(name, value, type, tags = []) {
|
|
1660
|
-
sendTelemetry({
|
|
1661
|
-
metrics: [{ name, value, type, tags }]
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
function metricTags(workflowId, actionId) {
|
|
1665
|
-
return actionId ? [`workflow:${workflowId}`, `action:${actionId}`] : [`workflow:${workflowId}`];
|
|
1666
|
-
}
|
|
1667
|
-
function logTags(workflowId, actionId, appId) {
|
|
1668
|
-
return [
|
|
1669
|
-
...metricTags(workflowId, actionId),
|
|
1670
|
-
...appId ? [`app_id:${appId}`] : []
|
|
1671
|
-
];
|
|
1672
|
-
}
|
|
1673
|
-
function emitTelemetryLog(level, message, attributes, tags) {
|
|
1674
|
-
if (level === "info") {
|
|
1675
|
-
logger.info(attributes, message);
|
|
1676
|
-
} else {
|
|
1677
|
-
logger.error(attributes, message);
|
|
1678
|
-
}
|
|
1679
|
-
sendLog(level, message, attributes, tags);
|
|
1680
|
-
}
|
|
1681
|
-
function trackWorkflowStart(ctx) {
|
|
1682
|
-
const attributes = {
|
|
1683
|
-
event: "wizard.workflow.start",
|
|
1684
|
-
workflow_id: ctx.workflowId,
|
|
1685
|
-
app_id: ctx.appId
|
|
1686
|
-
};
|
|
1687
|
-
emitTelemetryLog(
|
|
1688
|
-
"info",
|
|
1689
|
-
"wizard workflow started",
|
|
1690
|
-
attributes,
|
|
1691
|
-
logTags(ctx.workflowId, void 0, ctx.appId)
|
|
1692
|
-
);
|
|
1693
|
-
sendMetric("wizard.workflow.invocation", 1, 1, metricTags(ctx.workflowId));
|
|
1261
|
+
sendMetric("wizard.workflow.invocation", 1, 1, metricTags(ctx.workflowId));
|
|
1694
1262
|
}
|
|
1695
1263
|
function trackActionStart(ctx) {
|
|
1696
1264
|
const attributes = {
|
|
@@ -1783,6 +1351,7 @@ function trackWorkflowError(ctx) {
|
|
|
1783
1351
|
}
|
|
1784
1352
|
|
|
1785
1353
|
// src/lib/events.ts
|
|
1354
|
+
import "zod";
|
|
1786
1355
|
function track(event, payload) {
|
|
1787
1356
|
if (isTelemetryOptedOut()) return;
|
|
1788
1357
|
const token = getAuthToken();
|
|
@@ -1801,514 +1370,81 @@ function track(event, payload) {
|
|
|
1801
1370
|
});
|
|
1802
1371
|
}
|
|
1803
1372
|
|
|
1804
|
-
// src/
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
import terminalLink from "terminal-link";
|
|
1808
|
-
|
|
1809
|
-
// src/ui/Code.tsx
|
|
1810
|
-
import { Box as Box17, Text as Text17 } from "ink";
|
|
1811
|
-
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1812
|
-
var TOKEN_RE = /("(?:\\.|[^"\\])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\]:,])/g;
|
|
1813
|
-
function highlightJson(json) {
|
|
1814
|
-
const parts = json.split(TOKEN_RE);
|
|
1815
|
-
return parts.map((part, i) => {
|
|
1816
|
-
if (!part) return null;
|
|
1817
|
-
if (part[0] === '"') {
|
|
1818
|
-
const next = parts.slice(i + 1).find((p) => p.trim());
|
|
1819
|
-
const isKey = next?.trimStart().startsWith(":");
|
|
1820
|
-
return /* @__PURE__ */ jsx15(Text17, { color: isKey ? "cyan" : "green", children: part }, i);
|
|
1821
|
-
}
|
|
1822
|
-
if (part === "true" || part === "false" || part === "null") {
|
|
1823
|
-
return /* @__PURE__ */ jsx15(Text17, { color: "magenta", children: part }, i);
|
|
1824
|
-
}
|
|
1825
|
-
if (/^-?\d/.test(part)) {
|
|
1826
|
-
return /* @__PURE__ */ jsx15(Text17, { color: "yellow", children: part }, i);
|
|
1827
|
-
}
|
|
1828
|
-
if (/^[{}[\]:,]$/.test(part)) {
|
|
1829
|
-
return /* @__PURE__ */ jsx15(Text17, { dimColor: true, children: part }, i);
|
|
1830
|
-
}
|
|
1831
|
-
return /* @__PURE__ */ jsx15(Text17, { children: part }, i);
|
|
1832
|
-
});
|
|
1373
|
+
// src/core/orchestrator.ts
|
|
1374
|
+
function defineStep(step) {
|
|
1375
|
+
return { visible: true, ...step };
|
|
1833
1376
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1377
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1378
|
+
function ensureExecutedStepCount(state) {
|
|
1379
|
+
if (state.executedStepCount == null) {
|
|
1380
|
+
state.executedStepCount = state.steps.filter(
|
|
1381
|
+
(s) => s.status === "done" && isStepVisible(s)
|
|
1382
|
+
).length;
|
|
1383
|
+
}
|
|
1836
1384
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
},
|
|
1853
|
-
{
|
|
1854
|
-
title: "A Record is a JSON object inside of an index.",
|
|
1855
|
-
chunks: [
|
|
1856
|
-
{
|
|
1857
|
-
type: "text",
|
|
1858
|
-
value: "Every object you index is just a JSON document with a unique objectID."
|
|
1859
|
-
},
|
|
1860
|
-
{
|
|
1861
|
-
type: "text",
|
|
1862
|
-
value: "No fixed schema, no hard requirements."
|
|
1863
|
-
},
|
|
1864
|
-
{
|
|
1865
|
-
type: "text",
|
|
1866
|
-
value: "Everything else is up to you:"
|
|
1867
|
-
},
|
|
1868
|
-
{
|
|
1869
|
-
type: "code",
|
|
1870
|
-
value: JSON.stringify(
|
|
1871
|
-
{
|
|
1872
|
-
objectID: "sku_48213",
|
|
1873
|
-
name: "Trail Running Shoe",
|
|
1874
|
-
brand: "Northline",
|
|
1875
|
-
categories: ["Footwear", "Running", "Mens"],
|
|
1876
|
-
price: 129.99,
|
|
1877
|
-
in_stock: true,
|
|
1878
|
-
rating: 4.6
|
|
1879
|
-
},
|
|
1880
|
-
null,
|
|
1881
|
-
2
|
|
1882
|
-
)
|
|
1385
|
+
function reconcileWorkflowState(state, workflow) {
|
|
1386
|
+
const persistedIds = state.steps.map((s) => s.id).join("\n");
|
|
1387
|
+
const definedIds = workflow.steps.map((s) => s.id).join("\n");
|
|
1388
|
+
if (persistedIds !== definedIds) return null;
|
|
1389
|
+
for (let i = 0; i < state.steps.length; i++) {
|
|
1390
|
+
const record = state.steps[i];
|
|
1391
|
+
const step = workflow.steps[i];
|
|
1392
|
+
if (record.status === "done") {
|
|
1393
|
+
const parsed = step.outputSchema.safeParse(record.output);
|
|
1394
|
+
if (!parsed.success) {
|
|
1395
|
+
logger.warn(
|
|
1396
|
+
{ stepId: step.id, issues: parsed.error.issues },
|
|
1397
|
+
"reconcileWorkflowState: persisted output for a completed step no longer matches its schema; restarting the run from scratch"
|
|
1398
|
+
);
|
|
1399
|
+
return null;
|
|
1883
1400
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
title: "Speed isn't a feature, it's our business",
|
|
1888
|
-
chunks: [
|
|
1889
|
-
{
|
|
1890
|
-
type: "text",
|
|
1891
|
-
value: "Algolia's engine processes most search queries in 1 to 50 milliseconds."
|
|
1892
|
-
},
|
|
1893
|
-
{
|
|
1894
|
-
type: "text",
|
|
1895
|
-
value: "This is why our search-as-you-type experience feels instant."
|
|
1896
|
-
}
|
|
1897
|
-
]
|
|
1898
|
-
},
|
|
1899
|
-
{
|
|
1900
|
-
title: "Searchable attributes are your relevance dial #1",
|
|
1901
|
-
chunks: [
|
|
1902
|
-
{
|
|
1903
|
-
type: "text",
|
|
1904
|
-
value: "Order matters. Attributes listed first in"
|
|
1905
|
-
},
|
|
1906
|
-
{ type: "codeword", value: "searchableAttributes" },
|
|
1907
|
-
{ type: "text", value: "carry more ranking weight." },
|
|
1908
|
-
{
|
|
1909
|
-
type: "text",
|
|
1910
|
-
value: "This is the single highest-leverage lever new users don't know exists."
|
|
1911
|
-
}
|
|
1912
|
-
]
|
|
1913
|
-
},
|
|
1914
|
-
{
|
|
1915
|
-
title: "Facets aren't just filters, they help build your UI",
|
|
1916
|
-
chunks: [
|
|
1917
|
-
{ type: "codeword", value: "attributesForFaceting" },
|
|
1918
|
-
{
|
|
1919
|
-
type: "text",
|
|
1920
|
-
value: "unlock category sidebars, price sliders, tag clouds without extra backend work."
|
|
1921
|
-
}
|
|
1922
|
-
]
|
|
1923
|
-
},
|
|
1924
|
-
{
|
|
1925
|
-
title: "Test relevance in the dashboard before you write a line of ranking code",
|
|
1926
|
-
chunks: [
|
|
1927
|
-
{
|
|
1928
|
-
type: "text",
|
|
1929
|
-
value: "Our Search dashboard has a live preview where you can browse results."
|
|
1930
|
-
},
|
|
1931
|
-
{
|
|
1932
|
-
type: "text",
|
|
1933
|
-
value: "Tune your settings and see how it alters your results in"
|
|
1934
|
-
},
|
|
1935
|
-
{
|
|
1936
|
-
type: "link",
|
|
1937
|
-
value: "real-time.",
|
|
1938
|
-
href: "https://dashboard.algolia.com/explorer/browse"
|
|
1939
|
-
}
|
|
1940
|
-
]
|
|
1941
|
-
},
|
|
1942
|
-
{
|
|
1943
|
-
title: "Search Analytics helps you discover opportunities",
|
|
1944
|
-
chunks: [
|
|
1945
|
-
{
|
|
1946
|
-
type: "text",
|
|
1947
|
-
value: "No click results for a particular query? Low click-through rates for another?"
|
|
1948
|
-
},
|
|
1949
|
-
{
|
|
1950
|
-
type: "text",
|
|
1951
|
-
value: "Our Search Analytics will help you identify synonyms, rules or other relevancy settings to improve your results."
|
|
1952
|
-
}
|
|
1953
|
-
]
|
|
1954
|
-
},
|
|
1955
|
-
{
|
|
1956
|
-
title: "Let Algolia act as your recommendation engine",
|
|
1957
|
-
chunks: [
|
|
1958
|
-
{
|
|
1959
|
-
type: "text",
|
|
1960
|
-
value: "Beyond search, Algolia Recommend runs models trained on your existing indices and event data to power your recommendation engine."
|
|
1961
|
-
},
|
|
1962
|
-
{
|
|
1963
|
-
type: "text",
|
|
1964
|
-
value: "You can improve engagement with related, popular or visually similar items."
|
|
1965
|
-
}
|
|
1966
|
-
]
|
|
1401
|
+
}
|
|
1402
|
+
record.title = step.title;
|
|
1403
|
+
record.visible = step.visible;
|
|
1967
1404
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
// src/ui/Tips.tsx
|
|
1971
|
-
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1972
|
-
var TICK_MS = 16;
|
|
1973
|
-
var CHUNK_HOLD_MS = 2e3;
|
|
1974
|
-
var HOLD_MS = 8e3;
|
|
1975
|
-
function TipTitle({ children }) {
|
|
1976
|
-
return /* @__PURE__ */ jsxs16(Box18, { gap: 1, children: [
|
|
1977
|
-
/* @__PURE__ */ jsx16(Text18, { color: "cyan", children: "\u2726" }),
|
|
1978
|
-
/* @__PURE__ */ jsx16(Text18, { color: "white", bold: true, children })
|
|
1979
|
-
] });
|
|
1405
|
+
return state;
|
|
1980
1406
|
}
|
|
1981
|
-
function
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1407
|
+
function initWorkflowState(workflow, now) {
|
|
1408
|
+
return {
|
|
1409
|
+
workflowId: workflow.id,
|
|
1410
|
+
startedAt: now,
|
|
1411
|
+
updatedAt: now,
|
|
1412
|
+
currentStepIndex: 0,
|
|
1413
|
+
steps: workflow.steps.map((s) => ({
|
|
1414
|
+
id: s.id,
|
|
1415
|
+
title: s.title,
|
|
1416
|
+
visible: s.visible,
|
|
1417
|
+
status: "pending"
|
|
1418
|
+
})),
|
|
1419
|
+
algoliaState: {},
|
|
1420
|
+
userInputs: {}
|
|
1421
|
+
};
|
|
1992
1422
|
}
|
|
1993
|
-
function
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
return slice;
|
|
1423
|
+
async function ensureConsent() {
|
|
1424
|
+
const config = await loadConfig();
|
|
1425
|
+
if (config.aiConsent) return;
|
|
1426
|
+
const store = useWizard.getState();
|
|
1427
|
+
const answer = await store.requestUserInput({
|
|
1428
|
+
prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
|
|
1429
|
+
promptType: "enterToContinue",
|
|
1430
|
+
options: []
|
|
2002
1431
|
});
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
const slice = slices[i];
|
|
2007
|
-
if (!slice) continue;
|
|
2008
|
-
const segment = {
|
|
2009
|
-
type: chunk.type,
|
|
2010
|
-
value: slice,
|
|
2011
|
-
href: chunk.type === "link" ? chunk.href : void 0
|
|
2012
|
-
};
|
|
2013
|
-
if (chunk.type === "code") {
|
|
2014
|
-
blocks.push({ type: "code", segment });
|
|
2015
|
-
continue;
|
|
2016
|
-
}
|
|
2017
|
-
const last = blocks[blocks.length - 1];
|
|
2018
|
-
if (last?.type === "inline") {
|
|
2019
|
-
last.segments.push(segment);
|
|
2020
|
-
} else {
|
|
2021
|
-
blocks.push({ type: "inline", segments: [segment] });
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
return /* @__PURE__ */ jsx16(Box18, { flexDirection: "column", marginLeft: 2, gap: 1, children: blocks.map(
|
|
2025
|
-
(block, i) => block.type === "code" ? /* @__PURE__ */ jsx16(Code, { children: block.segment.value }, i) : /* @__PURE__ */ jsx16(Text18, { children: block.segments.map((segment, j) => /* @__PURE__ */ jsxs16(Text18, { children: [
|
|
2026
|
-
j > 0 && " ",
|
|
2027
|
-
/* @__PURE__ */ jsx16(InlineSegment, { segment })
|
|
2028
|
-
] }, j)) }, i)
|
|
2029
|
-
) });
|
|
2030
|
-
}
|
|
2031
|
-
function Tips() {
|
|
2032
|
-
const [tipIndex, setTipIndex] = useState8(0);
|
|
2033
|
-
const [revealed, setRevealed] = useState8(0);
|
|
2034
|
-
const tip = tips[tipIndex];
|
|
2035
|
-
const contentLength = tip.chunks.reduce((sum, c) => sum + c.value.length, 0);
|
|
2036
|
-
const totalLength = tip.title.length + contentLength;
|
|
2037
|
-
const segments = [
|
|
2038
|
-
{ type: "title", length: tip.title.length },
|
|
2039
|
-
...tip.chunks.map((c) => ({ type: c.type, length: c.value.length }))
|
|
2040
|
-
];
|
|
2041
|
-
const noPauseAfterText = [
|
|
2042
|
-
"highlight",
|
|
2043
|
-
"link",
|
|
2044
|
-
"codeword"
|
|
2045
|
-
];
|
|
2046
|
-
const chunkBoundaries = [];
|
|
2047
|
-
let cumulative = 0;
|
|
2048
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
2049
|
-
cumulative += segments[i].length;
|
|
2050
|
-
const skipPause = noPauseAfterText.includes(
|
|
2051
|
-
segments[i + 1].type
|
|
1432
|
+
if (answer !== true) {
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
"AI consent declined \u2014 cannot proceed. If you change your mind, just run the Wizard again!"
|
|
2052
1435
|
);
|
|
2053
|
-
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
2054
1436
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
const delay2 = chunkBoundaries.includes(revealed) ? CHUNK_HOLD_MS : TICK_MS;
|
|
2058
|
-
const timer2 = setTimeout(() => setRevealed((r) => r + 1), delay2);
|
|
2059
|
-
return () => clearTimeout(timer2);
|
|
2060
|
-
}
|
|
2061
|
-
const timer = setTimeout(() => {
|
|
2062
|
-
setTipIndex((i) => {
|
|
2063
|
-
if (i < tips.length - 1) return i + 1;
|
|
2064
|
-
return 0;
|
|
2065
|
-
});
|
|
2066
|
-
setRevealed(0);
|
|
2067
|
-
}, HOLD_MS);
|
|
2068
|
-
return () => clearTimeout(timer);
|
|
2069
|
-
}, [revealed, totalLength]);
|
|
2070
|
-
const titleRevealed = tip.title.slice(0, revealed);
|
|
2071
|
-
const contentRevealed = Math.max(0, revealed - tip.title.length);
|
|
2072
|
-
return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", marginBottom: 1, gap: 1, children: [
|
|
2073
|
-
/* @__PURE__ */ jsx16(TipTitle, { children: titleRevealed }),
|
|
2074
|
-
/* @__PURE__ */ jsx16(TipContent, { chunks: tip.chunks, revealed: contentRevealed })
|
|
2075
|
-
] });
|
|
1437
|
+
config.aiConsent = true;
|
|
1438
|
+
await saveConfig(config);
|
|
2076
1439
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
currentStepIndex,
|
|
2086
|
-
steps,
|
|
2087
|
-
inputReq,
|
|
2088
|
-
user,
|
|
2089
|
-
notices
|
|
2090
|
-
} = useWizard();
|
|
2091
|
-
const { exit } = useApp();
|
|
2092
|
-
const { columns, rows } = useWindowSize7();
|
|
2093
|
-
const [showLogs, setShowLogs] = useState9(false);
|
|
2094
|
-
const finished = phase === "done" || phase === "error";
|
|
2095
|
-
const currentStep = steps[currentStepIndex];
|
|
2096
|
-
const isInitialAnalysisStep = currentStepIndex === 0;
|
|
2097
|
-
const isAwaitingUserInput = phase === "awaitingInput" && !!inputReq;
|
|
2098
|
-
const showTips = phase === "running" && isInitialAnalysisStep && notices.length > 0;
|
|
2099
|
-
const showNoticesInMain = !isInitialAnalysisStep;
|
|
2100
|
-
const showNotices = !isAwaitingUserInput;
|
|
2101
|
-
useInput6(
|
|
2102
|
-
(_input, key) => {
|
|
2103
|
-
if (key.return) {
|
|
2104
|
-
exit();
|
|
2105
|
-
}
|
|
2106
|
-
},
|
|
2107
|
-
{ isActive: finished }
|
|
2108
|
-
);
|
|
2109
|
-
useInput6((_input, key) => {
|
|
2110
|
-
if (phase === "idle" || phase === "authenticating") return;
|
|
2111
|
-
if (key.tab) {
|
|
2112
|
-
setShowLogs(!showLogs);
|
|
2113
|
-
track("AI Wizard Interaction", {
|
|
2114
|
-
context: "global",
|
|
2115
|
-
key: "tab",
|
|
2116
|
-
currentStep: currentStep?.id
|
|
2117
|
-
});
|
|
2118
|
-
}
|
|
2119
|
-
});
|
|
2120
|
-
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
2121
|
-
useInput6((_input, key) => {
|
|
2122
|
-
if (escOwnedElsewhere) return;
|
|
2123
|
-
if (key.escape) {
|
|
2124
|
-
track("AI Wizard Interaction", {
|
|
2125
|
-
context: "global",
|
|
2126
|
-
key: "esc",
|
|
2127
|
-
currentStep: currentStep?.id ?? phase
|
|
2128
|
-
});
|
|
2129
|
-
exit();
|
|
2130
|
-
}
|
|
2131
|
-
});
|
|
2132
|
-
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
2133
|
-
const flexDirection = columns > 90 ? "row" : "column";
|
|
2134
|
-
const showSidebar = flexDirection === "row";
|
|
2135
|
-
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
2136
|
-
return (
|
|
2137
|
-
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
2138
|
-
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
2139
|
-
flicker and leftover rows. */
|
|
2140
|
-
/* @__PURE__ */ jsxs17(
|
|
2141
|
-
Box19,
|
|
2142
|
-
{
|
|
2143
|
-
backgroundColor: COLORS.bg.main,
|
|
2144
|
-
flexDirection: "row",
|
|
2145
|
-
width: columns,
|
|
2146
|
-
height: scrollsPastViewport ? void 0 : rows,
|
|
2147
|
-
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
2148
|
-
children: [
|
|
2149
|
-
mainWindowVisible && /* @__PURE__ */ jsxs17(
|
|
2150
|
-
Box19,
|
|
2151
|
-
{
|
|
2152
|
-
flexDirection,
|
|
2153
|
-
width: "100%",
|
|
2154
|
-
maxHeight: rows,
|
|
2155
|
-
justifyContent: "space-between",
|
|
2156
|
-
children: [
|
|
2157
|
-
showLogs ? /* @__PURE__ */ jsx17(Logs, {}) : /* @__PURE__ */ jsxs17(
|
|
2158
|
-
Box19,
|
|
2159
|
-
{
|
|
2160
|
-
flexDirection: "column",
|
|
2161
|
-
paddingX: 4,
|
|
2162
|
-
paddingY: 2,
|
|
2163
|
-
width: showSidebar ? 70 : "100%",
|
|
2164
|
-
flexGrow: 1,
|
|
2165
|
-
gap: 1,
|
|
2166
|
-
children: [
|
|
2167
|
-
/* @__PURE__ */ jsxs17(Box19, { flexGrow: 2, flexDirection: "column", children: [
|
|
2168
|
-
phase === "preflight" && !user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
2169
|
-
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
2170
|
-
" Signing in to Algolia"
|
|
2171
|
-
] }) }),
|
|
2172
|
-
phase === "preflight" && user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
2173
|
-
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
2174
|
-
" Getting things ready"
|
|
2175
|
-
] }) }),
|
|
2176
|
-
phase === "authenticating" && /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", marginBottom: 1, children: [
|
|
2177
|
-
/* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
2178
|
-
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
2179
|
-
" Signing in to Algolia"
|
|
2180
|
-
] }),
|
|
2181
|
-
/* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
2182
|
-
] }),
|
|
2183
|
-
/* @__PURE__ */ jsx17(CliOutput, {}),
|
|
2184
|
-
showTips && /* @__PURE__ */ jsx17(Tips, {}),
|
|
2185
|
-
showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, { showAll: true, border: false }),
|
|
2186
|
-
/* @__PURE__ */ jsx17(PromptInput, {}),
|
|
2187
|
-
phase === "error" && error && /* @__PURE__ */ jsx17(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.status.error, children: [
|
|
2188
|
-
"\u2716 ",
|
|
2189
|
-
error
|
|
2190
|
-
] }) })
|
|
2191
|
-
] }),
|
|
2192
|
-
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, {})
|
|
2193
|
-
]
|
|
2194
|
-
}
|
|
2195
|
-
),
|
|
2196
|
-
showSidebar ? /* @__PURE__ */ jsx17(Sidebar, {}) : /* @__PURE__ */ jsx17(Ribbon, {})
|
|
2197
|
-
]
|
|
2198
|
-
}
|
|
2199
|
-
),
|
|
2200
|
-
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx17(LearnMore, {}) : /* @__PURE__ */ jsx17(Welcome, {}))
|
|
2201
|
-
]
|
|
2202
|
-
}
|
|
2203
|
-
)
|
|
2204
|
-
);
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
// src/core/orchestrator.ts
|
|
2208
|
-
import "zod";
|
|
2209
|
-
|
|
2210
|
-
// src/core/config.ts
|
|
2211
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2212
|
-
import { join as join5 } from "node:path";
|
|
2213
|
-
var configFile = () => join5(stateDir(), "config.json");
|
|
2214
|
-
var defaultConfig = () => ({
|
|
2215
|
-
version: 1,
|
|
2216
|
-
aiConsent: false,
|
|
2217
|
-
workflowsRun: []
|
|
2218
|
-
});
|
|
2219
|
-
async function loadConfig() {
|
|
2220
|
-
try {
|
|
2221
|
-
const raw = await readFile2(configFile(), "utf8");
|
|
2222
|
-
return { ...defaultConfig(), ...JSON.parse(raw) };
|
|
2223
|
-
} catch {
|
|
2224
|
-
return defaultConfig();
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
async function saveConfig(config) {
|
|
2228
|
-
await mkdir2(stateDir(), { recursive: true });
|
|
2229
|
-
await writeFile2(configFile(), JSON.stringify(config, null, 2), "utf8");
|
|
2230
|
-
}
|
|
2231
|
-
async function recordWorkflowRun(workflowId, completedAt) {
|
|
2232
|
-
const config = await loadConfig();
|
|
2233
|
-
config.workflowsRun.push({ workflowId, completedAt });
|
|
2234
|
-
await saveConfig(config);
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
// src/core/orchestrator.ts
|
|
2238
|
-
function defineStep(step) {
|
|
2239
|
-
return { visible: true, ...step };
|
|
2240
|
-
}
|
|
2241
|
-
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2242
|
-
function ensureExecutedStepCount(state) {
|
|
2243
|
-
if (state.executedStepCount == null) {
|
|
2244
|
-
state.executedStepCount = state.steps.filter(
|
|
2245
|
-
(s) => s.status === "done" && isStepVisible(s)
|
|
2246
|
-
).length;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
function reconcileWorkflowState(state, workflow) {
|
|
2250
|
-
const persistedIds = state.steps.map((s) => s.id).join("\n");
|
|
2251
|
-
const definedIds = workflow.steps.map((s) => s.id).join("\n");
|
|
2252
|
-
if (persistedIds !== definedIds) return null;
|
|
2253
|
-
for (let i = 0; i < state.steps.length; i++) {
|
|
2254
|
-
const record = state.steps[i];
|
|
2255
|
-
const step = workflow.steps[i];
|
|
2256
|
-
if (record.status === "done") {
|
|
2257
|
-
const parsed = step.outputSchema.safeParse(record.output);
|
|
2258
|
-
if (!parsed.success) {
|
|
2259
|
-
logger.warn(
|
|
2260
|
-
{ stepId: step.id, issues: parsed.error.issues },
|
|
2261
|
-
"reconcileWorkflowState: persisted output for a completed step no longer matches its schema; restarting the run from scratch"
|
|
2262
|
-
);
|
|
2263
|
-
return null;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
record.title = step.title;
|
|
2267
|
-
record.visible = step.visible;
|
|
2268
|
-
}
|
|
2269
|
-
return state;
|
|
2270
|
-
}
|
|
2271
|
-
function initWorkflowState(workflow, now) {
|
|
2272
|
-
return {
|
|
2273
|
-
workflowId: workflow.id,
|
|
2274
|
-
startedAt: now,
|
|
2275
|
-
updatedAt: now,
|
|
2276
|
-
currentStepIndex: 0,
|
|
2277
|
-
steps: workflow.steps.map((s) => ({
|
|
2278
|
-
id: s.id,
|
|
2279
|
-
title: s.title,
|
|
2280
|
-
visible: s.visible,
|
|
2281
|
-
status: "pending"
|
|
2282
|
-
})),
|
|
2283
|
-
algoliaState: {},
|
|
2284
|
-
userInputs: {}
|
|
2285
|
-
};
|
|
2286
|
-
}
|
|
2287
|
-
async function ensureConsent() {
|
|
2288
|
-
const config = await loadConfig();
|
|
2289
|
-
if (config.aiConsent) return;
|
|
2290
|
-
const store = useWizard.getState();
|
|
2291
|
-
const answer = await store.requestUserInput({
|
|
2292
|
-
prompt: "Wizard will make AI-authored changes to this repository, and will propose shell commands to set it up. You approve each command before it runs.",
|
|
2293
|
-
promptType: "enterToContinue",
|
|
2294
|
-
options: []
|
|
2295
|
-
});
|
|
2296
|
-
if (answer !== true) {
|
|
2297
|
-
throw new Error(
|
|
2298
|
-
"AI consent declined \u2014 cannot proceed. If you change your mind, just run the Wizard again!"
|
|
2299
|
-
);
|
|
2300
|
-
}
|
|
2301
|
-
config.aiConsent = true;
|
|
2302
|
-
await saveConfig(config);
|
|
2303
|
-
}
|
|
2304
|
-
async function makeContext(state) {
|
|
2305
|
-
const outputs = {};
|
|
2306
|
-
const completedSteps = [];
|
|
2307
|
-
for (const s of state.steps) {
|
|
2308
|
-
if (s.status === "done") {
|
|
2309
|
-
outputs[s.id] = s.output;
|
|
2310
|
-
if (s.visible) {
|
|
2311
|
-
completedSteps.push({ id: s.id, title: s.title, output: s.output });
|
|
1440
|
+
async function makeContext(state) {
|
|
1441
|
+
const outputs = {};
|
|
1442
|
+
const completedSteps = [];
|
|
1443
|
+
for (const s of state.steps) {
|
|
1444
|
+
if (s.status === "done") {
|
|
1445
|
+
outputs[s.id] = s.output;
|
|
1446
|
+
if (s.visible) {
|
|
1447
|
+
completedSteps.push({ id: s.id, title: s.title, output: s.output });
|
|
2312
1448
|
}
|
|
2313
1449
|
}
|
|
2314
1450
|
}
|
|
@@ -2448,219 +1584,20 @@ async function runWorkflow(workflow, appId) {
|
|
|
2448
1584
|
}
|
|
2449
1585
|
}
|
|
2450
1586
|
|
|
2451
|
-
// src/
|
|
1587
|
+
// src/actions/listIndices.ts
|
|
2452
1588
|
import { z as z4 } from "zod";
|
|
2453
|
-
var
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
1589
|
+
var indicesListSchema = z4.object({
|
|
1590
|
+
items: z4.array(
|
|
1591
|
+
z4.object({
|
|
1592
|
+
name: z4.string(),
|
|
1593
|
+
entries: z4.number().default(0)
|
|
1594
|
+
})
|
|
1595
|
+
)
|
|
2457
1596
|
});
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
plan_label: z4.string().optional()
|
|
2463
|
-
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
2464
|
-
);
|
|
2465
|
-
async function currentApplication() {
|
|
2466
|
-
let raw;
|
|
2467
|
-
try {
|
|
2468
|
-
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
2469
|
-
} catch {
|
|
2470
|
-
return null;
|
|
2471
|
-
}
|
|
2472
|
-
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2473
|
-
return parsed.success ? parsed.data : null;
|
|
2474
|
-
}
|
|
2475
|
-
async function requireApplication() {
|
|
2476
|
-
const app = await currentApplication();
|
|
2477
|
-
if (!app) {
|
|
2478
|
-
throw new Error(
|
|
2479
|
-
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
2480
|
-
);
|
|
2481
|
-
}
|
|
2482
|
-
return app;
|
|
2483
|
-
}
|
|
2484
|
-
async function listApplications() {
|
|
2485
|
-
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
2486
|
-
const parsed = listSchema.safeParse(parseJson(raw));
|
|
2487
|
-
if (!parsed.success) {
|
|
2488
|
-
throw new Error("Could not read the list of Algolia applications.");
|
|
2489
|
-
}
|
|
2490
|
-
return parsed.data;
|
|
2491
|
-
}
|
|
2492
|
-
async function selectApplication(id) {
|
|
2493
|
-
const raw = await runAlgoliaCli(
|
|
2494
|
-
["application", "select", "--non-interactive", "--app-id", id],
|
|
2495
|
-
{ onOutput: stderrSink }
|
|
2496
|
-
);
|
|
2497
|
-
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
2498
|
-
if (!parsed.success) {
|
|
2499
|
-
throw new Error(
|
|
2500
|
-
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
2501
|
-
);
|
|
2502
|
-
}
|
|
2503
|
-
return parsed.data;
|
|
2504
|
-
}
|
|
2505
|
-
function parseJson(text) {
|
|
2506
|
-
try {
|
|
2507
|
-
return JSON.parse(text);
|
|
2508
|
-
} catch {
|
|
2509
|
-
return void 0;
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
// src/lib/envAppId.ts
|
|
2514
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
2515
|
-
import { join as join6 } from "node:path";
|
|
2516
|
-
var ENV_FILES = [".env", ".env.local"];
|
|
2517
|
-
var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
|
|
2518
|
-
async function findEnvApplicationId(root = process.cwd()) {
|
|
2519
|
-
for (const file of ENV_FILES) {
|
|
2520
|
-
let content;
|
|
2521
|
-
try {
|
|
2522
|
-
content = await readFile3(join6(root, file), "utf8");
|
|
2523
|
-
} catch (err) {
|
|
2524
|
-
if (err.code !== "ENOENT") {
|
|
2525
|
-
logger.warn(
|
|
2526
|
-
{ file, err },
|
|
2527
|
-
"could not read env file for an application id"
|
|
2528
|
-
);
|
|
2529
|
-
}
|
|
2530
|
-
continue;
|
|
2531
|
-
}
|
|
2532
|
-
for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
|
|
2533
|
-
const id = readValue(raw);
|
|
2534
|
-
if (id) {
|
|
2535
|
-
logger.info({ file, name, app: id }, "found an application id in env");
|
|
2536
|
-
return { id, name, file };
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
return null;
|
|
2541
|
-
}
|
|
2542
|
-
function readValue(raw) {
|
|
2543
|
-
const trimmed = raw.trim();
|
|
2544
|
-
const quoted = trimmed.match(/^(['"])(.*)\1/);
|
|
2545
|
-
const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
|
|
2546
|
-
return value.length > 0 && !value.startsWith("<") ? value : null;
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
// src/lib/algoliaAppPicker.ts
|
|
2550
|
-
function secondaryFor(app) {
|
|
2551
|
-
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
2552
|
-
}
|
|
2553
|
-
function labelFor(app) {
|
|
2554
|
-
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
2555
|
-
}
|
|
2556
|
-
function selectAndReport(app) {
|
|
2557
|
-
useWizard.getState().pushCliOutput(
|
|
2558
|
-
"stdout",
|
|
2559
|
-
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
2560
|
-
);
|
|
2561
|
-
return selectApplication(app.id);
|
|
2562
|
-
}
|
|
2563
|
-
async function promptForApplication(leadIn = []) {
|
|
2564
|
-
const store = useWizard.getState();
|
|
2565
|
-
const apps = await listApplications();
|
|
2566
|
-
if (apps.length === 0) {
|
|
2567
|
-
throw new Error(
|
|
2568
|
-
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
2569
|
-
);
|
|
2570
|
-
}
|
|
2571
|
-
if (apps.length === 1) {
|
|
2572
|
-
const only = apps[0];
|
|
2573
|
-
logger.info(
|
|
2574
|
-
{ app: only.id },
|
|
2575
|
-
"single application on the account; selecting it"
|
|
2576
|
-
);
|
|
2577
|
-
for (const line of leadIn) store.pushCliOutput("stdout", line);
|
|
2578
|
-
return selectAndReport(only);
|
|
2579
|
-
}
|
|
2580
|
-
const messages = [
|
|
2581
|
-
...leadIn,
|
|
2582
|
-
"Which Algolia application should the wizard work in?"
|
|
2583
|
-
];
|
|
2584
|
-
for (; ; ) {
|
|
2585
|
-
const choice = await store.requestUserInput({
|
|
2586
|
-
prompt: "Select an application",
|
|
2587
|
-
promptType: "multipleChoice",
|
|
2588
|
-
options: apps.map(labelFor),
|
|
2589
|
-
secondary: apps.map(secondaryFor),
|
|
2590
|
-
messages
|
|
2591
|
-
});
|
|
2592
|
-
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
2593
|
-
if (!chosen) {
|
|
2594
|
-
throw new Error("Application picker received an unexpected selection");
|
|
2595
|
-
}
|
|
2596
|
-
try {
|
|
2597
|
-
return await selectAndReport(chosen);
|
|
2598
|
-
} catch (err) {
|
|
2599
|
-
logger.warn(
|
|
2600
|
-
{ app: chosen.id, err: err.message },
|
|
2601
|
-
"application select failed; re-prompting"
|
|
2602
|
-
);
|
|
2603
|
-
messages.push(
|
|
2604
|
-
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
async function confirmEnvApplication(env, current) {
|
|
2610
|
-
const useEnv = `Use ${env.id} (from ${env.file})`;
|
|
2611
|
-
const choice = await useWizard.getState().requestUserInput({
|
|
2612
|
-
prompt: "Select an application",
|
|
2613
|
-
promptType: "multipleChoice",
|
|
2614
|
-
options: [
|
|
2615
|
-
useEnv,
|
|
2616
|
-
current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
|
|
2617
|
-
],
|
|
2618
|
-
messages: [
|
|
2619
|
-
`${env.file} already sets ${env.name}=${env.id}.`,
|
|
2620
|
-
"Which Algolia application should the wizard work in?"
|
|
2621
|
-
]
|
|
2622
|
-
});
|
|
2623
|
-
return choice === useEnv;
|
|
2624
|
-
}
|
|
2625
|
-
async function selectEnvApplication(env) {
|
|
2626
|
-
try {
|
|
2627
|
-
return await selectAndReport({ id: env.id, name: "" });
|
|
2628
|
-
} catch (err) {
|
|
2629
|
-
logger.warn(
|
|
2630
|
-
{ app: env.id, err: err.message },
|
|
2631
|
-
"could not select the application named in env; falling back to the picker"
|
|
2632
|
-
);
|
|
2633
|
-
return promptForApplication([
|
|
2634
|
-
`Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
|
|
2635
|
-
]);
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
async function ensureApplication() {
|
|
2639
|
-
const current = await currentApplication();
|
|
2640
|
-
const env = await findEnvApplicationId();
|
|
2641
|
-
if (env && env.id !== current?.id && await confirmEnvApplication(env, current)) {
|
|
2642
|
-
return selectEnvApplication(env);
|
|
2643
|
-
}
|
|
2644
|
-
return current ?? await promptForApplication();
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
// src/workflows/default.ts
|
|
2648
|
-
import { z as z29 } from "zod";
|
|
2649
|
-
|
|
2650
|
-
// src/actions/listIndices.ts
|
|
2651
|
-
import { z as z5 } from "zod";
|
|
2652
|
-
var indicesListSchema = z5.object({
|
|
2653
|
-
items: z5.array(
|
|
2654
|
-
z5.object({
|
|
2655
|
-
name: z5.string(),
|
|
2656
|
-
entries: z5.number().default(0)
|
|
2657
|
-
})
|
|
2658
|
-
)
|
|
2659
|
-
});
|
|
2660
|
-
async function listIndices() {
|
|
2661
|
-
const stdout = await runAlgoliaCli(["indices", "list", "-o", "json"]);
|
|
2662
|
-
const { items } = indicesListSchema.parse(JSON.parse(stdout));
|
|
2663
|
-
return items.map((i) => ({ name: i.name, entries: i.entries })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1597
|
+
async function listIndices() {
|
|
1598
|
+
const stdout = await runAlgoliaCli(["indices", "list", "-o", "json"]);
|
|
1599
|
+
const { items } = indicesListSchema.parse(JSON.parse(stdout));
|
|
1600
|
+
return items.map((i) => ({ name: i.name, entries: i.entries })).sort((a, b) => a.name.localeCompare(b.name));
|
|
2664
1601
|
}
|
|
2665
1602
|
|
|
2666
1603
|
// src/actions/selectIndex.ts
|
|
@@ -2715,8 +1652,8 @@ var selectIndexStep = async (ctx) => {
|
|
|
2715
1652
|
};
|
|
2716
1653
|
|
|
2717
1654
|
// src/lib/agent.ts
|
|
2718
|
-
import { ToolLoopAgent, hasToolCall, Output as
|
|
2719
|
-
import { createAnthropic as
|
|
1655
|
+
import { ToolLoopAgent, hasToolCall, Output as Output3 } from "ai";
|
|
1656
|
+
import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
|
|
2720
1657
|
import "zod";
|
|
2721
1658
|
|
|
2722
1659
|
// src/lib/tools/index.ts
|
|
@@ -2724,12 +1661,12 @@ import "zod";
|
|
|
2724
1661
|
|
|
2725
1662
|
// src/lib/tools/listFiles.ts
|
|
2726
1663
|
import { tool } from "ai";
|
|
2727
|
-
import
|
|
1664
|
+
import z5 from "zod";
|
|
2728
1665
|
import { readdir } from "node:fs/promises";
|
|
2729
1666
|
|
|
2730
1667
|
// src/lib/tools/path.ts
|
|
2731
1668
|
import { lstat } from "node:fs/promises";
|
|
2732
|
-
import { resolve as resolve2, relative, isAbsolute, dirname as
|
|
1669
|
+
import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join5, sep } from "node:path";
|
|
2733
1670
|
function resolveInRoot(ctx, path) {
|
|
2734
1671
|
const target = resolve2(ctx.cwd, path);
|
|
2735
1672
|
const rel = relative(ctx.root, target);
|
|
@@ -2743,9 +1680,9 @@ function resolveInRoot(ctx, path) {
|
|
|
2743
1680
|
}
|
|
2744
1681
|
async function hasSymlinkParent(ctx, target) {
|
|
2745
1682
|
let current = ctx.root;
|
|
2746
|
-
const parts = relative(ctx.root,
|
|
1683
|
+
const parts = relative(ctx.root, dirname2(target)).split(sep).filter(Boolean);
|
|
2747
1684
|
for (const part of parts) {
|
|
2748
|
-
current =
|
|
1685
|
+
current = join5(current, part);
|
|
2749
1686
|
try {
|
|
2750
1687
|
if ((await lstat(current)).isSymbolicLink()) return true;
|
|
2751
1688
|
} catch (err) {
|
|
@@ -2759,36 +1696,30 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
2759
1696
|
// src/lib/tools/listFiles.ts
|
|
2760
1697
|
function listFilesTool(ctx) {
|
|
2761
1698
|
return tool({
|
|
2762
|
-
description:
|
|
2763
|
-
inputSchema:
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
execute: async ({ path = "." }) => {
|
|
2767
|
-
logger.info({ path }, "called listFiles tool");
|
|
1699
|
+
description: "List files in the current working directory",
|
|
1700
|
+
inputSchema: z5.object(),
|
|
1701
|
+
execute: async () => {
|
|
1702
|
+
logger.info("called listFiles tool");
|
|
2768
1703
|
if (++ctx.counts.list > ctx.limits.list) {
|
|
2769
1704
|
return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
|
|
2770
1705
|
}
|
|
2771
|
-
const resolved2 = resolveInRoot(ctx,
|
|
1706
|
+
const resolved2 = resolveInRoot(ctx, ".");
|
|
2772
1707
|
if (!resolved2.ok) return resolved2.error;
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
2776
|
-
} catch (err) {
|
|
2777
|
-
return `Error listing ${path}: ${err.message}`;
|
|
2778
|
-
}
|
|
1708
|
+
const entries = await readdir(resolved2.target, { withFileTypes: true });
|
|
1709
|
+
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
2779
1710
|
}
|
|
2780
1711
|
});
|
|
2781
1712
|
}
|
|
2782
1713
|
|
|
2783
1714
|
// src/lib/tools/changeDirectory.ts
|
|
2784
1715
|
import { tool as tool2 } from "ai";
|
|
2785
|
-
import
|
|
1716
|
+
import z6 from "zod";
|
|
2786
1717
|
import { stat } from "node:fs/promises";
|
|
2787
1718
|
function changeDirectoryTool(ctx) {
|
|
2788
1719
|
return tool2({
|
|
2789
1720
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
2790
|
-
inputSchema:
|
|
2791
|
-
path:
|
|
1721
|
+
inputSchema: z6.object({
|
|
1722
|
+
path: z6.string().describe("Directory to change into")
|
|
2792
1723
|
}),
|
|
2793
1724
|
execute: async ({ path }) => {
|
|
2794
1725
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -2810,13 +1741,13 @@ function changeDirectoryTool(ctx) {
|
|
|
2810
1741
|
|
|
2811
1742
|
// src/lib/tools/reportStatus.ts
|
|
2812
1743
|
import { tool as tool3 } from "ai";
|
|
2813
|
-
import
|
|
1744
|
+
import z7 from "zod";
|
|
2814
1745
|
function reportStatusTool(output) {
|
|
2815
1746
|
return tool3({
|
|
2816
1747
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2817
|
-
inputSchema:
|
|
2818
|
-
status:
|
|
2819
|
-
reason:
|
|
1748
|
+
inputSchema: z7.object({
|
|
1749
|
+
status: z7.enum(["success", "fail"]),
|
|
1750
|
+
reason: z7.string().optional(),
|
|
2820
1751
|
output
|
|
2821
1752
|
}),
|
|
2822
1753
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2828,8 +1759,8 @@ function reportStatusTool(output) {
|
|
|
2828
1759
|
|
|
2829
1760
|
// src/lib/tools/readFile.ts
|
|
2830
1761
|
import { tool as tool4 } from "ai";
|
|
2831
|
-
import
|
|
2832
|
-
import { readFile as
|
|
1762
|
+
import z8 from "zod";
|
|
1763
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2833
1764
|
|
|
2834
1765
|
// src/lib/tools/env.ts
|
|
2835
1766
|
import { basename } from "node:path";
|
|
@@ -2856,8 +1787,8 @@ function redactEnvValues(content) {
|
|
|
2856
1787
|
function readFileTool(ctx) {
|
|
2857
1788
|
return tool4({
|
|
2858
1789
|
description: "Read the contents of a file at the given path",
|
|
2859
|
-
inputSchema:
|
|
2860
|
-
filePath:
|
|
1790
|
+
inputSchema: z8.object({
|
|
1791
|
+
filePath: z8.string().describe("Path to the file to read")
|
|
2861
1792
|
}),
|
|
2862
1793
|
execute: async ({ filePath }) => {
|
|
2863
1794
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2867,7 +1798,7 @@ function readFileTool(ctx) {
|
|
|
2867
1798
|
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2868
1799
|
if (!resolved2.ok) return resolved2.error;
|
|
2869
1800
|
try {
|
|
2870
|
-
const content = await
|
|
1801
|
+
const content = await readFile3(resolved2.target, "utf8");
|
|
2871
1802
|
return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
|
|
2872
1803
|
} catch (err) {
|
|
2873
1804
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2878,15 +1809,15 @@ function readFileTool(ctx) {
|
|
|
2878
1809
|
|
|
2879
1810
|
// src/lib/tools/writeFile.ts
|
|
2880
1811
|
import { tool as tool5 } from "ai";
|
|
2881
|
-
import
|
|
1812
|
+
import z9 from "zod";
|
|
2882
1813
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2883
|
-
import { dirname as
|
|
1814
|
+
import { dirname as dirname3 } from "node:path";
|
|
2884
1815
|
function writeFileTool(ctx) {
|
|
2885
1816
|
return tool5({
|
|
2886
1817
|
description: "Write content to a file at the given path, overwriting it. To set Algolia credentials in an env file, use writeCredentials instead of this tool.",
|
|
2887
|
-
inputSchema:
|
|
2888
|
-
filePath:
|
|
2889
|
-
content:
|
|
1818
|
+
inputSchema: z9.object({
|
|
1819
|
+
filePath: z9.string().describe("Path to the file to write"),
|
|
1820
|
+
content: z9.string().describe("Content to write to the file")
|
|
2890
1821
|
}),
|
|
2891
1822
|
execute: async ({ filePath, content }) => {
|
|
2892
1823
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2899,7 +1830,7 @@ function writeFileTool(ctx) {
|
|
|
2899
1830
|
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2900
1831
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2901
1832
|
}
|
|
2902
|
-
await mkdir3(
|
|
1833
|
+
await mkdir3(dirname3(resolved2.target), { recursive: true });
|
|
2903
1834
|
await writeFile3(resolved2.target, content, "utf8");
|
|
2904
1835
|
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
2905
1836
|
return `Wrote to ${filePath}`;
|
|
@@ -2913,8 +1844,70 @@ function writeFileTool(ctx) {
|
|
|
2913
1844
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2914
1845
|
import { tool as tool6 } from "ai";
|
|
2915
1846
|
import z13 from "zod";
|
|
2916
|
-
import { mkdir as mkdir4, readFile as
|
|
2917
|
-
import { dirname as
|
|
1847
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
1848
|
+
import { dirname as dirname4, relative as relative3 } from "node:path";
|
|
1849
|
+
|
|
1850
|
+
// src/lib/algoliaApp.ts
|
|
1851
|
+
import { z as z10 } from "zod";
|
|
1852
|
+
var applicationSchema = z10.object({
|
|
1853
|
+
id: z10.string().min(1),
|
|
1854
|
+
name: z10.string().default(""),
|
|
1855
|
+
plan: z10.string().optional()
|
|
1856
|
+
});
|
|
1857
|
+
var listSchema = z10.array(
|
|
1858
|
+
z10.object({
|
|
1859
|
+
id: z10.string().min(1),
|
|
1860
|
+
name: z10.string().default(""),
|
|
1861
|
+
plan_label: z10.string().optional()
|
|
1862
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
1863
|
+
);
|
|
1864
|
+
async function currentApplication() {
|
|
1865
|
+
let raw;
|
|
1866
|
+
try {
|
|
1867
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1868
|
+
} catch {
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
1872
|
+
return parsed.success ? parsed.data : null;
|
|
1873
|
+
}
|
|
1874
|
+
async function requireApplication() {
|
|
1875
|
+
const app = await currentApplication();
|
|
1876
|
+
if (!app) {
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
return app;
|
|
1882
|
+
}
|
|
1883
|
+
async function listApplications() {
|
|
1884
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
1885
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
1886
|
+
if (!parsed.success) {
|
|
1887
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
1888
|
+
}
|
|
1889
|
+
return parsed.data;
|
|
1890
|
+
}
|
|
1891
|
+
async function selectApplication(id) {
|
|
1892
|
+
const raw = await runAlgoliaCli(
|
|
1893
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
1894
|
+
{ onOutput: stderrSink }
|
|
1895
|
+
);
|
|
1896
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
1897
|
+
if (!parsed.success) {
|
|
1898
|
+
throw new Error(
|
|
1899
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
return parsed.data;
|
|
1903
|
+
}
|
|
1904
|
+
function parseJson(text) {
|
|
1905
|
+
try {
|
|
1906
|
+
return JSON.parse(text);
|
|
1907
|
+
} catch {
|
|
1908
|
+
return void 0;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
2918
1911
|
|
|
2919
1912
|
// src/lib/algoliaApiKey.ts
|
|
2920
1913
|
import { z as z12 } from "zod";
|
|
@@ -3085,7 +2078,8 @@ function resolveWriteKey(index, appId) {
|
|
|
3085
2078
|
`Algolia Wizard write key for ${index} index`
|
|
3086
2079
|
);
|
|
3087
2080
|
}
|
|
3088
|
-
async function resolveSearchOnlyKey(index, appId) {
|
|
2081
|
+
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
2082
|
+
if (envKey) return { key: envKey, source: "env" };
|
|
3089
2083
|
return resolveKey(
|
|
3090
2084
|
"search",
|
|
3091
2085
|
index,
|
|
@@ -3097,8 +2091,8 @@ async function resolveSearchOnlyKey(index, appId) {
|
|
|
3097
2091
|
|
|
3098
2092
|
// src/lib/gitignore.ts
|
|
3099
2093
|
import { execFile } from "node:child_process";
|
|
3100
|
-
import { lstat as lstat2, readFile as
|
|
3101
|
-
import { join as
|
|
2094
|
+
import { lstat as lstat2, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2095
|
+
import { join as join6, relative as relative2 } from "node:path";
|
|
3102
2096
|
var GIT_ENV_OVERRIDES = [
|
|
3103
2097
|
"GIT_DIR",
|
|
3104
2098
|
"GIT_WORK_TREE",
|
|
@@ -3124,12 +2118,6 @@ function isIgnoredByRule(root, relPath) {
|
|
|
3124
2118
|
function isTracked(root, relPath) {
|
|
3125
2119
|
return gitSucceeds(root, ["ls-files", "--error-unmatch", "--", relPath]);
|
|
3126
2120
|
}
|
|
3127
|
-
async function gitIgnoreStatus(root, target) {
|
|
3128
|
-
const { ignoredByRule, tracked } = await inspect(root, target);
|
|
3129
|
-
if (ignoredByRule === void 0) return "unknown";
|
|
3130
|
-
if (tracked) return "tracked";
|
|
3131
|
-
return ignoredByRule ? "covered" : "needsRule";
|
|
3132
|
-
}
|
|
3133
2121
|
async function inspect(root, target) {
|
|
3134
2122
|
const relPath = relative2(root, target);
|
|
3135
2123
|
if (!relPath || relPath.startsWith("..")) {
|
|
@@ -3150,7 +2138,7 @@ async function ensureGitIgnored(root, target) {
|
|
|
3150
2138
|
if (ignoredByRule === void 0) return "unknown";
|
|
3151
2139
|
if (ignoredByRule) return tracked ? "tracked" : "covered";
|
|
3152
2140
|
const pattern = relative2(root, target);
|
|
3153
|
-
const gitIgnore =
|
|
2141
|
+
const gitIgnore = join6(root, ".gitignore");
|
|
3154
2142
|
try {
|
|
3155
2143
|
const link = await lstat2(gitIgnore).catch(() => null);
|
|
3156
2144
|
if (link?.isSymbolicLink()) {
|
|
@@ -3160,7 +2148,7 @@ async function ensureGitIgnored(root, target) {
|
|
|
3160
2148
|
);
|
|
3161
2149
|
return "unknown";
|
|
3162
2150
|
}
|
|
3163
|
-
const existing = link ? await
|
|
2151
|
+
const existing = link ? await readFile4(gitIgnore, "utf8") : "";
|
|
3164
2152
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3165
2153
|
await writeFile4(gitIgnore, `${existing}${prefix}${pattern}
|
|
3166
2154
|
`, "utf8");
|
|
@@ -3175,9 +2163,31 @@ async function ensureGitIgnored(root, target) {
|
|
|
3175
2163
|
}
|
|
3176
2164
|
|
|
3177
2165
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
3178
|
-
var APP_ID_VAR = "
|
|
3179
|
-
var API_KEY_VAR = "
|
|
2166
|
+
var APP_ID_VAR = "ALGOLIA_APPLICATION_ID";
|
|
2167
|
+
var API_KEY_VAR = "ALGOLIA_WRITE_API_KEY";
|
|
3180
2168
|
var INDEX_NAME_VAR = "ALGOLIA_INDEX_NAME";
|
|
2169
|
+
var PUBLIC_APP_ID_SUFFIX = "ALGOLIA_APP_ID";
|
|
2170
|
+
var PUBLIC_SEARCH_KEY_SUFFIX = "ALGOLIA_SEARCH_KEY";
|
|
2171
|
+
var PUBLIC_INDEX_NAME_SUFFIX = "ALGOLIA_INDEX_NAME";
|
|
2172
|
+
function publicAppIdVar(prefix) {
|
|
2173
|
+
return `${prefix}${PUBLIC_APP_ID_SUFFIX}`;
|
|
2174
|
+
}
|
|
2175
|
+
function publicSearchKeyVar(prefix) {
|
|
2176
|
+
return `${prefix}${PUBLIC_SEARCH_KEY_SUFFIX}`;
|
|
2177
|
+
}
|
|
2178
|
+
function publicIndexNameVar(prefix) {
|
|
2179
|
+
return `${prefix}${PUBLIC_INDEX_NAME_SUFFIX}`;
|
|
2180
|
+
}
|
|
2181
|
+
function publicSearchEnvVars(prefix, index, appId, searchKey) {
|
|
2182
|
+
return [
|
|
2183
|
+
{ name: publicAppIdVar(prefix), value: appId ?? "<your-algolia-app-id>" },
|
|
2184
|
+
{
|
|
2185
|
+
name: publicSearchKeyVar(prefix),
|
|
2186
|
+
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
2187
|
+
},
|
|
2188
|
+
{ name: publicIndexNameVar(prefix), value: index }
|
|
2189
|
+
];
|
|
2190
|
+
}
|
|
3181
2191
|
function appendEnv(content, entries) {
|
|
3182
2192
|
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
3183
2193
|
const lines = entries.map(([name, value]) => `${name}=${value}
|
|
@@ -3185,13 +2195,11 @@ function appendEnv(content, entries) {
|
|
|
3185
2195
|
return content + prefix + lines;
|
|
3186
2196
|
}
|
|
3187
2197
|
function hasEnv(content, name) {
|
|
3188
|
-
return new RegExp(`^(
|
|
3189
|
-
content
|
|
3190
|
-
);
|
|
2198
|
+
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3191
2199
|
}
|
|
3192
2200
|
function readEnv(content, name) {
|
|
3193
2201
|
const found = content.match(
|
|
3194
|
-
new RegExp(
|
|
2202
|
+
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*(.*)$`, "m")
|
|
3195
2203
|
);
|
|
3196
2204
|
if (!found) return null;
|
|
3197
2205
|
const raw = found[1].trim();
|
|
@@ -3202,16 +2210,16 @@ function readEnv(content, name) {
|
|
|
3202
2210
|
function upsertEnv(content, name, value) {
|
|
3203
2211
|
if (!hasEnv(content, name)) return appendEnv(content, [[name, value]]);
|
|
3204
2212
|
return content.replace(
|
|
3205
|
-
new RegExp(
|
|
2213
|
+
new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "gm"),
|
|
3206
2214
|
() => `${name}=${value}`
|
|
3207
2215
|
);
|
|
3208
2216
|
}
|
|
3209
2217
|
function writeCredentialsTool(ctx) {
|
|
3210
2218
|
return tool6({
|
|
3211
|
-
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application. The env file is added to .gitignore automatically; do not edit .gitignore yourself
|
|
2219
|
+
description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application. The env file is added to .gitignore automatically; do not edit .gitignore yourself.`,
|
|
3212
2220
|
inputSchema: z13.object({
|
|
3213
2221
|
filePath: z13.string().describe(
|
|
3214
|
-
'Path to the env file to write credentials into
|
|
2222
|
+
'Path to the env file to write credentials into (e.g. ".env")'
|
|
3215
2223
|
)
|
|
3216
2224
|
}),
|
|
3217
2225
|
execute: async ({ filePath }) => {
|
|
@@ -3229,7 +2237,7 @@ function writeCredentialsTool(ctx) {
|
|
|
3229
2237
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
3230
2238
|
}
|
|
3231
2239
|
try {
|
|
3232
|
-
existing = await
|
|
2240
|
+
existing = await readFile5(resolved2.target, "utf8");
|
|
3233
2241
|
} catch (err) {
|
|
3234
2242
|
if (err.code !== "ENOENT") throw err;
|
|
3235
2243
|
}
|
|
@@ -3279,7 +2287,7 @@ function writeCredentialsTool(ctx) {
|
|
|
3279
2287
|
(content, [name, value]) => upsertEnv(content, name, value),
|
|
3280
2288
|
existing
|
|
3281
2289
|
);
|
|
3282
|
-
await mkdir4(
|
|
2290
|
+
await mkdir4(dirname4(resolved2.target), { recursive: true });
|
|
3283
2291
|
await writeFile5(resolved2.target, updated, "utf8");
|
|
3284
2292
|
const sentences = [
|
|
3285
2293
|
`Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
|
|
@@ -3319,8 +2327,8 @@ async function gitIgnoreOutcome(ctx, target) {
|
|
|
3319
2327
|
// src/lib/tools/searchFiles.ts
|
|
3320
2328
|
import { tool as tool7 } from "ai";
|
|
3321
2329
|
import z14 from "zod";
|
|
3322
|
-
import { readdir as readdir2, readFile as
|
|
3323
|
-
import { join as
|
|
2330
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2331
|
+
import { join as join7 } from "node:path";
|
|
3324
2332
|
var MAX_QUERY_LENGTH = 1e3;
|
|
3325
2333
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3326
2334
|
"node_modules",
|
|
@@ -3335,7 +2343,7 @@ async function walkFiles(dir) {
|
|
|
3335
2343
|
const out = [];
|
|
3336
2344
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
3337
2345
|
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
3338
|
-
const full =
|
|
2346
|
+
const full = join7(dir, e.name);
|
|
3339
2347
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
3340
2348
|
else if (e.isFile()) out.push(full);
|
|
3341
2349
|
}
|
|
@@ -3368,7 +2376,7 @@ function searchFilesTool(ctx) {
|
|
|
3368
2376
|
for (const file of await walkFiles(resolved2.target)) {
|
|
3369
2377
|
let content;
|
|
3370
2378
|
try {
|
|
3371
|
-
content = await
|
|
2379
|
+
content = await readFile6(file, "utf8");
|
|
3372
2380
|
} catch {
|
|
3373
2381
|
continue;
|
|
3374
2382
|
}
|
|
@@ -3389,7 +2397,8 @@ function searchFilesTool(ctx) {
|
|
|
3389
2397
|
}
|
|
3390
2398
|
|
|
3391
2399
|
// src/lib/tools/runShell.ts
|
|
3392
|
-
import { tool as tool8 } from "ai";
|
|
2400
|
+
import { tool as tool8, generateText, Output } from "ai";
|
|
2401
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3393
2402
|
import z15 from "zod";
|
|
3394
2403
|
import { relative as relative4 } from "node:path";
|
|
3395
2404
|
|
|
@@ -3498,8 +2507,10 @@ function runShell(command, opts) {
|
|
|
3498
2507
|
// src/lib/tools/runShell.ts
|
|
3499
2508
|
function storeApproval(root) {
|
|
3500
2509
|
return async (req) => {
|
|
2510
|
+
const store = useWizard.getState();
|
|
2511
|
+
if (store.isCommandApproved(req.command, req.cwd)) return "approve";
|
|
3501
2512
|
const rel = relative4(root, req.cwd);
|
|
3502
|
-
const answer = await
|
|
2513
|
+
const answer = await store.requestUserInput({
|
|
3503
2514
|
prompt: "Run this command?",
|
|
3504
2515
|
promptType: "commandApproval",
|
|
3505
2516
|
options: [],
|
|
@@ -3508,20 +2519,195 @@ function storeApproval(root) {
|
|
|
3508
2519
|
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
3509
2520
|
}
|
|
3510
2521
|
});
|
|
3511
|
-
|
|
2522
|
+
if (answer !== "approve") return "reject";
|
|
2523
|
+
store.approveCommand(req.command, req.cwd);
|
|
2524
|
+
return "approve";
|
|
3512
2525
|
};
|
|
3513
2526
|
}
|
|
3514
2527
|
var EXPLORATORY_COMMANDS = /* @__PURE__ */ new Set(["ls", "find", "tree", "dir"]);
|
|
2528
|
+
function commandSegments(command) {
|
|
2529
|
+
return command.split(/&&|;|\|/).map((segment) => segment.trim());
|
|
2530
|
+
}
|
|
3515
2531
|
function isExploratoryCommand(command) {
|
|
3516
|
-
return command
|
|
2532
|
+
return commandSegments(command).map((segment) => segment.split(/\s+/)[0]).some((word) => word !== void 0 && EXPLORATORY_COMMANDS.has(word));
|
|
2533
|
+
}
|
|
2534
|
+
var READ_ONLY_BINARIES = /* @__PURE__ */ new Set([
|
|
2535
|
+
"cat",
|
|
2536
|
+
"head",
|
|
2537
|
+
"tail",
|
|
2538
|
+
"wc",
|
|
2539
|
+
"pwd",
|
|
2540
|
+
"echo",
|
|
2541
|
+
"date",
|
|
2542
|
+
"whoami",
|
|
2543
|
+
"hostname",
|
|
2544
|
+
"uname",
|
|
2545
|
+
"which",
|
|
2546
|
+
"file",
|
|
2547
|
+
"stat",
|
|
2548
|
+
"grep",
|
|
2549
|
+
"egrep",
|
|
2550
|
+
"fgrep",
|
|
2551
|
+
"rg",
|
|
2552
|
+
"diff"
|
|
2553
|
+
]);
|
|
2554
|
+
var READ_ONLY_NO_ARGS_BINARIES = /* @__PURE__ */ new Set(["env", "printenv"]);
|
|
2555
|
+
var GIT_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
2556
|
+
"status",
|
|
2557
|
+
"log",
|
|
2558
|
+
"diff",
|
|
2559
|
+
"show",
|
|
2560
|
+
"describe",
|
|
2561
|
+
"blame",
|
|
2562
|
+
"ls-files",
|
|
2563
|
+
"rev-parse",
|
|
2564
|
+
"cat-file",
|
|
2565
|
+
"shortlog",
|
|
2566
|
+
"ls-remote"
|
|
2567
|
+
]);
|
|
2568
|
+
var VERSION_CHECK_BINARIES = /* @__PURE__ */ new Set([
|
|
2569
|
+
"node",
|
|
2570
|
+
"tsc",
|
|
2571
|
+
"npm",
|
|
2572
|
+
"pnpm",
|
|
2573
|
+
"yarn",
|
|
2574
|
+
"python",
|
|
2575
|
+
"python3",
|
|
2576
|
+
"ruby",
|
|
2577
|
+
"go",
|
|
2578
|
+
"cargo",
|
|
2579
|
+
"rustc",
|
|
2580
|
+
"php",
|
|
2581
|
+
"composer",
|
|
2582
|
+
"java",
|
|
2583
|
+
"mvn",
|
|
2584
|
+
"gradle",
|
|
2585
|
+
"bundle",
|
|
2586
|
+
"git"
|
|
2587
|
+
]);
|
|
2588
|
+
var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V"]);
|
|
2589
|
+
var FD_DUP_REDIRECT = /\d*>&\d+/g;
|
|
2590
|
+
function stripQuoted(segment) {
|
|
2591
|
+
let result = "";
|
|
2592
|
+
let quote = null;
|
|
2593
|
+
let i = 0;
|
|
2594
|
+
while (i < segment.length) {
|
|
2595
|
+
const char = segment[i];
|
|
2596
|
+
if (quote === "'") {
|
|
2597
|
+
if (char === "'") quote = null;
|
|
2598
|
+
i++;
|
|
2599
|
+
} else if (char === "\\") {
|
|
2600
|
+
i += 2;
|
|
2601
|
+
} else if (quote) {
|
|
2602
|
+
if (char === quote) quote = null;
|
|
2603
|
+
i++;
|
|
2604
|
+
} else if (char === '"' || char === "'") {
|
|
2605
|
+
quote = char;
|
|
2606
|
+
i++;
|
|
2607
|
+
} else {
|
|
2608
|
+
result += char;
|
|
2609
|
+
i++;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return quote === null ? result : segment;
|
|
3517
2613
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
return
|
|
2614
|
+
function hasFileRedirect(segment) {
|
|
2615
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes(">");
|
|
2616
|
+
}
|
|
2617
|
+
function hasShellInjectionRisk(segment) {
|
|
2618
|
+
if (segment.includes("$(") || segment.includes("<(") || segment.includes("`")) {
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes("&");
|
|
2622
|
+
}
|
|
2623
|
+
function fastPathSegmentSafety(segment) {
|
|
2624
|
+
if (!segment) return true;
|
|
2625
|
+
if (hasFileRedirect(segment)) return false;
|
|
2626
|
+
if (hasShellInjectionRisk(segment)) return false;
|
|
2627
|
+
const [cmd0, ...rest] = segment.split(/\s+/);
|
|
2628
|
+
if (cmd0 === void 0) return true;
|
|
2629
|
+
if (VERSION_CHECK_BINARIES.has(cmd0) && rest.length === 1 && VERSION_FLAGS.has(rest[0])) {
|
|
2630
|
+
return true;
|
|
2631
|
+
}
|
|
2632
|
+
if (READ_ONLY_BINARIES.has(cmd0)) return true;
|
|
2633
|
+
if (READ_ONLY_NO_ARGS_BINARIES.has(cmd0)) {
|
|
2634
|
+
return rest.length === 0 ? true : void 0;
|
|
2635
|
+
}
|
|
2636
|
+
if (cmd0 === "git") {
|
|
2637
|
+
return GIT_READ_ONLY_SUBCOMMANDS.has(rest[0] ?? "") ? true : void 0;
|
|
2638
|
+
}
|
|
2639
|
+
return void 0;
|
|
2640
|
+
}
|
|
2641
|
+
function fastPathSafety(command) {
|
|
2642
|
+
const results = commandSegments(command).map(fastPathSegmentSafety);
|
|
2643
|
+
if (results.some((r) => r === false)) return false;
|
|
2644
|
+
if (results.every((r) => r === true)) return true;
|
|
2645
|
+
return void 0;
|
|
2646
|
+
}
|
|
2647
|
+
var CLASSIFIER_MODEL = "claude-haiku-4-5";
|
|
2648
|
+
var commandSafetySchema = z15.object({
|
|
2649
|
+
safe: z15.boolean(),
|
|
2650
|
+
reason: z15.string().describe("One short sentence explaining the verdict.")
|
|
2651
|
+
});
|
|
2652
|
+
function defaultCreateModel() {
|
|
2653
|
+
const token = getAuthToken();
|
|
2654
|
+
if (!token) {
|
|
2655
|
+
throw new Error("Not authenticated: no user token available");
|
|
2656
|
+
}
|
|
2657
|
+
return createAnthropic({
|
|
2658
|
+
apiKey: token,
|
|
2659
|
+
baseURL: PROXY_BASE_URL,
|
|
2660
|
+
fetch: proxyFetch
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function approvedCommandHistory(approvedCommands) {
|
|
2664
|
+
return Array.from(approvedCommands).map((entry) => {
|
|
2665
|
+
const sep2 = entry.indexOf("\0");
|
|
2666
|
+
return { cwd: entry.slice(0, sep2), command: entry.slice(sep2 + 1) };
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
async function classifyCommandSafety(createModel, command, cwd, explanation, approvedHistory) {
|
|
2670
|
+
try {
|
|
2671
|
+
const anthropic = createModel();
|
|
2672
|
+
const { output } = await generateText({
|
|
2673
|
+
model: anthropic(CLASSIFIER_MODEL),
|
|
2674
|
+
temperature: 0,
|
|
2675
|
+
output: Output.object({ schema: commandSafetySchema }),
|
|
2676
|
+
prompt: [
|
|
2677
|
+
"A coding agent wants to run this shell command in a user's project without asking for approval first. The command may be in any programming language or ecosystem.",
|
|
2678
|
+
"Judge it SAFE only if it cannot modify, delete, move, rename, or install anything the project cares about, cannot push/publish/deploy/commit anything, and cannot make a network request that changes remote state.",
|
|
2679
|
+
"Three broad categories are safe, and most commands you will see fall into one of them:",
|
|
2680
|
+
"(1) Reporting or listing existing state, with no side effect \u2014 e.g. `git status`, `npm ls`, `npm outdated`, `pip show`, `pip freeze`, `cargo tree`, `docker ps`, `docker images`, `kubectl get pods`, `terraform state list`. This includes a read-only GET-style query to a remote registry or API that only fetches public metadata and changes nothing remote (e.g. `npm view <package>`, `pip index versions <package>`, `curl` with no -X/--request other than GET and no -d/--data) \u2014 safe because nothing changes, not because it stays local.",
|
|
2681
|
+
'(2) A "preview" or "check" mode that reports what a change WOULD do, or reports a problem, without making the change. This is a general pattern, not a fixed list \u2014 ANY command that takes a dry-run/check/plan/validate/diff/list-only flag is safe when that flag is present, regardless of whether the same flag would normally appear on a "safe" kind of command: `terraform plan`, `terraform validate`, `terraform fmt -check`, `black --check`, `prettier --check`, `isort --check`, `gofmt -l` (list-only), `stylelint` with no `--fix`, and equally `git push --dry-run`, `npm publish --dry-run`, `kubectl apply --dry-run=client` \u2014 these last three are otherwise-unsafe operations (push, publish, cluster changes) that the dry-run flag turns into a report. The corresponding command WITHOUT that flag (e.g. `terraform apply`, `black .`, `gofmt -w`, `git push`) is a different, unsafe command \u2014 the flag is what makes the difference, for any tool, not just the ones named here. This cuts both ways, so do not assume a bare invocation is the safe one: `cargo fmt`, `black`, `prettier`, `isort`, `rustfmt`, and `terraform fmt` all REWRITE FILES by default and need an explicit check/diff/dry-run flag to become safe, while `gofmt`, `eslint`, `stylelint`, `rubocop`, and `ruff check` default to a safe check-only mode and need an explicit fix/write flag to become unsafe \u2014 the same-looking bare command is safe for one group and unsafe for the other, so judge each by what its own flags actually say, not by resemblance to a tool you already judged.',
|
|
2682
|
+
"(3) Running the project's own tests, type checker, linter, or build/compile step (e.g. `npm run build`, `yarn build`, `vite build`, `webpack`, `tsc`, `cargo build`, `go build`, `mvn compile`), as long as it is not passed an autofix/write/update flag (e.g. --fix, -u, --write, rubocop -a) \u2014 this holds even though the point of a build step is to write compiled output to a build/dist/target directory inside the project (e.g. a target/, build/, dist/, or __pycache__ directory): writing that output does not make the command unsafe. It stops being safe the moment the command chain goes past building \u2014 a step that also deploys, publishes, uploads, or pushes the build (e.g. `next build && vercel deploy`, `npm run build && npm publish`) is unsafe for that later segment even though the build segment itself is fine; judge each chained segment on its own, same as elsewhere in this list.",
|
|
2683
|
+
"Deleting or removing anything is unsafe, even something in a cache or build directory (e.g. `rm -rf __pycache__`, `cargo clean`, `git clean`), and even if the command otherwise fits one of the three categories above. Installing, uninstalling, or upgrading a dependency, changing a database schema, or writing to a path outside the project is also unsafe. A command chained with && or ; is only safe if every part of the chain is safe on its own.",
|
|
2684
|
+
"One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe.",
|
|
2685
|
+
...approvedHistory.length > 0 ? [
|
|
2686
|
+
"The user has already explicitly approved these exact commands earlier in this session (working directory in parentheses, then the command):",
|
|
2687
|
+
approvedHistory.map((h) => `- (${h.cwd}) ${h.command}`).join("\n"),
|
|
2688
|
+
"If the new command performs the same action and side-effect profile as one of these \u2014 differing only in a trivial way, such as a different file path, package name, or argument value that does not change what kind of action it is \u2014 judge it SAFE on that precedent, even if it would not otherwise fit categories (1)-(3) above. Do not stretch this to a command that merely shares a binary name, or superficially resembles one on the list while doing something riskier or of a different kind (e.g. an approved `rm build/tmp.log` does not license a new `rm -rf src/`, and an approved `git push origin feature-x` does not license `git push --force`)."
|
|
2689
|
+
] : [],
|
|
2690
|
+
`Command: ${command}`,
|
|
2691
|
+
`Working directory: ${cwd}`,
|
|
2692
|
+
`Stated purpose: ${explanation}`
|
|
2693
|
+
].join("\n")
|
|
2694
|
+
});
|
|
2695
|
+
if (!output.safe) {
|
|
2696
|
+
logger.info(
|
|
2697
|
+
{ command, reason: output.reason },
|
|
2698
|
+
"runShell: classifier judged command unsafe, requiring approval"
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
return output.safe;
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
logger.warn(
|
|
2704
|
+
{ err, command },
|
|
2705
|
+
"runShell: command safety classifier failed, requiring approval"
|
|
2706
|
+
);
|
|
2707
|
+
return false;
|
|
3524
2708
|
}
|
|
2709
|
+
}
|
|
2710
|
+
async function runAndRecord(ctx, command, cwd) {
|
|
3525
2711
|
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
3526
2712
|
const env = await ctx.shell.env().catch((err) => {
|
|
3527
2713
|
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
@@ -3557,9 +2743,18 @@ async function approveAndRun(ctx, command, cwd, explanation) {
|
|
|
3557
2743
|
output: run2.output
|
|
3558
2744
|
};
|
|
3559
2745
|
}
|
|
3560
|
-
function
|
|
2746
|
+
async function approveAndRun(ctx, command, cwd, explanation) {
|
|
2747
|
+
const decision = await ctx.shell.approve({ command, cwd, explanation });
|
|
2748
|
+
if (decision === "reject") {
|
|
2749
|
+
ctx.shell.executions.push({ command, cwd, approved: false });
|
|
2750
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2751
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2752
|
+
}
|
|
2753
|
+
return runAndRecord(ctx, command, cwd);
|
|
2754
|
+
}
|
|
2755
|
+
function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
3561
2756
|
return tool8({
|
|
3562
|
-
description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands.
|
|
2757
|
+
description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. Do not use this to find or read files \u2014 use listFiles, searchFiles, and readFile instead of ls/find/cat/head/tail/grep/rg. This tool refuses ls/find/tree/dir outright; a read command like cat is not refused (some read-only commands run without approval, see below), but it's still the wrong tool for reading a file \u2014 the dedicated tools exist for that and won't count against this tool's command budget. The user approves any command that could change the project before it runs, so write a clear `explanation`. A command judged read-only (inspection, or running tests/typecheck/lint without an autofix flag, in any language) runs immediately without approval. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
3563
2758
|
inputSchema: z15.object({
|
|
3564
2759
|
command: z15.string().describe(
|
|
3565
2760
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
@@ -3578,13 +2773,33 @@ function runShellTool(ctx) {
|
|
|
3578
2773
|
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
3579
2774
|
if (!resolved2.ok) return resolved2.error;
|
|
3580
2775
|
if (isExploratoryCommand(command)) {
|
|
3581
|
-
return "Refused: use listFiles or searchFiles to
|
|
2776
|
+
return "Refused: use listFiles or searchFiles to find files, and readFile to read one, instead of ls/find/tree/dir.";
|
|
3582
2777
|
}
|
|
3583
2778
|
logger.info({ command, cwd: resolved2.target }, "called runShell tool");
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
)
|
|
3587
|
-
|
|
2779
|
+
const fast = fastPathSafety(command);
|
|
2780
|
+
let isSafe = fast;
|
|
2781
|
+
if (isSafe === void 0) {
|
|
2782
|
+
const store = useWizard.getState();
|
|
2783
|
+
isSafe = store.isCommandApproved(command, resolved2.target);
|
|
2784
|
+
if (!isSafe) {
|
|
2785
|
+
isSafe = await classifyCommandSafety(
|
|
2786
|
+
createModel,
|
|
2787
|
+
command,
|
|
2788
|
+
resolved2.target,
|
|
2789
|
+
explanation,
|
|
2790
|
+
approvedCommandHistory(store.approvedCommands)
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (isSafe) {
|
|
2795
|
+
return serializePrompt(
|
|
2796
|
+
() => runAndRecord(ctx, command, resolved2.target)
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2799
|
+
return serializePrompt(
|
|
2800
|
+
() => approveAndRun(ctx, command, resolved2.target, explanation)
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
3588
2803
|
});
|
|
3589
2804
|
}
|
|
3590
2805
|
|
|
@@ -3630,29 +2845,29 @@ function reviewScriptTool(ctx) {
|
|
|
3630
2845
|
}
|
|
3631
2846
|
|
|
3632
2847
|
// src/lib/tools/generateRecord.ts
|
|
3633
|
-
import { tool as tool10, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
3634
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2848
|
+
import { tool as tool10, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
|
|
2849
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
3635
2850
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3636
2851
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
|
|
3637
|
-
import { dirname as
|
|
2852
|
+
import { dirname as dirname5 } from "node:path";
|
|
3638
2853
|
import z17 from "zod";
|
|
3639
2854
|
var DATA_DIR = ".algolia-wizard/data";
|
|
3640
2855
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
3641
2856
|
var MAX_RECORDS = 100;
|
|
3642
2857
|
var BATCH_SIZE = 10;
|
|
3643
2858
|
var MAX_BATCH_ATTEMPTS = 3;
|
|
3644
|
-
function
|
|
2859
|
+
function defaultCreateModel2() {
|
|
3645
2860
|
const token = getAuthToken();
|
|
3646
2861
|
if (!token) {
|
|
3647
2862
|
throw new Error("Not authenticated: no user token available");
|
|
3648
2863
|
}
|
|
3649
|
-
return
|
|
2864
|
+
return createAnthropic2({
|
|
3650
2865
|
apiKey: token,
|
|
3651
2866
|
baseURL: PROXY_BASE_URL,
|
|
3652
2867
|
fetch: proxyFetch
|
|
3653
2868
|
});
|
|
3654
2869
|
}
|
|
3655
|
-
function generateRecordTool(ctx, createModel =
|
|
2870
|
+
function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
3656
2871
|
return tool10({
|
|
3657
2872
|
description: "Generate realistic sample records for an entity and write them to a JSON file. 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.",
|
|
3658
2873
|
inputSchema: z17.object({
|
|
@@ -3673,9 +2888,9 @@ function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
|
3673
2888
|
let lastError;
|
|
3674
2889
|
for (let attempt = 1; attempt <= MAX_BATCH_ATTEMPTS; attempt++) {
|
|
3675
2890
|
try {
|
|
3676
|
-
const { output } = await
|
|
2891
|
+
const { output } = await generateText2({
|
|
3677
2892
|
model: anthropic(RECORD_MODEL),
|
|
3678
|
-
output:
|
|
2893
|
+
output: Output2.object({
|
|
3679
2894
|
schema: z17.object({
|
|
3680
2895
|
records: z17.array(recordSchema).length(batchCount)
|
|
3681
2896
|
})
|
|
@@ -3714,7 +2929,7 @@ function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
|
3714
2929
|
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
3715
2930
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
3716
2931
|
}
|
|
3717
|
-
await mkdir5(
|
|
2932
|
+
await mkdir5(dirname5(resolved2.target), { recursive: true });
|
|
3718
2933
|
await writeFile6(
|
|
3719
2934
|
resolved2.target,
|
|
3720
2935
|
JSON.stringify(records, null, 2),
|
|
@@ -3840,7 +3055,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3840
3055
|
if (!token) {
|
|
3841
3056
|
throw new Error("Not authenticated: no user token available");
|
|
3842
3057
|
}
|
|
3843
|
-
const anthropic =
|
|
3058
|
+
const anthropic = createAnthropic3({
|
|
3844
3059
|
apiKey: token,
|
|
3845
3060
|
baseURL: PROXY_BASE_URL,
|
|
3846
3061
|
fetch: proxyFetch
|
|
@@ -3877,7 +3092,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3877
3092
|
}
|
|
3878
3093
|
};
|
|
3879
3094
|
}),
|
|
3880
|
-
output:
|
|
3095
|
+
output: Output3.object({ schema: req.outputSchema }),
|
|
3881
3096
|
tools: createTools(toolContext, {
|
|
3882
3097
|
output: req.outputSchema,
|
|
3883
3098
|
tools: req.tools
|
|
@@ -3965,7 +3180,6 @@ var detectLanguage = () => runAgent({
|
|
|
3965
3180
|
"Return the exact version",
|
|
3966
3181
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3967
3182
|
`Determine publicEnvVarPrefix: check the project's own env var usage first (e.g. names already referenced in code, .env/.env.example); if none exists, fall back to the detected framework's known convention for exposing env vars to client-side code; use "" when the project has no such convention (e.g. a backend-only project).`,
|
|
3968
|
-
"A brand-new project has no existing env var usage to find \u2014 one or two targeted checks (e.g. .env/.env.example, or a grep for the bundler's public-prefix convention) are enough to confirm that. Do not keep searching once those turn up nothing; fall back to the framework convention immediately.",
|
|
3969
3183
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
3970
3184
|
"When done, call reportStatus"
|
|
3971
3185
|
],
|
|
@@ -4061,7 +3275,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
4061
3275
|
// package.json
|
|
4062
3276
|
var package_default = {
|
|
4063
3277
|
name: "@algolia/wizard",
|
|
4064
|
-
version: "0.
|
|
3278
|
+
version: "0.35.0-rc.126.249",
|
|
4065
3279
|
description: "Magically implement Algolia functionality in your codebase",
|
|
4066
3280
|
type: "module",
|
|
4067
3281
|
engines: {
|
|
@@ -4466,13 +3680,14 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4466
3680
|
};
|
|
4467
3681
|
|
|
4468
3682
|
// src/actions/implement.ts
|
|
4469
|
-
import
|
|
4470
|
-
import {
|
|
3683
|
+
import z29 from "zod";
|
|
3684
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
3685
|
+
import { join as join10, relative as relative6 } from "node:path";
|
|
4471
3686
|
|
|
4472
3687
|
// src/lib/git.ts
|
|
4473
3688
|
import { execFile as execFile2 } from "node:child_process";
|
|
4474
|
-
import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
|
|
4475
|
-
import { basename as basename2, dirname as
|
|
3689
|
+
import { copyFile, mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
3690
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
|
|
4476
3691
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
4477
3692
|
function git(args) {
|
|
4478
3693
|
return new Promise((resolve4, reject) => {
|
|
@@ -4509,13 +3724,13 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
4509
3724
|
} catch {
|
|
4510
3725
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4511
3726
|
}
|
|
4512
|
-
const relPath =
|
|
4513
|
-
const dest =
|
|
3727
|
+
const relPath = join8(ingestDir, basename2(source));
|
|
3728
|
+
const dest = join8(repoRoot, relPath);
|
|
4514
3729
|
if (resolve3(source) === resolve3(dest)) {
|
|
4515
3730
|
return { ok: true, relPath };
|
|
4516
3731
|
}
|
|
4517
3732
|
try {
|
|
4518
|
-
await mkdir6(
|
|
3733
|
+
await mkdir6(dirname6(dest), { recursive: true });
|
|
4519
3734
|
await copyFile(source, dest);
|
|
4520
3735
|
} catch (err) {
|
|
4521
3736
|
return {
|
|
@@ -4525,6 +3740,42 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
4525
3740
|
}
|
|
4526
3741
|
return { ok: true, relPath };
|
|
4527
3742
|
}
|
|
3743
|
+
function hasEnvVar(content, name) {
|
|
3744
|
+
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3745
|
+
}
|
|
3746
|
+
async function readEnvVar(repoRoot, name) {
|
|
3747
|
+
let content;
|
|
3748
|
+
try {
|
|
3749
|
+
content = await readFile7(join8(repoRoot, ".env"), "utf8");
|
|
3750
|
+
} catch (err) {
|
|
3751
|
+
if (err.code !== "ENOENT") throw err;
|
|
3752
|
+
return void 0;
|
|
3753
|
+
}
|
|
3754
|
+
const match = new RegExp(
|
|
3755
|
+
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3756
|
+
"m"
|
|
3757
|
+
).exec(content);
|
|
3758
|
+
if (!match) return void 0;
|
|
3759
|
+
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3760
|
+
if (!value || value.startsWith("<")) return void 0;
|
|
3761
|
+
return value;
|
|
3762
|
+
}
|
|
3763
|
+
async function writeSearchEnvValues(repoRoot, vars) {
|
|
3764
|
+
const target = join8(repoRoot, ".env");
|
|
3765
|
+
let existing = "";
|
|
3766
|
+
try {
|
|
3767
|
+
existing = await readFile7(target, "utf8");
|
|
3768
|
+
} catch (err) {
|
|
3769
|
+
if (err.code !== "ENOENT") throw err;
|
|
3770
|
+
}
|
|
3771
|
+
const missing = vars.filter((v) => !hasEnvVar(existing, v.name));
|
|
3772
|
+
if (missing.length === 0) return [];
|
|
3773
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3774
|
+
const lines = missing.map(({ name, value }) => `${name}=${value}
|
|
3775
|
+
`).join("");
|
|
3776
|
+
await writeFile7(target, existing + prefix + lines, "utf8");
|
|
3777
|
+
return missing.map((v) => v.name);
|
|
3778
|
+
}
|
|
4528
3779
|
function normalizeFindingPaths(findings) {
|
|
4529
3780
|
return {
|
|
4530
3781
|
...findings,
|
|
@@ -4549,15 +3800,15 @@ function toRootRelative(p) {
|
|
|
4549
3800
|
|
|
4550
3801
|
// src/lib/algoliaDocs.ts
|
|
4551
3802
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
4552
|
-
import { dirname as
|
|
4553
|
-
import { fileURLToPath
|
|
4554
|
-
var DOCS_SUBPATH =
|
|
3803
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
3804
|
+
import { fileURLToPath } from "node:url";
|
|
3805
|
+
var DOCS_SUBPATH = join9("docs", "algolia-sdk");
|
|
4555
3806
|
function findDocsDir() {
|
|
4556
|
-
let dir =
|
|
3807
|
+
let dir = dirname7(fileURLToPath(import.meta.url));
|
|
4557
3808
|
for (; ; ) {
|
|
4558
|
-
const candidate =
|
|
3809
|
+
const candidate = join9(dir, DOCS_SUBPATH);
|
|
4559
3810
|
if (existsSync(candidate)) return candidate;
|
|
4560
|
-
const parent =
|
|
3811
|
+
const parent = dirname7(dir);
|
|
4561
3812
|
if (parent === dir) return void 0;
|
|
4562
3813
|
dir = parent;
|
|
4563
3814
|
}
|
|
@@ -4578,7 +3829,7 @@ function loadAlgoliaDoc(language) {
|
|
|
4578
3829
|
);
|
|
4579
3830
|
return "";
|
|
4580
3831
|
}
|
|
4581
|
-
return readFileSync(
|
|
3832
|
+
return readFileSync(join9(docsDir, files[0]), "utf8").trim();
|
|
4582
3833
|
}
|
|
4583
3834
|
function getNamedDoc(name, language) {
|
|
4584
3835
|
const docsDir = findDocsDir();
|
|
@@ -4586,7 +3837,7 @@ function getNamedDoc(name, language) {
|
|
|
4586
3837
|
logger.warn("docs/algolia-sdk not found");
|
|
4587
3838
|
return "";
|
|
4588
3839
|
}
|
|
4589
|
-
const file =
|
|
3840
|
+
const file = join9(docsDir, `${name}-${language}.md`);
|
|
4590
3841
|
if (!existsSync(file)) {
|
|
4591
3842
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
4592
3843
|
return "";
|
|
@@ -4605,36 +3856,46 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
4605
3856
|
return loadAlgoliaDoc("js");
|
|
4606
3857
|
}
|
|
4607
3858
|
|
|
3859
|
+
// src/actions/resolveEnvVarPrefix.ts
|
|
3860
|
+
import z28 from "zod";
|
|
3861
|
+
var resolveEnvVarPrefixSchema = z28.object({
|
|
3862
|
+
publicEnvVarPrefix: detectLanguageSchema.shape.publicEnvVarPrefix
|
|
3863
|
+
});
|
|
3864
|
+
var resolveEnvVarPrefix = (frameworkName) => runAgent({
|
|
3865
|
+
instructions: [
|
|
3866
|
+
`The developer corrected the project's framework to "${frameworkName}".`,
|
|
3867
|
+
`Determine publicEnvVarPrefix for this framework: check the project's own env var usage first (e.g. names already referenced in code, .env/.env.example); if none exists, fall back to this framework's known convention for exposing env vars to client-side code; use "" when the framework has no such convention (e.g. a backend-only framework).`,
|
|
3868
|
+
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
3869
|
+
"When done, call reportStatus"
|
|
3870
|
+
],
|
|
3871
|
+
tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
|
|
3872
|
+
outputSchema: resolveEnvVarPrefixSchema,
|
|
3873
|
+
modelSize: "small"
|
|
3874
|
+
});
|
|
3875
|
+
|
|
4608
3876
|
// src/actions/implement.ts
|
|
4609
|
-
var implementSchema =
|
|
4610
|
-
summary:
|
|
4611
|
-
ingestCommand:
|
|
4612
|
-
ingestScriptRan:
|
|
4613
|
-
ingestRecordCount:
|
|
4614
|
-
ingestDurationMs:
|
|
4615
|
-
ingestionSource:
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
})
|
|
4623
|
-
)
|
|
4624
|
-
}).optional()
|
|
3877
|
+
var implementSchema = z29.object({
|
|
3878
|
+
summary: z29.string(),
|
|
3879
|
+
ingestCommand: z29.string().optional(),
|
|
3880
|
+
ingestScriptRan: z29.boolean().optional(),
|
|
3881
|
+
ingestRecordCount: z29.number().optional(),
|
|
3882
|
+
ingestDurationMs: z29.number().optional(),
|
|
3883
|
+
ingestionSource: z29.enum(["local", "fileUpload", "generated"]),
|
|
3884
|
+
searchEnvVars: z29.array(
|
|
3885
|
+
z29.object({
|
|
3886
|
+
name: z29.string(),
|
|
3887
|
+
value: z29.string()
|
|
3888
|
+
})
|
|
3889
|
+
).optional()
|
|
4625
3890
|
});
|
|
4626
|
-
var implementationOutputSchema =
|
|
4627
|
-
summary:
|
|
4628
|
-
ingestCommand:
|
|
4629
|
-
// Only for the search use case: the path of whatever module the agent
|
|
4630
|
-
// defined the Algolia config constants in, so the wizard can check it
|
|
4631
|
-
// won't end up gitignored (it's public, meant to be committed).
|
|
4632
|
-
searchConfigFile: z28.string().optional()
|
|
3891
|
+
var implementationOutputSchema = z29.object({
|
|
3892
|
+
summary: z29.string(),
|
|
3893
|
+
ingestCommand: z29.string().optional()
|
|
4633
3894
|
});
|
|
4634
|
-
var verificationOutputSchema =
|
|
4635
|
-
summary:
|
|
4636
|
-
sufficient:
|
|
4637
|
-
additionalInstructions:
|
|
3895
|
+
var verificationOutputSchema = z29.object({
|
|
3896
|
+
summary: z29.string(),
|
|
3897
|
+
sufficient: z29.boolean(),
|
|
3898
|
+
additionalInstructions: z29.string().optional()
|
|
4638
3899
|
});
|
|
4639
3900
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4640
3901
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
@@ -4648,10 +3909,6 @@ function isJsProject(language) {
|
|
|
4648
3909
|
(name) => JS_LANGUAGES.some((js) => name.includes(js))
|
|
4649
3910
|
);
|
|
4650
3911
|
}
|
|
4651
|
-
var SEARCH_CONFIG_APP_ID = "ALGOLIA_APP_ID";
|
|
4652
|
-
var SEARCH_CONFIG_SEARCH_KEY = "ALGOLIA_SEARCH_API_KEY";
|
|
4653
|
-
var SEARCH_CONFIG_INDEX_NAME = "ALGOLIA_INDEX_NAME";
|
|
4654
|
-
var SEARCH_KEY_PLACEHOLDER = "<your-algolia-search-only-api-key>";
|
|
4655
3912
|
var UI_FRAMEWORKS = [
|
|
4656
3913
|
{ match: ["vue", "nuxt"], target: "Vue", doc: "vue" },
|
|
4657
3914
|
{ match: ["react", "next"], target: "React", doc: "react" },
|
|
@@ -4714,7 +3971,7 @@ function algoliaClientDoc(input) {
|
|
|
4714
3971
|
function ingestionInstructions(input) {
|
|
4715
3972
|
return [
|
|
4716
3973
|
...input.confirmed && input.confirmed.length ? [
|
|
4717
|
-
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3974
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root. That directory already exists \u2014 writeFile creates any nested path itself, so never run a shell command just to create a directory.`,
|
|
4718
3975
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4719
3976
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
4720
3977
|
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
@@ -4722,7 +3979,6 @@ function ingestionInstructions(input) {
|
|
|
4722
3979
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
4723
3980
|
...algoliaClientDoc(input),
|
|
4724
3981
|
"Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
|
|
4725
|
-
`If the script loads its env vars from a file (e.g. via dotenv or an equivalent for its language) rather than the process environment directly, decide that up front and call writeCredentials on that file before you finish the script \u2014 do not wait to discover the need for it by having a writeFile call refused.`,
|
|
4726
3982
|
"When the script is finished, call reviewScript with its path and wait: running it writes records to a live index, so the developer reads it first. Do not run it before that call returns.",
|
|
4727
3983
|
'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
|
|
4728
3984
|
"The summary should be extremely concise.",
|
|
@@ -4744,14 +4000,17 @@ function searchInstructions(input) {
|
|
|
4744
4000
|
`Create the search experience as its own component in a new file, following the project's existing component conventions (location, naming, styling approach). Do not write it inline into an existing file.`,
|
|
4745
4001
|
`Import and render that new component from ${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 search input and results list against the target index.`,
|
|
4746
4002
|
"If a search box already exists, replace its usage with an import and render of your new component; remove the old implementation.",
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4003
|
+
`Read the index name from the ${publicIndexNameVar(input.publicEnvVarPrefix)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
|
|
4004
|
+
"Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4005
|
+
// The key is provisioned only after verification passes, and the wizard
|
|
4006
|
+
// reads .env to decide whether a key already exists — an agent-invented
|
|
4007
|
+
// value there would be reused as if it were real.
|
|
4008
|
+
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
4009
|
+
// The wizard writes these exact names into .env right after this step.
|
|
4010
|
+
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4752
4011
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
4753
4012
|
"Match the styles of the application as closely as possible.",
|
|
4754
|
-
"The summary should be extremely concise; do not mention manual testing steps."
|
|
4013
|
+
"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."
|
|
4755
4014
|
];
|
|
4756
4015
|
}
|
|
4757
4016
|
function verificationInstructions(input) {
|
|
@@ -4882,6 +4141,21 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4882
4141
|
languages: ctx.getStepOutput("confirm-language")?.languages ?? scan.languages,
|
|
4883
4142
|
frameworks: ctx.getStepOutput("confirm-framework")?.frameworks ?? scan.frameworks
|
|
4884
4143
|
};
|
|
4144
|
+
const normalizeFrameworkName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4145
|
+
const confirmedPrimaryFramework = language.frameworks[0]?.name;
|
|
4146
|
+
const frameworkWasCorrected = confirmedPrimaryFramework !== void 0 && !scan.frameworks.some(
|
|
4147
|
+
(fw) => normalizeFrameworkName(fw.name) === normalizeFrameworkName(confirmedPrimaryFramework)
|
|
4148
|
+
);
|
|
4149
|
+
const publicEnvVarPrefixPromise = frameworkWasCorrected ? resolveEnvVarPrefix(confirmedPrimaryFramework).then(
|
|
4150
|
+
(r) => r.publicEnvVarPrefix,
|
|
4151
|
+
(err) => {
|
|
4152
|
+
logger.warn(
|
|
4153
|
+
{ err, framework: confirmedPrimaryFramework },
|
|
4154
|
+
"implement: could not re-resolve publicEnvVarPrefix after a framework correction; using the stale scan value"
|
|
4155
|
+
);
|
|
4156
|
+
return scan.publicEnvVarPrefix;
|
|
4157
|
+
}
|
|
4158
|
+
) : Promise.resolve(scan.publicEnvVarPrefix);
|
|
4885
4159
|
const selected = ctx.getStepOutput(
|
|
4886
4160
|
"select-index"
|
|
4887
4161
|
);
|
|
@@ -4932,6 +4206,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4932
4206
|
const targetIndex = selected?.selection;
|
|
4933
4207
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4934
4208
|
await assertGitRepoWithHead(repoRoot);
|
|
4209
|
+
if (useCases.includes("ingestion")) {
|
|
4210
|
+
await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
|
|
4211
|
+
}
|
|
4935
4212
|
const normalized = normalizeFindingPaths(findings);
|
|
4936
4213
|
const confirmed2 = normalized.confirmedEntities;
|
|
4937
4214
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -4962,42 +4239,49 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4962
4239
|
);
|
|
4963
4240
|
}
|
|
4964
4241
|
}
|
|
4965
|
-
const
|
|
4966
|
-
if (uploadWarning) summaries.push(uploadWarning);
|
|
4967
|
-
let searchKey;
|
|
4968
|
-
let searchKeyError;
|
|
4969
|
-
if (useCases.includes("search") && appId) {
|
|
4970
|
-
try {
|
|
4971
|
-
const resolved2 = await resolveSearchOnlyKey(targetIndex, appId);
|
|
4972
|
-
searchKey = resolved2.key;
|
|
4973
|
-
summaries.push(
|
|
4974
|
-
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4975
|
-
);
|
|
4976
|
-
} catch (err) {
|
|
4977
|
-
searchKeyError = err.message;
|
|
4978
|
-
summaries.push(
|
|
4979
|
-
`Could not provision a search-only Algolia API key (${searchKeyError}) \u2014 the search agent will scaffold a placeholder with a TODO for you to fill in.`
|
|
4980
|
-
);
|
|
4981
|
-
logger.warn(
|
|
4982
|
-
{ err: searchKeyError },
|
|
4983
|
-
"implement: could not provision a search-only API key; the agent will scaffold a placeholder"
|
|
4984
|
-
);
|
|
4985
|
-
}
|
|
4986
|
-
}
|
|
4242
|
+
const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
|
|
4987
4243
|
const input = {
|
|
4988
4244
|
findings: normalized,
|
|
4989
4245
|
confirmed: confirmed2,
|
|
4990
4246
|
searchLocation,
|
|
4991
4247
|
targetIndex,
|
|
4992
4248
|
language,
|
|
4249
|
+
publicEnvVarPrefix,
|
|
4993
4250
|
appId,
|
|
4994
|
-
|
|
4995
|
-
searchKeyError,
|
|
4251
|
+
searchEnvVars: publicSearchEnvVars(publicEnvVarPrefix, targetIndex, appId),
|
|
4996
4252
|
ingestDir: INGEST_DIR,
|
|
4997
4253
|
ingestionSource,
|
|
4998
4254
|
uploadFilePath,
|
|
4999
4255
|
searchUiTarget: searchUiTarget(language)
|
|
5000
4256
|
};
|
|
4257
|
+
const summaries = [];
|
|
4258
|
+
if (uploadWarning) summaries.push(uploadWarning);
|
|
4259
|
+
let envSearchKey;
|
|
4260
|
+
let envAppIdMismatch = false;
|
|
4261
|
+
if (useCases.includes("search") && appId) {
|
|
4262
|
+
const envAppId = await readEnvVar(
|
|
4263
|
+
repoRoot,
|
|
4264
|
+
publicAppIdVar(publicEnvVarPrefix)
|
|
4265
|
+
);
|
|
4266
|
+
if (envAppId === appId) {
|
|
4267
|
+
envSearchKey = await readEnvVar(
|
|
4268
|
+
repoRoot,
|
|
4269
|
+
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4270
|
+
);
|
|
4271
|
+
} else if (envAppId) {
|
|
4272
|
+
envAppIdMismatch = true;
|
|
4273
|
+
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4274
|
+
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4275
|
+
summaries.push(
|
|
4276
|
+
`\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
|
|
4277
|
+
);
|
|
4278
|
+
logger.warn(
|
|
4279
|
+
{ envAppId, appId },
|
|
4280
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
5001
4285
|
let agentRuns = 0;
|
|
5002
4286
|
let ingestCommand;
|
|
5003
4287
|
let ingestScriptRan = false;
|
|
@@ -5107,7 +4391,6 @@ ${detail}` : ""}`
|
|
|
5107
4391
|
]
|
|
5108
4392
|
});
|
|
5109
4393
|
}
|
|
5110
|
-
let searchConfigFile;
|
|
5111
4394
|
if (useCases.includes("search")) {
|
|
5112
4395
|
let extraInstructions = [];
|
|
5113
4396
|
useWizard.getState().clearWrittenFiles();
|
|
@@ -5122,14 +4405,11 @@ ${detail}` : ""}`
|
|
|
5122
4405
|
"implement: retrying search implementation after failed verification"
|
|
5123
4406
|
);
|
|
5124
4407
|
}
|
|
5125
|
-
const
|
|
4408
|
+
const { summary } = await runImplementationUseCase(
|
|
5126
4409
|
"search",
|
|
5127
4410
|
extraInstructions
|
|
5128
4411
|
);
|
|
5129
|
-
summaries.push(formatSummary("search",
|
|
5130
|
-
if (searchResult.searchConfigFile) {
|
|
5131
|
-
searchConfigFile = searchResult.searchConfigFile;
|
|
5132
|
-
}
|
|
4412
|
+
summaries.push(formatSummary("search", summary));
|
|
5133
4413
|
const verification = await runVerificationUseCase();
|
|
5134
4414
|
summaries.push(formatSummary("verification", verification.summary));
|
|
5135
4415
|
if (verification.sufficient) {
|
|
@@ -5145,152 +4425,1247 @@ ${detail}` : ""}`
|
|
|
5145
4425
|
});
|
|
5146
4426
|
break;
|
|
5147
4427
|
}
|
|
5148
|
-
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
5149
|
-
ctx.setUserInput("implementation", "fail");
|
|
5150
|
-
throw new Error(
|
|
5151
|
-
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4428
|
+
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
4429
|
+
ctx.setUserInput("implementation", "fail");
|
|
4430
|
+
throw new Error(
|
|
4431
|
+
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4434
|
+
extraInstructions = verificationRetryInstructions(verification);
|
|
4435
|
+
}
|
|
4436
|
+
let searchKey;
|
|
4437
|
+
let searchKeyError;
|
|
4438
|
+
if (appId) {
|
|
4439
|
+
try {
|
|
4440
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
4441
|
+
targetIndex,
|
|
4442
|
+
appId,
|
|
4443
|
+
envSearchKey
|
|
4444
|
+
);
|
|
4445
|
+
searchKey = resolved2.key;
|
|
4446
|
+
summaries.push(
|
|
4447
|
+
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4448
|
+
);
|
|
4449
|
+
} catch (err) {
|
|
4450
|
+
searchKeyError = err.message;
|
|
4451
|
+
logger.warn(
|
|
4452
|
+
{ err: searchKeyError },
|
|
4453
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4454
|
+
);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
finalSearchEnvVars = publicSearchEnvVars(
|
|
4458
|
+
publicEnvVarPrefix,
|
|
4459
|
+
targetIndex,
|
|
4460
|
+
appId,
|
|
4461
|
+
searchKey
|
|
4462
|
+
);
|
|
4463
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4464
|
+
(v) => !v.value.startsWith("<")
|
|
4465
|
+
);
|
|
4466
|
+
if (resolvedSearchEnvVars.length > 0) {
|
|
4467
|
+
const written = await writeSearchEnvValues(
|
|
4468
|
+
repoRoot,
|
|
4469
|
+
resolvedSearchEnvVars
|
|
4470
|
+
);
|
|
4471
|
+
if (written.length > 0) {
|
|
4472
|
+
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4473
|
+
}
|
|
4474
|
+
const ignored = await ensureGitIgnored(repoRoot, join10(repoRoot, ".env"));
|
|
4475
|
+
if (ignored === "added") {
|
|
4476
|
+
summaries.push("Added .env to .gitignore.");
|
|
4477
|
+
} else if (ignored === "tracked") {
|
|
4478
|
+
summaries.push(
|
|
4479
|
+
'\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
|
|
4480
|
+
);
|
|
4481
|
+
}
|
|
4482
|
+
const stale = [];
|
|
4483
|
+
for (const v of resolvedSearchEnvVars) {
|
|
4484
|
+
if (written.includes(v.name)) continue;
|
|
4485
|
+
const current = await readEnvVar(repoRoot, v.name);
|
|
4486
|
+
if (current && current !== v.value) stale.push(v);
|
|
4487
|
+
}
|
|
4488
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
4489
|
+
summaries.push(
|
|
4490
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
4491
|
+
);
|
|
4492
|
+
logger.warn(
|
|
4493
|
+
{ vars: stale.map((v) => v.name) },
|
|
4494
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
4495
|
+
);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
4499
|
+
(v) => v.value.startsWith("<")
|
|
4500
|
+
);
|
|
4501
|
+
if (unresolvedSearchEnvVars.length > 0) {
|
|
4502
|
+
summaries.push(
|
|
4503
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4504
|
+
);
|
|
4505
|
+
}
|
|
4506
|
+
} else {
|
|
4507
|
+
ctx.setUserInput("implementation", "success");
|
|
4508
|
+
}
|
|
4509
|
+
return {
|
|
4510
|
+
ingestionSource,
|
|
4511
|
+
summary: summaries.join("\n\n"),
|
|
4512
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
4513
|
+
ingestCommand,
|
|
4514
|
+
ingestScriptRan,
|
|
4515
|
+
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
4516
|
+
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
4517
|
+
} : {},
|
|
4518
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
4519
|
+
};
|
|
4520
|
+
}
|
|
4521
|
+
|
|
4522
|
+
// src/workflows/default.ts
|
|
4523
|
+
var defaultWorkflow = {
|
|
4524
|
+
id: "default",
|
|
4525
|
+
title: "Default Workflow",
|
|
4526
|
+
description: "This workflow explores your repo and implements Algolia on your behalf",
|
|
4527
|
+
steps: [
|
|
4528
|
+
defineStep({
|
|
4529
|
+
id: "project-scan",
|
|
4530
|
+
title: "project scan",
|
|
4531
|
+
outputSchema: projectScanSchema,
|
|
4532
|
+
tips: ["apps-and-indices", "records-are-json", "speed"],
|
|
4533
|
+
run: (ctx) => {
|
|
4534
|
+
ctx.notify({
|
|
4535
|
+
messages: [
|
|
4536
|
+
"Scanning your project for languages, frameworks, and Algolia integration points\u2026"
|
|
4537
|
+
]
|
|
4538
|
+
});
|
|
4539
|
+
return projectScan(ctx);
|
|
4540
|
+
}
|
|
4541
|
+
}),
|
|
4542
|
+
defineStep({
|
|
4543
|
+
id: "confirm-language",
|
|
4544
|
+
title: "confirm language",
|
|
4545
|
+
outputSchema: confirmLanguageSchema,
|
|
4546
|
+
visible: false,
|
|
4547
|
+
run: (ctx) => confirmLanguage(ctx)
|
|
4548
|
+
}),
|
|
4549
|
+
defineStep({
|
|
4550
|
+
id: "confirm-entities",
|
|
4551
|
+
title: "confirm entities",
|
|
4552
|
+
outputSchema: confirmEntitiesSchema,
|
|
4553
|
+
visible: false,
|
|
4554
|
+
run: (ctx) => confirmEntities(ctx)
|
|
4555
|
+
}),
|
|
4556
|
+
defineStep({
|
|
4557
|
+
id: "select-index",
|
|
4558
|
+
title: "Set up index",
|
|
4559
|
+
outputSchema: z30.object({
|
|
4560
|
+
selection: z30.string()
|
|
4561
|
+
}),
|
|
4562
|
+
run: (ctx) => selectIndexStep(ctx)
|
|
4563
|
+
}),
|
|
4564
|
+
defineStep({
|
|
4565
|
+
id: "ingestion",
|
|
4566
|
+
title: "ingest records",
|
|
4567
|
+
outputSchema: implementSchema,
|
|
4568
|
+
tips: ["records-are-json", "searchable-attributes", "facets"],
|
|
4569
|
+
run: (ctx) => {
|
|
4570
|
+
ctx.notify({
|
|
4571
|
+
messages: [
|
|
4572
|
+
"Setting up an Algolia ingestion pipeline in your project\u2026"
|
|
4573
|
+
]
|
|
4574
|
+
});
|
|
4575
|
+
return implement(ctx, ["ingestion"]);
|
|
4576
|
+
}
|
|
4577
|
+
}),
|
|
4578
|
+
defineStep({
|
|
4579
|
+
id: "confirm-framework",
|
|
4580
|
+
title: "Confirm framework",
|
|
4581
|
+
outputSchema: confirmFrameworkSchema,
|
|
4582
|
+
visible: false,
|
|
4583
|
+
run: (ctx) => confirmFramework(ctx)
|
|
4584
|
+
}),
|
|
4585
|
+
defineStep({
|
|
4586
|
+
id: "search",
|
|
4587
|
+
title: "create search ui",
|
|
4588
|
+
outputSchema: implementSchema,
|
|
4589
|
+
tips: [
|
|
4590
|
+
"test-relevance",
|
|
4591
|
+
"search-analytics",
|
|
4592
|
+
"recommend",
|
|
4593
|
+
"searchable-attributes",
|
|
4594
|
+
"facets"
|
|
4595
|
+
],
|
|
4596
|
+
run: (ctx) => {
|
|
4597
|
+
ctx.notify({
|
|
4598
|
+
messages: ["Building your Algolia search experience\u2026"]
|
|
4599
|
+
});
|
|
4600
|
+
return implement(ctx, ["search"]);
|
|
4601
|
+
}
|
|
4602
|
+
}),
|
|
4603
|
+
defineStep({
|
|
4604
|
+
id: "review",
|
|
4605
|
+
title: "done",
|
|
4606
|
+
outputSchema: reviewSchema,
|
|
4607
|
+
run: (ctx) => {
|
|
4608
|
+
ctx.notify({
|
|
4609
|
+
messages: ["Summarizing what we did\u2026"]
|
|
4610
|
+
});
|
|
4611
|
+
const ingestion2 = ctx.getStepOutput(
|
|
4612
|
+
"ingestion"
|
|
4613
|
+
);
|
|
4614
|
+
return reviewStep(ctx, {
|
|
4615
|
+
// ingestCommand was already shown verbatim as a notice; an
|
|
4616
|
+
// LLM-paraphrased restatement in nextSteps risks being wrong.
|
|
4617
|
+
nextStepsGuidance: ingestion2?.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."
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
})
|
|
4621
|
+
]
|
|
4622
|
+
};
|
|
4623
|
+
|
|
4624
|
+
// src/workflows/index.ts
|
|
4625
|
+
var workflows = {
|
|
4626
|
+
[defaultWorkflow.id]: defaultWorkflow
|
|
4627
|
+
};
|
|
4628
|
+
function getWorkflow(id) {
|
|
4629
|
+
return workflows[id];
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
// src/ui/Welcome.tsx
|
|
4633
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4634
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4635
|
+
import { useState as useState7 } from "react";
|
|
4636
|
+
import { Box as Box10, Spacer, Text as Text10, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
4637
|
+
|
|
4638
|
+
// src/ui/copy/welcome.ts
|
|
4639
|
+
var sidebarItems = [
|
|
4640
|
+
{
|
|
4641
|
+
title: "scan your project",
|
|
4642
|
+
description: "detect models, schemas, and data worth indexing"
|
|
4643
|
+
},
|
|
4644
|
+
{
|
|
4645
|
+
title: "select and index",
|
|
4646
|
+
description: "push 100 records to Algolia in seconds"
|
|
4647
|
+
},
|
|
4648
|
+
{
|
|
4649
|
+
title: "detect your stack",
|
|
4650
|
+
description: "whatever language and framework you already use"
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
title: "scaffold a search UI",
|
|
4654
|
+
description: "a search box and results, wired into your app"
|
|
4655
|
+
},
|
|
4656
|
+
{
|
|
4657
|
+
title: "ship it",
|
|
4658
|
+
description: "build check passes, search is live with your real data"
|
|
4659
|
+
}
|
|
4660
|
+
];
|
|
4661
|
+
|
|
4662
|
+
// src/ui/Welcome.tsx
|
|
4663
|
+
import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
|
|
4664
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4665
|
+
var IMAGE_PATH = join11(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
|
|
4666
|
+
var TERMINAL_INFO = {
|
|
4667
|
+
...defaultTerminalInfo,
|
|
4668
|
+
supportsUnicode: true,
|
|
4669
|
+
supportsColor: true
|
|
4670
|
+
};
|
|
4671
|
+
function SidebarItem({
|
|
4672
|
+
title,
|
|
4673
|
+
description
|
|
4674
|
+
}) {
|
|
4675
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
4676
|
+
/* @__PURE__ */ jsxs9(Box10, { gap: 1, children: [
|
|
4677
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.success, children: "\u2192" }),
|
|
4678
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: title })
|
|
4679
|
+
] }),
|
|
4680
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 2, children: [
|
|
4681
|
+
/* @__PURE__ */ jsx8(Spacer, {}),
|
|
4682
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: description })
|
|
4683
|
+
] })
|
|
4684
|
+
] });
|
|
4685
|
+
}
|
|
4686
|
+
function Welcome() {
|
|
4687
|
+
const confirmStart = useWizard((s) => s.confirmStart);
|
|
4688
|
+
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
4689
|
+
const { rows } = useWindowSize5();
|
|
4690
|
+
const actions = [
|
|
4691
|
+
{ label: "start wizard", run: confirmStart },
|
|
4692
|
+
{ label: "learn more", run: openLearnMore }
|
|
4693
|
+
];
|
|
4694
|
+
const [index, setIndex] = useState7(0);
|
|
4695
|
+
useInput3((input, key) => {
|
|
4696
|
+
if (key.upArrow || input === "k") {
|
|
4697
|
+
setIndex((i) => (i - 1 + actions.length) % actions.length);
|
|
4698
|
+
} else if (key.downArrow || input === "j") {
|
|
4699
|
+
setIndex((i) => (i + 1) % actions.length);
|
|
4700
|
+
} else if (key.return) {
|
|
4701
|
+
actions[index].run();
|
|
4702
|
+
}
|
|
4703
|
+
});
|
|
4704
|
+
const scales = {
|
|
4705
|
+
large: {
|
|
4706
|
+
sidebar: { padding: { x: 4, y: 2 }, gap: 2 },
|
|
4707
|
+
main: { padding: { x: 8, y: 4 } }
|
|
4708
|
+
},
|
|
4709
|
+
small: {
|
|
4710
|
+
sidebar: { padding: { x: 2, y: 1 }, gap: 1 },
|
|
4711
|
+
main: { padding: { x: 4, y: 2 } }
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4714
|
+
let layout = scales["large"];
|
|
4715
|
+
if (rows < 30) {
|
|
4716
|
+
layout = scales["small"];
|
|
4717
|
+
}
|
|
4718
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
4719
|
+
/* @__PURE__ */ jsx8(
|
|
4720
|
+
Box10,
|
|
4721
|
+
{
|
|
4722
|
+
paddingY: layout.main.padding.y,
|
|
4723
|
+
paddingX: layout.main.padding.x,
|
|
4724
|
+
flexDirection: "column",
|
|
4725
|
+
justifyContent: "center",
|
|
4726
|
+
children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 2, children: [
|
|
4727
|
+
/* @__PURE__ */ jsx8(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx8(
|
|
4728
|
+
Image,
|
|
4729
|
+
{
|
|
4730
|
+
src: IMAGE_PATH,
|
|
4731
|
+
objectFit: "contain",
|
|
4732
|
+
alt: "Algolia",
|
|
4733
|
+
width: 20,
|
|
4734
|
+
height: 10,
|
|
4735
|
+
protocol: "halfBlock"
|
|
4736
|
+
}
|
|
4737
|
+
) }),
|
|
4738
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
4739
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: actions.map((action, i) => /* @__PURE__ */ jsx8(
|
|
4740
|
+
SelectRow,
|
|
4741
|
+
{
|
|
4742
|
+
highlighted: i === index,
|
|
4743
|
+
highlightBackground: false,
|
|
4744
|
+
label: action.label
|
|
4745
|
+
},
|
|
4746
|
+
action.label
|
|
4747
|
+
)) }),
|
|
4748
|
+
/* @__PURE__ */ jsxs9(Box10, { gap: 2, children: [
|
|
4749
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
4750
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[\u2191] [\u2193]" }),
|
|
4751
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " move" })
|
|
4752
|
+
] }),
|
|
4753
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
4754
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[enter]" }),
|
|
4755
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " confirm" })
|
|
4756
|
+
] })
|
|
4757
|
+
] })
|
|
4758
|
+
] })
|
|
4759
|
+
}
|
|
4760
|
+
),
|
|
4761
|
+
/* @__PURE__ */ jsxs9(
|
|
4762
|
+
Box10,
|
|
4763
|
+
{
|
|
4764
|
+
backgroundColor: COLORS.bg.sidebar,
|
|
4765
|
+
width: 40,
|
|
4766
|
+
paddingY: layout.sidebar.padding.y,
|
|
4767
|
+
paddingX: layout.sidebar.padding.x,
|
|
4768
|
+
gap: layout.sidebar.gap,
|
|
4769
|
+
flexDirection: "column",
|
|
4770
|
+
justifyContent: "center",
|
|
4771
|
+
children: [
|
|
4772
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
4773
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx8(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
4774
|
+
]
|
|
4775
|
+
}
|
|
4776
|
+
)
|
|
4777
|
+
] });
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// src/ui/LearnMore.tsx
|
|
4781
|
+
import { Fragment } from "react";
|
|
4782
|
+
import { Box as Box11, Text as Text11, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
4783
|
+
|
|
4784
|
+
// src/ui/copy/learn-more.ts
|
|
4785
|
+
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
4786
|
+
var accessItems = [
|
|
4787
|
+
{
|
|
4788
|
+
tag: "READ",
|
|
4789
|
+
title: "Project files",
|
|
4790
|
+
description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
4791
|
+
},
|
|
4792
|
+
{
|
|
4793
|
+
tag: "WRITE",
|
|
4794
|
+
title: "Code changes",
|
|
4795
|
+
description: "creates & edits files (search UI, config) directly in your branch."
|
|
4796
|
+
},
|
|
4797
|
+
{
|
|
4798
|
+
tag: "EXEC",
|
|
4799
|
+
title: "Setup commands",
|
|
4800
|
+
description: "runs dependency installs, the ingestion script & your own checks. Every command is shown in full and needs your OK; its output is shown as-is, so a command that prints a secret will display it."
|
|
4801
|
+
},
|
|
4802
|
+
{
|
|
4803
|
+
tag: "NET",
|
|
4804
|
+
title: "Algolia API",
|
|
4805
|
+
description: "sends index settings & the records you pick to your Algolia app over HTTPS."
|
|
4806
|
+
},
|
|
4807
|
+
{
|
|
4808
|
+
tag: "KEY",
|
|
4809
|
+
title: "Credentials",
|
|
4810
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
|
|
4811
|
+
}
|
|
4812
|
+
];
|
|
4813
|
+
var neverItems = [
|
|
4814
|
+
"Send your source code to a model or third party",
|
|
4815
|
+
"Commit or push to git",
|
|
4816
|
+
"Run a command you haven't approved"
|
|
4817
|
+
];
|
|
4818
|
+
var policyLinks = [
|
|
4819
|
+
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
4820
|
+
{ label: "Privacy Policy", url: "https://www.algolia.com/policies/privacy" }
|
|
4821
|
+
];
|
|
4822
|
+
|
|
4823
|
+
// src/ui/LearnMore.tsx
|
|
4824
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4825
|
+
var TAG_COLORS = {
|
|
4826
|
+
READ: COLORS.success,
|
|
4827
|
+
WRITE: COLORS.badge,
|
|
4828
|
+
EXEC: COLORS.danger,
|
|
4829
|
+
NET: COLORS.accent,
|
|
4830
|
+
KEY: COLORS.muted
|
|
4831
|
+
};
|
|
4832
|
+
var TAG_COLUMN_WIDTH = 10;
|
|
4833
|
+
var PADDING_X = 6;
|
|
4834
|
+
var NEVER_BOX_PAD_X = 2;
|
|
4835
|
+
function NeverLine({
|
|
4836
|
+
width,
|
|
4837
|
+
segments = []
|
|
4838
|
+
}) {
|
|
4839
|
+
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
4840
|
+
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
4841
|
+
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
4842
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" }),
|
|
4843
|
+
" ".repeat(NEVER_BOX_PAD_X),
|
|
4844
|
+
segments.map((s, i) => /* @__PURE__ */ jsx9(Text11, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
4845
|
+
" ".repeat(rightPad),
|
|
4846
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" })
|
|
4847
|
+
] });
|
|
4848
|
+
}
|
|
4849
|
+
function LearnMore() {
|
|
4850
|
+
const confirmStart = useWizard((s) => s.confirmStart);
|
|
4851
|
+
const backToHome = useWizard((s) => s.backToHome);
|
|
4852
|
+
const { columns } = useWindowSize6();
|
|
4853
|
+
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
4854
|
+
useInput4((_input, key) => {
|
|
4855
|
+
if (key.escape) backToHome();
|
|
4856
|
+
else if (key.return) confirmStart();
|
|
4857
|
+
});
|
|
4858
|
+
return /* @__PURE__ */ jsxs10(
|
|
4859
|
+
Box11,
|
|
4860
|
+
{
|
|
4861
|
+
flexDirection: "column",
|
|
4862
|
+
paddingX: PADDING_X,
|
|
4863
|
+
paddingY: 2,
|
|
4864
|
+
width: "100%",
|
|
4865
|
+
gap: 1,
|
|
4866
|
+
children: [
|
|
4867
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
4868
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: accessIntro }),
|
|
4869
|
+
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
4870
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
4871
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
4872
|
+
/* @__PURE__ */ jsx9(Box11, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text11, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
4873
|
+
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
4874
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: item.title }),
|
|
4875
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
4876
|
+
] }) })
|
|
4877
|
+
] })
|
|
4878
|
+
] }, item.tag)) }),
|
|
4879
|
+
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
|
|
4880
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
4881
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4882
|
+
/* @__PURE__ */ jsx9(
|
|
4883
|
+
NeverLine,
|
|
4884
|
+
{
|
|
4885
|
+
width: dividerWidth,
|
|
4886
|
+
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
4887
|
+
}
|
|
4888
|
+
),
|
|
4889
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
4890
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4891
|
+
/* @__PURE__ */ jsx9(
|
|
4892
|
+
NeverLine,
|
|
4893
|
+
{
|
|
4894
|
+
width: dividerWidth,
|
|
4895
|
+
segments: [
|
|
4896
|
+
{ text: "\u2715", color: COLORS.danger },
|
|
4897
|
+
{ text: " " },
|
|
4898
|
+
{ text: item, color: COLORS.primary }
|
|
4899
|
+
]
|
|
4900
|
+
}
|
|
4901
|
+
)
|
|
4902
|
+
] }, item)),
|
|
4903
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4904
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
4905
|
+
] }),
|
|
4906
|
+
/* @__PURE__ */ jsx9(Box11, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4907
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
4908
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.accent, children: link.url })
|
|
4909
|
+
] }, link.label)) }),
|
|
4910
|
+
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
4911
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4912
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
4913
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "esc" }),
|
|
4914
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "] back" })
|
|
4915
|
+
] }),
|
|
4916
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4917
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
4918
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "enter" }),
|
|
4919
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "]" }),
|
|
4920
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
4921
|
+
] })
|
|
4922
|
+
] })
|
|
4923
|
+
]
|
|
4924
|
+
}
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
// src/ui/Sidebar.tsx
|
|
4929
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
4930
|
+
|
|
4931
|
+
// src/ui/Steps.tsx
|
|
4932
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
4933
|
+
import Spinner from "ink-spinner";
|
|
4934
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
4935
|
+
function Steps() {
|
|
4936
|
+
const { steps } = useWizard();
|
|
4937
|
+
const visibleSteps = steps.filter(isStepVisible);
|
|
4938
|
+
return /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx10(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status[s.status], children: [
|
|
4939
|
+
s.status === "running" ? /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
4940
|
+
" ",
|
|
4941
|
+
s.title
|
|
4942
|
+
] }) }, s.id)) });
|
|
4943
|
+
}
|
|
4944
|
+
function CurrentStep() {
|
|
4945
|
+
const { steps } = useWizard();
|
|
4946
|
+
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
4947
|
+
if (!currentStep) return null;
|
|
4948
|
+
return /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status.running, children: [
|
|
4949
|
+
/* @__PURE__ */ jsx10(Spinner, { type: "dots" }),
|
|
4950
|
+
" ",
|
|
4951
|
+
` ${currentStep.title}`
|
|
4952
|
+
] });
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
// src/ui/Progress.tsx
|
|
4956
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
4957
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4958
|
+
function Progress() {
|
|
4959
|
+
const { steps, currentStepIndex } = useWizard();
|
|
4960
|
+
const visibleSteps = steps.filter(isStepVisible);
|
|
4961
|
+
if (visibleSteps.length === 0) return null;
|
|
4962
|
+
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
4963
|
+
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
4964
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
4965
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "STEP" }),
|
|
4966
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: activeStepNumber }),
|
|
4967
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: "/" }),
|
|
4968
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: visibleSteps.length })
|
|
4969
|
+
] });
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
// src/ui/copy/sidebar-commands.ts
|
|
4973
|
+
var sidebarCommands = [
|
|
4974
|
+
{ keyHint: "tab", description: "toggle logs" },
|
|
4975
|
+
{ keyHint: "esc", description: "exit wizard" }
|
|
4976
|
+
];
|
|
4977
|
+
|
|
4978
|
+
// src/ui/Sidebar.tsx
|
|
4979
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4980
|
+
function Sidebar() {
|
|
4981
|
+
return /* @__PURE__ */ jsxs13(
|
|
4982
|
+
Box14,
|
|
4983
|
+
{
|
|
4984
|
+
backgroundColor: "#14171E",
|
|
4985
|
+
width: 30,
|
|
4986
|
+
paddingX: 4,
|
|
4987
|
+
paddingY: 2,
|
|
4988
|
+
flexDirection: "column",
|
|
4989
|
+
justifyContent: "space-between",
|
|
4990
|
+
children: [
|
|
4991
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
4992
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: "PROGRESS" }),
|
|
4993
|
+
/* @__PURE__ */ jsx12(Steps, {})
|
|
4994
|
+
] }),
|
|
4995
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
4996
|
+
/* @__PURE__ */ jsx12(Progress, {}),
|
|
4997
|
+
/* @__PURE__ */ jsx12(Box14, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
4998
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
|
|
4999
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
5000
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: c.description })
|
|
5001
|
+
] });
|
|
5002
|
+
}) })
|
|
5003
|
+
] })
|
|
5004
|
+
]
|
|
5005
|
+
}
|
|
5006
|
+
);
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
// src/ui/Ribbon.tsx
|
|
5010
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
5011
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
5012
|
+
function Ribbon() {
|
|
5013
|
+
const firstCommand = sidebarCommands[0];
|
|
5014
|
+
return /* @__PURE__ */ jsxs14(
|
|
5015
|
+
Box15,
|
|
5016
|
+
{
|
|
5017
|
+
backgroundColor: "#14171E",
|
|
5018
|
+
flexDirection: "row",
|
|
5019
|
+
justifyContent: "space-between",
|
|
5020
|
+
paddingX: 2,
|
|
5021
|
+
paddingY: 1,
|
|
5022
|
+
children: [
|
|
5023
|
+
/* @__PURE__ */ jsx13(Progress, {}),
|
|
5024
|
+
/* @__PURE__ */ jsx13(CurrentStep, {}),
|
|
5025
|
+
/* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: 1, children: [
|
|
5026
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
5027
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: firstCommand.description })
|
|
5028
|
+
] })
|
|
5029
|
+
]
|
|
5030
|
+
}
|
|
5031
|
+
);
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
// src/ui/App.tsx
|
|
5035
|
+
import { useState as useState9 } from "react";
|
|
5036
|
+
|
|
5037
|
+
// src/ui/Logs.tsx
|
|
5038
|
+
import { Box as Box16, Text as Text16, useInput as useInput5 } from "ink";
|
|
5039
|
+
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
5040
|
+
var KIND_COLOR = {
|
|
5041
|
+
tool: COLORS.primary,
|
|
5042
|
+
prompt: COLORS.badge
|
|
5043
|
+
};
|
|
5044
|
+
var STATUS_COLOR = {
|
|
5045
|
+
running: COLORS.status.running,
|
|
5046
|
+
error: COLORS.danger
|
|
5047
|
+
};
|
|
5048
|
+
function logNameColor(entry) {
|
|
5049
|
+
return STATUS_COLOR[entry.status] ?? KIND_COLOR[entry.kind];
|
|
5050
|
+
}
|
|
5051
|
+
var ROW_GAP = 1;
|
|
5052
|
+
function truncate2(str, maxWidth) {
|
|
5053
|
+
if (maxWidth <= 0) return "";
|
|
5054
|
+
return str.length > maxWidth ? `${str.slice(0, maxWidth - 1)}\u2026` : str;
|
|
5055
|
+
}
|
|
5056
|
+
function rawInputText(input) {
|
|
5057
|
+
if (input === void 0) return "";
|
|
5058
|
+
const str = typeof input === "string" ? input : JSON.stringify(input);
|
|
5059
|
+
if (!str || str === "{}") return "";
|
|
5060
|
+
return str.replace(/\s+/g, " ").trim();
|
|
5061
|
+
}
|
|
5062
|
+
function formatTimestamp(ms) {
|
|
5063
|
+
const d = new Date(ms);
|
|
5064
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
5065
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
5066
|
+
}
|
|
5067
|
+
function Logs() {
|
|
5068
|
+
const logs = useWizard((s) => s.logs);
|
|
5069
|
+
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
5070
|
+
useInput5((_input, key) => {
|
|
5071
|
+
if (key.upArrow) scroll.scrollBy(-1);
|
|
5072
|
+
else if (key.downArrow) scroll.scrollBy(1);
|
|
5073
|
+
});
|
|
5074
|
+
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
5075
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
5076
|
+
logs.length === 0 && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "No logs yet." }),
|
|
5077
|
+
/* @__PURE__ */ jsx14(ScrollView, { scroll, children: visible.map((entry) => {
|
|
5078
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
5079
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
5080
|
+
const rawPreview = rawInputText(entry.input);
|
|
5081
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
5082
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
5083
|
+
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
5084
|
+
const name = truncate2(entry.name, budget);
|
|
5085
|
+
budget -= name.length;
|
|
5086
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
5087
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
5088
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: timestamp }),
|
|
5089
|
+
/* @__PURE__ */ jsx14(Text16, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
5090
|
+
preview && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
5091
|
+
durationText && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: durationText })
|
|
5092
|
+
] }, entry.id);
|
|
5093
|
+
}) }),
|
|
5094
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
5095
|
+
] });
|
|
5096
|
+
}
|
|
5097
|
+
|
|
5098
|
+
// src/ui/Tips.tsx
|
|
5099
|
+
import { useEffect as useEffect3, useState as useState8 } from "react";
|
|
5100
|
+
import { Box as Box18, Text as Text18 } from "ink";
|
|
5101
|
+
import terminalLink from "terminal-link";
|
|
5102
|
+
|
|
5103
|
+
// src/ui/Code.tsx
|
|
5104
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
5105
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
5106
|
+
var TOKEN_RE = /("(?:\\.|[^"\\])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\]:,])/g;
|
|
5107
|
+
function highlightJson(json) {
|
|
5108
|
+
const parts = json.split(TOKEN_RE);
|
|
5109
|
+
return parts.map((part, i) => {
|
|
5110
|
+
if (!part) return null;
|
|
5111
|
+
if (part[0] === '"') {
|
|
5112
|
+
const next = parts.slice(i + 1).find((p) => p.trim());
|
|
5113
|
+
const isKey = next?.trimStart().startsWith(":");
|
|
5114
|
+
return /* @__PURE__ */ jsx15(Text17, { color: isKey ? "cyan" : "green", children: part }, i);
|
|
5115
|
+
}
|
|
5116
|
+
if (part === "true" || part === "false" || part === "null") {
|
|
5117
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "magenta", children: part }, i);
|
|
5118
|
+
}
|
|
5119
|
+
if (/^-?\d/.test(part)) {
|
|
5120
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "yellow", children: part }, i);
|
|
5121
|
+
}
|
|
5122
|
+
if (/^[{}[\]:,]$/.test(part)) {
|
|
5123
|
+
return /* @__PURE__ */ jsx15(Text17, { dimColor: true, children: part }, i);
|
|
5124
|
+
}
|
|
5125
|
+
return /* @__PURE__ */ jsx15(Text17, { children: part }, i);
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
5128
|
+
function Code({ children }) {
|
|
5129
|
+
return /* @__PURE__ */ jsx15(Box17, { children: /* @__PURE__ */ jsx15(Text17, { children: highlightJson(children) }) });
|
|
5130
|
+
}
|
|
5131
|
+
|
|
5132
|
+
// src/ui/copy/tips.ts
|
|
5133
|
+
var tips = [
|
|
5134
|
+
{
|
|
5135
|
+
id: "apps-and-indices",
|
|
5136
|
+
title: "An Application is like your library. Indices are the books in it.",
|
|
5137
|
+
chunks: [
|
|
5138
|
+
{
|
|
5139
|
+
type: "text",
|
|
5140
|
+
value: "Applications hold your API keys and Indices."
|
|
5141
|
+
},
|
|
5142
|
+
{
|
|
5143
|
+
type: "text",
|
|
5144
|
+
value: "Each index is a searchable collection of records (think: products, articles, orders, concerts)."
|
|
5145
|
+
}
|
|
5146
|
+
]
|
|
5147
|
+
},
|
|
5148
|
+
{
|
|
5149
|
+
id: "records-are-json",
|
|
5150
|
+
title: "A Record is a JSON object inside of an index.",
|
|
5151
|
+
chunks: [
|
|
5152
|
+
{
|
|
5153
|
+
type: "text",
|
|
5154
|
+
value: "Every object you index is just a JSON document with a unique objectID."
|
|
5155
|
+
},
|
|
5156
|
+
{
|
|
5157
|
+
type: "text",
|
|
5158
|
+
value: "No fixed schema, no hard requirements."
|
|
5159
|
+
},
|
|
5160
|
+
{
|
|
5161
|
+
type: "text",
|
|
5162
|
+
value: "Everything else is up to you:"
|
|
5163
|
+
},
|
|
5164
|
+
{
|
|
5165
|
+
type: "code",
|
|
5166
|
+
value: JSON.stringify(
|
|
5167
|
+
{
|
|
5168
|
+
objectID: "sku_48213",
|
|
5169
|
+
name: "Trail Running Shoe",
|
|
5170
|
+
brand: "Northline",
|
|
5171
|
+
categories: ["Footwear", "Running", "Mens"],
|
|
5172
|
+
price: 129.99,
|
|
5173
|
+
in_stock: true,
|
|
5174
|
+
rating: 4.6
|
|
5175
|
+
},
|
|
5176
|
+
null,
|
|
5177
|
+
2
|
|
5178
|
+
)
|
|
5179
|
+
}
|
|
5180
|
+
]
|
|
5181
|
+
},
|
|
5182
|
+
{
|
|
5183
|
+
id: "speed",
|
|
5184
|
+
title: "Speed isn't a feature, it's our business",
|
|
5185
|
+
chunks: [
|
|
5186
|
+
{
|
|
5187
|
+
type: "text",
|
|
5188
|
+
value: "Algolia's engine processes most search queries in 1 to 50 milliseconds."
|
|
5189
|
+
},
|
|
5190
|
+
{
|
|
5191
|
+
type: "text",
|
|
5192
|
+
value: "This is why our search-as-you-type experience feels instant."
|
|
5193
|
+
}
|
|
5194
|
+
]
|
|
5195
|
+
},
|
|
5196
|
+
{
|
|
5197
|
+
id: "searchable-attributes",
|
|
5198
|
+
title: "Searchable attributes are your relevance dial #1",
|
|
5199
|
+
chunks: [
|
|
5200
|
+
{
|
|
5201
|
+
type: "text",
|
|
5202
|
+
value: "Order matters. Attributes listed first in"
|
|
5203
|
+
},
|
|
5204
|
+
{ type: "codeword", value: "searchableAttributes" },
|
|
5205
|
+
{ type: "text", value: "carry more ranking weight." },
|
|
5206
|
+
{
|
|
5207
|
+
type: "text",
|
|
5208
|
+
value: "This is the single highest-leverage lever new users don't know exists."
|
|
5209
|
+
}
|
|
5210
|
+
]
|
|
5211
|
+
},
|
|
5212
|
+
{
|
|
5213
|
+
id: "facets",
|
|
5214
|
+
title: "Facets aren't just filters, they help build your UI",
|
|
5215
|
+
chunks: [
|
|
5216
|
+
{ type: "codeword", value: "attributesForFaceting" },
|
|
5217
|
+
{
|
|
5218
|
+
type: "text",
|
|
5219
|
+
value: "unlock category sidebars, price sliders, tag clouds without extra backend work."
|
|
5220
|
+
}
|
|
5221
|
+
]
|
|
5222
|
+
},
|
|
5223
|
+
{
|
|
5224
|
+
id: "test-relevance",
|
|
5225
|
+
title: "Test relevance in the dashboard before you write a line of ranking code",
|
|
5226
|
+
chunks: [
|
|
5227
|
+
{
|
|
5228
|
+
type: "text",
|
|
5229
|
+
value: "Our Search dashboard has a live preview where you can browse results."
|
|
5230
|
+
},
|
|
5231
|
+
{
|
|
5232
|
+
type: "text",
|
|
5233
|
+
value: "Tune your settings and see how it alters your results in"
|
|
5234
|
+
},
|
|
5235
|
+
{
|
|
5236
|
+
type: "link",
|
|
5237
|
+
value: "real-time.",
|
|
5238
|
+
href: "https://dashboard.algolia.com/explorer/browse"
|
|
5239
|
+
}
|
|
5240
|
+
]
|
|
5241
|
+
},
|
|
5242
|
+
{
|
|
5243
|
+
id: "search-analytics",
|
|
5244
|
+
title: "Search Analytics helps you discover opportunities",
|
|
5245
|
+
chunks: [
|
|
5246
|
+
{
|
|
5247
|
+
type: "text",
|
|
5248
|
+
value: "No click results for a particular query? Low click-through rates for another?"
|
|
5249
|
+
},
|
|
5250
|
+
{
|
|
5251
|
+
type: "text",
|
|
5252
|
+
value: "Our Search Analytics will help you identify synonyms, rules or other relevancy settings to improve your results."
|
|
5253
|
+
}
|
|
5254
|
+
]
|
|
5255
|
+
},
|
|
5256
|
+
{
|
|
5257
|
+
id: "recommend",
|
|
5258
|
+
title: "Let Algolia act as your recommendation engine",
|
|
5259
|
+
chunks: [
|
|
5260
|
+
{
|
|
5261
|
+
type: "text",
|
|
5262
|
+
value: "Beyond search, Algolia Recommend runs models trained on your existing indices and event data to power your recommendation engine."
|
|
5263
|
+
},
|
|
5264
|
+
{
|
|
5265
|
+
type: "text",
|
|
5266
|
+
value: "You can improve engagement with related, popular or visually similar items."
|
|
5267
|
+
}
|
|
5268
|
+
]
|
|
5269
|
+
}
|
|
5270
|
+
];
|
|
5271
|
+
var tipsById = Object.fromEntries(
|
|
5272
|
+
tips.map((tip) => [tip.id, tip])
|
|
5273
|
+
);
|
|
5274
|
+
function tipsByIds(ids) {
|
|
5275
|
+
return ids.map((id) => tipsById[id]).filter((tip) => tip !== void 0);
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5278
|
+
// src/ui/Tips.tsx
|
|
5279
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5280
|
+
var TICK_MS = 16;
|
|
5281
|
+
var CHUNK_HOLD_MS = 2e3;
|
|
5282
|
+
var HOLD_MS = 8e3;
|
|
5283
|
+
function TipTitle({ children }) {
|
|
5284
|
+
return /* @__PURE__ */ jsxs16(Box18, { gap: 1, children: [
|
|
5285
|
+
/* @__PURE__ */ jsx16(Text18, { color: "cyan", children: "\u2726" }),
|
|
5286
|
+
/* @__PURE__ */ jsx16(Text18, { color: "white", bold: true, children })
|
|
5287
|
+
] });
|
|
5288
|
+
}
|
|
5289
|
+
function InlineSegment({ segment }) {
|
|
5290
|
+
switch (segment.type) {
|
|
5291
|
+
case "highlight":
|
|
5292
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.success, children: segment.value });
|
|
5293
|
+
case "link":
|
|
5294
|
+
return /* @__PURE__ */ jsx16(Text18, { color: "cyan", underline: true, children: segment.href ? terminalLink(segment.value, segment.href) : segment.value });
|
|
5295
|
+
case "codeword":
|
|
5296
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.highlight.fg, backgroundColor: COLORS.highlight.bg, children: segment.value });
|
|
5297
|
+
default:
|
|
5298
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.muted, children: segment.value });
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
function TipContent({
|
|
5302
|
+
chunks,
|
|
5303
|
+
revealed
|
|
5304
|
+
}) {
|
|
5305
|
+
let remaining = revealed;
|
|
5306
|
+
const slices = chunks.map((chunk) => {
|
|
5307
|
+
const slice = chunk.value.slice(0, Math.max(0, remaining));
|
|
5308
|
+
remaining -= chunk.value.length;
|
|
5309
|
+
return slice;
|
|
5310
|
+
});
|
|
5311
|
+
const blocks = [];
|
|
5312
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
5313
|
+
const chunk = chunks[i];
|
|
5314
|
+
const slice = slices[i];
|
|
5315
|
+
if (!slice) continue;
|
|
5316
|
+
const segment = {
|
|
5317
|
+
type: chunk.type,
|
|
5318
|
+
value: slice,
|
|
5319
|
+
href: chunk.type === "link" ? chunk.href : void 0
|
|
5320
|
+
};
|
|
5321
|
+
if (chunk.type === "code") {
|
|
5322
|
+
blocks.push({ type: "code", segment });
|
|
5323
|
+
continue;
|
|
5324
|
+
}
|
|
5325
|
+
const last = blocks[blocks.length - 1];
|
|
5326
|
+
if (last?.type === "inline") {
|
|
5327
|
+
last.segments.push(segment);
|
|
5328
|
+
} else {
|
|
5329
|
+
blocks.push({ type: "inline", segments: [segment] });
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
return /* @__PURE__ */ jsx16(Box18, { flexDirection: "column", marginLeft: 2, gap: 1, children: blocks.map(
|
|
5333
|
+
(block, i) => block.type === "code" ? /* @__PURE__ */ jsx16(Code, { children: block.segment.value }, i) : /* @__PURE__ */ jsx16(Text18, { children: block.segments.map((segment, j) => /* @__PURE__ */ jsxs16(Text18, { children: [
|
|
5334
|
+
j > 0 && " ",
|
|
5335
|
+
/* @__PURE__ */ jsx16(InlineSegment, { segment })
|
|
5336
|
+
] }, j)) }, i)
|
|
5337
|
+
) });
|
|
5338
|
+
}
|
|
5339
|
+
var progress = { stepId: null, tipIndex: 0, revealed: 0 };
|
|
5340
|
+
function Tips({ stepId, ids }) {
|
|
5341
|
+
const tips2 = tipsByIds(ids);
|
|
5342
|
+
const resuming = progress.stepId === stepId;
|
|
5343
|
+
if (!resuming) {
|
|
5344
|
+
progress.stepId = stepId;
|
|
5345
|
+
progress.tipIndex = 0;
|
|
5346
|
+
progress.revealed = 0;
|
|
5347
|
+
}
|
|
5348
|
+
const [tipIndex, setTipIndex] = useState8(resuming ? progress.tipIndex : 0);
|
|
5349
|
+
const [revealed, setRevealed] = useState8(resuming ? progress.revealed : 0);
|
|
5350
|
+
const tip = tips2.length > 0 ? tips2[tipIndex % tips2.length] : void 0;
|
|
5351
|
+
const contentLength = tip?.chunks.reduce((sum, c) => sum + c.value.length, 0) ?? 0;
|
|
5352
|
+
const totalLength = (tip?.title.length ?? 0) + contentLength;
|
|
5353
|
+
const segments = tip ? [
|
|
5354
|
+
{ type: "title", length: tip.title.length },
|
|
5355
|
+
...tip.chunks.map((c) => ({ type: c.type, length: c.value.length }))
|
|
5356
|
+
] : [];
|
|
5357
|
+
const noPauseAfterText = [
|
|
5358
|
+
"highlight",
|
|
5359
|
+
"link",
|
|
5360
|
+
"codeword"
|
|
5361
|
+
];
|
|
5362
|
+
const chunkBoundaries = [];
|
|
5363
|
+
let cumulative = 0;
|
|
5364
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
5365
|
+
cumulative += segments[i].length;
|
|
5366
|
+
const skipPause = noPauseAfterText.includes(
|
|
5367
|
+
segments[i + 1].type
|
|
5368
|
+
);
|
|
5369
|
+
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
5370
|
+
}
|
|
5371
|
+
useEffect3(() => {
|
|
5372
|
+
if (tips2.length === 0) return;
|
|
5373
|
+
if (revealed < totalLength) {
|
|
5374
|
+
const delay2 = chunkBoundaries.includes(revealed) ? CHUNK_HOLD_MS : TICK_MS;
|
|
5375
|
+
const timer2 = setTimeout(() => {
|
|
5376
|
+
setRevealed((r) => {
|
|
5377
|
+
const next = r + 1;
|
|
5378
|
+
progress.revealed = next;
|
|
5379
|
+
return next;
|
|
5380
|
+
});
|
|
5381
|
+
}, delay2);
|
|
5382
|
+
return () => clearTimeout(timer2);
|
|
5383
|
+
}
|
|
5384
|
+
const timer = setTimeout(() => {
|
|
5385
|
+
setTipIndex((i) => {
|
|
5386
|
+
const next = (i + 1) % tips2.length;
|
|
5387
|
+
progress.tipIndex = next;
|
|
5388
|
+
return next;
|
|
5389
|
+
});
|
|
5390
|
+
setRevealed(0);
|
|
5391
|
+
progress.revealed = 0;
|
|
5392
|
+
}, HOLD_MS);
|
|
5393
|
+
return () => clearTimeout(timer);
|
|
5394
|
+
}, [revealed, totalLength, tips2.length]);
|
|
5395
|
+
if (!tip) return null;
|
|
5396
|
+
const titleRevealed = tip.title.slice(0, revealed);
|
|
5397
|
+
const contentRevealed = Math.max(0, revealed - tip.title.length);
|
|
5398
|
+
return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", marginBottom: 1, gap: 1, children: [
|
|
5399
|
+
/* @__PURE__ */ jsx16(TipTitle, { children: titleRevealed }),
|
|
5400
|
+
/* @__PURE__ */ jsx16(TipContent, { chunks: tip.chunks, revealed: contentRevealed })
|
|
5401
|
+
] });
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
// src/ui/App.tsx
|
|
5405
|
+
import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
5406
|
+
function App() {
|
|
5407
|
+
const {
|
|
5408
|
+
phase,
|
|
5409
|
+
error,
|
|
5410
|
+
homeScreen,
|
|
5411
|
+
currentStepIndex,
|
|
5412
|
+
steps,
|
|
5413
|
+
inputReq,
|
|
5414
|
+
user,
|
|
5415
|
+
workflow
|
|
5416
|
+
} = useWizard();
|
|
5417
|
+
const { exit } = useApp();
|
|
5418
|
+
const { columns, rows } = useWindowSize7();
|
|
5419
|
+
const [showLogs, setShowLogs] = useState9(false);
|
|
5420
|
+
const finished = phase === "done" || phase === "error";
|
|
5421
|
+
const currentStep = steps[currentStepIndex];
|
|
5422
|
+
const stepDef = workflow ? getWorkflow(workflow.id)?.steps.find((s) => s.id === currentStep?.id) : void 0;
|
|
5423
|
+
const tipIds = stepDef?.tips ?? [];
|
|
5424
|
+
const stepHasTips = tipIds.length > 0;
|
|
5425
|
+
const isAwaitingUserInput = phase === "awaitingInput" && !!inputReq;
|
|
5426
|
+
const isCommandApprovalPrompt = isAwaitingUserInput && inputReq?.promptType === "commandApproval";
|
|
5427
|
+
const showTips = stepHasTips && (phase === "running" || isCommandApprovalPrompt);
|
|
5428
|
+
const showNoticesInMain = !stepHasTips;
|
|
5429
|
+
const showNotices = !isAwaitingUserInput;
|
|
5430
|
+
useInput6(
|
|
5431
|
+
(_input, key) => {
|
|
5432
|
+
if (key.return) {
|
|
5433
|
+
exit();
|
|
5434
|
+
}
|
|
5435
|
+
},
|
|
5436
|
+
{ isActive: finished }
|
|
5437
|
+
);
|
|
5438
|
+
useInput6((_input, key) => {
|
|
5439
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
5440
|
+
if (key.tab) {
|
|
5441
|
+
setShowLogs(!showLogs);
|
|
5442
|
+
track("AI Wizard Interaction", {
|
|
5443
|
+
context: "global",
|
|
5444
|
+
key: "tab",
|
|
5445
|
+
currentStep: currentStep?.id
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
});
|
|
5449
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
5450
|
+
useInput6((_input, key) => {
|
|
5451
|
+
if (escOwnedElsewhere) return;
|
|
5452
|
+
if (key.escape) {
|
|
5453
|
+
track("AI Wizard Interaction", {
|
|
5454
|
+
context: "global",
|
|
5455
|
+
key: "esc",
|
|
5456
|
+
currentStep: currentStep?.id ?? phase
|
|
5457
|
+
});
|
|
5458
|
+
exit();
|
|
5459
|
+
}
|
|
5460
|
+
});
|
|
5461
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
5462
|
+
const flexDirection = columns > 90 ? "row" : "column";
|
|
5463
|
+
const showSidebar = flexDirection === "row";
|
|
5464
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
5465
|
+
return (
|
|
5466
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
5467
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
5468
|
+
flicker and leftover rows. */
|
|
5469
|
+
/* @__PURE__ */ jsxs17(
|
|
5470
|
+
Box19,
|
|
5471
|
+
{
|
|
5472
|
+
backgroundColor: COLORS.bg.main,
|
|
5473
|
+
flexDirection: "row",
|
|
5474
|
+
width: columns,
|
|
5475
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
5476
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
5477
|
+
children: [
|
|
5478
|
+
mainWindowVisible && /* @__PURE__ */ jsxs17(
|
|
5479
|
+
Box19,
|
|
5480
|
+
{
|
|
5481
|
+
flexDirection,
|
|
5482
|
+
width: "100%",
|
|
5483
|
+
maxHeight: rows,
|
|
5484
|
+
justifyContent: "space-between",
|
|
5485
|
+
children: [
|
|
5486
|
+
showLogs ? /* @__PURE__ */ jsx17(Logs, {}) : /* @__PURE__ */ jsxs17(
|
|
5487
|
+
Box19,
|
|
5488
|
+
{
|
|
5489
|
+
flexDirection: "column",
|
|
5490
|
+
paddingX: 4,
|
|
5491
|
+
paddingY: 2,
|
|
5492
|
+
width: showSidebar ? 70 : "100%",
|
|
5493
|
+
flexGrow: 1,
|
|
5494
|
+
gap: 1,
|
|
5495
|
+
children: [
|
|
5496
|
+
/* @__PURE__ */ jsxs17(Box19, { flexGrow: 2, flexDirection: "column", children: [
|
|
5497
|
+
phase === "preflight" && !user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5498
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5499
|
+
" Signing in to Algolia"
|
|
5500
|
+
] }) }),
|
|
5501
|
+
phase === "preflight" && user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5502
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5503
|
+
" Getting things ready"
|
|
5504
|
+
] }) }),
|
|
5505
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", marginBottom: 1, children: [
|
|
5506
|
+
/* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5507
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5508
|
+
" Signing in to Algolia"
|
|
5509
|
+
] }),
|
|
5510
|
+
/* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
5511
|
+
] }),
|
|
5512
|
+
/* @__PURE__ */ jsx17(CliOutput, {}),
|
|
5513
|
+
showTips && currentStep && /* @__PURE__ */ jsx17(Tips, { stepId: currentStep.id, ids: tipIds }),
|
|
5514
|
+
showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, { showAll: true, border: false }),
|
|
5515
|
+
!showTips && /* @__PURE__ */ jsx17(PromptInput, {}),
|
|
5516
|
+
phase === "error" && error && /* @__PURE__ */ jsx17(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.status.error, children: [
|
|
5517
|
+
"\u2716 ",
|
|
5518
|
+
error
|
|
5519
|
+
] }) })
|
|
5520
|
+
] }),
|
|
5521
|
+
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, {}),
|
|
5522
|
+
showTips && /* @__PURE__ */ jsx17(PromptInput, {})
|
|
5523
|
+
]
|
|
5524
|
+
}
|
|
5525
|
+
),
|
|
5526
|
+
showSidebar ? /* @__PURE__ */ jsx17(Sidebar, {}) : /* @__PURE__ */ jsx17(Ribbon, {})
|
|
5527
|
+
]
|
|
5528
|
+
}
|
|
5529
|
+
),
|
|
5530
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx17(LearnMore, {}) : /* @__PURE__ */ jsx17(Welcome, {}))
|
|
5531
|
+
]
|
|
5532
|
+
}
|
|
5533
|
+
)
|
|
5534
|
+
);
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
// src/lib/envAppId.ts
|
|
5538
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
5539
|
+
import { join as join12 } from "node:path";
|
|
5540
|
+
var ENV_FILES = [".env", ".env.local"];
|
|
5541
|
+
var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
|
|
5542
|
+
async function findEnvApplicationId(root = process.cwd()) {
|
|
5543
|
+
for (const file of ENV_FILES) {
|
|
5544
|
+
let content;
|
|
5545
|
+
try {
|
|
5546
|
+
content = await readFile8(join12(root, file), "utf8");
|
|
5547
|
+
} catch (err) {
|
|
5548
|
+
if (err.code !== "ENOENT") {
|
|
5549
|
+
logger.warn(
|
|
5550
|
+
{ file, err },
|
|
5551
|
+
"could not read env file for an application id"
|
|
5152
5552
|
);
|
|
5153
5553
|
}
|
|
5154
|
-
|
|
5554
|
+
continue;
|
|
5155
5555
|
}
|
|
5156
|
-
|
|
5157
|
-
const
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
if (ignoreStatus === "covered") {
|
|
5162
|
-
summaries.push(
|
|
5163
|
-
`\u26A0\uFE0F ${searchConfigFile} is gitignored, so this public, safe-to-share search config won't reach teammates or CI. Remove whatever .gitignore rule covers it.`
|
|
5164
|
-
);
|
|
5556
|
+
for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
|
|
5557
|
+
const id = readValue(raw);
|
|
5558
|
+
if (id) {
|
|
5559
|
+
logger.info({ file, name, app: id }, "found an application id in env");
|
|
5560
|
+
return { id, name, file };
|
|
5165
5561
|
}
|
|
5166
5562
|
}
|
|
5167
|
-
} else {
|
|
5168
|
-
ctx.setUserInput("implementation", "success");
|
|
5169
5563
|
}
|
|
5170
|
-
return
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
5178
|
-
} : {},
|
|
5179
|
-
...useCases.includes("search") ? {
|
|
5180
|
-
searchConfig: {
|
|
5181
|
-
filePath: searchConfigFile,
|
|
5182
|
-
vars: [
|
|
5183
|
-
{ name: SEARCH_CONFIG_APP_ID, value: appId ?? "" },
|
|
5184
|
-
{
|
|
5185
|
-
name: SEARCH_CONFIG_SEARCH_KEY,
|
|
5186
|
-
value: searchKey ?? SEARCH_KEY_PLACEHOLDER
|
|
5187
|
-
},
|
|
5188
|
-
{ name: SEARCH_CONFIG_INDEX_NAME, value: targetIndex }
|
|
5189
|
-
]
|
|
5190
|
-
}
|
|
5191
|
-
} : {}
|
|
5192
|
-
};
|
|
5564
|
+
return null;
|
|
5565
|
+
}
|
|
5566
|
+
function readValue(raw) {
|
|
5567
|
+
const trimmed = raw.trim();
|
|
5568
|
+
const quoted = trimmed.match(/^(['"])(.*)\1/);
|
|
5569
|
+
const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
|
|
5570
|
+
return value.length > 0 && !value.startsWith("<") ? value : null;
|
|
5193
5571
|
}
|
|
5194
5572
|
|
|
5195
|
-
// src/
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
};
|
|
5292
|
-
function getWorkflow(id) {
|
|
5293
|
-
return workflows[id];
|
|
5573
|
+
// src/lib/algoliaAppPicker.ts
|
|
5574
|
+
function secondaryFor(app) {
|
|
5575
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
5576
|
+
}
|
|
5577
|
+
function labelFor(app) {
|
|
5578
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
5579
|
+
}
|
|
5580
|
+
function selectAndReport(app) {
|
|
5581
|
+
useWizard.getState().pushCliOutput(
|
|
5582
|
+
"stdout",
|
|
5583
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
5584
|
+
);
|
|
5585
|
+
return selectApplication(app.id);
|
|
5586
|
+
}
|
|
5587
|
+
async function promptForApplication(leadIn = []) {
|
|
5588
|
+
const store = useWizard.getState();
|
|
5589
|
+
const apps = await listApplications();
|
|
5590
|
+
if (apps.length === 0) {
|
|
5591
|
+
throw new Error(
|
|
5592
|
+
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
5593
|
+
);
|
|
5594
|
+
}
|
|
5595
|
+
if (apps.length === 1) {
|
|
5596
|
+
const only = apps[0];
|
|
5597
|
+
logger.info(
|
|
5598
|
+
{ app: only.id },
|
|
5599
|
+
"single application on the account; selecting it"
|
|
5600
|
+
);
|
|
5601
|
+
for (const line of leadIn) store.pushCliOutput("stdout", line);
|
|
5602
|
+
return selectAndReport(only);
|
|
5603
|
+
}
|
|
5604
|
+
const messages = [
|
|
5605
|
+
...leadIn,
|
|
5606
|
+
"Which Algolia application should the wizard work in?"
|
|
5607
|
+
];
|
|
5608
|
+
for (; ; ) {
|
|
5609
|
+
const choice = await store.requestUserInput({
|
|
5610
|
+
prompt: "Select an application",
|
|
5611
|
+
promptType: "multipleChoice",
|
|
5612
|
+
options: apps.map(labelFor),
|
|
5613
|
+
secondary: apps.map(secondaryFor),
|
|
5614
|
+
messages
|
|
5615
|
+
});
|
|
5616
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
5617
|
+
if (!chosen) {
|
|
5618
|
+
throw new Error("Application picker received an unexpected selection");
|
|
5619
|
+
}
|
|
5620
|
+
try {
|
|
5621
|
+
return await selectAndReport(chosen);
|
|
5622
|
+
} catch (err) {
|
|
5623
|
+
logger.warn(
|
|
5624
|
+
{ app: chosen.id, err: err.message },
|
|
5625
|
+
"application select failed; re-prompting"
|
|
5626
|
+
);
|
|
5627
|
+
messages.push(
|
|
5628
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
5629
|
+
);
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
async function confirmEnvApplication(env, current) {
|
|
5634
|
+
const useEnv = `Use ${env.id} (from ${env.file})`;
|
|
5635
|
+
const choice = await useWizard.getState().requestUserInput({
|
|
5636
|
+
prompt: "Select an application",
|
|
5637
|
+
promptType: "multipleChoice",
|
|
5638
|
+
options: [
|
|
5639
|
+
useEnv,
|
|
5640
|
+
current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
|
|
5641
|
+
],
|
|
5642
|
+
messages: [
|
|
5643
|
+
`${env.file} already sets ${env.name}=${env.id}.`,
|
|
5644
|
+
"Which Algolia application should the wizard work in?"
|
|
5645
|
+
]
|
|
5646
|
+
});
|
|
5647
|
+
return choice === useEnv;
|
|
5648
|
+
}
|
|
5649
|
+
async function selectEnvApplication(env) {
|
|
5650
|
+
try {
|
|
5651
|
+
return await selectAndReport({ id: env.id, name: "" });
|
|
5652
|
+
} catch (err) {
|
|
5653
|
+
logger.warn(
|
|
5654
|
+
{ app: env.id, err: err.message },
|
|
5655
|
+
"could not select the application named in env; falling back to the picker"
|
|
5656
|
+
);
|
|
5657
|
+
return promptForApplication([
|
|
5658
|
+
`Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
|
|
5659
|
+
]);
|
|
5660
|
+
}
|
|
5661
|
+
}
|
|
5662
|
+
async function ensureApplication() {
|
|
5663
|
+
const current = await currentApplication();
|
|
5664
|
+
const env = await findEnvApplicationId();
|
|
5665
|
+
if (env && env.id !== current?.id && await confirmEnvApplication(env, current)) {
|
|
5666
|
+
return selectEnvApplication(env);
|
|
5667
|
+
}
|
|
5668
|
+
return current ?? await promptForApplication();
|
|
5294
5669
|
}
|
|
5295
5670
|
|
|
5296
5671
|
// src/lib/seed.ts
|
|
@@ -5337,14 +5712,10 @@ var confirmFramework2 = {
|
|
|
5337
5712
|
var search = {
|
|
5338
5713
|
summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
|
|
5339
5714
|
ingestionSource: "generated",
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
{ name: "ALGOLIA_SEARCH_API_KEY", value: "seedsearchkey" },
|
|
5345
|
-
{ name: "ALGOLIA_INDEX_NAME", value: "wizard_seed_products" }
|
|
5346
|
-
]
|
|
5347
|
-
}
|
|
5715
|
+
searchEnvVars: [
|
|
5716
|
+
{ name: "NEXT_PUBLIC_ALGOLIA_APP_ID", value: "SEEDAPPID" },
|
|
5717
|
+
{ name: "NEXT_PUBLIC_ALGOLIA_SEARCH_KEY", value: "seedsearchkey" }
|
|
5718
|
+
]
|
|
5348
5719
|
};
|
|
5349
5720
|
var review = {
|
|
5350
5721
|
summaryPoints: [
|