@algolia/wizard 0.34.0-rc.125.245 → 0.35.0-rc.125.251
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 +1597 -1339
- package/package.json +1 -1
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 z29 } 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) {
|
|
@@ -2760,8 +1697,8 @@ async function hasSymlinkParent(ctx, target) {
|
|
|
2760
1697
|
function listFilesTool(ctx) {
|
|
2761
1698
|
return tool({
|
|
2762
1699
|
description: 'List files in a directory (default: the current working directory). Pass path to list a subdirectory directly \u2014 e.g. "packages/api" \u2014 without first changeDirectory-ing into it.',
|
|
2763
|
-
inputSchema:
|
|
2764
|
-
path:
|
|
1700
|
+
inputSchema: z5.object({
|
|
1701
|
+
path: z5.string().optional().describe("Directory to list, relative to cwd (default: cwd)")
|
|
2765
1702
|
}),
|
|
2766
1703
|
execute: async ({ path = "." }) => {
|
|
2767
1704
|
logger.info({ path }, "called listFiles tool");
|
|
@@ -2782,13 +1719,13 @@ function listFilesTool(ctx) {
|
|
|
2782
1719
|
|
|
2783
1720
|
// src/lib/tools/changeDirectory.ts
|
|
2784
1721
|
import { tool as tool2 } from "ai";
|
|
2785
|
-
import
|
|
1722
|
+
import z6 from "zod";
|
|
2786
1723
|
import { stat } from "node:fs/promises";
|
|
2787
1724
|
function changeDirectoryTool(ctx) {
|
|
2788
1725
|
return tool2({
|
|
2789
1726
|
description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
|
|
2790
|
-
inputSchema:
|
|
2791
|
-
path:
|
|
1727
|
+
inputSchema: z6.object({
|
|
1728
|
+
path: z6.string().describe("Directory to change into")
|
|
2792
1729
|
}),
|
|
2793
1730
|
execute: async ({ path }) => {
|
|
2794
1731
|
logger.info({ path }, "called changeDirectory tool");
|
|
@@ -2810,13 +1747,13 @@ function changeDirectoryTool(ctx) {
|
|
|
2810
1747
|
|
|
2811
1748
|
// src/lib/tools/reportStatus.ts
|
|
2812
1749
|
import { tool as tool3 } from "ai";
|
|
2813
|
-
import
|
|
1750
|
+
import z7 from "zod";
|
|
2814
1751
|
function reportStatusTool(output) {
|
|
2815
1752
|
return tool3({
|
|
2816
1753
|
description: "Report the status of your execution. Return a reason in case of failure.",
|
|
2817
|
-
inputSchema:
|
|
2818
|
-
status:
|
|
2819
|
-
reason:
|
|
1754
|
+
inputSchema: z7.object({
|
|
1755
|
+
status: z7.enum(["success", "fail"]),
|
|
1756
|
+
reason: z7.string().optional(),
|
|
2820
1757
|
output
|
|
2821
1758
|
}),
|
|
2822
1759
|
execute: async ({ status, reason, output: output2 }) => {
|
|
@@ -2828,8 +1765,8 @@ function reportStatusTool(output) {
|
|
|
2828
1765
|
|
|
2829
1766
|
// src/lib/tools/readFile.ts
|
|
2830
1767
|
import { tool as tool4 } from "ai";
|
|
2831
|
-
import
|
|
2832
|
-
import { readFile as
|
|
1768
|
+
import z8 from "zod";
|
|
1769
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
2833
1770
|
|
|
2834
1771
|
// src/lib/tools/env.ts
|
|
2835
1772
|
import { basename } from "node:path";
|
|
@@ -2856,8 +1793,8 @@ function redactEnvValues(content) {
|
|
|
2856
1793
|
function readFileTool(ctx) {
|
|
2857
1794
|
return tool4({
|
|
2858
1795
|
description: "Read the contents of a file at the given path",
|
|
2859
|
-
inputSchema:
|
|
2860
|
-
filePath:
|
|
1796
|
+
inputSchema: z8.object({
|
|
1797
|
+
filePath: z8.string().describe("Path to the file to read")
|
|
2861
1798
|
}),
|
|
2862
1799
|
execute: async ({ filePath }) => {
|
|
2863
1800
|
if (++ctx.counts.read > ctx.limits.read) {
|
|
@@ -2867,7 +1804,7 @@ function readFileTool(ctx) {
|
|
|
2867
1804
|
const resolved2 = resolveInRoot(ctx, filePath);
|
|
2868
1805
|
if (!resolved2.ok) return resolved2.error;
|
|
2869
1806
|
try {
|
|
2870
|
-
const content = await
|
|
1807
|
+
const content = await readFile3(resolved2.target, "utf8");
|
|
2871
1808
|
return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
|
|
2872
1809
|
} catch (err) {
|
|
2873
1810
|
return `Error reading ${filePath}: ${err.message}`;
|
|
@@ -2878,15 +1815,15 @@ function readFileTool(ctx) {
|
|
|
2878
1815
|
|
|
2879
1816
|
// src/lib/tools/writeFile.ts
|
|
2880
1817
|
import { tool as tool5 } from "ai";
|
|
2881
|
-
import
|
|
1818
|
+
import z9 from "zod";
|
|
2882
1819
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2883
|
-
import { dirname as
|
|
1820
|
+
import { dirname as dirname3 } from "node:path";
|
|
2884
1821
|
function writeFileTool(ctx) {
|
|
2885
1822
|
return tool5({
|
|
2886
1823
|
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:
|
|
1824
|
+
inputSchema: z9.object({
|
|
1825
|
+
filePath: z9.string().describe("Path to the file to write"),
|
|
1826
|
+
content: z9.string().describe("Content to write to the file")
|
|
2890
1827
|
}),
|
|
2891
1828
|
execute: async ({ filePath, content }) => {
|
|
2892
1829
|
logger.info({ filePath }, "called writeFile tool");
|
|
@@ -2899,7 +1836,7 @@ function writeFileTool(ctx) {
|
|
|
2899
1836
|
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
2900
1837
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
2901
1838
|
}
|
|
2902
|
-
await mkdir3(
|
|
1839
|
+
await mkdir3(dirname3(resolved2.target), { recursive: true });
|
|
2903
1840
|
await writeFile3(resolved2.target, content, "utf8");
|
|
2904
1841
|
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
2905
1842
|
return `Wrote to ${filePath}`;
|
|
@@ -2913,8 +1850,70 @@ function writeFileTool(ctx) {
|
|
|
2913
1850
|
// src/lib/tools/writeAlgoliaCredentials.ts
|
|
2914
1851
|
import { tool as tool6 } from "ai";
|
|
2915
1852
|
import z13 from "zod";
|
|
2916
|
-
import { mkdir as mkdir4, readFile as
|
|
2917
|
-
import { dirname as
|
|
1853
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
1854
|
+
import { dirname as dirname4, relative as relative3 } from "node:path";
|
|
1855
|
+
|
|
1856
|
+
// src/lib/algoliaApp.ts
|
|
1857
|
+
import { z as z10 } from "zod";
|
|
1858
|
+
var applicationSchema = z10.object({
|
|
1859
|
+
id: z10.string().min(1),
|
|
1860
|
+
name: z10.string().default(""),
|
|
1861
|
+
plan: z10.string().optional()
|
|
1862
|
+
});
|
|
1863
|
+
var listSchema = z10.array(
|
|
1864
|
+
z10.object({
|
|
1865
|
+
id: z10.string().min(1),
|
|
1866
|
+
name: z10.string().default(""),
|
|
1867
|
+
plan_label: z10.string().optional()
|
|
1868
|
+
}).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
|
|
1869
|
+
);
|
|
1870
|
+
async function currentApplication() {
|
|
1871
|
+
let raw;
|
|
1872
|
+
try {
|
|
1873
|
+
raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
|
|
1874
|
+
} catch {
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
1878
|
+
return parsed.success ? parsed.data : null;
|
|
1879
|
+
}
|
|
1880
|
+
async function requireApplication() {
|
|
1881
|
+
const app = await currentApplication();
|
|
1882
|
+
if (!app) {
|
|
1883
|
+
throw new Error(
|
|
1884
|
+
"No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
return app;
|
|
1888
|
+
}
|
|
1889
|
+
async function listApplications() {
|
|
1890
|
+
const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
|
|
1891
|
+
const parsed = listSchema.safeParse(parseJson(raw));
|
|
1892
|
+
if (!parsed.success) {
|
|
1893
|
+
throw new Error("Could not read the list of Algolia applications.");
|
|
1894
|
+
}
|
|
1895
|
+
return parsed.data;
|
|
1896
|
+
}
|
|
1897
|
+
async function selectApplication(id) {
|
|
1898
|
+
const raw = await runAlgoliaCli(
|
|
1899
|
+
["application", "select", "--non-interactive", "--app-id", id],
|
|
1900
|
+
{ onOutput: stderrSink }
|
|
1901
|
+
);
|
|
1902
|
+
const parsed = applicationSchema.safeParse(parseJson(raw));
|
|
1903
|
+
if (!parsed.success) {
|
|
1904
|
+
throw new Error(
|
|
1905
|
+
`Selected application ${id}, but the Algolia CLI returned an unreadable result.`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
return parsed.data;
|
|
1909
|
+
}
|
|
1910
|
+
function parseJson(text) {
|
|
1911
|
+
try {
|
|
1912
|
+
return JSON.parse(text);
|
|
1913
|
+
} catch {
|
|
1914
|
+
return void 0;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
2918
1917
|
|
|
2919
1918
|
// src/lib/algoliaApiKey.ts
|
|
2920
1919
|
import { z as z12 } from "zod";
|
|
@@ -3097,8 +2096,8 @@ async function resolveSearchOnlyKey(index, appId) {
|
|
|
3097
2096
|
|
|
3098
2097
|
// src/lib/gitignore.ts
|
|
3099
2098
|
import { execFile } from "node:child_process";
|
|
3100
|
-
import { lstat as lstat2, readFile as
|
|
3101
|
-
import { join as
|
|
2099
|
+
import { lstat as lstat2, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2100
|
+
import { join as join6, relative as relative2 } from "node:path";
|
|
3102
2101
|
var GIT_ENV_OVERRIDES = [
|
|
3103
2102
|
"GIT_DIR",
|
|
3104
2103
|
"GIT_WORK_TREE",
|
|
@@ -3150,7 +2149,7 @@ async function ensureGitIgnored(root, target) {
|
|
|
3150
2149
|
if (ignoredByRule === void 0) return "unknown";
|
|
3151
2150
|
if (ignoredByRule) return tracked ? "tracked" : "covered";
|
|
3152
2151
|
const pattern = relative2(root, target);
|
|
3153
|
-
const gitIgnore =
|
|
2152
|
+
const gitIgnore = join6(root, ".gitignore");
|
|
3154
2153
|
try {
|
|
3155
2154
|
const link = await lstat2(gitIgnore).catch(() => null);
|
|
3156
2155
|
if (link?.isSymbolicLink()) {
|
|
@@ -3160,7 +2159,7 @@ async function ensureGitIgnored(root, target) {
|
|
|
3160
2159
|
);
|
|
3161
2160
|
return "unknown";
|
|
3162
2161
|
}
|
|
3163
|
-
const existing = link ? await
|
|
2162
|
+
const existing = link ? await readFile4(gitIgnore, "utf8") : "";
|
|
3164
2163
|
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
3165
2164
|
await writeFile4(gitIgnore, `${existing}${prefix}${pattern}
|
|
3166
2165
|
`, "utf8");
|
|
@@ -3229,7 +2228,7 @@ function writeCredentialsTool(ctx) {
|
|
|
3229
2228
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
3230
2229
|
}
|
|
3231
2230
|
try {
|
|
3232
|
-
existing = await
|
|
2231
|
+
existing = await readFile5(resolved2.target, "utf8");
|
|
3233
2232
|
} catch (err) {
|
|
3234
2233
|
if (err.code !== "ENOENT") throw err;
|
|
3235
2234
|
}
|
|
@@ -3279,7 +2278,7 @@ function writeCredentialsTool(ctx) {
|
|
|
3279
2278
|
(content, [name, value]) => upsertEnv(content, name, value),
|
|
3280
2279
|
existing
|
|
3281
2280
|
);
|
|
3282
|
-
await mkdir4(
|
|
2281
|
+
await mkdir4(dirname4(resolved2.target), { recursive: true });
|
|
3283
2282
|
await writeFile5(resolved2.target, updated, "utf8");
|
|
3284
2283
|
const sentences = [
|
|
3285
2284
|
`Wrote ${[...credentials.map(([name]) => name), INDEX_NAME_VAR].join(", ")} to ${filePath}.`
|
|
@@ -3319,8 +2318,8 @@ async function gitIgnoreOutcome(ctx, target) {
|
|
|
3319
2318
|
// src/lib/tools/searchFiles.ts
|
|
3320
2319
|
import { tool as tool7 } from "ai";
|
|
3321
2320
|
import z14 from "zod";
|
|
3322
|
-
import { readdir as readdir2, readFile as
|
|
3323
|
-
import { join as
|
|
2321
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2322
|
+
import { join as join7 } from "node:path";
|
|
3324
2323
|
var MAX_QUERY_LENGTH = 1e3;
|
|
3325
2324
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3326
2325
|
"node_modules",
|
|
@@ -3335,7 +2334,7 @@ async function walkFiles(dir) {
|
|
|
3335
2334
|
const out = [];
|
|
3336
2335
|
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
3337
2336
|
if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
|
|
3338
|
-
const full =
|
|
2337
|
+
const full = join7(dir, e.name);
|
|
3339
2338
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
3340
2339
|
else if (e.isFile()) out.push(full);
|
|
3341
2340
|
}
|
|
@@ -3368,7 +2367,7 @@ function searchFilesTool(ctx) {
|
|
|
3368
2367
|
for (const file of await walkFiles(resolved2.target)) {
|
|
3369
2368
|
let content;
|
|
3370
2369
|
try {
|
|
3371
|
-
content = await
|
|
2370
|
+
content = await readFile6(file, "utf8");
|
|
3372
2371
|
} catch {
|
|
3373
2372
|
continue;
|
|
3374
2373
|
}
|
|
@@ -3389,7 +2388,8 @@ function searchFilesTool(ctx) {
|
|
|
3389
2388
|
}
|
|
3390
2389
|
|
|
3391
2390
|
// src/lib/tools/runShell.ts
|
|
3392
|
-
import { tool as tool8 } from "ai";
|
|
2391
|
+
import { tool as tool8, generateText, Output } from "ai";
|
|
2392
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3393
2393
|
import z15 from "zod";
|
|
3394
2394
|
import { relative as relative4 } from "node:path";
|
|
3395
2395
|
|
|
@@ -3498,8 +2498,10 @@ function runShell(command, opts) {
|
|
|
3498
2498
|
// src/lib/tools/runShell.ts
|
|
3499
2499
|
function storeApproval(root) {
|
|
3500
2500
|
return async (req) => {
|
|
2501
|
+
const store = useWizard.getState();
|
|
2502
|
+
if (store.isCommandApproved(req.command, req.cwd)) return "approve";
|
|
3501
2503
|
const rel = relative4(root, req.cwd);
|
|
3502
|
-
const answer = await
|
|
2504
|
+
const answer = await store.requestUserInput({
|
|
3503
2505
|
prompt: "Run this command?",
|
|
3504
2506
|
promptType: "commandApproval",
|
|
3505
2507
|
options: [],
|
|
@@ -3508,20 +2510,195 @@ function storeApproval(root) {
|
|
|
3508
2510
|
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
3509
2511
|
}
|
|
3510
2512
|
});
|
|
3511
|
-
|
|
2513
|
+
if (answer !== "approve") return "reject";
|
|
2514
|
+
store.approveCommand(req.command, req.cwd);
|
|
2515
|
+
return "approve";
|
|
3512
2516
|
};
|
|
3513
2517
|
}
|
|
3514
2518
|
var EXPLORATORY_COMMANDS = /* @__PURE__ */ new Set(["ls", "find", "tree", "dir"]);
|
|
2519
|
+
function commandSegments(command) {
|
|
2520
|
+
return command.split(/&&|;|\|/).map((segment) => segment.trim());
|
|
2521
|
+
}
|
|
3515
2522
|
function isExploratoryCommand(command) {
|
|
3516
|
-
return command
|
|
2523
|
+
return commandSegments(command).map((segment) => segment.split(/\s+/)[0]).some((word) => word !== void 0 && EXPLORATORY_COMMANDS.has(word));
|
|
2524
|
+
}
|
|
2525
|
+
var READ_ONLY_BINARIES = /* @__PURE__ */ new Set([
|
|
2526
|
+
"cat",
|
|
2527
|
+
"head",
|
|
2528
|
+
"tail",
|
|
2529
|
+
"wc",
|
|
2530
|
+
"pwd",
|
|
2531
|
+
"echo",
|
|
2532
|
+
"date",
|
|
2533
|
+
"whoami",
|
|
2534
|
+
"hostname",
|
|
2535
|
+
"uname",
|
|
2536
|
+
"which",
|
|
2537
|
+
"file",
|
|
2538
|
+
"stat",
|
|
2539
|
+
"grep",
|
|
2540
|
+
"egrep",
|
|
2541
|
+
"fgrep",
|
|
2542
|
+
"rg",
|
|
2543
|
+
"diff"
|
|
2544
|
+
]);
|
|
2545
|
+
var READ_ONLY_NO_ARGS_BINARIES = /* @__PURE__ */ new Set(["env", "printenv"]);
|
|
2546
|
+
var GIT_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
2547
|
+
"status",
|
|
2548
|
+
"log",
|
|
2549
|
+
"diff",
|
|
2550
|
+
"show",
|
|
2551
|
+
"describe",
|
|
2552
|
+
"blame",
|
|
2553
|
+
"ls-files",
|
|
2554
|
+
"rev-parse",
|
|
2555
|
+
"cat-file",
|
|
2556
|
+
"shortlog",
|
|
2557
|
+
"ls-remote"
|
|
2558
|
+
]);
|
|
2559
|
+
var VERSION_CHECK_BINARIES = /* @__PURE__ */ new Set([
|
|
2560
|
+
"node",
|
|
2561
|
+
"tsc",
|
|
2562
|
+
"npm",
|
|
2563
|
+
"pnpm",
|
|
2564
|
+
"yarn",
|
|
2565
|
+
"python",
|
|
2566
|
+
"python3",
|
|
2567
|
+
"ruby",
|
|
2568
|
+
"go",
|
|
2569
|
+
"cargo",
|
|
2570
|
+
"rustc",
|
|
2571
|
+
"php",
|
|
2572
|
+
"composer",
|
|
2573
|
+
"java",
|
|
2574
|
+
"mvn",
|
|
2575
|
+
"gradle",
|
|
2576
|
+
"bundle",
|
|
2577
|
+
"git"
|
|
2578
|
+
]);
|
|
2579
|
+
var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V"]);
|
|
2580
|
+
var FD_DUP_REDIRECT = /\d*>&\d+/g;
|
|
2581
|
+
function stripQuoted(segment) {
|
|
2582
|
+
let result = "";
|
|
2583
|
+
let quote = null;
|
|
2584
|
+
let i = 0;
|
|
2585
|
+
while (i < segment.length) {
|
|
2586
|
+
const char = segment[i];
|
|
2587
|
+
if (quote === "'") {
|
|
2588
|
+
if (char === "'") quote = null;
|
|
2589
|
+
i++;
|
|
2590
|
+
} else if (char === "\\") {
|
|
2591
|
+
i += 2;
|
|
2592
|
+
} else if (quote) {
|
|
2593
|
+
if (char === quote) quote = null;
|
|
2594
|
+
i++;
|
|
2595
|
+
} else if (char === '"' || char === "'") {
|
|
2596
|
+
quote = char;
|
|
2597
|
+
i++;
|
|
2598
|
+
} else {
|
|
2599
|
+
result += char;
|
|
2600
|
+
i++;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
return quote === null ? result : segment;
|
|
3517
2604
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
return
|
|
2605
|
+
function hasFileRedirect(segment) {
|
|
2606
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes(">");
|
|
2607
|
+
}
|
|
2608
|
+
function hasShellInjectionRisk(segment) {
|
|
2609
|
+
if (segment.includes("$(") || segment.includes("<(") || segment.includes("`")) {
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2612
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes("&");
|
|
2613
|
+
}
|
|
2614
|
+
function fastPathSegmentSafety(segment) {
|
|
2615
|
+
if (!segment) return true;
|
|
2616
|
+
if (hasFileRedirect(segment)) return false;
|
|
2617
|
+
if (hasShellInjectionRisk(segment)) return false;
|
|
2618
|
+
const [cmd0, ...rest] = segment.split(/\s+/);
|
|
2619
|
+
if (cmd0 === void 0) return true;
|
|
2620
|
+
if (VERSION_CHECK_BINARIES.has(cmd0) && rest.length === 1 && VERSION_FLAGS.has(rest[0])) {
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
if (READ_ONLY_BINARIES.has(cmd0)) return true;
|
|
2624
|
+
if (READ_ONLY_NO_ARGS_BINARIES.has(cmd0)) {
|
|
2625
|
+
return rest.length === 0 ? true : void 0;
|
|
2626
|
+
}
|
|
2627
|
+
if (cmd0 === "git") {
|
|
2628
|
+
return GIT_READ_ONLY_SUBCOMMANDS.has(rest[0] ?? "") ? true : void 0;
|
|
2629
|
+
}
|
|
2630
|
+
return void 0;
|
|
2631
|
+
}
|
|
2632
|
+
function fastPathSafety(command) {
|
|
2633
|
+
const results = commandSegments(command).map(fastPathSegmentSafety);
|
|
2634
|
+
if (results.some((r) => r === false)) return false;
|
|
2635
|
+
if (results.every((r) => r === true)) return true;
|
|
2636
|
+
return void 0;
|
|
2637
|
+
}
|
|
2638
|
+
var CLASSIFIER_MODEL = "claude-haiku-4-5";
|
|
2639
|
+
var commandSafetySchema = z15.object({
|
|
2640
|
+
safe: z15.boolean(),
|
|
2641
|
+
reason: z15.string().describe("One short sentence explaining the verdict.")
|
|
2642
|
+
});
|
|
2643
|
+
function defaultCreateModel() {
|
|
2644
|
+
const token = getAuthToken();
|
|
2645
|
+
if (!token) {
|
|
2646
|
+
throw new Error("Not authenticated: no user token available");
|
|
2647
|
+
}
|
|
2648
|
+
return createAnthropic({
|
|
2649
|
+
apiKey: token,
|
|
2650
|
+
baseURL: PROXY_BASE_URL,
|
|
2651
|
+
fetch: proxyFetch
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
function approvedCommandHistory(approvedCommands) {
|
|
2655
|
+
return Array.from(approvedCommands).map((entry) => {
|
|
2656
|
+
const sep2 = entry.indexOf("\0");
|
|
2657
|
+
return { cwd: entry.slice(0, sep2), command: entry.slice(sep2 + 1) };
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
async function classifyCommandSafety(createModel, command, cwd, explanation, approvedHistory) {
|
|
2661
|
+
try {
|
|
2662
|
+
const anthropic = createModel();
|
|
2663
|
+
const { output } = await generateText({
|
|
2664
|
+
model: anthropic(CLASSIFIER_MODEL),
|
|
2665
|
+
temperature: 0,
|
|
2666
|
+
output: Output.object({ schema: commandSafetySchema }),
|
|
2667
|
+
prompt: [
|
|
2668
|
+
"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.",
|
|
2669
|
+
"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.",
|
|
2670
|
+
"Three broad categories are safe, and most commands you will see fall into one of them:",
|
|
2671
|
+
"(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.",
|
|
2672
|
+
'(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.',
|
|
2673
|
+
"(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.",
|
|
2674
|
+
"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.",
|
|
2675
|
+
"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.",
|
|
2676
|
+
...approvedHistory.length > 0 ? [
|
|
2677
|
+
"The user has already explicitly approved these exact commands earlier in this session (working directory in parentheses, then the command):",
|
|
2678
|
+
approvedHistory.map((h) => `- (${h.cwd}) ${h.command}`).join("\n"),
|
|
2679
|
+
"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`)."
|
|
2680
|
+
] : [],
|
|
2681
|
+
`Command: ${command}`,
|
|
2682
|
+
`Working directory: ${cwd}`,
|
|
2683
|
+
`Stated purpose: ${explanation}`
|
|
2684
|
+
].join("\n")
|
|
2685
|
+
});
|
|
2686
|
+
if (!output.safe) {
|
|
2687
|
+
logger.info(
|
|
2688
|
+
{ command, reason: output.reason },
|
|
2689
|
+
"runShell: classifier judged command unsafe, requiring approval"
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
return output.safe;
|
|
2693
|
+
} catch (err) {
|
|
2694
|
+
logger.warn(
|
|
2695
|
+
{ err, command },
|
|
2696
|
+
"runShell: command safety classifier failed, requiring approval"
|
|
2697
|
+
);
|
|
2698
|
+
return false;
|
|
3524
2699
|
}
|
|
2700
|
+
}
|
|
2701
|
+
async function runAndRecord(ctx, command, cwd) {
|
|
3525
2702
|
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
3526
2703
|
const env = await ctx.shell.env().catch((err) => {
|
|
3527
2704
|
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
@@ -3557,9 +2734,18 @@ async function approveAndRun(ctx, command, cwd, explanation) {
|
|
|
3557
2734
|
output: run2.output
|
|
3558
2735
|
};
|
|
3559
2736
|
}
|
|
3560
|
-
function
|
|
2737
|
+
async function approveAndRun(ctx, command, cwd, explanation) {
|
|
2738
|
+
const decision = await ctx.shell.approve({ command, cwd, explanation });
|
|
2739
|
+
if (decision === "reject") {
|
|
2740
|
+
ctx.shell.executions.push({ command, cwd, approved: false });
|
|
2741
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2742
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2743
|
+
}
|
|
2744
|
+
return runAndRecord(ctx, command, cwd);
|
|
2745
|
+
}
|
|
2746
|
+
function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
3561
2747
|
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.
|
|
2748
|
+
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
2749
|
inputSchema: z15.object({
|
|
3564
2750
|
command: z15.string().describe(
|
|
3565
2751
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
@@ -3578,9 +2764,29 @@ function runShellTool(ctx) {
|
|
|
3578
2764
|
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
3579
2765
|
if (!resolved2.ok) return resolved2.error;
|
|
3580
2766
|
if (isExploratoryCommand(command)) {
|
|
3581
|
-
return "Refused: use listFiles or searchFiles to
|
|
2767
|
+
return "Refused: use listFiles or searchFiles to find files, and readFile to read one, instead of ls/find/tree/dir.";
|
|
3582
2768
|
}
|
|
3583
2769
|
logger.info({ command, cwd: resolved2.target }, "called runShell tool");
|
|
2770
|
+
const fast = fastPathSafety(command);
|
|
2771
|
+
let isSafe = fast;
|
|
2772
|
+
if (isSafe === void 0) {
|
|
2773
|
+
const store = useWizard.getState();
|
|
2774
|
+
isSafe = store.isCommandApproved(command, resolved2.target);
|
|
2775
|
+
if (!isSafe) {
|
|
2776
|
+
isSafe = await classifyCommandSafety(
|
|
2777
|
+
createModel,
|
|
2778
|
+
command,
|
|
2779
|
+
resolved2.target,
|
|
2780
|
+
explanation,
|
|
2781
|
+
approvedCommandHistory(store.approvedCommands)
|
|
2782
|
+
);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
if (isSafe) {
|
|
2786
|
+
return serializePrompt(
|
|
2787
|
+
() => runAndRecord(ctx, command, resolved2.target)
|
|
2788
|
+
);
|
|
2789
|
+
}
|
|
3584
2790
|
return serializePrompt(
|
|
3585
2791
|
() => approveAndRun(ctx, command, resolved2.target, explanation)
|
|
3586
2792
|
);
|
|
@@ -3630,29 +2836,29 @@ function reviewScriptTool(ctx) {
|
|
|
3630
2836
|
}
|
|
3631
2837
|
|
|
3632
2838
|
// src/lib/tools/generateRecord.ts
|
|
3633
|
-
import { tool as tool10, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
3634
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2839
|
+
import { tool as tool10, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
|
|
2840
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
3635
2841
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3636
2842
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
|
|
3637
|
-
import { dirname as
|
|
2843
|
+
import { dirname as dirname5 } from "node:path";
|
|
3638
2844
|
import z17 from "zod";
|
|
3639
2845
|
var DATA_DIR = ".algolia-wizard/data";
|
|
3640
2846
|
var RECORD_MODEL = "claude-haiku-4-5";
|
|
3641
2847
|
var MAX_RECORDS = 100;
|
|
3642
2848
|
var BATCH_SIZE = 10;
|
|
3643
2849
|
var MAX_BATCH_ATTEMPTS = 3;
|
|
3644
|
-
function
|
|
2850
|
+
function defaultCreateModel2() {
|
|
3645
2851
|
const token = getAuthToken();
|
|
3646
2852
|
if (!token) {
|
|
3647
2853
|
throw new Error("Not authenticated: no user token available");
|
|
3648
2854
|
}
|
|
3649
|
-
return
|
|
2855
|
+
return createAnthropic2({
|
|
3650
2856
|
apiKey: token,
|
|
3651
2857
|
baseURL: PROXY_BASE_URL,
|
|
3652
2858
|
fetch: proxyFetch
|
|
3653
2859
|
});
|
|
3654
2860
|
}
|
|
3655
|
-
function generateRecordTool(ctx, createModel =
|
|
2861
|
+
function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
3656
2862
|
return tool10({
|
|
3657
2863
|
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
2864
|
inputSchema: z17.object({
|
|
@@ -3673,9 +2879,9 @@ function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
|
3673
2879
|
let lastError;
|
|
3674
2880
|
for (let attempt = 1; attempt <= MAX_BATCH_ATTEMPTS; attempt++) {
|
|
3675
2881
|
try {
|
|
3676
|
-
const { output } = await
|
|
2882
|
+
const { output } = await generateText2({
|
|
3677
2883
|
model: anthropic(RECORD_MODEL),
|
|
3678
|
-
output:
|
|
2884
|
+
output: Output2.object({
|
|
3679
2885
|
schema: z17.object({
|
|
3680
2886
|
records: z17.array(recordSchema).length(batchCount)
|
|
3681
2887
|
})
|
|
@@ -3714,7 +2920,7 @@ function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
|
3714
2920
|
if (await hasSymlinkParent(ctx, resolved2.target)) {
|
|
3715
2921
|
return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
|
|
3716
2922
|
}
|
|
3717
|
-
await mkdir5(
|
|
2923
|
+
await mkdir5(dirname5(resolved2.target), { recursive: true });
|
|
3718
2924
|
await writeFile6(
|
|
3719
2925
|
resolved2.target,
|
|
3720
2926
|
JSON.stringify(records, null, 2),
|
|
@@ -3840,7 +3046,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3840
3046
|
if (!token) {
|
|
3841
3047
|
throw new Error("Not authenticated: no user token available");
|
|
3842
3048
|
}
|
|
3843
|
-
const anthropic =
|
|
3049
|
+
const anthropic = createAnthropic3({
|
|
3844
3050
|
apiKey: token,
|
|
3845
3051
|
baseURL: PROXY_BASE_URL,
|
|
3846
3052
|
fetch: proxyFetch
|
|
@@ -3877,7 +3083,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3877
3083
|
}
|
|
3878
3084
|
};
|
|
3879
3085
|
}),
|
|
3880
|
-
output:
|
|
3086
|
+
output: Output3.object({ schema: req.outputSchema }),
|
|
3881
3087
|
tools: createTools(toolContext, {
|
|
3882
3088
|
output: req.outputSchema,
|
|
3883
3089
|
tools: req.tools
|
|
@@ -4061,7 +3267,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
4061
3267
|
// package.json
|
|
4062
3268
|
var package_default = {
|
|
4063
3269
|
name: "@algolia/wizard",
|
|
4064
|
-
version: "0.
|
|
3270
|
+
version: "0.35.0-rc.125.251",
|
|
4065
3271
|
description: "Magically implement Algolia functionality in your codebase",
|
|
4066
3272
|
type: "module",
|
|
4067
3273
|
engines: {
|
|
@@ -4467,12 +3673,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4467
3673
|
|
|
4468
3674
|
// src/actions/implement.ts
|
|
4469
3675
|
import z28 from "zod";
|
|
4470
|
-
import {
|
|
3676
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
3677
|
+
import { join as join10, relative as relative6 } from "node:path";
|
|
4471
3678
|
|
|
4472
3679
|
// src/lib/git.ts
|
|
4473
3680
|
import { execFile as execFile2 } from "node:child_process";
|
|
4474
3681
|
import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
|
|
4475
|
-
import { basename as basename2, dirname as
|
|
3682
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
|
|
4476
3683
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
4477
3684
|
function git(args) {
|
|
4478
3685
|
return new Promise((resolve4, reject) => {
|
|
@@ -4509,13 +3716,13 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
|
4509
3716
|
} catch {
|
|
4510
3717
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4511
3718
|
}
|
|
4512
|
-
const relPath =
|
|
4513
|
-
const dest =
|
|
3719
|
+
const relPath = join8(ingestDir, basename2(source));
|
|
3720
|
+
const dest = join8(repoRoot, relPath);
|
|
4514
3721
|
if (resolve3(source) === resolve3(dest)) {
|
|
4515
3722
|
return { ok: true, relPath };
|
|
4516
3723
|
}
|
|
4517
3724
|
try {
|
|
4518
|
-
await mkdir6(
|
|
3725
|
+
await mkdir6(dirname6(dest), { recursive: true });
|
|
4519
3726
|
await copyFile(source, dest);
|
|
4520
3727
|
} catch (err) {
|
|
4521
3728
|
return {
|
|
@@ -4549,15 +3756,15 @@ function toRootRelative(p) {
|
|
|
4549
3756
|
|
|
4550
3757
|
// src/lib/algoliaDocs.ts
|
|
4551
3758
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
4552
|
-
import { dirname as
|
|
4553
|
-
import { fileURLToPath
|
|
4554
|
-
var DOCS_SUBPATH =
|
|
3759
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
3760
|
+
import { fileURLToPath } from "node:url";
|
|
3761
|
+
var DOCS_SUBPATH = join9("docs", "algolia-sdk");
|
|
4555
3762
|
function findDocsDir() {
|
|
4556
|
-
let dir =
|
|
3763
|
+
let dir = dirname7(fileURLToPath(import.meta.url));
|
|
4557
3764
|
for (; ; ) {
|
|
4558
|
-
const candidate =
|
|
3765
|
+
const candidate = join9(dir, DOCS_SUBPATH);
|
|
4559
3766
|
if (existsSync(candidate)) return candidate;
|
|
4560
|
-
const parent =
|
|
3767
|
+
const parent = dirname7(dir);
|
|
4561
3768
|
if (parent === dir) return void 0;
|
|
4562
3769
|
dir = parent;
|
|
4563
3770
|
}
|
|
@@ -4578,7 +3785,7 @@ function loadAlgoliaDoc(language) {
|
|
|
4578
3785
|
);
|
|
4579
3786
|
return "";
|
|
4580
3787
|
}
|
|
4581
|
-
return readFileSync(
|
|
3788
|
+
return readFileSync(join9(docsDir, files[0]), "utf8").trim();
|
|
4582
3789
|
}
|
|
4583
3790
|
function getNamedDoc(name, language) {
|
|
4584
3791
|
const docsDir = findDocsDir();
|
|
@@ -4586,7 +3793,7 @@ function getNamedDoc(name, language) {
|
|
|
4586
3793
|
logger.warn("docs/algolia-sdk not found");
|
|
4587
3794
|
return "";
|
|
4588
3795
|
}
|
|
4589
|
-
const file =
|
|
3796
|
+
const file = join9(docsDir, `${name}-${language}.md`);
|
|
4590
3797
|
if (!existsSync(file)) {
|
|
4591
3798
|
logger.warn({ name, language }, "named SDK reference not found");
|
|
4592
3799
|
return "";
|
|
@@ -4714,7 +3921,7 @@ function algoliaClientDoc(input) {
|
|
|
4714
3921
|
function ingestionInstructions(input) {
|
|
4715
3922
|
return [
|
|
4716
3923
|
...input.confirmed && input.confirmed.length ? [
|
|
4717
|
-
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3924
|
+
`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
3925
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4719
3926
|
`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
3927
|
`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.`,
|
|
@@ -4932,6 +4139,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4932
4139
|
const targetIndex = selected?.selection;
|
|
4933
4140
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4934
4141
|
await assertGitRepoWithHead(repoRoot);
|
|
4142
|
+
if (useCases.includes("ingestion")) {
|
|
4143
|
+
await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
|
|
4144
|
+
}
|
|
4935
4145
|
const normalized = normalizeFindingPaths(findings);
|
|
4936
4146
|
const confirmed2 = normalized.confirmedEntities;
|
|
4937
4147
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -5156,7 +4366,7 @@ ${detail}` : ""}`
|
|
|
5156
4366
|
if (searchConfigFile) {
|
|
5157
4367
|
const ignoreStatus = await gitIgnoreStatus(
|
|
5158
4368
|
repoRoot,
|
|
5159
|
-
|
|
4369
|
+
join10(repoRoot, searchConfigFile)
|
|
5160
4370
|
);
|
|
5161
4371
|
if (ignoreStatus === "covered") {
|
|
5162
4372
|
summaries.push(
|
|
@@ -5202,6 +4412,7 @@ var defaultWorkflow = {
|
|
|
5202
4412
|
id: "project-scan",
|
|
5203
4413
|
title: "project scan",
|
|
5204
4414
|
outputSchema: projectScanSchema,
|
|
4415
|
+
tips: ["apps-and-indices", "records-are-json", "speed"],
|
|
5205
4416
|
run: (ctx) => {
|
|
5206
4417
|
ctx.notify({
|
|
5207
4418
|
messages: [
|
|
@@ -5237,6 +4448,7 @@ var defaultWorkflow = {
|
|
|
5237
4448
|
id: "ingestion",
|
|
5238
4449
|
title: "ingest records",
|
|
5239
4450
|
outputSchema: implementSchema,
|
|
4451
|
+
tips: ["records-are-json", "searchable-attributes", "facets"],
|
|
5240
4452
|
run: (ctx) => {
|
|
5241
4453
|
ctx.notify({
|
|
5242
4454
|
messages: [
|
|
@@ -5257,6 +4469,13 @@ var defaultWorkflow = {
|
|
|
5257
4469
|
id: "search",
|
|
5258
4470
|
title: "create search ui",
|
|
5259
4471
|
outputSchema: implementSchema,
|
|
4472
|
+
tips: [
|
|
4473
|
+
"test-relevance",
|
|
4474
|
+
"search-analytics",
|
|
4475
|
+
"recommend",
|
|
4476
|
+
"searchable-attributes",
|
|
4477
|
+
"facets"
|
|
4478
|
+
],
|
|
5260
4479
|
run: (ctx) => {
|
|
5261
4480
|
ctx.notify({
|
|
5262
4481
|
messages: ["Building your Algolia search experience\u2026"]
|
|
@@ -5293,6 +4512,1045 @@ function getWorkflow(id) {
|
|
|
5293
4512
|
return workflows[id];
|
|
5294
4513
|
}
|
|
5295
4514
|
|
|
4515
|
+
// src/ui/Welcome.tsx
|
|
4516
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4517
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4518
|
+
import { useState as useState7 } from "react";
|
|
4519
|
+
import { Box as Box10, Spacer, Text as Text10, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
4520
|
+
|
|
4521
|
+
// src/ui/copy/welcome.ts
|
|
4522
|
+
var sidebarItems = [
|
|
4523
|
+
{
|
|
4524
|
+
title: "scan your project",
|
|
4525
|
+
description: "detect models, schemas, and data worth indexing"
|
|
4526
|
+
},
|
|
4527
|
+
{
|
|
4528
|
+
title: "select and index",
|
|
4529
|
+
description: "push 100 records to Algolia in seconds"
|
|
4530
|
+
},
|
|
4531
|
+
{
|
|
4532
|
+
title: "detect your stack",
|
|
4533
|
+
description: "whatever language and framework you already use"
|
|
4534
|
+
},
|
|
4535
|
+
{
|
|
4536
|
+
title: "scaffold a search UI",
|
|
4537
|
+
description: "a search box and results, wired into your app"
|
|
4538
|
+
},
|
|
4539
|
+
{
|
|
4540
|
+
title: "ship it",
|
|
4541
|
+
description: "build check passes, search is live with your real data"
|
|
4542
|
+
}
|
|
4543
|
+
];
|
|
4544
|
+
|
|
4545
|
+
// src/ui/Welcome.tsx
|
|
4546
|
+
import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
|
|
4547
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4548
|
+
var IMAGE_PATH = join11(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
|
|
4549
|
+
var TERMINAL_INFO = {
|
|
4550
|
+
...defaultTerminalInfo,
|
|
4551
|
+
supportsUnicode: true,
|
|
4552
|
+
supportsColor: true
|
|
4553
|
+
};
|
|
4554
|
+
function SidebarItem({
|
|
4555
|
+
title,
|
|
4556
|
+
description
|
|
4557
|
+
}) {
|
|
4558
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
4559
|
+
/* @__PURE__ */ jsxs9(Box10, { gap: 1, children: [
|
|
4560
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.success, children: "\u2192" }),
|
|
4561
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.strong, bold: true, children: title })
|
|
4562
|
+
] }),
|
|
4563
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 2, children: [
|
|
4564
|
+
/* @__PURE__ */ jsx8(Spacer, {}),
|
|
4565
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: description })
|
|
4566
|
+
] })
|
|
4567
|
+
] });
|
|
4568
|
+
}
|
|
4569
|
+
function Welcome() {
|
|
4570
|
+
const confirmStart = useWizard((s) => s.confirmStart);
|
|
4571
|
+
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
4572
|
+
const { rows } = useWindowSize5();
|
|
4573
|
+
const actions = [
|
|
4574
|
+
{ label: "start wizard", run: confirmStart },
|
|
4575
|
+
{ label: "learn more", run: openLearnMore }
|
|
4576
|
+
];
|
|
4577
|
+
const [index, setIndex] = useState7(0);
|
|
4578
|
+
useInput3((input, key) => {
|
|
4579
|
+
if (key.upArrow || input === "k") {
|
|
4580
|
+
setIndex((i) => (i - 1 + actions.length) % actions.length);
|
|
4581
|
+
} else if (key.downArrow || input === "j") {
|
|
4582
|
+
setIndex((i) => (i + 1) % actions.length);
|
|
4583
|
+
} else if (key.return) {
|
|
4584
|
+
actions[index].run();
|
|
4585
|
+
}
|
|
4586
|
+
});
|
|
4587
|
+
const scales = {
|
|
4588
|
+
large: {
|
|
4589
|
+
sidebar: { padding: { x: 4, y: 2 }, gap: 2 },
|
|
4590
|
+
main: { padding: { x: 8, y: 4 } }
|
|
4591
|
+
},
|
|
4592
|
+
small: {
|
|
4593
|
+
sidebar: { padding: { x: 2, y: 1 }, gap: 1 },
|
|
4594
|
+
main: { padding: { x: 4, y: 2 } }
|
|
4595
|
+
}
|
|
4596
|
+
};
|
|
4597
|
+
let layout = scales["large"];
|
|
4598
|
+
if (rows < 30) {
|
|
4599
|
+
layout = scales["small"];
|
|
4600
|
+
}
|
|
4601
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
4602
|
+
/* @__PURE__ */ jsx8(
|
|
4603
|
+
Box10,
|
|
4604
|
+
{
|
|
4605
|
+
paddingY: layout.main.padding.y,
|
|
4606
|
+
paddingX: layout.main.padding.x,
|
|
4607
|
+
flexDirection: "column",
|
|
4608
|
+
justifyContent: "center",
|
|
4609
|
+
children: /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 2, children: [
|
|
4610
|
+
/* @__PURE__ */ jsx8(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx8(
|
|
4611
|
+
Image,
|
|
4612
|
+
{
|
|
4613
|
+
src: IMAGE_PATH,
|
|
4614
|
+
objectFit: "contain",
|
|
4615
|
+
alt: "Algolia",
|
|
4616
|
+
width: 20,
|
|
4617
|
+
height: 10,
|
|
4618
|
+
protocol: "halfBlock"
|
|
4619
|
+
}
|
|
4620
|
+
) }),
|
|
4621
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
4622
|
+
/* @__PURE__ */ jsx8(Box10, { flexDirection: "column", children: actions.map((action, i) => /* @__PURE__ */ jsx8(
|
|
4623
|
+
SelectRow,
|
|
4624
|
+
{
|
|
4625
|
+
highlighted: i === index,
|
|
4626
|
+
highlightBackground: false,
|
|
4627
|
+
label: action.label
|
|
4628
|
+
},
|
|
4629
|
+
action.label
|
|
4630
|
+
)) }),
|
|
4631
|
+
/* @__PURE__ */ jsxs9(Box10, { gap: 2, children: [
|
|
4632
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
4633
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[\u2191] [\u2193]" }),
|
|
4634
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " move" })
|
|
4635
|
+
] }),
|
|
4636
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
4637
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.primary, children: "[enter]" }),
|
|
4638
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.dim, children: " confirm" })
|
|
4639
|
+
] })
|
|
4640
|
+
] })
|
|
4641
|
+
] })
|
|
4642
|
+
}
|
|
4643
|
+
),
|
|
4644
|
+
/* @__PURE__ */ jsxs9(
|
|
4645
|
+
Box10,
|
|
4646
|
+
{
|
|
4647
|
+
backgroundColor: COLORS.bg.sidebar,
|
|
4648
|
+
width: 40,
|
|
4649
|
+
paddingY: layout.sidebar.padding.y,
|
|
4650
|
+
paddingX: layout.sidebar.padding.x,
|
|
4651
|
+
gap: layout.sidebar.gap,
|
|
4652
|
+
flexDirection: "column",
|
|
4653
|
+
justifyContent: "center",
|
|
4654
|
+
children: [
|
|
4655
|
+
/* @__PURE__ */ jsx8(Text10, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
4656
|
+
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx8(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
4657
|
+
]
|
|
4658
|
+
}
|
|
4659
|
+
)
|
|
4660
|
+
] });
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
// src/ui/LearnMore.tsx
|
|
4664
|
+
import { Fragment } from "react";
|
|
4665
|
+
import { Box as Box11, Text as Text11, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
4666
|
+
|
|
4667
|
+
// src/ui/copy/learn-more.ts
|
|
4668
|
+
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
4669
|
+
var accessItems = [
|
|
4670
|
+
{
|
|
4671
|
+
tag: "READ",
|
|
4672
|
+
title: "Project files",
|
|
4673
|
+
description: "reads manifests, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
4674
|
+
},
|
|
4675
|
+
{
|
|
4676
|
+
tag: "WRITE",
|
|
4677
|
+
title: "Code changes",
|
|
4678
|
+
description: "creates & edits files (search UI, config) directly in your branch."
|
|
4679
|
+
},
|
|
4680
|
+
{
|
|
4681
|
+
tag: "EXEC",
|
|
4682
|
+
title: "Setup commands",
|
|
4683
|
+
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."
|
|
4684
|
+
},
|
|
4685
|
+
{
|
|
4686
|
+
tag: "NET",
|
|
4687
|
+
title: "Algolia API",
|
|
4688
|
+
description: "sends index settings & the records you pick to your Algolia app over HTTPS."
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
tag: "KEY",
|
|
4692
|
+
title: "Credentials",
|
|
4693
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
|
|
4694
|
+
}
|
|
4695
|
+
];
|
|
4696
|
+
var neverItems = [
|
|
4697
|
+
"Send your source code to a model or third party",
|
|
4698
|
+
"Commit or push to git",
|
|
4699
|
+
"Run a command you haven't approved"
|
|
4700
|
+
];
|
|
4701
|
+
var policyLinks = [
|
|
4702
|
+
{ label: "Terms", url: "https://www.algolia.com/policies/terms" },
|
|
4703
|
+
{ label: "Privacy Policy", url: "https://www.algolia.com/policies/privacy" }
|
|
4704
|
+
];
|
|
4705
|
+
|
|
4706
|
+
// src/ui/LearnMore.tsx
|
|
4707
|
+
import { jsx as jsx9, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4708
|
+
var TAG_COLORS = {
|
|
4709
|
+
READ: COLORS.success,
|
|
4710
|
+
WRITE: COLORS.badge,
|
|
4711
|
+
EXEC: COLORS.danger,
|
|
4712
|
+
NET: COLORS.accent,
|
|
4713
|
+
KEY: COLORS.muted
|
|
4714
|
+
};
|
|
4715
|
+
var TAG_COLUMN_WIDTH = 10;
|
|
4716
|
+
var PADDING_X = 6;
|
|
4717
|
+
var NEVER_BOX_PAD_X = 2;
|
|
4718
|
+
function NeverLine({
|
|
4719
|
+
width,
|
|
4720
|
+
segments = []
|
|
4721
|
+
}) {
|
|
4722
|
+
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
4723
|
+
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
4724
|
+
return /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
4725
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" }),
|
|
4726
|
+
" ".repeat(NEVER_BOX_PAD_X),
|
|
4727
|
+
segments.map((s, i) => /* @__PURE__ */ jsx9(Text11, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
4728
|
+
" ".repeat(rightPad),
|
|
4729
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: "\u2502" })
|
|
4730
|
+
] });
|
|
4731
|
+
}
|
|
4732
|
+
function LearnMore() {
|
|
4733
|
+
const confirmStart = useWizard((s) => s.confirmStart);
|
|
4734
|
+
const backToHome = useWizard((s) => s.backToHome);
|
|
4735
|
+
const { columns } = useWindowSize6();
|
|
4736
|
+
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
4737
|
+
useInput4((_input, key) => {
|
|
4738
|
+
if (key.escape) backToHome();
|
|
4739
|
+
else if (key.return) confirmStart();
|
|
4740
|
+
});
|
|
4741
|
+
return /* @__PURE__ */ jsxs10(
|
|
4742
|
+
Box11,
|
|
4743
|
+
{
|
|
4744
|
+
flexDirection: "column",
|
|
4745
|
+
paddingX: PADDING_X,
|
|
4746
|
+
paddingY: 2,
|
|
4747
|
+
width: "100%",
|
|
4748
|
+
gap: 1,
|
|
4749
|
+
children: [
|
|
4750
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
4751
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: accessIntro }),
|
|
4752
|
+
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
4753
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
4754
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
4755
|
+
/* @__PURE__ */ jsx9(Box11, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text11, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
4756
|
+
/* @__PURE__ */ jsx9(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: COLORS.primary, children: [
|
|
4757
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: item.title }),
|
|
4758
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
4759
|
+
] }) })
|
|
4760
|
+
] })
|
|
4761
|
+
] }, item.tag)) }),
|
|
4762
|
+
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
|
|
4763
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
4764
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4765
|
+
/* @__PURE__ */ jsx9(
|
|
4766
|
+
NeverLine,
|
|
4767
|
+
{
|
|
4768
|
+
width: dividerWidth,
|
|
4769
|
+
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
4770
|
+
}
|
|
4771
|
+
),
|
|
4772
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs10(Fragment, { children: [
|
|
4773
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4774
|
+
/* @__PURE__ */ jsx9(
|
|
4775
|
+
NeverLine,
|
|
4776
|
+
{
|
|
4777
|
+
width: dividerWidth,
|
|
4778
|
+
segments: [
|
|
4779
|
+
{ text: "\u2715", color: COLORS.danger },
|
|
4780
|
+
{ text: " " },
|
|
4781
|
+
{ text: item, color: COLORS.primary }
|
|
4782
|
+
]
|
|
4783
|
+
}
|
|
4784
|
+
)
|
|
4785
|
+
] }, item)),
|
|
4786
|
+
/* @__PURE__ */ jsx9(NeverLine, { width: dividerWidth }),
|
|
4787
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
4788
|
+
] }),
|
|
4789
|
+
/* @__PURE__ */ jsx9(Box11, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4790
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
4791
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.accent, children: link.url })
|
|
4792
|
+
] }, link.label)) }),
|
|
4793
|
+
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
4794
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4795
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
4796
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "esc" }),
|
|
4797
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "] back" })
|
|
4798
|
+
] }),
|
|
4799
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
4800
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "[" }),
|
|
4801
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.primary, children: "enter" }),
|
|
4802
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.muted, children: "]" }),
|
|
4803
|
+
/* @__PURE__ */ jsx9(Text11, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
4804
|
+
] })
|
|
4805
|
+
] })
|
|
4806
|
+
]
|
|
4807
|
+
}
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
|
|
4811
|
+
// src/ui/Sidebar.tsx
|
|
4812
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
4813
|
+
|
|
4814
|
+
// src/ui/Steps.tsx
|
|
4815
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
4816
|
+
import Spinner from "ink-spinner";
|
|
4817
|
+
import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
4818
|
+
function Steps() {
|
|
4819
|
+
const { steps } = useWizard();
|
|
4820
|
+
const visibleSteps = steps.filter(isStepVisible);
|
|
4821
|
+
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: [
|
|
4822
|
+
s.status === "running" ? /* @__PURE__ */ jsx10(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
4823
|
+
" ",
|
|
4824
|
+
s.title
|
|
4825
|
+
] }) }, s.id)) });
|
|
4826
|
+
}
|
|
4827
|
+
function CurrentStep() {
|
|
4828
|
+
const { steps } = useWizard();
|
|
4829
|
+
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
4830
|
+
if (!currentStep) return null;
|
|
4831
|
+
return /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status.running, children: [
|
|
4832
|
+
/* @__PURE__ */ jsx10(Spinner, { type: "dots" }),
|
|
4833
|
+
" ",
|
|
4834
|
+
` ${currentStep.title}`
|
|
4835
|
+
] });
|
|
4836
|
+
}
|
|
4837
|
+
|
|
4838
|
+
// src/ui/Progress.tsx
|
|
4839
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
4840
|
+
import { jsx as jsx11, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4841
|
+
function Progress() {
|
|
4842
|
+
const { steps, currentStepIndex } = useWizard();
|
|
4843
|
+
const visibleSteps = steps.filter(isStepVisible);
|
|
4844
|
+
if (visibleSteps.length === 0) return null;
|
|
4845
|
+
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
4846
|
+
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
4847
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 1, children: [
|
|
4848
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.muted, children: "STEP" }),
|
|
4849
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: activeStepNumber }),
|
|
4850
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: "/" }),
|
|
4851
|
+
/* @__PURE__ */ jsx11(Text13, { color: COLORS.strong, bold: true, children: visibleSteps.length })
|
|
4852
|
+
] });
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
// src/ui/copy/sidebar-commands.ts
|
|
4856
|
+
var sidebarCommands = [
|
|
4857
|
+
{ keyHint: "tab", description: "toggle logs" },
|
|
4858
|
+
{ keyHint: "esc", description: "exit wizard" }
|
|
4859
|
+
];
|
|
4860
|
+
|
|
4861
|
+
// src/ui/Sidebar.tsx
|
|
4862
|
+
import { jsx as jsx12, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4863
|
+
function Sidebar() {
|
|
4864
|
+
return /* @__PURE__ */ jsxs13(
|
|
4865
|
+
Box14,
|
|
4866
|
+
{
|
|
4867
|
+
backgroundColor: "#14171E",
|
|
4868
|
+
width: 30,
|
|
4869
|
+
paddingX: 4,
|
|
4870
|
+
paddingY: 2,
|
|
4871
|
+
flexDirection: "column",
|
|
4872
|
+
justifyContent: "space-between",
|
|
4873
|
+
children: [
|
|
4874
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
4875
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: "PROGRESS" }),
|
|
4876
|
+
/* @__PURE__ */ jsx12(Steps, {})
|
|
4877
|
+
] }),
|
|
4878
|
+
/* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", gap: 1, children: [
|
|
4879
|
+
/* @__PURE__ */ jsx12(Progress, {}),
|
|
4880
|
+
/* @__PURE__ */ jsx12(Box14, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
4881
|
+
return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "row", gap: 1, children: [
|
|
4882
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
4883
|
+
/* @__PURE__ */ jsx12(Text14, { color: COLORS.muted, children: c.description })
|
|
4884
|
+
] });
|
|
4885
|
+
}) })
|
|
4886
|
+
] })
|
|
4887
|
+
]
|
|
4888
|
+
}
|
|
4889
|
+
);
|
|
4890
|
+
}
|
|
4891
|
+
|
|
4892
|
+
// src/ui/Ribbon.tsx
|
|
4893
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
4894
|
+
import { jsx as jsx13, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4895
|
+
function Ribbon() {
|
|
4896
|
+
const firstCommand = sidebarCommands[0];
|
|
4897
|
+
return /* @__PURE__ */ jsxs14(
|
|
4898
|
+
Box15,
|
|
4899
|
+
{
|
|
4900
|
+
backgroundColor: "#14171E",
|
|
4901
|
+
flexDirection: "row",
|
|
4902
|
+
justifyContent: "space-between",
|
|
4903
|
+
paddingX: 2,
|
|
4904
|
+
paddingY: 1,
|
|
4905
|
+
children: [
|
|
4906
|
+
/* @__PURE__ */ jsx13(Progress, {}),
|
|
4907
|
+
/* @__PURE__ */ jsx13(CurrentStep, {}),
|
|
4908
|
+
/* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", gap: 1, children: [
|
|
4909
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
4910
|
+
/* @__PURE__ */ jsx13(Text15, { color: COLORS.muted, children: firstCommand.description })
|
|
4911
|
+
] })
|
|
4912
|
+
]
|
|
4913
|
+
}
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
|
|
4917
|
+
// src/ui/App.tsx
|
|
4918
|
+
import { useState as useState9 } from "react";
|
|
4919
|
+
|
|
4920
|
+
// src/ui/Logs.tsx
|
|
4921
|
+
import { Box as Box16, Text as Text16, useInput as useInput5 } from "ink";
|
|
4922
|
+
import { jsx as jsx14, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4923
|
+
var KIND_COLOR = {
|
|
4924
|
+
tool: COLORS.primary,
|
|
4925
|
+
prompt: COLORS.badge
|
|
4926
|
+
};
|
|
4927
|
+
var STATUS_COLOR = {
|
|
4928
|
+
running: COLORS.status.running,
|
|
4929
|
+
error: COLORS.danger
|
|
4930
|
+
};
|
|
4931
|
+
function logNameColor(entry) {
|
|
4932
|
+
return STATUS_COLOR[entry.status] ?? KIND_COLOR[entry.kind];
|
|
4933
|
+
}
|
|
4934
|
+
var ROW_GAP = 1;
|
|
4935
|
+
function truncate2(str, maxWidth) {
|
|
4936
|
+
if (maxWidth <= 0) return "";
|
|
4937
|
+
return str.length > maxWidth ? `${str.slice(0, maxWidth - 1)}\u2026` : str;
|
|
4938
|
+
}
|
|
4939
|
+
function rawInputText(input) {
|
|
4940
|
+
if (input === void 0) return "";
|
|
4941
|
+
const str = typeof input === "string" ? input : JSON.stringify(input);
|
|
4942
|
+
if (!str || str === "{}") return "";
|
|
4943
|
+
return str.replace(/\s+/g, " ").trim();
|
|
4944
|
+
}
|
|
4945
|
+
function formatTimestamp(ms) {
|
|
4946
|
+
const d = new Date(ms);
|
|
4947
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
4948
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
4949
|
+
}
|
|
4950
|
+
function Logs() {
|
|
4951
|
+
const logs = useWizard((s) => s.logs);
|
|
4952
|
+
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
4953
|
+
useInput5((_input, key) => {
|
|
4954
|
+
if (key.upArrow) scroll.scrollBy(-1);
|
|
4955
|
+
else if (key.downArrow) scroll.scrollBy(1);
|
|
4956
|
+
});
|
|
4957
|
+
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
4958
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
4959
|
+
logs.length === 0 && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "No logs yet." }),
|
|
4960
|
+
/* @__PURE__ */ jsx14(ScrollView, { scroll, children: visible.map((entry) => {
|
|
4961
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
4962
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
4963
|
+
const rawPreview = rawInputText(entry.input);
|
|
4964
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
4965
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
4966
|
+
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
4967
|
+
const name = truncate2(entry.name, budget);
|
|
4968
|
+
budget -= name.length;
|
|
4969
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
4970
|
+
return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
4971
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: timestamp }),
|
|
4972
|
+
/* @__PURE__ */ jsx14(Text16, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
4973
|
+
preview && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
4974
|
+
durationText && /* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: durationText })
|
|
4975
|
+
] }, entry.id);
|
|
4976
|
+
}) }),
|
|
4977
|
+
/* @__PURE__ */ jsx14(Text16, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
4978
|
+
] });
|
|
4979
|
+
}
|
|
4980
|
+
|
|
4981
|
+
// src/ui/Tips.tsx
|
|
4982
|
+
import { useEffect as useEffect3, useState as useState8 } from "react";
|
|
4983
|
+
import { Box as Box18, Text as Text18 } from "ink";
|
|
4984
|
+
import terminalLink from "terminal-link";
|
|
4985
|
+
|
|
4986
|
+
// src/ui/Code.tsx
|
|
4987
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
4988
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
4989
|
+
var TOKEN_RE = /("(?:\\.|[^"\\])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\]:,])/g;
|
|
4990
|
+
function highlightJson(json) {
|
|
4991
|
+
const parts = json.split(TOKEN_RE);
|
|
4992
|
+
return parts.map((part, i) => {
|
|
4993
|
+
if (!part) return null;
|
|
4994
|
+
if (part[0] === '"') {
|
|
4995
|
+
const next = parts.slice(i + 1).find((p) => p.trim());
|
|
4996
|
+
const isKey = next?.trimStart().startsWith(":");
|
|
4997
|
+
return /* @__PURE__ */ jsx15(Text17, { color: isKey ? "cyan" : "green", children: part }, i);
|
|
4998
|
+
}
|
|
4999
|
+
if (part === "true" || part === "false" || part === "null") {
|
|
5000
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "magenta", children: part }, i);
|
|
5001
|
+
}
|
|
5002
|
+
if (/^-?\d/.test(part)) {
|
|
5003
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "yellow", children: part }, i);
|
|
5004
|
+
}
|
|
5005
|
+
if (/^[{}[\]:,]$/.test(part)) {
|
|
5006
|
+
return /* @__PURE__ */ jsx15(Text17, { dimColor: true, children: part }, i);
|
|
5007
|
+
}
|
|
5008
|
+
return /* @__PURE__ */ jsx15(Text17, { children: part }, i);
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
5011
|
+
function Code({ children }) {
|
|
5012
|
+
return /* @__PURE__ */ jsx15(Box17, { children: /* @__PURE__ */ jsx15(Text17, { children: highlightJson(children) }) });
|
|
5013
|
+
}
|
|
5014
|
+
|
|
5015
|
+
// src/ui/copy/tips.ts
|
|
5016
|
+
var tips = [
|
|
5017
|
+
{
|
|
5018
|
+
id: "apps-and-indices",
|
|
5019
|
+
title: "An Application is like your library. Indices are the books in it.",
|
|
5020
|
+
chunks: [
|
|
5021
|
+
{
|
|
5022
|
+
type: "text",
|
|
5023
|
+
value: "Applications hold your API keys and Indices."
|
|
5024
|
+
},
|
|
5025
|
+
{
|
|
5026
|
+
type: "text",
|
|
5027
|
+
value: "Each index is a searchable collection of records (think: products, articles, orders, concerts)."
|
|
5028
|
+
}
|
|
5029
|
+
]
|
|
5030
|
+
},
|
|
5031
|
+
{
|
|
5032
|
+
id: "records-are-json",
|
|
5033
|
+
title: "A Record is a JSON object inside of an index.",
|
|
5034
|
+
chunks: [
|
|
5035
|
+
{
|
|
5036
|
+
type: "text",
|
|
5037
|
+
value: "Every object you index is just a JSON document with a unique objectID."
|
|
5038
|
+
},
|
|
5039
|
+
{
|
|
5040
|
+
type: "text",
|
|
5041
|
+
value: "No fixed schema, no hard requirements."
|
|
5042
|
+
},
|
|
5043
|
+
{
|
|
5044
|
+
type: "text",
|
|
5045
|
+
value: "Everything else is up to you:"
|
|
5046
|
+
},
|
|
5047
|
+
{
|
|
5048
|
+
type: "code",
|
|
5049
|
+
value: JSON.stringify(
|
|
5050
|
+
{
|
|
5051
|
+
objectID: "sku_48213",
|
|
5052
|
+
name: "Trail Running Shoe",
|
|
5053
|
+
brand: "Northline",
|
|
5054
|
+
categories: ["Footwear", "Running", "Mens"],
|
|
5055
|
+
price: 129.99,
|
|
5056
|
+
in_stock: true,
|
|
5057
|
+
rating: 4.6
|
|
5058
|
+
},
|
|
5059
|
+
null,
|
|
5060
|
+
2
|
|
5061
|
+
)
|
|
5062
|
+
}
|
|
5063
|
+
]
|
|
5064
|
+
},
|
|
5065
|
+
{
|
|
5066
|
+
id: "speed",
|
|
5067
|
+
title: "Speed isn't a feature, it's our business",
|
|
5068
|
+
chunks: [
|
|
5069
|
+
{
|
|
5070
|
+
type: "text",
|
|
5071
|
+
value: "Algolia's engine processes most search queries in 1 to 50 milliseconds."
|
|
5072
|
+
},
|
|
5073
|
+
{
|
|
5074
|
+
type: "text",
|
|
5075
|
+
value: "This is why our search-as-you-type experience feels instant."
|
|
5076
|
+
}
|
|
5077
|
+
]
|
|
5078
|
+
},
|
|
5079
|
+
{
|
|
5080
|
+
id: "searchable-attributes",
|
|
5081
|
+
title: "Searchable attributes are your relevance dial #1",
|
|
5082
|
+
chunks: [
|
|
5083
|
+
{
|
|
5084
|
+
type: "text",
|
|
5085
|
+
value: "Order matters. Attributes listed first in"
|
|
5086
|
+
},
|
|
5087
|
+
{ type: "codeword", value: "searchableAttributes" },
|
|
5088
|
+
{ type: "text", value: "carry more ranking weight." },
|
|
5089
|
+
{
|
|
5090
|
+
type: "text",
|
|
5091
|
+
value: "This is the single highest-leverage lever new users don't know exists."
|
|
5092
|
+
}
|
|
5093
|
+
]
|
|
5094
|
+
},
|
|
5095
|
+
{
|
|
5096
|
+
id: "facets",
|
|
5097
|
+
title: "Facets aren't just filters, they help build your UI",
|
|
5098
|
+
chunks: [
|
|
5099
|
+
{ type: "codeword", value: "attributesForFaceting" },
|
|
5100
|
+
{
|
|
5101
|
+
type: "text",
|
|
5102
|
+
value: "unlock category sidebars, price sliders, tag clouds without extra backend work."
|
|
5103
|
+
}
|
|
5104
|
+
]
|
|
5105
|
+
},
|
|
5106
|
+
{
|
|
5107
|
+
id: "test-relevance",
|
|
5108
|
+
title: "Test relevance in the dashboard before you write a line of ranking code",
|
|
5109
|
+
chunks: [
|
|
5110
|
+
{
|
|
5111
|
+
type: "text",
|
|
5112
|
+
value: "Our Search dashboard has a live preview where you can browse results."
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
type: "text",
|
|
5116
|
+
value: "Tune your settings and see how it alters your results in"
|
|
5117
|
+
},
|
|
5118
|
+
{
|
|
5119
|
+
type: "link",
|
|
5120
|
+
value: "real-time.",
|
|
5121
|
+
href: "https://dashboard.algolia.com/explorer/browse"
|
|
5122
|
+
}
|
|
5123
|
+
]
|
|
5124
|
+
},
|
|
5125
|
+
{
|
|
5126
|
+
id: "search-analytics",
|
|
5127
|
+
title: "Search Analytics helps you discover opportunities",
|
|
5128
|
+
chunks: [
|
|
5129
|
+
{
|
|
5130
|
+
type: "text",
|
|
5131
|
+
value: "No click results for a particular query? Low click-through rates for another?"
|
|
5132
|
+
},
|
|
5133
|
+
{
|
|
5134
|
+
type: "text",
|
|
5135
|
+
value: "Our Search Analytics will help you identify synonyms, rules or other relevancy settings to improve your results."
|
|
5136
|
+
}
|
|
5137
|
+
]
|
|
5138
|
+
},
|
|
5139
|
+
{
|
|
5140
|
+
id: "recommend",
|
|
5141
|
+
title: "Let Algolia act as your recommendation engine",
|
|
5142
|
+
chunks: [
|
|
5143
|
+
{
|
|
5144
|
+
type: "text",
|
|
5145
|
+
value: "Beyond search, Algolia Recommend runs models trained on your existing indices and event data to power your recommendation engine."
|
|
5146
|
+
},
|
|
5147
|
+
{
|
|
5148
|
+
type: "text",
|
|
5149
|
+
value: "You can improve engagement with related, popular or visually similar items."
|
|
5150
|
+
}
|
|
5151
|
+
]
|
|
5152
|
+
}
|
|
5153
|
+
];
|
|
5154
|
+
var tipsById = Object.fromEntries(
|
|
5155
|
+
tips.map((tip) => [tip.id, tip])
|
|
5156
|
+
);
|
|
5157
|
+
function tipsByIds(ids) {
|
|
5158
|
+
return ids.map((id) => tipsById[id]).filter((tip) => tip !== void 0);
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5161
|
+
// src/ui/Tips.tsx
|
|
5162
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5163
|
+
var TICK_MS = 16;
|
|
5164
|
+
var CHUNK_HOLD_MS = 2e3;
|
|
5165
|
+
var HOLD_MS = 8e3;
|
|
5166
|
+
function TipTitle({ children }) {
|
|
5167
|
+
return /* @__PURE__ */ jsxs16(Box18, { gap: 1, children: [
|
|
5168
|
+
/* @__PURE__ */ jsx16(Text18, { color: "cyan", children: "\u2726" }),
|
|
5169
|
+
/* @__PURE__ */ jsx16(Text18, { color: "white", bold: true, children })
|
|
5170
|
+
] });
|
|
5171
|
+
}
|
|
5172
|
+
function InlineSegment({ segment }) {
|
|
5173
|
+
switch (segment.type) {
|
|
5174
|
+
case "highlight":
|
|
5175
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.success, children: segment.value });
|
|
5176
|
+
case "link":
|
|
5177
|
+
return /* @__PURE__ */ jsx16(Text18, { color: "cyan", underline: true, children: segment.href ? terminalLink(segment.value, segment.href) : segment.value });
|
|
5178
|
+
case "codeword":
|
|
5179
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.highlight.fg, backgroundColor: COLORS.highlight.bg, children: segment.value });
|
|
5180
|
+
default:
|
|
5181
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.muted, children: segment.value });
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
function TipContent({
|
|
5185
|
+
chunks,
|
|
5186
|
+
revealed
|
|
5187
|
+
}) {
|
|
5188
|
+
let remaining = revealed;
|
|
5189
|
+
const slices = chunks.map((chunk) => {
|
|
5190
|
+
const slice = chunk.value.slice(0, Math.max(0, remaining));
|
|
5191
|
+
remaining -= chunk.value.length;
|
|
5192
|
+
return slice;
|
|
5193
|
+
});
|
|
5194
|
+
const blocks = [];
|
|
5195
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
5196
|
+
const chunk = chunks[i];
|
|
5197
|
+
const slice = slices[i];
|
|
5198
|
+
if (!slice) continue;
|
|
5199
|
+
const segment = {
|
|
5200
|
+
type: chunk.type,
|
|
5201
|
+
value: slice,
|
|
5202
|
+
href: chunk.type === "link" ? chunk.href : void 0
|
|
5203
|
+
};
|
|
5204
|
+
if (chunk.type === "code") {
|
|
5205
|
+
blocks.push({ type: "code", segment });
|
|
5206
|
+
continue;
|
|
5207
|
+
}
|
|
5208
|
+
const last = blocks[blocks.length - 1];
|
|
5209
|
+
if (last?.type === "inline") {
|
|
5210
|
+
last.segments.push(segment);
|
|
5211
|
+
} else {
|
|
5212
|
+
blocks.push({ type: "inline", segments: [segment] });
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
return /* @__PURE__ */ jsx16(Box18, { flexDirection: "column", marginLeft: 2, gap: 1, children: blocks.map(
|
|
5216
|
+
(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: [
|
|
5217
|
+
j > 0 && " ",
|
|
5218
|
+
/* @__PURE__ */ jsx16(InlineSegment, { segment })
|
|
5219
|
+
] }, j)) }, i)
|
|
5220
|
+
) });
|
|
5221
|
+
}
|
|
5222
|
+
var progress = { stepId: null, tipIndex: 0, revealed: 0 };
|
|
5223
|
+
function Tips({ stepId, ids }) {
|
|
5224
|
+
const tips2 = tipsByIds(ids);
|
|
5225
|
+
const resuming = progress.stepId === stepId;
|
|
5226
|
+
if (!resuming) {
|
|
5227
|
+
progress.stepId = stepId;
|
|
5228
|
+
progress.tipIndex = 0;
|
|
5229
|
+
progress.revealed = 0;
|
|
5230
|
+
}
|
|
5231
|
+
const [tipIndex, setTipIndex] = useState8(resuming ? progress.tipIndex : 0);
|
|
5232
|
+
const [revealed, setRevealed] = useState8(resuming ? progress.revealed : 0);
|
|
5233
|
+
const tip = tips2.length > 0 ? tips2[tipIndex % tips2.length] : void 0;
|
|
5234
|
+
const contentLength = tip?.chunks.reduce((sum, c) => sum + c.value.length, 0) ?? 0;
|
|
5235
|
+
const totalLength = (tip?.title.length ?? 0) + contentLength;
|
|
5236
|
+
const segments = tip ? [
|
|
5237
|
+
{ type: "title", length: tip.title.length },
|
|
5238
|
+
...tip.chunks.map((c) => ({ type: c.type, length: c.value.length }))
|
|
5239
|
+
] : [];
|
|
5240
|
+
const noPauseAfterText = [
|
|
5241
|
+
"highlight",
|
|
5242
|
+
"link",
|
|
5243
|
+
"codeword"
|
|
5244
|
+
];
|
|
5245
|
+
const chunkBoundaries = [];
|
|
5246
|
+
let cumulative = 0;
|
|
5247
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
5248
|
+
cumulative += segments[i].length;
|
|
5249
|
+
const skipPause = noPauseAfterText.includes(
|
|
5250
|
+
segments[i + 1].type
|
|
5251
|
+
);
|
|
5252
|
+
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
5253
|
+
}
|
|
5254
|
+
useEffect3(() => {
|
|
5255
|
+
if (tips2.length === 0) return;
|
|
5256
|
+
if (revealed < totalLength) {
|
|
5257
|
+
const delay2 = chunkBoundaries.includes(revealed) ? CHUNK_HOLD_MS : TICK_MS;
|
|
5258
|
+
const timer2 = setTimeout(() => {
|
|
5259
|
+
setRevealed((r) => {
|
|
5260
|
+
const next = r + 1;
|
|
5261
|
+
progress.revealed = next;
|
|
5262
|
+
return next;
|
|
5263
|
+
});
|
|
5264
|
+
}, delay2);
|
|
5265
|
+
return () => clearTimeout(timer2);
|
|
5266
|
+
}
|
|
5267
|
+
const timer = setTimeout(() => {
|
|
5268
|
+
setTipIndex((i) => {
|
|
5269
|
+
const next = (i + 1) % tips2.length;
|
|
5270
|
+
progress.tipIndex = next;
|
|
5271
|
+
return next;
|
|
5272
|
+
});
|
|
5273
|
+
setRevealed(0);
|
|
5274
|
+
progress.revealed = 0;
|
|
5275
|
+
}, HOLD_MS);
|
|
5276
|
+
return () => clearTimeout(timer);
|
|
5277
|
+
}, [revealed, totalLength, tips2.length]);
|
|
5278
|
+
if (!tip) return null;
|
|
5279
|
+
const titleRevealed = tip.title.slice(0, revealed);
|
|
5280
|
+
const contentRevealed = Math.max(0, revealed - tip.title.length);
|
|
5281
|
+
return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", marginBottom: 1, gap: 1, children: [
|
|
5282
|
+
/* @__PURE__ */ jsx16(TipTitle, { children: titleRevealed }),
|
|
5283
|
+
/* @__PURE__ */ jsx16(TipContent, { chunks: tip.chunks, revealed: contentRevealed })
|
|
5284
|
+
] });
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
// src/ui/App.tsx
|
|
5288
|
+
import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
5289
|
+
function App() {
|
|
5290
|
+
const {
|
|
5291
|
+
phase,
|
|
5292
|
+
error,
|
|
5293
|
+
homeScreen,
|
|
5294
|
+
currentStepIndex,
|
|
5295
|
+
steps,
|
|
5296
|
+
inputReq,
|
|
5297
|
+
user,
|
|
5298
|
+
workflow
|
|
5299
|
+
} = useWizard();
|
|
5300
|
+
const { exit } = useApp();
|
|
5301
|
+
const { columns, rows } = useWindowSize7();
|
|
5302
|
+
const [showLogs, setShowLogs] = useState9(false);
|
|
5303
|
+
const finished = phase === "done" || phase === "error";
|
|
5304
|
+
const currentStep = steps[currentStepIndex];
|
|
5305
|
+
const stepDef = workflow ? getWorkflow(workflow.id)?.steps.find((s) => s.id === currentStep?.id) : void 0;
|
|
5306
|
+
const tipIds = stepDef?.tips ?? [];
|
|
5307
|
+
const stepHasTips = tipIds.length > 0;
|
|
5308
|
+
const isAwaitingUserInput = phase === "awaitingInput" && !!inputReq;
|
|
5309
|
+
const isCommandApprovalPrompt = isAwaitingUserInput && inputReq?.promptType === "commandApproval";
|
|
5310
|
+
const showTips = stepHasTips && (phase === "running" || isCommandApprovalPrompt);
|
|
5311
|
+
const showNoticesInMain = !stepHasTips;
|
|
5312
|
+
const showNotices = !isAwaitingUserInput;
|
|
5313
|
+
useInput6(
|
|
5314
|
+
(_input, key) => {
|
|
5315
|
+
if (key.return) {
|
|
5316
|
+
exit();
|
|
5317
|
+
}
|
|
5318
|
+
},
|
|
5319
|
+
{ isActive: finished }
|
|
5320
|
+
);
|
|
5321
|
+
useInput6((_input, key) => {
|
|
5322
|
+
if (phase === "idle" || phase === "authenticating") return;
|
|
5323
|
+
if (key.tab) {
|
|
5324
|
+
setShowLogs(!showLogs);
|
|
5325
|
+
track("AI Wizard Interaction", {
|
|
5326
|
+
context: "global",
|
|
5327
|
+
key: "tab",
|
|
5328
|
+
currentStep: currentStep?.id
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
});
|
|
5332
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
5333
|
+
useInput6((_input, key) => {
|
|
5334
|
+
if (escOwnedElsewhere) return;
|
|
5335
|
+
if (key.escape) {
|
|
5336
|
+
track("AI Wizard Interaction", {
|
|
5337
|
+
context: "global",
|
|
5338
|
+
key: "esc",
|
|
5339
|
+
currentStep: currentStep?.id ?? phase
|
|
5340
|
+
});
|
|
5341
|
+
exit();
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
const mainWindowVisible = phase === "authenticating" || phase === "preflight" || phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
5345
|
+
const flexDirection = columns > 90 ? "row" : "column";
|
|
5346
|
+
const showSidebar = flexDirection === "row";
|
|
5347
|
+
const scrollsPastViewport = phase === "idle" && homeScreen === "learnMore";
|
|
5348
|
+
return (
|
|
5349
|
+
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
5350
|
+
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
5351
|
+
flicker and leftover rows. */
|
|
5352
|
+
/* @__PURE__ */ jsxs17(
|
|
5353
|
+
Box19,
|
|
5354
|
+
{
|
|
5355
|
+
backgroundColor: COLORS.bg.main,
|
|
5356
|
+
flexDirection: "row",
|
|
5357
|
+
width: columns,
|
|
5358
|
+
height: scrollsPastViewport ? void 0 : rows,
|
|
5359
|
+
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
5360
|
+
children: [
|
|
5361
|
+
mainWindowVisible && /* @__PURE__ */ jsxs17(
|
|
5362
|
+
Box19,
|
|
5363
|
+
{
|
|
5364
|
+
flexDirection,
|
|
5365
|
+
width: "100%",
|
|
5366
|
+
maxHeight: rows,
|
|
5367
|
+
justifyContent: "space-between",
|
|
5368
|
+
children: [
|
|
5369
|
+
showLogs ? /* @__PURE__ */ jsx17(Logs, {}) : /* @__PURE__ */ jsxs17(
|
|
5370
|
+
Box19,
|
|
5371
|
+
{
|
|
5372
|
+
flexDirection: "column",
|
|
5373
|
+
paddingX: 4,
|
|
5374
|
+
paddingY: 2,
|
|
5375
|
+
width: showSidebar ? 70 : "100%",
|
|
5376
|
+
flexGrow: 1,
|
|
5377
|
+
gap: 1,
|
|
5378
|
+
children: [
|
|
5379
|
+
/* @__PURE__ */ jsxs17(Box19, { flexGrow: 2, flexDirection: "column", children: [
|
|
5380
|
+
phase === "preflight" && !user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5381
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5382
|
+
" Signing in to Algolia"
|
|
5383
|
+
] }) }),
|
|
5384
|
+
phase === "preflight" && user && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5385
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5386
|
+
" Getting things ready"
|
|
5387
|
+
] }) }),
|
|
5388
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", marginBottom: 1, children: [
|
|
5389
|
+
/* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
|
|
5390
|
+
/* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
|
|
5391
|
+
" Signing in to Algolia"
|
|
5392
|
+
] }),
|
|
5393
|
+
/* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
5394
|
+
] }),
|
|
5395
|
+
/* @__PURE__ */ jsx17(CliOutput, {}),
|
|
5396
|
+
showTips && currentStep && /* @__PURE__ */ jsx17(Tips, { stepId: currentStep.id, ids: tipIds }),
|
|
5397
|
+
showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, { showAll: true, border: false }),
|
|
5398
|
+
!showTips && /* @__PURE__ */ jsx17(PromptInput, {}),
|
|
5399
|
+
phase === "error" && error && /* @__PURE__ */ jsx17(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.status.error, children: [
|
|
5400
|
+
"\u2716 ",
|
|
5401
|
+
error
|
|
5402
|
+
] }) })
|
|
5403
|
+
] }),
|
|
5404
|
+
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, {}),
|
|
5405
|
+
showTips && /* @__PURE__ */ jsx17(PromptInput, {})
|
|
5406
|
+
]
|
|
5407
|
+
}
|
|
5408
|
+
),
|
|
5409
|
+
showSidebar ? /* @__PURE__ */ jsx17(Sidebar, {}) : /* @__PURE__ */ jsx17(Ribbon, {})
|
|
5410
|
+
]
|
|
5411
|
+
}
|
|
5412
|
+
),
|
|
5413
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx17(LearnMore, {}) : /* @__PURE__ */ jsx17(Welcome, {}))
|
|
5414
|
+
]
|
|
5415
|
+
}
|
|
5416
|
+
)
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5420
|
+
// src/lib/envAppId.ts
|
|
5421
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
5422
|
+
import { join as join12 } from "node:path";
|
|
5423
|
+
var ENV_FILES = [".env", ".env.local"];
|
|
5424
|
+
var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
|
|
5425
|
+
async function findEnvApplicationId(root = process.cwd()) {
|
|
5426
|
+
for (const file of ENV_FILES) {
|
|
5427
|
+
let content;
|
|
5428
|
+
try {
|
|
5429
|
+
content = await readFile7(join12(root, file), "utf8");
|
|
5430
|
+
} catch (err) {
|
|
5431
|
+
if (err.code !== "ENOENT") {
|
|
5432
|
+
logger.warn(
|
|
5433
|
+
{ file, err },
|
|
5434
|
+
"could not read env file for an application id"
|
|
5435
|
+
);
|
|
5436
|
+
}
|
|
5437
|
+
continue;
|
|
5438
|
+
}
|
|
5439
|
+
for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
|
|
5440
|
+
const id = readValue(raw);
|
|
5441
|
+
if (id) {
|
|
5442
|
+
logger.info({ file, name, app: id }, "found an application id in env");
|
|
5443
|
+
return { id, name, file };
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
return null;
|
|
5448
|
+
}
|
|
5449
|
+
function readValue(raw) {
|
|
5450
|
+
const trimmed = raw.trim();
|
|
5451
|
+
const quoted = trimmed.match(/^(['"])(.*)\1/);
|
|
5452
|
+
const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
|
|
5453
|
+
return value.length > 0 && !value.startsWith("<") ? value : null;
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
// src/lib/algoliaAppPicker.ts
|
|
5457
|
+
function secondaryFor(app) {
|
|
5458
|
+
return app.plan ? { kind: "badge", value: app.plan } : void 0;
|
|
5459
|
+
}
|
|
5460
|
+
function labelFor(app) {
|
|
5461
|
+
return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
|
|
5462
|
+
}
|
|
5463
|
+
function selectAndReport(app) {
|
|
5464
|
+
useWizard.getState().pushCliOutput(
|
|
5465
|
+
"stdout",
|
|
5466
|
+
`Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
|
|
5467
|
+
);
|
|
5468
|
+
return selectApplication(app.id);
|
|
5469
|
+
}
|
|
5470
|
+
async function promptForApplication(leadIn = []) {
|
|
5471
|
+
const store = useWizard.getState();
|
|
5472
|
+
const apps = await listApplications();
|
|
5473
|
+
if (apps.length === 0) {
|
|
5474
|
+
throw new Error(
|
|
5475
|
+
"This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5478
|
+
if (apps.length === 1) {
|
|
5479
|
+
const only = apps[0];
|
|
5480
|
+
logger.info(
|
|
5481
|
+
{ app: only.id },
|
|
5482
|
+
"single application on the account; selecting it"
|
|
5483
|
+
);
|
|
5484
|
+
for (const line of leadIn) store.pushCliOutput("stdout", line);
|
|
5485
|
+
return selectAndReport(only);
|
|
5486
|
+
}
|
|
5487
|
+
const messages = [
|
|
5488
|
+
...leadIn,
|
|
5489
|
+
"Which Algolia application should the wizard work in?"
|
|
5490
|
+
];
|
|
5491
|
+
for (; ; ) {
|
|
5492
|
+
const choice = await store.requestUserInput({
|
|
5493
|
+
prompt: "Select an application",
|
|
5494
|
+
promptType: "multipleChoice",
|
|
5495
|
+
options: apps.map(labelFor),
|
|
5496
|
+
secondary: apps.map(secondaryFor),
|
|
5497
|
+
messages
|
|
5498
|
+
});
|
|
5499
|
+
const chosen = apps.find((app) => labelFor(app) === choice);
|
|
5500
|
+
if (!chosen) {
|
|
5501
|
+
throw new Error("Application picker received an unexpected selection");
|
|
5502
|
+
}
|
|
5503
|
+
try {
|
|
5504
|
+
return await selectAndReport(chosen);
|
|
5505
|
+
} catch (err) {
|
|
5506
|
+
logger.warn(
|
|
5507
|
+
{ app: chosen.id, err: err.message },
|
|
5508
|
+
"application select failed; re-prompting"
|
|
5509
|
+
);
|
|
5510
|
+
messages.push(
|
|
5511
|
+
`Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
|
|
5512
|
+
);
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
async function confirmEnvApplication(env, current) {
|
|
5517
|
+
const useEnv = `Use ${env.id} (from ${env.file})`;
|
|
5518
|
+
const choice = await useWizard.getState().requestUserInput({
|
|
5519
|
+
prompt: "Select an application",
|
|
5520
|
+
promptType: "multipleChoice",
|
|
5521
|
+
options: [
|
|
5522
|
+
useEnv,
|
|
5523
|
+
current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
|
|
5524
|
+
],
|
|
5525
|
+
messages: [
|
|
5526
|
+
`${env.file} already sets ${env.name}=${env.id}.`,
|
|
5527
|
+
"Which Algolia application should the wizard work in?"
|
|
5528
|
+
]
|
|
5529
|
+
});
|
|
5530
|
+
return choice === useEnv;
|
|
5531
|
+
}
|
|
5532
|
+
async function selectEnvApplication(env) {
|
|
5533
|
+
try {
|
|
5534
|
+
return await selectAndReport({ id: env.id, name: "" });
|
|
5535
|
+
} catch (err) {
|
|
5536
|
+
logger.warn(
|
|
5537
|
+
{ app: env.id, err: err.message },
|
|
5538
|
+
"could not select the application named in env; falling back to the picker"
|
|
5539
|
+
);
|
|
5540
|
+
return promptForApplication([
|
|
5541
|
+
`Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
|
|
5542
|
+
]);
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
async function ensureApplication() {
|
|
5546
|
+
const current = await currentApplication();
|
|
5547
|
+
const env = await findEnvApplicationId();
|
|
5548
|
+
if (env && env.id !== current?.id && await confirmEnvApplication(env, current)) {
|
|
5549
|
+
return selectEnvApplication(env);
|
|
5550
|
+
}
|
|
5551
|
+
return current ?? await promptForApplication();
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5296
5554
|
// src/lib/seed.ts
|
|
5297
5555
|
var projectScan2 = {
|
|
5298
5556
|
languages: [{ name: "TypeScript", version: "5.7.2" }],
|