@algolia/wizard 0.12.0 → 0.13.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 +252 -447
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1093,9 +1093,14 @@ var sidebarItems = [
1093
1093
  ];
1094
1094
 
1095
1095
  // src/ui/Welcome.tsx
1096
- import Image, { InkPictureProvider } from "ink-picture";
1096
+ import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
1097
1097
  import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
1098
1098
  var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
1099
+ var TERMINAL_INFO = {
1100
+ ...defaultTerminalInfo,
1101
+ supportsUnicode: true,
1102
+ supportsColor: true
1103
+ };
1099
1104
  function SidebarItem({
1100
1105
  title,
1101
1106
  description
@@ -1142,7 +1147,7 @@ function Welcome() {
1142
1147
  flexDirection: "column",
1143
1148
  justifyContent: "center",
1144
1149
  children: /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", gap: 2, children: [
1145
- /* @__PURE__ */ jsx7(InkPictureProvider, { children: /* @__PURE__ */ jsx7(
1150
+ /* @__PURE__ */ jsx7(TerminalInfoContext.Provider, { value: TERMINAL_INFO, children: /* @__PURE__ */ jsx7(
1146
1151
  Image,
1147
1152
  {
1148
1153
  src: IMAGE_PATH,
@@ -2241,7 +2246,7 @@ async function ensureApplication() {
2241
2246
  }
2242
2247
 
2243
2248
  // src/workflows/default.ts
2244
- import { z as z29 } from "zod";
2249
+ import { z as z28 } from "zod";
2245
2250
 
2246
2251
  // src/actions/listIndices.ts
2247
2252
  import { z as z5 } from "zod";
@@ -2874,102 +2879,13 @@ function searchFilesTool(ctx) {
2874
2879
  });
2875
2880
  }
2876
2881
 
2877
- // src/lib/tools/verifyImplementation.ts
2882
+ // src/lib/tools/runShell.ts
2878
2883
  import { tool as tool8 } from "ai";
2879
2884
  import z15 from "zod";
2880
-
2881
- // src/lib/tools/utils/runCommand.ts
2882
- import { spawn as spawn2 } from "node:child_process";
2883
- function runCommand(command, args, cwd) {
2884
- return new Promise((resolve4) => {
2885
- let output = "";
2886
- const child = spawn2(command, args, {
2887
- cwd,
2888
- stdio: ["ignore", "pipe", "pipe"]
2889
- });
2890
- child.stdout?.on("data", (d) => output += d);
2891
- child.stderr?.on("data", (d) => output += d);
2892
- child.on(
2893
- "error",
2894
- (err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
2895
- );
2896
- child.on("close", (code) => resolve4({ code: code ?? 1, output }));
2897
- });
2898
- }
2899
-
2900
- // src/lib/tools/utils/packageManager.ts
2901
- import { readFile as readFile7 } from "node:fs/promises";
2902
- import { existsSync } from "node:fs";
2903
- import { join as join9 } from "node:path";
2904
- var LOCKFILES = [
2905
- ["pnpm-lock.yaml", "pnpm"],
2906
- ["yarn.lock", "yarn"],
2907
- ["bun.lockb", "bun"],
2908
- ["bun.lock", "bun"],
2909
- ["package-lock.json", "npm"]
2910
- ];
2911
- async function readPackageJson(cwd = process.cwd()) {
2912
- return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
2913
- }
2914
- function packageManagerFrom(pkg) {
2915
- return pkg.packageManager?.split("@")[0] ?? "npm";
2916
- }
2917
- function packageManagerFromLockfile(cwd) {
2918
- return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
2919
- }
2920
- async function detectPackageManager(cwd) {
2921
- try {
2922
- const pkg = await readPackageJson(cwd);
2923
- if (pkg.packageManager) return packageManagerFrom(pkg);
2924
- } catch {
2925
- }
2926
- return packageManagerFromLockfile(cwd) ?? "npm";
2927
- }
2928
-
2929
- // src/lib/tools/repoVerification.ts
2930
- var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
2931
- async function runRepoVerificationCheck() {
2932
- let pkg;
2933
- try {
2934
- pkg = await readPackageJson();
2935
- } catch (err) {
2936
- const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
2937
- return { ok: false, checks: [], limitation };
2938
- }
2939
- const scripts = pkg.scripts ?? {};
2940
- const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
2941
- if (present.length === 0) {
2942
- const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
2943
- return { ok: false, checks: [], limitation };
2944
- }
2945
- const pm = await detectPackageManager(process.cwd());
2946
- const checks = [];
2947
- for (const script of present) {
2948
- const command = `${pm} run ${script}`;
2949
- const { code, output } = await runCommand(pm, ["run", script]);
2950
- checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
2951
- }
2952
- return { ok: checks.every((c) => c.ok), checks };
2953
- }
2954
-
2955
- // src/lib/tools/verifyImplementation.ts
2956
- function verifyImplementationTool() {
2957
- return tool8({
2958
- description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
2959
- inputSchema: z15.object(),
2960
- execute: async () => {
2961
- logger.info("called verifyImplementation tool");
2962
- return runRepoVerificationCheck();
2963
- }
2964
- });
2965
- }
2966
-
2967
- // src/lib/tools/runShell.ts
2968
- import { tool as tool9 } from "ai";
2969
- import z16 from "zod";
2885
+ import { relative as relative2 } from "node:path";
2970
2886
 
2971
2887
  // src/lib/tools/utils/runShell.ts
2972
- import { spawn as spawn3 } from "node:child_process";
2888
+ import { spawn as spawn2 } from "node:child_process";
2973
2889
 
2974
2890
  // src/lib/tools/context.ts
2975
2891
  var DEFAULT_TOOL_LIMITS = {
@@ -3024,7 +2940,7 @@ function runShell(command, opts) {
3024
2940
  let output = "";
3025
2941
  let timedOut = false;
3026
2942
  let settled = false;
3027
- const child = spawn3(command, {
2943
+ const child = spawn2(command, {
3028
2944
  shell: true,
3029
2945
  cwd: opts.cwd,
3030
2946
  stdio: ["ignore", "pipe", "pipe"],
@@ -3061,6 +2977,21 @@ function runShell(command, opts) {
3061
2977
  }
3062
2978
 
3063
2979
  // src/lib/tools/runShell.ts
2980
+ function storeApproval(root) {
2981
+ return async (req) => {
2982
+ const rel = relative2(root, req.cwd);
2983
+ const answer = await useWizard.getState().requestUserInput({
2984
+ prompt: "Run this command?",
2985
+ promptType: "commandApproval",
2986
+ options: [],
2987
+ command: {
2988
+ ...req,
2989
+ cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
2990
+ }
2991
+ });
2992
+ return answer === "approve" ? "approve" : "reject";
2993
+ };
2994
+ }
3064
2995
  var shellChain = Promise.resolve();
3065
2996
  function serialize(work) {
3066
2997
  const result = shellChain.then(work);
@@ -3111,16 +3042,16 @@ async function approveAndRun(ctx, command, cwd, explanation) {
3111
3042
  };
3112
3043
  }
3113
3044
  function runShellTool(ctx) {
3114
- return tool9({
3045
+ return tool8({
3115
3046
  description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. The user sees and approves every command before it runs, so write a clear `explanation`. If the user rejects a command, do not retry it \u2014 propose a different approach.",
3116
- inputSchema: z16.object({
3117
- command: z16.string().describe(
3047
+ inputSchema: z15.object({
3048
+ command: z15.string().describe(
3118
3049
  "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
3119
3050
  ),
3120
- cwd: z16.string().optional().describe(
3051
+ cwd: z15.string().optional().describe(
3121
3052
  "Directory to run in, relative to the project root. Defaults to the project root."
3122
3053
  ),
3123
- explanation: z16.string().describe(
3054
+ explanation: z15.string().describe(
3124
3055
  "One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
3125
3056
  )
3126
3057
  }),
@@ -3139,12 +3070,12 @@ function runShellTool(ctx) {
3139
3070
  }
3140
3071
 
3141
3072
  // src/lib/tools/generateRecord.ts
3142
- import { tool as tool10, generateText, Output, NoObjectGeneratedError } from "ai";
3073
+ import { tool as tool9, generateText, Output, NoObjectGeneratedError } from "ai";
3143
3074
  import { createAnthropic } from "@ai-sdk/anthropic";
3144
3075
  import { nanoid as nanoid2 } from "nanoid";
3145
3076
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
3146
3077
  import { dirname as dirname6 } from "node:path";
3147
- import z17 from "zod";
3078
+ import z16 from "zod";
3148
3079
  var DATA_DIR = ".algolia-wizard/data";
3149
3080
  var RECORD_MODEL = "claude-haiku-4-5";
3150
3081
  var MAX_RECORDS = 100;
@@ -3154,19 +3085,19 @@ var anthropic = createAnthropic({
3154
3085
  apiKey: process.env.PROVIDER_API_KEY ?? ""
3155
3086
  });
3156
3087
  function generateRecordTool(ctx) {
3157
- return tool10({
3088
+ return tool9({
3158
3089
  description: "Generate realistic sample records for an entity and write them to a JSON file in the worktree. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
3159
- inputSchema: z17.object({
3160
- entityName: z17.string().describe("Name of the entity to generate records for."),
3161
- attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
3162
- count: z17.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
3163
- hint: z17.string().optional().describe("Optional context to steer realistic values.")
3090
+ inputSchema: z16.object({
3091
+ entityName: z16.string().describe("Name of the entity to generate records for."),
3092
+ attributes: z16.array(z16.string()).describe("Attribute names each record must contain."),
3093
+ count: z16.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
3094
+ hint: z16.string().optional().describe("Optional context to steer realistic values.")
3164
3095
  }),
3165
3096
  execute: async ({ entityName, attributes, count, hint }) => {
3166
3097
  logger.info({ entityName, count }, "called generateRecord tool");
3167
3098
  try {
3168
- const value = z17.union([z17.string(), z17.number(), z17.boolean(), z17.null()]);
3169
- const recordSchema = z17.object(
3099
+ const value = z16.union([z16.string(), z16.number(), z16.boolean(), z16.null()]);
3100
+ const recordSchema = z16.object(
3170
3101
  Object.fromEntries(attributes.map((attr) => [attr, value]))
3171
3102
  );
3172
3103
  const generateBatch = async (batchCount) => {
@@ -3176,8 +3107,8 @@ function generateRecordTool(ctx) {
3176
3107
  const { output } = await generateText({
3177
3108
  model: anthropic(RECORD_MODEL),
3178
3109
  output: Output.object({
3179
- schema: z17.object({
3180
- records: z17.array(recordSchema).length(batchCount)
3110
+ schema: z16.object({
3111
+ records: z16.array(recordSchema).length(batchCount)
3181
3112
  })
3182
3113
  }),
3183
3114
  prompt: [
@@ -3234,13 +3165,13 @@ function generateRecordTool(ctx) {
3234
3165
  }
3235
3166
 
3236
3167
  // src/lib/tools/notifyUser.ts
3237
- import { tool as tool11 } from "ai";
3238
- import z18 from "zod";
3168
+ import { tool as tool10 } from "ai";
3169
+ import z17 from "zod";
3239
3170
  function notifyUserTool() {
3240
- return tool11({
3171
+ return tool10({
3241
3172
  description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
3242
- inputSchema: z18.object({
3243
- message: z18.string().describe(
3173
+ inputSchema: z17.object({
3174
+ message: z17.string().describe(
3244
3175
  "Short, plain-language description of what you are doing now."
3245
3176
  )
3246
3177
  }),
@@ -3283,10 +3214,6 @@ function createTools(ctx, { output, tools }) {
3283
3214
  writeCredentialsTool(ctx)
3284
3215
  ),
3285
3216
  searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
3286
- verifyImplementation: withLogging(
3287
- "verifyImplementation",
3288
- verifyImplementationTool()
3289
- ),
3290
3217
  runShell: withLogging("runShell", runShellTool(ctx)),
3291
3218
  generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
3292
3219
  notifyUser: withLogging("notifyUser", notifyUserTool())
@@ -3409,10 +3336,10 @@ async function runAgent(req) {
3409
3336
  }
3410
3337
 
3411
3338
  // src/actions/detectLanguage.ts
3412
- import z21 from "zod";
3413
- var detectLanguageSchema = z21.object({
3414
- languages: z21.array(z21.object({ name: z21.string(), version: z21.string() })),
3415
- frameworks: z21.array(z21.object({ name: z21.string(), version: z21.string() }))
3339
+ import z20 from "zod";
3340
+ var detectLanguageSchema = z20.object({
3341
+ languages: z20.array(z20.object({ name: z20.string(), version: z20.string() })),
3342
+ frameworks: z20.array(z20.object({ name: z20.string(), version: z20.string() }))
3416
3343
  });
3417
3344
  var detectLanguage = () => runAgent({
3418
3345
  instructions: [
@@ -3430,31 +3357,31 @@ var detectLanguage = () => runAgent({
3430
3357
  });
3431
3358
 
3432
3359
  // src/actions/analyzeCodebase.ts
3433
- import z22 from "zod";
3360
+ import z21 from "zod";
3434
3361
  var READONLY_TOOLS = [
3435
3362
  "listFiles",
3436
3363
  "changeDirectory",
3437
3364
  "readFile",
3438
3365
  "searchFiles"
3439
3366
  ];
3440
- var ingestionAnalysisSchema = z22.object({
3441
- ingestionAnalysis: z22.array(
3442
- z22.object({
3443
- name: z22.string(),
3444
- paths: z22.array(z22.string()),
3367
+ var ingestionAnalysisSchema = z21.object({
3368
+ ingestionAnalysis: z21.array(
3369
+ z21.object({
3370
+ name: z21.string(),
3371
+ paths: z21.array(z21.string()),
3445
3372
  // indexable fields the agent found for this entity
3446
- attributes: z22.array(z22.string())
3373
+ attributes: z21.array(z21.string())
3447
3374
  })
3448
3375
  )
3449
3376
  });
3450
- var searchImplementationAnalysisSchema = z22.object({
3451
- searchImplementationAnalysis: z22.string()
3377
+ var searchImplementationAnalysisSchema = z21.object({
3378
+ searchImplementationAnalysis: z21.string()
3452
3379
  });
3453
- var verificationSchema = z22.object({
3454
- verification: z22.array(z22.string())
3380
+ var verificationSchema = z21.object({
3381
+ verification: z21.array(z21.string())
3455
3382
  });
3456
3383
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
3457
- var analyzeCodebaseSchema = z22.object({
3384
+ var analyzeCodebaseSchema = z21.object({
3458
3385
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3459
3386
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
3460
3387
  verification: verificationSchema.shape.verification.optional(),
@@ -3516,7 +3443,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3516
3443
  // package.json
3517
3444
  var package_default = {
3518
3445
  name: "@algolia/wizard",
3519
- version: "0.12.0",
3446
+ version: "0.13.0",
3520
3447
  description: "Magically implement Algolia functionality in your codebase",
3521
3448
  type: "module",
3522
3449
  engines: {
@@ -3635,8 +3562,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
3635
3562
  }
3636
3563
 
3637
3564
  // src/actions/confirmLanguage.ts
3638
- import z24 from "zod";
3639
- var confirmLanguageSchema = z24.object({
3565
+ import z23 from "zod";
3566
+ var confirmLanguageSchema = z23.object({
3640
3567
  languages: detectLanguageSchema.shape.languages
3641
3568
  });
3642
3569
  async function confirmLanguage(ctx) {
@@ -3657,8 +3584,8 @@ async function confirmLanguage(ctx) {
3657
3584
  }
3658
3585
 
3659
3586
  // src/actions/confirmFramework.ts
3660
- import z25 from "zod";
3661
- var confirmFrameworkSchema = z25.object({
3587
+ import z24 from "zod";
3588
+ var confirmFrameworkSchema = z24.object({
3662
3589
  frameworks: detectLanguageSchema.shape.frameworks
3663
3590
  });
3664
3591
  var CURATED_FRAMEWORKS = [
@@ -3786,8 +3713,8 @@ async function promptUser(ctx, params) {
3786
3713
  }
3787
3714
 
3788
3715
  // src/actions/confirmEntities.ts
3789
- import z26 from "zod";
3790
- var confirmEntitiesSchema = z26.object({
3716
+ import z25 from "zod";
3717
+ var confirmEntitiesSchema = z25.object({
3791
3718
  // Final detection — the focused re-run may supersede project-scan's.
3792
3719
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
3793
3720
  confirmedEntities: confirmedEntitiesFieldSchema
@@ -3857,15 +3784,15 @@ async function confirmEntities(ctx) {
3857
3784
  }
3858
3785
 
3859
3786
  // src/actions/review.ts
3860
- import { z as z27 } from "zod";
3861
- var reviewSchema = z27.object({
3787
+ import { z as z26 } from "zod";
3788
+ var reviewSchema = z26.object({
3862
3789
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
3863
3790
  // not one entry per workflow step — a step's raw output can be a long,
3864
3791
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
3865
3792
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
3866
- summaryPoints: z27.array(z27.string()),
3867
- reviewPrompt: z27.string(),
3868
- nextSteps: z27.array(z27.string())
3793
+ summaryPoints: z26.array(z26.string()),
3794
+ reviewPrompt: z26.string(),
3795
+ nextSteps: z26.array(z26.string())
3869
3796
  });
3870
3797
  function formatCompletedSteps(steps) {
3871
3798
  if (!steps.length) return "(no prior steps completed)";
@@ -3916,19 +3843,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3916
3843
  };
3917
3844
 
3918
3845
  // src/actions/implement.ts
3919
- import z28 from "zod";
3846
+ import z27 from "zod";
3920
3847
 
3921
3848
  // src/lib/worktree.ts
3922
- import { execFile, spawn as spawn4 } from "node:child_process";
3923
- import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3924
- import {
3925
- basename as basename2,
3926
- dirname as dirname7,
3927
- isAbsolute as isAbsolute2,
3928
- join as join10,
3929
- relative as relative2,
3930
- resolve as resolve3
3931
- } from "node:path";
3849
+ import { execFile } from "node:child_process";
3850
+ import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile7, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3851
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
3932
3852
  var MAX_BUFFER = 32 * 1024 * 1024;
3933
3853
  var MAX_WIZARD_WORKTREES = 3;
3934
3854
  var WIZARD_BRANCH_PREFIX = "wizard/implement-";
@@ -3959,7 +3879,7 @@ async function isWorkingTreeDirty(repoRoot) {
3959
3879
  return out.trim().length > 0;
3960
3880
  }
3961
3881
  async function pruneOldWorktrees(repoRoot) {
3962
- const dir = join10(stateDir(repoRoot), "worktrees");
3882
+ const dir = join9(stateDir(repoRoot), "worktrees");
3963
3883
  const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
3964
3884
  for (const slug of stale) {
3965
3885
  const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
@@ -3970,7 +3890,7 @@ async function pruneOldWorktrees(repoRoot) {
3970
3890
  "worktree",
3971
3891
  "remove",
3972
3892
  "--force",
3973
- join10(dir, slug)
3893
+ join9(dir, slug)
3974
3894
  ]);
3975
3895
  await git(["-C", repoRoot, "branch", "-D", branch]);
3976
3896
  } catch (err) {
@@ -3984,113 +3904,13 @@ async function pruneOldWorktrees(repoRoot) {
3984
3904
  async function createWorktree(repoRoot) {
3985
3905
  const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
3986
3906
  const dirSlug = branch.replace(/\//g, "-");
3987
- const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
3907
+ const path = join9(stateDir(repoRoot), "worktrees", dirSlug);
3988
3908
  await git(["-C", repoRoot, "worktree", "prune"]);
3989
3909
  await pruneOldWorktrees(repoRoot);
3990
3910
  await mkdir6(dirname7(path), { recursive: true });
3991
3911
  await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
3992
3912
  return { path, branch };
3993
3913
  }
3994
- async function installWorktreeDeps(worktreePath) {
3995
- try {
3996
- await readPackageJson(worktreePath);
3997
- } catch {
3998
- return { ok: true, output: "no package.json; skipped install" };
3999
- }
4000
- const pm = await detectPackageManager(worktreePath);
4001
- return new Promise((resolve4) => {
4002
- let output = "";
4003
- const child = spawn4(pm, ["install"], {
4004
- cwd: worktreePath,
4005
- stdio: ["ignore", "pipe", "pipe"]
4006
- });
4007
- child.stdout?.on("data", (d) => output += d);
4008
- child.stderr?.on("data", (d) => output += d);
4009
- child.on(
4010
- "error",
4011
- (err) => resolve4({
4012
- ok: false,
4013
- output: `Failed to run ${pm} install: ${err.message}`
4014
- })
4015
- );
4016
- child.on(
4017
- "close",
4018
- (code) => resolve4({ ok: code === 0, output: output.trim() })
4019
- );
4020
- });
4021
- }
4022
- var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
4023
- function validateIngestEntrypoint(worktreePath, entrypoint) {
4024
- if (!entrypoint || entrypoint.startsWith("-")) {
4025
- return {
4026
- ok: false,
4027
- reason: `entrypoint "${entrypoint}" is not a plain file path`
4028
- };
4029
- }
4030
- const target = resolve3(worktreePath, entrypoint);
4031
- const rel = relative2(worktreePath, target);
4032
- if (rel.startsWith("..") || isAbsolute2(rel)) {
4033
- return {
4034
- ok: false,
4035
- reason: `entrypoint "${entrypoint}" resolves outside the worktree`
4036
- };
4037
- }
4038
- return { ok: true, target };
4039
- }
4040
- async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
4041
- if (!INGEST_RUNTIMES.includes(runtime)) {
4042
- return {
4043
- ran: false,
4044
- ok: false,
4045
- output: "",
4046
- reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
4047
- };
4048
- }
4049
- const validated = validateIngestEntrypoint(worktreePath, entrypoint);
4050
- if (!validated.ok) {
4051
- return { ran: false, ok: false, output: "", reason: validated.reason };
4052
- }
4053
- try {
4054
- if (!(await stat2(validated.target)).isFile()) {
4055
- return {
4056
- ran: false,
4057
- ok: false,
4058
- output: "",
4059
- reason: `entrypoint "${entrypoint}" is not a file`
4060
- };
4061
- }
4062
- } catch {
4063
- return {
4064
- ran: false,
4065
- ok: false,
4066
- output: "",
4067
- reason: `entrypoint "${entrypoint}" does not exist`
4068
- };
4069
- }
4070
- return new Promise((resolveRun) => {
4071
- let output = "";
4072
- const child = spawn4(runtime, [entrypoint], {
4073
- cwd: worktreePath,
4074
- shell: false,
4075
- stdio: ["ignore", "pipe", "pipe"],
4076
- env: { ...process.env, ...env }
4077
- });
4078
- child.stdout?.on("data", (d) => output += d);
4079
- child.stderr?.on("data", (d) => output += d);
4080
- child.on(
4081
- "error",
4082
- (err) => resolveRun({
4083
- ran: true,
4084
- ok: false,
4085
- output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
4086
- })
4087
- );
4088
- child.on(
4089
- "close",
4090
- (code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
4091
- );
4092
- });
4093
- }
4094
3914
  async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
4095
3915
  const trimmed = sourcePath.trim();
4096
3916
  if (!trimmed) {
@@ -4104,8 +3924,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
4104
3924
  } catch {
4105
3925
  return { ok: false, reason: `"${sourcePath}" does not exist` };
4106
3926
  }
4107
- const relPath = join10(ingestDir, basename2(source));
4108
- const dest = join10(worktreePath, relPath);
3927
+ const relPath = join9(ingestDir, basename2(source));
3928
+ const dest = join9(worktreePath, relPath);
4109
3929
  try {
4110
3930
  await mkdir6(dirname7(dest), { recursive: true });
4111
3931
  await copyFile(source, dest);
@@ -4123,7 +3943,7 @@ function hasEnvVar(content, name) {
4123
3943
  async function readEnvVar(worktreePath, name) {
4124
3944
  let content;
4125
3945
  try {
4126
- content = await readFile8(join10(worktreePath, ".env"), "utf8");
3946
+ content = await readFile7(join9(worktreePath, ".env"), "utf8");
4127
3947
  } catch (err) {
4128
3948
  if (err.code !== "ENOENT") throw err;
4129
3949
  return void 0;
@@ -4138,10 +3958,10 @@ async function readEnvVar(worktreePath, name) {
4138
3958
  return value;
4139
3959
  }
4140
3960
  async function writeSearchEnvValues(worktreePath, vars) {
4141
- const target = join10(worktreePath, ".env");
3961
+ const target = join9(worktreePath, ".env");
4142
3962
  let existing = "";
4143
3963
  try {
4144
- existing = await readFile8(target, "utf8");
3964
+ existing = await readFile7(target, "utf8");
4145
3965
  } catch (err) {
4146
3966
  if (err.code !== "ENOENT") throw err;
4147
3967
  }
@@ -4210,15 +4030,15 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
4210
4030
  }
4211
4031
 
4212
4032
  // src/lib/algoliaDocs.ts
4213
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
4214
- import { dirname as dirname8, join as join11 } from "node:path";
4033
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
4034
+ import { dirname as dirname8, join as join10 } from "node:path";
4215
4035
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4216
- var DOCS_SUBPATH = join11("docs", "algolia-sdk");
4036
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
4217
4037
  function findDocsDir() {
4218
4038
  let dir = dirname8(fileURLToPath2(import.meta.url));
4219
4039
  for (; ; ) {
4220
- const candidate = join11(dir, DOCS_SUBPATH);
4221
- if (existsSync2(candidate)) return candidate;
4040
+ const candidate = join10(dir, DOCS_SUBPATH);
4041
+ if (existsSync(candidate)) return candidate;
4222
4042
  const parent = dirname8(dir);
4223
4043
  if (parent === dir) return void 0;
4224
4044
  dir = parent;
@@ -4240,7 +4060,7 @@ function loadAlgoliaDoc(language) {
4240
4060
  );
4241
4061
  return "";
4242
4062
  }
4243
- return readFileSync(join11(docsDir, files[0]), "utf8").trim();
4063
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
4244
4064
  }
4245
4065
  function getNamedDoc(name, language) {
4246
4066
  const docsDir = findDocsDir();
@@ -4248,8 +4068,8 @@ function getNamedDoc(name, language) {
4248
4068
  logger.warn("docs/algolia-sdk not found");
4249
4069
  return "";
4250
4070
  }
4251
- const file = join11(docsDir, `${name}-${language}.md`);
4252
- if (!existsSync2(file)) {
4071
+ const file = join10(docsDir, `${name}-${language}.md`);
4072
+ if (!existsSync(file)) {
4253
4073
  logger.warn({ name, language }, "named SDK reference not found");
4254
4074
  return "";
4255
4075
  }
@@ -4275,34 +4095,30 @@ function shellQuote(value) {
4275
4095
  }
4276
4096
 
4277
4097
  // src/actions/implement.ts
4278
- var implementSchema = z28.object({
4279
- filesChanged: z28.array(z28.string()),
4280
- summary: z28.string(),
4281
- worktreePath: z28.string().optional(),
4282
- ingestCommand: z28.string().optional(),
4283
- ingestScriptRan: z28.boolean().optional(),
4284
- ingestRecordCount: z28.number().optional(),
4285
- ingestDurationMs: z28.number().optional(),
4286
- ingestionSource: z28.enum(["local", "fileUpload", "generated"]),
4287
- searchEnvVars: z28.array(
4288
- z28.object({
4289
- name: z28.string(),
4290
- value: z28.string()
4098
+ var implementSchema = z27.object({
4099
+ filesChanged: z27.array(z27.string()),
4100
+ summary: z27.string(),
4101
+ worktreePath: z27.string().optional(),
4102
+ ingestCommand: z27.string().optional(),
4103
+ ingestScriptRan: z27.boolean().optional(),
4104
+ ingestRecordCount: z27.number().optional(),
4105
+ ingestDurationMs: z27.number().optional(),
4106
+ ingestionSource: z27.enum(["local", "fileUpload", "generated"]),
4107
+ searchEnvVars: z27.array(
4108
+ z27.object({
4109
+ name: z27.string(),
4110
+ value: z27.string()
4291
4111
  })
4292
4112
  ).optional()
4293
4113
  });
4294
- var implementationOutputSchema = z28.object({
4295
- summary: z28.string(),
4296
- // Ingestion only: a structured pair the wizard turns into an argv, never a
4297
- // free-form command string. `runtime` is allowlisted and `entrypoint` is
4298
- // validated worktree-relative, so the agent cannot inject extra commands.
4299
- runtime: z28.enum(INGEST_RUNTIMES).optional(),
4300
- entrypoint: z28.string().optional()
4114
+ var implementationOutputSchema = z27.object({
4115
+ summary: z27.string(),
4116
+ ingestCommand: z27.string().optional()
4301
4117
  });
4302
- var verificationOutputSchema = z28.object({
4303
- summary: z28.string(),
4304
- sufficient: z28.boolean(),
4305
- additionalInstructions: z28.string().optional()
4118
+ var verificationOutputSchema = z27.object({
4119
+ summary: z27.string(),
4120
+ sufficient: z27.boolean(),
4121
+ additionalInstructions: z27.string().optional()
4306
4122
  });
4307
4123
  var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
4308
4124
  var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
@@ -4382,7 +4198,9 @@ function baseInstructions(input) {
4382
4198
  // index-scoped keys then reject with a 403.
4383
4199
  `Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
4384
4200
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
4385
- "Make minimal, idiomatic changes; do not touch unrelated code."
4201
+ "Make minimal, idiomatic changes; do not touch unrelated code.",
4202
+ `Commands run through a shell on ${process.platform}. Write commands that work there.`,
4203
+ "runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
4386
4204
  ];
4387
4205
  }
4388
4206
  function sourceSpecificInstructions(input) {
@@ -4418,9 +4236,9 @@ function ingestionInstructions(input) {
4418
4236
  "Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
4419
4237
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
4420
4238
  getNamedDoc("save-records", "js"),
4421
- 'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
4239
+ 'Install the Algolia client via runShell and add it to package.json "dependencies" with a valid version range, so the dependency is not just installed ad hoc.',
4240
+ 'Then run the script yourself via runShell, and report the command you ran as "ingestCommand" so the developer can re-run it. Its explanation must say that running it writes records to Algolia.',
4422
4241
  "The summary should be extremely concise.",
4423
- `Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
4424
4242
  ...sourceSpecificInstructions(input)
4425
4243
  ] : []
4426
4244
  ];
@@ -4434,6 +4252,7 @@ function searchInstructions(input) {
4434
4252
  doc,
4435
4253
  `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the target index.`,
4436
4254
  `Read the index name from the ${searchIndexVar(input.language)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
4255
+ "Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
4437
4256
  // The key is provisioned only after verification passes, so the agent never
4438
4257
  // sees one. It must also leave .env alone: the wizard reads that file to
4439
4258
  // decide whether a key already exists, and an agent-invented value there
@@ -4443,8 +4262,7 @@ function searchInstructions(input) {
4443
4262
  // ".env" right after this step, so a renamed prefix would leave the code
4444
4263
  // reading a var the wizard never wrote.
4445
4264
  `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
4446
- "Read the App ID, the search-only API key, and the index name from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
4447
- 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
4265
+ 'Install any Algolia/InstantSearch packages you import via runShell, and add them to package.json "dependencies" with a valid version range.',
4448
4266
  "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
4449
4267
  ];
4450
4268
  }
@@ -4452,11 +4270,12 @@ function verificationInstructions(input) {
4452
4270
  return [
4453
4271
  "Verify the Algolia implementation changes in the current worktree.",
4454
4272
  `Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
4455
- "Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
4456
- "For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
4457
- "Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4273
+ "Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
4274
+ "This worktree starts with no installed dependencies. If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
4275
+ "For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
4276
+ "Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
4458
4277
  "Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
4459
- `Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
4278
+ `Do not modify "${input.ingestDir}/" unless a check reports an actionable issue in its files.`,
4460
4279
  "Always call reportStatus with status=success once verification has run, even when sufficient=false.",
4461
4280
  "Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
4462
4281
  "Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
@@ -4477,14 +4296,15 @@ var IMPLEMENT_CONFIG = {
4477
4296
  }
4478
4297
  };
4479
4298
  var useCaseToolMap = {
4480
- ingestion: [...FS_READ_TOOLS, "writeFile", "writeCredentials", "notifyUser"],
4481
- search: [...FS_READ_TOOLS, "writeFile", "notifyUser"],
4482
- verification: [
4299
+ ingestion: [
4483
4300
  ...FS_READ_TOOLS,
4484
4301
  "writeFile",
4485
- "verifyImplementation",
4302
+ "writeCredentials",
4303
+ "runShell",
4486
4304
  "notifyUser"
4487
- ]
4305
+ ],
4306
+ search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
4307
+ verification: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"]
4488
4308
  };
4489
4309
  function toolsForUseCase(useCase, ingestionSource) {
4490
4310
  const tools = useCaseToolMap[useCase];
@@ -4507,15 +4327,51 @@ function formatSummary(useCase, summary) {
4507
4327
  const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
4508
4328
  return `${label}: ${summary}`;
4509
4329
  }
4510
- function buildIngestCommand(worktree, runtime, entrypoint) {
4511
- return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
4512
- }
4513
4330
  function parseIngestRecordCount(output) {
4514
4331
  const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
4515
4332
  if (!match) return void 0;
4516
4333
  const count = Number(match[1]);
4517
4334
  return Number.isFinite(count) ? count : void 0;
4518
4335
  }
4336
+ function ingestOutcome(executions, ingestCommand) {
4337
+ const newestFirst = [...executions].reverse();
4338
+ const withCount = newestFirst.filter(
4339
+ (e) => parseIngestRecordCount(e.output ?? "") != null
4340
+ );
4341
+ const succeeded = newestFirst.filter((e) => e.approved && e.exitCode === 0);
4342
+ return {
4343
+ run: succeeded.find((e) => e.command === ingestCommand) ?? succeeded.find((e) => withCount.includes(e)),
4344
+ attempt: ingestCommand ? newestFirst.find((e) => e.command === ingestCommand) : void 0,
4345
+ recordCount: parseIngestRecordCount(withCount[0]?.output ?? "")
4346
+ };
4347
+ }
4348
+ var INGEST_OUTPUT_TAIL_CHARS = 400;
4349
+ function outputTail(output) {
4350
+ const trimmed = output?.trim();
4351
+ if (!trimmed) return void 0;
4352
+ return trimmed.length > INGEST_OUTPUT_TAIL_CHARS ? `\u2026${trimmed.slice(-INGEST_OUTPUT_TAIL_CHARS)}` : trimmed;
4353
+ }
4354
+ function ingestFailure(attempt, executions) {
4355
+ if (!attempt) {
4356
+ return {
4357
+ reason: executions.some((e) => !e.approved) ? "a command it needed was declined" : "no successful run was recorded"
4358
+ };
4359
+ }
4360
+ if (!attempt.approved) return { reason: "you declined to run it" };
4361
+ const detail = outputTail(attempt.output);
4362
+ if (attempt.timedOut) {
4363
+ const seconds = Math.round((attempt.durationMs ?? 0) / 1e3);
4364
+ return { reason: `it timed out after ${seconds}s`, detail };
4365
+ }
4366
+ return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
4367
+ }
4368
+ function makeToolContext(worktree, env = async () => ({})) {
4369
+ return createToolContext(
4370
+ DEFAULT_TOOL_LIMITS,
4371
+ worktree,
4372
+ createShellContext({ env, approve: storeApproval(worktree) })
4373
+ );
4374
+ }
4519
4375
  function verificationRetryInstructions(verification) {
4520
4376
  return [
4521
4377
  `Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
@@ -4594,9 +4450,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4594
4450
  const confirmed2 = normalized.confirmedEntities;
4595
4451
  const searchLocation = normalized.searchImplementationAnalysis;
4596
4452
  let appId;
4453
+ let ingestAppId;
4597
4454
  if (useCases.includes("search")) {
4598
4455
  appId = (await requireApplication()).id;
4599
4456
  }
4457
+ if (useCases.includes("ingestion")) {
4458
+ ingestAppId = appId ?? (await requireApplication()).id;
4459
+ }
4600
4460
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
4601
4461
  try {
4602
4462
  process.chdir(worktree);
@@ -4654,41 +4514,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4654
4514
  }
4655
4515
  let finalSearchEnvVars = input.searchEnvVars;
4656
4516
  let agentRuns = 0;
4657
- let ingestRuntime;
4658
- let ingestEntrypoint;
4517
+ let ingestCommand;
4659
4518
  let ingestScriptRan = false;
4660
4519
  let ingestRecordCount;
4661
4520
  let ingestDurationMs;
4662
- let installFailed = false;
4663
4521
  let ingestOutcomeMessage;
4522
+ const ingestKeyAppId = ingestAppId;
4523
+ const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
4524
+ [APP_ID_VAR]: ingestKeyAppId,
4525
+ [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4526
+ [INDEX_NAME_VAR]: targetIndex
4527
+ })) : void 0;
4528
+ const searchTools = makeToolContext(worktree);
4664
4529
  async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
4665
4530
  if (agentRuns > 0) ctx.recordStepExecution();
4666
4531
  agentRuns += 1;
4667
- const result = await runAgent({
4532
+ return runAgent({
4668
4533
  instructions: buildAgentInstructions(
4669
4534
  currentUseCase,
4670
4535
  input,
4671
4536
  extraInstructions
4672
4537
  ),
4673
4538
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
4674
- outputSchema: implementationOutputSchema
4675
- });
4676
- ctx.notify({
4677
- messages: [`Installing dependencies for ${currentUseCase}\u2026`]
4678
- });
4679
- const installLogId = ctx.logStart("installWorktreeDeps", {
4680
- useCase: currentUseCase
4539
+ outputSchema: implementationOutputSchema,
4540
+ toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
4681
4541
  });
4682
- const install = await installWorktreeDeps(worktree);
4683
- ctx.logEnd(installLogId, install.ok ? "success" : "error");
4684
- if (!install.ok) {
4685
- installFailed = true;
4686
- logger.warn(
4687
- { useCase: currentUseCase, output: install.output },
4688
- "implement: dependency install in worktree failed; generated commands may not run until deps are installed"
4689
- );
4690
- }
4691
- return result;
4692
4542
  }
4693
4543
  async function runVerificationUseCase() {
4694
4544
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -4696,111 +4546,75 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
4696
4546
  return runAgent({
4697
4547
  instructions: buildAgentInstructions("verification", input),
4698
4548
  tools: toolsForUseCase("verification"),
4699
- outputSchema: verificationOutputSchema
4549
+ outputSchema: verificationOutputSchema,
4550
+ toolContext: searchTools
4700
4551
  });
4701
4552
  }
4702
4553
  if (useCases.includes("ingestion")) {
4703
- const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
4704
- summaries.push(formatSummary("ingestion", summary));
4705
- ingestRuntime = runtime;
4706
- ingestEntrypoint = entrypoint;
4707
- if (ingestRuntime && ingestEntrypoint && !installFailed) {
4708
- ctx.clearNotices();
4709
- const runNow = await ctx.requestUserInput({
4710
- prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
4711
- promptType: "acceptReject",
4712
- options: ["Yes", "No"],
4713
- messages: []
4714
- }) === true;
4715
- if (runNow) {
4716
- const ingestApp = await requireApplication();
4717
- const writeKey = (await resolveWriteKey(targetIndex, ingestApp.id)).key;
4718
- ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
4719
- const scriptLogId = ctx.logStart("runIngestScript", {
4720
- runtime: ingestRuntime,
4721
- entrypoint: ingestEntrypoint
4554
+ let ingestFailureDetail;
4555
+ const result = await runImplementationUseCase("ingestion");
4556
+ summaries.push(formatSummary("ingestion", result.summary));
4557
+ ingestCommand = result.ingestCommand;
4558
+ const executions = (ingestionTools ?? searchTools).shell.executions;
4559
+ const {
4560
+ run: ingestRun,
4561
+ attempt: ingestAttempt,
4562
+ recordCount
4563
+ } = ingestOutcome(executions, ingestCommand);
4564
+ ingestScriptRan = ingestRun != null;
4565
+ ingestRecordCount = recordCount;
4566
+ ingestDurationMs = ingestRun?.durationMs;
4567
+ if (ingestScriptRan) {
4568
+ ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4569
+ if (ingestRecordCount != null) {
4570
+ track("AI Wizard Ingest Successful", {
4571
+ entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4572
+ record_count: ingestRecordCount,
4573
+ duration_ms: ingestDurationMs ?? 0
4722
4574
  });
4723
- const startedAt = Date.now();
4724
- const run2 = await runIngestScript(
4725
- worktree,
4726
- ingestRuntime,
4727
- ingestEntrypoint,
4728
- {
4729
- [APP_ID_VAR]: ingestApp.id,
4730
- [API_KEY_VAR]: writeKey,
4731
- [INDEX_NAME_VAR]: targetIndex
4732
- }
4733
- );
4734
- ctx.logEnd(scriptLogId, run2.ok ? "success" : "error");
4735
- ingestScriptRan = run2.ran && run2.ok;
4736
- if (ingestScriptRan) {
4737
- ingestDurationMs = Date.now() - startedAt;
4738
- ingestRecordCount = parseIngestRecordCount(run2.output);
4739
- if (ingestRecordCount != null) {
4740
- track("AI Wizard Ingest Successful", {
4741
- entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
4742
- record_count: ingestRecordCount,
4743
- duration_ms: ingestDurationMs
4744
- });
4745
- }
4746
- }
4747
- let summaryLine;
4748
- let outcomeMessage;
4749
- if (!run2.ran) {
4750
- summaryLine = `\u26A0\uFE0F Skipped running the ingestion script: ${run2.reason}`;
4751
- outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
4752
- logger.warn(
4753
- {
4754
- runtime: ingestRuntime,
4755
- entrypoint: ingestEntrypoint,
4756
- reason: run2.reason
4757
- },
4758
- "implement: refused to auto-run ingestion script"
4759
- );
4760
- track("Error", {
4761
- step: "Push Data",
4762
- error: `ingestion script skipped: ${run2.reason}`,
4763
- product_area: "AI Wizard"
4764
- });
4765
- } else if (run2.ok) {
4766
- const status = "Ingestion run: succeeded.";
4767
- summaryLine = run2.output ? `${status}
4768
- ${run2.output}` : status;
4769
- outcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
4770
- } else {
4771
- const status = "\u26A0\uFE0F Ingestion run failed:";
4772
- summaryLine = run2.output ? `${status}
4773
- ${run2.output}` : status;
4774
- outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
4775
- logger.warn(
4776
- {
4777
- runtime: ingestRuntime,
4778
- entrypoint: ingestEntrypoint,
4779
- output: run2.output
4780
- },
4781
- "implement: ingestion script run failed"
4782
- );
4783
- track("Error", {
4784
- step: "Push Data",
4785
- error: run2.output || "ingestion script exited non-zero",
4786
- product_area: "AI Wizard"
4787
- });
4788
- }
4789
- summaries.push(summaryLine);
4790
- ingestOutcomeMessage = outcomeMessage;
4791
4575
  }
4576
+ } else {
4577
+ const { reason, detail } = ingestFailure(ingestAttempt, executions);
4578
+ ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
4579
+ ingestFailureDetail = detail;
4580
+ summaries.push(
4581
+ `\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
4582
+ ${detail}` : ""}`
4583
+ );
4584
+ logger.warn(
4585
+ {
4586
+ ingestCommand,
4587
+ reason,
4588
+ approved: ingestAttempt?.approved,
4589
+ exitCode: ingestAttempt?.exitCode,
4590
+ timedOut: ingestAttempt?.timedOut,
4591
+ commandsRun: executions.length
4592
+ },
4593
+ "implement: ingestion script did not complete successfully"
4594
+ );
4595
+ track("Error", {
4596
+ step: "Push Data",
4597
+ error: `ingestion did not complete: ${reason}`,
4598
+ product_area: "AI Wizard"
4599
+ });
4792
4600
  }
4793
4601
  const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
4794
- if (ingestRuntime && ingestEntrypoint) {
4795
- commandMessages.push(
4796
- `Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
4797
- );
4602
+ if (ingestCommand) {
4603
+ commandMessages.push(`Ingestion command: ${ingestCommand}`);
4798
4604
  }
4799
4605
  await ctx.requestUserInput({
4800
4606
  prompt: "",
4801
4607
  promptType: "enterToContinue",
4802
4608
  options: [],
4803
- messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
4609
+ // The wizard never streams command output, so a failed run's tail is the
4610
+ // only place the developer sees why it failed. One message per line:
4611
+ // the panel's height accounting counts a message as one wrapped line
4612
+ // (see Notices.tsx), so an embedded newline overflows it.
4613
+ messages: [
4614
+ ingestOutcomeMessage,
4615
+ ...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
4616
+ ...commandMessages
4617
+ ]
4804
4618
  });
4805
4619
  }
4806
4620
  if (useCases.includes("search")) {
@@ -4917,22 +4731,13 @@ ${run2.output}` : status;
4917
4731
  "implement: agent reported success but no files changed in the worktree"
4918
4732
  );
4919
4733
  }
4920
- if (installFailed) {
4921
- summaries.push(
4922
- '\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
4923
- );
4924
- }
4925
4734
  return {
4926
4735
  ingestionSource,
4927
4736
  filesChanged,
4928
4737
  summary: summaries.join("\n\n"),
4929
4738
  worktreePath: worktree,
4930
- ...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
4931
- ingestCommand: buildIngestCommand(
4932
- worktree,
4933
- ingestRuntime,
4934
- ingestEntrypoint
4935
- ),
4739
+ ...useCases.includes("ingestion") && ingestCommand ? {
4740
+ ingestCommand,
4936
4741
  ingestScriptRan,
4937
4742
  ...ingestRecordCount != null ? { ingestRecordCount } : {},
4938
4743
  ...ingestDurationMs != null ? { ingestDurationMs } : {}
@@ -4980,8 +4785,8 @@ var defaultWorkflow = {
4980
4785
  defineStep({
4981
4786
  id: "select-index",
4982
4787
  title: "Set up index",
4983
- outputSchema: z29.object({
4984
- selection: z29.string()
4788
+ outputSchema: z28.object({
4789
+ selection: z28.string()
4985
4790
  }),
4986
4791
  run: (ctx) => selectIndexStep(ctx)
4987
4792
  }),
@@ -5264,7 +5069,7 @@ function parseCliArgs(argv) {
5264
5069
 
5265
5070
  // src/lib/resetState.ts
5266
5071
  import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
5267
- import { join as join12 } from "node:path";
5072
+ import { join as join11 } from "node:path";
5268
5073
  var KEEP = ["wizard.log"];
5269
5074
  async function resetProjectState() {
5270
5075
  const dir = stateDir();
@@ -5278,7 +5083,7 @@ async function resetProjectState() {
5278
5083
  const targets = entries.filter((name) => !KEEP.includes(name));
5279
5084
  await Promise.all(
5280
5085
  targets.map(
5281
- (name) => rm2(join12(dir, name), { recursive: true, force: true })
5086
+ (name) => rm2(join11(dir, name), { recursive: true, force: true })
5282
5087
  )
5283
5088
  );
5284
5089
  return { dir, removed: targets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {