@algolia/wizard 0.8.0-rc.58.45 → 0.8.0-rc.60.48
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
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 Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/core/store.ts
|
|
10
10
|
import { create } from "zustand";
|
|
@@ -497,9 +497,9 @@ function Notices() {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
// src/ui/PromptInput.tsx
|
|
500
|
-
import { Box as
|
|
500
|
+
import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
|
|
501
501
|
import TextInput from "ink-text-input";
|
|
502
|
-
import { useState as
|
|
502
|
+
import { useState as useState4 } from "react";
|
|
503
503
|
|
|
504
504
|
// src/ui/NextAction.tsx
|
|
505
505
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -525,14 +525,13 @@ function NextAction({
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
529
|
-
import { useLayoutEffect
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
var INDICATOR_ROWS = 2;
|
|
528
|
+
import { Box as Box4, Text as Text4, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
|
|
529
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
530
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
531
|
+
var CANCEL = "cancel";
|
|
532
|
+
var ARROW_WIDTH = 4;
|
|
533
|
+
var COLUMN_GAP = 2;
|
|
534
|
+
var BAR_PADDING = 2;
|
|
536
535
|
function fittedWidth(node, columns) {
|
|
537
536
|
let left = 0;
|
|
538
537
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -540,89 +539,6 @@ function fittedWidth(node, columns) {
|
|
|
540
539
|
}
|
|
541
540
|
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
542
541
|
}
|
|
543
|
-
function useScrollWindow({
|
|
544
|
-
itemCount,
|
|
545
|
-
rowHeight = 1,
|
|
546
|
-
followBottom = false
|
|
547
|
-
}) {
|
|
548
|
-
const viewportRef = useRef2(null);
|
|
549
|
-
const { columns } = useWindowSize3();
|
|
550
|
-
const [size, setSize] = useState3(
|
|
551
|
-
null
|
|
552
|
-
);
|
|
553
|
-
useLayoutEffect(() => {
|
|
554
|
-
if (!viewportRef.current) return;
|
|
555
|
-
const width = fittedWidth(viewportRef.current, columns);
|
|
556
|
-
const { height } = measureElement2(viewportRef.current);
|
|
557
|
-
setSize(
|
|
558
|
-
(prev) => prev?.width === width && prev.height === height ? prev : { width, height }
|
|
559
|
-
);
|
|
560
|
-
});
|
|
561
|
-
const capacity = size === null || itemCount * rowHeight <= size.height ? itemCount : Math.max(Math.floor((size.height - INDICATOR_ROWS) / rowHeight), 1);
|
|
562
|
-
const maxOffset = Math.max(itemCount - capacity, 0);
|
|
563
|
-
const [offset, setOffset] = useState3(0);
|
|
564
|
-
const prevMaxOffsetRef = useRef2(0);
|
|
565
|
-
useLayoutEffect(() => {
|
|
566
|
-
const wasAtBottom = offset >= prevMaxOffsetRef.current;
|
|
567
|
-
prevMaxOffsetRef.current = maxOffset;
|
|
568
|
-
setOffset(
|
|
569
|
-
(o) => followBottom && wasAtBottom ? maxOffset : Math.min(o, maxOffset)
|
|
570
|
-
);
|
|
571
|
-
}, [maxOffset, followBottom]);
|
|
572
|
-
const scrollBy = useCallback(
|
|
573
|
-
(delta) => {
|
|
574
|
-
setOffset((o) => Math.min(Math.max(o + delta, 0), maxOffset));
|
|
575
|
-
},
|
|
576
|
-
[maxOffset]
|
|
577
|
-
);
|
|
578
|
-
const revealIndex = useCallback(
|
|
579
|
-
(index) => {
|
|
580
|
-
setOffset((o) => {
|
|
581
|
-
if (index < o) return index;
|
|
582
|
-
if (index >= o + capacity) {
|
|
583
|
-
return Math.min(index - capacity + 1, maxOffset);
|
|
584
|
-
}
|
|
585
|
-
return o;
|
|
586
|
-
});
|
|
587
|
-
},
|
|
588
|
-
[capacity, maxOffset]
|
|
589
|
-
);
|
|
590
|
-
const visibleCount = Math.min(capacity, Math.max(itemCount - offset, 0));
|
|
591
|
-
return {
|
|
592
|
-
viewportRef,
|
|
593
|
-
width: size?.width ?? columns,
|
|
594
|
-
offset,
|
|
595
|
-
capacity,
|
|
596
|
-
maxOffset,
|
|
597
|
-
hiddenAbove: Math.min(offset, itemCount),
|
|
598
|
-
hiddenBelow: Math.max(itemCount - offset - visibleCount, 0),
|
|
599
|
-
scrollBy,
|
|
600
|
-
revealIndex
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function ScrollView({ scroll, children }) {
|
|
604
|
-
return /* @__PURE__ */ jsxs3(Box4, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
605
|
-
scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
606
|
-
"\u2191 ",
|
|
607
|
-
scroll.hiddenAbove,
|
|
608
|
-
" more"
|
|
609
|
-
] }),
|
|
610
|
-
children,
|
|
611
|
-
scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
612
|
-
"\u2193 ",
|
|
613
|
-
scroll.hiddenBelow,
|
|
614
|
-
" more"
|
|
615
|
-
] })
|
|
616
|
-
] });
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// src/ui/SelectPrompt.tsx
|
|
620
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
621
|
-
var CANCEL = "cancel";
|
|
622
|
-
var ARROW_WIDTH = 4;
|
|
623
|
-
var COLUMN_GAP = 2;
|
|
624
|
-
var BAR_PADDING = 2;
|
|
625
|
-
var ROW_HEIGHT = 3;
|
|
626
542
|
function SelectPrompt({
|
|
627
543
|
options,
|
|
628
544
|
onSelect,
|
|
@@ -636,10 +552,10 @@ function SelectPrompt({
|
|
|
636
552
|
secondary,
|
|
637
553
|
defaultSelectedIndex = 0
|
|
638
554
|
}) {
|
|
639
|
-
const [index, setIndex] =
|
|
555
|
+
const [index, setIndex] = useState3(
|
|
640
556
|
() => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
|
|
641
557
|
);
|
|
642
|
-
const [checked, setChecked] =
|
|
558
|
+
const [checked, setChecked] = useState3(() => /* @__PURE__ */ new Set());
|
|
643
559
|
const hasCancel = Boolean(multi || cancelable);
|
|
644
560
|
const rows = hasCancel ? [...options, "Cancel"] : options;
|
|
645
561
|
const cancelIndex = hasCancel ? options.length : -1;
|
|
@@ -647,14 +563,14 @@ function SelectPrompt({
|
|
|
647
563
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
648
564
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
649
565
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
650
|
-
const containerRef =
|
|
651
|
-
const { columns } =
|
|
652
|
-
const [width, setWidth] =
|
|
653
|
-
|
|
654
|
-
if (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
566
|
+
const containerRef = useRef2(null);
|
|
567
|
+
const { columns } = useWindowSize3();
|
|
568
|
+
const [width, setWidth] = useState3(columns);
|
|
569
|
+
useLayoutEffect(() => {
|
|
570
|
+
if (containerRef.current) {
|
|
571
|
+
setWidth(fittedWidth(containerRef.current, columns));
|
|
572
|
+
}
|
|
573
|
+
}, [columns]);
|
|
658
574
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
659
575
|
const labelWidth = Math.min(
|
|
660
576
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -670,15 +586,6 @@ function SelectPrompt({
|
|
|
670
586
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
671
587
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
672
588
|
const textWidth = inner - labelWidth;
|
|
673
|
-
const scroll = useScrollWindow({
|
|
674
|
-
itemCount: rows.length,
|
|
675
|
-
rowHeight: ROW_HEIGHT
|
|
676
|
-
});
|
|
677
|
-
const { revealIndex } = scroll;
|
|
678
|
-
useLayoutEffect2(() => {
|
|
679
|
-
revealIndex(index);
|
|
680
|
-
}, [index, revealIndex]);
|
|
681
|
-
const visible = rows.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
682
589
|
useInput((input, key) => {
|
|
683
590
|
if (rows.length === 0) return;
|
|
684
591
|
if (key.upArrow || input === "k") {
|
|
@@ -702,65 +609,62 @@ function SelectPrompt({
|
|
|
702
609
|
}
|
|
703
610
|
}
|
|
704
611
|
});
|
|
705
|
-
return /* @__PURE__ */ jsx4(
|
|
706
|
-
/* @__PURE__ */
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
/* @__PURE__ */
|
|
711
|
-
|
|
712
|
-
helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
|
|
713
|
-
] })
|
|
612
|
+
return /* @__PURE__ */ jsx4(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
|
|
613
|
+
error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
|
|
614
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
615
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
616
|
+
/* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
617
|
+
question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
|
|
618
|
+
helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
|
|
714
619
|
] }),
|
|
715
|
-
/* @__PURE__ */ jsx4(
|
|
716
|
-
const i = scroll.offset + visibleIndex;
|
|
620
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rows.map((option, i) => {
|
|
717
621
|
const highlighted = i === index;
|
|
718
622
|
const isCancel = i === cancelIndex;
|
|
719
623
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
720
624
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
721
625
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
722
|
-
const label = /* @__PURE__ */
|
|
626
|
+
const label = /* @__PURE__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
|
|
723
627
|
highlighted ? "\u276F " : " ",
|
|
724
628
|
bullet,
|
|
725
629
|
option
|
|
726
630
|
] });
|
|
727
631
|
const isText = sec?.kind === "text";
|
|
728
|
-
return /* @__PURE__ */
|
|
729
|
-
|
|
632
|
+
return /* @__PURE__ */ jsxs3(
|
|
633
|
+
Box4,
|
|
730
634
|
{
|
|
731
635
|
width: isText ? "100%" : barWidth,
|
|
732
636
|
paddingX: 1,
|
|
733
637
|
paddingY: 1,
|
|
734
638
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
735
639
|
children: [
|
|
736
|
-
/* @__PURE__ */ jsx4(
|
|
737
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
738
|
-
|
|
640
|
+
/* @__PURE__ */ jsx4(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
641
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
642
|
+
Text4,
|
|
739
643
|
{
|
|
740
644
|
wrap: "truncate",
|
|
741
645
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
742
646
|
children: sec.value
|
|
743
647
|
}
|
|
744
648
|
) }),
|
|
745
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
649
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
746
650
|
]
|
|
747
651
|
},
|
|
748
652
|
`row-${i}`
|
|
749
653
|
);
|
|
750
654
|
}) }),
|
|
751
|
-
/* @__PURE__ */ jsx4(
|
|
655
|
+
/* @__PURE__ */ jsx4(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
752
656
|
i > 0 ? " " : "",
|
|
753
|
-
/* @__PURE__ */ jsx4(
|
|
754
|
-
/* @__PURE__ */
|
|
657
|
+
/* @__PURE__ */ jsx4(Text4, { color: COLORS.primary, children: key }),
|
|
658
|
+
/* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
|
|
755
659
|
" ",
|
|
756
660
|
label
|
|
757
661
|
] })
|
|
758
|
-
] }, label)) })
|
|
662
|
+
] }, label)) })
|
|
759
663
|
] }) });
|
|
760
664
|
}
|
|
761
665
|
|
|
762
666
|
// src/ui/PromptInput.tsx
|
|
763
|
-
import { jsx as jsx5, jsxs as
|
|
667
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
764
668
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
765
669
|
function EnterToContinuePrompt({
|
|
766
670
|
question,
|
|
@@ -771,10 +675,10 @@ function EnterToContinuePrompt({
|
|
|
771
675
|
if (key.return) onDecide(true);
|
|
772
676
|
else if (key.escape) onDecide(false);
|
|
773
677
|
});
|
|
774
|
-
return /* @__PURE__ */
|
|
775
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
776
|
-
question && /* @__PURE__ */ jsx5(
|
|
777
|
-
/* @__PURE__ */
|
|
678
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
|
|
679
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
680
|
+
question && /* @__PURE__ */ jsx5(Text5, { color: COLORS.primary, children: question }),
|
|
681
|
+
/* @__PURE__ */ jsxs4(Box5, { gap: 1, flexDirection: "column", children: [
|
|
778
682
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
779
683
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
780
684
|
] })
|
|
@@ -782,13 +686,13 @@ function EnterToContinuePrompt({
|
|
|
782
686
|
}
|
|
783
687
|
function PromptInput() {
|
|
784
688
|
const { phase, inputReq, submitInput } = useWizard();
|
|
785
|
-
const [draft, setDraft] =
|
|
689
|
+
const [draft, setDraft] = useState4("");
|
|
786
690
|
if (phase === "done" || phase === "error") {
|
|
787
|
-
return /* @__PURE__ */ jsx5(
|
|
691
|
+
return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
788
692
|
}
|
|
789
693
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
790
694
|
if (inputReq.promptType === "multipleChoice") {
|
|
791
|
-
return /* @__PURE__ */ jsx5(
|
|
695
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
792
696
|
SelectPrompt,
|
|
793
697
|
{
|
|
794
698
|
question: inputReq.prompt,
|
|
@@ -805,7 +709,7 @@ function PromptInput() {
|
|
|
805
709
|
) });
|
|
806
710
|
}
|
|
807
711
|
if (inputReq.promptType === "multiSelect") {
|
|
808
|
-
return /* @__PURE__ */ jsx5(
|
|
712
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
809
713
|
SelectPrompt,
|
|
810
714
|
{
|
|
811
715
|
multi: true,
|
|
@@ -820,7 +724,7 @@ function PromptInput() {
|
|
|
820
724
|
) });
|
|
821
725
|
}
|
|
822
726
|
if (inputReq.promptType === "notice") {
|
|
823
|
-
return /* @__PURE__ */ jsx5(
|
|
727
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
824
728
|
SelectPrompt,
|
|
825
729
|
{
|
|
826
730
|
question: inputReq.prompt,
|
|
@@ -842,7 +746,7 @@ function PromptInput() {
|
|
|
842
746
|
}
|
|
843
747
|
if (inputReq.promptType === "acceptReject") {
|
|
844
748
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
845
|
-
return /* @__PURE__ */ jsx5(
|
|
749
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
|
|
846
750
|
SelectPrompt,
|
|
847
751
|
{
|
|
848
752
|
question: inputReq.prompt,
|
|
@@ -853,11 +757,11 @@ function PromptInput() {
|
|
|
853
757
|
}
|
|
854
758
|
) });
|
|
855
759
|
}
|
|
856
|
-
return /* @__PURE__ */
|
|
857
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
858
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
859
|
-
/* @__PURE__ */
|
|
860
|
-
/* @__PURE__ */
|
|
760
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
761
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text5, { color: COLORS.danger, children: inputReq.error }),
|
|
762
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
763
|
+
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
764
|
+
/* @__PURE__ */ jsxs4(Text5, { color: COLORS.primary, children: [
|
|
861
765
|
inputReq.prompt,
|
|
862
766
|
" "
|
|
863
767
|
] }),
|
|
@@ -879,7 +783,7 @@ function PromptInput() {
|
|
|
879
783
|
// src/ui/Welcome.tsx
|
|
880
784
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
881
785
|
import { fileURLToPath } from "node:url";
|
|
882
|
-
import { Box as
|
|
786
|
+
import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
|
|
883
787
|
|
|
884
788
|
// src/ui/copy/welcome.ts
|
|
885
789
|
var sidebarItems = [
|
|
@@ -892,12 +796,12 @@ var sidebarItems = [
|
|
|
892
796
|
description: "push 100 records to Algolia in seconds"
|
|
893
797
|
},
|
|
894
798
|
{
|
|
895
|
-
title: "detect your
|
|
896
|
-
description: "React, Vue, Angular,
|
|
799
|
+
title: "detect your stack",
|
|
800
|
+
description: "React, Vue, Angular, Rails, Django, Laravel & more"
|
|
897
801
|
},
|
|
898
802
|
{
|
|
899
803
|
title: "scaffold a search UI",
|
|
900
|
-
description: "a styled InstantSearch
|
|
804
|
+
description: "a styled InstantSearch UI, wired into your app or templates"
|
|
901
805
|
},
|
|
902
806
|
{
|
|
903
807
|
title: "ship it",
|
|
@@ -907,27 +811,27 @@ var sidebarItems = [
|
|
|
907
811
|
|
|
908
812
|
// src/ui/Welcome.tsx
|
|
909
813
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
910
|
-
import { jsx as jsx6, jsxs as
|
|
814
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
911
815
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
912
816
|
function SidebarItem({
|
|
913
817
|
title,
|
|
914
818
|
description
|
|
915
819
|
}) {
|
|
916
|
-
return /* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
918
|
-
/* @__PURE__ */ jsx6(
|
|
919
|
-
/* @__PURE__ */ jsx6(
|
|
820
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
821
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
|
|
822
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.success, children: "\u2192" }),
|
|
823
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.strong, bold: true, children: title })
|
|
920
824
|
] }),
|
|
921
|
-
/* @__PURE__ */
|
|
825
|
+
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
|
|
922
826
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
923
|
-
/* @__PURE__ */ jsx6(
|
|
827
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
|
|
924
828
|
] })
|
|
925
829
|
] });
|
|
926
830
|
}
|
|
927
831
|
function Welcome() {
|
|
928
832
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
929
833
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
930
|
-
const { rows } =
|
|
834
|
+
const { rows } = useWindowSize4();
|
|
931
835
|
useInput3((input, key) => {
|
|
932
836
|
if (key.return) confirmStart();
|
|
933
837
|
else if (input === "i") openLearnMore();
|
|
@@ -946,15 +850,15 @@ function Welcome() {
|
|
|
946
850
|
if (rows < 30) {
|
|
947
851
|
layout = scales["small"];
|
|
948
852
|
}
|
|
949
|
-
return /* @__PURE__ */
|
|
853
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
950
854
|
/* @__PURE__ */ jsx6(
|
|
951
|
-
|
|
855
|
+
Box6,
|
|
952
856
|
{
|
|
953
857
|
paddingY: layout.main.padding.y,
|
|
954
858
|
paddingX: layout.main.padding.x,
|
|
955
859
|
flexDirection: "column",
|
|
956
860
|
justifyContent: "center",
|
|
957
|
-
children: /* @__PURE__ */
|
|
861
|
+
children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
|
|
958
862
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
959
863
|
Image,
|
|
960
864
|
{
|
|
@@ -966,16 +870,16 @@ function Welcome() {
|
|
|
966
870
|
protocol: "halfBlock"
|
|
967
871
|
}
|
|
968
872
|
) }),
|
|
969
|
-
/* @__PURE__ */ jsx6(
|
|
970
|
-
/* @__PURE__ */
|
|
873
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
874
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
|
|
971
875
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
972
876
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
973
877
|
] })
|
|
974
878
|
] })
|
|
975
879
|
}
|
|
976
880
|
),
|
|
977
|
-
/* @__PURE__ */
|
|
978
|
-
|
|
881
|
+
/* @__PURE__ */ jsxs5(
|
|
882
|
+
Box6,
|
|
979
883
|
{
|
|
980
884
|
backgroundColor: COLORS.bg.sidebar,
|
|
981
885
|
width: 40,
|
|
@@ -985,7 +889,7 @@ function Welcome() {
|
|
|
985
889
|
flexDirection: "column",
|
|
986
890
|
justifyContent: "center",
|
|
987
891
|
children: [
|
|
988
|
-
/* @__PURE__ */ jsx6(
|
|
892
|
+
/* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
989
893
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
990
894
|
]
|
|
991
895
|
}
|
|
@@ -995,7 +899,7 @@ function Welcome() {
|
|
|
995
899
|
|
|
996
900
|
// src/ui/LearnMore.tsx
|
|
997
901
|
import { Fragment as Fragment2 } from "react";
|
|
998
|
-
import { Box as
|
|
902
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
|
|
999
903
|
|
|
1000
904
|
// src/ui/copy/learn-more.ts
|
|
1001
905
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -1003,7 +907,7 @@ var accessItems = [
|
|
|
1003
907
|
{
|
|
1004
908
|
tag: "READ",
|
|
1005
909
|
title: "Project files",
|
|
1006
|
-
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
910
|
+
description: "reads your dependency manifests (package.json, pyproject.toml\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
1007
911
|
},
|
|
1008
912
|
{
|
|
1009
913
|
tag: "WRITE",
|
|
@@ -1032,7 +936,7 @@ var policyLinks = [
|
|
|
1032
936
|
];
|
|
1033
937
|
|
|
1034
938
|
// src/ui/LearnMore.tsx
|
|
1035
|
-
import { jsx as jsx7, jsxs as
|
|
939
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1036
940
|
var TAG_COLORS = {
|
|
1037
941
|
READ: COLORS.success,
|
|
1038
942
|
WRITE: COLORS.badge,
|
|
@@ -1048,25 +952,25 @@ function NeverLine({
|
|
|
1048
952
|
}) {
|
|
1049
953
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
1050
954
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
1051
|
-
return /* @__PURE__ */
|
|
1052
|
-
/* @__PURE__ */ jsx7(
|
|
955
|
+
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
956
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" }),
|
|
1053
957
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
1054
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
958
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
1055
959
|
" ".repeat(rightPad),
|
|
1056
|
-
/* @__PURE__ */ jsx7(
|
|
960
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" })
|
|
1057
961
|
] });
|
|
1058
962
|
}
|
|
1059
963
|
function LearnMore() {
|
|
1060
964
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
1061
965
|
const backToHome = useWizard((s) => s.backToHome);
|
|
1062
|
-
const { columns } =
|
|
966
|
+
const { columns } = useWindowSize5();
|
|
1063
967
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
1064
968
|
useInput4((_input, key) => {
|
|
1065
969
|
if (key.escape) backToHome();
|
|
1066
970
|
else if (key.return) confirmStart();
|
|
1067
971
|
});
|
|
1068
|
-
return /* @__PURE__ */
|
|
1069
|
-
|
|
972
|
+
return /* @__PURE__ */ jsxs6(
|
|
973
|
+
Box7,
|
|
1070
974
|
{
|
|
1071
975
|
flexDirection: "column",
|
|
1072
976
|
paddingX: PADDING_X,
|
|
@@ -1074,20 +978,20 @@ function LearnMore() {
|
|
|
1074
978
|
width: "100%",
|
|
1075
979
|
gap: 1,
|
|
1076
980
|
children: [
|
|
1077
|
-
/* @__PURE__ */ jsx7(
|
|
1078
|
-
/* @__PURE__ */ jsx7(
|
|
1079
|
-
/* @__PURE__ */ jsx7(
|
|
1080
|
-
/* @__PURE__ */ jsx7(
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */ jsx7(
|
|
1083
|
-
/* @__PURE__ */ jsx7(
|
|
1084
|
-
/* @__PURE__ */ jsx7(
|
|
1085
|
-
/* @__PURE__ */ jsx7(
|
|
981
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
982
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: accessIntro }),
|
|
983
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
984
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
985
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
986
|
+
/* @__PURE__ */ jsx7(Box7, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text7, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
987
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
988
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: item.title }),
|
|
989
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
1086
990
|
] }) })
|
|
1087
991
|
] })
|
|
1088
992
|
] }, item.tag)) }),
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
/* @__PURE__ */ jsx7(
|
|
993
|
+
/* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
994
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
1091
995
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1092
996
|
/* @__PURE__ */ jsx7(
|
|
1093
997
|
NeverLine,
|
|
@@ -1096,7 +1000,7 @@ function LearnMore() {
|
|
|
1096
1000
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1097
1001
|
}
|
|
1098
1002
|
),
|
|
1099
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1003
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1100
1004
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1101
1005
|
/* @__PURE__ */ jsx7(
|
|
1102
1006
|
NeverLine,
|
|
@@ -1111,23 +1015,23 @@ function LearnMore() {
|
|
|
1111
1015
|
)
|
|
1112
1016
|
] }, item)),
|
|
1113
1017
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1114
|
-
/* @__PURE__ */ jsx7(
|
|
1018
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1115
1019
|
] }),
|
|
1116
|
-
/* @__PURE__ */ jsx7(
|
|
1117
|
-
/* @__PURE__ */ jsx7(
|
|
1118
|
-
/* @__PURE__ */ jsx7(
|
|
1020
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
1021
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1022
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.accent, children: link.url })
|
|
1119
1023
|
] }, link.label)) }),
|
|
1120
|
-
/* @__PURE__ */
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */ jsx7(
|
|
1123
|
-
/* @__PURE__ */ jsx7(
|
|
1124
|
-
/* @__PURE__ */ jsx7(
|
|
1024
|
+
/* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1025
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
1026
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
|
|
1027
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "esc" }),
|
|
1028
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "] back" })
|
|
1125
1029
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1127
|
-
/* @__PURE__ */ jsx7(
|
|
1128
|
-
/* @__PURE__ */ jsx7(
|
|
1129
|
-
/* @__PURE__ */ jsx7(
|
|
1130
|
-
/* @__PURE__ */ jsx7(
|
|
1030
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
|
|
1031
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
|
|
1032
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "enter" }),
|
|
1033
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "]" }),
|
|
1034
|
+
/* @__PURE__ */ jsx7(Text7, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1131
1035
|
] })
|
|
1132
1036
|
] })
|
|
1133
1037
|
]
|
|
@@ -1136,10 +1040,10 @@ function LearnMore() {
|
|
|
1136
1040
|
}
|
|
1137
1041
|
|
|
1138
1042
|
// src/ui/Sidebar.tsx
|
|
1139
|
-
import { Box as
|
|
1043
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1140
1044
|
|
|
1141
1045
|
// src/ui/Steps.tsx
|
|
1142
|
-
import { Box as
|
|
1046
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
1143
1047
|
import Spinner from "ink-spinner";
|
|
1144
1048
|
|
|
1145
1049
|
// src/core/persistence.ts
|
|
@@ -1168,11 +1072,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1168
1072
|
}
|
|
1169
1073
|
|
|
1170
1074
|
// src/ui/Steps.tsx
|
|
1171
|
-
import { jsx as jsx8, jsxs as
|
|
1075
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1172
1076
|
function Steps() {
|
|
1173
1077
|
const { steps } = useWizard();
|
|
1174
1078
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1175
|
-
return /* @__PURE__ */ jsx8(
|
|
1079
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status[s.status], children: [
|
|
1176
1080
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1177
1081
|
" ",
|
|
1178
1082
|
s.title
|
|
@@ -1182,7 +1086,7 @@ function CurrentStep() {
|
|
|
1182
1086
|
const { steps } = useWizard();
|
|
1183
1087
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1184
1088
|
if (!currentStep) return null;
|
|
1185
|
-
return /* @__PURE__ */
|
|
1089
|
+
return /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
|
|
1186
1090
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1187
1091
|
" ",
|
|
1188
1092
|
` ${currentStep.title}`
|
|
@@ -1190,19 +1094,19 @@ function CurrentStep() {
|
|
|
1190
1094
|
}
|
|
1191
1095
|
|
|
1192
1096
|
// src/ui/Progress.tsx
|
|
1193
|
-
import { Box as
|
|
1194
|
-
import { jsx as jsx9, jsxs as
|
|
1097
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1098
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1195
1099
|
function Progress() {
|
|
1196
1100
|
const { steps, currentStepIndex } = useWizard();
|
|
1197
1101
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1198
1102
|
if (visibleSteps.length === 0) return null;
|
|
1199
1103
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1200
1104
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1201
|
-
return /* @__PURE__ */
|
|
1202
|
-
/* @__PURE__ */ jsx9(
|
|
1203
|
-
/* @__PURE__ */ jsx9(
|
|
1204
|
-
/* @__PURE__ */ jsx9(
|
|
1205
|
-
/* @__PURE__ */ jsx9(
|
|
1105
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
|
|
1106
|
+
/* @__PURE__ */ jsx9(Text9, { color: COLORS.muted, children: "STEP" }),
|
|
1107
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: activeStepNumber }),
|
|
1108
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "/" }),
|
|
1109
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: visibleSteps.length })
|
|
1206
1110
|
] });
|
|
1207
1111
|
}
|
|
1208
1112
|
|
|
@@ -1213,10 +1117,10 @@ var sidebarCommands = [
|
|
|
1213
1117
|
];
|
|
1214
1118
|
|
|
1215
1119
|
// src/ui/Sidebar.tsx
|
|
1216
|
-
import { jsx as jsx10, jsxs as
|
|
1120
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1217
1121
|
function Sidebar() {
|
|
1218
|
-
return /* @__PURE__ */
|
|
1219
|
-
|
|
1122
|
+
return /* @__PURE__ */ jsxs9(
|
|
1123
|
+
Box10,
|
|
1220
1124
|
{
|
|
1221
1125
|
backgroundColor: "#14171E",
|
|
1222
1126
|
width: 30,
|
|
@@ -1225,16 +1129,16 @@ function Sidebar() {
|
|
|
1225
1129
|
flexDirection: "column",
|
|
1226
1130
|
justifyContent: "space-between",
|
|
1227
1131
|
children: [
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */ jsx10(
|
|
1132
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
|
|
1133
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1230
1134
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1231
1135
|
] }),
|
|
1232
|
-
/* @__PURE__ */
|
|
1136
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
|
|
1233
1137
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1234
|
-
/* @__PURE__ */ jsx10(
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */ jsx10(
|
|
1237
|
-
/* @__PURE__ */ jsx10(
|
|
1138
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1139
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1140
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1141
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: c.description })
|
|
1238
1142
|
] });
|
|
1239
1143
|
}) })
|
|
1240
1144
|
] })
|
|
@@ -1244,12 +1148,12 @@ function Sidebar() {
|
|
|
1244
1148
|
}
|
|
1245
1149
|
|
|
1246
1150
|
// src/ui/Ribbon.tsx
|
|
1247
|
-
import { Box as
|
|
1248
|
-
import { jsx as jsx11, jsxs as
|
|
1151
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1152
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1249
1153
|
function Ribbon() {
|
|
1250
1154
|
const firstCommand = sidebarCommands[0];
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
|
|
1155
|
+
return /* @__PURE__ */ jsxs10(
|
|
1156
|
+
Box11,
|
|
1253
1157
|
{
|
|
1254
1158
|
backgroundColor: "#14171E",
|
|
1255
1159
|
flexDirection: "row",
|
|
@@ -1259,9 +1163,9 @@ function Ribbon() {
|
|
|
1259
1163
|
children: [
|
|
1260
1164
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1261
1165
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1262
|
-
/* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */ jsx11(
|
|
1264
|
-
/* @__PURE__ */ jsx11(
|
|
1166
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1167
|
+
/* @__PURE__ */ jsx11(Text11, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1168
|
+
/* @__PURE__ */ jsx11(Text11, { color: COLORS.muted, children: firstCommand.description })
|
|
1265
1169
|
] })
|
|
1266
1170
|
]
|
|
1267
1171
|
}
|
|
@@ -1272,8 +1176,9 @@ function Ribbon() {
|
|
|
1272
1176
|
import { useState as useState6 } from "react";
|
|
1273
1177
|
|
|
1274
1178
|
// src/ui/Logs.tsx
|
|
1275
|
-
import {
|
|
1276
|
-
import {
|
|
1179
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
|
|
1180
|
+
import { Box as Box12, Text as Text12, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
|
|
1181
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1277
1182
|
var KIND_COLOR = {
|
|
1278
1183
|
tool: COLORS.primary,
|
|
1279
1184
|
prompt: COLORS.badge
|
|
@@ -1303,32 +1208,75 @@ function formatTimestamp(ms) {
|
|
|
1303
1208
|
}
|
|
1304
1209
|
function Logs() {
|
|
1305
1210
|
const logs = useWizard((s) => s.logs);
|
|
1306
|
-
const
|
|
1211
|
+
const { rows, columns } = useWindowSize6();
|
|
1212
|
+
const viewportRef = useRef3(null);
|
|
1213
|
+
const [viewportHeight, setViewportHeight] = useState5(0);
|
|
1214
|
+
const [viewportWidth, setViewportWidth] = useState5(0);
|
|
1215
|
+
const [scrollOffset, setScrollOffset] = useState5(0);
|
|
1216
|
+
const prevMaxOffsetRef = useRef3(0);
|
|
1217
|
+
useLayoutEffect2(() => {
|
|
1218
|
+
if (!viewportRef.current) return;
|
|
1219
|
+
const { width, height } = measureElement3(viewportRef.current);
|
|
1220
|
+
setViewportHeight(height);
|
|
1221
|
+
setViewportWidth(width);
|
|
1222
|
+
}, [rows, columns, logs.length === 0]);
|
|
1223
|
+
let capacity = viewportHeight;
|
|
1224
|
+
for (let i = 0; i < 2; i++) {
|
|
1225
|
+
const hasAbove = scrollOffset > 0;
|
|
1226
|
+
const hasBelow = scrollOffset + capacity < logs.length;
|
|
1227
|
+
capacity = Math.max(
|
|
1228
|
+
viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
|
|
1229
|
+
0
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
|
|
1233
|
+
const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
|
|
1234
|
+
useLayoutEffect2(() => {
|
|
1235
|
+
const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
|
|
1236
|
+
prevMaxOffsetRef.current = maxOffset;
|
|
1237
|
+
setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
|
|
1238
|
+
}, [maxOffset]);
|
|
1307
1239
|
useInput5((_input, key) => {
|
|
1308
|
-
if (key.upArrow
|
|
1309
|
-
|
|
1240
|
+
if (!key.upArrow && !key.downArrow) return;
|
|
1241
|
+
setScrollOffset(
|
|
1242
|
+
(o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
|
|
1243
|
+
);
|
|
1310
1244
|
});
|
|
1311
|
-
const visible = logs.slice(
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1245
|
+
const visible = logs.slice(scrollOffset, scrollOffset + capacity);
|
|
1246
|
+
const hiddenAbove = scrollOffset;
|
|
1247
|
+
const hiddenBelow = logs.length - scrollOffset - visible.length;
|
|
1248
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1249
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
|
|
1250
|
+
/* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
|
|
1251
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1252
|
+
"\u2191 ",
|
|
1253
|
+
hiddenAbove,
|
|
1254
|
+
" more"
|
|
1255
|
+
] }),
|
|
1256
|
+
visible.map((entry) => {
|
|
1257
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1258
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1259
|
+
const rawPreview = rawInputText(entry.input);
|
|
1260
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1261
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
1262
|
+
let budget = viewportWidth - timestamp.length - durationText.length - gaps;
|
|
1263
|
+
const name = truncate2(entry.name, budget);
|
|
1264
|
+
budget -= name.length;
|
|
1265
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1266
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1267
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
|
|
1268
|
+
/* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1269
|
+
preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1270
|
+
durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
|
|
1271
|
+
] }, entry.id);
|
|
1272
|
+
}),
|
|
1273
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
|
|
1274
|
+
"\u2193 ",
|
|
1275
|
+
hiddenBelow,
|
|
1276
|
+
" more"
|
|
1277
|
+
] })
|
|
1278
|
+
] }),
|
|
1279
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1332
1280
|
] });
|
|
1333
1281
|
}
|
|
1334
1282
|
|
|
@@ -1520,7 +1468,7 @@ function track(event, payload) {
|
|
|
1520
1468
|
}
|
|
1521
1469
|
|
|
1522
1470
|
// src/ui/App.tsx
|
|
1523
|
-
import { jsx as jsx13, jsxs as
|
|
1471
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1524
1472
|
function App() {
|
|
1525
1473
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1526
1474
|
const { exit } = useApp();
|
|
@@ -1563,44 +1511,36 @@ function App() {
|
|
|
1563
1511
|
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1564
1512
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1565
1513
|
const showSidebar = flexDirection === "row";
|
|
1566
|
-
return /* @__PURE__ */
|
|
1567
|
-
|
|
1514
|
+
return /* @__PURE__ */ jsxs12(
|
|
1515
|
+
Box13,
|
|
1568
1516
|
{
|
|
1569
1517
|
backgroundColor: COLORS.bg.main,
|
|
1570
1518
|
flexDirection: "row",
|
|
1571
1519
|
width: columns,
|
|
1572
1520
|
minHeight: rows,
|
|
1573
1521
|
children: [
|
|
1574
|
-
mainWindowVisible &&
|
|
1575
|
-
|
|
1576
|
-
// `useScrollWindow`). The home screens below stay uncapped: they are
|
|
1577
|
-
// long static copy that would be clipped rather than windowed.
|
|
1578
|
-
/* @__PURE__ */ jsxs13(
|
|
1579
|
-
Box14,
|
|
1522
|
+
mainWindowVisible && /* @__PURE__ */ jsxs12(
|
|
1523
|
+
Box13,
|
|
1580
1524
|
{
|
|
1581
1525
|
flexDirection,
|
|
1582
1526
|
width: "100%",
|
|
1583
|
-
maxHeight: rows,
|
|
1584
1527
|
justifyContent: "space-between",
|
|
1585
1528
|
children: [
|
|
1586
1529
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1587
|
-
/* Fill the
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
against (see SelectPrompt). */
|
|
1591
|
-
/* @__PURE__ */ jsxs13(
|
|
1592
|
-
Box14,
|
|
1530
|
+
/* Fill the width beside the sidebar; row layout only (would grow vertically when stacked). */
|
|
1531
|
+
/* @__PURE__ */ jsxs12(
|
|
1532
|
+
Box13,
|
|
1593
1533
|
{
|
|
1594
1534
|
flexDirection: "column",
|
|
1595
1535
|
paddingX: 4,
|
|
1596
1536
|
paddingY: 2,
|
|
1597
1537
|
width: showSidebar ? 70 : "100%",
|
|
1598
|
-
flexGrow: 1,
|
|
1538
|
+
flexGrow: showSidebar ? 1 : 0,
|
|
1599
1539
|
children: [
|
|
1600
1540
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1601
1541
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1602
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1603
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1542
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1543
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
|
|
1604
1544
|
"\u2716 ",
|
|
1605
1545
|
error
|
|
1606
1546
|
] }) })
|
|
@@ -2221,15 +2161,363 @@ function writeCredentialsTool(ctx) {
|
|
|
2221
2161
|
// src/lib/tools/searchFiles.ts
|
|
2222
2162
|
import { tool as tool7 } from "ai";
|
|
2223
2163
|
import z10 from "zod";
|
|
2224
|
-
import { readdir as
|
|
2164
|
+
import { readdir as readdir3, readFile as readFile8 } from "node:fs/promises";
|
|
2165
|
+
import { join as join10 } from "node:path";
|
|
2166
|
+
|
|
2167
|
+
// src/lib/languages.ts
|
|
2168
|
+
import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
|
|
2169
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2170
|
+
import { join as join9 } from "node:path";
|
|
2171
|
+
|
|
2172
|
+
// src/lib/tools/utils/packageManager.ts
|
|
2173
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2174
|
+
import { existsSync } from "node:fs";
|
|
2225
2175
|
import { join as join8 } from "node:path";
|
|
2176
|
+
var LOCKFILES = [
|
|
2177
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
2178
|
+
["yarn.lock", "yarn"],
|
|
2179
|
+
["bun.lockb", "bun"],
|
|
2180
|
+
["bun.lock", "bun"],
|
|
2181
|
+
["package-lock.json", "npm"]
|
|
2182
|
+
];
|
|
2183
|
+
async function readPackageJson(cwd = process.cwd()) {
|
|
2184
|
+
return JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
|
|
2185
|
+
}
|
|
2186
|
+
function packageManagerFrom(pkg) {
|
|
2187
|
+
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2188
|
+
}
|
|
2189
|
+
function packageManagerFromLockfile(cwd) {
|
|
2190
|
+
return LOCKFILES.find(([file]) => existsSync(join8(cwd, file)))?.[1];
|
|
2191
|
+
}
|
|
2192
|
+
async function detectPackageManager(cwd) {
|
|
2193
|
+
try {
|
|
2194
|
+
const pkg = await readPackageJson(cwd);
|
|
2195
|
+
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/lib/shell.ts
|
|
2202
|
+
function shellQuote(value) {
|
|
2203
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// src/lib/languages.ts
|
|
2207
|
+
var ENTRYPOINT_TOKEN = "{entrypoint}";
|
|
2208
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
2209
|
+
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2210
|
+
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2211
|
+
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2212
|
+
var LANGUAGE_PROFILES = {
|
|
2213
|
+
javascript: {
|
|
2214
|
+
id: "javascript",
|
|
2215
|
+
displayName: "JavaScript/TypeScript",
|
|
2216
|
+
aliases: [
|
|
2217
|
+
"javascript",
|
|
2218
|
+
"js",
|
|
2219
|
+
"typescript",
|
|
2220
|
+
"ts",
|
|
2221
|
+
"node",
|
|
2222
|
+
"nodejs",
|
|
2223
|
+
"node.js",
|
|
2224
|
+
"bun",
|
|
2225
|
+
"deno",
|
|
2226
|
+
"ecmascript",
|
|
2227
|
+
"jsx",
|
|
2228
|
+
"tsx"
|
|
2229
|
+
],
|
|
2230
|
+
manifests: ["package.json"],
|
|
2231
|
+
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2232
|
+
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2233
|
+
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2234
|
+
// binary below.
|
|
2235
|
+
packageManagers: [
|
|
2236
|
+
{
|
|
2237
|
+
id: "npm",
|
|
2238
|
+
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2239
|
+
installSteps: [{ argv: ["npm", "install"] }],
|
|
2240
|
+
ingest: {
|
|
2241
|
+
kind: "auto",
|
|
2242
|
+
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2243
|
+
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
],
|
|
2247
|
+
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2248
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2249
|
+
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2250
|
+
// repoVerification rather than listed here.
|
|
2251
|
+
verification: [],
|
|
2252
|
+
envReadInstruction: "Read them from `process.env`.",
|
|
2253
|
+
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2254
|
+
},
|
|
2255
|
+
python: {
|
|
2256
|
+
id: "python",
|
|
2257
|
+
displayName: "Python",
|
|
2258
|
+
aliases: ["python", "python3", "py", "cpython"],
|
|
2259
|
+
manifests: [
|
|
2260
|
+
"pyproject.toml",
|
|
2261
|
+
"requirements.txt",
|
|
2262
|
+
"setup.py",
|
|
2263
|
+
"setup.cfg",
|
|
2264
|
+
"Pipfile"
|
|
2265
|
+
],
|
|
2266
|
+
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2267
|
+
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2268
|
+
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2269
|
+
// declare-here/install-there split is the main way ingestion silently ends
|
|
2270
|
+
// up without the SDK installed. The tradeoff: the script can import the
|
|
2271
|
+
// Algolia client and anything it declares itself, but not the project's own
|
|
2272
|
+
// packages (see the optional root-requirements step below).
|
|
2273
|
+
packageManagers: [
|
|
2274
|
+
{
|
|
2275
|
+
id: "pip-venv",
|
|
2276
|
+
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2277
|
+
installSteps: [
|
|
2278
|
+
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2279
|
+
{
|
|
2280
|
+
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2281
|
+
},
|
|
2282
|
+
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2283
|
+
// ORMs) when the repo pins them the classic way.
|
|
2284
|
+
{
|
|
2285
|
+
argv: [
|
|
2286
|
+
PY_VENV_PYTHON,
|
|
2287
|
+
"-m",
|
|
2288
|
+
"pip",
|
|
2289
|
+
"install",
|
|
2290
|
+
"-r",
|
|
2291
|
+
"requirements.txt"
|
|
2292
|
+
],
|
|
2293
|
+
requiresFile: "requirements.txt",
|
|
2294
|
+
optional: true
|
|
2295
|
+
}
|
|
2296
|
+
],
|
|
2297
|
+
ingest: {
|
|
2298
|
+
kind: "auto",
|
|
2299
|
+
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2300
|
+
entrypointExtensions: [".py"]
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
],
|
|
2304
|
+
sdk: {
|
|
2305
|
+
packageName: "algoliasearch",
|
|
2306
|
+
versionPin: ">=4,<5",
|
|
2307
|
+
docKey: "python"
|
|
2308
|
+
},
|
|
2309
|
+
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2310
|
+
localSourceCaveat: {
|
|
2311
|
+
unless: "requirements.txt",
|
|
2312
|
+
message: "The ingestion script runs in its own environment under .algolia-wizard/, so it can install the Algolia client but not this project's packages (no requirements.txt to install from). If the script needs your database driver or ORM, add those packages to .algolia-wizard/requirements.txt and re-run the install."
|
|
2313
|
+
},
|
|
2314
|
+
verification: [
|
|
2315
|
+
{
|
|
2316
|
+
// -x skips the venv this same directory holds; without it the check
|
|
2317
|
+
// compiles every installed package instead of the generated script.
|
|
2318
|
+
label: "python compileall",
|
|
2319
|
+
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2320
|
+
requiresFile: INGEST_DIR
|
|
2321
|
+
}
|
|
2322
|
+
],
|
|
2323
|
+
envReadInstruction: "Read them from `os.environ`.",
|
|
2324
|
+
skipDirs: [
|
|
2325
|
+
"venv",
|
|
2326
|
+
"__pycache__",
|
|
2327
|
+
"site-packages",
|
|
2328
|
+
"dist",
|
|
2329
|
+
"build",
|
|
2330
|
+
"htmlcov"
|
|
2331
|
+
]
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2335
|
+
var JAVASCRIPT = "javascript";
|
|
2336
|
+
var CURATED_LANGUAGES = Object.values(
|
|
2337
|
+
LANGUAGE_PROFILES
|
|
2338
|
+
).map((profile) => profile.displayName);
|
|
2339
|
+
function isBackendLanguage(profile) {
|
|
2340
|
+
return profile.id !== JAVASCRIPT;
|
|
2341
|
+
}
|
|
2342
|
+
function normalizeLanguageName(name) {
|
|
2343
|
+
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2344
|
+
}
|
|
2345
|
+
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2346
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2347
|
+
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2348
|
+
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
function resolveLanguageProfile(name) {
|
|
2352
|
+
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2353
|
+
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2354
|
+
}
|
|
2355
|
+
function isSameLanguage(a, b) {
|
|
2356
|
+
const x = resolveLanguageProfile(a);
|
|
2357
|
+
const y = resolveLanguageProfile(b);
|
|
2358
|
+
if (x && y) return x.id === y.id;
|
|
2359
|
+
if (x || y) return false;
|
|
2360
|
+
const folded = normalizeLanguageName(a);
|
|
2361
|
+
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2362
|
+
}
|
|
2363
|
+
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2364
|
+
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2365
|
+
...BASE_SKIP_DIRS,
|
|
2366
|
+
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2367
|
+
]);
|
|
2368
|
+
var ALLOWED_BINARIES = new Set(
|
|
2369
|
+
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2370
|
+
...profile.packageManagers.flatMap((pm) => [
|
|
2371
|
+
...pm.installSteps.map((s) => s.argv[0]),
|
|
2372
|
+
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2373
|
+
]),
|
|
2374
|
+
...profile.verification.map((v) => v.argv[0])
|
|
2375
|
+
])
|
|
2376
|
+
);
|
|
2377
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2378
|
+
function isWorktreeRelativeCommand(command) {
|
|
2379
|
+
return command.includes("/");
|
|
2380
|
+
}
|
|
2381
|
+
function withCommand(argv, command) {
|
|
2382
|
+
return [command, ...argv.slice(1)];
|
|
2383
|
+
}
|
|
2384
|
+
function resolveDeclaredManifest(root, packageManager) {
|
|
2385
|
+
const { dependency } = packageManager;
|
|
2386
|
+
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2387
|
+
return packageManager;
|
|
2388
|
+
}
|
|
2389
|
+
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2390
|
+
(file) => existsSync2(join9(root, file))
|
|
2391
|
+
);
|
|
2392
|
+
if (!present || present === dependency.file) return packageManager;
|
|
2393
|
+
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2394
|
+
}
|
|
2395
|
+
async function manifestPresent(root, manifest, listing) {
|
|
2396
|
+
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2397
|
+
if (!listing.entries) {
|
|
2398
|
+
const entries = await readdir2(root).catch(() => []);
|
|
2399
|
+
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2400
|
+
}
|
|
2401
|
+
const suffix = manifest.slice(1);
|
|
2402
|
+
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2403
|
+
}
|
|
2404
|
+
async function profileManifestPresent(root, profile, listing) {
|
|
2405
|
+
for (const manifest of profile.manifests) {
|
|
2406
|
+
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2407
|
+
}
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
async function detectProfilesFromManifests(root) {
|
|
2411
|
+
const listing = {};
|
|
2412
|
+
const found = [];
|
|
2413
|
+
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2414
|
+
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2415
|
+
}
|
|
2416
|
+
return found;
|
|
2417
|
+
}
|
|
2418
|
+
async function hasProfileManifest(root, profile) {
|
|
2419
|
+
return profileManifestPresent(root, profile, {});
|
|
2420
|
+
}
|
|
2421
|
+
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2422
|
+
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2423
|
+
const onDisk = await detectProfilesFromManifests(root);
|
|
2424
|
+
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2425
|
+
const candidates = [
|
|
2426
|
+
...new Map(
|
|
2427
|
+
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2428
|
+
).values()
|
|
2429
|
+
];
|
|
2430
|
+
return { candidates, confirmed: confirmed3, onDisk };
|
|
2431
|
+
}
|
|
2432
|
+
async function resolveToolchain(root, profile) {
|
|
2433
|
+
const signals = (pm) => [
|
|
2434
|
+
...pm.lockfiles ?? [],
|
|
2435
|
+
...pm.detectFiles ?? []
|
|
2436
|
+
];
|
|
2437
|
+
const matched = profile.packageManagers.find(
|
|
2438
|
+
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2439
|
+
);
|
|
2440
|
+
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2441
|
+
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2442
|
+
let { installSteps, ingest } = packageManager;
|
|
2443
|
+
installSteps = installSteps.map(
|
|
2444
|
+
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2445
|
+
);
|
|
2446
|
+
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2447
|
+
ingest = {
|
|
2448
|
+
...ingest,
|
|
2449
|
+
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
if (profile.id === "javascript") {
|
|
2453
|
+
const pm = await detectPackageManager(root);
|
|
2454
|
+
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2455
|
+
installSteps = installSteps.map((step) => ({
|
|
2456
|
+
...step,
|
|
2457
|
+
argv: withCommand(step.argv, pm)
|
|
2458
|
+
}));
|
|
2459
|
+
if (pm === "bun" && ingest.kind === "auto") {
|
|
2460
|
+
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return { profile, packageManager, installSteps, ingest };
|
|
2465
|
+
}
|
|
2466
|
+
function resolveIngestArgv(ingest, entrypoint) {
|
|
2467
|
+
if (ingest.kind !== "auto") {
|
|
2468
|
+
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2469
|
+
}
|
|
2470
|
+
return ingest.argv.map(
|
|
2471
|
+
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
function describeIngestCommand(ingest, entrypoint) {
|
|
2475
|
+
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2476
|
+
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2477
|
+
}
|
|
2478
|
+
function ingestScriptDir(profile) {
|
|
2479
|
+
const parts = profile.ingestEntrypointExample.split("/");
|
|
2480
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
2481
|
+
}
|
|
2482
|
+
function localSourceLimitation(root, profile) {
|
|
2483
|
+
const caveat = profile.localSourceCaveat;
|
|
2484
|
+
if (!caveat) return void 0;
|
|
2485
|
+
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2486
|
+
}
|
|
2487
|
+
async function missingBuildTask(root, toolchain) {
|
|
2488
|
+
const { ingest, packageManager } = toolchain;
|
|
2489
|
+
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2490
|
+
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2491
|
+
const buildFile = join9(root, packageManager.dependency.file);
|
|
2492
|
+
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2493
|
+
if (contents === void 0) return void 0;
|
|
2494
|
+
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2495
|
+
}
|
|
2496
|
+
function sdkVersionPin(profile, packageManager) {
|
|
2497
|
+
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2498
|
+
}
|
|
2499
|
+
function dependencyInstruction(toolchain) {
|
|
2500
|
+
const { profile, packageManager } = toolchain;
|
|
2501
|
+
const { packageName } = profile.sdk;
|
|
2502
|
+
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2503
|
+
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2504
|
+
switch (packageManager.dependency.mode) {
|
|
2505
|
+
case "wizard-installs":
|
|
2506
|
+
return `The wizard installs ${packageName} ${versionPin} in the worktree after you finish \u2014 import it directly and do not edit dependency manifests for it.${also}`;
|
|
2507
|
+
case "code-imports":
|
|
2508
|
+
return `Import ${packageName} in the script; the wizard resolves and fetches it in the worktree after you finish. Do not edit dependency manifests by hand.${also}`;
|
|
2509
|
+
case "agent-declares":
|
|
2510
|
+
return `Declare ${packageName} ${versionPin} in "${packageManager.dependency.file}" (create the file if needed), plus any other dependency your script imports; the wizard installs them in the worktree after you finish.${also}`;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/lib/tools/searchFiles.ts
|
|
2226
2515
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2227
2516
|
async function walkFiles(dir) {
|
|
2228
|
-
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2229
2517
|
const out = [];
|
|
2230
|
-
for (const e of await
|
|
2231
|
-
if (e.name.startsWith(".") ||
|
|
2232
|
-
const full =
|
|
2518
|
+
for (const e of await readdir3(dir, { withFileTypes: true })) {
|
|
2519
|
+
if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
|
|
2520
|
+
const full = join10(dir, e.name);
|
|
2233
2521
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2234
2522
|
else if (e.isFile()) out.push(full);
|
|
2235
2523
|
}
|
|
@@ -2262,7 +2550,7 @@ function searchFilesTool(ctx) {
|
|
|
2262
2550
|
for (const file of await walkFiles(resolved.target)) {
|
|
2263
2551
|
let content;
|
|
2264
2552
|
try {
|
|
2265
|
-
content = await
|
|
2553
|
+
content = await readFile8(file, "utf8");
|
|
2266
2554
|
} catch {
|
|
2267
2555
|
continue;
|
|
2268
2556
|
}
|
|
@@ -2286,88 +2574,144 @@ function searchFilesTool(ctx) {
|
|
|
2286
2574
|
import { tool as tool8 } from "ai";
|
|
2287
2575
|
import z11 from "zod";
|
|
2288
2576
|
|
|
2577
|
+
// src/lib/tools/repoVerification.ts
|
|
2578
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2579
|
+
import { join as join11 } from "node:path";
|
|
2580
|
+
|
|
2289
2581
|
// src/lib/tools/utils/runCommand.ts
|
|
2290
2582
|
import { spawn as spawn2 } from "node:child_process";
|
|
2291
|
-
|
|
2583
|
+
var INSTALL_TIMEOUT_MS = 15 * 6e4;
|
|
2584
|
+
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2585
|
+
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2586
|
+
var KILL_GRACE_MS = 5e3;
|
|
2587
|
+
function runCommand(command, args, options = {}) {
|
|
2588
|
+
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2292
2589
|
return new Promise((resolve4) => {
|
|
2293
2590
|
let output = "";
|
|
2591
|
+
let settled = false;
|
|
2294
2592
|
const child = spawn2(command, args, {
|
|
2295
2593
|
cwd,
|
|
2296
|
-
|
|
2594
|
+
shell: false,
|
|
2595
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2596
|
+
...env ? { env: { ...process.env, ...env } } : {}
|
|
2297
2597
|
});
|
|
2598
|
+
const settle = (result) => {
|
|
2599
|
+
if (settled) return;
|
|
2600
|
+
settled = true;
|
|
2601
|
+
clearTimeout(timer);
|
|
2602
|
+
resolve4(result);
|
|
2603
|
+
};
|
|
2604
|
+
const timer = setTimeout(() => {
|
|
2605
|
+
child.kill("SIGTERM");
|
|
2606
|
+
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2607
|
+
const seconds = Math.round(timeoutMs / 1e3);
|
|
2608
|
+
settle({
|
|
2609
|
+
code: 1,
|
|
2610
|
+
output: `${output}
|
|
2611
|
+
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2612
|
+
timedOut: true
|
|
2613
|
+
});
|
|
2614
|
+
}, timeoutMs);
|
|
2298
2615
|
child.stdout?.on("data", (d) => output += d);
|
|
2299
2616
|
child.stderr?.on("data", (d) => output += d);
|
|
2300
2617
|
child.on(
|
|
2301
2618
|
"error",
|
|
2302
|
-
(err) =>
|
|
2619
|
+
(err) => settle({
|
|
2620
|
+
code: 1,
|
|
2621
|
+
output: `Failed to run ${command}: ${err.message}`,
|
|
2622
|
+
timedOut: false
|
|
2623
|
+
})
|
|
2624
|
+
);
|
|
2625
|
+
child.on(
|
|
2626
|
+
"close",
|
|
2627
|
+
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2303
2628
|
);
|
|
2304
|
-
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
2305
2629
|
});
|
|
2306
2630
|
}
|
|
2307
2631
|
|
|
2308
|
-
// src/lib/tools/utils/packageManager.ts
|
|
2309
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2310
|
-
import { existsSync } from "node:fs";
|
|
2311
|
-
import { join as join9 } from "node:path";
|
|
2312
|
-
var LOCKFILES = [
|
|
2313
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
2314
|
-
["yarn.lock", "yarn"],
|
|
2315
|
-
["bun.lockb", "bun"],
|
|
2316
|
-
["bun.lock", "bun"],
|
|
2317
|
-
["package-lock.json", "npm"]
|
|
2318
|
-
];
|
|
2319
|
-
async function readPackageJson(cwd = process.cwd()) {
|
|
2320
|
-
return JSON.parse(await readFile7(join9(cwd, "package.json"), "utf8"));
|
|
2321
|
-
}
|
|
2322
|
-
function packageManagerFrom(pkg) {
|
|
2323
|
-
return pkg.packageManager?.split("@")[0] ?? "npm";
|
|
2324
|
-
}
|
|
2325
|
-
function packageManagerFromLockfile(cwd) {
|
|
2326
|
-
return LOCKFILES.find(([file]) => existsSync(join9(cwd, file)))?.[1];
|
|
2327
|
-
}
|
|
2328
|
-
async function detectPackageManager(cwd) {
|
|
2329
|
-
try {
|
|
2330
|
-
const pkg = await readPackageJson(cwd);
|
|
2331
|
-
if (pkg.packageManager) return packageManagerFrom(pkg);
|
|
2332
|
-
} catch {
|
|
2333
|
-
}
|
|
2334
|
-
return packageManagerFromLockfile(cwd) ?? "npm";
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
2632
|
// src/lib/tools/repoVerification.ts
|
|
2338
2633
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
2339
|
-
async function
|
|
2634
|
+
async function runCheck(command, binary, args) {
|
|
2635
|
+
const { code, output } = await runCommand(binary, args, {
|
|
2636
|
+
timeoutMs: VERIFY_TIMEOUT_MS
|
|
2637
|
+
});
|
|
2638
|
+
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
2639
|
+
}
|
|
2640
|
+
async function javascriptChecks() {
|
|
2340
2641
|
let pkg;
|
|
2341
2642
|
try {
|
|
2342
2643
|
pkg = await readPackageJson();
|
|
2343
2644
|
} catch (err) {
|
|
2344
|
-
|
|
2345
|
-
|
|
2645
|
+
return {
|
|
2646
|
+
limitation: `Could not read package.json to detect verification conventions: ${err.message}`
|
|
2647
|
+
};
|
|
2346
2648
|
}
|
|
2347
2649
|
const scripts = pkg.scripts ?? {};
|
|
2348
2650
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
2349
2651
|
if (present.length === 0) {
|
|
2350
|
-
|
|
2351
|
-
|
|
2652
|
+
return {
|
|
2653
|
+
limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
|
|
2654
|
+
};
|
|
2352
2655
|
}
|
|
2353
2656
|
const pm = await detectPackageManager(process.cwd());
|
|
2354
2657
|
const checks = [];
|
|
2355
2658
|
for (const script of present) {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2659
|
+
checks.push(
|
|
2660
|
+
await runCheck(`${pm} run ${script}`, pm, ["run", script])
|
|
2661
|
+
);
|
|
2359
2662
|
}
|
|
2360
|
-
return {
|
|
2663
|
+
return { checks };
|
|
2664
|
+
}
|
|
2665
|
+
async function registryChecks(id) {
|
|
2666
|
+
const profile = LANGUAGE_PROFILES[id];
|
|
2667
|
+
const runnable = profile.verification.filter(
|
|
2668
|
+
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
2669
|
+
);
|
|
2670
|
+
if (runnable.length === 0) {
|
|
2671
|
+
return {
|
|
2672
|
+
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
const checks = [];
|
|
2676
|
+
for (const spec of runnable) {
|
|
2677
|
+
checks.push(
|
|
2678
|
+
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
return { checks };
|
|
2682
|
+
}
|
|
2683
|
+
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
2684
|
+
const ids = [...new Set(languages)];
|
|
2685
|
+
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
2686
|
+
const checks = [];
|
|
2687
|
+
const limitations = [];
|
|
2688
|
+
for (const id of ids) {
|
|
2689
|
+
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
2690
|
+
if ("checks" in result) checks.push(...result.checks);
|
|
2691
|
+
else limitations.push(result.limitation);
|
|
2692
|
+
}
|
|
2693
|
+
if (checks.length === 0) {
|
|
2694
|
+
return {
|
|
2695
|
+
ok: false,
|
|
2696
|
+
checks: [],
|
|
2697
|
+
limitation: limitations.join(" ") || "No verification checks available."
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
return {
|
|
2701
|
+
ok: checks.every((c) => c.ok),
|
|
2702
|
+
checks,
|
|
2703
|
+
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
2704
|
+
};
|
|
2361
2705
|
}
|
|
2362
2706
|
|
|
2363
2707
|
// src/lib/tools/verifyImplementation.ts
|
|
2364
|
-
function verifyImplementationTool() {
|
|
2708
|
+
function verifyImplementationTool(ctx) {
|
|
2365
2709
|
return tool8({
|
|
2366
|
-
description: "Run the repo's mechanical verification
|
|
2710
|
+
description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
|
|
2367
2711
|
inputSchema: z11.object(),
|
|
2368
2712
|
execute: async () => {
|
|
2369
|
-
logger.info("called verifyImplementation tool");
|
|
2370
|
-
return runRepoVerificationCheck();
|
|
2713
|
+
logger.info({ languages: ctx.languages }, "called verifyImplementation tool");
|
|
2714
|
+
return runRepoVerificationCheck(ctx.languages);
|
|
2371
2715
|
}
|
|
2372
2716
|
});
|
|
2373
2717
|
}
|
|
@@ -2493,12 +2837,17 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
2493
2837
|
read: 20,
|
|
2494
2838
|
match: 100
|
|
2495
2839
|
};
|
|
2496
|
-
function createToolContext(
|
|
2840
|
+
function createToolContext({
|
|
2841
|
+
limits = DEFAULT_TOOL_LIMITS,
|
|
2842
|
+
cwd = process.cwd(),
|
|
2843
|
+
languages = [DEFAULT_LANGUAGE_ID]
|
|
2844
|
+
} = {}) {
|
|
2497
2845
|
return {
|
|
2498
2846
|
root: cwd,
|
|
2499
2847
|
cwd,
|
|
2500
2848
|
limits,
|
|
2501
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
2849
|
+
counts: { list: 0, search: 0, read: 0 },
|
|
2850
|
+
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2502
2851
|
};
|
|
2503
2852
|
}
|
|
2504
2853
|
|
|
@@ -2535,7 +2884,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
2535
2884
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
2536
2885
|
verifyImplementation: withLogging(
|
|
2537
2886
|
"verifyImplementation",
|
|
2538
|
-
verifyImplementationTool()
|
|
2887
|
+
verifyImplementationTool(ctx)
|
|
2539
2888
|
),
|
|
2540
2889
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
2541
2890
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -2571,7 +2920,7 @@ async function runAgent(req) {
|
|
|
2571
2920
|
baseURL: PROXY_BASE_URL,
|
|
2572
2921
|
fetch: proxyFetch
|
|
2573
2922
|
});
|
|
2574
|
-
const toolContext = createToolContext();
|
|
2923
|
+
const toolContext = createToolContext({ languages: req.languages });
|
|
2575
2924
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
2576
2925
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
2577
2926
|
const instructions = [
|
|
@@ -2662,8 +3011,11 @@ var detectLanguageSchema = z16.object({
|
|
|
2662
3011
|
var detectLanguage = () => runAgent({
|
|
2663
3012
|
instructions: [
|
|
2664
3013
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3014
|
+
"Start from the dependency manifests: package.json, pyproject.toml, requirements.txt.",
|
|
3015
|
+
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
2665
3016
|
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
2666
3017
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3018
|
+
"Frameworks include backend and server-rendering frameworks (e.g. Rails, Django, Laravel, Symfony, Spring Boot, ASP.NET Core, Flask, Gin, Ktor) as well as frontend ones (React, Vue, Angular, Svelte) and mobile ones (Flutter, SwiftUI).",
|
|
2667
3019
|
"Return the exact version",
|
|
2668
3020
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
2669
3021
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -2712,6 +3064,7 @@ var MODE_CONFIG = {
|
|
|
2712
3064
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
2713
3065
|
"For each entity, return its name, the file path(s) where it is defined, and its indexable attribute keys (the fields a user would search or filter on).",
|
|
2714
3066
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3067
|
+
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Pydantic models.",
|
|
2715
3068
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
2716
3069
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
2717
3070
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -2723,8 +3076,9 @@ var MODE_CONFIG = {
|
|
|
2723
3076
|
instructions: [
|
|
2724
3077
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
2725
3078
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
2726
|
-
"
|
|
2727
|
-
|
|
3079
|
+
"It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
|
|
3080
|
+
"Return one file path as searchImplementationAnalysis.",
|
|
3081
|
+
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2728
3082
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2729
3083
|
"When done, call reportStatus"
|
|
2730
3084
|
],
|
|
@@ -2733,8 +3087,8 @@ var MODE_CONFIG = {
|
|
|
2733
3087
|
verification: {
|
|
2734
3088
|
instructions: [
|
|
2735
3089
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
2736
|
-
"Look at package.json scripts,
|
|
2737
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3090
|
+
"Look at the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier, pyproject.toml or setup.cfg (ruff, mypy, black).",
|
|
3091
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"] or ["ruff", "mypy"].',
|
|
2738
3092
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
2739
3093
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
2740
3094
|
"When done, call reportStatus"
|
|
@@ -2761,7 +3115,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
2761
3115
|
// package.json
|
|
2762
3116
|
var package_default = {
|
|
2763
3117
|
name: "@algolia/wizard",
|
|
2764
|
-
version: "0.8.0-rc.
|
|
3118
|
+
version: "0.8.0-rc.60.48",
|
|
2765
3119
|
description: "Magically implement Algolia functionality in your codebase",
|
|
2766
3120
|
type: "module",
|
|
2767
3121
|
engines: {
|
|
@@ -2783,7 +3137,7 @@ var package_default = {
|
|
|
2783
3137
|
prepare: "husky",
|
|
2784
3138
|
prepublishOnly: "pnpm build",
|
|
2785
3139
|
reset: "tsx ./scripts/reset-state.ts",
|
|
2786
|
-
"test:
|
|
3140
|
+
"test:toolchains": "tsx ./scripts/verify-toolchains.ts",
|
|
2787
3141
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
2788
3142
|
test: "vitest",
|
|
2789
3143
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -2864,82 +3218,185 @@ function parseEntries(raw) {
|
|
|
2864
3218
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
2865
3219
|
}
|
|
2866
3220
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
2867
|
-
|
|
3221
|
+
|
|
3222
|
+
// src/actions/confirmLanguage.ts
|
|
3223
|
+
import z19 from "zod";
|
|
3224
|
+
var confirmLanguageSchema = z19.object({
|
|
3225
|
+
languages: detectLanguageSchema.shape.languages
|
|
3226
|
+
});
|
|
3227
|
+
var OTHER_OPTION = "Other";
|
|
3228
|
+
function confirmed(languages) {
|
|
3229
|
+
track("AI Wizard Language Confirmed", { languages });
|
|
3230
|
+
return { languages };
|
|
3231
|
+
}
|
|
3232
|
+
async function askOtherLanguage(ctx) {
|
|
3233
|
+
let prompt = "enter the language for your ingestion script";
|
|
2868
3234
|
for (; ; ) {
|
|
2869
3235
|
const answer = await ctx.requestUserInput({
|
|
2870
3236
|
prompt,
|
|
2871
3237
|
promptType: "textInput",
|
|
2872
|
-
options: []
|
|
2873
|
-
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3238
|
+
options: []
|
|
2874
3239
|
});
|
|
2875
3240
|
if (typeof answer !== "string") {
|
|
2876
|
-
throw new Error("
|
|
3241
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
2877
3242
|
}
|
|
2878
|
-
const
|
|
2879
|
-
if (
|
|
2880
|
-
prompt = "
|
|
3243
|
+
const name = parseEntries(answer)[0]?.name;
|
|
3244
|
+
if (name) return name;
|
|
3245
|
+
prompt = "please enter a language name:";
|
|
2881
3246
|
}
|
|
2882
3247
|
}
|
|
2883
|
-
|
|
2884
|
-
// src/actions/confirmLanguage.ts
|
|
2885
|
-
import z19 from "zod";
|
|
2886
|
-
var confirmLanguageSchema = z19.object({
|
|
2887
|
-
languages: detectLanguageSchema.shape.languages
|
|
2888
|
-
});
|
|
2889
3248
|
async function confirmLanguage(ctx) {
|
|
2890
3249
|
const detected = ctx.getStepOutput("project-scan");
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
3250
|
+
const detectedLanguages = detected.languages ?? [];
|
|
3251
|
+
const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
|
|
3252
|
+
const primary = detectedLanguages[0];
|
|
3253
|
+
if (primary) {
|
|
3254
|
+
const accepted = await ctx.requestUserInput({
|
|
3255
|
+
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3256
|
+
promptType: "acceptReject",
|
|
3257
|
+
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3258
|
+
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3259
|
+
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3260
|
+
});
|
|
3261
|
+
if (accepted === true) return confirmed(detectedLanguages);
|
|
3262
|
+
}
|
|
3263
|
+
const options = [...CURATED_LANGUAGES];
|
|
3264
|
+
for (const language of detectedLanguages) {
|
|
3265
|
+
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3266
|
+
options.push(language.name);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
options.push(OTHER_OPTION);
|
|
3270
|
+
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3271
|
+
const secondary = options.map(
|
|
3272
|
+
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3273
|
+
);
|
|
3274
|
+
const defaultSelectedIndex = Math.max(
|
|
3275
|
+
options.findIndex((o) => detectedFor(o)),
|
|
3276
|
+
0
|
|
3277
|
+
);
|
|
3278
|
+
const selection = await ctx.requestUserInput({
|
|
3279
|
+
prompt: "select the language for your ingestion script",
|
|
3280
|
+
promptType: "multipleChoice",
|
|
3281
|
+
options,
|
|
3282
|
+
secondary,
|
|
3283
|
+
defaultSelectedIndex
|
|
2902
3284
|
});
|
|
2903
|
-
|
|
3285
|
+
if (typeof selection !== "string") {
|
|
3286
|
+
throw new Error("confirmLanguage received an unexpected non-text result");
|
|
3287
|
+
}
|
|
3288
|
+
const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
|
|
3289
|
+
const version = detectedFor(name)?.version ?? "unknown";
|
|
3290
|
+
return confirmed([{ name, version }, ...others(name)]);
|
|
2904
3291
|
}
|
|
2905
3292
|
|
|
2906
3293
|
// src/actions/confirmFramework.ts
|
|
2907
3294
|
import z20 from "zod";
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
var
|
|
2912
|
-
|
|
2913
|
-
"
|
|
2914
|
-
"
|
|
2915
|
-
"
|
|
2916
|
-
"
|
|
2917
|
-
|
|
3295
|
+
|
|
3296
|
+
// src/lib/frameworks.ts
|
|
3297
|
+
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3298
|
+
var FRAMEWORKS = [
|
|
3299
|
+
// Frontend — InstantSearch component flavors.
|
|
3300
|
+
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3301
|
+
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3302
|
+
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3303
|
+
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3304
|
+
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3305
|
+
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3306
|
+
{
|
|
3307
|
+
name: "Vanilla JS",
|
|
3308
|
+
strategy: "js",
|
|
3309
|
+
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3310
|
+
},
|
|
3311
|
+
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3312
|
+
// templates get InstantSearch.js from a CDN.
|
|
3313
|
+
{
|
|
3314
|
+
name: "Rails",
|
|
3315
|
+
strategy: "cdn-template",
|
|
3316
|
+
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3317
|
+
},
|
|
3318
|
+
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3319
|
+
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3320
|
+
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3321
|
+
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3322
|
+
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3323
|
+
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3324
|
+
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3325
|
+
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3326
|
+
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
2918
3327
|
];
|
|
2919
|
-
var
|
|
3328
|
+
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3329
|
+
(f) => f.name
|
|
3330
|
+
);
|
|
2920
3331
|
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2921
|
-
var
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
javascript: "vanillajs",
|
|
2935
|
-
js: "vanillajs"
|
|
2936
|
-
};
|
|
2937
|
-
var isSameFramework = (a, b) => {
|
|
2938
|
-
const x = FRAMEWORK_ALIASES[normalize(a)] ?? normalize(a);
|
|
2939
|
-
const y = FRAMEWORK_ALIASES[normalize(b)] ?? normalize(b);
|
|
3332
|
+
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3333
|
+
for (const framework of FRAMEWORKS) {
|
|
3334
|
+
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3335
|
+
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3339
|
+
function canonicalFrameworkName(name) {
|
|
3340
|
+
return ALIAS_TO_NAME.get(normalize(name));
|
|
3341
|
+
}
|
|
3342
|
+
function isSameFramework(a, b) {
|
|
3343
|
+
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3344
|
+
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
2940
3345
|
return x !== "" && x === y;
|
|
2941
|
-
}
|
|
2942
|
-
function
|
|
3346
|
+
}
|
|
3347
|
+
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3348
|
+
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3349
|
+
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3350
|
+
if (strategy) return strategy;
|
|
3351
|
+
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3352
|
+
}
|
|
3353
|
+
function searchDocKey(strategy) {
|
|
3354
|
+
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3355
|
+
}
|
|
3356
|
+
function bundlesJavaScript(strategy) {
|
|
3357
|
+
return strategy !== "cdn-template" && strategy !== "none";
|
|
3358
|
+
}
|
|
3359
|
+
function canScaffoldSearchUI(strategy) {
|
|
3360
|
+
return strategy !== "none";
|
|
3361
|
+
}
|
|
3362
|
+
var ENV_PREFIXES = [
|
|
3363
|
+
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3364
|
+
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3365
|
+
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3366
|
+
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3367
|
+
];
|
|
3368
|
+
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3369
|
+
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3370
|
+
if (!bundlesJavaScript(strategy)) return "";
|
|
3371
|
+
const present = new Set(frameworkNames.map(normalize));
|
|
3372
|
+
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3373
|
+
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3374
|
+
}
|
|
3375
|
+
return DEFAULT_ENV_PREFIX;
|
|
3376
|
+
}
|
|
3377
|
+
function describeSearchTarget(strategy, frameworkName) {
|
|
3378
|
+
switch (strategy) {
|
|
3379
|
+
case "react":
|
|
3380
|
+
return "React (react-instantsearch)";
|
|
3381
|
+
case "vue":
|
|
3382
|
+
return "Vue (vue-instantsearch)";
|
|
3383
|
+
case "angular":
|
|
3384
|
+
return "Angular (angular-instantsearch)";
|
|
3385
|
+
case "js":
|
|
3386
|
+
return "plain JavaScript (InstantSearch.js)";
|
|
3387
|
+
case "cdn-template":
|
|
3388
|
+
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3389
|
+
case "none":
|
|
3390
|
+
return frameworkName ?? "a native mobile app";
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
// src/actions/confirmFramework.ts
|
|
3395
|
+
var confirmFrameworkSchema = z20.object({
|
|
3396
|
+
frameworks: detectLanguageSchema.shape.frameworks
|
|
3397
|
+
});
|
|
3398
|
+
var OTHER_OPTION2 = "Other";
|
|
3399
|
+
function confirmed2(name, version) {
|
|
2943
3400
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
2944
3401
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
2945
3402
|
return { frameworks };
|
|
@@ -2967,7 +3424,7 @@ async function confirmFramework(ctx) {
|
|
|
2967
3424
|
for (const fw of detectedFrameworks) {
|
|
2968
3425
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
2969
3426
|
}
|
|
2970
|
-
options.push(
|
|
3427
|
+
options.push(OTHER_OPTION2);
|
|
2971
3428
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
2972
3429
|
const primary = detectedFrameworks[0];
|
|
2973
3430
|
if (primary) {
|
|
@@ -2977,7 +3434,7 @@ async function confirmFramework(ctx) {
|
|
|
2977
3434
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
2978
3435
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
2979
3436
|
});
|
|
2980
|
-
if (accepted === true) return
|
|
3437
|
+
if (accepted === true) return confirmed2(primary.name, primary.version);
|
|
2981
3438
|
}
|
|
2982
3439
|
const secondary = options.map(
|
|
2983
3440
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -2987,7 +3444,7 @@ async function confirmFramework(ctx) {
|
|
|
2987
3444
|
0
|
|
2988
3445
|
);
|
|
2989
3446
|
const selection = await ctx.requestUserInput({
|
|
2990
|
-
prompt: "select
|
|
3447
|
+
prompt: "select the framework that renders your UI",
|
|
2991
3448
|
promptType: "multipleChoice",
|
|
2992
3449
|
options,
|
|
2993
3450
|
secondary,
|
|
@@ -2996,10 +3453,10 @@ async function confirmFramework(ctx) {
|
|
|
2996
3453
|
if (typeof selection !== "string") {
|
|
2997
3454
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
2998
3455
|
}
|
|
2999
|
-
if (selection ===
|
|
3000
|
-
return
|
|
3456
|
+
if (selection === OTHER_OPTION2) {
|
|
3457
|
+
return confirmed2(await askOtherFramework(ctx));
|
|
3001
3458
|
}
|
|
3002
|
-
return
|
|
3459
|
+
return confirmed2(selection, detectedFor(selection)?.version);
|
|
3003
3460
|
}
|
|
3004
3461
|
|
|
3005
3462
|
// src/actions/promptUser.ts
|
|
@@ -3092,15 +3549,15 @@ async function confirmEntities(ctx) {
|
|
|
3092
3549
|
onSubmit: () => {
|
|
3093
3550
|
}
|
|
3094
3551
|
});
|
|
3095
|
-
const
|
|
3096
|
-
if (
|
|
3552
|
+
const confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3553
|
+
if (confirmed3.length === 0) {
|
|
3097
3554
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3098
3555
|
}
|
|
3099
|
-
ctx.setUserInput("confirmedEntities",
|
|
3556
|
+
ctx.setUserInput("confirmedEntities", confirmed3);
|
|
3100
3557
|
track("AI Wizard Entities Confirmed", {
|
|
3101
|
-
entities: toEntitySummary(
|
|
3558
|
+
entities: toEntitySummary(confirmed3)
|
|
3102
3559
|
});
|
|
3103
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3560
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
|
|
3104
3561
|
}
|
|
3105
3562
|
|
|
3106
3563
|
// src/actions/review.ts
|
|
@@ -3124,7 +3581,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3124
3581
|
}
|
|
3125
3582
|
function formatReviewSummary(result) {
|
|
3126
3583
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3127
|
-
const isIngestCommand = step.includes("
|
|
3584
|
+
const isIngestCommand = step.includes("algolia-wizard/");
|
|
3128
3585
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3129
3586
|
return {
|
|
3130
3587
|
text: `\u2192 ${step}`,
|
|
@@ -3166,13 +3623,14 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
3166
3623
|
import z24 from "zod";
|
|
3167
3624
|
|
|
3168
3625
|
// src/lib/worktree.ts
|
|
3169
|
-
import { execFile
|
|
3170
|
-
import {
|
|
3626
|
+
import { execFile } from "node:child_process";
|
|
3627
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3628
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3171
3629
|
import {
|
|
3172
3630
|
basename as basename2,
|
|
3173
3631
|
dirname as dirname7,
|
|
3174
3632
|
isAbsolute as isAbsolute2,
|
|
3175
|
-
join as
|
|
3633
|
+
join as join12,
|
|
3176
3634
|
relative as relative2,
|
|
3177
3635
|
resolve as resolve3
|
|
3178
3636
|
} from "node:path";
|
|
@@ -3206,8 +3664,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
3206
3664
|
return out.trim().length > 0;
|
|
3207
3665
|
}
|
|
3208
3666
|
async function pruneOldWorktrees(repoRoot) {
|
|
3209
|
-
const dir =
|
|
3210
|
-
const stale = (await
|
|
3667
|
+
const dir = join12(stateDir(repoRoot), "worktrees");
|
|
3668
|
+
const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
3211
3669
|
for (const slug of stale) {
|
|
3212
3670
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
3213
3671
|
try {
|
|
@@ -3217,7 +3675,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3217
3675
|
"worktree",
|
|
3218
3676
|
"remove",
|
|
3219
3677
|
"--force",
|
|
3220
|
-
|
|
3678
|
+
join12(dir, slug)
|
|
3221
3679
|
]);
|
|
3222
3680
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
3223
3681
|
} catch (err) {
|
|
@@ -3231,43 +3689,55 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
3231
3689
|
async function createWorktree(repoRoot) {
|
|
3232
3690
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
3233
3691
|
const dirSlug = branch.replace(/\//g, "-");
|
|
3234
|
-
const path =
|
|
3692
|
+
const path = join12(stateDir(repoRoot), "worktrees", dirSlug);
|
|
3235
3693
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
3236
3694
|
await pruneOldWorktrees(repoRoot);
|
|
3237
3695
|
await mkdir6(dirname7(path), { recursive: true });
|
|
3238
3696
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3239
3697
|
return { path, branch };
|
|
3240
3698
|
}
|
|
3241
|
-
async function
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
return { ok: true, output: "no package.json; skipped install" };
|
|
3246
|
-
}
|
|
3247
|
-
const pm = await detectPackageManager(worktreePath);
|
|
3248
|
-
return new Promise((resolve4) => {
|
|
3249
|
-
let output = "";
|
|
3250
|
-
const child = spawn3(pm, ["install"], {
|
|
3251
|
-
cwd: worktreePath,
|
|
3252
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3253
|
-
});
|
|
3254
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3255
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3256
|
-
child.on(
|
|
3257
|
-
"error",
|
|
3258
|
-
(err) => resolve4({
|
|
3259
|
-
ok: false,
|
|
3260
|
-
output: `Failed to run ${pm} install: ${err.message}`
|
|
3261
|
-
})
|
|
3262
|
-
);
|
|
3263
|
-
child.on(
|
|
3264
|
-
"close",
|
|
3265
|
-
(code) => resolve4({ ok: code === 0, output: output.trim() })
|
|
3266
|
-
);
|
|
3699
|
+
async function spawnStep(worktreePath, argv) {
|
|
3700
|
+
const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
|
|
3701
|
+
cwd: worktreePath,
|
|
3702
|
+
timeoutMs: INSTALL_TIMEOUT_MS
|
|
3267
3703
|
});
|
|
3704
|
+
return { ok: code === 0, output: output.trim() };
|
|
3268
3705
|
}
|
|
3269
|
-
|
|
3270
|
-
|
|
3706
|
+
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
3707
|
+
const { profile, installSteps, packageManager } = toolchain;
|
|
3708
|
+
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
3709
|
+
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
3710
|
+
if (!haveSomethingToInstall) {
|
|
3711
|
+
return {
|
|
3712
|
+
ok: true,
|
|
3713
|
+
output: `no ${profile.displayName} manifest; skipped install`
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
if (installSteps.length === 0) {
|
|
3717
|
+
return {
|
|
3718
|
+
ok: true,
|
|
3719
|
+
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
const outputs = [];
|
|
3723
|
+
for (const step of installSteps) {
|
|
3724
|
+
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
3725
|
+
continue;
|
|
3726
|
+
const result = await spawnStep(worktreePath, step.argv);
|
|
3727
|
+
if (result.output) outputs.push(result.output);
|
|
3728
|
+
if (result.ok) continue;
|
|
3729
|
+
if (step.optional) {
|
|
3730
|
+
logger.warn(
|
|
3731
|
+
{ step: step.argv.join(" "), output: result.output },
|
|
3732
|
+
"installWorktreeDeps: optional install step failed; continuing"
|
|
3733
|
+
);
|
|
3734
|
+
continue;
|
|
3735
|
+
}
|
|
3736
|
+
return { ok: false, output: outputs.join("\n").trim() };
|
|
3737
|
+
}
|
|
3738
|
+
return { ok: true, output: outputs.join("\n").trim() };
|
|
3739
|
+
}
|
|
3740
|
+
function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
3271
3741
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
3272
3742
|
return {
|
|
3273
3743
|
ok: false,
|
|
@@ -3282,18 +3752,29 @@ function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
|
3282
3752
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
3283
3753
|
};
|
|
3284
3754
|
}
|
|
3755
|
+
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
3756
|
+
return {
|
|
3757
|
+
ok: false,
|
|
3758
|
+
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3285
3761
|
return { ok: true, target };
|
|
3286
3762
|
}
|
|
3287
|
-
async function runIngestScript(worktreePath,
|
|
3288
|
-
|
|
3763
|
+
async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
3764
|
+
const { ingest, profile, packageManager } = toolchain;
|
|
3765
|
+
if (ingest.kind !== "auto") {
|
|
3289
3766
|
return {
|
|
3290
3767
|
ran: false,
|
|
3291
3768
|
ok: false,
|
|
3292
3769
|
output: "",
|
|
3293
|
-
reason:
|
|
3770
|
+
reason: `${profile.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
|
|
3294
3771
|
};
|
|
3295
3772
|
}
|
|
3296
|
-
const validated = validateIngestEntrypoint(
|
|
3773
|
+
const validated = validateIngestEntrypoint(
|
|
3774
|
+
worktreePath,
|
|
3775
|
+
entrypoint,
|
|
3776
|
+
ingest.entrypointExtensions
|
|
3777
|
+
);
|
|
3297
3778
|
if (!validated.ok) {
|
|
3298
3779
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
3299
3780
|
}
|
|
@@ -3314,29 +3795,13 @@ async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
|
3314
3795
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
3315
3796
|
};
|
|
3316
3797
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3323
|
-
env: { ...process.env, ...env }
|
|
3324
|
-
});
|
|
3325
|
-
child.stdout?.on("data", (d) => output += d);
|
|
3326
|
-
child.stderr?.on("data", (d) => output += d);
|
|
3327
|
-
child.on(
|
|
3328
|
-
"error",
|
|
3329
|
-
(err) => resolveRun({
|
|
3330
|
-
ran: true,
|
|
3331
|
-
ok: false,
|
|
3332
|
-
output: `Failed to run ${runtime} ${entrypoint}: ${err.message}`
|
|
3333
|
-
})
|
|
3334
|
-
);
|
|
3335
|
-
child.on(
|
|
3336
|
-
"close",
|
|
3337
|
-
(code) => resolveRun({ ran: true, ok: code === 0, output: output.trim() })
|
|
3338
|
-
);
|
|
3798
|
+
const argv = resolveIngestArgv(ingest, entrypoint);
|
|
3799
|
+
const { code, output } = await runCommand(argv[0], argv.slice(1), {
|
|
3800
|
+
cwd: worktreePath,
|
|
3801
|
+
env,
|
|
3802
|
+
timeoutMs: INGEST_TIMEOUT_MS
|
|
3339
3803
|
});
|
|
3804
|
+
return { ran: true, ok: code === 0, output: output.trim() };
|
|
3340
3805
|
}
|
|
3341
3806
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
3342
3807
|
const trimmed = sourcePath.trim();
|
|
@@ -3351,8 +3816,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
3351
3816
|
} catch {
|
|
3352
3817
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
3353
3818
|
}
|
|
3354
|
-
const relPath =
|
|
3355
|
-
const dest =
|
|
3819
|
+
const relPath = join12(ingestDir, basename2(source));
|
|
3820
|
+
const dest = join12(worktreePath, relPath);
|
|
3356
3821
|
try {
|
|
3357
3822
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
3358
3823
|
await copyFile(source, dest);
|
|
@@ -3368,10 +3833,10 @@ function hasEnvVar(content, name) {
|
|
|
3368
3833
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
3369
3834
|
}
|
|
3370
3835
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
3371
|
-
const target =
|
|
3836
|
+
const target = join12(worktreePath, ".env");
|
|
3372
3837
|
let existing = "";
|
|
3373
3838
|
try {
|
|
3374
|
-
existing = await
|
|
3839
|
+
existing = await readFile9(target, "utf8");
|
|
3375
3840
|
} catch (err) {
|
|
3376
3841
|
if (err.code !== "ENOENT") throw err;
|
|
3377
3842
|
}
|
|
@@ -3488,69 +3953,33 @@ async function resolveSearchOnlyKey(index) {
|
|
|
3488
3953
|
}
|
|
3489
3954
|
|
|
3490
3955
|
// src/lib/algoliaDocs.ts
|
|
3491
|
-
import { readFileSync,
|
|
3492
|
-
import { dirname as dirname8, join as
|
|
3956
|
+
import { readFileSync, existsSync as existsSync5 } from "node:fs";
|
|
3957
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
3493
3958
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3494
|
-
var DOCS_SUBPATH =
|
|
3959
|
+
var DOCS_SUBPATH = join13("docs", "algolia-sdk");
|
|
3495
3960
|
function findDocsDir() {
|
|
3496
3961
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
3497
3962
|
for (; ; ) {
|
|
3498
|
-
const candidate =
|
|
3499
|
-
if (
|
|
3963
|
+
const candidate = join13(dir, DOCS_SUBPATH);
|
|
3964
|
+
if (existsSync5(candidate)) return candidate;
|
|
3500
3965
|
const parent = dirname8(dir);
|
|
3501
3966
|
if (parent === dir) return void 0;
|
|
3502
3967
|
dir = parent;
|
|
3503
3968
|
}
|
|
3504
3969
|
}
|
|
3505
|
-
function
|
|
3506
|
-
const docsDir = findDocsDir();
|
|
3507
|
-
if (!docsDir) {
|
|
3508
|
-
logger.warn(
|
|
3509
|
-
"algoliaDocs: docs/algolia-sdk not found; skipping SDK reference"
|
|
3510
|
-
);
|
|
3511
|
-
return "";
|
|
3512
|
-
}
|
|
3513
|
-
const files = readdirSync(docsDir).filter((f) => f.includes(language));
|
|
3514
|
-
if (files.length === 0) {
|
|
3515
|
-
logger.warn(
|
|
3516
|
-
{ language },
|
|
3517
|
-
"algoliaDocs: no SDK reference found for language; skipping"
|
|
3518
|
-
);
|
|
3519
|
-
return "";
|
|
3520
|
-
}
|
|
3521
|
-
return readFileSync(join11(docsDir, files[0]), "utf8").trim();
|
|
3522
|
-
}
|
|
3523
|
-
function getNamedDoc(name, language) {
|
|
3970
|
+
function getNamedDoc(name, key) {
|
|
3524
3971
|
const docsDir = findDocsDir();
|
|
3525
3972
|
if (!docsDir) {
|
|
3526
3973
|
logger.warn("docs/algolia-sdk not found");
|
|
3527
3974
|
return "";
|
|
3528
3975
|
}
|
|
3529
|
-
const file =
|
|
3530
|
-
if (!
|
|
3531
|
-
logger.warn({ name,
|
|
3976
|
+
const file = join13(docsDir, `${name}-${key}.md`);
|
|
3977
|
+
if (!existsSync5(file)) {
|
|
3978
|
+
logger.warn({ name, key }, "named SDK reference not found");
|
|
3532
3979
|
return "";
|
|
3533
3980
|
}
|
|
3534
3981
|
return readFileSync(file, "utf8").trim();
|
|
3535
3982
|
}
|
|
3536
|
-
function getFrameworkSpecificDoc(frameworks) {
|
|
3537
|
-
const fw = frameworks.map((f) => f.toLowerCase());
|
|
3538
|
-
if (fw.includes("vue") || fw.includes("nuxt")) {
|
|
3539
|
-
return loadAlgoliaDoc("vue");
|
|
3540
|
-
}
|
|
3541
|
-
if (fw.includes("react") || fw.includes("next.js")) {
|
|
3542
|
-
return loadAlgoliaDoc("react");
|
|
3543
|
-
}
|
|
3544
|
-
if (fw.includes("angular")) {
|
|
3545
|
-
return loadAlgoliaDoc("angular");
|
|
3546
|
-
}
|
|
3547
|
-
return loadAlgoliaDoc("js");
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
// src/lib/shell.ts
|
|
3551
|
-
function shellQuote(value) {
|
|
3552
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
3553
|
-
}
|
|
3554
3983
|
|
|
3555
3984
|
// src/actions/implement.ts
|
|
3556
3985
|
var implementSchema = z24.object({
|
|
@@ -3585,12 +4014,11 @@ var implementSchema = z24.object({
|
|
|
3585
4014
|
});
|
|
3586
4015
|
var implementationOutputSchema = z24.object({
|
|
3587
4016
|
summary: z24.string(),
|
|
3588
|
-
// Ingestion only:
|
|
3589
|
-
//
|
|
3590
|
-
//
|
|
3591
|
-
//
|
|
3592
|
-
// the agent
|
|
3593
|
-
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4017
|
+
// Ingestion only: the script the wizard should run, as a bare path — never a
|
|
4018
|
+
// command string, and never the interpreter. The command comes from the
|
|
4019
|
+
// resolved language toolchain (a registry constant); this path is validated to
|
|
4020
|
+
// a worktree-relative file with a runnable extension and substituted into it.
|
|
4021
|
+
// So the agent contributes no part of the command that gets executed.
|
|
3594
4022
|
entrypoint: z24.string().optional()
|
|
3595
4023
|
});
|
|
3596
4024
|
var verificationOutputSchema = z24.object({
|
|
@@ -3600,47 +4028,11 @@ var verificationOutputSchema = z24.object({
|
|
|
3600
4028
|
});
|
|
3601
4029
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
3602
4030
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
if (names.some((n) => n.includes("react") || n.includes("next")))
|
|
3608
|
-
return "React";
|
|
3609
|
-
if (names.some((n) => n.includes("angular"))) return "Angular";
|
|
3610
|
-
return "JavaScript";
|
|
3611
|
-
}
|
|
3612
|
-
function frameworksForDoc(framework) {
|
|
3613
|
-
switch (framework) {
|
|
3614
|
-
case "React":
|
|
3615
|
-
return ["react"];
|
|
3616
|
-
case "Vue":
|
|
3617
|
-
return ["vue"];
|
|
3618
|
-
case "Angular":
|
|
3619
|
-
return ["angular"];
|
|
3620
|
-
case "JavaScript":
|
|
3621
|
-
return [];
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
function publicEnvPrefix(language) {
|
|
3625
|
-
const frameworkNames = language.frameworks.map(
|
|
3626
|
-
(framework) => framework.name.toLowerCase()
|
|
4031
|
+
function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
4032
|
+
const prefix = publicEnvPrefix(
|
|
4033
|
+
language.frameworks.map((framework) => framework.name),
|
|
4034
|
+
strategy
|
|
3627
4035
|
);
|
|
3628
|
-
if (frameworkNames.some((name) => name.includes("next"))) {
|
|
3629
|
-
return "NEXT_PUBLIC_";
|
|
3630
|
-
}
|
|
3631
|
-
if (frameworkNames.some((name) => name.includes("nuxt"))) {
|
|
3632
|
-
return "NUXT_PUBLIC_";
|
|
3633
|
-
}
|
|
3634
|
-
if (frameworkNames.some((name) => name.includes("astro"))) {
|
|
3635
|
-
return "PUBLIC_";
|
|
3636
|
-
}
|
|
3637
|
-
if (frameworkNames.some((name) => name.includes("vite"))) {
|
|
3638
|
-
return "VITE_";
|
|
3639
|
-
}
|
|
3640
|
-
return "PUBLIC_";
|
|
3641
|
-
}
|
|
3642
|
-
function searchEnvVars(language, appId, searchKey) {
|
|
3643
|
-
const prefix = publicEnvPrefix(language);
|
|
3644
4036
|
return [
|
|
3645
4037
|
{
|
|
3646
4038
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -3652,6 +4044,38 @@ function searchEnvVars(language, appId, searchKey) {
|
|
|
3652
4044
|
}
|
|
3653
4045
|
];
|
|
3654
4046
|
}
|
|
4047
|
+
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4048
|
+
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4049
|
+
repoRoot,
|
|
4050
|
+
language.languages.map((l) => l.name)
|
|
4051
|
+
);
|
|
4052
|
+
if (candidates.length === 0) {
|
|
4053
|
+
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4054
|
+
logger.warn(
|
|
4055
|
+
{
|
|
4056
|
+
confirmed: language.languages.map((l) => l.name),
|
|
4057
|
+
onDisk: onDisk.map((p) => p.id),
|
|
4058
|
+
chosen: chosen.id
|
|
4059
|
+
},
|
|
4060
|
+
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4061
|
+
);
|
|
4062
|
+
return chosen;
|
|
4063
|
+
}
|
|
4064
|
+
if (candidates.length === 1) return candidates[0];
|
|
4065
|
+
const backends = candidates.filter(isBackendLanguage);
|
|
4066
|
+
if (backends.length === 1) return backends[0];
|
|
4067
|
+
if (backends.length === 0) return candidates[0];
|
|
4068
|
+
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4069
|
+
const options = backends.map((p) => p.displayName);
|
|
4070
|
+
const selection = await ctx.requestUserInput({
|
|
4071
|
+
prompt: "Which language should the ingestion script use?",
|
|
4072
|
+
promptType: "multipleChoice",
|
|
4073
|
+
options,
|
|
4074
|
+
defaultSelectedIndex: 0
|
|
4075
|
+
});
|
|
4076
|
+
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4077
|
+
return picked ?? backends[0];
|
|
4078
|
+
}
|
|
3655
4079
|
function baseInstructions(input) {
|
|
3656
4080
|
return [
|
|
3657
4081
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -3679,37 +4103,48 @@ function sourceSpecificInstructions(input) {
|
|
|
3679
4103
|
generated: [
|
|
3680
4104
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
3681
4105
|
"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 in the worktree, returning the file path. Do not write records or objectIDs yourself.",
|
|
3682
|
-
"In the script, read and parse each returned file path at runtime
|
|
4106
|
+
"In the script, read and parse each returned JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
|
|
3683
4107
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
3684
4108
|
]
|
|
3685
4109
|
};
|
|
3686
4110
|
return byLine[input.ingestionSource];
|
|
3687
4111
|
}
|
|
3688
4112
|
function ingestionInstructions(input) {
|
|
4113
|
+
const { ingestionProfile: profile, toolchain } = input;
|
|
4114
|
+
const { ingest } = toolchain;
|
|
4115
|
+
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4116
|
+
const runInstruction = ingest.kind === "auto" ? `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard runs it with \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so it must be a plain path with no flags or arguments and must run as-is under that command.` : `Return "entrypoint": the script path relative to the worktree root (e.g. "${profile.ingestEntrypointExample}"), ending in one of ${extensions}. The wizard does NOT run ${profile.displayName} ${toolchain.packageManager.id} projects itself \u2014 it tells the developer to run \`${describeIngestCommand(ingest, profile.ingestEntrypointExample)}\`, so also add whatever build configuration that command needs${ingest.kind === "manual" && ingest.requiresBuildTask ? `, including a "${ingest.requiresBuildTask}" task in "${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"}" that runs the script` : ""}.`;
|
|
3689
4117
|
return [
|
|
3690
4118
|
...input.confirmed && input.confirmed.length ? [
|
|
3691
|
-
`
|
|
4119
|
+
`Write the ingestion script in ${profile.displayName}, at "${ingestScriptDir(profile)}/" in the repo.`,
|
|
3692
4120
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
3693
|
-
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
|
|
3694
|
-
|
|
4121
|
+
`Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. ${profile.envReadInstruction} The wizard sets these when it runs the script.`,
|
|
4122
|
+
`Use the official Algolia ${profile.displayName} client (${profile.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
|
|
3695
4123
|
"After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
|
|
3696
|
-
getNamedDoc("save-records",
|
|
3697
|
-
|
|
4124
|
+
getNamedDoc("save-records", profile.sdk.docKey),
|
|
4125
|
+
dependencyInstruction(toolchain),
|
|
3698
4126
|
"The summary should be extremely concise.",
|
|
3699
|
-
|
|
4127
|
+
runInstruction,
|
|
3700
4128
|
...sourceSpecificInstructions(input)
|
|
3701
4129
|
] : []
|
|
3702
4130
|
];
|
|
3703
4131
|
}
|
|
3704
4132
|
function searchInstructions(input) {
|
|
3705
|
-
const doc =
|
|
4133
|
+
const doc = getNamedDoc(
|
|
4134
|
+
"instantsearch-setup",
|
|
4135
|
+
searchDocKey(input.searchStrategy)
|
|
4136
|
+
);
|
|
4137
|
+
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4138
|
+
const placement = isTemplate ? input.searchLocation ? `Add the search UI to the server-rendered template at "${input.searchLocation}" \u2014 ideally a shared layout, so it is reachable across the app.` : `This project has no shared template to host the UI, so create a standalone page at "${input.ingestDir}/search-demo.html" the developer can open directly, and add a TODO explaining how to move the snippet into their own layout.` : `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app.`;
|
|
3706
4139
|
return [
|
|
3707
4140
|
"Implement an in-app Algolia search experience.",
|
|
3708
|
-
`Build the search UI for ${input.
|
|
3709
|
-
"Follow the Algolia
|
|
4141
|
+
`Build the search UI for ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
|
|
4142
|
+
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3710
4143
|
doc,
|
|
3711
|
-
|
|
3712
|
-
|
|
4144
|
+
placement,
|
|
4145
|
+
`It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
4146
|
+
isTemplate ? "Load InstantSearch from a CDN with script tags as shown in the reference. Do not add JavaScript package dependencies, a bundler, or a build step." : 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4147
|
+
isTemplate ? "Read the App ID and search-only API key from server-side configuration/environment and render them into the page (e.g. as data- attributes the script reads); never hardcode them, and never put a write/admin key in HTML." : "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
|
|
3713
4148
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
3714
4149
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
3715
4150
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -3717,20 +4152,22 @@ function searchInstructions(input) {
|
|
|
3717
4152
|
// resolved app id / search-only key into ".env" under these exact names
|
|
3718
4153
|
// right after this step, so a renamed prefix here would leave the code
|
|
3719
4154
|
// reading a var the wizard never wrote.
|
|
3720
|
-
`Use exactly these
|
|
3721
|
-
'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
|
|
4155
|
+
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3722
4156
|
"The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
|
|
3723
4157
|
];
|
|
3724
4158
|
}
|
|
3725
4159
|
function verificationInstructions(input) {
|
|
4160
|
+
const protectedDirs = [
|
|
4161
|
+
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4162
|
+
];
|
|
3726
4163
|
return [
|
|
3727
4164
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
3728
4165
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
3729
|
-
|
|
4166
|
+
`Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
|
|
3730
4167
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
3731
4168
|
"Do not make speculative fixes when verifyImplementation cannot run, no checks exist, or failures are unrelated to these changes \u2014 note the limitation in your summary.",
|
|
3732
4169
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
3733
|
-
`Do not modify "${
|
|
4170
|
+
`Do not modify ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
|
|
3734
4171
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
3735
4172
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
3736
4173
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -3739,14 +4176,17 @@ function verificationInstructions(input) {
|
|
|
3739
4176
|
var IMPLEMENT_CONFIG = {
|
|
3740
4177
|
ingestion: {
|
|
3741
4178
|
title: "Algolia ingestion",
|
|
4179
|
+
label: "Ingestion",
|
|
3742
4180
|
buildInstructions: ingestionInstructions
|
|
3743
4181
|
},
|
|
3744
4182
|
search: {
|
|
3745
4183
|
title: "Algolia search",
|
|
4184
|
+
label: "Search",
|
|
3746
4185
|
buildInstructions: searchInstructions
|
|
3747
4186
|
},
|
|
3748
4187
|
verification: {
|
|
3749
4188
|
title: "Algolia verification",
|
|
4189
|
+
label: "Verification",
|
|
3750
4190
|
buildInstructions: verificationInstructions
|
|
3751
4191
|
}
|
|
3752
4192
|
};
|
|
@@ -3778,11 +4218,10 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
3778
4218
|
];
|
|
3779
4219
|
}
|
|
3780
4220
|
function formatSummary(useCase, summary) {
|
|
3781
|
-
|
|
3782
|
-
return `${label}: ${summary}`;
|
|
4221
|
+
return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
|
|
3783
4222
|
}
|
|
3784
|
-
function buildIngestCommand(worktree,
|
|
3785
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
4223
|
+
function buildIngestCommand(worktree, toolchain, entrypoint) {
|
|
4224
|
+
return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
|
|
3786
4225
|
}
|
|
3787
4226
|
function parseIngestRecordCount(output) {
|
|
3788
4227
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -3864,7 +4303,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3864
4303
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
3865
4304
|
}
|
|
3866
4305
|
const normalized = normalizeFindingPaths(findings);
|
|
3867
|
-
const
|
|
4306
|
+
const confirmed3 = normalized.confirmedEntities;
|
|
3868
4307
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
3869
4308
|
let appId;
|
|
3870
4309
|
let searchKey;
|
|
@@ -3902,31 +4341,66 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3902
4341
|
);
|
|
3903
4342
|
}
|
|
3904
4343
|
}
|
|
4344
|
+
const ingestionProfile = await resolveIngestionProfile(
|
|
4345
|
+
ctx,
|
|
4346
|
+
language,
|
|
4347
|
+
worktree
|
|
4348
|
+
);
|
|
4349
|
+
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4350
|
+
const verificationLanguages = [
|
|
4351
|
+
.../* @__PURE__ */ new Set([
|
|
4352
|
+
ingestionProfile.id,
|
|
4353
|
+
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4354
|
+
])
|
|
4355
|
+
];
|
|
4356
|
+
const frameworkName = language.frameworks[0]?.name;
|
|
4357
|
+
const searchStrategy = resolveSearchStrategy(
|
|
4358
|
+
frameworkName,
|
|
4359
|
+
verificationLanguages.includes(JAVASCRIPT)
|
|
4360
|
+
);
|
|
4361
|
+
logger.info(
|
|
4362
|
+
{
|
|
4363
|
+
language: ingestionProfile.id,
|
|
4364
|
+
packageManager: toolchain.packageManager.id,
|
|
4365
|
+
ingest: toolchain.ingest.kind,
|
|
4366
|
+
framework: frameworkName,
|
|
4367
|
+
searchStrategy
|
|
4368
|
+
},
|
|
4369
|
+
"implement: resolved ingestion toolchain and search strategy"
|
|
4370
|
+
);
|
|
3905
4371
|
const input = {
|
|
3906
4372
|
findings: normalized,
|
|
3907
|
-
confirmed:
|
|
4373
|
+
confirmed: confirmed3,
|
|
3908
4374
|
searchLocation,
|
|
3909
4375
|
targetIndex,
|
|
3910
4376
|
language,
|
|
3911
4377
|
appId,
|
|
3912
4378
|
searchKey,
|
|
3913
|
-
searchEnvVars:
|
|
4379
|
+
searchEnvVars: buildSearchEnvVars(
|
|
4380
|
+
language,
|
|
4381
|
+
searchStrategy,
|
|
4382
|
+
appId,
|
|
4383
|
+
searchKey
|
|
4384
|
+
),
|
|
3914
4385
|
ingestDir: INGEST_DIR,
|
|
3915
4386
|
ingestionSource,
|
|
3916
4387
|
uploadFilePath,
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4388
|
+
searchStrategy,
|
|
4389
|
+
frameworkName,
|
|
4390
|
+
ingestionProfile,
|
|
4391
|
+
toolchain,
|
|
4392
|
+
verificationLanguages
|
|
3920
4393
|
};
|
|
4394
|
+
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4395
|
+
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
3921
4396
|
const summaries = [];
|
|
3922
4397
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
3923
4398
|
let agentRuns = 0;
|
|
3924
|
-
let ingestRuntime;
|
|
3925
4399
|
let ingestEntrypoint;
|
|
3926
4400
|
let ingestScriptRan = false;
|
|
3927
4401
|
let ingestRecordCount;
|
|
3928
4402
|
let ingestDurationMs;
|
|
3929
|
-
|
|
4403
|
+
const failedInstalls = /* @__PURE__ */ new Set();
|
|
3930
4404
|
let ingestOutcomeMessage;
|
|
3931
4405
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
3932
4406
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -3940,16 +4414,19 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3940
4414
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
3941
4415
|
outputSchema: implementationOutputSchema
|
|
3942
4416
|
});
|
|
4417
|
+
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4418
|
+
if (!useCaseToolchain) return result;
|
|
3943
4419
|
ctx.notify({
|
|
3944
4420
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
3945
4421
|
});
|
|
3946
4422
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
3947
|
-
useCase: currentUseCase
|
|
4423
|
+
useCase: currentUseCase,
|
|
4424
|
+
language: useCaseToolchain.profile.id
|
|
3948
4425
|
});
|
|
3949
|
-
const install = await installWorktreeDeps(worktree);
|
|
4426
|
+
const install = await installWorktreeDeps(worktree, useCaseToolchain);
|
|
3950
4427
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
3951
4428
|
if (!install.ok) {
|
|
3952
|
-
|
|
4429
|
+
failedInstalls.add(useCaseToolchain.profile.displayName);
|
|
3953
4430
|
logger.warn(
|
|
3954
4431
|
{ useCase: currentUseCase, output: install.output },
|
|
3955
4432
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -3963,15 +4440,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3963
4440
|
return runAgent({
|
|
3964
4441
|
instructions: buildAgentInstructions("verification", input),
|
|
3965
4442
|
tools: toolsForUseCase("verification"),
|
|
3966
|
-
outputSchema: verificationOutputSchema
|
|
4443
|
+
outputSchema: verificationOutputSchema,
|
|
4444
|
+
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4445
|
+
languages: input.verificationLanguages
|
|
3967
4446
|
});
|
|
3968
4447
|
}
|
|
3969
4448
|
if (useCases.includes("ingestion")) {
|
|
3970
|
-
const { summary,
|
|
4449
|
+
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3971
4450
|
summaries.push(formatSummary("ingestion", summary));
|
|
3972
|
-
ingestRuntime = runtime;
|
|
3973
4451
|
ingestEntrypoint = entrypoint;
|
|
3974
|
-
if (
|
|
4452
|
+
if (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
|
|
3975
4453
|
ctx.clearNotices();
|
|
3976
4454
|
const runNow = await ctx.requestUserInput({
|
|
3977
4455
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -3983,13 +4461,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
3983
4461
|
const profile = await loadActiveProfile();
|
|
3984
4462
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
3985
4463
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
3986
|
-
|
|
4464
|
+
language: ingestionProfile.id,
|
|
3987
4465
|
entrypoint: ingestEntrypoint
|
|
3988
4466
|
});
|
|
3989
4467
|
const startedAt = Date.now();
|
|
3990
4468
|
const run2 = await runIngestScript(
|
|
3991
4469
|
worktree,
|
|
3992
|
-
|
|
4470
|
+
toolchain,
|
|
3993
4471
|
ingestEntrypoint,
|
|
3994
4472
|
{
|
|
3995
4473
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4003,7 +4481,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4003
4481
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4004
4482
|
if (ingestRecordCount != null) {
|
|
4005
4483
|
track("AI Wizard Ingest Successful", {
|
|
4006
|
-
entity_name:
|
|
4484
|
+
entity_name: confirmed3?.map((e) => e.name).join(", ") || "unknown",
|
|
4007
4485
|
record_count: ingestRecordCount,
|
|
4008
4486
|
duration_ms: ingestDurationMs
|
|
4009
4487
|
});
|
|
@@ -4016,7 +4494,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4016
4494
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4017
4495
|
logger.warn(
|
|
4018
4496
|
{
|
|
4019
|
-
|
|
4497
|
+
language: ingestionProfile.id,
|
|
4020
4498
|
entrypoint: ingestEntrypoint,
|
|
4021
4499
|
reason: run2.reason
|
|
4022
4500
|
},
|
|
@@ -4039,7 +4517,7 @@ ${run2.output}` : status;
|
|
|
4039
4517
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4040
4518
|
logger.warn(
|
|
4041
4519
|
{
|
|
4042
|
-
|
|
4520
|
+
language: ingestionProfile.id,
|
|
4043
4521
|
entrypoint: ingestEntrypoint,
|
|
4044
4522
|
output: run2.output
|
|
4045
4523
|
},
|
|
@@ -4056,10 +4534,28 @@ ${run2.output}` : status;
|
|
|
4056
4534
|
}
|
|
4057
4535
|
}
|
|
4058
4536
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4059
|
-
if (
|
|
4537
|
+
if (ingestEntrypoint) {
|
|
4060
4538
|
commandMessages.push(
|
|
4061
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4539
|
+
`Ingestion command: ${buildIngestCommand(worktree, toolchain, ingestEntrypoint)}`
|
|
4062
4540
|
);
|
|
4541
|
+
if (toolchain.ingest.kind === "manual") {
|
|
4542
|
+
commandMessages.push(
|
|
4543
|
+
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4544
|
+
);
|
|
4545
|
+
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4546
|
+
if (missingTask) {
|
|
4547
|
+
const warning = `\u26A0\uFE0F The command above needs a "${missingTask}" task, which is not in ${toolchain.packageManager.dependency.mode === "agent-declares" ? toolchain.packageManager.dependency.file : "the build file"} \u2014 add it before running, or run the script through your IDE instead.`;
|
|
4548
|
+
commandMessages.push(warning);
|
|
4549
|
+
summaries.push(warning);
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
if (ingestionSource === "local") {
|
|
4554
|
+
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4555
|
+
if (limitation) {
|
|
4556
|
+
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4557
|
+
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4558
|
+
}
|
|
4063
4559
|
}
|
|
4064
4560
|
await ctx.requestUserInput({
|
|
4065
4561
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4070,7 +4566,20 @@ ${run2.output}` : status;
|
|
|
4070
4566
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4071
4567
|
});
|
|
4072
4568
|
}
|
|
4073
|
-
|
|
4569
|
+
const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
|
|
4570
|
+
if (skipSearch) {
|
|
4571
|
+
const target = describeSearchTarget(
|
|
4572
|
+
input.searchStrategy,
|
|
4573
|
+
input.frameworkName
|
|
4574
|
+
);
|
|
4575
|
+
summaries.push(
|
|
4576
|
+
`Search UI skipped: the wizard can't scaffold a native search UI for ${target}. Your records are in the "${targetIndex}" index \u2014 build the UI with Algolia's mobile InstantSearch libraries (https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/ for iOS, .../android for Android).`
|
|
4577
|
+
);
|
|
4578
|
+
track("AI Wizard Search UI Skipped", {
|
|
4579
|
+
framework: input.frameworkName ?? "unknown"
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
if (useCases.includes("search") && !skipSearch) {
|
|
4074
4583
|
let extraInstructions = [];
|
|
4075
4584
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4076
4585
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -4141,9 +4650,9 @@ ${run2.output}` : status;
|
|
|
4141
4650
|
"implement: agent reported success but no files changed in the worktree"
|
|
4142
4651
|
);
|
|
4143
4652
|
}
|
|
4144
|
-
if (
|
|
4653
|
+
if (failedInstalls.size > 0) {
|
|
4145
4654
|
summaries.push(
|
|
4146
|
-
|
|
4655
|
+
`\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
|
|
4147
4656
|
);
|
|
4148
4657
|
}
|
|
4149
4658
|
return {
|
|
@@ -4151,10 +4660,10 @@ ${run2.output}` : status;
|
|
|
4151
4660
|
filesChanged,
|
|
4152
4661
|
summary: summaries.join("\n\n"),
|
|
4153
4662
|
worktreePath: worktree,
|
|
4154
|
-
...useCases.includes("ingestion") &&
|
|
4663
|
+
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4155
4664
|
ingestCommand: buildIngestCommand(
|
|
4156
4665
|
worktree,
|
|
4157
|
-
|
|
4666
|
+
toolchain,
|
|
4158
4667
|
ingestEntrypoint
|
|
4159
4668
|
),
|
|
4160
4669
|
ingestScriptRan,
|
|
@@ -4483,20 +4992,20 @@ function parseCliArgs(argv) {
|
|
|
4483
4992
|
}
|
|
4484
4993
|
|
|
4485
4994
|
// src/lib/resetState.ts
|
|
4486
|
-
import { readdir as
|
|
4487
|
-
import { join as
|
|
4995
|
+
import { readdir as readdir5, rm as rm2 } from "node:fs/promises";
|
|
4996
|
+
import { join as join14 } from "node:path";
|
|
4488
4997
|
var KEEP = ["wizard.log"];
|
|
4489
4998
|
async function resetProjectState() {
|
|
4490
4999
|
const dir = stateDir();
|
|
4491
5000
|
let entries;
|
|
4492
5001
|
try {
|
|
4493
|
-
entries = await
|
|
5002
|
+
entries = await readdir5(dir);
|
|
4494
5003
|
} catch {
|
|
4495
5004
|
return { dir, removed: [] };
|
|
4496
5005
|
}
|
|
4497
5006
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
4498
5007
|
await Promise.all(
|
|
4499
|
-
targets.map((name) => rm2(
|
|
5008
|
+
targets.map((name) => rm2(join14(dir, name), { recursive: true, force: true }))
|
|
4500
5009
|
);
|
|
4501
5010
|
return { dir, removed: targets };
|
|
4502
5011
|
}
|