@algolia/wizard 0.62.0 → 0.64.0

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 (2) hide show
  1. package/dist/main.js +371 -117
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1697,29 +1697,153 @@ function trackWorkflowError(ctx) {
1697
1697
 
1698
1698
  // src/lib/events.ts
1699
1699
  import "zod";
1700
- function track(event, payload) {
1700
+ var analyticsAppId = void 0;
1701
+ var analyticsAgentRunId = void 0;
1702
+ function setAnalyticsContext(next) {
1703
+ analyticsAppId = next.appId;
1704
+ analyticsAgentRunId = next.agentRunId;
1705
+ }
1706
+ function wizardErrorType(actionId, step, error) {
1707
+ if (actionId === "project-scan" || step === "project scan") {
1708
+ return "Scan Codebase";
1709
+ }
1710
+ if (actionId === "ingestion" || step === "Push Data" || step === "ingest records") {
1711
+ return "Ingest Records";
1712
+ }
1713
+ if (actionId === "search" || step === "create search ui") {
1714
+ if (error && error.toLowerCase().includes("verification failed")) {
1715
+ return "Wire Component to UI";
1716
+ }
1717
+ return "Generate UI";
1718
+ }
1719
+ return void 0;
1720
+ }
1721
+ function commonEventProperties() {
1722
+ const properties = {};
1723
+ if (analyticsAppId) properties.app_id = analyticsAppId;
1724
+ if (analyticsAgentRunId) properties.agent_run_id = analyticsAgentRunId;
1725
+ return properties;
1726
+ }
1727
+ function postAnalytics(path, body, label) {
1701
1728
  if (isTelemetryOptedOut()) return;
1702
1729
  const token = getAuthToken();
1703
1730
  if (!token) return;
1704
1731
  const userId = useWizard.getState().user?.userId;
1705
1732
  if (!userId) return;
1706
- void proxyFetch(`${PROXY_BASE_URL}/events`, {
1733
+ void proxyFetch(`${PROXY_BASE_URL}${path}`, {
1707
1734
  method: "POST",
1708
1735
  headers: {
1709
1736
  "content-type": "application/json",
1710
1737
  authorization: `Bearer ${token}`
1711
1738
  },
1712
- body: JSON.stringify({ userId, event, properties: payload })
1739
+ body: JSON.stringify(body)
1713
1740
  }).catch((err) => {
1714
- logger.warn({ err, event }, "failed to send analytics event");
1741
+ logger.warn({ err, event: label }, "failed to send analytics event");
1715
1742
  });
1716
1743
  }
1744
+ function track(event, payload) {
1745
+ postAnalytics(
1746
+ "/events",
1747
+ {
1748
+ userId: useWizard.getState().user?.userId,
1749
+ event,
1750
+ properties: { ...payload, ...commonEventProperties() }
1751
+ },
1752
+ event
1753
+ );
1754
+ }
1755
+ function identify(traits) {
1756
+ const userId = useWizard.getState().user?.userId;
1757
+ postAnalytics("/identify", { userId, traits }, "identify");
1758
+ }
1759
+
1760
+ // package.json
1761
+ var package_default = {
1762
+ name: "@algolia/wizard",
1763
+ version: "0.64.0",
1764
+ description: "Magically implement Algolia functionality in your codebase",
1765
+ type: "module",
1766
+ engines: {
1767
+ node: ">=24"
1768
+ },
1769
+ bin: {
1770
+ wizard: "dist/main.js"
1771
+ },
1772
+ files: [
1773
+ "dist",
1774
+ "docs"
1775
+ ],
1776
+ scripts: {
1777
+ "build:proxy": "node scripts/build.mjs proxy",
1778
+ build: "node scripts/build.mjs",
1779
+ "dev:proxy": "touch .env && NODE_OPTIONS=--use-system-ca tsx watch --env-file=.env src/proxy/index.ts",
1780
+ dev: "touch .env && tsx --env-file=.env ./src/main.tsx",
1781
+ "env:load": "pnpm exec -- varlock load",
1782
+ prepare: "husky",
1783
+ prepublishOnly: "pnpm build",
1784
+ reset: "tsx ./scripts/reset-state.ts",
1785
+ "test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
1786
+ "test:tools": "tsx ./tool-evals/toolEval.ts",
1787
+ "test:tools:improve": "tsx ./tool-evals/improveFromPlan.ts",
1788
+ test: "vitest",
1789
+ typecheck: "tsc --noEmit -p tsconfig.json"
1790
+ },
1791
+ keywords: [],
1792
+ author: "",
1793
+ license: "ISC",
1794
+ packageManager: "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
1795
+ devDependencies: {
1796
+ "@clack/prompts": "^1.6.0",
1797
+ "@types/ioredis-mock": "^8.2.7",
1798
+ "@types/node": "^25.9.1",
1799
+ "@types/react": "^19.2.16",
1800
+ esbuild: "^0.28.0",
1801
+ husky: "^9.1.7",
1802
+ "ink-testing-library": "^4.0.0",
1803
+ "ioredis-mock": "^8.13.1",
1804
+ "lint-staged": "^17.0.8",
1805
+ tsx: "^4.22.3",
1806
+ typescript: "^6.0.3",
1807
+ vitest: "^4.1.8"
1808
+ },
1809
+ dependencies: {
1810
+ "@ai-sdk/anthropic": "^3.0.81",
1811
+ "@ai-sdk/openai-compatible": "^2.0.47",
1812
+ "@hono/node-server": "^2.0.10",
1813
+ "@segment/analytics-node": "^3.1.0",
1814
+ ai: "^6.0.190",
1815
+ "cross-keychain": "^1.1.0",
1816
+ dotenv: "^17.4.2",
1817
+ hono: "^4.12.27",
1818
+ ink: "^7.0.5",
1819
+ "ink-picture": "^2.1.0",
1820
+ "ink-spinner": "^5.0.0",
1821
+ ioredis: "^5.11.1",
1822
+ nanoid: "^5.1.15",
1823
+ pino: "^10.3.1",
1824
+ react: "^19.2.7",
1825
+ "terminal-link": "^5.0.0",
1826
+ varlock: "^1.5.1",
1827
+ zod: "^4.4.3",
1828
+ zustand: "^5.0.14"
1829
+ }
1830
+ };
1717
1831
 
1718
1832
  // src/core/orchestrator.ts
1719
1833
  function defineStep(step) {
1720
1834
  return { visible: true, ...step };
1721
1835
  }
1722
1836
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
1837
+ var ReturnToStepSignal = class extends Error {
1838
+ constructor(targetIndex, stepId) {
1839
+ super(`returnToStep: ${stepId}`);
1840
+ this.targetIndex = targetIndex;
1841
+ this.stepId = stepId;
1842
+ this.name = "ReturnToStepSignal";
1843
+ }
1844
+ targetIndex;
1845
+ stepId;
1846
+ };
1723
1847
  function ensureExecutedStepCount(state) {
1724
1848
  if (state.executedStepCount == null) {
1725
1849
  state.executedStepCount = state.steps.filter(
@@ -1766,9 +1890,37 @@ function initWorkflowState(workflow, now, appId) {
1766
1890
  status: "pending"
1767
1891
  })),
1768
1892
  algoliaState: appId ? { appId } : {},
1769
- userInputs: {}
1893
+ userInputs: {},
1894
+ agentRunId: crypto.randomUUID()
1770
1895
  };
1771
1896
  }
1897
+ function ensureAgentRunId(state) {
1898
+ if (!state.agentRunId) state.agentRunId = crypto.randomUUID();
1899
+ }
1900
+ function completeEventProperties(state, totalDurationMs, stepCount) {
1901
+ const indexName = state.steps.find((s) => s.id === "select-index")?.output?.selection;
1902
+ const framework = state.steps.find((s) => s.id === "confirm-framework")?.output?.frameworks?.[0]?.name;
1903
+ return {
1904
+ total_duration_ms: totalDurationMs,
1905
+ duration_ms: totalDurationMs,
1906
+ step_count: stepCount,
1907
+ index_name: indexName ?? "unknown",
1908
+ framework: framework ?? "unknown"
1909
+ };
1910
+ }
1911
+ function errorCodeFrom(err) {
1912
+ let current = err;
1913
+ for (let i = 0; i < 4 && current; i++) {
1914
+ if (current && typeof current === "object" && "statusCode" in current) {
1915
+ const code = current.statusCode;
1916
+ if (typeof code === "number" || typeof code === "string") {
1917
+ return String(code);
1918
+ }
1919
+ }
1920
+ current = current instanceof Error ? current.cause : void 0;
1921
+ }
1922
+ return void 0;
1923
+ }
1772
1924
  async function ensureConsent() {
1773
1925
  const config = await loadConfig();
1774
1926
  if (config.aiConsent) return;
@@ -1812,12 +1964,37 @@ async function makeContext(state) {
1812
1964
  setUserInput: (key, value) => {
1813
1965
  state.userInputs[key] = value;
1814
1966
  },
1967
+ getUserInput: (key) => state.userInputs[key],
1815
1968
  recordStepExecution: (count = 1) => {
1816
1969
  state.executedStepCount = (state.executedStepCount ?? 0) + count;
1817
1970
  },
1971
+ returnToStep: (stepId) => {
1972
+ const targetIndex = state.steps.findIndex((s) => s.id === stepId);
1973
+ if (targetIndex === -1) {
1974
+ throw new Error(`returnToStep: unknown step id "${stepId}"`);
1975
+ }
1976
+ if (targetIndex >= state.currentStepIndex) {
1977
+ throw new Error(
1978
+ `returnToStep: "${stepId}" is not before the running step "${state.steps[state.currentStepIndex]?.id}"`
1979
+ );
1980
+ }
1981
+ throw new ReturnToStepSignal(targetIndex, stepId);
1982
+ },
1818
1983
  config: await loadConfig()
1819
1984
  };
1820
1985
  }
1986
+ async function rewindTo(state, targetIndex, fromIndex) {
1987
+ for (let i = targetIndex; i <= fromIndex; i++) {
1988
+ const record = state.steps[i];
1989
+ record.status = "pending";
1990
+ delete record.output;
1991
+ delete record.error;
1992
+ }
1993
+ state.currentStepIndex = targetIndex;
1994
+ state.updatedAt = nowIso();
1995
+ useWizard.getState().syncSteps([...state.steps], targetIndex);
1996
+ await saveWorkflowState(state);
1997
+ }
1821
1998
  async function runStep(state, index, step, appId) {
1822
1999
  const store = useWizard.getState();
1823
2000
  const record = state.steps[index];
@@ -1839,9 +2016,18 @@ async function runStep(state, index, step, appId) {
1839
2016
  await saveWorkflowState(state);
1840
2017
  await markInteraction();
1841
2018
  const ctx = await makeContext(state);
1842
- const raw = await step.run(ctx);
1843
- const output = step.outputSchema.parse(raw);
1844
- await step.onStepComplete?.(output, ctx);
2019
+ let output;
2020
+ try {
2021
+ const raw = await step.run(ctx);
2022
+ output = step.outputSchema.parse(raw);
2023
+ await step.onStepComplete?.(output, ctx);
2024
+ } catch (err) {
2025
+ if (err instanceof ReturnToStepSignal) {
2026
+ await rewindTo(state, err.targetIndex, index);
2027
+ return err.targetIndex;
2028
+ }
2029
+ throw err;
2030
+ }
1845
2031
  record.status = "done";
1846
2032
  record.output = output;
1847
2033
  state.updatedAt = nowIso();
@@ -1854,6 +2040,7 @@ async function runStep(state, index, step, appId) {
1854
2040
  actionTitle: step.title,
1855
2041
  durationMs: Date.now() - startedAt
1856
2042
  });
2043
+ return void 0;
1857
2044
  }
1858
2045
  var RESUME_OPTION = "Resume";
1859
2046
  var START_NEW_OPTION = "Start a new one";
@@ -1881,6 +2068,8 @@ async function runWorkflow(workflow, appId, options) {
1881
2068
  try {
1882
2069
  const state = await resolveWorkflowState(workflow, appId, options);
1883
2070
  ensureExecutedStepCount(state);
2071
+ ensureAgentRunId(state);
2072
+ setAnalyticsContext({ appId, agentRunId: state.agentRunId });
1884
2073
  store.startWorkflow(
1885
2074
  {
1886
2075
  id: workflow.id,
@@ -1891,8 +2080,15 @@ async function runWorkflow(workflow, appId, options) {
1891
2080
  );
1892
2081
  await ensureConsent();
1893
2082
  trackWorkflowStart({ workflowId: workflow.id, appId });
1894
- for (let i = state.currentStepIndex; i < workflow.steps.length; i++) {
1895
- await runStep(state, i, workflow.steps[i], appId);
2083
+ track("AI Wizard Started", {
2084
+ version: package_default.version,
2085
+ wizard_version: package_default.version,
2086
+ os: process.platform
2087
+ });
2088
+ let i = state.currentStepIndex;
2089
+ while (i < workflow.steps.length) {
2090
+ const rewoundTo = await runStep(state, i, workflow.steps[i], appId);
2091
+ i = rewoundTo ?? i + 1;
1896
2092
  }
1897
2093
  const totalDurationMs = Date.now() - Date.parse(state.startedAt);
1898
2094
  const stepCount = state.executedStepCount ?? state.steps.filter((s) => s.status === "done" && isStepVisible(s)).length;
@@ -1902,10 +2098,11 @@ async function runWorkflow(workflow, appId, options) {
1902
2098
  total_duration: Math.round(totalDurationMs / 1e3),
1903
2099
  total_steps: stepCount
1904
2100
  });
1905
- track("AI Wizard Complete", {
1906
- total_duration_ms: totalDurationMs,
1907
- step_count: stepCount
1908
- });
2101
+ track(
2102
+ "AI Wizard Completed",
2103
+ completeEventProperties(state, totalDurationMs, stepCount)
2104
+ );
2105
+ identify({ is_Wizard_user: true });
1909
2106
  await recordWorkflowRun(workflow.id, nowIso());
1910
2107
  await clearWorkflowState(workflow.id);
1911
2108
  store.setDone();
@@ -1946,10 +2143,15 @@ async function runWorkflow(workflow, appId, options) {
1946
2143
  actionId: failedActionId,
1947
2144
  cause: err
1948
2145
  });
2146
+ const errorType = wizardErrorType(failedActionId, step, message);
2147
+ const errorCode2 = errorCodeFrom(err);
1949
2148
  track("Error", {
1950
2149
  step,
1951
2150
  error: message,
1952
- product_area: "AI Wizard"
2151
+ error_message: message,
2152
+ product_area: "AI Wizard",
2153
+ ...errorType ? { error_type: errorType } : {},
2154
+ ...errorCode2 ? { error_code: errorCode2 } : {}
1953
2155
  });
1954
2156
  store.setError(message);
1955
2157
  }
@@ -3529,7 +3731,7 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
3529
3731
  try {
3530
3732
  const anthropic = createModel();
3531
3733
  const value = z17.union([z17.string(), z17.number(), z17.boolean(), z17.null()]);
3532
- const recordSchema = z17.object(
3734
+ const recordSchema2 = z17.object(
3533
3735
  Object.fromEntries(attributes.map((attr) => [attr, value]))
3534
3736
  );
3535
3737
  const generateBatch = async (batchCount) => {
@@ -3540,7 +3742,7 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
3540
3742
  model: anthropic(RECORD_MODEL),
3541
3743
  output: Output2.object({
3542
3744
  schema: z17.object({
3543
- records: z17.array(recordSchema).length(batchCount)
3745
+ records: z17.array(recordSchema2).length(batchCount)
3544
3746
  })
3545
3747
  }),
3546
3748
  prompt: [
@@ -3975,78 +4177,6 @@ async function runAnalysis(mode, extraInstructions = []) {
3975
4177
  return Object.assign({}, ...results);
3976
4178
  }
3977
4179
 
3978
- // package.json
3979
- var package_default = {
3980
- name: "@algolia/wizard",
3981
- version: "0.62.0",
3982
- description: "Magically implement Algolia functionality in your codebase",
3983
- type: "module",
3984
- engines: {
3985
- node: ">=24"
3986
- },
3987
- bin: {
3988
- wizard: "dist/main.js"
3989
- },
3990
- files: [
3991
- "dist",
3992
- "docs"
3993
- ],
3994
- scripts: {
3995
- "build:proxy": "node scripts/build.mjs proxy",
3996
- build: "node scripts/build.mjs",
3997
- "dev:proxy": "touch .env && NODE_OPTIONS=--use-system-ca tsx watch --env-file=.env src/proxy/index.ts",
3998
- dev: "touch .env && tsx --env-file=.env ./src/main.tsx",
3999
- "env:load": "pnpm exec -- varlock load",
4000
- prepare: "husky",
4001
- prepublishOnly: "pnpm build",
4002
- reset: "tsx ./scripts/reset-state.ts",
4003
- "test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
4004
- "test:tools": "tsx ./tool-evals/toolEval.ts",
4005
- "test:tools:improve": "tsx ./tool-evals/improveFromPlan.ts",
4006
- test: "vitest",
4007
- typecheck: "tsc --noEmit -p tsconfig.json"
4008
- },
4009
- keywords: [],
4010
- author: "",
4011
- license: "ISC",
4012
- packageManager: "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
4013
- devDependencies: {
4014
- "@clack/prompts": "^1.6.0",
4015
- "@types/ioredis-mock": "^8.2.7",
4016
- "@types/node": "^25.9.1",
4017
- "@types/react": "^19.2.16",
4018
- esbuild: "^0.28.0",
4019
- husky: "^9.1.7",
4020
- "ink-testing-library": "^4.0.0",
4021
- "ioredis-mock": "^8.13.1",
4022
- "lint-staged": "^17.0.8",
4023
- tsx: "^4.22.3",
4024
- typescript: "^6.0.3",
4025
- vitest: "^4.1.8"
4026
- },
4027
- dependencies: {
4028
- "@ai-sdk/anthropic": "^3.0.81",
4029
- "@ai-sdk/openai-compatible": "^2.0.47",
4030
- "@hono/node-server": "^2.0.10",
4031
- "@segment/analytics-node": "^3.1.0",
4032
- ai: "^6.0.190",
4033
- "cross-keychain": "^1.1.0",
4034
- dotenv: "^17.4.2",
4035
- hono: "^4.12.27",
4036
- ink: "^7.0.5",
4037
- "ink-picture": "^2.1.0",
4038
- "ink-spinner": "^5.0.0",
4039
- ioredis: "^5.11.1",
4040
- nanoid: "^5.1.15",
4041
- pino: "^10.3.1",
4042
- react: "^19.2.7",
4043
- "terminal-link": "^5.0.0",
4044
- varlock: "^1.5.1",
4045
- zod: "^4.4.3",
4046
- zustand: "^5.0.14"
4047
- }
4048
- };
4049
-
4050
4180
  // src/actions/projectScan.ts
4051
4181
  import "zod";
4052
4182
  var projectScanSchema = detectLanguageSchema.extend({
@@ -4055,16 +4185,19 @@ var projectScanSchema = detectLanguageSchema.extend({
4055
4185
  verification: analyzeCodebaseSchema.shape.verification
4056
4186
  });
4057
4187
  async function projectScan(ctx) {
4188
+ const startedAt = Date.now();
4058
4189
  const [detected, analyses] = await Promise.all([
4059
4190
  detectLanguage(),
4060
4191
  runAnalysis()
4061
4192
  ]);
4062
- track("AI Wizard Started", {
4063
- wizard_version: package_default.version,
4064
- os: process.platform
4065
- });
4193
+ const entities = toEntitySummary(analyses.ingestionAnalysis ?? []);
4066
4194
  track("AI Wizard Scan Completed", {
4067
- entities: toEntitySummary(analyses.ingestionAnalysis ?? [])
4195
+ languages: detected.languages,
4196
+ frameworks: detected.frameworks,
4197
+ data_models_found: entities.map((entity) => entity.name),
4198
+ back_end_language_detected: detected.languages[0]?.name ?? null,
4199
+ front_end_framework_detected: detected.frameworks[0]?.name ?? null,
4200
+ duration_ms: Date.now() - startedAt
4068
4201
  });
4069
4202
  for (const [key, value] of Object.entries(analyses)) {
4070
4203
  ctx.setUserInput(key, value);
@@ -4162,7 +4295,7 @@ var isSameFramework = (a, b) => {
4162
4295
  };
4163
4296
  function confirmed(name, version) {
4164
4297
  const frameworks = [{ name, version: version ?? "unknown" }];
4165
- track("AI Wizard Frontend Framework Confirmed", { frameworks });
4298
+ track("AI Wizard Frontend Framework Confirmed", { frameworks, framework: name });
4166
4299
  return { frameworks };
4167
4300
  }
4168
4301
  async function askOtherFramework(ctx) {
@@ -4266,6 +4399,11 @@ var confirmEntitiesSchema = z26.object({
4266
4399
  confirmedEntities: confirmedEntitiesFieldSchema
4267
4400
  });
4268
4401
  var SUGGEST_ENTITY = "Suggest a different entity\u2026";
4402
+ function usedDetectedBackend(detected, confirmed2) {
4403
+ if (!detected?.length || !confirmed2?.length) return false;
4404
+ const detectedNames = new Set(detected.map((entry) => entry.name.toLowerCase()));
4405
+ return confirmed2.every((entry) => detectedNames.has(entry.name.toLowerCase()));
4406
+ }
4269
4407
  async function askEntityName(ctx, opts) {
4270
4408
  const blankHint = opts?.blankHint ?? "cancel";
4271
4409
  const declared = await promptUser(ctx, {
@@ -4372,8 +4510,14 @@ async function confirmEntities(ctx) {
4372
4510
  throw new Error("User cancelled entity selection \u2014 analysis halted.");
4373
4511
  }
4374
4512
  ctx.setUserInput("confirmedEntities", confirmed2);
4375
- track("AI Wizard Entities Confirmed", {
4376
- entities: toEntitySummary(confirmed2)
4513
+ const languages = ctx.getStepOutput(
4514
+ "confirm-language"
4515
+ )?.languages;
4516
+ const summary = toEntitySummary(confirmed2);
4517
+ track("AI Wizard Data Model Confirmed", {
4518
+ data_model: summary[0]?.name ?? "unknown",
4519
+ attribute_count: summary[0]?.attributes.length ?? 0,
4520
+ used_detected_back_end: usedDetectedBackend(scan.languages, languages)
4377
4521
  });
4378
4522
  return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
4379
4523
  }
@@ -4423,7 +4567,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
4423
4567
 
4424
4568
  // src/actions/implement.ts
4425
4569
  import z28 from "zod";
4426
- import { mkdir as mkdir5 } from "node:fs/promises";
4570
+ import { mkdir as mkdir5, readFile as readFile8 } from "node:fs/promises";
4427
4571
  import { join as join10, relative as relative6 } from "node:path";
4428
4572
 
4429
4573
  // src/lib/git.ts
@@ -4812,6 +4956,50 @@ function outputTail(output) {
4812
4956
  if (!trimmed) return void 0;
4813
4957
  return trimmed.length > INGEST_OUTPUT_TAIL_CHARS ? `\u2026${trimmed.slice(-INGEST_OUTPUT_TAIL_CHARS)}` : trimmed;
4814
4958
  }
4959
+ function jsTypeName(value) {
4960
+ if (value === null) return "null";
4961
+ if (Array.isArray(value)) return "array";
4962
+ const type = typeof value;
4963
+ if (type === "string" || type === "number" || type === "boolean" || type === "object") {
4964
+ return type;
4965
+ }
4966
+ return "unknown";
4967
+ }
4968
+ async function firstRecordIn(filePath) {
4969
+ try {
4970
+ const parsed = JSON.parse(await readFile8(filePath, "utf8"));
4971
+ const first = Array.isArray(parsed) ? parsed[0] : parsed;
4972
+ if (first && typeof first === "object" && !Array.isArray(first)) {
4973
+ return first;
4974
+ }
4975
+ } catch {
4976
+ return void 0;
4977
+ }
4978
+ return void 0;
4979
+ }
4980
+ async function ingestedRecordSample(repoRoot, entityName, uploadFilePath) {
4981
+ const candidates = [];
4982
+ if (entityName) {
4983
+ const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
4984
+ candidates.push(join10(repoRoot, INGEST_DIR, "data", `${slug}.json`));
4985
+ }
4986
+ if (uploadFilePath?.toLowerCase().endsWith(".json")) {
4987
+ candidates.push(join10(repoRoot, uploadFilePath));
4988
+ }
4989
+ for (const candidate of candidates) {
4990
+ const sample = await firstRecordIn(candidate);
4991
+ if (sample) return sample;
4992
+ }
4993
+ return void 0;
4994
+ }
4995
+ function recordSchema(sample, attributes) {
4996
+ if (sample) {
4997
+ return Object.fromEntries(
4998
+ Object.entries(sample).map(([key, value]) => [key, jsTypeName(value)])
4999
+ );
5000
+ }
5001
+ return Object.fromEntries(attributes.map((key) => [key, "unknown"]));
5002
+ }
4815
5003
  function ingestFailure(attempt, executions) {
4816
5004
  if (!attempt) {
4817
5005
  return {
@@ -5041,11 +5229,24 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5041
5229
  if (ingestScriptRan) {
5042
5230
  ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
5043
5231
  if (ingestRecordCount != null) {
5232
+ const data_model = confirmed2?.[0]?.name ?? "unknown";
5233
+ const sample = await ingestedRecordSample(
5234
+ repoRoot,
5235
+ confirmed2?.[0]?.name,
5236
+ uploadFilePath
5237
+ );
5044
5238
  track("AI Wizard Ingest Successful", {
5045
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
5239
+ data_model,
5046
5240
  record_count: ingestRecordCount,
5047
- duration_ms: ingestDurationMs ?? 0
5241
+ duration_ms: ingestDurationMs ?? 0,
5242
+ index_name: targetIndex,
5243
+ record_schema: recordSchema(sample, confirmed2?.[0]?.attributes ?? [])
5048
5244
  });
5245
+ } else {
5246
+ logger.warn(
5247
+ { ingestCommand },
5248
+ "implement: ingestion ran but printed no record count; skipping Ingest Successful event"
5249
+ );
5049
5250
  }
5050
5251
  } else {
5051
5252
  const { reason, detail } = ingestFailure(ingestAttempt, executions);
@@ -5069,7 +5270,10 @@ ${detail}` : ""}`
5069
5270
  track("Error", {
5070
5271
  step: "Push Data",
5071
5272
  error: `ingestion did not complete: ${reason}`,
5072
- product_area: "AI Wizard"
5273
+ error_message: `ingestion did not complete: ${reason}`,
5274
+ error_type: "Ingest Records",
5275
+ product_area: "AI Wizard",
5276
+ ...ingestAttempt?.exitCode != null ? { error_code: String(ingestAttempt.exitCode) } : {}
5073
5277
  });
5074
5278
  }
5075
5279
  if (ingestScriptRan) {
@@ -5103,6 +5307,29 @@ ${detail}` : ""}`
5103
5307
  let searchConfigFile;
5104
5308
  if (useCases.includes("search")) {
5105
5309
  let extraInstructions = [];
5310
+ const confirmedFrameworks = language.frameworks ?? [];
5311
+ const detectedFrameworks = scan.frameworks ?? [];
5312
+ const framework = confirmedFrameworks[0]?.name ?? "unknown";
5313
+ const used_detected_framework = confirmedFrameworks.some(
5314
+ (confirmedFw) => detectedFrameworks.some(
5315
+ (detectedFw) => isSameFramework(confirmedFw.name, detectedFw.name)
5316
+ )
5317
+ );
5318
+ const searchStartedAt = Date.now();
5319
+ const trackComponentGenerated = (mounted_successfully, attempts) => {
5320
+ const searchFilesChanged = [
5321
+ ...new Set(useWizard.getState().writtenFiles)
5322
+ ].map((file) => relative6(repoRoot, file));
5323
+ track("AI Wizard Frontend Component Generated", {
5324
+ file_paths: searchFilesChanged,
5325
+ framework,
5326
+ duration_ms: Date.now() - searchStartedAt,
5327
+ used_detected_framework,
5328
+ mounted_successfully,
5329
+ location_heuristic: searchLocation ?? "unknown",
5330
+ attempts
5331
+ });
5332
+ };
5106
5333
  useWizard.getState().clearWrittenFiles();
5107
5334
  for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
5108
5335
  if (attempt > 1) {
@@ -5127,19 +5354,12 @@ ${detail}` : ""}`
5127
5354
  summaries.push(formatSummary("verification", verification.summary));
5128
5355
  if (verification.sufficient) {
5129
5356
  ctx.setUserInput("implementation", "success");
5130
- const searchFilesChanged = [
5131
- ...new Set(useWizard.getState().writtenFiles)
5132
- ].map((file) => relative6(repoRoot, file));
5133
- track("AI Wizard Frontend Component Generated", {
5134
- filePaths: searchFilesChanged
5135
- });
5136
- track("AI Wizard Wired to UI", {
5137
- location_heuristic: searchLocation ?? "unknown"
5138
- });
5357
+ trackComponentGenerated(true, attempt);
5139
5358
  break;
5140
5359
  }
5141
5360
  if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
5142
5361
  ctx.setUserInput("implementation", "fail");
5362
+ trackComponentGenerated(false, attempt);
5143
5363
  throw new Error(
5144
5364
  `Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
5145
5365
  );
@@ -5356,10 +5576,11 @@ async function selectPlaybookStep(ctx, requestedSlug) {
5356
5576
  if (slugs.length === 0) {
5357
5577
  throw new Error("No playbooks are available.");
5358
5578
  }
5359
- if (requestedSlug && slugs.includes(requestedSlug)) {
5579
+ const alreadyChose = typeof ctx.getUserInput("playbookSlug") === "string";
5580
+ if (!alreadyChose && requestedSlug && slugs.includes(requestedSlug)) {
5360
5581
  return persistSlug(ctx, requestedSlug);
5361
5582
  }
5362
- const messages = requestedSlug ? [
5583
+ const messages = !alreadyChose && requestedSlug ? [
5363
5584
  `"${requestedSlug}" is not a valid playbook. Choose one of the available playbooks:`
5364
5585
  ] : ["Choose a playbook:"];
5365
5586
  const selection = await ctx.requestUserInput({
@@ -5374,6 +5595,28 @@ async function selectPlaybookStep(ctx, requestedSlug) {
5374
5595
  return persistSlug(ctx, selection);
5375
5596
  }
5376
5597
 
5598
+ // src/actions/playbookSteps/confirmPlaybook.ts
5599
+ import { z as z33 } from "zod";
5600
+ var confirmPlaybookSchema = z33.void();
5601
+ var CONTINUE_OPTION = "Looks good, continue";
5602
+ var GO_BACK_OPTION = "Go back";
5603
+ async function confirmPlaybookStep(ctx) {
5604
+ const { slug } = ctx.getStepOutput("select-playbook");
5605
+ const selection = await ctx.requestUserInput({
5606
+ prompt: "Ready to continue?",
5607
+ promptType: "multipleChoice",
5608
+ options: [CONTINUE_OPTION, GO_BACK_OPTION],
5609
+ messages: [`Playbook: ${slug}`],
5610
+ helpText: "Going back re-opens the playbook catalogue."
5611
+ });
5612
+ if (typeof selection !== "string") {
5613
+ throw new Error("confirm-playbook received an unexpected result");
5614
+ }
5615
+ if (selection === GO_BACK_OPTION) {
5616
+ return ctx.returnToStep("select-playbook");
5617
+ }
5618
+ }
5619
+
5377
5620
  // src/lib/cli.ts
5378
5621
  var USAGE = `Usage: wizard [workflow-id] [options]
5379
5622
  wizard playbook [playbook-slug] [options]
@@ -5440,6 +5683,13 @@ var playbookWorkflow = {
5440
5683
  title: "Select playbook",
5441
5684
  outputSchema: selectPlaybookSchema,
5442
5685
  run: (ctx) => selectPlaybookStep(ctx, getPlaybookSlugArg())
5686
+ }),
5687
+ defineStep({
5688
+ id: "confirm-playbook",
5689
+ title: "Confirm selections",
5690
+ outputSchema: confirmPlaybookSchema,
5691
+ visible: false,
5692
+ run: (ctx) => confirmPlaybookStep(ctx)
5443
5693
  })
5444
5694
  ]
5445
5695
  };
@@ -6459,7 +6709,7 @@ function App() {
6459
6709
  }
6460
6710
 
6461
6711
  // src/lib/envAppId.ts
6462
- import { readFile as readFile8 } from "node:fs/promises";
6712
+ import { readFile as readFile9 } from "node:fs/promises";
6463
6713
  import { join as join12 } from "node:path";
6464
6714
  var ENV_FILES = [".env", ".env.local"];
6465
6715
  var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
@@ -6467,7 +6717,7 @@ async function findEnvApplicationId(root = process.cwd()) {
6467
6717
  for (const file of ENV_FILES) {
6468
6718
  let content;
6469
6719
  try {
6470
- content = await readFile8(join12(root, file), "utf8");
6720
+ content = await readFile9(join12(root, file), "utf8");
6471
6721
  } catch (err) {
6472
6722
  if (err.code !== "ENOENT") {
6473
6723
  logger.warn(
@@ -6654,9 +6904,13 @@ var SEEDS = {
6654
6904
  review
6655
6905
  },
6656
6906
  playbook: {
6657
- "select-playbook": selectPlaybook
6907
+ "select-playbook": selectPlaybook,
6908
+ "confirm-playbook": void 0
6658
6909
  }
6659
6910
  };
6911
+ var SEED_ALGOLIA_STATE = {
6912
+ playbook: { appId: "SEEDAPPID" }
6913
+ };
6660
6914
  var SEED_USER_INPUTS = {
6661
6915
  default: {
6662
6916
  index: selectIndex.selection,
@@ -6708,7 +6962,7 @@ function buildSeedState(workflow, startIndex) {
6708
6962
  updatedAt: now,
6709
6963
  currentStepIndex: startIndex,
6710
6964
  steps,
6711
- algoliaState: {},
6965
+ algoliaState: startIndex > 0 ? { ...SEED_ALGOLIA_STATE[workflow.id] } : {},
6712
6966
  userInputs: startIndex > 0 ? { ...SEED_USER_INPUTS[workflow.id] } : {}
6713
6967
  };
6714
6968
  }
@@ -6820,7 +7074,7 @@ function delay(ms) {
6820
7074
  // package.json with { type: 'json' }
6821
7075
  var package_default2 = {
6822
7076
  name: "@algolia/wizard",
6823
- version: "0.62.0",
7077
+ version: "0.64.0",
6824
7078
  description: "Magically implement Algolia functionality in your codebase",
6825
7079
  type: "module",
6826
7080
  engines: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {