@algolia/wizard 0.35.0-rc.126.249 → 0.35.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.
- package/dist/main.js +131 -261
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -253,7 +253,6 @@ var useWizard = create((set, get) => ({
|
|
|
253
253
|
cliOutput: [],
|
|
254
254
|
targetIndex: null,
|
|
255
255
|
writtenFiles: [],
|
|
256
|
-
approvedCommands: /* @__PURE__ */ new Set(),
|
|
257
256
|
logs: [],
|
|
258
257
|
error: null,
|
|
259
258
|
inputReq: null,
|
|
@@ -349,10 +348,6 @@ var useWizard = create((set, get) => ({
|
|
|
349
348
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
350
349
|
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
351
350
|
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
|
-
})),
|
|
356
351
|
logStart: (kind, name, input) => {
|
|
357
352
|
const id = nanoid();
|
|
358
353
|
set((s) => ({
|
|
@@ -401,7 +396,6 @@ var useWizard = create((set, get) => ({
|
|
|
401
396
|
cliOutput: [],
|
|
402
397
|
targetIndex: null,
|
|
403
398
|
writtenFiles: [],
|
|
404
|
-
approvedCommands: /* @__PURE__ */ new Set(),
|
|
405
399
|
logs: [],
|
|
406
400
|
error: null,
|
|
407
401
|
inputReq: null,
|
|
@@ -1652,8 +1646,8 @@ var selectIndexStep = async (ctx) => {
|
|
|
1652
1646
|
};
|
|
1653
1647
|
|
|
1654
1648
|
// src/lib/agent.ts
|
|
1655
|
-
import { ToolLoopAgent, hasToolCall, Output as
|
|
1656
|
-
import { createAnthropic as
|
|
1649
|
+
import { ToolLoopAgent, hasToolCall, Output as Output2 } from "ai";
|
|
1650
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
1657
1651
|
import "zod";
|
|
1658
1652
|
|
|
1659
1653
|
// src/lib/tools/index.ts
|
|
@@ -2397,8 +2391,7 @@ function searchFilesTool(ctx) {
|
|
|
2397
2391
|
}
|
|
2398
2392
|
|
|
2399
2393
|
// src/lib/tools/runShell.ts
|
|
2400
|
-
import { tool as tool8
|
|
2401
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2394
|
+
import { tool as tool8 } from "ai";
|
|
2402
2395
|
import z15 from "zod";
|
|
2403
2396
|
import { relative as relative4 } from "node:path";
|
|
2404
2397
|
|
|
@@ -2507,10 +2500,8 @@ function runShell(command, opts) {
|
|
|
2507
2500
|
// src/lib/tools/runShell.ts
|
|
2508
2501
|
function storeApproval(root) {
|
|
2509
2502
|
return async (req) => {
|
|
2510
|
-
const store = useWizard.getState();
|
|
2511
|
-
if (store.isCommandApproved(req.command, req.cwd)) return "approve";
|
|
2512
2503
|
const rel = relative4(root, req.cwd);
|
|
2513
|
-
const answer = await
|
|
2504
|
+
const answer = await useWizard.getState().requestUserInput({
|
|
2514
2505
|
prompt: "Run this command?",
|
|
2515
2506
|
promptType: "commandApproval",
|
|
2516
2507
|
options: [],
|
|
@@ -2519,195 +2510,20 @@ function storeApproval(root) {
|
|
|
2519
2510
|
cwd: rel === "" || rel.startsWith("..") ? req.cwd : rel
|
|
2520
2511
|
}
|
|
2521
2512
|
});
|
|
2522
|
-
|
|
2523
|
-
store.approveCommand(req.command, req.cwd);
|
|
2524
|
-
return "approve";
|
|
2513
|
+
return answer === "approve" ? "approve" : "reject";
|
|
2525
2514
|
};
|
|
2526
2515
|
}
|
|
2527
2516
|
var EXPLORATORY_COMMANDS = /* @__PURE__ */ new Set(["ls", "find", "tree", "dir"]);
|
|
2528
|
-
function commandSegments(command) {
|
|
2529
|
-
return command.split(/&&|;|\|/).map((segment) => segment.trim());
|
|
2530
|
-
}
|
|
2531
2517
|
function isExploratoryCommand(command) {
|
|
2532
|
-
return
|
|
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;
|
|
2613
|
-
}
|
|
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
|
-
});
|
|
2518
|
+
return command.split(/&&|;|\|/).map((segment) => segment.trim().split(/\s+/)[0]).some((word) => word !== void 0 && EXPLORATORY_COMMANDS.has(word));
|
|
2662
2519
|
}
|
|
2663
|
-
function
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
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;
|
|
2520
|
+
async function approveAndRun(ctx, command, cwd, explanation) {
|
|
2521
|
+
const decision = await ctx.shell.approve({ command, cwd, explanation });
|
|
2522
|
+
if (decision === "reject") {
|
|
2523
|
+
ctx.shell.executions.push({ command, cwd, approved: false });
|
|
2524
|
+
logger.info({ command }, "runShell: user rejected the command");
|
|
2525
|
+
return "The user rejected this command. Do not retry it. Propose a different command, or report the limitation via reportStatus.";
|
|
2708
2526
|
}
|
|
2709
|
-
}
|
|
2710
|
-
async function runAndRecord(ctx, command, cwd) {
|
|
2711
2527
|
useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
|
|
2712
2528
|
const env = await ctx.shell.env().catch((err) => {
|
|
2713
2529
|
logger.warn({ err, command }, "runShell: could not resolve command env");
|
|
@@ -2743,18 +2559,9 @@ async function runAndRecord(ctx, command, cwd) {
|
|
|
2743
2559
|
output: run2.output
|
|
2744
2560
|
};
|
|
2745
2561
|
}
|
|
2746
|
-
|
|
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) {
|
|
2562
|
+
function runShellTool(ctx) {
|
|
2756
2563
|
return tool8({
|
|
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.
|
|
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. To inspect the project, use listFiles or searchFiles instead of ls/find \u2014 this tool refuses those. 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.",
|
|
2758
2565
|
inputSchema: z15.object({
|
|
2759
2566
|
command: z15.string().describe(
|
|
2760
2567
|
"The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
|
|
@@ -2773,29 +2580,9 @@ function runShellTool(ctx, createModel = defaultCreateModel) {
|
|
|
2773
2580
|
const resolved2 = resolveInRoot(ctx, cwd ?? ".");
|
|
2774
2581
|
if (!resolved2.ok) return resolved2.error;
|
|
2775
2582
|
if (isExploratoryCommand(command)) {
|
|
2776
|
-
return "Refused: use listFiles or searchFiles to
|
|
2583
|
+
return "Refused: use listFiles or searchFiles to inspect the project instead of ls/find/tree.";
|
|
2777
2584
|
}
|
|
2778
2585
|
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
|
-
}
|
|
2799
2586
|
return serializePrompt(
|
|
2800
2587
|
() => approveAndRun(ctx, command, resolved2.target, explanation)
|
|
2801
2588
|
);
|
|
@@ -2845,8 +2632,8 @@ function reviewScriptTool(ctx) {
|
|
|
2845
2632
|
}
|
|
2846
2633
|
|
|
2847
2634
|
// src/lib/tools/generateRecord.ts
|
|
2848
|
-
import { tool as tool10, generateText
|
|
2849
|
-
import { createAnthropic
|
|
2635
|
+
import { tool as tool10, generateText, Output, NoObjectGeneratedError } from "ai";
|
|
2636
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2850
2637
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2851
2638
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
|
|
2852
2639
|
import { dirname as dirname5 } from "node:path";
|
|
@@ -2856,18 +2643,18 @@ var RECORD_MODEL = "claude-haiku-4-5";
|
|
|
2856
2643
|
var MAX_RECORDS = 100;
|
|
2857
2644
|
var BATCH_SIZE = 10;
|
|
2858
2645
|
var MAX_BATCH_ATTEMPTS = 3;
|
|
2859
|
-
function
|
|
2646
|
+
function defaultCreateModel() {
|
|
2860
2647
|
const token = getAuthToken();
|
|
2861
2648
|
if (!token) {
|
|
2862
2649
|
throw new Error("Not authenticated: no user token available");
|
|
2863
2650
|
}
|
|
2864
|
-
return
|
|
2651
|
+
return createAnthropic({
|
|
2865
2652
|
apiKey: token,
|
|
2866
2653
|
baseURL: PROXY_BASE_URL,
|
|
2867
2654
|
fetch: proxyFetch
|
|
2868
2655
|
});
|
|
2869
2656
|
}
|
|
2870
|
-
function generateRecordTool(ctx, createModel =
|
|
2657
|
+
function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
2871
2658
|
return tool10({
|
|
2872
2659
|
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.",
|
|
2873
2660
|
inputSchema: z17.object({
|
|
@@ -2888,9 +2675,9 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
|
|
|
2888
2675
|
let lastError;
|
|
2889
2676
|
for (let attempt = 1; attempt <= MAX_BATCH_ATTEMPTS; attempt++) {
|
|
2890
2677
|
try {
|
|
2891
|
-
const { output } = await
|
|
2678
|
+
const { output } = await generateText({
|
|
2892
2679
|
model: anthropic(RECORD_MODEL),
|
|
2893
|
-
output:
|
|
2680
|
+
output: Output.object({
|
|
2894
2681
|
schema: z17.object({
|
|
2895
2682
|
records: z17.array(recordSchema).length(batchCount)
|
|
2896
2683
|
})
|
|
@@ -3055,7 +2842,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3055
2842
|
if (!token) {
|
|
3056
2843
|
throw new Error("Not authenticated: no user token available");
|
|
3057
2844
|
}
|
|
3058
|
-
const anthropic =
|
|
2845
|
+
const anthropic = createAnthropic2({
|
|
3059
2846
|
apiKey: token,
|
|
3060
2847
|
baseURL: PROXY_BASE_URL,
|
|
3061
2848
|
fetch: proxyFetch
|
|
@@ -3092,7 +2879,7 @@ async function runAgentAttempt(req, attempt) {
|
|
|
3092
2879
|
}
|
|
3093
2880
|
};
|
|
3094
2881
|
}),
|
|
3095
|
-
output:
|
|
2882
|
+
output: Output2.object({ schema: req.outputSchema }),
|
|
3096
2883
|
tools: createTools(toolContext, {
|
|
3097
2884
|
output: req.outputSchema,
|
|
3098
2885
|
tools: req.tools
|
|
@@ -3275,7 +3062,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3275
3062
|
// package.json
|
|
3276
3063
|
var package_default = {
|
|
3277
3064
|
name: "@algolia/wizard",
|
|
3278
|
-
version: "0.35.0
|
|
3065
|
+
version: "0.35.0",
|
|
3279
3066
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3280
3067
|
type: "module",
|
|
3281
3068
|
engines: {
|
|
@@ -3681,7 +3468,6 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3681
3468
|
|
|
3682
3469
|
// src/actions/implement.ts
|
|
3683
3470
|
import z29 from "zod";
|
|
3684
|
-
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
3685
3471
|
import { join as join10, relative as relative6 } from "node:path";
|
|
3686
3472
|
|
|
3687
3473
|
// src/lib/git.ts
|
|
@@ -3971,7 +3757,7 @@ function algoliaClientDoc(input) {
|
|
|
3971
3757
|
function ingestionInstructions(input) {
|
|
3972
3758
|
return [
|
|
3973
3759
|
...input.confirmed && input.confirmed.length ? [
|
|
3974
|
-
`Create an ingestion script under "${input.ingestDir}/" at the repo root
|
|
3760
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
3975
3761
|
`Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3976
3762
|
`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.`,
|
|
3977
3763
|
`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.`,
|
|
@@ -4206,9 +3992,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
|
4206
3992
|
const targetIndex = selected?.selection;
|
|
4207
3993
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4208
3994
|
await assertGitRepoWithHead(repoRoot);
|
|
4209
|
-
if (useCases.includes("ingestion")) {
|
|
4210
|
-
await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
|
|
4211
|
-
}
|
|
4212
3995
|
const normalized = normalizeFindingPaths(findings);
|
|
4213
3996
|
const confirmed2 = normalized.confirmedEntities;
|
|
4214
3997
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -4529,7 +4312,12 @@ var defaultWorkflow = {
|
|
|
4529
4312
|
id: "project-scan",
|
|
4530
4313
|
title: "project scan",
|
|
4531
4314
|
outputSchema: projectScanSchema,
|
|
4532
|
-
tips: [
|
|
4315
|
+
tips: [
|
|
4316
|
+
"apps-and-indices",
|
|
4317
|
+
"records-are-json",
|
|
4318
|
+
"speed",
|
|
4319
|
+
"searchable-attributes"
|
|
4320
|
+
],
|
|
4533
4321
|
run: (ctx) => {
|
|
4534
4322
|
ctx.notify({
|
|
4535
4323
|
messages: [
|
|
@@ -4565,7 +4353,7 @@ var defaultWorkflow = {
|
|
|
4565
4353
|
id: "ingestion",
|
|
4566
4354
|
title: "ingest records",
|
|
4567
4355
|
outputSchema: implementSchema,
|
|
4568
|
-
tips: ["
|
|
4356
|
+
tips: ["facets", "test-relevance", "search-analytics", "recommend"],
|
|
4569
4357
|
run: (ctx) => {
|
|
4570
4358
|
ctx.notify({
|
|
4571
4359
|
messages: [
|
|
@@ -4586,13 +4374,7 @@ var defaultWorkflow = {
|
|
|
4586
4374
|
id: "search",
|
|
4587
4375
|
title: "create search ui",
|
|
4588
4376
|
outputSchema: implementSchema,
|
|
4589
|
-
tips: [
|
|
4590
|
-
"test-relevance",
|
|
4591
|
-
"search-analytics",
|
|
4592
|
-
"recommend",
|
|
4593
|
-
"searchable-attributes",
|
|
4594
|
-
"facets"
|
|
4595
|
-
],
|
|
4377
|
+
tips: ["typo-tolerance", "synonyms", "multi-index-search", "rules"],
|
|
4596
4378
|
run: (ctx) => {
|
|
4597
4379
|
ctx.notify({
|
|
4598
4380
|
messages: ["Building your Algolia search experience\u2026"]
|
|
@@ -5096,7 +4878,7 @@ function Logs() {
|
|
|
5096
4878
|
}
|
|
5097
4879
|
|
|
5098
4880
|
// src/ui/Tips.tsx
|
|
5099
|
-
import { useEffect as useEffect3, useState as useState8 } from "react";
|
|
4881
|
+
import { useEffect as useEffect3, useRef as useRef4, useState as useState8 } from "react";
|
|
5100
4882
|
import { Box as Box18, Text as Text18 } from "ink";
|
|
5101
4883
|
import terminalLink from "terminal-link";
|
|
5102
4884
|
|
|
@@ -5120,7 +4902,7 @@ function highlightJson(json) {
|
|
|
5120
4902
|
return /* @__PURE__ */ jsx15(Text17, { color: "yellow", children: part }, i);
|
|
5121
4903
|
}
|
|
5122
4904
|
if (/^[{}[\]:,]$/.test(part)) {
|
|
5123
|
-
return /* @__PURE__ */ jsx15(Text17, {
|
|
4905
|
+
return /* @__PURE__ */ jsx15(Text17, { color: COLORS.dim, children: part }, i);
|
|
5124
4906
|
}
|
|
5125
4907
|
return /* @__PURE__ */ jsx15(Text17, { children: part }, i);
|
|
5126
4908
|
});
|
|
@@ -5253,6 +5035,70 @@ var tips = [
|
|
|
5253
5035
|
}
|
|
5254
5036
|
]
|
|
5255
5037
|
},
|
|
5038
|
+
{
|
|
5039
|
+
id: "typo-tolerance",
|
|
5040
|
+
title: "Your users will misspell things. Algolia already handles it.",
|
|
5041
|
+
chunks: [
|
|
5042
|
+
{
|
|
5043
|
+
type: "text",
|
|
5044
|
+
value: 'Typo tolerance is on out of the box. Someone searching "rstaurnt" still gets restaurants.'
|
|
5045
|
+
},
|
|
5046
|
+
{
|
|
5047
|
+
type: "text",
|
|
5048
|
+
value: "Exact matches always rank above one-typo matches, which rank above two."
|
|
5049
|
+
}
|
|
5050
|
+
]
|
|
5051
|
+
},
|
|
5052
|
+
{
|
|
5053
|
+
id: "synonyms",
|
|
5054
|
+
title: "Synonyms teach the engine your words",
|
|
5055
|
+
chunks: [
|
|
5056
|
+
{
|
|
5057
|
+
type: "text",
|
|
5058
|
+
value: "Pants, trousers, slacks: one synonym entry and all three find each other."
|
|
5059
|
+
},
|
|
5060
|
+
{
|
|
5061
|
+
type: "text",
|
|
5062
|
+
value: 'One-way synonyms go further. "tablet" can find iPad without iPad returning every tablet.'
|
|
5063
|
+
},
|
|
5064
|
+
{
|
|
5065
|
+
type: "text",
|
|
5066
|
+
value: 'And a placeholder like "Case for iPhone <model>" covers a whole product line without duplicating records.'
|
|
5067
|
+
}
|
|
5068
|
+
]
|
|
5069
|
+
},
|
|
5070
|
+
{
|
|
5071
|
+
id: "multi-index-search",
|
|
5072
|
+
title: "One search box, many indices",
|
|
5073
|
+
chunks: [
|
|
5074
|
+
{
|
|
5075
|
+
type: "text",
|
|
5076
|
+
value: "Federated search queries several indices at once, so a single dropdown can show products, categories and articles side by side."
|
|
5077
|
+
},
|
|
5078
|
+
{
|
|
5079
|
+
type: "text",
|
|
5080
|
+
value: "Each source gets its own layout and its own hit count: 4 categories, 8 products, whatever fits."
|
|
5081
|
+
}
|
|
5082
|
+
]
|
|
5083
|
+
},
|
|
5084
|
+
{
|
|
5085
|
+
id: "rules",
|
|
5086
|
+
title: "Rules let you put a thumb on the scale",
|
|
5087
|
+
chunks: [
|
|
5088
|
+
{
|
|
5089
|
+
type: "text",
|
|
5090
|
+
value: "When a query matches a condition, a Rule can pin a result to the top, hide one, boost a brand or attach a banner."
|
|
5091
|
+
},
|
|
5092
|
+
{
|
|
5093
|
+
type: "text",
|
|
5094
|
+
value: 'A search for "sale" can filter to discounted items. A search for "iphone" can pin your best-selling case.'
|
|
5095
|
+
},
|
|
5096
|
+
{
|
|
5097
|
+
type: "text",
|
|
5098
|
+
value: "The Visual Editor in the dashboard does all of it by drag and drop, no reindex needed."
|
|
5099
|
+
}
|
|
5100
|
+
]
|
|
5101
|
+
},
|
|
5256
5102
|
{
|
|
5257
5103
|
id: "recommend",
|
|
5258
5104
|
title: "Let Algolia act as your recommendation engine",
|
|
@@ -5279,7 +5125,7 @@ function tipsByIds(ids) {
|
|
|
5279
5125
|
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5280
5126
|
var TICK_MS = 16;
|
|
5281
5127
|
var CHUNK_HOLD_MS = 2e3;
|
|
5282
|
-
var HOLD_MS =
|
|
5128
|
+
var HOLD_MS = 12e3;
|
|
5283
5129
|
function TipTitle({ children }) {
|
|
5284
5130
|
return /* @__PURE__ */ jsxs16(Box18, { gap: 1, children: [
|
|
5285
5131
|
/* @__PURE__ */ jsx16(Text18, { color: "cyan", children: "\u2726" }),
|
|
@@ -5337,7 +5183,11 @@ function TipContent({
|
|
|
5337
5183
|
) });
|
|
5338
5184
|
}
|
|
5339
5185
|
var progress = { stepId: null, tipIndex: 0, revealed: 0 };
|
|
5340
|
-
function Tips({
|
|
5186
|
+
function Tips({
|
|
5187
|
+
stepId,
|
|
5188
|
+
ids,
|
|
5189
|
+
onRevealStateChange
|
|
5190
|
+
}) {
|
|
5341
5191
|
const tips2 = tipsByIds(ids);
|
|
5342
5192
|
const resuming = progress.stepId === stepId;
|
|
5343
5193
|
if (!resuming) {
|
|
@@ -5368,6 +5218,16 @@ function Tips({ stepId, ids }) {
|
|
|
5368
5218
|
);
|
|
5369
5219
|
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
5370
5220
|
}
|
|
5221
|
+
const revealState = tips2.length === 0 ? "idle" : revealed < totalLength ? "revealing" : "revealed";
|
|
5222
|
+
const onRevealStateChangeRef = useRef4(onRevealStateChange);
|
|
5223
|
+
onRevealStateChangeRef.current = onRevealStateChange;
|
|
5224
|
+
const reportedRef = useRef4(null);
|
|
5225
|
+
useEffect3(() => {
|
|
5226
|
+
if (reportedRef.current === revealState) return;
|
|
5227
|
+
reportedRef.current = revealState;
|
|
5228
|
+
onRevealStateChangeRef.current?.(revealState);
|
|
5229
|
+
}, [revealState]);
|
|
5230
|
+
useEffect3(() => () => onRevealStateChangeRef.current?.("idle"), []);
|
|
5371
5231
|
useEffect3(() => {
|
|
5372
5232
|
if (tips2.length === 0) return;
|
|
5373
5233
|
if (revealed < totalLength) {
|
|
@@ -5417,6 +5277,7 @@ function App() {
|
|
|
5417
5277
|
const { exit } = useApp();
|
|
5418
5278
|
const { columns, rows } = useWindowSize7();
|
|
5419
5279
|
const [showLogs, setShowLogs] = useState9(false);
|
|
5280
|
+
const [tipState, setTipState] = useState9("idle");
|
|
5420
5281
|
const finished = phase === "done" || phase === "error";
|
|
5421
5282
|
const currentStep = steps[currentStepIndex];
|
|
5422
5283
|
const stepDef = workflow ? getWorkflow(workflow.id)?.steps.find((s) => s.id === currentStep?.id) : void 0;
|
|
@@ -5424,9 +5285,11 @@ function App() {
|
|
|
5424
5285
|
const stepHasTips = tipIds.length > 0;
|
|
5425
5286
|
const isAwaitingUserInput = phase === "awaitingInput" && !!inputReq;
|
|
5426
5287
|
const isCommandApprovalPrompt = isAwaitingUserInput && inputReq?.promptType === "commandApproval";
|
|
5427
|
-
const
|
|
5288
|
+
const promptPending = isAwaitingUserInput && !isCommandApprovalPrompt;
|
|
5289
|
+
const holdForTip = stepHasTips && promptPending && tipState === "revealing";
|
|
5290
|
+
const showTips = stepHasTips && (phase === "running" || isCommandApprovalPrompt || holdForTip);
|
|
5428
5291
|
const showNoticesInMain = !stepHasTips;
|
|
5429
|
-
const showNotices = !isAwaitingUserInput;
|
|
5292
|
+
const showNotices = !isAwaitingUserInput || holdForTip;
|
|
5430
5293
|
useInput6(
|
|
5431
5294
|
(_input, key) => {
|
|
5432
5295
|
if (key.return) {
|
|
@@ -5446,7 +5309,7 @@ function App() {
|
|
|
5446
5309
|
});
|
|
5447
5310
|
}
|
|
5448
5311
|
});
|
|
5449
|
-
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
5312
|
+
const escOwnedElsewhere = phase === "idle" || phase === "authenticating" || phase === "awaitingInput" && !holdForTip && (inputReq?.promptType === "enterToContinue" || inputReq?.promptType === "commandApproval");
|
|
5450
5313
|
useInput6((_input, key) => {
|
|
5451
5314
|
if (escOwnedElsewhere) return;
|
|
5452
5315
|
if (key.escape) {
|
|
@@ -5510,7 +5373,14 @@ function App() {
|
|
|
5510
5373
|
/* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
5511
5374
|
] }),
|
|
5512
5375
|
/* @__PURE__ */ jsx17(CliOutput, {}),
|
|
5513
|
-
showTips && currentStep && /* @__PURE__ */ jsx17(
|
|
5376
|
+
showTips && currentStep && /* @__PURE__ */ jsx17(
|
|
5377
|
+
Tips,
|
|
5378
|
+
{
|
|
5379
|
+
stepId: currentStep.id,
|
|
5380
|
+
ids: tipIds,
|
|
5381
|
+
onRevealStateChange: setTipState
|
|
5382
|
+
}
|
|
5383
|
+
),
|
|
5514
5384
|
showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, { showAll: true, border: false }),
|
|
5515
5385
|
!showTips && /* @__PURE__ */ jsx17(PromptInput, {}),
|
|
5516
5386
|
phase === "error" && error && /* @__PURE__ */ jsx17(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.status.error, children: [
|
|
@@ -5519,7 +5389,7 @@ function App() {
|
|
|
5519
5389
|
] }) })
|
|
5520
5390
|
] }),
|
|
5521
5391
|
!showNoticesInMain && showNotices && /* @__PURE__ */ jsx17(Notices, {}),
|
|
5522
|
-
showTips && /* @__PURE__ */ jsx17(PromptInput, {})
|
|
5392
|
+
showTips && !holdForTip && /* @__PURE__ */ jsx17(PromptInput, {})
|
|
5523
5393
|
]
|
|
5524
5394
|
}
|
|
5525
5395
|
),
|