@anytio/pspm 0.0.5 → 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) {
479
817
  return false;
480
818
  }
481
- delete lockfile.skills[fullName];
819
+ const packages = getPackages(lockfile);
820
+ if (!packages[fullName]) {
821
+ return false;
822
+ }
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`
@@ -543,10 +901,12 @@ async function add(specifier, _options) {
543
901
  }
544
902
  const versionInfo = versionResponse.data;
545
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
+ }
546
908
  const tarballResponse = await fetch(versionInfo.downloadUrl, {
547
- headers: isPresignedUrl ? {} : {
548
- Authorization: `Bearer ${apiKey}`
549
- },
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,35 +1228,58 @@ async function install(options) {
686
1228
  }
687
1229
  const [, username, name] = match;
688
1230
  console.log(`Installing ${fullName}@${entry.version}...`);
689
- const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
690
- const response = await fetch(entry.resolved, {
691
- headers: isPresignedUrl ? {} : {
692
- Authorization: `Bearer ${apiKey}`
693
- },
694
- redirect: "follow"
695
- });
696
- if (!response.ok) {
697
- console.error(
698
- ` Error: Failed to download ${fullName} (${response.status})`
699
- );
700
- continue;
701
- }
702
- const tarballBuffer = Buffer.from(await response.arrayBuffer());
703
- const { createHash: createHash3 } = await import('crypto');
704
- const actualIntegrity = `sha256-${createHash3("sha256").update(tarballBuffer).digest("base64")}`;
705
- if (actualIntegrity !== entry.integrity) {
706
- console.error(` Error: Checksum verification failed for ${fullName}`);
707
- if (options.frozenLockfile) {
708
- 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}`;
709
1242
  }
710
- 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);
711
1277
  }
712
1278
  const destDir = join(skillsDir, username, name);
713
1279
  await rm(destDir, { recursive: true, force: true });
714
1280
  await mkdir(destDir, { recursive: true });
715
1281
  const tempFile = join(destDir, ".temp.tgz");
716
- const { writeFile: writeFile4 } = await import('fs/promises');
717
- await writeFile4(tempFile, tarballBuffer);
1282
+ await writeFile(tempFile, tarballBuffer);
718
1283
  const { exec: exec2 } = await import('child_process');
719
1284
  const { promisify: promisify2 } = await import('util');
720
1285
  const execAsync = promisify2(exec2);
@@ -725,7 +1290,9 @@ async function install(options) {
725
1290
  } finally {
726
1291
  await rm(tempFile, { force: true });
727
1292
  }
728
- console.log(` Installed to ${destDir}`);
1293
+ console.log(
1294
+ ` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
1295
+ );
729
1296
  }
730
1297
  console.log(`
731
1298
  All ${skillCount} skill(s) installed.`);
@@ -755,7 +1322,7 @@ async function list(options) {
755
1322
  const skillPath = join(skillsDir, username, skillName);
756
1323
  let status = "installed";
757
1324
  try {
758
- await access(skillPath);
1325
+ await access$1(skillPath);
759
1326
  } catch {
760
1327
  status = "missing";
761
1328
  }
@@ -778,7 +1345,7 @@ function getWebAppUrl(registryUrl) {
778
1345
  return process.env.PSPM_WEB_URL.replace(/\/$/, "");
779
1346
  }
780
1347
  try {
781
- const url = new URL(registryUrl);
1348
+ const url = new URL$1(registryUrl);
782
1349
  return `${url.protocol}//${url.host}`;
783
1350
  } catch {
784
1351
  return DEFAULT_WEB_APP_URL;
@@ -786,7 +1353,7 @@ function getWebAppUrl(registryUrl) {
786
1353
  }
787
1354
  function getServerUrl(registryUrl) {
788
1355
  try {
789
- const url = new URL(registryUrl);
1356
+ const url = new URL$1(registryUrl);
790
1357
  return `${url.protocol}//${url.host}`;
791
1358
  } catch {
792
1359
  return DEFAULT_WEB_APP_URL;
@@ -818,7 +1385,7 @@ function startCallbackServer(expectedState) {
818
1385
  rejectToken = reject;
819
1386
  });
820
1387
  const server = http.createServer((req, res) => {
821
- const url = new URL(req.url || "/", `http://localhost`);
1388
+ const url = new URL$1(req.url || "/", `http://localhost`);
822
1389
  if (url.pathname === "/callback") {
823
1390
  const token = url.searchParams.get("token");
824
1391
  const state = url.searchParams.get("state");
@@ -964,7 +1531,132 @@ async function logout() {
964
1531
  process.exit(1);
965
1532
  }
966
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
+ }
967
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
+ }
968
1660
  function formatBytes(bytes) {
969
1661
  if (bytes < 1024) return `${bytes}B`;
970
1662
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
@@ -996,23 +1688,26 @@ async function publishCommand(options) {
996
1688
  try {
997
1689
  const apiKey = await requireApiKey();
998
1690
  const registryUrl = await getRegistryUrl();
999
- const packageJsonPath = join(process.cwd(), "package.json");
1000
- let packageJson2;
1001
- try {
1002
- const content = await readFile(packageJsonPath, "utf-8");
1003
- packageJson2 = JSON.parse(content);
1004
- } catch {
1005
- console.error("Error: No package.json found in current directory");
1006
- process.exit(1);
1007
- }
1008
- if (!packageJson2.name) {
1009
- console.error("Error: package.json must have a 'name' field");
1010
- 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("");
1011
1699
  }
1012
- if (!packageJson2.version) {
1013
- 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}`);
1014
1703
  process.exit(1);
1015
1704
  }
1705
+ const packageJson2 = {
1706
+ name: manifest.name,
1707
+ version: manifest.version,
1708
+ description: manifest.description,
1709
+ files: manifest.files
1710
+ };
1016
1711
  if (options.bump) {
1017
1712
  const semver2 = await import('semver');
1018
1713
  const newVersion = semver2.default.inc(packageJson2.version, options.bump);
@@ -1030,13 +1725,7 @@ async function publishCommand(options) {
1030
1725
  const tempDir = join(process.cwd(), ".pspm-publish");
1031
1726
  try {
1032
1727
  await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
1033
- const files = packageJson2.files || [
1034
- "package.json",
1035
- "SKILL.md",
1036
- "runtime",
1037
- "scripts",
1038
- "data"
1039
- ];
1728
+ const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
1040
1729
  await exec(`mkdir -p "${tempDir}/package"`);
1041
1730
  for (const file of files) {
1042
1731
  try {
@@ -1046,7 +1735,18 @@ async function publishCommand(options) {
1046
1735
  } catch {
1047
1736
  }
1048
1737
  }
1049
- 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
+ }
1050
1750
  const packageDir = join(tempDir, "package");
1051
1751
  const tarballContents = await getFilesWithSizes(packageDir, packageDir);
1052
1752
  const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
@@ -1103,6 +1803,25 @@ async function publishCommand(options) {
1103
1803
  `+ @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
1104
1804
  );
1105
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
+ }
1106
1825
  } finally {
1107
1826
  await exec(`rm -rf "${tempDir}"`).catch(() => {
1108
1827
  });
@@ -1356,6 +2075,19 @@ program.command("logout").description("Log out and clear stored credentials").ac
1356
2075
  program.command("whoami").description("Show current user information").action(async () => {
1357
2076
  await whoami();
1358
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
+ });
1359
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) => {
1360
2092
  await add(specifier, { save: options.save ?? true });
1361
2093
  });
@@ -1374,15 +2106,29 @@ program.command("install").alias("i").description("Install all skills from lockf
1374
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) => {
1375
2107
  await update({ dryRun: options.dryRun });
1376
2108
  });
1377
- 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) => {
1378
2110
  await publishCommand({
1379
2111
  bump: options.bump,
1380
- tag: options.tag
2112
+ tag: options.tag,
2113
+ access: options.access
1381
2114
  });
1382
2115
  });
1383
- 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) => {
1384
2119
  await unpublish(specifier, { force: options.force });
1385
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
+ });
1386
2132
  program.parse();
1387
2133
  //# sourceMappingURL=index.js.map
1388
2134
  //# sourceMappingURL=index.js.map