@algolia/wizard 0.23.0 → 0.24.0-rc.117.212
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/README.md +0 -14
- package/dist/main.js +669 -518
- package/package.json +2 -1
package/dist/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/ui/App.tsx
|
|
7
|
-
import { Box as
|
|
7
|
+
import { Box as Box19, Text as Text19, useApp, useInput as useInput7, useWindowSize as useWindowSize7 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -251,6 +251,7 @@ var useWizard = create((set, get) => ({
|
|
|
251
251
|
_noticeTimer: null,
|
|
252
252
|
cliOutput: [],
|
|
253
253
|
targetIndex: null,
|
|
254
|
+
writtenFiles: [],
|
|
254
255
|
logs: [],
|
|
255
256
|
error: null,
|
|
256
257
|
inputReq: null,
|
|
@@ -344,6 +345,8 @@ var useWizard = create((set, get) => ({
|
|
|
344
345
|
})),
|
|
345
346
|
clearCliOutput: () => set({ cliOutput: [] }),
|
|
346
347
|
setTargetIndex: (index) => set({ targetIndex: index }),
|
|
348
|
+
recordWrittenFile: (path) => set((s) => ({ writtenFiles: [...s.writtenFiles, path] })),
|
|
349
|
+
clearWrittenFiles: () => set({ writtenFiles: [] }),
|
|
347
350
|
logStart: (kind, name, input) => {
|
|
348
351
|
const id = nanoid();
|
|
349
352
|
set((s) => ({
|
|
@@ -391,6 +394,7 @@ var useWizard = create((set, get) => ({
|
|
|
391
394
|
notices: [],
|
|
392
395
|
cliOutput: [],
|
|
393
396
|
targetIndex: null,
|
|
397
|
+
writtenFiles: [],
|
|
394
398
|
logs: [],
|
|
395
399
|
error: null,
|
|
396
400
|
inputReq: null,
|
|
@@ -484,7 +488,7 @@ function CliOutput() {
|
|
|
484
488
|
}
|
|
485
489
|
|
|
486
490
|
// src/ui/Notices.tsx
|
|
487
|
-
import { Box as Box3, Text as Text3
|
|
491
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
488
492
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
489
493
|
|
|
490
494
|
// src/ui/Table.tsx
|
|
@@ -543,31 +547,6 @@ var truncate = (s, width) => s.length <= width ? s : width <= 1 ? s.slice(0, wid
|
|
|
543
547
|
// src/ui/Notices.tsx
|
|
544
548
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
545
549
|
var AGENT_MARKER = "\u2726";
|
|
546
|
-
var RESERVED_ROWS2 = 14;
|
|
547
|
-
var PANEL_TEXT_WIDTH2 = 45;
|
|
548
|
-
function messageLineCount(text) {
|
|
549
|
-
return Math.max(1, Math.ceil(text.length / PANEL_TEXT_WIDTH2));
|
|
550
|
-
}
|
|
551
|
-
function noticeLineCount(notice) {
|
|
552
|
-
const messageLines = (notice.messages ?? []).reduce((sum, m) => {
|
|
553
|
-
const text = typeof m === "string" ? m : m.text;
|
|
554
|
-
return sum + messageLineCount(text);
|
|
555
|
-
}, 0);
|
|
556
|
-
const tableLines = notice.table ? notice.table.rows.length + 4 : 0;
|
|
557
|
-
return messageLines + tableLines;
|
|
558
|
-
}
|
|
559
|
-
function fitVisibleNotices(notices, windowRows) {
|
|
560
|
-
const budget = Math.max(windowRows - RESERVED_ROWS2, 3);
|
|
561
|
-
let used = 0;
|
|
562
|
-
let count = 0;
|
|
563
|
-
for (let i = notices.length - 1; i >= 0; i--) {
|
|
564
|
-
const height = noticeLineCount(notices[i]) + (count > 0 ? 1 : 0);
|
|
565
|
-
if (count > 0 && used + height > budget) break;
|
|
566
|
-
used += height;
|
|
567
|
-
count++;
|
|
568
|
-
}
|
|
569
|
-
return notices.slice(notices.length - count);
|
|
570
|
-
}
|
|
571
550
|
var PULSE_STEPS = 12;
|
|
572
551
|
var PULSE_STEP_MS = 150;
|
|
573
552
|
var PULSE_COLORS = Array.from(
|
|
@@ -590,8 +569,7 @@ function parseHex(hex) {
|
|
|
590
569
|
}
|
|
591
570
|
function Notices() {
|
|
592
571
|
const notices = useWizard((s) => s.notices);
|
|
593
|
-
const
|
|
594
|
-
const visible = fitVisibleNotices(notices, windowRows);
|
|
572
|
+
const latest = notices[notices.length - 1];
|
|
595
573
|
const [pulseStep, setPulseStep] = useState2(0);
|
|
596
574
|
useEffect2(() => {
|
|
597
575
|
let direction = 1;
|
|
@@ -605,30 +583,40 @@ function Notices() {
|
|
|
605
583
|
}, PULSE_STEP_MS);
|
|
606
584
|
return () => clearInterval(id);
|
|
607
585
|
}, []);
|
|
608
|
-
if (!
|
|
586
|
+
if (!latest) return null;
|
|
609
587
|
const pulseColor = PULSE_COLORS[pulseStep];
|
|
610
|
-
return /* @__PURE__ */
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
588
|
+
return /* @__PURE__ */ jsxs2(
|
|
589
|
+
Box3,
|
|
590
|
+
{
|
|
591
|
+
borderColor: COLORS.border,
|
|
592
|
+
borderStyle: "single",
|
|
593
|
+
borderBackgroundColor: COLORS.bg.main,
|
|
594
|
+
borderLeft: false,
|
|
595
|
+
borderRight: false,
|
|
596
|
+
borderBottom: false,
|
|
597
|
+
paddingTop: 1,
|
|
598
|
+
flexDirection: "column",
|
|
599
|
+
children: [
|
|
600
|
+
latest.messages?.map((m, j) => {
|
|
601
|
+
const line = typeof m === "string" ? { text: m } : m;
|
|
602
|
+
const prefix = j === 0 ? `${AGENT_MARKER} ` : " ";
|
|
603
|
+
return /* @__PURE__ */ jsxs2(
|
|
604
|
+
Text3,
|
|
605
|
+
{
|
|
606
|
+
color: !line.color ? pulseColor : line.color ?? COLORS.dim,
|
|
607
|
+
bold: line.bold,
|
|
608
|
+
children: [
|
|
609
|
+
prefix,
|
|
610
|
+
line.text
|
|
611
|
+
]
|
|
612
|
+
},
|
|
613
|
+
`notice-${j}`
|
|
614
|
+
);
|
|
615
|
+
}),
|
|
616
|
+
latest.table && /* @__PURE__ */ jsx2(Table, { columns: latest.table.columns, rows: latest.table.rows })
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
);
|
|
632
620
|
}
|
|
633
621
|
|
|
634
622
|
// src/ui/PromptInput.tsx
|
|
@@ -709,11 +697,11 @@ function CommandApproval({
|
|
|
709
697
|
}
|
|
710
698
|
|
|
711
699
|
// src/ui/SelectPrompt.tsx
|
|
712
|
-
import { Box as Box8, Text as Text8, useInput as useInput2, useWindowSize as
|
|
700
|
+
import { Box as Box8, Text as Text8, useInput as useInput2, useWindowSize as useWindowSize4 } from "ink";
|
|
713
701
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
714
702
|
|
|
715
703
|
// src/ui/ScrollView.tsx
|
|
716
|
-
import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as
|
|
704
|
+
import { Box as Box6, Text as Text6, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
|
|
717
705
|
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
718
706
|
import { jsxs as jsxs5 } from "react/jsx-runtime";
|
|
719
707
|
var INDICATOR_ROWS = 2;
|
|
@@ -730,7 +718,7 @@ function useScrollWindow({
|
|
|
730
718
|
followBottom = false
|
|
731
719
|
}) {
|
|
732
720
|
const viewportRef = useRef2(null);
|
|
733
|
-
const { columns } =
|
|
721
|
+
const { columns } = useWindowSize3();
|
|
734
722
|
const [size, setSize] = useState3(
|
|
735
723
|
null
|
|
736
724
|
);
|
|
@@ -871,7 +859,7 @@ function SelectPrompt({
|
|
|
871
859
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
872
860
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
873
861
|
const containerRef = useRef3(null);
|
|
874
|
-
const { columns } =
|
|
862
|
+
const { columns } = useWindowSize4();
|
|
875
863
|
const [width, setWidth] = useState4(columns);
|
|
876
864
|
useLayoutEffect2(() => {
|
|
877
865
|
if (!containerRef.current) return;
|
|
@@ -1100,7 +1088,7 @@ function PromptInput() {
|
|
|
1100
1088
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
1101
1089
|
import { fileURLToPath } from "node:url";
|
|
1102
1090
|
import { useState as useState6 } from "react";
|
|
1103
|
-
import { Box as Box10, Spacer, Text as Text10, useInput as useInput4, useWindowSize as
|
|
1091
|
+
import { Box as Box10, Spacer, Text as Text10, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
|
|
1104
1092
|
|
|
1105
1093
|
// src/ui/copy/welcome.ts
|
|
1106
1094
|
var sidebarItems = [
|
|
@@ -1153,7 +1141,7 @@ function SidebarItem({
|
|
|
1153
1141
|
function Welcome() {
|
|
1154
1142
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1155
1143
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
1156
|
-
const { rows } =
|
|
1144
|
+
const { rows } = useWindowSize5();
|
|
1157
1145
|
const actions = [
|
|
1158
1146
|
{ label: "start wizard", run: confirmStart },
|
|
1159
1147
|
{ label: "learn more", run: openLearnMore }
|
|
@@ -1246,7 +1234,7 @@ function Welcome() {
|
|
|
1246
1234
|
|
|
1247
1235
|
// src/ui/LearnMore.tsx
|
|
1248
1236
|
import { Fragment as Fragment2 } from "react";
|
|
1249
|
-
import { Box as Box11, Text as Text11, useInput as useInput5, useWindowSize as
|
|
1237
|
+
import { Box as Box11, Text as Text11, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
|
|
1250
1238
|
|
|
1251
1239
|
// src/ui/copy/learn-more.ts
|
|
1252
1240
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1259,7 +1247,7 @@ var accessItems = [
|
|
|
1259
1247
|
{
|
|
1260
1248
|
tag: "WRITE",
|
|
1261
1249
|
title: "Code changes",
|
|
1262
|
-
description: "creates & edits files (search UI, config) in
|
|
1250
|
+
description: "creates & edits files (search UI, config) directly in your branch."
|
|
1263
1251
|
},
|
|
1264
1252
|
{
|
|
1265
1253
|
tag: "EXEC",
|
|
@@ -1274,7 +1262,7 @@ var accessItems = [
|
|
|
1274
1262
|
{
|
|
1275
1263
|
tag: "KEY",
|
|
1276
1264
|
title: "Credentials",
|
|
1277
|
-
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in
|
|
1265
|
+
description: "writes your Algolia app id and a search-only key (safe to expose) to .env in your project."
|
|
1278
1266
|
}
|
|
1279
1267
|
];
|
|
1280
1268
|
var neverItems = [
|
|
@@ -1316,7 +1304,7 @@ function NeverLine({
|
|
|
1316
1304
|
function LearnMore() {
|
|
1317
1305
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1318
1306
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1319
|
-
const { columns } =
|
|
1307
|
+
const { columns } = useWindowSize6();
|
|
1320
1308
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1321
1309
|
useInput5((_input, key) => {
|
|
1322
1310
|
if (key.escape) backToHome();
|
|
@@ -1526,7 +1514,7 @@ function Ribbon() {
|
|
|
1526
1514
|
}
|
|
1527
1515
|
|
|
1528
1516
|
// src/ui/App.tsx
|
|
1529
|
-
import { useState as
|
|
1517
|
+
import { useState as useState8 } from "react";
|
|
1530
1518
|
|
|
1531
1519
|
// src/ui/Logs.tsx
|
|
1532
1520
|
import { Box as Box16, Text as Text16, useInput as useInput6 } from "ink";
|
|
@@ -1776,13 +1764,287 @@ function track(event, payload) {
|
|
|
1776
1764
|
});
|
|
1777
1765
|
}
|
|
1778
1766
|
|
|
1767
|
+
// src/ui/Tips.tsx
|
|
1768
|
+
import { useEffect as useEffect3, useState as useState7 } from "react";
|
|
1769
|
+
import { Box as Box18, Text as Text18 } from "ink";
|
|
1770
|
+
import terminalLink from "terminal-link";
|
|
1771
|
+
|
|
1772
|
+
// src/ui/Code.tsx
|
|
1773
|
+
import { Box as Box17, Text as Text17 } from "ink";
|
|
1774
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1775
|
+
var TOKEN_RE = /("(?:\\.|[^"\\])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\]:,])/g;
|
|
1776
|
+
function highlightJson(json) {
|
|
1777
|
+
const parts = json.split(TOKEN_RE);
|
|
1778
|
+
return parts.map((part, i) => {
|
|
1779
|
+
if (!part) return null;
|
|
1780
|
+
if (part[0] === '"') {
|
|
1781
|
+
const next = parts.slice(i + 1).find((p) => p.trim());
|
|
1782
|
+
const isKey = next?.trimStart().startsWith(":");
|
|
1783
|
+
return /* @__PURE__ */ jsx15(Text17, { color: isKey ? "cyan" : "green", children: part }, i);
|
|
1784
|
+
}
|
|
1785
|
+
if (part === "true" || part === "false" || part === "null") {
|
|
1786
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "magenta", children: part }, i);
|
|
1787
|
+
}
|
|
1788
|
+
if (/^-?\d/.test(part)) {
|
|
1789
|
+
return /* @__PURE__ */ jsx15(Text17, { color: "yellow", children: part }, i);
|
|
1790
|
+
}
|
|
1791
|
+
if (/^[{}[\]:,]$/.test(part)) {
|
|
1792
|
+
return /* @__PURE__ */ jsx15(Text17, { dimColor: true, children: part }, i);
|
|
1793
|
+
}
|
|
1794
|
+
return /* @__PURE__ */ jsx15(Text17, { children: part }, i);
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
function Code({ children }) {
|
|
1798
|
+
return /* @__PURE__ */ jsx15(Box17, { children: /* @__PURE__ */ jsx15(Text17, { children: highlightJson(children) }) });
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// src/ui/copy/tips.ts
|
|
1802
|
+
var tips = [
|
|
1803
|
+
{
|
|
1804
|
+
title: "An Application is like your library. Indices are the books in it.",
|
|
1805
|
+
chunks: [
|
|
1806
|
+
{
|
|
1807
|
+
type: "text",
|
|
1808
|
+
value: "Applications hold your API keys and Indices."
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
type: "text",
|
|
1812
|
+
value: "Each index is a searchable collection of records (think: products, articles, orders, concerts)."
|
|
1813
|
+
}
|
|
1814
|
+
]
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
title: "A Record is a JSON object inside of an index.",
|
|
1818
|
+
chunks: [
|
|
1819
|
+
{
|
|
1820
|
+
type: "text",
|
|
1821
|
+
value: "Every object you index is just a JSON document with a unique objectID."
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
type: "text",
|
|
1825
|
+
value: "No fixed schema, no hard requirements."
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
type: "text",
|
|
1829
|
+
value: "Everything else is up to you:"
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
type: "code",
|
|
1833
|
+
value: JSON.stringify(
|
|
1834
|
+
{
|
|
1835
|
+
objectID: "sku_48213",
|
|
1836
|
+
name: "Trail Running Shoe",
|
|
1837
|
+
brand: "Northline",
|
|
1838
|
+
categories: ["Footwear", "Running", "Mens"],
|
|
1839
|
+
price: 129.99,
|
|
1840
|
+
in_stock: true,
|
|
1841
|
+
rating: 4.6
|
|
1842
|
+
},
|
|
1843
|
+
null,
|
|
1844
|
+
2
|
|
1845
|
+
)
|
|
1846
|
+
}
|
|
1847
|
+
]
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
title: "Speed isn't a feature, it's our business",
|
|
1851
|
+
chunks: [
|
|
1852
|
+
{
|
|
1853
|
+
type: "text",
|
|
1854
|
+
value: "Algolia's engine processes most search queries in 1 to 50 milliseconds."
|
|
1855
|
+
},
|
|
1856
|
+
{
|
|
1857
|
+
type: "text",
|
|
1858
|
+
value: "This is why our search-as-you-type experience feels instant."
|
|
1859
|
+
}
|
|
1860
|
+
]
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
title: "Searchable attributes are your relevance dial #1",
|
|
1864
|
+
chunks: [
|
|
1865
|
+
{
|
|
1866
|
+
type: "text",
|
|
1867
|
+
value: "Order matters. Attributes listed first in"
|
|
1868
|
+
},
|
|
1869
|
+
{ type: "codeword", value: "searchableAttributes" },
|
|
1870
|
+
{ type: "text", value: "carry more ranking weight." },
|
|
1871
|
+
{
|
|
1872
|
+
type: "text",
|
|
1873
|
+
value: "This is the single highest-leverage lever new users don't know exists."
|
|
1874
|
+
}
|
|
1875
|
+
]
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
title: "Facets aren't just filters, they help build your UI",
|
|
1879
|
+
chunks: [
|
|
1880
|
+
{ type: "codeword", value: "attributesForFaceting" },
|
|
1881
|
+
{
|
|
1882
|
+
type: "text",
|
|
1883
|
+
value: "unlock category sidebars, price sliders, tag clouds without extra backend work."
|
|
1884
|
+
}
|
|
1885
|
+
]
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
title: "Test relevance in the dashboard before you write a line of ranking code",
|
|
1889
|
+
chunks: [
|
|
1890
|
+
{
|
|
1891
|
+
type: "text",
|
|
1892
|
+
value: "Our Search dashboard has a live preview where you can browse results."
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
type: "text",
|
|
1896
|
+
value: "Tune your settings and see how it alters your results in"
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
type: "link",
|
|
1900
|
+
value: "real-time.",
|
|
1901
|
+
href: "https://dashboard.algolia.com/explorer/browse"
|
|
1902
|
+
}
|
|
1903
|
+
]
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
title: "Search Analytics helps you discover opportunities",
|
|
1907
|
+
chunks: [
|
|
1908
|
+
{
|
|
1909
|
+
type: "text",
|
|
1910
|
+
value: "No click results for a particular query? Low click-through rates for another?"
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
type: "text",
|
|
1914
|
+
value: "Our Search Analytics will help you identify synonyms, rules or other relevancy settings to improve your results."
|
|
1915
|
+
}
|
|
1916
|
+
]
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
title: "Let Algolia act as your recommendation engine",
|
|
1920
|
+
chunks: [
|
|
1921
|
+
{
|
|
1922
|
+
type: "text",
|
|
1923
|
+
value: "Beyond search, Algolia Recommend runs models trained on your existing indices and event data to power your recommendation engine."
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
type: "text",
|
|
1927
|
+
value: "You can improve engagement with related, popular or visually similar items."
|
|
1928
|
+
}
|
|
1929
|
+
]
|
|
1930
|
+
}
|
|
1931
|
+
];
|
|
1932
|
+
|
|
1933
|
+
// src/ui/Tips.tsx
|
|
1934
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1935
|
+
var TICK_MS = 16;
|
|
1936
|
+
var CHUNK_HOLD_MS = 2e3;
|
|
1937
|
+
var HOLD_MS = 8e3;
|
|
1938
|
+
function TipTitle({ children }) {
|
|
1939
|
+
return /* @__PURE__ */ jsxs16(Box18, { gap: 1, children: [
|
|
1940
|
+
/* @__PURE__ */ jsx16(Text18, { color: "cyan", children: "\u2726" }),
|
|
1941
|
+
/* @__PURE__ */ jsx16(Text18, { color: "white", bold: true, children })
|
|
1942
|
+
] });
|
|
1943
|
+
}
|
|
1944
|
+
function InlineSegment({ segment }) {
|
|
1945
|
+
switch (segment.type) {
|
|
1946
|
+
case "highlight":
|
|
1947
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.success, children: segment.value });
|
|
1948
|
+
case "link":
|
|
1949
|
+
return /* @__PURE__ */ jsx16(Text18, { color: "cyan", underline: true, children: segment.href ? terminalLink(segment.value, segment.href) : segment.value });
|
|
1950
|
+
case "codeword":
|
|
1951
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.highlight.fg, backgroundColor: COLORS.highlight.bg, children: segment.value });
|
|
1952
|
+
default:
|
|
1953
|
+
return /* @__PURE__ */ jsx16(Text18, { color: COLORS.muted, children: segment.value });
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function TipContent({
|
|
1957
|
+
chunks,
|
|
1958
|
+
revealed
|
|
1959
|
+
}) {
|
|
1960
|
+
let remaining = revealed;
|
|
1961
|
+
const slices = chunks.map((chunk) => {
|
|
1962
|
+
const slice = chunk.value.slice(0, Math.max(0, remaining));
|
|
1963
|
+
remaining -= chunk.value.length;
|
|
1964
|
+
return slice;
|
|
1965
|
+
});
|
|
1966
|
+
const blocks = [];
|
|
1967
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1968
|
+
const chunk = chunks[i];
|
|
1969
|
+
const slice = slices[i];
|
|
1970
|
+
if (!slice) continue;
|
|
1971
|
+
const segment = {
|
|
1972
|
+
type: chunk.type,
|
|
1973
|
+
value: slice,
|
|
1974
|
+
href: chunk.type === "link" ? chunk.href : void 0
|
|
1975
|
+
};
|
|
1976
|
+
if (chunk.type === "code") {
|
|
1977
|
+
blocks.push({ type: "code", segment });
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
const last = blocks[blocks.length - 1];
|
|
1981
|
+
if (last?.type === "inline") {
|
|
1982
|
+
last.segments.push(segment);
|
|
1983
|
+
} else {
|
|
1984
|
+
blocks.push({ type: "inline", segments: [segment] });
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
return /* @__PURE__ */ jsx16(Box18, { flexDirection: "column", marginLeft: 2, gap: 1, children: blocks.map(
|
|
1988
|
+
(block, i) => block.type === "code" ? /* @__PURE__ */ jsx16(Code, { children: block.segment.value }, i) : /* @__PURE__ */ jsx16(Text18, { children: block.segments.map((segment, j) => /* @__PURE__ */ jsxs16(Text18, { children: [
|
|
1989
|
+
j > 0 && " ",
|
|
1990
|
+
/* @__PURE__ */ jsx16(InlineSegment, { segment })
|
|
1991
|
+
] }, j)) }, i)
|
|
1992
|
+
) });
|
|
1993
|
+
}
|
|
1994
|
+
function Tips() {
|
|
1995
|
+
const [tipIndex, setTipIndex] = useState7(0);
|
|
1996
|
+
const [revealed, setRevealed] = useState7(0);
|
|
1997
|
+
const tip = tips[tipIndex];
|
|
1998
|
+
const contentLength = tip.chunks.reduce((sum, c) => sum + c.value.length, 0);
|
|
1999
|
+
const totalLength = tip.title.length + contentLength;
|
|
2000
|
+
const segments = [
|
|
2001
|
+
{ type: "title", length: tip.title.length },
|
|
2002
|
+
...tip.chunks.map((c) => ({ type: c.type, length: c.value.length }))
|
|
2003
|
+
];
|
|
2004
|
+
const noPauseAfterText = [
|
|
2005
|
+
"highlight",
|
|
2006
|
+
"link",
|
|
2007
|
+
"codeword"
|
|
2008
|
+
];
|
|
2009
|
+
const chunkBoundaries = [];
|
|
2010
|
+
let cumulative = 0;
|
|
2011
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
2012
|
+
cumulative += segments[i].length;
|
|
2013
|
+
const skipPause = noPauseAfterText.includes(
|
|
2014
|
+
segments[i + 1].type
|
|
2015
|
+
);
|
|
2016
|
+
if (!skipPause) chunkBoundaries.push(cumulative);
|
|
2017
|
+
}
|
|
2018
|
+
useEffect3(() => {
|
|
2019
|
+
if (revealed < totalLength) {
|
|
2020
|
+
const delay2 = chunkBoundaries.includes(revealed) ? CHUNK_HOLD_MS : TICK_MS;
|
|
2021
|
+
const timer2 = setTimeout(() => setRevealed((r) => r + 1), delay2);
|
|
2022
|
+
return () => clearTimeout(timer2);
|
|
2023
|
+
}
|
|
2024
|
+
const timer = setTimeout(() => {
|
|
2025
|
+
setTipIndex((i) => {
|
|
2026
|
+
if (i < tips.length - 1) return i + 1;
|
|
2027
|
+
return 0;
|
|
2028
|
+
});
|
|
2029
|
+
setRevealed(0);
|
|
2030
|
+
}, HOLD_MS);
|
|
2031
|
+
return () => clearTimeout(timer);
|
|
2032
|
+
}, [revealed, totalLength]);
|
|
2033
|
+
const titleRevealed = tip.title.slice(0, revealed);
|
|
2034
|
+
const contentRevealed = Math.max(0, revealed - tip.title.length);
|
|
2035
|
+
return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", marginBottom: 1, gap: 1, children: [
|
|
2036
|
+
/* @__PURE__ */ jsx16(TipTitle, { children: titleRevealed }),
|
|
2037
|
+
/* @__PURE__ */ jsx16(TipContent, { chunks: tip.chunks, revealed: contentRevealed })
|
|
2038
|
+
] });
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1779
2041
|
// src/ui/App.tsx
|
|
1780
|
-
import { jsx as
|
|
2042
|
+
import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1781
2043
|
function App() {
|
|
1782
|
-
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
2044
|
+
const { phase, error, homeScreen, currentStepIndex, steps, inputReq, notices } = useWizard();
|
|
1783
2045
|
const { exit } = useApp();
|
|
1784
|
-
const { columns, rows } =
|
|
1785
|
-
const [showLogs, setShowLogs] =
|
|
2046
|
+
const { columns, rows } = useWindowSize7();
|
|
2047
|
+
const [showLogs, setShowLogs] = useState8(false);
|
|
1786
2048
|
const finished = phase === "done" || phase === "error";
|
|
1787
2049
|
const currentStep = steps[currentStepIndex];
|
|
1788
2050
|
useInput7(
|
|
@@ -1824,8 +2086,8 @@ function App() {
|
|
|
1824
2086
|
/* Clamped to exactly the viewport: a taller frame makes Ink clear and repaint
|
|
1825
2087
|
the whole screen, and the scrolling throws off its cursor arithmetic —
|
|
1826
2088
|
flicker and leftover rows. */
|
|
1827
|
-
/* @__PURE__ */
|
|
1828
|
-
|
|
2089
|
+
/* @__PURE__ */ jsxs17(
|
|
2090
|
+
Box19,
|
|
1829
2091
|
{
|
|
1830
2092
|
backgroundColor: COLORS.bg.main,
|
|
1831
2093
|
flexDirection: "row",
|
|
@@ -1833,42 +2095,46 @@ function App() {
|
|
|
1833
2095
|
height: scrollsPastViewport ? void 0 : rows,
|
|
1834
2096
|
overflow: scrollsPastViewport ? "visible" : "hidden",
|
|
1835
2097
|
children: [
|
|
1836
|
-
mainWindowVisible && /* @__PURE__ */
|
|
1837
|
-
|
|
2098
|
+
mainWindowVisible && /* @__PURE__ */ jsxs17(
|
|
2099
|
+
Box19,
|
|
1838
2100
|
{
|
|
1839
2101
|
flexDirection,
|
|
1840
2102
|
width: "100%",
|
|
1841
2103
|
maxHeight: rows,
|
|
1842
2104
|
justifyContent: "space-between",
|
|
1843
2105
|
children: [
|
|
1844
|
-
showLogs ? /* @__PURE__ */
|
|
1845
|
-
|
|
2106
|
+
showLogs ? /* @__PURE__ */ jsx17(Logs, {}) : /* @__PURE__ */ jsxs17(
|
|
2107
|
+
Box19,
|
|
1846
2108
|
{
|
|
1847
2109
|
flexDirection: "column",
|
|
1848
2110
|
paddingX: 4,
|
|
1849
2111
|
paddingY: 2,
|
|
1850
2112
|
width: showSidebar ? 70 : "100%",
|
|
1851
2113
|
flexGrow: 1,
|
|
2114
|
+
gap: 1,
|
|
1852
2115
|
children: [
|
|
1853
|
-
|
|
1854
|
-
/* @__PURE__ */
|
|
1855
|
-
|
|
2116
|
+
/* @__PURE__ */ jsxs17(Box19, { flexGrow: 2, flexDirection: "column", children: [
|
|
2117
|
+
phase === "authenticating" && /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", marginBottom: 1, children: [
|
|
2118
|
+
/* @__PURE__ */ jsx17(Text19, { color: COLORS.strong, bold: true, children: "Signing in to Algolia" }),
|
|
2119
|
+
/* @__PURE__ */ jsx17(Text19, { color: COLORS.muted, children: "A browser window will open \u2014 complete sign-in there." })
|
|
2120
|
+
] }),
|
|
2121
|
+
/* @__PURE__ */ jsx17(CliOutput, {}),
|
|
2122
|
+
notices.length > 0 && phase === "running" && /* @__PURE__ */ jsx17(Tips, {}),
|
|
2123
|
+
/* @__PURE__ */ jsx17(PromptInput, {}),
|
|
2124
|
+
phase === "error" && error && /* @__PURE__ */ jsx17(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text19, { color: COLORS.status.error, children: [
|
|
2125
|
+
"\u2716 ",
|
|
2126
|
+
error
|
|
2127
|
+
] }) })
|
|
1856
2128
|
] }),
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
/* @__PURE__ */ jsx15(Notices, {}),
|
|
1859
|
-
/* @__PURE__ */ jsx15(PromptInput, {}),
|
|
1860
|
-
phase === "error" && error && /* @__PURE__ */ jsx15(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: COLORS.status.error, children: [
|
|
1861
|
-
"\u2716 ",
|
|
1862
|
-
error
|
|
1863
|
-
] }) })
|
|
2129
|
+
/* @__PURE__ */ jsx17(Notices, {})
|
|
1864
2130
|
]
|
|
1865
2131
|
}
|
|
1866
2132
|
),
|
|
1867
|
-
showSidebar ? /* @__PURE__ */
|
|
2133
|
+
showSidebar ? /* @__PURE__ */ jsx17(Sidebar, {}) : /* @__PURE__ */ jsx17(Ribbon, {})
|
|
1868
2134
|
]
|
|
1869
2135
|
}
|
|
1870
2136
|
),
|
|
1871
|
-
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */
|
|
2137
|
+
phase === "idle" && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx17(LearnMore, {}) : /* @__PURE__ */ jsx17(Welcome, {}))
|
|
1872
2138
|
]
|
|
1873
2139
|
}
|
|
1874
2140
|
)
|
|
@@ -2554,6 +2820,7 @@ function writeFileTool(ctx) {
|
|
|
2554
2820
|
}
|
|
2555
2821
|
await mkdir3(dirname4(resolved2.target), { recursive: true });
|
|
2556
2822
|
await writeFile3(resolved2.target, content, "utf8");
|
|
2823
|
+
useWizard.getState().recordWrittenFile(resolved2.target);
|
|
2557
2824
|
return `Wrote to ${filePath}`;
|
|
2558
2825
|
} catch (err) {
|
|
2559
2826
|
return `Error writing ${filePath}: ${err.message}`;
|
|
@@ -3334,7 +3601,7 @@ function defaultCreateModel() {
|
|
|
3334
3601
|
}
|
|
3335
3602
|
function generateRecordTool(ctx, createModel = defaultCreateModel) {
|
|
3336
3603
|
return tool10({
|
|
3337
|
-
description: "Generate realistic sample records for an entity and write them to a JSON file
|
|
3604
|
+
description: "Generate realistic sample records for an entity and write them to a JSON file. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
|
|
3338
3605
|
inputSchema: z17.object({
|
|
3339
3606
|
entityName: z17.string().describe("Name of the entity to generate records for."),
|
|
3340
3607
|
attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
|
|
@@ -3421,10 +3688,10 @@ import { tool as tool11 } from "ai";
|
|
|
3421
3688
|
import z18 from "zod";
|
|
3422
3689
|
function notifyUserTool() {
|
|
3423
3690
|
return tool11({
|
|
3424
|
-
description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work.`,
|
|
3691
|
+
description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work. Only the latest update is shown on screen, in one line, so keep it under 140 characters.`,
|
|
3425
3692
|
inputSchema: z18.object({
|
|
3426
|
-
message: z18.string().describe(
|
|
3427
|
-
"Short, plain-language description of what you are doing now."
|
|
3693
|
+
message: z18.string().max(140).describe(
|
|
3694
|
+
"Short, plain-language description of what you are doing now. Under 140 characters \u2014 only the latest update is shown, on one line."
|
|
3428
3695
|
)
|
|
3429
3696
|
}),
|
|
3430
3697
|
execute: async ({ message }) => {
|
|
@@ -3700,7 +3967,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3700
3967
|
// package.json
|
|
3701
3968
|
var package_default = {
|
|
3702
3969
|
name: "@algolia/wizard",
|
|
3703
|
-
version: "0.
|
|
3970
|
+
version: "0.24.0-rc.117.212",
|
|
3704
3971
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3705
3972
|
type: "module",
|
|
3706
3973
|
engines: {
|
|
@@ -3762,6 +4029,7 @@ var package_default = {
|
|
|
3762
4029
|
nanoid: "^5.1.15",
|
|
3763
4030
|
pino: "^10.3.1",
|
|
3764
4031
|
react: "^19.2.7",
|
|
4032
|
+
"terminal-link": "^5.0.0",
|
|
3765
4033
|
varlock: "^1.5.1",
|
|
3766
4034
|
zod: "^4.4.3",
|
|
3767
4035
|
zustand: "^5.0.14"
|
|
@@ -4067,11 +4335,10 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
4067
4335
|
function formatReviewSummary(result) {
|
|
4068
4336
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
4069
4337
|
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
4070
|
-
const isWorktreeCommand = step.includes("/worktrees/");
|
|
4071
4338
|
return {
|
|
4072
4339
|
text: `\u2192 ${step}`,
|
|
4073
|
-
color: isIngestCommand ? COLORS.brand :
|
|
4074
|
-
bold: isIngestCommand
|
|
4340
|
+
color: isIngestCommand ? COLORS.brand : void 0,
|
|
4341
|
+
bold: isIngestCommand
|
|
4075
4342
|
};
|
|
4076
4343
|
});
|
|
4077
4344
|
return [
|
|
@@ -4106,15 +4373,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4106
4373
|
|
|
4107
4374
|
// src/actions/implement.ts
|
|
4108
4375
|
import z29 from "zod";
|
|
4109
|
-
import { join as join12 } from "node:path";
|
|
4376
|
+
import { join as join12, relative as relative6 } from "node:path";
|
|
4110
4377
|
|
|
4111
|
-
// src/lib/
|
|
4378
|
+
// src/lib/git.ts
|
|
4112
4379
|
import { execFile as execFile2 } from "node:child_process";
|
|
4113
|
-
import { copyFile, mkdir as mkdir6,
|
|
4380
|
+
import { copyFile, mkdir as mkdir6, readFile as readFile8, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4114
4381
|
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
|
|
4115
4382
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
4116
|
-
var MAX_WIZARD_WORKTREES = 3;
|
|
4117
|
-
var WIZARD_BRANCH_PREFIX = "wizard/implement-";
|
|
4118
4383
|
function git(args) {
|
|
4119
4384
|
return new Promise((resolve4, reject) => {
|
|
4120
4385
|
execFile2("git", args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
|
|
@@ -4137,44 +4402,7 @@ async function assertGitRepoWithHead(repoRoot) {
|
|
|
4137
4402
|
);
|
|
4138
4403
|
}
|
|
4139
4404
|
}
|
|
4140
|
-
async function
|
|
4141
|
-
const out = await git(["-C", repoRoot, "status", "--porcelain"]);
|
|
4142
|
-
return out.trim().length > 0;
|
|
4143
|
-
}
|
|
4144
|
-
async function pruneOldWorktrees(repoRoot) {
|
|
4145
|
-
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
4146
|
-
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4147
|
-
for (const slug of stale) {
|
|
4148
|
-
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4149
|
-
try {
|
|
4150
|
-
await git([
|
|
4151
|
-
"-C",
|
|
4152
|
-
repoRoot,
|
|
4153
|
-
"worktree",
|
|
4154
|
-
"remove",
|
|
4155
|
-
"--force",
|
|
4156
|
-
join10(dir, slug)
|
|
4157
|
-
]);
|
|
4158
|
-
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4159
|
-
} catch (err) {
|
|
4160
|
-
logger.warn(
|
|
4161
|
-
{ branch, err: err.message },
|
|
4162
|
-
"createWorktree: failed to prune a stale wizard worktree; continuing"
|
|
4163
|
-
);
|
|
4164
|
-
}
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
async function createWorktree(repoRoot) {
|
|
4168
|
-
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4169
|
-
const dirSlug = branch.replace(/\//g, "-");
|
|
4170
|
-
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4171
|
-
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4172
|
-
await pruneOldWorktrees(repoRoot);
|
|
4173
|
-
await mkdir6(dirname7(path), { recursive: true });
|
|
4174
|
-
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4175
|
-
return { path, branch };
|
|
4176
|
-
}
|
|
4177
|
-
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4405
|
+
async function copyUploadIntoProject(repoRoot, ingestDir, sourcePath) {
|
|
4178
4406
|
const trimmed = sourcePath.trim();
|
|
4179
4407
|
if (!trimmed) {
|
|
4180
4408
|
return { ok: false, reason: "no file path was provided" };
|
|
@@ -4188,7 +4416,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4188
4416
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4189
4417
|
}
|
|
4190
4418
|
const relPath = join10(ingestDir, basename2(source));
|
|
4191
|
-
const dest = join10(
|
|
4419
|
+
const dest = join10(repoRoot, relPath);
|
|
4420
|
+
if (resolve3(source) === resolve3(dest)) {
|
|
4421
|
+
return { ok: true, relPath };
|
|
4422
|
+
}
|
|
4192
4423
|
try {
|
|
4193
4424
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4194
4425
|
await copyFile(source, dest);
|
|
@@ -4203,10 +4434,10 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4203
4434
|
function hasEnvVar(content, name) {
|
|
4204
4435
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4205
4436
|
}
|
|
4206
|
-
async function readEnvVar(
|
|
4437
|
+
async function readEnvVar(repoRoot, name) {
|
|
4207
4438
|
let content;
|
|
4208
4439
|
try {
|
|
4209
|
-
content = await readFile8(join10(
|
|
4440
|
+
content = await readFile8(join10(repoRoot, ".env"), "utf8");
|
|
4210
4441
|
} catch (err) {
|
|
4211
4442
|
if (err.code !== "ENOENT") throw err;
|
|
4212
4443
|
return void 0;
|
|
@@ -4220,8 +4451,8 @@ async function readEnvVar(worktreePath, name) {
|
|
|
4220
4451
|
if (!value || value.startsWith("<")) return void 0;
|
|
4221
4452
|
return value;
|
|
4222
4453
|
}
|
|
4223
|
-
async function writeSearchEnvValues(
|
|
4224
|
-
const target = join10(
|
|
4454
|
+
async function writeSearchEnvValues(repoRoot, vars) {
|
|
4455
|
+
const target = join10(repoRoot, ".env");
|
|
4225
4456
|
let existing = "";
|
|
4226
4457
|
try {
|
|
4227
4458
|
existing = await readFile8(target, "utf8");
|
|
@@ -4236,61 +4467,27 @@ async function writeSearchEnvValues(worktreePath, vars) {
|
|
|
4236
4467
|
await writeFile7(target, existing + prefix + lines, "utf8");
|
|
4237
4468
|
return missing.map((v) => v.name);
|
|
4238
4469
|
}
|
|
4239
|
-
async function listChangedFiles(worktreePath) {
|
|
4240
|
-
const raw = await git(["-C", worktreePath, "status", "--porcelain", "-z"]);
|
|
4241
|
-
const entries = raw.split("\0");
|
|
4242
|
-
const files = [];
|
|
4243
|
-
for (let i = 0; i < entries.length; i += 1) {
|
|
4244
|
-
const entry = entries[i];
|
|
4245
|
-
if (!entry) continue;
|
|
4246
|
-
files.push(entry.slice(3));
|
|
4247
|
-
if (["R", "C"].includes(entry[0]) || ["R", "C"].includes(entry[1])) i += 1;
|
|
4248
|
-
}
|
|
4249
|
-
return files;
|
|
4250
|
-
}
|
|
4251
4470
|
function normalizeFindingPaths(findings) {
|
|
4252
4471
|
return {
|
|
4253
4472
|
...findings,
|
|
4254
4473
|
ingestionAnalysis: findings.ingestionAnalysis?.map((e) => ({
|
|
4255
4474
|
...e,
|
|
4256
|
-
paths: e.paths.map(
|
|
4475
|
+
paths: e.paths.map(toRootRelative)
|
|
4257
4476
|
})),
|
|
4258
4477
|
searchImplementationAnalysis: findings.searchImplementationAnalysis ? normalizeSearchLocation(findings.searchImplementationAnalysis) : void 0,
|
|
4259
4478
|
confirmedEntities: findings.confirmedEntities?.map((e) => ({
|
|
4260
4479
|
...e,
|
|
4261
|
-
paths: e.paths.map(
|
|
4480
|
+
paths: e.paths.map(toRootRelative)
|
|
4262
4481
|
}))
|
|
4263
4482
|
};
|
|
4264
4483
|
}
|
|
4265
4484
|
function normalizeSearchLocation(path) {
|
|
4266
|
-
const normalized = path ?
|
|
4485
|
+
const normalized = path ? toRootRelative(path).trim() : "";
|
|
4267
4486
|
return normalized && normalized.toLowerCase() !== "unknown" ? normalized : void 0;
|
|
4268
4487
|
}
|
|
4269
|
-
function
|
|
4488
|
+
function toRootRelative(p) {
|
|
4270
4489
|
return p.replace(/^\/+/, "");
|
|
4271
4490
|
}
|
|
4272
|
-
async function confirmDirtyWorkingTree(ctx, repoRoot) {
|
|
4273
|
-
const MAX_LISTED_DIRTY_FILES = 10;
|
|
4274
|
-
const dirty = await listChangedFiles(repoRoot);
|
|
4275
|
-
const shown = dirty.slice(0, MAX_LISTED_DIRTY_FILES);
|
|
4276
|
-
const overflow = dirty.length - shown.length;
|
|
4277
|
-
const answer = await ctx.requestUserInput({
|
|
4278
|
-
prompt: "Proceed using HEAD only? Uncommitted changes will NOT be included in the generated implementation.",
|
|
4279
|
-
promptType: "acceptReject",
|
|
4280
|
-
options: [],
|
|
4281
|
-
messages: [
|
|
4282
|
-
`${dirty.length} uncommitted change(s) detected. The wizard builds an isolated worktree from HEAD, so these are ignored:`,
|
|
4283
|
-
...shown.map((file) => ` \u2022 ${file}`),
|
|
4284
|
-
...overflow > 0 ? [` \u2022 \u2026and ${overflow} more`] : [],
|
|
4285
|
-
"Commit or stash them first to include them in the implementation."
|
|
4286
|
-
]
|
|
4287
|
-
});
|
|
4288
|
-
if (answer !== true) {
|
|
4289
|
-
throw new Error(
|
|
4290
|
-
"implement aborted: commit or stash your changes so they are built into the worktree, then re-run the wizard."
|
|
4291
|
-
);
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
4491
|
|
|
4295
4492
|
// src/lib/algoliaDocs.ts
|
|
4296
4493
|
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
@@ -4350,11 +4547,6 @@ function getFrameworkSpecificDoc(frameworks) {
|
|
|
4350
4547
|
return loadAlgoliaDoc("js");
|
|
4351
4548
|
}
|
|
4352
4549
|
|
|
4353
|
-
// src/lib/shell.ts
|
|
4354
|
-
function shellQuote(value) {
|
|
4355
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
4356
|
-
}
|
|
4357
|
-
|
|
4358
4550
|
// src/actions/resolveEnvVarPrefix.ts
|
|
4359
4551
|
import z28 from "zod";
|
|
4360
4552
|
var resolveEnvVarPrefixSchema = z28.object({
|
|
@@ -4374,9 +4566,7 @@ var resolveEnvVarPrefix = (frameworkName) => runAgent({
|
|
|
4374
4566
|
|
|
4375
4567
|
// src/actions/implement.ts
|
|
4376
4568
|
var implementSchema = z29.object({
|
|
4377
|
-
filesChanged: z29.array(z29.string()),
|
|
4378
4569
|
summary: z29.string(),
|
|
4379
|
-
worktreePath: z29.string().optional(),
|
|
4380
4570
|
ingestCommand: z29.string().optional(),
|
|
4381
4571
|
ingestScriptRan: z29.boolean().optional(),
|
|
4382
4572
|
ingestRecordCount: z29.number().optional(),
|
|
@@ -4429,8 +4619,6 @@ function frameworksForDoc(language) {
|
|
|
4429
4619
|
}
|
|
4430
4620
|
function baseInstructions(input) {
|
|
4431
4621
|
return [
|
|
4432
|
-
// Agents have renamed this (e.g. appending the project name), which the
|
|
4433
|
-
// index-scoped keys then reject with a 403.
|
|
4434
4622
|
`Target Algolia index, to be used exactly as written \u2014 never renamed, re-cased, prefixed, or suffixed: "${input.targetIndex}"`,
|
|
4435
4623
|
`Project languages and frameworks: ${JSON.stringify(input.language)}`,
|
|
4436
4624
|
"Make minimal, idiomatic changes; do not touch unrelated code.",
|
|
@@ -4448,14 +4636,14 @@ function sourceSpecificInstructions(input) {
|
|
|
4448
4636
|
"Add env vars for any local source access (DB URL, API base, file paths) that is not a fixed repo path."
|
|
4449
4637
|
],
|
|
4450
4638
|
fileUpload: [
|
|
4451
|
-
`Records come from the developer's file, already copied into the
|
|
4639
|
+
`Records come from the developer's file, already copied into the project at "${input.uploadFilePath}". Read and parse that exact file.`,
|
|
4452
4640
|
"Parse by extension: JSON = array of objects; CSV/TSV = header row maps to keys.",
|
|
4453
4641
|
"Map parsed columns/fields to the confirmed entity attributes.",
|
|
4454
4642
|
"Never fabricate, hardcode, or substitute a different file."
|
|
4455
4643
|
],
|
|
4456
4644
|
generated: [
|
|
4457
4645
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4458
|
-
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file
|
|
4646
|
+
"Call the generateRecord tool once per entity (entityName, attributes, count 20-50); it invents the values and unique objectIDs and writes them to a JSON file, returning the file path. Do not write records or objectIDs yourself.",
|
|
4459
4647
|
"In the script, read and parse each returned file path at runtime using your language's standard JSON support, instead of inlining the records as literals.",
|
|
4460
4648
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4461
4649
|
]
|
|
@@ -4504,14 +4692,11 @@ function searchInstructions(input) {
|
|
|
4504
4692
|
"If a search box already exists, replace it with yours.",
|
|
4505
4693
|
`Read the index name from the ${publicIndexNameVar(input.publicEnvVarPrefix)} env var, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or component name.`,
|
|
4506
4694
|
"Read the App ID, the search-only API key, and the index name from env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
4507
|
-
// The key is provisioned only after verification passes,
|
|
4508
|
-
//
|
|
4509
|
-
//
|
|
4510
|
-
// would be reused as if it were real.
|
|
4695
|
+
// The key is provisioned only after verification passes, and the wizard
|
|
4696
|
+
// reads .env to decide whether a key already exists — an agent-invented
|
|
4697
|
+
// value there would be reused as if it were real.
|
|
4511
4698
|
`Add Algolia App ID "${input.appId}"; leave the search-only key as a placeholder. Do not create or edit .env \u2014 the wizard writes the resolved key there itself.`,
|
|
4512
|
-
//
|
|
4513
|
-
// ".env" right after this step, so a renamed prefix would leave the code
|
|
4514
|
-
// reading a var the wizard never wrote.
|
|
4699
|
+
// The wizard writes these exact names into .env right after this step.
|
|
4515
4700
|
`Use exactly these env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
4516
4701
|
"Install any Algolia packages you import with the project's own package manager via runShell, and declare them in the project's dependency manifest.",
|
|
4517
4702
|
"Match the styles of the application as closely as possible.",
|
|
@@ -4520,10 +4705,10 @@ function searchInstructions(input) {
|
|
|
4520
4705
|
}
|
|
4521
4706
|
function verificationInstructions(input) {
|
|
4522
4707
|
return [
|
|
4523
|
-
"Verify the Algolia implementation changes
|
|
4708
|
+
"Verify the Algolia implementation changes.",
|
|
4524
4709
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4525
4710
|
"Run the project's own checks (lint, type check, tests) via runShell, using the commands the project actually defines \u2014 its task runner, manifest scripts, or Makefile. Run every check that applies, not just the first.",
|
|
4526
|
-
"
|
|
4711
|
+
"If a check fails because packages or modules are missing, install the dependencies via runShell and re-run it rather than changing the code.",
|
|
4527
4712
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run the checks.",
|
|
4528
4713
|
"Do not make speculative fixes when no checks exist, a check cannot run, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
4529
4714
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
@@ -4618,11 +4803,11 @@ function ingestFailure(attempt, executions) {
|
|
|
4618
4803
|
}
|
|
4619
4804
|
return { reason: `it failed (exit ${attempt.exitCode ?? "unknown"})`, detail };
|
|
4620
4805
|
}
|
|
4621
|
-
function makeToolContext(
|
|
4806
|
+
function makeToolContext(root, env = async () => ({})) {
|
|
4622
4807
|
return createToolContext(
|
|
4623
4808
|
DEFAULT_TOOL_LIMITS,
|
|
4624
|
-
|
|
4625
|
-
createShellContext({ env, approve: storeApproval(
|
|
4809
|
+
root,
|
|
4810
|
+
createShellContext({ env, approve: storeApproval(root) })
|
|
4626
4811
|
);
|
|
4627
4812
|
}
|
|
4628
4813
|
function verificationRetryInstructions(verification) {
|
|
@@ -4630,7 +4815,7 @@ function verificationRetryInstructions(verification) {
|
|
|
4630
4815
|
`Implementation insufficient. Address these findings before reporting completion: ${verification.additionalInstructions ?? verification.summary}`
|
|
4631
4816
|
];
|
|
4632
4817
|
}
|
|
4633
|
-
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES
|
|
4818
|
+
async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
|
|
4634
4819
|
const repoRoot = process.cwd();
|
|
4635
4820
|
const scan = ctx.getStepOutput("project-scan");
|
|
4636
4821
|
const entities = ctx.getStepOutput(
|
|
@@ -4711,9 +4896,6 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4711
4896
|
const targetIndex = selected?.selection;
|
|
4712
4897
|
useWizard.getState().setTargetIndex(targetIndex ?? null);
|
|
4713
4898
|
await assertGitRepoWithHead(repoRoot);
|
|
4714
|
-
if (await isWorkingTreeDirty(repoRoot)) {
|
|
4715
|
-
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4716
|
-
}
|
|
4717
4899
|
const normalized = normalizeFindingPaths(findings);
|
|
4718
4900
|
const confirmed2 = normalized.confirmedEntities;
|
|
4719
4901
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
@@ -4725,328 +4907,303 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4725
4907
|
if (useCases.includes("ingestion")) {
|
|
4726
4908
|
ingestAppId = appId ?? (await requireApplication()).id;
|
|
4727
4909
|
}
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4910
|
+
let uploadFilePath;
|
|
4911
|
+
let uploadWarning;
|
|
4912
|
+
if (ingestionSource === "fileUpload") {
|
|
4913
|
+
const copied = await copyUploadIntoProject(
|
|
4914
|
+
repoRoot,
|
|
4915
|
+
INGEST_DIR,
|
|
4916
|
+
uploadSourcePath ?? ""
|
|
4917
|
+
);
|
|
4918
|
+
if (copied.ok) {
|
|
4919
|
+
uploadFilePath = copied.relPath;
|
|
4920
|
+
} else {
|
|
4921
|
+
ingestionSource = "generated";
|
|
4922
|
+
uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
|
|
4923
|
+
logger.warn(
|
|
4924
|
+
{ reason: copied.reason },
|
|
4925
|
+
"implement: file upload unavailable; falling back to generated sample records"
|
|
4926
|
+
);
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
const publicEnvVarPrefix = await publicEnvVarPrefixPromise;
|
|
4930
|
+
const input = {
|
|
4931
|
+
findings: normalized,
|
|
4932
|
+
confirmed: confirmed2,
|
|
4933
|
+
searchLocation,
|
|
4934
|
+
targetIndex,
|
|
4935
|
+
language,
|
|
4936
|
+
publicEnvVarPrefix,
|
|
4937
|
+
appId,
|
|
4938
|
+
searchEnvVars: publicSearchEnvVars(publicEnvVarPrefix, targetIndex, appId),
|
|
4939
|
+
ingestDir: INGEST_DIR,
|
|
4940
|
+
ingestionSource,
|
|
4941
|
+
uploadFilePath,
|
|
4942
|
+
searchUiTarget: searchUiTarget(language)
|
|
4943
|
+
};
|
|
4944
|
+
const summaries = [];
|
|
4945
|
+
if (uploadWarning) summaries.push(uploadWarning);
|
|
4946
|
+
let envSearchKey;
|
|
4947
|
+
let envAppIdMismatch = false;
|
|
4948
|
+
if (useCases.includes("search") && appId) {
|
|
4949
|
+
const envAppId = await readEnvVar(
|
|
4950
|
+
repoRoot,
|
|
4951
|
+
publicAppIdVar(publicEnvVarPrefix)
|
|
4952
|
+
);
|
|
4953
|
+
if (envAppId === appId) {
|
|
4954
|
+
envSearchKey = await readEnvVar(
|
|
4735
4955
|
repoRoot,
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4956
|
+
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4957
|
+
);
|
|
4958
|
+
} else if (envAppId) {
|
|
4959
|
+
envAppIdMismatch = true;
|
|
4960
|
+
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4961
|
+
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4962
|
+
summaries.push(
|
|
4963
|
+
`\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
|
|
4964
|
+
);
|
|
4965
|
+
logger.warn(
|
|
4966
|
+
{ envAppId, appId },
|
|
4967
|
+
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4739
4968
|
);
|
|
4740
|
-
if (copied.ok) {
|
|
4741
|
-
uploadFilePath = copied.relPath;
|
|
4742
|
-
} else {
|
|
4743
|
-
ingestionSource = "generated";
|
|
4744
|
-
uploadWarning = `\u26A0\uFE0F Could not use the uploaded file (${copied.reason}); generating sample records instead.`;
|
|
4745
|
-
logger.warn(
|
|
4746
|
-
{ reason: copied.reason },
|
|
4747
|
-
"implement: file upload unavailable; falling back to generated sample records"
|
|
4748
|
-
);
|
|
4749
|
-
}
|
|
4750
4969
|
}
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4970
|
+
}
|
|
4971
|
+
let finalSearchEnvVars = input.searchEnvVars;
|
|
4972
|
+
let agentRuns = 0;
|
|
4973
|
+
let ingestCommand;
|
|
4974
|
+
let ingestScriptRan = false;
|
|
4975
|
+
let ingestRecordCount;
|
|
4976
|
+
let ingestDurationMs;
|
|
4977
|
+
let ingestOutcomeMessage;
|
|
4978
|
+
const ingestKeyAppId = ingestAppId;
|
|
4979
|
+
const ingestionTools = ingestKeyAppId ? makeToolContext(repoRoot, async () => ({
|
|
4980
|
+
[APP_ID_VAR]: ingestKeyAppId,
|
|
4981
|
+
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4982
|
+
[INDEX_NAME_VAR]: targetIndex
|
|
4983
|
+
})) : void 0;
|
|
4984
|
+
const searchTools = makeToolContext(repoRoot);
|
|
4985
|
+
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4986
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4987
|
+
agentRuns += 1;
|
|
4988
|
+
return runAgent({
|
|
4989
|
+
instructions: buildAgentInstructions(
|
|
4990
|
+
currentUseCase,
|
|
4991
|
+
input,
|
|
4992
|
+
extraInstructions
|
|
4764
4993
|
),
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
if (
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4994
|
+
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4995
|
+
outputSchema: implementationOutputSchema,
|
|
4996
|
+
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4997
|
+
});
|
|
4998
|
+
}
|
|
4999
|
+
async function runVerificationUseCase() {
|
|
5000
|
+
if (agentRuns > 0) ctx.recordStepExecution();
|
|
5001
|
+
agentRuns += 1;
|
|
5002
|
+
return runAgent({
|
|
5003
|
+
instructions: buildAgentInstructions("verification", input),
|
|
5004
|
+
tools: toolsForUseCase("verification"),
|
|
5005
|
+
outputSchema: verificationOutputSchema,
|
|
5006
|
+
toolContext: searchTools
|
|
5007
|
+
});
|
|
5008
|
+
}
|
|
5009
|
+
if (useCases.includes("ingestion")) {
|
|
5010
|
+
let ingestFailureDetail;
|
|
5011
|
+
const result = await runImplementationUseCase("ingestion");
|
|
5012
|
+
summaries.push(formatSummary("ingestion", result.summary));
|
|
5013
|
+
ingestCommand = result.ingestCommand;
|
|
5014
|
+
const ingestionContext = ingestionTools ?? searchTools;
|
|
5015
|
+
const executions = ingestionContext.shell.executions;
|
|
5016
|
+
const {
|
|
5017
|
+
run: ingestRun,
|
|
5018
|
+
attempt: ingestAttempt,
|
|
5019
|
+
recordCount
|
|
5020
|
+
} = ingestOutcome(executions, ingestCommand);
|
|
5021
|
+
ingestScriptRan = ingestRun != null;
|
|
5022
|
+
ingestRecordCount = recordCount;
|
|
5023
|
+
ingestDurationMs = ingestRun?.durationMs;
|
|
5024
|
+
if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
|
|
5025
|
+
summaries.push(
|
|
5026
|
+
"\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in your project before trusting the index contents."
|
|
5027
|
+
);
|
|
5028
|
+
logger.warn(
|
|
5029
|
+
{ ingestCommand },
|
|
5030
|
+
"implement: ingestion ran without a reviewScript call"
|
|
4778
5031
|
);
|
|
4779
|
-
if (envAppId === appId) {
|
|
4780
|
-
envSearchKey = await readEnvVar(
|
|
4781
|
-
worktree,
|
|
4782
|
-
publicSearchKeyVar(publicEnvVarPrefix)
|
|
4783
|
-
);
|
|
4784
|
-
} else if (envAppId) {
|
|
4785
|
-
envAppIdMismatch = true;
|
|
4786
|
-
const appIdVarName = publicAppIdVar(publicEnvVarPrefix);
|
|
4787
|
-
const searchKeyVarName = publicSearchKeyVar(publicEnvVarPrefix);
|
|
4788
|
-
summaries.push(
|
|
4789
|
-
`\u26A0\uFE0F .env already sets ${appIdVarName}=${envAppId}, but the active Algolia application is ${appId}. The wizard left those values alone \u2014 update ${appIdVarName} and ${searchKeyVarName} by hand, or searches will fail.`
|
|
4790
|
-
);
|
|
4791
|
-
logger.warn(
|
|
4792
|
-
{ envAppId, appId },
|
|
4793
|
-
"implement: .env holds credentials for a different Algolia application; not reusing its search key"
|
|
4794
|
-
);
|
|
4795
|
-
}
|
|
4796
|
-
}
|
|
4797
|
-
let finalSearchEnvVars = input.searchEnvVars;
|
|
4798
|
-
let agentRuns = 0;
|
|
4799
|
-
let ingestCommand;
|
|
4800
|
-
let ingestScriptRan = false;
|
|
4801
|
-
let ingestRecordCount;
|
|
4802
|
-
let ingestDurationMs;
|
|
4803
|
-
let ingestOutcomeMessage;
|
|
4804
|
-
const ingestKeyAppId = ingestAppId;
|
|
4805
|
-
const ingestionTools = ingestKeyAppId ? makeToolContext(worktree, async () => ({
|
|
4806
|
-
[APP_ID_VAR]: ingestKeyAppId,
|
|
4807
|
-
[API_KEY_VAR]: (await resolveWriteKey(targetIndex, ingestKeyAppId)).key,
|
|
4808
|
-
[INDEX_NAME_VAR]: targetIndex
|
|
4809
|
-
})) : void 0;
|
|
4810
|
-
const searchTools = makeToolContext(worktree);
|
|
4811
|
-
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4812
|
-
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4813
|
-
agentRuns += 1;
|
|
4814
|
-
return runAgent({
|
|
4815
|
-
instructions: buildAgentInstructions(
|
|
4816
|
-
currentUseCase,
|
|
4817
|
-
input,
|
|
4818
|
-
extraInstructions
|
|
4819
|
-
),
|
|
4820
|
-
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4821
|
-
outputSchema: implementationOutputSchema,
|
|
4822
|
-
toolContext: currentUseCase === "ingestion" ? ingestionTools ?? searchTools : searchTools
|
|
4823
|
-
});
|
|
4824
|
-
}
|
|
4825
|
-
async function runVerificationUseCase() {
|
|
4826
|
-
if (agentRuns > 0) ctx.recordStepExecution();
|
|
4827
|
-
agentRuns += 1;
|
|
4828
|
-
return runAgent({
|
|
4829
|
-
instructions: buildAgentInstructions("verification", input),
|
|
4830
|
-
tools: toolsForUseCase("verification"),
|
|
4831
|
-
outputSchema: verificationOutputSchema,
|
|
4832
|
-
toolContext: searchTools
|
|
4833
|
-
});
|
|
4834
5032
|
}
|
|
4835
|
-
if (
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
run: ingestRun,
|
|
4844
|
-
attempt: ingestAttempt,
|
|
4845
|
-
recordCount
|
|
4846
|
-
} = ingestOutcome(executions, ingestCommand);
|
|
4847
|
-
ingestScriptRan = ingestRun != null;
|
|
4848
|
-
ingestRecordCount = recordCount;
|
|
4849
|
-
ingestDurationMs = ingestRun?.durationMs;
|
|
4850
|
-
if (ingestScriptRan && ingestionContext.reviewed.length === 0) {
|
|
4851
|
-
summaries.push(
|
|
4852
|
-
"\u26A0\uFE0F The ingestion script ran without being shown to you for review. Read it in the worktree before trusting the index contents."
|
|
4853
|
-
);
|
|
4854
|
-
logger.warn(
|
|
4855
|
-
{ ingestCommand },
|
|
4856
|
-
"implement: ingestion ran without a reviewScript call"
|
|
4857
|
-
);
|
|
5033
|
+
if (ingestScriptRan) {
|
|
5034
|
+
ingestOutcomeMessage = `\u2705 Ingestion succeeded${ingestRecordCount != null ? ` \u2014 ${ingestRecordCount} record(s) indexed.` : "."}`;
|
|
5035
|
+
if (ingestRecordCount != null) {
|
|
5036
|
+
track("AI Wizard Ingest Successful", {
|
|
5037
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
5038
|
+
record_count: ingestRecordCount,
|
|
5039
|
+
duration_ms: ingestDurationMs ?? 0
|
|
5040
|
+
});
|
|
4858
5041
|
}
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
duration_ms: ingestDurationMs ?? 0
|
|
4866
|
-
});
|
|
4867
|
-
}
|
|
4868
|
-
} else {
|
|
4869
|
-
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
4870
|
-
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
4871
|
-
ingestFailureDetail = detail;
|
|
4872
|
-
summaries.push(
|
|
4873
|
-
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
5042
|
+
} else {
|
|
5043
|
+
const { reason, detail } = ingestFailure(ingestAttempt, executions);
|
|
5044
|
+
ingestOutcomeMessage = `\u26A0\uFE0F Records were not indexed \u2014 ${reason}.${ingestCommand ? " Run the command below when you are ready." : ""}`;
|
|
5045
|
+
ingestFailureDetail = detail;
|
|
5046
|
+
summaries.push(
|
|
5047
|
+
`\u26A0\uFE0F Ingestion did not complete: ${reason}.${detail ? `
|
|
4874
5048
|
${detail}` : ""}`
|
|
4875
|
-
|
|
4876
|
-
|
|
5049
|
+
);
|
|
5050
|
+
logger.warn(
|
|
5051
|
+
{
|
|
5052
|
+
ingestCommand,
|
|
5053
|
+
reason,
|
|
5054
|
+
approved: ingestAttempt?.approved,
|
|
5055
|
+
exitCode: ingestAttempt?.exitCode,
|
|
5056
|
+
timedOut: ingestAttempt?.timedOut,
|
|
5057
|
+
commandsRun: executions.length
|
|
5058
|
+
},
|
|
5059
|
+
"implement: ingestion script did not complete successfully"
|
|
5060
|
+
);
|
|
5061
|
+
track("Error", {
|
|
5062
|
+
step: "Push Data",
|
|
5063
|
+
error: `ingestion did not complete: ${reason}`,
|
|
5064
|
+
product_area: "AI Wizard"
|
|
5065
|
+
});
|
|
5066
|
+
}
|
|
5067
|
+
const commandMessages = ingestCommand ? [`Ingestion command: ${ingestCommand}`] : [];
|
|
5068
|
+
await ctx.requestUserInput({
|
|
5069
|
+
prompt: "",
|
|
5070
|
+
promptType: "enterToContinue",
|
|
5071
|
+
options: [],
|
|
5072
|
+
// One message per line: Notices.tsx counts a message as one wrapped
|
|
5073
|
+
// line, so an embedded newline overflows the panel's height accounting.
|
|
5074
|
+
messages: [
|
|
5075
|
+
ingestOutcomeMessage,
|
|
5076
|
+
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
5077
|
+
...commandMessages
|
|
5078
|
+
]
|
|
5079
|
+
});
|
|
5080
|
+
}
|
|
5081
|
+
if (useCases.includes("search")) {
|
|
5082
|
+
let extraInstructions = [];
|
|
5083
|
+
useWizard.getState().clearWrittenFiles();
|
|
5084
|
+
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
5085
|
+
if (attempt > 1) {
|
|
5086
|
+
logger.info(
|
|
4877
5087
|
{
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
exitCode: ingestAttempt?.exitCode,
|
|
4882
|
-
timedOut: ingestAttempt?.timedOut,
|
|
4883
|
-
commandsRun: executions.length
|
|
5088
|
+
attempt,
|
|
5089
|
+
maxAttempts: MAX_IMPLEMENT_VERIFICATION_ATTEMPTS,
|
|
5090
|
+
extraInstructions
|
|
4884
5091
|
},
|
|
4885
|
-
"implement:
|
|
5092
|
+
"implement: retrying search implementation after failed verification"
|
|
4886
5093
|
);
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
5094
|
+
}
|
|
5095
|
+
const { summary } = await runImplementationUseCase(
|
|
5096
|
+
"search",
|
|
5097
|
+
extraInstructions
|
|
5098
|
+
);
|
|
5099
|
+
summaries.push(formatSummary("search", summary));
|
|
5100
|
+
const verification = await runVerificationUseCase();
|
|
5101
|
+
summaries.push(formatSummary("verification", verification.summary));
|
|
5102
|
+
if (verification.sufficient) {
|
|
5103
|
+
ctx.setUserInput("implementation", "success");
|
|
5104
|
+
const searchFilesChanged = [
|
|
5105
|
+
...new Set(useWizard.getState().writtenFiles)
|
|
5106
|
+
].map((file) => relative6(repoRoot, file));
|
|
5107
|
+
track("AI Wizard Frontend Component Generated", {
|
|
5108
|
+
filePaths: searchFilesChanged
|
|
4891
5109
|
});
|
|
5110
|
+
track("AI Wizard Wired to UI", {
|
|
5111
|
+
location_heuristic: searchLocation ?? "unknown"
|
|
5112
|
+
});
|
|
5113
|
+
break;
|
|
4892
5114
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
5115
|
+
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
5116
|
+
ctx.setUserInput("implementation", "fail");
|
|
5117
|
+
throw new Error(
|
|
5118
|
+
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
5119
|
+
);
|
|
4896
5120
|
}
|
|
4897
|
-
|
|
4898
|
-
prompt: "",
|
|
4899
|
-
promptType: "enterToContinue",
|
|
4900
|
-
options: [],
|
|
4901
|
-
// The wizard never streams command output, so a failed run's tail is the
|
|
4902
|
-
// only place the developer sees why it failed. One message per line:
|
|
4903
|
-
// the panel's height accounting counts a message as one wrapped line
|
|
4904
|
-
// (see Notices.tsx), so an embedded newline overflows it.
|
|
4905
|
-
messages: [
|
|
4906
|
-
ingestOutcomeMessage,
|
|
4907
|
-
...ingestFailureDetail?.split("\n").filter((l) => l.trim()) ?? [],
|
|
4908
|
-
...commandMessages
|
|
4909
|
-
]
|
|
4910
|
-
});
|
|
5121
|
+
extraInstructions = verificationRetryInstructions(verification);
|
|
4911
5122
|
}
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
5123
|
+
let searchKey;
|
|
5124
|
+
let searchKeyError;
|
|
5125
|
+
if (appId) {
|
|
5126
|
+
try {
|
|
5127
|
+
const resolved2 = await resolveSearchOnlyKey(
|
|
5128
|
+
targetIndex,
|
|
5129
|
+
appId,
|
|
5130
|
+
envSearchKey
|
|
5131
|
+
);
|
|
5132
|
+
searchKey = resolved2.key;
|
|
5133
|
+
summaries.push(
|
|
5134
|
+
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
5135
|
+
);
|
|
5136
|
+
} catch (err) {
|
|
5137
|
+
searchKeyError = err.message;
|
|
5138
|
+
logger.warn(
|
|
5139
|
+
{ err: searchKeyError },
|
|
5140
|
+
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4929
5141
|
);
|
|
4930
|
-
summaries.push(formatSummary("search", summary));
|
|
4931
|
-
const verification = await runVerificationUseCase();
|
|
4932
|
-
summaries.push(formatSummary("verification", verification.summary));
|
|
4933
|
-
if (verification.sufficient) {
|
|
4934
|
-
ctx.setUserInput("implementation", "success");
|
|
4935
|
-
const searchFilesChanged = (await listChangedFiles(worktree)).filter(
|
|
4936
|
-
(file) => !preSearchFiles.has(file)
|
|
4937
|
-
);
|
|
4938
|
-
track("AI Wizard Frontend Component Generated", {
|
|
4939
|
-
filePaths: searchFilesChanged
|
|
4940
|
-
});
|
|
4941
|
-
track("AI Wizard Wired to UI", {
|
|
4942
|
-
location_heuristic: searchLocation ?? "unknown"
|
|
4943
|
-
});
|
|
4944
|
-
break;
|
|
4945
|
-
}
|
|
4946
|
-
if (attempt === MAX_IMPLEMENT_VERIFICATION_ATTEMPTS) {
|
|
4947
|
-
ctx.setUserInput("implementation", "fail");
|
|
4948
|
-
throw new Error(
|
|
4949
|
-
`Implementation verification failed after ${MAX_IMPLEMENT_VERIFICATION_ATTEMPTS} attempts: ${verification.additionalInstructions ?? verification.summary}`
|
|
4950
|
-
);
|
|
4951
|
-
}
|
|
4952
|
-
extraInstructions = verificationRetryInstructions(verification);
|
|
4953
|
-
}
|
|
4954
|
-
let searchKey;
|
|
4955
|
-
let searchKeyError;
|
|
4956
|
-
if (appId) {
|
|
4957
|
-
try {
|
|
4958
|
-
const resolved2 = await resolveSearchOnlyKey(
|
|
4959
|
-
targetIndex,
|
|
4960
|
-
appId,
|
|
4961
|
-
envSearchKey
|
|
4962
|
-
);
|
|
4963
|
-
searchKey = resolved2.key;
|
|
4964
|
-
summaries.push(
|
|
4965
|
-
resolved2.source === "created" ? `Created a new search-only Algolia API key for the "${targetIndex}" index in app ${appId} \u2014 safe to expose in frontend code.` : `Reused the existing search-only Algolia API key for the "${targetIndex}" index in app ${appId}.`
|
|
4966
|
-
);
|
|
4967
|
-
} catch (err) {
|
|
4968
|
-
searchKeyError = err.message;
|
|
4969
|
-
logger.warn(
|
|
4970
|
-
{ err: searchKeyError },
|
|
4971
|
-
"implement: could not provision a search-only API key; the .env value stays a placeholder"
|
|
4972
|
-
);
|
|
4973
|
-
}
|
|
4974
5142
|
}
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
5143
|
+
}
|
|
5144
|
+
finalSearchEnvVars = publicSearchEnvVars(
|
|
5145
|
+
publicEnvVarPrefix,
|
|
5146
|
+
targetIndex,
|
|
5147
|
+
appId,
|
|
5148
|
+
searchKey
|
|
5149
|
+
);
|
|
5150
|
+
const resolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
5151
|
+
(v) => !v.value.startsWith("<")
|
|
5152
|
+
);
|
|
5153
|
+
if (resolvedSearchEnvVars.length > 0) {
|
|
5154
|
+
const written = await writeSearchEnvValues(
|
|
5155
|
+
repoRoot,
|
|
5156
|
+
resolvedSearchEnvVars
|
|
4983
5157
|
);
|
|
4984
|
-
if (
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5158
|
+
if (written.length > 0) {
|
|
5159
|
+
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
5160
|
+
}
|
|
5161
|
+
const ignored = await ensureGitIgnored(repoRoot, join12(repoRoot, ".env"));
|
|
5162
|
+
if (ignored === "added") {
|
|
5163
|
+
summaries.push("Added .env to .gitignore.");
|
|
5164
|
+
} else if (ignored === "tracked") {
|
|
5165
|
+
summaries.push(
|
|
5166
|
+
'\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
|
|
4988
5167
|
);
|
|
4989
|
-
if (written.length > 0) {
|
|
4990
|
-
summaries.push(`Wrote ${written.join(", ")} to .env.`);
|
|
4991
|
-
}
|
|
4992
|
-
const ignored = await ensureGitIgnored(worktree, join12(worktree, ".env"));
|
|
4993
|
-
if (ignored === "added") {
|
|
4994
|
-
summaries.push("Added .env to .gitignore.");
|
|
4995
|
-
} else if (ignored === "tracked") {
|
|
4996
|
-
summaries.push(
|
|
4997
|
-
'\u26A0\uFE0F .env is tracked by git, so a .gitignore rule cannot un-stage it. Run "git rm --cached .env" before committing, or the credentials go into history.'
|
|
4998
|
-
);
|
|
4999
|
-
}
|
|
5000
|
-
const stale = [];
|
|
5001
|
-
for (const v of resolvedSearchEnvVars) {
|
|
5002
|
-
if (written.includes(v.name)) continue;
|
|
5003
|
-
const current = await readEnvVar(worktree, v.name);
|
|
5004
|
-
if (current && current !== v.value) stale.push(v);
|
|
5005
|
-
}
|
|
5006
|
-
if (stale.length > 0 && !envAppIdMismatch) {
|
|
5007
|
-
summaries.push(
|
|
5008
|
-
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
5009
|
-
);
|
|
5010
|
-
logger.warn(
|
|
5011
|
-
{ vars: stale.map((v) => v.name) },
|
|
5012
|
-
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
5013
|
-
);
|
|
5014
|
-
}
|
|
5015
5168
|
}
|
|
5016
|
-
const
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5169
|
+
const stale = [];
|
|
5170
|
+
for (const v of resolvedSearchEnvVars) {
|
|
5171
|
+
if (written.includes(v.name)) continue;
|
|
5172
|
+
const current = await readEnvVar(repoRoot, v.name);
|
|
5173
|
+
if (current && current !== v.value) stale.push(v);
|
|
5174
|
+
}
|
|
5175
|
+
if (stale.length > 0 && !envAppIdMismatch) {
|
|
5020
5176
|
summaries.push(
|
|
5021
|
-
|
|
5022
|
-
|
|
5177
|
+
`\u26A0\uFE0F .env already assigns a different value to ${stale.map((v) => `${v.name} (should be ${v.value})`).join(", ")} \u2014 the wizard left it alone. Fix it by hand, or searches will fail.`
|
|
5178
|
+
);
|
|
5179
|
+
logger.warn(
|
|
5180
|
+
{ vars: stale.map((v) => v.name) },
|
|
5181
|
+
"implement: .env holds different values for the resolved search credentials; not overwriting them"
|
|
5023
5182
|
);
|
|
5024
5183
|
}
|
|
5025
|
-
} else {
|
|
5026
|
-
ctx.setUserInput("implementation", "success");
|
|
5027
5184
|
}
|
|
5028
|
-
const
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5185
|
+
const unresolvedSearchEnvVars = finalSearchEnvVars.filter(
|
|
5186
|
+
(v) => v.value.startsWith("<")
|
|
5187
|
+
);
|
|
5188
|
+
if (unresolvedSearchEnvVars.length > 0) {
|
|
5189
|
+
summaries.push(
|
|
5190
|
+
`Could not resolve a value for ${unresolvedSearchEnvVars.map((v) => v.name).join(", ")} \u2014 fill it in manually in .env.` + (searchKeyError ? ` Reason: ${searchKeyError}` : "")
|
|
5032
5191
|
);
|
|
5033
5192
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
filesChanged,
|
|
5037
|
-
summary: summaries.join("\n\n"),
|
|
5038
|
-
worktreePath: worktree,
|
|
5039
|
-
...useCases.includes("ingestion") && ingestCommand ? {
|
|
5040
|
-
ingestCommand,
|
|
5041
|
-
ingestScriptRan,
|
|
5042
|
-
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
5043
|
-
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
5044
|
-
} : {},
|
|
5045
|
-
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
5046
|
-
};
|
|
5047
|
-
} finally {
|
|
5048
|
-
process.chdir(repoRoot);
|
|
5193
|
+
} else {
|
|
5194
|
+
ctx.setUserInput("implementation", "success");
|
|
5049
5195
|
}
|
|
5196
|
+
return {
|
|
5197
|
+
ingestionSource,
|
|
5198
|
+
summary: summaries.join("\n\n"),
|
|
5199
|
+
...useCases.includes("ingestion") && ingestCommand ? {
|
|
5200
|
+
ingestCommand,
|
|
5201
|
+
ingestScriptRan,
|
|
5202
|
+
...ingestRecordCount != null ? { ingestRecordCount } : {},
|
|
5203
|
+
...ingestDurationMs != null ? { ingestDurationMs } : {}
|
|
5204
|
+
} : {},
|
|
5205
|
+
...useCases.includes("search") ? { searchEnvVars: finalSearchEnvVars } : {}
|
|
5206
|
+
};
|
|
5050
5207
|
}
|
|
5051
5208
|
|
|
5052
5209
|
// src/workflows/default.ts
|
|
@@ -5118,10 +5275,7 @@ var defaultWorkflow = {
|
|
|
5118
5275
|
ctx.notify({
|
|
5119
5276
|
messages: ["Building your Algolia search experience\u2026"]
|
|
5120
5277
|
});
|
|
5121
|
-
|
|
5122
|
-
"ingestion"
|
|
5123
|
-
);
|
|
5124
|
-
return implement(ctx, ["search"], ingestion2?.worktreePath);
|
|
5278
|
+
return implement(ctx, ["search"]);
|
|
5125
5279
|
}
|
|
5126
5280
|
}),
|
|
5127
5281
|
defineStep({
|
|
@@ -5136,9 +5290,8 @@ var defaultWorkflow = {
|
|
|
5136
5290
|
"ingestion"
|
|
5137
5291
|
);
|
|
5138
5292
|
return reviewStep(ctx, {
|
|
5139
|
-
//
|
|
5140
|
-
//
|
|
5141
|
-
// an LLM-paraphrased command risks being wrong.
|
|
5293
|
+
// ingestCommand was already shown verbatim as a notice; an
|
|
5294
|
+
// LLM-paraphrased restatement in nextSteps risks being wrong.
|
|
5142
5295
|
nextStepsGuidance: ingestion2?.ingestScriptRan ? "The wizard already ran the ingestion script and records are in the index. Do NOT tell the user to run it again; instead point them at the target index to confirm the records. Do not restate the ingestion command \u2014 the wizard already showed it to them." : "Tell the user to run the ingestion script; do not restate the exact command \u2014 the wizard already showed it to them above."
|
|
5143
5296
|
});
|
|
5144
5297
|
}
|
|
@@ -5185,7 +5338,6 @@ var selectIndex = {
|
|
|
5185
5338
|
selection: "wizard_seed_products"
|
|
5186
5339
|
};
|
|
5187
5340
|
var ingestion = {
|
|
5188
|
-
filesChanged: ["algolia/ingest.mjs", "algolia/records.json", "package.json"],
|
|
5189
5341
|
summary: "Generated sample Product records and an ingestion script that pushes them to the target index with algoliasearch.",
|
|
5190
5342
|
ingestCommand: "node algolia/ingest.mjs",
|
|
5191
5343
|
ingestScriptRan: true,
|
|
@@ -5197,7 +5349,6 @@ var confirmFramework2 = {
|
|
|
5197
5349
|
frameworks: projectScan2.frameworks
|
|
5198
5350
|
};
|
|
5199
5351
|
var search = {
|
|
5200
|
-
filesChanged: ["src/components/Search.tsx", "src/components/Header.tsx", ".env"],
|
|
5201
5352
|
summary: "Added an InstantSearch-powered search box and results list, mounted in the shared header component.",
|
|
5202
5353
|
ingestionSource: "generated",
|
|
5203
5354
|
searchEnvVars: [
|
|
@@ -5210,7 +5361,7 @@ var review = {
|
|
|
5210
5361
|
"Ingested 25 generated Product records into wizard_seed_products.",
|
|
5211
5362
|
"Added an InstantSearch search experience to the shared header."
|
|
5212
5363
|
],
|
|
5213
|
-
reviewPrompt: "Review the Algolia ingestion and search changes
|
|
5364
|
+
reviewPrompt: "Review the Algolia ingestion and search changes.",
|
|
5214
5365
|
nextSteps: ["Point the ingestion script at your real product data."]
|
|
5215
5366
|
};
|
|
5216
5367
|
var SEEDS = {
|
|
@@ -5327,9 +5478,9 @@ Options:
|
|
|
5327
5478
|
steps pre-filled with test data. Pass with no value to print
|
|
5328
5479
|
the step ids. See CONTRIBUTING.md.
|
|
5329
5480
|
--no-telemetry Send no telemetry or analytics for this run.
|
|
5330
|
-
--reset-on-run Wipe this project's wizard state (run state, AI consent
|
|
5331
|
-
|
|
5332
|
-
|
|
5481
|
+
--reset-on-run Wipe this project's wizard state (run state, AI consent)
|
|
5482
|
+
before starting, so the run behaves like a first-ever
|
|
5483
|
+
run. Also drops every API key the wizard has
|
|
5333
5484
|
stored in your keychain (or, where the platform has none,
|
|
5334
5485
|
the encrypted file it falls back to \u2014 see CONTRIBUTING.md),
|
|
5335
5486
|
for this project and any other, so later runs create new
|
|
@@ -5369,7 +5520,7 @@ function parseCliArgs(argv) {
|
|
|
5369
5520
|
}
|
|
5370
5521
|
|
|
5371
5522
|
// src/lib/resetState.ts
|
|
5372
|
-
import { readdir as
|
|
5523
|
+
import { readdir as readdir3, rm as rm2 } from "node:fs/promises";
|
|
5373
5524
|
import { join as join13 } from "node:path";
|
|
5374
5525
|
var KEEP = ["wizard.log"];
|
|
5375
5526
|
async function resetProjectState() {
|
|
@@ -5377,7 +5528,7 @@ async function resetProjectState() {
|
|
|
5377
5528
|
await forgetResolvedKeys();
|
|
5378
5529
|
let entries;
|
|
5379
5530
|
try {
|
|
5380
|
-
entries = await
|
|
5531
|
+
entries = await readdir3(dir);
|
|
5381
5532
|
} catch {
|
|
5382
5533
|
return { dir, removed: [] };
|
|
5383
5534
|
}
|
|
@@ -5430,7 +5581,7 @@ function delay(ms) {
|
|
|
5430
5581
|
}
|
|
5431
5582
|
|
|
5432
5583
|
// src/main.tsx
|
|
5433
|
-
import { jsx as
|
|
5584
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
5434
5585
|
async function startup() {
|
|
5435
5586
|
setProjectRoot(process.cwd());
|
|
5436
5587
|
let args;
|
|
@@ -5480,7 +5631,7 @@ ${formatStepList(workflow)}`);
|
|
|
5480
5631
|
}
|
|
5481
5632
|
async function run(workflow) {
|
|
5482
5633
|
const store = useWizard.getState();
|
|
5483
|
-
const instance = render(/* @__PURE__ */
|
|
5634
|
+
const instance = render(/* @__PURE__ */ jsx18(App, {}), { incrementalRendering: true });
|
|
5484
5635
|
await store.waitForStart();
|
|
5485
5636
|
let user = await getUser();
|
|
5486
5637
|
if (!user) {
|