@algolia/wizard 0.5.0-rc.45.14 → 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.
Files changed (2) hide show
  1. package/dist/main.js +182 -133
  2. 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 useWindowSize7 } from "ink";
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, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
529
- import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
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 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(
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 barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
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__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
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, wrap: "truncate", children: [
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: isText ? "100%" : barWidth,
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: 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 }) })
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(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
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 useWindowSize4 } from "ink";
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 } = useWindowSize4();
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 useWindowSize5 } from "ink";
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 } = useWindowSize5();
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 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";
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 } = useWindowSize6();
1212
- const viewportRef = useRef3(null);
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 = useRef3(0);
1217
- useLayoutEffect2(() => {
1182
+ const prevMaxOffsetRef = useRef2(0);
1183
+ useLayoutEffect(() => {
1218
1184
  if (!viewportRef.current) return;
1219
- const { width, height } = measureElement3(viewportRef.current);
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
- useLayoutEffect2(() => {
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 } = useWindowSize7();
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,25 +1324,6 @@ function App() {
1358
1324
  width: "100%",
1359
1325
  justifyContent: "space-between",
1360
1326
  children: [
1361
- /* @__PURE__ */ jsxs12(
1362
- Box13,
1363
- {
1364
- flexDirection: "column",
1365
- paddingX: 4,
1366
- paddingY: 2,
1367
- width: showSidebar ? 70 : "100%",
1368
- flexGrow: showSidebar ? 1 : 0,
1369
- children: [
1370
- /* @__PURE__ */ jsx13(Notices, {}),
1371
- /* @__PURE__ */ jsx13(PromptInput, {}),
1372
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1373
- phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1374
- "\u2716 ",
1375
- error
1376
- ] }) })
1377
- ]
1378
- }
1379
- ),
1380
1327
  showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
1381
1328
  /* @__PURE__ */ jsx13(Notices, {}),
1382
1329
  /* @__PURE__ */ jsx13(PromptInput, {}),
@@ -1406,7 +1353,8 @@ var configFile = () => join5(stateDir(), "config.json");
1406
1353
  var DEFAULT_CONFIG = {
1407
1354
  version: 1,
1408
1355
  aiConsent: false,
1409
- workflowsRun: []
1356
+ workflowsRun: [],
1357
+ searchApiKeys: {}
1410
1358
  };
1411
1359
  async function loadConfig() {
1412
1360
  try {
@@ -1425,6 +1373,38 @@ async function recordWorkflowRun(workflowId, completedAt) {
1425
1373
  config.workflowsRun.push({ workflowId, completedAt });
1426
1374
  await saveConfig(config);
1427
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
+ }
1428
1408
 
1429
1409
  // src/lib/telemetry.ts
1430
1410
  function isTelemetryEnabled() {
@@ -2701,7 +2681,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2701
2681
  // package.json
2702
2682
  var package_default = {
2703
2683
  name: "@algolia/wizard",
2704
- version: "0.5.0-rc.45.14",
2684
+ version: "0.5.0-rc.49.16",
2705
2685
  description: "Magically implement Algolia functionality in your codebase",
2706
2686
  type: "module",
2707
2687
  engines: {
@@ -2749,7 +2729,7 @@ var package_default = {
2749
2729
  dependencies: {
2750
2730
  "@ai-sdk/anthropic": "^3.0.81",
2751
2731
  "@ai-sdk/openai-compatible": "^2.0.47",
2752
- "@algolia/cli": "^5.11.0",
2732
+ "@algolia/cli": "^5.15.0",
2753
2733
  "@hono/node-server": "^2.0.10",
2754
2734
  "@mishieck/ink-titled-box": "^0.4.2",
2755
2735
  "@segment/analytics-node": "^3.1.0",
@@ -3307,6 +3287,23 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
3307
3287
  function hasEnvVar(content, name) {
3308
3288
  return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
3309
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
+ }
3310
3307
  async function writeSearchEnvValues(worktreePath, vars) {
3311
3308
  const target = join10(worktreePath, ".env");
3312
3309
  let existing = "";
@@ -3381,50 +3378,72 @@ async function confirmDirtyWorkingTree(ctx, repoRoot) {
3381
3378
 
3382
3379
  // src/lib/algoliaApiKey.ts
3383
3380
  import { z as z23 } from "zod";
3384
- var SAFE_ACLS = /* @__PURE__ */ new Set(["search", "browse", "listIndexes"]);
3385
- var apiKeySchema = z23.object({
3386
- value: z23.string().min(1),
3387
- acl: z23.array(z23.string()).default([]),
3388
- indexes: z23.array(z23.string()).default([])
3389
- });
3390
- var apiKeyListSchema = z23.object({
3391
- items: z23.array(apiKeySchema).optional(),
3392
- keys: z23.array(apiKeySchema).optional()
3393
- }).transform((o) => o.items ?? o.keys ?? []);
3394
3381
  var createdKeySchema = z23.object({
3395
3382
  key: z23.string().min(1).optional(),
3396
3383
  value: z23.string().min(1).optional()
3397
- });
3398
- function canReuse(key, index) {
3399
- return key.acl.includes("search") && key.acl.every((acl) => SAFE_ACLS.has(acl)) && (key.indexes.length === 0 || key.indexes.includes("*") || key.indexes.includes(index));
3400
- }
3401
- 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");
3402
3387
  const stdout = await runAlgoliaCli([
3403
3388
  "apikeys",
3404
3389
  "create",
3390
+ "--acl",
3391
+ "search",
3405
3392
  "--indices",
3406
3393
  index,
3407
- "--acl",
3408
- "search,browse",
3409
3394
  "--description",
3410
- `wizard search-only key for ${index}`,
3395
+ `Algolia Wizard search-only key for ${index}`,
3411
3396
  "-o",
3412
3397
  "json"
3413
3398
  ]);
3414
- const { key, value } = createdKeySchema.parse(JSON.parse(stdout));
3415
- const created = key ?? value;
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);
3416
3406
  if (!created) throw new Error("apikeys create returned no key value");
3417
3407
  return created;
3418
3408
  }
3419
- async function resolveSearchOnlyKey(index) {
3420
- const stdout = await runAlgoliaCli(["apikeys", "list", "-o", "json"]);
3421
- const existing = apiKeyListSchema.parse(JSON.parse(stdout)).find((key) => canReuse(key, index))?.value;
3422
- if (existing) {
3423
- logger.info({ index }, "reusing existing search-only API key");
3424
- return existing;
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
+ );
3425
3446
  }
3426
- logger.info({ index }, "no reusable search-only key found; creating one");
3427
- return createSearchKey(index);
3428
3447
  }
3429
3448
 
3430
3449
  // src/lib/algoliaDocs.ts
@@ -3579,15 +3598,22 @@ function publicEnvPrefix(language) {
3579
3598
  }
3580
3599
  return "PUBLIC_";
3581
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
+ }
3582
3609
  function searchEnvVars(language, appId, searchKey) {
3583
- const prefix = publicEnvPrefix(language);
3584
3610
  return [
3585
3611
  {
3586
- name: `${prefix}ALGOLIA_APP_ID`,
3612
+ name: appIdVar(language),
3587
3613
  value: appId ?? "<your-algolia-app-id>"
3588
3614
  },
3589
3615
  {
3590
- name: `${prefix}ALGOLIA_SEARCH_API_KEY`,
3616
+ name: searchKeyVar(language),
3591
3617
  value: searchKey ?? "<your-algolia-search-only-api-key>"
3592
3618
  }
3593
3619
  ];
@@ -3649,15 +3675,9 @@ function searchInstructions(input) {
3649
3675
  "Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
3650
3676
  doc,
3651
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.`,
3652
- "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.",
3653
- // appId always resolves (loadActiveProfile throws otherwise); only the
3654
- // search-only key is best-effort and can fall back to a placeholder.
3655
- `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3656
- // Names are fixed, not the agent's to rename: the wizard writes the
3657
- // resolved app id / search-only key into ".env" under these exact names
3658
- // right after this step, so a renamed prefix here would leave the code
3659
- // 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)"}.`,
3660
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.",
3661
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.',
3662
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."
3663
3683
  ];
@@ -3810,17 +3830,43 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3810
3830
  let searchKey;
3811
3831
  if (useCases.includes("search")) {
3812
3832
  appId = (await loadActiveProfile()).appId;
3813
- try {
3814
- searchKey = await resolveSearchOnlyKey(targetIndex);
3815
- } catch (err) {
3816
- logger.warn(
3817
- { err: err.message },
3818
- "implement: could not resolve a search-only API key; the agent will scaffold a placeholder"
3819
- );
3820
- }
3821
3833
  }
3822
3834
  const worktree = existingWorktreePath ?? (await createWorktree(repoRoot)).path;
3823
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
+ }
3824
3870
  process.chdir(worktree);
3825
3871
  let uploadFilePath;
3826
3872
  let uploadWarning;
@@ -3860,6 +3906,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3860
3906
  };
3861
3907
  const summaries = [];
3862
3908
  if (uploadWarning) summaries.push(uploadWarning);
3909
+ summaries.push(...searchKeyNotices);
3863
3910
  let agentRuns = 0;
3864
3911
  let ingestRuntime;
3865
3912
  let ingestEntrypoint;
@@ -4069,7 +4116,9 @@ ${run.output}` : status;
4069
4116
  );
4070
4117
  if (unresolvedSearchEnvVars.length > 0) {
4071
4118
  summaries.push(
4072
- `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}` : "")
4073
4122
  );
4074
4123
  }
4075
4124
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.5.0-rc.45.14",
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.11.0",
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",