@hiver/skills 1.0.3 → 1.0.5

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 (2) hide show
  1. package/dist/cli.js +371 -113
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -239,6 +239,15 @@ function getAgentMeta(collectionName, agentName) {
239
239
  dependencies: Array.isArray(fm.dependencies) ? fm.dependencies : []
240
240
  };
241
241
  }
242
+ function findCollectionsContaining(name, type) {
243
+ const matches = [];
244
+ for (const col of listCollections()) {
245
+ const { skills, agents } = scanCollection(col);
246
+ if (type === "skill" && skills.includes(name)) matches.push(col);
247
+ if (type === "agent" && agents.includes(name)) matches.push(col);
248
+ }
249
+ return matches;
250
+ }
242
251
  function resolveDependencies(selected, selectedCollections) {
243
252
  const installedNames = /* @__PURE__ */ new Set([
244
253
  ...selected.skills.map((s) => s.name),
@@ -341,34 +350,8 @@ function Add({ directSkills }) {
341
350
  const [selectedType, setSelectedType] = useState3(null);
342
351
  const [toInstall, setToInstall] = useState3({ skills: [], agents: [] });
343
352
  const [results, setResults] = useState3({ skills: 0, agents: 0, deps: [] });
353
+ const [directMissing, setDirectMissing] = useState3([]);
344
354
  const destRoot = process.cwd();
345
- useEffect(() => {
346
- if (directSkills && directSkills.length > 0 && targetKey) {
347
- installByName(directSkills, targets[targetKey], destRoot).then((res) => {
348
- setResults(res);
349
- setStep(STEPS.DONE);
350
- });
351
- }
352
- }, [targetKey]);
353
- async function installByName(names, targetConfig, dest) {
354
- const collections = listCollections();
355
- let skillCount = 0;
356
- let agentCount = 0;
357
- for (const name of names) {
358
- for (const col of collections) {
359
- const { skills, agents } = scanCollection(col);
360
- if (skills.includes(name)) {
361
- if (await installSkill(col, name, targetConfig, dest)) skillCount++;
362
- break;
363
- }
364
- if (agents.includes(name)) {
365
- if (await installAgent(col, name, targetConfig, dest)) agentCount++;
366
- break;
367
- }
368
- }
369
- }
370
- return { skills: skillCount, agents: agentCount, deps: [] };
371
- }
372
355
  function getAvailableTypes(col) {
373
356
  const { skills, agents } = scanCollection(col);
374
357
  const types = [];
@@ -378,11 +361,29 @@ function Add({ directSkills }) {
378
361
  }
379
362
  function handleTargetSelect(key) {
380
363
  setTargetKey(key);
381
- if (directSkills && directSkills.length > 0) return;
382
364
  setStep(STEPS.COLLECTIONS);
383
365
  }
384
366
  function handleCollectionSelect(col) {
385
367
  setSelectedCollection(col);
368
+ if (directSkills && directSkills.length > 0) {
369
+ const { skills, agents } = scanCollection(col);
370
+ const skillItems = [];
371
+ const agentItems = [];
372
+ const missing = [];
373
+ for (const name of directSkills) {
374
+ if (skills.includes(name)) skillItems.push({ collection: col, name });
375
+ else if (agents.includes(name)) agentItems.push({ collection: col, name });
376
+ else missing.push(`${name} (not in ${col})`);
377
+ }
378
+ setDirectMissing(missing);
379
+ setToInstall({ skills: skillItems, agents: agentItems });
380
+ if (skillItems.length === 0 && agentItems.length === 0) {
381
+ setStep(STEPS.DONE);
382
+ } else {
383
+ setStep(STEPS.INSTALLING);
384
+ }
385
+ return;
386
+ }
386
387
  setToInstall({ skills: [], agents: [] });
387
388
  const types = getAvailableTypes(col);
388
389
  if (types.length === 1) {
@@ -466,8 +467,24 @@ function Add({ directSkills }) {
466
467
  );
467
468
  }
468
469
  if (step === STEPS.COLLECTIONS) {
469
- const collections = listCollections();
470
+ let collections = listCollections();
471
+ if (directSkills && directSkills.length > 0) {
472
+ const union = /* @__PURE__ */ new Set();
473
+ for (const name of directSkills) {
474
+ for (const c of findCollectionsContaining(name, "skill")) union.add(c);
475
+ for (const c of findCollectionsContaining(name, "agent")) union.add(c);
476
+ }
477
+ collections = collections.filter((c) => union.has(c));
478
+ }
470
479
  const items = collections.map((c) => ({ label: c, value: c }));
480
+ if (items.length === 0) {
481
+ setTimeout(() => exit(), 100);
482
+ return /* @__PURE__ */ jsxs4(ErrorMsg, { children: [
483
+ "None of: ",
484
+ directSkills.join(", "),
485
+ " found in any collection."
486
+ ] });
487
+ }
471
488
  return /* @__PURE__ */ jsx3(
472
489
  Select,
473
490
  {
@@ -530,6 +547,10 @@ function Add({ directSkills }) {
530
547
  if (step === STEPS.DONE) {
531
548
  setTimeout(() => exit(), 100);
532
549
  return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", marginTop: 1, children: [
550
+ directMissing.length > 0 && /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsxs4(ErrorMsg, { children: [
551
+ "Not found in any collection: ",
552
+ directMissing.join(", ")
553
+ ] }) }),
533
554
  results.deps.length > 0 && /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", marginBottom: 1, children: [
534
555
  /* @__PURE__ */ jsx3(Info, { children: "Auto-installed dependencies:" }),
535
556
  results.deps.map((d) => /* @__PURE__ */ jsxs4(Text4, { color: "cyan", children: [
@@ -625,8 +646,8 @@ function List() {
625
646
  // src/commands/Update.jsx
626
647
  import React8, { useState as useState4, useEffect as useEffect2 } from "react";
627
648
  import { Box as Box7, Text as Text8, useApp as useApp3 } from "ink";
628
- import path3 from "path";
629
- import fs3 from "fs-extra";
649
+ import path4 from "path";
650
+ import fs4 from "fs-extra";
630
651
 
631
652
  // src/components/Confirm.jsx
632
653
  import React6 from "react";
@@ -671,6 +692,8 @@ function DiffView({ patch }) {
671
692
  }
672
693
 
673
694
  // src/utils/diffUtil.js
695
+ import path3 from "path";
696
+ import fs3 from "fs-extra";
674
697
  import { createTwoFilesPatch } from "diff";
675
698
  function computeDiff(filePath, localContent, latestContent) {
676
699
  return createTwoFilesPatch(
@@ -685,133 +708,367 @@ function computeDiff(filePath, localContent, latestContent) {
685
708
  function hasChanges(localContent, latestContent) {
686
709
  return localContent.trim() !== latestContent.trim();
687
710
  }
711
+ function walkFiles(dir) {
712
+ const out = [];
713
+ if (!fs3.existsSync(dir)) return out;
714
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
715
+ for (const entry of entries) {
716
+ const full = path3.join(dir, entry.name);
717
+ if (entry.isDirectory()) {
718
+ for (const sub of walkFiles(full)) out.push(path3.join(entry.name, sub));
719
+ } else if (entry.isFile()) {
720
+ out.push(entry.name);
721
+ }
722
+ }
723
+ return out;
724
+ }
725
+ function isBinary(buf) {
726
+ const sample = buf.subarray(0, Math.min(buf.length, 8e3));
727
+ for (let i = 0; i < sample.length; i++) {
728
+ if (sample[i] === 0) return true;
729
+ }
730
+ return false;
731
+ }
732
+ function hasDirChanges(localDir, latestDir) {
733
+ const localFiles = new Set(walkFiles(localDir));
734
+ const latestFiles = new Set(walkFiles(latestDir));
735
+ if (localFiles.size !== latestFiles.size) return true;
736
+ for (const rel of latestFiles) {
737
+ if (!localFiles.has(rel)) return true;
738
+ const a = fs3.readFileSync(path3.join(localDir, rel));
739
+ const b = fs3.readFileSync(path3.join(latestDir, rel));
740
+ if (!a.equals(b)) return true;
741
+ }
742
+ for (const rel of localFiles) {
743
+ if (!latestFiles.has(rel)) return true;
744
+ }
745
+ return false;
746
+ }
747
+ function computeDirDiff(localDir, latestDir) {
748
+ const localFiles = new Set(walkFiles(localDir));
749
+ const latestFiles = new Set(walkFiles(latestDir));
750
+ const allFiles = /* @__PURE__ */ new Set([...localFiles, ...latestFiles]);
751
+ const parts = [];
752
+ for (const rel of [...allFiles].sort()) {
753
+ const localBuf = localFiles.has(rel) ? fs3.readFileSync(path3.join(localDir, rel)) : Buffer.alloc(0);
754
+ const latestBuf = latestFiles.has(rel) ? fs3.readFileSync(path3.join(latestDir, rel)) : Buffer.alloc(0);
755
+ if (localBuf.equals(latestBuf)) continue;
756
+ if (isBinary(localBuf) || isBinary(latestBuf)) {
757
+ const verb = !localFiles.has(rel) ? "added" : !latestFiles.has(rel) ? "removed" : "changed";
758
+ parts.push(`Binary file ${rel} ${verb} (${localBuf.length} \u2192 ${latestBuf.length} bytes)`);
759
+ continue;
760
+ }
761
+ const localContent = localBuf.toString("utf-8");
762
+ const latestContent = latestBuf.toString("utf-8");
763
+ if (localContent.trim() === latestContent.trim()) continue;
764
+ parts.push(computeDiff(rel, localContent, latestContent));
765
+ }
766
+ return parts.join("\n");
767
+ }
688
768
 
689
769
  // src/commands/Update.jsx
690
770
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
691
771
  var STEPS2 = {
692
772
  TARGET: "target",
693
- CHECKING: "checking",
773
+ COLLECTION: "collection",
774
+ ITEMS: "items",
775
+ RESOLVE: "resolve",
694
776
  CONFIRM: "confirm",
695
777
  DONE: "done"
696
778
  };
697
- function Update() {
779
+ function detectType(name, targetConfig, destRoot) {
780
+ const skillPath = path4.join(destRoot, targetConfig.skillsDir, name, "SKILL.md");
781
+ if (fs4.existsSync(skillPath)) return "skill";
782
+ const agentPath = path4.join(destRoot, targetConfig.agentsDir, `${name}.md`);
783
+ if (fs4.existsSync(agentPath)) return "agent";
784
+ return null;
785
+ }
786
+ function Update({ directSkills }) {
698
787
  const { exit } = useApp3();
699
788
  const [step, setStep] = useState4(STEPS2.TARGET);
700
789
  const [targetKey, setTargetKey] = useState4(null);
701
- const [changedItems, setChangedItems] = useState4([]);
790
+ const [selectedCollection, setSelectedCollection] = useState4(null);
791
+ const [queue, setQueue] = useState4([]);
702
792
  const [currentIdx, setCurrentIdx] = useState4(0);
793
+ const [currentPatch, setCurrentPatch] = useState4(null);
703
794
  const [updatedCount, setUpdatedCount] = useState4(0);
704
795
  const [skippedCount, setSkippedCount] = useState4(0);
796
+ const [installedCount, setInstalledCount] = useState4(0);
797
+ const [missingNames, setMissingNames] = useState4([]);
798
+ const [directInfo, setDirectInfo] = useState4([]);
799
+ const [skipReasons, setSkipReasons] = useState4([]);
705
800
  const destRoot = process.cwd();
706
- function findSource(name, type) {
707
- for (const col of listCollections()) {
708
- const { skills, agents } = scanCollection(col);
709
- if (type === "skill" && skills.includes(name)) return { collection: col };
710
- if (type === "agent" && agents.includes(name)) return { collection: col };
801
+ function handleTargetSelect(key) {
802
+ setTargetKey(key);
803
+ if (directSkills && directSkills.length > 0) {
804
+ const targetConfig = targets[key];
805
+ const info = [];
806
+ const missing = [];
807
+ for (const name of directSkills) {
808
+ let type = detectType(name, targetConfig, destRoot);
809
+ let installed = type !== null;
810
+ if (!type) {
811
+ if (findCollectionsContaining(name, "skill").length > 0) type = "skill";
812
+ else if (findCollectionsContaining(name, "agent").length > 0) type = "agent";
813
+ }
814
+ if (!type) {
815
+ missing.push(name);
816
+ continue;
817
+ }
818
+ const cols = findCollectionsContaining(name, type);
819
+ if (cols.length === 0) {
820
+ missing.push(name);
821
+ continue;
822
+ }
823
+ info.push({ name, type, installed, collections: cols });
824
+ }
825
+ setMissingNames(missing);
826
+ setDirectInfo(info);
827
+ if (info.length === 0) {
828
+ setStep(STEPS2.DONE);
829
+ return;
830
+ }
711
831
  }
712
- return null;
832
+ setStep(STEPS2.COLLECTION);
713
833
  }
714
- useEffect2(() => {
715
- if (step !== STEPS2.CHECKING) return;
716
- const targetConfig = targets[targetKey];
717
- const installed = listInstalled(targetConfig, destRoot);
718
- const changed = [];
719
- for (const skillName of installed.skills) {
720
- const source = findSource(skillName, "skill");
721
- if (!source) continue;
722
- const localFile = path3.join(destRoot, targetConfig.skillsDir, skillName, "SKILL.md");
723
- const latestFile = path3.join(getCollectionsDir(), source.collection, "skills", skillName, "SKILL.md");
724
- if (!fs3.existsSync(localFile) || !fs3.existsSync(latestFile)) continue;
725
- const localContent = fs3.readFileSync(localFile, "utf-8");
726
- const latestContent = fs3.readFileSync(latestFile, "utf-8");
727
- if (hasChanges(localContent, latestContent)) {
728
- changed.push({
729
- name: skillName,
730
- type: "skill",
731
- collection: source.collection,
732
- patch: computeDiff("SKILL.md", localContent, latestContent)
733
- });
834
+ function handleCollectionSelect(collection) {
835
+ setSelectedCollection(collection);
836
+ if (directSkills && directSkills.length > 0) {
837
+ const items = [];
838
+ const skipped = [];
839
+ for (const entry of directInfo) {
840
+ if (entry.collections.includes(collection)) {
841
+ items.push({ name: entry.name, type: entry.type, installed: entry.installed });
842
+ } else {
843
+ skipped.push(`${entry.name} (not in ${collection})`);
844
+ }
734
845
  }
735
- }
736
- for (const agentName of installed.agents) {
737
- const source = findSource(agentName, "agent");
738
- if (!source) continue;
739
- const localFile = path3.join(destRoot, targetConfig.agentsDir, `${agentName}.md`);
740
- const latestFile = path3.join(getCollectionsDir(), source.collection, "agents", `${agentName}.md`);
741
- if (!fs3.existsSync(localFile) || !fs3.existsSync(latestFile)) continue;
742
- const localContent = fs3.readFileSync(localFile, "utf-8");
743
- const latestContent = fs3.readFileSync(latestFile, "utf-8");
744
- if (hasChanges(localContent, latestContent)) {
745
- changed.push({
746
- name: agentName,
747
- type: "agent",
748
- collection: source.collection,
749
- patch: computeDiff(`${agentName}.md`, localContent, latestContent)
750
- });
846
+ if (skipped.length > 0) {
847
+ setMissingNames((prev) => [...prev, ...skipped]);
848
+ }
849
+ setQueue(items);
850
+ setCurrentIdx(0);
851
+ if (items.length === 0) {
852
+ setStep(STEPS2.DONE);
853
+ } else {
854
+ setStep(STEPS2.RESOLVE);
751
855
  }
856
+ } else {
857
+ setStep(STEPS2.ITEMS);
752
858
  }
753
- setChangedItems(changed);
754
- if (changed.length === 0) {
859
+ }
860
+ function handleItemsSubmit(values) {
861
+ const items = values.map((v) => {
862
+ const [type, name] = v.split(":");
863
+ return { name, type, installed: true };
864
+ });
865
+ setQueue(items);
866
+ setCurrentIdx(0);
867
+ if (items.length === 0) {
755
868
  setStep(STEPS2.DONE);
756
869
  } else {
757
- setCurrentIdx(0);
870
+ setStep(STEPS2.RESOLVE);
871
+ }
872
+ }
873
+ useEffect2(() => {
874
+ if (step !== STEPS2.RESOLVE) return;
875
+ const item = queue[currentIdx];
876
+ if (!item) {
877
+ setStep(STEPS2.DONE);
878
+ return;
879
+ }
880
+ processItem(item);
881
+ }, [step, currentIdx]);
882
+ async function processItem(item) {
883
+ const targetConfig = targets[targetKey];
884
+ const collection = selectedCollection;
885
+ if (!item.installed) {
886
+ if (item.type === "skill") {
887
+ await installSkill(collection, item.name, targetConfig, destRoot);
888
+ } else {
889
+ await installAgent(collection, item.name, targetConfig, destRoot);
890
+ }
891
+ setInstalledCount((c) => c + 1);
892
+ advance();
893
+ return;
894
+ }
895
+ if (item.type === "skill") {
896
+ const localDir = path4.join(destRoot, targetConfig.skillsDir, item.name);
897
+ const latestDir = path4.join(getCollectionsDir(), collection, "skills", item.name);
898
+ if (!fs4.existsSync(localDir) || !fs4.existsSync(latestDir)) {
899
+ recordSkip(item.name, "source or local directory missing");
900
+ advance();
901
+ return;
902
+ }
903
+ if (!hasDirChanges(localDir, latestDir)) {
904
+ recordSkip(item.name, `already up-to-date with ${collection}`);
905
+ advance();
906
+ return;
907
+ }
908
+ setCurrentPatch(computeDirDiff(localDir, latestDir));
758
909
  setStep(STEPS2.CONFIRM);
910
+ return;
759
911
  }
760
- }, [step]);
761
- function handleTargetSelect(key) {
762
- setTargetKey(key);
763
- setStep(STEPS2.CHECKING);
912
+ const localFile = path4.join(destRoot, targetConfig.agentsDir, `${item.name}.md`);
913
+ const latestFile = path4.join(getCollectionsDir(), collection, "agents", `${item.name}.md`);
914
+ if (!fs4.existsSync(localFile) || !fs4.existsSync(latestFile)) {
915
+ recordSkip(item.name, "source or local file missing");
916
+ advance();
917
+ return;
918
+ }
919
+ const localContent = fs4.readFileSync(localFile, "utf-8");
920
+ const latestContent = fs4.readFileSync(latestFile, "utf-8");
921
+ if (!hasChanges(localContent, latestContent)) {
922
+ recordSkip(item.name, `already up-to-date with ${collection}`);
923
+ advance();
924
+ return;
925
+ }
926
+ setCurrentPatch(computeDiff(`${item.name}.md`, localContent, latestContent));
927
+ setStep(STEPS2.CONFIRM);
928
+ }
929
+ function recordSkip(name, reason) {
930
+ setSkippedCount((c) => c + 1);
931
+ setSkipReasons((prev) => [...prev, { name, reason }]);
764
932
  }
765
933
  function handleConfirm(yes) {
766
- const item = changedItems[currentIdx];
934
+ const item = queue[currentIdx];
767
935
  const targetConfig = targets[targetKey];
768
936
  if (yes) {
769
937
  if (item.type === "skill") {
770
- const src = path3.join(getCollectionsDir(), item.collection, "skills", item.name);
771
- const dest = path3.join(destRoot, targetConfig.skillsDir, item.name);
772
- fs3.copySync(src, dest, { overwrite: true });
938
+ const src = path4.join(getCollectionsDir(), selectedCollection, "skills", item.name);
939
+ const dest = path4.join(destRoot, targetConfig.skillsDir, item.name);
940
+ fs4.copySync(src, dest, { overwrite: true });
773
941
  } else {
774
- const src = path3.join(getCollectionsDir(), item.collection, "agents", `${item.name}.md`);
775
- const dest = path3.join(destRoot, targetConfig.agentsDir, `${item.name}.md`);
776
- fs3.copySync(src, dest, { overwrite: true });
942
+ const src = path4.join(getCollectionsDir(), selectedCollection, "agents", `${item.name}.md`);
943
+ const dest = path4.join(destRoot, targetConfig.agentsDir, `${item.name}.md`);
944
+ fs4.copySync(src, dest, { overwrite: true });
777
945
  }
778
946
  setUpdatedCount((c) => c + 1);
779
947
  } else {
780
- setSkippedCount((c) => c + 1);
948
+ recordSkip(item.name, "declined");
781
949
  }
782
- if (currentIdx + 1 < changedItems.length) {
950
+ advance();
951
+ }
952
+ function advance() {
953
+ setCurrentPatch(null);
954
+ if (currentIdx + 1 < queue.length) {
783
955
  setCurrentIdx((i) => i + 1);
956
+ setStep(STEPS2.RESOLVE);
784
957
  } else {
785
958
  setStep(STEPS2.DONE);
786
959
  }
787
960
  }
788
961
  if (step === STEPS2.TARGET) {
789
- return /* @__PURE__ */ jsx7(Select, { items: targetList, onSelect: handleTargetSelect, onBack: () => exit(), message: "Which target to update?" });
962
+ return /* @__PURE__ */ jsx7(
963
+ Select,
964
+ {
965
+ items: targetList,
966
+ onSelect: handleTargetSelect,
967
+ onBack: () => exit(),
968
+ message: "Which target to update?"
969
+ }
970
+ );
971
+ }
972
+ if (step === STEPS2.COLLECTION) {
973
+ let collectionNames;
974
+ if (directSkills && directSkills.length > 0) {
975
+ const union = /* @__PURE__ */ new Set();
976
+ for (const entry of directInfo) {
977
+ for (const c of entry.collections) union.add(c);
978
+ }
979
+ collectionNames = listCollections().filter((c) => union.has(c));
980
+ } else {
981
+ collectionNames = listCollections();
982
+ }
983
+ const items = collectionNames.map((c) => ({ label: c, value: c }));
984
+ return /* @__PURE__ */ jsx7(
985
+ Select,
986
+ {
987
+ items,
988
+ onSelect: handleCollectionSelect,
989
+ onBack: () => setStep(STEPS2.TARGET),
990
+ message: "Pick collection to update from:"
991
+ }
992
+ );
993
+ }
994
+ if (step === STEPS2.ITEMS) {
995
+ const targetConfig = targets[targetKey];
996
+ const installed = listInstalled(targetConfig, destRoot);
997
+ const { skills, agents } = scanCollection(selectedCollection);
998
+ const items = [
999
+ ...installed.skills.filter((name) => skills.includes(name)).map((name) => ({
1000
+ label: `${name} (skill)`,
1001
+ value: `skill:${name}`,
1002
+ checked: false
1003
+ })),
1004
+ ...installed.agents.filter((name) => agents.includes(name)).map((name) => ({
1005
+ label: `${name} (agent)`,
1006
+ value: `agent:${name}`,
1007
+ checked: false
1008
+ }))
1009
+ ];
1010
+ if (items.length === 0) {
1011
+ setTimeout(() => exit(), 100);
1012
+ return /* @__PURE__ */ jsxs7(Info, { children: [
1013
+ 'No installed skills/agents from "',
1014
+ selectedCollection,
1015
+ '" under ',
1016
+ targetConfig.skillsDir,
1017
+ "/."
1018
+ ] });
1019
+ }
1020
+ return /* @__PURE__ */ jsx7(
1021
+ MultiSelect,
1022
+ {
1023
+ items,
1024
+ onSubmit: handleItemsSubmit,
1025
+ onBack: () => setStep(STEPS2.COLLECTION),
1026
+ message: `Select skills/agents from "${selectedCollection}" to update:`
1027
+ }
1028
+ );
790
1029
  }
791
- if (step === STEPS2.CHECKING) {
792
- return /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: "Checking for updates..." });
1030
+ if (step === STEPS2.RESOLVE) {
1031
+ return /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: "Checking..." });
793
1032
  }
794
1033
  if (step === STEPS2.CONFIRM) {
795
- const item = changedItems[currentIdx];
1034
+ const item = queue[currentIdx];
796
1035
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
797
1036
  /* @__PURE__ */ jsxs7(Warning, { children: [
798
1037
  item.name,
799
- ": local differs from latest"
1038
+ ": local differs from ",
1039
+ selectedCollection
800
1040
  ] }),
801
- /* @__PURE__ */ jsx7(DiffView, { patch: item.patch }),
1041
+ /* @__PURE__ */ jsx7(DiffView, { patch: currentPatch }),
802
1042
  /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Confirm, { message: `Update ${item.name}?`, onConfirm: handleConfirm }) })
803
1043
  ] });
804
1044
  }
805
1045
  if (step === STEPS2.DONE) {
806
1046
  setTimeout(() => exit(), 100);
807
- if (changedItems.length === 0) {
808
- return /* @__PURE__ */ jsx7(Success, { children: "All installed skills are up to date." });
809
- }
810
- return /* @__PURE__ */ jsxs7(Success, { children: [
811
- updatedCount,
812
- " updated, ",
813
- skippedCount,
814
- " skipped"
1047
+ if (queue.length === 0 && missingNames.length === 0) {
1048
+ return /* @__PURE__ */ jsx7(Success, { children: "Nothing to update." });
1049
+ }
1050
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1051
+ missingNames.length > 0 && /* @__PURE__ */ jsxs7(ErrorMsg, { children: [
1052
+ "Not found: ",
1053
+ missingNames.join(", ")
1054
+ ] }),
1055
+ skipReasons.length > 0 && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
1056
+ /* @__PURE__ */ jsx7(Info, { children: "Skipped:" }),
1057
+ skipReasons.map((s, i) => /* @__PURE__ */ jsxs7(Text8, { color: "gray", children: [
1058
+ " \u2022 ",
1059
+ s.name,
1060
+ " \u2014 ",
1061
+ s.reason
1062
+ ] }, `${s.name}-${i}`))
1063
+ ] }),
1064
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Success, { children: [
1065
+ updatedCount,
1066
+ " updated, ",
1067
+ installedCount,
1068
+ " installed, ",
1069
+ skippedCount,
1070
+ " skipped"
1071
+ ] }) })
815
1072
  ] });
816
1073
  }
817
1074
  return null;
@@ -820,8 +1077,8 @@ function Update() {
820
1077
  // src/commands/Remove.jsx
821
1078
  import React9, { useState as useState5 } from "react";
822
1079
  import { Box as Box8, Text as Text9, useApp as useApp4 } from "ink";
823
- import path4 from "path";
824
- import fs4 from "fs-extra";
1080
+ import path5 from "path";
1081
+ import fs5 from "fs-extra";
825
1082
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
826
1083
  var STEPS3 = {
827
1084
  TARGET: "target",
@@ -841,9 +1098,9 @@ function Remove({ skillNames }) {
841
1098
  const targetConfig = targets[key];
842
1099
  const missing = [];
843
1100
  for (const name of skillNames) {
844
- const skillPath = path4.join(destRoot, targetConfig.skillsDir, name);
845
- const agentPath = path4.join(destRoot, targetConfig.agentsDir, `${name}.md`);
846
- if (!fs4.existsSync(skillPath) && !fs4.existsSync(agentPath)) {
1101
+ const skillPath = path5.join(destRoot, targetConfig.skillsDir, name);
1102
+ const agentPath = path5.join(destRoot, targetConfig.agentsDir, `${name}.md`);
1103
+ if (!fs5.existsSync(skillPath) && !fs5.existsSync(agentPath)) {
847
1104
  missing.push(name);
848
1105
  }
849
1106
  }
@@ -914,7 +1171,7 @@ function Help() {
914
1171
  const commands = [
915
1172
  { name: "add [skills...]", desc: "Install skills/agents (interactive if no args)" },
916
1173
  { name: "list", desc: "List all available skills and agents" },
917
- { name: "update", desc: "Update installed skills to latest version" },
1174
+ { name: "update [skills...]", desc: "Update installed skills (interactive if no args)" },
918
1175
  { name: "remove <skills>", desc: "Remove installed skills/agents" },
919
1176
  { name: "help", desc: "Show this help message" }
920
1177
  ];
@@ -923,6 +1180,7 @@ function Help() {
923
1180
  "npx @hiver/skills add discuss-problem create-prd",
924
1181
  "npx @hiver/skills list",
925
1182
  "npx @hiver/skills update",
1183
+ "npx @hiver/skills update build-component",
926
1184
  "npx @hiver/skills remove discuss-problem"
927
1185
  ];
928
1186
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
@@ -959,7 +1217,7 @@ function App() {
959
1217
  case "list":
960
1218
  return /* @__PURE__ */ jsx9(List, {});
961
1219
  case "update":
962
- return /* @__PURE__ */ jsx9(Update, {});
1220
+ return /* @__PURE__ */ jsx9(Update, { directSkills: restArgs.length > 0 ? restArgs : null });
963
1221
  case "remove":
964
1222
  if (restArgs.length === 0) {
965
1223
  return /* @__PURE__ */ jsxs9(Text10, { color: "red", children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiver/skills",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Shared Claude Code skills & agents for Hiver teams",
5
5
  "type": "module",
6
6
  "bin": {