@algolia/wizard 0.37.0 → 0.38.0-rc.131.265

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 +675 -576
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -258,10 +258,14 @@ var useWizard = create((set, get) => ({
258
258
  error: null,
259
259
  inputReq: null,
260
260
  _resolve: null,
261
+ settingUpAppId: null,
262
+ setSettingUpApp: (appId) => set({ settingUpAppId: appId }),
261
263
  // `endAuth` lands on 'preflight', not 'idle': sign-in happens after the
262
264
  // welcome screen, so going back would gate the run a second time.
263
265
  beginAuth: () => set({ phase: "authenticating", cliOutput: [] }),
264
- endAuth: () => set((s) => s.phase === "authenticating" ? { phase: "preflight" } : {}),
266
+ endAuth: () => set(
267
+ (s) => s.phase === "authenticating" ? { phase: "preflight", cliOutput: [] } : {}
268
+ ),
265
269
  confirmStart: () => set(
266
270
  (s) => s.phase === "idle" ? { phase: "preflight", homeScreen: "home" } : {}
267
271
  ),
@@ -405,7 +409,8 @@ var useWizard = create((set, get) => ({
405
409
  logs: [],
406
410
  error: null,
407
411
  inputReq: null,
408
- _resolve: null
412
+ _resolve: null,
413
+ settingUpAppId: null
409
414
  });
410
415
  }
411
416
  }));
@@ -653,25 +658,27 @@ function SelectRow({
653
658
  labelWidth,
654
659
  highlightBackground = true,
655
660
  usePadding = false,
661
+ disabled = false,
656
662
  children
657
663
  }) {
658
- const labelColor = highlighted ? highlightBackground ? COLORS.highlight.fg : COLORS.success : COLORS.primary;
664
+ const active = highlighted && !disabled;
665
+ const labelColor = disabled ? COLORS.dim : active ? highlightBackground ? COLORS.highlight.fg : COLORS.success : COLORS.primary;
659
666
  return /* @__PURE__ */ jsxs3(
660
667
  Box4,
661
668
  {
662
669
  width,
663
670
  paddingX: usePadding ? 1 : 0,
664
671
  paddingY: usePadding ? 1 : 0,
665
- backgroundColor: highlighted && highlightBackground ? COLORS.highlight.bg : void 0,
672
+ backgroundColor: active && highlightBackground ? COLORS.highlight.bg : void 0,
666
673
  children: [
667
674
  /* @__PURE__ */ jsx3(Box4, { width: labelWidth, children: /* @__PURE__ */ jsxs3(
668
675
  Text4,
669
676
  {
670
677
  color: labelColor,
671
- bold: highlighted && !highlightBackground,
678
+ bold: active && !highlightBackground,
672
679
  wrap: "truncate",
673
680
  children: [
674
- highlighted ? "\u276F " : " ",
681
+ active ? "\u276F " : " ",
675
682
  label
676
683
  ]
677
684
  }
@@ -870,6 +877,14 @@ var ARROW_WIDTH = 4;
870
877
  var COLUMN_GAP = 2;
871
878
  var BAR_PADDING = 2;
872
879
  var ROW_HEIGHT = 3;
880
+ function nextSelectableIndex(start, dir, rowCount, isDisabled) {
881
+ let i = start;
882
+ for (let step = 0; step < rowCount; step++) {
883
+ i = (i + dir + rowCount) % rowCount;
884
+ if (!isDisabled(i)) return i;
885
+ }
886
+ return start;
887
+ }
873
888
  function SelectPrompt({
874
889
  options,
875
890
  onSelect,
@@ -881,15 +896,18 @@ function SelectPrompt({
881
896
  multi,
882
897
  cancelable,
883
898
  secondary,
899
+ disabled,
884
900
  defaultSelectedIndex = 0
885
901
  }) {
886
- const [index, setIndex] = useState5(
887
- () => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
888
- );
889
- const [checked, setChecked] = useState5(() => /* @__PURE__ */ new Set());
890
902
  const hasCancel = Boolean(multi || cancelable);
891
903
  const rows = hasCancel ? [...options, "Cancel"] : options;
892
904
  const cancelIndex = hasCancel ? options.length : -1;
905
+ const isDisabled = (i) => i !== cancelIndex && Boolean(disabled?.[i]);
906
+ const [index, setIndex] = useState5(() => {
907
+ const start = defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0;
908
+ return isDisabled(start) ? nextSelectableIndex(start, 1, rows.length, isDisabled) : start;
909
+ });
910
+ const [checked, setChecked] = useState5(() => /* @__PURE__ */ new Set());
893
911
  const hints = [];
894
912
  if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
895
913
  if (multi) hints.push({ key: "[space]", label: "select" });
@@ -929,9 +947,15 @@ function SelectPrompt({
929
947
  useInput2((input, key) => {
930
948
  if (rows.length === 0) return;
931
949
  if (key.upArrow || input === "k") {
932
- setIndex((i) => (i - 1 + rows.length) % rows.length);
950
+ setIndex((i) => {
951
+ const next = (i - 1 + rows.length) % rows.length;
952
+ return isDisabled(next) ? i : next;
953
+ });
933
954
  } else if (key.downArrow || input === "j") {
934
- setIndex((i) => (i + 1) % rows.length);
955
+ setIndex((i) => {
956
+ const next = (i + 1) % rows.length;
957
+ return isDisabled(next) ? i : next;
958
+ });
935
959
  } else if (multi && input === " " && index !== cancelIndex) {
936
960
  setChecked((prev) => {
937
961
  const next = new Set(prev);
@@ -942,6 +966,7 @@ function SelectPrompt({
942
966
  } else if (key.return) {
943
967
  if (index === cancelIndex) {
944
968
  onSelect(CANCEL);
969
+ } else if (isDisabled(index)) {
945
970
  } else if (!multi) {
946
971
  onSelect(options[index]);
947
972
  } else if (checked.size > 0) {
@@ -966,10 +991,12 @@ function SelectPrompt({
966
991
  const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
967
992
  const sec = isCancel ? void 0 : secondary?.[i];
968
993
  const isText = sec?.kind === "text";
994
+ const rowDisabled = isDisabled(i);
969
995
  return /* @__PURE__ */ jsxs7(
970
996
  SelectRow,
971
997
  {
972
998
  highlighted,
999
+ disabled: rowDisabled,
973
1000
  width: isText ? "100%" : barWidth,
974
1001
  labelWidth: isText ? labelWidth : barLabelWidth,
975
1002
  label: `${bullet}${option}`,
@@ -979,7 +1006,7 @@ function SelectPrompt({
979
1006
  Text8,
980
1007
  {
981
1008
  wrap: "truncate",
982
- color: highlighted ? COLORS.primary : COLORS.muted,
1009
+ color: rowDisabled ? COLORS.dim : highlighted ? COLORS.primary : COLORS.muted,
983
1010
  children: sec.value
984
1011
  }
985
1012
  ) }),
@@ -1041,6 +1068,7 @@ function PromptInput() {
1041
1068
  table: inputReq.table,
1042
1069
  options: inputReq.options,
1043
1070
  secondary: inputReq.secondary,
1071
+ disabled: inputReq.disabled,
1044
1072
  defaultSelectedIndex: inputReq.defaultSelectedIndex,
1045
1073
  cancelable: inputReq.cancelable,
1046
1074
  error: inputReq.error,
@@ -1112,7 +1140,7 @@ function PromptInput() {
1112
1140
  inputReq.prompt,
1113
1141
  " "
1114
1142
  ] }),
1115
- /* @__PURE__ */ jsx7(
1143
+ /* @__PURE__ */ jsx7(Text9, { color: COLORS.primary, children: /* @__PURE__ */ jsx7(
1116
1144
  TextInput,
1117
1145
  {
1118
1146
  value: draft,
@@ -1122,7 +1150,7 @@ function PromptInput() {
1122
1150
  setDraft("");
1123
1151
  }
1124
1152
  }
1125
- )
1153
+ ) })
1126
1154
  ] })
1127
1155
  ] });
1128
1156
  }
@@ -1404,6 +1432,10 @@ function reconcileWorkflowState(state, workflow) {
1404
1432
  }
1405
1433
  return state;
1406
1434
  }
1435
+ async function isResumableWorkflow(workflow) {
1436
+ const persisted = await loadWorkflowState(workflow.id);
1437
+ return persisted != null && reconcileWorkflowState(persisted, workflow) != null;
1438
+ }
1407
1439
  function initWorkflowState(workflow, now) {
1408
1440
  return {
1409
1441
  workflowId: workflow.id,
@@ -1600,273 +1632,31 @@ async function listIndices() {
1600
1632
  return items.map((i) => ({ name: i.name, entries: i.entries })).sort((a, b) => a.name.localeCompare(b.name));
1601
1633
  }
1602
1634
 
1603
- // src/actions/selectIndex.ts
1604
- var CREATE_NEW_INDEX = "Create a new index\u2026";
1605
- var selectIndexStep = async (ctx) => {
1606
- const indices = await listIndices();
1607
- const names = indices.map((i) => i.name);
1608
- const hasIndices = names.length > 0;
1609
- let error;
1610
- for (; ; ) {
1611
- const selection = hasIndices ? await ctx.requestUserInput({
1612
- prompt: "Which index do you want to ingest into?",
1613
- promptType: "multipleChoice",
1614
- options: [...names, CREATE_NEW_INDEX],
1615
- messages: ["We found these indices:"],
1616
- error
1617
- }) : await ctx.requestUserInput({
1618
- prompt: "Name the index to create and ingest into:",
1619
- promptType: "textInput",
1620
- options: [],
1621
- messages: [
1622
- "This app has no existing indices \u2014 enter a name to create one."
1623
- ],
1624
- error
1625
- });
1626
- if (typeof selection !== "string") {
1627
- throw new Error("selectIndex received an unexpected non-text result");
1628
- }
1629
- let chosen;
1630
- if (!hasIndices) {
1631
- chosen = selection.trim();
1632
- } else if (selection === CREATE_NEW_INDEX) {
1633
- const name = await ctx.requestUserInput({
1634
- prompt: "Name the new index:",
1635
- promptType: "textInput",
1636
- options: []
1637
- });
1638
- if (typeof name !== "string") {
1639
- throw new Error("selectIndex received an unexpected non-text result");
1640
- }
1641
- chosen = name.trim();
1642
- } else {
1643
- chosen = selection;
1644
- }
1645
- if (!chosen) {
1646
- error = "Index name cannot be empty.";
1647
- continue;
1648
- }
1649
- ctx.setUserInput("index", chosen);
1650
- return { selection: chosen };
1651
- }
1652
- };
1653
-
1654
- // src/lib/agent.ts
1655
- import { ToolLoopAgent, hasToolCall, Output as Output3 } from "ai";
1656
- import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
1657
- import "zod";
1658
-
1659
- // src/lib/tools/index.ts
1660
- import "zod";
1661
-
1662
- // src/lib/tools/listFiles.ts
1663
- import { tool } from "ai";
1664
- import z5 from "zod";
1665
- import { readdir } from "node:fs/promises";
1666
-
1667
- // src/lib/tools/path.ts
1668
- import { lstat } from "node:fs/promises";
1669
- import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join5, sep } from "node:path";
1670
- function resolveInRoot(ctx, path) {
1671
- const target = resolve2(ctx.cwd, path);
1672
- const rel = relative(ctx.root, target);
1673
- if (rel.startsWith("..") || isAbsolute(rel)) {
1674
- return {
1675
- ok: false,
1676
- error: `Refused: ${target} is outside the repo root (${ctx.root}).`
1677
- };
1678
- }
1679
- return { ok: true, target };
1680
- }
1681
- async function hasSymlinkParent(ctx, target) {
1682
- let current = ctx.root;
1683
- const parts = relative(ctx.root, dirname2(target)).split(sep).filter(Boolean);
1684
- for (const part of parts) {
1685
- current = join5(current, part);
1686
- try {
1687
- if ((await lstat(current)).isSymbolicLink()) return true;
1688
- } catch (err) {
1689
- if (err.code === "ENOENT") return false;
1690
- throw err;
1691
- }
1692
- }
1693
- return false;
1694
- }
1695
-
1696
- // src/lib/tools/listFiles.ts
1697
- function listFilesTool(ctx) {
1698
- return tool({
1699
- description: 'List files in a directory (default: the current working directory). Pass path to list a subdirectory directly \u2014 e.g. "packages/api" \u2014 without first changeDirectory-ing into it.',
1700
- inputSchema: z5.object({
1701
- path: z5.string().optional().describe("Directory to list, relative to cwd (default: cwd)")
1702
- }),
1703
- execute: async ({ path = "." }) => {
1704
- logger.info({ path }, "called listFiles tool");
1705
- if (++ctx.counts.list > ctx.limits.list) {
1706
- return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
1707
- }
1708
- const resolved2 = resolveInRoot(ctx, path);
1709
- if (!resolved2.ok) return resolved2.error;
1710
- try {
1711
- const entries = await readdir(resolved2.target, { withFileTypes: true });
1712
- return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
1713
- } catch (err) {
1714
- return `Error listing ${path}: ${err.message}`;
1715
- }
1716
- }
1717
- });
1718
- }
1719
-
1720
- // src/lib/tools/changeDirectory.ts
1721
- import { tool as tool2 } from "ai";
1722
- import z6 from "zod";
1723
- import { stat } from "node:fs/promises";
1724
- function changeDirectoryTool(ctx) {
1725
- return tool2({
1726
- description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
1727
- inputSchema: z6.object({
1728
- path: z6.string().describe("Directory to change into")
1729
- }),
1730
- execute: async ({ path }) => {
1731
- logger.info({ path }, "called changeDirectory tool");
1732
- const resolved2 = resolveInRoot(ctx, path);
1733
- if (!resolved2.ok) return resolved2.error;
1734
- try {
1735
- const info = await stat(resolved2.target);
1736
- if (!info.isDirectory()) {
1737
- return `Error changing directory to ${path}: not a directory`;
1738
- }
1739
- ctx.cwd = resolved2.target;
1740
- return `Changed working directory to ${ctx.cwd}`;
1741
- } catch (err) {
1742
- return `Error changing directory to ${path}: ${err.message}`;
1743
- }
1744
- }
1745
- });
1746
- }
1747
-
1748
- // src/lib/tools/reportStatus.ts
1749
- import { tool as tool3 } from "ai";
1750
- import z7 from "zod";
1751
- function reportStatusTool(output) {
1752
- return tool3({
1753
- description: "Report the status of your execution. Return a reason in case of failure.",
1754
- inputSchema: z7.object({
1755
- status: z7.enum(["success", "fail"]),
1756
- reason: z7.string().optional(),
1757
- output
1758
- }),
1759
- execute: async ({ status, reason, output: output2 }) => {
1760
- logger.info({ status, reason }, "called reportStatus tool");
1761
- return { status, reason, output: output2 };
1762
- }
1763
- });
1764
- }
1765
-
1766
- // src/lib/tools/readFile.ts
1767
- import { tool as tool4 } from "ai";
1768
- import z8 from "zod";
1769
- import { readFile as readFile3 } from "node:fs/promises";
1770
-
1771
- // src/lib/tools/env.ts
1772
- import { basename } from "node:path";
1773
- function isEnvFile(filePath) {
1774
- const name = basename(filePath);
1775
- return name === ".env" || name.startsWith(".env.");
1776
- }
1777
- function isSecretEnvFile(filePath) {
1778
- if (!isEnvFile(filePath)) return false;
1779
- const name = basename(filePath);
1780
- return !/\.(example|sample|template)$/.test(name);
1781
- }
1782
-
1783
- // src/lib/tools/readFile.ts
1784
- function redactEnvValues(content) {
1785
- return content.split("\n").map((line) => {
1786
- const match = line.match(/^(\s*(?:export\s+)?[\w.-]+\s*=)(.*)$/);
1787
- if (!match) return line;
1788
- const value = match[2].trim();
1789
- if (value === "") return line;
1790
- return `${match[1]}[REDACTED]`;
1791
- }).join("\n");
1792
- }
1793
- function readFileTool(ctx) {
1794
- return tool4({
1795
- description: "Read the contents of a file at the given path",
1796
- inputSchema: z8.object({
1797
- filePath: z8.string().describe("Path to the file to read")
1798
- }),
1799
- execute: async ({ filePath }) => {
1800
- if (++ctx.counts.read > ctx.limits.read) {
1801
- return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
1802
- }
1803
- logger.info({ filePath }, "called readFile tool");
1804
- const resolved2 = resolveInRoot(ctx, filePath);
1805
- if (!resolved2.ok) return resolved2.error;
1806
- try {
1807
- const content = await readFile3(resolved2.target, "utf8");
1808
- return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
1809
- } catch (err) {
1810
- return `Error reading ${filePath}: ${err.message}`;
1811
- }
1812
- }
1813
- });
1814
- }
1815
-
1816
- // src/lib/tools/writeFile.ts
1817
- import { tool as tool5 } from "ai";
1818
- import z9 from "zod";
1819
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1820
- import { dirname as dirname3 } from "node:path";
1821
- function writeFileTool(ctx) {
1822
- return tool5({
1823
- description: "Write content to a file at the given path, overwriting it. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
1824
- inputSchema: z9.object({
1825
- filePath: z9.string().describe("Path to the file to write"),
1826
- content: z9.string().describe("Content to write to the file")
1827
- }),
1828
- execute: async ({ filePath, content }) => {
1829
- logger.info({ filePath }, "called writeFile tool");
1830
- const resolved2 = resolveInRoot(ctx, filePath);
1831
- if (resolved2.ok === false) return resolved2.error;
1832
- if (isSecretEnvFile(resolved2.target)) {
1833
- return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
1834
- }
1835
- try {
1836
- if (await hasSymlinkParent(ctx, resolved2.target)) {
1837
- return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
1838
- }
1839
- await mkdir3(dirname3(resolved2.target), { recursive: true });
1840
- await writeFile3(resolved2.target, content, "utf8");
1841
- useWizard.getState().recordWrittenFile(resolved2.target);
1842
- return `Wrote to ${filePath}`;
1843
- } catch (err) {
1844
- return `Error writing ${filePath}: ${err.message}`;
1845
- }
1846
- }
1847
- });
1848
- }
1849
-
1850
- // src/lib/tools/writeAlgoliaCredentials.ts
1851
- import { tool as tool6 } from "ai";
1852
- import z13 from "zod";
1853
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
1854
- import { dirname as dirname4, relative as relative3 } from "node:path";
1855
-
1856
1635
  // src/lib/algoliaApp.ts
1857
- import { z as z10 } from "zod";
1858
- var applicationSchema = z10.object({
1859
- id: z10.string().min(1),
1860
- name: z10.string().default(""),
1861
- plan: z10.string().optional()
1636
+ import { z as z5 } from "zod";
1637
+ var applicationSchema = z5.object({
1638
+ id: z5.string().min(1),
1639
+ name: z5.string().default(""),
1640
+ plan: z5.string().optional()
1862
1641
  });
1863
- var listSchema = z10.array(
1864
- z10.object({
1865
- id: z10.string().min(1),
1866
- name: z10.string().default(""),
1867
- plan_label: z10.string().optional()
1868
- }).transform(({ id, name, plan_label }) => ({ id, name, plan: plan_label }))
1642
+ var listSchema = z5.array(
1643
+ z5.object({
1644
+ id: z5.string().min(1),
1645
+ name: z5.string().default(""),
1646
+ plan_label: z5.string().optional(),
1647
+ status: z5.string().optional(),
1648
+ acl: z5.array(z5.string()).optional()
1649
+ }).transform(({ id, name, plan_label, status, acl }) => ({
1650
+ id,
1651
+ name,
1652
+ plan: plan_label,
1653
+ status,
1654
+ acl
1655
+ }))
1869
1656
  );
1657
+ function canSelectApplication(app) {
1658
+ return app.status === "active" && (app.acl ?? []).includes("keys");
1659
+ }
1870
1660
  async function currentApplication() {
1871
1661
  let raw;
1872
1662
  try {
@@ -1915,23 +1705,163 @@ function parseJson(text) {
1915
1705
  }
1916
1706
  }
1917
1707
 
1918
- // src/lib/algoliaApiKey.ts
1919
- import { z as z12 } from "zod";
1920
-
1921
- // src/lib/keychain.ts
1922
- import { deletePassword, getPassword, setPassword } from "cross-keychain";
1923
- import { z as z11 } from "zod";
1924
- var SERVICE = "algolia-wizard";
1925
- var ACCOUNT = "api-keys";
1926
- var storedKeysSchema = z11.record(z11.string(), z11.string());
1927
- function entryId(kind, index, appId) {
1928
- return `${kind}:${appId}:${index}`;
1708
+ // src/lib/envAppId.ts
1709
+ import { readFile as readFile3 } from "node:fs/promises";
1710
+ import { join as join5 } from "node:path";
1711
+ var ENV_FILES = [".env", ".env.local"];
1712
+ var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
1713
+ async function findEnvApplicationId(root = process.cwd()) {
1714
+ for (const file of ENV_FILES) {
1715
+ let content;
1716
+ try {
1717
+ content = await readFile3(join5(root, file), "utf8");
1718
+ } catch (err) {
1719
+ if (err.code !== "ENOENT") {
1720
+ logger.warn(
1721
+ { file, err },
1722
+ "could not read env file for an application id"
1723
+ );
1724
+ }
1725
+ continue;
1726
+ }
1727
+ for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
1728
+ const id = readValue(raw);
1729
+ if (id) {
1730
+ logger.info({ file, name, app: id }, "found an application id in env");
1731
+ return { id, name, file };
1732
+ }
1733
+ }
1734
+ }
1735
+ return null;
1929
1736
  }
1930
- async function loadKeys() {
1931
- const raw = await getPassword(SERVICE, ACCOUNT);
1932
- if (!raw) return {};
1933
- let payload;
1934
- try {
1737
+ function readValue(raw) {
1738
+ const trimmed = raw.trim();
1739
+ const quoted = trimmed.match(/^(['"])(.*)\1/);
1740
+ const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
1741
+ return value.length > 0 && !value.startsWith("<") ? value : null;
1742
+ }
1743
+
1744
+ // src/lib/algoliaAppPicker.ts
1745
+ function blockReasonFor(app) {
1746
+ return app.status !== "active" ? "Inactive" : "Missing permissions";
1747
+ }
1748
+ function secondaryFor(app) {
1749
+ if (!canSelectApplication(app)) {
1750
+ return { kind: "text", value: blockReasonFor(app) };
1751
+ }
1752
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
1753
+ }
1754
+ function labelFor(app) {
1755
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
1756
+ }
1757
+ async function selectAndReport(app) {
1758
+ const store = useWizard.getState();
1759
+ store.setSettingUpApp(app.id);
1760
+ try {
1761
+ return await selectApplication(app.id);
1762
+ } finally {
1763
+ store.setSettingUpApp(null);
1764
+ }
1765
+ }
1766
+ async function promptForApplication(leadIn = []) {
1767
+ const store = useWizard.getState();
1768
+ const unordered = await listApplications();
1769
+ if (unordered.length === 0) {
1770
+ throw new Error(
1771
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
1772
+ );
1773
+ }
1774
+ const apps = [
1775
+ ...unordered.filter(canSelectApplication),
1776
+ ...unordered.filter((app) => !canSelectApplication(app))
1777
+ ];
1778
+ const messages = [...leadIn];
1779
+ for (; ; ) {
1780
+ const choice = await store.requestUserInput({
1781
+ prompt: "Which Algolia application should the wizard work in?",
1782
+ promptType: "multipleChoice",
1783
+ options: apps.map(labelFor),
1784
+ secondary: apps.map(secondaryFor),
1785
+ disabled: apps.map((app) => !canSelectApplication(app)),
1786
+ messages
1787
+ });
1788
+ const chosen = apps.find((app) => labelFor(app) === choice);
1789
+ if (!chosen || !canSelectApplication(chosen)) {
1790
+ throw new Error("Application picker received an unexpected selection");
1791
+ }
1792
+ try {
1793
+ return await selectAndReport(chosen);
1794
+ } catch (err) {
1795
+ logger.warn(
1796
+ { app: chosen.id, err: err.message },
1797
+ "application select failed; re-prompting"
1798
+ );
1799
+ messages.push(
1800
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
1801
+ );
1802
+ }
1803
+ }
1804
+ }
1805
+ async function confirmEnvApplication(env, current) {
1806
+ const useEnv = `Use ${env.id} (from ${env.file})`;
1807
+ const choice = await useWizard.getState().requestUserInput({
1808
+ prompt: "Select an application",
1809
+ promptType: "multipleChoice",
1810
+ options: [
1811
+ useEnv,
1812
+ current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
1813
+ ],
1814
+ messages: [
1815
+ `${env.file} already sets ${env.name}=${env.id}.`,
1816
+ "Which Algolia application would you like to use?"
1817
+ ]
1818
+ });
1819
+ if (choice === useEnv) return selectEnvApplication(env);
1820
+ return current ? selectAndReport(current) : null;
1821
+ }
1822
+ async function selectEnvApplication(env) {
1823
+ try {
1824
+ return await selectAndReport({ id: env.id, name: "" });
1825
+ } catch (err) {
1826
+ logger.warn(
1827
+ { app: env.id, err: err.message },
1828
+ "could not select the application named in env; falling back to the picker"
1829
+ );
1830
+ return promptForApplication([
1831
+ `Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
1832
+ ]);
1833
+ }
1834
+ }
1835
+ async function ensureApplication(isResuming) {
1836
+ const current = await currentApplication();
1837
+ if (isResuming) {
1838
+ return current ?? await promptForApplication();
1839
+ }
1840
+ const env = await findEnvApplicationId();
1841
+ if (env && env.id !== current?.id) {
1842
+ const chosen = await confirmEnvApplication(env, current);
1843
+ if (chosen) return chosen;
1844
+ }
1845
+ return promptForApplication();
1846
+ }
1847
+
1848
+ // src/lib/algoliaApiKey.ts
1849
+ import { z as z7 } from "zod";
1850
+
1851
+ // src/lib/keychain.ts
1852
+ import { deletePassword, getPassword, setPassword } from "cross-keychain";
1853
+ import { z as z6 } from "zod";
1854
+ var SERVICE = "algolia-wizard";
1855
+ var account = (userId) => `api-keys:${userId}`;
1856
+ var storedKeysSchema = z6.record(z6.string(), z6.string());
1857
+ function entryId(kind, index, appId) {
1858
+ return `${kind}:${appId}:${index}`;
1859
+ }
1860
+ async function loadKeys(userId) {
1861
+ const raw = await getPassword(SERVICE, account(userId));
1862
+ if (!raw) return {};
1863
+ let payload;
1864
+ try {
1935
1865
  payload = JSON.parse(raw);
1936
1866
  } catch {
1937
1867
  payload = null;
@@ -1950,154 +1880,445 @@ function serialized(op) {
1950
1880
  });
1951
1881
  return next;
1952
1882
  }
1953
- async function readStoredKey(kind, index, appId) {
1883
+ async function readStoredKey(kind, userId, index, appId) {
1954
1884
  try {
1955
- return (await loadKeys())[entryId(kind, index, appId)] ?? null;
1885
+ return (await loadKeys(userId))[entryId(kind, index, appId)] ?? null;
1956
1886
  } catch (err) {
1957
1887
  logger.warn(
1958
- { err: err.message, kind, index, appId },
1888
+ { err: err.message, kind, index, appId, userId },
1959
1889
  "could not read the API key from the keychain"
1960
1890
  );
1961
1891
  return null;
1962
1892
  }
1963
1893
  }
1964
- function storeKey(kind, index, appId, value) {
1894
+ function storeKey(kind, userId, index, appId, value) {
1965
1895
  return serialized(async () => {
1966
1896
  const id = entryId(kind, index, appId);
1967
1897
  try {
1968
- const keys = await loadKeys();
1898
+ const keys = await loadKeys(userId);
1969
1899
  await setPassword(
1970
1900
  SERVICE,
1971
- ACCOUNT,
1901
+ account(userId),
1972
1902
  JSON.stringify({ ...keys, [id]: value })
1973
1903
  );
1974
- if ((await loadKeys())[id] !== value) {
1904
+ if ((await loadKeys(userId))[id] !== value) {
1975
1905
  throw new Error("the keychain did not store the value");
1976
1906
  }
1977
- } catch (err) {
1978
- logger.warn(
1979
- { err: err.message, kind, index, appId },
1980
- "could not store the API key in the keychain; the next run will create another"
1981
- );
1907
+ } catch (err) {
1908
+ logger.warn(
1909
+ { err: err.message, kind, index, appId, userId },
1910
+ "could not store the API key in the keychain; the next run will create another"
1911
+ );
1912
+ }
1913
+ });
1914
+ }
1915
+ function deleteStoredKeys(userId) {
1916
+ return serialized(async () => {
1917
+ try {
1918
+ await deletePassword(SERVICE, account(userId));
1919
+ } catch (err) {
1920
+ const message = err.message;
1921
+ if (/not found/i.test(message)) return;
1922
+ logger.warn(
1923
+ { err: message, userId },
1924
+ "could not delete the API keys from the keychain"
1925
+ );
1926
+ }
1927
+ });
1928
+ }
1929
+
1930
+ // src/lib/algoliaApiKey.ts
1931
+ function requireUserId() {
1932
+ const userId = useWizard.getState().user?.userId;
1933
+ if (!userId) {
1934
+ throw new Error("No Algolia user is signed in; cannot scope the API key.");
1935
+ }
1936
+ return userId;
1937
+ }
1938
+ async function currentUserId() {
1939
+ return useWizard.getState().user?.userId ?? (await getUser())?.userId ?? null;
1940
+ }
1941
+ var WRITE_ACLS = [
1942
+ "addObject",
1943
+ "deleteObject",
1944
+ "settings",
1945
+ "editSettings",
1946
+ "listIndexes"
1947
+ ];
1948
+ var createdKeySchema = z7.object({
1949
+ key: z7.string().min(1).optional(),
1950
+ value: z7.string().min(1).optional()
1951
+ }).transform((o) => o.key ?? o.value);
1952
+ async function createKey(index, acls, description) {
1953
+ logger.info({ index, acls }, "creating an API key");
1954
+ const stdout = await runAlgoliaCli([
1955
+ "apikeys",
1956
+ "create",
1957
+ "--acl",
1958
+ acls.join(","),
1959
+ "--indices",
1960
+ index,
1961
+ "--description",
1962
+ description,
1963
+ "-o",
1964
+ "json"
1965
+ ]);
1966
+ let payload;
1967
+ try {
1968
+ payload = JSON.parse(stdout);
1969
+ } catch {
1970
+ throw new Error("apikeys create returned output that is not valid JSON");
1971
+ }
1972
+ const created = createdKeySchema.parse(payload);
1973
+ if (!created) throw new Error("apikeys create returned no key value");
1974
+ return created;
1975
+ }
1976
+ async function keyExists(key) {
1977
+ try {
1978
+ await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
1979
+ return true;
1980
+ } catch (err) {
1981
+ return !/does not exist/i.test(err.message);
1982
+ }
1983
+ }
1984
+ var resolved = /* @__PURE__ */ new Map();
1985
+ async function forgetResolvedKeys() {
1986
+ resolved.clear();
1987
+ const userId = await currentUserId();
1988
+ if (userId) await deleteStoredKeys(userId);
1989
+ }
1990
+ function resolveKey(kind, index, appId, acls, description) {
1991
+ const cacheKey = `${kind}:${appId}:${index}`;
1992
+ const cached = resolved.get(cacheKey);
1993
+ if (cached) return cached;
1994
+ const pending = provisionKey(kind, index, appId, acls, description).catch(
1995
+ (err) => {
1996
+ resolved.delete(cacheKey);
1997
+ throw err;
1998
+ }
1999
+ );
2000
+ resolved.set(cacheKey, pending);
2001
+ return pending;
2002
+ }
2003
+ async function provisionKey(kind, index, appId, acls, description) {
2004
+ const userId = requireUserId();
2005
+ const stored = await readStoredKey(kind, userId, index, appId);
2006
+ if (stored) {
2007
+ if (await keyExists(stored)) {
2008
+ logger.info({ kind, index, appId }, "reusing the stored API key");
2009
+ return { key: stored, source: "keychain" };
2010
+ }
2011
+ logger.info(
2012
+ { kind, index, appId },
2013
+ "the stored API key no longer exists; creating another"
2014
+ );
2015
+ }
2016
+ const key = await createKey(index, acls, description);
2017
+ await storeKey(kind, userId, index, appId, key);
2018
+ return { key, source: "created" };
2019
+ }
2020
+ function resolveWriteKey(index, appId) {
2021
+ return resolveKey(
2022
+ "write",
2023
+ index,
2024
+ appId,
2025
+ WRITE_ACLS,
2026
+ `Algolia Wizard write key for ${index} index`
2027
+ );
2028
+ }
2029
+ async function resolveSearchOnlyKey(index, appId) {
2030
+ return resolveKey(
2031
+ "search",
2032
+ index,
2033
+ appId,
2034
+ ["search"],
2035
+ `Algolia Wizard search-only key for ${index} index`
2036
+ );
2037
+ }
2038
+
2039
+ // src/actions/selectIndex.ts
2040
+ var CREATE_NEW_INDEX = "Create a new index\u2026";
2041
+ async function canIssueKeys(index, appId) {
2042
+ try {
2043
+ await resolveWriteKey(index, appId);
2044
+ await resolveSearchOnlyKey(index, appId);
2045
+ return { ok: true };
2046
+ } catch (err) {
2047
+ return { ok: false, error: err.message };
2048
+ }
2049
+ }
2050
+ var selectIndexStep = async (ctx) => {
2051
+ let appId = (await requireApplication()).id;
2052
+ for (; ; ) {
2053
+ const indices = await listIndices();
2054
+ const names = indices.map((i) => i.name);
2055
+ const hasIndices = names.length > 0;
2056
+ let error;
2057
+ let chosen;
2058
+ while (chosen === void 0) {
2059
+ const selection = hasIndices ? await ctx.requestUserInput({
2060
+ prompt: "Which index do you want to ingest into?",
2061
+ promptType: "multipleChoice",
2062
+ options: [...names, CREATE_NEW_INDEX],
2063
+ messages: ["We found these indices:"],
2064
+ error
2065
+ }) : await ctx.requestUserInput({
2066
+ prompt: "Name the index to create and ingest into:",
2067
+ promptType: "textInput",
2068
+ options: [],
2069
+ messages: [
2070
+ "This app has no existing indices \u2014 enter a name to create one."
2071
+ ],
2072
+ error
2073
+ });
2074
+ if (typeof selection !== "string") {
2075
+ throw new Error("selectIndex received an unexpected non-text result");
2076
+ }
2077
+ let candidate;
2078
+ if (!hasIndices) {
2079
+ candidate = selection.trim();
2080
+ } else if (selection === CREATE_NEW_INDEX) {
2081
+ const name = await ctx.requestUserInput({
2082
+ prompt: "Name the new index:",
2083
+ promptType: "textInput",
2084
+ options: []
2085
+ });
2086
+ if (typeof name !== "string") {
2087
+ throw new Error("selectIndex received an unexpected non-text result");
2088
+ }
2089
+ candidate = name.trim();
2090
+ } else {
2091
+ candidate = selection;
2092
+ }
2093
+ if (!candidate) {
2094
+ error = "Index name cannot be empty.";
2095
+ continue;
2096
+ }
2097
+ chosen = candidate;
2098
+ }
2099
+ const issued = await canIssueKeys(chosen, appId);
2100
+ if (issued.ok) {
2101
+ ctx.setUserInput("index", chosen);
2102
+ return { selection: chosen };
2103
+ }
2104
+ logger.warn(
2105
+ { appId, index: chosen, err: issued.error },
2106
+ "selectIndex: could not mint API keys in the selected application; asking for another"
2107
+ );
2108
+ const reselected = await promptForApplication([
2109
+ `Could not create an Algolia API key in application ${appId} (${issued.error}).`,
2110
+ "Pick a different application to work in."
2111
+ ]);
2112
+ appId = reselected.id;
2113
+ }
2114
+ };
2115
+
2116
+ // src/lib/agent.ts
2117
+ import { ToolLoopAgent, hasToolCall, Output as Output3 } from "ai";
2118
+ import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
2119
+ import "zod";
2120
+
2121
+ // src/lib/tools/index.ts
2122
+ import "zod";
2123
+
2124
+ // src/lib/tools/listFiles.ts
2125
+ import { tool } from "ai";
2126
+ import z8 from "zod";
2127
+ import { readdir } from "node:fs/promises";
2128
+
2129
+ // src/lib/tools/path.ts
2130
+ import { lstat } from "node:fs/promises";
2131
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join6, sep } from "node:path";
2132
+ function resolveInRoot(ctx, path) {
2133
+ const target = resolve2(ctx.cwd, path);
2134
+ const rel = relative(ctx.root, target);
2135
+ if (rel.startsWith("..") || isAbsolute(rel)) {
2136
+ return {
2137
+ ok: false,
2138
+ error: `Refused: ${target} is outside the repo root (${ctx.root}).`
2139
+ };
2140
+ }
2141
+ return { ok: true, target };
2142
+ }
2143
+ async function hasSymlinkParent(ctx, target) {
2144
+ let current = ctx.root;
2145
+ const parts = relative(ctx.root, dirname2(target)).split(sep).filter(Boolean);
2146
+ for (const part of parts) {
2147
+ current = join6(current, part);
2148
+ try {
2149
+ if ((await lstat(current)).isSymbolicLink()) return true;
2150
+ } catch (err) {
2151
+ if (err.code === "ENOENT") return false;
2152
+ throw err;
2153
+ }
2154
+ }
2155
+ return false;
2156
+ }
2157
+
2158
+ // src/lib/tools/listFiles.ts
2159
+ function listFilesTool(ctx) {
2160
+ return tool({
2161
+ description: 'List files in a directory (default: the current working directory). Pass path to list a subdirectory directly \u2014 e.g. "packages/api" \u2014 without first changeDirectory-ing into it.',
2162
+ inputSchema: z8.object({
2163
+ path: z8.string().optional().describe("Directory to list, relative to cwd (default: cwd)")
2164
+ }),
2165
+ execute: async ({ path = "." }) => {
2166
+ logger.info({ path }, "called listFiles tool");
2167
+ if (++ctx.counts.list > ctx.limits.list) {
2168
+ return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
2169
+ }
2170
+ const resolved2 = resolveInRoot(ctx, path);
2171
+ if (!resolved2.ok) return resolved2.error;
2172
+ try {
2173
+ const entries = await readdir(resolved2.target, { withFileTypes: true });
2174
+ return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
2175
+ } catch (err) {
2176
+ return `Error listing ${path}: ${err.message}`;
2177
+ }
2178
+ }
2179
+ });
2180
+ }
2181
+
2182
+ // src/lib/tools/changeDirectory.ts
2183
+ import { tool as tool2 } from "ai";
2184
+ import z9 from "zod";
2185
+ import { stat } from "node:fs/promises";
2186
+ function changeDirectoryTool(ctx) {
2187
+ return tool2({
2188
+ description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
2189
+ inputSchema: z9.object({
2190
+ path: z9.string().describe("Directory to change into")
2191
+ }),
2192
+ execute: async ({ path }) => {
2193
+ logger.info({ path }, "called changeDirectory tool");
2194
+ const resolved2 = resolveInRoot(ctx, path);
2195
+ if (!resolved2.ok) return resolved2.error;
2196
+ try {
2197
+ const info = await stat(resolved2.target);
2198
+ if (!info.isDirectory()) {
2199
+ return `Error changing directory to ${path}: not a directory`;
2200
+ }
2201
+ ctx.cwd = resolved2.target;
2202
+ return `Changed working directory to ${ctx.cwd}`;
2203
+ } catch (err) {
2204
+ return `Error changing directory to ${path}: ${err.message}`;
2205
+ }
2206
+ }
2207
+ });
2208
+ }
2209
+
2210
+ // src/lib/tools/reportStatus.ts
2211
+ import { tool as tool3 } from "ai";
2212
+ import z10 from "zod";
2213
+ function reportStatusTool(output) {
2214
+ return tool3({
2215
+ description: "Report the status of your execution. Return a reason in case of failure.",
2216
+ inputSchema: z10.object({
2217
+ status: z10.enum(["success", "fail"]),
2218
+ reason: z10.string().optional(),
2219
+ output
2220
+ }),
2221
+ execute: async ({ status, reason, output: output2 }) => {
2222
+ logger.info({ status, reason }, "called reportStatus tool");
2223
+ return { status, reason, output: output2 };
2224
+ }
2225
+ });
2226
+ }
2227
+
2228
+ // src/lib/tools/readFile.ts
2229
+ import { tool as tool4 } from "ai";
2230
+ import z11 from "zod";
2231
+ import { readFile as readFile4 } from "node:fs/promises";
2232
+
2233
+ // src/lib/tools/env.ts
2234
+ import { basename } from "node:path";
2235
+ function isEnvFile(filePath) {
2236
+ const name = basename(filePath);
2237
+ return name === ".env" || name.startsWith(".env.");
2238
+ }
2239
+ function isSecretEnvFile(filePath) {
2240
+ if (!isEnvFile(filePath)) return false;
2241
+ const name = basename(filePath);
2242
+ return !/\.(example|sample|template)$/.test(name);
2243
+ }
2244
+
2245
+ // src/lib/tools/readFile.ts
2246
+ function redactEnvValues(content) {
2247
+ return content.split("\n").map((line) => {
2248
+ const match = line.match(/^(\s*(?:export\s+)?[\w.-]+\s*=)(.*)$/);
2249
+ if (!match) return line;
2250
+ const value = match[2].trim();
2251
+ if (value === "") return line;
2252
+ return `${match[1]}[REDACTED]`;
2253
+ }).join("\n");
2254
+ }
2255
+ function readFileTool(ctx) {
2256
+ return tool4({
2257
+ description: "Read the contents of a file at the given path",
2258
+ inputSchema: z11.object({
2259
+ filePath: z11.string().describe("Path to the file to read")
2260
+ }),
2261
+ execute: async ({ filePath }) => {
2262
+ if (++ctx.counts.read > ctx.limits.read) {
2263
+ return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
2264
+ }
2265
+ logger.info({ filePath }, "called readFile tool");
2266
+ const resolved2 = resolveInRoot(ctx, filePath);
2267
+ if (!resolved2.ok) return resolved2.error;
2268
+ try {
2269
+ const content = await readFile4(resolved2.target, "utf8");
2270
+ return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
2271
+ } catch (err) {
2272
+ return `Error reading ${filePath}: ${err.message}`;
2273
+ }
1982
2274
  }
1983
2275
  });
1984
2276
  }
1985
- function deleteStoredKeys() {
1986
- return serialized(async () => {
1987
- try {
1988
- await deletePassword(SERVICE, ACCOUNT);
1989
- } catch (err) {
1990
- const message = err.message;
1991
- if (/not found/i.test(message)) return;
1992
- logger.warn(
1993
- { err: message },
1994
- "could not delete the API keys from the keychain"
1995
- );
2277
+
2278
+ // src/lib/tools/writeFile.ts
2279
+ import { tool as tool5 } from "ai";
2280
+ import z12 from "zod";
2281
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2282
+ import { dirname as dirname3 } from "node:path";
2283
+ function writeFileTool(ctx) {
2284
+ return tool5({
2285
+ description: "Write content to a file at the given path, overwriting it. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
2286
+ inputSchema: z12.object({
2287
+ filePath: z12.string().describe("Path to the file to write"),
2288
+ content: z12.string().describe("Content to write to the file")
2289
+ }),
2290
+ execute: async ({ filePath, content }) => {
2291
+ logger.info({ filePath }, "called writeFile tool");
2292
+ const resolved2 = resolveInRoot(ctx, filePath);
2293
+ if (resolved2.ok === false) return resolved2.error;
2294
+ if (isSecretEnvFile(resolved2.target)) {
2295
+ return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
2296
+ }
2297
+ try {
2298
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
2299
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2300
+ }
2301
+ await mkdir3(dirname3(resolved2.target), { recursive: true });
2302
+ await writeFile3(resolved2.target, content, "utf8");
2303
+ useWizard.getState().recordWrittenFile(resolved2.target);
2304
+ return `Wrote to ${filePath}`;
2305
+ } catch (err) {
2306
+ return `Error writing ${filePath}: ${err.message}`;
2307
+ }
1996
2308
  }
1997
2309
  });
1998
2310
  }
1999
2311
 
2000
- // src/lib/algoliaApiKey.ts
2001
- var WRITE_ACLS = [
2002
- "addObject",
2003
- "deleteObject",
2004
- "settings",
2005
- "editSettings",
2006
- "listIndexes"
2007
- ];
2008
- var createdKeySchema = z12.object({
2009
- key: z12.string().min(1).optional(),
2010
- value: z12.string().min(1).optional()
2011
- }).transform((o) => o.key ?? o.value);
2012
- async function createKey(index, acls, description) {
2013
- logger.info({ index, acls }, "creating an API key");
2014
- const stdout = await runAlgoliaCli([
2015
- "apikeys",
2016
- "create",
2017
- "--acl",
2018
- acls.join(","),
2019
- "--indices",
2020
- index,
2021
- "--description",
2022
- description,
2023
- "-o",
2024
- "json"
2025
- ]);
2026
- let payload;
2027
- try {
2028
- payload = JSON.parse(stdout);
2029
- } catch {
2030
- throw new Error("apikeys create returned output that is not valid JSON");
2031
- }
2032
- const created = createdKeySchema.parse(payload);
2033
- if (!created) throw new Error("apikeys create returned no key value");
2034
- return created;
2035
- }
2036
- async function keyExists(key) {
2037
- try {
2038
- await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
2039
- return true;
2040
- } catch (err) {
2041
- return !/does not exist/i.test(err.message);
2042
- }
2043
- }
2044
- var resolved = /* @__PURE__ */ new Map();
2045
- async function forgetResolvedKeys() {
2046
- resolved.clear();
2047
- await deleteStoredKeys();
2048
- }
2049
- function resolveKey(kind, index, appId, acls, description) {
2050
- const cacheKey = `${kind}:${appId}:${index}`;
2051
- const cached = resolved.get(cacheKey);
2052
- if (cached) return cached;
2053
- const pending = provisionKey(kind, index, appId, acls, description).catch(
2054
- (err) => {
2055
- resolved.delete(cacheKey);
2056
- throw err;
2057
- }
2058
- );
2059
- resolved.set(cacheKey, pending);
2060
- return pending;
2061
- }
2062
- async function provisionKey(kind, index, appId, acls, description) {
2063
- const stored = await readStoredKey(kind, index, appId);
2064
- if (stored) {
2065
- if (await keyExists(stored)) {
2066
- logger.info({ kind, index, appId }, "reusing the stored API key");
2067
- return { key: stored, source: "keychain" };
2068
- }
2069
- logger.info(
2070
- { kind, index, appId },
2071
- "the stored API key no longer exists; creating another"
2072
- );
2073
- }
2074
- const key = await createKey(index, acls, description);
2075
- await storeKey(kind, index, appId, key);
2076
- return { key, source: "created" };
2077
- }
2078
- function resolveWriteKey(index, appId) {
2079
- return resolveKey(
2080
- "write",
2081
- index,
2082
- appId,
2083
- WRITE_ACLS,
2084
- `Algolia Wizard write key for ${index} index`
2085
- );
2086
- }
2087
- async function resolveSearchOnlyKey(index, appId) {
2088
- return resolveKey(
2089
- "search",
2090
- index,
2091
- appId,
2092
- ["search"],
2093
- `Algolia Wizard search-only key for ${index} index`
2094
- );
2095
- }
2312
+ // src/lib/tools/writeAlgoliaCredentials.ts
2313
+ import { tool as tool6 } from "ai";
2314
+ import z13 from "zod";
2315
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
2316
+ import { dirname as dirname4, relative as relative3 } from "node:path";
2096
2317
 
2097
2318
  // src/lib/gitignore.ts
2098
2319
  import { execFile } from "node:child_process";
2099
- import { lstat as lstat2, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2100
- import { join as join6, relative as relative2 } from "node:path";
2320
+ import { lstat as lstat2, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
2321
+ import { join as join7, relative as relative2 } from "node:path";
2101
2322
  var GIT_ENV_OVERRIDES = [
2102
2323
  "GIT_DIR",
2103
2324
  "GIT_WORK_TREE",
@@ -2149,7 +2370,7 @@ async function ensureGitIgnored(root, target) {
2149
2370
  if (ignoredByRule === void 0) return "unknown";
2150
2371
  if (ignoredByRule) return tracked ? "tracked" : "covered";
2151
2372
  const pattern = relative2(root, target);
2152
- const gitIgnore = join6(root, ".gitignore");
2373
+ const gitIgnore = join7(root, ".gitignore");
2153
2374
  try {
2154
2375
  const link = await lstat2(gitIgnore).catch(() => null);
2155
2376
  if (link?.isSymbolicLink()) {
@@ -2159,7 +2380,7 @@ async function ensureGitIgnored(root, target) {
2159
2380
  );
2160
2381
  return "unknown";
2161
2382
  }
2162
- const existing = link ? await readFile4(gitIgnore, "utf8") : "";
2383
+ const existing = link ? await readFile5(gitIgnore, "utf8") : "";
2163
2384
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2164
2385
  await writeFile4(gitIgnore, `${existing}${prefix}${pattern}
2165
2386
  `, "utf8");
@@ -2228,7 +2449,7 @@ function writeCredentialsTool(ctx) {
2228
2449
  return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2229
2450
  }
2230
2451
  try {
2231
- existing = await readFile5(resolved2.target, "utf8");
2452
+ existing = await readFile6(resolved2.target, "utf8");
2232
2453
  } catch (err) {
2233
2454
  if (err.code !== "ENOENT") throw err;
2234
2455
  }
@@ -2318,8 +2539,8 @@ async function gitIgnoreOutcome(ctx, target) {
2318
2539
  // src/lib/tools/searchFiles.ts
2319
2540
  import { tool as tool7 } from "ai";
2320
2541
  import z14 from "zod";
2321
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2322
- import { join as join7 } from "node:path";
2542
+ import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
2543
+ import { join as join8 } from "node:path";
2323
2544
  var MAX_QUERY_LENGTH = 1e3;
2324
2545
  var SKIP_DIRS = /* @__PURE__ */ new Set([
2325
2546
  "node_modules",
@@ -2334,7 +2555,7 @@ async function walkFiles(dir) {
2334
2555
  const out = [];
2335
2556
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2336
2557
  if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2337
- const full = join7(dir, e.name);
2558
+ const full = join8(dir, e.name);
2338
2559
  if (e.isDirectory()) out.push(...await walkFiles(full));
2339
2560
  else if (e.isFile()) out.push(full);
2340
2561
  }
@@ -2367,7 +2588,7 @@ function searchFilesTool(ctx) {
2367
2588
  for (const file of await walkFiles(resolved2.target)) {
2368
2589
  let content;
2369
2590
  try {
2370
- content = await readFile6(file, "utf8");
2591
+ content = await readFile7(file, "utf8");
2371
2592
  } catch {
2372
2593
  continue;
2373
2594
  }
@@ -3267,7 +3488,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3267
3488
  // package.json
3268
3489
  var package_default = {
3269
3490
  name: "@algolia/wizard",
3270
- version: "0.37.0",
3491
+ version: "0.38.0-rc.131.265",
3271
3492
  description: "Magically implement Algolia functionality in your codebase",
3272
3493
  type: "module",
3273
3494
  engines: {
@@ -3675,12 +3896,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3675
3896
  // src/actions/implement.ts
3676
3897
  import z28 from "zod";
3677
3898
  import { mkdir as mkdir7 } from "node:fs/promises";
3678
- import { join as join10, relative as relative6 } from "node:path";
3899
+ import { join as join11, relative as relative6 } from "node:path";
3679
3900
 
3680
3901
  // src/lib/git.ts
3681
3902
  import { execFile as execFile2 } from "node:child_process";
3682
3903
  import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
3683
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3904
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
3684
3905
  var MAX_BUFFER = 32 * 1024 * 1024;
3685
3906
  function git(args) {
3686
3907
  return new Promise((resolve4, reject) => {
@@ -3717,8 +3938,8 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
3717
3938
  } catch {
3718
3939
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3719
3940
  }
3720
- const relPath = join8(ingestDir, basename2(source));
3721
- const dest = join8(repoRoot, relPath);
3941
+ const relPath = join9(ingestDir, basename2(source));
3942
+ const dest = join9(repoRoot, relPath);
3722
3943
  if (resolve3(source) === resolve3(dest)) {
3723
3944
  return { ok: true, relPath };
3724
3945
  }
@@ -3757,13 +3978,13 @@ function toRootRelative(p) {
3757
3978
 
3758
3979
  // src/lib/algoliaDocs.ts
3759
3980
  import { readFileSync, readdirSync, existsSync } from "node:fs";
3760
- import { dirname as dirname7, join as join9 } from "node:path";
3981
+ import { dirname as dirname7, join as join10 } from "node:path";
3761
3982
  import { fileURLToPath } from "node:url";
3762
- var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3983
+ var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3763
3984
  function findDocsDir() {
3764
3985
  let dir = dirname7(fileURLToPath(import.meta.url));
3765
3986
  for (; ; ) {
3766
- const candidate = join9(dir, DOCS_SUBPATH);
3987
+ const candidate = join10(dir, DOCS_SUBPATH);
3767
3988
  if (existsSync(candidate)) return candidate;
3768
3989
  const parent = dirname7(dir);
3769
3990
  if (parent === dir) return void 0;
@@ -3786,7 +4007,7 @@ function loadAlgoliaDoc(language) {
3786
4007
  );
3787
4008
  return "";
3788
4009
  }
3789
- return readFileSync(join9(docsDir, files[0]), "utf8").trim();
4010
+ return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3790
4011
  }
3791
4012
  function getNamedDoc(name, language) {
3792
4013
  const docsDir = findDocsDir();
@@ -3794,7 +4015,7 @@ function getNamedDoc(name, language) {
3794
4015
  logger.warn("docs/algolia-sdk not found");
3795
4016
  return "";
3796
4017
  }
3797
- const file = join9(docsDir, `${name}-${language}.md`);
4018
+ const file = join10(docsDir, `${name}-${language}.md`);
3798
4019
  if (!existsSync(file)) {
3799
4020
  logger.warn({ name, language }, "named SDK reference not found");
3800
4021
  return "";
@@ -4141,7 +4362,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4141
4362
  useWizard.getState().setTargetIndex(targetIndex ?? null);
4142
4363
  await assertGitRepoWithHead(repoRoot);
4143
4364
  if (useCases.includes("ingestion")) {
4144
- await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
4365
+ await mkdir7(join11(repoRoot, INGEST_DIR), { recursive: true });
4145
4366
  }
4146
4367
  const normalized = normalizeFindingPaths(findings);
4147
4368
  const confirmed2 = normalized.confirmedEntities;
@@ -4154,6 +4375,10 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4154
4375
  if (useCases.includes("ingestion")) {
4155
4376
  ingestAppId = appId ?? (await requireApplication()).id;
4156
4377
  }
4378
+ let ingestWriteKey;
4379
+ if (useCases.includes("ingestion") && ingestAppId) {
4380
+ ingestWriteKey = (await resolveWriteKey(targetIndex, ingestAppId)).key;
4381
+ }
4157
4382
  let uploadFilePath;
4158
4383
  let uploadWarning;
4159
4384
  if (ingestionSource === "fileUpload") {
@@ -4216,9 +4441,9 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4216
4441
  let ingestDurationMs;
4217
4442
  let ingestOutcomeMessage;
4218
4443
  const ingestKeyAppId = ingestAppId;
4219
- const ingestionTools = ingestKeyAppId ? makeToolContext(repoRoot, async () => ({
4444
+ const ingestionTools = ingestKeyAppId && ingestWriteKey ? makeToolContext(repoRoot, async () => ({
4220
4445
  [APP_ID_VAR]: ingestKeyAppId,
4221
- [API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
4446
+ [API_KEY_VAR]: ingestWriteKey,
4222
4447
  [INDEX_NAME_VAR]: targetIndex
4223
4448
  })) : void 0;
4224
4449
  const searchTools = makeToolContext(repoRoot);
@@ -4367,7 +4592,7 @@ ${detail}` : ""}`
4367
4592
  if (searchConfigFile) {
4368
4593
  const ignoreStatus = await gitIgnoreStatus(
4369
4594
  repoRoot,
4370
- join10(repoRoot, searchConfigFile)
4595
+ join11(repoRoot, searchConfigFile)
4371
4596
  );
4372
4597
  if (ignoreStatus === "covered") {
4373
4598
  summaries.push(
@@ -4513,7 +4738,7 @@ function getWorkflow(id) {
4513
4738
  }
4514
4739
 
4515
4740
  // src/ui/Welcome.tsx
4516
- import { dirname as dirname8, join as join11 } from "node:path";
4741
+ import { dirname as dirname8, join as join12 } from "node:path";
4517
4742
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4518
4743
  import { useState as useState7 } from "react";
4519
4744
  import { Box as Box10, Spacer, Text as Text10, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
@@ -4545,7 +4770,7 @@ var sidebarItems = [
4545
4770
  // src/ui/Welcome.tsx
4546
4771
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
4547
4772
  import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
4548
- var IMAGE_PATH = join11(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
4773
+ var IMAGE_PATH = join12(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
4549
4774
  var TERMINAL_INFO = {
4550
4775
  ...defaultTerminalInfo,
4551
4776
  supportsUnicode: true,
@@ -5373,7 +5598,8 @@ function App() {
5373
5598
  steps,
5374
5599
  inputReq,
5375
5600
  user,
5376
- workflow
5601
+ workflow,
5602
+ settingUpAppId
5377
5603
  } = useWizard();
5378
5604
  const { exit } = useApp();
5379
5605
  const { columns, rows } = useWindowSize7();
@@ -5388,7 +5614,7 @@ function App() {
5388
5614
  const isCommandApprovalPrompt = isAwaitingUserInput && inputReq?.promptType === "commandApproval";
5389
5615
  const promptPending = isAwaitingUserInput && !isCommandApprovalPrompt;
5390
5616
  const holdForTip = stepHasTips && promptPending && tipState === "revealing";
5391
- const showTips = stepHasTips && (phase === "running" || isCommandApprovalPrompt || holdForTip);
5617
+ const showTips = stepHasTips && (phase === "running" && currentStep?.status === "running" || isCommandApprovalPrompt || holdForTip);
5392
5618
  const showNoticesInMain = !stepHasTips;
5393
5619
  const showNotices = !isAwaitingUserInput || holdForTip;
5394
5620
  useInput6(
@@ -5473,6 +5699,12 @@ function App() {
5473
5699
  ] }),
5474
5700
  /* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
5475
5701
  ] }),
5702
+ settingUpAppId && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
5703
+ /* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
5704
+ " Setting up wizard using app",
5705
+ " ",
5706
+ settingUpAppId
5707
+ ] }) }),
5476
5708
  /* @__PURE__ */ jsx17(CliOutput, {}),
5477
5709
  showTips && currentStep && /* @__PURE__ */ jsx17(
5478
5710
  Tips,
@@ -5505,140 +5737,6 @@ function App() {
5505
5737
  );
5506
5738
  }
5507
5739
 
5508
- // src/lib/envAppId.ts
5509
- import { readFile as readFile7 } from "node:fs/promises";
5510
- import { join as join12 } from "node:path";
5511
- var ENV_FILES = [".env", ".env.local"];
5512
- var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
5513
- async function findEnvApplicationId(root = process.cwd()) {
5514
- for (const file of ENV_FILES) {
5515
- let content;
5516
- try {
5517
- content = await readFile7(join12(root, file), "utf8");
5518
- } catch (err) {
5519
- if (err.code !== "ENOENT") {
5520
- logger.warn(
5521
- { file, err },
5522
- "could not read env file for an application id"
5523
- );
5524
- }
5525
- continue;
5526
- }
5527
- for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
5528
- const id = readValue(raw);
5529
- if (id) {
5530
- logger.info({ file, name, app: id }, "found an application id in env");
5531
- return { id, name, file };
5532
- }
5533
- }
5534
- }
5535
- return null;
5536
- }
5537
- function readValue(raw) {
5538
- const trimmed = raw.trim();
5539
- const quoted = trimmed.match(/^(['"])(.*)\1/);
5540
- const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
5541
- return value.length > 0 && !value.startsWith("<") ? value : null;
5542
- }
5543
-
5544
- // src/lib/algoliaAppPicker.ts
5545
- function secondaryFor(app) {
5546
- return app.plan ? { kind: "badge", value: app.plan } : void 0;
5547
- }
5548
- function labelFor(app) {
5549
- return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
5550
- }
5551
- function selectAndReport(app) {
5552
- useWizard.getState().pushCliOutput(
5553
- "stdout",
5554
- `Selecting ${labelFor(app)} \u2014 provisioning its API key\u2026`
5555
- );
5556
- return selectApplication(app.id);
5557
- }
5558
- async function promptForApplication(leadIn = []) {
5559
- const store = useWizard.getState();
5560
- const apps = await listApplications();
5561
- if (apps.length === 0) {
5562
- throw new Error(
5563
- "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
5564
- );
5565
- }
5566
- if (apps.length === 1) {
5567
- const only = apps[0];
5568
- logger.info(
5569
- { app: only.id },
5570
- "single application on the account; selecting it"
5571
- );
5572
- for (const line of leadIn) store.pushCliOutput("stdout", line);
5573
- return selectAndReport(only);
5574
- }
5575
- const messages = [
5576
- ...leadIn,
5577
- "Which Algolia application should the wizard work in?"
5578
- ];
5579
- for (; ; ) {
5580
- const choice = await store.requestUserInput({
5581
- prompt: "Select an application",
5582
- promptType: "multipleChoice",
5583
- options: apps.map(labelFor),
5584
- secondary: apps.map(secondaryFor),
5585
- messages
5586
- });
5587
- const chosen = apps.find((app) => labelFor(app) === choice);
5588
- if (!chosen) {
5589
- throw new Error("Application picker received an unexpected selection");
5590
- }
5591
- try {
5592
- return await selectAndReport(chosen);
5593
- } catch (err) {
5594
- logger.warn(
5595
- { app: chosen.id, err: err.message },
5596
- "application select failed; re-prompting"
5597
- );
5598
- messages.push(
5599
- `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
5600
- );
5601
- }
5602
- }
5603
- }
5604
- async function confirmEnvApplication(env, current) {
5605
- const useEnv = `Use ${env.id} (from ${env.file})`;
5606
- const choice = await useWizard.getState().requestUserInput({
5607
- prompt: "Select an application",
5608
- promptType: "multipleChoice",
5609
- options: [
5610
- useEnv,
5611
- current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
5612
- ],
5613
- messages: [
5614
- `${env.file} already sets ${env.name}=${env.id}.`,
5615
- "Which Algolia application should the wizard work in?"
5616
- ]
5617
- });
5618
- return choice === useEnv;
5619
- }
5620
- async function selectEnvApplication(env) {
5621
- try {
5622
- return await selectAndReport({ id: env.id, name: "" });
5623
- } catch (err) {
5624
- logger.warn(
5625
- { app: env.id, err: err.message },
5626
- "could not select the application named in env; falling back to the picker"
5627
- );
5628
- return promptForApplication([
5629
- `Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
5630
- ]);
5631
- }
5632
- }
5633
- async function ensureApplication() {
5634
- const current = await currentApplication();
5635
- const env = await findEnvApplicationId();
5636
- if (env && env.id !== current?.id && await confirmEnvApplication(env, current)) {
5637
- return selectEnvApplication(env);
5638
- }
5639
- return current ?? await promptForApplication();
5640
- }
5641
-
5642
5740
  // src/lib/seed.ts
5643
5741
  var projectScan2 = {
5644
5742
  languages: [{ name: "TypeScript", version: "5.7.2" }],
@@ -6001,7 +6099,8 @@ async function run(workflow) {
6001
6099
  store.setUser(user);
6002
6100
  let app;
6003
6101
  try {
6004
- app = await ensureApplication();
6102
+ const resuming = await isResumableWorkflow(workflow);
6103
+ app = await ensureApplication(resuming);
6005
6104
  } catch (err) {
6006
6105
  store.setError(err instanceof Error ? err.message : String(err));
6007
6106
  await instance.waitUntilExit();