@anytio/pspm 0.0.4 → 0.0.7

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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from 'fs';
3
- import { dirname, join, relative } from 'path';
4
- import { fileURLToPath, URL } from 'url';
3
+ import { dirname, join, basename, relative } from 'path';
4
+ import { fileURLToPath, URL as URL$1 } from 'url';
5
5
  import { Command } from 'commander';
6
- import { stat, writeFile, mkdir, rm, access, readFile, readdir, unlink } from 'fs/promises';
7
6
  import { createHash, randomBytes } from 'crypto';
8
7
  import * as semver from 'semver';
8
+ import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, unlink } from 'fs/promises';
9
9
  import { homedir } from 'os';
10
10
  import * as ini from 'ini';
11
11
  import http from 'http';
@@ -18,6 +18,36 @@ function calculateIntegrity(data) {
18
18
  return `sha256-${hash}`;
19
19
  }
20
20
 
21
+ // ../../packages/shared/pspm-types/src/manifest.ts
22
+ var DEFAULT_SKILL_FILES = [
23
+ "SKILL.md",
24
+ "runtime",
25
+ "scripts",
26
+ "data"
27
+ ];
28
+ var PSPM_SCHEMA_URL = "https://pspm.dev/schema/pspm.json";
29
+ function validateManifest(manifest) {
30
+ if (!manifest.name) {
31
+ return { valid: false, error: "Manifest must have a 'name' field" };
32
+ }
33
+ if (!manifest.version) {
34
+ return { valid: false, error: "Manifest must have a 'version' field" };
35
+ }
36
+ if (!/^[a-z][a-z0-9_-]*$/.test(manifest.name)) {
37
+ return {
38
+ valid: false,
39
+ error: "Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores"
40
+ };
41
+ }
42
+ if (!/^\d+\.\d+\.\d+/.test(manifest.version)) {
43
+ return {
44
+ valid: false,
45
+ error: "Version must be a valid semantic version (e.g., 1.0.0)"
46
+ };
47
+ }
48
+ return { valid: true };
49
+ }
50
+
21
51
  // ../../packages/shared/pspm-types/src/specifier.ts
22
52
  var SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
23
53
  function parseSkillSpecifier(specifier) {
@@ -53,13 +83,16 @@ function getConfig() {
53
83
  async function customFetch(url, options) {
54
84
  const { baseUrl, apiKey } = getConfig();
55
85
  const fullUrl = `${baseUrl}${url}`;
86
+ const headers = {
87
+ ...options.headers ?? {},
88
+ "Content-Type": "application/json"
89
+ };
90
+ if (apiKey) {
91
+ headers.Authorization = `Bearer ${apiKey}`;
92
+ }
56
93
  const response = await fetch(fullUrl, {
57
94
  ...options,
58
- headers: {
59
- ...options.headers,
60
- Authorization: `Bearer ${apiKey}`,
61
- "Content-Type": "application/json"
62
- }
95
+ headers
63
96
  });
64
97
  const text = await response.text();
65
98
  let data = null;
@@ -179,6 +212,102 @@ async function whoamiRequest(registryUrl, apiKey) {
179
212
  return null;
180
213
  }
181
214
  }
215
+ async function deprecateSkillVersion(skillName, version2, message) {
216
+ const config2 = getConfig();
217
+ if (!config2) {
218
+ return { status: 401, error: "SDK not configured" };
219
+ }
220
+ try {
221
+ const response = await fetch(
222
+ `${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
223
+ {
224
+ method: "POST",
225
+ headers: {
226
+ "Content-Type": "application/json",
227
+ Authorization: `Bearer ${config2.apiKey}`
228
+ },
229
+ body: JSON.stringify({ message })
230
+ }
231
+ );
232
+ if (!response.ok) {
233
+ const error = await response.text();
234
+ return { status: response.status, error };
235
+ }
236
+ const data = await response.json();
237
+ return { status: response.status, data };
238
+ } catch (error) {
239
+ return {
240
+ status: 500,
241
+ error: error instanceof Error ? error.message : "Unknown error"
242
+ };
243
+ }
244
+ }
245
+ async function undeprecateSkillVersion(skillName, version2) {
246
+ const config2 = getConfig();
247
+ if (!config2) {
248
+ return { status: 401, error: "SDK not configured" };
249
+ }
250
+ try {
251
+ const response = await fetch(
252
+ `${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
253
+ {
254
+ method: "DELETE",
255
+ headers: {
256
+ Authorization: `Bearer ${config2.apiKey}`
257
+ }
258
+ }
259
+ );
260
+ if (!response.ok) {
261
+ const error = await response.text();
262
+ return { status: response.status, error };
263
+ }
264
+ const data = await response.json();
265
+ return { status: response.status, data };
266
+ } catch (error) {
267
+ return {
268
+ status: 500,
269
+ error: error instanceof Error ? error.message : "Unknown error"
270
+ };
271
+ }
272
+ }
273
+ async function changeSkillAccess(skillName, input) {
274
+ const config2 = getConfig();
275
+ if (!config2) {
276
+ return { status: 401, error: "SDK not configured" };
277
+ }
278
+ try {
279
+ const response = await fetch(
280
+ `${config2.baseUrl}/api/skills/${skillName}/access`,
281
+ {
282
+ method: "POST",
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ Authorization: `Bearer ${config2.apiKey}`
286
+ },
287
+ body: JSON.stringify(input)
288
+ }
289
+ );
290
+ if (!response.ok) {
291
+ const error = await response.text();
292
+ try {
293
+ const errorJson = JSON.parse(error);
294
+ return {
295
+ status: response.status,
296
+ error: errorJson.message || errorJson.error || error
297
+ };
298
+ } catch {
299
+ return { status: response.status, error };
300
+ }
301
+ }
302
+ const data = await response.json();
303
+ return { status: response.status, data };
304
+ } catch (error) {
305
+ return {
306
+ status: 500,
307
+ error: error instanceof Error ? error.message : "Unknown error"
308
+ };
309
+ }
310
+ }
182
311
 
183
312
  // src/errors.ts
184
313
  var ConfigError = class extends Error {
@@ -238,12 +367,24 @@ function getConfigPath() {
238
367
  function getLegacyConfigPath() {
239
368
  return join(homedir(), ".pspm", "config.json");
240
369
  }
370
+ function getPspmDir() {
371
+ return join(process.cwd(), ".pspm");
372
+ }
241
373
  function getSkillsDir() {
242
- return join(process.cwd(), ".skills");
374
+ return join(process.cwd(), ".pspm", "skills");
375
+ }
376
+ function getCacheDir() {
377
+ return join(process.cwd(), ".pspm", "cache");
243
378
  }
244
379
  function getLockfilePath() {
380
+ return join(process.cwd(), "pspm-lock.json");
381
+ }
382
+ function getLegacyLockfilePath() {
245
383
  return join(process.cwd(), "skill-lock.json");
246
384
  }
385
+ function getLegacySkillsDir() {
386
+ return join(process.cwd(), ".skills");
387
+ }
247
388
  async function readUserConfig() {
248
389
  const configPath = getConfigPath();
249
390
  if (process.env.PSPM_DEBUG) {
@@ -255,10 +396,35 @@ async function readUserConfig() {
255
396
  if (process.env.PSPM_DEBUG) {
256
397
  console.log(`[config] Parsed config:`, JSON.stringify(parsed, null, 2));
257
398
  }
399
+ const scopedRegistries = {};
400
+ for (const key of Object.keys(parsed)) {
401
+ const scopeMatch = key.match(/^(@[^:]+):registry$/);
402
+ if (scopeMatch) {
403
+ const scope = scopeMatch[1];
404
+ scopedRegistries[scope] = parsed[key];
405
+ }
406
+ }
407
+ const registryTokens = {};
408
+ for (const key of Object.keys(parsed)) {
409
+ const tokenMatch = key.match(/^\/\/([^:]+):authToken$/);
410
+ if (tokenMatch) {
411
+ const host = tokenMatch[1];
412
+ registryTokens[host] = parsed[key];
413
+ }
414
+ if (key.startsWith("//") && typeof parsed[key] === "object") {
415
+ const host = key.slice(2);
416
+ const section = parsed[key];
417
+ if (section.authToken) {
418
+ registryTokens[host] = section.authToken;
419
+ }
420
+ }
421
+ }
258
422
  return {
259
423
  registry: parsed.registry,
260
424
  authToken: parsed.authToken,
261
- username: parsed.username
425
+ username: parsed.username,
426
+ scopedRegistries: Object.keys(scopedRegistries).length > 0 ? scopedRegistries : void 0,
427
+ registryTokens: Object.keys(registryTokens).length > 0 ? registryTokens : void 0
262
428
  };
263
429
  } catch (error) {
264
430
  if (process.env.PSPM_DEBUG) {
@@ -374,6 +540,8 @@ async function resolveConfig() {
374
540
  let registryUrl = DEFAULT_REGISTRY_URL;
375
541
  let apiKey = userConfig.authToken;
376
542
  const username = userConfig.username;
543
+ const scopedRegistries = userConfig.scopedRegistries ?? {};
544
+ const registryTokens = userConfig.registryTokens ?? {};
377
545
  if (userConfig.registry) {
378
546
  registryUrl = userConfig.registry;
379
547
  }
@@ -391,13 +559,33 @@ async function resolveConfig() {
391
559
  console.log(`[config] registryUrl: ${registryUrl}`);
392
560
  console.log(`[config] apiKey: ${apiKey ? "***" : "(not set)"}`);
393
561
  console.log(`[config] username: ${username || "(not set)"}`);
562
+ console.log(
563
+ `[config] scopedRegistries: ${JSON.stringify(scopedRegistries)}`
564
+ );
565
+ console.log(
566
+ `[config] registryTokens: ${Object.keys(registryTokens).length} configured`
567
+ );
394
568
  }
395
569
  return {
396
570
  registryUrl,
397
571
  apiKey,
398
- username
572
+ username,
573
+ scopedRegistries,
574
+ registryTokens
399
575
  };
400
576
  }
577
+ function getTokenForRegistry(config2, registryUrl) {
578
+ try {
579
+ const url = new URL(registryUrl);
580
+ const host = url.host;
581
+ if (config2.registryTokens[host]) {
582
+ return config2.registryTokens[host];
583
+ }
584
+ return config2.apiKey;
585
+ } catch {
586
+ return config2.apiKey;
587
+ }
588
+ }
401
589
  async function setCredentials(authToken, username, registry) {
402
590
  const config2 = await readUserConfig();
403
591
  config2.authToken = authToken;
@@ -442,43 +630,198 @@ async function getRegistryUrl() {
442
630
  const resolved = await resolveConfig();
443
631
  return resolved.registryUrl;
444
632
  }
633
+
634
+ // src/commands/access.ts
635
+ async function access(specifier, options) {
636
+ try {
637
+ const apiKey = await requireApiKey();
638
+ const registryUrl = await getRegistryUrl();
639
+ if (options.public && options.private) {
640
+ console.error("Error: Cannot specify both --public and --private");
641
+ process.exit(1);
642
+ }
643
+ if (!options.public && !options.private) {
644
+ console.error("Error: Must specify either --public or --private");
645
+ process.exit(1);
646
+ }
647
+ const visibility = options.public ? "public" : "private";
648
+ let packageName;
649
+ if (specifier) {
650
+ const parsed = parseSkillSpecifier(specifier);
651
+ if (!parsed) {
652
+ console.error(
653
+ `Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}`
654
+ );
655
+ process.exit(1);
656
+ }
657
+ packageName = parsed.name;
658
+ } else {
659
+ const { readFile: readFile6 } = await import('fs/promises');
660
+ const { join: join10 } = await import('path');
661
+ let manifest = null;
662
+ try {
663
+ const content = await readFile6(
664
+ join10(process.cwd(), "pspm.json"),
665
+ "utf-8"
666
+ );
667
+ manifest = JSON.parse(content);
668
+ } catch {
669
+ try {
670
+ const content = await readFile6(
671
+ join10(process.cwd(), "package.json"),
672
+ "utf-8"
673
+ );
674
+ manifest = JSON.parse(content);
675
+ } catch {
676
+ console.error(
677
+ "Error: No pspm.json or package.json found in current directory"
678
+ );
679
+ console.error(
680
+ "Either run this command in a package directory or specify a package name"
681
+ );
682
+ process.exit(1);
683
+ }
684
+ }
685
+ if (!manifest?.name) {
686
+ console.error("Error: Package manifest is missing 'name' field");
687
+ process.exit(1);
688
+ }
689
+ packageName = manifest.name;
690
+ }
691
+ configure2({ registryUrl, apiKey });
692
+ console.log(`Setting ${packageName} to ${visibility}...`);
693
+ const response = await changeSkillAccess(packageName, { visibility });
694
+ if (response.status !== 200 || !response.data) {
695
+ const errorMessage = response.error ?? "Failed to change visibility";
696
+ console.error(`Error: ${errorMessage}`);
697
+ process.exit(1);
698
+ }
699
+ const result = response.data;
700
+ console.log(
701
+ `+ @user/${result.username}/${result.name} is now ${result.visibility}`
702
+ );
703
+ if (visibility === "public") {
704
+ console.log("");
705
+ console.log(
706
+ "Note: This action is irreversible. Public packages cannot be made private."
707
+ );
708
+ }
709
+ } catch (error) {
710
+ const message = error instanceof Error ? error.message : "Unknown error";
711
+ console.error(`Error: ${message}`);
712
+ process.exit(1);
713
+ }
714
+ }
715
+ async function hasLegacyLockfile() {
716
+ try {
717
+ await stat(getLegacyLockfilePath());
718
+ return true;
719
+ } catch {
720
+ return false;
721
+ }
722
+ }
723
+ async function migrateLockfileIfNeeded() {
724
+ const legacyPath = getLegacyLockfilePath();
725
+ const newPath = getLockfilePath();
726
+ try {
727
+ await stat(legacyPath);
728
+ } catch {
729
+ return false;
730
+ }
731
+ try {
732
+ await stat(newPath);
733
+ return false;
734
+ } catch {
735
+ }
736
+ try {
737
+ const content = await readFile(legacyPath, "utf-8");
738
+ const oldLockfile = JSON.parse(content);
739
+ const newLockfile = {
740
+ lockfileVersion: 2,
741
+ registryUrl: oldLockfile.registryUrl,
742
+ packages: oldLockfile.skills ?? {}
743
+ };
744
+ await writeFile(newPath, `${JSON.stringify(newLockfile, null, 2)}
745
+ `);
746
+ console.log(`Migrated lockfile: skill-lock.json \u2192 pspm-lock.json`);
747
+ return true;
748
+ } catch {
749
+ return false;
750
+ }
751
+ }
445
752
  async function readLockfile() {
446
753
  const lockfilePath = getLockfilePath();
447
754
  try {
448
755
  const content = await readFile(lockfilePath, "utf-8");
449
- return JSON.parse(content);
756
+ const lockfile = JSON.parse(content);
757
+ if (lockfile.lockfileVersion === 1 && lockfile.skills && !lockfile.packages) {
758
+ return {
759
+ ...lockfile,
760
+ lockfileVersion: 2,
761
+ packages: lockfile.skills
762
+ };
763
+ }
764
+ return lockfile;
450
765
  } catch {
766
+ if (await hasLegacyLockfile()) {
767
+ try {
768
+ const content = await readFile(getLegacyLockfilePath(), "utf-8");
769
+ const legacyLockfile = JSON.parse(content);
770
+ return {
771
+ lockfileVersion: 2,
772
+ registryUrl: legacyLockfile.registryUrl,
773
+ packages: legacyLockfile.skills ?? {}
774
+ };
775
+ } catch {
776
+ return null;
777
+ }
778
+ }
451
779
  return null;
452
780
  }
453
781
  }
454
782
  async function writeLockfile(lockfile) {
455
783
  const lockfilePath = getLockfilePath();
456
784
  await mkdir(dirname(lockfilePath), { recursive: true });
457
- await writeFile(lockfilePath, `${JSON.stringify(lockfile, null, 2)}
785
+ const normalized = {
786
+ lockfileVersion: 2,
787
+ registryUrl: lockfile.registryUrl,
788
+ packages: lockfile.packages ?? lockfile.skills ?? {}
789
+ };
790
+ await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
458
791
  `);
459
792
  }
460
793
  async function createEmptyLockfile() {
461
794
  const registryUrl = await getRegistryUrl();
462
795
  return {
463
- lockfileVersion: 1,
796
+ lockfileVersion: 2,
464
797
  registryUrl,
465
- skills: {}
798
+ packages: {}
466
799
  };
467
800
  }
801
+ function getPackages(lockfile) {
802
+ return lockfile.packages ?? lockfile.skills ?? {};
803
+ }
468
804
  async function addToLockfile(fullName, entry) {
469
805
  let lockfile = await readLockfile();
470
806
  if (!lockfile) {
471
807
  lockfile = await createEmptyLockfile();
472
808
  }
473
- lockfile.skills[fullName] = entry;
809
+ const packages = getPackages(lockfile);
810
+ packages[fullName] = entry;
811
+ lockfile.packages = packages;
474
812
  await writeLockfile(lockfile);
475
813
  }
476
814
  async function removeFromLockfile(fullName) {
477
815
  const lockfile = await readLockfile();
478
- if (!lockfile || !lockfile.skills[fullName]) {
816
+ if (!lockfile) {
817
+ return false;
818
+ }
819
+ const packages = getPackages(lockfile);
820
+ if (!packages[fullName]) {
479
821
  return false;
480
822
  }
481
- delete lockfile.skills[fullName];
823
+ delete packages[fullName];
824
+ lockfile.packages = packages;
482
825
  await writeLockfile(lockfile);
483
826
  return true;
484
827
  }
@@ -487,7 +830,8 @@ async function listLockfileSkills() {
487
830
  if (!lockfile) {
488
831
  return [];
489
832
  }
490
- return Object.entries(lockfile.skills).map(([name, entry]) => ({
833
+ const packages = getPackages(lockfile);
834
+ return Object.entries(packages).map(([name, entry]) => ({
491
835
  name,
492
836
  entry
493
837
  }));
@@ -496,8 +840,9 @@ async function listLockfileSkills() {
496
840
  // src/commands/add.ts
497
841
  async function add(specifier, _options) {
498
842
  try {
499
- const apiKey = await requireApiKey();
500
- const registryUrl = await getRegistryUrl();
843
+ const config2 = await resolveConfig();
844
+ const registryUrl = config2.registryUrl;
845
+ const apiKey = getTokenForRegistry(config2, registryUrl);
501
846
  const parsed = parseSkillSpecifier(specifier);
502
847
  if (!parsed) {
503
848
  console.error(
@@ -506,10 +851,23 @@ async function add(specifier, _options) {
506
851
  process.exit(1);
507
852
  }
508
853
  const { username, name, versionRange } = parsed;
509
- configure2({ registryUrl, apiKey });
854
+ configure2({ registryUrl, apiKey: apiKey ?? "" });
510
855
  console.log(`Resolving ${specifier}...`);
511
856
  const versionsResponse = await listSkillVersions(username, name);
512
857
  if (versionsResponse.status !== 200) {
858
+ if (versionsResponse.status === 401) {
859
+ if (!apiKey) {
860
+ console.error(
861
+ `Error: Package @user/${username}/${name} requires authentication`
862
+ );
863
+ console.error("Please run 'pspm login' to authenticate");
864
+ } else {
865
+ console.error(
866
+ `Error: Access denied to @user/${username}/${name}. You may not have permission to access this private package.`
867
+ );
868
+ }
869
+ process.exit(1);
870
+ }
513
871
  const errorMessage = extractApiErrorMessage(
514
872
  versionsResponse,
515
873
  `Skill @user/${username}/${name} not found`
@@ -542,11 +900,13 @@ async function add(specifier, _options) {
542
900
  process.exit(1);
543
901
  }
544
902
  const versionInfo = versionResponse.data;
545
- const downloadUrl = `${registryUrl}/@user/${username}/${name}/${resolved}/download`;
546
- const tarballResponse = await fetch(downloadUrl, {
547
- headers: {
548
- Authorization: `Bearer ${apiKey}`
549
- },
903
+ const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
904
+ const downloadHeaders = {};
905
+ if (!isPresignedUrl && apiKey) {
906
+ downloadHeaders.Authorization = `Bearer ${apiKey}`;
907
+ }
908
+ const tarballResponse = await fetch(versionInfo.downloadUrl, {
909
+ headers: downloadHeaders,
550
910
  redirect: "follow"
551
911
  });
552
912
  if (!tarballResponse.ok) {
@@ -565,16 +925,16 @@ async function add(specifier, _options) {
565
925
  const skillsDir = getSkillsDir();
566
926
  const destDir = join(skillsDir, username, name);
567
927
  await mkdir(destDir, { recursive: true });
568
- const { writeFile: writeFile4 } = await import('fs/promises');
928
+ const { writeFile: writeFile6 } = await import('fs/promises');
569
929
  const tempFile = join(destDir, ".temp.tgz");
570
- await writeFile4(tempFile, tarballBuffer);
930
+ await writeFile6(tempFile, tarballBuffer);
571
931
  const { exec: exec2 } = await import('child_process');
572
932
  const { promisify: promisify2 } = await import('util');
573
933
  const execAsync = promisify2(exec2);
574
934
  try {
575
935
  await rm(destDir, { recursive: true, force: true });
576
936
  await mkdir(destDir, { recursive: true });
577
- await writeFile4(tempFile, tarballBuffer);
937
+ await writeFile6(tempFile, tarballBuffer);
578
938
  await execAsync(
579
939
  `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
580
940
  );
@@ -654,11 +1014,190 @@ async function configShow() {
654
1014
  process.exit(1);
655
1015
  }
656
1016
  }
657
- async function install(options) {
1017
+
1018
+ // src/commands/deprecate.ts
1019
+ async function deprecate(specifier, message, options) {
658
1020
  try {
659
1021
  const apiKey = await requireApiKey();
660
- await getRegistryUrl();
1022
+ const registryUrl = await getRegistryUrl();
1023
+ const parsed = parseSkillSpecifier(specifier);
1024
+ if (!parsed) {
1025
+ console.error(
1026
+ `Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}@{version}`
1027
+ );
1028
+ process.exit(1);
1029
+ }
1030
+ const { username, name, versionRange } = parsed;
1031
+ if (!versionRange) {
1032
+ console.error(
1033
+ "Error: Version is required for deprecation. Use format: @user/{username}/{name}@{version}"
1034
+ );
1035
+ process.exit(1);
1036
+ }
1037
+ configure2({ registryUrl, apiKey });
1038
+ if (options.undo) {
1039
+ console.log(
1040
+ `Removing deprecation from @user/${username}/${name}@${versionRange}...`
1041
+ );
1042
+ const response = await undeprecateSkillVersion(name, versionRange);
1043
+ if (response.status !== 200) {
1044
+ console.error(
1045
+ `Error: ${response.error || "Failed to remove deprecation"}`
1046
+ );
1047
+ process.exit(1);
1048
+ }
1049
+ console.log(
1050
+ `Removed deprecation from @user/${username}/${name}@${versionRange}`
1051
+ );
1052
+ } else {
1053
+ if (!message) {
1054
+ console.error(
1055
+ "Error: Deprecation message is required. Usage: pspm deprecate <specifier> <message>"
1056
+ );
1057
+ process.exit(1);
1058
+ }
1059
+ console.log(`Deprecating @user/${username}/${name}@${versionRange}...`);
1060
+ const response = await deprecateSkillVersion(name, versionRange, message);
1061
+ if (response.status !== 200) {
1062
+ console.error(
1063
+ `Error: ${response.error || "Failed to deprecate version"}`
1064
+ );
1065
+ process.exit(1);
1066
+ }
1067
+ console.log(`Deprecated @user/${username}/${name}@${versionRange}`);
1068
+ console.log(`Message: ${message}`);
1069
+ console.log("");
1070
+ console.log(
1071
+ "Users installing this version will see a deprecation warning."
1072
+ );
1073
+ console.log("The package is still available for download.");
1074
+ }
1075
+ } catch (error) {
1076
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1077
+ console.error(`Error: ${errorMessage}`);
1078
+ process.exit(1);
1079
+ }
1080
+ }
1081
+ async function readExistingPackageJson() {
1082
+ try {
1083
+ const content = await readFile(
1084
+ join(process.cwd(), "package.json"),
1085
+ "utf-8"
1086
+ );
1087
+ const pkg = JSON.parse(content);
1088
+ return {
1089
+ name: pkg.name,
1090
+ version: pkg.version,
1091
+ description: pkg.description,
1092
+ author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name,
1093
+ license: pkg.license
1094
+ };
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ }
1099
+ function sanitizeName(name) {
1100
+ const withoutScope = name.replace(/^@[^/]+\//, "");
1101
+ return withoutScope.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
1102
+ }
1103
+ async function init(options) {
1104
+ try {
1105
+ const pspmJsonPath = join(process.cwd(), "pspm.json");
1106
+ try {
1107
+ await stat(pspmJsonPath);
1108
+ console.error("Error: pspm.json already exists in this directory.");
1109
+ console.error("Use --force to overwrite (not yet implemented).");
1110
+ process.exit(1);
1111
+ } catch {
1112
+ }
1113
+ const existingPkg = await readExistingPackageJson();
1114
+ const defaultName = options.name || sanitizeName(existingPkg?.name || basename(process.cwd()));
1115
+ const defaultVersion = existingPkg?.version || "0.1.0";
1116
+ const defaultDescription = options.description || existingPkg?.description || "";
1117
+ const defaultAuthor = options.author || existingPkg?.author || "";
1118
+ const defaultLicense = existingPkg?.license || "MIT";
1119
+ const manifest = {
1120
+ $schema: PSPM_SCHEMA_URL,
1121
+ name: defaultName,
1122
+ version: defaultVersion,
1123
+ description: defaultDescription || void 0,
1124
+ author: defaultAuthor || void 0,
1125
+ license: defaultLicense,
1126
+ type: "skill",
1127
+ capabilities: [],
1128
+ main: "SKILL.md",
1129
+ requirements: {
1130
+ pspm: ">=0.1.0"
1131
+ },
1132
+ files: [...DEFAULT_SKILL_FILES],
1133
+ dependencies: {},
1134
+ private: false
1135
+ };
1136
+ if (!manifest.description) delete manifest.description;
1137
+ if (!manifest.author) delete manifest.author;
1138
+ const content = JSON.stringify(manifest, null, 2);
1139
+ await writeFile(pspmJsonPath, `${content}
1140
+ `);
1141
+ console.log("Created pspm.json:");
1142
+ console.log("");
1143
+ console.log(content);
1144
+ console.log("");
1145
+ try {
1146
+ await stat(join(process.cwd(), "SKILL.md"));
1147
+ } catch {
1148
+ console.log(
1149
+ "Note: Create a SKILL.md file with your skill's prompt content."
1150
+ );
1151
+ }
1152
+ if (existingPkg) {
1153
+ console.log("Note: Values were derived from existing package.json.");
1154
+ console.log(" pspm.json is for publishing to PSPM registry.");
1155
+ console.log(" package.json can still be used for npm dependencies.");
1156
+ }
1157
+ } catch (error) {
1158
+ const message = error instanceof Error ? error.message : "Unknown error";
1159
+ console.error(`Error: ${message}`);
1160
+ process.exit(1);
1161
+ }
1162
+ }
1163
+ function getCacheFilePath(cacheDir, integrity) {
1164
+ const match = integrity.match(/^sha256-(.+)$/);
1165
+ if (!match) {
1166
+ throw new Error(`Invalid integrity format: ${integrity}`);
1167
+ }
1168
+ const base64Hash = match[1];
1169
+ const hexHash = Buffer.from(base64Hash, "base64").toString("hex");
1170
+ return join(cacheDir, `sha256-${hexHash}.tgz`);
1171
+ }
1172
+ async function readFromCache(cacheDir, integrity) {
1173
+ try {
1174
+ const cachePath = getCacheFilePath(cacheDir, integrity);
1175
+ const data = await readFile(cachePath);
1176
+ const actualIntegrity = `sha256-${createHash("sha256").update(data).digest("base64")}`;
1177
+ if (actualIntegrity !== integrity) {
1178
+ await rm(cachePath, { force: true });
1179
+ return null;
1180
+ }
1181
+ return data;
1182
+ } catch {
1183
+ return null;
1184
+ }
1185
+ }
1186
+ async function writeToCache(cacheDir, integrity, data) {
1187
+ try {
1188
+ await mkdir(cacheDir, { recursive: true });
1189
+ const cachePath = getCacheFilePath(cacheDir, integrity);
1190
+ await writeFile(cachePath, data);
1191
+ } catch {
1192
+ }
1193
+ }
1194
+ async function install(options) {
1195
+ try {
1196
+ const config2 = await resolveConfig();
1197
+ const apiKey = getTokenForRegistry(config2, config2.registryUrl);
661
1198
  const skillsDir = options.dir || getSkillsDir();
1199
+ const cacheDir = getCacheDir();
1200
+ await migrateLockfileIfNeeded();
662
1201
  const lockfile = await readLockfile();
663
1202
  if (!lockfile) {
664
1203
  if (options.frozenLockfile) {
@@ -670,14 +1209,17 @@ async function install(options) {
670
1209
  console.log("No lockfile found. Nothing to install.");
671
1210
  return;
672
1211
  }
673
- const skillCount = Object.keys(lockfile.skills).length;
1212
+ const skillCount = Object.keys(
1213
+ lockfile.packages ?? lockfile.skills ?? {}
1214
+ ).length;
674
1215
  if (skillCount === 0) {
675
1216
  console.log("No skills in lockfile. Nothing to install.");
676
1217
  return;
677
1218
  }
678
1219
  console.log(`Installing ${skillCount} skill(s)...
679
1220
  `);
680
- const entries = Object.entries(lockfile.skills);
1221
+ const packages = lockfile.packages ?? lockfile.skills ?? {};
1222
+ const entries = Object.entries(packages);
681
1223
  for (const [fullName, entry] of entries) {
682
1224
  const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
683
1225
  if (!match) {
@@ -686,34 +1228,58 @@ async function install(options) {
686
1228
  }
687
1229
  const [, username, name] = match;
688
1230
  console.log(`Installing ${fullName}@${entry.version}...`);
689
- const response = await fetch(entry.resolved, {
690
- headers: {
691
- Authorization: `Bearer ${apiKey}`
692
- },
693
- redirect: "follow"
694
- });
695
- if (!response.ok) {
696
- console.error(
697
- ` Error: Failed to download ${fullName} (${response.status})`
698
- );
699
- continue;
700
- }
701
- const tarballBuffer = Buffer.from(await response.arrayBuffer());
702
- const { createHash: createHash3 } = await import('crypto');
703
- const actualIntegrity = `sha256-${createHash3("sha256").update(tarballBuffer).digest("base64")}`;
704
- if (actualIntegrity !== entry.integrity) {
705
- console.error(` Error: Checksum verification failed for ${fullName}`);
706
- if (options.frozenLockfile) {
707
- process.exit(1);
1231
+ let tarballBuffer;
1232
+ let fromCache = false;
1233
+ const cachedTarball = await readFromCache(cacheDir, entry.integrity);
1234
+ if (cachedTarball) {
1235
+ tarballBuffer = cachedTarball;
1236
+ fromCache = true;
1237
+ } else {
1238
+ const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
1239
+ const downloadHeaders = {};
1240
+ if (!isPresignedUrl && apiKey) {
1241
+ downloadHeaders.Authorization = `Bearer ${apiKey}`;
708
1242
  }
709
- continue;
1243
+ const response = await fetch(entry.resolved, {
1244
+ headers: downloadHeaders,
1245
+ redirect: "follow"
1246
+ });
1247
+ if (!response.ok) {
1248
+ if (response.status === 401) {
1249
+ if (!apiKey) {
1250
+ console.error(
1251
+ ` Error: ${fullName} requires authentication. Run 'pspm login' first.`
1252
+ );
1253
+ } else {
1254
+ console.error(
1255
+ ` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
1256
+ );
1257
+ }
1258
+ } else {
1259
+ console.error(
1260
+ ` Error: Failed to download ${fullName} (${response.status})`
1261
+ );
1262
+ }
1263
+ continue;
1264
+ }
1265
+ tarballBuffer = Buffer.from(await response.arrayBuffer());
1266
+ const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
1267
+ if (actualIntegrity !== entry.integrity) {
1268
+ console.error(
1269
+ ` Error: Checksum verification failed for ${fullName}`
1270
+ );
1271
+ if (options.frozenLockfile) {
1272
+ process.exit(1);
1273
+ }
1274
+ continue;
1275
+ }
1276
+ await writeToCache(cacheDir, entry.integrity, tarballBuffer);
710
1277
  }
711
1278
  const destDir = join(skillsDir, username, name);
712
1279
  await rm(destDir, { recursive: true, force: true });
713
1280
  await mkdir(destDir, { recursive: true });
714
1281
  const tempFile = join(destDir, ".temp.tgz");
715
- const { writeFile: writeFile4 } = await import('fs/promises');
716
- await writeFile4(tempFile, tarballBuffer);
1282
+ await writeFile(tempFile, tarballBuffer);
717
1283
  const { exec: exec2 } = await import('child_process');
718
1284
  const { promisify: promisify2 } = await import('util');
719
1285
  const execAsync = promisify2(exec2);
@@ -724,7 +1290,9 @@ async function install(options) {
724
1290
  } finally {
725
1291
  await rm(tempFile, { force: true });
726
1292
  }
727
- console.log(` Installed to ${destDir}`);
1293
+ console.log(
1294
+ ` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
1295
+ );
728
1296
  }
729
1297
  console.log(`
730
1298
  All ${skillCount} skill(s) installed.`);
@@ -754,7 +1322,7 @@ async function list(options) {
754
1322
  const skillPath = join(skillsDir, username, skillName);
755
1323
  let status = "installed";
756
1324
  try {
757
- await access(skillPath);
1325
+ await access$1(skillPath);
758
1326
  } catch {
759
1327
  status = "missing";
760
1328
  }
@@ -777,7 +1345,7 @@ function getWebAppUrl(registryUrl) {
777
1345
  return process.env.PSPM_WEB_URL.replace(/\/$/, "");
778
1346
  }
779
1347
  try {
780
- const url = new URL(registryUrl);
1348
+ const url = new URL$1(registryUrl);
781
1349
  return `${url.protocol}//${url.host}`;
782
1350
  } catch {
783
1351
  return DEFAULT_WEB_APP_URL;
@@ -785,7 +1353,7 @@ function getWebAppUrl(registryUrl) {
785
1353
  }
786
1354
  function getServerUrl(registryUrl) {
787
1355
  try {
788
- const url = new URL(registryUrl);
1356
+ const url = new URL$1(registryUrl);
789
1357
  return `${url.protocol}//${url.host}`;
790
1358
  } catch {
791
1359
  return DEFAULT_WEB_APP_URL;
@@ -817,7 +1385,7 @@ function startCallbackServer(expectedState) {
817
1385
  rejectToken = reject;
818
1386
  });
819
1387
  const server = http.createServer((req, res) => {
820
- const url = new URL(req.url || "/", `http://localhost`);
1388
+ const url = new URL$1(req.url || "/", `http://localhost`);
821
1389
  if (url.pathname === "/callback") {
822
1390
  const token = url.searchParams.get("token");
823
1391
  const state = url.searchParams.get("state");
@@ -963,7 +1531,132 @@ async function logout() {
963
1531
  process.exit(1);
964
1532
  }
965
1533
  }
1534
+ async function migrate(options) {
1535
+ try {
1536
+ const legacySkillsDir = getLegacySkillsDir();
1537
+ const newSkillsDir = getSkillsDir();
1538
+ const legacyLockfilePath = getLegacyLockfilePath();
1539
+ const newLockfilePath = getLockfilePath();
1540
+ const pspmDir = getPspmDir();
1541
+ let migrationNeeded = false;
1542
+ const actions = [];
1543
+ try {
1544
+ const legacyStats = await stat(legacySkillsDir);
1545
+ if (legacyStats.isDirectory()) {
1546
+ const contents = await readdir(legacySkillsDir);
1547
+ if (contents.length > 0) {
1548
+ migrationNeeded = true;
1549
+ actions.push(`Move .skills/ \u2192 .pspm/skills/`);
1550
+ }
1551
+ }
1552
+ } catch {
1553
+ }
1554
+ try {
1555
+ await stat(legacyLockfilePath);
1556
+ try {
1557
+ await stat(newLockfilePath);
1558
+ actions.push(
1559
+ `Note: Both skill-lock.json and pspm-lock.json exist. Manual merge may be needed.`
1560
+ );
1561
+ } catch {
1562
+ migrationNeeded = true;
1563
+ actions.push(`Migrate skill-lock.json \u2192 pspm-lock.json`);
1564
+ }
1565
+ } catch {
1566
+ }
1567
+ if (!migrationNeeded && actions.length === 0) {
1568
+ console.log(
1569
+ "No migration needed. Project is already using the new structure."
1570
+ );
1571
+ return;
1572
+ }
1573
+ if (options.dryRun) {
1574
+ console.log("Migration plan (dry run):");
1575
+ console.log("");
1576
+ for (const action of actions) {
1577
+ console.log(` - ${action}`);
1578
+ }
1579
+ console.log("");
1580
+ console.log("Run without --dry-run to perform migration.");
1581
+ return;
1582
+ }
1583
+ console.log("Migrating project structure...\n");
1584
+ const lockfileMigrated = await migrateLockfileIfNeeded();
1585
+ if (lockfileMigrated) {
1586
+ console.log(" \u2713 Migrated skill-lock.json \u2192 pspm-lock.json");
1587
+ }
1588
+ try {
1589
+ const legacyStats = await stat(legacySkillsDir);
1590
+ if (legacyStats.isDirectory()) {
1591
+ const contents = await readdir(legacySkillsDir);
1592
+ if (contents.length > 0) {
1593
+ await mkdir(pspmDir, { recursive: true });
1594
+ try {
1595
+ const newStats = await stat(newSkillsDir);
1596
+ if (newStats.isDirectory()) {
1597
+ const newContents = await readdir(newSkillsDir);
1598
+ if (newContents.length > 0) {
1599
+ console.log(
1600
+ " ! Both .skills/ and .pspm/skills/ have content. Manual merge required."
1601
+ );
1602
+ } else {
1603
+ await rm(newSkillsDir, { recursive: true, force: true });
1604
+ await rename(legacySkillsDir, newSkillsDir);
1605
+ console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
1606
+ }
1607
+ }
1608
+ } catch {
1609
+ await rename(legacySkillsDir, newSkillsDir);
1610
+ console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
1611
+ }
1612
+ }
1613
+ }
1614
+ } catch {
1615
+ }
1616
+ console.log("");
1617
+ console.log("Migration complete!");
1618
+ console.log("");
1619
+ console.log(
1620
+ "You can safely delete these legacy files if they still exist:"
1621
+ );
1622
+ console.log(" - skill-lock.json (replaced by pspm-lock.json)");
1623
+ console.log(" - .skills/ (replaced by .pspm/skills/)");
1624
+ console.log("");
1625
+ console.log("Update your .gitignore to include:");
1626
+ console.log(" .pspm/cache/");
1627
+ } catch (error) {
1628
+ const message = error instanceof Error ? error.message : "Unknown error";
1629
+ console.error(`Error: ${message}`);
1630
+ process.exit(1);
1631
+ }
1632
+ }
966
1633
  var exec = promisify(exec$1);
1634
+ async function detectManifest() {
1635
+ const cwd = process.cwd();
1636
+ const pspmJsonPath = join(cwd, "pspm.json");
1637
+ try {
1638
+ const content = await readFile(pspmJsonPath, "utf-8");
1639
+ const manifest = JSON.parse(content);
1640
+ return { type: "pspm.json", manifest, path: pspmJsonPath };
1641
+ } catch {
1642
+ }
1643
+ const packageJsonPath = join(cwd, "package.json");
1644
+ try {
1645
+ const content = await readFile(packageJsonPath, "utf-8");
1646
+ const packageJson2 = JSON.parse(content);
1647
+ const manifest = {
1648
+ name: packageJson2.name,
1649
+ version: packageJson2.version,
1650
+ description: packageJson2.description,
1651
+ author: typeof packageJson2.author === "string" ? packageJson2.author : packageJson2.author?.name,
1652
+ license: packageJson2.license,
1653
+ files: packageJson2.files
1654
+ };
1655
+ return { type: "package.json", manifest, path: packageJsonPath };
1656
+ } catch {
1657
+ throw new Error("No pspm.json or package.json found in current directory");
1658
+ }
1659
+ }
967
1660
  function formatBytes(bytes) {
968
1661
  if (bytes < 1024) return `${bytes}B`;
969
1662
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
@@ -995,23 +1688,26 @@ async function publishCommand(options) {
995
1688
  try {
996
1689
  const apiKey = await requireApiKey();
997
1690
  const registryUrl = await getRegistryUrl();
998
- const packageJsonPath = join(process.cwd(), "package.json");
999
- let packageJson2;
1000
- try {
1001
- const content = await readFile(packageJsonPath, "utf-8");
1002
- packageJson2 = JSON.parse(content);
1003
- } catch {
1004
- console.error("Error: No package.json found in current directory");
1005
- process.exit(1);
1006
- }
1007
- if (!packageJson2.name) {
1008
- console.error("Error: package.json must have a 'name' field");
1009
- process.exit(1);
1691
+ const detection = await detectManifest();
1692
+ const manifest = detection.manifest;
1693
+ if (detection.type === "package.json") {
1694
+ console.log("pspm warn Using package.json instead of pspm.json");
1695
+ console.log(
1696
+ "pspm warn Run 'pspm init' to create a dedicated pspm.json manifest"
1697
+ );
1698
+ console.log("");
1010
1699
  }
1011
- if (!packageJson2.version) {
1012
- console.error("Error: package.json must have a 'version' field");
1700
+ const validation = validateManifest(manifest);
1701
+ if (!validation.valid) {
1702
+ console.error(`Error: ${validation.error}`);
1013
1703
  process.exit(1);
1014
1704
  }
1705
+ const packageJson2 = {
1706
+ name: manifest.name,
1707
+ version: manifest.version,
1708
+ description: manifest.description,
1709
+ files: manifest.files
1710
+ };
1015
1711
  if (options.bump) {
1016
1712
  const semver2 = await import('semver');
1017
1713
  const newVersion = semver2.default.inc(packageJson2.version, options.bump);
@@ -1029,13 +1725,7 @@ async function publishCommand(options) {
1029
1725
  const tempDir = join(process.cwd(), ".pspm-publish");
1030
1726
  try {
1031
1727
  await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
1032
- const files = packageJson2.files || [
1033
- "package.json",
1034
- "SKILL.md",
1035
- "runtime",
1036
- "scripts",
1037
- "data"
1038
- ];
1728
+ const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
1039
1729
  await exec(`mkdir -p "${tempDir}/package"`);
1040
1730
  for (const file of files) {
1041
1731
  try {
@@ -1045,7 +1735,18 @@ async function publishCommand(options) {
1045
1735
  } catch {
1046
1736
  }
1047
1737
  }
1048
- await exec(`cp package.json "${tempDir}/package/"`);
1738
+ if (detection.type === "pspm.json") {
1739
+ await exec(`cp pspm.json "${tempDir}/package/"`);
1740
+ try {
1741
+ await stat(join(process.cwd(), "package.json"));
1742
+ await exec(
1743
+ `cp package.json "${tempDir}/package/" 2>/dev/null || true`
1744
+ );
1745
+ } catch {
1746
+ }
1747
+ } else {
1748
+ await exec(`cp package.json "${tempDir}/package/"`);
1749
+ }
1049
1750
  const packageDir = join(tempDir, "package");
1050
1751
  const tarballContents = await getFilesWithSizes(packageDir, packageDir);
1051
1752
  const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
@@ -1102,6 +1803,25 @@ async function publishCommand(options) {
1102
1803
  `+ @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
1103
1804
  );
1104
1805
  console.log(`Checksum: ${result.version.checksum}`);
1806
+ if (options.access) {
1807
+ console.log(`
1808
+ Setting visibility to ${options.access}...`);
1809
+ const accessResponse = await changeSkillAccess(packageJson2.name, {
1810
+ visibility: options.access
1811
+ });
1812
+ if (accessResponse.status !== 200 || !accessResponse.data) {
1813
+ console.warn(
1814
+ `Warning: Failed to set visibility: ${accessResponse.error ?? "Unknown error"}`
1815
+ );
1816
+ } else {
1817
+ console.log(`Package is now ${accessResponse.data.visibility}`);
1818
+ if (options.access === "public") {
1819
+ console.log(
1820
+ "Note: This action is irreversible. Public packages cannot be made private."
1821
+ );
1822
+ }
1823
+ }
1824
+ }
1105
1825
  } finally {
1106
1826
  await exec(`rm -rf "${tempDir}"`).catch(() => {
1107
1827
  });
@@ -1355,6 +2075,19 @@ program.command("logout").description("Log out and clear stored credentials").ac
1355
2075
  program.command("whoami").description("Show current user information").action(async () => {
1356
2076
  await whoami();
1357
2077
  });
2078
+ program.command("init").description("Create a new pspm.json manifest in the current directory").option("-n, --name <name>", "Skill name").option("-d, --description <desc>", "Skill description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
2079
+ await init({
2080
+ name: options.name,
2081
+ description: options.description,
2082
+ author: options.author,
2083
+ yes: options.yes
2084
+ });
2085
+ });
2086
+ program.command("migrate").description(
2087
+ "Migrate from old directory structure (.skills/, skill-lock.json)"
2088
+ ).option("--dry-run", "Show what would be migrated without making changes").action(async (options) => {
2089
+ await migrate({ dryRun: options.dryRun });
2090
+ });
1358
2091
  program.command("add <specifier>").description("Add a skill (e.g., @user/bsheng/vite_slides@^2.0.0)").option("--save", "Save to lockfile (default)").action(async (specifier, options) => {
1359
2092
  await add(specifier, { save: options.save ?? true });
1360
2093
  });
@@ -1373,15 +2106,29 @@ program.command("install").alias("i").description("Install all skills from lockf
1373
2106
  program.command("update").description("Update all skills to latest compatible versions").option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
1374
2107
  await update({ dryRun: options.dryRun });
1375
2108
  });
1376
- program.command("publish").description("Publish current directory as a skill").option("--bump <level>", "Bump version (major, minor, patch)").option("--tag <tag>", "Tag for the release").action(async (options) => {
2109
+ program.command("publish").description("Publish current directory as a skill").option("--bump <level>", "Bump version (major, minor, patch)").option("--tag <tag>", "Tag for the release").option("--access <level>", "Set package visibility (public or private)").action(async (options) => {
1377
2110
  await publishCommand({
1378
2111
  bump: options.bump,
1379
- tag: options.tag
2112
+ tag: options.tag,
2113
+ access: options.access
1380
2114
  });
1381
2115
  });
1382
- program.command("unpublish <specifier>").description("Remove a published skill version").option("--force", "Confirm destructive action").action(async (specifier, options) => {
2116
+ program.command("unpublish <specifier>").description(
2117
+ "Remove a published skill version (only within 72 hours of publishing)"
2118
+ ).option("--force", "Confirm destructive action").action(async (specifier, options) => {
1383
2119
  await unpublish(specifier, { force: options.force });
1384
2120
  });
2121
+ 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) => {
2122
+ await access(specifier, {
2123
+ public: options.public,
2124
+ private: options.private
2125
+ });
2126
+ });
2127
+ program.command("deprecate <specifier> [message]").description(
2128
+ "Mark a skill version as deprecated (alternative to unpublish after 72 hours)"
2129
+ ).option("--undo", "Remove deprecation status").action(async (specifier, message, options) => {
2130
+ await deprecate(specifier, message, { undo: options.undo });
2131
+ });
1385
2132
  program.parse();
1386
2133
  //# sourceMappingURL=index.js.map
1387
2134
  //# sourceMappingURL=index.js.map