@algolia/wizard 0.8.0-rc.67.55 → 0.8.0
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 +620 -1511
- package/docs/algolia-sdk/README.md +30 -53
- package/docs/algolia-sdk/search-single-index.md +42 -0
- package/package.json +2 -2
- package/docs/algolia-sdk/instantsearch-setup-templates.md +0 -92
- package/docs/algolia-sdk/save-records-csharp.md +0 -71
- package/docs/algolia-sdk/save-records-dart.md +0 -74
- package/docs/algolia-sdk/save-records-go.md +0 -62
- package/docs/algolia-sdk/save-records-java.md +0 -66
- package/docs/algolia-sdk/save-records-kotlin.md +0 -60
- package/docs/algolia-sdk/save-records-php.md +0 -50
- package/docs/algolia-sdk/save-records-python.md +0 -51
- package/docs/algolia-sdk/save-records-ruby.md +0 -48
- package/docs/algolia-sdk/save-records-scala.md +0 -68
- package/docs/algolia-sdk/save-records-swift.md +0 -88
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 Box14, Text as Text14, 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 Box6, Text as Text6, useInput as useInput2 } from "ink";
|
|
501
501
|
import TextInput from "ink-text-input";
|
|
502
|
-
import { useState as
|
|
502
|
+
import { useState as useState5 } from "react";
|
|
503
503
|
|
|
504
504
|
// src/ui/NextAction.tsx
|
|
505
505
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -525,13 +525,14 @@ function NextAction({
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// src/ui/SelectPrompt.tsx
|
|
528
|
-
import { Box as
|
|
529
|
-
import { useLayoutEffect, useRef as
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
528
|
+
import { Box as Box5, Text as Text5, useInput, useWindowSize as useWindowSize4 } from "ink";
|
|
529
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState4 } from "react";
|
|
530
|
+
|
|
531
|
+
// src/ui/ScrollView.tsx
|
|
532
|
+
import { Box as Box4, Text as Text4, measureElement as measureElement2, useWindowSize as useWindowSize3 } from "ink";
|
|
533
|
+
import { useCallback, useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
534
|
+
import { jsxs as jsxs3 } from "react/jsx-runtime";
|
|
535
|
+
var INDICATOR_ROWS = 2;
|
|
535
536
|
function fittedWidth(node, columns) {
|
|
536
537
|
let left = 0;
|
|
537
538
|
for (let n = node; n; n = n.parentNode) {
|
|
@@ -539,6 +540,89 @@ function fittedWidth(node, columns) {
|
|
|
539
540
|
}
|
|
540
541
|
return Math.max(Math.min(measureElement2(node).width, columns - left), 0);
|
|
541
542
|
}
|
|
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;
|
|
542
626
|
function SelectPrompt({
|
|
543
627
|
options,
|
|
544
628
|
onSelect,
|
|
@@ -552,10 +636,10 @@ function SelectPrompt({
|
|
|
552
636
|
secondary,
|
|
553
637
|
defaultSelectedIndex = 0
|
|
554
638
|
}) {
|
|
555
|
-
const [index, setIndex] =
|
|
639
|
+
const [index, setIndex] = useState4(
|
|
556
640
|
() => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
|
|
557
641
|
);
|
|
558
|
-
const [checked, setChecked] =
|
|
642
|
+
const [checked, setChecked] = useState4(() => /* @__PURE__ */ new Set());
|
|
559
643
|
const hasCancel = Boolean(multi || cancelable);
|
|
560
644
|
const rows = hasCancel ? [...options, "Cancel"] : options;
|
|
561
645
|
const cancelIndex = hasCancel ? options.length : -1;
|
|
@@ -563,14 +647,14 @@ function SelectPrompt({
|
|
|
563
647
|
if (rows.length > 1) hints.push({ key: "[\u2191] [\u2193]", label: "move" });
|
|
564
648
|
if (multi) hints.push({ key: "[space]", label: "select" });
|
|
565
649
|
hints.push({ key: "[enter]", label: "confirm" });
|
|
566
|
-
const containerRef =
|
|
567
|
-
const { columns } =
|
|
568
|
-
const [width, setWidth] =
|
|
569
|
-
|
|
570
|
-
if (containerRef.current)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
650
|
+
const containerRef = useRef3(null);
|
|
651
|
+
const { columns } = useWindowSize4();
|
|
652
|
+
const [width, setWidth] = useState4(columns);
|
|
653
|
+
useLayoutEffect2(() => {
|
|
654
|
+
if (!containerRef.current) return;
|
|
655
|
+
const measured = fittedWidth(containerRef.current, columns);
|
|
656
|
+
setWidth((prev) => prev === measured ? prev : measured);
|
|
657
|
+
});
|
|
574
658
|
const inner = Math.max(width - BAR_PADDING, 0);
|
|
575
659
|
const labelWidth = Math.min(
|
|
576
660
|
ARROW_WIDTH + (multi ? 2 : 0) + Math.max(0, ...rows.map((opt) => opt.length)) + COLUMN_GAP,
|
|
@@ -586,6 +670,15 @@ function SelectPrompt({
|
|
|
586
670
|
const barWidth = Math.min(labelWidth + badgeWidth + BAR_PADDING, width);
|
|
587
671
|
const barLabelWidth = Math.max(barWidth - BAR_PADDING - badgeWidth, 0);
|
|
588
672
|
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);
|
|
589
682
|
useInput((input, key) => {
|
|
590
683
|
if (rows.length === 0) return;
|
|
591
684
|
if (key.upArrow || input === "k") {
|
|
@@ -609,62 +702,65 @@ function SelectPrompt({
|
|
|
609
702
|
}
|
|
610
703
|
}
|
|
611
704
|
});
|
|
612
|
-
return /* @__PURE__ */ jsx4(
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
705
|
+
return /* @__PURE__ */ jsx4(Box5, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, width, children: [
|
|
706
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, flexShrink: 0, children: [
|
|
707
|
+
error && /* @__PURE__ */ jsx4(Text5, { color: COLORS.danger, children: error }),
|
|
708
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
709
|
+
table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
|
|
710
|
+
/* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
711
|
+
question && /* @__PURE__ */ jsx4(Text5, { color: COLORS.muted, children: question }),
|
|
712
|
+
helpText && /* @__PURE__ */ jsx4(Text5, { color: COLORS.dim, children: helpText })
|
|
713
|
+
] })
|
|
619
714
|
] }),
|
|
620
|
-
/* @__PURE__ */ jsx4(
|
|
715
|
+
/* @__PURE__ */ jsx4(ScrollView, { scroll, children: visible.map((option, visibleIndex) => {
|
|
716
|
+
const i = scroll.offset + visibleIndex;
|
|
621
717
|
const highlighted = i === index;
|
|
622
718
|
const isCancel = i === cancelIndex;
|
|
623
719
|
const bullet = multi && !isCancel ? checked.has(i) ? "\u25CF " : "\u25CB " : "";
|
|
624
720
|
const sec = isCancel ? void 0 : secondary?.[i];
|
|
625
721
|
const labelColor = highlighted ? COLORS.highlight.fg : void 0;
|
|
626
|
-
const label = /* @__PURE__ */
|
|
722
|
+
const label = /* @__PURE__ */ jsxs4(Text5, { color: labelColor, wrap: "truncate", children: [
|
|
627
723
|
highlighted ? "\u276F " : " ",
|
|
628
724
|
bullet,
|
|
629
725
|
option
|
|
630
726
|
] });
|
|
631
727
|
const isText = sec?.kind === "text";
|
|
632
|
-
return /* @__PURE__ */
|
|
633
|
-
|
|
728
|
+
return /* @__PURE__ */ jsxs4(
|
|
729
|
+
Box5,
|
|
634
730
|
{
|
|
635
731
|
width: isText ? "100%" : barWidth,
|
|
636
732
|
paddingX: 1,
|
|
637
733
|
paddingY: 1,
|
|
638
734
|
backgroundColor: highlighted ? COLORS.highlight.bg : void 0,
|
|
639
735
|
children: [
|
|
640
|
-
/* @__PURE__ */ jsx4(
|
|
641
|
-
isText && textWidth > 0 && /* @__PURE__ */ jsx4(
|
|
642
|
-
|
|
736
|
+
/* @__PURE__ */ jsx4(Box5, { width: isText ? labelWidth : barLabelWidth, children: label }),
|
|
737
|
+
isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box5, { width: textWidth, children: /* @__PURE__ */ jsx4(
|
|
738
|
+
Text5,
|
|
643
739
|
{
|
|
644
740
|
wrap: "truncate",
|
|
645
741
|
color: highlighted ? COLORS.primary : COLORS.muted,
|
|
646
742
|
children: sec.value
|
|
647
743
|
}
|
|
648
744
|
) }),
|
|
649
|
-
sec?.kind === "badge" && /* @__PURE__ */ jsx4(
|
|
745
|
+
sec?.kind === "badge" && /* @__PURE__ */ jsx4(Box5, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text5, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
|
|
650
746
|
]
|
|
651
747
|
},
|
|
652
748
|
`row-${i}`
|
|
653
749
|
);
|
|
654
750
|
}) }),
|
|
655
|
-
/* @__PURE__ */ jsx4(
|
|
751
|
+
/* @__PURE__ */ jsx4(Box5, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
656
752
|
i > 0 ? " " : "",
|
|
657
|
-
/* @__PURE__ */ jsx4(
|
|
658
|
-
/* @__PURE__ */
|
|
753
|
+
/* @__PURE__ */ jsx4(Text5, { color: COLORS.primary, children: key }),
|
|
754
|
+
/* @__PURE__ */ jsxs4(Text5, { color: COLORS.dim, children: [
|
|
659
755
|
" ",
|
|
660
756
|
label
|
|
661
757
|
] })
|
|
662
|
-
] }, label)) })
|
|
758
|
+
] }, label)) }) })
|
|
663
759
|
] }) });
|
|
664
760
|
}
|
|
665
761
|
|
|
666
762
|
// src/ui/PromptInput.tsx
|
|
667
|
-
import { jsx as jsx5, jsxs as
|
|
763
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
668
764
|
var ACCEPT_REJECT_OPTIONS = ["Accept", "Reject"];
|
|
669
765
|
function EnterToContinuePrompt({
|
|
670
766
|
question,
|
|
@@ -675,10 +771,10 @@ function EnterToContinuePrompt({
|
|
|
675
771
|
if (key.return) onDecide(true);
|
|
676
772
|
else if (key.escape) onDecide(false);
|
|
677
773
|
});
|
|
678
|
-
return /* @__PURE__ */
|
|
679
|
-
messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
680
|
-
question && /* @__PURE__ */ jsx5(
|
|
681
|
-
/* @__PURE__ */
|
|
774
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 1, children: [
|
|
775
|
+
messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
776
|
+
question && /* @__PURE__ */ jsx5(Text6, { color: COLORS.primary, children: question }),
|
|
777
|
+
/* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
|
|
682
778
|
/* @__PURE__ */ jsx5(NextAction, { action: "continue", keyHint: "enter" }),
|
|
683
779
|
/* @__PURE__ */ jsx5(NextAction, { action: "decline", keyHint: "esc", hierarchy: "secondary" })
|
|
684
780
|
] })
|
|
@@ -686,13 +782,13 @@ function EnterToContinuePrompt({
|
|
|
686
782
|
}
|
|
687
783
|
function PromptInput() {
|
|
688
784
|
const { phase, inputReq, submitInput } = useWizard();
|
|
689
|
-
const [draft, setDraft] =
|
|
785
|
+
const [draft, setDraft] = useState5("");
|
|
690
786
|
if (phase === "done" || phase === "error") {
|
|
691
|
-
return /* @__PURE__ */ jsx5(
|
|
787
|
+
return /* @__PURE__ */ jsx5(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
|
|
692
788
|
}
|
|
693
789
|
if (phase !== "awaitingInput" || !inputReq) return null;
|
|
694
790
|
if (inputReq.promptType === "multipleChoice") {
|
|
695
|
-
return /* @__PURE__ */ jsx5(
|
|
791
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
696
792
|
SelectPrompt,
|
|
697
793
|
{
|
|
698
794
|
question: inputReq.prompt,
|
|
@@ -709,7 +805,7 @@ function PromptInput() {
|
|
|
709
805
|
) });
|
|
710
806
|
}
|
|
711
807
|
if (inputReq.promptType === "multiSelect") {
|
|
712
|
-
return /* @__PURE__ */ jsx5(
|
|
808
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
713
809
|
SelectPrompt,
|
|
714
810
|
{
|
|
715
811
|
multi: true,
|
|
@@ -724,7 +820,7 @@ function PromptInput() {
|
|
|
724
820
|
) });
|
|
725
821
|
}
|
|
726
822
|
if (inputReq.promptType === "notice") {
|
|
727
|
-
return /* @__PURE__ */ jsx5(
|
|
823
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
728
824
|
SelectPrompt,
|
|
729
825
|
{
|
|
730
826
|
question: inputReq.prompt,
|
|
@@ -746,7 +842,7 @@ function PromptInput() {
|
|
|
746
842
|
}
|
|
747
843
|
if (inputReq.promptType === "acceptReject") {
|
|
748
844
|
const labels = inputReq.options?.length ? inputReq.options : ACCEPT_REJECT_OPTIONS;
|
|
749
|
-
return /* @__PURE__ */ jsx5(
|
|
845
|
+
return /* @__PURE__ */ jsx5(Box6, { flexGrow: 1, children: /* @__PURE__ */ jsx5(
|
|
750
846
|
SelectPrompt,
|
|
751
847
|
{
|
|
752
848
|
question: inputReq.prompt,
|
|
@@ -757,11 +853,11 @@ function PromptInput() {
|
|
|
757
853
|
}
|
|
758
854
|
) });
|
|
759
855
|
}
|
|
760
|
-
return /* @__PURE__ */
|
|
761
|
-
inputReq.error && /* @__PURE__ */ jsx5(
|
|
762
|
-
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
856
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
857
|
+
inputReq.error && /* @__PURE__ */ jsx5(Text6, { color: COLORS.danger, children: inputReq.error }),
|
|
858
|
+
inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text6, { color: COLORS.muted, children: m }, `msg-${i}`)),
|
|
859
|
+
/* @__PURE__ */ jsxs5(Box6, { children: [
|
|
860
|
+
/* @__PURE__ */ jsxs5(Text6, { color: COLORS.primary, children: [
|
|
765
861
|
inputReq.prompt,
|
|
766
862
|
" "
|
|
767
863
|
] }),
|
|
@@ -783,7 +879,7 @@ function PromptInput() {
|
|
|
783
879
|
// src/ui/Welcome.tsx
|
|
784
880
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
785
881
|
import { fileURLToPath } from "node:url";
|
|
786
|
-
import { Box as
|
|
882
|
+
import { Box as Box7, Spacer, Text as Text7, useInput as useInput3, useWindowSize as useWindowSize5 } from "ink";
|
|
787
883
|
|
|
788
884
|
// src/ui/copy/welcome.ts
|
|
789
885
|
var sidebarItems = [
|
|
@@ -796,12 +892,12 @@ var sidebarItems = [
|
|
|
796
892
|
description: "push 100 records to Algolia in seconds"
|
|
797
893
|
},
|
|
798
894
|
{
|
|
799
|
-
title: "detect your
|
|
800
|
-
description: "React, Vue, Angular,
|
|
895
|
+
title: "detect your framework",
|
|
896
|
+
description: "React, Vue, Angular, Vanilla JS"
|
|
801
897
|
},
|
|
802
898
|
{
|
|
803
899
|
title: "scaffold a search UI",
|
|
804
|
-
description: "a styled InstantSearch
|
|
900
|
+
description: "a styled InstantSearch component, wired into your app"
|
|
805
901
|
},
|
|
806
902
|
{
|
|
807
903
|
title: "ship it",
|
|
@@ -811,27 +907,27 @@ var sidebarItems = [
|
|
|
811
907
|
|
|
812
908
|
// src/ui/Welcome.tsx
|
|
813
909
|
import Image, { InkPictureProvider } from "ink-picture";
|
|
814
|
-
import { jsx as jsx6, jsxs as
|
|
910
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
815
911
|
var IMAGE_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "algolia.png");
|
|
816
912
|
function SidebarItem({
|
|
817
913
|
title,
|
|
818
914
|
description
|
|
819
915
|
}) {
|
|
820
|
-
return /* @__PURE__ */
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
/* @__PURE__ */ jsx6(
|
|
823
|
-
/* @__PURE__ */ jsx6(
|
|
916
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
917
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
|
|
918
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.success, children: "\u2192" }),
|
|
919
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.strong, bold: true, children: title })
|
|
824
920
|
] }),
|
|
825
|
-
/* @__PURE__ */
|
|
921
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
|
|
826
922
|
/* @__PURE__ */ jsx6(Spacer, {}),
|
|
827
|
-
/* @__PURE__ */ jsx6(
|
|
923
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: description })
|
|
828
924
|
] })
|
|
829
925
|
] });
|
|
830
926
|
}
|
|
831
927
|
function Welcome() {
|
|
832
928
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
833
929
|
const openLearnMore = useWizard((s) => s.openLearnMore);
|
|
834
|
-
const { rows } =
|
|
930
|
+
const { rows } = useWindowSize5();
|
|
835
931
|
useInput3((input, key) => {
|
|
836
932
|
if (key.return) confirmStart();
|
|
837
933
|
else if (input === "i") openLearnMore();
|
|
@@ -850,15 +946,15 @@ function Welcome() {
|
|
|
850
946
|
if (rows < 30) {
|
|
851
947
|
layout = scales["small"];
|
|
852
948
|
}
|
|
853
|
-
return /* @__PURE__ */
|
|
949
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
|
|
854
950
|
/* @__PURE__ */ jsx6(
|
|
855
|
-
|
|
951
|
+
Box7,
|
|
856
952
|
{
|
|
857
953
|
paddingY: layout.main.padding.y,
|
|
858
954
|
paddingX: layout.main.padding.x,
|
|
859
955
|
flexDirection: "column",
|
|
860
956
|
justifyContent: "center",
|
|
861
|
-
children: /* @__PURE__ */
|
|
957
|
+
children: /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 2, children: [
|
|
862
958
|
/* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
|
|
863
959
|
Image,
|
|
864
960
|
{
|
|
@@ -870,16 +966,16 @@ function Welcome() {
|
|
|
870
966
|
protocol: "halfBlock"
|
|
871
967
|
}
|
|
872
968
|
) }),
|
|
873
|
-
/* @__PURE__ */ jsx6(
|
|
874
|
-
/* @__PURE__ */
|
|
969
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
|
|
970
|
+
/* @__PURE__ */ jsxs6(Box7, { gap: 1, flexDirection: "column", children: [
|
|
875
971
|
/* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "enter" }),
|
|
876
972
|
/* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
|
|
877
973
|
] })
|
|
878
974
|
] })
|
|
879
975
|
}
|
|
880
976
|
),
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
|
|
977
|
+
/* @__PURE__ */ jsxs6(
|
|
978
|
+
Box7,
|
|
883
979
|
{
|
|
884
980
|
backgroundColor: COLORS.bg.sidebar,
|
|
885
981
|
width: 40,
|
|
@@ -889,7 +985,7 @@ function Welcome() {
|
|
|
889
985
|
flexDirection: "column",
|
|
890
986
|
justifyContent: "center",
|
|
891
987
|
children: [
|
|
892
|
-
/* @__PURE__ */ jsx6(
|
|
988
|
+
/* @__PURE__ */ jsx6(Text7, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
|
|
893
989
|
sidebarItems.map((i, idx) => /* @__PURE__ */ jsx6(SidebarItem, { title: i.title, description: i.description }, idx))
|
|
894
990
|
]
|
|
895
991
|
}
|
|
@@ -899,7 +995,7 @@ function Welcome() {
|
|
|
899
995
|
|
|
900
996
|
// src/ui/LearnMore.tsx
|
|
901
997
|
import { Fragment as Fragment2 } from "react";
|
|
902
|
-
import { Box as
|
|
998
|
+
import { Box as Box8, Text as Text8, useInput as useInput4, useWindowSize as useWindowSize6 } from "ink";
|
|
903
999
|
|
|
904
1000
|
// src/ui/copy/learn-more.ts
|
|
905
1001
|
var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
|
|
@@ -907,7 +1003,7 @@ var accessItems = [
|
|
|
907
1003
|
{
|
|
908
1004
|
tag: "READ",
|
|
909
1005
|
title: "Project files",
|
|
910
|
-
description: "reads
|
|
1006
|
+
description: "reads package.json, configs & source to detect your stack. Read-only; nothing is uploaded."
|
|
911
1007
|
},
|
|
912
1008
|
{
|
|
913
1009
|
tag: "WRITE",
|
|
@@ -936,7 +1032,7 @@ var policyLinks = [
|
|
|
936
1032
|
];
|
|
937
1033
|
|
|
938
1034
|
// src/ui/LearnMore.tsx
|
|
939
|
-
import { jsx as jsx7, jsxs as
|
|
1035
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
940
1036
|
var TAG_COLORS = {
|
|
941
1037
|
READ: COLORS.success,
|
|
942
1038
|
WRITE: COLORS.badge,
|
|
@@ -952,25 +1048,25 @@ function NeverLine({
|
|
|
952
1048
|
}) {
|
|
953
1049
|
const used = segments.reduce((n, s) => n + s.text.length, 0);
|
|
954
1050
|
const rightPad = Math.max(0, width - 2 - NEVER_BOX_PAD_X - used);
|
|
955
|
-
return /* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */ jsx7(
|
|
1051
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1052
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" }),
|
|
957
1053
|
" ".repeat(NEVER_BOX_PAD_X),
|
|
958
|
-
segments.map((s, i) => /* @__PURE__ */ jsx7(
|
|
1054
|
+
segments.map((s, i) => /* @__PURE__ */ jsx7(Text8, { color: s.color, bold: s.bold, children: s.text }, i)),
|
|
959
1055
|
" ".repeat(rightPad),
|
|
960
|
-
/* @__PURE__ */ jsx7(
|
|
1056
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: "\u2502" })
|
|
961
1057
|
] });
|
|
962
1058
|
}
|
|
963
1059
|
function LearnMore() {
|
|
964
1060
|
const confirmStart = useWizard((s) => s.confirmStart);
|
|
965
1061
|
const backToHome = useWizard((s) => s.backToHome);
|
|
966
|
-
const { columns } =
|
|
1062
|
+
const { columns } = useWindowSize6();
|
|
967
1063
|
const dividerWidth = Math.max(0, columns - PADDING_X * 2);
|
|
968
1064
|
useInput4((_input, key) => {
|
|
969
1065
|
if (key.escape) backToHome();
|
|
970
1066
|
else if (key.return) confirmStart();
|
|
971
1067
|
});
|
|
972
|
-
return /* @__PURE__ */
|
|
973
|
-
|
|
1068
|
+
return /* @__PURE__ */ jsxs7(
|
|
1069
|
+
Box8,
|
|
974
1070
|
{
|
|
975
1071
|
flexDirection: "column",
|
|
976
1072
|
paddingX: PADDING_X,
|
|
@@ -978,20 +1074,20 @@ function LearnMore() {
|
|
|
978
1074
|
width: "100%",
|
|
979
1075
|
gap: 1,
|
|
980
1076
|
children: [
|
|
981
|
-
/* @__PURE__ */ jsx7(
|
|
982
|
-
/* @__PURE__ */ jsx7(
|
|
983
|
-
/* @__PURE__ */ jsx7(
|
|
984
|
-
/* @__PURE__ */ jsx7(
|
|
985
|
-
/* @__PURE__ */
|
|
986
|
-
/* @__PURE__ */ jsx7(
|
|
987
|
-
/* @__PURE__ */ jsx7(
|
|
988
|
-
/* @__PURE__ */ jsx7(
|
|
989
|
-
/* @__PURE__ */ jsx7(
|
|
1077
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
|
|
1078
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: accessIntro }),
|
|
1079
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1080
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
|
|
1081
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, marginTop: 1, children: [
|
|
1082
|
+
/* @__PURE__ */ jsx7(Box8, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text8, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
|
|
1083
|
+
/* @__PURE__ */ jsx7(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1084
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: item.title }),
|
|
1085
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
|
|
990
1086
|
] }) })
|
|
991
1087
|
] })
|
|
992
1088
|
] }, item.tag)) }),
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */ jsx7(
|
|
1089
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1090
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
|
|
995
1091
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
996
1092
|
/* @__PURE__ */ jsx7(
|
|
997
1093
|
NeverLine,
|
|
@@ -1000,7 +1096,7 @@ function LearnMore() {
|
|
|
1000
1096
|
segments: [{ text: "I NEVER", color: COLORS.danger, bold: true }]
|
|
1001
1097
|
}
|
|
1002
1098
|
),
|
|
1003
|
-
neverItems.map((item) => /* @__PURE__ */
|
|
1099
|
+
neverItems.map((item) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1004
1100
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1005
1101
|
/* @__PURE__ */ jsx7(
|
|
1006
1102
|
NeverLine,
|
|
@@ -1015,23 +1111,23 @@ function LearnMore() {
|
|
|
1015
1111
|
)
|
|
1016
1112
|
] }, item)),
|
|
1017
1113
|
/* @__PURE__ */ jsx7(NeverLine, { width: dividerWidth }),
|
|
1018
|
-
/* @__PURE__ */ jsx7(
|
|
1114
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
|
|
1019
1115
|
] }),
|
|
1020
|
-
/* @__PURE__ */ jsx7(
|
|
1021
|
-
/* @__PURE__ */ jsx7(
|
|
1022
|
-
/* @__PURE__ */ jsx7(
|
|
1116
|
+
/* @__PURE__ */ jsx7(Box8, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1117
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
|
|
1118
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.accent, children: link.url })
|
|
1023
1119
|
] }, link.label)) }),
|
|
1024
|
-
/* @__PURE__ */
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */ jsx7(
|
|
1027
|
-
/* @__PURE__ */ jsx7(
|
|
1028
|
-
/* @__PURE__ */ jsx7(
|
|
1120
|
+
/* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "row", gap: 3, children: [
|
|
1121
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1122
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1123
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "esc" }),
|
|
1124
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "] back" })
|
|
1029
1125
|
] }),
|
|
1030
|
-
/* @__PURE__ */
|
|
1031
|
-
/* @__PURE__ */ jsx7(
|
|
1032
|
-
/* @__PURE__ */ jsx7(
|
|
1033
|
-
/* @__PURE__ */ jsx7(
|
|
1034
|
-
/* @__PURE__ */ jsx7(
|
|
1126
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", gap: 1, children: [
|
|
1127
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "[" }),
|
|
1128
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.primary, children: "enter" }),
|
|
1129
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.muted, children: "]" }),
|
|
1130
|
+
/* @__PURE__ */ jsx7(Text8, { color: COLORS.success, bold: true, children: "start wizard" })
|
|
1035
1131
|
] })
|
|
1036
1132
|
] })
|
|
1037
1133
|
]
|
|
@@ -1040,10 +1136,10 @@ function LearnMore() {
|
|
|
1040
1136
|
}
|
|
1041
1137
|
|
|
1042
1138
|
// src/ui/Sidebar.tsx
|
|
1043
|
-
import { Box as
|
|
1139
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1044
1140
|
|
|
1045
1141
|
// src/ui/Steps.tsx
|
|
1046
|
-
import { Box as
|
|
1142
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1047
1143
|
import Spinner from "ink-spinner";
|
|
1048
1144
|
|
|
1049
1145
|
// src/core/persistence.ts
|
|
@@ -1072,11 +1168,11 @@ async function clearWorkflowState(workflowId) {
|
|
|
1072
1168
|
}
|
|
1073
1169
|
|
|
1074
1170
|
// src/ui/Steps.tsx
|
|
1075
|
-
import { jsx as jsx8, jsxs as
|
|
1171
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1076
1172
|
function Steps() {
|
|
1077
1173
|
const { steps } = useWizard();
|
|
1078
1174
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1079
|
-
return /* @__PURE__ */ jsx8(
|
|
1175
|
+
return /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status[s.status], children: [
|
|
1080
1176
|
s.status === "running" ? /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) : MARKER[s.status],
|
|
1081
1177
|
" ",
|
|
1082
1178
|
s.title
|
|
@@ -1086,7 +1182,7 @@ function CurrentStep() {
|
|
|
1086
1182
|
const { steps } = useWizard();
|
|
1087
1183
|
const currentStep = steps.filter(isStepVisible).find((s) => s.status === "running");
|
|
1088
1184
|
if (!currentStep) return null;
|
|
1089
|
-
return /* @__PURE__ */
|
|
1185
|
+
return /* @__PURE__ */ jsxs8(Text9, { color: COLORS.status.running, children: [
|
|
1090
1186
|
/* @__PURE__ */ jsx8(Spinner, { type: "dots" }),
|
|
1091
1187
|
" ",
|
|
1092
1188
|
` ${currentStep.title}`
|
|
@@ -1094,19 +1190,19 @@ function CurrentStep() {
|
|
|
1094
1190
|
}
|
|
1095
1191
|
|
|
1096
1192
|
// src/ui/Progress.tsx
|
|
1097
|
-
import { Box as
|
|
1098
|
-
import { jsx as jsx9, jsxs as
|
|
1193
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1194
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1099
1195
|
function Progress() {
|
|
1100
1196
|
const { steps, currentStepIndex } = useWizard();
|
|
1101
1197
|
const visibleSteps = steps.filter(isStepVisible);
|
|
1102
1198
|
if (visibleSteps.length === 0) return null;
|
|
1103
1199
|
const visibleCountThroughCurrent = steps.slice(0, currentStepIndex + 1).filter(isStepVisible).length;
|
|
1104
1200
|
const activeStepNumber = Math.max(1, visibleCountThroughCurrent);
|
|
1105
|
-
return /* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */ jsx9(
|
|
1107
|
-
/* @__PURE__ */ jsx9(
|
|
1108
|
-
/* @__PURE__ */ jsx9(
|
|
1109
|
-
/* @__PURE__ */ jsx9(
|
|
1201
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
|
|
1202
|
+
/* @__PURE__ */ jsx9(Text10, { color: COLORS.muted, children: "STEP" }),
|
|
1203
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: activeStepNumber }),
|
|
1204
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "/" }),
|
|
1205
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: visibleSteps.length })
|
|
1110
1206
|
] });
|
|
1111
1207
|
}
|
|
1112
1208
|
|
|
@@ -1117,10 +1213,10 @@ var sidebarCommands = [
|
|
|
1117
1213
|
];
|
|
1118
1214
|
|
|
1119
1215
|
// src/ui/Sidebar.tsx
|
|
1120
|
-
import { jsx as jsx10, jsxs as
|
|
1216
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1121
1217
|
function Sidebar() {
|
|
1122
|
-
return /* @__PURE__ */
|
|
1123
|
-
|
|
1218
|
+
return /* @__PURE__ */ jsxs10(
|
|
1219
|
+
Box11,
|
|
1124
1220
|
{
|
|
1125
1221
|
backgroundColor: "#14171E",
|
|
1126
1222
|
width: 30,
|
|
@@ -1129,16 +1225,16 @@ function Sidebar() {
|
|
|
1129
1225
|
flexDirection: "column",
|
|
1130
1226
|
justifyContent: "space-between",
|
|
1131
1227
|
children: [
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */ jsx10(
|
|
1228
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1229
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: "PROGRESS" }),
|
|
1134
1230
|
/* @__PURE__ */ jsx10(Steps, {})
|
|
1135
1231
|
] }),
|
|
1136
|
-
/* @__PURE__ */
|
|
1232
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
|
|
1137
1233
|
/* @__PURE__ */ jsx10(Progress, {}),
|
|
1138
|
-
/* @__PURE__ */ jsx10(
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */ jsx10(
|
|
1141
|
-
/* @__PURE__ */ jsx10(
|
|
1234
|
+
/* @__PURE__ */ jsx10(Box11, { flexDirection: "column", children: sidebarCommands.map((c) => {
|
|
1235
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
|
|
1236
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.primary, children: `[${c.keyHint}]` }),
|
|
1237
|
+
/* @__PURE__ */ jsx10(Text11, { color: COLORS.muted, children: c.description })
|
|
1142
1238
|
] });
|
|
1143
1239
|
}) })
|
|
1144
1240
|
] })
|
|
@@ -1148,12 +1244,12 @@ function Sidebar() {
|
|
|
1148
1244
|
}
|
|
1149
1245
|
|
|
1150
1246
|
// src/ui/Ribbon.tsx
|
|
1151
|
-
import { Box as
|
|
1152
|
-
import { jsx as jsx11, jsxs as
|
|
1247
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1248
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1153
1249
|
function Ribbon() {
|
|
1154
1250
|
const firstCommand = sidebarCommands[0];
|
|
1155
|
-
return /* @__PURE__ */
|
|
1156
|
-
|
|
1251
|
+
return /* @__PURE__ */ jsxs11(
|
|
1252
|
+
Box12,
|
|
1157
1253
|
{
|
|
1158
1254
|
backgroundColor: "#14171E",
|
|
1159
1255
|
flexDirection: "row",
|
|
@@ -1163,9 +1259,9 @@ function Ribbon() {
|
|
|
1163
1259
|
children: [
|
|
1164
1260
|
/* @__PURE__ */ jsx11(Progress, {}),
|
|
1165
1261
|
/* @__PURE__ */ jsx11(CurrentStep, {}),
|
|
1166
|
-
/* @__PURE__ */
|
|
1167
|
-
/* @__PURE__ */ jsx11(
|
|
1168
|
-
/* @__PURE__ */ jsx11(
|
|
1262
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 1, children: [
|
|
1263
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
|
|
1264
|
+
/* @__PURE__ */ jsx11(Text12, { color: COLORS.muted, children: firstCommand.description })
|
|
1169
1265
|
] })
|
|
1170
1266
|
]
|
|
1171
1267
|
}
|
|
@@ -1176,9 +1272,8 @@ function Ribbon() {
|
|
|
1176
1272
|
import { useState as useState6 } from "react";
|
|
1177
1273
|
|
|
1178
1274
|
// src/ui/Logs.tsx
|
|
1179
|
-
import {
|
|
1180
|
-
import {
|
|
1181
|
-
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1275
|
+
import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
|
|
1276
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1182
1277
|
var KIND_COLOR = {
|
|
1183
1278
|
tool: COLORS.primary,
|
|
1184
1279
|
prompt: COLORS.badge
|
|
@@ -1208,75 +1303,32 @@ function formatTimestamp(ms) {
|
|
|
1208
1303
|
}
|
|
1209
1304
|
function Logs() {
|
|
1210
1305
|
const logs = useWizard((s) => s.logs);
|
|
1211
|
-
const {
|
|
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]);
|
|
1306
|
+
const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
|
|
1239
1307
|
useInput5((_input, key) => {
|
|
1240
|
-
if (
|
|
1241
|
-
|
|
1242
|
-
(o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
|
|
1243
|
-
);
|
|
1308
|
+
if (key.upArrow) scroll.scrollBy(-1);
|
|
1309
|
+
else if (key.downArrow) scroll.scrollBy(1);
|
|
1244
1310
|
});
|
|
1245
|
-
const visible = logs.slice(
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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" })
|
|
1311
|
+
const visible = logs.slice(scroll.offset, scroll.offset + scroll.capacity);
|
|
1312
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
|
|
1313
|
+
logs.length === 0 && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "No logs yet." }),
|
|
1314
|
+
/* @__PURE__ */ jsx12(ScrollView, { scroll, children: visible.map((entry) => {
|
|
1315
|
+
const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
|
|
1316
|
+
const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
|
|
1317
|
+
const rawPreview = rawInputText(entry.input);
|
|
1318
|
+
const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
|
|
1319
|
+
const gaps = (partCount - 1) * ROW_GAP;
|
|
1320
|
+
let budget = scroll.width - timestamp.length - durationText.length - gaps;
|
|
1321
|
+
const name = truncate2(entry.name, budget);
|
|
1322
|
+
budget -= name.length;
|
|
1323
|
+
const preview = rawPreview ? truncate2(rawPreview, budget) : "";
|
|
1324
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: ROW_GAP, children: [
|
|
1325
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: timestamp }),
|
|
1326
|
+
/* @__PURE__ */ jsx12(Text13, { color: logNameColor(entry), wrap: "truncate", children: name }),
|
|
1327
|
+
preview && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, wrap: "truncate", children: preview }),
|
|
1328
|
+
durationText && /* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: durationText })
|
|
1329
|
+
] }, entry.id);
|
|
1330
|
+
}) }),
|
|
1331
|
+
/* @__PURE__ */ jsx12(Text13, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
|
|
1280
1332
|
] });
|
|
1281
1333
|
}
|
|
1282
1334
|
|
|
@@ -1468,7 +1520,7 @@ function track(event, payload) {
|
|
|
1468
1520
|
}
|
|
1469
1521
|
|
|
1470
1522
|
// src/ui/App.tsx
|
|
1471
|
-
import { jsx as jsx13, jsxs as
|
|
1523
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1472
1524
|
function App() {
|
|
1473
1525
|
const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
|
|
1474
1526
|
const { exit } = useApp();
|
|
@@ -1511,36 +1563,44 @@ function App() {
|
|
|
1511
1563
|
const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
|
|
1512
1564
|
const flexDirection = columns > 90 ? "row" : "column";
|
|
1513
1565
|
const showSidebar = flexDirection === "row";
|
|
1514
|
-
return /* @__PURE__ */
|
|
1515
|
-
|
|
1566
|
+
return /* @__PURE__ */ jsxs13(
|
|
1567
|
+
Box14,
|
|
1516
1568
|
{
|
|
1517
1569
|
backgroundColor: COLORS.bg.main,
|
|
1518
1570
|
flexDirection: "row",
|
|
1519
1571
|
width: columns,
|
|
1520
1572
|
minHeight: rows,
|
|
1521
1573
|
children: [
|
|
1522
|
-
mainWindowVisible &&
|
|
1523
|
-
|
|
1574
|
+
mainWindowVisible && // Ink sizes the root by width only, so without a cap the scrolling
|
|
1575
|
+
// lists in here grow to their content instead of windowing (see
|
|
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,
|
|
1524
1580
|
{
|
|
1525
1581
|
flexDirection,
|
|
1526
1582
|
width: "100%",
|
|
1583
|
+
maxHeight: rows,
|
|
1527
1584
|
justifyContent: "space-between",
|
|
1528
1585
|
children: [
|
|
1529
1586
|
showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : (
|
|
1530
|
-
/* Fill the
|
|
1531
|
-
|
|
1532
|
-
|
|
1587
|
+
/* Fill the space the sidebar/ribbon leaves — width beside the
|
|
1588
|
+
sidebar, height above the ribbon. The height matters even
|
|
1589
|
+
stacked: it is what the prompt's scrolling list measures itself
|
|
1590
|
+
against (see SelectPrompt). */
|
|
1591
|
+
/* @__PURE__ */ jsxs13(
|
|
1592
|
+
Box14,
|
|
1533
1593
|
{
|
|
1534
1594
|
flexDirection: "column",
|
|
1535
1595
|
paddingX: 4,
|
|
1536
1596
|
paddingY: 2,
|
|
1537
1597
|
width: showSidebar ? 70 : "100%",
|
|
1538
|
-
flexGrow:
|
|
1598
|
+
flexGrow: 1,
|
|
1539
1599
|
children: [
|
|
1540
1600
|
/* @__PURE__ */ jsx13(Notices, {}),
|
|
1541
1601
|
/* @__PURE__ */ jsx13(PromptInput, {}),
|
|
1542
|
-
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(
|
|
1543
|
-
phase === "error" && error && /* @__PURE__ */ jsx13(
|
|
1602
|
+
phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
|
|
1603
|
+
phase === "error" && error && /* @__PURE__ */ jsx13(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: COLORS.status.error, children: [
|
|
1544
1604
|
"\u2716 ",
|
|
1545
1605
|
error
|
|
1546
1606
|
] }) })
|
|
@@ -2161,745 +2221,15 @@ function writeCredentialsTool(ctx) {
|
|
|
2161
2221
|
// src/lib/tools/searchFiles.ts
|
|
2162
2222
|
import { tool as tool7 } from "ai";
|
|
2163
2223
|
import z10 from "zod";
|
|
2164
|
-
import { readdir as
|
|
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";
|
|
2224
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
2175
2225
|
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 SWIFT_PACKAGE_DIR = `${INGEST_DIR}/Ingest`;
|
|
2210
|
-
var CSHARP_PROJECT = `${INGEST_DIR}/ingest/ingest.csproj`;
|
|
2211
|
-
var VISIBLE_INGEST_DIR = "algolia-wizard";
|
|
2212
|
-
var PY_VENV = `${INGEST_DIR}/.venv`;
|
|
2213
|
-
var PY_VENV_PYTHON = `${PY_VENV}/bin/python`;
|
|
2214
|
-
var PY_REQUIREMENTS = `${INGEST_DIR}/requirements.txt`;
|
|
2215
|
-
var LANGUAGE_PROFILES = {
|
|
2216
|
-
javascript: {
|
|
2217
|
-
id: "javascript",
|
|
2218
|
-
displayName: "JavaScript/TypeScript",
|
|
2219
|
-
aliases: [
|
|
2220
|
-
"javascript",
|
|
2221
|
-
"js",
|
|
2222
|
-
"typescript",
|
|
2223
|
-
"ts",
|
|
2224
|
-
"node",
|
|
2225
|
-
"nodejs",
|
|
2226
|
-
"node.js",
|
|
2227
|
-
"bun",
|
|
2228
|
-
"deno",
|
|
2229
|
-
"ecmascript",
|
|
2230
|
-
"jsx",
|
|
2231
|
-
"tsx"
|
|
2232
|
-
],
|
|
2233
|
-
manifests: ["package.json"],
|
|
2234
|
-
// The concrete npm-family manager is resolved by detectPackageManager (it
|
|
2235
|
-
// honours the package.json `packageManager` field, which lockfiles can't
|
|
2236
|
-
// express), so one spec covers all four and `resolveToolchain` rewrites the
|
|
2237
|
-
// binary below.
|
|
2238
|
-
packageManagers: [
|
|
2239
|
-
{
|
|
2240
|
-
id: "npm",
|
|
2241
|
-
dependency: { mode: "agent-declares", file: "package.json" },
|
|
2242
|
-
installSteps: [{ argv: ["npm", "install"] }],
|
|
2243
|
-
ingest: {
|
|
2244
|
-
kind: "auto",
|
|
2245
|
-
argv: ["node", ENTRYPOINT_TOKEN],
|
|
2246
|
-
entrypointExtensions: [".mjs", ".cjs", ".js"]
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
],
|
|
2250
|
-
sdk: { packageName: "algoliasearch", versionPin: "^5", docKey: "js" },
|
|
2251
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.mjs`,
|
|
2252
|
-
// package.json scripts are repo-defined, so they're resolved at run time by
|
|
2253
|
-
// repoVerification rather than listed here.
|
|
2254
|
-
verification: [],
|
|
2255
|
-
envReadInstruction: "Read them from `process.env`.",
|
|
2256
|
-
skipDirs: ["node_modules", "dist", "build", "coverage", ".next", "out"]
|
|
2257
|
-
},
|
|
2258
|
-
python: {
|
|
2259
|
-
id: "python",
|
|
2260
|
-
displayName: "Python",
|
|
2261
|
-
aliases: ["python", "python3", "py", "cpython"],
|
|
2262
|
-
manifests: [
|
|
2263
|
-
"pyproject.toml",
|
|
2264
|
-
"requirements.txt",
|
|
2265
|
-
"setup.py",
|
|
2266
|
-
"setup.cfg",
|
|
2267
|
-
"Pipfile"
|
|
2268
|
-
],
|
|
2269
|
-
// Deliberately one path for every Python repo: a wizard-owned venv under
|
|
2270
|
-
// .algolia-wizard. Reusing the project's uv/poetry environment would mean
|
|
2271
|
-
// mutating the developer's real dependency manifest and lockfile, and the
|
|
2272
|
-
// declare-here/install-there split is the main way ingestion silently ends
|
|
2273
|
-
// up without the SDK installed. The tradeoff: the script can import the
|
|
2274
|
-
// Algolia client and anything it declares itself, but not the project's own
|
|
2275
|
-
// packages (see the optional root-requirements step below).
|
|
2276
|
-
packageManagers: [
|
|
2277
|
-
{
|
|
2278
|
-
id: "pip-venv",
|
|
2279
|
-
dependency: { mode: "agent-declares", file: PY_REQUIREMENTS },
|
|
2280
|
-
installSteps: [
|
|
2281
|
-
{ argv: ["python3", "-m", "venv", PY_VENV] },
|
|
2282
|
-
{
|
|
2283
|
-
argv: [PY_VENV_PYTHON, "-m", "pip", "install", "-r", PY_REQUIREMENTS]
|
|
2284
|
-
},
|
|
2285
|
-
// Best-effort access to the project's own dependencies (DB drivers,
|
|
2286
|
-
// ORMs) when the repo pins them the classic way.
|
|
2287
|
-
{
|
|
2288
|
-
argv: [
|
|
2289
|
-
PY_VENV_PYTHON,
|
|
2290
|
-
"-m",
|
|
2291
|
-
"pip",
|
|
2292
|
-
"install",
|
|
2293
|
-
"-r",
|
|
2294
|
-
"requirements.txt"
|
|
2295
|
-
],
|
|
2296
|
-
requiresFile: "requirements.txt",
|
|
2297
|
-
optional: true
|
|
2298
|
-
}
|
|
2299
|
-
],
|
|
2300
|
-
ingest: {
|
|
2301
|
-
kind: "auto",
|
|
2302
|
-
argv: [PY_VENV_PYTHON, ENTRYPOINT_TOKEN],
|
|
2303
|
-
entrypointExtensions: [".py"]
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
],
|
|
2307
|
-
sdk: {
|
|
2308
|
-
packageName: "algoliasearch",
|
|
2309
|
-
versionPin: ">=4,<5",
|
|
2310
|
-
docKey: "python"
|
|
2311
|
-
},
|
|
2312
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.py`,
|
|
2313
|
-
localSourceCaveat: {
|
|
2314
|
-
unless: "requirements.txt",
|
|
2315
|
-
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."
|
|
2316
|
-
},
|
|
2317
|
-
verification: [
|
|
2318
|
-
{
|
|
2319
|
-
// -x skips the venv this same directory holds; without it the check
|
|
2320
|
-
// compiles every installed package instead of the generated script.
|
|
2321
|
-
label: "python compileall",
|
|
2322
|
-
argv: ["python3", "-m", "compileall", "-q", "-x", "[.]venv", INGEST_DIR],
|
|
2323
|
-
requiresFile: INGEST_DIR
|
|
2324
|
-
}
|
|
2325
|
-
],
|
|
2326
|
-
envReadInstruction: "Read them from `os.environ`.",
|
|
2327
|
-
skipDirs: [
|
|
2328
|
-
"venv",
|
|
2329
|
-
"__pycache__",
|
|
2330
|
-
"site-packages",
|
|
2331
|
-
"dist",
|
|
2332
|
-
"build",
|
|
2333
|
-
"htmlcov"
|
|
2334
|
-
]
|
|
2335
|
-
},
|
|
2336
|
-
ruby: {
|
|
2337
|
-
id: "ruby",
|
|
2338
|
-
displayName: "Ruby",
|
|
2339
|
-
aliases: ["ruby", "rb", "rails", "ruby on rails", "rubyonrails"],
|
|
2340
|
-
manifests: ["Gemfile", "*.gemspec"],
|
|
2341
|
-
packageManagers: [
|
|
2342
|
-
{
|
|
2343
|
-
id: "bundler",
|
|
2344
|
-
dependency: { mode: "agent-declares", file: "Gemfile" },
|
|
2345
|
-
installSteps: [{ argv: ["bundle", "install"] }],
|
|
2346
|
-
ingest: {
|
|
2347
|
-
kind: "auto",
|
|
2348
|
-
argv: ["bundle", "exec", "ruby", ENTRYPOINT_TOKEN],
|
|
2349
|
-
entrypointExtensions: [".rb"]
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
],
|
|
2353
|
-
sdk: { packageName: "algolia", versionPin: "~> 3.0", docKey: "ruby" },
|
|
2354
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.rb`,
|
|
2355
|
-
// Ruby has no directory-level syntax check (`ruby -c` is one file at a
|
|
2356
|
-
// time), so verification relies on the agent's own review here.
|
|
2357
|
-
verification: [],
|
|
2358
|
-
envReadInstruction: "Read them from `ENV.fetch('NAME')`.",
|
|
2359
|
-
skipDirs: ["vendor", "tmp", "log", "coverage"]
|
|
2360
|
-
},
|
|
2361
|
-
php: {
|
|
2362
|
-
id: "php",
|
|
2363
|
-
displayName: "PHP",
|
|
2364
|
-
aliases: ["php", "laravel", "symfony"],
|
|
2365
|
-
manifests: ["composer.json"],
|
|
2366
|
-
packageManagers: [
|
|
2367
|
-
{
|
|
2368
|
-
id: "composer",
|
|
2369
|
-
// `composer require` both declares and installs, and unlike editing
|
|
2370
|
-
// composer.json by hand it can't leave composer.lock out of date (which
|
|
2371
|
-
// makes a later `composer install` refuse to run).
|
|
2372
|
-
dependency: { mode: "wizard-installs" },
|
|
2373
|
-
installSteps: [
|
|
2374
|
-
{
|
|
2375
|
-
argv: [
|
|
2376
|
-
"composer",
|
|
2377
|
-
"require",
|
|
2378
|
-
"algolia/algoliasearch-client-php:^4",
|
|
2379
|
-
"--no-interaction",
|
|
2380
|
-
// Repo post-install scripts are the project's code, not ours to
|
|
2381
|
-
// trigger; Laravel's package:discover also fails in a bare tree.
|
|
2382
|
-
"--no-scripts"
|
|
2383
|
-
]
|
|
2384
|
-
}
|
|
2385
|
-
],
|
|
2386
|
-
ingest: {
|
|
2387
|
-
kind: "auto",
|
|
2388
|
-
argv: ["php", ENTRYPOINT_TOKEN],
|
|
2389
|
-
entrypointExtensions: [".php"]
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
],
|
|
2393
|
-
sdk: {
|
|
2394
|
-
packageName: "algolia/algoliasearch-client-php",
|
|
2395
|
-
versionPin: "^4",
|
|
2396
|
-
docKey: "php"
|
|
2397
|
-
},
|
|
2398
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.php`,
|
|
2399
|
-
verification: [],
|
|
2400
|
-
envReadInstruction: "Read them from `getenv('NAME')`.",
|
|
2401
|
-
skipDirs: ["vendor", "node_modules"]
|
|
2402
|
-
},
|
|
2403
|
-
go: {
|
|
2404
|
-
id: "go",
|
|
2405
|
-
displayName: "Go",
|
|
2406
|
-
aliases: ["go", "golang"],
|
|
2407
|
-
manifests: ["go.mod"],
|
|
2408
|
-
packageManagers: [
|
|
2409
|
-
{
|
|
2410
|
-
id: "gomod",
|
|
2411
|
-
// Imports in the generated file are the declaration; `go mod tidy`
|
|
2412
|
-
// resolves and fetches them — which only works because the script lives
|
|
2413
|
-
// outside INGEST_DIR (see VISIBLE_INGEST_DIR).
|
|
2414
|
-
dependency: { mode: "code-imports" },
|
|
2415
|
-
installSteps: [{ argv: ["go", "mod", "tidy"] }],
|
|
2416
|
-
ingest: {
|
|
2417
|
-
kind: "auto",
|
|
2418
|
-
argv: ["go", "run", ENTRYPOINT_TOKEN],
|
|
2419
|
-
entrypointExtensions: [".go"]
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
],
|
|
2423
|
-
sdk: {
|
|
2424
|
-
packageName: "github.com/algolia/algoliasearch-client-go/v4",
|
|
2425
|
-
versionPin: "v4",
|
|
2426
|
-
docKey: "go"
|
|
2427
|
-
},
|
|
2428
|
-
ingestEntrypointExample: `${VISIBLE_INGEST_DIR}/ingest.go`,
|
|
2429
|
-
verification: [
|
|
2430
|
-
{ label: "go vet", argv: ["go", "vet", "./..."], requiresFile: "go.mod" }
|
|
2431
|
-
],
|
|
2432
|
-
envReadInstruction: "Read them from `os.Getenv`.",
|
|
2433
|
-
skipDirs: ["vendor", "bin"]
|
|
2434
|
-
},
|
|
2435
|
-
java: {
|
|
2436
|
-
id: "java",
|
|
2437
|
-
displayName: "Java",
|
|
2438
|
-
aliases: ["java"],
|
|
2439
|
-
manifests: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
2440
|
-
packageManagers: [
|
|
2441
|
-
{
|
|
2442
|
-
id: "maven",
|
|
2443
|
-
detectFiles: ["pom.xml"],
|
|
2444
|
-
sdkVersionPin: "[4,5)",
|
|
2445
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2446
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2447
|
-
// The main class is a wizard constant the instructions require the agent
|
|
2448
|
-
// to use, so execution can't be redirected by agent output. Runnable only
|
|
2449
|
-
// because the install step above compiles src/main/java first — which is
|
|
2450
|
-
// why the entrypoint lives there rather than under .algolia-wizard/.
|
|
2451
|
-
ingest: {
|
|
2452
|
-
kind: "auto",
|
|
2453
|
-
argv: [
|
|
2454
|
-
"mvn",
|
|
2455
|
-
"-q",
|
|
2456
|
-
"org.codehaus.mojo:exec-maven-plugin:3.5.0:java",
|
|
2457
|
-
"-Dexec.mainClass=AlgoliaWizardIngest"
|
|
2458
|
-
],
|
|
2459
|
-
entrypointExtensions: [".java"]
|
|
2460
|
-
}
|
|
2461
|
-
},
|
|
2462
|
-
{
|
|
2463
|
-
id: "gradle",
|
|
2464
|
-
detectFiles: ["build.gradle", "build.gradle.kts"],
|
|
2465
|
-
dependency: {
|
|
2466
|
-
mode: "agent-declares",
|
|
2467
|
-
file: "build.gradle",
|
|
2468
|
-
alternatives: ["build.gradle.kts"]
|
|
2469
|
-
},
|
|
2470
|
-
installSteps: [],
|
|
2471
|
-
// Auto-running means executing the repo's own ./gradlew wrapper; out of
|
|
2472
|
-
// scope for now, so the wizard writes the code and prints the command.
|
|
2473
|
-
ingest: {
|
|
2474
|
-
kind: "manual",
|
|
2475
|
-
entrypointExtensions: [".java"],
|
|
2476
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2477
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
],
|
|
2481
|
-
sdk: {
|
|
2482
|
-
packageName: "com.algolia:algoliasearch",
|
|
2483
|
-
versionPin: "4.+",
|
|
2484
|
-
docKey: "java",
|
|
2485
|
-
alsoRequires: "The class must be named AlgoliaWizardIngest, in the default package (no `package` statement), with a `public static void main`."
|
|
2486
|
-
},
|
|
2487
|
-
// Not under .algolia-wizard/: Maven and Gradle only compile src/main/<lang>,
|
|
2488
|
-
// so a class outside it never makes it onto the classpath and the run command
|
|
2489
|
-
// fails with "class not found".
|
|
2490
|
-
ingestEntrypointExample: "src/main/java/AlgoliaWizardIngest.java",
|
|
2491
|
-
verification: [
|
|
2492
|
-
{
|
|
2493
|
-
label: "mvn compile",
|
|
2494
|
-
argv: ["mvn", "-q", "-DskipTests", "compile"],
|
|
2495
|
-
requiresFile: "pom.xml"
|
|
2496
|
-
}
|
|
2497
|
-
],
|
|
2498
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2499
|
-
skipDirs: ["target", "build", "out"]
|
|
2500
|
-
},
|
|
2501
|
-
kotlin: {
|
|
2502
|
-
id: "kotlin",
|
|
2503
|
-
displayName: "Kotlin",
|
|
2504
|
-
aliases: ["kotlin", "kt", "ktor"],
|
|
2505
|
-
manifests: ["build.gradle.kts", "build.gradle", "pom.xml"],
|
|
2506
|
-
packageManagers: [
|
|
2507
|
-
{
|
|
2508
|
-
id: "gradle",
|
|
2509
|
-
detectFiles: ["build.gradle.kts", "build.gradle"],
|
|
2510
|
-
dependency: {
|
|
2511
|
-
mode: "agent-declares",
|
|
2512
|
-
file: "build.gradle.kts",
|
|
2513
|
-
alternatives: ["build.gradle"]
|
|
2514
|
-
},
|
|
2515
|
-
installSteps: [],
|
|
2516
|
-
ingest: {
|
|
2517
|
-
kind: "manual",
|
|
2518
|
-
entrypointExtensions: [".kt"],
|
|
2519
|
-
runCommand: "./gradlew runAlgoliaIngest",
|
|
2520
|
-
requiresBuildTask: "runAlgoliaIngest"
|
|
2521
|
-
}
|
|
2522
|
-
},
|
|
2523
|
-
// Kotlin/Maven is rare but real, and pom.xml is a Kotlin manifest — without
|
|
2524
|
-
// this spec such a repo falls through to Gradle and is told to run a
|
|
2525
|
-
// ./gradlew task that doesn't exist. Compiling needs the repo's own
|
|
2526
|
-
// kotlin-maven-plugin, so the run stays the developer's step.
|
|
2527
|
-
{
|
|
2528
|
-
id: "maven",
|
|
2529
|
-
detectFiles: ["pom.xml"],
|
|
2530
|
-
sdkVersionPin: "[3,4)",
|
|
2531
|
-
dependency: { mode: "agent-declares", file: "pom.xml" },
|
|
2532
|
-
installSteps: [{ argv: ["mvn", "-q", "-DskipTests", "compile"] }],
|
|
2533
|
-
ingest: {
|
|
2534
|
-
kind: "manual",
|
|
2535
|
-
entrypointExtensions: [".kt"],
|
|
2536
|
-
runCommand: "mvn -q org.codehaus.mojo:exec-maven-plugin:3.5.0:java -Dexec.mainClass=AlgoliaWizardIngest"
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
],
|
|
2540
|
-
sdk: {
|
|
2541
|
-
packageName: "com.algolia:algoliasearch-client-kotlin",
|
|
2542
|
-
versionPin: "3.+",
|
|
2543
|
-
docKey: "kotlin",
|
|
2544
|
-
// The published client's commonMain ships only ktor-client-core; without an
|
|
2545
|
-
// engine the script compiles and then fails at its first request.
|
|
2546
|
-
alsoRequires: "The Kotlin client bundles no HTTP engine, so also declare one (e.g. io.ktor:ktor-client-okhttp). Name the object AlgoliaWizardIngest in the default package, with a @JvmStatic main."
|
|
2547
|
-
},
|
|
2548
|
-
ingestEntrypointExample: "src/main/kotlin/AlgoliaWizardIngest.kt",
|
|
2549
|
-
verification: [],
|
|
2550
|
-
envReadInstruction: "Read them from `System.getenv`.",
|
|
2551
|
-
skipDirs: ["build", "out"]
|
|
2552
|
-
},
|
|
2553
|
-
scala: {
|
|
2554
|
-
id: "scala",
|
|
2555
|
-
displayName: "Scala",
|
|
2556
|
-
aliases: ["scala", "sbt"],
|
|
2557
|
-
manifests: ["build.sbt", "build.sc"],
|
|
2558
|
-
packageManagers: [
|
|
2559
|
-
{
|
|
2560
|
-
id: "sbt",
|
|
2561
|
-
dependency: { mode: "agent-declares", file: "build.sbt" },
|
|
2562
|
-
installSteps: [],
|
|
2563
|
-
ingest: {
|
|
2564
|
-
kind: "manual",
|
|
2565
|
-
entrypointExtensions: [".scala"],
|
|
2566
|
-
runCommand: 'sbt "runMain AlgoliaWizardIngest"'
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
],
|
|
2570
|
-
sdk: {
|
|
2571
|
-
packageName: "com.algolia:algoliasearch-scala_2.13",
|
|
2572
|
-
versionPin: "2.+",
|
|
2573
|
-
docKey: "scala",
|
|
2574
|
-
alsoRequires: "Name the object AlgoliaWizardIngest in the default package (no `package` statement) so `runMain AlgoliaWizardIngest` resolves it."
|
|
2575
|
-
},
|
|
2576
|
-
ingestEntrypointExample: "src/main/scala/AlgoliaWizardIngest.scala",
|
|
2577
|
-
verification: [],
|
|
2578
|
-
envReadInstruction: "Read them from `sys.env`.",
|
|
2579
|
-
// `project/` holds sbt's build definition, but the name is generic enough
|
|
2580
|
-
// that some repos use it for source; scanning it is cheap, missing source
|
|
2581
|
-
// is not.
|
|
2582
|
-
skipDirs: ["target"]
|
|
2583
|
-
},
|
|
2584
|
-
csharp: {
|
|
2585
|
-
id: "csharp",
|
|
2586
|
-
displayName: "C#",
|
|
2587
|
-
aliases: ["c#", "csharp", "cs", ".net", "dotnet", "net", "asp.net"],
|
|
2588
|
-
manifests: ["*.csproj", "*.sln", "global.json"],
|
|
2589
|
-
packageManagers: [
|
|
2590
|
-
{
|
|
2591
|
-
id: "dotnet",
|
|
2592
|
-
// A self-contained project under .algolia-wizard keeps the ingest script
|
|
2593
|
-
// out of the repo's own build graph.
|
|
2594
|
-
dependency: { mode: "agent-declares", file: CSHARP_PROJECT },
|
|
2595
|
-
installSteps: [{ argv: ["dotnet", "restore", CSHARP_PROJECT] }],
|
|
2596
|
-
ingest: {
|
|
2597
|
-
kind: "auto",
|
|
2598
|
-
argv: ["dotnet", "run", "--project", ENTRYPOINT_TOKEN],
|
|
2599
|
-
entrypointExtensions: [".csproj"]
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
],
|
|
2603
|
-
sdk: {
|
|
2604
|
-
packageName: "Algolia.Search",
|
|
2605
|
-
versionPin: "7.*",
|
|
2606
|
-
docKey: "csharp"
|
|
2607
|
-
},
|
|
2608
|
-
ingestEntrypointExample: CSHARP_PROJECT,
|
|
2609
|
-
verification: [
|
|
2610
|
-
{
|
|
2611
|
-
label: "dotnet build",
|
|
2612
|
-
argv: ["dotnet", "build", CSHARP_PROJECT, "--nologo"],
|
|
2613
|
-
requiresFile: CSHARP_PROJECT
|
|
2614
|
-
}
|
|
2615
|
-
],
|
|
2616
|
-
envReadInstruction: 'Read them from `Environment.GetEnvironmentVariable("NAME")`.',
|
|
2617
|
-
// Deliberately not `packages`: modern .NET uses PackageReference, and
|
|
2618
|
-
// `packages/` is where pnpm/Lerna/Turborepo monorepos keep all their source —
|
|
2619
|
-
// skipping it would hide the entities the scan is looking for.
|
|
2620
|
-
skipDirs: ["bin", "obj"]
|
|
2621
|
-
},
|
|
2622
|
-
swift: {
|
|
2623
|
-
id: "swift",
|
|
2624
|
-
displayName: "Swift",
|
|
2625
|
-
aliases: ["swift", "swiftui", "ios", "vapor"],
|
|
2626
|
-
manifests: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
|
|
2627
|
-
packageManagers: [
|
|
2628
|
-
{
|
|
2629
|
-
id: "swiftpm",
|
|
2630
|
-
dependency: {
|
|
2631
|
-
mode: "agent-declares",
|
|
2632
|
-
file: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2633
|
-
},
|
|
2634
|
-
// `swift build` resolves and fetches; a cold build of the client is slow
|
|
2635
|
-
// (minutes), which is why the caller degrades to the manual command when
|
|
2636
|
-
// this fails.
|
|
2637
|
-
installSteps: [
|
|
2638
|
-
{
|
|
2639
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2640
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2641
|
-
}
|
|
2642
|
-
],
|
|
2643
|
-
ingest: {
|
|
2644
|
-
kind: "auto",
|
|
2645
|
-
argv: ["swift", "run", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2646
|
-
entrypointExtensions: [".swift"]
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
],
|
|
2650
|
-
sdk: {
|
|
2651
|
-
packageName: "algoliasearch-client-swift",
|
|
2652
|
-
// SwiftPM range syntax, not an exact version — a bare "9.0.0" in a
|
|
2653
|
-
// Package.swift dependency pins the patch.
|
|
2654
|
-
versionPin: 'from: "9.0.0"',
|
|
2655
|
-
docKey: "swift"
|
|
2656
|
-
},
|
|
2657
|
-
ingestEntrypointExample: `${SWIFT_PACKAGE_DIR}/Sources/Ingest/main.swift`,
|
|
2658
|
-
verification: [
|
|
2659
|
-
{
|
|
2660
|
-
label: "swift build",
|
|
2661
|
-
argv: ["swift", "build", "--package-path", SWIFT_PACKAGE_DIR],
|
|
2662
|
-
requiresFile: `${SWIFT_PACKAGE_DIR}/Package.swift`
|
|
2663
|
-
}
|
|
2664
|
-
],
|
|
2665
|
-
envReadInstruction: "Read them from `ProcessInfo.processInfo.environment`.",
|
|
2666
|
-
skipDirs: ["Pods", "DerivedData", "Carthage", ".build"]
|
|
2667
|
-
},
|
|
2668
|
-
dart: {
|
|
2669
|
-
id: "dart",
|
|
2670
|
-
displayName: "Dart",
|
|
2671
|
-
aliases: ["dart", "flutter"],
|
|
2672
|
-
manifests: ["pubspec.yaml"],
|
|
2673
|
-
packageManagers: [
|
|
2674
|
-
{
|
|
2675
|
-
id: "flutter-pub",
|
|
2676
|
-
detectFiles: [".metadata"],
|
|
2677
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2678
|
-
installSteps: [{ argv: ["flutter", "pub", "get"] }],
|
|
2679
|
-
ingest: {
|
|
2680
|
-
kind: "auto",
|
|
2681
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2682
|
-
entrypointExtensions: [".dart"]
|
|
2683
|
-
}
|
|
2684
|
-
},
|
|
2685
|
-
{
|
|
2686
|
-
id: "pub",
|
|
2687
|
-
dependency: { mode: "agent-declares", file: "pubspec.yaml" },
|
|
2688
|
-
installSteps: [{ argv: ["dart", "pub", "get"] }],
|
|
2689
|
-
ingest: {
|
|
2690
|
-
kind: "auto",
|
|
2691
|
-
argv: ["dart", "run", ENTRYPOINT_TOKEN],
|
|
2692
|
-
entrypointExtensions: [".dart"]
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
],
|
|
2696
|
-
sdk: {
|
|
2697
|
-
packageName: "algolia_client_search",
|
|
2698
|
-
versionPin: "^1.0.0",
|
|
2699
|
-
docKey: "dart"
|
|
2700
|
-
},
|
|
2701
|
-
ingestEntrypointExample: `${INGEST_DIR}/ingest.dart`,
|
|
2702
|
-
verification: [
|
|
2703
|
-
{
|
|
2704
|
-
// Gated on the directory it analyzes, not just pubspec.yaml: a run that
|
|
2705
|
-
// only built a search UI never created it, and `dart analyze` on a
|
|
2706
|
-
// missing path fails the whole verification pass.
|
|
2707
|
-
label: "dart analyze",
|
|
2708
|
-
argv: ["dart", "analyze", INGEST_DIR],
|
|
2709
|
-
requiresFile: INGEST_DIR
|
|
2710
|
-
}
|
|
2711
|
-
],
|
|
2712
|
-
envReadInstruction: "Read them from `Platform.environment`.",
|
|
2713
|
-
skipDirs: ["build"]
|
|
2714
|
-
}
|
|
2715
|
-
};
|
|
2716
|
-
var DEFAULT_LANGUAGE_ID = "javascript";
|
|
2717
|
-
var JAVASCRIPT = "javascript";
|
|
2718
|
-
var CURATED_LANGUAGES = Object.values(
|
|
2719
|
-
LANGUAGE_PROFILES
|
|
2720
|
-
).map((profile) => profile.displayName);
|
|
2721
|
-
function isBackendLanguage(profile) {
|
|
2722
|
-
return profile.id !== JAVASCRIPT;
|
|
2723
|
-
}
|
|
2724
|
-
function normalizeLanguageName(name) {
|
|
2725
|
-
return name.toLowerCase().trim().replace(/[\s_-]+/g, "");
|
|
2726
|
-
}
|
|
2727
|
-
var ALIAS_TO_ID = /* @__PURE__ */ new Map();
|
|
2728
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2729
|
-
for (const alias of [profile.id, profile.displayName, ...profile.aliases]) {
|
|
2730
|
-
ALIAS_TO_ID.set(normalizeLanguageName(alias), profile.id);
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
function resolveLanguageProfile(name) {
|
|
2734
|
-
const id = ALIAS_TO_ID.get(normalizeLanguageName(name));
|
|
2735
|
-
return id ? LANGUAGE_PROFILES[id] : void 0;
|
|
2736
|
-
}
|
|
2737
|
-
function isSameLanguage(a, b) {
|
|
2738
|
-
const x = resolveLanguageProfile(a);
|
|
2739
|
-
const y = resolveLanguageProfile(b);
|
|
2740
|
-
if (x && y) return x.id === y.id;
|
|
2741
|
-
if (x || y) return false;
|
|
2742
|
-
const folded = normalizeLanguageName(a);
|
|
2743
|
-
return folded !== "" && folded === normalizeLanguageName(b);
|
|
2744
|
-
}
|
|
2745
|
-
var BASE_SKIP_DIRS = ["node_modules", ".git", "dist"];
|
|
2746
|
-
var ALL_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2747
|
-
...BASE_SKIP_DIRS,
|
|
2748
|
-
...Object.values(LANGUAGE_PROFILES).flatMap((p) => p.skipDirs)
|
|
2749
|
-
]);
|
|
2750
|
-
var ALLOWED_BINARIES = new Set(
|
|
2751
|
-
Object.values(LANGUAGE_PROFILES).flatMap((profile) => [
|
|
2752
|
-
...profile.packageManagers.flatMap((pm) => [
|
|
2753
|
-
...pm.installSteps.map((s) => s.argv[0]),
|
|
2754
|
-
...pm.ingest.kind === "auto" ? [pm.ingest.argv[0]] : []
|
|
2755
|
-
]),
|
|
2756
|
-
...profile.verification.map((v) => v.argv[0])
|
|
2757
|
-
])
|
|
2758
|
-
);
|
|
2759
|
-
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
2760
|
-
function isWorktreeRelativeCommand(command) {
|
|
2761
|
-
return command.includes("/");
|
|
2762
|
-
}
|
|
2763
|
-
function withCommand(argv, command) {
|
|
2764
|
-
return [command, ...argv.slice(1)];
|
|
2765
|
-
}
|
|
2766
|
-
function resolveDeclaredManifest(root, packageManager) {
|
|
2767
|
-
const { dependency } = packageManager;
|
|
2768
|
-
if (dependency.mode !== "agent-declares" || !dependency.alternatives?.length) {
|
|
2769
|
-
return packageManager;
|
|
2770
|
-
}
|
|
2771
|
-
const present = [dependency.file, ...dependency.alternatives].find(
|
|
2772
|
-
(file) => existsSync2(join9(root, file))
|
|
2773
|
-
);
|
|
2774
|
-
if (!present || present === dependency.file) return packageManager;
|
|
2775
|
-
return { ...packageManager, dependency: { ...dependency, file: present } };
|
|
2776
|
-
}
|
|
2777
|
-
async function manifestPresent(root, manifest, listing) {
|
|
2778
|
-
if (!manifest.startsWith("*.")) return existsSync2(join9(root, manifest));
|
|
2779
|
-
if (!listing.entries) {
|
|
2780
|
-
const entries = await readdir2(root).catch(() => []);
|
|
2781
|
-
listing.entries = Array.isArray(entries) ? entries : [];
|
|
2782
|
-
}
|
|
2783
|
-
const suffix = manifest.slice(1);
|
|
2784
|
-
return listing.entries.some((e) => e.endsWith(suffix));
|
|
2785
|
-
}
|
|
2786
|
-
async function profileManifestPresent(root, profile, listing) {
|
|
2787
|
-
for (const manifest of profile.manifests) {
|
|
2788
|
-
if (await manifestPresent(root, manifest, listing)) return true;
|
|
2789
|
-
}
|
|
2790
|
-
return false;
|
|
2791
|
-
}
|
|
2792
|
-
async function detectProfilesFromManifests(root) {
|
|
2793
|
-
const listing = {};
|
|
2794
|
-
const found = [];
|
|
2795
|
-
for (const profile of Object.values(LANGUAGE_PROFILES)) {
|
|
2796
|
-
if (await profileManifestPresent(root, profile, listing)) found.push(profile);
|
|
2797
|
-
}
|
|
2798
|
-
return found;
|
|
2799
|
-
}
|
|
2800
|
-
async function hasProfileManifest(root, profile) {
|
|
2801
|
-
return profileManifestPresent(root, profile, {});
|
|
2802
|
-
}
|
|
2803
|
-
async function pickIngestionCandidates(root, confirmedNames) {
|
|
2804
|
-
const confirmed3 = confirmedNames.map((name) => resolveLanguageProfile(name)).filter((p) => p !== void 0);
|
|
2805
|
-
const onDisk = await detectProfilesFromManifests(root);
|
|
2806
|
-
const onDiskIds = new Set(onDisk.map((p) => p.id));
|
|
2807
|
-
const candidates = [
|
|
2808
|
-
...new Map(
|
|
2809
|
-
confirmed3.filter((p) => onDiskIds.has(p.id)).map((p) => [p.id, p])
|
|
2810
|
-
).values()
|
|
2811
|
-
];
|
|
2812
|
-
return { candidates, confirmed: confirmed3, onDisk };
|
|
2813
|
-
}
|
|
2814
|
-
async function resolveToolchain(root, profile) {
|
|
2815
|
-
const signals = (pm) => [
|
|
2816
|
-
...pm.lockfiles ?? [],
|
|
2817
|
-
...pm.detectFiles ?? []
|
|
2818
|
-
];
|
|
2819
|
-
const matched = profile.packageManagers.find(
|
|
2820
|
-
(pm) => signals(pm).some((f) => existsSync2(join9(root, f)))
|
|
2821
|
-
);
|
|
2822
|
-
const fallback = profile.packageManagers.find((pm) => signals(pm).length === 0) ?? profile.packageManagers[0];
|
|
2823
|
-
const packageManager = resolveDeclaredManifest(root, matched ?? fallback);
|
|
2824
|
-
let { installSteps, ingest } = packageManager;
|
|
2825
|
-
installSteps = installSteps.map(
|
|
2826
|
-
(step) => isWorktreeRelativeCommand(step.argv[0]) ? { ...step, argv: withCommand(step.argv, join9(root, step.argv[0])) } : step
|
|
2827
|
-
);
|
|
2828
|
-
if (ingest.kind === "auto" && isWorktreeRelativeCommand(ingest.argv[0])) {
|
|
2829
|
-
ingest = {
|
|
2830
|
-
...ingest,
|
|
2831
|
-
argv: withCommand(ingest.argv, join9(root, ingest.argv[0]))
|
|
2832
|
-
};
|
|
2833
|
-
}
|
|
2834
|
-
if (profile.id === "javascript") {
|
|
2835
|
-
const pm = await detectPackageManager(root);
|
|
2836
|
-
if (JS_PACKAGE_MANAGERS.has(pm)) {
|
|
2837
|
-
installSteps = installSteps.map((step) => ({
|
|
2838
|
-
...step,
|
|
2839
|
-
argv: withCommand(step.argv, pm)
|
|
2840
|
-
}));
|
|
2841
|
-
if (pm === "bun" && ingest.kind === "auto") {
|
|
2842
|
-
ingest = { ...ingest, argv: withCommand(ingest.argv, "bun") };
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
return { profile, packageManager, installSteps, ingest };
|
|
2847
|
-
}
|
|
2848
|
-
function resolveIngestArgv(ingest, entrypoint) {
|
|
2849
|
-
if (ingest.kind !== "auto") {
|
|
2850
|
-
throw new Error("resolveIngestArgv called for a manual-run toolchain");
|
|
2851
|
-
}
|
|
2852
|
-
return ingest.argv.map(
|
|
2853
|
-
(part) => part === ENTRYPOINT_TOKEN ? entrypoint : part
|
|
2854
|
-
);
|
|
2855
|
-
}
|
|
2856
|
-
function describeIngestCommand(ingest, entrypoint) {
|
|
2857
|
-
if (ingest.kind !== "auto") return ingest.runCommand;
|
|
2858
|
-
return ingest.argv.map((part) => part === ENTRYPOINT_TOKEN ? shellQuote(entrypoint) : part).join(" ");
|
|
2859
|
-
}
|
|
2860
|
-
function ingestScriptDir(profile) {
|
|
2861
|
-
const parts = profile.ingestEntrypointExample.split("/");
|
|
2862
|
-
return parts.slice(0, -1).join("/") || ".";
|
|
2863
|
-
}
|
|
2864
|
-
function localSourceLimitation(root, profile) {
|
|
2865
|
-
const caveat = profile.localSourceCaveat;
|
|
2866
|
-
if (!caveat) return void 0;
|
|
2867
|
-
return existsSync2(join9(root, caveat.unless)) ? void 0 : caveat.message;
|
|
2868
|
-
}
|
|
2869
|
-
async function missingBuildTask(root, toolchain) {
|
|
2870
|
-
const { ingest, packageManager } = toolchain;
|
|
2871
|
-
if (ingest.kind !== "manual" || !ingest.requiresBuildTask) return void 0;
|
|
2872
|
-
if (packageManager.dependency.mode !== "agent-declares") return void 0;
|
|
2873
|
-
const buildFile = join9(root, packageManager.dependency.file);
|
|
2874
|
-
const contents = await readFile7(buildFile, "utf8").catch(() => void 0);
|
|
2875
|
-
if (contents === void 0) return void 0;
|
|
2876
|
-
return contents.includes(ingest.requiresBuildTask) ? void 0 : ingest.requiresBuildTask;
|
|
2877
|
-
}
|
|
2878
|
-
function sdkVersionPin(profile, packageManager) {
|
|
2879
|
-
return packageManager.sdkVersionPin ?? profile.sdk.versionPin;
|
|
2880
|
-
}
|
|
2881
|
-
function dependencyInstruction(toolchain) {
|
|
2882
|
-
const { profile, packageManager } = toolchain;
|
|
2883
|
-
const { packageName } = profile.sdk;
|
|
2884
|
-
const versionPin = sdkVersionPin(profile, packageManager);
|
|
2885
|
-
const also = profile.sdk.alsoRequires ? ` ${profile.sdk.alsoRequires}` : "";
|
|
2886
|
-
switch (packageManager.dependency.mode) {
|
|
2887
|
-
case "wizard-installs":
|
|
2888
|
-
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}`;
|
|
2889
|
-
case "code-imports":
|
|
2890
|
-
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}`;
|
|
2891
|
-
case "agent-declares":
|
|
2892
|
-
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}`;
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
// src/lib/tools/searchFiles.ts
|
|
2897
2226
|
var MAX_QUERY_LENGTH = 1e3;
|
|
2898
2227
|
async function walkFiles(dir) {
|
|
2228
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
|
|
2899
2229
|
const out = [];
|
|
2900
|
-
for (const e of await
|
|
2901
|
-
if (e.name.startsWith(".") ||
|
|
2902
|
-
const full =
|
|
2230
|
+
for (const e of await readdir2(dir, { withFileTypes: true })) {
|
|
2231
|
+
if (e.name.startsWith(".") || skip.has(e.name)) continue;
|
|
2232
|
+
const full = join8(dir, e.name);
|
|
2903
2233
|
if (e.isDirectory()) out.push(...await walkFiles(full));
|
|
2904
2234
|
else if (e.isFile()) out.push(full);
|
|
2905
2235
|
}
|
|
@@ -2932,7 +2262,7 @@ function searchFilesTool(ctx) {
|
|
|
2932
2262
|
for (const file of await walkFiles(resolved.target)) {
|
|
2933
2263
|
let content;
|
|
2934
2264
|
try {
|
|
2935
|
-
content = await
|
|
2265
|
+
content = await readFile6(file, "utf8");
|
|
2936
2266
|
} catch {
|
|
2937
2267
|
continue;
|
|
2938
2268
|
}
|
|
@@ -2956,144 +2286,88 @@ function searchFilesTool(ctx) {
|
|
|
2956
2286
|
import { tool as tool8 } from "ai";
|
|
2957
2287
|
import z11 from "zod";
|
|
2958
2288
|
|
|
2959
|
-
// src/lib/tools/repoVerification.ts
|
|
2960
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
2961
|
-
import { join as join11 } from "node:path";
|
|
2962
|
-
|
|
2963
2289
|
// src/lib/tools/utils/runCommand.ts
|
|
2964
2290
|
import { spawn as spawn2 } from "node:child_process";
|
|
2965
|
-
|
|
2966
|
-
var INGEST_TIMEOUT_MS = 15 * 6e4;
|
|
2967
|
-
var VERIFY_TIMEOUT_MS = 10 * 6e4;
|
|
2968
|
-
var KILL_GRACE_MS = 5e3;
|
|
2969
|
-
function runCommand(command, args, options = {}) {
|
|
2970
|
-
const { cwd, env, timeoutMs = VERIFY_TIMEOUT_MS } = options;
|
|
2291
|
+
function runCommand(command, args, cwd) {
|
|
2971
2292
|
return new Promise((resolve4) => {
|
|
2972
2293
|
let output = "";
|
|
2973
|
-
let settled = false;
|
|
2974
2294
|
const child = spawn2(command, args, {
|
|
2975
2295
|
cwd,
|
|
2976
|
-
|
|
2977
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2978
|
-
...env ? { env: { ...process.env, ...env } } : {}
|
|
2296
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2979
2297
|
});
|
|
2980
|
-
const settle = (result) => {
|
|
2981
|
-
if (settled) return;
|
|
2982
|
-
settled = true;
|
|
2983
|
-
clearTimeout(timer);
|
|
2984
|
-
resolve4(result);
|
|
2985
|
-
};
|
|
2986
|
-
const timer = setTimeout(() => {
|
|
2987
|
-
child.kill("SIGTERM");
|
|
2988
|
-
setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
|
|
2989
|
-
const seconds = Math.round(timeoutMs / 1e3);
|
|
2990
|
-
settle({
|
|
2991
|
-
code: 1,
|
|
2992
|
-
output: `${output}
|
|
2993
|
-
Timed out after ${seconds}s: ${command} ${args.join(" ")}`.trim(),
|
|
2994
|
-
timedOut: true
|
|
2995
|
-
});
|
|
2996
|
-
}, timeoutMs);
|
|
2997
2298
|
child.stdout?.on("data", (d) => output += d);
|
|
2998
2299
|
child.stderr?.on("data", (d) => output += d);
|
|
2999
2300
|
child.on(
|
|
3000
2301
|
"error",
|
|
3001
|
-
(err) =>
|
|
3002
|
-
code: 1,
|
|
3003
|
-
output: `Failed to run ${command}: ${err.message}`,
|
|
3004
|
-
timedOut: false
|
|
3005
|
-
})
|
|
3006
|
-
);
|
|
3007
|
-
child.on(
|
|
3008
|
-
"close",
|
|
3009
|
-
(code) => settle({ code: code ?? 1, output, timedOut: false })
|
|
2302
|
+
(err) => resolve4({ code: 1, output: `Failed to run ${command}: ${err.message}` })
|
|
3010
2303
|
);
|
|
2304
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, output }));
|
|
3011
2305
|
});
|
|
3012
2306
|
}
|
|
3013
2307
|
|
|
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
|
+
|
|
3014
2337
|
// src/lib/tools/repoVerification.ts
|
|
3015
2338
|
var VERIFICATION_SCRIPT_CANDIDATES = ["lint", "typecheck", "check"];
|
|
3016
|
-
async function
|
|
3017
|
-
const { code, output } = await runCommand(binary, args, {
|
|
3018
|
-
timeoutMs: VERIFY_TIMEOUT_MS
|
|
3019
|
-
});
|
|
3020
|
-
return { command, exitCode: code, ok: code === 0, output: output.trim() };
|
|
3021
|
-
}
|
|
3022
|
-
async function javascriptChecks() {
|
|
2339
|
+
async function runRepoVerificationCheck() {
|
|
3023
2340
|
let pkg;
|
|
3024
2341
|
try {
|
|
3025
2342
|
pkg = await readPackageJson();
|
|
3026
2343
|
} catch (err) {
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
};
|
|
2344
|
+
const limitation = `Could not read package.json to detect verification conventions: ${err.message}`;
|
|
2345
|
+
return { ok: false, checks: [], limitation };
|
|
3030
2346
|
}
|
|
3031
2347
|
const scripts = pkg.scripts ?? {};
|
|
3032
2348
|
const present = VERIFICATION_SCRIPT_CANDIDATES.filter((s) => s in scripts);
|
|
3033
2349
|
if (present.length === 0) {
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
};
|
|
2350
|
+
const limitation = `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`;
|
|
2351
|
+
return { ok: false, checks: [], limitation };
|
|
3037
2352
|
}
|
|
3038
2353
|
const pm = await detectPackageManager(process.cwd());
|
|
3039
2354
|
const checks = [];
|
|
3040
2355
|
for (const script of present) {
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
);
|
|
3044
|
-
}
|
|
3045
|
-
return { checks };
|
|
3046
|
-
}
|
|
3047
|
-
async function registryChecks(id) {
|
|
3048
|
-
const profile = LANGUAGE_PROFILES[id];
|
|
3049
|
-
const runnable = profile.verification.filter(
|
|
3050
|
-
(spec) => !spec.requiresFile || existsSync3(join11(process.cwd(), spec.requiresFile))
|
|
3051
|
-
);
|
|
3052
|
-
if (runnable.length === 0) {
|
|
3053
|
-
return {
|
|
3054
|
-
limitation: `No mechanical verification available for ${profile.displayName} in this repo.`
|
|
3055
|
-
};
|
|
3056
|
-
}
|
|
3057
|
-
const checks = [];
|
|
3058
|
-
for (const spec of runnable) {
|
|
3059
|
-
checks.push(
|
|
3060
|
-
await runCheck(spec.argv.join(" "), spec.argv[0], [...spec.argv.slice(1)])
|
|
3061
|
-
);
|
|
2356
|
+
const command = `${pm} run ${script}`;
|
|
2357
|
+
const { code, output } = await runCommand(pm, ["run", script]);
|
|
2358
|
+
checks.push({ command, exitCode: code, ok: code === 0, output: output.trim() });
|
|
3062
2359
|
}
|
|
3063
|
-
return { checks };
|
|
3064
|
-
}
|
|
3065
|
-
async function runRepoVerificationCheck(languages = [DEFAULT_LANGUAGE_ID]) {
|
|
3066
|
-
const ids = [...new Set(languages)];
|
|
3067
|
-
if (ids.length === 0) ids.push(DEFAULT_LANGUAGE_ID);
|
|
3068
|
-
const checks = [];
|
|
3069
|
-
const limitations = [];
|
|
3070
|
-
for (const id of ids) {
|
|
3071
|
-
const result = id === JAVASCRIPT ? await javascriptChecks() : await registryChecks(id);
|
|
3072
|
-
if ("checks" in result) checks.push(...result.checks);
|
|
3073
|
-
else limitations.push(result.limitation);
|
|
3074
|
-
}
|
|
3075
|
-
if (checks.length === 0) {
|
|
3076
|
-
return {
|
|
3077
|
-
ok: false,
|
|
3078
|
-
checks: [],
|
|
3079
|
-
limitation: limitations.join(" ") || "No verification checks available."
|
|
3080
|
-
};
|
|
3081
|
-
}
|
|
3082
|
-
return {
|
|
3083
|
-
ok: checks.every((c) => c.ok),
|
|
3084
|
-
checks,
|
|
3085
|
-
...limitations.length ? { limitation: limitations.join(" ") } : {}
|
|
3086
|
-
};
|
|
2360
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
3087
2361
|
}
|
|
3088
2362
|
|
|
3089
2363
|
// src/lib/tools/verifyImplementation.ts
|
|
3090
|
-
function verifyImplementationTool(
|
|
2364
|
+
function verifyImplementationTool() {
|
|
3091
2365
|
return tool8({
|
|
3092
|
-
description: "Run the repo's mechanical verification
|
|
2366
|
+
description: "Run the repo's mechanical verification check for generated implementation changes. Detects lint/typecheck/check from package.json and returns structured pass/fail evidence for the verifier to interpret.",
|
|
3093
2367
|
inputSchema: z11.object(),
|
|
3094
2368
|
execute: async () => {
|
|
3095
|
-
logger.info(
|
|
3096
|
-
return runRepoVerificationCheck(
|
|
2369
|
+
logger.info("called verifyImplementation tool");
|
|
2370
|
+
return runRepoVerificationCheck();
|
|
3097
2371
|
}
|
|
3098
2372
|
});
|
|
3099
2373
|
}
|
|
@@ -3219,17 +2493,12 @@ var DEFAULT_TOOL_LIMITS = {
|
|
|
3219
2493
|
read: 20,
|
|
3220
2494
|
match: 100
|
|
3221
2495
|
};
|
|
3222
|
-
function createToolContext({
|
|
3223
|
-
limits = DEFAULT_TOOL_LIMITS,
|
|
3224
|
-
cwd = process.cwd(),
|
|
3225
|
-
languages = [DEFAULT_LANGUAGE_ID]
|
|
3226
|
-
} = {}) {
|
|
2496
|
+
function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
|
|
3227
2497
|
return {
|
|
3228
2498
|
root: cwd,
|
|
3229
2499
|
cwd,
|
|
3230
2500
|
limits,
|
|
3231
|
-
counts: { list: 0, search: 0, read: 0 }
|
|
3232
|
-
languages: languages.length ? languages : [DEFAULT_LANGUAGE_ID]
|
|
2501
|
+
counts: { list: 0, search: 0, read: 0 }
|
|
3233
2502
|
};
|
|
3234
2503
|
}
|
|
3235
2504
|
|
|
@@ -3266,7 +2535,7 @@ function createTools(ctx, { output, tools }) {
|
|
|
3266
2535
|
searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
|
|
3267
2536
|
verifyImplementation: withLogging(
|
|
3268
2537
|
"verifyImplementation",
|
|
3269
|
-
verifyImplementationTool(
|
|
2538
|
+
verifyImplementationTool()
|
|
3270
2539
|
),
|
|
3271
2540
|
generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
|
|
3272
2541
|
notifyUser: withLogging("notifyUser", notifyUserTool())
|
|
@@ -3302,7 +2571,7 @@ async function runAgent(req) {
|
|
|
3302
2571
|
baseURL: PROXY_BASE_URL,
|
|
3303
2572
|
fetch: proxyFetch
|
|
3304
2573
|
});
|
|
3305
|
-
const toolContext = createToolContext(
|
|
2574
|
+
const toolContext = createToolContext();
|
|
3306
2575
|
const readTools = ["readFile", "searchFiles", "listFiles"];
|
|
3307
2576
|
const hasReadTools = !req.tools || req.tools.some((t) => readTools.includes(t));
|
|
3308
2577
|
const instructions = [
|
|
@@ -3393,11 +2662,8 @@ var detectLanguageSchema = z16.object({
|
|
|
3393
2662
|
var detectLanguage = () => runAgent({
|
|
3394
2663
|
instructions: [
|
|
3395
2664
|
"Analyze the codebase and determine the programming languages and frameworks used",
|
|
3396
|
-
"
|
|
3397
|
-
"List the language that owns the backend/data code first \u2014 that is the one an ingestion script will be written in.",
|
|
3398
|
-
"If a superset language is found, exclude the subset language. TS-over-JS. Kotlin-over-Java when Kotlin is primary.",
|
|
2665
|
+
"If a superset language is found, exclude the subset language. TS-over-JS.",
|
|
3399
2666
|
"If a meta-framework is used, exclude the framework. Next-over-React.",
|
|
3400
|
-
"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).",
|
|
3401
2667
|
"Return the exact version",
|
|
3402
2668
|
"Exclude things like CSS frameworks, build tools, or testing frameworks",
|
|
3403
2669
|
'Use as few tools as possible, but do not guess. If you cant find the answer, say "unknown"',
|
|
@@ -3446,7 +2712,6 @@ var MODE_CONFIG = {
|
|
|
3446
2712
|
"Analyze the codebase to find the data entities (models) that should be ingested into Algolia.",
|
|
3447
2713
|
"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).",
|
|
3448
2714
|
"Inspect the source of each entity to extract real field names for attributes \u2014 do not guess or leave attributes empty.",
|
|
3449
|
-
"Entities live wherever the stack keeps them: TypeScript interfaces or a Prisma schema, Django models.py, Rails app/models, Laravel Eloquent models, JPA @Entity classes, Go structs, C# entity classes, Pydantic models.",
|
|
3450
2715
|
"Prefer domain models (e.g. Document, Product, User) over framework or infrastructure types.",
|
|
3451
2716
|
"Use as few tools as possible, but do not guess. If you cannot find any entities, return an empty array.",
|
|
3452
2717
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
@@ -3458,9 +2723,8 @@ var MODE_CONFIG = {
|
|
|
3458
2723
|
instructions: [
|
|
3459
2724
|
"Analyze the codebase to determine the single best location to add search UI functionality.",
|
|
3460
2725
|
"Prefer a shared, always-rendered layout location (e.g. a header or navigation component) so search is reachable across the app.",
|
|
3461
|
-
"
|
|
3462
|
-
|
|
3463
|
-
'Use as few tools as possible, but do not guess. If the project renders no UI at all (an API-only service), say "unknown".',
|
|
2726
|
+
"Return one file path as searchImplementationAnalysis (e.g. /layouts/header.tsx).",
|
|
2727
|
+
'Use as few tools as possible, but do not guess. If you cannot find a clear location, say "unknown".',
|
|
3464
2728
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3465
2729
|
"When done, call reportStatus"
|
|
3466
2730
|
],
|
|
@@ -3469,8 +2733,8 @@ var MODE_CONFIG = {
|
|
|
3469
2733
|
verification: {
|
|
3470
2734
|
instructions: [
|
|
3471
2735
|
"Analyze the codebase to determine which code-quality tools are available to validate changes.",
|
|
3472
|
-
"Look at
|
|
3473
|
-
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"]
|
|
2736
|
+
"Look at package.json scripts, config files (e.g. .eslintrc, tsconfig, prettier), and dev dependencies.",
|
|
2737
|
+
'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"].',
|
|
3474
2738
|
"Use as few tools as possible, but do not guess. If you cannot find any, return an empty array.",
|
|
3475
2739
|
"Ignore directories that may be related to testing, like `/fixtures`, `/tests`, etc",
|
|
3476
2740
|
"When done, call reportStatus"
|
|
@@ -3497,7 +2761,7 @@ async function runAnalysis(mode, extraInstructions = []) {
|
|
|
3497
2761
|
// package.json
|
|
3498
2762
|
var package_default = {
|
|
3499
2763
|
name: "@algolia/wizard",
|
|
3500
|
-
version: "0.8.0
|
|
2764
|
+
version: "0.8.0",
|
|
3501
2765
|
description: "Magically implement Algolia functionality in your codebase",
|
|
3502
2766
|
type: "module",
|
|
3503
2767
|
engines: {
|
|
@@ -3519,7 +2783,7 @@ var package_default = {
|
|
|
3519
2783
|
prepare: "husky",
|
|
3520
2784
|
prepublishOnly: "pnpm build",
|
|
3521
2785
|
reset: "tsx ./scripts/reset-state.ts",
|
|
3522
|
-
"test:
|
|
2786
|
+
"test:fixtures": "touch .env && tsx --env-file=.env ./fixtures/run-fixtures.ts",
|
|
3523
2787
|
"test:tools": "tsx ./tool-evals/toolEval.ts",
|
|
3524
2788
|
test: "vitest",
|
|
3525
2789
|
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
@@ -3600,185 +2864,82 @@ function parseEntries(raw) {
|
|
|
3600
2864
|
return raw.split(",").map(clean).filter(Boolean).slice(0, MAX_ENTRIES).map((name) => ({ name, version: "unknown" }));
|
|
3601
2865
|
}
|
|
3602
2866
|
var summarize = (entries) => entries.length ? entries.map((e) => e.name).join(", ") : "none";
|
|
3603
|
-
|
|
3604
|
-
// src/actions/confirmLanguage.ts
|
|
3605
|
-
import z19 from "zod";
|
|
3606
|
-
var confirmLanguageSchema = z19.object({
|
|
3607
|
-
languages: detectLanguageSchema.shape.languages
|
|
3608
|
-
});
|
|
3609
|
-
var OTHER_OPTION = "Other";
|
|
3610
|
-
function confirmed(languages) {
|
|
3611
|
-
track("AI Wizard Language Confirmed", { languages });
|
|
3612
|
-
return { languages };
|
|
3613
|
-
}
|
|
3614
|
-
async function askOtherLanguage(ctx) {
|
|
3615
|
-
let prompt = "enter the language for your ingestion script";
|
|
2867
|
+
async function askList(ctx, prompt, { required = false } = {}) {
|
|
3616
2868
|
for (; ; ) {
|
|
3617
2869
|
const answer = await ctx.requestUserInput({
|
|
3618
2870
|
prompt,
|
|
3619
2871
|
promptType: "textInput",
|
|
3620
|
-
options: []
|
|
2872
|
+
options: [],
|
|
2873
|
+
helpText: 'Comma-separated, e.g. "TypeScript, Node".'
|
|
3621
2874
|
});
|
|
3622
2875
|
if (typeof answer !== "string") {
|
|
3623
|
-
throw new Error("
|
|
2876
|
+
throw new Error("askList received an unexpected non-text result");
|
|
3624
2877
|
}
|
|
3625
|
-
const
|
|
3626
|
-
if (
|
|
3627
|
-
prompt = "
|
|
2878
|
+
const entries = parseEntries(answer);
|
|
2879
|
+
if (entries.length || !required) return entries;
|
|
2880
|
+
prompt = "Please enter at least one entry:";
|
|
3628
2881
|
}
|
|
3629
2882
|
}
|
|
2883
|
+
|
|
2884
|
+
// src/actions/confirmLanguage.ts
|
|
2885
|
+
import z19 from "zod";
|
|
2886
|
+
var confirmLanguageSchema = z19.object({
|
|
2887
|
+
languages: detectLanguageSchema.shape.languages
|
|
2888
|
+
});
|
|
3630
2889
|
async function confirmLanguage(ctx) {
|
|
3631
2890
|
const detected = ctx.getStepOutput("project-scan");
|
|
3632
|
-
const
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
prompt: `Write the ingestion script in ${primary.name}?`,
|
|
3638
|
-
promptType: "acceptReject",
|
|
3639
|
-
options: [`Confirm ${primary.name}`, "Use a different language"],
|
|
3640
|
-
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0],
|
|
3641
|
-
messages: detectedLanguages.length > 1 ? [`Detected: ${summarize(detectedLanguages)}`] : []
|
|
3642
|
-
});
|
|
3643
|
-
if (accepted === true) return confirmed(detectedLanguages);
|
|
3644
|
-
}
|
|
3645
|
-
const options = [...CURATED_LANGUAGES];
|
|
3646
|
-
for (const language of detectedLanguages) {
|
|
3647
|
-
if (!options.some((o) => isSameLanguage(o, language.name))) {
|
|
3648
|
-
options.push(language.name);
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
options.push(OTHER_OPTION);
|
|
3652
|
-
const detectedFor = (option) => detectedLanguages.find((l) => isSameLanguage(option, l.name));
|
|
3653
|
-
const secondary = options.map(
|
|
3654
|
-
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
3655
|
-
);
|
|
3656
|
-
const defaultSelectedIndex = Math.max(
|
|
3657
|
-
options.findIndex((o) => detectedFor(o)),
|
|
3658
|
-
0
|
|
3659
|
-
);
|
|
3660
|
-
const selection = await ctx.requestUserInput({
|
|
3661
|
-
prompt: "select the language for your ingestion script",
|
|
3662
|
-
promptType: "multipleChoice",
|
|
3663
|
-
options,
|
|
3664
|
-
secondary,
|
|
3665
|
-
defaultSelectedIndex
|
|
2891
|
+
const answer = await ctx.requestUserInput({
|
|
2892
|
+
prompt: "Did we detect your language(s) correctly?",
|
|
2893
|
+
promptType: "acceptReject",
|
|
2894
|
+
options: ["Yes", "No"],
|
|
2895
|
+
messages: [`Languages: ${summarize(detected.languages)}`]
|
|
3666
2896
|
});
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
2897
|
+
const languages = answer === true ? detected.languages : await askList(ctx, "List the languages your project uses:", {
|
|
2898
|
+
required: true
|
|
2899
|
+
});
|
|
2900
|
+
track("AI Wizard Language Confirmed", {
|
|
2901
|
+
languages
|
|
2902
|
+
});
|
|
2903
|
+
return { languages };
|
|
3673
2904
|
}
|
|
3674
2905
|
|
|
3675
2906
|
// src/actions/confirmFramework.ts
|
|
3676
2907
|
import z20 from "zod";
|
|
3677
|
-
|
|
3678
|
-
// src/lib/frameworks.ts
|
|
3679
|
-
var BACKEND_ONLY_FRAMEWORK = "Backend only / API";
|
|
3680
|
-
var FRAMEWORKS = [
|
|
3681
|
-
// Frontend — InstantSearch component flavors.
|
|
3682
|
-
{ name: "Next.js", strategy: "react", aliases: ["next", "nextjs"] },
|
|
3683
|
-
{ name: "React", strategy: "react", aliases: ["reactjs"] },
|
|
3684
|
-
{ name: "Vue", strategy: "vue", aliases: ["vuejs", "nuxt", "nuxtjs"] },
|
|
3685
|
-
{ name: "Angular", strategy: "angular", aliases: ["angularjs"] },
|
|
3686
|
-
// No Svelte InstantSearch flavor exists, so it uses InstantSearch.js.
|
|
3687
|
-
{ name: "Svelte", strategy: "js", aliases: ["sveltekit"] },
|
|
3688
|
-
{
|
|
3689
|
-
name: "Vanilla JS",
|
|
3690
|
-
strategy: "js",
|
|
3691
|
-
aliases: ["vanilla", "javascript", "js", "astro", "vite"]
|
|
3692
|
-
},
|
|
3693
|
-
// Backend — Algolia's official framework integrations. Server-rendered
|
|
3694
|
-
// templates get InstantSearch.js from a CDN.
|
|
3695
|
-
{
|
|
3696
|
-
name: "Rails",
|
|
3697
|
-
strategy: "cdn-template",
|
|
3698
|
-
aliases: ["rubyonrails", "ruby on rails", "erb"]
|
|
3699
|
-
},
|
|
3700
|
-
{ name: "Django", strategy: "cdn-template", aliases: ["jinja", "jinja2"] },
|
|
3701
|
-
{ name: "Laravel", strategy: "cdn-template", aliases: ["blade"] },
|
|
3702
|
-
{ name: "Symfony", strategy: "cdn-template", aliases: ["twig"] },
|
|
3703
|
-
// Mobile — Algolia ships InstantSearch iOS/Android and Dart clients, but the
|
|
3704
|
-
// wizard can't scaffold a native UI, so it points at the docs instead.
|
|
3705
|
-
{ name: "Flutter", strategy: "none", aliases: [] },
|
|
3706
|
-
{ name: "iOS", strategy: "none", aliases: ["swiftui", "uikit"] },
|
|
3707
|
-
{ name: "Android", strategy: "none", aliases: ["jetpack compose", "compose"] },
|
|
3708
|
-
{ name: BACKEND_ONLY_FRAMEWORK, strategy: "cdn-template", aliases: [] }
|
|
3709
|
-
];
|
|
3710
|
-
var CURATED_FRAMEWORKS = FRAMEWORKS.map(
|
|
3711
|
-
(f) => f.name
|
|
3712
|
-
);
|
|
3713
|
-
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3714
|
-
var ALIAS_TO_NAME = /* @__PURE__ */ new Map();
|
|
3715
|
-
for (const framework of FRAMEWORKS) {
|
|
3716
|
-
for (const alias of [framework.name, ...framework.aliases]) {
|
|
3717
|
-
ALIAS_TO_NAME.set(normalize(alias), framework.name);
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
var STRATEGY_BY_NAME = new Map(FRAMEWORKS.map((f) => [f.name, f.strategy]));
|
|
3721
|
-
function canonicalFrameworkName(name) {
|
|
3722
|
-
return ALIAS_TO_NAME.get(normalize(name));
|
|
3723
|
-
}
|
|
3724
|
-
function isSameFramework(a, b) {
|
|
3725
|
-
const x = canonicalFrameworkName(a) ?? normalize(a);
|
|
3726
|
-
const y = canonicalFrameworkName(b) ?? normalize(b);
|
|
3727
|
-
return x !== "" && x === y;
|
|
3728
|
-
}
|
|
3729
|
-
function resolveSearchStrategy(frameworkName, hasJavaScriptInStack) {
|
|
3730
|
-
const canonical = frameworkName ? canonicalFrameworkName(frameworkName) : void 0;
|
|
3731
|
-
const strategy = canonical ? STRATEGY_BY_NAME.get(canonical) : void 0;
|
|
3732
|
-
if (strategy) return strategy;
|
|
3733
|
-
return hasJavaScriptInStack ? "js" : "cdn-template";
|
|
3734
|
-
}
|
|
3735
|
-
function searchDocKey(strategy) {
|
|
3736
|
-
return strategy === "cdn-template" ? "templates" : strategy;
|
|
3737
|
-
}
|
|
3738
|
-
function bundlesJavaScript(strategy) {
|
|
3739
|
-
return strategy !== "cdn-template" && strategy !== "none";
|
|
3740
|
-
}
|
|
3741
|
-
function canScaffoldSearchUI(strategy) {
|
|
3742
|
-
return strategy !== "none";
|
|
3743
|
-
}
|
|
3744
|
-
var ENV_PREFIXES = [
|
|
3745
|
-
{ aliases: ["next", "nextjs"], prefix: "NEXT_PUBLIC_" },
|
|
3746
|
-
{ aliases: ["nuxt", "nuxtjs"], prefix: "NUXT_PUBLIC_" },
|
|
3747
|
-
{ aliases: ["astro"], prefix: "PUBLIC_" },
|
|
3748
|
-
{ aliases: ["vite"], prefix: "VITE_" }
|
|
3749
|
-
];
|
|
3750
|
-
var DEFAULT_ENV_PREFIX = "PUBLIC_";
|
|
3751
|
-
function publicEnvPrefix(frameworkNames, strategy) {
|
|
3752
|
-
if (!bundlesJavaScript(strategy)) return "";
|
|
3753
|
-
const present = new Set(frameworkNames.map(normalize));
|
|
3754
|
-
for (const { aliases, prefix } of ENV_PREFIXES) {
|
|
3755
|
-
if (aliases.some((alias) => present.has(alias))) return prefix;
|
|
3756
|
-
}
|
|
3757
|
-
return DEFAULT_ENV_PREFIX;
|
|
3758
|
-
}
|
|
3759
|
-
function describeSearchTarget(strategy, frameworkName) {
|
|
3760
|
-
switch (strategy) {
|
|
3761
|
-
case "react":
|
|
3762
|
-
return "React (react-instantsearch)";
|
|
3763
|
-
case "vue":
|
|
3764
|
-
return "Vue (vue-instantsearch)";
|
|
3765
|
-
case "angular":
|
|
3766
|
-
return "Angular (angular-instantsearch)";
|
|
3767
|
-
case "js":
|
|
3768
|
-
return "plain JavaScript (InstantSearch.js)";
|
|
3769
|
-
case "cdn-template":
|
|
3770
|
-
return `${frameworkName ?? "server-rendered"} templates (InstantSearch.js via CDN)`;
|
|
3771
|
-
case "none":
|
|
3772
|
-
return frameworkName ?? "a native mobile app";
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
|
|
3776
|
-
// src/actions/confirmFramework.ts
|
|
3777
2908
|
var confirmFrameworkSchema = z20.object({
|
|
3778
2909
|
frameworks: detectLanguageSchema.shape.frameworks
|
|
3779
2910
|
});
|
|
3780
|
-
var
|
|
3781
|
-
|
|
2911
|
+
var CURATED_FRAMEWORKS = [
|
|
2912
|
+
"Next.js",
|
|
2913
|
+
"React",
|
|
2914
|
+
"Vue",
|
|
2915
|
+
"Angular",
|
|
2916
|
+
"Svelte",
|
|
2917
|
+
"Vanilla JS"
|
|
2918
|
+
];
|
|
2919
|
+
var OTHER_OPTION = "Other";
|
|
2920
|
+
var normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2921
|
+
var FRAMEWORK_ALIASES = {
|
|
2922
|
+
next: "nextjs",
|
|
2923
|
+
nextjs: "nextjs",
|
|
2924
|
+
react: "react",
|
|
2925
|
+
reactjs: "react",
|
|
2926
|
+
vue: "vue",
|
|
2927
|
+
vuejs: "vue",
|
|
2928
|
+
angular: "angular",
|
|
2929
|
+
angularjs: "angular",
|
|
2930
|
+
svelte: "svelte",
|
|
2931
|
+
sveltekit: "svelte",
|
|
2932
|
+
vanillajs: "vanillajs",
|
|
2933
|
+
vanilla: "vanillajs",
|
|
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);
|
|
2940
|
+
return x !== "" && x === y;
|
|
2941
|
+
};
|
|
2942
|
+
function confirmed(name, version) {
|
|
3782
2943
|
const frameworks = [{ name, version: version ?? "unknown" }];
|
|
3783
2944
|
track("AI Wizard Frontend Framework Confirmed", { frameworks });
|
|
3784
2945
|
return { frameworks };
|
|
@@ -3806,7 +2967,7 @@ async function confirmFramework(ctx) {
|
|
|
3806
2967
|
for (const fw of detectedFrameworks) {
|
|
3807
2968
|
if (!options.some((o) => isSameFramework(o, fw.name))) options.push(fw.name);
|
|
3808
2969
|
}
|
|
3809
|
-
options.push(
|
|
2970
|
+
options.push(OTHER_OPTION);
|
|
3810
2971
|
const detectedFor = (option) => detectedFrameworks.find((fw) => isSameFramework(option, fw.name));
|
|
3811
2972
|
const primary = detectedFrameworks[0];
|
|
3812
2973
|
if (primary) {
|
|
@@ -3816,7 +2977,7 @@ async function confirmFramework(ctx) {
|
|
|
3816
2977
|
options: [`Confirm ${primary.name}`, "Use a different framework"],
|
|
3817
2978
|
secondary: [{ kind: "badge", value: "[DETECTED]" }, void 0]
|
|
3818
2979
|
});
|
|
3819
|
-
if (accepted === true) return
|
|
2980
|
+
if (accepted === true) return confirmed(primary.name, primary.version);
|
|
3820
2981
|
}
|
|
3821
2982
|
const secondary = options.map(
|
|
3822
2983
|
(o) => detectedFor(o) ? { kind: "badge", value: "[DETECTED]" } : void 0
|
|
@@ -3826,7 +2987,7 @@ async function confirmFramework(ctx) {
|
|
|
3826
2987
|
0
|
|
3827
2988
|
);
|
|
3828
2989
|
const selection = await ctx.requestUserInput({
|
|
3829
|
-
prompt: "select
|
|
2990
|
+
prompt: "select a framework",
|
|
3830
2991
|
promptType: "multipleChoice",
|
|
3831
2992
|
options,
|
|
3832
2993
|
secondary,
|
|
@@ -3835,10 +2996,10 @@ async function confirmFramework(ctx) {
|
|
|
3835
2996
|
if (typeof selection !== "string") {
|
|
3836
2997
|
throw new Error("confirmFramework received an unexpected non-text result");
|
|
3837
2998
|
}
|
|
3838
|
-
if (selection ===
|
|
3839
|
-
return
|
|
2999
|
+
if (selection === OTHER_OPTION) {
|
|
3000
|
+
return confirmed(await askOtherFramework(ctx));
|
|
3840
3001
|
}
|
|
3841
|
-
return
|
|
3002
|
+
return confirmed(selection, detectedFor(selection)?.version);
|
|
3842
3003
|
}
|
|
3843
3004
|
|
|
3844
3005
|
// src/actions/promptUser.ts
|
|
@@ -3931,15 +3092,15 @@ async function confirmEntities(ctx) {
|
|
|
3931
3092
|
onSubmit: () => {
|
|
3932
3093
|
}
|
|
3933
3094
|
});
|
|
3934
|
-
const
|
|
3935
|
-
if (
|
|
3095
|
+
const confirmed2 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
|
|
3096
|
+
if (confirmed2.length === 0) {
|
|
3936
3097
|
throw new Error("User cancelled entity selection \u2014 analysis halted.");
|
|
3937
3098
|
}
|
|
3938
|
-
ctx.setUserInput("confirmedEntities",
|
|
3099
|
+
ctx.setUserInput("confirmedEntities", confirmed2);
|
|
3939
3100
|
track("AI Wizard Entities Confirmed", {
|
|
3940
|
-
entities: toEntitySummary(
|
|
3101
|
+
entities: toEntitySummary(confirmed2)
|
|
3941
3102
|
});
|
|
3942
|
-
return { ingestionAnalysis: entities, confirmedEntities:
|
|
3103
|
+
return { ingestionAnalysis: entities, confirmedEntities: confirmed2 };
|
|
3943
3104
|
}
|
|
3944
3105
|
|
|
3945
3106
|
// src/actions/review.ts
|
|
@@ -3963,7 +3124,7 @@ ${JSON.stringify(s.output, null, 2)}`
|
|
|
3963
3124
|
}
|
|
3964
3125
|
function formatReviewSummary(result) {
|
|
3965
3126
|
const nextStepLines = result.nextSteps.map((step) => {
|
|
3966
|
-
const isIngestCommand = step.includes("algolia-wizard/
|
|
3127
|
+
const isIngestCommand = step.includes(".algolia-wizard/ingest.sh");
|
|
3967
3128
|
const isWorktreeCommand = step.includes("/worktrees/");
|
|
3968
3129
|
return {
|
|
3969
3130
|
text: `\u2192 ${step}`,
|
|
@@ -4005,14 +3166,13 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
|
|
|
4005
3166
|
import z24 from "zod";
|
|
4006
3167
|
|
|
4007
3168
|
// src/lib/worktree.ts
|
|
4008
|
-
import { execFile } from "node:child_process";
|
|
4009
|
-
import {
|
|
4010
|
-
import { copyFile, mkdir as mkdir6, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
3169
|
+
import { execFile, spawn as spawn3 } from "node:child_process";
|
|
3170
|
+
import { copyFile, mkdir as mkdir6, readdir as readdir3, readFile as readFile8, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
|
|
4011
3171
|
import {
|
|
4012
3172
|
basename as basename2,
|
|
4013
3173
|
dirname as dirname7,
|
|
4014
3174
|
isAbsolute as isAbsolute2,
|
|
4015
|
-
join as
|
|
3175
|
+
join as join10,
|
|
4016
3176
|
relative as relative2,
|
|
4017
3177
|
resolve as resolve3
|
|
4018
3178
|
} from "node:path";
|
|
@@ -4046,8 +3206,8 @@ async function isWorkingTreeDirty(repoRoot) {
|
|
|
4046
3206
|
return out.trim().length > 0;
|
|
4047
3207
|
}
|
|
4048
3208
|
async function pruneOldWorktrees(repoRoot) {
|
|
4049
|
-
const dir =
|
|
4050
|
-
const stale = (await
|
|
3209
|
+
const dir = join10(stateDir(repoRoot), "worktrees");
|
|
3210
|
+
const stale = (await readdir3(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
|
|
4051
3211
|
for (const slug of stale) {
|
|
4052
3212
|
const branch = slug.replace("wizard-implement-", WIZARD_BRANCH_PREFIX);
|
|
4053
3213
|
try {
|
|
@@ -4057,7 +3217,7 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4057
3217
|
"worktree",
|
|
4058
3218
|
"remove",
|
|
4059
3219
|
"--force",
|
|
4060
|
-
|
|
3220
|
+
join10(dir, slug)
|
|
4061
3221
|
]);
|
|
4062
3222
|
await git(["-C", repoRoot, "branch", "-D", branch]);
|
|
4063
3223
|
} catch (err) {
|
|
@@ -4071,55 +3231,43 @@ async function pruneOldWorktrees(repoRoot) {
|
|
|
4071
3231
|
async function createWorktree(repoRoot) {
|
|
4072
3232
|
const branch = `${WIZARD_BRANCH_PREFIX}${Date.now()}`;
|
|
4073
3233
|
const dirSlug = branch.replace(/\//g, "-");
|
|
4074
|
-
const path =
|
|
3234
|
+
const path = join10(stateDir(repoRoot), "worktrees", dirSlug);
|
|
4075
3235
|
await git(["-C", repoRoot, "worktree", "prune"]);
|
|
4076
3236
|
await pruneOldWorktrees(repoRoot);
|
|
4077
3237
|
await mkdir6(dirname7(path), { recursive: true });
|
|
4078
3238
|
await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
|
|
4079
3239
|
return { path, branch };
|
|
4080
3240
|
}
|
|
4081
|
-
async function
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
return { ok: code === 0, output: output.trim() };
|
|
4087
|
-
}
|
|
4088
|
-
async function installWorktreeDeps(worktreePath, toolchain) {
|
|
4089
|
-
const { profile, installSteps, packageManager } = toolchain;
|
|
4090
|
-
const declared = packageManager.dependency.mode === "agent-declares" ? packageManager.dependency.file : void 0;
|
|
4091
|
-
const haveSomethingToInstall = await hasProfileManifest(worktreePath, profile) || declared !== void 0 && existsSync4(join12(worktreePath, declared));
|
|
4092
|
-
if (!haveSomethingToInstall) {
|
|
4093
|
-
return {
|
|
4094
|
-
ok: true,
|
|
4095
|
-
output: `no ${profile.displayName} manifest; skipped install`
|
|
4096
|
-
};
|
|
4097
|
-
}
|
|
4098
|
-
if (installSteps.length === 0) {
|
|
4099
|
-
return {
|
|
4100
|
-
ok: true,
|
|
4101
|
-
output: `${profile.displayName} (${toolchain.packageManager.id}) has no wizard-run install step`
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
const outputs = [];
|
|
4105
|
-
for (const step of installSteps) {
|
|
4106
|
-
if (step.requiresFile && !existsSync4(join12(worktreePath, step.requiresFile)))
|
|
4107
|
-
continue;
|
|
4108
|
-
const result = await spawnStep(worktreePath, step.argv);
|
|
4109
|
-
if (result.output) outputs.push(result.output);
|
|
4110
|
-
if (result.ok) continue;
|
|
4111
|
-
if (step.optional) {
|
|
4112
|
-
logger.warn(
|
|
4113
|
-
{ step: step.argv.join(" "), output: result.output },
|
|
4114
|
-
"installWorktreeDeps: optional install step failed; continuing"
|
|
4115
|
-
);
|
|
4116
|
-
continue;
|
|
4117
|
-
}
|
|
4118
|
-
return { ok: false, output: outputs.join("\n").trim() };
|
|
3241
|
+
async function installWorktreeDeps(worktreePath) {
|
|
3242
|
+
try {
|
|
3243
|
+
await readPackageJson(worktreePath);
|
|
3244
|
+
} catch {
|
|
3245
|
+
return { ok: true, output: "no package.json; skipped install" };
|
|
4119
3246
|
}
|
|
4120
|
-
|
|
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
|
+
);
|
|
3267
|
+
});
|
|
4121
3268
|
}
|
|
4122
|
-
|
|
3269
|
+
var INGEST_RUNTIMES = ["node", "python", "python3", "bun"];
|
|
3270
|
+
function validateIngestEntrypoint(worktreePath, entrypoint) {
|
|
4123
3271
|
if (!entrypoint || entrypoint.startsWith("-")) {
|
|
4124
3272
|
return {
|
|
4125
3273
|
ok: false,
|
|
@@ -4134,29 +3282,18 @@ function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
|
|
|
4134
3282
|
reason: `entrypoint "${entrypoint}" resolves outside the worktree`
|
|
4135
3283
|
};
|
|
4136
3284
|
}
|
|
4137
|
-
if (allowedExtensions?.length && !allowedExtensions.some((ext) => entrypoint.endsWith(ext))) {
|
|
4138
|
-
return {
|
|
4139
|
-
ok: false,
|
|
4140
|
-
reason: `entrypoint "${entrypoint}" is not one of ${allowedExtensions.join(", ")}`
|
|
4141
|
-
};
|
|
4142
|
-
}
|
|
4143
3285
|
return { ok: true, target };
|
|
4144
3286
|
}
|
|
4145
|
-
async function runIngestScript(worktreePath,
|
|
4146
|
-
|
|
4147
|
-
if (ingest.kind !== "auto") {
|
|
3287
|
+
async function runIngestScript(worktreePath, runtime, entrypoint, env = {}) {
|
|
3288
|
+
if (!INGEST_RUNTIMES.includes(runtime)) {
|
|
4148
3289
|
return {
|
|
4149
3290
|
ran: false,
|
|
4150
3291
|
ok: false,
|
|
4151
3292
|
output: "",
|
|
4152
|
-
reason:
|
|
3293
|
+
reason: `runtime "${runtime}" is not an allowed interpreter (${INGEST_RUNTIMES.join(", ")})`
|
|
4153
3294
|
};
|
|
4154
3295
|
}
|
|
4155
|
-
const validated = validateIngestEntrypoint(
|
|
4156
|
-
worktreePath,
|
|
4157
|
-
entrypoint,
|
|
4158
|
-
ingest.entrypointExtensions
|
|
4159
|
-
);
|
|
3296
|
+
const validated = validateIngestEntrypoint(worktreePath, entrypoint);
|
|
4160
3297
|
if (!validated.ok) {
|
|
4161
3298
|
return { ran: false, ok: false, output: "", reason: validated.reason };
|
|
4162
3299
|
}
|
|
@@ -4177,13 +3314,29 @@ async function runIngestScript(worktreePath, toolchain, entrypoint, env = {}) {
|
|
|
4177
3314
|
reason: `entrypoint "${entrypoint}" does not exist`
|
|
4178
3315
|
};
|
|
4179
3316
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
3317
|
+
return new Promise((resolveRun) => {
|
|
3318
|
+
let output = "";
|
|
3319
|
+
const child = spawn3(runtime, [entrypoint], {
|
|
3320
|
+
cwd: worktreePath,
|
|
3321
|
+
shell: false,
|
|
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
|
+
);
|
|
4185
3339
|
});
|
|
4186
|
-
return { ran: true, ok: code === 0, output: output.trim() };
|
|
4187
3340
|
}
|
|
4188
3341
|
async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourcePath) {
|
|
4189
3342
|
const trimmed = sourcePath.trim();
|
|
@@ -4198,8 +3351,8 @@ async function copyUploadIntoWorktree(repoRoot, worktreePath, ingestDir, sourceP
|
|
|
4198
3351
|
} catch {
|
|
4199
3352
|
return { ok: false, reason: `"${sourcePath}" does not exist` };
|
|
4200
3353
|
}
|
|
4201
|
-
const relPath =
|
|
4202
|
-
const dest =
|
|
3354
|
+
const relPath = join10(ingestDir, basename2(source));
|
|
3355
|
+
const dest = join10(worktreePath, relPath);
|
|
4203
3356
|
try {
|
|
4204
3357
|
await mkdir6(dirname7(dest), { recursive: true });
|
|
4205
3358
|
await copyFile(source, dest);
|
|
@@ -4215,10 +3368,10 @@ function hasEnvVar(content, name) {
|
|
|
4215
3368
|
return new RegExp(`^(\\s*(?:export\\s+)?${name})\\s*=`, "m").test(content);
|
|
4216
3369
|
}
|
|
4217
3370
|
async function writeSearchEnvValues(worktreePath, vars) {
|
|
4218
|
-
const target =
|
|
3371
|
+
const target = join10(worktreePath, ".env");
|
|
4219
3372
|
let existing = "";
|
|
4220
3373
|
try {
|
|
4221
|
-
existing = await
|
|
3374
|
+
existing = await readFile8(target, "utf8");
|
|
4222
3375
|
} catch (err) {
|
|
4223
3376
|
if (err.code !== "ENOENT") throw err;
|
|
4224
3377
|
}
|
|
@@ -4335,33 +3488,69 @@ async function resolveSearchOnlyKey(index) {
|
|
|
4335
3488
|
}
|
|
4336
3489
|
|
|
4337
3490
|
// src/lib/algoliaDocs.ts
|
|
4338
|
-
import { readFileSync, existsSync as
|
|
4339
|
-
import { dirname as dirname8, join as
|
|
3491
|
+
import { readFileSync, readdirSync, existsSync as existsSync2 } from "node:fs";
|
|
3492
|
+
import { dirname as dirname8, join as join11 } from "node:path";
|
|
4340
3493
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4341
|
-
var DOCS_SUBPATH =
|
|
3494
|
+
var DOCS_SUBPATH = join11("docs", "algolia-sdk");
|
|
4342
3495
|
function findDocsDir() {
|
|
4343
3496
|
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
4344
3497
|
for (; ; ) {
|
|
4345
|
-
const candidate =
|
|
4346
|
-
if (
|
|
3498
|
+
const candidate = join11(dir, DOCS_SUBPATH);
|
|
3499
|
+
if (existsSync2(candidate)) return candidate;
|
|
4347
3500
|
const parent = dirname8(dir);
|
|
4348
3501
|
if (parent === dir) return void 0;
|
|
4349
3502
|
dir = parent;
|
|
4350
3503
|
}
|
|
4351
3504
|
}
|
|
4352
|
-
function
|
|
3505
|
+
function loadAlgoliaDoc(language) {
|
|
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) {
|
|
4353
3524
|
const docsDir = findDocsDir();
|
|
4354
3525
|
if (!docsDir) {
|
|
4355
3526
|
logger.warn("docs/algolia-sdk not found");
|
|
4356
3527
|
return "";
|
|
4357
3528
|
}
|
|
4358
|
-
const file =
|
|
4359
|
-
if (!
|
|
4360
|
-
logger.warn({ name,
|
|
3529
|
+
const file = join11(docsDir, `${name}-${language}.md`);
|
|
3530
|
+
if (!existsSync2(file)) {
|
|
3531
|
+
logger.warn({ name, language }, "named SDK reference not found");
|
|
4361
3532
|
return "";
|
|
4362
3533
|
}
|
|
4363
3534
|
return readFileSync(file, "utf8").trim();
|
|
4364
3535
|
}
|
|
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
|
+
}
|
|
4365
3554
|
|
|
4366
3555
|
// src/actions/implement.ts
|
|
4367
3556
|
var implementSchema = z24.object({
|
|
@@ -4396,11 +3585,12 @@ var implementSchema = z24.object({
|
|
|
4396
3585
|
});
|
|
4397
3586
|
var implementationOutputSchema = z24.object({
|
|
4398
3587
|
summary: z24.string(),
|
|
4399
|
-
// Ingestion only:
|
|
4400
|
-
//
|
|
4401
|
-
//
|
|
4402
|
-
//
|
|
4403
|
-
//
|
|
3588
|
+
// Ingestion only: how to run the generated script, as a structured pair the
|
|
3589
|
+
// wizard turns into an argv (`<runtime> <entrypoint>`) — never a free-form
|
|
3590
|
+
// command string. `runtime` is constrained to an allowlisted interpreter and
|
|
3591
|
+
// `entrypoint` is validated to a worktree-relative path before execution, so
|
|
3592
|
+
// the agent cannot inject extra commands or swap the interpreter.
|
|
3593
|
+
runtime: z24.enum(INGEST_RUNTIMES).optional(),
|
|
4404
3594
|
entrypoint: z24.string().optional()
|
|
4405
3595
|
});
|
|
4406
3596
|
var verificationOutputSchema = z24.object({
|
|
@@ -4410,11 +3600,47 @@ var verificationOutputSchema = z24.object({
|
|
|
4410
3600
|
});
|
|
4411
3601
|
var MAX_IMPLEMENT_VERIFICATION_ATTEMPTS = 3;
|
|
4412
3602
|
var DEFAULT_IMPLEMENT_USE_CASES = ["ingestion", "search"];
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
3603
|
+
var INGEST_DIR = ".algolia-wizard";
|
|
3604
|
+
function detectUiFramework(language) {
|
|
3605
|
+
const names = language.frameworks.map((f) => f.name.toLowerCase());
|
|
3606
|
+
if (names.some((n) => n.includes("vue") || n.includes("nuxt"))) return "Vue";
|
|
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()
|
|
4417
3627
|
);
|
|
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);
|
|
4418
3644
|
return [
|
|
4419
3645
|
{
|
|
4420
3646
|
name: `${prefix}ALGOLIA_APP_ID`,
|
|
@@ -4426,38 +3652,6 @@ function buildSearchEnvVars(language, strategy, appId, searchKey) {
|
|
|
4426
3652
|
}
|
|
4427
3653
|
];
|
|
4428
3654
|
}
|
|
4429
|
-
async function resolveIngestionProfile(ctx, language, repoRoot) {
|
|
4430
|
-
const { candidates, confirmed: confirmed3, onDisk } = await pickIngestionCandidates(
|
|
4431
|
-
repoRoot,
|
|
4432
|
-
language.languages.map((l) => l.name)
|
|
4433
|
-
);
|
|
4434
|
-
if (candidates.length === 0) {
|
|
4435
|
-
const chosen = confirmed3[0] ?? onDisk[0] ?? LANGUAGE_PROFILES[DEFAULT_LANGUAGE_ID];
|
|
4436
|
-
logger.warn(
|
|
4437
|
-
{
|
|
4438
|
-
confirmed: language.languages.map((l) => l.name),
|
|
4439
|
-
onDisk: onDisk.map((p) => p.id),
|
|
4440
|
-
chosen: chosen.id
|
|
4441
|
-
},
|
|
4442
|
-
"implement: no confirmed language matched a manifest on disk; falling back"
|
|
4443
|
-
);
|
|
4444
|
-
return chosen;
|
|
4445
|
-
}
|
|
4446
|
-
if (candidates.length === 1) return candidates[0];
|
|
4447
|
-
const backends = candidates.filter(isBackendLanguage);
|
|
4448
|
-
if (backends.length === 1) return backends[0];
|
|
4449
|
-
if (backends.length === 0) return candidates[0];
|
|
4450
|
-
if (isBackendLanguage(candidates[0])) return candidates[0];
|
|
4451
|
-
const options = backends.map((p) => p.displayName);
|
|
4452
|
-
const selection = await ctx.requestUserInput({
|
|
4453
|
-
prompt: "Which language should the ingestion script use?",
|
|
4454
|
-
promptType: "multipleChoice",
|
|
4455
|
-
options,
|
|
4456
|
-
defaultSelectedIndex: 0
|
|
4457
|
-
});
|
|
4458
|
-
const picked = typeof selection === "string" ? backends.find((p) => p.displayName === selection) : void 0;
|
|
4459
|
-
return picked ?? backends[0];
|
|
4460
|
-
}
|
|
4461
3655
|
function baseInstructions(input) {
|
|
4462
3656
|
return [
|
|
4463
3657
|
`Target Algolia index: ${input.targetIndex}`,
|
|
@@ -4485,48 +3679,37 @@ function sourceSpecificInstructions(input) {
|
|
|
4485
3679
|
generated: [
|
|
4486
3680
|
"No real data source exists; use sample records for each confirmed entity.",
|
|
4487
3681
|
"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.",
|
|
4488
|
-
"In the script, read and parse each returned
|
|
3682
|
+
"In the script, read and parse each returned file path at runtime (e.g. JSON.parse(readFileSync(...)) in Node/Bun, json.load(open(...)) in Python) instead of inlining the records as literals.",
|
|
4489
3683
|
"Add a prominent TODO where the developer swaps the generated records (and the JSON file under `.algolia-wizard/data/`) for their real record source."
|
|
4490
3684
|
]
|
|
4491
3685
|
};
|
|
4492
3686
|
return byLine[input.ingestionSource];
|
|
4493
3687
|
}
|
|
4494
3688
|
function ingestionInstructions(input) {
|
|
4495
|
-
const { ingestionProfile: profile, toolchain } = input;
|
|
4496
|
-
const { ingest } = toolchain;
|
|
4497
|
-
const extensions = ingest.entrypointExtensions.join(", ");
|
|
4498
|
-
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` : ""}.`;
|
|
4499
3689
|
return [
|
|
4500
3690
|
...input.confirmed && input.confirmed.length ? [
|
|
4501
|
-
`
|
|
3691
|
+
`Create an ingestion script under "${input.ingestDir}/" at the repo root.`,
|
|
4502
3692
|
`Ingest only these confirmed entities (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
|
|
4503
|
-
`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.
|
|
4504
|
-
|
|
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
|
+
"Use the appropriate Algolia package in the ingestion script. Do not use the raw HTTP API.",
|
|
4505
3695
|
"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.",
|
|
4506
|
-
getNamedDoc("save-records",
|
|
4507
|
-
|
|
3696
|
+
getNamedDoc("save-records", "js"),
|
|
3697
|
+
'Add algoliasearch to package.json "dependencies" with a valid version range; the wizard installs the worktree deps after you finish.',
|
|
4508
3698
|
"The summary should be extremely concise.",
|
|
4509
|
-
|
|
3699
|
+
`Return how to run the script as two fields, not a command string: "runtime" (one of ${INGEST_RUNTIMES.join(", ")}) and "entrypoint" (the script path relative to the worktree root, e.g. "${input.ingestDir}/ingest.mjs"). The wizard runs \`<runtime> <entrypoint>\` directly, so the entrypoint must be a plain path with no flags or arguments. Write a script one of those interpreters can run as-is.`,
|
|
4510
3700
|
...sourceSpecificInstructions(input)
|
|
4511
3701
|
] : []
|
|
4512
3702
|
];
|
|
4513
3703
|
}
|
|
4514
3704
|
function searchInstructions(input) {
|
|
4515
|
-
const doc =
|
|
4516
|
-
"instantsearch-setup",
|
|
4517
|
-
searchDocKey(input.searchStrategy)
|
|
4518
|
-
);
|
|
4519
|
-
const isTemplate = input.searchStrategy === "cdn-template";
|
|
4520
|
-
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.`;
|
|
3705
|
+
const doc = getFrameworkSpecificDoc(frameworksForDoc(input.uiFramework));
|
|
4521
3706
|
return [
|
|
4522
3707
|
"Implement an in-app Algolia search experience.",
|
|
4523
|
-
`Build the search UI for ${
|
|
4524
|
-
"Follow the Algolia reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
3708
|
+
`Build the search UI for ${input.uiFramework}.`,
|
|
3709
|
+
"Follow the Algolia JS SDK reference below for client setup and InstantSearch wiring; prefer it over prior knowledge:",
|
|
4525
3710
|
doc,
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
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.',
|
|
4529
|
-
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.",
|
|
3711
|
+
`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 \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
|
|
3712
|
+
"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.",
|
|
4530
3713
|
// appId always resolves (loadActiveProfile throws otherwise); only the
|
|
4531
3714
|
// search-only key is best-effort and can fall back to a placeholder.
|
|
4532
3715
|
`Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
|
|
@@ -4534,22 +3717,20 @@ function searchInstructions(input) {
|
|
|
4534
3717
|
// resolved app id / search-only key into ".env" under these exact names
|
|
4535
3718
|
// right after this step, so a renamed prefix here would leave the code
|
|
4536
3719
|
// reading a var the wizard never wrote.
|
|
4537
|
-
`Use exactly these env var names: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
3720
|
+
`Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
|
|
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.',
|
|
4538
3722
|
"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."
|
|
4539
3723
|
];
|
|
4540
3724
|
}
|
|
4541
3725
|
function verificationInstructions(input) {
|
|
4542
|
-
const protectedDirs = [
|
|
4543
|
-
.../* @__PURE__ */ new Set([input.ingestDir, ingestScriptDir(input.ingestionProfile)])
|
|
4544
|
-
];
|
|
4545
3726
|
return [
|
|
4546
3727
|
"Verify the Algolia implementation changes in the current worktree.",
|
|
4547
3728
|
`Verification tools found in the codebase: ${JSON.stringify(input.findings.verification ?? [])}.`,
|
|
4548
|
-
|
|
3729
|
+
"Call verifyImplementation at least once; it runs every repo-defined lint/typecheck/check script and returns per-check results plus an aggregate ok.",
|
|
4549
3730
|
"For issues caused by the implementation, make minimal fixes with writeFile and re-run verifyImplementation.",
|
|
4550
3731
|
"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.",
|
|
4551
3732
|
"Do not add new Algolia functionality here; only validate and make minimal correctness fixes.",
|
|
4552
|
-
`Do not modify ${
|
|
3733
|
+
`Do not modify "${input.ingestDir}/" unless verifyImplementation reports an actionable issue in its files.`,
|
|
4553
3734
|
"Always call reportStatus with status=success once verification has run, even when sufficient=false.",
|
|
4554
3735
|
"Set sufficient=true only when the implementation is complete and checks pass (or fail for a clearly unrelated reason).",
|
|
4555
3736
|
"Set sufficient=false when the implementation is incomplete or has implementation-caused failures; include concrete additionalInstructions for the next pass."
|
|
@@ -4558,17 +3739,14 @@ function verificationInstructions(input) {
|
|
|
4558
3739
|
var IMPLEMENT_CONFIG = {
|
|
4559
3740
|
ingestion: {
|
|
4560
3741
|
title: "Algolia ingestion",
|
|
4561
|
-
label: "Ingestion",
|
|
4562
3742
|
buildInstructions: ingestionInstructions
|
|
4563
3743
|
},
|
|
4564
3744
|
search: {
|
|
4565
3745
|
title: "Algolia search",
|
|
4566
|
-
label: "Search",
|
|
4567
3746
|
buildInstructions: searchInstructions
|
|
4568
3747
|
},
|
|
4569
3748
|
verification: {
|
|
4570
3749
|
title: "Algolia verification",
|
|
4571
|
-
label: "Verification",
|
|
4572
3750
|
buildInstructions: verificationInstructions
|
|
4573
3751
|
}
|
|
4574
3752
|
};
|
|
@@ -4600,10 +3778,11 @@ function buildAgentInstructions(useCase, input, extraInstructions = []) {
|
|
|
4600
3778
|
];
|
|
4601
3779
|
}
|
|
4602
3780
|
function formatSummary(useCase, summary) {
|
|
4603
|
-
|
|
3781
|
+
const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
|
|
3782
|
+
return `${label}: ${summary}`;
|
|
4604
3783
|
}
|
|
4605
|
-
function buildIngestCommand(worktree,
|
|
4606
|
-
return `cd ${shellQuote(worktree)} && ${
|
|
3784
|
+
function buildIngestCommand(worktree, runtime, entrypoint) {
|
|
3785
|
+
return `cd ${shellQuote(worktree)} && ${runtime} ${shellQuote(entrypoint)}`;
|
|
4607
3786
|
}
|
|
4608
3787
|
function parseIngestRecordCount(output) {
|
|
4609
3788
|
const match = output.match(/ALGOLIA_WIZARD_RECORD_COUNT=(\d+)/);
|
|
@@ -4685,7 +3864,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4685
3864
|
await confirmDirtyWorkingTree(ctx, repoRoot);
|
|
4686
3865
|
}
|
|
4687
3866
|
const normalized = normalizeFindingPaths(findings);
|
|
4688
|
-
const
|
|
3867
|
+
const confirmed2 = normalized.confirmedEntities;
|
|
4689
3868
|
const searchLocation = normalized.searchImplementationAnalysis;
|
|
4690
3869
|
let appId;
|
|
4691
3870
|
let searchKey;
|
|
@@ -4723,66 +3902,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4723
3902
|
);
|
|
4724
3903
|
}
|
|
4725
3904
|
}
|
|
4726
|
-
const ingestionProfile = await resolveIngestionProfile(
|
|
4727
|
-
ctx,
|
|
4728
|
-
language,
|
|
4729
|
-
worktree
|
|
4730
|
-
);
|
|
4731
|
-
const toolchain = await resolveToolchain(worktree, ingestionProfile);
|
|
4732
|
-
const verificationLanguages = [
|
|
4733
|
-
.../* @__PURE__ */ new Set([
|
|
4734
|
-
ingestionProfile.id,
|
|
4735
|
-
...(await detectProfilesFromManifests(worktree)).map((p) => p.id)
|
|
4736
|
-
])
|
|
4737
|
-
];
|
|
4738
|
-
const frameworkName = language.frameworks[0]?.name;
|
|
4739
|
-
const searchStrategy = resolveSearchStrategy(
|
|
4740
|
-
frameworkName,
|
|
4741
|
-
verificationLanguages.includes(JAVASCRIPT)
|
|
4742
|
-
);
|
|
4743
|
-
logger.info(
|
|
4744
|
-
{
|
|
4745
|
-
language: ingestionProfile.id,
|
|
4746
|
-
packageManager: toolchain.packageManager.id,
|
|
4747
|
-
ingest: toolchain.ingest.kind,
|
|
4748
|
-
framework: frameworkName,
|
|
4749
|
-
searchStrategy
|
|
4750
|
-
},
|
|
4751
|
-
"implement: resolved ingestion toolchain and search strategy"
|
|
4752
|
-
);
|
|
4753
3905
|
const input = {
|
|
4754
3906
|
findings: normalized,
|
|
4755
|
-
confirmed:
|
|
3907
|
+
confirmed: confirmed2,
|
|
4756
3908
|
searchLocation,
|
|
4757
3909
|
targetIndex,
|
|
4758
3910
|
language,
|
|
4759
3911
|
appId,
|
|
4760
3912
|
searchKey,
|
|
4761
|
-
searchEnvVars:
|
|
4762
|
-
language,
|
|
4763
|
-
searchStrategy,
|
|
4764
|
-
appId,
|
|
4765
|
-
searchKey
|
|
4766
|
-
),
|
|
3913
|
+
searchEnvVars: searchEnvVars(language, appId, searchKey),
|
|
4767
3914
|
ingestDir: INGEST_DIR,
|
|
4768
3915
|
ingestionSource,
|
|
4769
3916
|
uploadFilePath,
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
toolchain,
|
|
4774
|
-
verificationLanguages
|
|
3917
|
+
// language.frameworks already prefers the confirm-framework step output,
|
|
3918
|
+
// so the user's confirmed stack (not just raw detection) picks the flavor.
|
|
3919
|
+
uiFramework: detectUiFramework(language)
|
|
4775
3920
|
};
|
|
4776
|
-
const searchToolchain = !bundlesJavaScript(searchStrategy) ? void 0 : ingestionProfile.id === JAVASCRIPT ? toolchain : await resolveToolchain(worktree, LANGUAGE_PROFILES[JAVASCRIPT]);
|
|
4777
|
-
const toolchainForUseCase = (useCase) => useCase === "search" ? searchToolchain : toolchain;
|
|
4778
3921
|
const summaries = [];
|
|
4779
3922
|
if (uploadWarning) summaries.push(uploadWarning);
|
|
4780
3923
|
let agentRuns = 0;
|
|
3924
|
+
let ingestRuntime;
|
|
4781
3925
|
let ingestEntrypoint;
|
|
4782
3926
|
let ingestScriptRan = false;
|
|
4783
3927
|
let ingestRecordCount;
|
|
4784
3928
|
let ingestDurationMs;
|
|
4785
|
-
|
|
3929
|
+
let installFailed = false;
|
|
4786
3930
|
let ingestOutcomeMessage;
|
|
4787
3931
|
async function runImplementationUseCase(currentUseCase, extraInstructions = []) {
|
|
4788
3932
|
if (agentRuns > 0) ctx.recordStepExecution();
|
|
@@ -4796,19 +3940,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4796
3940
|
tools: toolsForUseCase(currentUseCase, input.ingestionSource),
|
|
4797
3941
|
outputSchema: implementationOutputSchema
|
|
4798
3942
|
});
|
|
4799
|
-
const useCaseToolchain = toolchainForUseCase(currentUseCase);
|
|
4800
|
-
if (!useCaseToolchain) return result;
|
|
4801
3943
|
ctx.notify({
|
|
4802
3944
|
messages: [`Installing dependencies for ${currentUseCase}\u2026`]
|
|
4803
3945
|
});
|
|
4804
3946
|
const installLogId = ctx.logStart("installWorktreeDeps", {
|
|
4805
|
-
useCase: currentUseCase
|
|
4806
|
-
language: useCaseToolchain.profile.id
|
|
3947
|
+
useCase: currentUseCase
|
|
4807
3948
|
});
|
|
4808
|
-
const install = await installWorktreeDeps(worktree
|
|
3949
|
+
const install = await installWorktreeDeps(worktree);
|
|
4809
3950
|
ctx.logEnd(installLogId, install.ok ? "success" : "error");
|
|
4810
3951
|
if (!install.ok) {
|
|
4811
|
-
|
|
3952
|
+
installFailed = true;
|
|
4812
3953
|
logger.warn(
|
|
4813
3954
|
{ useCase: currentUseCase, output: install.output },
|
|
4814
3955
|
"implement: dependency install in worktree failed; generated commands may not run until deps are installed"
|
|
@@ -4822,16 +3963,15 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4822
3963
|
return runAgent({
|
|
4823
3964
|
instructions: buildAgentInstructions("verification", input),
|
|
4824
3965
|
tools: toolsForUseCase("verification"),
|
|
4825
|
-
outputSchema: verificationOutputSchema
|
|
4826
|
-
// So verifyImplementation runs this repo's checks, not just npm scripts.
|
|
4827
|
-
languages: input.verificationLanguages
|
|
3966
|
+
outputSchema: verificationOutputSchema
|
|
4828
3967
|
});
|
|
4829
3968
|
}
|
|
4830
3969
|
if (useCases.includes("ingestion")) {
|
|
4831
|
-
const { summary, entrypoint } = await runImplementationUseCase("ingestion");
|
|
3970
|
+
const { summary, runtime, entrypoint } = await runImplementationUseCase("ingestion");
|
|
4832
3971
|
summaries.push(formatSummary("ingestion", summary));
|
|
3972
|
+
ingestRuntime = runtime;
|
|
4833
3973
|
ingestEntrypoint = entrypoint;
|
|
4834
|
-
if (
|
|
3974
|
+
if (ingestRuntime && ingestEntrypoint && !installFailed) {
|
|
4835
3975
|
ctx.clearNotices();
|
|
4836
3976
|
const runNow = await ctx.requestUserInput({
|
|
4837
3977
|
prompt: `Run the ingestion script now? This writes records to the "${targetIndex}" index.`,
|
|
@@ -4843,13 +3983,13 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4843
3983
|
const profile = await loadActiveProfile();
|
|
4844
3984
|
ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
|
|
4845
3985
|
const scriptLogId = ctx.logStart("runIngestScript", {
|
|
4846
|
-
|
|
3986
|
+
runtime: ingestRuntime,
|
|
4847
3987
|
entrypoint: ingestEntrypoint
|
|
4848
3988
|
});
|
|
4849
3989
|
const startedAt = Date.now();
|
|
4850
3990
|
const run2 = await runIngestScript(
|
|
4851
3991
|
worktree,
|
|
4852
|
-
|
|
3992
|
+
ingestRuntime,
|
|
4853
3993
|
ingestEntrypoint,
|
|
4854
3994
|
{
|
|
4855
3995
|
[APP_ID_VAR]: profile.appId,
|
|
@@ -4863,7 +4003,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4863
4003
|
ingestRecordCount = parseIngestRecordCount(run2.output);
|
|
4864
4004
|
if (ingestRecordCount != null) {
|
|
4865
4005
|
track("AI Wizard Ingest Successful", {
|
|
4866
|
-
entity_name:
|
|
4006
|
+
entity_name: confirmed2?.map((e) => e.name).join(", ") || "unknown",
|
|
4867
4007
|
record_count: ingestRecordCount,
|
|
4868
4008
|
duration_ms: ingestDurationMs
|
|
4869
4009
|
});
|
|
@@ -4876,7 +4016,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
|
|
|
4876
4016
|
outcomeMessage = `\u26A0\uFE0F The ingestion script did not run: ${run2.reason}`;
|
|
4877
4017
|
logger.warn(
|
|
4878
4018
|
{
|
|
4879
|
-
|
|
4019
|
+
runtime: ingestRuntime,
|
|
4880
4020
|
entrypoint: ingestEntrypoint,
|
|
4881
4021
|
reason: run2.reason
|
|
4882
4022
|
},
|
|
@@ -4899,7 +4039,7 @@ ${run2.output}` : status;
|
|
|
4899
4039
|
outcomeMessage = `\u274C Ingestion failed.${run2.output ? ` ${run2.output}` : ""}`;
|
|
4900
4040
|
logger.warn(
|
|
4901
4041
|
{
|
|
4902
|
-
|
|
4042
|
+
runtime: ingestRuntime,
|
|
4903
4043
|
entrypoint: ingestEntrypoint,
|
|
4904
4044
|
output: run2.output
|
|
4905
4045
|
},
|
|
@@ -4916,28 +4056,10 @@ ${run2.output}` : status;
|
|
|
4916
4056
|
}
|
|
4917
4057
|
}
|
|
4918
4058
|
const commandMessages = [`Open the worktree: cd ${shellQuote(worktree)}`];
|
|
4919
|
-
if (ingestEntrypoint) {
|
|
4059
|
+
if (ingestRuntime && ingestEntrypoint) {
|
|
4920
4060
|
commandMessages.push(
|
|
4921
|
-
`Ingestion command: ${buildIngestCommand(worktree,
|
|
4061
|
+
`Ingestion command: ${buildIngestCommand(worktree, ingestRuntime, ingestEntrypoint)}`
|
|
4922
4062
|
);
|
|
4923
|
-
if (toolchain.ingest.kind === "manual") {
|
|
4924
|
-
commandMessages.push(
|
|
4925
|
-
`The wizard does not run ${ingestionProfile.displayName} ${toolchain.packageManager.id} projects \u2014 run the command above yourself to ingest.`
|
|
4926
|
-
);
|
|
4927
|
-
const missingTask = await missingBuildTask(worktree, toolchain);
|
|
4928
|
-
if (missingTask) {
|
|
4929
|
-
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.`;
|
|
4930
|
-
commandMessages.push(warning);
|
|
4931
|
-
summaries.push(warning);
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
if (ingestionSource === "local") {
|
|
4936
|
-
const limitation = localSourceLimitation(worktree, ingestionProfile);
|
|
4937
|
-
if (limitation) {
|
|
4938
|
-
commandMessages.push(`\u26A0\uFE0F ${limitation}`);
|
|
4939
|
-
summaries.push(`\u26A0\uFE0F ${limitation}`);
|
|
4940
|
-
}
|
|
4941
4063
|
}
|
|
4942
4064
|
await ctx.requestUserInput({
|
|
4943
4065
|
// No question being asked here, just an acknowledgement — the
|
|
@@ -4948,20 +4070,7 @@ ${run2.output}` : status;
|
|
|
4948
4070
|
messages: ingestOutcomeMessage ? [ingestOutcomeMessage, ...commandMessages] : commandMessages
|
|
4949
4071
|
});
|
|
4950
4072
|
}
|
|
4951
|
-
|
|
4952
|
-
if (skipSearch) {
|
|
4953
|
-
const target = describeSearchTarget(
|
|
4954
|
-
input.searchStrategy,
|
|
4955
|
-
input.frameworkName
|
|
4956
|
-
);
|
|
4957
|
-
summaries.push(
|
|
4958
|
-
`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).`
|
|
4959
|
-
);
|
|
4960
|
-
track("AI Wizard Search UI Skipped", {
|
|
4961
|
-
framework: input.frameworkName ?? "unknown"
|
|
4962
|
-
});
|
|
4963
|
-
}
|
|
4964
|
-
if (useCases.includes("search") && !skipSearch) {
|
|
4073
|
+
if (useCases.includes("search")) {
|
|
4965
4074
|
let extraInstructions = [];
|
|
4966
4075
|
const preSearchFiles = new Set(await listChangedFiles(worktree));
|
|
4967
4076
|
for (let attempt = 1; attempt <= MAX_IMPLEMENT_VERIFICATION_ATTEMPTS; attempt++) {
|
|
@@ -5032,9 +4141,9 @@ ${run2.output}` : status;
|
|
|
5032
4141
|
"implement: agent reported success but no files changed in the worktree"
|
|
5033
4142
|
);
|
|
5034
4143
|
}
|
|
5035
|
-
if (
|
|
4144
|
+
if (installFailed) {
|
|
5036
4145
|
summaries.push(
|
|
5037
|
-
|
|
4146
|
+
'\u26A0\uFE0F Dependency install in the worktree failed. Run your package manager install in the worktree before the command below, or it will fail with "Cannot find module".'
|
|
5038
4147
|
);
|
|
5039
4148
|
}
|
|
5040
4149
|
return {
|
|
@@ -5042,10 +4151,10 @@ ${run2.output}` : status;
|
|
|
5042
4151
|
filesChanged,
|
|
5043
4152
|
summary: summaries.join("\n\n"),
|
|
5044
4153
|
worktreePath: worktree,
|
|
5045
|
-
...useCases.includes("ingestion") && ingestEntrypoint ? {
|
|
4154
|
+
...useCases.includes("ingestion") && ingestRuntime && ingestEntrypoint ? {
|
|
5046
4155
|
ingestCommand: buildIngestCommand(
|
|
5047
4156
|
worktree,
|
|
5048
|
-
|
|
4157
|
+
ingestRuntime,
|
|
5049
4158
|
ingestEntrypoint
|
|
5050
4159
|
),
|
|
5051
4160
|
ingestScriptRan,
|
|
@@ -5374,20 +4483,20 @@ function parseCliArgs(argv) {
|
|
|
5374
4483
|
}
|
|
5375
4484
|
|
|
5376
4485
|
// src/lib/resetState.ts
|
|
5377
|
-
import { readdir as
|
|
5378
|
-
import { join as
|
|
4486
|
+
import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
4487
|
+
import { join as join12 } from "node:path";
|
|
5379
4488
|
var KEEP = ["wizard.log"];
|
|
5380
4489
|
async function resetProjectState() {
|
|
5381
4490
|
const dir = stateDir();
|
|
5382
4491
|
let entries;
|
|
5383
4492
|
try {
|
|
5384
|
-
entries = await
|
|
4493
|
+
entries = await readdir4(dir);
|
|
5385
4494
|
} catch {
|
|
5386
4495
|
return { dir, removed: [] };
|
|
5387
4496
|
}
|
|
5388
4497
|
const targets = entries.filter((name) => !KEEP.includes(name));
|
|
5389
4498
|
await Promise.all(
|
|
5390
|
-
targets.map((name) => rm2(
|
|
4499
|
+
targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
|
|
5391
4500
|
);
|
|
5392
4501
|
return { dir, removed: targets };
|
|
5393
4502
|
}
|