@anytio/pspm 0.0.2 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # PSPM - Prompt Skill Package Manager
2
+
3
+ A CLI for managing prompt skills across AI coding agents.
4
+
5
+ ## What is PSPM?
6
+
7
+ PSPM (Prompt Skill Package Manager) is a package manager for prompt skills - small, discoverable capabilities packaged as `SKILL.md` files. Think of it as npm for AI agent skills.
8
+
9
+ Skills are designed to work with any AI coding agent that supports the SKILL.md format, including Claude Code, Cursor, Windsurf, and others.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install -g @anytio/pspm
15
+ ```
16
+
17
+ Or use with npx:
18
+
19
+ ```bash
20
+ npx @anytio/pspm <command>
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ # Login with your API key
27
+ pspm login --api-key <your-api-key>
28
+
29
+ # Add a skill to your project
30
+ pspm add @user/username/skill-name
31
+
32
+ # List installed skills
33
+ pspm list
34
+
35
+ # Install all skills from lockfile
36
+ pspm install
37
+ ```
38
+
39
+ ## Commands
40
+
41
+ ### Authentication
42
+
43
+ ```bash
44
+ pspm login --api-key <key> # Authenticate with API key
45
+ pspm logout # Clear stored credentials
46
+ pspm whoami # Show current user info
47
+ ```
48
+
49
+ ### Skill Management
50
+
51
+ ```bash
52
+ pspm add <specifier> # Add and install a skill
53
+ pspm remove <name> # Remove an installed skill (alias: rm)
54
+ pspm list # List installed skills (alias: ls)
55
+ pspm install # Install all skills from lockfile (alias: i)
56
+ pspm update # Update skills to latest compatible versions
57
+ ```
58
+
59
+ **Specifier formats:**
60
+ - `@user/username/skillname` - Latest version
61
+ - `@user/username/skillname@2.0.0` - Specific version
62
+ - `@user/username/skillname@^2.0.0` - Semver range
63
+
64
+ ### Publishing
65
+
66
+ ```bash
67
+ pspm publish # Publish current directory as a skill
68
+ pspm publish --bump patch # Auto-bump version (major, minor, patch)
69
+ pspm unpublish <spec> --force # Remove a published skill version
70
+ ```
71
+
72
+ ### Configuration
73
+
74
+ ```bash
75
+ pspm config list # List all profiles
76
+ pspm config show # Show resolved configuration
77
+ pspm config add <name> # Create a new profile
78
+ pspm config use <name> # Set the default profile
79
+ pspm config set <profile> <key> <value> # Set a profile field
80
+ pspm config delete <name> # Delete a profile
81
+ pspm config init # Create .pspmrc in current directory
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ### User Config (`~/.pspm/config.json`)
87
+
88
+ ```json
89
+ {
90
+ "version": 2,
91
+ "defaultProfile": "default",
92
+ "profiles": {
93
+ "default": {
94
+ "registryUrl": "https://pspm.dev/api/skills",
95
+ "apiKey": "sk_...",
96
+ "username": "myuser"
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Project Config (`.pspmrc`)
103
+
104
+ ```json
105
+ {
106
+ "profile": "production",
107
+ "registryUrl": "https://custom-registry.example.com/api/skills"
108
+ }
109
+ ```
110
+
111
+ ### Lockfile (`skill-lock.json`)
112
+
113
+ ```json
114
+ {
115
+ "lockfileVersion": 1,
116
+ "registryUrl": "https://pspm.dev/api/skills",
117
+ "skills": {
118
+ "@user/username/skillname": {
119
+ "version": "1.0.0",
120
+ "resolved": "https://pspm.dev/api/skills/@user/username/skillname/1.0.0/download",
121
+ "integrity": "sha256-..."
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## Environment Variables
128
+
129
+ | Variable | Description |
130
+ |----------|-------------|
131
+ | `PSPM_PROFILE` | Select active profile |
132
+ | `PSPM_REGISTRY_URL` | Override registry URL |
133
+ | `PSPM_API_KEY` | Override API key |
134
+ | `PSPM_DEBUG` | Enable debug logging |
135
+
136
+ ## Directory Structure
137
+
138
+ ```
139
+ project/
140
+ ├── .pspmrc # Project config (optional)
141
+ ├── skill-lock.json # Lockfile
142
+ └── .skills/ # Installed skills
143
+ └── username/
144
+ └── skillname/
145
+ ├── SKILL.md
146
+ └── ...
147
+
148
+ ~/.pspm/
149
+ └── config.json # User config
150
+ ```
151
+
152
+ ## Creating a Skill
153
+
154
+ A skill is a directory containing at minimum a `package.json` and `SKILL.md`:
155
+
156
+ ```
157
+ my-skill/
158
+ ├── package.json # Required: name, version
159
+ ├── SKILL.md # Required: skill instructions
160
+ ├── runtime/ # Optional: runtime files
161
+ ├── scripts/ # Optional: scripts
162
+ └── data/ # Optional: data files
163
+ ```
164
+
165
+ **package.json:**
166
+ ```json
167
+ {
168
+ "name": "@user/myusername/my-skill",
169
+ "version": "1.0.0",
170
+ "description": "A helpful skill for...",
171
+ "files": ["package.json", "SKILL.md", "runtime", "scripts", "data"]
172
+ }
173
+ ```
174
+
175
+ **SKILL.md:**
176
+ ```markdown
177
+ ---
178
+ name: my-skill
179
+ description: A helpful skill that does X
180
+ ---
181
+
182
+ # Instructions
183
+
184
+ When activated, this skill helps you...
185
+ ```
186
+
187
+ ## CI/CD Integration
188
+
189
+ ```bash
190
+ # Use environment variables
191
+ export PSPM_API_KEY=sk_ci_key
192
+ export PSPM_REGISTRY_URL=https://registry.example.com/api/skills
193
+
194
+ # Install with frozen lockfile (fails if lockfile is outdated)
195
+ pspm install --frozen-lockfile
196
+ ```
197
+
198
+ ## Multi-Registry Setup
199
+
200
+ ```bash
201
+ # Create profiles for different registries
202
+ pspm config add production --registry-url https://prod.example.com/api/skills
203
+ pspm config add staging --registry-url https://staging.example.com/api/skills
204
+
205
+ # Login to each
206
+ pspm login --api-key sk_prod_xxx --profile production
207
+ pspm login --api-key sk_staging_xxx --profile staging
208
+
209
+ # Use specific profile for operations
210
+ pspm add @user/bsheng/skill --profile production
211
+ ```
212
+
213
+ ## License
214
+
215
+ See LICENSE file.
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ function resolveVersion(range, availableVersions) {
38
38
  return semver.maxSatisfying(sorted, range);
39
39
  }
40
40
 
41
- // ../../packages/sdk/src/client.ts
41
+ // ../../packages/sdk/src/fetcher.ts
42
42
  var config = null;
43
43
  function configure(options) {
44
44
  config = options;
@@ -49,18 +49,10 @@ function getConfig() {
49
49
  }
50
50
  return config;
51
51
  }
52
- var SDKError = class extends Error {
53
- constructor(message, status, body) {
54
- super(message);
55
- this.status = status;
56
- this.body = body;
57
- this.name = "SDKError";
58
- }
59
- };
60
- async function apiFetch(path, options = {}) {
52
+ async function customFetch(url, options) {
61
53
  const { baseUrl, apiKey } = getConfig();
62
- const url = `${baseUrl}${path}`;
63
- const response = await fetch(url, {
54
+ const fullUrl = `${baseUrl}${url}`;
55
+ const response = await fetch(fullUrl, {
64
56
  ...options,
65
57
  headers: {
66
58
  ...options.headers,
@@ -68,64 +60,114 @@ async function apiFetch(path, options = {}) {
68
60
  "Content-Type": "application/json"
69
61
  }
70
62
  });
71
- if (!response.ok) {
72
- const errorText = await response.text();
73
- let errorMessage = `API error: ${response.status}`;
63
+ const text = await response.text();
64
+ let data = null;
65
+ if (text) {
74
66
  try {
75
- const errorJson = JSON.parse(errorText);
76
- if (errorJson.message) {
77
- errorMessage = errorJson.message;
78
- } else if (errorJson.error) {
79
- errorMessage = errorJson.error;
80
- }
67
+ data = JSON.parse(text);
81
68
  } catch {
82
- if (errorText) {
83
- errorMessage = errorText;
84
- }
69
+ data = text;
85
70
  }
86
- throw new SDKError(errorMessage, response.status, errorText);
87
- }
88
- const text = await response.text();
89
- if (!text) {
90
- return null;
91
71
  }
92
- return JSON.parse(text);
93
- }
94
- async function me() {
95
- return apiFetch("/me", { method: "GET" });
72
+ return {
73
+ data,
74
+ status: response.status,
75
+ headers: response.headers
76
+ };
96
77
  }
97
- async function listVersions(params) {
98
- return apiFetch(
99
- `/@user/${params.username}/${params.name}/versions`,
100
- { method: "GET" }
78
+
79
+ // ../../packages/sdk/src/generated/index.ts
80
+ var getMeUrl = () => {
81
+ return `/me`;
82
+ };
83
+ var me = async (options) => {
84
+ return customFetch(
85
+ getMeUrl(),
86
+ {
87
+ ...options,
88
+ method: "GET"
89
+ }
101
90
  );
102
- }
103
- async function getVersion(params) {
104
- return apiFetch(
105
- `/@user/${params.username}/${params.name}/${params.version}`,
106
- { method: "GET" }
91
+ };
92
+ var getListSkillVersionsUrl = (username, name) => {
93
+ return `/@user/${username}/${name}/versions`;
94
+ };
95
+ var listSkillVersions = async (username, name, options) => {
96
+ return customFetch(
97
+ getListSkillVersionsUrl(username, name),
98
+ {
99
+ ...options,
100
+ method: "GET"
101
+ }
107
102
  );
108
- }
109
- async function publish(body) {
110
- return apiFetch("/publish", {
111
- method: "POST",
112
- body: JSON.stringify(body)
113
- });
114
- }
115
- async function deleteSkill(params) {
116
- return apiFetch(`/${params.name}`, { method: "DELETE" });
117
- }
118
- async function deleteVersion(params) {
119
- return apiFetch(`/${params.name}/${params.version}`, {
120
- method: "DELETE"
121
- });
122
- }
103
+ };
104
+ var getGetSkillVersionUrl = (username, name, version2) => {
105
+ return `/@user/${username}/${name}/${version2}`;
106
+ };
107
+ var getSkillVersion = async (username, name, version2, options) => {
108
+ return customFetch(
109
+ getGetSkillVersionUrl(username, name, version2),
110
+ {
111
+ ...options,
112
+ method: "GET"
113
+ }
114
+ );
115
+ };
116
+ var getPublishSkillUrl = () => {
117
+ return `/publish`;
118
+ };
119
+ var publishSkill = async (publishSkillBody, options) => {
120
+ return customFetch(
121
+ getPublishSkillUrl(),
122
+ {
123
+ ...options,
124
+ method: "POST",
125
+ headers: { "Content-Type": "application/json", ...options?.headers },
126
+ body: JSON.stringify(
127
+ publishSkillBody
128
+ )
129
+ }
130
+ );
131
+ };
132
+ var getDeleteSkillUrl = (name) => {
133
+ return `/${name}`;
134
+ };
135
+ var deleteSkill = async (name, deleteSkillBody, options) => {
136
+ return customFetch(
137
+ getDeleteSkillUrl(name),
138
+ {
139
+ ...options,
140
+ method: "DELETE",
141
+ headers: { "Content-Type": "application/json", ...options?.headers },
142
+ body: JSON.stringify(
143
+ deleteSkillBody
144
+ )
145
+ }
146
+ );
147
+ };
148
+ var getDeleteSkillVersionUrl = (name, version2) => {
149
+ return `/${name}/${version2}`;
150
+ };
151
+ var deleteSkillVersion = async (name, version2, deleteSkillVersionBody, options) => {
152
+ return customFetch(
153
+ getDeleteSkillVersionUrl(name, version2),
154
+ {
155
+ ...options,
156
+ method: "DELETE",
157
+ headers: { "Content-Type": "application/json", ...options?.headers },
158
+ body: JSON.stringify(
159
+ deleteSkillVersionBody
160
+ )
161
+ }
162
+ );
163
+ };
123
164
 
124
165
  // src/api-client.ts
125
166
  async function whoamiRequest(registryUrl, apiKey) {
126
167
  try {
127
- configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
128
- const user = await me();
168
+ configure({ baseUrl: registryUrl, apiKey });
169
+ const response = await me();
170
+ const user = response.data;
129
171
  if (!user) {
130
172
  return null;
131
173
  }
@@ -492,9 +534,10 @@ async function add(specifier, _options, globalOptions) {
492
534
  process.exit(1);
493
535
  }
494
536
  const { username, name, versionRange } = parsed;
495
- configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
537
+ configure({ baseUrl: registryUrl, apiKey });
496
538
  console.log(`Resolving ${specifier}...`);
497
- const versions = await listVersions({ username, name });
539
+ const versionsResponse = await listSkillVersions(username, name);
540
+ const versions = versionsResponse.data;
498
541
  if (versions.length === 0) {
499
542
  console.error(`Error: Skill @user/${username}/${name} not found`);
500
543
  process.exit(1);
@@ -509,11 +552,8 @@ async function add(specifier, _options, globalOptions) {
509
552
  process.exit(1);
510
553
  }
511
554
  console.log(`Installing @user/${username}/${name}@${resolved}...`);
512
- const versionInfo = await getVersion({
513
- username,
514
- name,
515
- version: resolved
516
- });
555
+ const versionResponse = await getSkillVersion(username, name, resolved);
556
+ const versionInfo = versionResponse.data;
517
557
  if (!versionInfo) {
518
558
  console.error(`Error: Version ${resolved} not found`);
519
559
  process.exit(1);
@@ -924,9 +964,9 @@ function getServerUrl(registryUrl) {
924
964
  return DEFAULT_WEB_APP_URL;
925
965
  }
926
966
  }
927
- async function exchangeCliToken(registryUrl, token) {
967
+ async function exchangeCliToken2(registryUrl, token) {
928
968
  const serverUrl = getServerUrl(registryUrl);
929
- const rpcUrl = `${serverUrl}/api/api-keys/rpc/exchangeCliToken`;
969
+ const rpcUrl = `${serverUrl}/api/api-keys/cli-token-exchange`;
930
970
  const response = await fetch(rpcUrl, {
931
971
  method: "POST",
932
972
  headers: {
@@ -1050,7 +1090,7 @@ async function browserLogin(globalOptions) {
1050
1090
  const token = await tokenPromise;
1051
1091
  cleanup();
1052
1092
  console.log("Received token, exchanging for API key...");
1053
- const { apiKey, username } = await exchangeCliToken(registryUrl, token);
1093
+ const { apiKey, username } = await exchangeCliToken2(registryUrl, token);
1054
1094
  await setProfileCredentials(
1055
1095
  resolved.profileName,
1056
1096
  apiKey,
@@ -1175,11 +1215,17 @@ async function publishCommand(options, globalOptions) {
1175
1215
  );
1176
1216
  const tarballBuffer = await readFile(tarballPath);
1177
1217
  const tarballBase64 = tarballBuffer.toString("base64");
1178
- configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
1179
- const result = await publish({
1218
+ configure({ baseUrl: registryUrl, apiKey });
1219
+ const response = await publishSkill({
1180
1220
  manifest: packageJson2,
1181
1221
  tarballBase64
1182
1222
  });
1223
+ if (response.status !== 200) {
1224
+ const errorData = response.data;
1225
+ const errorMessage = errorData?.error || errorData?.message || "Publish failed";
1226
+ throw new Error(errorMessage);
1227
+ }
1228
+ const result = response.data;
1183
1229
  console.log(
1184
1230
  `
1185
1231
  Published @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
@@ -1295,7 +1341,7 @@ async function unpublish(specifier, options, globalOptions) {
1295
1341
  process.exit(1);
1296
1342
  }
1297
1343
  const { username, name, versionRange } = parsed;
1298
- configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
1344
+ configure({ baseUrl: registryUrl, apiKey });
1299
1345
  if (versionRange) {
1300
1346
  console.log(`Unpublishing ${specifier}...`);
1301
1347
  if (!options.force) {
@@ -1304,16 +1350,14 @@ async function unpublish(specifier, options, globalOptions) {
1304
1350
  );
1305
1351
  process.exit(1);
1306
1352
  }
1307
- const result = await deleteVersion({
1308
- name,
1309
- version: versionRange
1310
- });
1311
- if (result.success) {
1312
- console.log(`Unpublished @user/${username}/${name}@${versionRange}`);
1313
- } else {
1314
- console.error(`Error: Failed to unpublish. Version may not exist.`);
1353
+ const response = await deleteSkillVersion(name, versionRange);
1354
+ if (response.status !== 200) {
1355
+ const errorData = response.data;
1356
+ const errorMessage = errorData?.error || errorData?.message || "Failed to unpublish. Version may not exist.";
1357
+ console.error(`Error: ${errorMessage}`);
1315
1358
  process.exit(1);
1316
1359
  }
1360
+ console.log(`Unpublished @user/${username}/${name}@${versionRange}`);
1317
1361
  } else {
1318
1362
  console.log(`Unpublishing all versions of @user/${username}/${name}...`);
1319
1363
  if (!options.force) {
@@ -1322,13 +1366,14 @@ async function unpublish(specifier, options, globalOptions) {
1322
1366
  );
1323
1367
  process.exit(1);
1324
1368
  }
1325
- const result = await deleteSkill({ name });
1326
- if (result.success) {
1327
- console.log(`Unpublished @user/${username}/${name} (all versions)`);
1328
- } else {
1329
- console.error(`Error: Failed to unpublish. Skill may not exist.`);
1369
+ const response = await deleteSkill(name);
1370
+ if (response.status !== 200) {
1371
+ const errorData = response.data;
1372
+ const errorMessage = errorData?.error || errorData?.message || "Failed to unpublish. Skill may not exist.";
1373
+ console.error(`Error: ${errorMessage}`);
1330
1374
  process.exit(1);
1331
1375
  }
1376
+ console.log(`Unpublished @user/${username}/${name} (all versions)`);
1332
1377
  }
1333
1378
  } catch (error) {
1334
1379
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -1347,7 +1392,7 @@ async function update(options, globalOptions) {
1347
1392
  console.log("No skills installed.");
1348
1393
  return;
1349
1394
  }
1350
- configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
1395
+ configure({ baseUrl: registryUrl, apiKey });
1351
1396
  const updates = [];
1352
1397
  console.log("Checking for updates...\n");
1353
1398
  for (const { name, entry } of skills) {
@@ -1355,10 +1400,8 @@ async function update(options, globalOptions) {
1355
1400
  if (!match) continue;
1356
1401
  const [, username, skillName] = match;
1357
1402
  try {
1358
- const versions = await listVersions({
1359
- username,
1360
- name: skillName
1361
- });
1403
+ const versionsResponse = await listSkillVersions(username, skillName);
1404
+ const versions = versionsResponse.data;
1362
1405
  if (versions.length === 0) continue;
1363
1406
  const versionStrings = versions.map(
1364
1407
  (v) => v.version