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