yourskills 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +22 -8
  2. package/dist/main.js +2533 -878
  3. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -49,7 +49,13 @@ async function resolveAgents(flagAgents) {
49
49
  return config.agents ?? [];
50
50
  }
51
51
  // src/settings/keys.ts
52
- var CONFIG_KEYS = ["scope", "agents", "server", "track"];
52
+ var CONFIG_KEYS = [
53
+ "scope",
54
+ "agents",
55
+ "server",
56
+ "track",
57
+ "autoUpdate"
58
+ ];
53
59
  function isConfigKey(key) {
54
60
  return CONFIG_KEYS.includes(key);
55
61
  }
@@ -65,6 +71,8 @@ function configValue(config, key) {
65
71
  return config.serverUrl ?? UNSET;
66
72
  case "track":
67
73
  return config.track === undefined ? UNSET : String(config.track);
74
+ case "autoUpdate":
75
+ return String(config.autoUpdate !== false);
68
76
  }
69
77
  }
70
78
  function setConfigValue(config, key, value) {
@@ -125,6 +133,17 @@ function setConfigValue(config, key, value) {
125
133
  const track = value === "true";
126
134
  return { ok: true, config: { ...config, track }, shown: String(track) };
127
135
  }
136
+ case "autoUpdate": {
137
+ if (value !== "true" && value !== "false") {
138
+ return { ok: false, message: "autoUpdate must be `true` or `false`." };
139
+ }
140
+ const autoUpdate = value === "true";
141
+ return {
142
+ ok: true,
143
+ config: { ...config, autoUpdate },
144
+ shown: String(autoUpdate)
145
+ };
146
+ }
128
147
  }
129
148
  }
130
149
  function configLines(config) {
@@ -367,17 +386,25 @@ async function requireToken(server) {
367
386
  async function hasSession() {
368
387
  return Boolean((await readConfig()).token);
369
388
  }
389
+ async function localAccount(server) {
390
+ const config = await readConfig();
391
+ return {
392
+ signedIn: Boolean(config.token),
393
+ email: config.userEmail ?? null,
394
+ server: serverUrl(config, server)
395
+ };
396
+ }
370
397
  // src/auth/screens/gate.tsx
371
- import { Box as Box5, Text as Text7, useApp as useApp2, useInput } from "ink";
398
+ import { Box as Box6, Text as Text8, useApp as useApp2, useInput as useInput3 } from "ink";
372
399
  import {
373
400
  useCallback,
374
401
  useEffect as useEffect3,
375
402
  useMemo,
376
- useState as useState3
403
+ useState as useState4
377
404
  } from "react";
378
405
 
379
- // src/ui/command.tsx
380
- import { Box as Box2, Text as Text3 } from "ink";
406
+ // src/ui/back.tsx
407
+ import { useInput } from "ink";
381
408
 
382
409
  // ../../packages/ui/src/lib/ascii.ts
383
410
  var DEFAULT_ASCII_CHARS = {
@@ -502,23 +529,157 @@ function clip(text, width) {
502
529
  return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}~`;
503
530
  }
504
531
 
532
+ // ../../packages/skills/src/lifecycle.ts
533
+ var LIFECYCLES = [
534
+ { status: "active", word: "active", glyph: "#", tone: "state-active" },
535
+ {
536
+ status: "experimental",
537
+ word: "experimental",
538
+ glyph: "?",
539
+ tone: "state-experimental"
540
+ },
541
+ {
542
+ status: "deprecated",
543
+ word: "deprecated",
544
+ glyph: "x",
545
+ tone: "state-deprecated"
546
+ }
547
+ ];
548
+ var LIFECYCLE_ORDER = LIFECYCLES.map((lifecycle) => lifecycle.status);
549
+ var BY_STATUS = new Map(LIFECYCLES.map((lifecycle) => [lifecycle.status, lifecycle]));
550
+ function lifecycleOf(status) {
551
+ const lifecycle = BY_STATUS.get(status);
552
+ if (!lifecycle) {
553
+ throw new Error(`No lifecycle vocabulary for "${status}". Add it to LIFECYCLES.`);
554
+ }
555
+ return lifecycle;
556
+ }
557
+ // src/ui/rows.tsx
558
+ import { Box as Box2, Text as Text2 } from "ink";
559
+ import { jsx as jsx2, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
560
+ function lifecycleTag(status) {
561
+ if (!status || status === "active")
562
+ return "";
563
+ return `[${lifecycleOf(status).word}]`;
564
+ }
565
+ function lifecycleColor(status) {
566
+ return stateTone[lifecycleOf(status ?? "active").tone];
567
+ }
568
+ var GUTTER = 4;
569
+ var GAP = 2;
570
+ function nameWidth(rows, max = 28) {
571
+ return Math.min(max, Math.max(8, ...rows.map((r) => r.name.length)));
572
+ }
573
+ function SkillRow({
574
+ row,
575
+ width,
576
+ names,
577
+ cursor,
578
+ selected,
579
+ selectable = false
580
+ }) {
581
+ const tag = lifecycleTag(row.status);
582
+ const meta = row.count !== undefined ? `${row.count} skills` : tag;
583
+ const gutter = selectable ? GUTTER : 0;
584
+ const rest = width - gutter - names - GAP - (meta ? meta.length + GAP : 0);
585
+ return /* @__PURE__ */ jsxs2(Box2, {
586
+ children: [
587
+ selectable ? /* @__PURE__ */ jsx2(Text2, {
588
+ color: cursor ? "cyan" : undefined,
589
+ children: `${cursor ? mark.cursor : " "} ${selected ? mark.selected : mark.unselected} `
590
+ }) : null,
591
+ /* @__PURE__ */ jsx2(Text2, {
592
+ bold: cursor,
593
+ color: lifecycleColor(row.status),
594
+ children: clip(row.name, names).padEnd(names)
595
+ }),
596
+ /* @__PURE__ */ jsx2(Text2, {
597
+ children: " ".repeat(GAP)
598
+ }),
599
+ /* @__PURE__ */ jsx2(Text2, {
600
+ dimColor: true,
601
+ children: clip(row.description ?? "", Math.max(rest, 0))
602
+ }),
603
+ meta ? /* @__PURE__ */ jsxs2(Fragment, {
604
+ children: [
605
+ /* @__PURE__ */ jsx2(Text2, {
606
+ children: " ".repeat(GAP)
607
+ }),
608
+ /* @__PURE__ */ jsx2(Text2, {
609
+ color: lifecycleColor(row.status),
610
+ dimColor: !tag,
611
+ children: meta
612
+ })
613
+ ]
614
+ }) : null
615
+ ]
616
+ });
617
+ }
618
+ function Hints({ hints }) {
619
+ return /* @__PURE__ */ jsx2(Box2, {
620
+ children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs2(Text2, {
621
+ children: [
622
+ i > 0 ? /* @__PURE__ */ jsx2(Text2, {
623
+ dimColor: true,
624
+ children: " "
625
+ }) : null,
626
+ /* @__PURE__ */ jsx2(Text2, {
627
+ color: "cyan",
628
+ children: key
629
+ }),
630
+ /* @__PURE__ */ jsx2(Text2, {
631
+ dimColor: true,
632
+ children: ` ${label}`
633
+ })
634
+ ]
635
+ }, key))
636
+ });
637
+ }
638
+
639
+ // src/ui/back.tsx
640
+ import { jsx as jsx3, jsxs as jsxs3, Fragment as Fragment2 } from "react/jsx-runtime";
641
+ function Back({
642
+ width,
643
+ onBack,
644
+ extra = []
645
+ }) {
646
+ useInput((input, key) => {
647
+ if (key.escape || input === "x")
648
+ onBack?.();
649
+ }, { isActive: Boolean(onBack) });
650
+ if (!onBack)
651
+ return null;
652
+ return /* @__PURE__ */ jsxs3(Fragment2, {
653
+ children: [
654
+ /* @__PURE__ */ jsx3(Divider, {
655
+ width
656
+ }),
657
+ /* @__PURE__ */ jsx3(Hints, {
658
+ hints: [...extra, ["esc/x", "back"]]
659
+ })
660
+ ]
661
+ });
662
+ }
663
+ // src/ui/command.tsx
664
+ import { Box as Box3, Text as Text4 } from "ink";
665
+
505
666
  // src/ui/text-input.tsx
506
- import { Text as Text2 } from "ink";
507
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
667
+ import { Text as Text3 } from "ink";
668
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
508
669
  function SearchInput({
509
670
  value,
510
671
  placeholder = "",
511
672
  focus = true
512
673
  }) {
513
- return /* @__PURE__ */ jsxs2(Text2, {
674
+ return /* @__PURE__ */ jsxs4(Text3, {
514
675
  children: [
515
- value.length > 0 ? /* @__PURE__ */ jsx2(Text2, {
676
+ value.length > 0 ? /* @__PURE__ */ jsx4(Text3, {
516
677
  children: value
517
- }) : /* @__PURE__ */ jsx2(Text2, {
678
+ }) : /* @__PURE__ */ jsx4(Text3, {
518
679
  dimColor: true,
519
680
  children: placeholder
520
681
  }),
521
- focus ? /* @__PURE__ */ jsx2(Text2, {
682
+ focus ? /* @__PURE__ */ jsx4(Text3, {
522
683
  color: tone.primary,
523
684
  children: "_"
524
685
  }) : null
@@ -527,7 +688,7 @@ function SearchInput({
527
688
  }
528
689
 
529
690
  // src/ui/command.tsx
530
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
691
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
531
692
  function filterCommands(groups, query) {
532
693
  if (query.length === 0)
533
694
  return groups;
@@ -541,28 +702,28 @@ function flattenCommands(groups) {
541
702
  return groups.flatMap((group) => group.items);
542
703
  }
543
704
  var POINTER = 2;
544
- var GAP = 2;
705
+ var GAP2 = 2;
545
706
  function CommandRow({
546
707
  item,
547
708
  width,
548
709
  active
549
710
  }) {
550
711
  const hint = item.hint ?? "";
551
- const room = width - POINTER - (hint ? hint.length + GAP : 0);
712
+ const room = width - POINTER - (hint ? hint.length + GAP2 : 0);
552
713
  const label = clip(item.label, Math.max(room, 0));
553
714
  const gap = Math.max(width - POINTER - label.length - hint.length, hint ? 1 : 0);
554
- return /* @__PURE__ */ jsxs3(Text3, {
715
+ return /* @__PURE__ */ jsxs5(Text4, {
555
716
  children: [
556
- /* @__PURE__ */ jsx3(Text3, {
717
+ /* @__PURE__ */ jsx5(Text4, {
557
718
  color: tone.primary,
558
719
  children: `${active ? mark.cursor : " "} `
559
720
  }),
560
- /* @__PURE__ */ jsx3(Text3, {
721
+ /* @__PURE__ */ jsx5(Text4, {
561
722
  bold: active,
562
723
  color: active ? tone.primary : undefined,
563
724
  children: label
564
725
  }),
565
- /* @__PURE__ */ jsx3(Text3, {
726
+ /* @__PURE__ */ jsx5(Text4, {
566
727
  dimColor: true,
567
728
  children: `${" ".repeat(gap)}${hint}`
568
729
  })
@@ -581,38 +742,38 @@ function CommandPalette({
581
742
  const rows = flattenCommands(visible);
582
743
  const current = rows[cursor];
583
744
  const interior = width - FRAME_CHROME;
584
- return /* @__PURE__ */ jsxs3(Box2, {
745
+ return /* @__PURE__ */ jsxs5(Box3, {
585
746
  flexDirection: "column",
586
747
  children: [
587
- /* @__PURE__ */ jsxs3(Box2, {
748
+ /* @__PURE__ */ jsxs5(Box3, {
588
749
  children: [
589
- /* @__PURE__ */ jsx3(Text3, {
750
+ /* @__PURE__ */ jsx5(Text4, {
590
751
  color: tone.primary,
591
752
  children: "> "
592
753
  }),
593
- /* @__PURE__ */ jsx3(SearchInput, {
754
+ /* @__PURE__ */ jsx5(SearchInput, {
594
755
  value: query,
595
756
  placeholder
596
757
  })
597
758
  ]
598
759
  }),
599
- /* @__PURE__ */ jsx3(Divider, {
760
+ /* @__PURE__ */ jsx5(Divider, {
600
761
  width
601
762
  }),
602
- rows.length === 0 ? /* @__PURE__ */ jsx3(Text3, {
763
+ rows.length === 0 ? /* @__PURE__ */ jsx5(Text4, {
603
764
  dimColor: true,
604
765
  children: emptyMessage
605
- }) : visible.map((group, i) => /* @__PURE__ */ jsxs3(Box2, {
766
+ }) : visible.map((group, i) => /* @__PURE__ */ jsxs5(Box3, {
606
767
  flexDirection: "column",
607
768
  children: [
608
- i > 0 ? /* @__PURE__ */ jsx3(Divider, {
769
+ i > 0 ? /* @__PURE__ */ jsx5(Divider, {
609
770
  width
610
771
  }) : null,
611
- /* @__PURE__ */ jsx3(Text3, {
772
+ /* @__PURE__ */ jsx5(Text4, {
612
773
  dimColor: true,
613
774
  children: group.group.toUpperCase()
614
775
  }),
615
- group.items.map((item) => /* @__PURE__ */ jsx3(CommandRow, {
776
+ group.items.map((item) => /* @__PURE__ */ jsx5(CommandRow, {
616
777
  item,
617
778
  width: interior,
618
779
  active: item.value === current?.value
@@ -622,143 +783,147 @@ function CommandPalette({
622
783
  ]
623
784
  });
624
785
  }
625
- // ../../packages/skills/src/lifecycle.ts
626
- var LIFECYCLES = [
627
- { status: "active", word: "active", glyph: "#", tone: "state-active" },
628
- {
629
- status: "experimental",
630
- word: "experimental",
631
- glyph: "?",
632
- tone: "state-experimental"
633
- },
634
- {
635
- status: "deprecated",
636
- word: "deprecated",
637
- glyph: "x",
638
- tone: "state-deprecated"
639
- }
640
- ];
641
- var LIFECYCLE_ORDER = LIFECYCLES.map((lifecycle) => lifecycle.status);
642
- var BY_STATUS = new Map(LIFECYCLES.map((lifecycle) => [lifecycle.status, lifecycle]));
643
- function lifecycleOf(status) {
644
- const lifecycle = BY_STATUS.get(status);
645
- if (!lifecycle) {
646
- throw new Error(`No lifecycle vocabulary for "${status}". Add it to LIFECYCLES.`);
647
- }
648
- return lifecycle;
649
- }
650
- // src/ui/rows.tsx
651
- import { Box as Box3, Text as Text4 } from "ink";
652
- import { jsx as jsx4, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
653
- function lifecycleTag(status) {
654
- if (!status || status === "active")
655
- return "";
656
- return `[${lifecycleOf(status).word}]`;
657
- }
658
- function lifecycleColor(status) {
659
- return stateTone[lifecycleOf(status ?? "active").tone];
660
- }
661
- var GUTTER = 4;
662
- var GAP2 = 2;
663
- function nameWidth(rows, max = 28) {
664
- return Math.min(max, Math.max(8, ...rows.map((r) => r.name.length)));
665
- }
666
- function SkillRow({
667
- row,
668
- width,
669
- names,
670
- cursor,
671
- selected,
672
- selectable = false
786
+ // src/ui/confirm.tsx
787
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
788
+ import { useState } from "react";
789
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
790
+ function ConfirmScreen({
791
+ title,
792
+ headline,
793
+ lines,
794
+ notes = [],
795
+ yes,
796
+ no,
797
+ argv,
798
+ onYes,
799
+ onNo
673
800
  }) {
674
- const tag = lifecycleTag(row.status);
675
- const meta = row.count !== undefined ? `${row.count} skills` : tag;
676
- const gutter = selectable ? GUTTER : 0;
677
- const rest = width - gutter - names - GAP2 - (meta ? meta.length + GAP2 : 0);
678
- return /* @__PURE__ */ jsxs4(Box3, {
679
- children: [
680
- selectable ? /* @__PURE__ */ jsx4(Text4, {
681
- color: cursor ? "cyan" : undefined,
682
- children: `${cursor ? mark.cursor : " "} ${selected ? mark.selected : mark.unselected} `
683
- }) : null,
684
- /* @__PURE__ */ jsx4(Text4, {
685
- bold: cursor,
686
- color: lifecycleColor(row.status),
687
- children: clip(row.name, names).padEnd(names)
688
- }),
689
- /* @__PURE__ */ jsx4(Text4, {
690
- children: " ".repeat(GAP2)
691
- }),
692
- /* @__PURE__ */ jsx4(Text4, {
693
- dimColor: true,
694
- children: clip(row.description ?? "", Math.max(rest, 0))
695
- }),
696
- meta ? /* @__PURE__ */ jsxs4(Fragment, {
697
- children: [
698
- /* @__PURE__ */ jsx4(Text4, {
699
- children: " ".repeat(GAP2)
700
- }),
701
- /* @__PURE__ */ jsx4(Text4, {
702
- color: lifecycleColor(row.status),
703
- dimColor: !tag,
704
- children: meta
705
- })
706
- ]
707
- }) : null
708
- ]
801
+ const width = useFrameWidth();
802
+ const [choice, setChoice] = useState("yes");
803
+ useInput2((input, key) => {
804
+ if (key.upArrow || key.downArrow) {
805
+ setChoice((c) => c === "yes" ? "no" : "yes");
806
+ } else if (key.return) {
807
+ if (choice === "yes")
808
+ onYes();
809
+ else
810
+ onNo();
811
+ } else if (key.escape || input === "x") {
812
+ onNo();
813
+ }
709
814
  });
710
- }
711
- function Hints({ hints }) {
712
- return /* @__PURE__ */ jsx4(Box3, {
713
- children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs4(Text4, {
815
+ const interior = width - FRAME_CHROME;
816
+ const row = (label, active, hint) => {
817
+ const room = interior - 2 - (hint ? hint.length + 2 : 0);
818
+ const text = clip(label, Math.max(room, 0));
819
+ const gap = Math.max(interior - 2 - text.length - (hint?.length ?? 0), 1);
820
+ return /* @__PURE__ */ jsxs6(Text5, {
714
821
  children: [
715
- i > 0 ? /* @__PURE__ */ jsx4(Text4, {
716
- dimColor: true,
717
- children: " "
718
- }) : null,
719
- /* @__PURE__ */ jsx4(Text4, {
720
- color: "cyan",
721
- children: key
822
+ /* @__PURE__ */ jsx6(Text5, {
823
+ color: tone.primary,
824
+ children: `${active ? mark.cursor : " "} `
825
+ }),
826
+ /* @__PURE__ */ jsx6(Text5, {
827
+ bold: active,
828
+ color: active ? tone.primary : undefined,
829
+ children: text
722
830
  }),
723
- /* @__PURE__ */ jsx4(Text4, {
831
+ hint ? /* @__PURE__ */ jsx6(Text5, {
724
832
  dimColor: true,
725
- children: ` ${label}`
726
- })
833
+ children: `${" ".repeat(gap)}${hint}`
834
+ }) : null
727
835
  ]
728
- }, key))
836
+ });
837
+ };
838
+ return /* @__PURE__ */ jsxs6(Frame, {
839
+ title,
840
+ width,
841
+ children: [
842
+ /* @__PURE__ */ jsxs6(Box4, {
843
+ flexDirection: "column",
844
+ children: [
845
+ /* @__PURE__ */ jsx6(Text5, {
846
+ color: tone.notice,
847
+ children: clip(headline, interior)
848
+ }),
849
+ /* @__PURE__ */ jsx6(Text5, {
850
+ children: " "
851
+ }),
852
+ lines.map((line) => /* @__PURE__ */ jsxs6(Text5, {
853
+ children: [
854
+ /* @__PURE__ */ jsx6(Text5, {
855
+ color: line.mark === "fail" ? tone.danger : undefined,
856
+ children: line.mark === "none" ? " " : `${MARK[line.mark]} `
857
+ }),
858
+ /* @__PURE__ */ jsx6(Text5, {
859
+ children: clip(line.text, interior - 2)
860
+ }),
861
+ line.note ? /* @__PURE__ */ jsx6(Text5, {
862
+ dimColor: true,
863
+ children: clip(` ${line.note}`, Math.max(interior - 2 - line.text.length, 0))
864
+ }) : null
865
+ ]
866
+ }, `${line.mark}:${line.text}`)),
867
+ notes.length > 0 ? /* @__PURE__ */ jsx6(Text5, {
868
+ children: " "
869
+ }) : null,
870
+ notes.map((note) => /* @__PURE__ */ jsx6(Text5, {
871
+ dimColor: true,
872
+ children: clip(note, interior)
873
+ }, note)),
874
+ /* @__PURE__ */ jsx6(Text5, {
875
+ children: " "
876
+ }),
877
+ row(yes, choice === "yes", argv),
878
+ row(no, choice === "no")
879
+ ]
880
+ }),
881
+ /* @__PURE__ */ jsx6(Divider, {
882
+ width
883
+ }),
884
+ /* @__PURE__ */ jsx6(Hints, {
885
+ hints: [
886
+ ["^v", "choose"],
887
+ ["enter", choice === "yes" ? yes : no],
888
+ ["esc", no]
889
+ ]
890
+ })
891
+ ]
729
892
  });
730
893
  }
894
+ var MARK = { ok: mark.ok, fail: mark.fail, pending: mark.pending };
731
895
  // src/ui/spinner.tsx
732
- import { Text as Text5 } from "ink";
733
- import { useEffect, useState } from "react";
734
- import { jsx as jsx5 } from "react/jsx-runtime";
896
+ import { Text as Text6 } from "ink";
897
+ import { useEffect, useState as useState2 } from "react";
898
+ import { jsx as jsx7 } from "react/jsx-runtime";
735
899
  var FRAMES = ["-", "\\", "|", "/"];
736
900
  var INTERVAL_MS = 90;
737
901
  function Spinner() {
738
- const [frame, setFrame] = useState(0);
902
+ const [frame, setFrame] = useState2(0);
739
903
  useEffect(() => {
740
904
  const timer = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), INTERVAL_MS);
741
905
  return () => clearInterval(timer);
742
906
  }, []);
743
- return /* @__PURE__ */ jsx5(Text5, {
907
+ return /* @__PURE__ */ jsx7(Text6, {
744
908
  color: tone.primary,
745
909
  children: FRAMES[frame]
746
910
  });
747
911
  }
748
912
  // src/auth/screens/login.tsx
749
- import { Box as Box4, Text as Text6, useApp } from "ink";
750
- import { useEffect as useEffect2, useState as useState2 } from "react";
751
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
913
+ import { Box as Box5, Text as Text7, useApp } from "ink";
914
+ import { useEffect as useEffect2, useState as useState3 } from "react";
915
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
752
916
  function minutesLeft(expiresAt) {
753
917
  return Math.max(0, Math.ceil((expiresAt - Date.now()) / 60000));
754
918
  }
755
919
  function LoginScreen({
756
920
  onError,
757
- onDone
921
+ onDone,
922
+ onBack
758
923
  }) {
759
924
  const { exit } = useApp();
760
925
  const width = useFrameWidth();
761
- const [state, setState] = useState2({ phase: "starting" });
926
+ const [state, setState] = useState3({ phase: "starting" });
762
927
  useEffect2(() => {
763
928
  let live = true;
764
929
  (async () => {
@@ -789,16 +954,16 @@ function LoginScreen({
789
954
  };
790
955
  }, [exit, onError, onDone]);
791
956
  if (state.phase === "done") {
792
- return /* @__PURE__ */ jsx6(Frame, {
957
+ return /* @__PURE__ */ jsx8(Frame, {
793
958
  title: "login",
794
959
  width,
795
- children: /* @__PURE__ */ jsxs5(Text6, {
960
+ children: /* @__PURE__ */ jsxs7(Text7, {
796
961
  children: [
797
- /* @__PURE__ */ jsx6(Text6, {
962
+ /* @__PURE__ */ jsx8(Text7, {
798
963
  color: "green",
799
964
  children: mark.ok
800
965
  }),
801
- /* @__PURE__ */ jsx6(Text6, {
966
+ /* @__PURE__ */ jsx8(Text7, {
802
967
  children: ` Logged in${state.email ? ` as ${state.email}` : ""}.`
803
968
  })
804
969
  ]
@@ -806,66 +971,78 @@ function LoginScreen({
806
971
  });
807
972
  }
808
973
  if (state.phase === "starting") {
809
- return /* @__PURE__ */ jsx6(Frame, {
974
+ return /* @__PURE__ */ jsxs7(Frame, {
810
975
  title: "login",
811
976
  width,
812
- children: /* @__PURE__ */ jsxs5(Text6, {
813
- children: [
814
- /* @__PURE__ */ jsx6(Spinner, {}),
815
- /* @__PURE__ */ jsx6(Text6, {
816
- dimColor: true,
817
- children: " Requesting a device code..."
818
- })
819
- ]
820
- })
977
+ children: [
978
+ /* @__PURE__ */ jsxs7(Text7, {
979
+ children: [
980
+ /* @__PURE__ */ jsx8(Spinner, {}),
981
+ /* @__PURE__ */ jsx8(Text7, {
982
+ dimColor: true,
983
+ children: " Requesting a device code..."
984
+ })
985
+ ]
986
+ }),
987
+ /* @__PURE__ */ jsx8(Back, {
988
+ width,
989
+ onBack
990
+ })
991
+ ]
821
992
  });
822
993
  }
823
994
  const { prompt } = state;
824
- return /* @__PURE__ */ jsx6(Frame, {
995
+ return /* @__PURE__ */ jsxs7(Frame, {
825
996
  title: "login",
826
997
  width,
827
- children: /* @__PURE__ */ jsxs5(Box4, {
828
- flexDirection: "column",
829
- children: [
830
- /* @__PURE__ */ jsx6(Text6, {
831
- dimColor: true,
832
- children: "Open this URL in your browser:"
833
- }),
834
- /* @__PURE__ */ jsx6(Text6, {
835
- color: tone.primary,
836
- children: prompt.verificationUri
837
- }),
838
- /* @__PURE__ */ jsx6(Box4, {
839
- height: 1
840
- }),
841
- /* @__PURE__ */ jsx6(Text6, {
842
- dimColor: true,
843
- children: "and enter the code:"
844
- }),
845
- /* @__PURE__ */ jsx6(Text6, {
846
- bold: true,
847
- color: tone.notice,
848
- children: prompt.userCode
849
- }),
850
- /* @__PURE__ */ jsx6(Box4, {
851
- height: 1
852
- }),
853
- /* @__PURE__ */ jsxs5(Text6, {
854
- children: [
855
- /* @__PURE__ */ jsx6(Spinner, {}),
856
- /* @__PURE__ */ jsx6(Text6, {
857
- dimColor: true,
858
- children: ` Waiting for approval — expires in ${minutesLeft(prompt.expiresAt)} min`
859
- })
860
- ]
861
- })
862
- ]
863
- })
998
+ children: [
999
+ /* @__PURE__ */ jsxs7(Box5, {
1000
+ flexDirection: "column",
1001
+ children: [
1002
+ /* @__PURE__ */ jsx8(Text7, {
1003
+ dimColor: true,
1004
+ children: "Open this URL in your browser:"
1005
+ }),
1006
+ /* @__PURE__ */ jsx8(Text7, {
1007
+ color: tone.primary,
1008
+ children: prompt.verificationUri
1009
+ }),
1010
+ /* @__PURE__ */ jsx8(Box5, {
1011
+ height: 1
1012
+ }),
1013
+ /* @__PURE__ */ jsx8(Text7, {
1014
+ dimColor: true,
1015
+ children: "and enter the code:"
1016
+ }),
1017
+ /* @__PURE__ */ jsx8(Text7, {
1018
+ bold: true,
1019
+ color: tone.notice,
1020
+ children: prompt.userCode
1021
+ }),
1022
+ /* @__PURE__ */ jsx8(Box5, {
1023
+ height: 1
1024
+ }),
1025
+ /* @__PURE__ */ jsxs7(Text7, {
1026
+ children: [
1027
+ /* @__PURE__ */ jsx8(Spinner, {}),
1028
+ /* @__PURE__ */ jsx8(Text7, {
1029
+ dimColor: true,
1030
+ children: ` Waiting for approval — expires in ${minutesLeft(prompt.expiresAt)} min`
1031
+ })
1032
+ ]
1033
+ })
1034
+ ]
1035
+ }),
1036
+ /* @__PURE__ */ jsx8(Back, {
1037
+ width,
1038
+ onBack
1039
+ })
1040
+ ]
864
1041
  });
865
1042
  }
866
1043
 
867
1044
  // src/auth/screens/gate.tsx
868
- import { jsx as jsx7, jsxs as jsxs6, Fragment as Fragment2 } from "react/jsx-runtime";
1045
+ import { jsx as jsx9, jsxs as jsxs8, Fragment as Fragment3 } from "react/jsx-runtime";
869
1046
  function AuthGate({
870
1047
  title,
871
1048
  build,
@@ -875,7 +1052,7 @@ function AuthGate({
875
1052
  }) {
876
1053
  const { exit } = useApp2();
877
1054
  const width = useFrameWidth();
878
- const [phase, setPhase] = useState3({ kind: "checking" });
1055
+ const [phase, setPhase] = useState4({ kind: "checking" });
879
1056
  const open = useCallback(() => {
880
1057
  connect().then((client) => {
881
1058
  process.exitCode = 0;
@@ -899,7 +1076,7 @@ function AuthGate({
899
1076
  live = false;
900
1077
  };
901
1078
  }, [check, open, onError]);
902
- useInput((input, key) => {
1079
+ useInput3((input, key) => {
903
1080
  if (input === "l")
904
1081
  setPhase({ kind: "logging-in" });
905
1082
  if (input === "q" || key.escape)
@@ -907,67 +1084,67 @@ function AuthGate({
907
1084
  }, { isActive: phase.kind === "blocked" });
908
1085
  const child = useMemo(() => phase.kind === "ready" ? build(phase.client) : null, [phase, build]);
909
1086
  if (phase.kind === "ready")
910
- return /* @__PURE__ */ jsx7(Fragment2, {
1087
+ return /* @__PURE__ */ jsx9(Fragment3, {
911
1088
  children: child
912
1089
  });
913
1090
  if (phase.kind === "logging-in")
914
- return /* @__PURE__ */ jsx7(LoginScreen, {
1091
+ return /* @__PURE__ */ jsx9(LoginScreen, {
915
1092
  onError,
916
1093
  onDone: open
917
1094
  });
918
1095
  if (phase.kind === "checking")
919
- return /* @__PURE__ */ jsx7(Frame, {
1096
+ return /* @__PURE__ */ jsx9(Frame, {
920
1097
  title,
921
1098
  width,
922
- children: /* @__PURE__ */ jsxs6(Text7, {
1099
+ children: /* @__PURE__ */ jsxs8(Text8, {
923
1100
  children: [
924
- /* @__PURE__ */ jsx7(Spinner, {}),
925
- /* @__PURE__ */ jsx7(Text7, {
1101
+ /* @__PURE__ */ jsx9(Spinner, {}),
1102
+ /* @__PURE__ */ jsx9(Text8, {
926
1103
  dimColor: true,
927
1104
  children: " Checking session..."
928
1105
  })
929
1106
  ]
930
1107
  })
931
1108
  });
932
- return /* @__PURE__ */ jsx7(Frame, {
1109
+ return /* @__PURE__ */ jsx9(Frame, {
933
1110
  title,
934
1111
  width,
935
- children: /* @__PURE__ */ jsxs6(Box5, {
1112
+ children: /* @__PURE__ */ jsxs8(Box6, {
936
1113
  flexDirection: "column",
937
1114
  children: [
938
- /* @__PURE__ */ jsxs6(Text7, {
1115
+ /* @__PURE__ */ jsxs8(Text8, {
939
1116
  children: [
940
- /* @__PURE__ */ jsx7(Text7, {
1117
+ /* @__PURE__ */ jsx9(Text8, {
941
1118
  color: tone.notice,
942
1119
  children: mark.fail
943
1120
  }),
944
- /* @__PURE__ */ jsx7(Text7, {
1121
+ /* @__PURE__ */ jsx9(Text8, {
945
1122
  children: ` Not signed in — ${title} needs a session.`
946
1123
  })
947
1124
  ]
948
1125
  }),
949
- /* @__PURE__ */ jsx7(Blank, {}),
950
- /* @__PURE__ */ jsxs6(Text7, {
1126
+ /* @__PURE__ */ jsx9(Blank, {}),
1127
+ /* @__PURE__ */ jsxs8(Text8, {
951
1128
  dimColor: true,
952
1129
  children: [
953
1130
  "press ",
954
- /* @__PURE__ */ jsx7(Text7, {
1131
+ /* @__PURE__ */ jsx9(Text8, {
955
1132
  color: tone.primary,
956
1133
  children: "l"
957
1134
  }),
958
1135
  " to log in, ",
959
- /* @__PURE__ */ jsx7(Text7, {
1136
+ /* @__PURE__ */ jsx9(Text8, {
960
1137
  color: tone.primary,
961
1138
  children: "q"
962
1139
  }),
963
1140
  " to quit"
964
1141
  ]
965
1142
  }),
966
- /* @__PURE__ */ jsxs6(Text7, {
1143
+ /* @__PURE__ */ jsxs8(Text8, {
967
1144
  dimColor: true,
968
1145
  children: [
969
1146
  "or run ",
970
- /* @__PURE__ */ jsx7(Text7, {
1147
+ /* @__PURE__ */ jsx9(Text8, {
971
1148
  color: tone.primary,
972
1149
  children: "yourskills login"
973
1150
  }),
@@ -979,8 +1156,8 @@ function AuthGate({
979
1156
  });
980
1157
  }
981
1158
  // src/catalog/screens/browser.tsx
982
- import { Box as Box7, Text as Text9, useApp as useApp4, useInput as useInput2, useWindowSize as useWindowSize2 } from "ink";
983
- import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
1159
+ import { Box as Box8, Text as Text10, useApp as useApp4, useInput as useInput4, useWindowSize as useWindowSize2 } from "ink";
1160
+ import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useState as useState6 } from "react";
984
1161
 
985
1162
  // src/install/install.ts
986
1163
  import { spawn } from "node:child_process";
@@ -1193,6 +1370,57 @@ async function* installResolution(entries, opts, bundle) {
1193
1370
  yield* installTargets(entry.locations, opts, bundle);
1194
1371
  }
1195
1372
  }
1373
+
1374
+ // src/install/update.ts
1375
+ function update(client, bundle, opts = {}) {
1376
+ return installBundle(client, bundle, opts);
1377
+ }
1378
+ function updateScope(bundles, loose) {
1379
+ return [
1380
+ bundles > 0 ? `${bundles} ${bundles === 1 ? "bundle" : "bundles"}` : "",
1381
+ loose > 0 ? `${loose} loose ${loose === 1 ? "skill" : "skills"}` : ""
1382
+ ].filter(Boolean).join(" and ");
1383
+ }
1384
+ function looseSkills(manifest) {
1385
+ return Object.values(manifest.skills).filter((entry) => !entry.bundle).map((entry) => entry.name);
1386
+ }
1387
+ async function* updateTracked(client, manifest, opts = {}, all = false) {
1388
+ for (const bundle of Object.keys(manifest.bundles)) {
1389
+ yield* installBundle(client, bundle, opts);
1390
+ }
1391
+ const loose = all ? looseSkills(manifest) : [];
1392
+ if (loose.length > 0)
1393
+ yield* install(client, loose, opts);
1394
+ }
1395
+
1396
+ // src/install/empty.ts
1397
+ function nothingTracked(nearest, kind, all = false) {
1398
+ const has = kind === "install" ? Object.keys(nearest.manifest.skills).length > 0 : Object.keys(nearest.manifest.bundles).length > 0 || all && looseSkills(nearest.manifest).length > 0;
1399
+ if (has)
1400
+ return null;
1401
+ return {
1402
+ title: kind,
1403
+ lines: [
1404
+ {
1405
+ mark: "none",
1406
+ text: kind === "install" ? "Nothing recorded to install yet." : "No bundles tracked here yet.",
1407
+ note: nearest.path
1408
+ }
1409
+ ],
1410
+ json: { manifest: nearest.path, skills: [], bundles: [] },
1411
+ next: { command: "yourskills browse", why: "pick something to install" }
1412
+ };
1413
+ }
1414
+ // src/install/options.ts
1415
+ async function resolveInstallOptions(opts) {
1416
+ const { scope, manifestPath } = await resolveScope({ flag: opts.scope });
1417
+ const path = scope === "project" && manifestPath ? manifestPath : manifestPathFor(scope);
1418
+ return {
1419
+ scope,
1420
+ agents: await resolveAgents(opts.agents),
1421
+ manifest: { path, scope }
1422
+ };
1423
+ }
1196
1424
  // src/install/reinstall.ts
1197
1425
  function targetOf(entry) {
1198
1426
  return {
@@ -1255,9 +1483,9 @@ async function removeSkills(names, manifest, manifestPath, opts = {}) {
1255
1483
  };
1256
1484
  }
1257
1485
  // src/install/screens/installer.tsx
1258
- import { Box as Box6, Text as Text8, useApp as useApp3 } from "ink";
1259
- import { useEffect as useEffect4, useState as useState4 } from "react";
1260
- import { jsx as jsx8, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
1486
+ import { Box as Box7, Text as Text9, useApp as useApp3 } from "ink";
1487
+ import { useEffect as useEffect4, useState as useState5 } from "react";
1488
+ import { jsx as jsx10, jsxs as jsxs9, Fragment as Fragment4 } from "react/jsx-runtime";
1261
1489
  function where(location) {
1262
1490
  return `${location.source}/${location.path}@${location.ref.slice(0, 8)}`;
1263
1491
  }
@@ -1306,76 +1534,76 @@ function InstallLine({ line, width }) {
1306
1534
  const name = clip(line.name, names).padEnd(names);
1307
1535
  switch (line.state) {
1308
1536
  case "resolving":
1309
- return /* @__PURE__ */ jsxs7(Text8, {
1537
+ return /* @__PURE__ */ jsxs9(Text9, {
1310
1538
  children: [
1311
- /* @__PURE__ */ jsx8(Spinner, {}),
1312
- /* @__PURE__ */ jsx8(Text8, {
1539
+ /* @__PURE__ */ jsx10(Spinner, {}),
1540
+ /* @__PURE__ */ jsx10(Text9, {
1313
1541
  children: ` ${name}`
1314
1542
  }),
1315
- /* @__PURE__ */ jsx8(Text8, {
1543
+ /* @__PURE__ */ jsx10(Text9, {
1316
1544
  dimColor: true,
1317
1545
  children: "resolving..."
1318
1546
  })
1319
1547
  ]
1320
1548
  });
1321
1549
  case "installing":
1322
- return /* @__PURE__ */ jsxs7(Text8, {
1550
+ return /* @__PURE__ */ jsxs9(Text9, {
1323
1551
  children: [
1324
- /* @__PURE__ */ jsx8(Spinner, {}),
1325
- /* @__PURE__ */ jsx8(Text8, {
1552
+ /* @__PURE__ */ jsx10(Spinner, {}),
1553
+ /* @__PURE__ */ jsx10(Text9, {
1326
1554
  children: ` ${name}`
1327
1555
  }),
1328
- /* @__PURE__ */ jsx8(Text8, {
1556
+ /* @__PURE__ */ jsx10(Text9, {
1329
1557
  dimColor: true,
1330
1558
  children: clip(line.where, width - names - 3)
1331
1559
  })
1332
1560
  ]
1333
1561
  });
1334
1562
  case "installed":
1335
- return /* @__PURE__ */ jsxs7(Text8, {
1563
+ return /* @__PURE__ */ jsxs9(Text9, {
1336
1564
  children: [
1337
- /* @__PURE__ */ jsx8(Text8, {
1565
+ /* @__PURE__ */ jsx10(Text9, {
1338
1566
  color: "green",
1339
1567
  children: mark.ok
1340
1568
  }),
1341
- /* @__PURE__ */ jsx8(Text8, {
1569
+ /* @__PURE__ */ jsx10(Text9, {
1342
1570
  children: ` ${name}`
1343
1571
  }),
1344
- /* @__PURE__ */ jsx8(Text8, {
1572
+ /* @__PURE__ */ jsx10(Text9, {
1345
1573
  dimColor: true,
1346
1574
  children: clip(line.where, width - names - 3)
1347
1575
  }),
1348
- line.deprecated ? /* @__PURE__ */ jsx8(Text8, {
1576
+ line.deprecated ? /* @__PURE__ */ jsx10(Text9, {
1349
1577
  color: lifecycleColor("deprecated"),
1350
1578
  children: ` ${lifecycleTag("deprecated")}`
1351
1579
  }) : null
1352
1580
  ]
1353
1581
  });
1354
1582
  case "pending":
1355
- return /* @__PURE__ */ jsxs7(Box6, {
1583
+ return /* @__PURE__ */ jsxs9(Box7, {
1356
1584
  flexDirection: "column",
1357
1585
  children: [
1358
- /* @__PURE__ */ jsxs7(Text8, {
1586
+ /* @__PURE__ */ jsxs9(Text9, {
1359
1587
  children: [
1360
- /* @__PURE__ */ jsx8(Text8, {
1588
+ /* @__PURE__ */ jsx10(Text9, {
1361
1589
  color: tone.notice,
1362
1590
  children: mark.pending
1363
1591
  }),
1364
- /* @__PURE__ */ jsx8(Text8, {
1592
+ /* @__PURE__ */ jsx10(Text9, {
1365
1593
  children: ` ${name}`
1366
1594
  }),
1367
- /* @__PURE__ */ jsx8(Text8, {
1595
+ /* @__PURE__ */ jsx10(Text9, {
1368
1596
  color: tone.notice,
1369
1597
  children: "not in your registry yet"
1370
1598
  })
1371
1599
  ]
1372
1600
  }),
1373
- line.vendor.prUrl ? /* @__PURE__ */ jsxs7(Text8, {
1601
+ line.vendor.prUrl ? /* @__PURE__ */ jsxs9(Text9, {
1374
1602
  children: [
1375
- /* @__PURE__ */ jsx8(Text8, {
1603
+ /* @__PURE__ */ jsx10(Text9, {
1376
1604
  children: " "
1377
1605
  }),
1378
- /* @__PURE__ */ jsx8(Text8, {
1606
+ /* @__PURE__ */ jsx10(Text9, {
1379
1607
  color: tone.primary,
1380
1608
  children: line.vendor.prUrl
1381
1609
  })
@@ -1384,26 +1612,26 @@ function InstallLine({ line, width }) {
1384
1612
  ]
1385
1613
  });
1386
1614
  case "failed":
1387
- return /* @__PURE__ */ jsxs7(Box6, {
1615
+ return /* @__PURE__ */ jsxs9(Box7, {
1388
1616
  flexDirection: "column",
1389
1617
  children: [
1390
- /* @__PURE__ */ jsxs7(Text8, {
1618
+ /* @__PURE__ */ jsxs9(Text9, {
1391
1619
  children: [
1392
- /* @__PURE__ */ jsx8(Text8, {
1620
+ /* @__PURE__ */ jsx10(Text9, {
1393
1621
  color: tone.danger,
1394
1622
  children: mark.fail
1395
1623
  }),
1396
- /* @__PURE__ */ jsx8(Text8, {
1624
+ /* @__PURE__ */ jsx10(Text9, {
1397
1625
  children: ` ${name}`
1398
1626
  }),
1399
- /* @__PURE__ */ jsx8(Text8, {
1627
+ /* @__PURE__ */ jsx10(Text9, {
1400
1628
  color: tone.danger,
1401
1629
  children: clip(line.message, width - names - 3)
1402
1630
  })
1403
1631
  ]
1404
1632
  }),
1405
1633
  line.detail ? line.detail.split(`
1406
- `).slice(-3).map((d) => /* @__PURE__ */ jsx8(Text8, {
1634
+ `).slice(-3).map((d) => /* @__PURE__ */ jsx10(Text9, {
1407
1635
  dimColor: true,
1408
1636
  children: clip(` ${d}`, width)
1409
1637
  }, d)) : null
@@ -1415,13 +1643,15 @@ function Installer({
1415
1643
  title,
1416
1644
  stream,
1417
1645
  onDone,
1418
- exitWhenDone = true
1646
+ exitWhenDone = true,
1647
+ onBack,
1648
+ plan
1419
1649
  }) {
1420
1650
  const { exit } = useApp3();
1421
1651
  const width = useFrameWidth();
1422
- const [lines, setLines] = useState4([]);
1423
- const [error, setError] = useState4(null);
1424
- const [done, setDone] = useState4(false);
1652
+ const [lines, setLines] = useState5([]);
1653
+ const [error, setError] = useState5(null);
1654
+ const [done, setDone] = useState5(false);
1425
1655
  useEffect4(() => {
1426
1656
  let live = true;
1427
1657
  (async () => {
@@ -1452,51 +1682,86 @@ function Installer({
1452
1682
  };
1453
1683
  }, [stream, onDone, exit, exitWhenDone]);
1454
1684
  const pending = lines.filter((l) => l.state === "pending");
1455
- return /* @__PURE__ */ jsx8(Frame, {
1685
+ const installed = lines.filter((l) => l.state === "installed").length;
1686
+ const failed = lines.filter((l) => l.state === "failed").length;
1687
+ return /* @__PURE__ */ jsxs9(Frame, {
1456
1688
  title,
1457
1689
  width,
1458
- children: /* @__PURE__ */ jsxs7(Box6, {
1459
- flexDirection: "column",
1460
- children: [
1461
- lines.length === 0 && !error ? /* @__PURE__ */ jsxs7(Text8, {
1462
- children: [
1463
- /* @__PURE__ */ jsx8(Spinner, {}),
1464
- /* @__PURE__ */ jsx8(Text8, {
1465
- dimColor: true,
1466
- children: " Resolving..."
1467
- })
1468
- ]
1469
- }) : null,
1470
- lines.map((line) => /* @__PURE__ */ jsx8(InstallLine, {
1471
- line,
1472
- width: width - 2
1473
- }, line.name)),
1474
- error ? /* @__PURE__ */ jsx8(Text8, {
1475
- color: tone.danger,
1476
- children: `${mark.fail} ${error}`
1477
- }) : null,
1478
- done && pending.length > 0 ? /* @__PURE__ */ jsxs7(Fragment3, {
1479
- children: [
1480
- /* @__PURE__ */ jsx8(Divider, {
1481
- width
1482
- }),
1483
- /* @__PURE__ */ jsx8(Text8, {
1484
- color: tone.notice,
1485
- children: pending.length === 1 ? "1 skill is waiting on a pull request." : `${pending.length} skills are waiting on a pull request.`
1486
- }),
1487
- /* @__PURE__ */ jsx8(Text8, {
1488
- dimColor: true,
1489
- children: "Merge it, then run this command again."
1490
- })
1491
- ]
1492
- }) : null
1493
- ]
1494
- })
1690
+ children: [
1691
+ /* @__PURE__ */ jsxs9(Box7, {
1692
+ flexDirection: "column",
1693
+ children: [
1694
+ plan ? /* @__PURE__ */ jsx10(Text9, {
1695
+ dimColor: true,
1696
+ children: clip(plan, width - 4)
1697
+ }) : null,
1698
+ lines.length === 0 && !error ? /* @__PURE__ */ jsxs9(Text9, {
1699
+ children: [
1700
+ /* @__PURE__ */ jsx10(Spinner, {}),
1701
+ /* @__PURE__ */ jsx10(Text9, {
1702
+ dimColor: true,
1703
+ children: " Resolving..."
1704
+ })
1705
+ ]
1706
+ }) : null,
1707
+ lines.map((line) => /* @__PURE__ */ jsx10(InstallLine, {
1708
+ line,
1709
+ width: width - 2
1710
+ }, line.name)),
1711
+ error ? /* @__PURE__ */ jsx10(Text9, {
1712
+ color: tone.danger,
1713
+ children: `${mark.fail} ${error}`
1714
+ }) : null,
1715
+ done && lines.length > 0 ? /* @__PURE__ */ jsxs9(Fragment4, {
1716
+ children: [
1717
+ /* @__PURE__ */ jsx10(Divider, {
1718
+ width
1719
+ }),
1720
+ /* @__PURE__ */ jsxs9(Text9, {
1721
+ children: [
1722
+ /* @__PURE__ */ jsx10(Text9, {
1723
+ color: "green",
1724
+ children: `${installed} installed`
1725
+ }),
1726
+ /* @__PURE__ */ jsx10(Text9, {
1727
+ color: pending.length > 0 ? tone.notice : undefined,
1728
+ dimColor: pending.length === 0,
1729
+ children: ` ${pending.length} pending`
1730
+ }),
1731
+ /* @__PURE__ */ jsx10(Text9, {
1732
+ color: failed > 0 ? tone.danger : undefined,
1733
+ dimColor: failed === 0,
1734
+ children: ` ${failed} failed`
1735
+ })
1736
+ ]
1737
+ })
1738
+ ]
1739
+ }) : null,
1740
+ done && pending.length > 0 ? /* @__PURE__ */ jsxs9(Fragment4, {
1741
+ children: [
1742
+ /* @__PURE__ */ jsx10(Text9, {
1743
+ color: tone.notice,
1744
+ children: pending.length === 1 ? "1 skill is waiting on a pull request." : `${pending.length} skills are waiting on a pull request.`
1745
+ }),
1746
+ /* @__PURE__ */ jsx10(Text9, {
1747
+ dimColor: true,
1748
+ children: "Merge it, then run this command again."
1749
+ })
1750
+ ]
1751
+ }) : null
1752
+ ]
1753
+ }),
1754
+ done || error !== null ? /* @__PURE__ */ jsx10(Back, {
1755
+ width,
1756
+ onBack
1757
+ }) : null
1758
+ ]
1495
1759
  });
1496
1760
  }
1497
1761
  async function installPlain(stream) {
1498
1762
  const pending = [];
1499
1763
  let failed = 0;
1764
+ let installed = 0;
1500
1765
  for await (const event of stream) {
1501
1766
  switch (event.kind) {
1502
1767
  case "resolving":
@@ -1508,6 +1773,7 @@ async function installPlain(stream) {
1508
1773
  console.log(`${event.location.skillName} ${where(event.location)}`);
1509
1774
  break;
1510
1775
  case "installed":
1776
+ installed += 1;
1511
1777
  break;
1512
1778
  case "pending":
1513
1779
  console.log(pendingMessage(event.vendor));
@@ -1521,25 +1787,13 @@ async function installPlain(stream) {
1521
1787
  break;
1522
1788
  }
1523
1789
  }
1524
- return { pending, failed };
1525
- }
1526
- // src/install/update.ts
1527
- function update(client, bundle, opts = {}) {
1528
- return installBundle(client, bundle, opts);
1529
- }
1530
- function looseSkills(manifest) {
1531
- return Object.values(manifest.skills).filter((entry) => !entry.bundle).map((entry) => entry.name);
1532
- }
1533
- async function* updateTracked(client, manifest, opts = {}, all = false) {
1534
- for (const bundle of Object.keys(manifest.bundles)) {
1535
- yield* installBundle(client, bundle, opts);
1790
+ if (pending.length > 0 || failed > 0) {
1791
+ console.log(`${installed} installed, ${pending.length} pending, ${failed} failed`);
1536
1792
  }
1537
- const loose = all ? looseSkills(manifest) : [];
1538
- if (loose.length > 0)
1539
- yield* install(client, loose, opts);
1793
+ return { pending, failed };
1540
1794
  }
1541
1795
  // src/catalog/screens/browser.tsx
1542
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1796
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1543
1797
  var TABS = ["bundles", "skills"];
1544
1798
  function pageSize(rows) {
1545
1799
  return Math.max(3, Math.min(20, rows - 11));
@@ -1551,18 +1805,18 @@ function matches(row, query) {
1551
1805
  return row.name.toLowerCase().includes(q) || (row.description ?? "").toLowerCase().includes(q);
1552
1806
  }
1553
1807
  function TabBar({ active }) {
1554
- return /* @__PURE__ */ jsx9(Box7, {
1555
- children: TABS.map((tab, i) => /* @__PURE__ */ jsxs8(Text9, {
1808
+ return /* @__PURE__ */ jsx11(Box8, {
1809
+ children: TABS.map((tab, i) => /* @__PURE__ */ jsxs10(Text10, {
1556
1810
  children: [
1557
- i > 0 ? /* @__PURE__ */ jsx9(Text9, {
1811
+ i > 0 ? /* @__PURE__ */ jsx11(Text10, {
1558
1812
  dimColor: true,
1559
1813
  children: " "
1560
1814
  }) : null,
1561
- tab === active ? /* @__PURE__ */ jsx9(Text9, {
1815
+ tab === active ? /* @__PURE__ */ jsx11(Text10, {
1562
1816
  bold: true,
1563
1817
  color: tone.primary,
1564
1818
  children: `[${tab}]`
1565
- }) : /* @__PURE__ */ jsx9(Text9, {
1819
+ }) : /* @__PURE__ */ jsx11(Text10, {
1566
1820
  dimColor: true,
1567
1821
  children: ` ${tab} `
1568
1822
  })
@@ -1575,20 +1829,22 @@ function BrowserScreen({
1575
1829
  opts,
1576
1830
  onError,
1577
1831
  onSummary,
1578
- onExit
1832
+ onExit,
1833
+ initialTab = "bundles",
1834
+ initialQuery = ""
1579
1835
  }) {
1580
1836
  const { exit } = useApp4();
1581
1837
  const width = useFrameWidth();
1582
1838
  const { rows: terminalRows } = useWindowSize2();
1583
- const [catalog, setCatalog] = useState5(null);
1584
- const [tab, setTab] = useState5("bundles");
1585
- const [query, setQuery] = useState5("");
1586
- const [cursor, setCursor] = useState5(0);
1587
- const [picked, setPicked] = useState5({
1839
+ const [catalog, setCatalog] = useState6(null);
1840
+ const [tab, setTab] = useState6(initialTab);
1841
+ const [query, setQuery] = useState6(initialQuery);
1842
+ const [cursor, setCursor] = useState6(0);
1843
+ const [picked, setPicked] = useState6({
1588
1844
  bundles: new Set,
1589
1845
  skills: new Set
1590
1846
  });
1591
- const [running, setRunning] = useState5(null);
1847
+ const [running, setRunning] = useState6(null);
1592
1848
  useEffect5(() => {
1593
1849
  let live = true;
1594
1850
  Promise.all([client.skills.list.query({}), client.bundles.list.query()]).then(([skills, bundles]) => {
@@ -1631,7 +1887,7 @@ function BrowserScreen({
1631
1887
  names: names2
1632
1888
  });
1633
1889
  }, [selected, visible, cursor, tab]);
1634
- useInput2((input, key) => {
1890
+ useInput4((input, key) => {
1635
1891
  if (key.upArrow) {
1636
1892
  setCursor((c) => Math.max(0, c - 1));
1637
1893
  } else if (key.downArrow) {
@@ -1675,7 +1931,7 @@ function BrowserScreen({
1675
1931
  if (running) {
1676
1932
  const names2 = running.names;
1677
1933
  const stream = tab === "bundles" ? () => installBundle(client, names2[0], opts) : () => install(client, names2, opts);
1678
- return /* @__PURE__ */ jsx9(Installer, {
1934
+ return /* @__PURE__ */ jsx11(Installer, {
1679
1935
  title: running.title,
1680
1936
  stream,
1681
1937
  exitWhenDone: true,
@@ -1683,13 +1939,13 @@ function BrowserScreen({
1683
1939
  });
1684
1940
  }
1685
1941
  if (!catalog) {
1686
- return /* @__PURE__ */ jsx9(Frame, {
1942
+ return /* @__PURE__ */ jsx11(Frame, {
1687
1943
  title: "yourskills",
1688
1944
  width,
1689
- children: /* @__PURE__ */ jsxs8(Text9, {
1945
+ children: /* @__PURE__ */ jsxs10(Text10, {
1690
1946
  children: [
1691
- /* @__PURE__ */ jsx9(Spinner, {}),
1692
- /* @__PURE__ */ jsx9(Text9, {
1947
+ /* @__PURE__ */ jsx11(Spinner, {}),
1948
+ /* @__PURE__ */ jsx11(Text10, {
1693
1949
  dimColor: true,
1694
1950
  children: " Loading your registry..."
1695
1951
  })
@@ -1701,37 +1957,37 @@ function BrowserScreen({
1701
1957
  const first = Math.max(0, Math.min(cursor - Math.floor(size / 2), visible.length - size));
1702
1958
  const page = visible.slice(Math.max(0, first), Math.max(0, first) + size);
1703
1959
  const names = nameWidth(visible.length > 0 ? visible : catalog[tab]);
1704
- return /* @__PURE__ */ jsx9(Frame, {
1960
+ return /* @__PURE__ */ jsx11(Frame, {
1705
1961
  title: "yourskills",
1706
1962
  width,
1707
- children: /* @__PURE__ */ jsxs8(Box7, {
1963
+ children: /* @__PURE__ */ jsxs10(Box8, {
1708
1964
  flexDirection: "column",
1709
1965
  children: [
1710
- /* @__PURE__ */ jsx9(TabBar, {
1966
+ /* @__PURE__ */ jsx11(TabBar, {
1711
1967
  active: tab
1712
1968
  }),
1713
- /* @__PURE__ */ jsx9(Divider, {
1969
+ /* @__PURE__ */ jsx11(Divider, {
1714
1970
  width
1715
1971
  }),
1716
- /* @__PURE__ */ jsxs8(Box7, {
1972
+ /* @__PURE__ */ jsxs10(Box8, {
1717
1973
  children: [
1718
- /* @__PURE__ */ jsx9(Text9, {
1974
+ /* @__PURE__ */ jsx11(Text10, {
1719
1975
  color: tone.primary,
1720
1976
  children: "search> "
1721
1977
  }),
1722
- /* @__PURE__ */ jsx9(SearchInput, {
1978
+ /* @__PURE__ */ jsx11(SearchInput, {
1723
1979
  value: query,
1724
1980
  placeholder: "type to filter"
1725
1981
  })
1726
1982
  ]
1727
1983
  }),
1728
- /* @__PURE__ */ jsx9(Divider, {
1984
+ /* @__PURE__ */ jsx11(Divider, {
1729
1985
  width
1730
1986
  }),
1731
- page.length === 0 ? /* @__PURE__ */ jsx9(Text9, {
1987
+ page.length === 0 ? /* @__PURE__ */ jsx11(Text10, {
1732
1988
  dimColor: true,
1733
1989
  children: `No ${tab} match "${query}".`
1734
- }) : page.map((row, i) => /* @__PURE__ */ jsx9(SkillRow, {
1990
+ }) : page.map((row, i) => /* @__PURE__ */ jsx11(SkillRow, {
1735
1991
  row,
1736
1992
  width: width - 2,
1737
1993
  names,
@@ -1739,23 +1995,23 @@ function BrowserScreen({
1739
1995
  cursor: Math.max(0, first) + i === cursor,
1740
1996
  selected: selected.has(row.name)
1741
1997
  }, row.name)),
1742
- /* @__PURE__ */ jsx9(Divider, {
1998
+ /* @__PURE__ */ jsx11(Divider, {
1743
1999
  width
1744
2000
  }),
1745
- /* @__PURE__ */ jsxs8(Box7, {
2001
+ /* @__PURE__ */ jsxs10(Box8, {
1746
2002
  children: [
1747
- /* @__PURE__ */ jsx9(Text9, {
2003
+ /* @__PURE__ */ jsx11(Text10, {
1748
2004
  dimColor: true,
1749
2005
  children: `${visible.length > 0 ? cursor + 1 : 0}/${visible.length}`
1750
2006
  }),
1751
- selected.size > 0 ? /* @__PURE__ */ jsx9(Text9, {
2007
+ selected.size > 0 ? /* @__PURE__ */ jsx11(Text10, {
1752
2008
  color: tone.notice,
1753
2009
  children: ` ${selected.size} selected`
1754
2010
  }) : null,
1755
- /* @__PURE__ */ jsx9(Text9, {
2011
+ /* @__PURE__ */ jsx11(Text10, {
1756
2012
  children: " "
1757
2013
  }),
1758
- /* @__PURE__ */ jsx9(Hints, {
2014
+ /* @__PURE__ */ jsx11(Hints, {
1759
2015
  hints: [
1760
2016
  ["^v", "move"],
1761
2017
  ["space", "select"],
@@ -1771,9 +2027,9 @@ function BrowserScreen({
1771
2027
  });
1772
2028
  }
1773
2029
  // src/catalog/screens/catalog.tsx
1774
- import { Box as Box8, Text as Text10, useApp as useApp5 } from "ink";
1775
- import { useEffect as useEffect6, useState as useState6 } from "react";
1776
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2030
+ import { Box as Box9, Text as Text11, useApp as useApp5 } from "ink";
2031
+ import { useEffect as useEffect6, useState as useState7 } from "react";
2032
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1777
2033
  function CatalogScreen({
1778
2034
  title,
1779
2035
  load,
@@ -1781,7 +2037,7 @@ function CatalogScreen({
1781
2037
  }) {
1782
2038
  const { exit } = useApp5();
1783
2039
  const width = useFrameWidth();
1784
- const [rows, setRows] = useState6(null);
2040
+ const [rows, setRows] = useState7(null);
1785
2041
  useEffect6(() => {
1786
2042
  let live = true;
1787
2043
  load().then((result) => {
@@ -1798,13 +2054,13 @@ function CatalogScreen({
1798
2054
  };
1799
2055
  }, [load, exit, onError]);
1800
2056
  if (rows === null) {
1801
- return /* @__PURE__ */ jsx10(Frame, {
2057
+ return /* @__PURE__ */ jsx12(Frame, {
1802
2058
  title,
1803
2059
  width,
1804
- children: /* @__PURE__ */ jsxs9(Text10, {
2060
+ children: /* @__PURE__ */ jsxs11(Text11, {
1805
2061
  children: [
1806
- /* @__PURE__ */ jsx10(Spinner, {}),
1807
- /* @__PURE__ */ jsx10(Text10, {
2062
+ /* @__PURE__ */ jsx12(Spinner, {}),
2063
+ /* @__PURE__ */ jsx12(Text11, {
1808
2064
  dimColor: true,
1809
2065
  children: " Loading..."
1810
2066
  })
@@ -1813,22 +2069,22 @@ function CatalogScreen({
1813
2069
  });
1814
2070
  }
1815
2071
  if (rows.length === 0) {
1816
- return /* @__PURE__ */ jsx10(Frame, {
2072
+ return /* @__PURE__ */ jsx12(Frame, {
1817
2073
  title,
1818
2074
  width,
1819
- children: /* @__PURE__ */ jsx10(Text10, {
2075
+ children: /* @__PURE__ */ jsx12(Text11, {
1820
2076
  dimColor: true,
1821
2077
  children: "No skills found."
1822
2078
  })
1823
2079
  });
1824
2080
  }
1825
2081
  const names = nameWidth(rows);
1826
- return /* @__PURE__ */ jsx10(Frame, {
2082
+ return /* @__PURE__ */ jsx12(Frame, {
1827
2083
  title,
1828
2084
  width,
1829
- children: /* @__PURE__ */ jsx10(Box8, {
2085
+ children: /* @__PURE__ */ jsx12(Box9, {
1830
2086
  flexDirection: "column",
1831
- children: rows.map((row) => /* @__PURE__ */ jsx10(SkillRow, {
2087
+ children: rows.map((row) => /* @__PURE__ */ jsx12(SkillRow, {
1832
2088
  row,
1833
2089
  width: width - 2,
1834
2090
  names
@@ -2007,6 +2263,75 @@ async function doctorReport(nearest, server) {
2007
2263
  failed: checks.some((check) => !check.ok)
2008
2264
  };
2009
2265
  }
2266
+ // src/errors.ts
2267
+ function isUnauthorized(error) {
2268
+ if (!(error instanceof Error))
2269
+ return false;
2270
+ const data = error.data;
2271
+ if (data?.code === "UNAUTHORIZED" || data?.httpStatus === 401)
2272
+ return true;
2273
+ return error.message.startsWith("Not logged in") || error.message.startsWith("Session is no longer valid");
2274
+ }
2275
+
2276
+ class CliError extends Error {
2277
+ why;
2278
+ next;
2279
+ code;
2280
+ constructor(what, detail = {}) {
2281
+ super(what);
2282
+ this.name = "CliError";
2283
+ this.why = detail.why;
2284
+ this.next = detail.next;
2285
+ this.code = detail.code ?? EXIT.failed;
2286
+ }
2287
+ }
2288
+ var EXIT = {
2289
+ ok: 0,
2290
+ failed: 1,
2291
+ usage: 2,
2292
+ session: 3
2293
+ };
2294
+ var EXIT_CODES = [
2295
+ [EXIT.ok, "done"],
2296
+ [
2297
+ EXIT.failed,
2298
+ "something did not land: a failed install, a pending pull request"
2299
+ ],
2300
+ [EXIT.usage, "the command line was wrong; the last line says the spelling"],
2301
+ [EXIT.session, "needs a session: run `yourskills login`"]
2302
+ ];
2303
+ var LOGIN = "yourskills login";
2304
+ function describeError(error) {
2305
+ if (error instanceof CliError) {
2306
+ return {
2307
+ lines: shape(error.message, error.why, error.next),
2308
+ code: error.code
2309
+ };
2310
+ }
2311
+ const message = error instanceof Error ? error.message : String(error);
2312
+ if (isUnauthorized(error)) {
2313
+ return {
2314
+ lines: shape("you are not signed in", message.startsWith("Session") ? "the registry no longer accepts the token on this machine" : "the catalog needs a session, and there is no token on this machine", LOGIN),
2315
+ code: EXIT.session
2316
+ };
2317
+ }
2318
+ if (/fetch failed|ECONNREFUSED|ENOTFOUND|timed? ?out/i.test(message)) {
2319
+ return {
2320
+ lines: shape("could not reach the registry", message, "check your connection, then run the same command again"),
2321
+ code: EXIT.failed
2322
+ };
2323
+ }
2324
+ return { lines: shape(message), code: EXIT.failed };
2325
+ }
2326
+ function shape(what, why, next) {
2327
+ const lines = [what];
2328
+ if (why)
2329
+ lines.push(` ${why}`);
2330
+ if (next)
2331
+ lines.push(` ${next}`);
2332
+ return lines;
2333
+ }
2334
+
2010
2335
  // src/hooks/adapters.ts
2011
2336
  import { homedir as homedir3 } from "node:os";
2012
2337
  import { join as join6 } from "node:path";
@@ -2023,7 +2348,8 @@ var UPDATE_CHECK_ENTRY = {
2023
2348
  kind: "update-check",
2024
2349
  event: "SessionStart",
2025
2350
  matcher: "startup|resume",
2026
- command: `${BINARY} hook session-start`
2351
+ command: `${BINARY} hook session-start`,
2352
+ timeout: 120
2027
2353
  };
2028
2354
  var USAGE_ENTRY = {
2029
2355
  kind: "usage",
@@ -2061,57 +2387,294 @@ function agentForEvent(kind, event) {
2061
2387
  const owner = HOOK_ADAPTERS.find((adapter) => adapter.entries.some((entry) => entry.kind === kind && entry.event === event));
2062
2388
  return owner?.agent ?? "unknown";
2063
2389
  }
2064
- // src/hooks/ensure.ts
2065
- import { existsSync as existsSync3 } from "node:fs";
2066
-
2067
- // src/usage/queue.ts
2068
- import { mkdirSync as mkdirSync3 } from "node:fs";
2069
- import { appendFile, readFile as readFile3, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
2070
- import { join as join7 } from "node:path";
2390
+ // ../../packages/bundles/src/drift.ts
2391
+ var SHORT_SHA = 7;
2392
+ function namesSameCommit(prefix, full) {
2393
+ const short = prefix.trim().toLowerCase();
2394
+ const long = full.trim().toLowerCase();
2395
+ if (short === long)
2396
+ return true;
2397
+ return short.length >= SHORT_SHA && long.startsWith(short);
2398
+ }
2071
2399
 
2072
- // ../../node_modules/.bun/@trpc+client@11.18.0+acc6907a906fa511/node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
2073
- var __create = Object.create;
2074
- var __defProp = Object.defineProperty;
2075
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2076
- var __getOwnPropNames = Object.getOwnPropertyNames;
2077
- var __getProtoOf = Object.getPrototypeOf;
2078
- var __hasOwnProp = Object.prototype.hasOwnProperty;
2079
- var __commonJS = (cb, mod) => function() {
2080
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
2081
- };
2082
- var __copyProps = (to, from, except, desc) => {
2083
- if (from && typeof from === "object" || typeof from === "function")
2084
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key;i < n; i++) {
2085
- key = keys[i];
2086
- if (!__hasOwnProp.call(to, key) && key !== except)
2087
- __defProp(to, key, {
2088
- get: ((k) => from[k]).bind(null, key),
2089
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
2090
- });
2400
+ // src/installed/installed.ts
2401
+ var shortRef = (ref) => ref.slice(0, SHORT_SHA);
2402
+ function installedRows(nearest) {
2403
+ return Object.values(nearest.manifest.skills).map((entry) => ({
2404
+ name: entry.name,
2405
+ ref: entry.ref,
2406
+ bundle: entry.bundle ?? null,
2407
+ scope: nearest.manifest.scope
2408
+ })).sort((a, b) => a.name.localeCompare(b.name));
2409
+ }
2410
+ function columns(rows) {
2411
+ const names = Math.max(...rows.map((r) => r.name.length));
2412
+ const bundles = Math.max(...rows.map((r) => (r.bundle ?? "-").length));
2413
+ return rows.map((row) => ({
2414
+ mark: "none",
2415
+ text: `${row.name.padEnd(names)} ${shortRef(row.ref)}`,
2416
+ note: `${(row.bundle ?? "-").padEnd(bundles)} ${row.scope}`
2417
+ }));
2418
+ }
2419
+ function installedReport(nearest) {
2420
+ const rows = installedRows(nearest);
2421
+ const lines = rows.length === 0 ? [
2422
+ {
2423
+ mark: "none",
2424
+ text: "Nothing installed. Run `yourskills bundle <name>` or `yourskills add <skill>`.",
2425
+ note: nearest.path
2091
2426
  }
2092
- return to;
2093
- };
2094
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
2095
- value: mod,
2096
- enumerable: true
2097
- }) : target, mod));
2098
- var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
2099
- function _typeof$2(o) {
2100
- "@babel/helpers - typeof";
2101
- return module.exports = _typeof$2 = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o$1) {
2102
- return typeof o$1;
2103
- } : function(o$1) {
2104
- return o$1 && typeof Symbol == "function" && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
2105
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
2106
- }
2107
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
2108
- } });
2109
- var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
2110
- var _typeof$1 = require_typeof()["default"];
2111
- function toPrimitive$1(t, r) {
2112
- if (_typeof$1(t) != "object" || !t)
2113
- return t;
2114
- var e = t[Symbol.toPrimitive];
2427
+ ] : columns(rows);
2428
+ return {
2429
+ title: "installed",
2430
+ lines,
2431
+ json: {
2432
+ manifest: nearest.path,
2433
+ scope: nearest.manifest.scope,
2434
+ skills: rows
2435
+ },
2436
+ ...rows.length === 0 ? {
2437
+ next: {
2438
+ command: "yourskills browse",
2439
+ why: "pick something to install"
2440
+ }
2441
+ } : {}
2442
+ };
2443
+ }
2444
+ // src/installed/outdated.ts
2445
+ function hasMoved(installed, available) {
2446
+ return !(namesSameCommit(available, installed) || namesSameCommit(installed, available));
2447
+ }
2448
+ function drift(manifest, pins, catalog) {
2449
+ const byExternalId = new Map(catalog.map((row) => [row.externalId, row]));
2450
+ const byName = new Map(catalog.map((row) => [row.name, row]));
2451
+ const moved = [];
2452
+ const unknown = [];
2453
+ for (const entry of Object.values(manifest.skills)) {
2454
+ const row = byExternalId.get(entry.externalId) ?? byName.get(entry.name);
2455
+ if (!row) {
2456
+ unknown.push(entry.name);
2457
+ continue;
2458
+ }
2459
+ const pin = entry.bundle ? pins.find((p) => p.bundle === entry.bundle && p.skillId === row.id) : undefined;
2460
+ const available = pin?.pinnedSha ?? row.currentSha;
2461
+ if (!hasMoved(entry.ref, available))
2462
+ continue;
2463
+ moved.push({
2464
+ name: entry.name,
2465
+ bundle: entry.bundle ?? null,
2466
+ installed: entry.ref,
2467
+ available,
2468
+ deprecated: pin?.deprecated ?? row.status === "deprecated"
2469
+ });
2470
+ }
2471
+ moved.sort((a, b) => a.name.localeCompare(b.name));
2472
+ return { moved, unknown };
2473
+ }
2474
+ async function driftNow(client, nearest) {
2475
+ const [pins, catalog] = await Promise.all([
2476
+ client.bundles.pins.query(),
2477
+ client.skills.list.query({})
2478
+ ]);
2479
+ return drift(nearest.manifest, pins, catalog);
2480
+ }
2481
+ async function outdatedReport(client, nearest, quiet = false) {
2482
+ let report;
2483
+ try {
2484
+ report = await driftNow(client, nearest);
2485
+ } catch (error) {
2486
+ const message = error instanceof Error ? error.message : String(error);
2487
+ return {
2488
+ title: "outdated",
2489
+ lines: [
2490
+ { mark: "fail", text: "Could not reach the registry.", note: message }
2491
+ ],
2492
+ json: { manifest: nearest.path, error: message },
2493
+ silent: quiet
2494
+ };
2495
+ }
2496
+ const lines = report.moved.map((row) => ({
2497
+ mark: "pending",
2498
+ text: `${row.name} ${shortRef(row.installed)} -> ${shortRef(row.available)}`,
2499
+ note: [row.bundle ?? "-", row.deprecated ? "[deprecated]" : ""].filter(Boolean).join(" ")
2500
+ }));
2501
+ for (const name of report.unknown) {
2502
+ lines.push({
2503
+ mark: "fail",
2504
+ text: name,
2505
+ note: "no longer in your registry"
2506
+ });
2507
+ }
2508
+ const current = report.moved.length === 0 && report.unknown.length === 0;
2509
+ if (current && !quiet) {
2510
+ lines.push({
2511
+ mark: "ok",
2512
+ text: "Everything is at the ref your registry pins."
2513
+ });
2514
+ }
2515
+ return {
2516
+ title: "outdated",
2517
+ lines,
2518
+ json: {
2519
+ manifest: nearest.path,
2520
+ outdated: report.moved,
2521
+ unknown: report.unknown
2522
+ },
2523
+ silent: quiet && current,
2524
+ ...report.moved.length > 0 ? {
2525
+ next: {
2526
+ command: "yourskills update --all",
2527
+ why: "install what moved"
2528
+ }
2529
+ } : {}
2530
+ };
2531
+ }
2532
+ // src/hooks/emit.ts
2533
+ function describe(outcome) {
2534
+ switch (outcome.kind) {
2535
+ case "quiet":
2536
+ return [];
2537
+ case "signed-out":
2538
+ return [
2539
+ "yourskills: your sign-in has expired, so your skills are no longer kept up to date.",
2540
+ " run: yourskills login"
2541
+ ];
2542
+ case "behind": {
2543
+ const lines = [
2544
+ `yourskills: ${count(outcome.moved.length, "skill")} ${outcome.moved.length === 1 ? "has" : "have"} a newer version in your registry (autoUpdate is off)`
2545
+ ];
2546
+ for (const row of outcome.moved) {
2547
+ lines.push(` ${row.name} ${shortRef(row.installed)} -> ${shortRef(row.available)}${row.deprecated ? " [deprecated]" : ""}`);
2548
+ }
2549
+ lines.push(" run: yourskills update --all");
2550
+ return lines;
2551
+ }
2552
+ case "updated": {
2553
+ const { applied, failed } = outcome;
2554
+ const lines = [
2555
+ applied.length > 0 ? `yourskills: updated ${count(applied.length, "skill")} to what your registry pins; ${applied.length === 1 ? "it is" : "they are"} live in this session.` : `yourskills: could not update ${count(failed.length, "skill")}`
2556
+ ];
2557
+ for (const row of applied) {
2558
+ lines.push(` ${row.name} ${row.from} -> ${row.to}`);
2559
+ }
2560
+ for (const row of failed) {
2561
+ lines.push(` ${row.name} failed: ${row.message}`);
2562
+ }
2563
+ if (applied.length > 0)
2564
+ lines.push(` recorded in ${outcome.manifest}`);
2565
+ if (failed.length > 0)
2566
+ lines.push(" run: yourskills doctor");
2567
+ return lines;
2568
+ }
2569
+ }
2570
+ }
2571
+ function describeForModel(outcome) {
2572
+ switch (outcome.kind) {
2573
+ case "quiet":
2574
+ return [];
2575
+ case "signed-out":
2576
+ return [
2577
+ "yourskills: the user's session with the skills registry has expired, so installed skills are no longer being updated. The user has been told to run `yourskills login`; it is their call."
2578
+ ];
2579
+ case "behind": {
2580
+ const lines = [
2581
+ `yourskills: ${count(outcome.moved.length, "installed skill")} ${outcome.moved.length === 1 ? "is" : "are"} behind the registry. Automatic updates are off; the user has been told and it is their call whether to run \`yourskills update --all\`.`
2582
+ ];
2583
+ for (const row of outcome.moved) {
2584
+ lines.push(` ${row.name} installed ${shortRef(row.installed)}, registry pins ${shortRef(row.available)}${row.deprecated ? " [deprecated]" : ""}`);
2585
+ }
2586
+ return lines;
2587
+ }
2588
+ case "updated": {
2589
+ const { applied, failed } = outcome;
2590
+ const lines = [];
2591
+ if (applied.length > 0) {
2592
+ lines.push(`yourskills updated ${count(applied.length, "installed skill")} on disk at the start of this session. The contents changed: re-read a skill before relying on what you remember of it.`);
2593
+ for (const row of applied) {
2594
+ lines.push(` ${row.name} ${row.from} -> ${row.to}`);
2595
+ }
2596
+ lines.push(` recorded in ${outcome.manifest}`);
2597
+ }
2598
+ if (failed.length > 0) {
2599
+ lines.push(`yourskills could not update ${count(failed.length, "installed skill")}; the version on disk is unchanged. The user has been told.`);
2600
+ for (const row of failed) {
2601
+ lines.push(` ${row.name} ${row.message}`);
2602
+ }
2603
+ }
2604
+ return lines;
2605
+ }
2606
+ }
2607
+ }
2608
+ function count(n, noun) {
2609
+ return `${n} ${noun}${n === 1 ? "" : "s"}`;
2610
+ }
2611
+ function emit(outcome) {
2612
+ const lines = describe(outcome);
2613
+ if (lines.length === 0)
2614
+ return;
2615
+ const installed = outcome.kind === "updated" && outcome.applied.length > 0;
2616
+ console.log(JSON.stringify({
2617
+ systemMessage: lines.join(`
2618
+ `),
2619
+ hookSpecificOutput: {
2620
+ hookEventName: "SessionStart",
2621
+ additionalContext: describeForModel(outcome).join(`
2622
+ `),
2623
+ ...installed ? { reloadSkills: true } : {}
2624
+ }
2625
+ }));
2626
+ }
2627
+ // src/hooks/ensure.ts
2628
+ import { existsSync as existsSync3 } from "node:fs";
2629
+
2630
+ // src/usage/queue.ts
2631
+ import { mkdirSync as mkdirSync3 } from "node:fs";
2632
+ import { appendFile, readFile as readFile3, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
2633
+ import { join as join7 } from "node:path";
2634
+
2635
+ // ../../node_modules/.bun/@trpc+client@11.18.0+acc6907a906fa511/node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
2636
+ var __create = Object.create;
2637
+ var __defProp = Object.defineProperty;
2638
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2639
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2640
+ var __getProtoOf = Object.getPrototypeOf;
2641
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
2642
+ var __commonJS = (cb, mod) => function() {
2643
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
2644
+ };
2645
+ var __copyProps = (to, from, except, desc) => {
2646
+ if (from && typeof from === "object" || typeof from === "function")
2647
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key;i < n; i++) {
2648
+ key = keys[i];
2649
+ if (!__hasOwnProp.call(to, key) && key !== except)
2650
+ __defProp(to, key, {
2651
+ get: ((k) => from[k]).bind(null, key),
2652
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
2653
+ });
2654
+ }
2655
+ return to;
2656
+ };
2657
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
2658
+ value: mod,
2659
+ enumerable: true
2660
+ }) : target, mod));
2661
+ var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
2662
+ function _typeof$2(o) {
2663
+ "@babel/helpers - typeof";
2664
+ return module.exports = _typeof$2 = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o$1) {
2665
+ return typeof o$1;
2666
+ } : function(o$1) {
2667
+ return o$1 && typeof Symbol == "function" && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
2668
+ }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
2669
+ }
2670
+ module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
2671
+ } });
2672
+ var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
2673
+ var _typeof$1 = require_typeof()["default"];
2674
+ function toPrimitive$1(t, r) {
2675
+ if (_typeof$1(t) != "object" || !t)
2676
+ return t;
2677
+ var e = t[Symbol.toPrimitive];
2115
2678
  if (e !== undefined) {
2116
2679
  var i = e.call(t, r || "default");
2117
2680
  if (_typeof$1(i) != "object")
@@ -2879,10 +3442,10 @@ function dataLoader(batchLoader) {
2879
3442
  }
2880
3443
  function allAbortSignals(...signals) {
2881
3444
  const ac = new AbortController;
2882
- const count = signals.length;
3445
+ const count2 = signals.length;
2883
3446
  let abortedCount = 0;
2884
3447
  const onAbort = () => {
2885
- if (++abortedCount === count)
3448
+ if (++abortedCount === count2)
2886
3449
  ac.abort();
2887
3450
  };
2888
3451
  for (const signal of signals)
@@ -3457,6 +4020,55 @@ var import_awaitAsyncGenerator = __toESM(require_awaitAsyncGenerator(), 1);
3457
4020
  var import_wrapAsyncGenerator = __toESM(require_wrapAsyncGenerator(), 1);
3458
4021
  var import_objectSpread29 = __toESM(require_objectSpread2(), 1);
3459
4022
 
4023
+ // src/errors.ts
4024
+ function isUnauthorized2(error) {
4025
+ if (!(error instanceof Error))
4026
+ return false;
4027
+ const data = error.data;
4028
+ if (data?.code === "UNAUTHORIZED" || data?.httpStatus === 401)
4029
+ return true;
4030
+ return error.message.startsWith("Not logged in") || error.message.startsWith("Session is no longer valid");
4031
+ }
4032
+ var EXIT2 = {
4033
+ ok: 0,
4034
+ failed: 1,
4035
+ usage: 2,
4036
+ session: 3
4037
+ };
4038
+ var EXIT_CODES2 = [
4039
+ [EXIT2.ok, "done"],
4040
+ [
4041
+ EXIT2.failed,
4042
+ "something did not land: a failed install, a pending pull request"
4043
+ ],
4044
+ [EXIT2.usage, "the command line was wrong; the last line says the spelling"],
4045
+ [EXIT2.session, "needs a session: run `yourskills login`"]
4046
+ ];
4047
+ function closest(word, candidates, maxDistance = 2) {
4048
+ let best;
4049
+ for (const candidate of candidates) {
4050
+ const distance = edits(word.toLowerCase(), candidate.toLowerCase());
4051
+ if (distance <= maxDistance && (!best || distance < best.distance)) {
4052
+ best = { word: candidate, distance };
4053
+ }
4054
+ }
4055
+ return best?.word;
4056
+ }
4057
+ function edits(a, b) {
4058
+ const rows = a.length + 1;
4059
+ const cols = b.length + 1;
4060
+ let previous = Array.from({ length: cols }, (_, j2) => j2);
4061
+ for (let i = 1;i < rows; i++) {
4062
+ const current = [i];
4063
+ for (let j2 = 1;j2 < cols; j2++) {
4064
+ const cost = a[i - 1] === b[j2 - 1] ? 0 : 1;
4065
+ current[j2] = Math.min(previous[j2] + 1, current[j2 - 1] + 1, previous[j2 - 1] + cost);
4066
+ }
4067
+ previous = current;
4068
+ }
4069
+ return previous[cols - 1];
4070
+ }
4071
+
3460
4072
  // src/client.ts
3461
4073
  async function createClient(server) {
3462
4074
  const { token, base } = await requireToken(server);
@@ -3638,17 +4250,25 @@ function addHooks(settings, entries) {
3638
4250
  const hooks = { ...next.hooks ?? {} };
3639
4251
  const groups = [...hooks[entry.event] ?? []];
3640
4252
  const ours = groups.findIndex((group) => group.matcher === entry.matcher && groupIsOurs(group));
4253
+ const ourCommand = {
4254
+ type: "command",
4255
+ command: entry.command,
4256
+ ...entry.timeout === undefined ? {} : { timeout: entry.timeout }
4257
+ };
3641
4258
  if (ours === -1) {
3642
- groups.push({
3643
- matcher: entry.matcher,
3644
- hooks: [{ type: "command", command: entry.command }]
3645
- });
4259
+ groups.push({ matcher: entry.matcher, hooks: [ourCommand] });
3646
4260
  } else {
3647
4261
  const group = groups[ours];
3648
4262
  const commands = [...group.hooks ?? []];
3649
- if (commands.some((hook) => hook.command === entry.command))
3650
- continue;
3651
- commands.push({ type: "command", command: entry.command });
4263
+ const at = commands.findIndex((hook) => hook.command === entry.command);
4264
+ if (at === -1) {
4265
+ commands.push(ourCommand);
4266
+ } else {
4267
+ const existing = commands[at];
4268
+ if (existing.timeout === entry.timeout)
4269
+ continue;
4270
+ commands[at] = { ...existing, ...ourCommand };
4271
+ }
3652
4272
  groups[ours] = { ...group, hooks: commands };
3653
4273
  }
3654
4274
  hooks[entry.event] = groups;
@@ -3755,17 +4375,16 @@ async function registerInto(adapter) {
3755
4375
  return { agent: adapter.agent, path, entries: wanted };
3756
4376
  }
3757
4377
  function linesFor(registered) {
4378
+ const agents2 = registered.map((r) => r.agent).join(", ");
3758
4379
  const lines = [
3759
- "yourskills registered its update-check hook so your agent can tell you when an installed skill has moved:"
4380
+ `+ yourskills will check your skills once a day, when ${agents2} starts, and update what moved`
3760
4381
  ];
3761
- for (const { agent, path, entries } of registered) {
3762
- for (const entry of entries) {
3763
- lines.push(` ${agent} ${entry.event}(${entry.matcher}) ${path}`);
3764
- }
4382
+ for (const { agent, path } of registered) {
4383
+ lines.push(` ${agent} ${path}`);
3765
4384
  }
3766
- lines.push(" undo any time with `yourskills hooks uninstall`");
4385
+ lines.push(" undo with: yourskills hooks uninstall");
3767
4386
  if (!trackingDisabledByEnv()) {
3768
- lines.push(" usage counting stays off; `yourskills hooks install --track` turns it on");
4387
+ lines.push(" usage counting stays off; turn it on with: yourskills hooks install --track");
3769
4388
  }
3770
4389
  return lines;
3771
4390
  }
@@ -3865,8 +4484,12 @@ async function installInto(adapter, optedIn) {
3865
4484
  result.installed.push(entry.kind);
3866
4485
  }
3867
4486
  const merged = addHooks(file.settings, wanted);
3868
- if (serialiseSettings(merged, file.indent) !== serialiseSettings(file.settings, file.indent)) {
4487
+ const changed = serialiseSettings(merged, file.indent) !== serialiseSettings(file.settings, file.indent);
4488
+ if (changed)
3869
4489
  await writeSettingsFile(path, merged, file.indent);
4490
+ if (changed && result.already.length > 0) {
4491
+ result.repaired = result.already;
4492
+ result.already = [];
3870
4493
  }
3871
4494
  return result;
3872
4495
  }
@@ -3887,6 +4510,13 @@ function linesFor2(result) {
3887
4510
  note: result.path ?? ""
3888
4511
  });
3889
4512
  }
4513
+ for (const kind of result.repaired ?? []) {
4514
+ lines.push({
4515
+ mark: "ok",
4516
+ text: `${result.agent} ${kind}`,
4517
+ note: `updated ${result.path ?? ""}`.trim()
4518
+ });
4519
+ }
3890
4520
  for (const kind of result.already) {
3891
4521
  lines.push({
3892
4522
  mark: "ok",
@@ -4048,141 +4678,116 @@ async function hooksStatus() {
4048
4678
  json: { track: optedIn, agents: agents2 }
4049
4679
  };
4050
4680
  }
4051
- // ../../packages/bundles/src/drift.ts
4052
- var SHORT_SHA = 7;
4053
- function namesSameCommit(prefix, full) {
4054
- const short = prefix.trim().toLowerCase();
4055
- const long = full.trim().toLowerCase();
4056
- if (short === long)
4681
+ // src/hooks/session.ts
4682
+ var CHECK_BUDGET_MS = 5000;
4683
+ var UPDATE_BUDGET_MS = 90000;
4684
+ var DAY_MS = 86400000;
4685
+ function updateCheckDue(last, now = Date.now()) {
4686
+ if (!last)
4057
4687
  return true;
4058
- return short.length >= SHORT_SHA && long.startsWith(short);
4688
+ const at = Date.parse(last);
4689
+ return Number.isNaN(at) || now - at >= DAY_MS;
4059
4690
  }
4060
-
4061
- // src/installed/installed.ts
4062
- var shortRef = (ref) => ref.slice(0, SHORT_SHA);
4063
- function installedRows(nearest) {
4064
- return Object.values(nearest.manifest.skills).map((entry) => ({
4065
- name: entry.name,
4066
- ref: entry.ref,
4067
- bundle: entry.bundle ?? null,
4068
- scope: nearest.manifest.scope
4069
- })).sort((a, b) => a.name.localeCompare(b.name));
4691
+ function withBudget(work, ms) {
4692
+ return Promise.race([
4693
+ work,
4694
+ new Promise((resolve3) => {
4695
+ const timer = setTimeout(() => resolve3(undefined), ms);
4696
+ timer.unref?.();
4697
+ })
4698
+ ]);
4070
4699
  }
4071
- function columns(rows) {
4072
- const names = Math.max(...rows.map((r) => r.name.length));
4073
- const bundles = Math.max(...rows.map((r) => (r.bundle ?? "-").length));
4074
- return rows.map((row) => ({
4075
- mark: "none",
4076
- text: `${row.name.padEnd(names)} ${shortRef(row.ref)}`,
4077
- note: `${(row.bundle ?? "-").padEnd(bundles)} ${row.scope}`
4078
- }));
4700
+ async function check(client, nearest) {
4701
+ try {
4702
+ const report = await driftNow(client, nearest);
4703
+ return { ok: true, moved: report.moved };
4704
+ } catch (error) {
4705
+ return { ok: false, signedOut: isUnauthorized2(error) };
4706
+ }
4079
4707
  }
4080
- function installedReport(nearest) {
4081
- const rows = installedRows(nearest);
4082
- const lines = rows.length === 0 ? [
4083
- {
4084
- mark: "none",
4085
- text: "Nothing installed. Run `yourskills bundle <name>` or `yourskills add <skill>`.",
4086
- note: nearest.path
4708
+ async function applyUpdates(client, nearest, moved, deadline) {
4709
+ const opts = { scope: nearest.scope };
4710
+ const wanted = new Map(moved.map((row) => [row.name, row]));
4711
+ const applied = [];
4712
+ const failed = [];
4713
+ const consume = async (label, stream) => {
4714
+ try {
4715
+ await drain(stream);
4716
+ } catch (error) {
4717
+ failed.push({
4718
+ name: label,
4719
+ message: error instanceof Error ? error.message : String(error)
4720
+ });
4087
4721
  }
4088
- ] : columns(rows);
4089
- return {
4090
- title: "installed",
4091
- lines,
4092
- json: {
4093
- manifest: nearest.path,
4094
- scope: nearest.manifest.scope,
4095
- skills: rows
4722
+ };
4723
+ const drain = async (stream) => {
4724
+ for await (const event of stream) {
4725
+ if (event.kind === "installed") {
4726
+ const row = wanted.get(event.location.skillName);
4727
+ if (row) {
4728
+ applied.push({
4729
+ name: row.name,
4730
+ from: shortRef(row.installed),
4731
+ to: shortRef(row.available)
4732
+ });
4733
+ }
4734
+ } else if (event.kind === "failed" && wanted.has(event.name)) {
4735
+ failed.push({ name: event.name, message: event.message });
4736
+ }
4096
4737
  }
4097
4738
  };
4739
+ const bundles = [
4740
+ ...new Set(moved.map((row) => row.bundle).filter((name) => !!name))
4741
+ ].sort();
4742
+ const loose = moved.filter((row) => !row.bundle).map((row) => row.name).sort();
4743
+ for (const bundle of bundles) {
4744
+ if (Date.now() > deadline)
4745
+ break;
4746
+ await consume(bundle, installBundle(client, bundle, opts));
4747
+ }
4748
+ if (loose.length > 0 && Date.now() <= deadline) {
4749
+ await consume(loose.join(", "), install(client, loose, opts));
4750
+ }
4751
+ if (applied.length === 0 && failed.length === 0)
4752
+ return { kind: "quiet" };
4753
+ return { kind: "updated", applied, failed, manifest: nearest.path };
4098
4754
  }
4099
- // src/installed/outdated.ts
4100
- function hasMoved(installed, available) {
4101
- return !(namesSameCommit(available, installed) || namesSameCommit(installed, available));
4102
- }
4103
- function drift(manifest, pins, catalog) {
4104
- const byExternalId = new Map(catalog.map((row) => [row.externalId, row]));
4105
- const byName = new Map(catalog.map((row) => [row.name, row]));
4106
- const moved = [];
4107
- const unknown = [];
4108
- for (const entry of Object.values(manifest.skills)) {
4109
- const row = byExternalId.get(entry.externalId) ?? byName.get(entry.name);
4110
- if (!row) {
4111
- unknown.push(entry.name);
4112
- continue;
4113
- }
4114
- const pin = entry.bundle ? pins.find((p) => p.bundle === entry.bundle && p.skillId === row.id) : undefined;
4115
- const available = pin?.pinnedSha ?? row.currentSha;
4116
- if (!hasMoved(entry.ref, available))
4117
- continue;
4118
- moved.push({
4119
- name: entry.name,
4120
- bundle: entry.bundle ?? null,
4121
- installed: entry.ref,
4122
- available,
4123
- deprecated: pin?.deprecated ?? row.status === "deprecated"
4124
- });
4755
+ async function sessionStart(now = new Date) {
4756
+ const config = await readConfig();
4757
+ if (!updateCheckDue(config.lastUpdateCheck, now.getTime())) {
4758
+ return { kind: "quiet" };
4125
4759
  }
4126
- moved.sort((a, b) => a.name.localeCompare(b.name));
4127
- return { moved, unknown };
4128
- }
4129
- async function outdatedReport(client, nearest, quiet = false) {
4130
- let pins;
4131
- let catalog;
4760
+ await writeConfig({ ...config, lastUpdateCheck: now.toISOString() });
4761
+ if (!config.token)
4762
+ return { kind: "quiet" };
4763
+ let client;
4764
+ let nearest;
4132
4765
  try {
4133
- [pins, catalog] = await Promise.all([
4134
- client.bundles.pins.query(),
4135
- client.skills.list.query({})
4136
- ]);
4137
- } catch (error) {
4138
- const message = error instanceof Error ? error.message : String(error);
4139
- return {
4140
- title: "outdated",
4141
- lines: [
4142
- { mark: "fail", text: "Could not reach the registry.", note: message }
4143
- ],
4144
- json: { manifest: nearest.path, error: message },
4145
- silent: quiet
4146
- };
4147
- }
4148
- const report = drift(nearest.manifest, pins, catalog);
4149
- const lines = report.moved.map((row) => ({
4150
- mark: "pending",
4151
- text: `${row.name} ${shortRef(row.installed)} -> ${shortRef(row.available)}`,
4152
- note: [row.bundle ?? "-", row.deprecated ? "[deprecated]" : ""].filter(Boolean).join(" ")
4153
- }));
4154
- for (const name of report.unknown) {
4155
- lines.push({
4156
- mark: "fail",
4157
- text: name,
4158
- note: "no longer in your registry"
4159
- });
4160
- }
4161
- const current = report.moved.length === 0 && report.unknown.length === 0;
4162
- if (current && !quiet) {
4163
- lines.push({
4164
- mark: "ok",
4165
- text: "Everything is at the ref your registry pins."
4166
- });
4167
- }
4168
- return {
4169
- title: "outdated",
4170
- lines,
4171
- json: {
4172
- manifest: nearest.path,
4173
- outdated: report.moved,
4174
- unknown: report.unknown
4175
- },
4176
- silent: quiet && current
4177
- };
4766
+ client = await createClient();
4767
+ nearest = await nearestManifest();
4768
+ } catch {
4769
+ return { kind: "quiet" };
4770
+ }
4771
+ if (Object.keys(nearest.manifest.skills).length === 0)
4772
+ return { kind: "quiet" };
4773
+ const found = await withBudget(check(client, nearest), CHECK_BUDGET_MS);
4774
+ if (!found)
4775
+ return { kind: "quiet" };
4776
+ if (!found.ok)
4777
+ return found.signedOut ? { kind: "signed-out" } : { kind: "quiet" };
4778
+ if (found.moved.length === 0)
4779
+ return { kind: "quiet" };
4780
+ if (config.autoUpdate === false) {
4781
+ return { kind: "behind", moved: found.moved, manifest: nearest.path };
4782
+ }
4783
+ return applyUpdates(client, nearest, found.moved, Date.now() + UPDATE_BUDGET_MS);
4178
4784
  }
4785
+
4179
4786
  // src/hooks/run.ts
4180
4787
  function isHookEventName(value) {
4181
4788
  return value === "session-start" || value === "post-tool-use";
4182
4789
  }
4183
4790
  var STDIN_BUDGET_MS = 2000;
4184
- var SESSION_BUDGET_MS = 5000;
4185
- var DAY_MS = 86400000;
4186
4791
  async function readStdin() {
4187
4792
  if (process.stdin.isTTY)
4188
4793
  return {};
@@ -4235,46 +4840,19 @@ async function queueInvocation(payload, now = new Date) {
4235
4840
  await appendEvent(event);
4236
4841
  return event;
4237
4842
  }
4238
- function updateCheckDue(last, now = Date.now()) {
4239
- if (!last)
4240
- return true;
4241
- const at = Date.parse(last);
4242
- return Number.isNaN(at) || now - at >= DAY_MS;
4243
- }
4244
- async function sessionStart() {
4245
- await flushQuietly();
4246
- const config = await readConfig();
4247
- if (!updateCheckDue(config.lastUpdateCheck))
4248
- return;
4249
- await writeConfig({ ...config, lastUpdateCheck: new Date().toISOString() });
4250
- const client = await createClient();
4251
- const nearest = await nearestManifest();
4252
- const report = await outdatedReport(client, nearest, true);
4253
- if (!report.silent && report.lines.length > 0) {
4254
- const moved = report.json.outdated?.length ?? 0;
4255
- if (moved > 0) {
4256
- console.log(`yourskills: ${moved} skill(s) behind your registry — run \`yourskills update\``);
4257
- }
4258
- }
4259
- }
4260
4843
  async function runHook(event) {
4261
4844
  try {
4262
4845
  const payload = await readStdin();
4263
4846
  if (event === "post-tool-use")
4264
4847
  await queueInvocation(payload);
4265
4848
  else
4266
- await withBudget(sessionStart(), SESSION_BUDGET_MS);
4849
+ await runSessionStart();
4267
4850
  } catch {}
4268
4851
  process.exitCode = 0;
4269
4852
  }
4270
- function withBudget(work, ms) {
4271
- return Promise.race([
4272
- work,
4273
- new Promise((resolve3) => {
4274
- const timer = setTimeout(() => resolve3(undefined), ms);
4275
- timer.unref?.();
4276
- })
4277
- ]);
4853
+ async function runSessionStart() {
4854
+ await flushQuietly();
4855
+ emit(await sessionStart());
4278
4856
  }
4279
4857
  // src/org/org.ts
4280
4858
  var authBase2 = (base) => `${base.replace(/\/$/, "")}/api/auth`;
@@ -4386,68 +4964,138 @@ async function orgSwitchReport(name, server) {
4386
4964
  };
4387
4965
  }
4388
4966
  // src/report.ts
4967
+ function nextLine(next) {
4968
+ return `next ${next.command} ${next.why}`;
4969
+ }
4389
4970
  function plainLine(line2, glyph) {
4390
4971
  const head = line2.mark === "none" ? "" : `${glyph[line2.mark]} `;
4391
4972
  return `${head}${line2.text}${line2.note ? ` ${line2.note}` : ""}`;
4392
4973
  }
4393
4974
  // src/shell/commands.ts
4394
- function paletteGroups() {
4395
- return [
4396
- {
4397
- group: "Catalog",
4398
- items: [
4399
- { value: "catalog.bundles", label: "browse bundles", hint: "browse" },
4400
- { value: "catalog.skills", label: "browse skills", hint: "list" },
4401
- { value: "catalog.search", label: "search", hint: "search <query>" }
4402
- ]
4403
- },
4404
- {
4405
- group: "Installed",
4406
- items: [
4407
- { value: "installed.list", label: "list installed", hint: "installed" },
4408
- {
4409
- value: "installed.update",
4410
- label: "update all",
4411
- hint: "update --all"
4412
- },
4413
- { value: "installed.outdated", label: "outdated", hint: "outdated" },
4414
- { value: "installed.remove", label: "remove", hint: "remove <skill>" }
4415
- ]
4416
- },
4417
- {
4418
- group: "Account",
4419
- items: [
4420
- { value: "account.login", label: "login", hint: "login" },
4421
- { value: "account.logout", label: "logout", hint: "logout" },
4422
- { value: "account.whoami", label: "whoami", hint: "whoami" },
4423
- { value: "account.org", label: "switch org", hint: "org <name>" }
4424
- ]
4425
- },
4426
- {
4427
- group: "Setup",
4428
- items: [
4429
- {
4430
- value: "setup.scope",
4431
- label: "scope",
4432
- hint: "config set scope project"
4433
- },
4434
- {
4435
- value: "setup.agents",
4436
- label: "agents",
4437
- hint: "config set agents <name>"
4438
- },
4439
- { value: "setup.doctor", label: "doctor", hint: "doctor" }
4440
- ]
4975
+ function count2(n, one, many = `${one}s`) {
4976
+ return `${n} ${n === 1 ? one : many}`;
4977
+ }
4978
+ function describe2(value, facts) {
4979
+ switch (value) {
4980
+ case undefined:
4981
+ return "";
4982
+ case "catalog.bundles":
4983
+ return "browse the bundles your registry publishes, and install one";
4984
+ case "catalog.skills":
4985
+ return "browse every skill in your registry, and install any of them";
4986
+ case "catalog.search":
4987
+ return "filter the same catalog by name and description";
4988
+ case "installed.list":
4989
+ return `show the ${count2(facts.skills, "skill")} recorded in ${facts.manifest}`;
4990
+ case "installed.update": {
4991
+ const scope = updateScope(facts.bundles, facts.loose);
4992
+ if (scope === "")
4993
+ return "nothing is tracked here to update";
4994
+ return `re-install ${scope} at what the registry blesses today`;
4441
4995
  }
4442
- ];
4996
+ case "installed.outdated":
4997
+ return `check your ${count2(facts.skills, "installed skill")} against the registry — reads only, changes nothing`;
4998
+ case "installed.remove":
4999
+ return `pick which of the ${count2(facts.skills, "skill")} to delete from this machine; it asks before it does`;
5000
+ case "account.login":
5001
+ return "sign in with a code you enter in your browser";
5002
+ case "account.logout":
5003
+ return "forget the token on this machine and revoke it server-side";
5004
+ case "account.whoami":
5005
+ return "ask the server who this token belongs to";
5006
+ case "account.org":
5007
+ return "list the organizations you can switch this machine to";
5008
+ case "setup.scope":
5009
+ return `where installs land: ${facts.scope} right now`;
5010
+ case "setup.agents":
5011
+ return "which agents a skill is written for when it is installed";
5012
+ case "setup.doctor":
5013
+ return "check the stock CLI, the manifest and the session in one pass";
5014
+ }
5015
+ }
5016
+ var CATALOG = [
5017
+ { value: "catalog.bundles", label: "browse bundles", hint: "browse" },
5018
+ { value: "catalog.skills", label: "browse skills", hint: "list" },
5019
+ { value: "catalog.search", label: "search", hint: "search <query>" }
5020
+ ];
5021
+ var INSTALLED = {
5022
+ list: { value: "installed.list", label: "list installed", hint: "installed" },
5023
+ update: {
5024
+ value: "installed.update",
5025
+ label: "update all",
5026
+ hint: "update --all"
5027
+ },
5028
+ outdated: {
5029
+ value: "installed.outdated",
5030
+ label: "outdated",
5031
+ hint: "outdated"
5032
+ },
5033
+ remove: {
5034
+ value: "installed.remove",
5035
+ label: "remove",
5036
+ hint: "remove <skill>"
5037
+ }
5038
+ };
5039
+ var ACCOUNT = {
5040
+ login: { value: "account.login", label: "login", hint: "login" },
5041
+ logout: { value: "account.logout", label: "logout", hint: "logout" },
5042
+ whoami: { value: "account.whoami", label: "whoami", hint: "whoami" },
5043
+ org: { value: "account.org", label: "switch org", hint: "org <name>" }
5044
+ };
5045
+ var SETUP = [
5046
+ { value: "setup.scope", label: "scope", hint: "config set scope project" },
5047
+ { value: "setup.agents", label: "agents", hint: "config set agents <name>" },
5048
+ { value: "setup.doctor", label: "doctor", hint: "doctor" }
5049
+ ];
5050
+ function paletteGroups(facts) {
5051
+ const groups = [];
5052
+ if (facts.signedIn)
5053
+ groups.push({ group: "Catalog", items: CATALOG });
5054
+ const installed = [];
5055
+ if (facts.skills > 0) {
5056
+ installed.push(INSTALLED.list);
5057
+ if (facts.signedIn)
5058
+ installed.push(INSTALLED.update, INSTALLED.outdated);
5059
+ installed.push(INSTALLED.remove);
5060
+ }
5061
+ if (installed.length > 0)
5062
+ groups.push({ group: "Installed", items: installed });
5063
+ groups.push({
5064
+ group: "Account",
5065
+ items: facts.signedIn ? [ACCOUNT.logout, ACCOUNT.whoami, ACCOUNT.org] : [ACCOUNT.login]
5066
+ });
5067
+ groups.push({ group: "Setup", items: SETUP });
5068
+ return groups;
5069
+ }
5070
+ // src/shell/facts.ts
5071
+ import { existsSync as existsSync5 } from "node:fs";
5072
+ async function paletteFacts(scope, server) {
5073
+ const [account, nearest, config] = await Promise.all([
5074
+ localAccount(server),
5075
+ nearestManifest(scope),
5076
+ readConfig()
5077
+ ]);
5078
+ const skills = Object.keys(nearest.manifest.skills).length;
5079
+ return {
5080
+ signedIn: account.signedIn,
5081
+ email: account.email,
5082
+ server: account.server,
5083
+ firstRun: !account.signedIn && skills === 0 && !config.welcomed,
5084
+ agentsOnDisk: HOOK_ADAPTERS.filter((adapter) => existsSync5(adapter.home())).map((adapter) => adapter.agent),
5085
+ scope: nearest.scope,
5086
+ manifest: nearest.path,
5087
+ skills,
5088
+ bundles: Object.keys(nearest.manifest.bundles).length,
5089
+ loose: looseSkills(nearest.manifest).length
5090
+ };
4443
5091
  }
4444
5092
  // src/shell/screens/home.tsx
4445
- import { Box as Box10, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
4446
- import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo3, useRef, useState as useState8 } from "react";
5093
+ import { Box as Box14, Text as Text16, useApp as useApp7, useInput as useInput8 } from "ink";
5094
+ import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo4, useRef, useState as useState11 } from "react";
4447
5095
  // src/screens.tsx
4448
- import { Box as Box9, Text as Text11, useApp as useApp6 } from "ink";
4449
- import { useEffect as useEffect7, useState as useState7 } from "react";
4450
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
5096
+ import { Box as Box10, Text as Text12, useApp as useApp6, useInput as useInput5 } from "ink";
5097
+ import { useEffect as useEffect7, useState as useState8 } from "react";
5098
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4451
5099
  var MARKS = {
4452
5100
  ok: { glyph: mark.ok, color: "green" },
4453
5101
  fail: { glyph: mark.fail, color: tone.danger },
@@ -4458,16 +5106,16 @@ function ReportRow({ line: line2, width }) {
4458
5106
  const { glyph, color } = MARKS[line2.mark];
4459
5107
  const head = line2.mark === "none" ? "" : `${glyph} `;
4460
5108
  const room = width - FRAME_CHROME - head.length;
4461
- return /* @__PURE__ */ jsxs10(Text11, {
5109
+ return /* @__PURE__ */ jsxs12(Text12, {
4462
5110
  children: [
4463
- head ? /* @__PURE__ */ jsx11(Text11, {
5111
+ head ? /* @__PURE__ */ jsx13(Text12, {
4464
5112
  color,
4465
5113
  children: head
4466
5114
  }) : null,
4467
- /* @__PURE__ */ jsx11(Text11, {
5115
+ /* @__PURE__ */ jsx13(Text12, {
4468
5116
  children: clip(line2.text, room)
4469
5117
  }),
4470
- line2.note ? /* @__PURE__ */ jsx11(Text11, {
5118
+ line2.note ? /* @__PURE__ */ jsx13(Text12, {
4471
5119
  dimColor: true,
4472
5120
  children: clip(` ${line2.note}`, Math.max(room - line2.text.length, 0))
4473
5121
  }) : null
@@ -4479,11 +5127,14 @@ function ReportScreen({
4479
5127
  load,
4480
5128
  onError,
4481
5129
  onReport,
4482
- exitWhenDone = true
5130
+ exitWhenDone = true,
5131
+ onBack,
5132
+ working = "Working...",
5133
+ onNext
4483
5134
  }) {
4484
5135
  const { exit } = useApp6();
4485
5136
  const width = useFrameWidth();
4486
- const [report, setReport] = useState7(null);
5137
+ const [report, setReport] = useState8(null);
4487
5138
  useEffect7(() => {
4488
5139
  let live = true;
4489
5140
  load().then((result) => {
@@ -4503,29 +5154,632 @@ function ReportScreen({
4503
5154
  if (report && exitWhenDone)
4504
5155
  exit();
4505
5156
  }, [report, exit, exitWhenDone]);
4506
- return /* @__PURE__ */ jsx11(Frame, {
5157
+ const next = report?.next;
5158
+ useInput5((input) => {
5159
+ if (input === "n" && next)
5160
+ onNext?.(next.command);
5161
+ }, { isActive: Boolean(onNext && next) });
5162
+ return /* @__PURE__ */ jsxs12(Frame, {
5163
+ title,
5164
+ width,
5165
+ children: [
5166
+ report ? /* @__PURE__ */ jsxs12(Box10, {
5167
+ flexDirection: "column",
5168
+ children: [
5169
+ report.lines.map((line2) => /* @__PURE__ */ jsx13(ReportRow, {
5170
+ line: line2,
5171
+ width
5172
+ }, `${line2.mark}:${line2.text}`)),
5173
+ next ? /* @__PURE__ */ jsxs12(Text12, {
5174
+ children: [
5175
+ /* @__PURE__ */ jsx13(Text12, {
5176
+ dimColor: true,
5177
+ children: "next "
5178
+ }),
5179
+ /* @__PURE__ */ jsx13(Text12, {
5180
+ color: tone.primary,
5181
+ children: next.command
5182
+ }),
5183
+ /* @__PURE__ */ jsx13(Text12, {
5184
+ dimColor: true,
5185
+ children: clip(` ${next.why}`, width - FRAME_CHROME)
5186
+ })
5187
+ ]
5188
+ }) : null
5189
+ ]
5190
+ }) : /* @__PURE__ */ jsxs12(Text12, {
5191
+ children: [
5192
+ /* @__PURE__ */ jsx13(Spinner, {}),
5193
+ /* @__PURE__ */ jsx13(Text12, {
5194
+ dimColor: true,
5195
+ children: ` ${working}`
5196
+ })
5197
+ ]
5198
+ }),
5199
+ /* @__PURE__ */ jsx13(Back, {
5200
+ width,
5201
+ onBack,
5202
+ extra: onNext && next ? [["n", "run next"]] : []
5203
+ })
5204
+ ]
5205
+ });
5206
+ }
5207
+
5208
+ // src/args.ts
5209
+ var COMMAND_WORDS = [
5210
+ "browse",
5211
+ "login",
5212
+ "logout",
5213
+ "whoami",
5214
+ "org",
5215
+ "list",
5216
+ "search",
5217
+ "bundle",
5218
+ "add",
5219
+ "install",
5220
+ "remove",
5221
+ "update",
5222
+ "installed",
5223
+ "outdated",
5224
+ "doctor",
5225
+ "config",
5226
+ "hooks",
5227
+ "help",
5228
+ "version"
5229
+ ];
5230
+ function configCommand(rest) {
5231
+ const [action, key, ...value] = rest;
5232
+ if (action === undefined || action === "get") {
5233
+ return { kind: "config", action: "get", ...key ? { key } : {} };
5234
+ }
5235
+ if (action !== "set") {
5236
+ return {
5237
+ kind: "error",
5238
+ message: `config takes get or set, not ${action}: yourskills config get [key]`
5239
+ };
5240
+ }
5241
+ if (!key) {
5242
+ return {
5243
+ kind: "error",
5244
+ message: "config set needs a key: yourskills config set <key> <value>"
5245
+ };
5246
+ }
5247
+ if (value.length === 0) {
5248
+ return {
5249
+ kind: "error",
5250
+ message: `config set needs a value: yourskills config set ${key} <value>`
5251
+ };
5252
+ }
5253
+ return { kind: "config", action: "set", key, value: value.join(" ") };
5254
+ }
5255
+ function hooksCommand(rest, agent, track) {
5256
+ const action = rest[0] ?? "status";
5257
+ if (action !== "install" && action !== "uninstall" && action !== "status") {
5258
+ return {
5259
+ kind: "error",
5260
+ message: `hooks takes install, uninstall or status, not ${action}`
5261
+ };
5262
+ }
5263
+ return {
5264
+ kind: "hooks",
5265
+ action,
5266
+ ...agent ? { agent } : {},
5267
+ ...track === undefined ? {} : { track }
5268
+ };
5269
+ }
5270
+ function parseArgv(args, interactive = true) {
5271
+ const opts = {};
5272
+ const positional = [];
5273
+ let plain = !interactive;
5274
+ let json = false;
5275
+ let all = false;
5276
+ let quiet = false;
5277
+ let server;
5278
+ let track;
5279
+ const done = (command2) => ({
5280
+ command: command2,
5281
+ output: { plain, json },
5282
+ ...server ? { server } : {}
5283
+ });
5284
+ for (let i = 0;i < args.length; i++) {
5285
+ const arg = args[i];
5286
+ if (arg === "-h" || arg === "--help")
5287
+ return done({ kind: "help" });
5288
+ if (arg === "-v" || arg === "--version")
5289
+ return done({ kind: "version" });
5290
+ if (arg === "--plain") {
5291
+ plain = true;
5292
+ } else if (arg === "--json") {
5293
+ json = true;
5294
+ plain = true;
5295
+ } else if (arg === "-g" || arg === "--global") {
5296
+ opts.scope = "global";
5297
+ } else if (arg === "-p" || arg === "--project") {
5298
+ opts.scope = "project";
5299
+ } else if (arg === "--all") {
5300
+ all = true;
5301
+ } else if (arg === "-q" || arg === "--quiet") {
5302
+ quiet = true;
5303
+ } else if (arg === "--track") {
5304
+ track = true;
5305
+ } else if (arg === "--no-track") {
5306
+ track = false;
5307
+ } else if (arg === "--server") {
5308
+ const value = args[++i];
5309
+ if (!value)
5310
+ return done({ kind: "error", message: "--server requires a URL" });
5311
+ server = value;
5312
+ } else if (arg === "-a" || arg === "--agent") {
5313
+ const value = args[++i];
5314
+ if (!value)
5315
+ return done({
5316
+ kind: "error",
5317
+ message: "--agent requires an agent name"
5318
+ });
5319
+ opts.agents = [...opts.agents ?? [], value];
5320
+ } else if (arg.startsWith("-")) {
5321
+ return done({
5322
+ kind: "error",
5323
+ message: `unknown option ${arg}`,
5324
+ next: "yourskills --help"
5325
+ });
5326
+ } else {
5327
+ positional.push(arg);
5328
+ }
5329
+ }
5330
+ const [command, ...rest] = positional;
5331
+ switch (command) {
5332
+ case undefined:
5333
+ return done(plain ? { kind: "help" } : { kind: "home" });
5334
+ case "help":
5335
+ return done({ kind: "help" });
5336
+ case "version":
5337
+ return done({ kind: "version" });
5338
+ case "browse":
5339
+ return done(plain ? {
5340
+ kind: "error",
5341
+ message: "browse needs a terminal; use `list` or `search` instead"
5342
+ } : { kind: "browse" });
5343
+ case "login":
5344
+ return done({ kind: "login" });
5345
+ case "logout":
5346
+ return done({ kind: "logout" });
5347
+ case "whoami":
5348
+ return done({ kind: "whoami" });
5349
+ case "list":
5350
+ return done({ kind: "list" });
5351
+ case "search":
5352
+ return done(rest.length > 0 ? { kind: "search", query: rest.join(" ") } : {
5353
+ kind: "error",
5354
+ message: "search needs a query: yourskills search <query>"
5355
+ });
5356
+ case "add":
5357
+ return done(rest.length > 0 ? { kind: "add", names: rest, opts } : {
5358
+ kind: "error",
5359
+ message: "add needs at least one skill: yourskills add <skill...>"
5360
+ });
5361
+ case "install":
5362
+ return done(rest.length > 0 ? { kind: "add", names: rest, opts } : { kind: "install", opts });
5363
+ case "remove":
5364
+ return done(rest.length > 0 ? { kind: "remove", names: rest, opts } : {
5365
+ kind: "error",
5366
+ message: "remove needs at least one skill: yourskills remove <skill...>"
5367
+ });
5368
+ case "installed":
5369
+ return done({ kind: "installed" });
5370
+ case "outdated":
5371
+ return done({ kind: "outdated", ...quiet ? { quiet: true } : {} });
5372
+ case "doctor":
5373
+ return done({ kind: "doctor" });
5374
+ case "org":
5375
+ return done(rest[0] ? { kind: "org", name: rest[0] } : { kind: "org" });
5376
+ case "config":
5377
+ return done(configCommand(rest));
5378
+ case "hooks":
5379
+ return done(hooksCommand(rest, opts.agents?.[0], track));
5380
+ case "hook": {
5381
+ const event = rest[0];
5382
+ return done(event && isHookEventName(event) ? { kind: "hook", event } : {
5383
+ kind: "error",
5384
+ message: "hook takes session-start or post-tool-use"
5385
+ });
5386
+ }
5387
+ case "bundle":
5388
+ return done(rest[0] ? { kind: "bundle", name: rest[0], opts } : {
5389
+ kind: "error",
5390
+ message: "bundle needs a name: yourskills bundle <name>"
5391
+ });
5392
+ case "update":
5393
+ return done({
5394
+ kind: "update",
5395
+ ...rest[0] ? { bundle: rest[0] } : {},
5396
+ ...all ? { all: true } : {},
5397
+ opts
5398
+ });
5399
+ default: {
5400
+ const guess = closest(command, COMMAND_WORDS);
5401
+ return done({
5402
+ kind: "error",
5403
+ message: `unknown command ${command}`,
5404
+ ...guess ? { why: `did you mean ${guess}?`, next: `yourskills ${guess}` } : { next: "yourskills --help" }
5405
+ });
5406
+ }
5407
+ }
5408
+ }
5409
+
5410
+ // src/shell/typed.ts
5411
+ var RUNNABLE = new Set([
5412
+ "browse",
5413
+ "login",
5414
+ "logout",
5415
+ "whoami",
5416
+ "org",
5417
+ "list",
5418
+ "search",
5419
+ "add",
5420
+ "bundle",
5421
+ "install",
5422
+ "remove",
5423
+ "update",
5424
+ "installed",
5425
+ "outdated",
5426
+ "doctor",
5427
+ "config",
5428
+ "hooks"
5429
+ ]);
5430
+ function typedCommand(query) {
5431
+ const words = query.trim().split(/\s+/).filter(Boolean);
5432
+ if (words.length === 0)
5433
+ return null;
5434
+ if (words[0] === "yourskills")
5435
+ words.shift();
5436
+ if (words.length === 0)
5437
+ return null;
5438
+ const { command } = parseArgv(words, true);
5439
+ if (command.kind !== "error" && !RUNNABLE.has(command.kind))
5440
+ return null;
5441
+ return { command, text: words.join(" ") };
5442
+ }
5443
+ function runnable(typed) {
5444
+ return typed !== null && typed.command.kind !== "error";
5445
+ }
5446
+ function count3(n, one, many = `${one}s`) {
5447
+ return `${n} ${n === 1 ? one : many}`;
5448
+ }
5449
+ function where2(scope, facts) {
5450
+ return (scope ?? facts.scope) === "project" ? "into this project" : "everywhere on this computer";
5451
+ }
5452
+ function describeCommand(command, facts) {
5453
+ switch (command.kind) {
5454
+ case "add":
5455
+ return `install ${count3(command.names.length, "skill")} ${where2(command.opts.scope, facts)}: ${command.names.join(", ")}`;
5456
+ case "bundle":
5457
+ return `install the bundle ${command.name} ${where2(command.opts.scope, facts)}, at the refs your registry pins today`;
5458
+ case "install":
5459
+ return facts.skills > 0 ? `re-install the ${count3(facts.skills, "skill")} recorded in ${facts.manifest}, at their recorded refs` : "nothing is recorded here to re-install";
5460
+ case "update": {
5461
+ if (command.bundle)
5462
+ return `re-install ${command.bundle} at what the registry blesses today`;
5463
+ const scope = updateScope(facts.bundles, command.all ? facts.loose : 0);
5464
+ return scope === "" ? "nothing is tracked here to update" : `re-install ${scope} at what the registry blesses today`;
5465
+ }
5466
+ case "remove":
5467
+ return `delete ${command.names.join(", ")} from disk and from ${facts.manifest} (asks first)`;
5468
+ case "installed":
5469
+ return describe2("installed.list", facts);
5470
+ case "outdated":
5471
+ return describe2("installed.outdated", facts);
5472
+ case "doctor":
5473
+ return describe2("setup.doctor", facts);
5474
+ case "whoami":
5475
+ return describe2("account.whoami", facts);
5476
+ case "logout":
5477
+ return describe2("account.logout", facts);
5478
+ case "login":
5479
+ return describe2("account.login", facts);
5480
+ case "org":
5481
+ return command.name ? `switch this machine to the organization ${command.name}` : describe2("account.org", facts);
5482
+ case "config":
5483
+ return command.action === "set" ? `set ${command.key} to ${command.value} in your config file` : command.key ? `show the stored ${command.key}` : "show every stored setting";
5484
+ case "hooks":
5485
+ return command.action === "install" ? "register our hooks in your agents' settings, so skills stay updated" : command.action === "uninstall" ? "remove our hooks from your agents' settings, leaving every other hook alone" : "show which agents carry our hooks, and where";
5486
+ case "list":
5487
+ return describe2("catalog.skills", facts);
5488
+ case "search":
5489
+ return `open the catalog filtered on "${command.query}"`;
5490
+ case "browse":
5491
+ return describe2("catalog.bundles", facts);
5492
+ case "error":
5493
+ return command.why ? `${command.message}, ${command.why}` : command.message;
5494
+ case "home":
5495
+ case "help":
5496
+ case "version":
5497
+ case "hook":
5498
+ return "";
5499
+ }
5500
+ }
5501
+
5502
+ // src/shell/screens/keys.tsx
5503
+ import { Box as Box11, Text as Text13 } from "ink";
5504
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
5505
+ var KEYS = [
5506
+ ["^ v", "move the cursor", "move, or scroll"],
5507
+ ["enter", "run the highlighted row", "confirm the layer's action"],
5508
+ ["tab", "complete the prompt from the row", "switch tab in the browser"],
5509
+ ["space", "types into the prompt", "mark a row in a list"],
5510
+ ["n", "types into the prompt", "run the next step a report suggests"],
5511
+ ["esc", "clear the prompt, then quit", "back one layer"],
5512
+ ["x", "types into the prompt", "back, where there is no prompt"],
5513
+ ["?", "this", "this"],
5514
+ ["ctrl-c", "quit now", "quit now"]
5515
+ ];
5516
+ function KeysScreen({ onBack }) {
5517
+ const width = useFrameWidth();
5518
+ const interior = width - FRAME_CHROME;
5519
+ const key = 8;
5520
+ const half = Math.floor((interior - key) / 2);
5521
+ return /* @__PURE__ */ jsxs13(Frame, {
5522
+ title: "keys",
5523
+ width,
5524
+ children: [
5525
+ /* @__PURE__ */ jsxs13(Box11, {
5526
+ flexDirection: "column",
5527
+ children: [
5528
+ /* @__PURE__ */ jsxs13(Text13, {
5529
+ children: [
5530
+ /* @__PURE__ */ jsx14(Text13, {
5531
+ dimColor: true,
5532
+ children: "".padEnd(key)
5533
+ }),
5534
+ /* @__PURE__ */ jsx14(Text13, {
5535
+ dimColor: true,
5536
+ children: "at the prompt".padEnd(half)
5537
+ }),
5538
+ /* @__PURE__ */ jsx14(Text13, {
5539
+ dimColor: true,
5540
+ children: "in a layer"
5541
+ })
5542
+ ]
5543
+ }),
5544
+ KEYS.map(([k, root, layer]) => /* @__PURE__ */ jsxs13(Text13, {
5545
+ children: [
5546
+ /* @__PURE__ */ jsx14(Text13, {
5547
+ color: tone.primary,
5548
+ children: k.padEnd(key)
5549
+ }),
5550
+ /* @__PURE__ */ jsx14(Text13, {
5551
+ children: clip(root, half - 2).padEnd(half)
5552
+ }),
5553
+ /* @__PURE__ */ jsx14(Text13, {
5554
+ children: clip(layer, interior - key - half)
5555
+ })
5556
+ ]
5557
+ }, k))
5558
+ ]
5559
+ }),
5560
+ /* @__PURE__ */ jsx14(Back, {
5561
+ width,
5562
+ onBack
5563
+ })
5564
+ ]
5565
+ });
5566
+ }
5567
+
5568
+ // src/shell/screens/picker.tsx
5569
+ import { Box as Box12, Text as Text14, useInput as useInput6 } from "ink";
5570
+ import { useEffect as useEffect8, useMemo as useMemo3, useState as useState9 } from "react";
5571
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
5572
+ function PickScreen({
5573
+ title,
5574
+ rows: rows2,
5575
+ empty,
5576
+ verb,
5577
+ onPick,
5578
+ onBack
5579
+ }) {
5580
+ const width = useFrameWidth();
5581
+ const [query, setQuery] = useState9("");
5582
+ const [cursor, setCursor] = useState9(0);
5583
+ const [picked, setPicked] = useState9(new Set);
5584
+ const visible = useMemo3(() => {
5585
+ if (query.length === 0)
5586
+ return rows2;
5587
+ const q = query.toLowerCase();
5588
+ return rows2.filter((row) => row.name.toLowerCase().includes(q) || (row.description ?? "").toLowerCase().includes(q));
5589
+ }, [rows2, query]);
5590
+ useEffect8(() => {
5591
+ setCursor((c) => Math.max(0, Math.min(c, visible.length - 1)));
5592
+ }, [visible.length]);
5593
+ useInput6((input, key) => {
5594
+ if (key.upArrow) {
5595
+ setCursor((c) => Math.max(0, c - 1));
5596
+ } else if (key.downArrow) {
5597
+ setCursor((c) => Math.min(visible.length - 1, c + 1));
5598
+ } else if (key.return) {
5599
+ const names2 = picked.size > 0 ? [...picked] : visible[cursor] ? [visible[cursor].name] : [];
5600
+ if (names2.length > 0)
5601
+ onPick(names2);
5602
+ } else if (key.escape) {
5603
+ if (query.length > 0)
5604
+ setQuery("");
5605
+ else
5606
+ onBack();
5607
+ } else if (key.backspace || key.delete) {
5608
+ setQuery((q) => q.slice(0, -1));
5609
+ setCursor(0);
5610
+ } else if (input === " ") {
5611
+ const row = visible[cursor];
5612
+ if (!row)
5613
+ return;
5614
+ setPicked((current) => {
5615
+ const next = new Set(current);
5616
+ if (next.has(row.name))
5617
+ next.delete(row.name);
5618
+ else
5619
+ next.add(row.name);
5620
+ return next;
5621
+ });
5622
+ } else if (input && !key.ctrl && !key.meta) {
5623
+ setQuery((q) => q + input);
5624
+ setCursor(0);
5625
+ }
5626
+ });
5627
+ const names = nameWidth(rows2);
5628
+ return /* @__PURE__ */ jsx15(Frame, {
4507
5629
  title,
4508
5630
  width,
4509
- children: report ? /* @__PURE__ */ jsx11(Box9, {
5631
+ children: /* @__PURE__ */ jsxs14(Box12, {
4510
5632
  flexDirection: "column",
4511
- children: report.lines.map((line2) => /* @__PURE__ */ jsx11(ReportRow, {
4512
- line: line2,
5633
+ children: [
5634
+ /* @__PURE__ */ jsxs14(Box12, {
5635
+ children: [
5636
+ /* @__PURE__ */ jsx15(Text14, {
5637
+ color: tone.primary,
5638
+ children: "> "
5639
+ }),
5640
+ /* @__PURE__ */ jsx15(SearchInput, {
5641
+ value: query,
5642
+ placeholder: "type to filter"
5643
+ })
5644
+ ]
5645
+ }),
5646
+ /* @__PURE__ */ jsx15(Divider, {
5647
+ width
5648
+ }),
5649
+ rows2.length === 0 ? /* @__PURE__ */ jsx15(Text14, {
5650
+ dimColor: true,
5651
+ children: empty
5652
+ }) : visible.length === 0 ? /* @__PURE__ */ jsx15(Text14, {
5653
+ dimColor: true,
5654
+ children: `Nothing matches "${query}".`
5655
+ }) : visible.map((row, i) => /* @__PURE__ */ jsx15(SkillRow, {
5656
+ row,
5657
+ width: width - 2,
5658
+ names,
5659
+ selectable: true,
5660
+ cursor: i === cursor,
5661
+ selected: picked.has(row.name)
5662
+ }, row.name)),
5663
+ /* @__PURE__ */ jsx15(Divider, {
5664
+ width
5665
+ }),
5666
+ /* @__PURE__ */ jsxs14(Box12, {
5667
+ children: [
5668
+ picked.size > 0 ? /* @__PURE__ */ jsx15(Text14, {
5669
+ color: tone.notice,
5670
+ children: `${mark.selected} ${picked.size} marked `
5671
+ }) : null,
5672
+ /* @__PURE__ */ jsx15(Hints, {
5673
+ hints: [
5674
+ ["^v", "move"],
5675
+ ["space", "mark"],
5676
+ ["enter", verb],
5677
+ ["esc", query ? "clear" : "back"]
5678
+ ]
5679
+ })
5680
+ ]
5681
+ })
5682
+ ]
5683
+ })
5684
+ });
5685
+ }
5686
+
5687
+ // src/shell/screens/welcome.tsx
5688
+ import { Box as Box13, Text as Text15, useInput as useInput7 } from "ink";
5689
+ import { useState as useState10 } from "react";
5690
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
5691
+ function WelcomeScreen({
5692
+ agents: agents2,
5693
+ onStart,
5694
+ onSkip
5695
+ }) {
5696
+ const width = useFrameWidth();
5697
+ const interior = width - FRAME_CHROME;
5698
+ const [choice, setChoice] = useState10("start");
5699
+ useInput7((_input, key) => {
5700
+ if (key.upArrow || key.downArrow) {
5701
+ setChoice((c) => c === "start" ? "skip" : "start");
5702
+ } else if (key.return) {
5703
+ if (choice === "start")
5704
+ onStart();
5705
+ else
5706
+ onSkip();
5707
+ } else if (key.escape) {
5708
+ onSkip();
5709
+ }
5710
+ });
5711
+ const found = agents2.length > 0 ? `found on this machine: ${agents2.join(", ")}` : "none found yet; the catalog will ask";
5712
+ const steps = [
5713
+ ["1", "sign in", "a code you enter in your browser"],
5714
+ ["2", "pick your tools", found],
5715
+ ["3", "get skills", "the bundle your team uses"]
5716
+ ];
5717
+ const row = (label, hint, active) => {
5718
+ const gap = Math.max(interior - 2 - label.length - hint.length, 1);
5719
+ return /* @__PURE__ */ jsxs15(Text15, {
5720
+ children: [
5721
+ /* @__PURE__ */ jsx16(Text15, {
5722
+ color: tone.primary,
5723
+ children: `${active ? mark.cursor : " "} `
5724
+ }),
5725
+ /* @__PURE__ */ jsx16(Text15, {
5726
+ bold: active,
5727
+ color: active ? tone.primary : undefined,
5728
+ children: label
5729
+ }),
5730
+ /* @__PURE__ */ jsx16(Text15, {
5731
+ dimColor: true,
5732
+ children: `${" ".repeat(gap)}${hint}`
5733
+ })
5734
+ ]
5735
+ });
5736
+ };
5737
+ return /* @__PURE__ */ jsxs15(Frame, {
5738
+ title: "yourskills",
5739
+ width,
5740
+ children: [
5741
+ /* @__PURE__ */ jsxs15(Box13, {
5742
+ flexDirection: "column",
5743
+ children: [
5744
+ /* @__PURE__ */ jsx16(Text15, {
5745
+ children: "Welcome. Three steps, about a minute."
5746
+ }),
5747
+ /* @__PURE__ */ jsx16(Blank, {}),
5748
+ steps.map(([n, what, how]) => /* @__PURE__ */ jsxs15(Text15, {
5749
+ children: [
5750
+ /* @__PURE__ */ jsx16(Text15, {
5751
+ color: tone.primary,
5752
+ children: `${n} `
5753
+ }),
5754
+ /* @__PURE__ */ jsx16(Text15, {
5755
+ children: what.padEnd(16)
5756
+ }),
5757
+ /* @__PURE__ */ jsx16(Text15, {
5758
+ dimColor: true,
5759
+ children: clip(how, interior - 19)
5760
+ })
5761
+ ]
5762
+ }, n)),
5763
+ /* @__PURE__ */ jsx16(Blank, {}),
5764
+ row("start", "", choice === "start"),
5765
+ row("skip, I know what I am doing", "yourskills login", choice === "skip")
5766
+ ]
5767
+ }),
5768
+ /* @__PURE__ */ jsx16(Divider, {
4513
5769
  width
4514
- }, `${line2.mark}:${line2.text}`))
4515
- }) : /* @__PURE__ */ jsxs10(Text11, {
4516
- children: [
4517
- /* @__PURE__ */ jsx11(Spinner, {}),
4518
- /* @__PURE__ */ jsx11(Text11, {
4519
- dimColor: true,
4520
- children: " Working..."
4521
- })
4522
- ]
4523
- })
5770
+ }),
5771
+ /* @__PURE__ */ jsx16(Hints, {
5772
+ hints: [
5773
+ ["enter", choice === "start" ? "start" : "skip"],
5774
+ ["esc", "skip"]
5775
+ ]
5776
+ })
5777
+ ]
4524
5778
  });
4525
5779
  }
4526
5780
 
4527
5781
  // src/shell/screens/home.tsx
4528
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
5782
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
4529
5783
  function failure(title, error) {
4530
5784
  const message = error instanceof Error ? error.message : String(error);
4531
5785
  return {
@@ -4549,23 +5803,88 @@ function identityReport(who) {
4549
5803
  json: { ...who }
4550
5804
  };
4551
5805
  }
5806
+ function hostOf(server) {
5807
+ try {
5808
+ return new URL(server).host;
5809
+ } catch {
5810
+ return server;
5811
+ }
5812
+ }
5813
+ function removeArgv(names, opts) {
5814
+ return `yourskills remove ${names.join(" ")}${opts.scope === "project" ? " --project" : ""}`;
5815
+ }
5816
+ function AccountLine({ facts, width }) {
5817
+ const right = `${facts.scope} ${facts.skills} installed`;
5818
+ const head = facts.signedIn ? facts.email ?? "signed in" : "not signed in";
5819
+ const room = Math.max(width - FRAME_CHROME - right.length - 2, 12);
5820
+ const tail = facts.signedIn ? ` ${hostOf(facts.server)}` : " — the catalog needs a session";
5821
+ const spare = room - 2 - head.length;
5822
+ return /* @__PURE__ */ jsxs16(Box14, {
5823
+ width: width - FRAME_CHROME,
5824
+ justifyContent: "space-between",
5825
+ children: [
5826
+ /* @__PURE__ */ jsxs16(Text16, {
5827
+ children: [
5828
+ /* @__PURE__ */ jsx17(Text16, {
5829
+ color: facts.signedIn ? "green" : tone.notice,
5830
+ children: facts.signedIn ? mark.ok : mark.fail
5831
+ }),
5832
+ /* @__PURE__ */ jsx17(Text16, {
5833
+ color: facts.signedIn ? undefined : tone.notice,
5834
+ children: ` ${clip(head, room - 2)}`
5835
+ }),
5836
+ spare >= tail.length ? /* @__PURE__ */ jsx17(Text16, {
5837
+ dimColor: true,
5838
+ children: tail
5839
+ }) : null
5840
+ ]
5841
+ }),
5842
+ /* @__PURE__ */ jsx17(Text16, {
5843
+ dimColor: true,
5844
+ children: right
5845
+ })
5846
+ ]
5847
+ });
5848
+ }
5849
+ var RUN = "run";
4552
5850
  function HomeScreen({
4553
5851
  opts,
5852
+ facts: initial,
5853
+ reload,
4554
5854
  server,
4555
5855
  openClient,
4556
5856
  onError,
4557
- onSummary
5857
+ onSummary,
5858
+ onWelcomed = async () => {
5859
+ return;
5860
+ }
4558
5861
  }) {
4559
5862
  const { exit } = useApp7();
4560
5863
  const width = useFrameWidth();
4561
- const groups = useMemo3(() => paletteGroups(), []);
4562
- const [query, setQuery] = useState8("");
4563
- const [cursor, setCursor] = useState8(0);
4564
- const [route, setRoute] = useState8(null);
4565
- const [client, setClient] = useState8(null);
4566
- const [refused, setRefused] = useState8(null);
4567
- const rows2 = useMemo3(() => flattenCommands(filterCommands(groups, query)), [groups, query]);
4568
- useEffect8(() => {
5864
+ const [facts, setFacts] = useState11(initial);
5865
+ const groups = useMemo4(() => paletteGroups(facts), [facts]);
5866
+ const [query, setQuery] = useState11("");
5867
+ const [cursor, setCursor] = useState11(0);
5868
+ const [route, setRoute] = useState11(null);
5869
+ const [client, setClient] = useState11(null);
5870
+ const [refused, setRefused] = useState11(null);
5871
+ const [welcome, setWelcome] = useState11(initial.firstRun);
5872
+ const [wizard, setWizard] = useState11(false);
5873
+ const typed = useMemo4(() => typedCommand(query), [query]);
5874
+ const visible = useMemo4(() => {
5875
+ const filtered = filterCommands(groups, query);
5876
+ if (!runnable(typed))
5877
+ return filtered;
5878
+ return [
5879
+ {
5880
+ group: "Run",
5881
+ items: [{ value: RUN, label: typed.text, hint: "enter to run" }]
5882
+ },
5883
+ ...filtered
5884
+ ];
5885
+ }, [groups, query, typed]);
5886
+ const rows2 = useMemo4(() => flattenCommands(visible), [visible]);
5887
+ useEffect9(() => {
4569
5888
  setCursor((c) => Math.max(0, Math.min(c, rows2.length - 1)));
4570
5889
  }, [rows2.length]);
4571
5890
  const held = useRef(null);
@@ -4573,9 +5892,10 @@ function HomeScreen({
4573
5892
  held.current ??= await openClient();
4574
5893
  return held.current;
4575
5894
  }, [openClient]);
4576
- const answering = useCallback3((title, load) => ({
5895
+ const answering = useCallback3((title, working, load) => ({
4577
5896
  kind: "report",
4578
5897
  title,
5898
+ working,
4579
5899
  load: async () => {
4580
5900
  try {
4581
5901
  return await load();
@@ -4584,77 +5904,239 @@ function HomeScreen({
4584
5904
  }
4585
5905
  }
4586
5906
  }), []);
4587
- const routeFor = useCallback3((value) => {
4588
- switch (value) {
4589
- case "catalog.bundles":
4590
- case "catalog.skills":
4591
- case "catalog.search":
5907
+ const signOut = useCallback3(async () => {
5908
+ await logout();
5909
+ held.current = null;
5910
+ setClient(null);
5911
+ return {
5912
+ title: "logout",
5913
+ lines: [{ mark: "ok", text: "Logged out." }],
5914
+ json: { ok: true }
5915
+ };
5916
+ }, []);
5917
+ const routeForCommand = useCallback3(async (command) => {
5918
+ const plan = describeCommand(command, facts);
5919
+ switch (command.kind) {
5920
+ case "login":
5921
+ return { kind: "login" };
5922
+ case "browse":
4592
5923
  return { kind: "browse" };
4593
- case "installed.list":
4594
- return answering("installed", async () => installedReport(await nearestManifest(opts.scope)));
4595
- case "installed.update":
5924
+ case "list":
5925
+ return { kind: "browse", tab: "skills" };
5926
+ case "search":
5927
+ return { kind: "browse", tab: "skills", query: command.query };
5928
+ case "add": {
5929
+ const resolved = await resolveInstallOptions(command.opts);
4596
5930
  return {
4597
5931
  kind: "install",
4598
- title: "update",
5932
+ title: "add",
5933
+ plan,
4599
5934
  stream: async function* stream() {
4600
- const nearest = await nearestManifest(opts.scope);
4601
- yield* updateTracked(await connect(), nearest.manifest, opts, true);
5935
+ yield* install(await connect(), command.names, resolved);
4602
5936
  }
4603
5937
  };
4604
- case "installed.outdated":
4605
- return answering("outdated", async () => outdatedReport(await connect(), await nearestManifest(opts.scope)));
4606
- case "installed.remove":
4607
- return answering("remove", async () => {
4608
- const report = installedReport(await nearestManifest(opts.scope));
5938
+ }
5939
+ case "bundle": {
5940
+ const resolved = await resolveInstallOptions(command.opts);
5941
+ return {
5942
+ kind: "install",
5943
+ title: `bundle: ${command.name}`,
5944
+ plan,
5945
+ stream: async function* stream() {
5946
+ yield* installBundle(await connect(), command.name, resolved);
5947
+ }
5948
+ };
5949
+ }
5950
+ case "install": {
5951
+ const resolved = await resolveInstallOptions(command.opts);
5952
+ const nearest = await nearestManifest(resolved.scope);
5953
+ const empty2 = nothingTracked(nearest, "install");
5954
+ if (empty2)
5955
+ return answering("install", plan, async () => empty2);
5956
+ return {
5957
+ kind: "install",
5958
+ title: "install",
5959
+ plan,
5960
+ stream: () => reinstall(nearest.manifest, resolved)
5961
+ };
5962
+ }
5963
+ case "update": {
5964
+ const resolved = await resolveInstallOptions(command.opts);
5965
+ if (command.bundle) {
5966
+ const name = command.bundle;
4609
5967
  return {
4610
- ...report,
4611
- title: "remove",
4612
- lines: [
4613
- {
4614
- mark: "none",
4615
- text: "Run `yourskills remove <skill>` to delete one."
4616
- },
4617
- ...report.lines
4618
- ]
5968
+ kind: "install",
5969
+ title: `update: ${name}`,
5970
+ plan,
5971
+ stream: async function* stream() {
5972
+ yield* update(await connect(), name, resolved);
5973
+ }
4619
5974
  };
4620
- });
5975
+ }
5976
+ const nearest = await nearestManifest(resolved.scope);
5977
+ const all = command.all ?? false;
5978
+ const empty2 = nothingTracked(nearest, "update", all);
5979
+ if (empty2)
5980
+ return answering("update", plan, async () => empty2);
5981
+ return {
5982
+ kind: "install",
5983
+ title: "update",
5984
+ plan,
5985
+ stream: async function* stream() {
5986
+ yield* updateTracked(await connect(), nearest.manifest, resolved, all);
5987
+ }
5988
+ };
5989
+ }
5990
+ case "remove": {
5991
+ const resolved = await resolveInstallOptions(command.opts);
5992
+ return {
5993
+ kind: "confirm",
5994
+ names: command.names,
5995
+ nearest: await nearestManifest(resolved.scope),
5996
+ opts: resolved
5997
+ };
5998
+ }
5999
+ case "installed":
6000
+ return answering("installed", plan, async () => installedReport(await nearestManifest(opts.scope)));
6001
+ case "outdated":
6002
+ return answering("outdated", plan, async () => outdatedReport(await connect(), await nearestManifest(opts.scope)));
6003
+ case "doctor":
6004
+ return answering("doctor", plan, async () => doctorReport(await nearestManifest(opts.scope), server));
6005
+ case "org":
6006
+ return answering("org", plan, () => command.name ? orgSwitchReport(command.name, server) : orgListReport(server));
6007
+ case "config":
6008
+ return answering("config", plan, () => command.action === "get" ? configGet(command.key) : configSet(command.key, command.value));
6009
+ case "whoami":
6010
+ return answering("whoami", plan, async () => identityReport(await whoami(server)));
6011
+ case "logout":
6012
+ return answering("logout", plan, signOut);
6013
+ case "hooks":
6014
+ return answering("hooks", plan, () => command.action === "install" ? hooksInstall({
6015
+ ...command.agent ? { agent: command.agent } : {},
6016
+ ...command.track === undefined ? {} : { track: command.track }
6017
+ }) : command.action === "uninstall" ? hooksUninstall(command.agent ? { agent: command.agent } : {}) : hooksStatus());
6018
+ case "error":
6019
+ case "home":
6020
+ case "help":
6021
+ case "version":
6022
+ case "hook":
6023
+ return null;
6024
+ }
6025
+ }, [answering, connect, facts, opts, server, signOut]);
6026
+ const routeFor = useCallback3(async (value) => {
6027
+ switch (value) {
6028
+ case "catalog.bundles":
6029
+ return { kind: "browse" };
6030
+ case "catalog.skills":
6031
+ case "catalog.search":
6032
+ return { kind: "browse", tab: "skills" };
6033
+ case "installed.list":
6034
+ return routeForCommand({ kind: "installed" });
6035
+ case "installed.update":
6036
+ return routeForCommand({ kind: "update", all: true, opts: {} });
6037
+ case "installed.outdated":
6038
+ return routeForCommand({ kind: "outdated" });
6039
+ case "installed.remove": {
6040
+ const nearest = await nearestManifest(opts.scope);
6041
+ return {
6042
+ kind: "pick",
6043
+ rows: installedRows(nearest).map((row) => ({
6044
+ name: row.name,
6045
+ description: `${row.bundle ?? "loose"} ${shortRef(row.ref)}`
6046
+ }))
6047
+ };
6048
+ }
4621
6049
  case "account.login":
4622
6050
  return { kind: "login" };
4623
6051
  case "account.logout":
4624
- return answering("logout", async () => {
4625
- await logout();
4626
- held.current = null;
4627
- setClient(null);
4628
- return {
4629
- title: "logout",
4630
- lines: [{ mark: "ok", text: "Logged out." }],
4631
- json: { ok: true }
4632
- };
4633
- });
6052
+ return routeForCommand({ kind: "logout" });
4634
6053
  case "account.whoami":
4635
- return answering("whoami", async () => identityReport(await whoami(server)));
6054
+ return routeForCommand({ kind: "whoami" });
4636
6055
  case "account.org":
4637
- return answering("org", () => orgListReport(server));
6056
+ return routeForCommand({ kind: "org" });
4638
6057
  case "setup.scope":
4639
- return answering("scope", () => configGet("scope"));
6058
+ return routeForCommand({
6059
+ kind: "config",
6060
+ action: "get",
6061
+ key: "scope"
6062
+ });
4640
6063
  case "setup.agents":
4641
- return answering("agents", () => configGet("agents"));
6064
+ return routeForCommand({
6065
+ kind: "config",
6066
+ action: "get",
6067
+ key: "agents"
6068
+ });
4642
6069
  case "setup.doctor":
4643
- return answering("doctor", async () => doctorReport(await nearestManifest(opts.scope), server));
6070
+ return routeForCommand({ kind: "doctor" });
4644
6071
  }
4645
- }, [answering, connect, opts, server]);
6072
+ }, [opts.scope, routeForCommand]);
6073
+ const open = useCallback3((pending, title) => {
6074
+ pending.then((next) => {
6075
+ if (next)
6076
+ setRoute(next);
6077
+ }).catch((error) => {
6078
+ setRoute(answering(title, "", async () => failure(title, error)));
6079
+ });
6080
+ }, [answering]);
6081
+ const runText = useCallback3((text) => {
6082
+ const parsed = typedCommand(text);
6083
+ if (!runnable(parsed))
6084
+ return;
6085
+ open(routeForCommand(parsed.command), parsed.text);
6086
+ }, [open, routeForCommand]);
6087
+ const current = rows2[cursor];
4646
6088
  const activate = useCallback3(() => {
4647
6089
  const item = rows2[cursor];
4648
6090
  if (!item)
4649
6091
  return;
4650
6092
  item.onSelect?.();
4651
- setRoute(routeFor(item.value));
4652
- }, [rows2, cursor, routeFor]);
6093
+ if (item.value === RUN) {
6094
+ if (runnable(typed))
6095
+ open(routeForCommand(typed.command), typed.text);
6096
+ return;
6097
+ }
6098
+ open(routeFor(item.value), item.label);
6099
+ }, [rows2, cursor, typed, open, routeFor, routeForCommand]);
6100
+ const complete = useCallback3(() => {
6101
+ const item = rows2[cursor]?.value === RUN ? rows2[cursor + 1] : rows2[cursor];
6102
+ if (!item?.hint)
6103
+ return;
6104
+ const bare = item.hint.replace(/<[^>]+>/g, "").trimEnd();
6105
+ setQuery(item.hint.includes("<") ? `${bare} ` : bare);
6106
+ setCursor(0);
6107
+ }, [rows2, cursor]);
4653
6108
  const back = useCallback3(() => {
4654
6109
  setRoute(null);
4655
6110
  setRefused(null);
4656
- }, []);
4657
- useEffect8(() => {
6111
+ setQuery("");
6112
+ setCursor(0);
6113
+ reload().then(setFacts).catch(() => {
6114
+ return;
6115
+ });
6116
+ }, [reload]);
6117
+ const afterLogin = useCallback3(() => {
6118
+ if (!wizard) {
6119
+ back();
6120
+ return;
6121
+ }
6122
+ setWizard(false);
6123
+ setRefused(null);
6124
+ reload().then((next) => {
6125
+ setFacts(next);
6126
+ setRoute({ kind: "browse" });
6127
+ }).catch(() => setRoute(null));
6128
+ }, [wizard, back, reload]);
6129
+ const leaveWelcome = useCallback3((start) => {
6130
+ setWelcome(false);
6131
+ onWelcomed().catch(() => {
6132
+ return;
6133
+ });
6134
+ if (start) {
6135
+ setWizard(true);
6136
+ setRoute({ kind: "login" });
6137
+ }
6138
+ }, [onWelcomed]);
6139
+ useEffect9(() => {
4658
6140
  if (route?.kind !== "browse" || client || refused)
4659
6141
  return;
4660
6142
  let live = true;
@@ -4669,14 +6151,15 @@ function HomeScreen({
4669
6151
  live = false;
4670
6152
  };
4671
6153
  }, [route, client, refused, connect]);
4672
- const browsing = route?.kind === "browse" && client !== null;
4673
- useInput3((input, key) => {
6154
+ useInput8((input, key) => {
4674
6155
  if (key.upArrow) {
4675
6156
  setCursor((c) => Math.max(0, c - 1));
4676
6157
  } else if (key.downArrow) {
4677
6158
  setCursor((c) => Math.min(rows2.length - 1, c + 1));
4678
6159
  } else if (key.return) {
4679
6160
  activate();
6161
+ } else if (key.tab) {
6162
+ complete();
4680
6163
  } else if (key.escape) {
4681
6164
  if (query.length > 0)
4682
6165
  setQuery("");
@@ -4685,104 +6168,192 @@ function HomeScreen({
4685
6168
  } else if (key.backspace || key.delete) {
4686
6169
  setQuery((q) => q.slice(0, -1));
4687
6170
  setCursor(0);
6171
+ } else if (input === "?" && query.length === 0) {
6172
+ setRoute({ kind: "keys" });
4688
6173
  } else if (input && !key.ctrl && !key.meta) {
4689
6174
  setQuery((q) => q + input);
4690
6175
  setCursor(0);
4691
6176
  }
4692
- }, { isActive: route === null });
4693
- useInput3((_input, key) => {
4694
- if (key.escape)
4695
- back();
4696
- }, { isActive: route !== null && !browsing });
6177
+ }, { isActive: route === null && !welcome });
6178
+ if (welcome) {
6179
+ return /* @__PURE__ */ jsx17(WelcomeScreen, {
6180
+ agents: facts.agentsOnDisk,
6181
+ onStart: () => leaveWelcome(true),
6182
+ onSkip: () => leaveWelcome(false)
6183
+ });
6184
+ }
4697
6185
  if (route?.kind === "login") {
4698
- return /* @__PURE__ */ jsx12(LoginScreen, {
6186
+ return /* @__PURE__ */ jsx17(LoginScreen, {
4699
6187
  onError,
4700
- onDone: back
6188
+ onDone: afterLogin,
6189
+ onBack: back
6190
+ });
6191
+ }
6192
+ if (route?.kind === "keys") {
6193
+ return /* @__PURE__ */ jsx17(KeysScreen, {
6194
+ onBack: back
4701
6195
  });
4702
6196
  }
4703
6197
  if (route?.kind === "report") {
4704
- return /* @__PURE__ */ jsx12(ReportScreen, {
6198
+ return /* @__PURE__ */ jsx17(ReportScreen, {
4705
6199
  title: route.title,
4706
6200
  load: route.load,
6201
+ working: route.working,
4707
6202
  onError,
4708
- exitWhenDone: false
6203
+ exitWhenDone: false,
6204
+ onBack: back,
6205
+ onNext: runText
4709
6206
  });
4710
6207
  }
4711
6208
  if (route?.kind === "install") {
4712
- return /* @__PURE__ */ jsx12(Installer, {
6209
+ return /* @__PURE__ */ jsx17(Installer, {
4713
6210
  title: route.title,
4714
6211
  stream: route.stream,
6212
+ plan: route.plan,
4715
6213
  exitWhenDone: false,
4716
- onDone: onSummary
6214
+ onDone: onSummary,
6215
+ onBack: back
6216
+ });
6217
+ }
6218
+ if (route?.kind === "pick") {
6219
+ return /* @__PURE__ */ jsx17(PickScreen, {
6220
+ title: "remove",
6221
+ rows: route.rows,
6222
+ empty: "Nothing installed here.",
6223
+ verb: "remove",
6224
+ onPick: (names) => {
6225
+ open(routeForCommand({ kind: "remove", names, opts: {} }), "remove");
6226
+ },
6227
+ onBack: back
6228
+ });
6229
+ }
6230
+ if (route?.kind === "confirm") {
6231
+ const { names, nearest, opts: resolved } = route;
6232
+ const lines = names.map((name) => {
6233
+ const entry = nearest.manifest.skills[name];
6234
+ return entry ? {
6235
+ mark: "fail",
6236
+ text: name,
6237
+ note: entry.agents.length > 0 ? entry.agents.join(", ") : ""
6238
+ } : {
6239
+ mark: "none",
6240
+ text: name,
6241
+ note: "not installed here, nothing to delete"
6242
+ };
6243
+ });
6244
+ const known = names.filter((name) => nearest.manifest.skills[name]);
6245
+ return /* @__PURE__ */ jsx17(ConfirmScreen, {
6246
+ title: "remove",
6247
+ headline: `You are about to delete ${known.length === 1 ? "1 skill" : `${known.length} skills`}`,
6248
+ lines,
6249
+ notes: [
6250
+ `The list of what is installed is updated too: ${nearest.path}`,
6251
+ `Nothing on ${hostOf(facts.server)} changes.`
6252
+ ],
6253
+ yes: "delete them",
6254
+ no: "keep them",
6255
+ argv: removeArgv(names, resolved),
6256
+ onYes: () => setRoute(answering("remove", describeCommand({ kind: "remove", names, opts: resolved }, facts), () => removeSkills(names, nearest.manifest, nearest.path, resolved))),
6257
+ onNo: back
4717
6258
  });
4718
6259
  }
4719
6260
  if (route?.kind === "browse") {
4720
6261
  if (refused) {
4721
- return /* @__PURE__ */ jsx12(Frame, {
6262
+ return /* @__PURE__ */ jsxs16(Frame, {
4722
6263
  title: "browse",
4723
6264
  width,
4724
- children: /* @__PURE__ */ jsxs11(Box10, {
4725
- flexDirection: "column",
4726
- children: [
4727
- /* @__PURE__ */ jsxs11(Text12, {
4728
- children: [
4729
- /* @__PURE__ */ jsx12(Text12, {
4730
- color: tone.danger,
4731
- children: mark.fail
4732
- }),
4733
- /* @__PURE__ */ jsx12(Text12, {
4734
- children: ` ${refused}`
4735
- })
4736
- ]
4737
- }),
4738
- /* @__PURE__ */ jsx12(Text12, {
4739
- dimColor: true,
4740
- children: "Pick `login` from the palette, then try again."
4741
- })
4742
- ]
4743
- })
6265
+ children: [
6266
+ /* @__PURE__ */ jsxs16(Box14, {
6267
+ flexDirection: "column",
6268
+ children: [
6269
+ /* @__PURE__ */ jsxs16(Text16, {
6270
+ children: [
6271
+ /* @__PURE__ */ jsx17(Text16, {
6272
+ color: tone.danger,
6273
+ children: mark.fail
6274
+ }),
6275
+ /* @__PURE__ */ jsx17(Text16, {
6276
+ children: ` ${refused}`
6277
+ })
6278
+ ]
6279
+ }),
6280
+ /* @__PURE__ */ jsx17(Text16, {
6281
+ dimColor: true,
6282
+ children: "Pick `login` from the palette, then try again."
6283
+ })
6284
+ ]
6285
+ }),
6286
+ /* @__PURE__ */ jsx17(Back, {
6287
+ width,
6288
+ onBack: back
6289
+ })
6290
+ ]
4744
6291
  });
4745
6292
  }
4746
6293
  if (!client) {
4747
- return /* @__PURE__ */ jsx12(Frame, {
6294
+ return /* @__PURE__ */ jsxs16(Frame, {
4748
6295
  title: "browse",
4749
6296
  width,
4750
- children: /* @__PURE__ */ jsxs11(Text12, {
4751
- children: [
4752
- /* @__PURE__ */ jsx12(Spinner, {}),
4753
- /* @__PURE__ */ jsx12(Text12, {
4754
- dimColor: true,
4755
- children: " Connecting..."
4756
- })
4757
- ]
4758
- })
6297
+ children: [
6298
+ /* @__PURE__ */ jsxs16(Text16, {
6299
+ children: [
6300
+ /* @__PURE__ */ jsx17(Spinner, {}),
6301
+ /* @__PURE__ */ jsx17(Text16, {
6302
+ dimColor: true,
6303
+ children: " Connecting..."
6304
+ })
6305
+ ]
6306
+ }),
6307
+ /* @__PURE__ */ jsx17(Back, {
6308
+ width,
6309
+ onBack: back
6310
+ })
6311
+ ]
4759
6312
  });
4760
6313
  }
4761
- return /* @__PURE__ */ jsx12(BrowserScreen, {
6314
+ return /* @__PURE__ */ jsx17(BrowserScreen, {
4762
6315
  client,
4763
6316
  opts,
4764
6317
  onError,
4765
6318
  onSummary,
4766
- onExit: back
6319
+ onExit: back,
6320
+ ...route.tab ? { initialTab: route.tab } : {},
6321
+ ...route.query ? { initialQuery: route.query } : {}
4767
6322
  });
4768
6323
  }
4769
- return /* @__PURE__ */ jsxs11(Frame, {
6324
+ const description = current?.value === RUN && runnable(typed) ? describeCommand(typed.command, facts) : current ? describe2(current.value, facts) : typed ? describeCommand(typed.command, facts) : "";
6325
+ const empty = typed?.command.kind === "error" && typed.command.next ? `${mark.fail} ${typed.command.message} try: ${typed.command.next.replace(/^yourskills /, "")}` : "No matching commands.";
6326
+ return /* @__PURE__ */ jsxs16(Frame, {
4770
6327
  title: "yourskills",
4771
6328
  width,
4772
6329
  children: [
4773
- /* @__PURE__ */ jsx12(CommandPalette, {
4774
- groups,
6330
+ /* @__PURE__ */ jsx17(AccountLine, {
6331
+ facts,
6332
+ width
6333
+ }),
6334
+ /* @__PURE__ */ jsx17(Divider, {
6335
+ width
6336
+ }),
6337
+ /* @__PURE__ */ jsx17(CommandPalette, {
6338
+ groups: visible,
4775
6339
  query,
4776
6340
  cursor,
4777
- width
6341
+ width,
6342
+ emptyMessage: empty
4778
6343
  }),
4779
- /* @__PURE__ */ jsx12(Divider, {
6344
+ /* @__PURE__ */ jsx17(Divider, {
4780
6345
  width
4781
6346
  }),
4782
- /* @__PURE__ */ jsx12(Hints, {
6347
+ /* @__PURE__ */ jsx17(Text16, {
6348
+ dimColor: true,
6349
+ children: clip(description, width - FRAME_CHROME)
6350
+ }),
6351
+ /* @__PURE__ */ jsx17(Hints, {
4783
6352
  hints: [
4784
6353
  ["^v", "move"],
4785
- ["enter", "open"],
6354
+ ["tab", "complete"],
6355
+ ["enter", current?.value === RUN ? "run" : "open"],
6356
+ ["?", "keys"],
4786
6357
  ["esc", query ? "clear" : "quit"]
4787
6358
  ]
4788
6359
  })
@@ -4790,6 +6361,27 @@ function HomeScreen({
4790
6361
  });
4791
6362
  }
4792
6363
  // src/args.ts
6364
+ var COMMAND_WORDS2 = [
6365
+ "browse",
6366
+ "login",
6367
+ "logout",
6368
+ "whoami",
6369
+ "org",
6370
+ "list",
6371
+ "search",
6372
+ "bundle",
6373
+ "add",
6374
+ "install",
6375
+ "remove",
6376
+ "update",
6377
+ "installed",
6378
+ "outdated",
6379
+ "doctor",
6380
+ "config",
6381
+ "hooks",
6382
+ "help",
6383
+ "version"
6384
+ ];
4793
6385
  var HELP = `yourskills — install skills from your organization's registry
4794
6386
 
4795
6387
  Usage: yourskills <command> [options]
@@ -4815,9 +6407,9 @@ Commands:
4815
6407
  outdated Report skills whose registry pin has moved
4816
6408
  doctor Check the server, the session, the agents, the manifest
4817
6409
  config get [key] Show the stored settings, or one of them
4818
- config set <key> <val> Set one: scope, agents, server, track
6410
+ config set <key> <val> Set one: scope, agents, server, track, autoUpdate
4819
6411
  hooks status Show which agents carry our hooks, and where
4820
- hooks install Register them: an update check, and usage if opted in
6412
+ hooks install Register them: the session update, and usage if opted in
4821
6413
  hooks uninstall Remove ours, leaving every other hook alone
4822
6414
 
4823
6415
  Options:
@@ -4836,8 +6428,15 @@ Options:
4836
6428
 
4837
6429
  Environment:
4838
6430
  YOURSKILLS_URL Registry base URL (default api.yourskills.store)
4839
- YOURSKILLS_NO_TRACK Set to 1 to disable usage counting outright`;
4840
- function configCommand(rest) {
6431
+ YOURSKILLS_NO_TRACK Set to 1 to disable usage counting outright
6432
+ YOURSKILLS_DEBUG Set to 1 to print the stack behind an error
6433
+
6434
+ Exit codes:
6435
+ 0 Done
6436
+ 1 Something did not land: a failed install, a pending pull request
6437
+ 2 The command line was wrong; the last line says the spelling
6438
+ 3 Needs a session: run yourskills login`;
6439
+ function configCommand2(rest) {
4841
6440
  const [action, key, ...value] = rest;
4842
6441
  if (action === undefined || action === "get") {
4843
6442
  return { kind: "config", action: "get", ...key ? { key } : {} };
@@ -4862,7 +6461,7 @@ function configCommand(rest) {
4862
6461
  }
4863
6462
  return { kind: "config", action: "set", key, value: value.join(" ") };
4864
6463
  }
4865
- function hooksCommand(rest, agent, track) {
6464
+ function hooksCommand2(rest, agent, track) {
4866
6465
  const action = rest[0] ?? "status";
4867
6466
  if (action !== "install" && action !== "uninstall" && action !== "status") {
4868
6467
  return {
@@ -4877,7 +6476,7 @@ function hooksCommand(rest, agent, track) {
4877
6476
  ...track === undefined ? {} : { track }
4878
6477
  };
4879
6478
  }
4880
- function parseArgv(args, interactive = true) {
6479
+ function parseArgv2(args, interactive = true) {
4881
6480
  const opts = {};
4882
6481
  const positional = [];
4883
6482
  let plain = !interactive;
@@ -4928,7 +6527,11 @@ function parseArgv(args, interactive = true) {
4928
6527
  });
4929
6528
  opts.agents = [...opts.agents ?? [], value];
4930
6529
  } else if (arg.startsWith("-")) {
4931
- return done({ kind: "error", message: `Unknown option: ${arg}` });
6530
+ return done({
6531
+ kind: "error",
6532
+ message: `unknown option ${arg}`,
6533
+ next: "yourskills --help"
6534
+ });
4932
6535
  } else {
4933
6536
  positional.push(arg);
4934
6537
  }
@@ -4980,9 +6583,9 @@ function parseArgv(args, interactive = true) {
4980
6583
  case "org":
4981
6584
  return done(rest[0] ? { kind: "org", name: rest[0] } : { kind: "org" });
4982
6585
  case "config":
4983
- return done(configCommand(rest));
6586
+ return done(configCommand2(rest));
4984
6587
  case "hooks":
4985
- return done(hooksCommand(rest, opts.agents?.[0], track));
6588
+ return done(hooksCommand2(rest, opts.agents?.[0], track));
4986
6589
  case "hook": {
4987
6590
  const event = rest[0];
4988
6591
  return done(event && isHookEventName(event) ? { kind: "hook", event } : {
@@ -5002,8 +6605,14 @@ function parseArgv(args, interactive = true) {
5002
6605
  ...all ? { all: true } : {},
5003
6606
  opts
5004
6607
  });
5005
- default:
5006
- return done({ kind: "error", message: `Unknown command: ${command}` });
6608
+ default: {
6609
+ const guess = closest(command, COMMAND_WORDS2);
6610
+ return done({
6611
+ kind: "error",
6612
+ message: `unknown command ${command}`,
6613
+ ...guess ? { why: `did you mean ${guess}?`, next: `yourskills ${guess}` } : { next: "yourskills --help" }
6614
+ });
6615
+ }
5007
6616
  }
5008
6617
  }
5009
6618
 
@@ -5021,9 +6630,9 @@ async function createClient2(server) {
5021
6630
  }
5022
6631
 
5023
6632
  // src/screens.tsx
5024
- import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
5025
- import { useEffect as useEffect9, useState as useState9 } from "react";
5026
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
6633
+ import { Box as Box15, Text as Text17, useApp as useApp8, useInput as useInput9 } from "ink";
6634
+ import { useEffect as useEffect10, useState as useState12 } from "react";
6635
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
5027
6636
  var HELP_COLUMN = 26;
5028
6637
  function WhoamiScreen({
5029
6638
  load,
@@ -5031,8 +6640,8 @@ function WhoamiScreen({
5031
6640
  }) {
5032
6641
  const { exit } = useApp8();
5033
6642
  const width = useFrameWidth();
5034
- const [who, setWho] = useState9(null);
5035
- useEffect9(() => {
6643
+ const [who, setWho] = useState12(null);
6644
+ useEffect10(() => {
5036
6645
  let live = true;
5037
6646
  load().then((result) => {
5038
6647
  if (!live)
@@ -5048,13 +6657,13 @@ function WhoamiScreen({
5048
6657
  };
5049
6658
  }, [load, exit, onError]);
5050
6659
  if (!who) {
5051
- return /* @__PURE__ */ jsx13(Frame, {
6660
+ return /* @__PURE__ */ jsx18(Frame, {
5052
6661
  title: "whoami",
5053
6662
  width,
5054
- children: /* @__PURE__ */ jsxs12(Text13, {
6663
+ children: /* @__PURE__ */ jsxs17(Text17, {
5055
6664
  children: [
5056
- /* @__PURE__ */ jsx13(Spinner, {}),
5057
- /* @__PURE__ */ jsx13(Text13, {
6665
+ /* @__PURE__ */ jsx18(Spinner, {}),
6666
+ /* @__PURE__ */ jsx18(Text17, {
5058
6667
  dimColor: true,
5059
6668
  children: " Checking session..."
5060
6669
  })
@@ -5062,44 +6671,44 @@ function WhoamiScreen({
5062
6671
  })
5063
6672
  });
5064
6673
  }
5065
- return /* @__PURE__ */ jsx13(Frame, {
6674
+ return /* @__PURE__ */ jsx18(Frame, {
5066
6675
  title: "whoami",
5067
6676
  width,
5068
- children: /* @__PURE__ */ jsxs12(Box11, {
6677
+ children: /* @__PURE__ */ jsxs17(Box15, {
5069
6678
  flexDirection: "column",
5070
6679
  children: [
5071
- /* @__PURE__ */ jsxs12(Text13, {
6680
+ /* @__PURE__ */ jsxs17(Text17, {
5072
6681
  children: [
5073
- /* @__PURE__ */ jsx13(Text13, {
6682
+ /* @__PURE__ */ jsx18(Text17, {
5074
6683
  dimColor: true,
5075
6684
  children: "user "
5076
6685
  }),
5077
- /* @__PURE__ */ jsx13(Text13, {
6686
+ /* @__PURE__ */ jsx18(Text17, {
5078
6687
  children: who.user
5079
6688
  })
5080
6689
  ]
5081
6690
  }),
5082
- /* @__PURE__ */ jsxs12(Text13, {
6691
+ /* @__PURE__ */ jsxs17(Text17, {
5083
6692
  children: [
5084
- /* @__PURE__ */ jsx13(Text13, {
6693
+ /* @__PURE__ */ jsx18(Text17, {
5085
6694
  dimColor: true,
5086
6695
  children: "org "
5087
6696
  }),
5088
- who.org ? /* @__PURE__ */ jsx13(Text13, {
6697
+ who.org ? /* @__PURE__ */ jsx18(Text17, {
5089
6698
  children: who.org
5090
- }) : /* @__PURE__ */ jsx13(Text13, {
6699
+ }) : /* @__PURE__ */ jsx18(Text17, {
5091
6700
  color: tone.notice,
5092
6701
  children: "none — set an active organization in the web UI"
5093
6702
  })
5094
6703
  ]
5095
6704
  }),
5096
- /* @__PURE__ */ jsxs12(Text13, {
6705
+ /* @__PURE__ */ jsxs17(Text17, {
5097
6706
  children: [
5098
- /* @__PURE__ */ jsx13(Text13, {
6707
+ /* @__PURE__ */ jsx18(Text17, {
5099
6708
  dimColor: true,
5100
6709
  children: "server "
5101
6710
  }),
5102
- /* @__PURE__ */ jsx13(Text13, {
6711
+ /* @__PURE__ */ jsx18(Text17, {
5103
6712
  color: tone.primary,
5104
6713
  children: who.server
5105
6714
  })
@@ -5115,17 +6724,17 @@ function NoticeScreen({
5115
6724
  }) {
5116
6725
  const { exit } = useApp8();
5117
6726
  const width = useFrameWidth();
5118
- useEffect9(exit, [exit]);
5119
- return /* @__PURE__ */ jsx13(Frame, {
6727
+ useEffect10(exit, [exit]);
6728
+ return /* @__PURE__ */ jsx18(Frame, {
5120
6729
  title,
5121
6730
  width,
5122
- children: /* @__PURE__ */ jsxs12(Text13, {
6731
+ children: /* @__PURE__ */ jsxs17(Text17, {
5123
6732
  children: [
5124
- /* @__PURE__ */ jsx13(Text13, {
6733
+ /* @__PURE__ */ jsx18(Text17, {
5125
6734
  color: "green",
5126
6735
  children: mark.ok
5127
6736
  }),
5128
- /* @__PURE__ */ jsx13(Text13, {
6737
+ /* @__PURE__ */ jsx18(Text17, {
5129
6738
  children: ` ${message}`
5130
6739
  })
5131
6740
  ]
@@ -5142,16 +6751,16 @@ function ReportRow2({ line: line2, width }) {
5142
6751
  const { glyph, color } = MARKS2[line2.mark];
5143
6752
  const head = line2.mark === "none" ? "" : `${glyph} `;
5144
6753
  const room = width - FRAME_CHROME - head.length;
5145
- return /* @__PURE__ */ jsxs12(Text13, {
6754
+ return /* @__PURE__ */ jsxs17(Text17, {
5146
6755
  children: [
5147
- head ? /* @__PURE__ */ jsx13(Text13, {
6756
+ head ? /* @__PURE__ */ jsx18(Text17, {
5148
6757
  color,
5149
6758
  children: head
5150
6759
  }) : null,
5151
- /* @__PURE__ */ jsx13(Text13, {
6760
+ /* @__PURE__ */ jsx18(Text17, {
5152
6761
  children: clip(line2.text, room)
5153
6762
  }),
5154
- line2.note ? /* @__PURE__ */ jsx13(Text13, {
6763
+ line2.note ? /* @__PURE__ */ jsx18(Text17, {
5155
6764
  dimColor: true,
5156
6765
  children: clip(` ${line2.note}`, Math.max(room - line2.text.length, 0))
5157
6766
  }) : null
@@ -5163,12 +6772,15 @@ function ReportScreen2({
5163
6772
  load,
5164
6773
  onError,
5165
6774
  onReport,
5166
- exitWhenDone = true
6775
+ exitWhenDone = true,
6776
+ onBack,
6777
+ working = "Working...",
6778
+ onNext
5167
6779
  }) {
5168
6780
  const { exit } = useApp8();
5169
6781
  const width = useFrameWidth();
5170
- const [report, setReport] = useState9(null);
5171
- useEffect9(() => {
6782
+ const [report, setReport] = useState12(null);
6783
+ useEffect10(() => {
5172
6784
  let live = true;
5173
6785
  load().then((result) => {
5174
6786
  if (!live)
@@ -5183,28 +6795,58 @@ function ReportScreen2({
5183
6795
  live = false;
5184
6796
  };
5185
6797
  }, [load, onError, onReport]);
5186
- useEffect9(() => {
6798
+ useEffect10(() => {
5187
6799
  if (report && exitWhenDone)
5188
6800
  exit();
5189
6801
  }, [report, exit, exitWhenDone]);
5190
- return /* @__PURE__ */ jsx13(Frame, {
6802
+ const next = report?.next;
6803
+ useInput9((input) => {
6804
+ if (input === "n" && next)
6805
+ onNext?.(next.command);
6806
+ }, { isActive: Boolean(onNext && next) });
6807
+ return /* @__PURE__ */ jsxs17(Frame, {
5191
6808
  title,
5192
6809
  width,
5193
- children: report ? /* @__PURE__ */ jsx13(Box11, {
5194
- flexDirection: "column",
5195
- children: report.lines.map((line2) => /* @__PURE__ */ jsx13(ReportRow2, {
5196
- line: line2,
5197
- width
5198
- }, `${line2.mark}:${line2.text}`))
5199
- }) : /* @__PURE__ */ jsxs12(Text13, {
5200
- children: [
5201
- /* @__PURE__ */ jsx13(Spinner, {}),
5202
- /* @__PURE__ */ jsx13(Text13, {
5203
- dimColor: true,
5204
- children: " Working..."
5205
- })
5206
- ]
5207
- })
6810
+ children: [
6811
+ report ? /* @__PURE__ */ jsxs17(Box15, {
6812
+ flexDirection: "column",
6813
+ children: [
6814
+ report.lines.map((line2) => /* @__PURE__ */ jsx18(ReportRow2, {
6815
+ line: line2,
6816
+ width
6817
+ }, `${line2.mark}:${line2.text}`)),
6818
+ next ? /* @__PURE__ */ jsxs17(Text17, {
6819
+ children: [
6820
+ /* @__PURE__ */ jsx18(Text17, {
6821
+ dimColor: true,
6822
+ children: "next "
6823
+ }),
6824
+ /* @__PURE__ */ jsx18(Text17, {
6825
+ color: tone.primary,
6826
+ children: next.command
6827
+ }),
6828
+ /* @__PURE__ */ jsx18(Text17, {
6829
+ dimColor: true,
6830
+ children: clip(` ${next.why}`, width - FRAME_CHROME)
6831
+ })
6832
+ ]
6833
+ }) : null
6834
+ ]
6835
+ }) : /* @__PURE__ */ jsxs17(Text17, {
6836
+ children: [
6837
+ /* @__PURE__ */ jsx18(Spinner, {}),
6838
+ /* @__PURE__ */ jsx18(Text17, {
6839
+ dimColor: true,
6840
+ children: ` ${working}`
6841
+ })
6842
+ ]
6843
+ }),
6844
+ /* @__PURE__ */ jsx18(Back, {
6845
+ width,
6846
+ onBack,
6847
+ extra: onNext && next ? [["n", "run next"]] : []
6848
+ })
6849
+ ]
5208
6850
  });
5209
6851
  }
5210
6852
  function numberedLines(text) {
@@ -5214,26 +6856,26 @@ function numberedLines(text) {
5214
6856
  function HelpScreen({ text }) {
5215
6857
  const { exit } = useApp8();
5216
6858
  const width = useFrameWidth();
5217
- useEffect9(exit, [exit]);
5218
- return /* @__PURE__ */ jsx13(Frame, {
6859
+ useEffect10(exit, [exit]);
6860
+ return /* @__PURE__ */ jsx18(Frame, {
5219
6861
  title: "yourskills",
5220
6862
  width,
5221
- children: /* @__PURE__ */ jsx13(Box11, {
6863
+ children: /* @__PURE__ */ jsx18(Box15, {
5222
6864
  flexDirection: "column",
5223
6865
  children: numberedLines(text).map(({ id, line: line2 }) => {
5224
6866
  if (line2.length === 0)
5225
- return /* @__PURE__ */ jsx13(Blank, {}, id);
6867
+ return /* @__PURE__ */ jsx18(Blank, {}, id);
5226
6868
  if (!line2.startsWith(" "))
5227
- return /* @__PURE__ */ jsx13(Text13, {
6869
+ return /* @__PURE__ */ jsx18(Text17, {
5228
6870
  children: clip(line2, width - FRAME_CHROME)
5229
6871
  }, id);
5230
- return /* @__PURE__ */ jsxs12(Text13, {
6872
+ return /* @__PURE__ */ jsxs17(Text17, {
5231
6873
  children: [
5232
- /* @__PURE__ */ jsx13(Text13, {
6874
+ /* @__PURE__ */ jsx18(Text17, {
5233
6875
  color: tone.primary,
5234
6876
  children: line2.slice(0, HELP_COLUMN)
5235
6877
  }),
5236
- /* @__PURE__ */ jsx13(Text13, {
6878
+ /* @__PURE__ */ jsx18(Text17, {
5237
6879
  dimColor: true,
5238
6880
  children: clip(line2.slice(HELP_COLUMN), width - FRAME_CHROME - HELP_COLUMN)
5239
6881
  })
@@ -5246,7 +6888,7 @@ function HelpScreen({ text }) {
5246
6888
  // package.json
5247
6889
  var package_default = {
5248
6890
  name: "yourskills",
5249
- version: "0.1.2",
6891
+ version: "0.1.3",
5250
6892
  description: "Browse, install and keep up to date the agent skills and bundles your team publishes on yourskills.",
5251
6893
  license: "UNLICENSED",
5252
6894
  homepage: "https://yourskills.store",
@@ -5303,7 +6945,7 @@ var package_default = {
5303
6945
  var VERSION = package_default.version ?? "0.0.0-dev";
5304
6946
 
5305
6947
  // src/main.tsx
5306
- import { jsx as jsx14 } from "react/jsx-runtime";
6948
+ import { jsx as jsx19 } from "react/jsx-runtime";
5307
6949
  function applySummary(summary) {
5308
6950
  if (summary.pending.length > 0 || summary.failed > 0)
5309
6951
  process.exitCode = 1;
@@ -5314,7 +6956,7 @@ var PLAIN_MARKS = {
5314
6956
  pending: mark.pending,
5315
6957
  none: ""
5316
6958
  };
5317
- function emit(report, output) {
6959
+ function emit2(report, output) {
5318
6960
  if (report.failed)
5319
6961
  process.exitCode = 1;
5320
6962
  if (output.json) {
@@ -5325,6 +6967,8 @@ function emit(report, output) {
5325
6967
  return;
5326
6968
  for (const line2 of report.lines)
5327
6969
  console.log(plainLine(line2, PLAIN_MARKS));
6970
+ if (report.next)
6971
+ console.log(nextLine(report.next));
5328
6972
  }
5329
6973
  function toRows(skills) {
5330
6974
  return skills.map((s) => ({
@@ -5354,15 +6998,6 @@ function catalogReport(title, skills) {
5354
6998
  json: { skills }
5355
6999
  };
5356
7000
  }
5357
- async function resolveInstallOptions(opts) {
5358
- const { scope, manifestPath } = await resolveScope({ flag: opts.scope });
5359
- const path = scope === "project" && manifestPath ? manifestPath : manifestPathFor(scope);
5360
- return {
5361
- scope,
5362
- agents: await resolveAgents(opts.agents),
5363
- manifest: { path, scope }
5364
- };
5365
- }
5366
7001
  var INVITES_HOOKS = new Set([
5367
7002
  "login",
5368
7003
  "add",
@@ -5388,22 +7023,6 @@ async function prepare(command) {
5388
7023
  function nearestFor(opts) {
5389
7024
  return nearestManifest(opts.scope);
5390
7025
  }
5391
- function nothingTracked(nearest, kind, all = false) {
5392
- const has = kind === "install" ? Object.keys(nearest.manifest.skills).length > 0 : Object.keys(nearest.manifest.bundles).length > 0 || all && looseSkills(nearest.manifest).length > 0;
5393
- if (has)
5394
- return null;
5395
- return {
5396
- title: kind,
5397
- lines: [
5398
- {
5399
- mark: "none",
5400
- text: kind === "install" ? "Nothing recorded to install. Run `yourskills bundle <name>` first." : "No bundles tracked here. Run `yourskills bundle <name>` first.",
5401
- note: nearest.path
5402
- }
5403
- ],
5404
- json: { manifest: nearest.path, skills: [], bundles: [] }
5405
- };
5406
- }
5407
7026
  async function runPlain(command, output, server) {
5408
7027
  switch (command.kind) {
5409
7028
  case "help":
@@ -5412,12 +7031,25 @@ async function runPlain(command, output, server) {
5412
7031
  case "version":
5413
7032
  console.log(VERSION);
5414
7033
  return;
7034
+ case "error":
7035
+ throw new CliError(command.message, {
7036
+ ...command.why ? { why: command.why } : {},
7037
+ ...command.next ? { next: command.next } : {},
7038
+ code: EXIT.usage
7039
+ });
5415
7040
  case "home":
5416
7041
  case "browse":
5417
- case "error":
5418
- throw new Error(command.kind === "error" ? command.message : `${command.kind === "home" ? "the palette" : "browse"} needs a terminal`);
7042
+ throw new CliError(`${command.kind === "home" ? "the palette" : "browse"} needs a terminal`, {
7043
+ why: "stdout is not a terminal, so there is nothing to draw the frame into",
7044
+ next: "yourskills list, or yourskills search <query>",
7045
+ code: EXIT.usage
7046
+ });
5419
7047
  case "login":
5420
- throw new Error("login needs a terminal. Sign in on a machine that has one; the token is then copied from ~/.config/yourskills/config.json.");
7048
+ throw new CliError("login needs a terminal", {
7049
+ why: "the device code has to be read by a person",
7050
+ next: "sign in on a machine with a terminal, then copy ~/.config/yourskills/config.json",
7051
+ code: EXIT.usage
7052
+ });
5421
7053
  case "logout":
5422
7054
  await logout();
5423
7055
  console.log("Logged out.");
@@ -5432,7 +7064,7 @@ async function runPlain(command, output, server) {
5432
7064
  case "search": {
5433
7065
  const client = await createClient2(server);
5434
7066
  const skills = command.kind === "list" ? await client.skills.list.query({}) : await client.skills.search.query({ q: command.query });
5435
- emit(catalogReport(command.kind === "list" ? "skills" : `search: ${command.query}`, skills), output);
7067
+ emit2(catalogReport(command.kind === "list" ? "skills" : `search: ${command.query}`, skills), output);
5436
7068
  return;
5437
7069
  }
5438
7070
  case "add":
@@ -5445,7 +7077,7 @@ async function runPlain(command, output, server) {
5445
7077
  const nearest = await nearestFor(command.opts);
5446
7078
  const empty = nothingTracked(nearest, "install");
5447
7079
  if (empty) {
5448
- emit(empty, output);
7080
+ emit2(empty, output);
5449
7081
  return;
5450
7082
  }
5451
7083
  applySummary(await installPlain(reinstall(nearest.manifest, command.opts)));
@@ -5459,7 +7091,7 @@ async function runPlain(command, output, server) {
5459
7091
  const nearest = await nearestFor(command.opts);
5460
7092
  const empty = nothingTracked(nearest, "update", command.all ?? false);
5461
7093
  if (empty) {
5462
- emit(empty, output);
7094
+ emit2(empty, output);
5463
7095
  return;
5464
7096
  }
5465
7097
  applySummary(await installPlain(updateTracked(await createClient2(server), nearest.manifest, command.opts, command.all ?? false)));
@@ -5467,26 +7099,26 @@ async function runPlain(command, output, server) {
5467
7099
  }
5468
7100
  case "remove": {
5469
7101
  const nearest = await nearestFor(command.opts);
5470
- emit(await removeSkills(command.names, nearest.manifest, nearest.path, command.opts), output);
7102
+ emit2(await removeSkills(command.names, nearest.manifest, nearest.path, command.opts), output);
5471
7103
  return;
5472
7104
  }
5473
7105
  case "installed":
5474
- emit(installedReport(await nearestManifest()), output);
7106
+ emit2(installedReport(await nearestManifest()), output);
5475
7107
  return;
5476
7108
  case "outdated":
5477
- emit(await outdatedReport(await createClient2(server), await nearestManifest(), command.quiet ?? false), output);
7109
+ emit2(await outdatedReport(await createClient2(server), await nearestManifest(), command.quiet ?? false), output);
5478
7110
  return;
5479
7111
  case "doctor":
5480
- emit(await doctorReport(await nearestManifest(), server), output);
7112
+ emit2(await doctorReport(await nearestManifest(), server), output);
5481
7113
  return;
5482
7114
  case "org":
5483
- emit(command.name ? await orgSwitchReport(command.name, server) : await orgListReport(server), output);
7115
+ emit2(command.name ? await orgSwitchReport(command.name, server) : await orgListReport(server), output);
5484
7116
  return;
5485
7117
  case "config":
5486
- emit(command.action === "get" ? await configGet(command.key) : await configSet(command.key, command.value), output);
7118
+ emit2(command.action === "get" ? await configGet(command.key) : await configSet(command.key, command.value), output);
5487
7119
  return;
5488
7120
  case "hooks":
5489
- emit(await hooksReport(command), output);
7121
+ emit2(await hooksReport(command), output);
5490
7122
  return;
5491
7123
  case "hook":
5492
7124
  await runHook(command.event);
@@ -5504,13 +7136,13 @@ function hooksReport(command) {
5504
7136
  return hooksStatus();
5505
7137
  }
5506
7138
  async function screenFor(command, fail, summarise, report, server) {
5507
- const gate = (title, build) => /* @__PURE__ */ jsx14(AuthGate, {
7139
+ const gate = (title, build) => /* @__PURE__ */ jsx19(AuthGate, {
5508
7140
  title,
5509
7141
  build,
5510
7142
  connect: () => createClient2(server),
5511
7143
  onError: fail
5512
7144
  });
5513
- const local = (title, load) => /* @__PURE__ */ jsx14(ReportScreen2, {
7145
+ const local = (title, load) => /* @__PURE__ */ jsx19(ReportScreen2, {
5514
7146
  title,
5515
7147
  load,
5516
7148
  onError: fail,
@@ -5518,40 +7150,44 @@ async function screenFor(command, fail, summarise, report, server) {
5518
7150
  });
5519
7151
  switch (command.kind) {
5520
7152
  case "help":
5521
- return /* @__PURE__ */ jsx14(HelpScreen, {
7153
+ return /* @__PURE__ */ jsx19(HelpScreen, {
5522
7154
  text: HELP
5523
7155
  });
5524
7156
  case "version":
5525
- return /* @__PURE__ */ jsx14(NoticeScreen, {
7157
+ return /* @__PURE__ */ jsx19(NoticeScreen, {
5526
7158
  title: "version",
5527
7159
  message: VERSION
5528
7160
  });
5529
7161
  case "error":
5530
- throw new Error(command.message);
7162
+ throw new CliError(command.message, {
7163
+ ...command.why ? { why: command.why } : {},
7164
+ ...command.next ? { next: command.next } : {},
7165
+ code: EXIT.usage
7166
+ });
5531
7167
  case "login":
5532
- return /* @__PURE__ */ jsx14(LoginScreen, {
7168
+ return /* @__PURE__ */ jsx19(LoginScreen, {
5533
7169
  onError: fail
5534
7170
  });
5535
7171
  case "logout":
5536
7172
  await logout();
5537
- return /* @__PURE__ */ jsx14(NoticeScreen, {
7173
+ return /* @__PURE__ */ jsx19(NoticeScreen, {
5538
7174
  title: "logout",
5539
7175
  message: "Logged out."
5540
7176
  });
5541
7177
  case "whoami":
5542
- return gate("whoami", () => /* @__PURE__ */ jsx14(WhoamiScreen, {
7178
+ return gate("whoami", () => /* @__PURE__ */ jsx19(WhoamiScreen, {
5543
7179
  load: () => whoami(server),
5544
7180
  onError: fail
5545
7181
  }));
5546
7182
  case "list":
5547
- return gate("skills", (client) => /* @__PURE__ */ jsx14(CatalogScreen, {
7183
+ return gate("skills", (client) => /* @__PURE__ */ jsx19(CatalogScreen, {
5548
7184
  title: "skills",
5549
7185
  load: async () => toRows(await client.skills.list.query({})),
5550
7186
  onError: fail
5551
7187
  }));
5552
7188
  case "search": {
5553
7189
  const q = command.query;
5554
- return gate(`search: ${q}`, (client) => /* @__PURE__ */ jsx14(CatalogScreen, {
7190
+ return gate(`search: ${q}`, (client) => /* @__PURE__ */ jsx19(CatalogScreen, {
5555
7191
  title: `search: ${q}`,
5556
7192
  load: async () => toRows(await client.skills.search.query({ q })),
5557
7193
  onError: fail
@@ -5559,17 +7195,27 @@ async function screenFor(command, fail, summarise, report, server) {
5559
7195
  }
5560
7196
  case "home": {
5561
7197
  const opts = await resolveInstallOptions({});
5562
- return /* @__PURE__ */ jsx14(HomeScreen, {
7198
+ const reload = () => paletteFacts(opts.scope, server);
7199
+ return /* @__PURE__ */ jsx19(HomeScreen, {
5563
7200
  opts,
7201
+ facts: await reload(),
7202
+ reload,
5564
7203
  server,
5565
7204
  openClient: () => createClient2(server),
5566
7205
  onError: fail,
5567
- onSummary: summarise
7206
+ onSummary: summarise,
7207
+ onWelcomed: async () => {
7208
+ const config = await readConfig();
7209
+ await writeConfig({
7210
+ ...config,
7211
+ welcomed: new Date().toISOString()
7212
+ });
7213
+ }
5568
7214
  });
5569
7215
  }
5570
7216
  case "browse": {
5571
7217
  const opts = await resolveInstallOptions({});
5572
- return gate("browse", (client) => /* @__PURE__ */ jsx14(BrowserScreen, {
7218
+ return gate("browse", (client) => /* @__PURE__ */ jsx19(BrowserScreen, {
5573
7219
  client,
5574
7220
  opts,
5575
7221
  onError: fail,
@@ -5577,13 +7223,13 @@ async function screenFor(command, fail, summarise, report, server) {
5577
7223
  }));
5578
7224
  }
5579
7225
  case "add":
5580
- return gate("add", (client) => /* @__PURE__ */ jsx14(Installer, {
7226
+ return gate("add", (client) => /* @__PURE__ */ jsx19(Installer, {
5581
7227
  title: "add",
5582
7228
  stream: () => install(client, command.names, command.opts),
5583
7229
  onDone: summarise
5584
7230
  }));
5585
7231
  case "bundle":
5586
- return gate(`bundle: ${command.name}`, (client) => /* @__PURE__ */ jsx14(Installer, {
7232
+ return gate(`bundle: ${command.name}`, (client) => /* @__PURE__ */ jsx19(Installer, {
5587
7233
  title: `bundle: ${command.name}`,
5588
7234
  stream: () => installBundle(client, command.name, command.opts),
5589
7235
  onDone: summarise
@@ -5593,7 +7239,7 @@ async function screenFor(command, fail, summarise, report, server) {
5593
7239
  const empty = nothingTracked(nearest, "install");
5594
7240
  if (empty)
5595
7241
  return local("install", async () => empty);
5596
- return /* @__PURE__ */ jsx14(Installer, {
7242
+ return /* @__PURE__ */ jsx19(Installer, {
5597
7243
  title: "install",
5598
7244
  stream: () => reinstall(nearest.manifest, command.opts),
5599
7245
  onDone: summarise
@@ -5602,7 +7248,7 @@ async function screenFor(command, fail, summarise, report, server) {
5602
7248
  case "update": {
5603
7249
  if (command.bundle) {
5604
7250
  const name = command.bundle;
5605
- return gate(`update: ${name}`, (client) => /* @__PURE__ */ jsx14(Installer, {
7251
+ return gate(`update: ${name}`, (client) => /* @__PURE__ */ jsx19(Installer, {
5606
7252
  title: `update: ${name}`,
5607
7253
  stream: () => update(client, name, command.opts),
5608
7254
  onDone: summarise
@@ -5612,8 +7258,10 @@ async function screenFor(command, fail, summarise, report, server) {
5612
7258
  const empty = nothingTracked(nearest, "update", command.all ?? false);
5613
7259
  if (empty)
5614
7260
  return local("update", async () => empty);
5615
- return gate("update", (client) => /* @__PURE__ */ jsx14(Installer, {
7261
+ const scope = updateScope(Object.keys(nearest.manifest.bundles).length, command.all ? looseSkills(nearest.manifest).length : 0);
7262
+ return gate("update", (client) => /* @__PURE__ */ jsx19(Installer, {
5616
7263
  title: "update",
7264
+ plan: `updating ${scope} to what the registry blesses today`,
5617
7265
  stream: () => updateTracked(client, nearest.manifest, command.opts, command.all ?? false),
5618
7266
  onDone: summarise
5619
7267
  }));
@@ -5633,14 +7281,15 @@ async function screenFor(command, fail, summarise, report, server) {
5633
7281
  case "hook":
5634
7282
  throw new Error("hooks are not interactive");
5635
7283
  case "outdated":
5636
- return gate("outdated", (client) => /* @__PURE__ */ jsx14(ReportScreen2, {
7284
+ return gate("outdated", (client) => /* @__PURE__ */ jsx19(ReportScreen2, {
5637
7285
  title: "outdated",
7286
+ working: "Checking what you have installed against the registry...",
5638
7287
  load: async () => outdatedReport(client, await nearestManifest(), command.quiet),
5639
7288
  onError: fail,
5640
7289
  onReport: report
5641
7290
  }));
5642
7291
  case "org":
5643
- return gate("org", () => /* @__PURE__ */ jsx14(ReportScreen2, {
7292
+ return gate("org", () => /* @__PURE__ */ jsx19(ReportScreen2, {
5644
7293
  title: "org",
5645
7294
  load: () => command.name ? orgSwitchReport(command.name, server) : orgListReport(server),
5646
7295
  onError: fail,
@@ -5674,7 +7323,7 @@ function isEntrypoint() {
5674
7323
  }
5675
7324
  if (isEntrypoint()) {
5676
7325
  const interactive = Boolean(process.stdout.isTTY);
5677
- const parsed = parseArgv(process.argv.slice(2), interactive);
7326
+ const parsed = parseArgv2(process.argv.slice(2), interactive);
5678
7327
  const { output, server } = parsed;
5679
7328
  try {
5680
7329
  const command = await prepare(parsed.command);
@@ -5688,7 +7337,13 @@ if (isEntrypoint()) {
5688
7337
  console.log(line2);
5689
7338
  }
5690
7339
  } catch (error) {
5691
- console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
5692
- process.exit(1);
7340
+ const { lines, code } = describeError(error);
7341
+ const [what, ...rest] = lines;
7342
+ console.error(`${mark.fail} ${what}`);
7343
+ for (const line2 of rest)
7344
+ console.error(line2);
7345
+ if (process.env.YOURSKILLS_DEBUG === "1" && error instanceof Error)
7346
+ console.error(error.stack);
7347
+ process.exit(code);
5693
7348
  }
5694
7349
  }