@algolia/wizard 0.38.0-rc.131.265 → 0.38.0-rc.131.266
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +476 -489
- 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/
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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
|
|
1801
|
+
return false;
|
|
1736
1802
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
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/
|
|
1745
|
-
|
|
1746
|
-
|
|
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
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
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
|
-
|
|
1755
|
-
|
|
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
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
);
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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
|
|
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
|
|
1969
|
+
import { z as z11 } from "zod";
|
|
1854
1970
|
var SERVICE = "algolia-wizard";
|
|
1855
1971
|
var account = (userId) => `api-keys:${userId}`;
|
|
1856
|
-
var storedKeysSchema =
|
|
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 =
|
|
1949
|
-
key:
|
|
1950
|
-
value:
|
|
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");
|
|
@@ -1971,354 +2087,80 @@ async function createKey(index, acls, description) {
|
|
|
1971
2087
|
}
|
|
1972
2088
|
const created = createdKeySchema.parse(payload);
|
|
1973
2089
|
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.");
|
|
2090
|
+
return created;
|
|
2238
2091
|
}
|
|
2239
|
-
function
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
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
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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
|
|
2321
|
-
import { join as
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
2543
|
-
import { join as
|
|
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 =
|
|
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
|
|
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.
|
|
3333
|
+
version: "0.38.0-rc.131.266",
|
|
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
|
|
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
|
|
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 =
|
|
3942
|
-
const dest =
|
|
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
|
|
3823
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
3982
3824
|
import { fileURLToPath } from "node:url";
|
|
3983
|
-
var DOCS_SUBPATH =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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" ? "Inactive" : "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 inactive 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" }],
|