@algolia/wizard 0.5.0-rc.45.15 → 0.5.0-rc.49.16
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 +191 -136
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as
|
|
7
|
+
import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize6 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -525,20 +525,10 @@ function NextAction({
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as Box4, Text as Text4,
|
|
529
|
-
import {
|
|
528
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
529
|
+
import { useState as useState3 } from "react";
|
|
530
530
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
531
531
|
var CANCEL = "cancel";
|
|
532
|
-
var ARROW_WIDTH = 4;
|
|
533
|
-
var COLUMN_GAP = 2;
|
|
534
|
-
var BAR_PADDING = 2;
|
|
535
|
-
function fittedWidth(node, columns) {
|
|
536
|
-
let left = 0;
|
|
537
|
-
for (let n = node; n; n = n.parentNode) {
|
|
538
|
-
left += n.yogaNode?.getComputedLeft() ?? 0;
|
|
539
|
-
}
|
|
540
|
-
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
541
|
-
}
|
|
542
532
|
function SelectPrompt({
|
|
543
533
|
options,
|
|
544
534
|
onSelect,
|
|
@@ -563,29 +553,12 @@ function SelectPrompt({
|
|
|
563
553
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
564
554
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
555
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
const [width, setWidth] = useState3(columns);
|
|
569
|
-
useLayoutEffect(() => {
|
|
570
|
-
if (containerRef.current) {
|
|
571
|
-
setWidth(fittedWidth(containerRef.current, columns));
|
|
572
|
-
}
|
|
573
|
-
}, [columns]);
|
|
574
|
-
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
|
-
const labelWidth = Math.min(
|
|
576
|
-
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
577
|
-
inner
|
|
578
|
-
);
|
|
579
|
-
const badgeWidth = Math.max(
|
|
556
|
+
const labelColWidth = 6 + Math.max(0, ...rows.map((opt) => opt.length));
|
|
557
|
+
const secondaryWidth = Math.max(
|
|
580
558
|
0,
|
|
581
|
-
...rows.map((_, i) =>
|
|
582
|
-
const s = secondary?.[i];
|
|
583
|
-
return s?.kind === "badge" ? s.value.length : 0;
|
|
584
|
-
})
|
|
559
|
+
...rows.map((_, i) => secondary?.[i]?.value.length ?? 0)
|
|
585
560
|
);
|
|
586
|
-
const
|
|
587
|
-
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
|
-
const textWidth = inner - labelWidth;
|
|
561
|
+
const rowWidth = labelColWidth + (secondaryWidth ? secondaryWidth + 2 : 0) + 6;
|
|
589
562
|
useInput((input, key) => {
|
|
590
563
|
if (rows.length === 0) return;
|
|
591
564
|
if (key.upArrow || input === "k") {
|
|
@@ -609,7 +582,7 @@ function SelectPrompt({
|
|
|
609
582
|
}
|
|
610
583
|
}
|
|
611
584
|
});
|
|
612
|
-
return /* @__PURE__ */
|
|
585
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, children: [
|
|
613
586
|
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
614
587
|
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
615
588
|
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
@@ -623,36 +596,29 @@ function SelectPrompt({
|
|
|
623
596
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
597
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
598
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
-
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor,
|
|
599
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, children: [
|
|
627
600
|
highlighted ? "\u276F " : " ",
|
|
628
601
|
bullet,
|
|
629
602
|
option
|
|
630
603
|
] });
|
|
631
|
-
const isText = sec?.kind === "text";
|
|
632
604
|
return /* @__PURE__ */ jsxs3(
|
|
633
605
|
Box4,
|
|
634
606
|
{
|
|
635
|
-
width:
|
|
607
|
+
width: rowWidth,
|
|
636
608
|
paddingX: 1,
|
|
637
609
|
paddingY: 1,
|
|
638
610
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
611
|
children: [
|
|
640
|
-
/* @__PURE__ */ jsx4(Box4, { width:
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
wrap: "truncate",
|
|
645
|
-
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
|
-
children: sec.value
|
|
647
|
-
}
|
|
648
|
-
) }),
|
|
649
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
612
|
+
sec?.kind === "text" ? /* @__PURE__ */ jsx4(Box4, { width: labelColWidth, flexShrink: 0, children: label }) : label,
|
|
613
|
+
sec?.kind === "text" && /* @__PURE__ */ jsx4(Text4, { color: highlighted ? COLORS.primary : COLORS.muted, children: sec.value }),
|
|
614
|
+
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1 }),
|
|
615
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, children: sec.value })
|
|
650
616
|
]
|
|
651
617
|
},
|
|
652
618
|
`row-${i}`
|
|
653
619
|
);
|
|
654
620
|
}) }),
|
|
655
|
-
/* @__PURE__ */ jsx4(
|
|
621
|
+
/* @__PURE__ */ jsx4(Box4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
656
622
|
i > 0 ? " " : "",
|
|
657
623
|
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
658
624
|
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
@@ -660,7 +626,7 @@ function SelectPrompt({
|
|
|
660
626
|
label
|
|
661
627
|
] })
|
|
662
628
|
] }, label)) })
|
|
663
|
-
] })
|
|
629
|
+
] });
|
|
664
630
|
}
|
|
665
631
|
|
|
666
632
|
// src/ui/PromptInput.tsx
|
|
@@ -783,7 +749,7 @@ function PromptInput() {
|
|
|
783
749
|
// src/ui/Welcome.tsx
|
|
784
750
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
785
751
|
import { fileURLToPath } from "node:url";
|
|
786
|
-
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as
|
|
752
|
+
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize3 } from "ink";
|
|
787
753
|
|
|
788
754
|
// src/ui/copy/welcome.ts
|
|
789
755
|
var sidebarItems = [
|
|
@@ -831,7 +797,7 @@ function SidebarItem({
|
|
|
831
797
|
function Welcome() {
|
|
832
798
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
833
799
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
834
|
-
const { rows } =
|
|
800
|
+
const { rows } = useWindowSize3();
|
|
835
801
|
useInput3((input) => {
|
|
836
802
|
if (input === " ") confirmStart();
|
|
837
803
|
else if (input === "i") openLearnMore();
|
|
@@ -899,7 +865,7 @@ function Welcome() {
|
|
|
899
865
|
|
|
900
866
|
// src/ui/LearnMore.tsx
|
|
901
867
|
import { Fragment as Fragment2 } from "react";
|
|
902
|
-
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as
|
|
868
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize4 } from "ink";
|
|
903
869
|
|
|
904
870
|
// src/ui/copy/learn-more.ts
|
|
905
871
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -963,7 +929,7 @@ function NeverLine({
|
|
|
963
929
|
function LearnMore() {
|
|
964
930
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
965
931
|
const backToHome = useWizard((s) => s.backToHome);
|
|
966
|
-
const { columns } =
|
|
932
|
+
const { columns } = useWindowSize4();
|
|
967
933
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
968
934
|
useInput4((input, key) => {
|
|
969
935
|
if (key.escape) backToHome();
|
|
@@ -1176,8 +1142,8 @@ function Ribbon() {
|
|
|
1176
1142
|
import { useState as useState6 } from "react";
|
|
1177
1143
|
|
|
1178
1144
|
// src/ui/Logs.tsx
|
|
1179
|
-
import { useLayoutEffect
|
|
1180
|
-
import { Box as Box12, Text as Text12, measureElement as
|
|
1145
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
|
|
1146
|
+
import { Box as Box12, Text as Text12, measureElement as measureElement2, useInput as useInput5, useWindowSize as useWindowSize5 } from "ink";
|
|
1181
1147
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1182
1148
|
var KIND_COLOR = {
|
|
1183
1149
|
tool: COLORS.primary,
|
|
@@ -1208,15 +1174,15 @@ function formatTimestamp(ms) {
|
|
|
1208
1174
|
}
|
|
1209
1175
|
function Logs() {
|
|
1210
1176
|
const logs = useWizard((s) => s.logs);
|
|
1211
|
-
const { rows, columns } =
|
|
1212
|
-
const viewportRef =
|
|
1177
|
+
const { rows, columns } = useWindowSize5();
|
|
1178
|
+
const viewportRef = useRef2(null);
|
|
1213
1179
|
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1214
1180
|
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1215
1181
|
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1216
|
-
const prevMaxOffsetRef =
|
|
1217
|
-
|
|
1182
|
+
const prevMaxOffsetRef = useRef2(0);
|
|
1183
|
+
useLayoutEffect(() => {
|
|
1218
1184
|
if (!viewportRef.current) return;
|
|
1219
|
-
const { width, height } =
|
|
1185
|
+
const { width, height } = measureElement2(viewportRef.current);
|
|
1220
1186
|
setViewportHeight(height);
|
|
1221
1187
|
setViewportWidth(width);
|
|
1222
1188
|
}, [rows, columns, logs.length === 0]);
|
|
@@ -1231,7 +1197,7 @@ function Logs() {
|
|
|
1231
1197
|
}
|
|
1232
1198
|
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1233
1199
|
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1234
|
-
|
|
1200
|
+
useLayoutEffect(() => {
|
|
1235
1201
|
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1236
1202
|
prevMaxOffsetRef.current = maxOffset;
|
|
1237
1203
|
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
@@ -1304,7 +1270,7 @@ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
|
1304
1270
|
function App() {
|
|
1305
1271
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1306
1272
|
const { exit } = useApp();
|
|
1307
|
-
const { columns, rows } =
|
|
1273
|
+
const { columns, rows } = useWindowSize6();
|
|
1308
1274
|
const [showLogs, setShowLogs] = useState6(false);
|
|
1309
1275
|
const finished = phase === "done" || phase === "error";
|
|
1310
1276
|
const currentStep = steps[currentStepIndex];
|
|
@@ -1358,28 +1324,15 @@ function App() {
|
|
|
1358
1324
|
width: "100%",
|
|
1359
1325
|
justifyContent: "space-between",
|
|
1360
1326
|
children: [
|
|
1361
|
-
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1362
|
-
/*
|
|
1363
|
-
/* @__PURE__ */
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
flexGrow: showSidebar ? 1 : 0,
|
|
1371
|
-
children: [
|
|
1372
|
-
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1373
|
-
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1374
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1375
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
1376
|
-
"\u2716 ",
|
|
1377
|
-
error
|
|
1378
|
-
] }) })
|
|
1379
|
-
]
|
|
1380
|
-
}
|
|
1381
|
-
)
|
|
1382
|
-
),
|
|
1327
|
+
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
|
|
1328
|
+
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1329
|
+
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1330
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1331
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
1332
|
+
"\u2716 ",
|
|
1333
|
+
error
|
|
1334
|
+
] }) })
|
|
1335
|
+
] }),
|
|
1383
1336
|
showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
|
|
1384
1337
|
]
|
|
1385
1338
|
}
|
|
@@ -1400,7 +1353,8 @@ var configFile = () => join5(stateDir(), "config.json");
|
|
|
1400
1353
|
var DEFAULT_CONFIG = {
|
|
1401
1354
|
version: 1,
|
|
1402
1355
|
aiConsent: false,
|
|
1403
|
-
workflowsRun: []
|
|
1356
|
+
workflowsRun: [],
|
|
1357
|
+
searchApiKeys: {}
|
|
1404
1358
|
};
|
|
1405
1359
|
async function loadConfig() {
|
|
1406
1360
|
try {
|
|
@@ -1419,6 +1373,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
|
|
|
1419
1373
|
config.workflowsRun.push({ workflowId, completedAt });
|
|
1420
1374
|
await saveConfig(config);
|
|
1421
1375
|
}
|
|
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
|
+
}
|
|
1422
1408
|
|
|
1423
1409
|
// src/lib/telemetry.ts
|
|
1424
1410
|
function isTelemetryEnabled() {
|
|
@@ -2695,7 +2681,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2695
2681
|
// package.json
|
|
2696
2682
|
var package_default = {
|
|
2697
2683
|
name: "@algolia/wizard",
|
|
2698
|
-
version: "0.5.0-rc.
|
|
2684
|
+
version: "0.5.0-rc.49.16",
|
|
2699
2685
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2700
2686
|
type: "module",
|
|
2701
2687
|
engines: {
|
|
@@ -2743,7 +2729,7 @@ var package_default = {
|
|
|
2743
2729
|
dependencies: {
|
|
2744
2730
|
"@ai-sdk/anthropic": "^3.0.81",
|
|
2745
2731
|
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
2746
|
-
"@algolia/cli": "^5.
|
|
2732
|
+
"@algolia/cli": "^5.15.0",
|
|
2747
2733
|
"@hono/node-server": "^2.0.10",
|
|
2748
2734
|
"@mishieck/ink-titled-box": "^0.4.2",
|
|
2749
2735
|
"@segment/analytics-node": "^3.1.0",
|
|
@@ -3301,6 +3287,23 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3301
3287
|
function hasEnvVar(content, name) {
|
|
3302
3288
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3303
3289
|
}
|
|
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
|
+
}
|
|
3304
3307
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3305
3308
|
const target = join10(worktreePath, ".env");
|
|
3306
3309
|
let existing = "";
|
|
@@ -3375,50 +3378,72 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
|
3375
3378
|
|
|
3376
3379
|
// src/lib/algoliaApiKey.ts
|
|
3377
3380
|
import { z as z23 } from "zod";
|
|
3378
|
-
var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
|
|
3379
|
-
var apiKeySchema = z23.object({
|
|
3380
|
-
value: z23.string().min(1),
|
|
3381
|
-
acl: z23.array(z23.string()).default([]),
|
|
3382
|
-
indexes: z23.array(z23.string()).default([])
|
|
3383
|
-
});
|
|
3384
|
-
var apiKeyListSchema = z23.object({
|
|
3385
|
-
items: z23.array(apiKeySchema).optional(),
|
|
3386
|
-
keys: z23.array(apiKeySchema).optional()
|
|
3387
|
-
}).transform((o) => o.items ?? o.keys ?? []);
|
|
3388
3381
|
var createdKeySchema = z23.object({
|
|
3389
3382
|
key: z23.string().min(1).optional(),
|
|
3390
3383
|
value: z23.string().min(1).optional()
|
|
3391
|
-
});
|
|
3392
|
-
function
|
|
3393
|
-
|
|
3394
|
-
}
|
|
3395
|
-
async function createSearchKey(index) {
|
|
3384
|
+
}).transform((o) => o.key ?? o.value);
|
|
3385
|
+
async function createSearchOnlyKey(index) {
|
|
3386
|
+
logger.info({ index }, "creating a search-only API key");
|
|
3396
3387
|
const stdout = await runAlgoliaCli([
|
|
3397
3388
|
"apikeys",
|
|
3398
3389
|
"create",
|
|
3390
|
+
"--acl",
|
|
3391
|
+
"search",
|
|
3399
3392
|
"--indices",
|
|
3400
3393
|
index,
|
|
3401
|
-
"--acl",
|
|
3402
|
-
"search,browse",
|
|
3403
3394
|
"--description",
|
|
3404
|
-
`
|
|
3395
|
+
`Algolia Wizard search-only key for ${index}`,
|
|
3405
3396
|
"-o",
|
|
3406
3397
|
"json"
|
|
3407
3398
|
]);
|
|
3408
|
-
|
|
3409
|
-
|
|
3399
|
+
let payload;
|
|
3400
|
+
try {
|
|
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);
|
|
3410
3406
|
if (!created) throw new Error("apikeys create returned no key value");
|
|
3411
3407
|
return created;
|
|
3412
3408
|
}
|
|
3413
|
-
async function
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
return
|
|
3409
|
+
async function apiKeyExists(key) {
|
|
3410
|
+
try {
|
|
3411
|
+
await runAlgoliaCli(["apikeys", "get", key, "-o", "json"]);
|
|
3412
|
+
return true;
|
|
3413
|
+
} catch (err) {
|
|
3414
|
+
return !/does not exist|not found|404/i.test(err.message);
|
|
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
|
+
);
|
|
3419
3446
|
}
|
|
3420
|
-
logger.info({ index }, "no reusable search-only key found; creating one");
|
|
3421
|
-
return createSearchKey(index);
|
|
3422
3447
|
}
|
|
3423
3448
|
|
|
3424
3449
|
// src/lib/algoliaDocs.ts
|
|
@@ -3573,15 +3598,22 @@ function publicEnvPrefix(language) {
|
|
|
3573
3598
|
}
|
|
3574
3599
|
return "PUBLIC_";
|
|
3575
3600
|
}
|
|
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
|
+
}
|
|
3576
3609
|
function searchEnvVars(language, appId, searchKey) {
|
|
3577
|
-
const prefix = publicEnvPrefix(language);
|
|
3578
3610
|
return [
|
|
3579
3611
|
{
|
|
3580
|
-
name:
|
|
3612
|
+
name: appIdVar(language),
|
|
3581
3613
|
value: appId ?? "<your-algolia-app-id>"
|
|
3582
3614
|
},
|
|
3583
3615
|
{
|
|
3584
|
-
name:
|
|
3616
|
+
name: searchKeyVar(language),
|
|
3585
3617
|
value: searchKey ?? "<your-algolia-search-only-api-key>"
|
|
3586
3618
|
}
|
|
3587
3619
|
];
|
|
@@ -3643,15 +3675,9 @@ function searchInstructions(input) {
|
|
|
3643
3675
|
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3644
3676
|
doc,
|
|
3645
3677
|
`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.`,
|
|
3646
|
-
|
|
3647
|
-
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3648
|
-
// search-only key is best-effort and can fall back to a placeholder.
|
|
3649
|
-
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3650
|
-
// Names are fixed, not the agent's to rename: the wizard writes the
|
|
3651
|
-
// resolved app id / search-only key into ".env" under these exact names
|
|
3652
|
-
// right after this step, so a renamed prefix here would leave the code
|
|
3653
|
-
// reading a var the wizard never wrote.
|
|
3678
|
+
`Add Algolia App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
3654
3679
|
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3680
|
+
"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.",
|
|
3655
3681
|
'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.',
|
|
3656
3682
|
"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."
|
|
3657
3683
|
];
|
|
@@ -3804,17 +3830,43 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3804
3830
|
let searchKey;
|
|
3805
3831
|
if (useCases.includes("search")) {
|
|
3806
3832
|
appId = (await loadActiveProfile()).appId;
|
|
3807
|
-
try {
|
|
3808
|
-
searchKey = await resolveSearchOnlyKey(targetIndex);
|
|
3809
|
-
} catch (err) {
|
|
3810
|
-
logger.warn(
|
|
3811
|
-
{ err: err.message },
|
|
3812
|
-
"implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
|
|
3813
|
-
);
|
|
3814
|
-
}
|
|
3815
3833
|
}
|
|
3816
3834
|
const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
|
|
3817
3835
|
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
|
+
}
|
|
3818
3870
|
process.chdir(worktree);
|
|
3819
3871
|
let uploadFilePath;
|
|
3820
3872
|
let uploadWarning;
|
|
@@ -3854,6 +3906,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3854
3906
|
};
|
|
3855
3907
|
const summaries = [];
|
|
3856
3908
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3909
|
+
summaries.push(...searchKeyNotices);
|
|
3857
3910
|
let agentRuns = 0;
|
|
3858
3911
|
let ingestRuntime;
|
|
3859
3912
|
let ingestEntrypoint;
|
|
@@ -4063,7 +4116,9 @@ ${run.output}` : status;
|
|
|
4063
4116
|
);
|
|
4064
4117
|
if (unresolvedSearchEnvVars.length > 0) {
|
|
4065
4118
|
summaries.push(
|
|
4066
|
-
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.`
|
|
4119
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + // The reason is the actionable half ("key has no manage rights" tells
|
|
4120
|
+
// the user what to fix); without it the line is a dead end.
|
|
4121
|
+
(searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
4067
4122
|
);
|
|
4068
4123
|
}
|
|
4069
4124
|
} 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.49.16",
|
|
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.15.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",
|