@anytio/pspm 0.3.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
@@ -1,21 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, lstat, unlink, cp, readlink, symlink } from 'fs/promises';
3
- import { homedir } from 'os';
4
- import { dirname, join, basename, relative } from 'path';
5
- import * as ini from 'ini';
6
- import ignore from 'ignore';
7
- import { createHash, randomBytes } from 'crypto';
8
- import * as semver from 'semver';
9
- import { checkbox } from '@inquirer/prompts';
10
- import { readFileSync } from 'fs';
11
- import { fileURLToPath, URL as URL$1 } from 'url';
12
- import { Command } from 'commander';
13
- import { createInterface } from 'readline';
14
- import http from 'http';
15
- import open from 'open';
16
- import { exec as exec$1 } from 'child_process';
17
- import { promisify } from 'util';
18
-
19
2
  var __defProp = Object.defineProperty;
20
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
21
4
  var __esm = (fn, res) => function __init() {
@@ -26,7 +9,7 @@ var __export = (target, all) => {
26
9
  __defProp(target, name, { get: all[name], enumerable: true });
27
10
  };
28
11
 
29
- // ../../packages/shared/sdk/src/fetcher.ts
12
+ // src/sdk/fetcher.ts
30
13
  function configure(options) {
31
14
  config = options;
32
15
  }
@@ -67,15 +50,17 @@ async function customFetch(url, options) {
67
50
  }
68
51
  var config;
69
52
  var init_fetcher = __esm({
70
- "../../packages/shared/sdk/src/fetcher.ts"() {
53
+ "src/sdk/fetcher.ts"() {
54
+ "use strict";
71
55
  config = null;
72
56
  }
73
57
  });
74
58
 
75
- // ../../packages/shared/sdk/src/generated/index.ts
59
+ // src/sdk/generated/index.ts
76
60
  var getMeUrl, me, getListSkillVersionsUrl, listSkillVersions, getGetSkillVersionUrl, getSkillVersion, getPublishSkillUrl, publishSkill, getDeleteSkillUrl, deleteSkill, getDeleteSkillVersionUrl, deleteSkillVersion;
77
61
  var init_generated = __esm({
78
- "../../packages/shared/sdk/src/generated/index.ts"() {
62
+ "src/sdk/generated/index.ts"() {
63
+ "use strict";
79
64
  init_fetcher();
80
65
  getMeUrl = () => {
81
66
  return `/api/skills/me`;
@@ -156,14 +141,6 @@ var init_generated = __esm({
156
141
  }
157
142
  });
158
143
 
159
- // ../../packages/shared/sdk/src/index.ts
160
- var init_src = __esm({
161
- "../../packages/shared/sdk/src/index.ts"() {
162
- init_fetcher();
163
- init_generated();
164
- }
165
- });
166
-
167
144
  // src/api-client.ts
168
145
  function registryUrlToBaseUrl(registryUrl) {
169
146
  return registryUrl.replace(/\/api\/skills\/?$/, "");
@@ -286,7 +263,9 @@ async function changeSkillAccess(skillName, input) {
286
263
  }
287
264
  var init_api_client = __esm({
288
265
  "src/api-client.ts"() {
289
- init_src();
266
+ "use strict";
267
+ init_fetcher();
268
+ init_generated();
290
269
  }
291
270
  });
292
271
 
@@ -328,6 +307,7 @@ ${issueMessages}`;
328
307
  var ConfigError, NotLoggedInError;
329
308
  var init_errors = __esm({
330
309
  "src/errors.ts"() {
310
+ "use strict";
331
311
  ConfigError = class extends Error {
332
312
  constructor(message) {
333
313
  super(message);
@@ -344,6 +324,12 @@ var init_errors = __esm({
344
324
  };
345
325
  }
346
326
  });
327
+
328
+ // src/config.ts
329
+ import { mkdir, readFile, stat, unlink, writeFile } from "fs/promises";
330
+ import { homedir } from "os";
331
+ import { dirname, join } from "path";
332
+ import * as ini from "ini";
347
333
  function getConfigPath() {
348
334
  return join(homedir(), ".pspmrc");
349
335
  }
@@ -616,55 +602,106 @@ async function getRegistryUrl() {
616
602
  var DEFAULT_REGISTRY_URL;
617
603
  var init_config = __esm({
618
604
  "src/config.ts"() {
605
+ "use strict";
619
606
  init_errors();
620
607
  DEFAULT_REGISTRY_URL = "https://pspm.dev";
621
608
  }
622
609
  });
623
- async function loadIgnorePatterns(cwd = process.cwd()) {
624
- const ig = ignore();
625
- ig.add(ALWAYS_IGNORED);
626
- const pspmIgnorePath = join(cwd, ".pspmignore");
610
+
611
+ // src/lib/integrity.ts
612
+ import { createHash } from "crypto";
613
+ function calculateIntegrity(data) {
614
+ const hash = createHash("sha256").update(data).digest("base64");
615
+ return `sha256-${hash}`;
616
+ }
617
+ var init_integrity = __esm({
618
+ "src/lib/integrity.ts"() {
619
+ "use strict";
620
+ }
621
+ });
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) {
627
664
  try {
628
- const content = await readFile(pspmIgnorePath, "utf-8");
629
- const patterns = parseIgnorePatterns(content);
630
- ig.add(patterns);
631
- return { ig, source: ".pspmignore", patterns };
665
+ const stats = await stat2(absolutePath);
666
+ if (!stats.isDirectory()) {
667
+ return { valid: false, error: `Not a directory: ${absolutePath}` };
668
+ }
632
669
  } catch {
670
+ return { valid: false, error: `Directory not found: ${absolutePath}` };
633
671
  }
634
- const gitIgnorePath = join(cwd, ".gitignore");
672
+ const manifestPath = join2(absolutePath, "pspm.json");
635
673
  try {
636
- const content = await readFile(gitIgnorePath, "utf-8");
637
- const patterns = parseIgnorePatterns(content);
638
- ig.add(patterns);
639
- return { ig, source: ".gitignore", patterns };
674
+ await access(manifestPath);
640
675
  } catch {
676
+ return {
677
+ valid: false,
678
+ error: `No pspm.json found in ${absolutePath}`
679
+ };
641
680
  }
642
- return { ig, source: null, patterns: [] };
643
- }
644
- function getExcludeArgsForRsync(patterns) {
645
- const allPatterns = [.../* @__PURE__ */ new Set([...ALWAYS_IGNORED, ...patterns])];
646
- return allPatterns.map((p) => `--exclude='${p}'`).join(" ");
647
- }
648
- function parseIgnorePatterns(content) {
649
- return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
650
- }
651
- var ALWAYS_IGNORED;
652
- var init_ignore = __esm({
653
- "src/lib/ignore.ts"() {
654
- ALWAYS_IGNORED = [
655
- "node_modules",
656
- ".git",
657
- ".pspm-publish"
658
- // temp directory used during publish
659
- ];
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
+ };
660
698
  }
661
- });
662
- function calculateIntegrity(data) {
663
- const hash = createHash("sha256").update(data).digest("base64");
664
- return `sha256-${hash}`;
665
699
  }
666
- var init_integrity = __esm({
667
- "src/lib/integrity.ts"() {
700
+ var LOCAL_SPECIFIER_PATTERN;
701
+ var init_local = __esm({
702
+ "src/lib/local.ts"() {
703
+ "use strict";
704
+ LOCAL_SPECIFIER_PATTERN = /^file:(.+)$/;
668
705
  }
669
706
  });
670
707
 
@@ -693,6 +730,7 @@ function validateManifest(manifest) {
693
730
  var DEFAULT_SKILL_FILES, PSPM_SCHEMA_URL;
694
731
  var init_manifest = __esm({
695
732
  "src/lib/manifest.ts"() {
733
+ "use strict";
696
734
  DEFAULT_SKILL_FILES = [
697
735
  "SKILL.md",
698
736
  "runtime",
@@ -702,6 +740,9 @@ var init_manifest = __esm({
702
740
  PSPM_SCHEMA_URL = "https://pspm.dev/schema/v1/pspm.json";
703
741
  }
704
742
  });
743
+
744
+ // src/lib/version.ts
745
+ import * as semver from "semver";
705
746
  function resolveVersion(range, availableVersions) {
706
747
  const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
707
748
  if (!range || range === "latest" || range === "*") {
@@ -727,6 +768,7 @@ function findHighestSatisfying(ranges, availableVersions) {
727
768
  }
728
769
  var init_version = __esm({
729
770
  "src/lib/version.ts"() {
771
+ "use strict";
730
772
  }
731
773
  });
732
774
 
@@ -1035,6 +1077,7 @@ function printResolutionErrors(errors, conflicts = []) {
1035
1077
  var MAX_DEPENDENCY_DEPTH;
1036
1078
  var init_resolver = __esm({
1037
1079
  "src/lib/resolver.ts"() {
1080
+ "use strict";
1038
1081
  init_api_client();
1039
1082
  init_version();
1040
1083
  MAX_DEPENDENCY_DEPTH = 5;
@@ -1047,14 +1090,9 @@ function parseSkillSpecifier(specifier) {
1047
1090
  if (!match) {
1048
1091
  return null;
1049
1092
  }
1050
- const username = match[1];
1051
- const name = match[2];
1052
- if (!username || !name) {
1053
- return null;
1054
- }
1055
1093
  return {
1056
- username,
1057
- name,
1094
+ username: match[1],
1095
+ name: match[2],
1058
1096
  versionRange: match[3]
1059
1097
  };
1060
1098
  }
@@ -1064,9 +1102,6 @@ function parseGitHubSpecifier(specifier) {
1064
1102
  return null;
1065
1103
  }
1066
1104
  const [, owner, repo, pathWithSlash, ref] = match;
1067
- if (!owner || !repo) {
1068
- return null;
1069
- }
1070
1105
  return {
1071
1106
  owner,
1072
1107
  repo,
@@ -1088,10 +1123,7 @@ function formatGitHubSpecifier(spec) {
1088
1123
  function getGitHubSkillName(spec) {
1089
1124
  if (spec.path) {
1090
1125
  const segments = spec.path.split("/").filter(Boolean);
1091
- const lastSegment = segments[segments.length - 1];
1092
- if (lastSegment) {
1093
- return lastSegment;
1094
- }
1126
+ return segments[segments.length - 1];
1095
1127
  }
1096
1128
  return spec.repo;
1097
1129
  }
@@ -1101,6 +1133,7 @@ function isGitHubSpecifier(specifier) {
1101
1133
  var SPECIFIER_PATTERN, GITHUB_SPECIFIER_PATTERN;
1102
1134
  var init_specifier = __esm({
1103
1135
  "src/lib/specifier.ts"() {
1136
+ "use strict";
1104
1137
  SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
1105
1138
  GITHUB_SPECIFIER_PATTERN = /^github:([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)(\/[^@]+)?(?:@(.+))?$/;
1106
1139
  }
@@ -1109,14 +1142,18 @@ var init_specifier = __esm({
1109
1142
  // src/lib/index.ts
1110
1143
  var init_lib = __esm({
1111
1144
  "src/lib/index.ts"() {
1112
- init_ignore();
1145
+ "use strict";
1113
1146
  init_integrity();
1147
+ init_local();
1114
1148
  init_manifest();
1115
1149
  init_resolver();
1116
1150
  init_specifier();
1117
1151
  init_version();
1118
1152
  }
1119
1153
  });
1154
+
1155
+ // src/agents.ts
1156
+ import { checkbox } from "@inquirer/prompts";
1120
1157
  function resolveAgentConfig(name, overrides) {
1121
1158
  if (overrides?.[name]) {
1122
1159
  return overrides[name];
@@ -1159,6 +1196,7 @@ async function promptForAgents() {
1159
1196
  var AGENT_INFO, DEFAULT_AGENT_CONFIGS, ALL_AGENTS;
1160
1197
  var init_agents = __esm({
1161
1198
  "src/agents.ts"() {
1199
+ "use strict";
1162
1200
  AGENT_INFO = {
1163
1201
  "claude-code": {
1164
1202
  displayName: "Claude Code",
@@ -1203,6 +1241,10 @@ var init_agents = __esm({
1203
1241
  ];
1204
1242
  }
1205
1243
  });
1244
+
1245
+ // src/github.ts
1246
+ import { cp, lstat, mkdir as mkdir2, readdir, rm, writeFile as writeFile2 } from "fs/promises";
1247
+ import { join as join3 } from "path";
1206
1248
  function getGitHubHeaders() {
1207
1249
  const headers = {
1208
1250
  Accept: "application/vnd.github+json",
@@ -1278,14 +1320,14 @@ async function downloadGitHubPackage(spec) {
1278
1320
  return { buffer, commit, integrity };
1279
1321
  }
1280
1322
  async function extractGitHubPackage(spec, buffer, skillsDir) {
1281
- const destPath = spec.path ? join(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join(skillsDir, "_github", spec.owner, spec.repo);
1282
- const tempDir = join(skillsDir, "_github", ".temp", `${Date.now()}`);
1283
- await mkdir(tempDir, { recursive: true });
1284
- const tempFile = join(tempDir, "archive.tgz");
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()}`);
1325
+ await mkdir2(tempDir, { recursive: true });
1326
+ const tempFile = join3(tempDir, "archive.tgz");
1285
1327
  try {
1286
- await writeFile(tempFile, buffer);
1287
- const { exec: exec2 } = await import('child_process');
1288
- const { promisify: promisify2 } = await import('util');
1328
+ await writeFile2(tempFile, buffer);
1329
+ const { exec: exec2 } = await import("child_process");
1330
+ const { promisify: promisify2 } = await import("util");
1289
1331
  const execAsync = promisify2(exec2);
1290
1332
  await execAsync(`tar -xzf "${tempFile}" -C "${tempDir}"`);
1291
1333
  const entries = await readdir(tempDir);
@@ -1295,16 +1337,16 @@ async function extractGitHubPackage(spec, buffer, skillsDir) {
1295
1337
  if (!extractedDir) {
1296
1338
  throw new Error("Failed to find extracted directory in tarball");
1297
1339
  }
1298
- const sourcePath = join(tempDir, extractedDir);
1299
- const copySource = spec.path ? join(sourcePath, spec.path) : sourcePath;
1340
+ const sourcePath = join3(tempDir, extractedDir);
1341
+ const copySource = spec.path ? join3(sourcePath, spec.path) : sourcePath;
1300
1342
  if (spec.path) {
1301
1343
  const pathExists = await lstat(copySource).catch(() => null);
1302
1344
  if (!pathExists) {
1303
1345
  const rootEntries = await readdir(sourcePath);
1304
1346
  const dirs = [];
1305
1347
  for (const entry of rootEntries) {
1306
- const stat7 = await lstat(join(sourcePath, entry)).catch(() => null);
1307
- if (stat7?.isDirectory() && !entry.startsWith(".")) {
1348
+ const stat8 = await lstat(join3(sourcePath, entry)).catch(() => null);
1349
+ if (stat8?.isDirectory() && !entry.startsWith(".")) {
1308
1350
  dirs.push(entry);
1309
1351
  }
1310
1352
  }
@@ -1312,7 +1354,7 @@ async function extractGitHubPackage(spec, buffer, skillsDir) {
1312
1354
  }
1313
1355
  }
1314
1356
  await rm(destPath, { recursive: true, force: true });
1315
- await mkdir(destPath, { recursive: true });
1357
+ await mkdir2(destPath, { recursive: true });
1316
1358
  await cp(copySource, destPath, { recursive: true });
1317
1359
  return spec.path ? `.pspm/skills/_github/${spec.owner}/${spec.repo}/${spec.path}` : `.pspm/skills/_github/${spec.owner}/${spec.repo}`;
1318
1360
  } finally {
@@ -1326,13 +1368,15 @@ function getGitHubDisplayName(spec, commit) {
1326
1368
  }
1327
1369
  if (spec.ref || commit) {
1328
1370
  const ref = spec.ref || "HEAD";
1329
- name += ` (${ref}${""})`;
1371
+ const shortCommit = commit ? commit.slice(0, 7) : "";
1372
+ name += ` (${ref}${shortCommit ? `@${shortCommit}` : ""})`;
1330
1373
  }
1331
1374
  return name;
1332
1375
  }
1333
1376
  var GitHubRateLimitError, GitHubNotFoundError, GitHubPathNotFoundError;
1334
1377
  var init_github = __esm({
1335
1378
  "src/github.ts"() {
1379
+ "use strict";
1336
1380
  init_lib();
1337
1381
  GitHubRateLimitError = class extends Error {
1338
1382
  constructor() {
@@ -1365,9 +1409,13 @@ Available paths in repository root:
1365
1409
  };
1366
1410
  }
1367
1411
  });
1412
+
1413
+ // src/lockfile.ts
1414
+ import { mkdir as mkdir3, readFile as readFile2, stat as stat3, writeFile as writeFile3 } from "fs/promises";
1415
+ import { dirname as dirname2 } from "path";
1368
1416
  async function hasLegacyLockfile() {
1369
1417
  try {
1370
- await stat(getLegacyLockfilePath());
1418
+ await stat3(getLegacyLockfilePath());
1371
1419
  return true;
1372
1420
  } catch {
1373
1421
  return false;
@@ -1377,24 +1425,24 @@ async function migrateLockfileIfNeeded() {
1377
1425
  const legacyPath = getLegacyLockfilePath();
1378
1426
  const newPath = getLockfilePath();
1379
1427
  try {
1380
- await stat(legacyPath);
1428
+ await stat3(legacyPath);
1381
1429
  } catch {
1382
1430
  return false;
1383
1431
  }
1384
1432
  try {
1385
- await stat(newPath);
1433
+ await stat3(newPath);
1386
1434
  return false;
1387
1435
  } catch {
1388
1436
  }
1389
1437
  try {
1390
- const content = await readFile(legacyPath, "utf-8");
1438
+ const content = await readFile2(legacyPath, "utf-8");
1391
1439
  const oldLockfile = JSON.parse(content);
1392
1440
  const newLockfile = {
1393
1441
  lockfileVersion: 2,
1394
1442
  registryUrl: oldLockfile.registryUrl,
1395
1443
  packages: oldLockfile.skills ?? {}
1396
1444
  };
1397
- await writeFile(newPath, `${JSON.stringify(newLockfile, null, 2)}
1445
+ await writeFile3(newPath, `${JSON.stringify(newLockfile, null, 2)}
1398
1446
  `);
1399
1447
  console.log("Migrated lockfile: skill-lock.json \u2192 pspm-lock.json");
1400
1448
  return true;
@@ -1405,7 +1453,7 @@ async function migrateLockfileIfNeeded() {
1405
1453
  async function readLockfile() {
1406
1454
  const lockfilePath = getLockfilePath();
1407
1455
  try {
1408
- const content = await readFile(lockfilePath, "utf-8");
1456
+ const content = await readFile2(lockfilePath, "utf-8");
1409
1457
  const lockfile = JSON.parse(content);
1410
1458
  if (lockfile.lockfileVersion === 1 && lockfile.skills && !lockfile.packages) {
1411
1459
  return {
@@ -1418,7 +1466,7 @@ async function readLockfile() {
1418
1466
  } catch {
1419
1467
  if (await hasLegacyLockfile()) {
1420
1468
  try {
1421
- const content = await readFile(getLegacyLockfilePath(), "utf-8");
1469
+ const content = await readFile2(getLegacyLockfilePath(), "utf-8");
1422
1470
  const legacyLockfile = JSON.parse(content);
1423
1471
  return {
1424
1472
  lockfileVersion: 2,
@@ -1434,12 +1482,18 @@ async function readLockfile() {
1434
1482
  }
1435
1483
  async function writeLockfile(lockfile) {
1436
1484
  const lockfilePath = getLockfilePath();
1437
- await mkdir(dirname(lockfilePath), { recursive: true });
1485
+ await mkdir3(dirname2(lockfilePath), { recursive: true });
1438
1486
  const packages = lockfile.packages ?? lockfile.skills ?? {};
1439
1487
  const hasDependencies = Object.values(packages).some(
1440
1488
  (pkg) => pkg.dependencies && Object.keys(pkg.dependencies).length > 0
1441
1489
  );
1442
- const version2 = hasDependencies ? 4 : 3;
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
+ }
1443
1497
  const normalized = {
1444
1498
  lockfileVersion: version2,
1445
1499
  registryUrl: lockfile.registryUrl,
@@ -1448,7 +1502,10 @@ async function writeLockfile(lockfile) {
1448
1502
  if (lockfile.githubPackages && Object.keys(lockfile.githubPackages).length > 0) {
1449
1503
  normalized.githubPackages = lockfile.githubPackages;
1450
1504
  }
1451
- await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
1505
+ if (hasLocalPackages) {
1506
+ normalized.localPackages = lockfile.localPackages;
1507
+ }
1508
+ await writeFile3(lockfilePath, `${JSON.stringify(normalized, null, 2)}
1452
1509
  `);
1453
1510
  }
1454
1511
  async function createEmptyLockfile() {
@@ -1541,17 +1598,52 @@ async function listLockfileGitHubPackages() {
1541
1598
  entry
1542
1599
  }));
1543
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
+ }
1544
1631
  var init_lockfile = __esm({
1545
1632
  "src/lockfile.ts"() {
1633
+ "use strict";
1546
1634
  init_config();
1547
1635
  }
1548
1636
  });
1637
+
1638
+ // src/manifest.ts
1639
+ import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1640
+ import { join as join4 } from "path";
1549
1641
  function getManifestPath() {
1550
- return join(process.cwd(), "pspm.json");
1642
+ return join4(process.cwd(), "pspm.json");
1551
1643
  }
1552
1644
  async function readManifest() {
1553
1645
  try {
1554
- const content = await readFile(getManifestPath(), "utf-8");
1646
+ const content = await readFile3(getManifestPath(), "utf-8");
1555
1647
  return JSON.parse(content);
1556
1648
  } catch {
1557
1649
  return null;
@@ -1559,7 +1651,7 @@ async function readManifest() {
1559
1651
  }
1560
1652
  async function writeManifest(manifest) {
1561
1653
  const content = JSON.stringify(manifest, null, 2);
1562
- await writeFile(getManifestPath(), `${content}
1654
+ await writeFile4(getManifestPath(), `${content}
1563
1655
  `);
1564
1656
  }
1565
1657
  async function createMinimalManifest() {
@@ -1617,10 +1709,36 @@ async function removeGitHubDependency(specifier) {
1617
1709
  await writeManifest(manifest);
1618
1710
  return true;
1619
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
+ }
1620
1733
  var init_manifest2 = __esm({
1621
1734
  "src/manifest.ts"() {
1735
+ "use strict";
1622
1736
  }
1623
1737
  });
1738
+
1739
+ // src/symlinks.ts
1740
+ import { lstat as lstat2, mkdir as mkdir4, readlink, rm as rm2, symlink } from "fs/promises";
1741
+ import { dirname as dirname3, join as join5, relative } from "path";
1624
1742
  async function createAgentSymlinks(skills, options) {
1625
1743
  const { agents, projectRoot, agentConfigs } = options;
1626
1744
  if (agents.length === 1 && agents[0] === "none") {
@@ -1632,26 +1750,26 @@ async function createAgentSymlinks(skills, options) {
1632
1750
  console.warn(`Warning: Unknown agent "${agentName}", skipping symlinks`);
1633
1751
  continue;
1634
1752
  }
1635
- const agentSkillsDir = join(projectRoot, config2.skillsDir);
1636
- await mkdir(agentSkillsDir, { recursive: true });
1753
+ const agentSkillsDir = join5(projectRoot, config2.skillsDir);
1754
+ await mkdir4(agentSkillsDir, { recursive: true });
1637
1755
  for (const skill of skills) {
1638
- const symlinkPath = join(agentSkillsDir, skill.name);
1639
- const targetPath = join(projectRoot, skill.sourcePath);
1640
- const relativeTarget = relative(dirname(symlinkPath), targetPath);
1756
+ const symlinkPath = join5(agentSkillsDir, skill.name);
1757
+ const targetPath = join5(projectRoot, skill.sourcePath);
1758
+ const relativeTarget = relative(dirname3(symlinkPath), targetPath);
1641
1759
  await createSymlink(symlinkPath, relativeTarget, skill.name);
1642
1760
  }
1643
1761
  }
1644
1762
  }
1645
1763
  async function createSymlink(symlinkPath, target, skillName) {
1646
1764
  try {
1647
- const stats = await lstat(symlinkPath).catch(() => null);
1765
+ const stats = await lstat2(symlinkPath).catch(() => null);
1648
1766
  if (stats) {
1649
1767
  if (stats.isSymbolicLink()) {
1650
1768
  const existingTarget = await readlink(symlinkPath);
1651
1769
  if (existingTarget === target) {
1652
1770
  return;
1653
1771
  }
1654
- await rm(symlinkPath);
1772
+ await rm2(symlinkPath);
1655
1773
  } else {
1656
1774
  console.warn(
1657
1775
  `Warning: File exists at symlink path for "${skillName}", skipping: ${symlinkPath}`
@@ -1677,11 +1795,11 @@ async function removeAgentSymlinks(skillName, options) {
1677
1795
  if (!config2) {
1678
1796
  continue;
1679
1797
  }
1680
- const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
1798
+ const symlinkPath = join5(projectRoot, config2.skillsDir, skillName);
1681
1799
  try {
1682
- const stats = await lstat(symlinkPath).catch(() => null);
1800
+ const stats = await lstat2(symlinkPath).catch(() => null);
1683
1801
  if (stats?.isSymbolicLink()) {
1684
- await rm(symlinkPath);
1802
+ await rm2(symlinkPath);
1685
1803
  }
1686
1804
  } catch {
1687
1805
  }
@@ -1696,14 +1814,17 @@ function getGitHubSkillPath(owner, repo, path) {
1696
1814
  }
1697
1815
  return `.pspm/skills/_github/${owner}/${repo}`;
1698
1816
  }
1817
+ function getLocalSkillPath(skillName) {
1818
+ return `.pspm/skills/_local/${skillName}`;
1819
+ }
1699
1820
  async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
1700
1821
  const linkedAgents = [];
1701
1822
  for (const agentName of agents) {
1702
1823
  const config2 = resolveAgentConfig(agentName, agentConfigs);
1703
1824
  if (!config2) continue;
1704
- const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
1825
+ const symlinkPath = join5(projectRoot, config2.skillsDir, skillName);
1705
1826
  try {
1706
- const stats = await lstat(symlinkPath);
1827
+ const stats = await lstat2(symlinkPath);
1707
1828
  if (stats.isSymbolicLink()) {
1708
1829
  linkedAgents.push(agentName);
1709
1830
  }
@@ -1714,6 +1835,7 @@ async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
1714
1835
  }
1715
1836
  var init_symlinks = __esm({
1716
1837
  "src/symlinks.ts"() {
1838
+ "use strict";
1717
1839
  init_agents();
1718
1840
  }
1719
1841
  });
@@ -1723,13 +1845,21 @@ var add_exports = {};
1723
1845
  __export(add_exports, {
1724
1846
  add: () => add
1725
1847
  });
1848
+ import { mkdir as mkdir5, rm as rm3, symlink as symlink2 } from "fs/promises";
1849
+ import { dirname as dirname4, join as join6 } from "path";
1726
1850
  async function add(specifiers, options) {
1727
1851
  console.log("Resolving packages...\n");
1728
1852
  const resolvedPackages = [];
1729
1853
  const validationErrors = [];
1730
- for (const specifier of specifiers) {
1854
+ for (let specifier of specifiers) {
1731
1855
  try {
1732
- 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)) {
1733
1863
  const resolved = await validateGitHubPackage(specifier);
1734
1864
  resolvedPackages.push(resolved);
1735
1865
  } else {
@@ -1761,6 +1891,9 @@ async function add(specifiers, options) {
1761
1891
  const githubPackages = resolvedPackages.filter(
1762
1892
  (p) => p.type === "github"
1763
1893
  );
1894
+ const localPackages = resolvedPackages.filter(
1895
+ (p) => p.type === "local"
1896
+ );
1764
1897
  let resolutionResult = null;
1765
1898
  if (registryPackages.length > 0) {
1766
1899
  const rootDeps = {};
@@ -1845,6 +1978,24 @@ async function add(specifiers, options) {
1845
1978
  error: message
1846
1979
  });
1847
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
+ });
1990
+ results.push({ specifier: resolved.specifier, success: true });
1991
+ } catch (error) {
1992
+ const message = error instanceof Error ? error.message : "Unknown error";
1993
+ results.push({
1994
+ specifier: resolved.specifier,
1995
+ success: false,
1996
+ error: message
1997
+ });
1998
+ console.error(`Failed to install ${resolved.specifier}: ${message}
1848
1999
  `);
1849
2000
  }
1850
2001
  }
@@ -1885,23 +2036,23 @@ async function installFromNode(node, options) {
1885
2036
  throw new Error("Checksum verification failed");
1886
2037
  }
1887
2038
  const skillsDir = getSkillsDir();
1888
- const destDir = join(skillsDir, username, name);
1889
- await mkdir(destDir, { recursive: true });
1890
- const { writeFile: writeFile8 } = await import('fs/promises');
1891
- const tempFile = join(destDir, ".temp.tgz");
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");
1892
2043
  await writeFile8(tempFile, tarballBuffer);
1893
- const { exec: exec2 } = await import('child_process');
1894
- const { promisify: promisify2 } = await import('util');
2044
+ const { exec: exec2 } = await import("child_process");
2045
+ const { promisify: promisify2 } = await import("util");
1895
2046
  const execAsync = promisify2(exec2);
1896
2047
  try {
1897
- await rm(destDir, { recursive: true, force: true });
1898
- await mkdir(destDir, { recursive: true });
2048
+ await rm3(destDir, { recursive: true, force: true });
2049
+ await mkdir5(destDir, { recursive: true });
1899
2050
  await writeFile8(tempFile, tarballBuffer);
1900
2051
  await execAsync(
1901
2052
  `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
1902
2053
  );
1903
2054
  } finally {
1904
- await rm(tempFile, { force: true });
2055
+ await rm3(tempFile, { force: true });
1905
2056
  }
1906
2057
  const resolvedDeps = {};
1907
2058
  for (const [depName, _range] of Object.entries(node.dependencies)) {
@@ -2069,8 +2220,70 @@ async function installGitHubPackage(resolved, options) {
2069
2220
  );
2070
2221
  console.log(`Location: ${destPath}`);
2071
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
+ }
2072
2284
  var init_add = __esm({
2073
2285
  "src/commands/add.ts"() {
2286
+ "use strict";
2074
2287
  init_agents();
2075
2288
  init_api_client();
2076
2289
  init_config();
@@ -2083,11 +2296,17 @@ var init_add = __esm({
2083
2296
  }
2084
2297
  });
2085
2298
 
2299
+ // src/index.ts
2300
+ import { readFileSync } from "fs";
2301
+ import { dirname as dirname6, join as join13 } from "path";
2302
+ import { fileURLToPath } from "url";
2303
+ import { Command } from "commander";
2304
+
2086
2305
  // src/commands/access.ts
2087
2306
  init_api_client();
2088
2307
  init_config();
2089
2308
  init_lib();
2090
- async function access(specifier, options) {
2309
+ async function access2(specifier, options) {
2091
2310
  try {
2092
2311
  const apiKey = await requireApiKey();
2093
2312
  const registryUrl = await getRegistryUrl();
@@ -2111,18 +2330,18 @@ async function access(specifier, options) {
2111
2330
  }
2112
2331
  packageName = parsed.name;
2113
2332
  } else {
2114
- const { readFile: readFile8 } = await import('fs/promises');
2115
- const { join: join14 } = await import('path');
2333
+ const { readFile: readFile7 } = await import("fs/promises");
2334
+ const { join: join14 } = await import("path");
2116
2335
  let manifest = null;
2117
2336
  try {
2118
- const content = await readFile8(
2337
+ const content = await readFile7(
2119
2338
  join14(process.cwd(), "pspm.json"),
2120
2339
  "utf-8"
2121
2340
  );
2122
2341
  manifest = JSON.parse(content);
2123
2342
  } catch {
2124
2343
  try {
2125
- const content = await readFile8(
2344
+ const content = await readFile7(
2126
2345
  join14(process.cwd(), "package.json"),
2127
2346
  "utf-8"
2128
2347
  );
@@ -2170,11 +2389,15 @@ async function access(specifier, options) {
2170
2389
 
2171
2390
  // src/commands/index.ts
2172
2391
  init_add();
2392
+
2393
+ // src/commands/config/init.ts
2394
+ import { stat as stat4, writeFile as writeFile5 } from "fs/promises";
2395
+ import { join as join7 } from "path";
2173
2396
  async function configInit(options) {
2174
2397
  try {
2175
- const configPath = join(process.cwd(), ".pspmrc");
2398
+ const configPath = join7(process.cwd(), ".pspmrc");
2176
2399
  try {
2177
- await stat(configPath);
2400
+ await stat4(configPath);
2178
2401
  console.error("Error: .pspmrc already exists in this directory.");
2179
2402
  process.exit(1);
2180
2403
  } catch {
@@ -2187,7 +2410,7 @@ async function configInit(options) {
2187
2410
  lines.push("; registry = https://custom-registry.example.com");
2188
2411
  }
2189
2412
  lines.push("");
2190
- await writeFile(configPath, lines.join("\n"));
2413
+ await writeFile5(configPath, lines.join("\n"));
2191
2414
  console.log("Created .pspmrc");
2192
2415
  console.log("");
2193
2416
  console.log("Contents:");
@@ -2300,18 +2523,21 @@ async function deprecate(specifier, message, options) {
2300
2523
 
2301
2524
  // src/commands/init.ts
2302
2525
  init_lib();
2526
+ import { readFile as readFile4, stat as stat5, writeFile as writeFile6 } from "fs/promises";
2527
+ import { basename, join as join8 } from "path";
2528
+ import { createInterface } from "readline";
2303
2529
  function prompt(rl, question, defaultValue) {
2304
- return new Promise((resolve) => {
2530
+ return new Promise((resolve2) => {
2305
2531
  const displayDefault = defaultValue ? ` (${defaultValue})` : "";
2306
2532
  rl.question(`${question}${displayDefault} `, (answer) => {
2307
- resolve(answer.trim() || defaultValue);
2533
+ resolve2(answer.trim() || defaultValue);
2308
2534
  });
2309
2535
  });
2310
2536
  }
2311
2537
  async function readExistingPackageJson() {
2312
2538
  try {
2313
- const content = await readFile(
2314
- join(process.cwd(), "package.json"),
2539
+ const content = await readFile4(
2540
+ join8(process.cwd(), "package.json"),
2315
2541
  "utf-8"
2316
2542
  );
2317
2543
  const pkg = JSON.parse(content);
@@ -2328,8 +2554,8 @@ async function readExistingPackageJson() {
2328
2554
  }
2329
2555
  async function getGitAuthor() {
2330
2556
  try {
2331
- const { exec: exec2 } = await import('child_process');
2332
- const { promisify: promisify2 } = await import('util');
2557
+ const { exec: exec2 } = await import("child_process");
2558
+ const { promisify: promisify2 } = await import("util");
2333
2559
  const execAsync = promisify2(exec2);
2334
2560
  const [nameResult, emailResult] = await Promise.all([
2335
2561
  execAsync("git config user.name").catch(() => ({ stdout: "" })),
@@ -2360,10 +2586,10 @@ function isValidVersion(version2) {
2360
2586
  }
2361
2587
  async function init(options) {
2362
2588
  try {
2363
- const pspmJsonPath = join(process.cwd(), "pspm.json");
2589
+ const pspmJsonPath = join8(process.cwd(), "pspm.json");
2364
2590
  let exists = false;
2365
2591
  try {
2366
- await stat(pspmJsonPath);
2592
+ await stat5(pspmJsonPath);
2367
2593
  exists = true;
2368
2594
  } catch {
2369
2595
  }
@@ -2495,10 +2721,10 @@ async function init(options) {
2495
2721
  process.exit(0);
2496
2722
  }
2497
2723
  }
2498
- await writeFile(pspmJsonPath, `${content}
2724
+ await writeFile6(pspmJsonPath, `${content}
2499
2725
  `);
2500
2726
  try {
2501
- await stat(join(process.cwd(), "SKILL.md"));
2727
+ await stat5(join8(process.cwd(), "SKILL.md"));
2502
2728
  } catch {
2503
2729
  console.log(
2504
2730
  "Note: Create a SKILL.md file with your skill's prompt content."
@@ -2526,6 +2752,16 @@ init_lib();
2526
2752
  init_lockfile();
2527
2753
  init_manifest2();
2528
2754
  init_symlinks();
2755
+ import { createHash as createHash2 } from "crypto";
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";
2529
2765
  function getCacheFilePath(cacheDir, integrity) {
2530
2766
  const match = integrity.match(/^sha256-(.+)$/);
2531
2767
  if (!match) {
@@ -2533,15 +2769,15 @@ function getCacheFilePath(cacheDir, integrity) {
2533
2769
  }
2534
2770
  const base64Hash = match[1];
2535
2771
  const hexHash = Buffer.from(base64Hash, "base64").toString("hex");
2536
- return join(cacheDir, `sha256-${hexHash}.tgz`);
2772
+ return join9(cacheDir, `sha256-${hexHash}.tgz`);
2537
2773
  }
2538
2774
  async function readFromCache(cacheDir, integrity) {
2539
2775
  try {
2540
2776
  const cachePath = getCacheFilePath(cacheDir, integrity);
2541
- const data = await readFile(cachePath);
2542
- const actualIntegrity = `sha256-${createHash("sha256").update(data).digest("base64")}`;
2777
+ const data = await readFile5(cachePath);
2778
+ const actualIntegrity = `sha256-${createHash2("sha256").update(data).digest("base64")}`;
2543
2779
  if (actualIntegrity !== integrity) {
2544
- await rm(cachePath, { force: true });
2780
+ await rm4(cachePath, { force: true });
2545
2781
  return null;
2546
2782
  }
2547
2783
  return data;
@@ -2551,9 +2787,9 @@ async function readFromCache(cacheDir, integrity) {
2551
2787
  }
2552
2788
  async function writeToCache(cacheDir, integrity, data) {
2553
2789
  try {
2554
- await mkdir(cacheDir, { recursive: true });
2790
+ await mkdir6(cacheDir, { recursive: true });
2555
2791
  const cachePath = getCacheFilePath(cacheDir, integrity);
2556
- await writeFile(cachePath, data);
2792
+ await writeFile7(cachePath, data);
2557
2793
  } catch {
2558
2794
  }
2559
2795
  }
@@ -2580,8 +2816,10 @@ async function installFromLockfile(options) {
2580
2816
  let lockfile = await readLockfile();
2581
2817
  const manifestDeps = await getDependencies();
2582
2818
  const manifestGitHubDeps = await getGitHubDependencies();
2819
+ const manifestLocalDeps = await getLocalDependencies();
2583
2820
  const lockfilePackages = lockfile?.packages ?? lockfile?.skills ?? {};
2584
2821
  const lockfileGitHubPackages = lockfile?.githubPackages ?? {};
2822
+ const lockfileLocalPackages = lockfile?.localPackages ?? {};
2585
2823
  const installedSkills = [];
2586
2824
  const missingDeps = [];
2587
2825
  for (const [fullName, versionRange] of Object.entries(manifestDeps)) {
@@ -2732,6 +2970,53 @@ Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
2732
2970
  }
2733
2971
  lockfile = await readLockfile();
2734
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
+ }
2735
3020
  const manifest = await readManifest();
2736
3021
  const agentConfigs = manifest?.agents;
2737
3022
  let agents;
@@ -2797,7 +3082,7 @@ Installing ${packageCount} registry skill(s)...
2797
3082
  continue;
2798
3083
  }
2799
3084
  tarballBuffer = Buffer.from(await response.arrayBuffer());
2800
- const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
3085
+ const actualIntegrity = `sha256-${createHash2("sha256").update(tarballBuffer).digest("base64")}`;
2801
3086
  if (actualIntegrity !== entry.integrity) {
2802
3087
  console.error(
2803
3088
  ` Error: Checksum verification failed for ${fullName}`
@@ -2809,20 +3094,20 @@ Installing ${packageCount} registry skill(s)...
2809
3094
  }
2810
3095
  await writeToCache(cacheDir, entry.integrity, tarballBuffer);
2811
3096
  }
2812
- const destDir = join(skillsDir, username, name);
2813
- await rm(destDir, { recursive: true, force: true });
2814
- await mkdir(destDir, { recursive: true });
2815
- const tempFile = join(destDir, ".temp.tgz");
2816
- await writeFile(tempFile, tarballBuffer);
2817
- const { exec: exec2 } = await import('child_process');
2818
- const { promisify: promisify2 } = await import('util');
3097
+ const destDir = join9(skillsDir, username, name);
3098
+ await rm4(destDir, { recursive: true, force: true });
3099
+ await mkdir6(destDir, { recursive: true });
3100
+ const tempFile = join9(destDir, ".temp.tgz");
3101
+ await writeFile7(tempFile, tarballBuffer);
3102
+ const { exec: exec2 } = await import("child_process");
3103
+ const { promisify: promisify2 } = await import("util");
2819
3104
  const execAsync = promisify2(exec2);
2820
3105
  try {
2821
3106
  await execAsync(
2822
3107
  `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
2823
3108
  );
2824
3109
  } finally {
2825
- await rm(tempFile, { force: true });
3110
+ await rm4(tempFile, { force: true });
2826
3111
  }
2827
3112
  console.log(
2828
3113
  ` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
@@ -2910,6 +3195,52 @@ Installing ${githubCount} GitHub skill(s)...
2910
3195
  }
2911
3196
  }
2912
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
+ }
2913
3244
  if (installedSkills.length > 0 && agents[0] !== "none") {
2914
3245
  console.log(`
2915
3246
  Creating symlinks for agent(s): ${agents.join(", ")}...`);
@@ -2920,7 +3251,7 @@ Creating symlinks for agent(s): ${agents.join(", ")}...`);
2920
3251
  });
2921
3252
  console.log(" Symlinks created.");
2922
3253
  }
2923
- const totalCount = packageCount + githubCount;
3254
+ const totalCount = packageCount + githubCount + localCount;
2924
3255
  if (totalCount === 0) {
2925
3256
  console.log("No skills to install.");
2926
3257
  } else {
@@ -3017,6 +3348,8 @@ init_lib();
3017
3348
  init_lockfile();
3018
3349
  init_manifest2();
3019
3350
  init_symlinks();
3351
+ import { access as access3 } from "fs/promises";
3352
+ import { join as join10 } from "path";
3020
3353
  async function list(options) {
3021
3354
  try {
3022
3355
  const registrySkills = await listLockfileSkills();
@@ -3031,10 +3364,10 @@ async function list(options) {
3031
3364
  if (!match) continue;
3032
3365
  const [, username, skillName] = match;
3033
3366
  const sourcePath = getRegistrySkillPath(username, skillName);
3034
- const absolutePath = join(projectRoot, sourcePath);
3367
+ const absolutePath = join10(projectRoot, sourcePath);
3035
3368
  let status = "installed";
3036
3369
  try {
3037
- await access$1(absolutePath);
3370
+ await access3(absolutePath);
3038
3371
  } catch {
3039
3372
  status = "missing";
3040
3373
  }
@@ -3064,10 +3397,10 @@ async function list(options) {
3064
3397
  parsed.repo,
3065
3398
  parsed.path
3066
3399
  );
3067
- const absolutePath = join(projectRoot, sourcePath);
3400
+ const absolutePath = join10(projectRoot, sourcePath);
3068
3401
  let status = "installed";
3069
3402
  try {
3070
- await access$1(absolutePath);
3403
+ await access3(absolutePath);
3071
3404
  } catch {
3072
3405
  status = "missing";
3073
3406
  }
@@ -3089,6 +3422,35 @@ async function list(options) {
3089
3422
  gitCommit: ghEntry.gitCommit
3090
3423
  });
3091
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
+ }
3092
3454
  if (skills.length === 0) {
3093
3455
  console.log("No skills installed.");
3094
3456
  return;
@@ -3101,9 +3463,14 @@ async function list(options) {
3101
3463
  for (const skill of skills) {
3102
3464
  if (skill.source === "registry") {
3103
3465
  console.log(` ${skill.fullName}@${skill.version} (registry)`);
3104
- } else {
3466
+ } else if (skill.source === "github") {
3105
3467
  const refInfo = skill.gitRef ? `${skill.gitRef}@${skill.gitCommit?.slice(0, 7)}` : skill.version;
3106
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
+ }
3107
3474
  }
3108
3475
  if (skill.status === "missing") {
3109
3476
  console.log(` Status: MISSING (run 'pspm install' to restore)`);
@@ -3119,9 +3486,11 @@ async function list(options) {
3119
3486
  }
3120
3487
  const registryCount = skills.filter((s) => s.source === "registry").length;
3121
3488
  const githubCount = skills.filter((s) => s.source === "github").length;
3489
+ const localCount = skills.filter((s) => s.source === "local").length;
3122
3490
  const parts = [];
3123
3491
  if (registryCount > 0) parts.push(`${registryCount} registry`);
3124
3492
  if (githubCount > 0) parts.push(`${githubCount} github`);
3493
+ if (localCount > 0) parts.push(`${localCount} local`);
3125
3494
  console.log(`
3126
3495
  Total: ${skills.length} skill(s) (${parts.join(", ")})`);
3127
3496
  } catch (error) {
@@ -3134,13 +3503,17 @@ Total: ${skills.length} skill(s) (${parts.join(", ")})`);
3134
3503
  // src/commands/login.ts
3135
3504
  init_api_client();
3136
3505
  init_config();
3506
+ import { randomBytes } from "crypto";
3507
+ import http from "http";
3508
+ import { URL as URL2 } from "url";
3509
+ import open from "open";
3137
3510
  var DEFAULT_WEB_APP_URL = "https://pspm.dev";
3138
3511
  function getWebAppUrl(registryUrl) {
3139
3512
  if (process.env.PSPM_WEB_URL) {
3140
3513
  return process.env.PSPM_WEB_URL.replace(/\/$/, "");
3141
3514
  }
3142
3515
  try {
3143
- const url = new URL$1(registryUrl);
3516
+ const url = new URL2(registryUrl);
3144
3517
  return `${url.protocol}//${url.host}`;
3145
3518
  } catch {
3146
3519
  return DEFAULT_WEB_APP_URL;
@@ -3148,7 +3521,7 @@ function getWebAppUrl(registryUrl) {
3148
3521
  }
3149
3522
  function getServerUrl(registryUrl) {
3150
3523
  try {
3151
- const url = new URL$1(registryUrl);
3524
+ const url = new URL2(registryUrl);
3152
3525
  return `${url.protocol}//${url.host}`;
3153
3526
  } catch {
3154
3527
  return DEFAULT_WEB_APP_URL;
@@ -3175,12 +3548,12 @@ function startCallbackServer(expectedState) {
3175
3548
  let resolveToken;
3176
3549
  let rejectToken;
3177
3550
  let timeoutId;
3178
- const tokenPromise = new Promise((resolve, reject) => {
3179
- resolveToken = resolve;
3551
+ const tokenPromise = new Promise((resolve2, reject) => {
3552
+ resolveToken = resolve2;
3180
3553
  rejectToken = reject;
3181
3554
  });
3182
3555
  const server = http.createServer((req, res) => {
3183
- const url = new URL$1(req.url || "/", "http://localhost");
3556
+ const url = new URL2(req.url || "/", "http://localhost");
3184
3557
  if (url.pathname === "/callback") {
3185
3558
  const token = url.searchParams.get("token");
3186
3559
  const state = url.searchParams.get("state");
@@ -3331,6 +3704,7 @@ async function logout() {
3331
3704
  // src/commands/migrate.ts
3332
3705
  init_config();
3333
3706
  init_lockfile();
3707
+ import { mkdir as mkdir7, readdir as readdir2, rename, rm as rm5, stat as stat6 } from "fs/promises";
3334
3708
  async function migrate(options) {
3335
3709
  try {
3336
3710
  const legacySkillsDir = getLegacySkillsDir();
@@ -3341,9 +3715,9 @@ async function migrate(options) {
3341
3715
  let migrationNeeded = false;
3342
3716
  const actions = [];
3343
3717
  try {
3344
- const legacyStats = await stat(legacySkillsDir);
3718
+ const legacyStats = await stat6(legacySkillsDir);
3345
3719
  if (legacyStats.isDirectory()) {
3346
- const contents = await readdir(legacySkillsDir);
3720
+ const contents = await readdir2(legacySkillsDir);
3347
3721
  if (contents.length > 0) {
3348
3722
  migrationNeeded = true;
3349
3723
  actions.push("Move .skills/ \u2192 .pspm/skills/");
@@ -3352,9 +3726,9 @@ async function migrate(options) {
3352
3726
  } catch {
3353
3727
  }
3354
3728
  try {
3355
- await stat(legacyLockfilePath);
3729
+ await stat6(legacyLockfilePath);
3356
3730
  try {
3357
- await stat(newLockfilePath);
3731
+ await stat6(newLockfilePath);
3358
3732
  actions.push(
3359
3733
  "Note: Both skill-lock.json and pspm-lock.json exist. Manual merge may be needed."
3360
3734
  );
@@ -3386,21 +3760,21 @@ async function migrate(options) {
3386
3760
  console.log(" \u2713 Migrated skill-lock.json \u2192 pspm-lock.json");
3387
3761
  }
3388
3762
  try {
3389
- const legacyStats = await stat(legacySkillsDir);
3763
+ const legacyStats = await stat6(legacySkillsDir);
3390
3764
  if (legacyStats.isDirectory()) {
3391
- const contents = await readdir(legacySkillsDir);
3765
+ const contents = await readdir2(legacySkillsDir);
3392
3766
  if (contents.length > 0) {
3393
- await mkdir(pspmDir, { recursive: true });
3767
+ await mkdir7(pspmDir, { recursive: true });
3394
3768
  try {
3395
- const newStats = await stat(newSkillsDir);
3769
+ const newStats = await stat6(newSkillsDir);
3396
3770
  if (newStats.isDirectory()) {
3397
- const newContents = await readdir(newSkillsDir);
3771
+ const newContents = await readdir2(newSkillsDir);
3398
3772
  if (newContents.length > 0) {
3399
3773
  console.log(
3400
3774
  " ! Both .skills/ and .pspm/skills/ have content. Manual merge required."
3401
3775
  );
3402
3776
  } else {
3403
- await rm(newSkillsDir, { recursive: true, force: true });
3777
+ await rm5(newSkillsDir, { recursive: true, force: true });
3404
3778
  await rename(legacySkillsDir, newSkillsDir);
3405
3779
  console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
3406
3780
  }
@@ -3436,19 +3810,24 @@ init_api_client();
3436
3810
  init_config();
3437
3811
  init_errors();
3438
3812
  init_lib();
3439
- var exec = promisify(exec$1);
3813
+ import { exec as execCb } from "child_process";
3814
+ import { createHash as createHash3 } from "crypto";
3815
+ import { readdir as readdir3, readFile as readFile6, stat as stat7 } from "fs/promises";
3816
+ import { join as join11, relative as relative3 } from "path";
3817
+ import { promisify } from "util";
3818
+ var exec = promisify(execCb);
3440
3819
  async function detectManifest() {
3441
3820
  const cwd = process.cwd();
3442
- const pspmJsonPath = join(cwd, "pspm.json");
3821
+ const pspmJsonPath = join11(cwd, "pspm.json");
3443
3822
  try {
3444
- const content = await readFile(pspmJsonPath, "utf-8");
3823
+ const content = await readFile6(pspmJsonPath, "utf-8");
3445
3824
  const manifest = JSON.parse(content);
3446
3825
  return { type: "pspm.json", manifest, path: pspmJsonPath };
3447
3826
  } catch {
3448
3827
  }
3449
- const packageJsonPath = join(cwd, "package.json");
3828
+ const packageJsonPath = join11(cwd, "package.json");
3450
3829
  try {
3451
- const content = await readFile(packageJsonPath, "utf-8");
3830
+ const content = await readFile6(packageJsonPath, "utf-8");
3452
3831
  const packageJson2 = JSON.parse(content);
3453
3832
  const manifest = {
3454
3833
  name: packageJson2.name,
@@ -3468,31 +3847,21 @@ function formatBytes(bytes) {
3468
3847
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
3469
3848
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
3470
3849
  }
3471
- async function getFilesWithSizes(dir, baseDir, ignoreResult) {
3850
+ async function getFilesWithSizes(dir, baseDir) {
3472
3851
  const results = [];
3473
3852
  try {
3474
- const entries = await readdir(dir, { withFileTypes: true });
3853
+ const entries = await readdir3(dir, { withFileTypes: true });
3475
3854
  for (const entry of entries) {
3476
- const fullPath = join(dir, entry.name);
3477
- const relativePath = relative(baseDir, fullPath);
3478
- if (ALWAYS_IGNORED.includes(entry.name)) {
3855
+ const fullPath = join11(dir, entry.name);
3856
+ const relativePath = relative3(baseDir, fullPath);
3857
+ if (entry.name === "node_modules" || entry.name === ".git") {
3479
3858
  continue;
3480
3859
  }
3481
- if (ignoreResult?.ig) {
3482
- const pathToCheck = entry.isDirectory() ? `${relativePath}/` : relativePath;
3483
- if (ignoreResult.ig.ignores(pathToCheck)) {
3484
- continue;
3485
- }
3486
- }
3487
3860
  if (entry.isDirectory()) {
3488
- const subFiles = await getFilesWithSizes(
3489
- fullPath,
3490
- baseDir,
3491
- ignoreResult
3492
- );
3861
+ const subFiles = await getFilesWithSizes(fullPath, baseDir);
3493
3862
  results.push(...subFiles);
3494
3863
  } else {
3495
- const fileStat = await stat(fullPath);
3864
+ const fileStat = await stat7(fullPath);
3496
3865
  results.push({ path: relativePath, size: fileStat.size });
3497
3866
  }
3498
3867
  }
@@ -3525,7 +3894,7 @@ async function publishCommand(options) {
3525
3894
  files: manifest.files
3526
3895
  };
3527
3896
  if (options.bump) {
3528
- const semver2 = await import('semver');
3897
+ const semver2 = await import("semver");
3529
3898
  const newVersion = semver2.default.inc(packageJson2.version, options.bump);
3530
3899
  if (!newVersion) {
3531
3900
  console.error(
@@ -3536,16 +3905,9 @@ async function publishCommand(options) {
3536
3905
  packageJson2.version = newVersion;
3537
3906
  console.log(`Bumped version to ${newVersion}`);
3538
3907
  }
3539
- const ignoreResult = await loadIgnorePatterns();
3540
- if (ignoreResult.source) {
3541
- console.log(
3542
- `pspm notice Using ${ignoreResult.source} for ignore patterns`
3543
- );
3544
- }
3545
- const excludeArgs = getExcludeArgsForRsync(ignoreResult.patterns);
3546
3908
  const safeName = packageJson2.name.replace(/[@/]/g, "-").replace(/^-+/, "");
3547
3909
  const tarballName = `${safeName}-${packageJson2.version}.tgz`;
3548
- const tempDir = join(process.cwd(), ".pspm-publish");
3910
+ const tempDir = join11(process.cwd(), ".pspm-publish");
3549
3911
  try {
3550
3912
  await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
3551
3913
  const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
@@ -3553,7 +3915,7 @@ async function publishCommand(options) {
3553
3915
  for (const file of files) {
3554
3916
  try {
3555
3917
  await exec(
3556
- `rsync -a ${excludeArgs} "${file}" "${tempDir}/package/" 2>/dev/null || true`
3918
+ `rsync -a --exclude='node_modules' --exclude='.git' "${file}" "${tempDir}/package/" 2>/dev/null || true`
3557
3919
  );
3558
3920
  } catch {
3559
3921
  }
@@ -3561,7 +3923,7 @@ async function publishCommand(options) {
3561
3923
  if (detection.type === "pspm.json") {
3562
3924
  await exec(`cp pspm.json "${tempDir}/package/"`);
3563
3925
  try {
3564
- await stat(join(process.cwd(), "package.json"));
3926
+ await stat7(join11(process.cwd(), "package.json"));
3565
3927
  await exec(
3566
3928
  `cp package.json "${tempDir}/package/" 2>/dev/null || true`
3567
3929
  );
@@ -3570,22 +3932,18 @@ async function publishCommand(options) {
3570
3932
  } else {
3571
3933
  await exec(`cp package.json "${tempDir}/package/"`);
3572
3934
  }
3573
- const packageDir = join(tempDir, "package");
3574
- const tarballContents = await getFilesWithSizes(
3575
- packageDir,
3576
- packageDir,
3577
- ignoreResult
3578
- );
3935
+ const packageDir = join11(tempDir, "package");
3936
+ const tarballContents = await getFilesWithSizes(packageDir, packageDir);
3579
3937
  const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
3580
- const tarballPath = join(tempDir, tarballName);
3938
+ const tarballPath = join11(tempDir, tarballName);
3581
3939
  await exec(
3582
- `tar -czf "${tarballPath}" -C "${tempDir}" ${excludeArgs} package`
3940
+ `tar -czf "${tarballPath}" -C "${tempDir}" --exclude='node_modules' --exclude='.git' package`
3583
3941
  );
3584
- const tarballBuffer = await readFile(tarballPath);
3942
+ const tarballBuffer = await readFile6(tarballPath);
3585
3943
  const tarballBase64 = tarballBuffer.toString("base64");
3586
3944
  const tarballSize = tarballBuffer.length;
3587
- const shasum = createHash("sha1").update(tarballBuffer).digest("hex");
3588
- const integrityHash = createHash("sha512").update(tarballBuffer).digest("base64");
3945
+ const shasum = createHash3("sha1").update(tarballBuffer).digest("hex");
3946
+ const integrityHash = createHash3("sha512").update(tarballBuffer).digest("base64");
3589
3947
  const integrity = `sha512-${integrityHash}`;
3590
3948
  console.log("");
3591
3949
  console.log("pspm notice");
@@ -3670,12 +4028,16 @@ init_lib();
3670
4028
  init_lockfile();
3671
4029
  init_manifest2();
3672
4030
  init_symlinks();
4031
+ import { rm as rm6 } from "fs/promises";
4032
+ import { join as join12 } from "path";
3673
4033
  async function remove(nameOrSpecifier) {
3674
4034
  try {
3675
4035
  const manifest = await readManifest();
3676
4036
  const agentConfigs = manifest?.agents;
3677
4037
  const agents = getAvailableAgents(agentConfigs);
3678
- if (isGitHubSpecifier(nameOrSpecifier)) {
4038
+ if (isLocalSpecifier(nameOrSpecifier)) {
4039
+ await removeLocal(nameOrSpecifier, agents, agentConfigs);
4040
+ } else if (isGitHubSpecifier(nameOrSpecifier)) {
3679
4041
  await removeGitHub(nameOrSpecifier, agents, agentConfigs);
3680
4042
  } else if (nameOrSpecifier.startsWith("@user/")) {
3681
4043
  await removeRegistry(nameOrSpecifier, agents, agentConfigs);
@@ -3710,9 +4072,9 @@ async function removeRegistry(specifier, agents, agentConfigs) {
3710
4072
  agentConfigs
3711
4073
  });
3712
4074
  const skillsDir = getSkillsDir();
3713
- const destDir = join(skillsDir, username, name);
4075
+ const destDir = join12(skillsDir, username, name);
3714
4076
  try {
3715
- await rm(destDir, { recursive: true, force: true });
4077
+ await rm6(destDir, { recursive: true, force: true });
3716
4078
  } catch {
3717
4079
  }
3718
4080
  console.log(`Removed ${fullName}`);
@@ -3739,13 +4101,42 @@ async function removeGitHub(specifier, agents, agentConfigs) {
3739
4101
  });
3740
4102
  const skillsDir = getSkillsDir();
3741
4103
  const destPath = getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path);
3742
- const destDir = join(skillsDir, "..", destPath);
4104
+ const destDir = join12(skillsDir, "..", destPath);
3743
4105
  try {
3744
- await rm(destDir, { recursive: true, force: true });
4106
+ await rm6(destDir, { recursive: true, force: true });
3745
4107
  } catch {
3746
4108
  }
3747
4109
  console.log(`Removed ${lockfileKey}`);
3748
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
+ }
3749
4140
  async function removeByShortName(shortName, agents, agentConfigs) {
3750
4141
  const registrySkills = await listLockfileSkills();
3751
4142
  const foundRegistry = registrySkills.find((s) => {
@@ -3766,6 +4157,12 @@ async function removeByShortName(shortName, agents, agentConfigs) {
3766
4157
  await removeGitHub(foundGitHub.specifier, agents, agentConfigs);
3767
4158
  return;
3768
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
+ }
3769
4166
  console.error(`Error: Skill "${shortName}" not found in lockfile`);
3770
4167
  process.exit(1);
3771
4168
  }
@@ -3938,9 +4335,9 @@ async function whoami() {
3938
4335
  }
3939
4336
 
3940
4337
  // src/index.ts
3941
- var __dirname$1 = dirname(fileURLToPath(import.meta.url));
4338
+ var __dirname = dirname6(fileURLToPath(import.meta.url));
3942
4339
  var packageJson = JSON.parse(
3943
- readFileSync(join(__dirname$1, "..", "package.json"), "utf-8")
4340
+ readFileSync(join13(__dirname, "..", "package.json"), "utf-8")
3944
4341
  );
3945
4342
  var version = packageJson.version;
3946
4343
  var program = new Command();
@@ -4033,7 +4430,7 @@ program.command("unpublish <specifier>").description(
4033
4430
  await unpublish(specifier, { force: options.force });
4034
4431
  });
4035
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) => {
4036
- await access(specifier, {
4433
+ await access2(specifier, {
4037
4434
  public: options.public,
4038
4435
  private: options.private
4039
4436
  });
@@ -4044,5 +4441,4 @@ program.command("deprecate <specifier> [message]").description(
4044
4441
  await deprecate(specifier, message, { undo: options.undo });
4045
4442
  });
4046
4443
  program.parse();
4047
- //# sourceMappingURL=index.js.map
4048
4444
  //# sourceMappingURL=index.js.map