@algolia/wizard 0.6.0 → 0.7.0-rc.57.36

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.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/main.js +516 -235
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,8 +12,22 @@ npx @algolia/wizard
12
12
 
13
13
  # run a specific workflow by id
14
14
  npx @algolia/wizard <workflow-id>
15
+
16
+ # see all options
17
+ npx @algolia/wizard --help
15
18
  ```
16
19
 
20
+ ### Options
21
+
22
+ | Flag | Effect |
23
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
24
+ | `--seed <step-id>` | Start the workflow at the step with this id (e.g. `--seed ingestion`), with the earlier steps pre-filled with test data. Pass with no value to print the step ids. |
25
+ | `--no-telemetry` | Send no telemetry or analytics for this run. |
26
+ | `--reset-on-run` | Wipe this project's wizard state (run state, AI-changes consent, worktrees) before starting, so the run behaves like a first-ever run. Credentials are untouched. |
27
+ | `-h`, `--help` | Print usage. |
28
+
29
+ `--seed` pre-fills the earlier steps with fabricated data so a single step can be exercised without running the whole workflow — useful for testing a step, not for a real implementation. It replaces any in-progress run for that workflow and pre-grants the AI-changes consent. See [Starting mid-workflow](CONTRIBUTING.md#starting-mid-workflow---seed).
30
+
17
31
  On first run, Wizard checks for an Algolia profile. If none exists it launches `algolia auth login` and writes a default profile.
18
32
 
19
33
  You'll also be asked once to consent to AI-authored changes to the repository.
package/dist/main.js CHANGED
@@ -76,9 +76,9 @@ function refreshAuthToken() {
76
76
  inFlightRefresh ??= (async () => {
77
77
  try {
78
78
  const raw = await runAlgoliaCli(["auth", "get", "--with-access-token"]);
79
- const user2 = toUserInfo(JSON.parse(raw));
80
- useWizard.getState().setUser(user2);
81
- return user2.token;
79
+ const user = toUserInfo(JSON.parse(raw));
80
+ useWizard.getState().setUser(user);
81
+ return user.token;
82
82
  } catch {
83
83
  return null;
84
84
  } finally {
@@ -210,9 +210,9 @@ var useWizard = create((set, get) => ({
210
210
  }
211
211
  });
212
212
  }),
213
- startWorkflow: (workflow2, steps) => set({
213
+ startWorkflow: (workflow, steps) => set({
214
214
  phase: "running",
215
- workflow: workflow2,
215
+ workflow,
216
216
  steps,
217
217
  currentStepIndex: 0,
218
218
  error: null
@@ -222,7 +222,7 @@ var useWizard = create((set, get) => ({
222
222
  get()._clearNoticeQueue();
223
223
  set({ phase: "running", currentStepIndex: index, output: "", notices: [] });
224
224
  },
225
- setUser: (user2) => set({ user: user2 }),
225
+ setUser: (user) => set({ user }),
226
226
  appendToken: (text) => set((s) => ({ output: s.output + text })),
227
227
  clearOutput: () => set({ output: "" }),
228
228
  // Renders the first notice of a burst immediately, then holds later
@@ -1282,147 +1282,13 @@ function Logs() {
1282
1282
 
1283
1283
  // src/lib/events.ts
1284
1284
  import "zod";
1285
- function track(event, payload) {
1286
- const token = getAuthToken();
1287
- if (!token) return;
1288
- const userId = useWizard.getState().user?.userId;
1289
- if (!userId) return;
1290
- void proxyFetch(`${PROXY_BASE_URL}/events`, {
1291
- method: "POST",
1292
- headers: {
1293
- "content-type": "application/json",
1294
- authorization: `Bearer ${token}`
1295
- },
1296
- body: JSON.stringify({ userId, event, properties: payload })
1297
- }).catch((err) => {
1298
- logger.warn({ err, event }, "failed to send analytics event");
1299
- });
1300
- }
1301
-
1302
- // src/ui/App.tsx
1303
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1304
- function App() {
1305
- const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1306
- const { exit } = useApp();
1307
- const { columns, rows } = useWindowSize7();
1308
- const [showLogs, setShowLogs] = useState6(false);
1309
- const finished = phase === "done" || phase === "error";
1310
- const currentStep = steps[currentStepIndex];
1311
- useInput6(
1312
- (_input, key) => {
1313
- if (key.return) {
1314
- exit();
1315
- }
1316
- },
1317
- { isActive: finished }
1318
- );
1319
- useInput6((_input, key) => {
1320
- if (phase === "idle" || phase === "preflight") return;
1321
- if (key.tab) {
1322
- setShowLogs(!showLogs);
1323
- track("AI Wizard Interaction", {
1324
- context: "global",
1325
- key: "tab",
1326
- currentStep: currentStep?.id
1327
- });
1328
- }
1329
- });
1330
- const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1331
- useInput6((_input, key) => {
1332
- if (escOwnedElsewhere) return;
1333
- if (key.escape) {
1334
- track("AI Wizard Interaction", {
1335
- context: "global",
1336
- key: "esc",
1337
- // No step is active until `startWorkflow` — report the phase instead.
1338
- currentStep: currentStep?.id ?? phase
1339
- });
1340
- exit();
1341
- }
1342
- });
1343
- const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1344
- const flexDirection = columns > 90 ? "row" : "column";
1345
- const showSidebar = flexDirection === "row";
1346
- return /* @__PURE__ */ jsxs12(
1347
- Box13,
1348
- {
1349
- backgroundColor: COLORS.bg.main,
1350
- flexDirection: "row",
1351
- width: columns,
1352
- minHeight: rows,
1353
- children: [
1354
- mainWindowVisible && /* @__PURE__ */ jsxs12(
1355
- Box13,
1356
- {
1357
- flexDirection,
1358
- width: "100%",
1359
- justifyContent: "space-between",
1360
- children: [
1361
- showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1362
- /* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
1363
- /* @__PURE__ */ jsxs12(
1364
- Box13,
1365
- {
1366
- flexDirection: "column",
1367
- paddingX: 4,
1368
- paddingY: 2,
1369
- width: showSidebar ? 70 : "100%",
1370
- flexGrow: showSidebar ? 1 : 0,
1371
- children: [
1372
- /* @__PURE__ */ jsx13(Notices, {}),
1373
- /* @__PURE__ */ jsx13(PromptInput, {}),
1374
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1375
- phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1376
- "\u2716 ",
1377
- error
1378
- ] }) })
1379
- ]
1380
- }
1381
- )
1382
- ),
1383
- showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1384
- ]
1385
- }
1386
- ),
1387
- (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1388
- ]
1389
- }
1390
- );
1391
- }
1392
-
1393
- // src/core/orchestrator.ts
1394
- import "zod";
1395
-
1396
- // src/core/config.ts
1397
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1398
- import { join as join5 } from "node:path";
1399
- var configFile = () => join5(stateDir(), "config.json");
1400
- var DEFAULT_CONFIG = {
1401
- version: 1,
1402
- aiConsent: false,
1403
- workflowsRun: []
1404
- };
1405
- async function loadConfig() {
1406
- try {
1407
- const raw = await readFile2(configFile(), "utf8");
1408
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1409
- } catch {
1410
- return { ...DEFAULT_CONFIG };
1411
- }
1412
- }
1413
- async function saveConfig(config) {
1414
- await mkdir2(stateDir(), { recursive: true });
1415
- await writeFile2(configFile(), JSON.stringify(config, null, 2), "utf8");
1416
- }
1417
- async function recordWorkflowRun(workflowId, completedAt) {
1418
- const config = await loadConfig();
1419
- config.workflowsRun.push({ workflowId, completedAt });
1420
- await saveConfig(config);
1421
- }
1422
1285
 
1423
1286
  // src/lib/telemetry.ts
1287
+ function isTelemetryOptedOut() {
1288
+ return process.env.WIZARD_TELEMETRY === "false";
1289
+ }
1424
1290
  function isTelemetryEnabled() {
1425
- return Boolean(getAuthToken()) && !process.env.VITEST && process.env.WIZARD_TELEMETRY !== "false";
1291
+ return Boolean(getAuthToken()) && !process.env.VITEST && !isTelemetryOptedOut();
1426
1292
  }
1427
1293
  function fireAndForget(promise) {
1428
1294
  void promise.catch(() => {
@@ -1582,6 +1448,146 @@ function trackWorkflowError(ctx) {
1582
1448
  );
1583
1449
  }
1584
1450
 
1451
+ // src/lib/events.ts
1452
+ function track(event, payload) {
1453
+ if (isTelemetryOptedOut()) return;
1454
+ const token = getAuthToken();
1455
+ if (!token) return;
1456
+ const userId = useWizard.getState().user?.userId;
1457
+ if (!userId) return;
1458
+ void proxyFetch(`${PROXY_BASE_URL}/events`, {
1459
+ method: "POST",
1460
+ headers: {
1461
+ "content-type": "application/json",
1462
+ authorization: `Bearer ${token}`
1463
+ },
1464
+ body: JSON.stringify({ userId, event, properties: payload })
1465
+ }).catch((err) => {
1466
+ logger.warn({ err, event }, "failed to send analytics event");
1467
+ });
1468
+ }
1469
+
1470
+ // src/ui/App.tsx
1471
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1472
+ function App() {
1473
+ const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1474
+ const { exit } = useApp();
1475
+ const { columns, rows } = useWindowSize7();
1476
+ const [showLogs, setShowLogs] = useState6(false);
1477
+ const finished = phase === "done" || phase === "error";
1478
+ const currentStep = steps[currentStepIndex];
1479
+ useInput6(
1480
+ (_input, key) => {
1481
+ if (key.return) {
1482
+ exit();
1483
+ }
1484
+ },
1485
+ { isActive: finished }
1486
+ );
1487
+ useInput6((_input, key) => {
1488
+ if (phase === "idle" || phase === "preflight") return;
1489
+ if (key.tab) {
1490
+ setShowLogs(!showLogs);
1491
+ track("AI Wizard Interaction", {
1492
+ context: "global",
1493
+ key: "tab",
1494
+ currentStep: currentStep?.id
1495
+ });
1496
+ }
1497
+ });
1498
+ const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "enterToContinue";
1499
+ useInput6((_input, key) => {
1500
+ if (escOwnedElsewhere) return;
1501
+ if (key.escape) {
1502
+ track("AI Wizard Interaction", {
1503
+ context: "global",
1504
+ key: "esc",
1505
+ // No step is active until `startWorkflow` — report the phase instead.
1506
+ currentStep: currentStep?.id ?? phase
1507
+ });
1508
+ exit();
1509
+ }
1510
+ });
1511
+ const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1512
+ const flexDirection = columns > 90 ? "row" : "column";
1513
+ const showSidebar = flexDirection === "row";
1514
+ return /* @__PURE__ */ jsxs12(
1515
+ Box13,
1516
+ {
1517
+ backgroundColor: COLORS.bg.main,
1518
+ flexDirection: "row",
1519
+ width: columns,
1520
+ minHeight: rows,
1521
+ children: [
1522
+ mainWindowVisible && /* @__PURE__ */ jsxs12(
1523
+ Box13,
1524
+ {
1525
+ flexDirection,
1526
+ width: "100%",
1527
+ justifyContent: "space-between",
1528
+ children: [
1529
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
1530
+ /* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
1531
+ /* @__PURE__ */ jsxs12(
1532
+ Box13,
1533
+ {
1534
+ flexDirection: "column",
1535
+ paddingX: 4,
1536
+ paddingY: 2,
1537
+ width: showSidebar ? 70 : "100%",
1538
+ flexGrow: showSidebar ? 1 : 0,
1539
+ children: [
1540
+ /* @__PURE__ */ jsx13(Notices, {}),
1541
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1542
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1543
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1544
+ "\u2716 ",
1545
+ error
1546
+ ] }) })
1547
+ ]
1548
+ }
1549
+ )
1550
+ ),
1551
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1552
+ ]
1553
+ }
1554
+ ),
1555
+ (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1556
+ ]
1557
+ }
1558
+ );
1559
+ }
1560
+
1561
+ // src/core/orchestrator.ts
1562
+ import "zod";
1563
+
1564
+ // src/core/config.ts
1565
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1566
+ import { join as join5 } from "node:path";
1567
+ var configFile = () => join5(stateDir(), "config.json");
1568
+ var DEFAULT_CONFIG = {
1569
+ version: 1,
1570
+ aiConsent: false,
1571
+ workflowsRun: []
1572
+ };
1573
+ async function loadConfig() {
1574
+ try {
1575
+ const raw = await readFile2(configFile(), "utf8");
1576
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1577
+ } catch {
1578
+ return { ...DEFAULT_CONFIG };
1579
+ }
1580
+ }
1581
+ async function saveConfig(config) {
1582
+ await mkdir2(stateDir(), { recursive: true });
1583
+ await writeFile2(configFile(), JSON.stringify(config, null, 2), "utf8");
1584
+ }
1585
+ async function recordWorkflowRun(workflowId, completedAt) {
1586
+ const config = await loadConfig();
1587
+ config.workflowsRun.push({ workflowId, completedAt });
1588
+ await saveConfig(config);
1589
+ }
1590
+
1585
1591
  // src/core/orchestrator.ts
1586
1592
  function defineStep(step) {
1587
1593
  return { visible: true, ...step };
@@ -1594,23 +1600,23 @@ function ensureExecutedStepCount(state) {
1594
1600
  ).length;
1595
1601
  }
1596
1602
  }
1597
- function reconcileWorkflowState(state, workflow2) {
1603
+ function reconcileWorkflowState(state, workflow) {
1598
1604
  const persistedIds = state.steps.map((s) => s.id).join("\n");
1599
- const definedIds = workflow2.steps.map((s) => s.id).join("\n");
1605
+ const definedIds = workflow.steps.map((s) => s.id).join("\n");
1600
1606
  if (persistedIds !== definedIds) return null;
1601
1607
  for (let i = 0; i < state.steps.length; i++) {
1602
- state.steps[i].title = workflow2.steps[i].title;
1603
- state.steps[i].visible = workflow2.steps[i].visible;
1608
+ state.steps[i].title = workflow.steps[i].title;
1609
+ state.steps[i].visible = workflow.steps[i].visible;
1604
1610
  }
1605
1611
  return state;
1606
1612
  }
1607
- function initWorkflowState(workflow2, now) {
1613
+ function initWorkflowState(workflow, now) {
1608
1614
  return {
1609
- workflowId: workflow2.id,
1615
+ workflowId: workflow.id,
1610
1616
  startedAt: now,
1611
1617
  updatedAt: now,
1612
1618
  currentStepIndex: 0,
1613
- steps: workflow2.steps.map((s) => ({
1619
+ steps: workflow.steps.map((s) => ({
1614
1620
  id: s.id,
1615
1621
  title: s.title,
1616
1622
  visible: s.visible,
@@ -1623,8 +1629,8 @@ function initWorkflowState(workflow2, now) {
1623
1629
  async function ensureConsent() {
1624
1630
  const config = await loadConfig();
1625
1631
  if (config.aiConsent) return;
1626
- const store2 = useWizard.getState();
1627
- const answer = await store2.requestUserInput({
1632
+ const store = useWizard.getState();
1633
+ const answer = await store.requestUserInput({
1628
1634
  prompt: "Wizard will make AI-authored changes to this repository.",
1629
1635
  promptType: "enterToContinue",
1630
1636
  options: []
@@ -1670,7 +1676,7 @@ async function makeContext(state) {
1670
1676
  };
1671
1677
  }
1672
1678
  async function runStep(state, index, step, appId) {
1673
- const store2 = useWizard.getState();
1679
+ const store = useWizard.getState();
1674
1680
  const record = state.steps[index];
1675
1681
  const startedAt = Date.now();
1676
1682
  trackActionStart({
@@ -1685,8 +1691,8 @@ async function runStep(state, index, step, appId) {
1685
1691
  state.executedStepCount = (state.executedStepCount ?? 0) + 1;
1686
1692
  }
1687
1693
  state.updatedAt = nowIso();
1688
- store2.setActiveStep(index);
1689
- store2.syncSteps([...state.steps], index);
1694
+ store.setActiveStep(index);
1695
+ store.syncSteps([...state.steps], index);
1690
1696
  await saveWorkflowState(state);
1691
1697
  await markInteraction();
1692
1698
  const ctx = await makeContext(state);
@@ -1696,7 +1702,7 @@ async function runStep(state, index, step, appId) {
1696
1702
  record.status = "done";
1697
1703
  record.output = output;
1698
1704
  state.updatedAt = nowIso();
1699
- store2.syncSteps([...state.steps], index);
1705
+ store.syncSteps([...state.steps], index);
1700
1706
  await saveWorkflowState(state);
1701
1707
  trackActionEnd({
1702
1708
  workflowId: state.workflowId,
@@ -1706,29 +1712,29 @@ async function runStep(state, index, step, appId) {
1706
1712
  durationMs: Date.now() - startedAt
1707
1713
  });
1708
1714
  }
1709
- async function runWorkflow(workflow2, appId) {
1710
- const store2 = useWizard.getState();
1715
+ async function runWorkflow(workflow, appId) {
1716
+ const store = useWizard.getState();
1711
1717
  try {
1712
- const persisted = await loadWorkflowState(workflow2.id);
1713
- const state = (persisted && reconcileWorkflowState(persisted, workflow2)) ?? initWorkflowState(workflow2, nowIso());
1718
+ const persisted = await loadWorkflowState(workflow.id);
1719
+ const state = (persisted && reconcileWorkflowState(persisted, workflow)) ?? initWorkflowState(workflow, nowIso());
1714
1720
  ensureExecutedStepCount(state);
1715
- store2.startWorkflow(
1721
+ store.startWorkflow(
1716
1722
  {
1717
- id: workflow2.id,
1718
- title: workflow2.title,
1719
- description: workflow2.description
1723
+ id: workflow.id,
1724
+ title: workflow.title,
1725
+ description: workflow.description
1720
1726
  },
1721
1727
  [...state.steps]
1722
1728
  );
1723
1729
  await ensureConsent();
1724
- trackWorkflowStart({ workflowId: workflow2.id, appId });
1725
- for (let i = state.currentStepIndex; i < workflow2.steps.length; i++) {
1726
- await runStep(state, i, workflow2.steps[i], appId);
1730
+ trackWorkflowStart({ workflowId: workflow.id, appId });
1731
+ for (let i = state.currentStepIndex; i < workflow.steps.length; i++) {
1732
+ await runStep(state, i, workflow.steps[i], appId);
1727
1733
  }
1728
1734
  const totalDurationMs = Date.now() - Date.parse(state.startedAt);
1729
1735
  const stepCount = state.executedStepCount ?? state.steps.filter((s) => s.status === "done" && isStepVisible(s)).length;
1730
1736
  trackWizardComplete({
1731
- workflowId: workflow2.id,
1737
+ workflowId: workflow.id,
1732
1738
  appId,
1733
1739
  total_duration: Math.round(totalDurationMs / 1e3),
1734
1740
  total_steps: stepCount
@@ -1737,12 +1743,12 @@ async function runWorkflow(workflow2, appId) {
1737
1743
  total_duration_ms: totalDurationMs,
1738
1744
  step_count: stepCount
1739
1745
  });
1740
- await recordWorkflowRun(workflow2.id, nowIso());
1741
- await clearWorkflowState(workflow2.id);
1742
- store2.setDone();
1746
+ await recordWorkflowRun(workflow.id, nowIso());
1747
+ await clearWorkflowState(workflow.id);
1748
+ store.setDone();
1743
1749
  } catch (err) {
1744
1750
  const message = err instanceof Error ? err.message : String(err);
1745
- const state = await loadWorkflowState(workflow2.id);
1751
+ const state = await loadWorkflowState(workflow.id);
1746
1752
  let step = "unknown";
1747
1753
  let failedActionId;
1748
1754
  let failedActionTitle;
@@ -1754,7 +1760,7 @@ async function runWorkflow(workflow2, appId) {
1754
1760
  record.status = "error";
1755
1761
  record.error = message;
1756
1762
  await saveWorkflowState(state);
1757
- store2.syncSteps([...state.steps], state.currentStepIndex);
1763
+ store.syncSteps([...state.steps], state.currentStepIndex);
1758
1764
  }
1759
1765
  failedActionId = record.id;
1760
1766
  failedActionTitle = record.title;
@@ -1762,7 +1768,7 @@ async function runWorkflow(workflow2, appId) {
1762
1768
  }
1763
1769
  if (failedActionId && failedActionTitle) {
1764
1770
  trackActionError({
1765
- workflowId: workflow2.id,
1771
+ workflowId: workflow.id,
1766
1772
  appId,
1767
1773
  actionId: failedActionId,
1768
1774
  actionTitle: failedActionTitle,
@@ -1770,7 +1776,7 @@ async function runWorkflow(workflow2, appId) {
1770
1776
  });
1771
1777
  }
1772
1778
  trackWorkflowError({
1773
- workflowId: workflow2.id,
1779
+ workflowId: workflow.id,
1774
1780
  appId,
1775
1781
  error: message,
1776
1782
  actionId: failedActionId
@@ -1780,7 +1786,7 @@ async function runWorkflow(workflow2, appId) {
1780
1786
  error: message,
1781
1787
  product_area: "AI Wizard"
1782
1788
  });
1783
- store2.setError(message);
1789
+ store.setError(message);
1784
1790
  }
1785
1791
  }
1786
1792
 
@@ -1820,13 +1826,13 @@ async function loadActiveProfile() {
1820
1826
  } catch {
1821
1827
  profiles = [];
1822
1828
  }
1823
- const profile2 = profiles[0];
1824
- if (!profile2) {
1829
+ const profile = profiles[0];
1830
+ if (!profile) {
1825
1831
  throw new Error(
1826
1832
  "No Algolia profile is configured. Run `npx @algolia/cli auth login` to authenticate."
1827
1833
  );
1828
1834
  }
1829
- return profile2;
1835
+ return profile;
1830
1836
  }
1831
1837
 
1832
1838
  // src/workflows/default.ts
@@ -2116,9 +2122,9 @@ function writeCredentialsTool(ctx) {
2116
2122
  logger.info({ filePath }, "called writeCredentials tool");
2117
2123
  const resolved = resolveInRoot(ctx, filePath);
2118
2124
  if (resolved.ok === false) return resolved.error;
2119
- let profile2;
2125
+ let profile;
2120
2126
  try {
2121
- profile2 = await loadActiveProfile();
2127
+ profile = await loadActiveProfile();
2122
2128
  } catch {
2123
2129
  return "Error: no Algolia profile is configured, so credentials cannot be written. Ask the user to authenticate with the Algolia CLI first.";
2124
2130
  }
@@ -2139,8 +2145,8 @@ function writeCredentialsTool(ctx) {
2139
2145
  return `Skipped: ${filePath} already defines ${present.join(" and ")}. Leaving existing credentials untouched.`;
2140
2146
  }
2141
2147
  const envWithCredentials = appendEnv(existing, [
2142
- [APP_ID_VAR, profile2.appId],
2143
- [API_KEY_VAR, profile2.apiKey]
2148
+ [APP_ID_VAR, profile.appId],
2149
+ [API_KEY_VAR, profile.apiKey]
2144
2150
  ]);
2145
2151
  await mkdir4(dirname5(resolved.target), { recursive: true });
2146
2152
  await writeFile4(resolved.target, envWithCredentials, "utf8");
@@ -2695,7 +2701,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2695
2701
  // package.json
2696
2702
  var package_default = {
2697
2703
  name: "@algolia/wizard",
2698
- version: "0.6.0",
2704
+ version: "0.7.0-rc.57.36",
2699
2705
  description: "Magically implement Algolia functionality in your codebase",
2700
2706
  type: "module",
2701
2707
  engines: {
@@ -3914,27 +3920,27 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3914
3920
  messages: []
3915
3921
  }) === true;
3916
3922
  if (runNow) {
3917
- const profile2 = await loadActiveProfile();
3923
+ const profile = await loadActiveProfile();
3918
3924
  ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
3919
3925
  const scriptLogId = ctx.logStart("runIngestScript", {
3920
3926
  runtime: ingestRuntime,
3921
3927
  entrypoint: ingestEntrypoint
3922
3928
  });
3923
3929
  const startedAt = Date.now();
3924
- const run = await runIngestScript(
3930
+ const run2 = await runIngestScript(
3925
3931
  worktree,
3926
3932
  ingestRuntime,
3927
3933
  ingestEntrypoint,
3928
3934
  {
3929
- [APP_ID_VAR]: profile2.appId,
3930
- [API_KEY_VAR]: profile2.apiKey
3935
+ [APP_ID_VAR]: profile.appId,
3936
+ [API_KEY_VAR]: profile.apiKey
3931
3937
  }
3932
3938
  );
3933
- ctx.logEnd(scriptLogId, run.ok ? "success" : "error");
3934
- ingestScriptRan = run.ran && run.ok;
3939
+ ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
3940
+ ingestScriptRan = run2.ran && run2.ok;
3935
3941
  if (ingestScriptRan) {
3936
3942
  ingestDurationMs = Date.now() - startedAt;
3937
- ingestRecordCount = parseIngestRecordCount(run.output);
3943
+ ingestRecordCount = parseIngestRecordCount(run2.output);
3938
3944
  if (ingestRecordCount != null) {
3939
3945
  track("AI Wizard Ingest Successful", {
3940
3946
  entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
@@ -3945,43 +3951,43 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3945
3951
  }
3946
3952
  let summaryLine;
3947
3953
  let outcomeMessage;
3948
- if (!run.ran) {
3949
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run.reason}`;
3950
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run.reason}`;
3954
+ if (!run2.ran) {
3955
+ summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
3956
+ outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
3951
3957
  logger.warn(
3952
3958
  {
3953
3959
  runtime: ingestRuntime,
3954
3960
  entrypoint: ingestEntrypoint,
3955
- reason: run.reason
3961
+ reason: run2.reason
3956
3962
  },
3957
3963
  "implement: refused to auto-run ingestion script"
3958
3964
  );
3959
3965
  track("Error", {
3960
3966
  step: "Push Data",
3961
- error: `ingestion script skipped: ${run.reason}`,
3967
+ error: `ingestion script skipped: ${run2.reason}`,
3962
3968
  product_area: "AI Wizard"
3963
3969
  });
3964
- } else if (run.ok) {
3970
+ } else if (run2.ok) {
3965
3971
  const status = "Ingestion run: succeeded.";
3966
- summaryLine = run.output ? `${status}
3967
- ${run.output}` : status;
3972
+ summaryLine = run2.output ? `${status}
3973
+ ${run2.output}` : status;
3968
3974
  outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
3969
3975
  } else {
3970
3976
  const status = "\u26A0\uFE0F Ingestion run failed:";
3971
- summaryLine = run.output ? `${status}
3972
- ${run.output}` : status;
3973
- outcomeMessage = `\u274C Ingestion failed.${run.output ? ` ${run.output}` : ""}`;
3977
+ summaryLine = run2.output ? `${status}
3978
+ ${run2.output}` : status;
3979
+ outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
3974
3980
  logger.warn(
3975
3981
  {
3976
3982
  runtime: ingestRuntime,
3977
3983
  entrypoint: ingestEntrypoint,
3978
- output: run.output
3984
+ output: run2.output
3979
3985
  },
3980
3986
  "implement: ingestion script run failed"
3981
3987
  );
3982
3988
  track("Error", {
3983
3989
  step: "Push Data",
3984
- error: run.output || "ingestion script exited non-zero",
3990
+ error: run2.output || "ingestion script exited non-zero",
3985
3991
  product_area: "AI Wizard"
3986
3992
  });
3987
3993
  }
@@ -4171,10 +4177,10 @@ var defaultWorkflow = {
4171
4177
  ctx.notify({
4172
4178
  messages: ["Building your Algolia search experience\u2026"]
4173
4179
  });
4174
- const ingestion = ctx.getStepOutput(
4180
+ const ingestion2 = ctx.getStepOutput(
4175
4181
  "ingestion"
4176
4182
  );
4177
- return implement(ctx, ["search"], ingestion?.worktreePath);
4183
+ return implement(ctx, ["search"], ingestion2?.worktreePath);
4178
4184
  }
4179
4185
  }),
4180
4186
  defineStep({
@@ -4185,14 +4191,14 @@ var defaultWorkflow = {
4185
4191
  ctx.notify({
4186
4192
  messages: ["Summarizing what we did\u2026"]
4187
4193
  });
4188
- const ingestion = ctx.getStepOutput(
4194
+ const ingestion2 = ctx.getStepOutput(
4189
4195
  "ingestion"
4190
4196
  );
4191
4197
  return reviewStep(ctx, {
4192
4198
  // The ingestion step already showed the user the exact `ingestCommand`
4193
4199
  // and worktree path as a notice, so nextSteps must not restate it —
4194
4200
  // an LLM-paraphrased command risks being wrong.
4195
- nextStepsGuidance: ingestion?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
4201
+ nextStepsGuidance: ingestion2?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
4196
4202
  });
4197
4203
  }
4198
4204
  })
@@ -4207,38 +4213,313 @@ function getWorkflow(id) {
4207
4213
  return workflows[id];
4208
4214
  }
4209
4215
 
4216
+ // src/lib/seed.ts
4217
+ var projectScan2 = {
4218
+ languages: [{ name: "TypeScript", version: "5.7.2" }],
4219
+ frameworks: [{ name: "Next.js", version: "15.1.0" }],
4220
+ ingestionAnalysis: [
4221
+ {
4222
+ name: "Product",
4223
+ paths: ["src/models/product.ts"],
4224
+ attributes: ["id", "name", "description", "price", "category", "brand"]
4225
+ },
4226
+ {
4227
+ name: "Article",
4228
+ paths: ["src/models/article.ts"],
4229
+ attributes: ["id", "title", "body", "author", "tags", "publishedAt"]
4230
+ }
4231
+ ],
4232
+ searchImplementationAnalysis: "src/components/Header.tsx",
4233
+ verification: ["pnpm typecheck", "pnpm lint"]
4234
+ };
4235
+ var confirmLanguage2 = {
4236
+ languages: projectScan2.languages
4237
+ };
4238
+ var confirmEntities2 = {
4239
+ ingestionAnalysis: projectScan2.ingestionAnalysis,
4240
+ confirmedEntities: [projectScan2.ingestionAnalysis[0]]
4241
+ };
4242
+ var selectIndex = {
4243
+ selection: "wizard_seed_products"
4244
+ };
4245
+ var ingestion = {
4246
+ filesChanged: ["algolia/ingest.mjs", "algolia/records.json", "package.json"],
4247
+ summary: "Generated sample Product records and an ingestion script that pushes them to the target index with algoliasearch.",
4248
+ ingestCommand: "node algolia/ingest.mjs",
4249
+ ingestScriptRan: true,
4250
+ ingestRecordCount: 25,
4251
+ ingestDurationMs: 4200,
4252
+ ingestionSource: "generated"
4253
+ };
4254
+ var confirmFramework2 = {
4255
+ frameworks: projectScan2.frameworks
4256
+ };
4257
+ var search = {
4258
+ filesChanged: ["src/components/Search.tsx", "src/components/Header.tsx", ".env"],
4259
+ summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
4260
+ ingestionSource: "generated",
4261
+ searchEnvVars: [
4262
+ { name: "NEXT_PUBLIC_ALGOLIA_APP_ID", value: "SEEDAPPID" },
4263
+ { name: "NEXT_PUBLIC_ALGOLIA_SEARCH_KEY", value: "seedsearchkey" }
4264
+ ]
4265
+ };
4266
+ var review = {
4267
+ summaryPoints: [
4268
+ "Ingested 25 generated Product records into wizard_seed_products.",
4269
+ "Added an InstantSearch search experience to the shared header."
4270
+ ],
4271
+ reviewPrompt: "Review the Algolia ingestion and search changes in this worktree.",
4272
+ nextSteps: ["Point the ingestion script at your real product data."]
4273
+ };
4274
+ var SEEDS = {
4275
+ default: {
4276
+ "project-scan": projectScan2,
4277
+ "confirm-language": confirmLanguage2,
4278
+ "confirm-entities": confirmEntities2,
4279
+ "select-index": selectIndex,
4280
+ ingestion,
4281
+ "confirm-framework": confirmFramework2,
4282
+ search,
4283
+ review
4284
+ }
4285
+ };
4286
+ var SEED_USER_INPUTS = {
4287
+ default: {
4288
+ index: selectIndex.selection,
4289
+ confirmedEntities: confirmEntities2.confirmedEntities,
4290
+ ingestionAnalysis: confirmEntities2.ingestionAnalysis,
4291
+ implementation: "success"
4292
+ }
4293
+ };
4294
+ function formatStepList(workflow, startIndex) {
4295
+ return workflow.steps.map((step, i) => {
4296
+ const marker = i === startIndex ? "\u2192" : " ";
4297
+ const hidden = step.visible ? "" : " (hidden)";
4298
+ return ` ${marker} ${step.id} \u2014 ${step.title}${hidden}`;
4299
+ }).join("\n");
4300
+ }
4301
+ var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString();
4302
+ function buildSeedState(workflow, startIndex) {
4303
+ const seeds = SEEDS[workflow.id] ?? {};
4304
+ const now = nowIso2();
4305
+ const steps = workflow.steps.map((step, i) => {
4306
+ const base = {
4307
+ id: step.id,
4308
+ title: step.title,
4309
+ visible: step.visible,
4310
+ status: i < startIndex ? "done" : "pending"
4311
+ };
4312
+ if (i >= startIndex) return base;
4313
+ if (!(step.id in seeds)) {
4314
+ throw new Error(
4315
+ `No seed data for step "${step.id}" (step ${i + 1} of ${workflow.id}). Add it to SEEDS['${workflow.id}'] in src/lib/seed.ts.`
4316
+ );
4317
+ }
4318
+ let output;
4319
+ try {
4320
+ output = step.outputSchema.parse(seeds[step.id]);
4321
+ } catch (err) {
4322
+ throw new Error(
4323
+ `Seed data for step "${step.id}" does not match its outputSchema: ` + (err instanceof Error ? err.message : String(err))
4324
+ );
4325
+ }
4326
+ return { ...base, output };
4327
+ });
4328
+ return {
4329
+ workflowId: workflow.id,
4330
+ startedAt: now,
4331
+ updatedAt: now,
4332
+ currentStepIndex: startIndex,
4333
+ steps,
4334
+ algoliaState: {},
4335
+ userInputs: startIndex > 0 ? { ...SEED_USER_INPUTS[workflow.id] } : {}
4336
+ };
4337
+ }
4338
+ async function seedWorkflowState(workflow, value) {
4339
+ if (value === null || value.trim() === "") {
4340
+ throw new Error("--seed requires a step id.");
4341
+ }
4342
+ const stepId = value.trim();
4343
+ const startIndex = workflow.steps.findIndex((step) => step.id === stepId);
4344
+ if (startIndex === -1) {
4345
+ throw new Error(
4346
+ `--seed must name a step in "${workflow.id}", got "${stepId}".` + (/^\d+$/.test(stepId) ? " Step numbers are not accepted \u2014 pass the id." : "")
4347
+ );
4348
+ }
4349
+ const messages = [];
4350
+ const config = await loadConfig();
4351
+ if (!config.aiConsent) {
4352
+ config.aiConsent = true;
4353
+ await saveConfig(config);
4354
+ messages.push("Seed: set aiConsent in the project config (skips the consent prompt).");
4355
+ }
4356
+ if (startIndex === 0) {
4357
+ await clearWorkflowState(workflow.id);
4358
+ messages.push(
4359
+ `Seed: "${stepId}" is the first step, so this is a fresh run; cleared any existing state for "${workflow.id}".`,
4360
+ formatStepList(workflow, startIndex)
4361
+ );
4362
+ return { messages };
4363
+ }
4364
+ const existing = await loadWorkflowState(workflow.id);
4365
+ if (existing && existing.currentStepIndex > 0) {
4366
+ const wasAt = workflow.steps[existing.currentStepIndex];
4367
+ messages.push(
4368
+ `Seed: discarded in-progress state for "${workflow.id}" (was at ${wasAt ? `"${wasAt.id}"` : "the end"}).`
4369
+ );
4370
+ }
4371
+ const state = buildSeedState(workflow, startIndex);
4372
+ await saveWorkflowState(state);
4373
+ messages.push(
4374
+ `Seed: "${workflow.id}" will resume at "${stepId}" with ${startIndex} step(s) pre-filled.`,
4375
+ formatStepList(workflow, startIndex)
4376
+ );
4377
+ return { messages };
4378
+ }
4379
+
4380
+ // src/lib/cli.ts
4381
+ var USAGE = `Usage: wizard [workflow-id] [options]
4382
+
4383
+ Options:
4384
+ --seed <step-id> Start the workflow at the step with this id, with earlier
4385
+ steps pre-filled with test data. Pass with no value to print
4386
+ the step ids. See CONTRIBUTING.md.
4387
+ --no-telemetry Send no telemetry or analytics for this run.
4388
+ --reset-on-run Wipe this project's wizard state (run state, AI consent,
4389
+ worktrees) before starting, so the run behaves like a
4390
+ first-ever run. Algolia credentials are not touched.
4391
+ -h, --help Print this message.`;
4392
+ function parseCliArgs(argv) {
4393
+ const positionals = [];
4394
+ let seed;
4395
+ let telemetry = true;
4396
+ let resetOnRun = false;
4397
+ let help = false;
4398
+ for (let i = 0; i < argv.length; i++) {
4399
+ const arg = argv[i];
4400
+ if (arg === "--seed") {
4401
+ const next = argv[i + 1];
4402
+ if (next !== void 0 && !next.startsWith("-")) {
4403
+ seed = next;
4404
+ i++;
4405
+ } else {
4406
+ seed = null;
4407
+ }
4408
+ } else if (arg.startsWith("--seed=")) {
4409
+ seed = arg.slice("--seed=".length);
4410
+ } else if (arg === "--no-telemetry") {
4411
+ telemetry = false;
4412
+ } else if (arg === "--reset-on-run") {
4413
+ resetOnRun = true;
4414
+ } else if (arg === "--help" || arg === "-h") {
4415
+ help = true;
4416
+ } else if (arg.startsWith("-")) {
4417
+ throw new Error(`Unknown option "${arg}".`);
4418
+ } else {
4419
+ positionals.push(arg);
4420
+ }
4421
+ }
4422
+ return { positionals, seed, telemetry, resetOnRun, help };
4423
+ }
4424
+
4425
+ // src/lib/resetState.ts
4426
+ import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
4427
+ import { join as join12 } from "node:path";
4428
+ var KEEP = ["wizard.log"];
4429
+ async function resetProjectState() {
4430
+ const dir = stateDir();
4431
+ let entries;
4432
+ try {
4433
+ entries = await readdir4(dir);
4434
+ } catch {
4435
+ return { dir, removed: [] };
4436
+ }
4437
+ const targets = entries.filter((name) => !KEEP.includes(name));
4438
+ await Promise.all(
4439
+ targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
4440
+ );
4441
+ return { dir, removed: targets };
4442
+ }
4443
+
4210
4444
  // src/main.tsx
4211
4445
  import { jsx as jsx14 } from "react/jsx-runtime";
4212
- var requestedId = process.argv[2] ?? defaultWorkflow.id;
4213
- var workflow = getWorkflow(requestedId);
4214
- if (!workflow) {
4215
- const available = Object.keys(workflows).join(", ");
4216
- console.error(`Unknown workflow "${requestedId}". Available: ${available}`);
4217
- process.exit(1);
4218
- }
4219
- var store = useWizard.getState();
4220
- var instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4221
- var user = await getUser();
4222
- if (!user) {
4223
- await instance.waitUntilRenderFlush();
4224
- instance.cleanup();
4446
+ async function startup() {
4447
+ let args;
4225
4448
  try {
4226
- await runAuthLogin();
4449
+ args = parseCliArgs(process.argv.slice(2));
4227
4450
  } catch (err) {
4228
4451
  console.error(err instanceof Error ? err.message : String(err));
4229
- process.exit(1);
4452
+ console.error(`
4453
+ ${USAGE}`);
4454
+ return 1;
4230
4455
  }
4231
- instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4232
- user = await getUser();
4233
- if (!user) {
4234
- store.setError(
4235
- "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4456
+ if (args.help) {
4457
+ console.log(USAGE);
4458
+ return 0;
4459
+ }
4460
+ if (!args.telemetry) {
4461
+ process.env.WIZARD_TELEMETRY = "false";
4462
+ console.log(
4463
+ "Telemetry and analytics disabled for this run (--no-telemetry)."
4236
4464
  );
4237
- await instance.waitUntilExit();
4238
- process.exit(1);
4239
4465
  }
4466
+ const requestedId = args.positionals[0] ?? defaultWorkflow.id;
4467
+ const workflow = getWorkflow(requestedId);
4468
+ if (!workflow) {
4469
+ const available = Object.keys(workflows).join(", ");
4470
+ console.error(`Unknown workflow "${requestedId}". Available: ${available}`);
4471
+ return 1;
4472
+ }
4473
+ if (args.resetOnRun) {
4474
+ const { dir, removed } = await resetProjectState();
4475
+ console.log(
4476
+ removed.length > 0 ? `Reset wizard state in ${dir}: ${removed.join(", ")}` : `No wizard state to reset in ${dir}`
4477
+ );
4478
+ }
4479
+ if (args.seed !== void 0) {
4480
+ try {
4481
+ const { messages } = await seedWorkflowState(workflow, args.seed);
4482
+ for (const message of messages) console.log(message);
4483
+ } catch (err) {
4484
+ console.error(err instanceof Error ? err.message : String(err));
4485
+ console.error(`Steps in "${workflow.id}":
4486
+ ${formatStepList(workflow)}`);
4487
+ return 1;
4488
+ }
4489
+ }
4490
+ return workflow;
4491
+ }
4492
+ async function run(workflow) {
4493
+ const store = useWizard.getState();
4494
+ let instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4495
+ let user = await getUser();
4496
+ if (!user) {
4497
+ await instance.waitUntilRenderFlush();
4498
+ instance.cleanup();
4499
+ try {
4500
+ await runAuthLogin();
4501
+ } catch (err) {
4502
+ console.error(err instanceof Error ? err.message : String(err));
4503
+ process.exit(1);
4504
+ }
4505
+ instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
4506
+ user = await getUser();
4507
+ if (!user) {
4508
+ store.setError(
4509
+ "Authentication completed but no Algolia user was returned. Try running `npx @algolia/cli auth login` directly."
4510
+ );
4511
+ await instance.waitUntilExit();
4512
+ process.exit(1);
4513
+ }
4514
+ }
4515
+ store.setUser(user);
4516
+ const profile = await loadActiveProfile();
4517
+ await store.waitForStart();
4518
+ runWorkflow(workflow, profile?.appId);
4519
+ }
4520
+ var started = await startup();
4521
+ if (typeof started === "number") {
4522
+ process.exitCode = started;
4523
+ } else {
4524
+ await run(started);
4240
4525
  }
4241
- store.setUser(user);
4242
- var profile = await loadActiveProfile();
4243
- await store.waitForStart();
4244
- runWorkflow(workflow, profile?.appId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-rc.57.36",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {