@algolia/wizard 0.5.0-rc.49.18 → 0.5.0-rc.50.20
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 +156 -180
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -1324,7 +1324,18 @@ function App() {
|
|
|
1324
1324
|
width: "100%",
|
|
1325
1325
|
justifyContent: "space-between",
|
|
1326
1326
|
children: [
|
|
1327
|
-
showLogs ?
|
|
1327
|
+
showLogs ? (
|
|
1328
|
+
// The error also renders here, not just in the main panel — a user
|
|
1329
|
+
// watching the raw log through a long step would otherwise never
|
|
1330
|
+
// see that the workflow failed.
|
|
1331
|
+
/* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", flexGrow: 1, children: [
|
|
1332
|
+
/* @__PURE__ */ jsx13(Logs, {}),
|
|
1333
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { paddingX: 4, paddingBottom: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
1334
|
+
"\u2716 ",
|
|
1335
|
+
error
|
|
1336
|
+
] }) })
|
|
1337
|
+
] })
|
|
1338
|
+
) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
|
|
1328
1339
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1329
1340
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1330
1341
|
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
@@ -1353,8 +1364,7 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1353
1364
|
var DEFAULT_CONFIG = {
|
|
1354
1365
|
version: 1,
|
|
1355
1366
|
aiConsent: false,
|
|
1356
|
-
workflowsRun: []
|
|
1357
|
-
searchApiKeys: {}
|
|
1367
|
+
workflowsRun: []
|
|
1358
1368
|
};
|
|
1359
1369
|
async function loadConfig() {
|
|
1360
1370
|
try {
|
|
@@ -1373,38 +1383,6 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1373
1383
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1374
1384
|
await saveConfig(config);
|
|
1375
1385
|
}
|
|
1376
|
-
function isStoredSearchKey(value) {
|
|
1377
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1378
|
-
const { appId, key } = value;
|
|
1379
|
-
return typeof appId === "string" && !!appId && typeof key === "string" && !!key;
|
|
1380
|
-
}
|
|
1381
|
-
function storedSearchKeys(config) {
|
|
1382
|
-
const stored = config.searchApiKeys;
|
|
1383
|
-
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1384
|
-
return {};
|
|
1385
|
-
}
|
|
1386
|
-
return stored;
|
|
1387
|
-
}
|
|
1388
|
-
async function getStoredSearchKey(index, appId) {
|
|
1389
|
-
const entry = storedSearchKeys(await loadConfig())[index];
|
|
1390
|
-
if (!isStoredSearchKey(entry) || entry.appId !== appId) return void 0;
|
|
1391
|
-
return entry.key;
|
|
1392
|
-
}
|
|
1393
|
-
async function storeSearchKey(index, appId, key) {
|
|
1394
|
-
const config = await loadConfig();
|
|
1395
|
-
config.searchApiKeys = {
|
|
1396
|
-
...storedSearchKeys(config),
|
|
1397
|
-
[index]: { appId, key }
|
|
1398
|
-
};
|
|
1399
|
-
await saveConfig(config);
|
|
1400
|
-
}
|
|
1401
|
-
async function forgetSearchKey(index) {
|
|
1402
|
-
const config = await loadConfig();
|
|
1403
|
-
const remaining = { ...storedSearchKeys(config) };
|
|
1404
|
-
delete remaining[index];
|
|
1405
|
-
config.searchApiKeys = remaining;
|
|
1406
|
-
await saveConfig(config);
|
|
1407
|
-
}
|
|
1408
1386
|
|
|
1409
1387
|
// src/lib/telemetry.ts
|
|
1410
1388
|
function isTelemetryEnabled() {
|
|
@@ -1886,7 +1864,12 @@ var selectIndexStep = async (ctx) => {
|
|
|
1886
1864
|
};
|
|
1887
1865
|
|
|
1888
1866
|
// src/lib/agent.ts
|
|
1889
|
-
import {
|
|
1867
|
+
import {
|
|
1868
|
+
ToolLoopAgent,
|
|
1869
|
+
hasToolCall,
|
|
1870
|
+
stepCountIs,
|
|
1871
|
+
Output as Output2
|
|
1872
|
+
} from "ai";
|
|
1890
1873
|
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
1891
1874
|
import "zod";
|
|
1892
1875
|
|
|
@@ -2479,9 +2462,41 @@ var MODEL_BY_SIZE = {
|
|
|
2479
2462
|
medium: "claude-sonnet-4-6",
|
|
2480
2463
|
large: "claude-opus-4-8"
|
|
2481
2464
|
};
|
|
2465
|
+
var STEP_TIMEOUT_MS = 15e4;
|
|
2466
|
+
var CHUNK_TIMEOUT_MS = 45e3;
|
|
2467
|
+
var MAX_STEPS = 100;
|
|
2468
|
+
function withRollingCacheBreakpoint(messages) {
|
|
2469
|
+
const last = messages.at(-1);
|
|
2470
|
+
if (!last) return messages;
|
|
2471
|
+
return [
|
|
2472
|
+
...messages.slice(0, -1),
|
|
2473
|
+
{
|
|
2474
|
+
...last,
|
|
2475
|
+
providerOptions: {
|
|
2476
|
+
...last.providerOptions,
|
|
2477
|
+
anthropic: {
|
|
2478
|
+
...last.providerOptions?.anthropic,
|
|
2479
|
+
cacheControl: { type: "ephemeral" }
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
];
|
|
2484
|
+
}
|
|
2485
|
+
function asAgentError(name, err) {
|
|
2486
|
+
const aborted = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
2487
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2488
|
+
return new Error(
|
|
2489
|
+
aborted ? `${name} agent stalled \u2014 no response for ${STEP_TIMEOUT_MS / 1e3}s, or the stream died mid-response` : `${name} agent failed: ${message}`,
|
|
2490
|
+
{ cause: err }
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2482
2493
|
async function runAgent(req) {
|
|
2483
2494
|
const start = Date.now();
|
|
2484
|
-
|
|
2495
|
+
const name = req.name ?? "unnamed";
|
|
2496
|
+
logger.info(
|
|
2497
|
+
{ agent: name, startedAt: new Date(start).toISOString() },
|
|
2498
|
+
"runAgent started"
|
|
2499
|
+
);
|
|
2485
2500
|
const token = getAuthToken();
|
|
2486
2501
|
if (!token) {
|
|
2487
2502
|
throw new Error("Not authenticated: no user token available");
|
|
@@ -2501,11 +2516,16 @@ async function runAgent(req) {
|
|
|
2501
2516
|
] : [],
|
|
2502
2517
|
"Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use."
|
|
2503
2518
|
];
|
|
2519
|
+
let streamError;
|
|
2520
|
+
const logStreamErrors = {
|
|
2521
|
+
onError: ({ error }) => logger.error({ agent: name, err: error }, "agent stream error")
|
|
2522
|
+
};
|
|
2504
2523
|
const agent = new ToolLoopAgent({
|
|
2524
|
+
...logStreamErrors,
|
|
2505
2525
|
model: anthropic2(MODEL_BY_SIZE[req.modelSize ?? "medium"]),
|
|
2506
|
-
// Cache tools
|
|
2507
|
-
//
|
|
2508
|
-
// after the first.
|
|
2526
|
+
// Cache the tools and instructions too (see `withRollingCacheBreakpoint`).
|
|
2527
|
+
// Tools are sent ahead of instructions, so one mark on the last instruction
|
|
2528
|
+
// covers both; every step after the first reads them from cache.
|
|
2509
2529
|
instructions: instructions.map((i, idx, arr) => {
|
|
2510
2530
|
return {
|
|
2511
2531
|
role: "system",
|
|
@@ -2523,51 +2543,70 @@ async function runAgent(req) {
|
|
|
2523
2543
|
tools: req.tools
|
|
2524
2544
|
}),
|
|
2525
2545
|
toolChoice: "required",
|
|
2526
|
-
stopWhen: [hasToolCall("reportStatus")]
|
|
2546
|
+
stopWhen: [hasToolCall("reportStatus"), stepCountIs(MAX_STEPS)],
|
|
2547
|
+
prepareStep: ({ messages }) => ({
|
|
2548
|
+
messages: withRollingCacheBreakpoint(messages)
|
|
2549
|
+
})
|
|
2527
2550
|
});
|
|
2528
2551
|
const stream = await agent.stream({
|
|
2529
|
-
prompt: "Follow system instructions"
|
|
2552
|
+
prompt: "Follow system instructions",
|
|
2553
|
+
timeout: { stepMs: STEP_TIMEOUT_MS, chunkMs: CHUNK_TIMEOUT_MS }
|
|
2530
2554
|
});
|
|
2531
2555
|
let chunks = [];
|
|
2532
2556
|
const chunkLimit = 5;
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
for await (const part of stream.fullStream) {
|
|
2559
|
+
if (part.type === "error") {
|
|
2560
|
+
streamError ??= part.error;
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
if (part.type !== "text-delta") continue;
|
|
2564
|
+
if (chunks.length < chunkLimit) {
|
|
2565
|
+
chunks.push(part.text);
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
chunks.push(part.text);
|
|
2569
|
+
logger.debug(chunks.join(""));
|
|
2570
|
+
chunks = [];
|
|
2537
2571
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
chunks = [];
|
|
2572
|
+
} catch (err) {
|
|
2573
|
+
throw asAgentError(name, streamError ?? err);
|
|
2541
2574
|
}
|
|
2542
2575
|
if (chunks.length) {
|
|
2543
2576
|
logger.debug(chunks.join(""));
|
|
2544
2577
|
}
|
|
2545
2578
|
const end = Date.now();
|
|
2546
|
-
|
|
2579
|
+
let usage;
|
|
2580
|
+
let toolResults;
|
|
2581
|
+
try {
|
|
2582
|
+
usage = await stream.totalUsage;
|
|
2583
|
+
toolResults = await stream.toolResults;
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
throw asAgentError(name, streamError ?? err);
|
|
2586
|
+
}
|
|
2547
2587
|
logger.info(
|
|
2548
2588
|
{
|
|
2589
|
+
agent: name,
|
|
2549
2590
|
finishedAt: new Date(end).toISOString(),
|
|
2550
2591
|
durationMs: end - start,
|
|
2551
|
-
// cachedInputTokens > 0 confirms prompt caching engaged. If it stays 0
|
|
2552
|
-
// across turns, the tools+system prefix is under the model's min
|
|
2553
|
-
// cacheable size (2048 tokens for sonnet-4-6) and caching is a no-op.
|
|
2554
2592
|
usage
|
|
2555
2593
|
},
|
|
2556
2594
|
"runAgent finished"
|
|
2557
2595
|
);
|
|
2558
2596
|
logger.info(
|
|
2559
|
-
{ counts: toolContext.counts, limits: toolContext.limits },
|
|
2597
|
+
{ agent: name, counts: toolContext.counts, limits: toolContext.limits },
|
|
2560
2598
|
"tool usage"
|
|
2561
2599
|
);
|
|
2562
|
-
const toolResults = await stream.toolResults;
|
|
2563
2600
|
const report = [...toolResults].reverse().find((r) => r.toolName === "reportStatus");
|
|
2564
2601
|
if (!report) {
|
|
2565
|
-
throw new Error(
|
|
2602
|
+
throw new Error(`${name} agent finished without calling reportStatus`, {
|
|
2603
|
+
cause: streamError
|
|
2604
|
+
});
|
|
2566
2605
|
}
|
|
2567
2606
|
const result = report.output;
|
|
2568
2607
|
if (result.status !== "success") {
|
|
2569
2608
|
throw new Error(
|
|
2570
|
-
|
|
2609
|
+
`${name} agent reported failure: ${result.reason ?? "no reason given"}`
|
|
2571
2610
|
);
|
|
2572
2611
|
}
|
|
2573
2612
|
return result.output;
|
|
@@ -2591,7 +2630,8 @@ var detectLanguage = () => runAgent({
|
|
|
2591
2630
|
],
|
|
2592
2631
|
tools: ["listFiles", "changeDirectory", "readFile", "searchFiles"],
|
|
2593
2632
|
outputSchema: detectLanguageSchema,
|
|
2594
|
-
modelSize: "small"
|
|
2633
|
+
modelSize: "small",
|
|
2634
|
+
name: "detect-language"
|
|
2595
2635
|
});
|
|
2596
2636
|
|
|
2597
2637
|
// src/actions/analyzeCodebase.ts
|
|
@@ -2667,7 +2707,8 @@ function runMode(mode, extraInstructions = []) {
|
|
|
2667
2707
|
return runAgent({
|
|
2668
2708
|
instructions: [...instructions, ...extraInstructions],
|
|
2669
2709
|
tools: READONLY_TOOLS,
|
|
2670
|
-
outputSchema
|
|
2710
|
+
outputSchema,
|
|
2711
|
+
name: `analyze:${mode}`
|
|
2671
2712
|
});
|
|
2672
2713
|
}
|
|
2673
2714
|
async function runAnalysis(mode, extraInstructions = []) {
|
|
@@ -2681,7 +2722,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2681
2722
|
// package.json
|
|
2682
2723
|
var package_default = {
|
|
2683
2724
|
name: "@algolia/wizard",
|
|
2684
|
-
version: "0.5.0-rc.
|
|
2725
|
+
version: "0.5.0-rc.50.20",
|
|
2685
2726
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2686
2727
|
type: "module",
|
|
2687
2728
|
engines: {
|
|
@@ -2729,7 +2770,7 @@ var package_default = {
|
|
|
2729
2770
|
dependencies: {
|
|
2730
2771
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2731
2772
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2732
|
-
"@algolia/cli": "^5.
|
|
2773
|
+
"@algolia/cli": "^5.11.0",
|
|
2733
2774
|
"@hono/node-server": "^2.0.10",
|
|
2734
2775
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
2735
2776
|
"@segment/analytics-node": "^3.1.0",
|
|
@@ -3076,7 +3117,8 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3076
3117
|
],
|
|
3077
3118
|
tools: [],
|
|
3078
3119
|
outputSchema: reviewSchema,
|
|
3079
|
-
modelSize: "small"
|
|
3120
|
+
modelSize: "small",
|
|
3121
|
+
name: "review"
|
|
3080
3122
|
});
|
|
3081
3123
|
ctx.notify({ messages: formatReviewSummary(result) });
|
|
3082
3124
|
return result;
|
|
@@ -3287,23 +3329,6 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3287
3329
|
function hasEnvVar(content, name) {
|
|
3288
3330
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3289
3331
|
}
|
|
3290
|
-
async function readEnvVar(worktreePath, name) {
|
|
3291
|
-
let content;
|
|
3292
|
-
try {
|
|
3293
|
-
content = await readFile8(join10(worktreePath, ".env"), "utf8");
|
|
3294
|
-
} catch (err) {
|
|
3295
|
-
if (err.code !== "ENOENT") throw err;
|
|
3296
|
-
return void 0;
|
|
3297
|
-
}
|
|
3298
|
-
const match = new RegExp(
|
|
3299
|
-
`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=[ \\t]*(.*)$`,
|
|
3300
|
-
"m"
|
|
3301
|
-
).exec(content);
|
|
3302
|
-
if (!match) return void 0;
|
|
3303
|
-
const value = match[1].trim().replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
3304
|
-
if (!value || value.startsWith("<")) return void 0;
|
|
3305
|
-
return value;
|
|
3306
|
-
}
|
|
3307
3332
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3308
3333
|
const target = join10(worktreePath, ".env");
|
|
3309
3334
|
let existing = "";
|
|
@@ -3378,72 +3403,50 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3378
3403
|
|
|
3379
3404
|
// src/lib/algoliaApiKey.ts
|
|
3380
3405
|
import { z as z23 } from "zod";
|
|
3406
|
+
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
3407
|
+
var apiKeySchema = z23.object({
|
|
3408
|
+
value: z23.string().min(1),
|
|
3409
|
+
acl: z23.array(z23.string()).default([]),
|
|
3410
|
+
indexes: z23.array(z23.string()).default([])
|
|
3411
|
+
});
|
|
3412
|
+
var apiKeyListSchema = z23.object({
|
|
3413
|
+
items: z23.array(apiKeySchema).optional(),
|
|
3414
|
+
keys: z23.array(apiKeySchema).optional()
|
|
3415
|
+
}).transform((o) => o.items ?? o.keys ?? []);
|
|
3381
3416
|
var createdKeySchema = z23.object({
|
|
3382
3417
|
key: z23.string().min(1).optional(),
|
|
3383
3418
|
value: z23.string().min(1).optional()
|
|
3384
|
-
})
|
|
3385
|
-
|
|
3386
|
-
|
|
3419
|
+
});
|
|
3420
|
+
function canReuse(key, index) {
|
|
3421
|
+
return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
|
|
3422
|
+
}
|
|
3423
|
+
async function createSearchKey(index) {
|
|
3387
3424
|
const stdout = await runAlgoliaCli([
|
|
3388
3425
|
"apikeys",
|
|
3389
3426
|
"create",
|
|
3390
|
-
"--acl",
|
|
3391
|
-
"search",
|
|
3392
3427
|
"--indices",
|
|
3393
3428
|
index,
|
|
3429
|
+
"--acl",
|
|
3430
|
+
"search,browse",
|
|
3394
3431
|
"--description",
|
|
3395
|
-
`
|
|
3432
|
+
`wizard search-only key for ${index}`,
|
|
3396
3433
|
"-o",
|
|
3397
3434
|
"json"
|
|
3398
3435
|
]);
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
payload = JSON.parse(stdout);
|
|
3402
|
-
} catch {
|
|
3403
|
-
throw new Error("apikeys create returned output that is not valid JSON");
|
|
3404
|
-
}
|
|
3405
|
-
const created = createdKeySchema.parse(payload);
|
|
3436
|
+
const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
|
|
3437
|
+
const created = key ?? value;
|
|
3406
3438
|
if (!created) throw new Error("apikeys create returned no key value");
|
|
3407
3439
|
return created;
|
|
3408
3440
|
}
|
|
3409
|
-
async function
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
return
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
async function resolveSearchOnlyKey(index, appId, envKey) {
|
|
3418
|
-
if (envKey) {
|
|
3419
|
-
await recordSearchKey(index, appId, envKey);
|
|
3420
|
-
return { key: envKey, source: "env" };
|
|
3421
|
-
}
|
|
3422
|
-
const stored = await getStoredSearchKey(index, appId);
|
|
3423
|
-
if (stored) {
|
|
3424
|
-
if (await apiKeyExists(stored)) {
|
|
3425
|
-
logger.info({ index, appId }, "reusing the stored search-only API key");
|
|
3426
|
-
return { key: stored, source: "config" };
|
|
3427
|
-
}
|
|
3428
|
-
logger.warn(
|
|
3429
|
-
{ index, appId },
|
|
3430
|
-
"the stored search-only API key no longer exists; creating a replacement"
|
|
3431
|
-
);
|
|
3432
|
-
await forgetSearchKey(index);
|
|
3433
|
-
}
|
|
3434
|
-
const key = await createSearchOnlyKey(index);
|
|
3435
|
-
await recordSearchKey(index, appId, key);
|
|
3436
|
-
return { key, source: "created" };
|
|
3437
|
-
}
|
|
3438
|
-
async function recordSearchKey(index, appId, key) {
|
|
3439
|
-
try {
|
|
3440
|
-
await storeSearchKey(index, appId, key);
|
|
3441
|
-
} catch (err) {
|
|
3442
|
-
logger.warn(
|
|
3443
|
-
{ err: err.message, index },
|
|
3444
|
-
"could not record the search-only API key; a later run may create another"
|
|
3445
|
-
);
|
|
3441
|
+
async function resolveSearchOnlyKey(index) {
|
|
3442
|
+
const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
|
|
3443
|
+
const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
|
|
3444
|
+
if (existing) {
|
|
3445
|
+
logger.info({ index }, "reusing existing search-only API key");
|
|
3446
|
+
return existing;
|
|
3446
3447
|
}
|
|
3448
|
+
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
3449
|
+
return createSearchKey(index);
|
|
3447
3450
|
}
|
|
3448
3451
|
|
|
3449
3452
|
// src/lib/algoliaDocs.ts
|
|
@@ -3598,22 +3601,15 @@ function publicEnvPrefix(language) {
|
|
|
3598
3601
|
}
|
|
3599
3602
|
return "PUBLIC_";
|
|
3600
3603
|
}
|
|
3601
|
-
var APP_ID_VAR_SUFFIX = "ALGOLIA_APP_ID";
|
|
3602
|
-
var SEARCH_KEY_VAR_SUFFIX = "ALGOLIA_SEARCH_API_KEY";
|
|
3603
|
-
function appIdVar(language) {
|
|
3604
|
-
return `${publicEnvPrefix(language)}${APP_ID_VAR_SUFFIX}`;
|
|
3605
|
-
}
|
|
3606
|
-
function searchKeyVar(language) {
|
|
3607
|
-
return `${publicEnvPrefix(language)}${SEARCH_KEY_VAR_SUFFIX}`;
|
|
3608
|
-
}
|
|
3609
3604
|
function searchEnvVars(language, appId, searchKey) {
|
|
3605
|
+
const prefix = publicEnvPrefix(language);
|
|
3610
3606
|
return [
|
|
3611
3607
|
{
|
|
3612
|
-
name:
|
|
3608
|
+
name: `${prefix}ALGOLIA_APP_ID`,
|
|
3613
3609
|
value: appId ?? "<your-algolia-app-id>"
|
|
3614
3610
|
},
|
|
3615
3611
|
{
|
|
3616
|
-
name:
|
|
3612
|
+
name: `${prefix}ALGOLIA_SEARCH_API_KEY`,
|
|
3617
3613
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3618
3614
|
}
|
|
3619
3615
|
];
|
|
@@ -3675,9 +3671,15 @@ function searchInstructions(input) {
|
|
|
3675
3671
|
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3676
3672
|
doc,
|
|
3677
3673
|
`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 "${input.targetIndex}" index.`,
|
|
3678
|
-
`Add Algolia App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3679
|
-
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3680
3674
|
"Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3675
|
+
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3676
|
+
// search-only key is best-effort and can fall back to a placeholder.
|
|
3677
|
+
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3678
|
+
// Names are fixed, not the agent's to rename: the wizard writes the
|
|
3679
|
+
// resolved app id / search-only key into ".env" under these exact names
|
|
3680
|
+
// right after this step, so a renamed prefix here would leave the code
|
|
3681
|
+
// reading a var the wizard never wrote.
|
|
3682
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3681
3683
|
'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.',
|
|
3682
3684
|
"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."
|
|
3683
3685
|
];
|
|
@@ -3830,43 +3832,17 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3830
3832
|
let searchKey;
|
|
3831
3833
|
if (useCases.includes("search")) {
|
|
3832
3834
|
appId = (await loadActiveProfile()).appId;
|
|
3835
|
+
try {
|
|
3836
|
+
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
3837
|
+
} catch (err) {
|
|
3838
|
+
logger.warn(
|
|
3839
|
+
{ err: err.message },
|
|
3840
|
+
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3833
3843
|
}
|
|
3834
3844
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3835
3845
|
try {
|
|
3836
|
-
const searchKeyNotices = [];
|
|
3837
|
-
let searchKeyError;
|
|
3838
|
-
if (useCases.includes("search") && appId) {
|
|
3839
|
-
const envAppId = await readEnvVar(worktree, appIdVar(language));
|
|
3840
|
-
const envKey = envAppId === appId ? await readEnvVar(worktree, searchKeyVar(language)) : void 0;
|
|
3841
|
-
if (envAppId && envAppId !== appId) {
|
|
3842
|
-
searchKeyNotices.push(
|
|
3843
|
-
`\u26A0\uFE0F .env already sets ${appIdVar(language)}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVar(language)} and ${searchKeyVar(language)} by hand, or searches will fail.`
|
|
3844
|
-
);
|
|
3845
|
-
logger.warn(
|
|
3846
|
-
{ envAppId, appId },
|
|
3847
|
-
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
3848
|
-
);
|
|
3849
|
-
}
|
|
3850
|
-
try {
|
|
3851
|
-
const resolved = await resolveSearchOnlyKey(targetIndex, appId, envKey);
|
|
3852
|
-
searchKey = resolved.key;
|
|
3853
|
-
if (resolved.source === "created") {
|
|
3854
|
-
searchKeyNotices.push(
|
|
3855
|
-
`Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code. Delete it in the Algolia dashboard when you no longer need it; the wizard can't.`
|
|
3856
|
-
);
|
|
3857
|
-
} else {
|
|
3858
|
-
searchKeyNotices.push(
|
|
3859
|
-
`Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
3860
|
-
);
|
|
3861
|
-
}
|
|
3862
|
-
} catch (err) {
|
|
3863
|
-
searchKeyError = err.message;
|
|
3864
|
-
logger.warn(
|
|
3865
|
-
{ err: searchKeyError },
|
|
3866
|
-
"implement: could not provision a search-only API key; the agent will scaffold a placeholder"
|
|
3867
|
-
);
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
3846
|
process.chdir(worktree);
|
|
3871
3847
|
let uploadFilePath;
|
|
3872
3848
|
let uploadWarning;
|
|
@@ -3906,7 +3882,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3906
3882
|
};
|
|
3907
3883
|
const summaries = [];
|
|
3908
3884
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3909
|
-
summaries.push(...searchKeyNotices);
|
|
3910
3885
|
let agentRuns = 0;
|
|
3911
3886
|
let ingestRuntime;
|
|
3912
3887
|
let ingestEntrypoint;
|
|
@@ -3925,7 +3900,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3925
3900
|
extraInstructions
|
|
3926
3901
|
),
|
|
3927
3902
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3928
|
-
outputSchema: implementationOutputSchema
|
|
3903
|
+
outputSchema: implementationOutputSchema,
|
|
3904
|
+
name: `implement:${currentUseCase}`
|
|
3929
3905
|
});
|
|
3930
3906
|
ctx.notify({
|
|
3931
3907
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
@@ -3950,7 +3926,8 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3950
3926
|
return runAgent({
|
|
3951
3927
|
instructions: buildAgentInstructions("verification", input),
|
|
3952
3928
|
tools: toolsForUseCase("verification"),
|
|
3953
|
-
outputSchema: verificationOutputSchema
|
|
3929
|
+
outputSchema: verificationOutputSchema,
|
|
3930
|
+
name: "implement:verification"
|
|
3954
3931
|
});
|
|
3955
3932
|
}
|
|
3956
3933
|
if (useCases.includes("ingestion")) {
|
|
@@ -4116,8 +4093,7 @@ ${run.output}` : status;
|
|
|
4116
4093
|
);
|
|
4117
4094
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4118
4095
|
summaries.push(
|
|
4119
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4120
|
-
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4096
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4121
4097
|
);
|
|
4122
4098
|
}
|
|
4123
4099
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@algolia/wizard",
|
|
3
|
-
"version": "0.5.0-rc.
|
|
3
|
+
"version": "0.5.0-rc.50.20",
|
|
4
4
|
"description": "Magically implement Algolia functionality in your codebase",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
50
50
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
51
|
-
"@algolia/cli": "^5.
|
|
51
|
+
"@algolia/cli": "^5.11.0",
|
|
52
52
|
"@hono/node-server": "^2.0.10",
|
|
53
53
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
54
54
|
"@segment/analytics-node": "^3.1.0",
|