@anytio/pspm 0.12.0 → 0.13.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to the PSPM CLI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.13.0] - 2026-03-24
9
+
10
+ ### Added
11
+
12
+ - **Client-side encryption for private packages**: Encrypt skill packages before publishing with AES-256-GCM encryption
13
+ - `pspm config set-encryption-key` — Set an encryption key for a scope (`@user/x` or `@org/x`)
14
+ - `pspm config get-encryption-key` — Check if an encryption key is set for a scope
15
+ - `pspm config remove-encryption-key` — Remove an encryption key for a scope
16
+ - Private packages are automatically encrypted on publish and decrypted on install when a key is configured
17
+ - Uses scrypt key derivation for secure key management
18
+
8
19
  ## [0.12.0] - 2026-03-19
9
20
 
10
21
  ### Added
@@ -103,7 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103
114
  - **`audit` command**: Verify integrity of installed skills
104
115
  - Checks for missing packages, deprecated versions, corrupted installations
105
116
  - `--json` flag for CI integration
106
- - **Expanded agent support**: From 6 to 41 supported AI coding agents
117
+ - **Expanded agent support**: From 6 to 41 supported AI agents
107
118
  - Added Windsurf, Amp, Augment, Cline, Continue, Goose, Kilo Code, Kiro CLI, OpenCode, OpenHands, Replit, Roo Code, Trae, and 22 more
108
119
 
109
120
  ### Changed
package/CLI_GUIDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PSPM CLI Guide
2
2
 
3
- PSPM is a package manager for AI agent skills. It provides commands for authentication, configuration, skill management, and publishing across AI coding agents.
3
+ PSPM is a package manager for AI agent skills. It provides commands for authentication, configuration, skill management, and publishing across AI agents.
4
4
 
5
5
  ## Installation
6
6
 
@@ -24,7 +24,7 @@ Options:
24
24
  -h, --help display help for command
25
25
 
26
26
  Commands:
27
- config Manage PSPM configuration
27
+ config Manage PSPM configuration (show, init, set-encryption-key, get-encryption-key, remove-encryption-key)
28
28
  login [options] Log in via browser or with an API key
29
29
  logout Log out and clear stored credentials
30
30
  whoami Show current user information
@@ -36,11 +36,17 @@ Commands:
36
36
  install|i [options] [specifiers...] Install skills from lockfile, or add and install specific packages
37
37
  link [options] Recreate agent symlinks without reinstalling
38
38
  update [options] Update all skills to latest compatible versions
39
+ search|find [options] [query] Search and discover skills from the registry
40
+ audit [options] Verify integrity of installed skills
41
+ outdated [options] [packages...] Check for outdated skills
39
42
  version <bump> Bump package version (major, minor, patch)
40
43
  publish [options] Publish current directory as a skill
41
44
  unpublish [options] <specifier> Remove a published skill version (only within 72 hours of publishing)
42
45
  access [options] [specifier] Change package visibility (public/private)
43
46
  deprecate [options] <specifier> [message] Mark a skill version as deprecated (alternative to unpublish after 72 hours)
47
+ skill-list Manage skill lists (list, create, show, update, delete, add-skill, remove-skill, install)
48
+ notebook Manage notebooks (upload, list, download, delete)
49
+ upgrade Update pspm itself to the latest version
44
50
  help [command] display help for command
45
51
  ```
46
52
 
@@ -171,6 +177,7 @@ pspm add @user/alice/skill1 @user/bob/skill2
171
177
  pspm add @user/skill --agent claude-code,cursor # Link to multiple agents
172
178
  pspm add github:owner/repo --agent none # Skip symlink creation
173
179
  pspm add @user/skill -y # Skip agent selection prompt
180
+ pspm add @user/skill -g # Install to user home directory
174
181
  ```
175
182
 
176
183
  ### Remove Skill
@@ -193,6 +200,9 @@ pspm ls
193
200
  # JSON output for scripting
194
201
  pspm list --json
195
202
 
203
+ # List global skills
204
+ pspm list -g
205
+
196
206
  # Example output:
197
207
  # Installed skills:
198
208
  #
@@ -219,6 +229,7 @@ pspm install --dir ./vendor/skills # Install to specific directory
219
229
  pspm install --agent claude-code,cursor # Link to multiple agents
220
230
  pspm install --agent none # Skip symlink creation
221
231
  pspm install -y # Skip agent selection prompt
232
+ pspm install -g # Install to user home directory
222
233
 
223
234
  # Install specific packages (like npm):
224
235
  pspm install @user/alice/skill1 github:org/repo
@@ -236,6 +247,7 @@ Recreate agent symlinks without reinstalling (useful after adding agents):
236
247
  pspm link
237
248
  pspm link --agent claude-code,cursor # Link to specific agents
238
249
  pspm link -y # Skip agent selection prompt
250
+ pspm link -g # Recreate global agent symlinks
239
251
  ```
240
252
 
241
253
  ### Update Skills
@@ -245,6 +257,39 @@ pspm update
245
257
  pspm update --dry-run # Preview updates without applying
246
258
  ```
247
259
 
260
+ ### Search Skills
261
+
262
+ Search and discover skills from the registry:
263
+
264
+ ```bash
265
+ pspm search typescript # Search by keyword
266
+ pspm find react # Alias for search
267
+ pspm search react --json # JSON output
268
+ pspm search --sort recent --limit 10
269
+ ```
270
+
271
+ ### Check Outdated Skills
272
+
273
+ ```bash
274
+ pspm outdated # Check all packages
275
+ pspm outdated code-review # Check specific package
276
+ pspm outdated --json # JSON output
277
+ pspm outdated --all # Include up-to-date packages
278
+ ```
279
+
280
+ Exits with code `1` if any packages are outdated.
281
+
282
+ ### Audit Skills
283
+
284
+ Verify integrity of installed skills:
285
+
286
+ ```bash
287
+ pspm audit # Human-readable output
288
+ pspm audit --json # JSON output (for CI)
289
+ ```
290
+
291
+ Checks for: missing packages, deprecated versions, corrupted installations.
292
+
248
293
  ## Versioning
249
294
 
250
295
  ### Bump Version
@@ -276,12 +321,15 @@ The command:
276
321
  Publish the current directory as a skill:
277
322
 
278
323
  ```bash
279
- pspm publish
280
- pspm publish --bump patch # Auto-bump version (major, minor, patch)
281
- pspm publish --bump minor --tag beta
282
- pspm publish --access public # Publish and make public in one step
324
+ pspm publish --access public # Publish as public
325
+ pspm publish --access private # Publish as private
326
+ pspm publish --access team --org myorg # Publish under org
327
+ pspm publish --access public --bump patch # Auto-bump version
328
+ pspm publish --access public --bump minor --tag beta
283
329
  ```
284
330
 
331
+ The `--access` flag is required and must be `public`, `private`, or `team`.
332
+
285
333
  **Required `pspm.json` fields:**
286
334
  - `name` - Skill name (e.g., `@user/username/skillname`)
287
335
  - `version` - Semver version
@@ -338,6 +386,88 @@ pspm access @user/bsheng/vite_slides --public
338
386
  - **Private packages** (default): Require authentication to download
339
387
  - **Public packages**: Anyone can download without authentication
340
388
 
389
+ ## Client-Side Encryption
390
+
391
+ Private packages can be encrypted before upload so that the PSPM server and storage (R2) only ever see ciphertext. The encryption key never leaves your machine.
392
+
393
+ ### How It Works
394
+
395
+ - **Publish:** If an encryption key is set for the package scope, the CLI encrypts the tarball with AES-256-GCM before uploading. The server stores only ciphertext.
396
+ - **Install:** The CLI checks the package manifest for encryption metadata. If present, it decrypts the tarball locally before extracting.
397
+ - **Public packages** are never encrypted — encryption only applies to `private` and `team` visibility.
398
+
399
+ ### Set an Encryption Key
400
+
401
+ Each scope (`@user/yourname` or `@org/orgname`) has one encryption key. All private packages under that scope use the same key.
402
+
403
+ ```bash
404
+ # Set encryption key for your user scope
405
+ pspm config set-encryption-key @user/yourname my-secret-passphrase
406
+
407
+ # Set encryption key for an organization
408
+ pspm config set-encryption-key @org/myorg shared-team-secret
409
+ ```
410
+
411
+ Or use environment variables:
412
+
413
+ ```bash
414
+ export PSPM_ENCRYPTION_KEY_USER_YOURNAME="my-secret-passphrase"
415
+ export PSPM_ENCRYPTION_KEY_ORG_MYORG="shared-team-secret"
416
+ ```
417
+
418
+ ### Manage Encryption Keys
419
+
420
+ ```bash
421
+ # Check if a key is set
422
+ pspm config get-encryption-key @user/yourname
423
+
424
+ # Remove a key
425
+ pspm config remove-encryption-key @user/yourname
426
+ ```
427
+
428
+ ### Publish with Encryption
429
+
430
+ When you publish a private package and an encryption key is configured for the scope, the CLI automatically encrypts:
431
+
432
+ ```bash
433
+ pspm config set-encryption-key @user/yourname my-secret
434
+ pspm publish --access private
435
+ # Output: pspm notice Encrypting package (scope: @user/yourname)
436
+ ```
437
+
438
+ If no encryption key is set, the package is uploaded unencrypted with a warning.
439
+
440
+ ### Install Encrypted Packages
441
+
442
+ ```bash
443
+ # Set the same key used during publish
444
+ pspm config set-encryption-key @user/yourname my-secret
445
+
446
+ # Install as usual — decryption is automatic
447
+ pspm install
448
+ ```
449
+
450
+ If you don't have the key, the CLI will show an error with instructions:
451
+
452
+ ```
453
+ Error: Package @user/yourname/my-skill is encrypted.
454
+ Set the key: pspm config set-encryption-key @user/yourname <passphrase>
455
+ ```
456
+
457
+ ### Team Sharing
458
+
459
+ For organization packages, share the encryption key with team members through a secure channel (e.g., a password manager). Each team member adds it to their local config:
460
+
461
+ ```bash
462
+ pspm config set-encryption-key @org/myorg shared-team-secret
463
+ ```
464
+
465
+ ### Important Notes
466
+
467
+ - **Key loss = data loss.** If you lose your encryption key, encrypted packages cannot be recovered. Back up your keys.
468
+ - The server stores encryption metadata (algorithm, salt, IV) alongside the package — these are not secrets and are safe to store publicly.
469
+ - Encryption is opt-in. If no key is configured, private packages are uploaded unencrypted.
470
+
341
471
  ## Configuration Files
342
472
 
343
473
  ### User Config: `~/.pspmrc`
@@ -357,6 +487,10 @@ username = myuser
357
487
  ; Multi-registry: Per-registry tokens (optional)
358
488
  //pspm.dev:authToken = sk_public_token
359
489
  //corp.pspm.io:authToken = sk_corp_token
490
+
491
+ ; Encryption keys (optional)
492
+ encryption-key:@user/yourname = my-secret-passphrase
493
+ encryption-key:@org/myorg = shared-team-secret
360
494
  ```
361
495
 
362
496
  ### Project Config: `.pspmrc`
@@ -441,6 +575,7 @@ Configuration is resolved in priority order:
441
575
  | `PSPM_API_KEY` | Override API key |
442
576
  | `PSPM_DEBUG` | Enable debug logging |
443
577
  | `GITHUB_TOKEN` | GitHub token for private repos and higher rate limits |
578
+ | `PSPM_ENCRYPTION_KEY_<SCOPE>` | Encryption key for a scope (e.g., `PSPM_ENCRYPTION_KEY_USER_ALICE`) |
444
579
 
445
580
  ## Directory Structure
446
581
 
@@ -517,6 +652,106 @@ pspm init
517
652
  pspm publish --bump patch
518
653
  ```
519
654
 
655
+ ## Skill Lists
656
+
657
+ ### List Skill Lists
658
+
659
+ ```bash
660
+ pspm skill-list list # Your lists
661
+ pspm skill-list list --org myorg # Organization's lists
662
+ pspm skill-list list --json # JSON output
663
+ ```
664
+
665
+ ### Create Skill List
666
+
667
+ ```bash
668
+ pspm skill-list create my-favorites
669
+ pspm skill-list create my-favorites --visibility public
670
+ pspm skill-list create team-tools --org myorg -d "Our team's tools"
671
+ ```
672
+
673
+ ### Show Skill List
674
+
675
+ ```bash
676
+ pspm skill-list show @user/alice/my-favorites
677
+ pspm skill-list show @org/myorg/team-tools --json
678
+ ```
679
+
680
+ ### Update Skill List
681
+
682
+ ```bash
683
+ pspm skill-list update @user/alice/my-favorites --description "Updated desc"
684
+ pspm skill-list update @user/alice/my-favorites --visibility public
685
+ ```
686
+
687
+ ### Delete Skill List
688
+
689
+ ```bash
690
+ pspm skill-list delete @user/alice/my-favorites
691
+ ```
692
+
693
+ ### Add Skill to List
694
+
695
+ ```bash
696
+ pspm skill-list add-skill @user/alice/my-favorites @user/bob/code-review
697
+ pspm skill-list add-skill @user/alice/my-favorites @user/bob/lint --note "Great for CI"
698
+ ```
699
+
700
+ ### Remove Skill from List
701
+
702
+ ```bash
703
+ pspm skill-list remove-skill @user/alice/my-favorites @user/bob/code-review
704
+ ```
705
+
706
+ ### Install from Skill List
707
+
708
+ ```bash
709
+ pspm skill-list install @user/alice/my-favorites
710
+ pspm skill-list install @org/myorg/team-tools --agent claude-code
711
+ ```
712
+
713
+ ## Notebook Management
714
+
715
+ ### Upload Notebook
716
+
717
+ ```bash
718
+ pspm notebook upload notebook.anyt.md
719
+ pspm notebook upload notebook.anyt.md --visibility public
720
+ pspm notebook upload notebook.anyt.md --org myorg
721
+ ```
722
+
723
+ ### List Notebooks
724
+
725
+ ```bash
726
+ pspm notebook list
727
+ pspm notebook list --org myorg
728
+ pspm notebook list --json
729
+ ```
730
+
731
+ ### Download Notebook
732
+
733
+ ```bash
734
+ pspm notebook download <id>
735
+ ```
736
+
737
+ ### Delete Notebook
738
+
739
+ ```bash
740
+ pspm notebook delete <id>
741
+ ```
742
+
743
+ ## Self-Update
744
+
745
+ ### Upgrade PSPM
746
+
747
+ Update pspm itself to the latest version:
748
+
749
+ ```bash
750
+ pspm upgrade
751
+ ```
752
+
753
+ Auto-detects your package manager (pnpm, npm, yarn, bun). The CLI also checks for updates every 24 hours and notifies you when a newer version is available.
754
+
520
755
  ## Troubleshooting
521
756
 
522
757
  | Error | Solution |
package/README.md CHANGED
@@ -445,7 +445,7 @@ project/
445
445
  | | +-- _local/ # Local skill symlinks
446
446
  | +-- cache/ # Tarball cache
447
447
  +-- .claude/
448
- | +-- skills/ # Symlinks for Claude Code
448
+ | +-- skills/ # Symlinks for Claude Code (and other agents)
449
449
  +-- .cursor/
450
450
  +-- skills/ # Symlinks for Cursor (if configured)
451
451
  ```
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFi
3
3
  import { homedir } from 'os';
4
4
  import { join, dirname, basename, resolve, relative } from 'path';
5
5
  import * as ini from 'ini';
6
+ import { createHash, randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
6
7
  import ignore from 'ignore';
7
- import { createHash, randomBytes } from 'crypto';
8
8
  import * as semver2 from 'semver';
9
9
  import semver2__default from 'semver';
10
10
  import { checkbox } from '@inquirer/prompts';
@@ -603,6 +603,8 @@ __export(config_exports, {
603
603
  findProjectConfig: () => findProjectConfig,
604
604
  getCacheDir: () => getCacheDir,
605
605
  getConfigPath: () => getConfigPath,
606
+ getEncryptionKey: () => getEncryptionKey,
607
+ getEncryptionScope: () => getEncryptionScope,
606
608
  getLegacyLockfilePath: () => getLegacyLockfilePath,
607
609
  getLegacySkillsDir: () => getLegacySkillsDir,
608
610
  getLockfilePath: () => getLockfilePath,
@@ -613,9 +615,11 @@ __export(config_exports, {
613
615
  isGlobalMode: () => isGlobalMode,
614
616
  isLoggedIn: () => isLoggedIn,
615
617
  readUserConfig: () => readUserConfig,
618
+ removeEncryptionKey: () => removeEncryptionKey,
616
619
  requireApiKey: () => requireApiKey,
617
620
  resolveConfig: () => resolveConfig,
618
621
  setCredentials: () => setCredentials,
622
+ setEncryptionKey: () => setEncryptionKey,
619
623
  setGlobalMode: () => setGlobalMode,
620
624
  writeUserConfig: () => writeUserConfig
621
625
  });
@@ -685,12 +689,21 @@ async function readUserConfig() {
685
689
  }
686
690
  }
687
691
  }
692
+ const encryptionKeys = {};
693
+ for (const key of Object.keys(parsed)) {
694
+ const encKeyMatch = key.match(/^encryption-key:(.+)$/);
695
+ if (encKeyMatch) {
696
+ const scope = encKeyMatch[1];
697
+ encryptionKeys[scope] = parsed[key];
698
+ }
699
+ }
688
700
  return {
689
701
  registry: parsed.registry,
690
702
  authToken: parsed.authToken,
691
703
  username: parsed.username,
692
704
  scopedRegistries: Object.keys(scopedRegistries).length > 0 ? scopedRegistries : void 0,
693
- registryTokens: Object.keys(registryTokens).length > 0 ? registryTokens : void 0
705
+ registryTokens: Object.keys(registryTokens).length > 0 ? registryTokens : void 0,
706
+ encryptionKeys: Object.keys(encryptionKeys).length > 0 ? encryptionKeys : void 0
694
707
  };
695
708
  } catch (error) {
696
709
  if (process.env.PSPM_DEBUG) {
@@ -713,6 +726,12 @@ async function writeUserConfig(config2) {
713
726
  if (config2.username) {
714
727
  lines.push(`username = ${config2.username}`);
715
728
  }
729
+ if (config2.encryptionKeys && Object.keys(config2.encryptionKeys).length > 0) {
730
+ lines.push("; Encryption keys (scope -> passphrase)");
731
+ for (const [scope, key] of Object.entries(config2.encryptionKeys)) {
732
+ lines.push(`encryption-key:${scope} = ${key}`);
733
+ }
734
+ }
716
735
  lines.push("");
717
736
  await mkdir(dirname(configPath), { recursive: true });
718
737
  await writeFile(configPath, lines.join("\n"));
@@ -896,6 +915,36 @@ async function getRegistryUrl() {
896
915
  const resolved = await resolveConfig();
897
916
  return resolved.registryUrl;
898
917
  }
918
+ async function getEncryptionKey(scope) {
919
+ const envSuffix = scope.replace(/^@/, "").replace(/\//g, "_").toUpperCase();
920
+ const envKey = process.env[`PSPM_ENCRYPTION_KEY_${envSuffix}`];
921
+ if (envKey) {
922
+ return envKey;
923
+ }
924
+ const config2 = await readUserConfig();
925
+ return config2.encryptionKeys?.[scope];
926
+ }
927
+ async function setEncryptionKey(scope, key) {
928
+ const config2 = await readUserConfig();
929
+ if (!config2.encryptionKeys) {
930
+ config2.encryptionKeys = {};
931
+ }
932
+ config2.encryptionKeys[scope] = key;
933
+ await writeUserConfig(config2);
934
+ }
935
+ async function removeEncryptionKey(scope) {
936
+ const config2 = await readUserConfig();
937
+ if (config2.encryptionKeys) {
938
+ delete config2.encryptionKeys[scope];
939
+ if (Object.keys(config2.encryptionKeys).length === 0) {
940
+ config2.encryptionKeys = void 0;
941
+ }
942
+ }
943
+ await writeUserConfig(config2);
944
+ }
945
+ function getEncryptionScope(namespace, owner) {
946
+ return `@${namespace}/${owner}`;
947
+ }
899
948
  var DEFAULT_REGISTRY_URL, _globalMode;
900
949
  var init_config = __esm({
901
950
  "src/config.ts"() {
@@ -904,6 +953,62 @@ var init_config = __esm({
904
953
  _globalMode = false;
905
954
  }
906
955
  });
956
+ function deriveKey(passphrase, salt) {
957
+ return scryptSync(passphrase, salt, KEY_LENGTH, {
958
+ N: SCRYPT_COST,
959
+ r: SCRYPT_BLOCK_SIZE,
960
+ p: SCRYPT_PARALLELISM
961
+ });
962
+ }
963
+ function encryptBuffer(data, passphrase, scope) {
964
+ const salt = randomBytes(SALT_LENGTH);
965
+ const iv = randomBytes(IV_LENGTH);
966
+ const key = deriveKey(passphrase, salt);
967
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
968
+ authTagLength: AUTH_TAG_LENGTH
969
+ });
970
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
971
+ const authTag = cipher.getAuthTag();
972
+ return {
973
+ encrypted,
974
+ metadata: {
975
+ algorithm: ALGORITHM,
976
+ kdf: "scrypt",
977
+ salt: salt.toString("hex"),
978
+ iv: iv.toString("hex"),
979
+ authTag: authTag.toString("hex"),
980
+ scope
981
+ }
982
+ };
983
+ }
984
+ function decryptBuffer(encrypted, passphrase, metadata) {
985
+ const salt = Buffer.from(metadata.salt, "hex");
986
+ const iv = Buffer.from(metadata.iv, "hex");
987
+ const authTag = Buffer.from(metadata.authTag, "hex");
988
+ const key = deriveKey(passphrase, salt);
989
+ const decipher = createDecipheriv(ALGORITHM, key, iv, {
990
+ authTagLength: AUTH_TAG_LENGTH
991
+ });
992
+ decipher.setAuthTag(authTag);
993
+ const decrypted = Buffer.concat([
994
+ decipher.update(encrypted),
995
+ decipher.final()
996
+ ]);
997
+ return decrypted;
998
+ }
999
+ var ALGORITHM, KEY_LENGTH, IV_LENGTH, SALT_LENGTH, SCRYPT_COST, SCRYPT_BLOCK_SIZE, SCRYPT_PARALLELISM, AUTH_TAG_LENGTH;
1000
+ var init_encryption = __esm({
1001
+ "src/lib/encryption.ts"() {
1002
+ ALGORITHM = "aes-256-gcm";
1003
+ KEY_LENGTH = 32;
1004
+ IV_LENGTH = 16;
1005
+ SALT_LENGTH = 32;
1006
+ SCRYPT_COST = 16384;
1007
+ SCRYPT_BLOCK_SIZE = 8;
1008
+ SCRYPT_PARALLELISM = 1;
1009
+ AUTH_TAG_LENGTH = 16;
1010
+ }
1011
+ });
907
1012
  async function loadIgnorePatterns(cwd = process.cwd()) {
908
1013
  const ig = ignore();
909
1014
  ig.add(ALWAYS_IGNORED);
@@ -1626,6 +1731,7 @@ var init_specifier2 = __esm({
1626
1731
  // src/lib/index.ts
1627
1732
  var init_lib = __esm({
1628
1733
  "src/lib/index.ts"() {
1734
+ init_encryption();
1629
1735
  init_ignore();
1630
1736
  init_integrity();
1631
1737
  init_lockfile();
@@ -3539,11 +3645,15 @@ async function installFromLockfile(options) {
3539
3645
  }
3540
3646
  const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
3541
3647
  const integrity = calculateIntegrity(tarballBuffer);
3542
- await addToLockfile(fullName, {
3648
+ const lockfileEntry = {
3543
3649
  version: resolved,
3544
3650
  resolved: versionInfo.downloadUrl,
3545
3651
  integrity
3546
- });
3652
+ };
3653
+ if (versionInfo.manifest?.encryption) {
3654
+ lockfileEntry.encryption = versionInfo.manifest.encryption;
3655
+ }
3656
+ await addToLockfile(fullName, lockfileEntry);
3547
3657
  await writeToCache(cacheDir, integrity, tarballBuffer);
3548
3658
  console.log(` Resolved ${fullName}@${resolved}`);
3549
3659
  }
@@ -3686,6 +3796,28 @@ Installing ${packageCount} registry skill(s)...
3686
3796
  }
3687
3797
  await writeToCache(cacheDir, entry.integrity, tarballBuffer);
3688
3798
  }
3799
+ if (entry.encryption) {
3800
+ const scope = entry.encryption.scope;
3801
+ const encKey = await getEncryptionKey(scope);
3802
+ if (!encKey) {
3803
+ console.error(
3804
+ ` Error: Package ${fullName} is encrypted. Set the key: pspm config set-encryption-key ${scope} <passphrase>`
3805
+ );
3806
+ continue;
3807
+ }
3808
+ try {
3809
+ tarballBuffer = decryptBuffer(
3810
+ tarballBuffer,
3811
+ encKey,
3812
+ entry.encryption
3813
+ );
3814
+ } catch {
3815
+ console.error(
3816
+ ` Error: Failed to decrypt ${fullName}. Wrong encryption key for scope "${scope}".`
3817
+ );
3818
+ continue;
3819
+ }
3820
+ }
3689
3821
  const effectiveSkillName = subname ?? name;
3690
3822
  let destDir;
3691
3823
  if (ns === "org") {
@@ -3836,6 +3968,7 @@ var init_install = __esm({
3836
3968
  init_config();
3837
3969
  init_errors();
3838
3970
  init_github();
3971
+ init_encryption();
3839
3972
  init_lib();
3840
3973
  init_lockfile3();
3841
3974
  init_manifest3();
@@ -5453,6 +5586,7 @@ function printTable(results) {
5453
5586
  init_api_client();
5454
5587
  init_config();
5455
5588
  init_errors();
5589
+ init_encryption();
5456
5590
  init_lib();
5457
5591
  var exec = promisify(exec$1);
5458
5592
  function confirm(question) {
@@ -5693,19 +5827,53 @@ async function publishCommand(options) {
5693
5827
  process.exit(0);
5694
5828
  }
5695
5829
  console.log("");
5830
+ let finalTarballBase64 = tarballBase64;
5831
+ if (options.access === "private" || options.access === "team") {
5832
+ const config2 = await Promise.resolve().then(() => (init_config(), config_exports)).then((m) => m.resolveConfig());
5833
+ const namespace2 = options.org ? "org" : "user";
5834
+ const owner2 = options.org ?? config2.username;
5835
+ if (!owner2) {
5836
+ console.error(
5837
+ "Error: Cannot determine package owner. Run 'pspm login' first."
5838
+ );
5839
+ process.exit(1);
5840
+ }
5841
+ const scope = getEncryptionScope(namespace2, owner2);
5842
+ const encryptionKey = await getEncryptionKey(scope);
5843
+ if (encryptionKey) {
5844
+ console.log(`pspm notice Encrypting package (scope: ${scope})`);
5845
+ const { encrypted, metadata } = encryptBuffer(
5846
+ tarballBuffer,
5847
+ encryptionKey,
5848
+ scope
5849
+ );
5850
+ finalTarballBase64 = encrypted.toString("base64");
5851
+ packageJson2.encryption = metadata;
5852
+ console.log(
5853
+ "pspm notice Package encrypted with client-side encryption"
5854
+ );
5855
+ } else {
5856
+ console.log(
5857
+ `pspm warn No encryption key found for scope "${scope}". Publishing without encryption.`
5858
+ );
5859
+ console.log(
5860
+ `pspm warn To encrypt, run: pspm config set-encryption-key ${scope} <your-passphrase>`
5861
+ );
5862
+ }
5863
+ }
5696
5864
  console.log(`pspm notice Publishing to ${registryUrl} with tag latest`);
5697
5865
  configure2({ registryUrl, apiKey });
5698
5866
  let response;
5699
5867
  if (options.org) {
5700
5868
  response = await publishOrgSkill(options.org, {
5701
5869
  manifest: packageJson2,
5702
- tarballBase64,
5870
+ tarballBase64: finalTarballBase64,
5703
5871
  visibility: options.access
5704
5872
  });
5705
5873
  } else {
5706
5874
  response = await publishSkill({
5707
5875
  manifest: packageJson2,
5708
- tarballBase64,
5876
+ tarballBase64: finalTarballBase64,
5709
5877
  visibility: options.access
5710
5878
  });
5711
5879
  }
@@ -5731,7 +5899,9 @@ async function publishCommand(options) {
5731
5899
  `+ @${namespace}/${owner}/${result.skill.name}@${result.version.version}`
5732
5900
  );
5733
5901
  console.log(`Checksum: ${result.version.checksum}`);
5734
- console.log(`Visibility: ${visibilityIcon} ${visibility}`);
5902
+ console.log(
5903
+ `Visibility: ${visibilityIcon} ${visibility}${packageJson2.encryption ? " (encrypted)" : ""}`
5904
+ );
5735
5905
  if (visibility === "public") {
5736
5906
  console.log(
5737
5907
  "Note: Public packages cannot be made private. This is irreversible."
@@ -6729,6 +6899,28 @@ configCmd.command("init").description("Create a .pspmrc file in the current dire
6729
6899
  registry: options.registry
6730
6900
  });
6731
6901
  });
6902
+ configCmd.command("set-encryption-key <scope> <passphrase>").description(
6903
+ "Set encryption key for a scope (e.g., pspm config set-encryption-key @user/alice my-secret)"
6904
+ ).action(async (scope, passphrase) => {
6905
+ const { setEncryptionKey: setEncryptionKey2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6906
+ await setEncryptionKey2(scope, passphrase);
6907
+ console.log(`Encryption key set for scope "${scope}"`);
6908
+ });
6909
+ configCmd.command("remove-encryption-key <scope>").description("Remove encryption key for a scope").action(async (scope) => {
6910
+ const { removeEncryptionKey: removeEncryptionKey2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6911
+ await removeEncryptionKey2(scope);
6912
+ console.log(`Encryption key removed for scope "${scope}"`);
6913
+ });
6914
+ configCmd.command("get-encryption-key <scope>").description("Check if an encryption key is set for a scope").action(async (scope) => {
6915
+ const { getEncryptionKey: getEncryptionKey2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6916
+ const key = await getEncryptionKey2(scope);
6917
+ if (key) {
6918
+ const masked = `${key.substring(0, 4)}***`;
6919
+ console.log(`Encryption key for "${scope}": ${masked} (set)`);
6920
+ } else {
6921
+ console.log(`No encryption key set for "${scope}"`);
6922
+ }
6923
+ });
6732
6924
  program.command("upgrade").description("Upgrade pspm to the latest version").action(async () => {
6733
6925
  await upgrade();
6734
6926
  });