@anytio/pspm 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1532 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath, URL } from 'url';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { stat, writeFile, mkdir, rm, access, readFile } from 'fs/promises';
|
|
7
|
+
import { randomBytes, createHash } from 'crypto';
|
|
8
|
+
import * as semver from 'semver';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import http from 'http';
|
|
11
|
+
import open from 'open';
|
|
12
|
+
import { exec as exec$1 } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
|
|
15
|
+
function calculateIntegrity(data) {
|
|
16
|
+
const hash = createHash("sha256").update(data).digest("base64");
|
|
17
|
+
return `sha256-${hash}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ../../packages/shared/pspm-types/src/specifier.ts
|
|
21
|
+
var SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
|
|
22
|
+
function parseSkillSpecifier(specifier) {
|
|
23
|
+
const match = specifier.match(SPECIFIER_PATTERN);
|
|
24
|
+
if (!match) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
username: match[1],
|
|
29
|
+
name: match[2],
|
|
30
|
+
versionRange: match[3]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function resolveVersion(range, availableVersions) {
|
|
34
|
+
const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
|
|
35
|
+
if (!range || range === "latest" || range === "*") {
|
|
36
|
+
return sorted[0] ?? null;
|
|
37
|
+
}
|
|
38
|
+
return semver.maxSatisfying(sorted, range);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../../packages/sdk/src/client.ts
|
|
42
|
+
var config = null;
|
|
43
|
+
function configure(options) {
|
|
44
|
+
config = options;
|
|
45
|
+
}
|
|
46
|
+
function getConfig() {
|
|
47
|
+
if (!config) {
|
|
48
|
+
throw new Error("SDK not configured. Call configure() first.");
|
|
49
|
+
}
|
|
50
|
+
return config;
|
|
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 = {}) {
|
|
61
|
+
const { baseUrl, apiKey } = getConfig();
|
|
62
|
+
const url = `${baseUrl}${path}`;
|
|
63
|
+
const response = await fetch(url, {
|
|
64
|
+
...options,
|
|
65
|
+
headers: {
|
|
66
|
+
...options.headers,
|
|
67
|
+
Authorization: `Bearer ${apiKey}`,
|
|
68
|
+
"Content-Type": "application/json"
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorText = await response.text();
|
|
73
|
+
let errorMessage = `API error: ${response.status}`;
|
|
74
|
+
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
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
if (errorText) {
|
|
83
|
+
errorMessage = errorText;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw new SDKError(errorMessage, response.status, errorText);
|
|
87
|
+
}
|
|
88
|
+
const text = await response.text();
|
|
89
|
+
if (!text) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
async function me() {
|
|
95
|
+
return apiFetch("/me", { method: "GET" });
|
|
96
|
+
}
|
|
97
|
+
async function listVersions(params) {
|
|
98
|
+
return apiFetch(
|
|
99
|
+
`/@user/${params.username}/${params.name}/versions`,
|
|
100
|
+
{ method: "GET" }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
async function getVersion(params) {
|
|
104
|
+
return apiFetch(
|
|
105
|
+
`/@user/${params.username}/${params.name}/${params.version}`,
|
|
106
|
+
{ method: "GET" }
|
|
107
|
+
);
|
|
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
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/api-client.ts
|
|
125
|
+
async function whoamiRequest(registryUrl, apiKey) {
|
|
126
|
+
try {
|
|
127
|
+
configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
|
|
128
|
+
const user = await me();
|
|
129
|
+
if (!user) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
username: user.username,
|
|
134
|
+
userId: user.userId
|
|
135
|
+
};
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/errors.ts
|
|
142
|
+
var ConfigError = class extends Error {
|
|
143
|
+
constructor(message) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = "ConfigError";
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var ProfileNotFoundError = class extends ConfigError {
|
|
149
|
+
constructor(profileName) {
|
|
150
|
+
super(
|
|
151
|
+
`Profile "${profileName}" not found. Run 'pspm config list' to see available profiles.`
|
|
152
|
+
);
|
|
153
|
+
this.name = "ProfileNotFoundError";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var NotLoggedInError = class extends ConfigError {
|
|
157
|
+
constructor() {
|
|
158
|
+
super(
|
|
159
|
+
"Not logged in. Run 'pspm login --api-key <key>' first, or set PSPM_API_KEY env var."
|
|
160
|
+
);
|
|
161
|
+
this.name = "NotLoggedInError";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/config.ts
|
|
166
|
+
var DEFAULT_REGISTRY_URL = "https://pspm.dev/api/skills";
|
|
167
|
+
function getConfigPath() {
|
|
168
|
+
return join(homedir(), ".pspm", "config.json");
|
|
169
|
+
}
|
|
170
|
+
function getSkillsDir() {
|
|
171
|
+
return join(process.cwd(), ".skills");
|
|
172
|
+
}
|
|
173
|
+
function getLockfilePath() {
|
|
174
|
+
return join(process.cwd(), "skill-lock.json");
|
|
175
|
+
}
|
|
176
|
+
function createDefaultV2Config() {
|
|
177
|
+
return {
|
|
178
|
+
version: 2,
|
|
179
|
+
defaultProfile: "default",
|
|
180
|
+
profiles: {
|
|
181
|
+
default: {
|
|
182
|
+
registryUrl: DEFAULT_REGISTRY_URL
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function migrateV1ToV2(v1Config) {
|
|
188
|
+
return {
|
|
189
|
+
version: 2,
|
|
190
|
+
defaultProfile: "default",
|
|
191
|
+
profiles: {
|
|
192
|
+
default: {
|
|
193
|
+
registryUrl: v1Config.registryUrl || DEFAULT_REGISTRY_URL,
|
|
194
|
+
apiKey: v1Config.apiKey,
|
|
195
|
+
username: v1Config.username
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function isV2Config(config2) {
|
|
201
|
+
return typeof config2 === "object" && config2 !== null && "version" in config2 && config2.version === 2;
|
|
202
|
+
}
|
|
203
|
+
async function readUserConfig() {
|
|
204
|
+
const configPath = getConfigPath();
|
|
205
|
+
if (process.env.PSPM_DEBUG) {
|
|
206
|
+
console.log(`[config] Reading config from: ${configPath}`);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const content = await readFile(configPath, "utf-8");
|
|
210
|
+
if (process.env.PSPM_DEBUG) {
|
|
211
|
+
const apiKeyMatch = content.match(/"apiKey":\s*"([^"]+)"/);
|
|
212
|
+
if (apiKeyMatch) {
|
|
213
|
+
console.log(
|
|
214
|
+
`[config] Raw apiKey from file: ${apiKeyMatch[1].substring(
|
|
215
|
+
0,
|
|
216
|
+
15
|
|
217
|
+
)}...`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const parsed = JSON.parse(content);
|
|
222
|
+
if (process.env.PSPM_DEBUG) {
|
|
223
|
+
console.log(
|
|
224
|
+
`[config] Parsed config version: ${parsed.version}`
|
|
225
|
+
);
|
|
226
|
+
console.log(
|
|
227
|
+
`[config] Profiles found: ${Object.keys(
|
|
228
|
+
parsed.profiles || {}
|
|
229
|
+
).join(", ")}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (isV2Config(parsed)) {
|
|
233
|
+
return parsed;
|
|
234
|
+
}
|
|
235
|
+
const v2Config = migrateV1ToV2(parsed);
|
|
236
|
+
await writeUserConfig(v2Config);
|
|
237
|
+
return v2Config;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (process.env.PSPM_DEBUG) {
|
|
240
|
+
console.log(
|
|
241
|
+
`[config] Error reading config: ${error instanceof Error ? error.message : String(error)}`
|
|
242
|
+
);
|
|
243
|
+
console.log(`[config] Falling back to default config`);
|
|
244
|
+
}
|
|
245
|
+
return createDefaultV2Config();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function writeUserConfig(config2) {
|
|
249
|
+
const configPath = getConfigPath();
|
|
250
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
251
|
+
await writeFile(configPath, JSON.stringify(config2, null, 2));
|
|
252
|
+
}
|
|
253
|
+
async function findProjectConfig() {
|
|
254
|
+
let currentDir = process.cwd();
|
|
255
|
+
const root = dirname(currentDir);
|
|
256
|
+
while (currentDir !== root) {
|
|
257
|
+
const configPath = join(currentDir, ".pspmrc");
|
|
258
|
+
try {
|
|
259
|
+
const stats = await stat(configPath);
|
|
260
|
+
if (stats.isFile()) {
|
|
261
|
+
const content = await readFile(configPath, "utf-8");
|
|
262
|
+
return JSON.parse(content);
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
currentDir = dirname(currentDir);
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
async function resolveConfig(globalOptions) {
|
|
271
|
+
const userConfig = await readUserConfig();
|
|
272
|
+
const projectConfig = await findProjectConfig();
|
|
273
|
+
let profileName;
|
|
274
|
+
let profileSource;
|
|
275
|
+
if (globalOptions?.profile) {
|
|
276
|
+
profileName = globalOptions.profile;
|
|
277
|
+
profileSource = "cli";
|
|
278
|
+
} else if (process.env.PSPM_PROFILE) {
|
|
279
|
+
profileName = process.env.PSPM_PROFILE;
|
|
280
|
+
profileSource = "env";
|
|
281
|
+
} else if (projectConfig?.profile) {
|
|
282
|
+
profileName = projectConfig.profile;
|
|
283
|
+
profileSource = "project";
|
|
284
|
+
} else if (userConfig.defaultProfile) {
|
|
285
|
+
profileName = userConfig.defaultProfile;
|
|
286
|
+
profileSource = "user";
|
|
287
|
+
} else {
|
|
288
|
+
profileName = "default";
|
|
289
|
+
profileSource = "default";
|
|
290
|
+
}
|
|
291
|
+
const profile = userConfig.profiles[profileName];
|
|
292
|
+
if (!profile) {
|
|
293
|
+
if (process.env.PSPM_DEBUG) {
|
|
294
|
+
console.log(
|
|
295
|
+
`[config] Profile "${profileName}" not found. Available: ${Object.keys(
|
|
296
|
+
userConfig.profiles
|
|
297
|
+
).join(", ")}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
throw new ProfileNotFoundError(profileName);
|
|
301
|
+
}
|
|
302
|
+
if (process.env.PSPM_DEBUG) {
|
|
303
|
+
console.log(
|
|
304
|
+
`[config] Using profile: ${profileName} (source: ${profileSource})`
|
|
305
|
+
);
|
|
306
|
+
console.log(`[config] Profile has apiKey: ${!!profile.apiKey}`);
|
|
307
|
+
}
|
|
308
|
+
let registryUrl = profile.registryUrl;
|
|
309
|
+
let apiKey = profile.apiKey;
|
|
310
|
+
const username = profile.username;
|
|
311
|
+
if (projectConfig?.registryUrl) {
|
|
312
|
+
registryUrl = projectConfig.registryUrl;
|
|
313
|
+
}
|
|
314
|
+
if (process.env.PSPM_REGISTRY_URL) {
|
|
315
|
+
registryUrl = process.env.PSPM_REGISTRY_URL;
|
|
316
|
+
}
|
|
317
|
+
if (process.env.PSPM_API_KEY) {
|
|
318
|
+
apiKey = process.env.PSPM_API_KEY;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
profileName,
|
|
322
|
+
profileSource,
|
|
323
|
+
registryUrl,
|
|
324
|
+
apiKey,
|
|
325
|
+
username
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async function getProfile(profileName) {
|
|
329
|
+
const userConfig = await readUserConfig();
|
|
330
|
+
return userConfig.profiles[profileName] || null;
|
|
331
|
+
}
|
|
332
|
+
async function listProfiles() {
|
|
333
|
+
const userConfig = await readUserConfig();
|
|
334
|
+
return Object.keys(userConfig.profiles);
|
|
335
|
+
}
|
|
336
|
+
async function setProfile(profileName, config2) {
|
|
337
|
+
const userConfig = await readUserConfig();
|
|
338
|
+
const existing = userConfig.profiles[profileName] || {
|
|
339
|
+
registryUrl: DEFAULT_REGISTRY_URL
|
|
340
|
+
};
|
|
341
|
+
userConfig.profiles[profileName] = {
|
|
342
|
+
...existing,
|
|
343
|
+
...config2
|
|
344
|
+
};
|
|
345
|
+
await writeUserConfig(userConfig);
|
|
346
|
+
}
|
|
347
|
+
async function deleteProfile(profileName) {
|
|
348
|
+
const userConfig = await readUserConfig();
|
|
349
|
+
if (!userConfig.profiles[profileName]) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
delete userConfig.profiles[profileName];
|
|
353
|
+
if (userConfig.defaultProfile === profileName) {
|
|
354
|
+
const remainingProfiles = Object.keys(userConfig.profiles);
|
|
355
|
+
userConfig.defaultProfile = remainingProfiles[0] || "default";
|
|
356
|
+
}
|
|
357
|
+
await writeUserConfig(userConfig);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
async function setDefaultProfile(profileName) {
|
|
361
|
+
const userConfig = await readUserConfig();
|
|
362
|
+
if (!userConfig.profiles[profileName]) {
|
|
363
|
+
throw new ProfileNotFoundError(profileName);
|
|
364
|
+
}
|
|
365
|
+
userConfig.defaultProfile = profileName;
|
|
366
|
+
await writeUserConfig(userConfig);
|
|
367
|
+
}
|
|
368
|
+
async function getDefaultProfileName() {
|
|
369
|
+
const userConfig = await readUserConfig();
|
|
370
|
+
return userConfig.defaultProfile;
|
|
371
|
+
}
|
|
372
|
+
({
|
|
373
|
+
registryUrl: process.env.PSPM_REGISTRY_URL || DEFAULT_REGISTRY_URL
|
|
374
|
+
});
|
|
375
|
+
async function isLoggedIn(globalOptions) {
|
|
376
|
+
try {
|
|
377
|
+
const resolved = await resolveConfig(globalOptions);
|
|
378
|
+
return !!resolved.apiKey;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function requireApiKey(globalOptions) {
|
|
384
|
+
const resolved = await resolveConfig(globalOptions);
|
|
385
|
+
if (!resolved.apiKey) {
|
|
386
|
+
if (process.env.PSPM_DEBUG) {
|
|
387
|
+
console.log(
|
|
388
|
+
`[config] requireApiKey: No API key found for profile "${resolved.profileName}"`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
throw new NotLoggedInError();
|
|
392
|
+
}
|
|
393
|
+
if (process.env.PSPM_DEBUG) {
|
|
394
|
+
console.log(
|
|
395
|
+
`[config] requireApiKey: Got API key (${resolved.apiKey.substring(
|
|
396
|
+
0,
|
|
397
|
+
10
|
|
398
|
+
)}...)`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return resolved.apiKey;
|
|
402
|
+
}
|
|
403
|
+
async function getRegistryUrl(globalOptions) {
|
|
404
|
+
const resolved = await resolveConfig(globalOptions);
|
|
405
|
+
return resolved.registryUrl;
|
|
406
|
+
}
|
|
407
|
+
async function clearProfileCredentials(profileName) {
|
|
408
|
+
const userConfig = await readUserConfig();
|
|
409
|
+
if (userConfig.profiles[profileName]) {
|
|
410
|
+
userConfig.profiles[profileName].apiKey = void 0;
|
|
411
|
+
userConfig.profiles[profileName].username = void 0;
|
|
412
|
+
await writeUserConfig(userConfig);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function setProfileCredentials(profileName, apiKey, username, registryUrl) {
|
|
416
|
+
const userConfig = await readUserConfig();
|
|
417
|
+
if (!userConfig.profiles[profileName]) {
|
|
418
|
+
userConfig.profiles[profileName] = {
|
|
419
|
+
registryUrl: registryUrl || DEFAULT_REGISTRY_URL
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
userConfig.profiles[profileName].apiKey = apiKey;
|
|
423
|
+
if (username) {
|
|
424
|
+
userConfig.profiles[profileName].username = username;
|
|
425
|
+
}
|
|
426
|
+
if (registryUrl) {
|
|
427
|
+
userConfig.profiles[profileName].registryUrl = registryUrl;
|
|
428
|
+
}
|
|
429
|
+
await writeUserConfig(userConfig);
|
|
430
|
+
}
|
|
431
|
+
async function readLockfile() {
|
|
432
|
+
const lockfilePath = getLockfilePath();
|
|
433
|
+
try {
|
|
434
|
+
const content = await readFile(lockfilePath, "utf-8");
|
|
435
|
+
return JSON.parse(content);
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async function writeLockfile(lockfile) {
|
|
441
|
+
const lockfilePath = getLockfilePath();
|
|
442
|
+
await mkdir(dirname(lockfilePath), { recursive: true });
|
|
443
|
+
await writeFile(lockfilePath, `${JSON.stringify(lockfile, null, 2)}
|
|
444
|
+
`);
|
|
445
|
+
}
|
|
446
|
+
async function createEmptyLockfile() {
|
|
447
|
+
const registryUrl = await getRegistryUrl();
|
|
448
|
+
return {
|
|
449
|
+
lockfileVersion: 1,
|
|
450
|
+
registryUrl,
|
|
451
|
+
skills: {}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
async function addToLockfile(fullName, entry) {
|
|
455
|
+
let lockfile = await readLockfile();
|
|
456
|
+
if (!lockfile) {
|
|
457
|
+
lockfile = await createEmptyLockfile();
|
|
458
|
+
}
|
|
459
|
+
lockfile.skills[fullName] = entry;
|
|
460
|
+
await writeLockfile(lockfile);
|
|
461
|
+
}
|
|
462
|
+
async function removeFromLockfile(fullName) {
|
|
463
|
+
const lockfile = await readLockfile();
|
|
464
|
+
if (!lockfile || !lockfile.skills[fullName]) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
delete lockfile.skills[fullName];
|
|
468
|
+
await writeLockfile(lockfile);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
async function listLockfileSkills() {
|
|
472
|
+
const lockfile = await readLockfile();
|
|
473
|
+
if (!lockfile) {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
return Object.entries(lockfile.skills).map(([name, entry]) => ({
|
|
477
|
+
name,
|
|
478
|
+
entry
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/commands/add.ts
|
|
483
|
+
async function add(specifier, _options, globalOptions) {
|
|
484
|
+
try {
|
|
485
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
486
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
487
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
488
|
+
if (!parsed) {
|
|
489
|
+
console.error(
|
|
490
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}]`
|
|
491
|
+
);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
const { username, name, versionRange } = parsed;
|
|
495
|
+
configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
|
|
496
|
+
console.log(`Resolving ${specifier}...`);
|
|
497
|
+
const versions = await listVersions({ username, name });
|
|
498
|
+
if (versions.length === 0) {
|
|
499
|
+
console.error(`Error: Skill @user/${username}/${name} not found`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
const versionStrings = versions.map((v) => v.version);
|
|
503
|
+
const resolved = resolveVersion(versionRange || "*", versionStrings);
|
|
504
|
+
if (!resolved) {
|
|
505
|
+
console.error(
|
|
506
|
+
`Error: No version matching "${versionRange || "latest"}" found for @user/${username}/${name}`
|
|
507
|
+
);
|
|
508
|
+
console.error(`Available versions: ${versionStrings.join(", ")}`);
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
console.log(`Installing @user/${username}/${name}@${resolved}...`);
|
|
512
|
+
const versionInfo = await getVersion({
|
|
513
|
+
username,
|
|
514
|
+
name,
|
|
515
|
+
version: resolved
|
|
516
|
+
});
|
|
517
|
+
if (!versionInfo) {
|
|
518
|
+
console.error(`Error: Version ${resolved} not found`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
const downloadUrl = `${registryUrl}/@user/${username}/${name}/${resolved}/download`;
|
|
522
|
+
const tarballResponse = await fetch(downloadUrl, {
|
|
523
|
+
headers: {
|
|
524
|
+
Authorization: `Bearer ${apiKey}`
|
|
525
|
+
},
|
|
526
|
+
redirect: "follow"
|
|
527
|
+
});
|
|
528
|
+
if (!tarballResponse.ok) {
|
|
529
|
+
console.error(
|
|
530
|
+
`Error: Failed to download tarball (${tarballResponse.status})`
|
|
531
|
+
);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
|
|
535
|
+
const integrity = calculateIntegrity(tarballBuffer);
|
|
536
|
+
const expectedIntegrity = `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`;
|
|
537
|
+
if (integrity !== expectedIntegrity) {
|
|
538
|
+
console.error("Error: Checksum verification failed");
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
const skillsDir = getSkillsDir();
|
|
542
|
+
const destDir = join(skillsDir, username, name);
|
|
543
|
+
await mkdir(destDir, { recursive: true });
|
|
544
|
+
const { writeFile: writeFile4 } = await import('fs/promises');
|
|
545
|
+
const tempFile = join(destDir, ".temp.tgz");
|
|
546
|
+
await writeFile4(tempFile, tarballBuffer);
|
|
547
|
+
const { exec: exec2 } = await import('child_process');
|
|
548
|
+
const { promisify: promisify2 } = await import('util');
|
|
549
|
+
const execAsync = promisify2(exec2);
|
|
550
|
+
try {
|
|
551
|
+
await rm(destDir, { recursive: true, force: true });
|
|
552
|
+
await mkdir(destDir, { recursive: true });
|
|
553
|
+
await writeFile4(tempFile, tarballBuffer);
|
|
554
|
+
await execAsync(
|
|
555
|
+
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
556
|
+
);
|
|
557
|
+
} finally {
|
|
558
|
+
await rm(tempFile, { force: true });
|
|
559
|
+
}
|
|
560
|
+
const fullName = `@user/${username}/${name}`;
|
|
561
|
+
await addToLockfile(fullName, {
|
|
562
|
+
version: resolved,
|
|
563
|
+
resolved: versionInfo.downloadUrl,
|
|
564
|
+
integrity
|
|
565
|
+
});
|
|
566
|
+
console.log(`Installed @user/${username}/${name}@${resolved}`);
|
|
567
|
+
console.log(`Location: ${destDir}`);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
570
|
+
console.error(`Error: ${message}`);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/commands/config/add.ts
|
|
576
|
+
async function configAdd(name, options) {
|
|
577
|
+
try {
|
|
578
|
+
const existing = await getProfile(name);
|
|
579
|
+
if (existing) {
|
|
580
|
+
console.error(`Error: Profile "${name}" already exists.`);
|
|
581
|
+
console.log("Use 'pspm config set' to modify it.");
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
await setProfile(name, {
|
|
585
|
+
registryUrl: options.registryUrl || "https://pspm.dev/api/skills"
|
|
586
|
+
});
|
|
587
|
+
console.log(`Created profile "${name}".`);
|
|
588
|
+
if (options.registryUrl) {
|
|
589
|
+
console.log(` Registry URL: ${options.registryUrl}`);
|
|
590
|
+
}
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log(
|
|
593
|
+
`To set credentials, run: pspm login --api-key <key> --profile ${name}`
|
|
594
|
+
);
|
|
595
|
+
console.log(`To make this the default, run: pspm config use ${name}`);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
598
|
+
console.error(`Error: ${message}`);
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/commands/config/delete.ts
|
|
604
|
+
async function configDelete(name) {
|
|
605
|
+
try {
|
|
606
|
+
const profiles = await listProfiles();
|
|
607
|
+
const defaultProfile = await getDefaultProfileName();
|
|
608
|
+
if (!profiles.includes(name)) {
|
|
609
|
+
console.error(`Error: Profile "${name}" not found.`);
|
|
610
|
+
console.log("Run 'pspm config list' to see available profiles.");
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
if (profiles.length === 1) {
|
|
614
|
+
console.error("Error: Cannot delete the last profile.");
|
|
615
|
+
console.log("Create another profile first, then delete this one.");
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
const wasDefault = name === defaultProfile;
|
|
619
|
+
const success = await deleteProfile(name);
|
|
620
|
+
if (success) {
|
|
621
|
+
console.log(`Deleted profile "${name}".`);
|
|
622
|
+
if (wasDefault) {
|
|
623
|
+
const newDefault = await getDefaultProfileName();
|
|
624
|
+
console.log(`Default profile is now "${newDefault}".`);
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
console.error(`Error: Failed to delete profile "${name}".`);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
} catch (error) {
|
|
631
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
632
|
+
console.error(`Error: ${message}`);
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function configInit(options) {
|
|
637
|
+
try {
|
|
638
|
+
const configPath = join(process.cwd(), ".pspmrc");
|
|
639
|
+
try {
|
|
640
|
+
await stat(configPath);
|
|
641
|
+
console.error("Error: .pspmrc already exists in this directory.");
|
|
642
|
+
process.exit(1);
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
const config2 = {};
|
|
646
|
+
if (options.profile) {
|
|
647
|
+
config2.profile = options.profile;
|
|
648
|
+
}
|
|
649
|
+
if (options.registryUrl) {
|
|
650
|
+
config2.registryUrl = options.registryUrl;
|
|
651
|
+
}
|
|
652
|
+
if (Object.keys(config2).length === 0) {
|
|
653
|
+
config2.profile = "development";
|
|
654
|
+
}
|
|
655
|
+
await writeFile(configPath, `${JSON.stringify(config2, null, 2)}
|
|
656
|
+
`);
|
|
657
|
+
console.log("Created .pspmrc");
|
|
658
|
+
console.log("");
|
|
659
|
+
console.log("Contents:");
|
|
660
|
+
console.log(JSON.stringify(config2, null, 2));
|
|
661
|
+
console.log("");
|
|
662
|
+
console.log("Note: .pspmrc should be committed to version control.");
|
|
663
|
+
console.log("API keys should NOT be stored here - use pspm login instead.");
|
|
664
|
+
} catch (error) {
|
|
665
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
666
|
+
console.error(`Error: ${message}`);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/commands/config/list.ts
|
|
672
|
+
async function configList() {
|
|
673
|
+
try {
|
|
674
|
+
const profiles = await listProfiles();
|
|
675
|
+
const defaultProfile = await getDefaultProfileName();
|
|
676
|
+
if (profiles.length === 0) {
|
|
677
|
+
console.log("No profiles configured.");
|
|
678
|
+
console.log("Run 'pspm config add <name>' to create one.");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
console.log("Profiles:\n");
|
|
682
|
+
for (const name of profiles) {
|
|
683
|
+
const marker = name === defaultProfile ? " (default)" : "";
|
|
684
|
+
console.log(` ${name}${marker}`);
|
|
685
|
+
}
|
|
686
|
+
console.log("");
|
|
687
|
+
} catch (error) {
|
|
688
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
689
|
+
console.error(`Error: ${message}`);
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/commands/config/set.ts
|
|
695
|
+
var VALID_KEYS = ["registryUrl", "apiKey", "username"];
|
|
696
|
+
async function configSet(profile, key, value) {
|
|
697
|
+
try {
|
|
698
|
+
if (!VALID_KEYS.includes(key)) {
|
|
699
|
+
console.error(`Error: Invalid key "${key}".`);
|
|
700
|
+
console.log(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
const existing = await getProfile(profile);
|
|
704
|
+
if (!existing) {
|
|
705
|
+
console.error(`Error: Profile "${profile}" not found.`);
|
|
706
|
+
console.log("Run 'pspm config add' to create it first.");
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
await setProfile(profile, { [key]: value });
|
|
710
|
+
console.log(`Updated ${key} for profile "${profile}".`);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
713
|
+
console.error(`Error: ${message}`);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/commands/config/show.ts
|
|
719
|
+
async function configShow(options, globalOptions) {
|
|
720
|
+
try {
|
|
721
|
+
if (options.name) {
|
|
722
|
+
const profile = await getProfile(options.name);
|
|
723
|
+
if (!profile) {
|
|
724
|
+
console.error(`Error: Profile "${options.name}" not found.`);
|
|
725
|
+
console.log("Run 'pspm config list' to see available profiles.");
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const defaultProfile = await getDefaultProfileName();
|
|
729
|
+
const isDefault = options.name === defaultProfile;
|
|
730
|
+
console.log(`Profile: ${options.name}${isDefault ? " (default)" : ""}
|
|
731
|
+
`);
|
|
732
|
+
console.log(` Registry URL: ${profile.registryUrl}`);
|
|
733
|
+
console.log(` API Key: ${profile.apiKey ? "***" : "(not set)"}`);
|
|
734
|
+
console.log(` Username: ${profile.username || "(not set)"}`);
|
|
735
|
+
} else {
|
|
736
|
+
const resolved = await resolveConfig(globalOptions);
|
|
737
|
+
const projectConfig = await findProjectConfig();
|
|
738
|
+
const configPath = getConfigPath();
|
|
739
|
+
console.log("Resolved Configuration:\n");
|
|
740
|
+
console.log(` Active Profile: ${resolved.profileName}`);
|
|
741
|
+
console.log(` Profile Source: ${formatSource(resolved.profileSource)}`);
|
|
742
|
+
console.log(` Registry URL: ${resolved.registryUrl}`);
|
|
743
|
+
console.log(` API Key: ${resolved.apiKey ? "***" : "(not set)"}`);
|
|
744
|
+
console.log(` Username: ${resolved.username || "(not set)"}`);
|
|
745
|
+
console.log("");
|
|
746
|
+
console.log("Config Locations:");
|
|
747
|
+
console.log(` User config: ${configPath}`);
|
|
748
|
+
console.log(` Project config: ${projectConfig ? ".pspmrc" : "(none)"}`);
|
|
749
|
+
}
|
|
750
|
+
} catch (error) {
|
|
751
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
752
|
+
console.error(`Error: ${message}`);
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function formatSource(source) {
|
|
757
|
+
switch (source) {
|
|
758
|
+
case "cli":
|
|
759
|
+
return "--profile flag";
|
|
760
|
+
case "env":
|
|
761
|
+
return "PSPM_PROFILE env var";
|
|
762
|
+
case "project":
|
|
763
|
+
return ".pspmrc file";
|
|
764
|
+
case "user":
|
|
765
|
+
return "user config default";
|
|
766
|
+
case "default":
|
|
767
|
+
return "fallback default";
|
|
768
|
+
default:
|
|
769
|
+
return source;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/commands/config/use.ts
|
|
774
|
+
async function configUse(name) {
|
|
775
|
+
try {
|
|
776
|
+
const profile = await getProfile(name);
|
|
777
|
+
if (!profile) {
|
|
778
|
+
console.error(`Error: Profile "${name}" not found.`);
|
|
779
|
+
console.log("Run 'pspm config list' to see available profiles.");
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
await setDefaultProfile(name);
|
|
783
|
+
console.log(`Default profile set to "${name}".`);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
786
|
+
console.error(`Error: ${message}`);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
async function install(options, globalOptions) {
|
|
791
|
+
try {
|
|
792
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
793
|
+
await getRegistryUrl(globalOptions);
|
|
794
|
+
const skillsDir = options.dir || getSkillsDir();
|
|
795
|
+
const lockfile = await readLockfile();
|
|
796
|
+
if (!lockfile) {
|
|
797
|
+
if (options.frozenLockfile) {
|
|
798
|
+
console.error(
|
|
799
|
+
"Error: No lockfile found. Cannot install with --frozen-lockfile"
|
|
800
|
+
);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
803
|
+
console.log("No lockfile found. Nothing to install.");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const skillCount = Object.keys(lockfile.skills).length;
|
|
807
|
+
if (skillCount === 0) {
|
|
808
|
+
console.log("No skills in lockfile. Nothing to install.");
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
console.log(`Installing ${skillCount} skill(s)...
|
|
812
|
+
`);
|
|
813
|
+
const entries = Object.entries(lockfile.skills);
|
|
814
|
+
for (const [fullName, entry] of entries) {
|
|
815
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
816
|
+
if (!match) {
|
|
817
|
+
console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const [, username, name] = match;
|
|
821
|
+
console.log(`Installing ${fullName}@${entry.version}...`);
|
|
822
|
+
const response = await fetch(entry.resolved, {
|
|
823
|
+
headers: {
|
|
824
|
+
Authorization: `Bearer ${apiKey}`
|
|
825
|
+
},
|
|
826
|
+
redirect: "follow"
|
|
827
|
+
});
|
|
828
|
+
if (!response.ok) {
|
|
829
|
+
console.error(
|
|
830
|
+
` Error: Failed to download ${fullName} (${response.status})`
|
|
831
|
+
);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
const tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
835
|
+
const { createHash: createHash2 } = await import('crypto');
|
|
836
|
+
const actualIntegrity = `sha256-${createHash2("sha256").update(tarballBuffer).digest("base64")}`;
|
|
837
|
+
if (actualIntegrity !== entry.integrity) {
|
|
838
|
+
console.error(` Error: Checksum verification failed for ${fullName}`);
|
|
839
|
+
if (options.frozenLockfile) {
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
const destDir = join(skillsDir, username, name);
|
|
845
|
+
await rm(destDir, { recursive: true, force: true });
|
|
846
|
+
await mkdir(destDir, { recursive: true });
|
|
847
|
+
const tempFile = join(destDir, ".temp.tgz");
|
|
848
|
+
const { writeFile: writeFile4 } = await import('fs/promises');
|
|
849
|
+
await writeFile4(tempFile, tarballBuffer);
|
|
850
|
+
const { exec: exec2 } = await import('child_process');
|
|
851
|
+
const { promisify: promisify2 } = await import('util');
|
|
852
|
+
const execAsync = promisify2(exec2);
|
|
853
|
+
try {
|
|
854
|
+
await execAsync(
|
|
855
|
+
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
856
|
+
);
|
|
857
|
+
} finally {
|
|
858
|
+
await rm(tempFile, { force: true });
|
|
859
|
+
}
|
|
860
|
+
console.log(` Installed to ${destDir}`);
|
|
861
|
+
}
|
|
862
|
+
console.log(`
|
|
863
|
+
All ${skillCount} skill(s) installed.`);
|
|
864
|
+
} catch (error) {
|
|
865
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
866
|
+
console.error(`Error: ${message}`);
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
async function list(options) {
|
|
871
|
+
try {
|
|
872
|
+
const skills = await listLockfileSkills();
|
|
873
|
+
if (skills.length === 0) {
|
|
874
|
+
console.log("No skills installed.");
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (options.json) {
|
|
878
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const skillsDir = getSkillsDir();
|
|
882
|
+
console.log("Installed skills:\n");
|
|
883
|
+
for (const { name, entry } of skills) {
|
|
884
|
+
const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
885
|
+
if (!match) continue;
|
|
886
|
+
const [, username, skillName] = match;
|
|
887
|
+
const skillPath = join(skillsDir, username, skillName);
|
|
888
|
+
let status = "installed";
|
|
889
|
+
try {
|
|
890
|
+
await access(skillPath);
|
|
891
|
+
} catch {
|
|
892
|
+
status = "missing";
|
|
893
|
+
}
|
|
894
|
+
console.log(` ${name}@${entry.version}`);
|
|
895
|
+
if (status === "missing") {
|
|
896
|
+
console.log(` Status: MISSING (run 'pspm install' to restore)`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
console.log(`
|
|
900
|
+
Total: ${skills.length} skill(s)`);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
903
|
+
console.error(`Error: ${message}`);
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
var DEFAULT_WEB_APP_URL = "https://pspm.dev";
|
|
908
|
+
function getWebAppUrl(registryUrl) {
|
|
909
|
+
if (process.env.PSPM_WEB_URL) {
|
|
910
|
+
return process.env.PSPM_WEB_URL.replace(/\/$/, "");
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const url = new URL(registryUrl);
|
|
914
|
+
return `${url.protocol}//${url.host}`;
|
|
915
|
+
} catch {
|
|
916
|
+
return DEFAULT_WEB_APP_URL;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function getServerUrl(registryUrl) {
|
|
920
|
+
try {
|
|
921
|
+
const url = new URL(registryUrl);
|
|
922
|
+
return `${url.protocol}//${url.host}`;
|
|
923
|
+
} catch {
|
|
924
|
+
return DEFAULT_WEB_APP_URL;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
async function exchangeCliToken(registryUrl, token) {
|
|
928
|
+
const serverUrl = getServerUrl(registryUrl);
|
|
929
|
+
const rpcUrl = `${serverUrl}/api/api-keys/rpc/exchangeCliToken`;
|
|
930
|
+
const response = await fetch(rpcUrl, {
|
|
931
|
+
method: "POST",
|
|
932
|
+
headers: {
|
|
933
|
+
"Content-Type": "application/json"
|
|
934
|
+
},
|
|
935
|
+
body: JSON.stringify({ token })
|
|
936
|
+
});
|
|
937
|
+
if (!response.ok) {
|
|
938
|
+
const errorText = await response.text();
|
|
939
|
+
throw new Error(`Failed to exchange token: ${errorText}`);
|
|
940
|
+
}
|
|
941
|
+
return response.json();
|
|
942
|
+
}
|
|
943
|
+
function startCallbackServer(expectedState) {
|
|
944
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
945
|
+
let resolveToken;
|
|
946
|
+
let rejectToken;
|
|
947
|
+
let timeoutId;
|
|
948
|
+
const tokenPromise = new Promise((resolve, reject) => {
|
|
949
|
+
resolveToken = resolve;
|
|
950
|
+
rejectToken = reject;
|
|
951
|
+
});
|
|
952
|
+
const server = http.createServer((req, res) => {
|
|
953
|
+
const url = new URL(req.url || "/", `http://localhost`);
|
|
954
|
+
if (url.pathname === "/callback") {
|
|
955
|
+
const token = url.searchParams.get("token");
|
|
956
|
+
const state = url.searchParams.get("state");
|
|
957
|
+
if (state !== expectedState) {
|
|
958
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
959
|
+
res.end(`
|
|
960
|
+
<html>
|
|
961
|
+
<body style="font-family: system-ui; text-align: center; padding: 40px;">
|
|
962
|
+
<h1 style="color: #dc2626;">Security Error</h1>
|
|
963
|
+
<p>State mismatch - this may be a security issue.</p>
|
|
964
|
+
<p>Please try running <code>pspm login</code> again.</p>
|
|
965
|
+
</body>
|
|
966
|
+
</html>
|
|
967
|
+
`);
|
|
968
|
+
rejectToken(new Error("State mismatch - possible CSRF attack"));
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (!token) {
|
|
972
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
973
|
+
res.end(`
|
|
974
|
+
<html>
|
|
975
|
+
<body style="font-family: system-ui; text-align: center; padding: 40px;">
|
|
976
|
+
<h1 style="color: #dc2626;">Error</h1>
|
|
977
|
+
<p>No token received from the server.</p>
|
|
978
|
+
<p>Please try running <code>pspm login</code> again.</p>
|
|
979
|
+
</body>
|
|
980
|
+
</html>
|
|
981
|
+
`);
|
|
982
|
+
rejectToken(new Error("No token received"));
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
986
|
+
res.end(`
|
|
987
|
+
<html>
|
|
988
|
+
<head>
|
|
989
|
+
<script>
|
|
990
|
+
// Try to close the window after a short delay
|
|
991
|
+
setTimeout(function() {
|
|
992
|
+
window.close();
|
|
993
|
+
}, 1500);
|
|
994
|
+
</script>
|
|
995
|
+
</head>
|
|
996
|
+
<body style="font-family: system-ui; text-align: center; padding: 40px;">
|
|
997
|
+
<h1 style="color: #16a34a;">Success!</h1>
|
|
998
|
+
<p>You are now logged in to PSPM.</p>
|
|
999
|
+
<p style="color: #666; font-size: 14px;">This window will close automatically, or you can close it manually.</p>
|
|
1000
|
+
</body>
|
|
1001
|
+
</html>
|
|
1002
|
+
`);
|
|
1003
|
+
resolveToken(token);
|
|
1004
|
+
} else {
|
|
1005
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1006
|
+
res.end("Not found");
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
const cleanup = () => {
|
|
1010
|
+
clearTimeout(timeoutId);
|
|
1011
|
+
server.close();
|
|
1012
|
+
};
|
|
1013
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1014
|
+
const address = server.address();
|
|
1015
|
+
if (typeof address === "object" && address !== null) {
|
|
1016
|
+
resolveServer({ port: address.port, tokenPromise, cleanup });
|
|
1017
|
+
} else {
|
|
1018
|
+
rejectServer(new Error("Failed to get server address"));
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
server.on("error", (err) => {
|
|
1022
|
+
rejectServer(err);
|
|
1023
|
+
});
|
|
1024
|
+
timeoutId = setTimeout(
|
|
1025
|
+
() => {
|
|
1026
|
+
rejectToken(new Error("Login timed out - please try again"));
|
|
1027
|
+
server.close();
|
|
1028
|
+
},
|
|
1029
|
+
5 * 60 * 1e3
|
|
1030
|
+
);
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
async function browserLogin(globalOptions) {
|
|
1034
|
+
const resolved = await resolveConfig(globalOptions);
|
|
1035
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1036
|
+
const webAppUrl = getWebAppUrl(registryUrl);
|
|
1037
|
+
const state = randomBytes(32).toString("base64url");
|
|
1038
|
+
console.log("Starting browser-based login...");
|
|
1039
|
+
const { port, tokenPromise, cleanup } = await startCallbackServer(state);
|
|
1040
|
+
const loginUrl = `${webAppUrl}/cli/login?port=${port}&state=${encodeURIComponent(state)}`;
|
|
1041
|
+
console.log(`Opening browser to authenticate...`);
|
|
1042
|
+
console.log(`If the browser doesn't open, visit: ${loginUrl}`);
|
|
1043
|
+
try {
|
|
1044
|
+
await open(loginUrl);
|
|
1045
|
+
} catch {
|
|
1046
|
+
console.log("Could not open browser automatically.");
|
|
1047
|
+
console.log(`Please visit: ${loginUrl}`);
|
|
1048
|
+
}
|
|
1049
|
+
console.log("Waiting for authentication...");
|
|
1050
|
+
const token = await tokenPromise;
|
|
1051
|
+
cleanup();
|
|
1052
|
+
console.log("Received token, exchanging for API key...");
|
|
1053
|
+
const { apiKey, username } = await exchangeCliToken(registryUrl, token);
|
|
1054
|
+
await setProfileCredentials(
|
|
1055
|
+
resolved.profileName,
|
|
1056
|
+
apiKey,
|
|
1057
|
+
username,
|
|
1058
|
+
registryUrl
|
|
1059
|
+
);
|
|
1060
|
+
console.log(`Logged in as ${username}`);
|
|
1061
|
+
console.log(`Profile: ${resolved.profileName}`);
|
|
1062
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1063
|
+
}
|
|
1064
|
+
async function directLogin(apiKey, globalOptions) {
|
|
1065
|
+
console.log("Verifying API key...");
|
|
1066
|
+
const resolved = await resolveConfig(globalOptions);
|
|
1067
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1068
|
+
const user = await whoamiRequest(registryUrl, apiKey);
|
|
1069
|
+
if (!user) {
|
|
1070
|
+
console.error("Error: Invalid API key or not authenticated");
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
1073
|
+
await setProfileCredentials(
|
|
1074
|
+
resolved.profileName,
|
|
1075
|
+
apiKey,
|
|
1076
|
+
user.username,
|
|
1077
|
+
registryUrl
|
|
1078
|
+
);
|
|
1079
|
+
console.log(`Logged in as ${user.username}`);
|
|
1080
|
+
console.log(`Profile: ${resolved.profileName}`);
|
|
1081
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1082
|
+
}
|
|
1083
|
+
async function login(options, globalOptions) {
|
|
1084
|
+
try {
|
|
1085
|
+
if (options.apiKey) {
|
|
1086
|
+
await directLogin(options.apiKey, globalOptions);
|
|
1087
|
+
} else {
|
|
1088
|
+
await browserLogin(globalOptions);
|
|
1089
|
+
}
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1092
|
+
console.error(`Error: ${message}`);
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/commands/logout.ts
|
|
1098
|
+
async function logout(globalOptions) {
|
|
1099
|
+
try {
|
|
1100
|
+
const resolved = await resolveConfig(globalOptions);
|
|
1101
|
+
const loggedIn = await isLoggedIn(globalOptions);
|
|
1102
|
+
if (!loggedIn) {
|
|
1103
|
+
console.log(`Not logged in (profile: ${resolved.profileName}).`);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
await clearProfileCredentials(resolved.profileName);
|
|
1107
|
+
console.log(`Logged out from profile "${resolved.profileName}".`);
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1110
|
+
console.error(`Error: ${message}`);
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
var exec = promisify(exec$1);
|
|
1115
|
+
async function publishCommand(options, globalOptions) {
|
|
1116
|
+
try {
|
|
1117
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
1118
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1119
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
1120
|
+
let packageJson2;
|
|
1121
|
+
try {
|
|
1122
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
1123
|
+
packageJson2 = JSON.parse(content);
|
|
1124
|
+
} catch {
|
|
1125
|
+
console.error("Error: No package.json found in current directory");
|
|
1126
|
+
process.exit(1);
|
|
1127
|
+
}
|
|
1128
|
+
if (!packageJson2.name) {
|
|
1129
|
+
console.error("Error: package.json must have a 'name' field");
|
|
1130
|
+
process.exit(1);
|
|
1131
|
+
}
|
|
1132
|
+
if (!packageJson2.version) {
|
|
1133
|
+
console.error("Error: package.json must have a 'version' field");
|
|
1134
|
+
process.exit(1);
|
|
1135
|
+
}
|
|
1136
|
+
if (options.bump) {
|
|
1137
|
+
const semver2 = await import('semver');
|
|
1138
|
+
const newVersion = semver2.default.inc(packageJson2.version, options.bump);
|
|
1139
|
+
if (!newVersion) {
|
|
1140
|
+
console.error(
|
|
1141
|
+
`Error: Failed to bump version from ${packageJson2.version}`
|
|
1142
|
+
);
|
|
1143
|
+
process.exit(1);
|
|
1144
|
+
}
|
|
1145
|
+
packageJson2.version = newVersion;
|
|
1146
|
+
console.log(`Bumped version to ${newVersion}`);
|
|
1147
|
+
}
|
|
1148
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1149
|
+
console.log(`Publishing ${packageJson2.name}@${packageJson2.version}...`);
|
|
1150
|
+
const safeName = packageJson2.name.replace(/[@/]/g, "-").replace(/^-+/, "");
|
|
1151
|
+
const tarballName = `${safeName}-${packageJson2.version}.tgz`;
|
|
1152
|
+
const tempDir = join(process.cwd(), ".pspm-publish");
|
|
1153
|
+
try {
|
|
1154
|
+
await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
|
|
1155
|
+
const files = packageJson2.files || [
|
|
1156
|
+
"package.json",
|
|
1157
|
+
"SKILL.md",
|
|
1158
|
+
"runtime",
|
|
1159
|
+
"scripts",
|
|
1160
|
+
"data"
|
|
1161
|
+
];
|
|
1162
|
+
await exec(`mkdir -p "${tempDir}/package"`);
|
|
1163
|
+
for (const file of files) {
|
|
1164
|
+
try {
|
|
1165
|
+
await exec(
|
|
1166
|
+
`rsync -a --exclude='node_modules' --exclude='.git' "${file}" "${tempDir}/package/" 2>/dev/null || true`
|
|
1167
|
+
);
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
await exec(`cp package.json "${tempDir}/package/"`);
|
|
1172
|
+
const tarballPath = join(tempDir, tarballName);
|
|
1173
|
+
await exec(
|
|
1174
|
+
`tar -czf "${tarballPath}" -C "${tempDir}" --exclude='node_modules' --exclude='.git' package`
|
|
1175
|
+
);
|
|
1176
|
+
const tarballBuffer = await readFile(tarballPath);
|
|
1177
|
+
const tarballBase64 = tarballBuffer.toString("base64");
|
|
1178
|
+
configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
|
|
1179
|
+
const result = await publish({
|
|
1180
|
+
manifest: packageJson2,
|
|
1181
|
+
tarballBase64
|
|
1182
|
+
});
|
|
1183
|
+
console.log(
|
|
1184
|
+
`
|
|
1185
|
+
Published @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
|
|
1186
|
+
);
|
|
1187
|
+
console.log(`Checksum: ${result.version.checksum}`);
|
|
1188
|
+
} finally {
|
|
1189
|
+
await exec(`rm -rf "${tempDir}"`).catch(() => {
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
if (error instanceof Error) {
|
|
1194
|
+
console.error(`Error: ${error.message}`);
|
|
1195
|
+
const anyError = error;
|
|
1196
|
+
if (anyError.cause?.response) {
|
|
1197
|
+
try {
|
|
1198
|
+
const text = await anyError.cause.response.text();
|
|
1199
|
+
console.error(`Response body: ${text}`);
|
|
1200
|
+
} catch {
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (anyError.cause?.body) {
|
|
1204
|
+
console.error(
|
|
1205
|
+
`Cause body: ${JSON.stringify(anyError.cause.body, null, 2)}`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
if (anyError.body) {
|
|
1209
|
+
console.error(`Error body: ${JSON.stringify(anyError.body, null, 2)}`);
|
|
1210
|
+
}
|
|
1211
|
+
if (anyError.data) {
|
|
1212
|
+
console.error(`Error data: ${JSON.stringify(anyError.data, null, 2)}`);
|
|
1213
|
+
}
|
|
1214
|
+
if (process.env.PSPM_DEBUG) {
|
|
1215
|
+
console.error("\nFull error details:");
|
|
1216
|
+
console.error(error);
|
|
1217
|
+
if (error.stack) {
|
|
1218
|
+
console.error("\nStack trace:");
|
|
1219
|
+
console.error(error.stack);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} else {
|
|
1223
|
+
console.error(`Error: ${String(error)}`);
|
|
1224
|
+
}
|
|
1225
|
+
console.error("\nTip: Set PSPM_DEBUG=1 for more detailed error output");
|
|
1226
|
+
process.exit(1);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async function remove(nameOrSpecifier) {
|
|
1230
|
+
try {
|
|
1231
|
+
await requireApiKey();
|
|
1232
|
+
let fullName;
|
|
1233
|
+
let username;
|
|
1234
|
+
let name;
|
|
1235
|
+
if (nameOrSpecifier.startsWith("@user/")) {
|
|
1236
|
+
const match = nameOrSpecifier.match(/^@user\/([^/]+)\/([^@/]+)/);
|
|
1237
|
+
if (!match) {
|
|
1238
|
+
console.error(`Error: Invalid skill specifier: ${nameOrSpecifier}`);
|
|
1239
|
+
process.exit(1);
|
|
1240
|
+
}
|
|
1241
|
+
fullName = `@user/${match[1]}/${match[2]}`;
|
|
1242
|
+
username = match[1];
|
|
1243
|
+
name = match[2];
|
|
1244
|
+
} else {
|
|
1245
|
+
const skills = await listLockfileSkills();
|
|
1246
|
+
const found = skills.find((s) => {
|
|
1247
|
+
const match2 = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1248
|
+
return match2 && match2[2] === nameOrSpecifier;
|
|
1249
|
+
});
|
|
1250
|
+
if (!found) {
|
|
1251
|
+
console.error(
|
|
1252
|
+
`Error: Skill "${nameOrSpecifier}" not found in lockfile`
|
|
1253
|
+
);
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
fullName = found.name;
|
|
1257
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1258
|
+
if (!match) {
|
|
1259
|
+
console.error(`Error: Invalid skill name in lockfile: ${fullName}`);
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
username = match[1];
|
|
1263
|
+
name = match[2];
|
|
1264
|
+
}
|
|
1265
|
+
console.log(`Removing ${fullName}...`);
|
|
1266
|
+
const removed = await removeFromLockfile(fullName);
|
|
1267
|
+
if (!removed) {
|
|
1268
|
+
console.error(`Error: ${fullName} not found in lockfile`);
|
|
1269
|
+
process.exit(1);
|
|
1270
|
+
}
|
|
1271
|
+
const skillsDir = getSkillsDir();
|
|
1272
|
+
const destDir = join(skillsDir, username, name);
|
|
1273
|
+
try {
|
|
1274
|
+
await rm(destDir, { recursive: true, force: true });
|
|
1275
|
+
} catch {
|
|
1276
|
+
}
|
|
1277
|
+
console.log(`Removed ${fullName}`);
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1280
|
+
console.error(`Error: ${message}`);
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/commands/unpublish.ts
|
|
1286
|
+
async function unpublish(specifier, options, globalOptions) {
|
|
1287
|
+
try {
|
|
1288
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
1289
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1290
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
1291
|
+
if (!parsed) {
|
|
1292
|
+
console.error(
|
|
1293
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}]`
|
|
1294
|
+
);
|
|
1295
|
+
process.exit(1);
|
|
1296
|
+
}
|
|
1297
|
+
const { username, name, versionRange } = parsed;
|
|
1298
|
+
configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
|
|
1299
|
+
if (versionRange) {
|
|
1300
|
+
console.log(`Unpublishing ${specifier}...`);
|
|
1301
|
+
if (!options.force) {
|
|
1302
|
+
console.error(
|
|
1303
|
+
"Warning: This action is irreversible. Use --force to confirm."
|
|
1304
|
+
);
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
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.`);
|
|
1315
|
+
process.exit(1);
|
|
1316
|
+
}
|
|
1317
|
+
} else {
|
|
1318
|
+
console.log(`Unpublishing all versions of @user/${username}/${name}...`);
|
|
1319
|
+
if (!options.force) {
|
|
1320
|
+
console.error(
|
|
1321
|
+
"Warning: This will delete ALL versions. Use --force to confirm."
|
|
1322
|
+
);
|
|
1323
|
+
process.exit(1);
|
|
1324
|
+
}
|
|
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.`);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1335
|
+
console.error(`Error: ${message}`);
|
|
1336
|
+
process.exit(1);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// src/commands/update.ts
|
|
1341
|
+
async function update(options, globalOptions) {
|
|
1342
|
+
try {
|
|
1343
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
1344
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1345
|
+
const skills = await listLockfileSkills();
|
|
1346
|
+
if (skills.length === 0) {
|
|
1347
|
+
console.log("No skills installed.");
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
configure({ baseUrl: `${registryUrl}/rpc`, apiKey });
|
|
1351
|
+
const updates = [];
|
|
1352
|
+
console.log("Checking for updates...\n");
|
|
1353
|
+
for (const { name, entry } of skills) {
|
|
1354
|
+
const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1355
|
+
if (!match) continue;
|
|
1356
|
+
const [, username, skillName] = match;
|
|
1357
|
+
try {
|
|
1358
|
+
const versions = await listVersions({
|
|
1359
|
+
username,
|
|
1360
|
+
name: skillName
|
|
1361
|
+
});
|
|
1362
|
+
if (versions.length === 0) continue;
|
|
1363
|
+
const versionStrings = versions.map(
|
|
1364
|
+
(v) => v.version
|
|
1365
|
+
);
|
|
1366
|
+
const latest = resolveVersion("*", versionStrings);
|
|
1367
|
+
if (latest && latest !== entry.version) {
|
|
1368
|
+
updates.push({
|
|
1369
|
+
name,
|
|
1370
|
+
current: entry.version,
|
|
1371
|
+
latest
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
} catch {
|
|
1375
|
+
console.warn(` Warning: Could not check updates for ${name}`);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (updates.length === 0) {
|
|
1379
|
+
console.log("All skills are up to date.");
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
console.log("Updates available:\n");
|
|
1383
|
+
for (const { name, current, latest } of updates) {
|
|
1384
|
+
console.log(` ${name}: ${current} -> ${latest}`);
|
|
1385
|
+
}
|
|
1386
|
+
if (options.dryRun) {
|
|
1387
|
+
console.log("\nDry run - no changes made.");
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
console.log("\nUpdating...\n");
|
|
1391
|
+
for (const { name, latest } of updates) {
|
|
1392
|
+
const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1393
|
+
if (!match) continue;
|
|
1394
|
+
const [, username, skillName] = match;
|
|
1395
|
+
const specifier = `@user/${username}/${skillName}@${latest}`;
|
|
1396
|
+
await add(specifier, {}, globalOptions);
|
|
1397
|
+
}
|
|
1398
|
+
console.log("\nAll skills updated.");
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1401
|
+
console.error(`Error: ${message}`);
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/commands/whoami.ts
|
|
1407
|
+
async function whoami(globalOptions) {
|
|
1408
|
+
try {
|
|
1409
|
+
const resolved = await resolveConfig(globalOptions);
|
|
1410
|
+
const apiKey = await requireApiKey(globalOptions);
|
|
1411
|
+
const registryUrl = await getRegistryUrl(globalOptions);
|
|
1412
|
+
const user = await whoamiRequest(registryUrl, apiKey);
|
|
1413
|
+
if (user) {
|
|
1414
|
+
console.log(`Username: ${user.username}`);
|
|
1415
|
+
console.log(`User ID: ${user.userId}`);
|
|
1416
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1417
|
+
console.log(`Profile: ${resolved.profileName}`);
|
|
1418
|
+
} else if (resolved.username) {
|
|
1419
|
+
console.log(`Username: ${resolved.username} (cached)`);
|
|
1420
|
+
console.log(`Registry: ${registryUrl}`);
|
|
1421
|
+
console.log(`Profile: ${resolved.profileName}`);
|
|
1422
|
+
} else {
|
|
1423
|
+
console.error("Could not determine current user.");
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1428
|
+
console.error(`Error: ${message}`);
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/index.ts
|
|
1434
|
+
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
1435
|
+
var packageJson = JSON.parse(
|
|
1436
|
+
readFileSync(join(__dirname$1, "..", "package.json"), "utf-8")
|
|
1437
|
+
);
|
|
1438
|
+
var version = packageJson.version;
|
|
1439
|
+
var program = new Command();
|
|
1440
|
+
program.name("pspm").description("Prompt Skill Package Manager for AnyT").version(version).option("-p, --profile <name>", "Use a specific profile");
|
|
1441
|
+
function getGlobalOptions(cmd) {
|
|
1442
|
+
let current = cmd;
|
|
1443
|
+
while (current.parent) {
|
|
1444
|
+
current = current.parent;
|
|
1445
|
+
}
|
|
1446
|
+
const opts = current.opts();
|
|
1447
|
+
return {
|
|
1448
|
+
profile: opts.profile
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
var configCmd = program.command("config").description("Manage PSPM configuration and profiles");
|
|
1452
|
+
configCmd.command("list").description("List all profiles").action(async () => {
|
|
1453
|
+
await configList();
|
|
1454
|
+
});
|
|
1455
|
+
configCmd.command("show [name]").description("Show resolved config or a specific profile").action(async (name, _options, cmd) => {
|
|
1456
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1457
|
+
await configShow({ name }, globalOptions);
|
|
1458
|
+
});
|
|
1459
|
+
configCmd.command("add <name>").description("Create a new profile").option("--registry-url <url>", "Registry URL for the profile").action(async (name, options) => {
|
|
1460
|
+
await configAdd(name, { registryUrl: options.registryUrl });
|
|
1461
|
+
});
|
|
1462
|
+
configCmd.command("use <name>").description("Set the default profile").action(async (name) => {
|
|
1463
|
+
await configUse(name);
|
|
1464
|
+
});
|
|
1465
|
+
configCmd.command("set <profile> <key> <value>").description("Set a profile field (registryUrl, apiKey, username)").action(async (profile, key, value) => {
|
|
1466
|
+
await configSet(profile, key, value);
|
|
1467
|
+
});
|
|
1468
|
+
configCmd.command("delete <name>").description("Delete a profile").action(async (name) => {
|
|
1469
|
+
await configDelete(name);
|
|
1470
|
+
});
|
|
1471
|
+
configCmd.command("init").description("Create a .pspmrc file in the current directory").option("--profile <name>", "Profile to use for this project").option("--registry-url <url>", "Registry URL override").action(async (options) => {
|
|
1472
|
+
await configInit({
|
|
1473
|
+
profile: options.profile,
|
|
1474
|
+
registryUrl: options.registryUrl
|
|
1475
|
+
});
|
|
1476
|
+
});
|
|
1477
|
+
program.command("login").description("Log in via browser or with an API key").option(
|
|
1478
|
+
"--api-key <key>",
|
|
1479
|
+
"API key for direct authentication (skips browser)"
|
|
1480
|
+
).action(async (options, cmd) => {
|
|
1481
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1482
|
+
await login({ apiKey: options.apiKey }, globalOptions);
|
|
1483
|
+
});
|
|
1484
|
+
program.command("logout").description("Log out and clear stored credentials").action(async (_options, cmd) => {
|
|
1485
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1486
|
+
await logout(globalOptions);
|
|
1487
|
+
});
|
|
1488
|
+
program.command("whoami").description("Show current user information").action(async (_options, cmd) => {
|
|
1489
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1490
|
+
await whoami(globalOptions);
|
|
1491
|
+
});
|
|
1492
|
+
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, cmd) => {
|
|
1493
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1494
|
+
await add(specifier, { save: options.save ?? true }, globalOptions);
|
|
1495
|
+
});
|
|
1496
|
+
program.command("remove <name>").alias("rm").description("Remove an installed skill").action(async (name) => {
|
|
1497
|
+
await remove(name);
|
|
1498
|
+
});
|
|
1499
|
+
program.command("list").alias("ls").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
|
|
1500
|
+
await list({ json: options.json });
|
|
1501
|
+
});
|
|
1502
|
+
program.command("install").alias("i").description("Install all skills from lockfile").option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").action(async (options, cmd) => {
|
|
1503
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1504
|
+
await install(
|
|
1505
|
+
{
|
|
1506
|
+
frozenLockfile: options.frozenLockfile,
|
|
1507
|
+
dir: options.dir
|
|
1508
|
+
},
|
|
1509
|
+
globalOptions
|
|
1510
|
+
);
|
|
1511
|
+
});
|
|
1512
|
+
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, cmd) => {
|
|
1513
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1514
|
+
await update({ dryRun: options.dryRun }, globalOptions);
|
|
1515
|
+
});
|
|
1516
|
+
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, cmd) => {
|
|
1517
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1518
|
+
await publishCommand(
|
|
1519
|
+
{
|
|
1520
|
+
bump: options.bump,
|
|
1521
|
+
tag: options.tag
|
|
1522
|
+
},
|
|
1523
|
+
globalOptions
|
|
1524
|
+
);
|
|
1525
|
+
});
|
|
1526
|
+
program.command("unpublish <specifier>").description("Remove a published skill version").option("--force", "Confirm destructive action").action(async (specifier, options, cmd) => {
|
|
1527
|
+
const globalOptions = getGlobalOptions(cmd);
|
|
1528
|
+
await unpublish(specifier, { force: options.force }, globalOptions);
|
|
1529
|
+
});
|
|
1530
|
+
program.parse();
|
|
1531
|
+
//# sourceMappingURL=index.js.map
|
|
1532
|
+
//# sourceMappingURL=index.js.map
|