@algolia/wizard 0.71.0 → 0.73.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 +567 -315
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -177,7 +177,7 @@ function stateDir(cwd = pinnedRoot ?? process.cwd()) {
177
177
  // src/lib/logger.ts
178
178
  var STDERR_FD = 2;
179
179
  function resolveDest() {
180
- const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG ?? join2(stateDir(), "wizard.log");
180
+ const target = process.env.VITEST ? devNull : process.env.WIZARD_LOG || join2(stateDir(), "wizard.log");
181
181
  try {
182
182
  mkdirSync(dirname(target), { recursive: true });
183
183
  closeSync(openSync(target, "a"));
@@ -263,7 +263,9 @@ var useWizard = create((set, get) => ({
263
263
  inputReq: null,
264
264
  _resolve: null,
265
265
  settingUpAppId: null,
266
+ loadingMessage: null,
266
267
  setSettingUpApp: (appId) => set({ settingUpAppId: appId }),
268
+ setLoadingMessage: (message) => set({ loadingMessage: message }),
267
269
  // `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
268
270
  // welcome screen, so going back would gate the run a second time.
269
271
  beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
@@ -359,6 +361,11 @@ var useWizard = create((set, get) => ({
359
361
  setTargetIndex: (index) => set({ targetIndex: index }),
360
362
  recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
361
363
  clearWrittenFiles: () => set({ writtenFiles: [] }),
364
+ resetRunState: () => set({
365
+ targetIndex: null,
366
+ writtenFiles: [],
367
+ approvedCommands: /* @__PURE__ */ new Set()
368
+ }),
362
369
  isCommandApproved: (command, cwd) => get().approvedCommands.has(commandApprovalKey(command, cwd)),
363
370
  approveCommand: (command, cwd) => set((s) => ({
364
371
  approvedCommands: new Set(s.approvedCommands).add(
@@ -389,7 +396,8 @@ var useWizard = create((set, get) => ({
389
396
  set({
390
397
  phase: "awaitingInput",
391
398
  inputReq: req,
392
- _resolve: resolve5
399
+ _resolve: resolve5,
400
+ loadingMessage: null
393
401
  });
394
402
  }),
395
403
  submitInput: async (value) => {
@@ -402,8 +410,8 @@ var useWizard = create((set, get) => ({
402
410
  );
403
411
  get().logEnd(id, "success");
404
412
  },
405
- setDone: () => set({ phase: "done" }),
406
- setError: (message) => set({ phase: "error", error: message }),
413
+ setDone: () => set({ phase: "done", loadingMessage: null }),
414
+ setError: (message) => set({ phase: "error", error: message, loadingMessage: null }),
407
415
  reset: () => {
408
416
  get()._clearNoticeQueue();
409
417
  get()._resolve?.(false);
@@ -424,7 +432,8 @@ var useWizard = create((set, get) => ({
424
432
  error: null,
425
433
  inputReq: null,
426
434
  _resolve: null,
427
- settingUpAppId: null
435
+ settingUpAppId: null,
436
+ loadingMessage: null
428
437
  });
429
438
  }
430
439
  }));
@@ -1770,6 +1779,37 @@ function trackWorkflowStart(ctx) {
1770
1779
  );
1771
1780
  sendMetric("wizard.workflow.invocation", 1, 1, metricTags(ctx.workflowId));
1772
1781
  }
1782
+ function trackWorkflowAbandoned(ctx) {
1783
+ const attributes = {
1784
+ event: "wizard.workflow.abandoned",
1785
+ workflow_id: ctx.workflowId,
1786
+ app_id: ctx.appId,
1787
+ reason: ctx.reason
1788
+ };
1789
+ emitTelemetryLog(
1790
+ "info",
1791
+ "wizard workflow abandoned",
1792
+ attributes,
1793
+ logTags(ctx.workflowId, void 0, ctx.appId)
1794
+ );
1795
+ }
1796
+ function trackActionAbandoned(ctx) {
1797
+ const attributes = {
1798
+ event: "wizard.action.abandoned",
1799
+ workflow_id: ctx.workflowId,
1800
+ action_id: ctx.actionId,
1801
+ action_title: ctx.actionTitle,
1802
+ duration_ms: ctx.durationMs,
1803
+ app_id: ctx.appId,
1804
+ reason: ctx.reason
1805
+ };
1806
+ emitTelemetryLog(
1807
+ "info",
1808
+ "wizard action abandoned",
1809
+ attributes,
1810
+ logTags(ctx.workflowId, ctx.actionId, ctx.appId)
1811
+ );
1812
+ }
1773
1813
  function trackActionStart(ctx) {
1774
1814
  const attributes = {
1775
1815
  event: "wizard.action.start",
@@ -1966,10 +2006,261 @@ function identify(traits) {
1966
2006
  postAnalytics("/identify", { userId, traits }, "identify");
1967
2007
  }
1968
2008
 
2009
+ // src/lib/algoliaApp.ts
2010
+ import { z as z3 } from "zod";
2011
+ var applicationSchema = z3.object({
2012
+ id: z3.string().min(1),
2013
+ name: z3.string().default(""),
2014
+ plan: z3.string().optional()
2015
+ });
2016
+ var listSchema = z3.array(
2017
+ z3.object({
2018
+ id: z3.string().min(1),
2019
+ name: z3.string().default(""),
2020
+ plan_label: z3.string().optional(),
2021
+ status: z3.string().optional(),
2022
+ acl: z3.array(z3.string()).optional()
2023
+ }).transform(({ id, name, plan_label, status, acl }) => ({
2024
+ id,
2025
+ name,
2026
+ plan: plan_label,
2027
+ status,
2028
+ acl
2029
+ }))
2030
+ );
2031
+ function canSelectApplication(app) {
2032
+ return app.status === "active" && (app.acl ?? []).includes("keys");
2033
+ }
2034
+ async function currentApplication() {
2035
+ let raw;
2036
+ try {
2037
+ raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
2038
+ } catch {
2039
+ return null;
2040
+ }
2041
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2042
+ return parsed.success ? parsed.data : null;
2043
+ }
2044
+ async function requireApplication(expectedAppId) {
2045
+ const app = await currentApplication();
2046
+ if (!app) {
2047
+ throw new Error(
2048
+ "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2049
+ );
2050
+ }
2051
+ if (expectedAppId && app.id !== expectedAppId) {
2052
+ throw new Error(
2053
+ `The wizard is running in application ${expectedAppId}, but the Algolia CLI is pointed at ${app.id}. Select ${expectedAppId} with the Algolia CLI and restart the wizard: npx @algolia/cli@latest application select --app-id "${expectedAppId}"`
2054
+ );
2055
+ }
2056
+ return app;
2057
+ }
2058
+ async function listApplications() {
2059
+ const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2060
+ const parsed = listSchema.safeParse(parseJson(raw));
2061
+ if (!parsed.success) {
2062
+ throw new Error("Could not read the list of Algolia applications.");
2063
+ }
2064
+ return parsed.data;
2065
+ }
2066
+ async function selectApplication(id) {
2067
+ const raw = await runAlgoliaCli(
2068
+ ["application", "select", "--non-interactive", "--app-id", id],
2069
+ { onOutput: stderrSink }
2070
+ );
2071
+ const parsed = applicationSchema.safeParse(parseJson(raw));
2072
+ if (!parsed.success) {
2073
+ throw new Error(
2074
+ `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2075
+ );
2076
+ }
2077
+ return parsed.data;
2078
+ }
2079
+ function parseJson(text) {
2080
+ try {
2081
+ return JSON.parse(text);
2082
+ } catch {
2083
+ return void 0;
2084
+ }
2085
+ }
2086
+
2087
+ // src/lib/envAppId.ts
2088
+ import { readFile as readFile3 } from "node:fs/promises";
2089
+ import { join as join5 } from "node:path";
2090
+ var ENV_FILES = [".env", ".env.local"];
2091
+ var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
2092
+ async function findEnvApplicationId(root = process.cwd()) {
2093
+ for (const file of ENV_FILES) {
2094
+ let content;
2095
+ try {
2096
+ content = await readFile3(join5(root, file), "utf8");
2097
+ } catch (err) {
2098
+ if (err.code !== "ENOENT") {
2099
+ logger.warn(
2100
+ { file, err },
2101
+ "could not read env file for an application id"
2102
+ );
2103
+ }
2104
+ continue;
2105
+ }
2106
+ for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
2107
+ const id = readValue(raw);
2108
+ if (id) {
2109
+ logger.info({ file, name, app: id }, "found an application id in env");
2110
+ return { id, name, file };
2111
+ }
2112
+ }
2113
+ }
2114
+ return null;
2115
+ }
2116
+ function readValue(raw) {
2117
+ const trimmed = raw.trim();
2118
+ const quoted = trimmed.match(/^(['"])(.*)\1/);
2119
+ const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
2120
+ return value.length > 0 && !value.startsWith("<") ? value : null;
2121
+ }
2122
+
2123
+ // src/lib/algoliaAppPicker.ts
2124
+ function blockReasonFor(app) {
2125
+ return app.status !== "active" ? "Paused" : "Missing permissions";
2126
+ }
2127
+ function secondaryFor(app, marks) {
2128
+ if (!canSelectApplication(app)) {
2129
+ return { kind: "text", value: blockReasonFor(app) };
2130
+ }
2131
+ if (marks?.currentId && app.id === marks.currentId) {
2132
+ return { kind: "badge", value: "[CURRENT]" };
2133
+ }
2134
+ if (marks?.detectedId && app.id === marks.detectedId) {
2135
+ return { kind: "badge", value: "[DETECTED]" };
2136
+ }
2137
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
2138
+ }
2139
+ function labelFor(app) {
2140
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
2141
+ }
2142
+ async function selectAndReport(id) {
2143
+ const store = useWizard.getState();
2144
+ store.setSettingUpApp(id);
2145
+ try {
2146
+ return await selectApplication(id);
2147
+ } finally {
2148
+ store.setSettingUpApp(null);
2149
+ }
2150
+ }
2151
+ async function loadSelectableApps() {
2152
+ const unordered = await listApplications();
2153
+ if (unordered.length === 0) {
2154
+ throw new Error(
2155
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
2156
+ );
2157
+ }
2158
+ if (!unordered.some(canSelectApplication)) {
2159
+ throw new Error(
2160
+ "None of the applications on this Algolia account are usable \u2014 each is either paused or missing key-management access. Activate one, or grant it the `keys` ACL, then restart the wizard."
2161
+ );
2162
+ }
2163
+ return [
2164
+ ...unordered.filter(canSelectApplication),
2165
+ ...unordered.filter((app) => !canSelectApplication(app))
2166
+ ];
2167
+ }
2168
+ async function pickApplication(opts) {
2169
+ const store = useWizard.getState();
2170
+ const apps = await loadSelectableApps();
2171
+ const envHighlight = opts.highlight;
2172
+ const envApp = envHighlight ? apps.find((app) => app.id === envHighlight.id) : void 0;
2173
+ const highlightIndex = envApp && canSelectApplication(envApp) ? apps.indexOf(envApp) : -1;
2174
+ const marks = {
2175
+ detectedId: envHighlight?.id,
2176
+ currentId: opts.currentId
2177
+ };
2178
+ const messages = [];
2179
+ if (envHighlight && highlightIndex < 0) {
2180
+ messages.push(
2181
+ envApp ? `Could not use ${envHighlight.id} from ${envHighlight.file} \u2014 it\u2019s ${blockReasonFor(envApp).toLowerCase()}. Pick another below.` : `Could not use ${envHighlight.id} from ${envHighlight.file} \u2014 it may have been removed, or this account may not have access to it.`
2182
+ );
2183
+ }
2184
+ const extras = opts.extraOptions ?? [];
2185
+ const options = [...extras, ...apps.map(labelFor)];
2186
+ const secondary = [
2187
+ ...extras.map(() => void 0),
2188
+ ...apps.map((app) => secondaryFor(app, marks))
2189
+ ];
2190
+ const disabled = [
2191
+ ...extras.map(() => false),
2192
+ ...apps.map(
2193
+ (app) => !canSelectApplication(app) || app.id === opts.currentId
2194
+ )
2195
+ ];
2196
+ const defaultSelectedIndex = extras.length > 0 ? 0 : highlightIndex >= 0 ? highlightIndex : void 0;
2197
+ for (; ; ) {
2198
+ const choice = await store.requestUserInput({
2199
+ prompt: "Which Algolia application should the wizard work in?",
2200
+ promptType: "multipleChoice",
2201
+ options,
2202
+ secondary,
2203
+ disabled,
2204
+ ...defaultSelectedIndex !== void 0 ? { defaultSelectedIndex } : {},
2205
+ messages,
2206
+ ...opts.helpText ? { helpText: opts.helpText } : {}
2207
+ });
2208
+ if (typeof choice === "string" && extras.includes(choice)) {
2209
+ return { type: "option", value: choice };
2210
+ }
2211
+ const chosen = apps.find((app) => labelFor(app) === choice);
2212
+ if (!chosen || !canSelectApplication(chosen)) {
2213
+ throw new Error("Application picker received an unexpected selection");
2214
+ }
2215
+ try {
2216
+ return { type: "application", app: await selectAndReport(chosen.id) };
2217
+ } catch (err) {
2218
+ logger.warn(
2219
+ { app: chosen.id, err: err.message },
2220
+ "application select failed; re-prompting"
2221
+ );
2222
+ messages.push(
2223
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
2224
+ );
2225
+ }
2226
+ }
2227
+ }
2228
+ async function promptForApplication(highlight) {
2229
+ const result = await pickApplication({ highlight });
2230
+ if (result.type !== "application") {
2231
+ throw new Error("Application picker received an unexpected selection");
2232
+ }
2233
+ return result.app;
2234
+ }
2235
+ async function promptToChangeApplication(currentId) {
2236
+ const store = useWizard.getState();
2237
+ store.setLoadingMessage("Loading applications\u2026");
2238
+ try {
2239
+ const env = await findEnvApplicationId();
2240
+ const highlight = env ? { id: env.id, file: env.file } : void 0;
2241
+ const result = await pickApplication({
2242
+ currentId,
2243
+ highlight,
2244
+ extraOptions: ["Keep current application"],
2245
+ helpText: "Choosing another application restarts this wizard run. Progress so far is discarded."
2246
+ });
2247
+ if (result.type === "option") return null;
2248
+ return result.app;
2249
+ } finally {
2250
+ store.setLoadingMessage(null);
2251
+ }
2252
+ }
2253
+ async function ensureApplication(resumeAppId) {
2254
+ if (resumeAppId) return selectAndReport(resumeAppId);
2255
+ const env = await findEnvApplicationId();
2256
+ if (!env) return promptForApplication();
2257
+ return promptForApplication({ id: env.id, file: env.file });
2258
+ }
2259
+
1969
2260
  // package.json
1970
2261
  var package_default = {
1971
2262
  name: "@algolia/wizard",
1972
- version: "0.71.0",
2263
+ version: "0.73.0",
1973
2264
  description: "Magically implement Algolia functionality in your codebase",
1974
2265
  type: "module",
1975
2266
  engines: {
@@ -2053,6 +2344,14 @@ var ReturnToStepSignal = class extends Error {
2053
2344
  targetIndex;
2054
2345
  stepId;
2055
2346
  };
2347
+ var RestartWorkflowSignal = class extends Error {
2348
+ constructor(appId) {
2349
+ super(`restartWorkflow: ${appId}`);
2350
+ this.appId = appId;
2351
+ this.name = "RestartWorkflowSignal";
2352
+ }
2353
+ appId;
2354
+ };
2056
2355
  function ensureExecutedStepCount(state) {
2057
2356
  if (state.executedStepCount == null) {
2058
2357
  state.executedStepCount = state.steps.filter(
@@ -2189,6 +2488,12 @@ async function makeContext(state) {
2189
2488
  }
2190
2489
  throw new ReturnToStepSignal(targetIndex, stepId);
2191
2490
  },
2491
+ changeApplication: async () => {
2492
+ const currentAppId = runAppId(state);
2493
+ const next = await promptToChangeApplication(currentAppId);
2494
+ if (next?.id) throw new RestartWorkflowSignal(next.id);
2495
+ },
2496
+ appId: runAppId(state),
2192
2497
  config: await loadConfig()
2193
2498
  };
2194
2499
  }
@@ -2201,13 +2506,16 @@ async function rewindTo(state, targetIndex, fromIndex) {
2201
2506
  }
2202
2507
  state.currentStepIndex = targetIndex;
2203
2508
  state.updatedAt = nowIso();
2204
- useWizard.getState().syncSteps([...state.steps], targetIndex);
2509
+ const store = useWizard.getState();
2510
+ store.setActiveStep(targetIndex);
2511
+ store.syncSteps([...state.steps], targetIndex);
2205
2512
  await saveWorkflowState(state);
2206
2513
  }
2207
- async function runStep(state, index, step, appId) {
2514
+ async function runStep(state, index, step) {
2208
2515
  const store = useWizard.getState();
2209
2516
  const record = state.steps[index];
2210
2517
  const startedAt = Date.now();
2518
+ const appId = runAppId(state);
2211
2519
  trackActionStart({
2212
2520
  workflowId: state.workflowId,
2213
2521
  appId,
@@ -2235,6 +2543,16 @@ async function runStep(state, index, step, appId) {
2235
2543
  await rewindTo(state, err.targetIndex, index);
2236
2544
  return err.targetIndex;
2237
2545
  }
2546
+ if (err instanceof RestartWorkflowSignal) {
2547
+ trackActionAbandoned({
2548
+ workflowId: state.workflowId,
2549
+ appId,
2550
+ actionId: step.id,
2551
+ actionTitle: step.title,
2552
+ durationMs: Date.now() - startedAt,
2553
+ reason: "application_changed"
2554
+ });
2555
+ }
2238
2556
  throw err;
2239
2557
  }
2240
2558
  record.status = "done";
@@ -2253,57 +2571,106 @@ async function runStep(state, index, step, appId) {
2253
2571
  }
2254
2572
  var RESUME_OPTION = "Resume";
2255
2573
  var START_NEW_OPTION = "Start a new one";
2574
+ async function startFresh(workflow, appId) {
2575
+ await clearWorkflowState(workflow.id);
2576
+ return initWorkflowState(workflow, nowIso(), appId);
2577
+ }
2256
2578
  async function resolveWorkflowState(workflow, appId, options) {
2257
2579
  const resumed = options?.resumableState !== void 0 ? options.resumableState : await loadResumableState(workflow);
2258
2580
  if (!resumed) {
2259
2581
  return initWorkflowState(workflow, nowIso(), appId);
2260
2582
  }
2583
+ const savedAppId = runAppId(resumed);
2261
2584
  if (options?.skipResumePrompt) {
2585
+ if (appId && savedAppId === void 0) {
2586
+ resumed.algoliaState.appId = appId;
2587
+ }
2262
2588
  return resumed;
2263
2589
  }
2590
+ if (appId && savedAppId !== appId) {
2591
+ logger.warn(
2592
+ { workflowId: workflow.id, savedAppId, appId },
2593
+ "resolveWorkflowState: persisted run is not pinned to the selected application; starting from scratch"
2594
+ );
2595
+ return startFresh(workflow, appId);
2596
+ }
2264
2597
  const answer = await useWizard.getState().requestUserInput({
2265
2598
  prompt: "You have a previous session. Do you want to resume, or start a new one?",
2266
2599
  promptType: "multipleChoice",
2267
2600
  options: [RESUME_OPTION, START_NEW_OPTION]
2268
2601
  });
2269
2602
  if (answer === START_NEW_OPTION) {
2270
- await clearWorkflowState(workflow.id);
2271
- return initWorkflowState(workflow, nowIso(), appId);
2603
+ return startFresh(workflow, appId);
2272
2604
  }
2273
2605
  return resumed;
2274
2606
  }
2275
- async function runWorkflow(workflow, appId, options) {
2607
+ async function beginRun(workflow, state) {
2608
+ ensureExecutedStepCount(state);
2609
+ ensureAgentRunId(state);
2610
+ setAnalyticsContext({ appId: runAppId(state), agentRunId: state.agentRunId });
2611
+ useWizard.getState().startWorkflow(
2612
+ {
2613
+ id: workflow.id,
2614
+ title: workflow.title,
2615
+ description: workflow.description
2616
+ },
2617
+ [...state.steps]
2618
+ );
2619
+ await ensureConsent();
2620
+ trackWorkflowStart({ workflowId: workflow.id, appId: runAppId(state) });
2621
+ }
2622
+ async function handleRestartWorkflow(workflow, previous, newAppId) {
2623
+ trackWorkflowAbandoned({
2624
+ workflowId: workflow.id,
2625
+ appId: runAppId(previous),
2626
+ reason: "application_changed"
2627
+ });
2628
+ await clearWorkflowState(workflow.id);
2629
+ const next = initWorkflowState(workflow, nowIso(), newAppId);
2630
+ useWizard.getState().resetRunState();
2631
+ useWizard.getState().setLoadingMessage("Restarting wizard\u2026");
2632
+ try {
2633
+ await beginRun(workflow, next);
2634
+ } finally {
2635
+ useWizard.getState().setLoadingMessage(null);
2636
+ }
2637
+ return next;
2638
+ }
2639
+ async function runWorkflow(workflow, startWorkflowAppId, options) {
2276
2640
  const store = useWizard.getState();
2277
2641
  try {
2278
- const state = await resolveWorkflowState(workflow, appId, options);
2279
- ensureExecutedStepCount(state);
2280
- ensureAgentRunId(state);
2281
- setAnalyticsContext({ appId, agentRunId: state.agentRunId });
2282
- store.startWorkflow(
2283
- {
2284
- id: workflow.id,
2285
- title: workflow.title,
2286
- description: workflow.description
2287
- },
2288
- [...state.steps]
2642
+ let state = await resolveWorkflowState(
2643
+ workflow,
2644
+ startWorkflowAppId,
2645
+ options
2289
2646
  );
2290
- await ensureConsent();
2291
- trackWorkflowStart({ workflowId: workflow.id, appId });
2647
+ await beginRun(workflow, state);
2292
2648
  track("AI Wizard Started", {
2293
2649
  version: package_default.version,
2294
2650
  wizard_version: package_default.version,
2295
2651
  os: process.platform
2296
2652
  });
2297
- let i = state.currentStepIndex;
2298
- while (i < workflow.steps.length) {
2299
- const rewoundTo = await runStep(state, i, workflow.steps[i], appId);
2300
- i = rewoundTo ?? i + 1;
2653
+ for (; ; ) {
2654
+ try {
2655
+ let i = state.currentStepIndex;
2656
+ while (i < workflow.steps.length) {
2657
+ const rewoundTo = await runStep(state, i, workflow.steps[i]);
2658
+ i = rewoundTo ?? i + 1;
2659
+ }
2660
+ break;
2661
+ } catch (err) {
2662
+ if (err instanceof RestartWorkflowSignal) {
2663
+ state = await handleRestartWorkflow(workflow, state, err.appId);
2664
+ continue;
2665
+ }
2666
+ throw err;
2667
+ }
2301
2668
  }
2302
2669
  const totalDurationMs = Date.now() - Date.parse(state.startedAt);
2303
2670
  const stepCount = state.executedStepCount ?? state.steps.filter((s) => s.status === "done" && isStepVisible(s)).length;
2304
2671
  trackWizardComplete({
2305
2672
  workflowId: workflow.id,
2306
- appId,
2673
+ appId: runAppId(state),
2307
2674
  total_duration: Math.round(totalDurationMs / 1e3),
2308
2675
  total_steps: stepCount
2309
2676
  });
@@ -2317,19 +2684,20 @@ async function runWorkflow(workflow, appId, options) {
2317
2684
  store.setDone();
2318
2685
  } catch (err) {
2319
2686
  const message = err instanceof Error ? err.message : String(err);
2320
- const state = await loadWorkflowState(workflow.id);
2687
+ const failed = await loadWorkflowState(workflow.id);
2688
+ const failedAppId = failed ? runAppId(failed) : startWorkflowAppId;
2321
2689
  let step = "unknown";
2322
2690
  let failedActionId;
2323
2691
  let failedActionTitle;
2324
- if (state) {
2325
- const record = state.steps[state.currentStepIndex];
2692
+ if (failed) {
2693
+ const record = failed.steps[failed.currentStepIndex];
2326
2694
  if (record) {
2327
2695
  step = record.title;
2328
2696
  if (record.status === "running") {
2329
2697
  record.status = "error";
2330
2698
  record.error = message;
2331
- await saveWorkflowState(state);
2332
- store.syncSteps([...state.steps], state.currentStepIndex);
2699
+ await saveWorkflowState(failed);
2700
+ store.syncSteps([...failed.steps], failed.currentStepIndex);
2333
2701
  }
2334
2702
  failedActionId = record.id;
2335
2703
  failedActionTitle = record.title;
@@ -2338,7 +2706,7 @@ async function runWorkflow(workflow, appId, options) {
2338
2706
  if (failedActionId && failedActionTitle) {
2339
2707
  trackActionError({
2340
2708
  workflowId: workflow.id,
2341
- appId,
2709
+ appId: failedAppId,
2342
2710
  actionId: failedActionId,
2343
2711
  actionTitle: failedActionTitle,
2344
2712
  error: message,
@@ -2347,7 +2715,7 @@ async function runWorkflow(workflow, appId, options) {
2347
2715
  }
2348
2716
  trackWorkflowError({
2349
2717
  workflowId: workflow.id,
2350
- appId,
2718
+ appId: failedAppId,
2351
2719
  error: message,
2352
2720
  actionId: failedActionId,
2353
2721
  cause: err
@@ -2367,12 +2735,12 @@ async function runWorkflow(workflow, appId, options) {
2367
2735
  }
2368
2736
 
2369
2737
  // src/actions/listIndices.ts
2370
- import { z as z4 } from "zod";
2371
- var indicesListSchema = z4.object({
2372
- items: z4.array(
2373
- z4.object({
2374
- name: z4.string(),
2375
- entries: z4.number().default(0)
2738
+ import { z as z5 } from "zod";
2739
+ var indicesListSchema = z5.object({
2740
+ items: z5.array(
2741
+ z5.object({
2742
+ name: z5.string(),
2743
+ entries: z5.number().default(0)
2376
2744
  })
2377
2745
  )
2378
2746
  });
@@ -2382,83 +2750,10 @@ async function listIndices() {
2382
2750
  return items.map((i) => ({ name: i.name, entries: i.entries })).sort((a, b) => a.name.localeCompare(b.name));
2383
2751
  }
2384
2752
 
2385
- // src/lib/algoliaApp.ts
2386
- import { z as z5 } from "zod";
2387
- var applicationSchema = z5.object({
2388
- id: z5.string().min(1),
2389
- name: z5.string().default(""),
2390
- plan: z5.string().optional()
2391
- });
2392
- var listSchema = z5.array(
2393
- z5.object({
2394
- id: z5.string().min(1),
2395
- name: z5.string().default(""),
2396
- plan_label: z5.string().optional(),
2397
- status: z5.string().optional(),
2398
- acl: z5.array(z5.string()).optional()
2399
- }).transform(({ id, name, plan_label, status, acl }) => ({
2400
- id,
2401
- name,
2402
- plan: plan_label,
2403
- status,
2404
- acl
2405
- }))
2406
- );
2407
- function canSelectApplication(app) {
2408
- return app.status === "active" && (app.acl ?? []).includes("keys");
2409
- }
2410
- async function currentApplication() {
2411
- let raw;
2412
- try {
2413
- raw = await runAlgoliaCli(["application", "current", "-o", "json"]);
2414
- } catch {
2415
- return null;
2416
- }
2417
- const parsed = applicationSchema.safeParse(parseJson(raw));
2418
- return parsed.success ? parsed.data : null;
2419
- }
2420
- async function requireApplication() {
2421
- const app = await currentApplication();
2422
- if (!app) {
2423
- throw new Error(
2424
- "No Algolia application is selected. Run `npx @algolia/cli@latest application select` and restart the wizard."
2425
- );
2426
- }
2427
- return app;
2428
- }
2429
- async function listApplications() {
2430
- const raw = await runAlgoliaCli(["application", "list", "-o", "json"]);
2431
- const parsed = listSchema.safeParse(parseJson(raw));
2432
- if (!parsed.success) {
2433
- throw new Error("Could not read the list of Algolia applications.");
2434
- }
2435
- return parsed.data;
2436
- }
2437
- async function selectApplication(id) {
2438
- const raw = await runAlgoliaCli(
2439
- ["application", "select", "--non-interactive", "--app-id", id],
2440
- { onOutput: stderrSink }
2441
- );
2442
- const parsed = applicationSchema.safeParse(parseJson(raw));
2443
- if (!parsed.success) {
2444
- throw new Error(
2445
- `Selected application ${id}, but the Algolia CLI returned an unreadable result.`
2446
- );
2447
- }
2448
- return parsed.data;
2449
- }
2450
- function parseJson(text) {
2451
- try {
2452
- return JSON.parse(text);
2453
- } catch {
2454
- return void 0;
2455
- }
2456
- }
2457
-
2458
2753
  // src/actions/selectIndex.ts
2459
2754
  var CREATE_NEW_INDEX = "Create a new index\u2026";
2460
2755
  var selectIndexStep = async (ctx) => {
2461
- await requireApplication();
2756
+ await requireApplication(ctx.appId);
2462
2757
  const indices = await listIndices();
2463
2758
  const names = indices.map((i) => i.name);
2464
2759
  const hasIndices = names.length > 0;
@@ -2532,7 +2827,7 @@ import { readdir } from "node:fs/promises";
2532
2827
  // src/lib/tools/path.ts
2533
2828
  import { constants } from "node:fs";
2534
2829
  import { lstat, mkdir as mkdir3, open } from "node:fs/promises";
2535
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join5, sep } from "node:path";
2830
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join6, sep } from "node:path";
2536
2831
  var DEFAULT_FILE_MODE = 438;
2537
2832
  function errorCode(err) {
2538
2833
  if (typeof err === "object" && err !== null && "code" in err && typeof err.code === "string") {
@@ -2555,7 +2850,7 @@ async function hasSymlinkParent(ctx, target) {
2555
2850
  let current = ctx.root;
2556
2851
  const parts = relative(ctx.root, dirname2(target)).split(sep).filter(Boolean);
2557
2852
  for (const part of parts) {
2558
- current = join5(current, part);
2853
+ current = join6(current, part);
2559
2854
  try {
2560
2855
  if ((await lstat(current)).isSymbolicLink()) return true;
2561
2856
  } catch (err) {
@@ -2673,7 +2968,7 @@ function reportStatusTool(output) {
2673
2968
  // src/lib/tools/readFile.ts
2674
2969
  import { tool as tool4 } from "ai";
2675
2970
  import z9 from "zod";
2676
- import { readFile as readFile3 } from "node:fs/promises";
2971
+ import { readFile as readFile4 } from "node:fs/promises";
2677
2972
 
2678
2973
  // src/lib/tools/env.ts
2679
2974
  import { basename } from "node:path";
@@ -2761,7 +3056,7 @@ function readFileTool(ctx) {
2761
3056
  const resolved2 = resolveInRoot(ctx, filePath);
2762
3057
  if (resolved2.ok === false) return resolved2.error;
2763
3058
  try {
2764
- const content = await readFile3(resolved2.target, "utf8");
3059
+ const content = await readFile4(resolved2.target, "utf8");
2765
3060
  const safeContent = isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
2766
3061
  return readLines ? readLineRange(safeContent, [readLines[0], readLines[1]]) : sampleFile(safeContent);
2767
3062
  } catch (err) {
@@ -2803,7 +3098,7 @@ function writeFileTool(ctx) {
2803
3098
  // src/lib/tools/writeAlgoliaCredentials.ts
2804
3099
  import { tool as tool6 } from "ai";
2805
3100
  import z13 from "zod";
2806
- import { readFile as readFile5 } from "node:fs/promises";
3101
+ import { readFile as readFile6 } from "node:fs/promises";
2807
3102
  import { relative as relative3 } from "node:path";
2808
3103
 
2809
3104
  // src/lib/algoliaApiKey.ts
@@ -3007,8 +3302,8 @@ async function resolveSearchOnlyKey(index, appId) {
3007
3302
 
3008
3303
  // src/lib/gitignore.ts
3009
3304
  import { execFile } from "node:child_process";
3010
- import { lstat as lstat2, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
3011
- import { join as join6, relative as relative2 } from "node:path";
3305
+ import { lstat as lstat2, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
3306
+ import { join as join7, relative as relative2 } from "node:path";
3012
3307
  var GIT_ENV_OVERRIDES = [
3013
3308
  "GIT_DIR",
3014
3309
  "GIT_WORK_TREE",
@@ -3060,7 +3355,7 @@ async function ensureGitIgnored(root, target) {
3060
3355
  if (ignoredByRule === void 0) return "unknown";
3061
3356
  if (ignoredByRule) return tracked ? "tracked" : "covered";
3062
3357
  const pattern = relative2(root, target);
3063
- const gitIgnore = join6(root, ".gitignore");
3358
+ const gitIgnore = join7(root, ".gitignore");
3064
3359
  try {
3065
3360
  const link = await lstat2(gitIgnore).catch(() => null);
3066
3361
  if (link?.isSymbolicLink()) {
@@ -3070,7 +3365,7 @@ async function ensureGitIgnored(root, target) {
3070
3365
  );
3071
3366
  return "unknown";
3072
3367
  }
3073
- const existing = link ? await readFile4(gitIgnore, "utf8") : "";
3368
+ const existing = link ? await readFile5(gitIgnore, "utf8") : "";
3074
3369
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
3075
3370
  await writeFile3(gitIgnore, `${existing}${prefix}${pattern}
3076
3371
  `, "utf8");
@@ -3138,7 +3433,7 @@ function writeCredentialsTool(ctx) {
3138
3433
  const targetError = await validateSafeWriteTarget(ctx, resolved2.target);
3139
3434
  if (targetError) return targetError;
3140
3435
  try {
3141
- existing = await readFile5(resolved2.target, "utf8");
3436
+ existing = await readFile6(resolved2.target, "utf8");
3142
3437
  } catch (err) {
3143
3438
  if (err.code !== "ENOENT") throw err;
3144
3439
  }
@@ -3155,7 +3450,7 @@ function writeCredentialsTool(ctx) {
3155
3450
  const fileIndex = readEnv(existing, INDEX_NAME_VAR);
3156
3451
  if (fileAppId === null || fileKey === null) {
3157
3452
  try {
3158
- const selected = (await requireApplication()).id;
3453
+ const selected = (await requireApplication(ctx.appId)).id;
3159
3454
  if (fileAppId === null) credentials.push([APP_ID_VAR, selected]);
3160
3455
  if (fileKey === null) {
3161
3456
  if (fileAppId !== null && fileAppId !== selected) {
@@ -3174,7 +3469,7 @@ function writeCredentialsTool(ctx) {
3174
3469
  );
3175
3470
  }
3176
3471
  } catch (err) {
3177
- return `Error: could not resolve Algolia credentials (${err.message}). Ask the user to authenticate with the Algolia CLI first.`;
3472
+ return `Error: could not resolve Algolia credentials (${err.message})`;
3178
3473
  }
3179
3474
  }
3180
3475
  let wrote;
@@ -3228,8 +3523,8 @@ async function gitIgnoreOutcome(ctx, target) {
3228
3523
  // src/lib/tools/searchFiles.ts
3229
3524
  import { tool as tool7 } from "ai";
3230
3525
  import z14 from "zod";
3231
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
3232
- import { join as join7 } from "node:path";
3526
+ import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
3527
+ import { join as join8 } from "node:path";
3233
3528
  var MAX_QUERY_LENGTH = 1e3;
3234
3529
  var SKIP_DIRS = /* @__PURE__ */ new Set([
3235
3530
  "node_modules",
@@ -3244,7 +3539,7 @@ async function walkFiles(dir) {
3244
3539
  const out = [];
3245
3540
  for (const e of await readdir2(dir, { withFileTypes: true })) {
3246
3541
  if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
3247
- const full = join7(dir, e.name);
3542
+ const full = join8(dir, e.name);
3248
3543
  if (e.isDirectory()) out.push(...await walkFiles(full));
3249
3544
  else if (e.isFile()) out.push(full);
3250
3545
  }
@@ -3277,7 +3572,7 @@ function searchFilesTool(ctx) {
3277
3572
  for (const file of await walkFiles(resolved2.target)) {
3278
3573
  let content;
3279
3574
  try {
3280
- content = await readFile6(file, "utf8");
3575
+ content = await readFile7(file, "utf8");
3281
3576
  } catch {
3282
3577
  continue;
3283
3578
  }
@@ -3307,7 +3602,7 @@ import { relative as relative5 } from "node:path";
3307
3602
  import { tool as tool8 } from "ai";
3308
3603
  import z15 from "zod";
3309
3604
  import { createHash as createHash2 } from "node:crypto";
3310
- import { readFile as readFile7, stat as stat2 } from "node:fs/promises";
3605
+ import { readFile as readFile8, stat as stat2 } from "node:fs/promises";
3311
3606
  import { relative as relative4 } from "node:path";
3312
3607
 
3313
3608
  // src/lib/tools/utils/prompt.ts
@@ -3322,7 +3617,7 @@ function serializePrompt(work) {
3322
3617
  // src/lib/tools/reviewScript.ts
3323
3618
  var reviewedCommandApprovals = /* @__PURE__ */ new WeakMap();
3324
3619
  async function fileDigest(filePath) {
3325
- const content = await readFile7(filePath);
3620
+ const content = await readFile8(filePath);
3326
3621
  return createHash2("sha256").update(content).digest("hex");
3327
3622
  }
3328
3623
  function grantReviewedCommandApproval(ctx, approval) {
@@ -4913,13 +5208,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
4913
5208
 
4914
5209
  // src/actions/implement.ts
4915
5210
  import z28 from "zod";
4916
- import { access, mkdir as mkdir5, readFile as readFile8 } from "node:fs/promises";
4917
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join10, relative as relative6, resolve as resolve4, sep as sep2 } from "node:path";
5211
+ import { access, mkdir as mkdir5, readFile as readFile9 } from "node:fs/promises";
5212
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join11, relative as relative6, resolve as resolve4, sep as sep2 } from "node:path";
4918
5213
 
4919
5214
  // src/lib/git.ts
4920
5215
  import { execFile as execFile2 } from "node:child_process";
4921
5216
  import { copyFile, mkdir as mkdir4, stat as stat3 } from "node:fs/promises";
4922
- import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
5217
+ import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
4923
5218
  var MAX_BUFFER = 32 * 1024 * 1024;
4924
5219
  function git(args) {
4925
5220
  return new Promise((resolve5, reject) => {
@@ -4956,8 +5251,8 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
4956
5251
  } catch {
4957
5252
  return { ok: false, reason: `"${sourcePath}" does not exist` };
4958
5253
  }
4959
- const relPath = join8(ingestDir, basename2(source));
4960
- const dest = join8(repoRoot, relPath);
5254
+ const relPath = join9(ingestDir, basename2(source));
5255
+ const dest = join9(repoRoot, relPath);
4961
5256
  if (resolve3(source) === resolve3(dest)) {
4962
5257
  return { ok: true, relPath };
4963
5258
  }
@@ -4996,13 +5291,13 @@ function toRootRelative(p) {
4996
5291
 
4997
5292
  // src/lib/algoliaDocs.ts
4998
5293
  import { readFileSync, readdirSync, existsSync } from "node:fs";
4999
- import { dirname as dirname4, join as join9 } from "node:path";
5294
+ import { dirname as dirname4, join as join10 } from "node:path";
5000
5295
  import { fileURLToPath } from "node:url";
5001
- var DOCS_SUBPATH = join9("docs", "algolia-sdk");
5296
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
5002
5297
  function findDocsDir() {
5003
5298
  let dir = dirname4(fileURLToPath(import.meta.url));
5004
5299
  for (; ; ) {
5005
- const candidate = join9(dir, DOCS_SUBPATH);
5300
+ const candidate = join10(dir, DOCS_SUBPATH);
5006
5301
  if (existsSync(candidate)) return candidate;
5007
5302
  const parent = dirname4(dir);
5008
5303
  if (parent === dir) return void 0;
@@ -5025,7 +5320,7 @@ function loadAlgoliaDoc(language) {
5025
5320
  );
5026
5321
  return "";
5027
5322
  }
5028
- return readFileSync(join9(docsDir, files[0]), "utf8").trim();
5323
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
5029
5324
  }
5030
5325
  function getNamedDoc(name, language) {
5031
5326
  const docsDir = findDocsDir();
@@ -5033,7 +5328,7 @@ function getNamedDoc(name, language) {
5033
5328
  logger.warn("docs/algolia-sdk not found");
5034
5329
  return "";
5035
5330
  }
5036
- const file = join9(docsDir, `${name}-${language}.md`);
5331
+ const file = join10(docsDir, `${name}-${language}.md`);
5037
5332
  if (!existsSync(file)) {
5038
5333
  logger.warn({ name, language }, "named SDK reference not found");
5039
5334
  return "";
@@ -5363,7 +5658,7 @@ function jsTypeName(value) {
5363
5658
  }
5364
5659
  async function firstRecordIn(filePath) {
5365
5660
  try {
5366
- const parsed = JSON.parse(await readFile8(filePath, "utf8"));
5661
+ const parsed = JSON.parse(await readFile9(filePath, "utf8"));
5367
5662
  const first = Array.isArray(parsed) ? parsed[0] : parsed;
5368
5663
  if (first && typeof first === "object" && !Array.isArray(first)) {
5369
5664
  return first;
@@ -5377,10 +5672,10 @@ async function ingestedRecordSample(repoRoot, entityName, uploadFilePath) {
5377
5672
  const candidates = [];
5378
5673
  if (entityName) {
5379
5674
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
5380
- candidates.push(join10(repoRoot, INGEST_DIR, "data", `${slug}.json`));
5675
+ candidates.push(join11(repoRoot, INGEST_DIR, "data", `${slug}.json`));
5381
5676
  }
5382
5677
  if (uploadFilePath?.toLowerCase().endsWith(".json")) {
5383
- candidates.push(join10(repoRoot, uploadFilePath));
5678
+ candidates.push(join11(repoRoot, uploadFilePath));
5384
5679
  }
5385
5680
  for (const candidate of candidates) {
5386
5681
  const sample = await firstRecordIn(candidate);
@@ -5410,12 +5705,15 @@ function ingestFailure(attempt, executions) {
5410
5705
  }
5411
5706
  return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
5412
5707
  }
5413
- function makeToolContext(root, env = async () => ({})) {
5414
- return createToolContext(
5415
- DEFAULT_TOOL_LIMITS,
5416
- root,
5417
- createShellContext({ env, approve: storeApproval(root) })
5418
- );
5708
+ function makeToolContext(root, appId, env = async () => ({})) {
5709
+ return {
5710
+ ...createToolContext(
5711
+ DEFAULT_TOOL_LIMITS,
5712
+ root,
5713
+ createShellContext({ env, approve: storeApproval(root) })
5714
+ ),
5715
+ appId
5716
+ };
5419
5717
  }
5420
5718
  function validationRetryInstructions(validation) {
5421
5719
  return [
@@ -5439,7 +5737,7 @@ async function frontendHasPackageJson(repoRoot, searchLocation) {
5439
5737
  if (!outsideRoot) directory = candidate;
5440
5738
  }
5441
5739
  for (; ; ) {
5442
- if (await pathExists(join10(directory, "package.json"))) return true;
5740
+ if (await pathExists(join11(directory, "package.json"))) return true;
5443
5741
  if (directory === repoRoot) return false;
5444
5742
  directory = dirname5(directory);
5445
5743
  }
@@ -5448,7 +5746,7 @@ async function resolveBuildCheckPlan(ctx, repoRoot, input) {
5448
5746
  if (!input.findings.verification?.length) {
5449
5747
  return { run: false, installDependencies: false };
5450
5748
  }
5451
- const installDependencies = input.frontendHasPackageJson && isJsProject(input.language) && !await pathExists(join10(repoRoot, "node_modules"));
5749
+ const installDependencies = input.frontendHasPackageJson && isJsProject(input.language) && !await pathExists(join11(repoRoot, "node_modules"));
5452
5750
  const accepted = Boolean(
5453
5751
  await ctx.requestUserInput({
5454
5752
  prompt: installDependencies ? MISSING_DEPENDENCIES_PROMPT : BUILD_CHECK_PROMPT,
@@ -5526,7 +5824,7 @@ async function resolveIngestionSource(ctx, repoRoot) {
5526
5824
  messages: []
5527
5825
  });
5528
5826
  const uploadSourcePath = typeof answer === "string" ? answer : "";
5529
- await mkdir5(join10(repoRoot, INGEST_DIR), { recursive: true });
5827
+ await mkdir5(join11(repoRoot, INGEST_DIR), { recursive: true });
5530
5828
  const copied = await copyUploadIntoProject(
5531
5829
  repoRoot,
5532
5830
  INGEST_DIR,
@@ -5536,10 +5834,7 @@ async function resolveIngestionSource(ctx, repoRoot) {
5536
5834
  return { ingestionSource: "fileUpload", uploadFilePath: copied.relPath };
5537
5835
  }
5538
5836
  error = `Could not use the uploaded file (${copied.reason}).`;
5539
- logger.warn(
5540
- { reason: copied.reason },
5541
- "implement: file upload unavailable"
5542
- );
5837
+ logger.warn({ reason: copied.reason }, "implement: file upload unavailable");
5543
5838
  }
5544
5839
  }
5545
5840
  async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
@@ -5562,6 +5857,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5562
5857
  let ingestionSource = "generated";
5563
5858
  let uploadFilePath = void 0;
5564
5859
  if (useCases.includes("ingestion")) {
5860
+ ;
5565
5861
  ({ ingestionSource, uploadFilePath } = await resolveIngestionSource(
5566
5862
  ctx,
5567
5863
  repoRoot
@@ -5570,7 +5866,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5570
5866
  const targetIndex = selected?.selection;
5571
5867
  useWizard.getState().setTargetIndex(targetIndex ?? null);
5572
5868
  if (useCases.includes("ingestion")) {
5573
- await mkdir5(join10(repoRoot, INGEST_DIR), { recursive: true });
5869
+ await mkdir5(join11(repoRoot, INGEST_DIR), { recursive: true });
5574
5870
  }
5575
5871
  const findings = normalizeFindingPaths({
5576
5872
  ingestionAnalysis: entities?.ingestionAnalysis ?? scan.ingestionAnalysis,
@@ -5583,10 +5879,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5583
5879
  let appId;
5584
5880
  let ingestAppId;
5585
5881
  if (useCases.includes("search")) {
5586
- appId = (await requireApplication()).id;
5882
+ appId = (await requireApplication(ctx.appId)).id;
5587
5883
  }
5588
5884
  if (useCases.includes("ingestion")) {
5589
- ingestAppId = appId ?? (await requireApplication()).id;
5885
+ ingestAppId = appId ?? (await requireApplication(ctx.appId)).id;
5590
5886
  }
5591
5887
  let ingestWriteKey;
5592
5888
  if (useCases.includes("ingestion") && ingestAppId) {
@@ -5638,11 +5934,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5638
5934
  let ingestDurationMs;
5639
5935
  let ingestOutcomeMessage;
5640
5936
  const ingestKeyAppId = ingestAppId;
5641
- const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(repoRoot, async () => ({
5937
+ const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(repoRoot, ctx.appId, async () => ({
5642
5938
  [APP_ID_VAR]: ingestKeyAppId,
5643
5939
  [API_KEY_VAR]: ingestWriteKey,
5644
5940
  [INDEX_NAME_VAR]: targetIndex
5645
- })) : makeToolContext(repoRoot);
5941
+ })) : makeToolContext(repoRoot, ctx.appId);
5646
5942
  async function runImplementationUseCase(currentUseCase, extraInstructions = [], isRetry = false) {
5647
5943
  if (agentRuns > 0) ctx.recordStepExecution();
5648
5944
  agentRuns += 1;
@@ -5667,14 +5963,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5667
5963
  );
5668
5964
  return runAgent({
5669
5965
  operation: "search-validation",
5670
- instructions: buildAgentInstructions(
5671
- "validation",
5672
- input,
5673
- [
5674
- writtenFiles.length ? `Files written during search implementation: ${JSON.stringify(writtenFiles)}. Inspect these first, then trace the actual import and render site.` : "The search agent reported no written files. Inspect the current diff and set sufficient=false if no complete mounted search implementation exists.",
5675
- ...extraInstructions
5676
- ]
5677
- ),
5966
+ instructions: buildAgentInstructions("validation", input, [
5967
+ writtenFiles.length ? `Files written during search implementation: ${JSON.stringify(writtenFiles)}. Inspect these first, then trace the actual import and render site.` : "The search agent reported no written files. Inspect the current diff and set sufficient=false if no complete mounted search implementation exists.",
5968
+ ...extraInstructions
5969
+ ]),
5678
5970
  tools: toolsForUseCase("validation"),
5679
5971
  outputSchema: validationOutputSchema,
5680
5972
  modelProfile: "validation" /* validation */,
@@ -5884,7 +6176,7 @@ ${detail}` : ""}`
5884
6176
  if (searchConfigFile) {
5885
6177
  const ignoreStatus = await gitIgnoreStatus(
5886
6178
  repoRoot,
5887
- join10(repoRoot, searchConfigFile)
6179
+ join11(repoRoot, searchConfigFile)
5888
6180
  );
5889
6181
  if (ignoreStatus === "covered") {
5890
6182
  summaries.push(
@@ -6028,13 +6320,21 @@ import { z as z32 } from "zod";
6028
6320
  import "zod";
6029
6321
  var DASHBOARD_API_BASE_URL = `${PROXY_BASE_URL}/dashboard`;
6030
6322
  var DashboardApiError = class extends Error {
6031
- constructor(status, message) {
6032
- super(message);
6323
+ constructor(status, body) {
6324
+ super("Couldn't complete the request.");
6033
6325
  this.status = status;
6326
+ this.body = body;
6034
6327
  this.name = "DashboardApiError";
6035
6328
  }
6036
6329
  status;
6330
+ body;
6331
+ errorBody() {
6332
+ return isRecord(this.body) ? this.body : void 0;
6333
+ }
6037
6334
  };
6335
+ function isRecord(value) {
6336
+ return !!value && typeof value === "object" && !Array.isArray(value);
6337
+ }
6038
6338
  async function dashboardRequest(path, schema, init) {
6039
6339
  const token = getAuthToken();
6040
6340
  if (!token) {
@@ -6049,17 +6349,24 @@ async function dashboardRequest(path, schema, init) {
6049
6349
  }
6050
6350
  });
6051
6351
  if (!res.ok) {
6052
- throw new DashboardApiError(
6053
- res.status,
6054
- `Dashboard API responded ${res.status} for ${path}`
6055
- );
6352
+ throw await dashboardError(res);
6056
6353
  }
6057
6354
  const body = await res.json();
6058
6355
  return { status: res.status, data: schema.parse(body) };
6059
6356
  }
6357
+ async function dashboardError(res) {
6358
+ try {
6359
+ return new DashboardApiError(res.status, await res.json());
6360
+ } catch {
6361
+ return new DashboardApiError(res.status, void 0);
6362
+ }
6363
+ }
6060
6364
 
6061
6365
  // src/lib/dashboardApi/schemas.ts
6062
6366
  import { z as z31 } from "zod";
6367
+ var messageResponseSchema = z31.object({
6368
+ message: z31.string()
6369
+ });
6063
6370
  var playbookSchema = z31.object({
6064
6371
  slug: z31.string(),
6065
6372
  category: z31.string().optional(),
@@ -6067,6 +6374,57 @@ var playbookSchema = z31.object({
6067
6374
  description: z31.string().optional()
6068
6375
  });
6069
6376
  var playbooksListSchema = z31.array(playbookSchema);
6377
+ var playbookExecutionStepSchema = z31.enum([
6378
+ "assessment",
6379
+ "optimization",
6380
+ "implement"
6381
+ ]);
6382
+ var playbookExecutionStepStatusSchema = z31.enum([
6383
+ "not_started",
6384
+ "in_progress",
6385
+ "done",
6386
+ "error"
6387
+ ]);
6388
+ var schemaComparisonEntrySchema = z31.object({
6389
+ canonical_attr: z31.string(),
6390
+ canonical_type: z31.array(z31.string()),
6391
+ canonical_roles: z31.array(z31.string()),
6392
+ canonical_required: z31.boolean(),
6393
+ index_attr: z31.string().nullable(),
6394
+ index_type: z31.array(z31.string()).nullable(),
6395
+ index_required: z31.boolean().nullable(),
6396
+ status: z31.string(),
6397
+ mapping_confidence: z31.number()
6398
+ });
6399
+ var settingsRecommendationSchema = z31.record(
6400
+ z31.string(),
6401
+ z31.object({
6402
+ current: z31.any(),
6403
+ recommended: z31.any()
6404
+ })
6405
+ );
6406
+ var assessmentResultSchema = z31.object({
6407
+ ready: z31.boolean(),
6408
+ schema_comparison: z31.array(schemaComparisonEntrySchema),
6409
+ settings_recommendation: settingsRecommendationSchema
6410
+ });
6411
+ var playbookExecutionSchema = z31.object({
6412
+ uuid: z31.string(),
6413
+ playbook_slug: z31.string(),
6414
+ application_id: z31.string(),
6415
+ source_index_name: z31.string(),
6416
+ current_step: playbookExecutionStepSchema,
6417
+ step_status: playbookExecutionStepStatusSchema,
6418
+ status_reason: z31.string().nullable(),
6419
+ completed: z31.boolean(),
6420
+ assessment_result: assessmentResultSchema.nullable(),
6421
+ settings_snapshot: z31.json().nullable(),
6422
+ created_at: z31.iso.datetime()
6423
+ });
6424
+ var playbookExecutionCreateResponseSchema = playbookExecutionSchema.extend({
6425
+ resumed: z31.boolean(),
6426
+ sample_data: z31.boolean()
6427
+ });
6070
6428
 
6071
6429
  // src/lib/dashboardApi/api.ts
6072
6430
  async function getPlaybooks() {
@@ -6115,20 +6473,28 @@ import { z as z33 } from "zod";
6115
6473
  var confirmPlaybookSchema = z33.void();
6116
6474
  var CONTINUE_OPTION = "Looks good, continue";
6117
6475
  var GO_BACK_OPTION = "Go back";
6476
+ var SELECT_APP_OPTION = "Select app";
6118
6477
  async function confirmPlaybookStep(ctx) {
6119
6478
  const { slug } = ctx.getStepOutput("select-playbook");
6120
- const selection = await ctx.requestUserInput({
6121
- prompt: "Ready to continue?",
6122
- promptType: "multipleChoice",
6123
- options: [CONTINUE_OPTION, GO_BACK_OPTION],
6124
- messages: [`Playbook: ${slug}`],
6125
- helpText: "Going back re-opens the playbook catalogue."
6126
- });
6127
- if (typeof selection !== "string") {
6128
- throw new Error("confirm-playbook received an unexpected result");
6129
- }
6130
- if (selection === GO_BACK_OPTION) {
6131
- return ctx.returnToStep("select-playbook");
6479
+ for (; ; ) {
6480
+ const selection = await ctx.requestUserInput({
6481
+ prompt: "Ready to continue?",
6482
+ promptType: "multipleChoice",
6483
+ options: [CONTINUE_OPTION, GO_BACK_OPTION, SELECT_APP_OPTION],
6484
+ messages: [`Playbook: ${slug}`, `Application: ${ctx.appId}`],
6485
+ helpText: "Going back re-opens the playbook catalogue. Select app opens the application catalogue \u2014 keep the current app to stay here, or pick another to restart the wizard."
6486
+ });
6487
+ if (typeof selection !== "string") {
6488
+ throw new Error("confirm-playbook received an unexpected result");
6489
+ }
6490
+ if (selection === SELECT_APP_OPTION) {
6491
+ await ctx.changeApplication();
6492
+ continue;
6493
+ }
6494
+ if (selection === GO_BACK_OPTION) {
6495
+ return ctx.returnToStep("select-playbook");
6496
+ }
6497
+ return;
6132
6498
  }
6133
6499
  }
6134
6500
 
@@ -6219,7 +6585,7 @@ function getWorkflow(id) {
6219
6585
  }
6220
6586
 
6221
6587
  // src/ui/Welcome.tsx
6222
- import { dirname as dirname6, join as join11 } from "node:path";
6588
+ import { dirname as dirname6, join as join12 } from "node:path";
6223
6589
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6224
6590
  import { useState as useState10 } from "react";
6225
6591
  import { Box as Box11, Spacer, Text as Text12, useInput as useInput6, useWindowSize as useWindowSize5 } from "ink";
@@ -6251,7 +6617,7 @@ var sidebarItems = [
6251
6617
  // src/ui/Welcome.tsx
6252
6618
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
6253
6619
  import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
6254
- var IMAGE_PATH = join11(dirname6(fileURLToPath2(import.meta.url)), "algolia.png");
6620
+ var IMAGE_PATH = join12(dirname6(fileURLToPath2(import.meta.url)), "algolia.png");
6255
6621
  var TERMINAL_INFO = {
6256
6622
  ...defaultTerminalInfo,
6257
6623
  supportsUnicode: true,
@@ -7084,6 +7450,7 @@ function App() {
7084
7450
  user,
7085
7451
  workflow,
7086
7452
  settingUpAppId,
7453
+ loadingMessage,
7087
7454
  review: review2
7088
7455
  } = useWizard();
7089
7456
  const { exit } = useApp();
@@ -7190,6 +7557,11 @@ function App() {
7190
7557
  " ",
7191
7558
  settingUpAppId
7192
7559
  ] }) }),
7560
+ phase === "running" && !settingUpAppId && loadingMessage && /* @__PURE__ */ jsx19(Box20, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.strong, bold: true, children: [
7561
+ /* @__PURE__ */ jsx19(Spinner3, { type: "dots" }),
7562
+ " ",
7563
+ loadingMessage
7564
+ ] }) }),
7193
7565
  /* @__PURE__ */ jsx19(CliOutput, {}),
7194
7566
  showTips && currentStep && /* @__PURE__ */ jsx19(
7195
7567
  Tips,
@@ -7223,126 +7595,6 @@ function App() {
7223
7595
  );
7224
7596
  }
7225
7597
 
7226
- // src/lib/envAppId.ts
7227
- import { readFile as readFile9 } from "node:fs/promises";
7228
- import { join as join12 } from "node:path";
7229
- var ENV_FILES = [".env", ".env.local"];
7230
- var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
7231
- async function findEnvApplicationId(root = process.cwd()) {
7232
- for (const file of ENV_FILES) {
7233
- let content;
7234
- try {
7235
- content = await readFile9(join12(root, file), "utf8");
7236
- } catch (err) {
7237
- if (err.code !== "ENOENT") {
7238
- logger.warn(
7239
- { file, err },
7240
- "could not read env file for an application id"
7241
- );
7242
- }
7243
- continue;
7244
- }
7245
- for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
7246
- const id = readValue(raw);
7247
- if (id) {
7248
- logger.info({ file, name, app: id }, "found an application id in env");
7249
- return { id, name, file };
7250
- }
7251
- }
7252
- }
7253
- return null;
7254
- }
7255
- function readValue(raw) {
7256
- const trimmed = raw.trim();
7257
- const quoted = trimmed.match(/^(['"])(.*)\1/);
7258
- const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
7259
- return value.length > 0 && !value.startsWith("<") ? value : null;
7260
- }
7261
-
7262
- // src/lib/algoliaAppPicker.ts
7263
- function blockReasonFor(app) {
7264
- return app.status !== "active" ? "Paused" : "Missing permissions";
7265
- }
7266
- function secondaryFor(app, highlightId) {
7267
- if (!canSelectApplication(app)) {
7268
- return { kind: "text", value: blockReasonFor(app) };
7269
- }
7270
- if (app.id === highlightId) {
7271
- return { kind: "badge", value: "[DETECTED]" };
7272
- }
7273
- return app.plan ? { kind: "badge", value: app.plan } : void 0;
7274
- }
7275
- function labelFor(app) {
7276
- return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
7277
- }
7278
- async function selectAndReport(id) {
7279
- const store = useWizard.getState();
7280
- store.setSettingUpApp(id);
7281
- try {
7282
- return await selectApplication(id);
7283
- } finally {
7284
- store.setSettingUpApp(null);
7285
- }
7286
- }
7287
- async function promptForApplication(highlight) {
7288
- const store = useWizard.getState();
7289
- const unordered = await listApplications();
7290
- if (unordered.length === 0) {
7291
- throw new Error(
7292
- "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
7293
- );
7294
- }
7295
- if (!unordered.some(canSelectApplication)) {
7296
- throw new Error(
7297
- "None of the applications on this Algolia account are usable \u2014 each is either paused or missing key-management access. Activate one, or grant it the `keys` ACL, then restart the wizard."
7298
- );
7299
- }
7300
- const apps = [
7301
- ...unordered.filter(canSelectApplication),
7302
- ...unordered.filter((app) => !canSelectApplication(app))
7303
- ];
7304
- const envApp = highlight ? unordered.find((app) => app.id === highlight.id) : void 0;
7305
- const highlightIndex = envApp && canSelectApplication(envApp) ? apps.indexOf(envApp) : -1;
7306
- const messages = [];
7307
- if (highlight && highlightIndex < 0) {
7308
- messages.push(
7309
- envApp ? `Could not use ${highlight.id} from ${highlight.file} \u2014 it\u2019s ${blockReasonFor(envApp).toLowerCase()}. Pick another below.` : `Could not use ${highlight.id} from ${highlight.file} \u2014 it may have been removed, or this account may not have access to it.`
7310
- );
7311
- }
7312
- for (; ; ) {
7313
- const choice = await store.requestUserInput({
7314
- prompt: "Which Algolia application should the wizard work in?",
7315
- promptType: "multipleChoice",
7316
- options: apps.map(labelFor),
7317
- secondary: apps.map((app) => secondaryFor(app, highlight?.id)),
7318
- disabled: apps.map((app) => !canSelectApplication(app)),
7319
- ...highlightIndex >= 0 ? { defaultSelectedIndex: highlightIndex } : {},
7320
- messages
7321
- });
7322
- const chosen = apps.find((app) => labelFor(app) === choice);
7323
- if (!chosen || !canSelectApplication(chosen)) {
7324
- throw new Error("Application picker received an unexpected selection");
7325
- }
7326
- try {
7327
- return await selectAndReport(chosen.id);
7328
- } catch (err) {
7329
- logger.warn(
7330
- { app: chosen.id, err: err.message },
7331
- "application select failed; re-prompting"
7332
- );
7333
- messages.push(
7334
- `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
7335
- );
7336
- }
7337
- }
7338
- }
7339
- async function ensureApplication(resumeAppId) {
7340
- if (resumeAppId) return selectAndReport(resumeAppId);
7341
- const env = await findEnvApplicationId();
7342
- if (!env) return promptForApplication();
7343
- return promptForApplication({ id: env.id, file: env.file });
7344
- }
7345
-
7346
7598
  // src/lib/seed.ts
7347
7599
  var projectScan2 = {
7348
7600
  languages: [{ name: "TypeScript", version: "5.7.2" }],
@@ -7589,7 +7841,7 @@ function delay(ms) {
7589
7841
  // package.json with { type: 'json' }
7590
7842
  var package_default2 = {
7591
7843
  name: "@algolia/wizard",
7592
- version: "0.71.0",
7844
+ version: "0.73.0",
7593
7845
  description: "Magically implement Algolia functionality in your codebase",
7594
7846
  type: "module",
7595
7847
  engines: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {