@anytio/pspm 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,13 +63,16 @@ var init_generated = __esm({
63
63
  "use strict";
64
64
  init_fetcher();
65
65
  getMeUrl = () => {
66
- return "/api/skills/me";
66
+ return `/api/skills/me`;
67
67
  };
68
68
  me = async (options) => {
69
- return customFetch(getMeUrl(), {
70
- ...options,
71
- method: "GET"
72
- });
69
+ return customFetch(
70
+ getMeUrl(),
71
+ {
72
+ ...options,
73
+ method: "GET"
74
+ }
75
+ );
73
76
  };
74
77
  getListSkillVersionsUrl = (username, name) => {
75
78
  return `/api/skills/@user/${username}/${name}/versions`;
@@ -96,24 +99,32 @@ var init_generated = __esm({
96
99
  );
97
100
  };
98
101
  getPublishSkillUrl = () => {
99
- return "/api/skills/publish";
102
+ return `/api/skills/publish`;
100
103
  };
101
104
  publishSkill = async (publishSkillInput, options) => {
102
- return customFetch(getPublishSkillUrl(), {
103
- ...options,
104
- method: "POST",
105
- headers: { "Content-Type": "application/json", ...options?.headers },
106
- body: JSON.stringify(publishSkillInput)
107
- });
105
+ return customFetch(
106
+ getPublishSkillUrl(),
107
+ {
108
+ ...options,
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/json", ...options?.headers },
111
+ body: JSON.stringify(
112
+ publishSkillInput
113
+ )
114
+ }
115
+ );
108
116
  };
109
117
  getDeleteSkillUrl = (name) => {
110
118
  return `/api/skills/${name}`;
111
119
  };
112
120
  deleteSkill = async (name, options) => {
113
- return customFetch(getDeleteSkillUrl(name), {
114
- ...options,
115
- method: "DELETE"
116
- });
121
+ return customFetch(
122
+ getDeleteSkillUrl(name),
123
+ {
124
+ ...options,
125
+ method: "DELETE"
126
+ }
127
+ );
117
128
  };
118
129
  getDeleteSkillVersionUrl = (name, version2) => {
119
130
  return `/api/skills/${name}/${version2}`;
@@ -609,6 +620,91 @@ var init_integrity = __esm({
609
620
  }
610
621
  });
611
622
 
623
+ // src/lib/local.ts
624
+ import { access, stat as stat2 } from "fs/promises";
625
+ import { isAbsolute, join as join2, resolve } from "path";
626
+ function isLocalSpecifier(specifier) {
627
+ return specifier.startsWith("file:");
628
+ }
629
+ function isBareLocalPath(specifier) {
630
+ return specifier.startsWith("./") || specifier.startsWith("../");
631
+ }
632
+ function parseLocalSpecifier(specifier) {
633
+ const match = specifier.match(LOCAL_SPECIFIER_PATTERN);
634
+ if (!match) {
635
+ return null;
636
+ }
637
+ const path = match[1];
638
+ if (!path || path.trim() === "") {
639
+ return null;
640
+ }
641
+ return {
642
+ path,
643
+ isAbsolute: isAbsolute(path)
644
+ };
645
+ }
646
+ function getLocalSkillName(spec) {
647
+ const normalizedPath = spec.path.replace(/\/+$/, "");
648
+ const segments = normalizedPath.split("/").filter(Boolean);
649
+ return segments[segments.length - 1] || spec.path;
650
+ }
651
+ function normalizeToFileSpecifier(path) {
652
+ if (isLocalSpecifier(path)) {
653
+ return path;
654
+ }
655
+ return `file:${path}`;
656
+ }
657
+ function resolveLocalPath(spec, basePath = process.cwd()) {
658
+ if (spec.isAbsolute) {
659
+ return resolve(spec.path);
660
+ }
661
+ return resolve(basePath, spec.path);
662
+ }
663
+ async function validateLocalSkill(absolutePath) {
664
+ try {
665
+ const stats = await stat2(absolutePath);
666
+ if (!stats.isDirectory()) {
667
+ return { valid: false, error: `Not a directory: ${absolutePath}` };
668
+ }
669
+ } catch {
670
+ return { valid: false, error: `Directory not found: ${absolutePath}` };
671
+ }
672
+ const manifestPath = join2(absolutePath, "pspm.json");
673
+ try {
674
+ await access(manifestPath);
675
+ } catch {
676
+ return {
677
+ valid: false,
678
+ error: `No pspm.json found in ${absolutePath}`
679
+ };
680
+ }
681
+ try {
682
+ const { readFile: readFile7 } = await import("fs/promises");
683
+ const content = await readFile7(manifestPath, "utf-8");
684
+ const manifest = JSON.parse(content);
685
+ if (!manifest.name) {
686
+ return {
687
+ valid: false,
688
+ error: `Manifest in ${absolutePath} is missing 'name' field`
689
+ };
690
+ }
691
+ return { valid: true, manifest };
692
+ } catch (error) {
693
+ const message = error instanceof Error ? error.message : String(error);
694
+ return {
695
+ valid: false,
696
+ error: `Failed to read manifest in ${absolutePath}: ${message}`
697
+ };
698
+ }
699
+ }
700
+ var LOCAL_SPECIFIER_PATTERN;
701
+ var init_local = __esm({
702
+ "src/lib/local.ts"() {
703
+ "use strict";
704
+ LOCAL_SPECIFIER_PATTERN = /^file:(.+)$/;
705
+ }
706
+ });
707
+
612
708
  // src/lib/manifest.ts
613
709
  function validateManifest(manifest) {
614
710
  if (!manifest.name) {
@@ -645,6 +741,349 @@ var init_manifest = __esm({
645
741
  }
646
742
  });
647
743
 
744
+ // src/lib/version.ts
745
+ import * as semver from "semver";
746
+ function resolveVersion(range, availableVersions) {
747
+ const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
748
+ if (!range || range === "latest" || range === "*") {
749
+ return sorted[0] ?? null;
750
+ }
751
+ return semver.maxSatisfying(sorted, range);
752
+ }
753
+ function findHighestSatisfying(ranges, availableVersions) {
754
+ const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
755
+ if (sorted.length === 0) return null;
756
+ const normalizedRanges = ranges.map(
757
+ (r) => !r || r === "latest" || r === "*" ? "*" : r
758
+ );
759
+ for (const version2 of sorted) {
760
+ const satisfiesAll = normalizedRanges.every(
761
+ (range) => semver.satisfies(version2, range)
762
+ );
763
+ if (satisfiesAll) {
764
+ return version2;
765
+ }
766
+ }
767
+ return null;
768
+ }
769
+ var init_version = __esm({
770
+ "src/lib/version.ts"() {
771
+ "use strict";
772
+ }
773
+ });
774
+
775
+ // src/lib/resolver.ts
776
+ async function resolveRecursive(rootDeps, config2) {
777
+ const graph = {
778
+ nodes: /* @__PURE__ */ new Map(),
779
+ roots: Object.keys(rootDeps),
780
+ errors: [],
781
+ conflicts: []
782
+ };
783
+ configure2({
784
+ registryUrl: config2.registryUrl,
785
+ apiKey: config2.apiKey ?? ""
786
+ });
787
+ const rangesByPackage = /* @__PURE__ */ new Map();
788
+ const queue = [];
789
+ for (const [name, range] of Object.entries(rootDeps)) {
790
+ queue.push({
791
+ name,
792
+ versionRange: range,
793
+ depth: 0,
794
+ dependent: "root",
795
+ path: []
796
+ });
797
+ }
798
+ const processing = /* @__PURE__ */ new Set();
799
+ while (queue.length > 0) {
800
+ const item = queue.shift();
801
+ if (!item) continue;
802
+ const { name, versionRange, depth, dependent, path } = item;
803
+ if (depth > config2.maxDepth) {
804
+ graph.errors.push({
805
+ type: "max_depth_exceeded",
806
+ package: name,
807
+ message: `Maximum dependency depth (${config2.maxDepth}) exceeded at: ${[...path, name].join(" -> ")}`,
808
+ path: [...path, name]
809
+ });
810
+ continue;
811
+ }
812
+ if (path.includes(name)) {
813
+ graph.errors.push({
814
+ type: "circular_dependency",
815
+ package: name,
816
+ message: `Circular dependency detected: ${[...path, name].join(" -> ")}`,
817
+ path: [...path, name]
818
+ });
819
+ continue;
820
+ }
821
+ if (!rangesByPackage.has(name)) {
822
+ rangesByPackage.set(name, []);
823
+ }
824
+ rangesByPackage.get(name)?.push({
825
+ range: versionRange,
826
+ dependent,
827
+ depth
828
+ });
829
+ if (processing.has(name)) {
830
+ continue;
831
+ }
832
+ processing.add(name);
833
+ const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
834
+ if (!match) {
835
+ graph.errors.push({
836
+ type: "package_not_found",
837
+ package: name,
838
+ message: `Invalid package name format: ${name}`
839
+ });
840
+ continue;
841
+ }
842
+ const [, username, skillName] = match;
843
+ try {
844
+ const versionsResponse = await listSkillVersions(username, skillName);
845
+ if (versionsResponse.status !== 200) {
846
+ graph.errors.push({
847
+ type: "package_not_found",
848
+ package: name,
849
+ message: `Package ${name} not found in registry`
850
+ });
851
+ continue;
852
+ }
853
+ const versions = versionsResponse.data;
854
+ if (versions.length === 0) {
855
+ graph.errors.push({
856
+ type: "package_not_found",
857
+ package: name,
858
+ message: `Package ${name} has no versions`
859
+ });
860
+ continue;
861
+ }
862
+ const availableVersions = versions.map((v) => v.version);
863
+ const resolvedVersion = findHighestSatisfying(
864
+ [versionRange],
865
+ availableVersions
866
+ );
867
+ if (!resolvedVersion) {
868
+ graph.errors.push({
869
+ type: "no_satisfying_version",
870
+ package: name,
871
+ message: `No version of ${name} satisfies: ${versionRange}`
872
+ });
873
+ continue;
874
+ }
875
+ const versionResponse = await getSkillVersion(
876
+ username,
877
+ skillName,
878
+ resolvedVersion
879
+ );
880
+ if (versionResponse.status !== 200 || !versionResponse.data) {
881
+ graph.errors.push({
882
+ type: "fetch_error",
883
+ package: name,
884
+ message: `Failed to fetch ${name}@${resolvedVersion}`
885
+ });
886
+ continue;
887
+ }
888
+ const versionInfo = versionResponse.data;
889
+ const manifest = versionInfo.manifest;
890
+ const dependencies = manifest?.dependencies ?? {};
891
+ const node = {
892
+ name,
893
+ version: resolvedVersion,
894
+ versionRange,
895
+ downloadUrl: versionInfo.downloadUrl,
896
+ integrity: `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`,
897
+ depth,
898
+ dependencies,
899
+ dependents: [dependent],
900
+ isDirect: depth === 0,
901
+ deprecated: versionInfo.deprecationMessage ?? void 0
902
+ };
903
+ graph.nodes.set(name, node);
904
+ for (const [depName, depRange] of Object.entries(dependencies)) {
905
+ queue.push({
906
+ name: depName,
907
+ versionRange: depRange,
908
+ depth: depth + 1,
909
+ dependent: name,
910
+ path: [...path, name]
911
+ });
912
+ }
913
+ } catch (error) {
914
+ const message = error instanceof Error ? error.message : "Unknown error";
915
+ graph.errors.push({
916
+ type: "fetch_error",
917
+ package: name,
918
+ message: `Error fetching ${name}: ${message}`
919
+ });
920
+ }
921
+ }
922
+ for (const [name, ranges] of rangesByPackage.entries()) {
923
+ const node = graph.nodes.get(name);
924
+ if (!node) continue;
925
+ const uniqueDependents = [...new Set(ranges.map((r) => r.dependent))];
926
+ node.dependents = uniqueDependents;
927
+ const allRanges = ranges.map((r) => r.range);
928
+ const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
929
+ if (!match) continue;
930
+ const [, username, skillName] = match;
931
+ try {
932
+ const versionsResponse = await listSkillVersions(username, skillName);
933
+ if (versionsResponse.status !== 200) continue;
934
+ const versions = versionsResponse.data;
935
+ const availableVersions = versions.map((v) => v.version);
936
+ const finalVersion = findHighestSatisfying(allRanges, availableVersions);
937
+ if (!finalVersion) {
938
+ graph.conflicts.push({
939
+ package: name,
940
+ ranges: ranges.map((r) => ({
941
+ dependent: r.dependent,
942
+ range: r.range
943
+ })),
944
+ availableVersions
945
+ });
946
+ graph.errors.push({
947
+ type: "no_satisfying_version",
948
+ package: name,
949
+ message: `No version of ${name} satisfies all requirements: ${allRanges.join(", ")}`
950
+ });
951
+ continue;
952
+ }
953
+ if (finalVersion !== node.version) {
954
+ const versionResponse = await getSkillVersion(
955
+ username,
956
+ skillName,
957
+ finalVersion
958
+ );
959
+ if (versionResponse.status === 200 && versionResponse.data) {
960
+ const versionInfo = versionResponse.data;
961
+ node.version = finalVersion;
962
+ node.downloadUrl = versionInfo.downloadUrl;
963
+ node.integrity = `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`;
964
+ node.deprecated = versionInfo.deprecationMessage ?? void 0;
965
+ const manifest = versionInfo.manifest;
966
+ node.dependencies = manifest?.dependencies ?? {};
967
+ }
968
+ }
969
+ } catch {
970
+ }
971
+ }
972
+ const installOrder = topologicalSort(graph);
973
+ const success = graph.errors.length === 0 && graph.conflicts.length === 0;
974
+ return {
975
+ success,
976
+ graph,
977
+ installOrder
978
+ };
979
+ }
980
+ function topologicalSort(graph) {
981
+ const inDegree = /* @__PURE__ */ new Map();
982
+ const dependents = /* @__PURE__ */ new Map();
983
+ for (const name of graph.nodes.keys()) {
984
+ inDegree.set(name, 0);
985
+ dependents.set(name, []);
986
+ }
987
+ for (const [name, node] of graph.nodes.entries()) {
988
+ for (const depName of Object.keys(node.dependencies)) {
989
+ if (graph.nodes.has(depName)) {
990
+ inDegree.set(name, (inDegree.get(name) ?? 0) + 1);
991
+ if (!dependents.has(depName)) {
992
+ dependents.set(depName, []);
993
+ }
994
+ dependents.get(depName)?.push(name);
995
+ }
996
+ }
997
+ }
998
+ const queue = [];
999
+ for (const [name, degree] of inDegree.entries()) {
1000
+ if (degree === 0) {
1001
+ queue.push(name);
1002
+ }
1003
+ }
1004
+ const sorted = [];
1005
+ while (queue.length > 0) {
1006
+ const current = queue.shift();
1007
+ if (!current) continue;
1008
+ sorted.push(current);
1009
+ const deps = dependents.get(current) ?? [];
1010
+ for (const dependent of deps) {
1011
+ const newDegree = (inDegree.get(dependent) ?? 1) - 1;
1012
+ inDegree.set(dependent, newDegree);
1013
+ if (newDegree === 0 && !sorted.includes(dependent)) {
1014
+ queue.push(dependent);
1015
+ }
1016
+ }
1017
+ }
1018
+ return sorted;
1019
+ }
1020
+ function computeInstallOrder(packages) {
1021
+ const visited = /* @__PURE__ */ new Set();
1022
+ const order = [];
1023
+ function visit(name) {
1024
+ if (visited.has(name)) return;
1025
+ visited.add(name);
1026
+ const entry = packages[name];
1027
+ if (entry?.dependencies) {
1028
+ for (const dep of Object.keys(entry.dependencies)) {
1029
+ visit(dep);
1030
+ }
1031
+ }
1032
+ order.push(name);
1033
+ }
1034
+ for (const name of Object.keys(packages)) {
1035
+ visit(name);
1036
+ }
1037
+ return order;
1038
+ }
1039
+ function formatResolutionErrors(errors) {
1040
+ return errors.map((error) => {
1041
+ switch (error.type) {
1042
+ case "circular_dependency":
1043
+ return `Circular dependency: ${error.path?.join(" -> ") ?? error.package}`;
1044
+ case "max_depth_exceeded":
1045
+ return `Max depth exceeded at: ${error.path?.join(" -> ") ?? error.package}`;
1046
+ case "no_satisfying_version":
1047
+ return error.message;
1048
+ case "package_not_found":
1049
+ return `Package not found: ${error.package}`;
1050
+ case "fetch_error":
1051
+ return error.message;
1052
+ default:
1053
+ return error.message;
1054
+ }
1055
+ });
1056
+ }
1057
+ function formatVersionConflicts(conflicts) {
1058
+ return conflicts.map((conflict) => {
1059
+ const requirements = conflict.ranges.map((r) => `${r.dependent} needs ${r.range}`).join(", ");
1060
+ return `No version of ${conflict.package} satisfies: ${requirements}`;
1061
+ });
1062
+ }
1063
+ function printResolutionErrors(errors, conflicts = []) {
1064
+ if (errors.length > 0) {
1065
+ console.error("\nResolution errors:");
1066
+ for (const msg of formatResolutionErrors(errors)) {
1067
+ console.error(` - ${msg}`);
1068
+ }
1069
+ }
1070
+ if (conflicts.length > 0) {
1071
+ console.error("\nVersion conflicts:");
1072
+ for (const msg of formatVersionConflicts(conflicts)) {
1073
+ console.error(` - ${msg}`);
1074
+ }
1075
+ }
1076
+ }
1077
+ var MAX_DEPENDENCY_DEPTH;
1078
+ var init_resolver = __esm({
1079
+ "src/lib/resolver.ts"() {
1080
+ "use strict";
1081
+ init_api_client();
1082
+ init_version();
1083
+ MAX_DEPENDENCY_DEPTH = 5;
1084
+ }
1085
+ });
1086
+
648
1087
  // src/lib/specifier.ts
649
1088
  function parseSkillSpecifier(specifier) {
650
1089
  const match = specifier.match(SPECIFIER_PATTERN);
@@ -700,27 +1139,14 @@ var init_specifier = __esm({
700
1139
  }
701
1140
  });
702
1141
 
703
- // src/lib/version.ts
704
- import * as semver from "semver";
705
- function resolveVersion(range, availableVersions) {
706
- const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
707
- if (!range || range === "latest" || range === "*") {
708
- return sorted[0] ?? null;
709
- }
710
- return semver.maxSatisfying(sorted, range);
711
- }
712
- var init_version = __esm({
713
- "src/lib/version.ts"() {
714
- "use strict";
715
- }
716
- });
717
-
718
1142
  // src/lib/index.ts
719
1143
  var init_lib = __esm({
720
1144
  "src/lib/index.ts"() {
721
1145
  "use strict";
722
1146
  init_integrity();
1147
+ init_local();
723
1148
  init_manifest();
1149
+ init_resolver();
724
1150
  init_specifier();
725
1151
  init_version();
726
1152
  }
@@ -818,7 +1244,7 @@ var init_agents = __esm({
818
1244
 
819
1245
  // src/github.ts
820
1246
  import { cp, lstat, mkdir as mkdir2, readdir, rm, writeFile as writeFile2 } from "fs/promises";
821
- import { join as join2 } from "path";
1247
+ import { join as join3 } from "path";
822
1248
  function getGitHubHeaders() {
823
1249
  const headers = {
824
1250
  Accept: "application/vnd.github+json",
@@ -894,10 +1320,10 @@ async function downloadGitHubPackage(spec) {
894
1320
  return { buffer, commit, integrity };
895
1321
  }
896
1322
  async function extractGitHubPackage(spec, buffer, skillsDir) {
897
- const destPath = spec.path ? join2(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join2(skillsDir, "_github", spec.owner, spec.repo);
898
- const tempDir = join2(skillsDir, "_github", ".temp", `${Date.now()}`);
1323
+ const destPath = spec.path ? join3(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join3(skillsDir, "_github", spec.owner, spec.repo);
1324
+ const tempDir = join3(skillsDir, "_github", ".temp", `${Date.now()}`);
899
1325
  await mkdir2(tempDir, { recursive: true });
900
- const tempFile = join2(tempDir, "archive.tgz");
1326
+ const tempFile = join3(tempDir, "archive.tgz");
901
1327
  try {
902
1328
  await writeFile2(tempFile, buffer);
903
1329
  const { exec: exec2 } = await import("child_process");
@@ -911,16 +1337,16 @@ async function extractGitHubPackage(spec, buffer, skillsDir) {
911
1337
  if (!extractedDir) {
912
1338
  throw new Error("Failed to find extracted directory in tarball");
913
1339
  }
914
- const sourcePath = join2(tempDir, extractedDir);
915
- const copySource = spec.path ? join2(sourcePath, spec.path) : sourcePath;
1340
+ const sourcePath = join3(tempDir, extractedDir);
1341
+ const copySource = spec.path ? join3(sourcePath, spec.path) : sourcePath;
916
1342
  if (spec.path) {
917
1343
  const pathExists = await lstat(copySource).catch(() => null);
918
1344
  if (!pathExists) {
919
1345
  const rootEntries = await readdir(sourcePath);
920
1346
  const dirs = [];
921
1347
  for (const entry of rootEntries) {
922
- const stat7 = await lstat(join2(sourcePath, entry)).catch(() => null);
923
- if (stat7?.isDirectory() && !entry.startsWith(".")) {
1348
+ const stat8 = await lstat(join3(sourcePath, entry)).catch(() => null);
1349
+ if (stat8?.isDirectory() && !entry.startsWith(".")) {
924
1350
  dirs.push(entry);
925
1351
  }
926
1352
  }
@@ -985,11 +1411,11 @@ Available paths in repository root:
985
1411
  });
986
1412
 
987
1413
  // src/lockfile.ts
988
- import { mkdir as mkdir3, readFile as readFile2, stat as stat2, writeFile as writeFile3 } from "fs/promises";
1414
+ import { mkdir as mkdir3, readFile as readFile2, stat as stat3, writeFile as writeFile3 } from "fs/promises";
989
1415
  import { dirname as dirname2 } from "path";
990
1416
  async function hasLegacyLockfile() {
991
1417
  try {
992
- await stat2(getLegacyLockfilePath());
1418
+ await stat3(getLegacyLockfilePath());
993
1419
  return true;
994
1420
  } catch {
995
1421
  return false;
@@ -999,12 +1425,12 @@ async function migrateLockfileIfNeeded() {
999
1425
  const legacyPath = getLegacyLockfilePath();
1000
1426
  const newPath = getLockfilePath();
1001
1427
  try {
1002
- await stat2(legacyPath);
1428
+ await stat3(legacyPath);
1003
1429
  } catch {
1004
1430
  return false;
1005
1431
  }
1006
1432
  try {
1007
- await stat2(newPath);
1433
+ await stat3(newPath);
1008
1434
  return false;
1009
1435
  } catch {
1010
1436
  }
@@ -1057,21 +1483,35 @@ async function readLockfile() {
1057
1483
  async function writeLockfile(lockfile) {
1058
1484
  const lockfilePath = getLockfilePath();
1059
1485
  await mkdir3(dirname2(lockfilePath), { recursive: true });
1486
+ const packages = lockfile.packages ?? lockfile.skills ?? {};
1487
+ const hasDependencies = Object.values(packages).some(
1488
+ (pkg) => pkg.dependencies && Object.keys(pkg.dependencies).length > 0
1489
+ );
1490
+ const hasLocalPackages = lockfile.localPackages && Object.keys(lockfile.localPackages).length > 0;
1491
+ let version2 = 3;
1492
+ if (hasLocalPackages) {
1493
+ version2 = 5;
1494
+ } else if (hasDependencies) {
1495
+ version2 = 4;
1496
+ }
1060
1497
  const normalized = {
1061
- lockfileVersion: 3,
1498
+ lockfileVersion: version2,
1062
1499
  registryUrl: lockfile.registryUrl,
1063
- packages: lockfile.packages ?? lockfile.skills ?? {}
1500
+ packages
1064
1501
  };
1065
1502
  if (lockfile.githubPackages && Object.keys(lockfile.githubPackages).length > 0) {
1066
1503
  normalized.githubPackages = lockfile.githubPackages;
1067
1504
  }
1505
+ if (hasLocalPackages) {
1506
+ normalized.localPackages = lockfile.localPackages;
1507
+ }
1068
1508
  await writeFile3(lockfilePath, `${JSON.stringify(normalized, null, 2)}
1069
1509
  `);
1070
1510
  }
1071
1511
  async function createEmptyLockfile() {
1072
1512
  const registryUrl = await getRegistryUrl();
1073
1513
  return {
1074
- lockfileVersion: 3,
1514
+ lockfileVersion: 4,
1075
1515
  registryUrl,
1076
1516
  packages: {}
1077
1517
  };
@@ -1089,6 +1529,20 @@ async function addToLockfile(fullName, entry) {
1089
1529
  lockfile.packages = packages;
1090
1530
  await writeLockfile(lockfile);
1091
1531
  }
1532
+ async function addToLockfileWithDeps(fullName, entry, dependencies) {
1533
+ let lockfile = await readLockfile();
1534
+ if (!lockfile) {
1535
+ lockfile = await createEmptyLockfile();
1536
+ }
1537
+ const packages = getPackages(lockfile);
1538
+ const entryWithDeps = { ...entry };
1539
+ if (dependencies && Object.keys(dependencies).length > 0) {
1540
+ entryWithDeps.dependencies = dependencies;
1541
+ }
1542
+ packages[fullName] = entryWithDeps;
1543
+ lockfile.packages = packages;
1544
+ await writeLockfile(lockfile);
1545
+ }
1092
1546
  async function removeFromLockfile(fullName) {
1093
1547
  const lockfile = await readLockfile();
1094
1548
  if (!lockfile) {
@@ -1144,6 +1598,36 @@ async function listLockfileGitHubPackages() {
1144
1598
  entry
1145
1599
  }));
1146
1600
  }
1601
+ async function addLocalToLockfile(specifier, entry) {
1602
+ let lockfile = await readLockfile();
1603
+ if (!lockfile) {
1604
+ lockfile = await createEmptyLockfile();
1605
+ }
1606
+ if (!lockfile.localPackages) {
1607
+ lockfile.localPackages = {};
1608
+ }
1609
+ lockfile.localPackages[specifier] = entry;
1610
+ await writeLockfile(lockfile);
1611
+ }
1612
+ async function removeLocalFromLockfile(specifier) {
1613
+ const lockfile = await readLockfile();
1614
+ if (!lockfile?.localPackages?.[specifier]) {
1615
+ return false;
1616
+ }
1617
+ delete lockfile.localPackages[specifier];
1618
+ await writeLockfile(lockfile);
1619
+ return true;
1620
+ }
1621
+ async function listLockfileLocalPackages() {
1622
+ const lockfile = await readLockfile();
1623
+ if (!lockfile?.localPackages) {
1624
+ return [];
1625
+ }
1626
+ return Object.entries(lockfile.localPackages).map(([specifier, entry]) => ({
1627
+ specifier,
1628
+ entry
1629
+ }));
1630
+ }
1147
1631
  var init_lockfile = __esm({
1148
1632
  "src/lockfile.ts"() {
1149
1633
  "use strict";
@@ -1153,9 +1637,9 @@ var init_lockfile = __esm({
1153
1637
 
1154
1638
  // src/manifest.ts
1155
1639
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1156
- import { join as join3 } from "path";
1640
+ import { join as join4 } from "path";
1157
1641
  function getManifestPath() {
1158
- return join3(process.cwd(), "pspm.json");
1642
+ return join4(process.cwd(), "pspm.json");
1159
1643
  }
1160
1644
  async function readManifest() {
1161
1645
  try {
@@ -1225,6 +1709,27 @@ async function removeGitHubDependency(specifier) {
1225
1709
  await writeManifest(manifest);
1226
1710
  return true;
1227
1711
  }
1712
+ async function getLocalDependencies() {
1713
+ const manifest = await readManifest();
1714
+ return manifest?.localDependencies ?? {};
1715
+ }
1716
+ async function addLocalDependency(specifier, version2 = "*") {
1717
+ const manifest = await ensureManifest();
1718
+ if (!manifest.localDependencies) {
1719
+ manifest.localDependencies = {};
1720
+ }
1721
+ manifest.localDependencies[specifier] = version2;
1722
+ await writeManifest(manifest);
1723
+ }
1724
+ async function removeLocalDependency(specifier) {
1725
+ const manifest = await readManifest();
1726
+ if (!manifest?.localDependencies?.[specifier]) {
1727
+ return false;
1728
+ }
1729
+ delete manifest.localDependencies[specifier];
1730
+ await writeManifest(manifest);
1731
+ return true;
1732
+ }
1228
1733
  var init_manifest2 = __esm({
1229
1734
  "src/manifest.ts"() {
1230
1735
  "use strict";
@@ -1233,7 +1738,7 @@ var init_manifest2 = __esm({
1233
1738
 
1234
1739
  // src/symlinks.ts
1235
1740
  import { lstat as lstat2, mkdir as mkdir4, readlink, rm as rm2, symlink } from "fs/promises";
1236
- import { dirname as dirname3, join as join4, relative } from "path";
1741
+ import { dirname as dirname3, join as join5, relative } from "path";
1237
1742
  async function createAgentSymlinks(skills, options) {
1238
1743
  const { agents, projectRoot, agentConfigs } = options;
1239
1744
  if (agents.length === 1 && agents[0] === "none") {
@@ -1245,11 +1750,11 @@ async function createAgentSymlinks(skills, options) {
1245
1750
  console.warn(`Warning: Unknown agent "${agentName}", skipping symlinks`);
1246
1751
  continue;
1247
1752
  }
1248
- const agentSkillsDir = join4(projectRoot, config2.skillsDir);
1753
+ const agentSkillsDir = join5(projectRoot, config2.skillsDir);
1249
1754
  await mkdir4(agentSkillsDir, { recursive: true });
1250
1755
  for (const skill of skills) {
1251
- const symlinkPath = join4(agentSkillsDir, skill.name);
1252
- const targetPath = join4(projectRoot, skill.sourcePath);
1756
+ const symlinkPath = join5(agentSkillsDir, skill.name);
1757
+ const targetPath = join5(projectRoot, skill.sourcePath);
1253
1758
  const relativeTarget = relative(dirname3(symlinkPath), targetPath);
1254
1759
  await createSymlink(symlinkPath, relativeTarget, skill.name);
1255
1760
  }
@@ -1290,7 +1795,7 @@ async function removeAgentSymlinks(skillName, options) {
1290
1795
  if (!config2) {
1291
1796
  continue;
1292
1797
  }
1293
- const symlinkPath = join4(projectRoot, config2.skillsDir, skillName);
1798
+ const symlinkPath = join5(projectRoot, config2.skillsDir, skillName);
1294
1799
  try {
1295
1800
  const stats = await lstat2(symlinkPath).catch(() => null);
1296
1801
  if (stats?.isSymbolicLink()) {
@@ -1309,12 +1814,15 @@ function getGitHubSkillPath(owner, repo, path) {
1309
1814
  }
1310
1815
  return `.pspm/skills/_github/${owner}/${repo}`;
1311
1816
  }
1817
+ function getLocalSkillPath(skillName) {
1818
+ return `.pspm/skills/_local/${skillName}`;
1819
+ }
1312
1820
  async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
1313
1821
  const linkedAgents = [];
1314
1822
  for (const agentName of agents) {
1315
1823
  const config2 = resolveAgentConfig(agentName, agentConfigs);
1316
1824
  if (!config2) continue;
1317
- const symlinkPath = join4(projectRoot, config2.skillsDir, skillName);
1825
+ const symlinkPath = join5(projectRoot, config2.skillsDir, skillName);
1318
1826
  try {
1319
1827
  const stats = await lstat2(symlinkPath);
1320
1828
  if (stats.isSymbolicLink()) {
@@ -1337,15 +1845,21 @@ var add_exports = {};
1337
1845
  __export(add_exports, {
1338
1846
  add: () => add
1339
1847
  });
1340
- import { mkdir as mkdir5, rm as rm3 } from "fs/promises";
1341
- import { join as join5 } from "path";
1848
+ import { mkdir as mkdir5, rm as rm3, symlink as symlink2 } from "fs/promises";
1849
+ import { dirname as dirname4, join as join6 } from "path";
1342
1850
  async function add(specifiers, options) {
1343
1851
  console.log("Resolving packages...\n");
1344
1852
  const resolvedPackages = [];
1345
1853
  const validationErrors = [];
1346
- for (const specifier of specifiers) {
1854
+ for (let specifier of specifiers) {
1347
1855
  try {
1348
- if (isGitHubSpecifier(specifier)) {
1856
+ if (isBareLocalPath(specifier)) {
1857
+ specifier = normalizeToFileSpecifier(specifier);
1858
+ }
1859
+ if (isLocalSpecifier(specifier)) {
1860
+ const resolved = await validateLocalPackage(specifier);
1861
+ resolvedPackages.push(resolved);
1862
+ } else if (isGitHubSpecifier(specifier)) {
1349
1863
  const resolved = await validateGitHubPackage(specifier);
1350
1864
  resolvedPackages.push(resolved);
1351
1865
  } else {
@@ -1369,6 +1883,49 @@ async function add(specifiers, options) {
1369
1883
  `
1370
1884
  );
1371
1885
  }
1886
+ const config2 = await resolveConfig();
1887
+ const apiKey = getTokenForRegistry(config2, config2.registryUrl);
1888
+ const registryPackages = resolvedPackages.filter(
1889
+ (p) => p.type === "registry"
1890
+ );
1891
+ const githubPackages = resolvedPackages.filter(
1892
+ (p) => p.type === "github"
1893
+ );
1894
+ const localPackages = resolvedPackages.filter(
1895
+ (p) => p.type === "local"
1896
+ );
1897
+ let resolutionResult = null;
1898
+ if (registryPackages.length > 0) {
1899
+ const rootDeps = {};
1900
+ for (const pkg of registryPackages) {
1901
+ const fullName = `@user/${pkg.username}/${pkg.name}`;
1902
+ rootDeps[fullName] = pkg.versionRange || `^${pkg.resolvedVersion}`;
1903
+ }
1904
+ console.log("Resolving dependencies...");
1905
+ resolutionResult = await resolveRecursive(rootDeps, {
1906
+ maxDepth: MAX_DEPENDENCY_DEPTH,
1907
+ registryUrl: config2.registryUrl,
1908
+ apiKey
1909
+ });
1910
+ if (!resolutionResult.success) {
1911
+ printResolutionErrors(
1912
+ resolutionResult.graph.errors,
1913
+ resolutionResult.graph.conflicts
1914
+ );
1915
+ process.exit(1);
1916
+ }
1917
+ const transitiveDeps = resolutionResult.installOrder.filter(
1918
+ (name) => !rootDeps[name]
1919
+ );
1920
+ if (transitiveDeps.length > 0) {
1921
+ console.log(
1922
+ `Resolved ${transitiveDeps.length} transitive dependencies.
1923
+ `
1924
+ );
1925
+ } else {
1926
+ console.log();
1927
+ }
1928
+ }
1372
1929
  let agents;
1373
1930
  const manifest = await readManifest();
1374
1931
  if (options.agent) {
@@ -1383,19 +1940,53 @@ async function add(specifiers, options) {
1383
1940
  console.log();
1384
1941
  }
1385
1942
  const results = [];
1386
- for (const resolved of resolvedPackages) {
1387
- try {
1388
- if (resolved.type === "github") {
1389
- await installGitHubPackage(resolved, {
1943
+ if (resolutionResult) {
1944
+ for (const name of resolutionResult.installOrder) {
1945
+ const node = resolutionResult.graph.nodes.get(name);
1946
+ if (!node) continue;
1947
+ try {
1948
+ await installFromNode(node, {
1390
1949
  ...options,
1391
- resolvedAgents: agents
1950
+ resolvedAgents: agents,
1951
+ isDirect: node.isDirect
1392
1952
  });
1393
- } else {
1394
- await installRegistryPackage(resolved, {
1395
- ...options,
1396
- resolvedAgents: agents
1953
+ results.push({ specifier: name, success: true });
1954
+ } catch (error) {
1955
+ const message = error instanceof Error ? error.message : "Unknown error";
1956
+ results.push({
1957
+ specifier: name,
1958
+ success: false,
1959
+ error: message
1397
1960
  });
1961
+ console.error(`Failed to install ${name}: ${message}
1962
+ `);
1398
1963
  }
1964
+ }
1965
+ }
1966
+ for (const resolved of githubPackages) {
1967
+ try {
1968
+ await installGitHubPackage(resolved, {
1969
+ ...options,
1970
+ resolvedAgents: agents
1971
+ });
1972
+ results.push({ specifier: resolved.specifier, success: true });
1973
+ } catch (error) {
1974
+ const message = error instanceof Error ? error.message : "Unknown error";
1975
+ results.push({
1976
+ specifier: resolved.specifier,
1977
+ success: false,
1978
+ error: message
1979
+ });
1980
+ console.error(`Failed to install ${resolved.specifier}: ${message}
1981
+ `);
1982
+ }
1983
+ }
1984
+ for (const resolved of localPackages) {
1985
+ try {
1986
+ await installLocalPackage(resolved, {
1987
+ ...options,
1988
+ resolvedAgents: agents
1989
+ });
1399
1990
  results.push({ specifier: resolved.specifier, success: true });
1400
1991
  } catch (error) {
1401
1992
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -1418,6 +2009,85 @@ Summary: ${succeeded} added, ${failed} failed`);
1418
2009
  }
1419
2010
  }
1420
2011
  }
2012
+ async function installFromNode(node, options) {
2013
+ const match = node.name.match(/^@user\/([^/]+)\/([^/]+)$/);
2014
+ if (!match) {
2015
+ throw new Error(`Invalid package name: ${node.name}`);
2016
+ }
2017
+ const [, username, name] = match;
2018
+ console.log(`Installing ${node.name}@${node.version}...`);
2019
+ const config2 = await resolveConfig();
2020
+ const apiKey = getTokenForRegistry(config2, config2.registryUrl);
2021
+ const isPresignedUrl = node.downloadUrl.includes(".r2.cloudflarestorage.com") || node.downloadUrl.includes("X-Amz-Signature");
2022
+ const downloadHeaders = {};
2023
+ if (!isPresignedUrl && apiKey) {
2024
+ downloadHeaders.Authorization = `Bearer ${apiKey}`;
2025
+ }
2026
+ const tarballResponse = await fetch(node.downloadUrl, {
2027
+ headers: downloadHeaders,
2028
+ redirect: "follow"
2029
+ });
2030
+ if (!tarballResponse.ok) {
2031
+ throw new Error(`Failed to download tarball (${tarballResponse.status})`);
2032
+ }
2033
+ const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
2034
+ const integrity = calculateIntegrity(tarballBuffer);
2035
+ if (integrity !== node.integrity) {
2036
+ throw new Error("Checksum verification failed");
2037
+ }
2038
+ const skillsDir = getSkillsDir();
2039
+ const destDir = join6(skillsDir, username, name);
2040
+ await mkdir5(destDir, { recursive: true });
2041
+ const { writeFile: writeFile8 } = await import("fs/promises");
2042
+ const tempFile = join6(destDir, ".temp.tgz");
2043
+ await writeFile8(tempFile, tarballBuffer);
2044
+ const { exec: exec2 } = await import("child_process");
2045
+ const { promisify: promisify2 } = await import("util");
2046
+ const execAsync = promisify2(exec2);
2047
+ try {
2048
+ await rm3(destDir, { recursive: true, force: true });
2049
+ await mkdir5(destDir, { recursive: true });
2050
+ await writeFile8(tempFile, tarballBuffer);
2051
+ await execAsync(
2052
+ `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
2053
+ );
2054
+ } finally {
2055
+ await rm3(tempFile, { force: true });
2056
+ }
2057
+ const resolvedDeps = {};
2058
+ for (const [depName, _range] of Object.entries(node.dependencies)) {
2059
+ resolvedDeps[depName] = _range;
2060
+ }
2061
+ await addToLockfileWithDeps(
2062
+ node.name,
2063
+ {
2064
+ version: node.version,
2065
+ resolved: node.downloadUrl,
2066
+ integrity,
2067
+ deprecated: node.deprecated
2068
+ },
2069
+ Object.keys(resolvedDeps).length > 0 ? resolvedDeps : void 0
2070
+ );
2071
+ if (options.isDirect) {
2072
+ const dependencyRange = node.versionRange || `^${node.version}`;
2073
+ await addDependency(node.name, dependencyRange);
2074
+ }
2075
+ const agents = options.resolvedAgents;
2076
+ if (agents[0] !== "none") {
2077
+ const skillManifest = await readManifest();
2078
+ const skillInfo = {
2079
+ name,
2080
+ sourcePath: getRegistrySkillPath(username, name)
2081
+ };
2082
+ await createAgentSymlinks([skillInfo], {
2083
+ agents,
2084
+ projectRoot: process.cwd(),
2085
+ agentConfigs: skillManifest?.agents
2086
+ });
2087
+ }
2088
+ console.log(`Installed ${node.name}@${node.version}`);
2089
+ console.log(`Location: ${destDir}`);
2090
+ }
1421
2091
  async function validateRegistryPackage(specifier) {
1422
2092
  const config2 = await resolveConfig();
1423
2093
  const registryUrl = config2.registryUrl;
@@ -1505,72 +2175,6 @@ async function validateGitHubPackage(specifier) {
1505
2175
  downloadResult: result
1506
2176
  };
1507
2177
  }
1508
- async function installRegistryPackage(resolved, options) {
1509
- const { username, name, versionRange, resolvedVersion, versionInfo } = resolved;
1510
- console.log(`Installing @user/${username}/${name}@${resolvedVersion}...`);
1511
- const config2 = await resolveConfig();
1512
- const apiKey = getTokenForRegistry(config2, config2.registryUrl);
1513
- const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
1514
- const downloadHeaders = {};
1515
- if (!isPresignedUrl && apiKey) {
1516
- downloadHeaders.Authorization = `Bearer ${apiKey}`;
1517
- }
1518
- const tarballResponse = await fetch(versionInfo.downloadUrl, {
1519
- headers: downloadHeaders,
1520
- redirect: "follow"
1521
- });
1522
- if (!tarballResponse.ok) {
1523
- throw new Error(`Failed to download tarball (${tarballResponse.status})`);
1524
- }
1525
- const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
1526
- const integrity = calculateIntegrity(tarballBuffer);
1527
- const expectedIntegrity = `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`;
1528
- if (integrity !== expectedIntegrity) {
1529
- throw new Error("Checksum verification failed");
1530
- }
1531
- const skillsDir = getSkillsDir();
1532
- const destDir = join5(skillsDir, username, name);
1533
- await mkdir5(destDir, { recursive: true });
1534
- const { writeFile: writeFile8 } = await import("fs/promises");
1535
- const tempFile = join5(destDir, ".temp.tgz");
1536
- await writeFile8(tempFile, tarballBuffer);
1537
- const { exec: exec2 } = await import("child_process");
1538
- const { promisify: promisify2 } = await import("util");
1539
- const execAsync = promisify2(exec2);
1540
- try {
1541
- await rm3(destDir, { recursive: true, force: true });
1542
- await mkdir5(destDir, { recursive: true });
1543
- await writeFile8(tempFile, tarballBuffer);
1544
- await execAsync(
1545
- `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
1546
- );
1547
- } finally {
1548
- await rm3(tempFile, { force: true });
1549
- }
1550
- const fullName = `@user/${username}/${name}`;
1551
- await addToLockfile(fullName, {
1552
- version: resolvedVersion,
1553
- resolved: versionInfo.downloadUrl,
1554
- integrity
1555
- });
1556
- const dependencyRange = versionRange || `^${resolvedVersion}`;
1557
- await addDependency(fullName, dependencyRange);
1558
- const agents = options.resolvedAgents;
1559
- if (agents[0] !== "none") {
1560
- const skillManifest = await readManifest();
1561
- const skillInfo = {
1562
- name,
1563
- sourcePath: getRegistrySkillPath(username, name)
1564
- };
1565
- await createAgentSymlinks([skillInfo], {
1566
- agents,
1567
- projectRoot: process.cwd(),
1568
- agentConfigs: skillManifest?.agents
1569
- });
1570
- }
1571
- console.log(`Installed @user/${username}/${name}@${resolvedVersion}`);
1572
- console.log(`Location: ${destDir}`);
1573
- }
1574
2178
  async function installGitHubPackage(resolved, options) {
1575
2179
  const { specifier, parsed, ref, downloadResult } = resolved;
1576
2180
  console.log(
@@ -1616,6 +2220,67 @@ async function installGitHubPackage(resolved, options) {
1616
2220
  );
1617
2221
  console.log(`Location: ${destPath}`);
1618
2222
  }
2223
+ async function validateLocalPackage(specifier) {
2224
+ const parsed = parseLocalSpecifier(specifier);
2225
+ if (!parsed) {
2226
+ throw new Error(
2227
+ `Invalid local specifier "${specifier}". Use format: file:../path or file:/absolute/path`
2228
+ );
2229
+ }
2230
+ console.log(`Resolving ${specifier}...`);
2231
+ const resolvedPath = resolveLocalPath(parsed);
2232
+ const validation = await validateLocalSkill(resolvedPath);
2233
+ if (!validation.valid) {
2234
+ throw new Error(validation.error);
2235
+ }
2236
+ const name = validation.manifest?.name || getLocalSkillName(parsed);
2237
+ console.log(`Resolved ${specifier} -> ${name} (local)`);
2238
+ return {
2239
+ type: "local",
2240
+ specifier,
2241
+ parsed,
2242
+ name,
2243
+ resolvedPath
2244
+ };
2245
+ }
2246
+ async function installLocalPackage(resolved, options) {
2247
+ const { specifier, name, resolvedPath, parsed } = resolved;
2248
+ console.log(`Installing ${specifier} (local symlink)...`);
2249
+ const skillsDir = getSkillsDir();
2250
+ const localDir = join6(skillsDir, "_local");
2251
+ await mkdir5(localDir, { recursive: true });
2252
+ const symlinkPath = join6(localDir, name);
2253
+ try {
2254
+ await rm3(symlinkPath, { force: true });
2255
+ } catch {
2256
+ }
2257
+ const { relative: relativePath } = await import("path");
2258
+ const relativeTarget = relativePath(dirname4(symlinkPath), resolvedPath);
2259
+ await symlink2(relativeTarget, symlinkPath);
2260
+ const entry = {
2261
+ version: "local",
2262
+ path: parsed.path,
2263
+ resolvedPath,
2264
+ name
2265
+ };
2266
+ await addLocalToLockfile(specifier, entry);
2267
+ await addLocalDependency(specifier, "*");
2268
+ const agents = options.resolvedAgents;
2269
+ if (agents[0] !== "none") {
2270
+ const manifest = await readManifest();
2271
+ const skillInfo = {
2272
+ name,
2273
+ sourcePath: getLocalSkillPath(name)
2274
+ };
2275
+ await createAgentSymlinks([skillInfo], {
2276
+ agents,
2277
+ projectRoot: process.cwd(),
2278
+ agentConfigs: manifest?.agents
2279
+ });
2280
+ }
2281
+ console.log(`Installed ${specifier} (local)`);
2282
+ console.log(`Location: ${symlinkPath} -> ${resolvedPath}`);
2283
+ }
1619
2284
  var init_add = __esm({
1620
2285
  "src/commands/add.ts"() {
1621
2286
  "use strict";
@@ -1633,7 +2298,7 @@ var init_add = __esm({
1633
2298
 
1634
2299
  // src/index.ts
1635
2300
  import { readFileSync } from "fs";
1636
- import { dirname as dirname4, join as join12 } from "path";
2301
+ import { dirname as dirname6, join as join13 } from "path";
1637
2302
  import { fileURLToPath } from "url";
1638
2303
  import { Command } from "commander";
1639
2304
 
@@ -1641,7 +2306,7 @@ import { Command } from "commander";
1641
2306
  init_api_client();
1642
2307
  init_config();
1643
2308
  init_lib();
1644
- async function access(specifier, options) {
2309
+ async function access2(specifier, options) {
1645
2310
  try {
1646
2311
  const apiKey = await requireApiKey();
1647
2312
  const registryUrl = await getRegistryUrl();
@@ -1666,18 +2331,18 @@ async function access(specifier, options) {
1666
2331
  packageName = parsed.name;
1667
2332
  } else {
1668
2333
  const { readFile: readFile7 } = await import("fs/promises");
1669
- const { join: join13 } = await import("path");
2334
+ const { join: join14 } = await import("path");
1670
2335
  let manifest = null;
1671
2336
  try {
1672
2337
  const content = await readFile7(
1673
- join13(process.cwd(), "pspm.json"),
2338
+ join14(process.cwd(), "pspm.json"),
1674
2339
  "utf-8"
1675
2340
  );
1676
2341
  manifest = JSON.parse(content);
1677
2342
  } catch {
1678
2343
  try {
1679
2344
  const content = await readFile7(
1680
- join13(process.cwd(), "package.json"),
2345
+ join14(process.cwd(), "package.json"),
1681
2346
  "utf-8"
1682
2347
  );
1683
2348
  manifest = JSON.parse(content);
@@ -1726,13 +2391,13 @@ async function access(specifier, options) {
1726
2391
  init_add();
1727
2392
 
1728
2393
  // src/commands/config/init.ts
1729
- import { stat as stat3, writeFile as writeFile5 } from "fs/promises";
1730
- import { join as join6 } from "path";
2394
+ import { stat as stat4, writeFile as writeFile5 } from "fs/promises";
2395
+ import { join as join7 } from "path";
1731
2396
  async function configInit(options) {
1732
2397
  try {
1733
- const configPath = join6(process.cwd(), ".pspmrc");
2398
+ const configPath = join7(process.cwd(), ".pspmrc");
1734
2399
  try {
1735
- await stat3(configPath);
2400
+ await stat4(configPath);
1736
2401
  console.error("Error: .pspmrc already exists in this directory.");
1737
2402
  process.exit(1);
1738
2403
  } catch {
@@ -1858,21 +2523,21 @@ async function deprecate(specifier, message, options) {
1858
2523
 
1859
2524
  // src/commands/init.ts
1860
2525
  init_lib();
1861
- import { readFile as readFile4, stat as stat4, writeFile as writeFile6 } from "fs/promises";
1862
- import { basename, join as join7 } from "path";
2526
+ import { readFile as readFile4, stat as stat5, writeFile as writeFile6 } from "fs/promises";
2527
+ import { basename, join as join8 } from "path";
1863
2528
  import { createInterface } from "readline";
1864
2529
  function prompt(rl, question, defaultValue) {
1865
- return new Promise((resolve) => {
2530
+ return new Promise((resolve2) => {
1866
2531
  const displayDefault = defaultValue ? ` (${defaultValue})` : "";
1867
2532
  rl.question(`${question}${displayDefault} `, (answer) => {
1868
- resolve(answer.trim() || defaultValue);
2533
+ resolve2(answer.trim() || defaultValue);
1869
2534
  });
1870
2535
  });
1871
2536
  }
1872
2537
  async function readExistingPackageJson() {
1873
2538
  try {
1874
2539
  const content = await readFile4(
1875
- join7(process.cwd(), "package.json"),
2540
+ join8(process.cwd(), "package.json"),
1876
2541
  "utf-8"
1877
2542
  );
1878
2543
  const pkg = JSON.parse(content);
@@ -1921,10 +2586,10 @@ function isValidVersion(version2) {
1921
2586
  }
1922
2587
  async function init(options) {
1923
2588
  try {
1924
- const pspmJsonPath = join7(process.cwd(), "pspm.json");
2589
+ const pspmJsonPath = join8(process.cwd(), "pspm.json");
1925
2590
  let exists = false;
1926
2591
  try {
1927
- await stat4(pspmJsonPath);
2592
+ await stat5(pspmJsonPath);
1928
2593
  exists = true;
1929
2594
  } catch {
1930
2595
  }
@@ -2059,7 +2724,7 @@ async function init(options) {
2059
2724
  await writeFile6(pspmJsonPath, `${content}
2060
2725
  `);
2061
2726
  try {
2062
- await stat4(join7(process.cwd(), "SKILL.md"));
2727
+ await stat5(join8(process.cwd(), "SKILL.md"));
2063
2728
  } catch {
2064
2729
  console.log(
2065
2730
  "Note: Create a SKILL.md file with your skill's prompt content."
@@ -2088,8 +2753,15 @@ init_lockfile();
2088
2753
  init_manifest2();
2089
2754
  init_symlinks();
2090
2755
  import { createHash as createHash2 } from "crypto";
2091
- import { mkdir as mkdir6, readFile as readFile5, rm as rm4, writeFile as writeFile7 } from "fs/promises";
2092
- import { join as join8 } from "path";
2756
+ import {
2757
+ lstat as lstat3,
2758
+ mkdir as mkdir6,
2759
+ readFile as readFile5,
2760
+ rm as rm4,
2761
+ symlink as symlink3,
2762
+ writeFile as writeFile7
2763
+ } from "fs/promises";
2764
+ import { dirname as dirname5, join as join9, relative as relative2 } from "path";
2093
2765
  function getCacheFilePath(cacheDir, integrity) {
2094
2766
  const match = integrity.match(/^sha256-(.+)$/);
2095
2767
  if (!match) {
@@ -2097,7 +2769,7 @@ function getCacheFilePath(cacheDir, integrity) {
2097
2769
  }
2098
2770
  const base64Hash = match[1];
2099
2771
  const hexHash = Buffer.from(base64Hash, "base64").toString("hex");
2100
- return join8(cacheDir, `sha256-${hexHash}.tgz`);
2772
+ return join9(cacheDir, `sha256-${hexHash}.tgz`);
2101
2773
  }
2102
2774
  async function readFromCache(cacheDir, integrity) {
2103
2775
  try {
@@ -2144,8 +2816,10 @@ async function installFromLockfile(options) {
2144
2816
  let lockfile = await readLockfile();
2145
2817
  const manifestDeps = await getDependencies();
2146
2818
  const manifestGitHubDeps = await getGitHubDependencies();
2819
+ const manifestLocalDeps = await getLocalDependencies();
2147
2820
  const lockfilePackages = lockfile?.packages ?? lockfile?.skills ?? {};
2148
2821
  const lockfileGitHubPackages = lockfile?.githubPackages ?? {};
2822
+ const lockfileLocalPackages = lockfile?.localPackages ?? {};
2149
2823
  const installedSkills = [];
2150
2824
  const missingDeps = [];
2151
2825
  for (const [fullName, versionRange] of Object.entries(manifestDeps)) {
@@ -2296,6 +2970,53 @@ Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
2296
2970
  }
2297
2971
  lockfile = await readLockfile();
2298
2972
  }
2973
+ const missingLocalDeps = [];
2974
+ for (const [specifier] of Object.entries(manifestLocalDeps)) {
2975
+ if (!lockfileLocalPackages[specifier]) {
2976
+ missingLocalDeps.push({ specifier });
2977
+ }
2978
+ }
2979
+ if (missingLocalDeps.length > 0) {
2980
+ if (options.frozenLockfile) {
2981
+ console.error(
2982
+ "Error: Local dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
2983
+ );
2984
+ console.error("Missing local dependencies:");
2985
+ for (const dep of missingLocalDeps) {
2986
+ console.error(` - ${dep.specifier}`);
2987
+ }
2988
+ process.exit(1);
2989
+ }
2990
+ console.log(
2991
+ `
2992
+ Resolving ${missingLocalDeps.length} local dependency(ies)...
2993
+ `
2994
+ );
2995
+ for (const { specifier } of missingLocalDeps) {
2996
+ const parsed = parseLocalSpecifier(specifier);
2997
+ if (!parsed) {
2998
+ console.error(`Error: Invalid local specifier: ${specifier}`);
2999
+ continue;
3000
+ }
3001
+ console.log(`Resolving ${specifier}...`);
3002
+ const resolvedPath = resolveLocalPath(parsed);
3003
+ const validation = await validateLocalSkill(resolvedPath);
3004
+ if (!validation.valid) {
3005
+ console.error(`Error: ${validation.error}`);
3006
+ continue;
3007
+ }
3008
+ const name = validation.manifest?.name || parsed.path.split("/").pop() || "unknown";
3009
+ const entry = {
3010
+ version: "local",
3011
+ path: parsed.path,
3012
+ resolvedPath,
3013
+ name
3014
+ };
3015
+ await addLocalToLockfile(specifier, entry);
3016
+ console.log(` Resolved ${specifier} -> ${name} (local)`);
3017
+ }
3018
+ lockfile = await readLockfile();
3019
+ }
2299
3020
  const manifest = await readManifest();
2300
3021
  const agentConfigs = manifest?.agents;
2301
3022
  let agents;
@@ -2316,7 +3037,8 @@ Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
2316
3037
  console.log(`
2317
3038
  Installing ${packageCount} registry skill(s)...
2318
3039
  `);
2319
- const entries = Object.entries(packages);
3040
+ const installOrder = computeInstallOrder(packages);
3041
+ const entries = installOrder.filter((name) => packages[name]).map((name) => [name, packages[name]]);
2320
3042
  for (const [fullName, entry] of entries) {
2321
3043
  const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
2322
3044
  if (!match) {
@@ -2372,10 +3094,10 @@ Installing ${packageCount} registry skill(s)...
2372
3094
  }
2373
3095
  await writeToCache(cacheDir, entry.integrity, tarballBuffer);
2374
3096
  }
2375
- const destDir = join8(skillsDir, username, name);
3097
+ const destDir = join9(skillsDir, username, name);
2376
3098
  await rm4(destDir, { recursive: true, force: true });
2377
3099
  await mkdir6(destDir, { recursive: true });
2378
- const tempFile = join8(destDir, ".temp.tgz");
3100
+ const tempFile = join9(destDir, ".temp.tgz");
2379
3101
  await writeFile7(tempFile, tarballBuffer);
2380
3102
  const { exec: exec2 } = await import("child_process");
2381
3103
  const { promisify: promisify2 } = await import("util");
@@ -2473,6 +3195,52 @@ Installing ${githubCount} GitHub skill(s)...
2473
3195
  }
2474
3196
  }
2475
3197
  }
3198
+ const localPackages = lockfile?.localPackages ?? {};
3199
+ const localCount = Object.keys(localPackages).length;
3200
+ if (localCount > 0) {
3201
+ console.log(`
3202
+ Installing ${localCount} local skill(s)...
3203
+ `);
3204
+ for (const [specifier, entry] of Object.entries(localPackages)) {
3205
+ const localEntry = entry;
3206
+ console.log(`Installing ${specifier} (local symlink)...`);
3207
+ const validation = await validateLocalSkill(localEntry.resolvedPath);
3208
+ if (!validation.valid) {
3209
+ console.error(` Error: ${validation.error}`);
3210
+ console.error(
3211
+ ` Hint: The local skill at ${localEntry.resolvedPath} may have been moved or deleted.`
3212
+ );
3213
+ continue;
3214
+ }
3215
+ const localDir = join9(skillsDir, "_local");
3216
+ await mkdir6(localDir, { recursive: true });
3217
+ const symlinkPath = join9(localDir, localEntry.name);
3218
+ try {
3219
+ const stats = await lstat3(symlinkPath);
3220
+ if (stats.isSymbolicLink()) {
3221
+ await rm4(symlinkPath);
3222
+ }
3223
+ } catch {
3224
+ }
3225
+ const relativeTarget = relative2(
3226
+ dirname5(symlinkPath),
3227
+ localEntry.resolvedPath
3228
+ );
3229
+ try {
3230
+ await symlink3(relativeTarget, symlinkPath);
3231
+ console.log(
3232
+ ` Installed to ${symlinkPath} -> ${localEntry.resolvedPath}`
3233
+ );
3234
+ installedSkills.push({
3235
+ name: localEntry.name,
3236
+ sourcePath: getLocalSkillPath(localEntry.name)
3237
+ });
3238
+ } catch (error) {
3239
+ const message = error instanceof Error ? error.message : String(error);
3240
+ console.error(` Error creating symlink: ${message}`);
3241
+ }
3242
+ }
3243
+ }
2476
3244
  if (installedSkills.length > 0 && agents[0] !== "none") {
2477
3245
  console.log(`
2478
3246
  Creating symlinks for agent(s): ${agents.join(", ")}...`);
@@ -2483,7 +3251,7 @@ Creating symlinks for agent(s): ${agents.join(", ")}...`);
2483
3251
  });
2484
3252
  console.log(" Symlinks created.");
2485
3253
  }
2486
- const totalCount = packageCount + githubCount;
3254
+ const totalCount = packageCount + githubCount + localCount;
2487
3255
  if (totalCount === 0) {
2488
3256
  console.log("No skills to install.");
2489
3257
  } else {
@@ -2580,8 +3348,8 @@ init_lib();
2580
3348
  init_lockfile();
2581
3349
  init_manifest2();
2582
3350
  init_symlinks();
2583
- import { access as access2 } from "fs/promises";
2584
- import { join as join9 } from "path";
3351
+ import { access as access3 } from "fs/promises";
3352
+ import { join as join10 } from "path";
2585
3353
  async function list(options) {
2586
3354
  try {
2587
3355
  const registrySkills = await listLockfileSkills();
@@ -2596,10 +3364,10 @@ async function list(options) {
2596
3364
  if (!match) continue;
2597
3365
  const [, username, skillName] = match;
2598
3366
  const sourcePath = getRegistrySkillPath(username, skillName);
2599
- const absolutePath = join9(projectRoot, sourcePath);
3367
+ const absolutePath = join10(projectRoot, sourcePath);
2600
3368
  let status = "installed";
2601
3369
  try {
2602
- await access2(absolutePath);
3370
+ await access3(absolutePath);
2603
3371
  } catch {
2604
3372
  status = "missing";
2605
3373
  }
@@ -2629,10 +3397,10 @@ async function list(options) {
2629
3397
  parsed.repo,
2630
3398
  parsed.path
2631
3399
  );
2632
- const absolutePath = join9(projectRoot, sourcePath);
3400
+ const absolutePath = join10(projectRoot, sourcePath);
2633
3401
  let status = "installed";
2634
3402
  try {
2635
- await access2(absolutePath);
3403
+ await access3(absolutePath);
2636
3404
  } catch {
2637
3405
  status = "missing";
2638
3406
  }
@@ -2654,6 +3422,35 @@ async function list(options) {
2654
3422
  gitCommit: ghEntry.gitCommit
2655
3423
  });
2656
3424
  }
3425
+ const localSkills = await listLockfileLocalPackages();
3426
+ for (const { specifier, entry } of localSkills) {
3427
+ const localEntry = entry;
3428
+ const skillName = localEntry.name;
3429
+ const sourcePath = getLocalSkillPath(skillName);
3430
+ const absolutePath = join10(projectRoot, sourcePath);
3431
+ let status = "installed";
3432
+ try {
3433
+ await access3(absolutePath);
3434
+ } catch {
3435
+ status = "missing";
3436
+ }
3437
+ const linkedAgents = await getLinkedAgents(
3438
+ skillName,
3439
+ availableAgents,
3440
+ projectRoot,
3441
+ agentConfigs
3442
+ );
3443
+ skills.push({
3444
+ name: skillName,
3445
+ fullName: specifier,
3446
+ version: "local",
3447
+ source: "local",
3448
+ sourcePath,
3449
+ status,
3450
+ linkedAgents,
3451
+ localPath: localEntry.resolvedPath
3452
+ });
3453
+ }
2657
3454
  if (skills.length === 0) {
2658
3455
  console.log("No skills installed.");
2659
3456
  return;
@@ -2666,9 +3463,14 @@ async function list(options) {
2666
3463
  for (const skill of skills) {
2667
3464
  if (skill.source === "registry") {
2668
3465
  console.log(` ${skill.fullName}@${skill.version} (registry)`);
2669
- } else {
3466
+ } else if (skill.source === "github") {
2670
3467
  const refInfo = skill.gitRef ? `${skill.gitRef}@${skill.gitCommit?.slice(0, 7)}` : skill.version;
2671
3468
  console.log(` ${skill.fullName} (${refInfo})`);
3469
+ } else {
3470
+ console.log(` ${skill.fullName} [local]`);
3471
+ if (skill.localPath) {
3472
+ console.log(` Path: ${skill.localPath}`);
3473
+ }
2672
3474
  }
2673
3475
  if (skill.status === "missing") {
2674
3476
  console.log(` Status: MISSING (run 'pspm install' to restore)`);
@@ -2684,9 +3486,11 @@ async function list(options) {
2684
3486
  }
2685
3487
  const registryCount = skills.filter((s) => s.source === "registry").length;
2686
3488
  const githubCount = skills.filter((s) => s.source === "github").length;
3489
+ const localCount = skills.filter((s) => s.source === "local").length;
2687
3490
  const parts = [];
2688
3491
  if (registryCount > 0) parts.push(`${registryCount} registry`);
2689
3492
  if (githubCount > 0) parts.push(`${githubCount} github`);
3493
+ if (localCount > 0) parts.push(`${localCount} local`);
2690
3494
  console.log(`
2691
3495
  Total: ${skills.length} skill(s) (${parts.join(", ")})`);
2692
3496
  } catch (error) {
@@ -2744,8 +3548,8 @@ function startCallbackServer(expectedState) {
2744
3548
  let resolveToken;
2745
3549
  let rejectToken;
2746
3550
  let timeoutId;
2747
- const tokenPromise = new Promise((resolve, reject) => {
2748
- resolveToken = resolve;
3551
+ const tokenPromise = new Promise((resolve2, reject) => {
3552
+ resolveToken = resolve2;
2749
3553
  rejectToken = reject;
2750
3554
  });
2751
3555
  const server = http.createServer((req, res) => {
@@ -2900,7 +3704,7 @@ async function logout() {
2900
3704
  // src/commands/migrate.ts
2901
3705
  init_config();
2902
3706
  init_lockfile();
2903
- import { mkdir as mkdir7, readdir as readdir2, rename, rm as rm5, stat as stat5 } from "fs/promises";
3707
+ import { mkdir as mkdir7, readdir as readdir2, rename, rm as rm5, stat as stat6 } from "fs/promises";
2904
3708
  async function migrate(options) {
2905
3709
  try {
2906
3710
  const legacySkillsDir = getLegacySkillsDir();
@@ -2911,7 +3715,7 @@ async function migrate(options) {
2911
3715
  let migrationNeeded = false;
2912
3716
  const actions = [];
2913
3717
  try {
2914
- const legacyStats = await stat5(legacySkillsDir);
3718
+ const legacyStats = await stat6(legacySkillsDir);
2915
3719
  if (legacyStats.isDirectory()) {
2916
3720
  const contents = await readdir2(legacySkillsDir);
2917
3721
  if (contents.length > 0) {
@@ -2922,9 +3726,9 @@ async function migrate(options) {
2922
3726
  } catch {
2923
3727
  }
2924
3728
  try {
2925
- await stat5(legacyLockfilePath);
3729
+ await stat6(legacyLockfilePath);
2926
3730
  try {
2927
- await stat5(newLockfilePath);
3731
+ await stat6(newLockfilePath);
2928
3732
  actions.push(
2929
3733
  "Note: Both skill-lock.json and pspm-lock.json exist. Manual merge may be needed."
2930
3734
  );
@@ -2956,13 +3760,13 @@ async function migrate(options) {
2956
3760
  console.log(" \u2713 Migrated skill-lock.json \u2192 pspm-lock.json");
2957
3761
  }
2958
3762
  try {
2959
- const legacyStats = await stat5(legacySkillsDir);
3763
+ const legacyStats = await stat6(legacySkillsDir);
2960
3764
  if (legacyStats.isDirectory()) {
2961
3765
  const contents = await readdir2(legacySkillsDir);
2962
3766
  if (contents.length > 0) {
2963
3767
  await mkdir7(pspmDir, { recursive: true });
2964
3768
  try {
2965
- const newStats = await stat5(newSkillsDir);
3769
+ const newStats = await stat6(newSkillsDir);
2966
3770
  if (newStats.isDirectory()) {
2967
3771
  const newContents = await readdir2(newSkillsDir);
2968
3772
  if (newContents.length > 0) {
@@ -3008,20 +3812,20 @@ init_errors();
3008
3812
  init_lib();
3009
3813
  import { exec as execCb } from "child_process";
3010
3814
  import { createHash as createHash3 } from "crypto";
3011
- import { readdir as readdir3, readFile as readFile6, stat as stat6 } from "fs/promises";
3012
- import { join as join10, relative as relative2 } from "path";
3815
+ import { readdir as readdir3, readFile as readFile6, stat as stat7 } from "fs/promises";
3816
+ import { join as join11, relative as relative3 } from "path";
3013
3817
  import { promisify } from "util";
3014
3818
  var exec = promisify(execCb);
3015
3819
  async function detectManifest() {
3016
3820
  const cwd = process.cwd();
3017
- const pspmJsonPath = join10(cwd, "pspm.json");
3821
+ const pspmJsonPath = join11(cwd, "pspm.json");
3018
3822
  try {
3019
3823
  const content = await readFile6(pspmJsonPath, "utf-8");
3020
3824
  const manifest = JSON.parse(content);
3021
3825
  return { type: "pspm.json", manifest, path: pspmJsonPath };
3022
3826
  } catch {
3023
3827
  }
3024
- const packageJsonPath = join10(cwd, "package.json");
3828
+ const packageJsonPath = join11(cwd, "package.json");
3025
3829
  try {
3026
3830
  const content = await readFile6(packageJsonPath, "utf-8");
3027
3831
  const packageJson2 = JSON.parse(content);
@@ -3048,8 +3852,8 @@ async function getFilesWithSizes(dir, baseDir) {
3048
3852
  try {
3049
3853
  const entries = await readdir3(dir, { withFileTypes: true });
3050
3854
  for (const entry of entries) {
3051
- const fullPath = join10(dir, entry.name);
3052
- const relativePath = relative2(baseDir, fullPath);
3855
+ const fullPath = join11(dir, entry.name);
3856
+ const relativePath = relative3(baseDir, fullPath);
3053
3857
  if (entry.name === "node_modules" || entry.name === ".git") {
3054
3858
  continue;
3055
3859
  }
@@ -3057,7 +3861,7 @@ async function getFilesWithSizes(dir, baseDir) {
3057
3861
  const subFiles = await getFilesWithSizes(fullPath, baseDir);
3058
3862
  results.push(...subFiles);
3059
3863
  } else {
3060
- const fileStat = await stat6(fullPath);
3864
+ const fileStat = await stat7(fullPath);
3061
3865
  results.push({ path: relativePath, size: fileStat.size });
3062
3866
  }
3063
3867
  }
@@ -3103,7 +3907,7 @@ async function publishCommand(options) {
3103
3907
  }
3104
3908
  const safeName = packageJson2.name.replace(/[@/]/g, "-").replace(/^-+/, "");
3105
3909
  const tarballName = `${safeName}-${packageJson2.version}.tgz`;
3106
- const tempDir = join10(process.cwd(), ".pspm-publish");
3910
+ const tempDir = join11(process.cwd(), ".pspm-publish");
3107
3911
  try {
3108
3912
  await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
3109
3913
  const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
@@ -3119,7 +3923,7 @@ async function publishCommand(options) {
3119
3923
  if (detection.type === "pspm.json") {
3120
3924
  await exec(`cp pspm.json "${tempDir}/package/"`);
3121
3925
  try {
3122
- await stat6(join10(process.cwd(), "package.json"));
3926
+ await stat7(join11(process.cwd(), "package.json"));
3123
3927
  await exec(
3124
3928
  `cp package.json "${tempDir}/package/" 2>/dev/null || true`
3125
3929
  );
@@ -3128,10 +3932,10 @@ async function publishCommand(options) {
3128
3932
  } else {
3129
3933
  await exec(`cp package.json "${tempDir}/package/"`);
3130
3934
  }
3131
- const packageDir = join10(tempDir, "package");
3935
+ const packageDir = join11(tempDir, "package");
3132
3936
  const tarballContents = await getFilesWithSizes(packageDir, packageDir);
3133
3937
  const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
3134
- const tarballPath = join10(tempDir, tarballName);
3938
+ const tarballPath = join11(tempDir, tarballName);
3135
3939
  await exec(
3136
3940
  `tar -czf "${tarballPath}" -C "${tempDir}" --exclude='node_modules' --exclude='.git' package`
3137
3941
  );
@@ -3225,13 +4029,15 @@ init_lockfile();
3225
4029
  init_manifest2();
3226
4030
  init_symlinks();
3227
4031
  import { rm as rm6 } from "fs/promises";
3228
- import { join as join11 } from "path";
4032
+ import { join as join12 } from "path";
3229
4033
  async function remove(nameOrSpecifier) {
3230
4034
  try {
3231
4035
  const manifest = await readManifest();
3232
4036
  const agentConfigs = manifest?.agents;
3233
4037
  const agents = getAvailableAgents(agentConfigs);
3234
- if (isGitHubSpecifier(nameOrSpecifier)) {
4038
+ if (isLocalSpecifier(nameOrSpecifier)) {
4039
+ await removeLocal(nameOrSpecifier, agents, agentConfigs);
4040
+ } else if (isGitHubSpecifier(nameOrSpecifier)) {
3235
4041
  await removeGitHub(nameOrSpecifier, agents, agentConfigs);
3236
4042
  } else if (nameOrSpecifier.startsWith("@user/")) {
3237
4043
  await removeRegistry(nameOrSpecifier, agents, agentConfigs);
@@ -3266,7 +4072,7 @@ async function removeRegistry(specifier, agents, agentConfigs) {
3266
4072
  agentConfigs
3267
4073
  });
3268
4074
  const skillsDir = getSkillsDir();
3269
- const destDir = join11(skillsDir, username, name);
4075
+ const destDir = join12(skillsDir, username, name);
3270
4076
  try {
3271
4077
  await rm6(destDir, { recursive: true, force: true });
3272
4078
  } catch {
@@ -3295,13 +4101,42 @@ async function removeGitHub(specifier, agents, agentConfigs) {
3295
4101
  });
3296
4102
  const skillsDir = getSkillsDir();
3297
4103
  const destPath = getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path);
3298
- const destDir = join11(skillsDir, "..", destPath);
4104
+ const destDir = join12(skillsDir, "..", destPath);
3299
4105
  try {
3300
4106
  await rm6(destDir, { recursive: true, force: true });
3301
4107
  } catch {
3302
4108
  }
3303
4109
  console.log(`Removed ${lockfileKey}`);
3304
4110
  }
4111
+ async function removeLocal(specifier, agents, agentConfigs) {
4112
+ const parsed = parseLocalSpecifier(specifier);
4113
+ if (!parsed) {
4114
+ console.error(`Error: Invalid local specifier: ${specifier}`);
4115
+ process.exit(1);
4116
+ }
4117
+ console.log(`Removing ${specifier}...`);
4118
+ const removedFromLockfile = await removeLocalFromLockfile(specifier);
4119
+ const removedFromManifest = await removeLocalDependency(specifier);
4120
+ if (!removedFromLockfile && !removedFromManifest) {
4121
+ console.error(`Error: ${specifier} not found in lockfile or pspm.json`);
4122
+ process.exit(1);
4123
+ }
4124
+ const localSkills = await listLockfileLocalPackages();
4125
+ const foundLocal = localSkills.find((s) => s.specifier === specifier);
4126
+ const skillName = foundLocal?.entry.name || parsed.path.split("/").filter(Boolean).pop() || "unknown";
4127
+ await removeAgentSymlinks(skillName, {
4128
+ agents,
4129
+ projectRoot: process.cwd(),
4130
+ agentConfigs
4131
+ });
4132
+ const skillsDir = getSkillsDir();
4133
+ const symlinkPath = join12(skillsDir, "_local", skillName);
4134
+ try {
4135
+ await rm6(symlinkPath, { force: true });
4136
+ } catch {
4137
+ }
4138
+ console.log(`Removed ${specifier}`);
4139
+ }
3305
4140
  async function removeByShortName(shortName, agents, agentConfigs) {
3306
4141
  const registrySkills = await listLockfileSkills();
3307
4142
  const foundRegistry = registrySkills.find((s) => {
@@ -3322,6 +4157,12 @@ async function removeByShortName(shortName, agents, agentConfigs) {
3322
4157
  await removeGitHub(foundGitHub.specifier, agents, agentConfigs);
3323
4158
  return;
3324
4159
  }
4160
+ const localSkills = await listLockfileLocalPackages();
4161
+ const foundLocal = localSkills.find((s) => s.entry.name === shortName);
4162
+ if (foundLocal) {
4163
+ await removeLocal(foundLocal.specifier, agents, agentConfigs);
4164
+ return;
4165
+ }
3325
4166
  console.error(`Error: Skill "${shortName}" not found in lockfile`);
3326
4167
  process.exit(1);
3327
4168
  }
@@ -3494,9 +4335,9 @@ async function whoami() {
3494
4335
  }
3495
4336
 
3496
4337
  // src/index.ts
3497
- var __dirname = dirname4(fileURLToPath(import.meta.url));
4338
+ var __dirname = dirname6(fileURLToPath(import.meta.url));
3498
4339
  var packageJson = JSON.parse(
3499
- readFileSync(join12(__dirname, "..", "package.json"), "utf-8")
4340
+ readFileSync(join13(__dirname, "..", "package.json"), "utf-8")
3500
4341
  );
3501
4342
  var version = packageJson.version;
3502
4343
  var program = new Command();
@@ -3589,7 +4430,7 @@ program.command("unpublish <specifier>").description(
3589
4430
  await unpublish(specifier, { force: options.force });
3590
4431
  });
3591
4432
  program.command("access [specifier]").description("Change package visibility (public/private)").option("--public", "Make the package public (irreversible)").option("--private", "Make the package private (only for private packages)").action(async (specifier, options) => {
3592
- await access(specifier, {
4433
+ await access2(specifier, {
3593
4434
  public: options.public,
3594
4435
  private: options.private
3595
4436
  });