@algolia/wizard 0.34.0 → 0.35.0-rc.126.250
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.
- package/dist/main.js +243 -26
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -253,6 +253,7 @@ var useWizard = create((set, get) => ({
|
|
|
253
253
|
cliOutput: [],
|
|
254
254
|
targetIndex: null,
|
|
255
255
|
writtenFiles: [],
|
|
256
|
+
approvedCommands: /* @__PURE__ */ new Set(),
|
|
256
257
|
logs: [],
|
|
257
258
|
error: null,
|
|
258
259
|
inputReq: null,
|
|
@@ -348,6 +349,10 @@ var useWizard = create((set, get) => ({
|
|
|
348
349
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
349
350
|
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
350
351
|
clearWrittenFiles: () => set({ writtenFiles: [] }),
|
|
352
|
+
isCommandApproved: (command, cwd) => get().approvedCommands.has(`${cwd}\0${command}`),
|
|
353
|
+
approveCommand: (command, cwd) => set((s) => ({
|
|
354
|
+
approvedCommands: new Set(s.approvedCommands).add(`${cwd}\0${command}`)
|
|
355
|
+
})),
|
|
351
356
|
logStart: (kind, name, input) => {
|
|
352
357
|
const id = nanoid();
|
|
353
358
|
set((s) => ({
|
|
@@ -396,6 +401,7 @@ var useWizard = create((set, get) => ({
|
|
|
396
401
|
cliOutput: [],
|
|
397
402
|
targetIndex: null,
|
|
398
403
|
writtenFiles: [],
|
|
404
|
+
approvedCommands: /* @__PURE__ */ new Set(),
|
|
399
405
|
logs: [],
|
|
400
406
|
error: null,
|
|
401
407
|
inputReq: null,
|
|
@@ -1646,8 +1652,8 @@ var selectIndexStep = async (ctx) => {
|
|
|
1646
1652
|
};
|
|
1647
1653
|
|
|
1648
1654
|
// src/lib/agent.ts
|
|
1649
|
-
import { ToolLoopAgent, hasToolCall, Output as
|
|
1650
|
-
import { createAnthropic as
|
|
1655
|
+
import { ToolLoopAgent, hasToolCall, Output as Output3 } from "ai";
|
|
1656
|
+
import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
|
|
1651
1657
|
import "zod";
|
|
1652
1658
|
|
|
1653
1659
|
// src/lib/tools/index.ts
|
|
@@ -2391,7 +2397,8 @@ function searchFilesTool(ctx) {
|
|
|
2391
2397
|
}
|
|
2392
2398
|
|
|
2393
2399
|
// src/lib/tools/runShell.ts
|
|
2394
|
-
import { tool as tool8 } from "ai";
|
|
2400
|
+
import { tool as tool8, generateText, Output } from "ai";
|
|
2401
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2395
2402
|
import z15 from "zod";
|
|
2396
2403
|
import { relative as relative4 } from "node:path";
|
|
2397
2404
|
|
|
@@ -2500,8 +2507,10 @@ function runShell(command, opts) {
|
|
|
2500
2507
|
// src/lib/tools/runShell.ts
|
|
2501
2508
|
function storeApproval(root) {
|
|
2502
2509
|
return async (req) => {
|
|
2510
|
+
const store = useWizard.getState();
|
|
2511
|
+
if (store.isCommandApproved(req.command, req.cwd)) return "approve";
|
|
2503
2512
|
const rel = relative4(root, req.cwd);
|
|
2504
|
-
const answer = await
|
|
2513
|
+
const answer = await store.requestUserInput({
|
|
2505
2514
|
prompt: "Run this command?",
|
|
2506
2515
|
promptType: "commandApproval",
|
|
2507
2516
|
options: [],
|
|
@@ -2510,20 +2519,195 @@ function storeApproval(root) {
|
|
|
2510
2519
|
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
2511
2520
|
}
|
|
2512
2521
|
});
|
|
2513
|
-
|
|
2522
|
+
if (answer !== "approve") return "reject";
|
|
2523
|
+
store.approveCommand(req.command, req.cwd);
|
|
2524
|
+
return "approve";
|
|
2514
2525
|
};
|
|
2515
2526
|
}
|
|
2516
2527
|
var EXPLORATORY_COMMANDS = /* @__PURE__ */ new Set(["ls", "find", "tree", "dir"]);
|
|
2528
|
+
function commandSegments(command) {
|
|
2529
|
+
return command.split(/&&|;|\|/).map((segment) => segment.trim());
|
|
2530
|
+
}
|
|
2517
2531
|
function isExploratoryCommand(command) {
|
|
2518
|
-
return command
|
|
2532
|
+
return commandSegments(command).map((segment) => segment.split(/\s+/)[0]).some((word) => word !== void 0 && EXPLORATORY_COMMANDS.has(word));
|
|
2533
|
+
}
|
|
2534
|
+
var READ_ONLY_BINARIES = /* @__PURE__ */ new Set([
|
|
2535
|
+
"cat",
|
|
2536
|
+
"head",
|
|
2537
|
+
"tail",
|
|
2538
|
+
"wc",
|
|
2539
|
+
"pwd",
|
|
2540
|
+
"echo",
|
|
2541
|
+
"date",
|
|
2542
|
+
"whoami",
|
|
2543
|
+
"hostname",
|
|
2544
|
+
"uname",
|
|
2545
|
+
"which",
|
|
2546
|
+
"file",
|
|
2547
|
+
"stat",
|
|
2548
|
+
"grep",
|
|
2549
|
+
"egrep",
|
|
2550
|
+
"fgrep",
|
|
2551
|
+
"rg",
|
|
2552
|
+
"diff"
|
|
2553
|
+
]);
|
|
2554
|
+
var READ_ONLY_NO_ARGS_BINARIES = /* @__PURE__ */ new Set(["env", "printenv"]);
|
|
2555
|
+
var GIT_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
2556
|
+
"status",
|
|
2557
|
+
"log",
|
|
2558
|
+
"diff",
|
|
2559
|
+
"show",
|
|
2560
|
+
"describe",
|
|
2561
|
+
"blame",
|
|
2562
|
+
"ls-files",
|
|
2563
|
+
"rev-parse",
|
|
2564
|
+
"cat-file",
|
|
2565
|
+
"shortlog",
|
|
2566
|
+
"ls-remote"
|
|
2567
|
+
]);
|
|
2568
|
+
var VERSION_CHECK_BINARIES = /* @__PURE__ */ new Set([
|
|
2569
|
+
"node",
|
|
2570
|
+
"tsc",
|
|
2571
|
+
"npm",
|
|
2572
|
+
"pnpm",
|
|
2573
|
+
"yarn",
|
|
2574
|
+
"python",
|
|
2575
|
+
"python3",
|
|
2576
|
+
"ruby",
|
|
2577
|
+
"go",
|
|
2578
|
+
"cargo",
|
|
2579
|
+
"rustc",
|
|
2580
|
+
"php",
|
|
2581
|
+
"composer",
|
|
2582
|
+
"java",
|
|
2583
|
+
"mvn",
|
|
2584
|
+
"gradle",
|
|
2585
|
+
"bundle",
|
|
2586
|
+
"git"
|
|
2587
|
+
]);
|
|
2588
|
+
var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V"]);
|
|
2589
|
+
var FD_DUP_REDIRECT = /\d*>&\d+/g;
|
|
2590
|
+
function stripQuoted(segment) {
|
|
2591
|
+
let result = "";
|
|
2592
|
+
let quote = null;
|
|
2593
|
+
let i = 0;
|
|
2594
|
+
while (i < segment.length) {
|
|
2595
|
+
const char = segment[i];
|
|
2596
|
+
if (quote === "'") {
|
|
2597
|
+
if (char === "'") quote = null;
|
|
2598
|
+
i++;
|
|
2599
|
+
} else if (char === "\\") {
|
|
2600
|
+
i += 2;
|
|
2601
|
+
} else if (quote) {
|
|
2602
|
+
if (char === quote) quote = null;
|
|
2603
|
+
i++;
|
|
2604
|
+
} else if (char === '"' || char === "'") {
|
|
2605
|
+
quote = char;
|
|
2606
|
+
i++;
|
|
2607
|
+
} else {
|
|
2608
|
+
result += char;
|
|
2609
|
+
i++;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return quote === null ? result : segment;
|
|
2519
2613
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
return
|
|
2614
|
+
function hasFileRedirect(segment) {
|
|
2615
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes(">");
|
|
2616
|
+
}
|
|
2617
|
+
function hasShellInjectionRisk(segment) {
|
|
2618
|
+
if (segment.includes("$(") || segment.includes("<(") || segment.includes("`")) {
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
return stripQuoted(segment.replace(FD_DUP_REDIRECT, "")).includes("&");
|
|
2622
|
+
}
|
|
2623
|
+
function fastPathSegmentSafety(segment) {
|
|
2624
|
+
if (!segment) return true;
|
|
2625
|
+
if (hasFileRedirect(segment)) return false;
|
|
2626
|
+
if (hasShellInjectionRisk(segment)) return false;
|
|
2627
|
+
const [cmd0, ...rest] = segment.split(/\s+/);
|
|
2628
|
+
if (cmd0 === void 0) return true;
|
|
2629
|
+
if (VERSION_CHECK_BINARIES.has(cmd0) && rest.length === 1 && VERSION_FLAGS.has(rest[0])) {
|
|
2630
|
+
return true;
|
|
2631
|
+
}
|
|
2632
|
+
if (READ_ONLY_BINARIES.has(cmd0)) return true;
|
|
2633
|
+
if (READ_ONLY_NO_ARGS_BINARIES.has(cmd0)) {
|
|
2634
|
+
return rest.length === 0 ? true : void 0;
|
|
2635
|
+
}
|
|
2636
|
+
if (cmd0 === "git") {
|
|
2637
|
+
return GIT_READ_ONLY_SUBCOMMANDS.has(rest[0] ?? "") ? true : void 0;
|
|
2638
|
+
}
|
|
2639
|
+
return void 0;
|
|
2640
|
+
}
|
|
2641
|
+
function fastPathSafety(command) {
|
|
2642
|
+
const results = commandSegments(command).map(fastPathSegmentSafety);
|
|
2643
|
+
if (results.some((r) => r === false)) return false;
|
|
2644
|
+
if (results.every((r) => r === true)) return true;
|
|
2645
|
+
return void 0;
|
|
2646
|
+
}
|
|
2647
|
+
var CLASSIFIER_MODEL = "claude-haiku-4-5";
|
|
2648
|
+
var commandSafetySchema = z15.object({
|
|
2649
|
+
safe: z15.boolean(),
|
|
2650
|
+
reason: z15.string().describe("One short sentence explaining the verdict.")
|
|
2651
|
+
});
|
|
2652
|
+
function defaultCreateModel() {
|
|
2653
|
+
const token = getAuthToken();
|
|
2654
|
+
if (!token) {
|
|
2655
|
+
throw new Error("Not authenticated: no user token available");
|
|
2656
|
+
}
|
|
2657
|
+
return createAnthropic({
|
|
2658
|
+
apiKey: token,
|
|
2659
|
+
baseURL: PROXY_BASE_URL,
|
|
2660
|
+
fetch: proxyFetch
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function approvedCommandHistory(approvedCommands) {
|
|
2664
|
+
return Array.from(approvedCommands).map((entry) => {
|
|
2665
|
+
const sep2 = entry.indexOf("\0");
|
|
2666
|
+
return { cwd: entry.slice(0, sep2), command: entry.slice(sep2 + 1) };
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
async function classifyCommandSafety(createModel, command, cwd, explanation, approvedHistory) {
|
|
2670
|
+
try {
|
|
2671
|
+
const anthropic = createModel();
|
|
2672
|
+
const { output } = await generateText({
|
|
2673
|
+
model: anthropic(CLASSIFIER_MODEL),
|
|
2674
|
+
temperature: 0,
|
|
2675
|
+
output: Output.object({ schema: commandSafetySchema }),
|
|
2676
|
+
prompt: [
|
|
2677
|
+
"A coding agent wants to run this shell command in a user's project without asking for approval first. The command may be in any programming language or ecosystem.",
|
|
2678
|
+
"Judge it SAFE only if it cannot modify, delete, move, rename, or install anything the project cares about, cannot push/publish/deploy/commit anything, and cannot make a network request that changes remote state.",
|
|
2679
|
+
"Three broad categories are safe, and most commands you will see fall into one of them:",
|
|
2680
|
+
"(1) Reporting or listing existing state, with no side effect \u2014 e.g. `git status`, `npm ls`, `npm outdated`, `pip show`, `pip freeze`, `cargo tree`, `docker ps`, `docker images`, `kubectl get pods`, `terraform state list`. This includes a read-only GET-style query to a remote registry or API that only fetches public metadata and changes nothing remote (e.g. `npm view <package>`, `pip index versions <package>`, `curl` with no -X/--request other than GET and no -d/--data) \u2014 safe because nothing changes, not because it stays local.",
|
|
2681
|
+
'(2) A "preview" or "check" mode that reports what a change WOULD do, or reports a problem, without making the change. This is a general pattern, not a fixed list \u2014 ANY command that takes a dry-run/check/plan/validate/diff/list-only flag is safe when that flag is present, regardless of whether the same flag would normally appear on a "safe" kind of command: `terraform plan`, `terraform validate`, `terraform fmt -check`, `black --check`, `prettier --check`, `isort --check`, `gofmt -l` (list-only), `stylelint` with no `--fix`, and equally `git push --dry-run`, `npm publish --dry-run`, `kubectl apply --dry-run=client` \u2014 these last three are otherwise-unsafe operations (push, publish, cluster changes) that the dry-run flag turns into a report. The corresponding command WITHOUT that flag (e.g. `terraform apply`, `black .`, `gofmt -w`, `git push`) is a different, unsafe command \u2014 the flag is what makes the difference, for any tool, not just the ones named here. This cuts both ways, so do not assume a bare invocation is the safe one: `cargo fmt`, `black`, `prettier`, `isort`, `rustfmt`, and `terraform fmt` all REWRITE FILES by default and need an explicit check/diff/dry-run flag to become safe, while `gofmt`, `eslint`, `stylelint`, `rubocop`, and `ruff check` default to a safe check-only mode and need an explicit fix/write flag to become unsafe \u2014 the same-looking bare command is safe for one group and unsafe for the other, so judge each by what its own flags actually say, not by resemblance to a tool you already judged.',
|
|
2682
|
+
"(3) Running the project's own tests, type checker, linter, or build/compile step (e.g. `npm run build`, `yarn build`, `vite build`, `webpack`, `tsc`, `cargo build`, `go build`, `mvn compile`), as long as it is not passed an autofix/write/update flag (e.g. --fix, -u, --write, rubocop -a) \u2014 this holds even though the point of a build step is to write compiled output to a build/dist/target directory inside the project (e.g. a target/, build/, dist/, or __pycache__ directory): writing that output does not make the command unsafe. It stops being safe the moment the command chain goes past building \u2014 a step that also deploys, publishes, uploads, or pushes the build (e.g. `next build && vercel deploy`, `npm run build && npm publish`) is unsafe for that later segment even though the build segment itself is fine; judge each chained segment on its own, same as elsewhere in this list.",
|
|
2683
|
+
"Deleting or removing anything is unsafe, even something in a cache or build directory (e.g. `rm -rf __pycache__`, `cargo clean`, `git clean`), and even if the command otherwise fits one of the three categories above. Installing, uninstalling, or upgrading a dependency, changing a database schema, or writing to a path outside the project is also unsafe. A command chained with && or ; is only safe if every part of the chain is safe on its own.",
|
|
2684
|
+
"One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe.",
|
|
2685
|
+
...approvedHistory.length > 0 ? [
|
|
2686
|
+
"The user has already explicitly approved these exact commands earlier in this session (working directory in parentheses, then the command):",
|
|
2687
|
+
approvedHistory.map((h) => `- (${h.cwd}) ${h.command}`).join("\n"),
|
|
2688
|
+
"If the new command performs the same action and side-effect profile as one of these \u2014 differing only in a trivial way, such as a different file path, package name, or argument value that does not change what kind of action it is \u2014 judge it SAFE on that precedent, even if it would not otherwise fit categories (1)-(3) above. Do not stretch this to a command that merely shares a binary name, or superficially resembles one on the list while doing something riskier or of a different kind (e.g. an approved `rm build/tmp.log` does not license a new `rm -rf src/`, and an approved `git push origin feature-x` does not license `git push --force`)."
|
|
2689
|
+
] : [],
|
|
2690
|
+
`Command: ${command}`,
|
|
2691
|
+
`Working directory: ${cwd}`,
|
|
2692
|
+
`Stated purpose: ${explanation}`
|
|
2693
|
+
].join("\n")
|
|
2694
|
+
});
|
|
2695
|
+
if (!output.safe) {
|
|
2696
|
+
logger.info(
|
|
2697
|
+
{ command, reason: output.reason },
|
|
2698
|
+
"runShell: classifier judged command unsafe, requiring approval"
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
return output.safe;
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
logger.warn(
|
|
2704
|
+
{ err, command },
|
|
2705
|
+
"runShell: command safety classifier failed, requiring approval"
|
|
2706
|
+
);
|
|
2707
|
+
return false;
|
|
2526
2708
|
}
|
|
2709
|
+
}
|
|
2710
|
+
async function runAndRecord(ctx, command, cwd) {
|
|
2527
2711
|
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
2528
2712
|
const env = await ctx.shell.env().catch((err) => {
|
|
2529
2713
|
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
@@ -2559,9 +2743,18 @@ async function approveAndRun(ctx, command, cwd, explanation) {
|
|
|
2559
2743
|
output: run2.output
|
|
2560
2744
|
};
|
|
2561
2745
|
}
|
|
2562
|
-
function
|
|
2746
|
+
async function approveAndRun(ctx, command, cwd, explanation) {
|
|
2747
|
+
const decision = await ctx.shell.approve({ command, cwd, explanation });
|
|
2748
|
+
if (decision === "reject") {
|
|
2749
|
+
ctx.shell.executions.push({ command, cwd, approved: false });
|
|
2750
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2751
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2752
|
+
}
|
|
2753
|
+
return runAndRecord(ctx, command, cwd);
|
|
2754
|
+
}
|
|
2755
|
+
function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
2563
2756
|
return tool8({
|
|
2564
|
-
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.
|
|
2757
|
+
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. Do not use this to find or read files \u2014 use listFiles, searchFiles, and readFile instead of ls/find/cat/head/tail/grep/rg. This tool refuses ls/find/tree/dir outright; a read command like cat is not refused (some read-only commands run without approval, see below), but it's still the wrong tool for reading a file \u2014 the dedicated tools exist for that and won't count against this tool's command budget. The user approves any command that could change the project before it runs, so write a clear `explanation`. A command judged read-only (inspection, or running tests/typecheck/lint without an autofix flag, in any language) runs immediately without approval. If the user rejects a command, do not retry it \u2014 propose a different approach.",
|
|
2565
2758
|
inputSchema: z15.object({
|
|
2566
2759
|
command: z15.string().describe(
|
|
2567
2760
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
@@ -2580,9 +2773,29 @@ function runShellTool(ctx) {
|
|
|
2580
2773
|
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
2581
2774
|
if (!resolved2.ok) return resolved2.error;
|
|
2582
2775
|
if (isExploratoryCommand(command)) {
|
|
2583
|
-
return "Refused: use listFiles or searchFiles to
|
|
2776
|
+
return "Refused: use listFiles or searchFiles to find files, and readFile to read one, instead of ls/find/tree/dir.";
|
|
2584
2777
|
}
|
|
2585
2778
|
logger.info({ command, cwd: resolved2.target }, "called runShell tool");
|
|
2779
|
+
const fast = fastPathSafety(command);
|
|
2780
|
+
let isSafe = fast;
|
|
2781
|
+
if (isSafe === void 0) {
|
|
2782
|
+
const store = useWizard.getState();
|
|
2783
|
+
isSafe = store.isCommandApproved(command, resolved2.target);
|
|
2784
|
+
if (!isSafe) {
|
|
2785
|
+
isSafe = await classifyCommandSafety(
|
|
2786
|
+
createModel,
|
|
2787
|
+
command,
|
|
2788
|
+
resolved2.target,
|
|
2789
|
+
explanation,
|
|
2790
|
+
approvedCommandHistory(store.approvedCommands)
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (isSafe) {
|
|
2795
|
+
return serializePrompt(
|
|
2796
|
+
() => runAndRecord(ctx, command, resolved2.target)
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2586
2799
|
return serializePrompt(
|
|
2587
2800
|
() => approveAndRun(ctx, command, resolved2.target, explanation)
|
|
2588
2801
|
);
|
|
@@ -2632,8 +2845,8 @@ function reviewScriptTool(ctx) {
|
|
|
2632
2845
|
}
|
|
2633
2846
|
|
|
2634
2847
|
// src/lib/tools/generateRecord.ts
|
|
2635
|
-
import { tool as tool10, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
2636
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2848
|
+
import { tool as tool10, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
|
|
2849
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
2637
2850
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2638
2851
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
|
|
2639
2852
|
import { dirname as dirname5 } from "node:path";
|
|
@@ -2643,18 +2856,18 @@ var RECORD_MODEL = "claude-haiku-4-5";
|
|
|
2643
2856
|
var MAX_RECORDS = 100;
|
|
2644
2857
|
var BATCH_SIZE = 10;
|
|
2645
2858
|
var MAX_BATCH_ATTEMPTS = 3;
|
|
2646
|
-
function
|
|
2859
|
+
function defaultCreateModel2() {
|
|
2647
2860
|
const token = getAuthToken();
|
|
2648
2861
|
if (!token) {
|
|
2649
2862
|
throw new Error("Not authenticated: no user token available");
|
|
2650
2863
|
}
|
|
2651
|
-
return
|
|
2864
|
+
return createAnthropic2({
|
|
2652
2865
|
apiKey: token,
|
|
2653
2866
|
baseURL: PROXY_BASE_URL,
|
|
2654
2867
|
fetch: proxyFetch
|
|
2655
2868
|
});
|
|
2656
2869
|
}
|
|
2657
|
-
function generateRecordTool(ctx, createModel =
|
|
2870
|
+
function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
2658
2871
|
return tool10({
|
|
2659
2872
|
description: "Generate realistic sample records for an entity and write them to a JSON file. 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.",
|
|
2660
2873
|
inputSchema: z17.object({
|
|
@@ -2675,9 +2888,9 @@ function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
|
2675
2888
|
let lastError;
|
|
2676
2889
|
for (let attempt = 1; attempt <= MAX_BATCH_ATTEMPTS; attempt++) {
|
|
2677
2890
|
try {
|
|
2678
|
-
const { output } = await
|
|
2891
|
+
const { output } = await generateText2({
|
|
2679
2892
|
model: anthropic(RECORD_MODEL),
|
|
2680
|
-
output:
|
|
2893
|
+
output: Output2.object({
|
|
2681
2894
|
schema: z17.object({
|
|
2682
2895
|
records: z17.array(recordSchema).length(batchCount)
|
|
2683
2896
|
})
|
|
@@ -2842,7 +3055,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
2842
3055
|
if (!token) {
|
|
2843
3056
|
throw new Error("Not authenticated: no user token available");
|
|
2844
3057
|
}
|
|
2845
|
-
const anthropic =
|
|
3058
|
+
const anthropic = createAnthropic3({
|
|
2846
3059
|
apiKey: token,
|
|
2847
3060
|
baseURL: PROXY_BASE_URL,
|
|
2848
3061
|
fetch: proxyFetch
|
|
@@ -2879,7 +3092,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
2879
3092
|
}
|
|
2880
3093
|
};
|
|
2881
3094
|
}),
|
|
2882
|
-
output:
|
|
3095
|
+
output: Output3.object({ schema: req.outputSchema }),
|
|
2883
3096
|
tools: createTools(toolContext, {
|
|
2884
3097
|
output: req.outputSchema,
|
|
2885
3098
|
tools: req.tools
|
|
@@ -3062,7 +3275,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3062
3275
|
// package.json
|
|
3063
3276
|
var package_default = {
|
|
3064
3277
|
name: "@algolia/wizard",
|
|
3065
|
-
version: "0.
|
|
3278
|
+
version: "0.35.0-rc.126.250",
|
|
3066
3279
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3067
3280
|
type: "module",
|
|
3068
3281
|
engines: {
|
|
@@ -3468,6 +3681,7 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3468
3681
|
|
|
3469
3682
|
// src/actions/implement.ts
|
|
3470
3683
|
import z29 from "zod";
|
|
3684
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
3471
3685
|
import { join as join10, relative as relative6 } from "node:path";
|
|
3472
3686
|
|
|
3473
3687
|
// src/lib/git.ts
|
|
@@ -3757,7 +3971,7 @@ function algoliaClientDoc(input) {
|
|
|
3757
3971
|
function ingestionInstructions(input) {
|
|
3758
3972
|
return [
|
|
3759
3973
|
...input.confirmed && input.confirmed.length ? [
|
|
3760
|
-
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3974
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root. That directory already exists \u2014 writeFile creates any nested path itself, so never run a shell command just to create a directory.`,
|
|
3761
3975
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3762
3976
|
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3763
3977
|
`Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
|
|
@@ -3992,6 +4206,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
3992
4206
|
const targetIndex = selected?.selection;
|
|
3993
4207
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
3994
4208
|
await assertGitRepoWithHead(repoRoot);
|
|
4209
|
+
if (useCases.includes("ingestion")) {
|
|
4210
|
+
await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
|
|
4211
|
+
}
|
|
3995
4212
|
const normalized = normalizeFindingPaths(findings);
|
|
3996
4213
|
const confirmed2 = normalized.confirmedEntities;
|
|
3997
4214
|
const searchLocation = normalized.searchImplementationAnalysis;
|