@algolia/wizard 0.38.0-rc.131.265 → 0.38.0-rc.131.267

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 +477 -490
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1705,155 +1705,271 @@ function parseJson(text) {
1705
1705
  }
1706
1706
  }
1707
1707
 
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
- );
1708
+ // src/actions/selectIndex.ts
1709
+ var CREATE_NEW_INDEX = "Create a new index\u2026";
1710
+ var selectIndexStep = async (ctx) => {
1711
+ await requireApplication();
1712
+ const indices = await listIndices();
1713
+ const names = indices.map((i) => i.name);
1714
+ const hasIndices = names.length > 0;
1715
+ let error;
1716
+ let chosen;
1717
+ while (chosen === void 0) {
1718
+ const selection = hasIndices ? await ctx.requestUserInput({
1719
+ prompt: "Which index do you want to ingest into?",
1720
+ promptType: "multipleChoice",
1721
+ options: [...names, CREATE_NEW_INDEX],
1722
+ messages: ["We found these indices:"],
1723
+ error
1724
+ }) : await ctx.requestUserInput({
1725
+ prompt: "Name the index to create and ingest into:",
1726
+ promptType: "textInput",
1727
+ options: [],
1728
+ messages: [
1729
+ "This app has no existing indices \u2014 enter a name to create one."
1730
+ ],
1731
+ error
1732
+ });
1733
+ if (typeof selection !== "string") {
1734
+ throw new Error("selectIndex received an unexpected non-text result");
1735
+ }
1736
+ let candidate;
1737
+ if (!hasIndices) {
1738
+ candidate = selection.trim();
1739
+ } else if (selection === CREATE_NEW_INDEX) {
1740
+ const name = await ctx.requestUserInput({
1741
+ prompt: "Name the new index:",
1742
+ promptType: "textInput",
1743
+ options: []
1744
+ });
1745
+ if (typeof name !== "string") {
1746
+ throw new Error("selectIndex received an unexpected non-text result");
1724
1747
  }
1748
+ candidate = name.trim();
1749
+ } else {
1750
+ candidate = selection;
1751
+ }
1752
+ if (!candidate) {
1753
+ error = "Index name cannot be empty.";
1725
1754
  continue;
1726
1755
  }
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
- }
1756
+ chosen = candidate;
1757
+ }
1758
+ ctx.setUserInput("index", chosen);
1759
+ return { selection: chosen };
1760
+ };
1761
+
1762
+ // src/lib/agent.ts
1763
+ import { ToolLoopAgent, hasToolCall, Output as Output3 } from "ai";
1764
+ import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
1765
+ import "zod";
1766
+
1767
+ // src/lib/tools/index.ts
1768
+ import "zod";
1769
+
1770
+ // src/lib/tools/listFiles.ts
1771
+ import { tool } from "ai";
1772
+ import z6 from "zod";
1773
+ import { readdir } from "node:fs/promises";
1774
+
1775
+ // src/lib/tools/path.ts
1776
+ import { lstat } from "node:fs/promises";
1777
+ import { resolve as resolve2, relative, isAbsolute, dirname as dirname2, join as join5, sep } from "node:path";
1778
+ function resolveInRoot(ctx, path) {
1779
+ const target = resolve2(ctx.cwd, path);
1780
+ const rel = relative(ctx.root, target);
1781
+ if (rel.startsWith("..") || isAbsolute(rel)) {
1782
+ return {
1783
+ ok: false,
1784
+ error: `Refused: ${target} is outside the repo root (${ctx.root}).`
1785
+ };
1786
+ }
1787
+ return { ok: true, target };
1788
+ }
1789
+ async function hasSymlinkParent(ctx, target) {
1790
+ let current = ctx.root;
1791
+ const parts = relative(ctx.root, dirname2(target)).split(sep).filter(Boolean);
1792
+ for (const part of parts) {
1793
+ current = join5(current, part);
1794
+ try {
1795
+ if ((await lstat(current)).isSymbolicLink()) return true;
1796
+ } catch (err) {
1797
+ if (err.code === "ENOENT") return false;
1798
+ throw err;
1733
1799
  }
1734
1800
  }
1735
- return null;
1801
+ return false;
1736
1802
  }
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;
1803
+
1804
+ // src/lib/tools/listFiles.ts
1805
+ function listFilesTool(ctx) {
1806
+ return tool({
1807
+ 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.',
1808
+ inputSchema: z6.object({
1809
+ path: z6.string().optional().describe("Directory to list, relative to cwd (default: cwd)")
1810
+ }),
1811
+ execute: async ({ path = "." }) => {
1812
+ logger.info({ path }, "called listFiles tool");
1813
+ if (++ctx.counts.list > ctx.limits.list) {
1814
+ return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
1815
+ }
1816
+ const resolved2 = resolveInRoot(ctx, path);
1817
+ if (!resolved2.ok) return resolved2.error;
1818
+ try {
1819
+ const entries = await readdir(resolved2.target, { withFileTypes: true });
1820
+ return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
1821
+ } catch (err) {
1822
+ return `Error listing ${path}: ${err.message}`;
1823
+ }
1824
+ }
1825
+ });
1742
1826
  }
1743
1827
 
1744
- // src/lib/algoliaAppPicker.ts
1745
- function blockReasonFor(app) {
1746
- return app.status !== "active" ? "Inactive" : "Missing permissions";
1828
+ // src/lib/tools/changeDirectory.ts
1829
+ import { tool as tool2 } from "ai";
1830
+ import z7 from "zod";
1831
+ import { stat } from "node:fs/promises";
1832
+ function changeDirectoryTool(ctx) {
1833
+ return tool2({
1834
+ description: "Change the current working directory. Subsequent file operations resolve relative to it. Returns the new working directory.",
1835
+ inputSchema: z7.object({
1836
+ path: z7.string().describe("Directory to change into")
1837
+ }),
1838
+ execute: async ({ path }) => {
1839
+ logger.info({ path }, "called changeDirectory tool");
1840
+ const resolved2 = resolveInRoot(ctx, path);
1841
+ if (!resolved2.ok) return resolved2.error;
1842
+ try {
1843
+ const info = await stat(resolved2.target);
1844
+ if (!info.isDirectory()) {
1845
+ return `Error changing directory to ${path}: not a directory`;
1846
+ }
1847
+ ctx.cwd = resolved2.target;
1848
+ return `Changed working directory to ${ctx.cwd}`;
1849
+ } catch (err) {
1850
+ return `Error changing directory to ${path}: ${err.message}`;
1851
+ }
1852
+ }
1853
+ });
1747
1854
  }
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;
1855
+
1856
+ // src/lib/tools/reportStatus.ts
1857
+ import { tool as tool3 } from "ai";
1858
+ import z8 from "zod";
1859
+ function reportStatusTool(output) {
1860
+ return tool3({
1861
+ description: "Report the status of your execution. Return a reason in case of failure.",
1862
+ inputSchema: z8.object({
1863
+ status: z8.enum(["success", "fail"]),
1864
+ reason: z8.string().optional(),
1865
+ output
1866
+ }),
1867
+ execute: async ({ status, reason, output: output2 }) => {
1868
+ logger.info({ status, reason }, "called reportStatus tool");
1869
+ return { status, reason, output: output2 };
1870
+ }
1871
+ });
1753
1872
  }
1754
- function labelFor(app) {
1755
- return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
1873
+
1874
+ // src/lib/tools/readFile.ts
1875
+ import { tool as tool4 } from "ai";
1876
+ import z9 from "zod";
1877
+ import { readFile as readFile3 } from "node:fs/promises";
1878
+
1879
+ // src/lib/tools/env.ts
1880
+ import { basename } from "node:path";
1881
+ function isEnvFile(filePath) {
1882
+ const name = basename(filePath);
1883
+ return name === ".env" || name.startsWith(".env.");
1756
1884
  }
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
- }
1885
+ function isSecretEnvFile(filePath) {
1886
+ if (!isEnvFile(filePath)) return false;
1887
+ const name = basename(filePath);
1888
+ return !/\.(example|sample|template)$/.test(name);
1765
1889
  }
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");
1890
+
1891
+ // src/lib/tools/readFile.ts
1892
+ function redactEnvValues(content) {
1893
+ return content.split("\n").map((line) => {
1894
+ const match = line.match(/^(\s*(?:export\s+)?[\w.-]+\s*=)(.*)$/);
1895
+ if (!match) return line;
1896
+ const value = match[2].trim();
1897
+ if (value === "") return line;
1898
+ return `${match[1]}[REDACTED]`;
1899
+ }).join("\n");
1900
+ }
1901
+ function readFileTool(ctx) {
1902
+ return tool4({
1903
+ description: "Read the contents of a file at the given path",
1904
+ inputSchema: z9.object({
1905
+ filePath: z9.string().describe("Path to the file to read")
1906
+ }),
1907
+ execute: async ({ filePath }) => {
1908
+ if (++ctx.counts.read > ctx.limits.read) {
1909
+ return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
1910
+ }
1911
+ logger.info({ filePath }, "called readFile tool");
1912
+ const resolved2 = resolveInRoot(ctx, filePath);
1913
+ if (!resolved2.ok) return resolved2.error;
1914
+ try {
1915
+ const content = await readFile3(resolved2.target, "utf8");
1916
+ return isEnvFile(resolved2.target) ? redactEnvValues(content) : content;
1917
+ } catch (err) {
1918
+ return `Error reading ${filePath}: ${err.message}`;
1919
+ }
1791
1920
  }
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
- );
1921
+ });
1922
+ }
1923
+
1924
+ // src/lib/tools/writeFile.ts
1925
+ import { tool as tool5 } from "ai";
1926
+ import z10 from "zod";
1927
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1928
+ import { dirname as dirname3 } from "node:path";
1929
+ function writeFileTool(ctx) {
1930
+ return tool5({
1931
+ 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.",
1932
+ inputSchema: z10.object({
1933
+ filePath: z10.string().describe("Path to the file to write"),
1934
+ content: z10.string().describe("Content to write to the file")
1935
+ }),
1936
+ execute: async ({ filePath, content }) => {
1937
+ logger.info({ filePath }, "called writeFile tool");
1938
+ const resolved2 = resolveInRoot(ctx, filePath);
1939
+ if (resolved2.ok === false) return resolved2.error;
1940
+ if (isSecretEnvFile(resolved2.target)) {
1941
+ 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.`;
1942
+ }
1943
+ try {
1944
+ if (await hasSymlinkParent(ctx, resolved2.target)) {
1945
+ return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
1946
+ }
1947
+ await mkdir3(dirname3(resolved2.target), { recursive: true });
1948
+ await writeFile3(resolved2.target, content, "utf8");
1949
+ useWizard.getState().recordWrittenFile(resolved2.target);
1950
+ return `Wrote to ${filePath}`;
1951
+ } catch (err) {
1952
+ return `Error writing ${filePath}: ${err.message}`;
1953
+ }
1802
1954
  }
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
1955
  });
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
1956
  }
1847
1957
 
1958
+ // src/lib/tools/writeAlgoliaCredentials.ts
1959
+ import { tool as tool6 } from "ai";
1960
+ import z13 from "zod";
1961
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
1962
+ import { dirname as dirname4, relative as relative3 } from "node:path";
1963
+
1848
1964
  // src/lib/algoliaApiKey.ts
1849
- import { z as z7 } from "zod";
1965
+ import { z as z12 } from "zod";
1850
1966
 
1851
1967
  // src/lib/keychain.ts
1852
1968
  import { deletePassword, getPassword, setPassword } from "cross-keychain";
1853
- import { z as z6 } from "zod";
1969
+ import { z as z11 } from "zod";
1854
1970
  var SERVICE = "algolia-wizard";
1855
1971
  var account = (userId) => `api-keys:${userId}`;
1856
- var storedKeysSchema = z6.record(z6.string(), z6.string());
1972
+ var storedKeysSchema = z11.record(z11.string(), z11.string());
1857
1973
  function entryId(kind, index, appId) {
1858
1974
  return `${kind}:${appId}:${index}`;
1859
1975
  }
@@ -1945,9 +2061,9 @@ var WRITE_ACLS = [
1945
2061
  "editSettings",
1946
2062
  "listIndexes"
1947
2063
  ];
1948
- var createdKeySchema = z7.object({
1949
- key: z7.string().min(1).optional(),
1950
- value: z7.string().min(1).optional()
2064
+ var createdKeySchema = z12.object({
2065
+ key: z12.string().min(1).optional(),
2066
+ value: z12.string().min(1).optional()
1951
2067
  }).transform((o) => o.key ?? o.value);
1952
2068
  async function createKey(index, acls, description) {
1953
2069
  logger.info({ index, acls }, "creating an API key");
@@ -1970,355 +2086,81 @@ async function createKey(index, acls, description) {
1970
2086
  throw new Error("apikeys create returned output that is not valid JSON");
1971
2087
  }
1972
2088
  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.");
2089
+ if (!created) throw new Error("apikeys create returned no key value");
2090
+ return created;
2238
2091
  }
2239
- function isSecretEnvFile(filePath) {
2240
- if (!isEnvFile(filePath)) return false;
2241
- const name = basename(filePath);
2242
- return !/\.(example|sample|template)$/.test(name);
2092
+ async function keyExists(key) {
2093
+ try {
2094
+ await runAlgoliaCli(["apikeys", "get", key, "-o", "json"], { redact: key });
2095
+ return true;
2096
+ } catch (err) {
2097
+ return !/does not exist/i.test(err.message);
2098
+ }
2243
2099
  }
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");
2100
+ var resolved = /* @__PURE__ */ new Map();
2101
+ async function forgetResolvedKeys() {
2102
+ resolved.clear();
2103
+ const userId = await currentUserId();
2104
+ if (userId) await deleteStoredKeys(userId);
2254
2105
  }
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
- }
2274
- }
2106
+ function resolveKey(kind, index, appId, acls, description) {
2107
+ const userId = requireUserId();
2108
+ const cacheKey = `${userId}:${kind}:${appId}:${index}`;
2109
+ const cached = resolved.get(cacheKey);
2110
+ if (cached) return cached;
2111
+ const pending = provisionKey(
2112
+ kind,
2113
+ userId,
2114
+ index,
2115
+ appId,
2116
+ acls,
2117
+ description
2118
+ ).catch((err) => {
2119
+ resolved.delete(cacheKey);
2120
+ throw err;
2275
2121
  });
2122
+ resolved.set(cacheKey, pending);
2123
+ return pending;
2276
2124
  }
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
- }
2125
+ async function provisionKey(kind, userId, index, appId, acls, description) {
2126
+ const stored = await readStoredKey(kind, userId, index, appId);
2127
+ if (stored) {
2128
+ if (await keyExists(stored)) {
2129
+ logger.info({ kind, index, appId }, "reusing the stored API key");
2130
+ return { key: stored, source: "keychain" };
2308
2131
  }
2309
- });
2132
+ logger.info(
2133
+ { kind, index, appId },
2134
+ "the stored API key no longer exists; creating another"
2135
+ );
2136
+ }
2137
+ const key = await createKey(index, acls, description);
2138
+ await storeKey(kind, userId, index, appId, key);
2139
+ return { key, source: "created" };
2140
+ }
2141
+ function resolveWriteKey(index, appId) {
2142
+ return resolveKey(
2143
+ "write",
2144
+ index,
2145
+ appId,
2146
+ WRITE_ACLS,
2147
+ `Algolia Wizard write key for ${index} index`
2148
+ );
2149
+ }
2150
+ async function resolveSearchOnlyKey(index, appId) {
2151
+ return resolveKey(
2152
+ "search",
2153
+ index,
2154
+ appId,
2155
+ ["search"],
2156
+ `Algolia Wizard search-only key for ${index} index`
2157
+ );
2310
2158
  }
2311
-
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";
2317
2159
 
2318
2160
  // src/lib/gitignore.ts
2319
2161
  import { execFile } from "node:child_process";
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";
2162
+ import { lstat as lstat2, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
2163
+ import { join as join6, relative as relative2 } from "node:path";
2322
2164
  var GIT_ENV_OVERRIDES = [
2323
2165
  "GIT_DIR",
2324
2166
  "GIT_WORK_TREE",
@@ -2370,7 +2212,7 @@ async function ensureGitIgnored(root, target) {
2370
2212
  if (ignoredByRule === void 0) return "unknown";
2371
2213
  if (ignoredByRule) return tracked ? "tracked" : "covered";
2372
2214
  const pattern = relative2(root, target);
2373
- const gitIgnore = join7(root, ".gitignore");
2215
+ const gitIgnore = join6(root, ".gitignore");
2374
2216
  try {
2375
2217
  const link = await lstat2(gitIgnore).catch(() => null);
2376
2218
  if (link?.isSymbolicLink()) {
@@ -2380,7 +2222,7 @@ async function ensureGitIgnored(root, target) {
2380
2222
  );
2381
2223
  return "unknown";
2382
2224
  }
2383
- const existing = link ? await readFile5(gitIgnore, "utf8") : "";
2225
+ const existing = link ? await readFile4(gitIgnore, "utf8") : "";
2384
2226
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2385
2227
  await writeFile4(gitIgnore, `${existing}${prefix}${pattern}
2386
2228
  `, "utf8");
@@ -2449,7 +2291,7 @@ function writeCredentialsTool(ctx) {
2449
2291
  return `Refused: ${resolved2.target} is outside the repo root (${ctx.root}).`;
2450
2292
  }
2451
2293
  try {
2452
- existing = await readFile6(resolved2.target, "utf8");
2294
+ existing = await readFile5(resolved2.target, "utf8");
2453
2295
  } catch (err) {
2454
2296
  if (err.code !== "ENOENT") throw err;
2455
2297
  }
@@ -2539,8 +2381,8 @@ async function gitIgnoreOutcome(ctx, target) {
2539
2381
  // src/lib/tools/searchFiles.ts
2540
2382
  import { tool as tool7 } from "ai";
2541
2383
  import z14 from "zod";
2542
- import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
2543
- import { join as join8 } from "node:path";
2384
+ import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
2385
+ import { join as join7 } from "node:path";
2544
2386
  var MAX_QUERY_LENGTH = 1e3;
2545
2387
  var SKIP_DIRS = /* @__PURE__ */ new Set([
2546
2388
  "node_modules",
@@ -2555,7 +2397,7 @@ async function walkFiles(dir) {
2555
2397
  const out = [];
2556
2398
  for (const e of await readdir2(dir, { withFileTypes: true })) {
2557
2399
  if (e.name.startsWith(".") || SKIP_DIRS.has(e.name)) continue;
2558
- const full = join8(dir, e.name);
2400
+ const full = join7(dir, e.name);
2559
2401
  if (e.isDirectory()) out.push(...await walkFiles(full));
2560
2402
  else if (e.isFile()) out.push(full);
2561
2403
  }
@@ -2588,7 +2430,7 @@ function searchFilesTool(ctx) {
2588
2430
  for (const file of await walkFiles(resolved2.target)) {
2589
2431
  let content;
2590
2432
  try {
2591
- content = await readFile7(file, "utf8");
2433
+ content = await readFile6(file, "utf8");
2592
2434
  } catch {
2593
2435
  continue;
2594
2436
  }
@@ -3488,7 +3330,7 @@ async function runAnalysis(mode, extraInstructions = []) {
3488
3330
  // package.json
3489
3331
  var package_default = {
3490
3332
  name: "@algolia/wizard",
3491
- version: "0.38.0-rc.131.265",
3333
+ version: "0.38.0-rc.131.267",
3492
3334
  description: "Magically implement Algolia functionality in your codebase",
3493
3335
  type: "module",
3494
3336
  engines: {
@@ -3896,12 +3738,12 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
3896
3738
  // src/actions/implement.ts
3897
3739
  import z28 from "zod";
3898
3740
  import { mkdir as mkdir7 } from "node:fs/promises";
3899
- import { join as join11, relative as relative6 } from "node:path";
3741
+ import { join as join10, relative as relative6 } from "node:path";
3900
3742
 
3901
3743
  // src/lib/git.ts
3902
3744
  import { execFile as execFile2 } from "node:child_process";
3903
3745
  import { copyFile, mkdir as mkdir6, stat as stat3 } from "node:fs/promises";
3904
- import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
3746
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
3905
3747
  var MAX_BUFFER = 32 * 1024 * 1024;
3906
3748
  function git(args) {
3907
3749
  return new Promise((resolve4, reject) => {
@@ -3938,8 +3780,8 @@ async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
3938
3780
  } catch {
3939
3781
  return { ok: false, reason: `"${sourcePath}" does not exist` };
3940
3782
  }
3941
- const relPath = join9(ingestDir, basename2(source));
3942
- const dest = join9(repoRoot, relPath);
3783
+ const relPath = join8(ingestDir, basename2(source));
3784
+ const dest = join8(repoRoot, relPath);
3943
3785
  if (resolve3(source) === resolve3(dest)) {
3944
3786
  return { ok: true, relPath };
3945
3787
  }
@@ -3978,13 +3820,13 @@ function toRootRelative(p) {
3978
3820
 
3979
3821
  // src/lib/algoliaDocs.ts
3980
3822
  import { readFileSync, readdirSync, existsSync } from "node:fs";
3981
- import { dirname as dirname7, join as join10 } from "node:path";
3823
+ import { dirname as dirname7, join as join9 } from "node:path";
3982
3824
  import { fileURLToPath } from "node:url";
3983
- var DOCS_SUBPATH = join10("docs", "algolia-sdk");
3825
+ var DOCS_SUBPATH = join9("docs", "algolia-sdk");
3984
3826
  function findDocsDir() {
3985
3827
  let dir = dirname7(fileURLToPath(import.meta.url));
3986
3828
  for (; ; ) {
3987
- const candidate = join10(dir, DOCS_SUBPATH);
3829
+ const candidate = join9(dir, DOCS_SUBPATH);
3988
3830
  if (existsSync(candidate)) return candidate;
3989
3831
  const parent = dirname7(dir);
3990
3832
  if (parent === dir) return void 0;
@@ -4007,7 +3849,7 @@ function loadAlgoliaDoc(language) {
4007
3849
  );
4008
3850
  return "";
4009
3851
  }
4010
- return readFileSync(join10(docsDir, files[0]), "utf8").trim();
3852
+ return readFileSync(join9(docsDir, files[0]), "utf8").trim();
4011
3853
  }
4012
3854
  function getNamedDoc(name, language) {
4013
3855
  const docsDir = findDocsDir();
@@ -4015,7 +3857,7 @@ function getNamedDoc(name, language) {
4015
3857
  logger.warn("docs/algolia-sdk not found");
4016
3858
  return "";
4017
3859
  }
4018
- const file = join10(docsDir, `${name}-${language}.md`);
3860
+ const file = join9(docsDir, `${name}-${language}.md`);
4019
3861
  if (!existsSync(file)) {
4020
3862
  logger.warn({ name, language }, "named SDK reference not found");
4021
3863
  return "";
@@ -4362,7 +4204,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
4362
4204
  useWizard.getState().setTargetIndex(targetIndex ?? null);
4363
4205
  await assertGitRepoWithHead(repoRoot);
4364
4206
  if (useCases.includes("ingestion")) {
4365
- await mkdir7(join11(repoRoot, INGEST_DIR), { recursive: true });
4207
+ await mkdir7(join10(repoRoot, INGEST_DIR), { recursive: true });
4366
4208
  }
4367
4209
  const normalized = normalizeFindingPaths(findings);
4368
4210
  const confirmed2 = normalized.confirmedEntities;
@@ -4592,7 +4434,7 @@ ${detail}` : ""}`
4592
4434
  if (searchConfigFile) {
4593
4435
  const ignoreStatus = await gitIgnoreStatus(
4594
4436
  repoRoot,
4595
- join11(repoRoot, searchConfigFile)
4437
+ join10(repoRoot, searchConfigFile)
4596
4438
  );
4597
4439
  if (ignoreStatus === "covered") {
4598
4440
  summaries.push(
@@ -4738,7 +4580,7 @@ function getWorkflow(id) {
4738
4580
  }
4739
4581
 
4740
4582
  // src/ui/Welcome.tsx
4741
- import { dirname as dirname8, join as join12 } from "node:path";
4583
+ import { dirname as dirname8, join as join11 } from "node:path";
4742
4584
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4743
4585
  import { useState as useState7 } from "react";
4744
4586
  import { Box as Box10, Spacer, Text as Text10, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
@@ -4770,7 +4612,7 @@ var sidebarItems = [
4770
4612
  // src/ui/Welcome.tsx
4771
4613
  import Image, { TerminalInfoContext, defaultTerminalInfo } from "ink-picture";
4772
4614
  import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
4773
- var IMAGE_PATH = join12(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
4615
+ var IMAGE_PATH = join11(dirname8(fileURLToPath2(import.meta.url)), "algolia.png");
4774
4616
  var TERMINAL_INFO = {
4775
4617
  ...defaultTerminalInfo,
4776
4618
  supportsUnicode: true,
@@ -5701,7 +5543,7 @@ function App() {
5701
5543
  ] }),
5702
5544
  settingUpAppId && /* @__PURE__ */ jsx17(Box19, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.strong, bold: true, children: [
5703
5545
  /* @__PURE__ */ jsx17(Spinner2, { type: "dots" }),
5704
- " Setting up wizard using app",
5546
+ " Setting up Wizard using app",
5705
5547
  " ",
5706
5548
  settingUpAppId
5707
5549
  ] }) }),
@@ -5737,6 +5579,151 @@ function App() {
5737
5579
  );
5738
5580
  }
5739
5581
 
5582
+ // src/lib/envAppId.ts
5583
+ import { readFile as readFile7 } from "node:fs/promises";
5584
+ import { join as join12 } from "node:path";
5585
+ var ENV_FILES = [".env", ".env.local"];
5586
+ var APP_ID_LINE = /^[ \t]*(?:export[ \t]+)?([A-Z0-9_]*ALGOLIA_APP(?:LICATION)?_ID)[ \t]*=[ \t]*(.*)$/gm;
5587
+ async function findEnvApplicationId(root = process.cwd()) {
5588
+ for (const file of ENV_FILES) {
5589
+ let content;
5590
+ try {
5591
+ content = await readFile7(join12(root, file), "utf8");
5592
+ } catch (err) {
5593
+ if (err.code !== "ENOENT") {
5594
+ logger.warn(
5595
+ { file, err },
5596
+ "could not read env file for an application id"
5597
+ );
5598
+ }
5599
+ continue;
5600
+ }
5601
+ for (const [, name, raw] of content.matchAll(APP_ID_LINE)) {
5602
+ const id = readValue(raw);
5603
+ if (id) {
5604
+ logger.info({ file, name, app: id }, "found an application id in env");
5605
+ return { id, name, file };
5606
+ }
5607
+ }
5608
+ }
5609
+ return null;
5610
+ }
5611
+ function readValue(raw) {
5612
+ const trimmed = raw.trim();
5613
+ const quoted = trimmed.match(/^(['"])(.*)\1/);
5614
+ const value = quoted ? quoted[2].trim() : trimmed.replace(/\s+#.*$/, "").trim();
5615
+ return value.length > 0 && !value.startsWith("<") ? value : null;
5616
+ }
5617
+
5618
+ // src/lib/algoliaAppPicker.ts
5619
+ function blockReasonFor(app) {
5620
+ return app.status !== "active" ? "Paused" : "Missing permissions";
5621
+ }
5622
+ function secondaryFor(app) {
5623
+ if (!canSelectApplication(app)) {
5624
+ return { kind: "text", value: blockReasonFor(app) };
5625
+ }
5626
+ return app.plan ? { kind: "badge", value: app.plan } : void 0;
5627
+ }
5628
+ function labelFor(app) {
5629
+ return app.name.trim() ? `${app.name} \u2014 ${app.id}` : app.id;
5630
+ }
5631
+ async function selectAndReport(app) {
5632
+ const store = useWizard.getState();
5633
+ store.setSettingUpApp(app.id);
5634
+ try {
5635
+ return await selectApplication(app.id);
5636
+ } finally {
5637
+ store.setSettingUpApp(null);
5638
+ }
5639
+ }
5640
+ async function promptForApplication(leadIn = []) {
5641
+ const store = useWizard.getState();
5642
+ const unordered = await listApplications();
5643
+ if (unordered.length === 0) {
5644
+ throw new Error(
5645
+ "This Algolia account has no applications. Create one in the Algolia dashboard, or with `npx @algolia/cli@latest application create`, then restart the wizard."
5646
+ );
5647
+ }
5648
+ if (!unordered.some(canSelectApplication)) {
5649
+ throw new Error(
5650
+ "None of the applications on this Algolia account are usable \u2014 each is either paused or missing key-management access. Activate one, or grant it the `keys` ACL, then restart the wizard."
5651
+ );
5652
+ }
5653
+ const apps = [
5654
+ ...unordered.filter(canSelectApplication),
5655
+ ...unordered.filter((app) => !canSelectApplication(app))
5656
+ ];
5657
+ const messages = [...leadIn];
5658
+ for (; ; ) {
5659
+ const choice = await store.requestUserInput({
5660
+ prompt: "Which Algolia application should the wizard work in?",
5661
+ promptType: "multipleChoice",
5662
+ options: apps.map(labelFor),
5663
+ secondary: apps.map(secondaryFor),
5664
+ disabled: apps.map((app) => !canSelectApplication(app)),
5665
+ messages
5666
+ });
5667
+ const chosen = apps.find((app) => labelFor(app) === choice);
5668
+ if (!chosen || !canSelectApplication(chosen)) {
5669
+ throw new Error("Application picker received an unexpected selection");
5670
+ }
5671
+ try {
5672
+ return await selectAndReport(chosen);
5673
+ } catch (err) {
5674
+ logger.warn(
5675
+ { app: chosen.id, err: err.message },
5676
+ "application select failed; re-prompting"
5677
+ );
5678
+ messages.push(
5679
+ `Could not select \u201C${labelFor(chosen)}\u201D. It may have been removed \u2014 pick another.`
5680
+ );
5681
+ }
5682
+ }
5683
+ }
5684
+ async function confirmEnvApplication(env, current) {
5685
+ const useEnv = `Use ${env.id} (from ${env.file})`;
5686
+ const choice = await useWizard.getState().requestUserInput({
5687
+ prompt: "Select an application",
5688
+ promptType: "multipleChoice",
5689
+ options: [
5690
+ useEnv,
5691
+ current ? `Use ${labelFor(current)} (already selected)` : "Pick a different application"
5692
+ ],
5693
+ messages: [
5694
+ `${env.file} already sets ${env.name}=${env.id}.`,
5695
+ "Which Algolia application would you like to use?"
5696
+ ]
5697
+ });
5698
+ if (choice === useEnv) return selectEnvApplication(env);
5699
+ return current ? selectAndReport(current) : null;
5700
+ }
5701
+ async function selectEnvApplication(env) {
5702
+ try {
5703
+ return await selectAndReport({ id: env.id, name: "" });
5704
+ } catch (err) {
5705
+ logger.warn(
5706
+ { app: env.id, err: err.message },
5707
+ "could not select the application named in env; falling back to the picker"
5708
+ );
5709
+ return promptForApplication([
5710
+ `Could not select ${env.id} from ${env.file} \u2014 it may have been removed, or this account may not have access to it.`
5711
+ ]);
5712
+ }
5713
+ }
5714
+ async function ensureApplication(isResuming) {
5715
+ const current = await currentApplication();
5716
+ if (isResuming) {
5717
+ return current ?? await promptForApplication();
5718
+ }
5719
+ const env = await findEnvApplicationId();
5720
+ if (env && env.id !== current?.id) {
5721
+ const chosen = await confirmEnvApplication(env, current);
5722
+ if (chosen) return chosen;
5723
+ }
5724
+ return promptForApplication();
5725
+ }
5726
+
5740
5727
  // src/lib/seed.ts
5741
5728
  var projectScan2 = {
5742
5729
  languages: [{ name: "TypeScript", version: "5.7.2" }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.38.0-rc.131.265",
3
+ "version": "0.38.0-rc.131.267",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {