@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 CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/ui/App.tsx
7
- import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize7 } from "ink";
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 Box5, Text as Text5, useInput as useInput2 } from "ink";
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 useState4 } from "react";
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 Box4, Text as Text4, measureElement as measureElement2, useInput, useWindowSize as useWindowSize3 } from "ink";
529
- import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
530
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
531
- var CANCEL = "cancel";
532
- var ARROW_WIDTH = 4;
533
- var COLUMN_GAP = 2;
534
- var BAR_PADDING = 2;
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] = useState3(
639
+ const [index, setIndex] = useState4(
556
640
  () => defaultSelectedIndex > 0 && defaultSelectedIndex < options.length ? defaultSelectedIndex : 0
557
641
  );
558
- const [checked, setChecked] = useState3(() => /* @__PURE__ */ new Set());
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 = useRef2(null);
567
- const { columns } = useWindowSize3();
568
- const [width, setWidth] = useState3(columns);
569
- useLayoutEffect(() => {
570
- if (containerRef.current) {
571
- setWidth(fittedWidth(containerRef.current, columns));
572
- }
573
- }, [columns]);
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(Box4, { ref: containerRef, flexGrow: 1, children: /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", gap: 1, width, children: [
613
- error && /* @__PURE__ */ jsx4(Text4, { color: COLORS.danger, children: error }),
614
- messages?.map((m, i) => /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: m }, `msg-${i}`)),
615
- table && /* @__PURE__ */ jsx4(Table, { columns: table.columns, rows: table.rows }),
616
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
617
- question && /* @__PURE__ */ jsx4(Text4, { color: COLORS.muted, children: question }),
618
- helpText && /* @__PURE__ */ jsx4(Text4, { color: COLORS.dim, children: helpText })
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(Box4, { flexDirection: "column", children: rows.map((option, i) => {
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__ */ jsxs3(Text4, { color: labelColor, wrap: "truncate", children: [
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__ */ jsxs3(
633
- Box4,
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(Box4, { width: isText ? labelWidth : barLabelWidth, children: label }),
641
- isText && textWidth > 0 && /* @__PURE__ */ jsx4(Box4, { width: textWidth, children: /* @__PURE__ */ jsx4(
642
- Text4,
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(Box4, { width: badgeWidth, justifyContent: "flex-end", children: /* @__PURE__ */ jsx4(Text4, { color: COLORS.badge, wrap: "truncate", children: sec.value }) })
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(Text4, { children: hints.map(({ key, label }, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
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(Text4, { color: COLORS.primary, children: key }),
658
- /* @__PURE__ */ jsxs3(Text4, { color: COLORS.dim, children: [
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 jsxs4 } from "react/jsx-runtime";
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__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
679
- messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
680
- question && /* @__PURE__ */ jsx5(Text5, { color: COLORS.primary, children: question }),
681
- /* @__PURE__ */ jsxs4(Box5, { gap: 1, flexDirection: "column", children: [
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] = useState4("");
785
+ const [draft, setDraft] = useState5("");
690
786
  if (phase === "done" || phase === "error") {
691
- return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: "Press Enter or Esc to exit" }) });
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(Box5, { children: /* @__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(Box5, { children: /* @__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(Box5, { children: /* @__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(Box5, { children: /* @__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__ */ jsxs4(Box5, { flexDirection: "column", children: [
761
- inputReq.error && /* @__PURE__ */ jsx5(Text5, { color: COLORS.danger, children: inputReq.error }),
762
- inputReq.messages?.map((m, i) => /* @__PURE__ */ jsx5(Text5, { color: COLORS.muted, children: m }, `msg-${i}`)),
763
- /* @__PURE__ */ jsxs4(Box5, { children: [
764
- /* @__PURE__ */ jsxs4(Text5, { color: COLORS.primary, children: [
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 Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
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 stack",
800
- description: "React, Vue, Angular, Rails, Django, Laravel & more"
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 UI, wired into your app or templates"
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 jsxs5 } from "react/jsx-runtime";
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__ */ jsxs5(Box6, { flexDirection: "column", children: [
821
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, children: [
822
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.success, children: "\u2192" }),
823
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.strong, bold: true, children: title })
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__ */ jsxs5(Box6, { flexDirection: "row", gap: 2, children: [
921
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 2, children: [
826
922
  /* @__PURE__ */ jsx6(Spacer, {}),
827
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: description })
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 } = useWindowSize4();
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__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
949
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
854
950
  /* @__PURE__ */ jsx6(
855
- Box6,
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__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
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(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
874
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
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__ */ jsxs5(
882
- Box6,
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(Text6, { color: COLORS.muted, children: "WHAT THIS WIZARD WILL DO" }),
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 Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize5 } from "ink";
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 your dependency manifests (package.json, pyproject.toml, Gemfile, go.mod, pom.xml\u2026), configs & source to detect your stack. Read-only; nothing is uploaded."
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 jsxs6 } from "react/jsx-runtime";
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__ */ jsxs6(Text7, { children: [
956
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: "\u2502" }),
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(Text7, { color: s.color, bold: s.bold, children: s.text }, i)),
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(Text7, { color: COLORS.danger, children: "\u2502" })
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 } = useWindowSize5();
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__ */ jsxs6(
973
- Box7,
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(Text7, { color: COLORS.strong, bold: true, children: "What algolia wizard accesses" }),
982
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: accessIntro }),
983
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: accessItems.map((item) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
984
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.border, children: "\u2500".repeat(dividerWidth) }),
985
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, marginTop: 1, children: [
986
- /* @__PURE__ */ jsx7(Box7, { width: TAG_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ jsx7(Text7, { color: TAG_COLORS[item.tag], bold: true, children: `[${item.tag}]` }) }),
987
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Text7, { children: [
988
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: item.title }),
989
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: ` \u2014 ${item.description}` })
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__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
994
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.danger, children: `\u256D${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256E` }),
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__ */ jsxs6(Fragment2, { children: [
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(Text7, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1114
+ /* @__PURE__ */ jsx7(Text8, { color: COLORS.danger, children: `\u2570${"\u2500".repeat(Math.max(0, dividerWidth - 2))}\u256F` })
1019
1115
  ] }),
1020
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: policyLinks.map((link) => /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1021
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.strong, bold: true, children: `${link.label}:` }),
1022
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.accent, children: link.url })
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__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "row", gap: 3, children: [
1025
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1026
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1027
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "esc" }),
1028
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "] back" })
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__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
1031
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "[" }),
1032
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.primary, children: "enter" }),
1033
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.muted, children: "]" }),
1034
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.success, bold: true, children: "start wizard" })
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 Box10, Text as Text10 } from "ink";
1139
+ import { Box as Box11, Text as Text11 } from "ink";
1044
1140
 
1045
1141
  // src/ui/Steps.tsx
1046
- import { Box as Box8, Text as Text8 } from "ink";
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 jsxs7 } from "react/jsx-runtime";
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(Box8, { flexDirection: "column", gap: 1, children: visibleSteps.map((s) => /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs7(Text8, { color: COLORS.status[s.status], children: [
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__ */ jsxs7(Text8, { color: COLORS.status.running, children: [
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 Box9, Text as Text9 } from "ink";
1098
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
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__ */ jsxs8(Box9, { flexDirection: "row", gap: 1, children: [
1106
- /* @__PURE__ */ jsx9(Text9, { color: COLORS.muted, children: "STEP" }),
1107
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: activeStepNumber }),
1108
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "/" }),
1109
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: visibleSteps.length })
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 jsxs9 } from "react/jsx-runtime";
1216
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1121
1217
  function Sidebar() {
1122
- return /* @__PURE__ */ jsxs9(
1123
- Box10,
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__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1133
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: "PROGRESS" }),
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__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
1232
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", gap: 1, children: [
1137
1233
  /* @__PURE__ */ jsx10(Progress, {}),
1138
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: sidebarCommands.map((c) => {
1139
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", gap: 1, children: [
1140
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.primary, children: `[${c.keyHint}]` }),
1141
- /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: c.description })
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 Box11, Text as Text11 } from "ink";
1152
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
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__ */ jsxs10(
1156
- Box11,
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__ */ jsxs10(Box11, { flexDirection: "row", gap: 1, children: [
1167
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.primary, children: `[${firstCommand.keyHint}]` }),
1168
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.muted, children: firstCommand.description })
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 { useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState5 } from "react";
1180
- import { Box as Box12, Text as Text12, measureElement as measureElement3, useInput as useInput5, useWindowSize as useWindowSize6 } from "ink";
1181
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
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 { rows, columns } = useWindowSize6();
1212
- const viewportRef = useRef3(null);
1213
- const [viewportHeight, setViewportHeight] = useState5(0);
1214
- const [viewportWidth, setViewportWidth] = useState5(0);
1215
- const [scrollOffset, setScrollOffset] = useState5(0);
1216
- const prevMaxOffsetRef = useRef3(0);
1217
- useLayoutEffect2(() => {
1218
- if (!viewportRef.current) return;
1219
- const { width, height } = measureElement3(viewportRef.current);
1220
- setViewportHeight(height);
1221
- setViewportWidth(width);
1222
- }, [rows, columns, logs.length === 0]);
1223
- let capacity = viewportHeight;
1224
- for (let i = 0; i < 2; i++) {
1225
- const hasAbove = scrollOffset > 0;
1226
- const hasBelow = scrollOffset + capacity < logs.length;
1227
- capacity = Math.max(
1228
- viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
1229
- 0
1230
- );
1231
- }
1232
- const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
1233
- const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
1234
- useLayoutEffect2(() => {
1235
- const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
1236
- prevMaxOffsetRef.current = maxOffset;
1237
- setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
1238
- }, [maxOffset]);
1306
+ const scroll = useScrollWindow({ itemCount: logs.length, followBottom: true });
1239
1307
  useInput5((_input, key) => {
1240
- if (!key.upArrow && !key.downArrow) return;
1241
- setScrollOffset(
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(scrollOffset, scrollOffset + capacity);
1246
- const hiddenAbove = scrollOffset;
1247
- const hiddenBelow = logs.length - scrollOffset - visible.length;
1248
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1249
- logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
1250
- /* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1251
- hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1252
- "\u2191 ",
1253
- hiddenAbove,
1254
- " more"
1255
- ] }),
1256
- visible.map((entry) => {
1257
- const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1258
- const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1259
- const rawPreview = rawInputText(entry.input);
1260
- const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
1261
- const gaps = (partCount - 1) * ROW_GAP;
1262
- let budget = viewportWidth - timestamp.length - durationText.length - gaps;
1263
- const name = truncate2(entry.name, budget);
1264
- budget -= name.length;
1265
- const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1266
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
1267
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
1268
- /* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
1269
- preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
1270
- durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
1271
- ] }, entry.id);
1272
- }),
1273
- hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1274
- "\u2193 ",
1275
- hiddenBelow,
1276
- " more"
1277
- ] })
1278
- ] }),
1279
- /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
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 jsxs12 } from "react/jsx-runtime";
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__ */ jsxs12(
1515
- Box13,
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 && /* @__PURE__ */ jsxs12(
1523
- Box13,
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 width beside the sidebar; row layout only (would grow vertically when stacked). */
1531
- /* @__PURE__ */ jsxs12(
1532
- Box13,
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: showSidebar ? 1 : 0,
1598
+ flexGrow: 1,
1539
1599
  children: [
1540
1600
  /* @__PURE__ */ jsx13(Notices, {}),
1541
1601
  /* @__PURE__ */ jsx13(PromptInput, {}),
1542
- phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1543
- phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
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 readdir3, readFile as readFile8 } from "node:fs/promises";
2165
- import { join as join10 } from "node:path";
2166
-
2167
- // src/lib/languages.ts
2168
- import { readdir as readdir2, readFile as readFile7 } from "node:fs/promises";
2169
- import { existsSync as existsSync2 } from "node:fs";
2170
- import { join as join9 } from "node:path";
2171
-
2172
- // src/lib/tools/utils/packageManager.ts
2173
- import { readFile as readFile6 } from "node:fs/promises";
2174
- import { existsSync } from "node:fs";
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 readdir3(dir, { withFileTypes: true })) {
2901
- if (e.name.startsWith(".") || ALL_SKIP_DIRS.has(e.name)) continue;
2902
- const full = join10(dir, e.name);
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 readFile8(file, "utf8");
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
- var INSTALL_TIMEOUT_MS = 15 * 6e4;
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
- shell: false,
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) => settle({
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 runCheck(command, binary, args) {
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
- return {
3028
- limitation: `Could not read package.json to detect verification conventions: ${err.message}`
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
- return {
3035
- limitation: `No verification script found in package.json (looked for: ${VERIFICATION_SCRIPT_CANDIDATES.join(", ")}).`
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
- checks.push(
3042
- await runCheck(`${pm} run ${script}`, pm, ["run", script])
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(ctx) {
2364
+ function verifyImplementationTool() {
3091
2365
  return tool8({
3092
- description: "Run the repo's mechanical verification checks for generated implementation changes. Uses the conventions of the repo's languages (package.json lint/typecheck/check scripts for JavaScript, the equivalent compile/analyze command elsewhere) and returns structured pass/fail evidence for the verifier to interpret.",
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({ languages: ctx.languages }, "called verifyImplementation tool");
3096
- return runRepoVerificationCheck(ctx.languages);
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(ctx)
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({ languages: req.languages });
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
- "Start from the dependency manifests: package.json, pyproject.toml, requirements.txt, Gemfile, composer.json, go.mod, pom.xml, build.gradle(.kts), *.csproj, build.sbt, Package.swift, pubspec.yaml.",
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
- "It may be a client-side component or a server-rendered template \u2014 return whichever the project actually renders its UI from (e.g. /layouts/header.tsx, app/views/layouts/application.html.erb, templates/base.html, resources/views/layouts/app.blade.php, templates/base.html.twig).",
3462
- "Return one file path as searchImplementationAnalysis.",
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 the dependency manifest and config files for the project's languages: package.json scripts with tsconfig/eslint/prettier, pyproject.toml or setup.cfg (ruff, mypy, black), Gemfile with .rubocop.yml, composer.json scripts (phpstan, pint), go.mod with a golangci-lint config, Maven/Gradle verification tasks, .NET analyzers, analysis_options.yaml.",
3473
- 'Return the tool names as an array, e.g. ["eslint", "prettier", "tsc"] or ["ruff", "mypy"].',
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-rc.67.55",
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:toolchains": "tsx ./scripts/verify-toolchains.ts",
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("confirmLanguage received an unexpected non-text result");
2876
+ throw new Error("askList received an unexpected non-text result");
3624
2877
  }
3625
- const name = parseEntries(answer)[0]?.name;
3626
- if (name) return name;
3627
- prompt = "please enter a language name:";
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 detectedLanguages = detected.languages ?? [];
3633
- const others = (chosen) => detectedLanguages.filter((l) => !isSameLanguage(l.name, chosen));
3634
- const primary = detectedLanguages[0];
3635
- if (primary) {
3636
- const accepted = await ctx.requestUserInput({
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
- if (typeof selection !== "string") {
3668
- throw new Error("confirmLanguage received an unexpected non-text result");
3669
- }
3670
- const name = selection === OTHER_OPTION ? await askOtherLanguage(ctx) : selection;
3671
- const version = detectedFor(name)?.version ?? "unknown";
3672
- return confirmed([{ name, version }, ...others(name)]);
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 OTHER_OPTION2 = "Other";
3781
- function confirmed2(name, version) {
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(OTHER_OPTION2);
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 confirmed2(primary.name, primary.version);
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 the framework that renders your UI",
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 === OTHER_OPTION2) {
3839
- return confirmed2(await askOtherFramework(ctx));
2999
+ if (selection === OTHER_OPTION) {
3000
+ return confirmed(await askOtherFramework(ctx));
3840
3001
  }
3841
- return confirmed2(selection, detectedFor(selection)?.version);
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 confirmed3 = typeof selection === "string" ? entities.filter((e) => e.name === selection) : [];
3935
- if (confirmed3.length === 0) {
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", confirmed3);
3099
+ ctx.setUserInput("confirmedEntities", confirmed2);
3939
3100
  track("AI Wizard Entities Confirmed", {
3940
- entities: toEntitySummary(confirmed3)
3101
+ entities: toEntitySummary(confirmed2)
3941
3102
  });
3942
- return { ingestionAnalysis: entities, confirmedEntities: confirmed3 };
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/") || step.includes("AlgoliaWizardIngest");
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 { existsSync as existsSync4 } from "node:fs";
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 join12,
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 = join12(stateDir(repoRoot), "worktrees");
4050
- const stale = (await readdir4(dir).catch(() => [])).filter((name) => /^wizard-implement-\d+$/.test(name)).sort().reverse().slice(MAX_WIZARD_WORKTREES - 1);
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
- join12(dir, slug)
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 = join12(stateDir(repoRoot), "worktrees", dirSlug);
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 spawnStep(worktreePath, argv) {
4082
- const { code, output } = await runCommand(argv[0], [...argv.slice(1)], {
4083
- cwd: worktreePath,
4084
- timeoutMs: INSTALL_TIMEOUT_MS
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
- return { ok: true, output: outputs.join("\n").trim() };
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
- function validateIngestEntrypoint(worktreePath, entrypoint, allowedExtensions) {
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, toolchain, entrypoint, env = {}) {
4146
- const { ingest, profile, packageManager } = toolchain;
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: `${profile.displayName} (${packageManager.id}) projects must be run manually: ${ingest.runCommand}`
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
- const argv = resolveIngestArgv(ingest, entrypoint);
4181
- const { code, output } = await runCommand(argv[0], argv.slice(1), {
4182
- cwd: worktreePath,
4183
- env,
4184
- timeoutMs: INGEST_TIMEOUT_MS
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 = join12(ingestDir, basename2(source));
4202
- const dest = join12(worktreePath, relPath);
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 = join12(worktreePath, ".env");
3371
+ const target = join10(worktreePath, ".env");
4219
3372
  let existing = "";
4220
3373
  try {
4221
- existing = await readFile9(target, "utf8");
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 existsSync5 } from "node:fs";
4339
- import { dirname as dirname8, join as join13 } from "node:path";
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 = join13("docs", "algolia-sdk");
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 = join13(dir, DOCS_SUBPATH);
4346
- if (existsSync5(candidate)) return candidate;
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 getNamedDoc(name, key) {
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 = join13(docsDir, `${name}-${key}.md`);
4359
- if (!existsSync5(file)) {
4360
- logger.warn({ name, key }, "named SDK reference not found");
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: the script the wizard should run, as a bare path — never a
4400
- // command string, and never the interpreter. The command comes from the
4401
- // resolved language toolchain (a registry constant); this path is validated to
4402
- // a worktree-relative file with a runnable extension and substituted into it.
4403
- // So the agent contributes no part of the command that gets executed.
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
- function buildSearchEnvVars(language, strategy, appId, searchKey) {
4414
- const prefix = publicEnvPrefix(
4415
- language.frameworks.map((framework) => framework.name),
4416
- strategy
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 JSON file path at runtime using the idiomatic file read for the language you are writing in (the SDK reference above shows one) instead of inlining the records as literals.",
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
- `Write the ingestion script in ${profile.displayName}, at "${ingestScriptDir(profile)}/" in the repo.`,
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. ${profile.envReadInstruction} The wizard sets these when it runs the script.`,
4504
- `Use the official Algolia ${profile.displayName} client (${profile.sdk.packageName}). Do not use the raw HTTP API, and do not use a client for another language.`,
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", profile.sdk.docKey),
4507
- dependencyInstruction(toolchain),
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
- runInstruction,
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 = getNamedDoc(
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 ${describeSearchTarget(input.searchStrategy, input.frameworkName)}.`,
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
- placement,
4527
- `It needs at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
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
- `Call verifyImplementation at least once; it runs the mechanical checks available for this repo's languages (${input.verificationLanguages.join(", ")}) and returns per-check results plus an aggregate ok.`,
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 ${protectedDirs.map((dir) => `"${dir}/"`).join(" or ")} unless verifyImplementation reports an actionable issue in its files.`,
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
- return `${IMPLEMENT_CONFIG[useCase].label}: ${summary}`;
3781
+ const label = useCase === "ingestion" ? "Ingestion" : useCase === "search" ? "Search" : "Verification";
3782
+ return `${label}: ${summary}`;
4604
3783
  }
4605
- function buildIngestCommand(worktree, toolchain, entrypoint) {
4606
- return `cd ${shellQuote(worktree)} && ${describeIngestCommand(toolchain.ingest, entrypoint)}`;
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 confirmed3 = normalized.confirmedEntities;
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: confirmed3,
3907
+ confirmed: confirmed2,
4756
3908
  searchLocation,
4757
3909
  targetIndex,
4758
3910
  language,
4759
3911
  appId,
4760
3912
  searchKey,
4761
- searchEnvVars: buildSearchEnvVars(
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
- searchStrategy,
4771
- frameworkName,
4772
- ingestionProfile,
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
- const failedInstalls = /* @__PURE__ */ new Set();
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, useCaseToolchain);
3949
+ const install = await installWorktreeDeps(worktree);
4809
3950
  ctx.logEnd(installLogId, install.ok ? "success" : "error");
4810
3951
  if (!install.ok) {
4811
- failedInstalls.add(useCaseToolchain.profile.displayName);
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 (ingestEntrypoint && toolchain.ingest.kind === "auto" && failedInstalls.size === 0) {
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
- language: ingestionProfile.id,
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
- toolchain,
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: confirmed3?.map((e) => e.name).join(", ") || "unknown",
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
- language: ingestionProfile.id,
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
- language: ingestionProfile.id,
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, toolchain, ingestEntrypoint)}`
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
- const skipSearch = useCases.includes("search") && !canScaffoldSearchUI(input.searchStrategy);
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 (failedInstalls.size > 0) {
4144
+ if (installFailed) {
5036
4145
  summaries.push(
5037
- `\u26A0\uFE0F Dependency install in the worktree failed. Install the ${[...failedInstalls].join(" and ")} dependencies in the worktree before the command below, or it will fail on a missing package.`
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
- toolchain,
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 readdir5, rm as rm2 } from "node:fs/promises";
5378
- import { join as join14 } from "node:path";
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 readdir5(dir);
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(join14(dir, name), { recursive: true, force: true }))
4499
+ targets.map((name) => rm2(join12(dir, name), { recursive: true, force: true }))
5391
4500
  );
5392
4501
  return { dir, removed: targets };
5393
4502
  }