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