@algolia/wizard 0.72.0 → 0.74.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 +496 -309
  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.72.0",
2263
+ version: "0.74.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 "";
@@ -5221,6 +5516,7 @@ function searchInstructions(input) {
5221
5516
  "Style the search surfaces through the widget's class and CSS-variable overrides (or, when hand-built, the app's existing theme tokens): give the results panel and the element that actually draws the input's border and background \u2014 for a widget, the form or wrapper it renders around the bare input \u2014 non-transparent surfaces, keep that frame distinguishable from the background behind it in every supported theme, and never assume a light surface or reuse the surrounding page's colors unchanged inside the panel.",
5222
5517
  "The panel takes its width from the input by default, so a small input makes it unreadably narrow: give it `min-width: min(320px, calc(100vw - 2rem))` independent of the input, cap its maximum width to the viewport, and anchor it to the input edge it opens from so widening does not push it off-screen.",
5223
5518
  "Match the application styles closely, including light, dark, and high-contrast themes the project already supports.",
5519
+ "Before reporting, mount the component and fix any type-check or lint errors your changes cause. If a tool limit is reached or an error remains unresolved, keep the implementation, report status=success, and name the remaining issue in the summary so validation can drive the next pass. Report status=fail only when no search implementation can be written, with the concrete blocker as the reason.",
5224
5520
  "The summary should be extremely concise; do not mention manual testing steps."
5225
5521
  ];
5226
5522
  }
@@ -5363,7 +5659,7 @@ function jsTypeName(value) {
5363
5659
  }
5364
5660
  async function firstRecordIn(filePath) {
5365
5661
  try {
5366
- const parsed = JSON.parse(await readFile8(filePath, "utf8"));
5662
+ const parsed = JSON.parse(await readFile9(filePath, "utf8"));
5367
5663
  const first = Array.isArray(parsed) ? parsed[0] : parsed;
5368
5664
  if (first && typeof first === "object" && !Array.isArray(first)) {
5369
5665
  return first;
@@ -5377,10 +5673,10 @@ async function ingestedRecordSample(repoRoot, entityName, uploadFilePath) {
5377
5673
  const candidates = [];
5378
5674
  if (entityName) {
5379
5675
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
5380
- candidates.push(join10(repoRoot, INGEST_DIR, "data", `${slug}.json`));
5676
+ candidates.push(join11(repoRoot, INGEST_DIR, "data", `${slug}.json`));
5381
5677
  }
5382
5678
  if (uploadFilePath?.toLowerCase().endsWith(".json")) {
5383
- candidates.push(join10(repoRoot, uploadFilePath));
5679
+ candidates.push(join11(repoRoot, uploadFilePath));
5384
5680
  }
5385
5681
  for (const candidate of candidates) {
5386
5682
  const sample = await firstRecordIn(candidate);
@@ -5410,12 +5706,15 @@ function ingestFailure(attempt, executions) {
5410
5706
  }
5411
5707
  return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
5412
5708
  }
5413
- function makeToolContext(root, env = async () => ({})) {
5414
- return createToolContext(
5415
- DEFAULT_TOOL_LIMITS,
5416
- root,
5417
- createShellContext({ env, approve: storeApproval(root) })
5418
- );
5709
+ function makeToolContext(root, appId, env = async () => ({})) {
5710
+ return {
5711
+ ...createToolContext(
5712
+ DEFAULT_TOOL_LIMITS,
5713
+ root,
5714
+ createShellContext({ env, approve: storeApproval(root) })
5715
+ ),
5716
+ appId
5717
+ };
5419
5718
  }
5420
5719
  function validationRetryInstructions(validation) {
5421
5720
  return [
@@ -5439,7 +5738,7 @@ async function frontendHasPackageJson(repoRoot, searchLocation) {
5439
5738
  if (!outsideRoot) directory = candidate;
5440
5739
  }
5441
5740
  for (; ; ) {
5442
- if (await pathExists(join10(directory, "package.json"))) return true;
5741
+ if (await pathExists(join11(directory, "package.json"))) return true;
5443
5742
  if (directory === repoRoot) return false;
5444
5743
  directory = dirname5(directory);
5445
5744
  }
@@ -5448,7 +5747,7 @@ async function resolveBuildCheckPlan(ctx, repoRoot, input) {
5448
5747
  if (!input.findings.verification?.length) {
5449
5748
  return { run: false, installDependencies: false };
5450
5749
  }
5451
- const installDependencies = input.frontendHasPackageJson && isJsProject(input.language) && !await pathExists(join10(repoRoot, "node_modules"));
5750
+ const installDependencies = input.frontendHasPackageJson && isJsProject(input.language) && !await pathExists(join11(repoRoot, "node_modules"));
5452
5751
  const accepted = Boolean(
5453
5752
  await ctx.requestUserInput({
5454
5753
  prompt: installDependencies ? MISSING_DEPENDENCIES_PROMPT : BUILD_CHECK_PROMPT,
@@ -5526,7 +5825,7 @@ async function resolveIngestionSource(ctx, repoRoot) {
5526
5825
  messages: []
5527
5826
  });
5528
5827
  const uploadSourcePath = typeof answer === "string" ? answer : "";
5529
- await mkdir5(join10(repoRoot, INGEST_DIR), { recursive: true });
5828
+ await mkdir5(join11(repoRoot, INGEST_DIR), { recursive: true });
5530
5829
  const copied = await copyUploadIntoProject(
5531
5830
  repoRoot,
5532
5831
  INGEST_DIR,
@@ -5536,10 +5835,7 @@ async function resolveIngestionSource(ctx, repoRoot) {
5536
5835
  return { ingestionSource: "fileUpload", uploadFilePath: copied.relPath };
5537
5836
  }
5538
5837
  error = `Could not use the uploaded file (${copied.reason}).`;
5539
- logger.warn(
5540
- { reason: copied.reason },
5541
- "implement: file upload unavailable"
5542
- );
5838
+ logger.warn({ reason: copied.reason }, "implement: file upload unavailable");
5543
5839
  }
5544
5840
  }
5545
5841
  async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
@@ -5562,6 +5858,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5562
5858
  let ingestionSource = "generated";
5563
5859
  let uploadFilePath = void 0;
5564
5860
  if (useCases.includes("ingestion")) {
5861
+ ;
5565
5862
  ({ ingestionSource, uploadFilePath } = await resolveIngestionSource(
5566
5863
  ctx,
5567
5864
  repoRoot
@@ -5570,7 +5867,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5570
5867
  const targetIndex = selected?.selection;
5571
5868
  useWizard.getState().setTargetIndex(targetIndex ?? null);
5572
5869
  if (useCases.includes("ingestion")) {
5573
- await mkdir5(join10(repoRoot, INGEST_DIR), { recursive: true });
5870
+ await mkdir5(join11(repoRoot, INGEST_DIR), { recursive: true });
5574
5871
  }
5575
5872
  const findings = normalizeFindingPaths({
5576
5873
  ingestionAnalysis: entities?.ingestionAnalysis ?? scan.ingestionAnalysis,
@@ -5583,10 +5880,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5583
5880
  let appId;
5584
5881
  let ingestAppId;
5585
5882
  if (useCases.includes("search")) {
5586
- appId = (await requireApplication()).id;
5883
+ appId = (await requireApplication(ctx.appId)).id;
5587
5884
  }
5588
5885
  if (useCases.includes("ingestion")) {
5589
- ingestAppId = appId ?? (await requireApplication()).id;
5886
+ ingestAppId = appId ?? (await requireApplication(ctx.appId)).id;
5590
5887
  }
5591
5888
  let ingestWriteKey;
5592
5889
  if (useCases.includes("ingestion") && ingestAppId) {
@@ -5638,11 +5935,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5638
5935
  let ingestDurationMs;
5639
5936
  let ingestOutcomeMessage;
5640
5937
  const ingestKeyAppId = ingestAppId;
5641
- const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(repoRoot, async () => ({
5938
+ const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(repoRoot, ctx.appId, async () => ({
5642
5939
  [APP_ID_VAR]: ingestKeyAppId,
5643
5940
  [API_KEY_VAR]: ingestWriteKey,
5644
5941
  [INDEX_NAME_VAR]: targetIndex
5645
- })) : makeToolContext(repoRoot);
5942
+ })) : makeToolContext(repoRoot, ctx.appId);
5646
5943
  async function runImplementationUseCase(currentUseCase, extraInstructions = [], isRetry = false) {
5647
5944
  if (agentRuns > 0) ctx.recordStepExecution();
5648
5945
  agentRuns += 1;
@@ -5667,14 +5964,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
5667
5964
  );
5668
5965
  return runAgent({
5669
5966
  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
- ),
5967
+ instructions: buildAgentInstructions("validation", input, [
5968
+ 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.",
5969
+ ...extraInstructions
5970
+ ]),
5678
5971
  tools: toolsForUseCase("validation"),
5679
5972
  outputSchema: validationOutputSchema,
5680
5973
  modelProfile: "validation" /* validation */,
@@ -5884,7 +6177,7 @@ ${detail}` : ""}`
5884
6177
  if (searchConfigFile) {
5885
6178
  const ignoreStatus = await gitIgnoreStatus(
5886
6179
  repoRoot,
5887
- join10(repoRoot, searchConfigFile)
6180
+ join11(repoRoot, searchConfigFile)
5888
6181
  );
5889
6182
  if (ignoreStatus === "covered") {
5890
6183
  summaries.push(
@@ -6181,20 +6474,28 @@ import { z as z33 } from "zod";
6181
6474
  var confirmPlaybookSchema = z33.void();
6182
6475
  var CONTINUE_OPTION = "Looks good, continue";
6183
6476
  var GO_BACK_OPTION = "Go back";
6477
+ var SELECT_APP_OPTION = "Select app";
6184
6478
  async function confirmPlaybookStep(ctx) {
6185
6479
  const { slug } = ctx.getStepOutput("select-playbook");
6186
- const selection = await ctx.requestUserInput({
6187
- prompt: "Ready to continue?",
6188
- promptType: "multipleChoice",
6189
- options: [CONTINUE_OPTION, GO_BACK_OPTION],
6190
- messages: [`Playbook: ${slug}`],
6191
- helpText: "Going back re-opens the playbook catalogue."
6192
- });
6193
- if (typeof selection !== "string") {
6194
- throw new Error("confirm-playbook received an unexpected result");
6195
- }
6196
- if (selection === GO_BACK_OPTION) {
6197
- return ctx.returnToStep("select-playbook");
6480
+ for (; ; ) {
6481
+ const selection = await ctx.requestUserInput({
6482
+ prompt: "Ready to continue?",
6483
+ promptType: "multipleChoice",
6484
+ options: [CONTINUE_OPTION, GO_BACK_OPTION, SELECT_APP_OPTION],
6485
+ messages: [`Playbook: ${slug}`, `Application: ${ctx.appId}`],
6486
+ 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."
6487
+ });
6488
+ if (typeof selection !== "string") {
6489
+ throw new Error("confirm-playbook received an unexpected result");
6490
+ }
6491
+ if (selection === SELECT_APP_OPTION) {
6492
+ await ctx.changeApplication();
6493
+ continue;
6494
+ }
6495
+ if (selection === GO_BACK_OPTION) {
6496
+ return ctx.returnToStep("select-playbook");
6497
+ }
6498
+ return;
6198
6499
  }
6199
6500
  }
6200
6501
 
@@ -6285,7 +6586,7 @@ function getWorkflow(id) {
6285
6586
  }
6286
6587
 
6287
6588
  // src/ui/Welcome.tsx
6288
- import { dirname as dirname6, join as join11 } from "node:path";
6589
+ import { dirname as dirname6, join as join12 } from "node:path";
6289
6590
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6290
6591
  import { useState as useState10 } from "react";
6291
6592
  import { Box as Box11, Spacer, Text as Text12, useInput as useInput6, useWindowSize as useWindowSize5 } from "ink";
@@ -6317,7 +6618,7 @@ var sidebarItems = [
6317
6618
  // src/ui/Welcome.tsx
6318
6619
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
6319
6620
  import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
6320
- var IMAGE_PATH = join11(dirname6(fileURLToPath2(import.meta.url)), "algolia.png");
6621
+ var IMAGE_PATH = join12(dirname6(fileURLToPath2(import.meta.url)), "algolia.png");
6321
6622
  var TERMINAL_INFO = {
6322
6623
  ...defaultTerminalInfo,
6323
6624
  supportsUnicode: true,
@@ -7150,6 +7451,7 @@ function App() {
7150
7451
  user,
7151
7452
  workflow,
7152
7453
  settingUpAppId,
7454
+ loadingMessage,
7153
7455
  review: review2
7154
7456
  } = useWizard();
7155
7457
  const { exit } = useApp();
@@ -7256,6 +7558,11 @@ function App() {
7256
7558
  " ",
7257
7559
  settingUpAppId
7258
7560
  ] }) }),
7561
+ phase === "running" && !settingUpAppId && loadingMessage && /* @__PURE__ */ jsx19(Box20, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.strong, bold: true, children: [
7562
+ /* @__PURE__ */ jsx19(Spinner3, { type: "dots" }),
7563
+ " ",
7564
+ loadingMessage
7565
+ ] }) }),
7259
7566
  /* @__PURE__ */ jsx19(CliOutput, {}),
7260
7567
  showTips && currentStep && /* @__PURE__ */ jsx19(
7261
7568
  Tips,
@@ -7289,126 +7596,6 @@ function App() {
7289
7596
  );
7290
7597
  }
7291
7598
 
7292
- // src/lib/envAppId.ts
7293
- import { readFile as readFile9 } from "node:fs/promises";
7294
- import { join as join12 } from "node:path";
7295
- var ENV_FILES = [".env", ".env.local"];
7296
- var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
7297
- async function findEnvApplicationId(root = process.cwd()) {
7298
- for (const file of ENV_FILES) {
7299
- let content;
7300
- try {
7301
- content = await readFile9(join12(root, file), "utf8");
7302
- } catch (err) {
7303
- if (err.code !== "ENOENT") {
7304
- logger.warn(
7305
- { file, err },
7306
- "could not read env file for an application id"
7307
- );
7308
- }
7309
- continue;
7310
- }
7311
- for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
7312
- const id = readValue(raw);
7313
- if (id) {
7314
- logger.info({ file, name, app: id }, "found an application id in env");
7315
- return { id, name, file };
7316
- }
7317
- }
7318
- }
7319
- return null;
7320
- }
7321
- function readValue(raw) {
7322
- const trimmed = raw.trim();
7323
- const quoted = trimmed.match(/^(['"])(.*)\1/);
7324
- const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
7325
- return value.length > 0 && !value.startsWith("<") ? value : null;
7326
- }
7327
-
7328
- // src/lib/algoliaAppPicker.ts
7329
- function blockReasonFor(app) {
7330
- return app.status !== "active" ? "Paused" : "Missing permissions";
7331
- }
7332
- function secondaryFor(app, highlightId) {
7333
- if (!canSelectApplication(app)) {
7334
- return { kind: "text", value: blockReasonFor(app) };
7335
- }
7336
- if (app.id === highlightId) {
7337
- return { kind: "badge", value: "[DETECTED]" };
7338
- }
7339
- return app.plan ? { kind: "badge", value: app.plan } : void 0;
7340
- }
7341
- function labelFor(app) {
7342
- return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
7343
- }
7344
- async function selectAndReport(id) {
7345
- const store = useWizard.getState();
7346
- store.setSettingUpApp(id);
7347
- try {
7348
- return await selectApplication(id);
7349
- } finally {
7350
- store.setSettingUpApp(null);
7351
- }
7352
- }
7353
- async function promptForApplication(highlight) {
7354
- const store = useWizard.getState();
7355
- const unordered = await listApplications();
7356
- if (unordered.length === 0) {
7357
- throw new Error(
7358
- "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
7359
- );
7360
- }
7361
- if (!unordered.some(canSelectApplication)) {
7362
- throw new Error(
7363
- "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."
7364
- );
7365
- }
7366
- const apps = [
7367
- ...unordered.filter(canSelectApplication),
7368
- ...unordered.filter((app) => !canSelectApplication(app))
7369
- ];
7370
- const envApp = highlight ? unordered.find((app) => app.id === highlight.id) : void 0;
7371
- const highlightIndex = envApp && canSelectApplication(envApp) ? apps.indexOf(envApp) : -1;
7372
- const messages = [];
7373
- if (highlight && highlightIndex < 0) {
7374
- messages.push(
7375
- 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.`
7376
- );
7377
- }
7378
- for (; ; ) {
7379
- const choice = await store.requestUserInput({
7380
- prompt: "Which Algolia application should the wizard work in?",
7381
- promptType: "multipleChoice",
7382
- options: apps.map(labelFor),
7383
- secondary: apps.map((app) => secondaryFor(app, highlight?.id)),
7384
- disabled: apps.map((app) => !canSelectApplication(app)),
7385
- ...highlightIndex >= 0 ? { defaultSelectedIndex: highlightIndex } : {},
7386
- messages
7387
- });
7388
- const chosen = apps.find((app) => labelFor(app) === choice);
7389
- if (!chosen || !canSelectApplication(chosen)) {
7390
- throw new Error("Application picker received an unexpected selection");
7391
- }
7392
- try {
7393
- return await selectAndReport(chosen.id);
7394
- } catch (err) {
7395
- logger.warn(
7396
- { app: chosen.id, err: err.message },
7397
- "application select failed; re-prompting"
7398
- );
7399
- messages.push(
7400
- `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
7401
- );
7402
- }
7403
- }
7404
- }
7405
- async function ensureApplication(resumeAppId) {
7406
- if (resumeAppId) return selectAndReport(resumeAppId);
7407
- const env = await findEnvApplicationId();
7408
- if (!env) return promptForApplication();
7409
- return promptForApplication({ id: env.id, file: env.file });
7410
- }
7411
-
7412
7599
  // src/lib/seed.ts
7413
7600
  var projectScan2 = {
7414
7601
  languages: [{ name: "TypeScript", version: "5.7.2" }],
@@ -7655,7 +7842,7 @@ function delay(ms) {
7655
7842
  // package.json with { type: 'json' }
7656
7843
  var package_default2 = {
7657
7844
  name: "@algolia/wizard",
7658
- version: "0.72.0",
7845
+ version: "0.74.0",
7659
7846
  description: "Magically implement Algolia functionality in your codebase",
7660
7847
  type: "module",
7661
7848
  engines: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {