@agentscope-ai/platform-cli 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,2380 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CLIENT_ID,
4
+ CLI_API_PREFIX,
5
+ CLI_VERSION,
6
+ DEFAULT_SCOPE,
7
+ PLATFORM_URL,
8
+ clearCredentials,
9
+ credentialsFromTokenResponse,
10
+ getAspConfigDir,
11
+ isTokenExpired,
12
+ loadCredentials,
13
+ resolveConfig,
14
+ saveCredentials
15
+ } from "./chunk-YKR5C7LX.js";
16
+
17
+ // src/cli.ts
18
+ import { Command } from "commander";
19
+
20
+ // src/utils/errors.ts
21
+ var AspError = class _AspError extends Error {
22
+ code;
23
+ hint;
24
+ requestId;
25
+ statusCode;
26
+ exitCode;
27
+ constructor(message, options = {}) {
28
+ super(message, { cause: options.cause });
29
+ this.name = "AspError";
30
+ this.code = options.code ?? "UNKNOWN";
31
+ this.hint = options.hint;
32
+ this.requestId = options.requestId;
33
+ this.statusCode = options.statusCode;
34
+ this.exitCode = options.exitCode ?? 1;
35
+ }
36
+ static fromResponse(status, body) {
37
+ const parsed = body;
38
+ if (parsed?.error) {
39
+ const exitCode = mapErrorCodeToExit(parsed.error.code, status);
40
+ let hint = parsed.error.hint ?? parsed.error.detail;
41
+ if (parsed.error.code === "ASP.COMM.NOT_FOUND") {
42
+ hint = hint ?? "The API endpoint is not available on this Platform environment.";
43
+ }
44
+ return new _AspError(parsed.error.message, {
45
+ code: parsed.error.code,
46
+ hint,
47
+ requestId: parsed.request_id,
48
+ statusCode: status,
49
+ exitCode
50
+ });
51
+ }
52
+ return new _AspError(`HTTP ${status}`, {
53
+ code: "HTTP_ERROR",
54
+ statusCode: status,
55
+ exitCode: status >= 500 ? 2 : 1
56
+ });
57
+ }
58
+ };
59
+ function mapErrorCodeToExit(code, status) {
60
+ if (code === "UNAUTHORIZED" || code === "UNAUTHENTICATED" || code === "ASP.AUTH.UNAUTHORIZED" || code === "ASP.AUTH.SESSION_INVALID" || code === "SESSION_INVALID") {
61
+ return 3;
62
+ }
63
+ if (code === "FORBIDDEN") return 4;
64
+ if (status >= 500) return 2;
65
+ return 1;
66
+ }
67
+ function maskSecrets(text) {
68
+ return text.replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [REDACTED]").replace(/"access_token"\s*:\s*"[^"]+"/gi, '"access_token":"[REDACTED]"').replace(/"refresh_token"\s*:\s*"[^"]+"/gi, '"refresh_token":"[REDACTED]"').replace(/"token"\s*:\s*"[^"]+"/gi, '"token":"[REDACTED]"');
69
+ }
70
+
71
+ // src/auth/session.ts
72
+ function isSessionInvalidError(err) {
73
+ if (!(err instanceof AspError)) return false;
74
+ return err.code === "ASP.AUTH.SESSION_INVALID" || err.code === "SESSION_INVALID";
75
+ }
76
+ async function refreshStoredSession(config, refreshToken, platformUrl) {
77
+ const client = new CliApiClient({
78
+ ...config,
79
+ token: void 0,
80
+ platformUrl: platformUrl.replace(/\/$/, "")
81
+ });
82
+ const refreshed = await client.refresh(refreshToken);
83
+ const creds = credentialsFromTokenResponse(platformUrl, refreshed);
84
+ await saveCredentials(creds);
85
+ return creds;
86
+ }
87
+ async function finalizeLogin(config, loginResponse) {
88
+ if (!loginResponse.refresh_token) {
89
+ const creds = credentialsFromTokenResponse(config.platformUrl, loginResponse);
90
+ await saveCredentials(creds);
91
+ return creds;
92
+ }
93
+ try {
94
+ return await refreshStoredSession(
95
+ config,
96
+ loginResponse.refresh_token,
97
+ config.platformUrl
98
+ );
99
+ } catch {
100
+ const creds = credentialsFromTokenResponse(config.platformUrl, loginResponse);
101
+ await saveCredentials(creds);
102
+ return creds;
103
+ }
104
+ }
105
+
106
+ // src/api/client.ts
107
+ var HttpClient = class {
108
+ constructor(config) {
109
+ this.config = config;
110
+ }
111
+ config;
112
+ async request(opts) {
113
+ try {
114
+ return await this.doRequest(opts);
115
+ } catch (err) {
116
+ if (opts.auth !== false && !opts._retried && isSessionInvalidError(err) && !this.config.token) {
117
+ const creds = await loadCredentials();
118
+ if (creds?.refresh_token) {
119
+ await refreshStoredSession(configWithPlatform(this.config, creds), creds.refresh_token, creds.platform_url);
120
+ return this.doRequest({ ...opts, _retried: true });
121
+ }
122
+ }
123
+ throw err;
124
+ }
125
+ }
126
+ async doRequest(opts) {
127
+ const creds = opts.auth !== false && !this.config.token ? await loadCredentials() : null;
128
+ const platformUrl = creds?.platform_url ?? this.config.platformUrl;
129
+ const prefix = opts.prefix ?? "";
130
+ const url = `${platformUrl}${prefix}${opts.path}`;
131
+ const headers = {
132
+ Accept: "application/json",
133
+ ...opts.headers
134
+ };
135
+ if (opts.body !== void 0) {
136
+ headers["Content-Type"] = "application/json";
137
+ }
138
+ if (opts.auth !== false) {
139
+ const token = await this.resolveAccessToken(creds);
140
+ if (token) {
141
+ headers.Authorization = `Bearer ${token}`;
142
+ }
143
+ }
144
+ if (this.config.verbose) {
145
+ console.error(`[verbose] ${opts.method ?? "GET"} ${url}`);
146
+ if (opts.body) console.error(maskSecrets(JSON.stringify(opts.body)));
147
+ }
148
+ const res = await fetch(url, {
149
+ method: opts.method ?? "GET",
150
+ headers,
151
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
152
+ });
153
+ const text = await res.text();
154
+ let data = {};
155
+ if (text) {
156
+ try {
157
+ data = JSON.parse(text);
158
+ } catch {
159
+ data = { raw: text };
160
+ }
161
+ }
162
+ if (this.config.verbose) {
163
+ console.error(`[verbose] ${res.status}`, maskSecrets(text.slice(0, 2e3)));
164
+ }
165
+ if (!res.ok) {
166
+ throw AspError.fromResponse(res.status, data);
167
+ }
168
+ return data;
169
+ }
170
+ async resolveAccessToken(existing) {
171
+ if (this.config.token) return this.config.token;
172
+ const creds = existing ?? await loadCredentials();
173
+ if (!creds) return void 0;
174
+ if (isTokenExpired(creds) && creds.refresh_token) {
175
+ const refreshed = await refreshStoredSession(
176
+ configWithPlatform(this.config, creds),
177
+ creds.refresh_token,
178
+ creds.platform_url
179
+ );
180
+ return refreshed.access_token;
181
+ }
182
+ return creds.access_token;
183
+ }
184
+ };
185
+ function configWithPlatform(config, creds) {
186
+ return { ...config, platformUrl: creds.platform_url || config.platformUrl };
187
+ }
188
+
189
+ // src/api/cli-api.ts
190
+ var CliApiClient = class {
191
+ http;
192
+ constructor(config) {
193
+ this.http = new HttpClient(config);
194
+ }
195
+ req(opts) {
196
+ return this.http.request({ ...opts, prefix: CLI_API_PREFIX });
197
+ }
198
+ getMeta() {
199
+ return this.req({ path: "/meta", auth: false });
200
+ }
201
+ getMe() {
202
+ return this.req({ path: "/me" });
203
+ }
204
+ login(account, password) {
205
+ return this.req({
206
+ method: "POST",
207
+ path: "/auth/login",
208
+ auth: false,
209
+ body: { account, password }
210
+ });
211
+ }
212
+ refresh(refreshToken) {
213
+ return this.req({
214
+ method: "POST",
215
+ path: "/auth/refresh",
216
+ auth: false,
217
+ body: { refresh_token: refreshToken }
218
+ });
219
+ }
220
+ oauthToken(body) {
221
+ return this.req({ method: "POST", path: "/oauth/token", auth: false, body });
222
+ }
223
+ oauthRevoke(token, tokenTypeHint = "refresh_token") {
224
+ return this.req({
225
+ method: "POST",
226
+ path: "/oauth/revoke",
227
+ body: { token, token_type_hint: tokenTypeHint }
228
+ });
229
+ }
230
+ async logout() {
231
+ try {
232
+ return await this.req({ method: "POST", path: "/auth/logout" });
233
+ } catch {
234
+ return this.oauthRevoke("", "access_token");
235
+ }
236
+ }
237
+ getRequestLogs(params) {
238
+ const query = new URLSearchParams();
239
+ if (params?.page_size !== void 0) query.set("page_size", String(params.page_size));
240
+ if (params?.cursor) query.set("cursor", params.cursor);
241
+ const suffix = query.toString();
242
+ return this.req({ path: `/request-logs${suffix ? `?${suffix}` : ""}` });
243
+ }
244
+ searchPlugins(params) {
245
+ const query = new URLSearchParams();
246
+ if (params?.q) query.set("q", params.q);
247
+ if (params?.category) query.set("category", params.category);
248
+ if (params?.source) query.set("source", params.source);
249
+ if (params?.sort) query.set("sort", params.sort);
250
+ if (params?.page_size !== void 0) query.set("page_size", String(params.page_size));
251
+ if (params?.cursor) query.set("cursor", params.cursor);
252
+ const suffix = query.toString();
253
+ return this.req({ path: `/plugins${suffix ? `?${suffix}` : ""}`, auth: false });
254
+ }
255
+ getPluginInfo(pluginId, version) {
256
+ const query = new URLSearchParams();
257
+ if (version) query.set("version", version);
258
+ const suffix = query.toString();
259
+ return this.req({
260
+ path: `/plugins/${encodeURIComponent(pluginId)}${suffix ? `?${suffix}` : ""}`,
261
+ auth: false
262
+ });
263
+ }
264
+ getPluginVersions(pluginId) {
265
+ return this.req({
266
+ path: `/plugins/${encodeURIComponent(pluginId)}/versions`,
267
+ auth: false
268
+ });
269
+ }
270
+ createInstallIntent(pluginId, version) {
271
+ return this.req({
272
+ method: "POST",
273
+ path: `/plugins/${encodeURIComponent(pluginId)}/install-intent`,
274
+ body: version ? { version } : {}
275
+ });
276
+ }
277
+ searchSkills(params) {
278
+ const query = new URLSearchParams();
279
+ if (params?.q) query.set("q", params.q);
280
+ if (params?.category) query.set("category", params.category);
281
+ if (params?.source) query.set("source", params.source);
282
+ if (params?.tag) query.set("tag", params.tag);
283
+ if (params?.sort) query.set("sort", params.sort);
284
+ if (params?.page_size !== void 0) query.set("page_size", String(params.page_size));
285
+ if (params?.cursor) query.set("cursor", params.cursor);
286
+ const suffix = query.toString();
287
+ return this.req({ path: `/skills${suffix ? `?${suffix}` : ""}`, auth: false });
288
+ }
289
+ getSkillInfo(skillId, version) {
290
+ const query = new URLSearchParams();
291
+ if (version) query.set("version", version);
292
+ const suffix = query.toString();
293
+ return this.req({
294
+ path: `/skills/${encodeURIComponent(skillId)}${suffix ? `?${suffix}` : ""}`,
295
+ auth: false
296
+ });
297
+ }
298
+ getSkillVersions(skillId) {
299
+ return this.req({
300
+ path: `/skills/${encodeURIComponent(skillId)}/versions`,
301
+ auth: false
302
+ });
303
+ }
304
+ createSkillInstallIntent(skillId, version) {
305
+ return this.req({
306
+ method: "POST",
307
+ path: `/skills/${encodeURIComponent(skillId)}/install-intent`,
308
+ body: version ? { version } : {}
309
+ });
310
+ }
311
+ createArtifactSubmission(body) {
312
+ return this.req({
313
+ method: "POST",
314
+ path: "/artifact-submissions",
315
+ body
316
+ });
317
+ }
318
+ completeArtifactSubmission(submissionId, body) {
319
+ return this.req({
320
+ method: "POST",
321
+ path: `/artifact-submissions/${encodeURIComponent(submissionId)}/complete`,
322
+ body
323
+ });
324
+ }
325
+ getArtifactSubmission(submissionId) {
326
+ return this.req({
327
+ path: `/artifact-submissions/${encodeURIComponent(submissionId)}`
328
+ });
329
+ }
330
+ getArtifactSubmissionLogs(submissionId) {
331
+ return this.req({
332
+ path: `/artifact-submissions/${encodeURIComponent(submissionId)}/logs`
333
+ });
334
+ }
335
+ };
336
+
337
+ // src/auth/browser-login.ts
338
+ import { createServer } from "http";
339
+ import open from "open";
340
+
341
+ // src/auth/pkce.ts
342
+ import { randomBytes, createHash } from "crypto";
343
+ function base64Url(buf) {
344
+ return buf.toString("base64url");
345
+ }
346
+ function generatePkce() {
347
+ const codeVerifier = base64Url(randomBytes(32));
348
+ const codeChallenge = base64Url(createHash("sha256").update(codeVerifier).digest());
349
+ const state = `state_${base64Url(randomBytes(16))}`;
350
+ const nonce = `nonce_${base64Url(randomBytes(16))}`;
351
+ return { codeVerifier, codeChallenge, state, nonce };
352
+ }
353
+
354
+ // src/utils/platform.ts
355
+ function detectPlatformEnv(url) {
356
+ const normalized = url.replace(/\/$/, "");
357
+ if (normalized === PLATFORM_URL) return "default";
358
+ return "custom";
359
+ }
360
+ function formatPlatformLabel(url) {
361
+ const env = detectPlatformEnv(url);
362
+ if (env === "default") return `platform (${PLATFORM_URL})`;
363
+ return url;
364
+ }
365
+ function withPlatformUrl(config, platformUrl) {
366
+ return { ...config, platformUrl: platformUrl.replace(/\/$/, "") };
367
+ }
368
+ async function requireCredentials(config, targetPlatformUrl) {
369
+ if (config.token) {
370
+ return {
371
+ config: withPlatformUrl(config, targetPlatformUrl),
372
+ creds: {
373
+ access_token: config.token,
374
+ token_type: "Bearer",
375
+ platform_url: targetPlatformUrl
376
+ }
377
+ };
378
+ }
379
+ const creds = await loadCredentials();
380
+ if (!creds?.access_token) {
381
+ throw new AspError("Not logged in.", {
382
+ code: "UNAUTHORIZED",
383
+ hint: "Run `asp auth login`.",
384
+ exitCode: 3
385
+ });
386
+ }
387
+ const credsUrl = creds.platform_url.replace(/\/$/, "");
388
+ const targetUrl = targetPlatformUrl.replace(/\/$/, "");
389
+ if (credsUrl !== targetUrl) {
390
+ const loggedIn = formatPlatformLabel(credsUrl);
391
+ const target = formatPlatformLabel(targetUrl);
392
+ throw new AspError(`Credential environment mismatch: logged in to ${loggedIn}, command targets ${target}.`, {
393
+ code: "ENV_MISMATCH",
394
+ hint: "Run `asp auth login` for the current target platform.",
395
+ exitCode: 1
396
+ });
397
+ }
398
+ return {
399
+ config: withPlatformUrl(config, targetUrl),
400
+ creds
401
+ };
402
+ }
403
+
404
+ // src/utils/output.ts
405
+ import chalk from "chalk";
406
+ function printJson(data) {
407
+ console.log(JSON.stringify(data, null, 2));
408
+ }
409
+ function printSuccess(message) {
410
+ console.log(chalk.green("\u2713"), message);
411
+ }
412
+ function printInfo(message) {
413
+ console.log(chalk.blue("\u2139"), message);
414
+ }
415
+ function printWarn(message) {
416
+ console.log(chalk.yellow("\u26A0"), message);
417
+ }
418
+ function printError(message) {
419
+ console.error(chalk.red("\u2717"), message);
420
+ }
421
+ function printTable(headers, rows) {
422
+ const widths = headers.map(
423
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
424
+ );
425
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
426
+ console.log(chalk.bold(headerLine));
427
+ for (const row of rows) {
428
+ console.log(row.map((c, i) => (c ?? "").padEnd(widths[i])).join(" "));
429
+ }
430
+ }
431
+ function formatDateTime(iso) {
432
+ const date = new Date(iso);
433
+ if (Number.isNaN(date.getTime())) return iso;
434
+ const pad = (n) => String(n).padStart(2, "0");
435
+ return [
436
+ date.getFullYear(),
437
+ pad(date.getMonth() + 1),
438
+ pad(date.getDate())
439
+ ].join("-") + " " + [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())].join(":");
440
+ }
441
+
442
+ // src/auth/browser-login.ts
443
+ var LOGIN_TIMEOUT_MS = 3e5;
444
+ async function browserPkceLogin(config) {
445
+ const pkce = generatePkce();
446
+ const port = await pickPort();
447
+ const redirectUri = `http://127.0.0.1:${port}/callback/${pkce.nonce}`;
448
+ const loginUrl = new URL(`${config.platformUrl}/cli/login`);
449
+ loginUrl.searchParams.set("client_id", CLIENT_ID);
450
+ loginUrl.searchParams.set("redirect_uri", redirectUri);
451
+ loginUrl.searchParams.set("response_type", "code");
452
+ loginUrl.searchParams.set("code_challenge", pkce.codeChallenge);
453
+ loginUrl.searchParams.set("code_challenge_method", "S256");
454
+ loginUrl.searchParams.set("state", pkce.state);
455
+ loginUrl.searchParams.set("scope", DEFAULT_SCOPE);
456
+ const code = await waitForCallback(port, pkce.nonce, pkce.state, loginUrl.toString());
457
+ const client = new CliApiClient(config);
458
+ const token = await client.oauthToken({
459
+ grant_type: "authorization_code",
460
+ client_id: CLIENT_ID,
461
+ code,
462
+ code_verifier: pkce.codeVerifier,
463
+ redirect_uri: redirectUri
464
+ });
465
+ await finalizeLogin(config, token);
466
+ const display = token.user?.email ?? token.user?.display_name ?? "user";
467
+ printSuccess(`Logged in as ${display} (${formatPlatformLabel(config.platformUrl)})`);
468
+ }
469
+ async function pickPort() {
470
+ return new Promise((resolve, reject) => {
471
+ const server = createServer();
472
+ server.listen(0, "127.0.0.1", () => {
473
+ const addr = server.address();
474
+ if (!addr || typeof addr === "string") {
475
+ server.close();
476
+ reject(new Error("Failed to bind callback port"));
477
+ return;
478
+ }
479
+ const port = addr.port;
480
+ server.close(() => resolve(port));
481
+ });
482
+ server.on("error", reject);
483
+ });
484
+ }
485
+ function waitForCallback(port, nonce, expectedState, loginUrl) {
486
+ return new Promise((resolve, reject) => {
487
+ let settled = false;
488
+ let timeoutId;
489
+ let server;
490
+ const stopServer = () => {
491
+ if (timeoutId !== void 0) {
492
+ clearTimeout(timeoutId);
493
+ timeoutId = void 0;
494
+ }
495
+ server.closeAllConnections?.();
496
+ server.close();
497
+ };
498
+ const finish = (handler) => {
499
+ if (settled) return;
500
+ settled = true;
501
+ stopServer();
502
+ handler();
503
+ };
504
+ server = createServer(async (req, res) => {
505
+ try {
506
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
507
+ if (url.pathname !== `/callback/${nonce}`) {
508
+ res.writeHead(404);
509
+ res.end("Not found");
510
+ return;
511
+ }
512
+ const code = url.searchParams.get("code");
513
+ const state = url.searchParams.get("state");
514
+ if (!code || state !== expectedState) {
515
+ res.writeHead(400);
516
+ res.end("Invalid callback");
517
+ finish(
518
+ () => reject(new Error("Invalid OAuth callback: state mismatch or missing code"))
519
+ );
520
+ return;
521
+ }
522
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
523
+ res.end("<html><body><h2>Login successful. You can close this window.</h2></body></html>");
524
+ finish(() => resolve(code));
525
+ } catch (err) {
526
+ finish(() => reject(err));
527
+ }
528
+ });
529
+ server.listen(port, "127.0.0.1", () => {
530
+ printInfo("Opening browser to authorize AgentScope Platform CLI...");
531
+ printInfo(`If the browser does not open, visit:
532
+ ${loginUrl}`);
533
+ printInfo("Waiting for authorization...");
534
+ void open(loginUrl).catch(() => {
535
+ });
536
+ });
537
+ timeoutId = setTimeout(() => {
538
+ finish(() => reject(new Error("Login timed out")));
539
+ }, LOGIN_TIMEOUT_MS);
540
+ });
541
+ }
542
+
543
+ // src/utils/prompt.ts
544
+ import { createInterface } from "readline/promises";
545
+ import { stdin as input, stdout as output } from "process";
546
+ async function promptLine(message) {
547
+ const rl = createInterface({ input, output });
548
+ try {
549
+ const value = (await rl.question(message)).trim();
550
+ if (!value) {
551
+ throw new Error("Input is required.");
552
+ }
553
+ return value;
554
+ } finally {
555
+ rl.close();
556
+ }
557
+ }
558
+ async function promptPassword(message = "Password: ") {
559
+ return new Promise((resolve, reject) => {
560
+ const stdin = process.stdin;
561
+ const wasRaw = stdin.isRaw;
562
+ const wasPaused = stdin.isPaused();
563
+ if (!stdin.isTTY) {
564
+ reject(new Error("Password prompt requires an interactive terminal."));
565
+ return;
566
+ }
567
+ output.write(message);
568
+ stdin.setRawMode(true);
569
+ stdin.resume();
570
+ stdin.setEncoding("utf8");
571
+ let password = "";
572
+ const cleanup = () => {
573
+ stdin.removeListener("data", onData);
574
+ stdin.setRawMode(wasRaw ?? false);
575
+ if (wasPaused) stdin.pause();
576
+ else stdin.resume();
577
+ };
578
+ const onData = (char) => {
579
+ switch (char) {
580
+ case "\n":
581
+ case "\r":
582
+ case "":
583
+ cleanup();
584
+ output.write("\n");
585
+ if (!password) {
586
+ reject(new Error("Password is required."));
587
+ return;
588
+ }
589
+ resolve(password);
590
+ break;
591
+ case "":
592
+ cleanup();
593
+ output.write("\n");
594
+ reject(new Error("Login cancelled."));
595
+ break;
596
+ case "\x7F":
597
+ case "\b":
598
+ if (password.length > 0) {
599
+ password = password.slice(0, -1);
600
+ output.write("\b \b");
601
+ }
602
+ break;
603
+ default:
604
+ if (char < " " && char !== " ") return;
605
+ password += char;
606
+ output.write("*");
607
+ break;
608
+ }
609
+ };
610
+ stdin.on("data", onData);
611
+ });
612
+ }
613
+ async function promptCredentials(options) {
614
+ const account = options.account ?? await promptLine("Email: ");
615
+ const password = options.password ?? await promptPassword();
616
+ return { account, password };
617
+ }
618
+ async function promptLoginMethod() {
619
+ printLoginMethodMenu();
620
+ const rl = createInterface({ input, output });
621
+ try {
622
+ const answer = (await rl.question("Enter choice [1]: ")).trim();
623
+ if (!answer || answer === "1") return "browser";
624
+ if (answer === "2") return "password";
625
+ throw new Error("Invalid choice. Enter 1 or 2.");
626
+ } finally {
627
+ rl.close();
628
+ }
629
+ }
630
+ function printLoginMethodMenu() {
631
+ console.log("How would you like to log in?");
632
+ console.log(" 1. Browser authorization (recommended)");
633
+ console.log(" 2. Email and password");
634
+ }
635
+
636
+ // src/commands/auth.ts
637
+ var LOGIN_OPTIONS = [
638
+ ["--browser", "Login via browser authorization (opens /cli/login, PKCE)"],
639
+ ["--password", "Login with email and password"],
640
+ ["--token <token>", "Save and validate an existing access token"],
641
+ ["--account <email>", "Account email (non-interactive, use with --pass)"],
642
+ ["--pass <password>", "Account password (non-interactive, use with --account)"]
643
+ ];
644
+ function wantsPasswordLogin(opts) {
645
+ return !!(opts.password || opts.account || opts.pass);
646
+ }
647
+ async function ensureBrowserLoginEnabled(config) {
648
+ const meta = await new CliApiClient(config).getMeta().catch(() => null);
649
+ if (meta?.features?.browser_pkce_login === false) {
650
+ throw new AspError("Browser authorization login is not enabled on this platform.", {
651
+ code: "FEATURE_DISABLED",
652
+ hint: "Use `asp auth login --password` instead."
653
+ });
654
+ }
655
+ }
656
+ async function runPasswordLogin(config, opts) {
657
+ const { account, password } = await promptCredentials({
658
+ account: opts.account,
659
+ password: opts.pass
660
+ });
661
+ const loginResponse = await new CliApiClient(config).login(account, password);
662
+ await finalizeLogin(config, loginResponse);
663
+ const me = await new CliApiClient(config).getMe();
664
+ const display = me.user?.email ?? me.email ?? me.user?.display_name ?? me.account ?? account;
665
+ printSuccess(`Logged in as ${display} (${formatPlatformLabel(config.platformUrl)})`);
666
+ }
667
+ async function runBrowserLogin(config) {
668
+ await ensureBrowserLoginEnabled(config);
669
+ await browserPkceLogin(config);
670
+ }
671
+ async function runLogin(config, opts) {
672
+ if (opts.token) {
673
+ await saveTokenLogin(config, opts.token);
674
+ return;
675
+ }
676
+ if (opts.browser && wantsPasswordLogin(opts)) {
677
+ throw new AspError("Cannot use --browser together with --password, --account, or --pass.", {
678
+ code: "INVALID_REQUEST",
679
+ hint: "Choose one login method."
680
+ });
681
+ }
682
+ if (opts.browser) {
683
+ await runBrowserLogin(config);
684
+ return;
685
+ }
686
+ if (wantsPasswordLogin(opts)) {
687
+ await runPasswordLogin(config, opts);
688
+ return;
689
+ }
690
+ if (!process.stdin.isTTY) {
691
+ throw new AspError("Non-interactive login requires a login method flag.", {
692
+ code: "INVALID_REQUEST",
693
+ hint: "Use `asp auth login --browser` or `asp auth login --password --account <email> --pass <password>`."
694
+ });
695
+ }
696
+ const method = await promptLoginMethod();
697
+ if (method === "browser") {
698
+ await runBrowserLogin(config);
699
+ return;
700
+ }
701
+ await runPasswordLogin(config, opts);
702
+ }
703
+ async function saveTokenLogin(config, token) {
704
+ const creds = credentialsFromTokenResponse(config.platformUrl, {
705
+ access_token: token,
706
+ token_type: "Bearer"
707
+ });
708
+ await saveCredentials(creds);
709
+ const me = await new CliApiClient({ ...config, token }).getMe();
710
+ const display = me.user?.email ?? me.email ?? me.user?.display_name ?? me.account ?? me.user_id;
711
+ printSuccess(`Token saved. Logged in as ${display} (${formatPlatformLabel(config.platformUrl)})`);
712
+ }
713
+ async function runStatus(getConfig2) {
714
+ const base = getConfig2();
715
+ const creds = await loadCredentials();
716
+ if (!base.token && !creds) {
717
+ printInfo("Not logged in.");
718
+ printInfo("Run `asp auth login` to sign in.");
719
+ return;
720
+ }
721
+ const { config: apiConfig } = await requireCredentials(base, base.platformUrl);
722
+ const client = new CliApiClient(apiConfig);
723
+ const me = await client.getMe();
724
+ const latestCreds = await loadCredentials() ?? creds;
725
+ if (base.json) {
726
+ printJson({
727
+ logged_in: true,
728
+ environment: formatPlatformLabel(apiConfig.platformUrl),
729
+ user: me.user ?? {
730
+ id: me.user_id,
731
+ email: me.email,
732
+ display_name: me.username ?? me.account
733
+ },
734
+ scopes: me.scopes,
735
+ expires_at: latestCreds?.expires_at,
736
+ platform_url: apiConfig.platformUrl
737
+ });
738
+ return;
739
+ }
740
+ const user = me.user ?? {
741
+ id: me.user_id ?? "",
742
+ email: me.email,
743
+ display_name: me.username ?? me.account
744
+ };
745
+ printInfo(`Environment: ${formatPlatformLabel(apiConfig.platformUrl)}`);
746
+ printInfo(`User: ${user.display_name ?? user.email ?? user.id}`);
747
+ if (user.email) printInfo(`Email: ${user.email}`);
748
+ if (me.scopes?.length) printInfo(`Scopes: ${me.scopes.join(", ")}`);
749
+ if (latestCreds?.expires_at) {
750
+ printInfo(`Token expires: ${formatDateTime(latestCreds.expires_at)}`);
751
+ }
752
+ }
753
+ async function runRefresh(getConfig2) {
754
+ const base = getConfig2();
755
+ const { config, creds } = await requireCredentials(base, base.platformUrl);
756
+ if (!creds.refresh_token) {
757
+ throw new AspError("No refresh token available.", {
758
+ code: "UNAUTHORIZED",
759
+ hint: "Run `asp auth login`.",
760
+ exitCode: 3
761
+ });
762
+ }
763
+ await refreshStoredSession(config, creds.refresh_token, config.platformUrl);
764
+ printSuccess(`Token refreshed (${formatPlatformLabel(config.platformUrl)}).`);
765
+ }
766
+ async function runLogout(getConfig2) {
767
+ const base = getConfig2();
768
+ const platformUrl = base.platformUrl;
769
+ const creds = await loadCredentials();
770
+ if (!creds) {
771
+ printInfo(`Not logged in to ${formatPlatformLabel(platformUrl)}.`);
772
+ return;
773
+ }
774
+ if (creds.refresh_token) {
775
+ const client = new CliApiClient({ ...base, platformUrl: creds.platform_url });
776
+ await client.logout().catch(() => {
777
+ });
778
+ }
779
+ await clearCredentials();
780
+ printSuccess(`Logged out from ${formatPlatformLabel(creds.platform_url)}.`);
781
+ }
782
+ async function runLogs(getConfig2, opts) {
783
+ const base = getConfig2();
784
+ const { config } = await requireCredentials(base, base.platformUrl);
785
+ const response = await new CliApiClient(config).getRequestLogs({
786
+ page_size: opts.pageSize,
787
+ cursor: opts.cursor
788
+ });
789
+ if (base.json) {
790
+ printJson(response);
791
+ return;
792
+ }
793
+ if (response.items.length === 0) {
794
+ printInfo("No request logs.");
795
+ return;
796
+ }
797
+ const rows = response.items.map((item) => [
798
+ item.created_at,
799
+ item.method,
800
+ item.path,
801
+ String(item.response_status ?? "-"),
802
+ String(item.duration_ms ?? "-")
803
+ ]);
804
+ printTable(["time", "method", "path", "status", "ms"], rows);
805
+ if (response.next_cursor) {
806
+ printInfo(`Next cursor: ${response.next_cursor}`);
807
+ }
808
+ }
809
+ function registerAuthCommands(program2, getConfig2) {
810
+ const auth = program2.command("auth").description("Authentication commands");
811
+ let login = auth.command("login").description("Log in (default platform is pre environment)");
812
+ for (const [flags, desc] of LOGIN_OPTIONS) {
813
+ login = login.option(flags, desc);
814
+ }
815
+ login.action(async (opts) => {
816
+ await runLogin(getConfig2(), opts);
817
+ });
818
+ auth.command("status").description("Show login status").action(async () => {
819
+ await runStatus(getConfig2);
820
+ });
821
+ auth.command("refresh").description("Refresh access token").action(async () => {
822
+ await runRefresh(getConfig2);
823
+ });
824
+ auth.command("logout").description("Log out current account").action(async () => {
825
+ await runLogout(getConfig2);
826
+ });
827
+ auth.command("logs").description("Show CLI request audit logs").option("--page-size <n>", "Page size", (v) => Number.parseInt(v, 10)).option("--cursor <cursor>", "Cursor for next page").action(async (opts) => {
828
+ await runLogs(getConfig2, opts);
829
+ });
830
+ }
831
+
832
+ // src/commands/plugin.ts
833
+ import { basename } from "path";
834
+ import { createHash as createHash2 } from "crypto";
835
+ import { readFile as readFile2 } from "fs/promises";
836
+ import { spawn } from "child_process";
837
+ import { createInterface as createInterface2 } from "readline/promises";
838
+ import { stdin as input2, stdout as output2 } from "process";
839
+
840
+ // src/utils/artifact-publish.ts
841
+ function appendOptionalArtifactFields(body, fields) {
842
+ if (fields.artifactId) body.artifact_id = fields.artifactId;
843
+ if (fields.version) body.version = fields.version;
844
+ if (fields.repoUrl) body.repo_url = fields.repoUrl;
845
+ return body;
846
+ }
847
+ function looksLikeSubmissionId(id) {
848
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
849
+ }
850
+
851
+ // src/utils/publish-registry.ts
852
+ import { mkdir, readFile, writeFile } from "fs/promises";
853
+ import { join } from "path";
854
+ var REGISTRY_FILE = "publish-submissions.json";
855
+ var MAX_ENTRIES = 200;
856
+ function normalizePlatformUrl(url) {
857
+ return url.replace(/\/$/, "");
858
+ }
859
+ async function loadRegistry() {
860
+ try {
861
+ const path = join(getAspConfigDir(), REGISTRY_FILE);
862
+ const raw = await readFile(path, "utf8");
863
+ return JSON.parse(raw);
864
+ } catch {
865
+ return { entries: [] };
866
+ }
867
+ }
868
+ async function saveRegistry(registry) {
869
+ const dir = getAspConfigDir();
870
+ await mkdir(dir, { recursive: true });
871
+ const path = join(dir, REGISTRY_FILE);
872
+ await writeFile(path, `${JSON.stringify(registry, null, 2)}
873
+ `, "utf8");
874
+ }
875
+ async function recordPluginPublish(opts) {
876
+ const registry = await loadRegistry();
877
+ const platform = normalizePlatformUrl(opts.platformUrl);
878
+ const now = (/* @__PURE__ */ new Date()).toISOString();
879
+ registry.entries = registry.entries.filter((entry) => {
880
+ if (entry.platform_url !== platform) return true;
881
+ if (entry.submission_id === opts.submissionId) return false;
882
+ if (opts.pluginId && entry.plugin_id === opts.pluginId) return false;
883
+ return true;
884
+ });
885
+ registry.entries.unshift({
886
+ submission_id: opts.submissionId,
887
+ platform_url: platform,
888
+ plugin_id: opts.pluginId,
889
+ source_url: opts.sourceUrl,
890
+ updated_at: now
891
+ });
892
+ registry.entries = registry.entries.slice(0, MAX_ENTRIES);
893
+ await saveRegistry(registry);
894
+ }
895
+ async function lookupPluginSubmissionId(ref, platformUrl) {
896
+ return lookupArtifactSubmissionId(ref, platformUrl, "plugin");
897
+ }
898
+ async function recordSkillPublish(opts) {
899
+ const registry = await loadRegistry();
900
+ const platform = normalizePlatformUrl(opts.platformUrl);
901
+ const now = (/* @__PURE__ */ new Date()).toISOString();
902
+ registry.entries = registry.entries.filter((entry) => {
903
+ if (entry.platform_url !== platform) return true;
904
+ if (entry.submission_id === opts.submissionId) return false;
905
+ if (opts.skillId && entry.skill_id === opts.skillId) return false;
906
+ return true;
907
+ });
908
+ registry.entries.unshift({
909
+ submission_id: opts.submissionId,
910
+ platform_url: platform,
911
+ skill_id: opts.skillId,
912
+ source_url: opts.sourceUrl,
913
+ updated_at: now
914
+ });
915
+ registry.entries = registry.entries.slice(0, MAX_ENTRIES);
916
+ await saveRegistry(registry);
917
+ }
918
+ async function lookupSkillSubmissionId(ref, platformUrl) {
919
+ return lookupArtifactSubmissionId(ref, platformUrl, "skill");
920
+ }
921
+ async function lookupArtifactSubmissionId(ref, platformUrl, artifactType) {
922
+ const registry = await loadRegistry();
923
+ const platform = normalizePlatformUrl(platformUrl);
924
+ const refLower = ref.toLowerCase();
925
+ for (const entry of registry.entries) {
926
+ if (entry.platform_url !== platform) continue;
927
+ if (entry.submission_id === ref) return entry.submission_id;
928
+ const artifactId = artifactType === "plugin" ? entry.plugin_id : entry.skill_id;
929
+ if (artifactId === ref) return entry.submission_id;
930
+ if (entry.source_url?.toLowerCase().includes(refLower)) return entry.submission_id;
931
+ }
932
+ return void 0;
933
+ }
934
+
935
+ // src/utils/submission-resolve.ts
936
+ var SUBMISSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
937
+ var SUBMISSION_ID_IN_PATH = /\/artifact-submissions\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
938
+ function requestBodyMatchesArtifact(body, artifactId) {
939
+ if (!body || typeof body !== "object") return false;
940
+ const record = body;
941
+ if (record.artifact_id === artifactId) return true;
942
+ if (typeof record.source_url !== "string") return false;
943
+ const url = record.source_url.toLowerCase();
944
+ const id = artifactId.toLowerCase();
945
+ if (url.includes(id)) return true;
946
+ const segments = url.split(/[/\\?&#]/).filter(Boolean);
947
+ return segments.some((segment) => segment === id);
948
+ }
949
+ function submissionMatchesPlugin(state, pluginId) {
950
+ const anyState = state;
951
+ if (anyState.artifact_id === pluginId) return true;
952
+ const pluginIdLower = pluginId.toLowerCase();
953
+ const publicUrl = state.publish?.public_url?.toLowerCase();
954
+ if (publicUrl?.includes(`/plugins/${pluginIdLower}`)) return true;
955
+ return false;
956
+ }
957
+ async function rememberResolvedSubmission(submissionId, platformUrl, state) {
958
+ const anyState = state;
959
+ if (!anyState.artifact_id) return;
960
+ await recordPluginPublish({
961
+ submissionId,
962
+ platformUrl,
963
+ pluginId: anyState.artifact_id
964
+ });
965
+ }
966
+ async function resolvePluginSubmissionId(client, pluginId, platformUrl) {
967
+ if (SUBMISSION_ID_PATTERN.test(pluginId)) return pluginId;
968
+ const cached = await lookupPluginSubmissionId(pluginId, platformUrl);
969
+ if (cached) return cached;
970
+ const latestSeenAt = /* @__PURE__ */ new Map();
971
+ let cursor;
972
+ for (let page = 0; page < 10; page += 1) {
973
+ const response = await client.getRequestLogs({ page_size: 100, cursor });
974
+ for (const item of response.items) {
975
+ const match = item.path.match(SUBMISSION_ID_IN_PATH);
976
+ if (match) {
977
+ const submissionId = match[1];
978
+ const previous = latestSeenAt.get(submissionId);
979
+ if (!previous || item.created_at > previous) {
980
+ latestSeenAt.set(submissionId, item.created_at);
981
+ }
982
+ }
983
+ if (item.method === "POST" && item.path.endsWith("/artifact-submissions") && requestBodyMatchesArtifact(item.request_body, pluginId)) {
984
+ const postMatch = item.path.match(SUBMISSION_ID_IN_PATH);
985
+ if (postMatch) {
986
+ latestSeenAt.set(postMatch[1], item.created_at);
987
+ }
988
+ }
989
+ }
990
+ if (!response.next_cursor) break;
991
+ cursor = response.next_cursor ?? void 0;
992
+ }
993
+ const candidates = [...latestSeenAt.entries()].sort((a, b) => b[1].localeCompare(a[1])).map(([submissionId]) => submissionId).slice(0, 50);
994
+ const pluginIdLower = pluginId.toLowerCase();
995
+ for (const candidate of candidates) {
996
+ try {
997
+ const state = await client.getArtifactSubmission(candidate);
998
+ if (submissionMatchesPlugin(state, pluginId)) {
999
+ await rememberResolvedSubmission(candidate, platformUrl, state);
1000
+ return candidate;
1001
+ }
1002
+ const logs = await client.getArtifactSubmissionLogs(candidate);
1003
+ const matched = logs.items.some(
1004
+ (item) => item.message.toLowerCase().includes(pluginIdLower)
1005
+ );
1006
+ if (matched) {
1007
+ await rememberResolvedSubmission(candidate, platformUrl, state);
1008
+ return candidate;
1009
+ }
1010
+ } catch {
1011
+ continue;
1012
+ }
1013
+ }
1014
+ return void 0;
1015
+ }
1016
+ function submissionMatchesSkill(state, skillId) {
1017
+ const anyState = state;
1018
+ if (anyState.artifact_id === skillId) return true;
1019
+ const skillIdLower = skillId.toLowerCase();
1020
+ const publicUrl = state.publish?.public_url?.toLowerCase();
1021
+ if (publicUrl?.includes(`/skills/${skillIdLower}`)) return true;
1022
+ return false;
1023
+ }
1024
+ async function rememberResolvedSkillSubmission(submissionId, platformUrl, state) {
1025
+ const anyState = state;
1026
+ if (!anyState.artifact_id) return;
1027
+ await recordSkillPublish({
1028
+ submissionId,
1029
+ platformUrl,
1030
+ skillId: anyState.artifact_id
1031
+ });
1032
+ }
1033
+ async function resolveSkillSubmissionId(client, skillId, platformUrl) {
1034
+ if (SUBMISSION_ID_PATTERN.test(skillId)) return skillId;
1035
+ const cached = await lookupSkillSubmissionId(skillId, platformUrl);
1036
+ if (cached) return cached;
1037
+ const latestSeenAt = /* @__PURE__ */ new Map();
1038
+ let cursor;
1039
+ for (let page = 0; page < 10; page += 1) {
1040
+ const response = await client.getRequestLogs({ page_size: 100, cursor });
1041
+ for (const item of response.items) {
1042
+ const match = item.path.match(SUBMISSION_ID_IN_PATH);
1043
+ if (match) {
1044
+ const submissionId = match[1];
1045
+ const previous = latestSeenAt.get(submissionId);
1046
+ if (!previous || item.created_at > previous) {
1047
+ latestSeenAt.set(submissionId, item.created_at);
1048
+ }
1049
+ }
1050
+ if (item.method === "POST" && item.path.endsWith("/artifact-submissions") && requestBodyMatchesArtifact(item.request_body, skillId)) {
1051
+ const postMatch = item.path.match(SUBMISSION_ID_IN_PATH);
1052
+ if (postMatch) {
1053
+ latestSeenAt.set(postMatch[1], item.created_at);
1054
+ }
1055
+ }
1056
+ }
1057
+ if (!response.next_cursor) break;
1058
+ cursor = response.next_cursor ?? void 0;
1059
+ }
1060
+ const candidates = [...latestSeenAt.entries()].sort((a, b) => b[1].localeCompare(a[1])).map(([submissionId]) => submissionId).slice(0, 50);
1061
+ const skillIdLower = skillId.toLowerCase();
1062
+ for (const candidate of candidates) {
1063
+ try {
1064
+ const state = await client.getArtifactSubmission(candidate);
1065
+ if (submissionMatchesSkill(state, skillId)) {
1066
+ await rememberResolvedSkillSubmission(candidate, platformUrl, state);
1067
+ return candidate;
1068
+ }
1069
+ const logs = await client.getArtifactSubmissionLogs(candidate);
1070
+ const matched = logs.items.some(
1071
+ (item) => item.message.toLowerCase().includes(skillIdLower)
1072
+ );
1073
+ if (matched) {
1074
+ await rememberResolvedSkillSubmission(candidate, platformUrl, state);
1075
+ return candidate;
1076
+ }
1077
+ } catch {
1078
+ continue;
1079
+ }
1080
+ }
1081
+ return void 0;
1082
+ }
1083
+
1084
+ // src/utils/submission-poll.ts
1085
+ var SubmissionWaitInterruptedError = class extends Error {
1086
+ submissionId;
1087
+ constructor(submissionId) {
1088
+ super("Submission wait interrupted");
1089
+ this.name = "SubmissionWaitInterruptedError";
1090
+ this.submissionId = submissionId;
1091
+ }
1092
+ };
1093
+ function sleep(ms, signal) {
1094
+ return new Promise((resolve, reject) => {
1095
+ if (signal.aborted) {
1096
+ reject(new Error("aborted"));
1097
+ return;
1098
+ }
1099
+ const timer = setTimeout(() => {
1100
+ signal.removeEventListener("abort", onAbort);
1101
+ resolve();
1102
+ }, ms);
1103
+ const onAbort = () => {
1104
+ clearTimeout(timer);
1105
+ reject(new Error("aborted"));
1106
+ };
1107
+ signal.addEventListener("abort", onAbort, { once: true });
1108
+ });
1109
+ }
1110
+ async function pollUntilTerminal(client, submissionId, watch) {
1111
+ let lastStatus = "";
1112
+ const controller = new AbortController();
1113
+ const onInterrupt = () => {
1114
+ controller.abort();
1115
+ };
1116
+ process.once("SIGINT", onInterrupt);
1117
+ try {
1118
+ while (!controller.signal.aborted) {
1119
+ const current = await client.getArtifactSubmission(submissionId);
1120
+ if (watch && current.status !== lastStatus) {
1121
+ printInfo(`Submission ${submissionId}: ${current.status}`);
1122
+ lastStatus = current.status;
1123
+ }
1124
+ if (current.terminal) return current;
1125
+ const sleepSec = Math.max(current.next_poll_after_seconds ?? 15, 1);
1126
+ try {
1127
+ await sleep(sleepSec * 1e3, controller.signal);
1128
+ } catch {
1129
+ throw new SubmissionWaitInterruptedError(submissionId);
1130
+ }
1131
+ }
1132
+ throw new SubmissionWaitInterruptedError(submissionId);
1133
+ } finally {
1134
+ process.removeListener("SIGINT", onInterrupt);
1135
+ }
1136
+ }
1137
+
1138
+ // src/utils/marketplace-display.ts
1139
+ function formatPluginListTechType(item) {
1140
+ return item.tech_type ?? item.category ?? item.source ?? "-";
1141
+ }
1142
+
1143
+ // src/commands/plugin.ts
1144
+ var PLUGIN_CATEGORY_OPTIONS = [
1145
+ {
1146
+ value: "tool",
1147
+ label: "Agent Tool",
1148
+ describe: "Functions, APIs, or capabilities that Agents can invoke"
1149
+ },
1150
+ {
1151
+ value: "provider",
1152
+ label: "Model Provider",
1153
+ describe: "Connect custom LLM providers or model endpoints"
1154
+ },
1155
+ {
1156
+ value: "command",
1157
+ label: "Slash Command",
1158
+ describe: "Register console commands in the form /xxx"
1159
+ },
1160
+ {
1161
+ value: "hook",
1162
+ label: "Lifecycle Hook",
1163
+ describe: "Run code at key moments such as startup and shutdown"
1164
+ },
1165
+ {
1166
+ value: "frontend",
1167
+ label: "UI Extension",
1168
+ describe: "Provide frontend JS bundles loaded dynamically by the UI"
1169
+ },
1170
+ {
1171
+ value: "general",
1172
+ label: "General Plugin",
1173
+ describe: "Does not fit the categories above, or combines multiple capabilities"
1174
+ }
1175
+ ];
1176
+ async function runCommand(command, args) {
1177
+ return new Promise((resolve, reject) => {
1178
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1179
+ let stdout = "";
1180
+ let stderr = "";
1181
+ child.stdout.on("data", (chunk) => {
1182
+ stdout += String(chunk);
1183
+ });
1184
+ child.stderr.on("data", (chunk) => {
1185
+ stderr += String(chunk);
1186
+ });
1187
+ child.on("error", reject);
1188
+ child.on("close", (code) => {
1189
+ if (code === 0) resolve(stdout);
1190
+ else reject(new Error(stderr || `Command failed: ${command} ${args.join(" ")}`));
1191
+ });
1192
+ });
1193
+ }
1194
+ async function readPluginManifestFromZip(zipPath) {
1195
+ if (!zipPath) return void 0;
1196
+ try {
1197
+ const stdout = await runCommand("unzip", ["-p", zipPath, "plugin.json"]);
1198
+ return JSON.parse(stdout);
1199
+ } catch {
1200
+ return void 0;
1201
+ }
1202
+ }
1203
+ function getClient(getConfig2) {
1204
+ return new CliApiClient(getConfig2());
1205
+ }
1206
+ async function runSearch(getConfig2, query, opts) {
1207
+ const config = getConfig2();
1208
+ const client = getClient(getConfig2);
1209
+ const response = await client.searchPlugins({
1210
+ q: query,
1211
+ category: opts.category,
1212
+ source: opts.source,
1213
+ sort: opts.sort,
1214
+ page_size: opts.pageSize,
1215
+ cursor: opts.cursor
1216
+ });
1217
+ if (config.json) {
1218
+ printJson(response);
1219
+ return;
1220
+ }
1221
+ if (response.items.length === 0) {
1222
+ printInfo("No plugins found.");
1223
+ return;
1224
+ }
1225
+ const rows = response.items.map((item) => [
1226
+ item.plugin_id,
1227
+ String(item.latest_version ?? "-"),
1228
+ formatPluginListTechType(item),
1229
+ String(item.name ?? "-")
1230
+ ]);
1231
+ printTable(["plugin_id", "version", "tech_type", "name"], rows);
1232
+ if (response.next_cursor) {
1233
+ printInfo(`Next cursor: ${response.next_cursor}`);
1234
+ }
1235
+ }
1236
+ async function runInfo(getConfig2, pluginId, opts) {
1237
+ const config = getConfig2();
1238
+ const client = getClient(getConfig2);
1239
+ const response = await client.getPluginInfo(pluginId, opts.version);
1240
+ if (config.json) {
1241
+ printJson(response);
1242
+ return;
1243
+ }
1244
+ printInfo(`Plugin: ${response.plugin_id}`);
1245
+ if (response.latest_version) printInfo(`Version: ${response.latest_version}`);
1246
+ if (response.scan_status) printInfo(`Scan: ${response.scan_status}`);
1247
+ if (response.install_command) printInfo(`Install: ${response.install_command}`);
1248
+ if (response.public_url) printInfo(`URL: ${response.public_url}`);
1249
+ }
1250
+ async function runVersions(getConfig2, pluginId) {
1251
+ const config = getConfig2();
1252
+ const client = getClient(getConfig2);
1253
+ const response = await client.getPluginVersions(pluginId);
1254
+ if (config.json) {
1255
+ printJson(response);
1256
+ return;
1257
+ }
1258
+ if (response.items.length === 0) {
1259
+ printInfo("No published versions.");
1260
+ return;
1261
+ }
1262
+ const rows = response.items.map((item) => [
1263
+ item.version,
1264
+ item.published_at ?? "-",
1265
+ item.deprecated ? "yes" : "no"
1266
+ ]);
1267
+ printTable(["version", "published_at", "deprecated"], rows);
1268
+ }
1269
+ async function runInstall(getConfig2, pluginId, opts) {
1270
+ const base = getConfig2();
1271
+ const { config } = await requireCredentials(base, base.platformUrl);
1272
+ const client = new CliApiClient(config);
1273
+ const intent = await client.createInstallIntent(pluginId, opts.version);
1274
+ if (base.json) {
1275
+ printJson(intent);
1276
+ return;
1277
+ }
1278
+ if (opts.via !== void 0 && opts.via !== "qwenpaw") {
1279
+ throw new AspError("Only --via qwenpaw is supported now.", {
1280
+ code: "INVALID_REQUEST"
1281
+ });
1282
+ }
1283
+ const installCmd = `qwenpaw plugin install ${intent.download_url}`;
1284
+ printInfo(`Run to install:
1285
+ ${installCmd}`);
1286
+ if (intent.sha512) printInfo(`sha512: ${intent.sha512}`);
1287
+ if (intent.expires_at) printInfo(`URL expires at: ${intent.expires_at}`);
1288
+ const child = spawn("qwenpaw", ["plugin", "install", intent.download_url], {
1289
+ stdio: "inherit"
1290
+ });
1291
+ await new Promise((resolve, reject) => {
1292
+ child.on("exit", (code) => {
1293
+ if (code === 0) resolve();
1294
+ else reject(new AspError(`qwenpaw exited with code ${code ?? 1}`, { code: "QWENPAW_FAILED" }));
1295
+ });
1296
+ child.on("error", reject);
1297
+ });
1298
+ }
1299
+ async function uploadZipToSignedUrl(upload, fileBuffer) {
1300
+ const response = await fetch(upload.url, {
1301
+ method: upload.method ?? "PUT",
1302
+ headers: upload.headers ?? {},
1303
+ body: fileBuffer
1304
+ });
1305
+ if (!response.ok) {
1306
+ throw new AspError(`Upload failed with HTTP ${response.status}`, {
1307
+ code: "UPLOAD_FAILED",
1308
+ statusCode: response.status
1309
+ });
1310
+ }
1311
+ }
1312
+ function buildZipBody(filePath, fileBuffer, sha512, opts) {
1313
+ return appendOptionalArtifactFields(
1314
+ {
1315
+ artifact_type: "plugin",
1316
+ package_source: "zip",
1317
+ category: opts.category,
1318
+ filename: basename(filePath),
1319
+ size_bytes: fileBuffer.byteLength,
1320
+ sha512,
1321
+ publish_mode: opts.publishMode ?? "auto_after_scan",
1322
+ client: {
1323
+ name: "agentscope-platform-cli",
1324
+ version: CLI_VERSION
1325
+ }
1326
+ },
1327
+ {
1328
+ artifactId: opts.artifactId,
1329
+ version: opts.version,
1330
+ repoUrl: opts.repoUrl
1331
+ }
1332
+ );
1333
+ }
1334
+ function buildUrlBody(opts) {
1335
+ return appendOptionalArtifactFields(
1336
+ {
1337
+ artifact_type: "plugin",
1338
+ package_source: "url",
1339
+ source_url: opts.url,
1340
+ category: opts.category,
1341
+ publish_mode: opts.publishMode ?? "auto_after_scan",
1342
+ client: {
1343
+ name: "agentscope-platform-cli",
1344
+ version: CLI_VERSION
1345
+ }
1346
+ },
1347
+ {
1348
+ artifactId: opts.artifactId,
1349
+ version: opts.version,
1350
+ repoUrl: opts.repoUrl
1351
+ }
1352
+ );
1353
+ }
1354
+ function parseCategory(inputValue) {
1355
+ if (!inputValue) return void 0;
1356
+ const normalized = inputValue.trim().toLowerCase();
1357
+ if (PLUGIN_CATEGORY_OPTIONS.some((item) => item.value === normalized)) {
1358
+ return normalized;
1359
+ }
1360
+ return void 0;
1361
+ }
1362
+ function tryParseCategoryFromUrl(urlValue) {
1363
+ if (!urlValue) return void 0;
1364
+ try {
1365
+ const parsed = new URL(urlValue);
1366
+ const queryCategory = parseCategory(parsed.searchParams.get("category") ?? void 0);
1367
+ if (queryCategory) return queryCategory;
1368
+ const segments = parsed.pathname.split("/").map((item) => item.trim().toLowerCase()).filter(Boolean);
1369
+ for (let i = 0; i < segments.length; i += 1) {
1370
+ const current = parseCategory(segments[i]);
1371
+ if (current) return current;
1372
+ }
1373
+ return void 0;
1374
+ } catch {
1375
+ return void 0;
1376
+ }
1377
+ }
1378
+ function tryParsePluginIdFromUrl(urlValue) {
1379
+ if (!urlValue) return void 0;
1380
+ try {
1381
+ const parsed = new URL(urlValue);
1382
+ const segments = parsed.pathname.split("/").map((item) => item.trim()).filter(Boolean);
1383
+ const pluginsIdx = segments.findIndex((item) => item.toLowerCase() === "plugins");
1384
+ if (pluginsIdx >= 0 && segments[pluginsIdx + 2]) {
1385
+ return segments[pluginsIdx + 2];
1386
+ }
1387
+ const last = segments[segments.length - 1];
1388
+ if (last?.toLowerCase().endsWith(".zip")) {
1389
+ return last.replace(/-\d+\.\d+\.\d+.*\.zip$/i, "").replace(/\.zip$/i, "");
1390
+ }
1391
+ return void 0;
1392
+ } catch {
1393
+ return void 0;
1394
+ }
1395
+ }
1396
+ async function tryParseCategoryFromZip(zipPath, manifest) {
1397
+ const parsed = manifest ?? await readPluginManifestFromZip(zipPath);
1398
+ return parseCategory(parsed?.type);
1399
+ }
1400
+ function printManifestPreview(manifest, jsonOutput) {
1401
+ if (jsonOutput || !manifest) return;
1402
+ if (manifest.id) {
1403
+ printInfo(`Package plugin id: ${manifest.id} (parsed on complete if --artifact-id is omitted)`);
1404
+ }
1405
+ if (manifest.version) {
1406
+ printInfo(`Package version: ${manifest.version} (parsed on complete if --version is omitted)`);
1407
+ }
1408
+ }
1409
+ function validateExplicitManifestOverrides(opts, manifest) {
1410
+ if (opts.artifactId && manifest?.id && opts.artifactId !== manifest.id) {
1411
+ throw new AspError(
1412
+ `artifact_id mismatch: --artifact-id=${opts.artifactId}, plugin.json id=${manifest.id}`,
1413
+ {
1414
+ code: "ARTIFACT_ID_MISMATCH",
1415
+ hint: "Remove --artifact-id to let the server parse from package, or fix the value."
1416
+ }
1417
+ );
1418
+ }
1419
+ if (opts.version && manifest?.version && opts.version !== manifest.version) {
1420
+ throw new AspError(
1421
+ `version mismatch: --version=${opts.version}, plugin.json version=${manifest.version}`,
1422
+ {
1423
+ code: "VERSION_MISMATCH",
1424
+ hint: "Remove --version to let the server parse from package, or fix the value."
1425
+ }
1426
+ );
1427
+ }
1428
+ }
1429
+ async function resolvePublishCategoryWithAutoDetect(opts, zipPath, jsonOutput, manifest) {
1430
+ const fromArg = parseCategory(opts.category);
1431
+ if (fromArg) return fromArg;
1432
+ if (opts.category && !fromArg) {
1433
+ throw new AspError(`Invalid --category: ${opts.category}`, {
1434
+ code: "INVALID_REQUEST",
1435
+ hint: `Use one of: ${PLUGIN_CATEGORY_OPTIONS.map((item) => item.value).join(", ")}`
1436
+ });
1437
+ }
1438
+ const fromZip = await tryParseCategoryFromZip(zipPath, manifest);
1439
+ if (fromZip) {
1440
+ if (!jsonOutput) printInfo(`Detected category from plugin package: ${fromZip}`);
1441
+ return fromZip;
1442
+ }
1443
+ const fromUrl = tryParseCategoryFromUrl(opts.url);
1444
+ if (fromUrl) {
1445
+ if (!jsonOutput) printInfo(`Detected category from source URL: ${fromUrl}`);
1446
+ return fromUrl;
1447
+ }
1448
+ return resolvePublishCategory(opts, jsonOutput);
1449
+ }
1450
+ async function resolvePublishCategory(opts, jsonOutput) {
1451
+ const fromArg = parseCategory(opts.category);
1452
+ if (fromArg) return fromArg;
1453
+ if (opts.category && !fromArg) {
1454
+ throw new AspError(`Invalid --category: ${opts.category}`, {
1455
+ code: "INVALID_REQUEST",
1456
+ hint: `Use one of: ${PLUGIN_CATEGORY_OPTIONS.map((item) => item.value).join(", ")}`
1457
+ });
1458
+ }
1459
+ if (jsonOutput || !input2.isTTY || !output2.isTTY) {
1460
+ throw new AspError("Plugin publish requires --category.", {
1461
+ code: "INVALID_REQUEST",
1462
+ hint: `Use --category with one of: ${PLUGIN_CATEGORY_OPTIONS.map((item) => item.value).join(", ")}`
1463
+ });
1464
+ }
1465
+ printInfo("Select plugin category:");
1466
+ for (const [index, item] of PLUGIN_CATEGORY_OPTIONS.entries()) {
1467
+ printInfo(`${index + 1}) ${item.value} - ${item.label}`);
1468
+ printInfo(` ${item.describe}`);
1469
+ }
1470
+ const rl = createInterface2({ input: input2, output: output2 });
1471
+ try {
1472
+ while (true) {
1473
+ const answer = (await rl.question("Enter category number or value: ")).trim();
1474
+ const index = Number.parseInt(answer, 10);
1475
+ if (!Number.isNaN(index) && index >= 1 && index <= PLUGIN_CATEGORY_OPTIONS.length) {
1476
+ const selected = PLUGIN_CATEGORY_OPTIONS[index - 1].value;
1477
+ printInfo(`Selected category: ${selected}`);
1478
+ return selected;
1479
+ }
1480
+ const byValue = parseCategory(answer);
1481
+ if (byValue) {
1482
+ printInfo(`Selected category: ${byValue}`);
1483
+ return byValue;
1484
+ }
1485
+ printWarn(`Invalid choice. Use 1-${PLUGIN_CATEGORY_OPTIONS.length} or category value.`);
1486
+ }
1487
+ } finally {
1488
+ rl.close();
1489
+ }
1490
+ }
1491
+ function isMissingCategoryError(err) {
1492
+ if (!(err instanceof AspError)) return false;
1493
+ if (err.code !== "INVALID_REQUEST") return false;
1494
+ const message = `${err.message} ${err.hint ?? ""}`.toLowerCase();
1495
+ return message.includes("category") || message.includes("\u6280\u672F\u7C7B\u578B");
1496
+ }
1497
+ async function showPublishedPluginStatus(client, pluginId, jsonOutput) {
1498
+ try {
1499
+ const info = await client.getPluginInfo(pluginId);
1500
+ if (jsonOutput) {
1501
+ printJson({
1502
+ plugin_id: info.plugin_id,
1503
+ status: "published",
1504
+ terminal: true,
1505
+ latest_version: info.latest_version,
1506
+ scan_status: info.scan_status,
1507
+ public_url: info.public_url,
1508
+ install_command: info.install_command
1509
+ });
1510
+ return;
1511
+ }
1512
+ printInfo(`Plugin: ${info.plugin_id}`);
1513
+ printInfo("Status: published");
1514
+ printInfo("Terminal: yes");
1515
+ if (info.latest_version) printInfo(`Version: ${info.latest_version}`);
1516
+ if (info.scan_status) printInfo(`Scan: ${info.scan_status}`);
1517
+ if (info.public_url) printInfo(`URL: ${info.public_url}`);
1518
+ if (info.install_command) printInfo(`Install: ${info.install_command}`);
1519
+ } catch (err) {
1520
+ if (err instanceof AspError && err.statusCode === 404) {
1521
+ throw new AspError(`No submission or published plugin found: ${pluginId}`, {
1522
+ code: "NOT_FOUND",
1523
+ hint: "If the plugin is still scanning, use submission_id from publish output. Republish on this machine to cache plugin_id mapping locally."
1524
+ });
1525
+ }
1526
+ throw err;
1527
+ }
1528
+ }
1529
+ async function runPublish(getConfig2, zipPath, opts) {
1530
+ const base = getConfig2();
1531
+ const { config } = await requireCredentials(base, base.platformUrl);
1532
+ const client = new CliApiClient(config);
1533
+ const useUrlMode = Boolean(opts.url);
1534
+ const manifest = useUrlMode ? void 0 : await readPluginManifestFromZip(zipPath);
1535
+ printManifestPreview(manifest, base.json);
1536
+ validateExplicitManifestOverrides(opts, manifest);
1537
+ let publishOpts;
1538
+ try {
1539
+ const category = await resolvePublishCategoryWithAutoDetect(
1540
+ opts,
1541
+ zipPath,
1542
+ base.json,
1543
+ manifest
1544
+ );
1545
+ publishOpts = { ...opts, category };
1546
+ } catch (err) {
1547
+ if (!isMissingCategoryError(err)) throw err;
1548
+ const category = await resolvePublishCategory({ ...opts, category: void 0 }, base.json);
1549
+ publishOpts = { ...opts, category };
1550
+ }
1551
+ if (useUrlMode && zipPath) {
1552
+ throw new AspError("Do not pass zip path when using --url.", {
1553
+ code: "INVALID_REQUEST",
1554
+ hint: "Use either `asp plugin publish ./plugin.zip` or `asp plugin publish --url <url>`."
1555
+ });
1556
+ }
1557
+ if (!useUrlMode && !zipPath) {
1558
+ throw new AspError("Zip path is required unless --url is provided.", {
1559
+ code: "INVALID_REQUEST",
1560
+ hint: "Use `asp plugin publish ./plugin.zip`."
1561
+ });
1562
+ }
1563
+ let submission;
1564
+ const tryCreateSubmission = async () => {
1565
+ if (useUrlMode) {
1566
+ return client.createArtifactSubmission(buildUrlBody(publishOpts));
1567
+ }
1568
+ const fileBuffer = await readFile2(zipPath);
1569
+ const sha512 = createHash2("sha512").update(fileBuffer).digest("hex");
1570
+ let localSubmission = await client.createArtifactSubmission(
1571
+ buildZipBody(zipPath, fileBuffer, sha512, publishOpts)
1572
+ );
1573
+ if (!localSubmission.upload?.url) {
1574
+ throw new AspError("Missing upload URL for zip submission.", {
1575
+ code: "UPLOAD_URL_MISSING"
1576
+ });
1577
+ }
1578
+ printInfo("Uploading plugin zip to object storage...");
1579
+ await uploadZipToSignedUrl(localSubmission.upload, fileBuffer);
1580
+ localSubmission = await client.completeArtifactSubmission(localSubmission.submission_id, {
1581
+ sha512,
1582
+ size_bytes: fileBuffer.byteLength
1583
+ });
1584
+ return localSubmission;
1585
+ };
1586
+ try {
1587
+ submission = await tryCreateSubmission();
1588
+ } catch (err) {
1589
+ if (!isMissingCategoryError(err) || base.json || !input2.isTTY || !output2.isTTY) {
1590
+ throw err;
1591
+ }
1592
+ printWarn("Publish failed due to missing category, please choose one and retrying...");
1593
+ const category = await resolvePublishCategory({ ...opts, category: void 0 }, base.json);
1594
+ publishOpts = { ...opts, category };
1595
+ submission = await tryCreateSubmission();
1596
+ }
1597
+ const urlPluginId = useUrlMode ? tryParsePluginIdFromUrl(publishOpts.url) : void 0;
1598
+ await recordPluginPublish({
1599
+ submissionId: submission.submission_id,
1600
+ platformUrl: config.platformUrl,
1601
+ pluginId: publishOpts.artifactId ?? manifest?.id ?? urlPluginId,
1602
+ sourceUrl: useUrlMode ? publishOpts.url : void 0
1603
+ });
1604
+ if (base.json) {
1605
+ printJson(submission);
1606
+ return;
1607
+ }
1608
+ printSuccess(`Submission created: ${submission.submission_id}`);
1609
+ printInfo(`Initial status: ${submission.status}`);
1610
+ const trackRef = publishOpts.artifactId ?? manifest?.id ?? urlPluginId ?? submission.submission_id;
1611
+ if (opts.noWait) {
1612
+ printInfo(`Track progress: asp plugin publish status ${trackRef}`);
1613
+ printInfo(`Show logs: asp plugin publish logs ${trackRef}`);
1614
+ return;
1615
+ }
1616
+ if (!submission.terminal) {
1617
+ printSuccess("Upload submitted successfully. Scanning continues asynchronously.");
1618
+ printInfo(`Track progress: asp plugin publish status ${trackRef} --watch`);
1619
+ printInfo(`Show logs: asp plugin publish logs ${trackRef}`);
1620
+ return;
1621
+ }
1622
+ if (submission.status === "published") {
1623
+ printSuccess(`Published successfully (${formatPlatformLabel(config.platformUrl)})`);
1624
+ if (submission.publish?.public_url) {
1625
+ printInfo(`URL: ${submission.publish.public_url}`);
1626
+ }
1627
+ return;
1628
+ }
1629
+ printWarn(`Submission ended with status: ${submission.status}`);
1630
+ if (submission.failure?.message) {
1631
+ printWarn(`Failure: ${submission.failure.message}`);
1632
+ }
1633
+ printInfo(`Inspect logs: asp plugin publish logs ${trackRef}`);
1634
+ }
1635
+ async function runPublishStatus(getConfig2, pluginRef, opts) {
1636
+ const base = getConfig2();
1637
+ const { config } = await requireCredentials(base, base.platformUrl);
1638
+ const client = new CliApiClient(config);
1639
+ if (looksLikeSubmissionId(pluginRef)) {
1640
+ const state2 = opts.watch ? await pollUntilTerminal(client, pluginRef, true) : await client.getArtifactSubmission(pluginRef);
1641
+ if (base.json) {
1642
+ printJson(state2);
1643
+ return;
1644
+ }
1645
+ printInfo(`Submission: ${state2.submission_id}`);
1646
+ printInfo(`Status: ${state2.status}`);
1647
+ printInfo(`Terminal: ${state2.terminal ? "yes" : "no"}`);
1648
+ if (state2.scan?.status) printInfo(`Scan: ${state2.scan.status}`);
1649
+ if (state2.publish?.public_url) printInfo(`URL: ${state2.publish.public_url}`);
1650
+ if (state2.failure?.message) printWarn(`Failure: ${state2.failure.message}`);
1651
+ return;
1652
+ }
1653
+ const submissionId = await resolvePluginSubmissionId(client, pluginRef, config.platformUrl);
1654
+ if (!submissionId) {
1655
+ await showPublishedPluginStatus(client, pluginRef, base.json);
1656
+ return;
1657
+ }
1658
+ if (!base.json) {
1659
+ printInfo(`Resolved plugin id "${pluginRef}" -> submission ${submissionId}`);
1660
+ }
1661
+ const current = await client.getArtifactSubmission(submissionId);
1662
+ if (current.terminal && current.status === "published") {
1663
+ await showPublishedPluginStatus(client, pluginRef, base.json);
1664
+ return;
1665
+ }
1666
+ const state = opts.watch ? await pollUntilTerminal(client, submissionId, true) : current;
1667
+ if (base.json) {
1668
+ printJson(state);
1669
+ return;
1670
+ }
1671
+ printInfo(`Submission: ${state.submission_id}`);
1672
+ printInfo(`Status: ${state.status}`);
1673
+ printInfo(`Terminal: ${state.terminal ? "yes" : "no"}`);
1674
+ if (state.scan?.status) printInfo(`Scan: ${state.scan.status}`);
1675
+ if (state.publish?.public_url) printInfo(`URL: ${state.publish.public_url}`);
1676
+ if (state.failure?.message) printWarn(`Failure: ${state.failure.message}`);
1677
+ }
1678
+ async function runPublishLogs(getConfig2, pluginRef, _opts) {
1679
+ const base = getConfig2();
1680
+ const { config } = await requireCredentials(base, base.platformUrl);
1681
+ const client = new CliApiClient(config);
1682
+ const submissionId = await resolvePluginSubmissionId(client, pluginRef, config.platformUrl);
1683
+ if (!submissionId) {
1684
+ throw new AspError(`No publish logs found for plugin id: ${pluginRef}`, {
1685
+ code: "NOT_FOUND",
1686
+ hint: "Use submission_id from publish output, or republish on this machine to cache the mapping."
1687
+ });
1688
+ }
1689
+ if (submissionId !== pluginRef && !base.json) {
1690
+ printInfo(`Resolved plugin id "${pluginRef}" -> submission ${submissionId}`);
1691
+ }
1692
+ const logs = await client.getArtifactSubmissionLogs(submissionId);
1693
+ if (base.json) {
1694
+ printJson(logs);
1695
+ return;
1696
+ }
1697
+ if (logs.items.length === 0) {
1698
+ printInfo("No logs yet.");
1699
+ return;
1700
+ }
1701
+ const rows = logs.items.map((item) => [item.timestamp, item.level, item.message]);
1702
+ printTable(["timestamp", "level", "message"], rows);
1703
+ }
1704
+ function registerPluginCommands(program2, getConfig2) {
1705
+ const plugin = program2.command("plugin").description("Plugin marketplace and publish commands");
1706
+ plugin.command("search [query]").description("Search plugins").option("--category <code>", "Tech type/category code").option("--source <source>", "official | verified | community").option("--sort <sort>", "recommended | downloads | updated_at").option("--page-size <n>", "Page size", (v) => Number.parseInt(v, 10)).option("--cursor <cursor>", "Cursor for next page").action(async (query, opts) => {
1707
+ await runSearch(getConfig2, query, opts);
1708
+ });
1709
+ plugin.command("info <pluginId>").description("Show plugin details").option("--version <version>", "Specify version").action(async (pluginId, opts) => {
1710
+ await runInfo(getConfig2, pluginId, opts);
1711
+ });
1712
+ plugin.command("versions <pluginId>").description("List published versions").action(async (pluginId) => {
1713
+ await runVersions(getConfig2, pluginId);
1714
+ });
1715
+ plugin.command("install <pluginId>").description("Install plugin via qwenpaw").option("--version <version>", "Specify version").option("--via <tool>", "Install tool (qwenpaw)", "qwenpaw").action(async (pluginId, opts) => {
1716
+ await runInstall(getConfig2, pluginId, opts);
1717
+ });
1718
+ const publish = plugin.command("publish [zipPath]").description("Publish plugin from local zip or URL").option("--url <url>", "Publish from remote zip URL").option("--artifact-id <id>", "Optional plugin id override (must match plugin.json in zip mode)").option("--version <version>", "Optional version override").option("--category <code>", "Plugin category code (tool|provider|command|hook|frontend|general)").option("--repo-url <url>", "Repository URL").option("--publish-mode <mode>", "Publish mode", "auto_after_scan").option("--no-wait", "Do not wait for terminal status").action(async (zipPath, opts) => {
1719
+ await runPublish(getConfig2, zipPath, opts);
1720
+ });
1721
+ publish.command("status <pluginId>").description("Show publish status by plugin id or submission id").option("--watch", "Poll until terminal status", false).action(async (pluginId, opts) => {
1722
+ await runPublishStatus(getConfig2, pluginId, opts);
1723
+ });
1724
+ publish.command("logs <pluginId>").description("Show publish logs by plugin id or submission id").action(async (pluginId, opts) => {
1725
+ await runPublishLogs(getConfig2, pluginId, opts);
1726
+ });
1727
+ }
1728
+
1729
+ // src/commands/skill.ts
1730
+ import { basename as basename2 } from "path";
1731
+ import { createHash as createHash3 } from "crypto";
1732
+ import { readFile as readFile3 } from "fs/promises";
1733
+ import { spawn as spawn3 } from "child_process";
1734
+ import { createInterface as createInterface3 } from "readline/promises";
1735
+ import { stdin as input3, stdout as output3 } from "process";
1736
+
1737
+ // src/utils/skill-manifest.ts
1738
+ import { spawn as spawn2 } from "child_process";
1739
+ async function runCommand2(command, args) {
1740
+ return new Promise((resolve, reject) => {
1741
+ const child = spawn2(command, args, { stdio: ["ignore", "pipe", "pipe"] });
1742
+ let stdout = "";
1743
+ let stderr = "";
1744
+ child.stdout.on("data", (chunk) => {
1745
+ stdout += chunk.toString();
1746
+ });
1747
+ child.stderr.on("data", (chunk) => {
1748
+ stderr += chunk.toString();
1749
+ });
1750
+ child.on("error", reject);
1751
+ child.on("close", (code) => {
1752
+ if (code === 0) resolve(stdout);
1753
+ else reject(new Error(stderr || `Command failed: ${command} ${args.join(" ")}`));
1754
+ });
1755
+ });
1756
+ }
1757
+ function parseSkillFrontmatter(content) {
1758
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1759
+ if (!match) return {};
1760
+ const yaml = match[1];
1761
+ const manifest = {};
1762
+ const nameMatch = yaml.match(/^name:\s*["']?([^"'\n]+)["']?\s*$/m);
1763
+ if (nameMatch) manifest.name = nameMatch[1].trim();
1764
+ const versionMatch = yaml.match(/^version:\s*["']?([^"'\n]+)["']?\s*$/m);
1765
+ if (versionMatch) {
1766
+ manifest.version = versionMatch[1].trim();
1767
+ return manifest;
1768
+ }
1769
+ const metadataVersionMatch = yaml.match(/^\s+version:\s*["']?([^"'\n]+)["']?\s*$/m);
1770
+ if (metadataVersionMatch) {
1771
+ manifest.version = metadataVersionMatch[1].trim();
1772
+ }
1773
+ return manifest;
1774
+ }
1775
+ async function findSkillMdPathInZip(zipPath) {
1776
+ const listing = await runCommand2("unzip", ["-Z1", zipPath]);
1777
+ const paths = listing.split("\n").map((item) => item.trim()).filter(Boolean);
1778
+ const matches = paths.filter((item) => /(^|\/)SKILL\.md$/i.test(item));
1779
+ if (matches.length === 0) return void 0;
1780
+ return matches.sort((a, b) => a.length - b.length)[0];
1781
+ }
1782
+ async function readSkillManifestFromZip(zipPath) {
1783
+ if (!zipPath) return void 0;
1784
+ try {
1785
+ const skillMdPath = await findSkillMdPathInZip(zipPath);
1786
+ if (!skillMdPath) return void 0;
1787
+ const stdout = await runCommand2("unzip", ["-p", zipPath, skillMdPath]);
1788
+ return parseSkillFrontmatter(stdout);
1789
+ } catch {
1790
+ return void 0;
1791
+ }
1792
+ }
1793
+
1794
+ // src/commands/skill.ts
1795
+ var SKILL_CATEGORY_OPTIONS = [
1796
+ { value: "engineering development", label: "\u5DE5\u7A0B\u5F00\u53D1" },
1797
+ { value: "data research", label: "\u6570\u636E\u7814\u7A76" },
1798
+ { value: "document office", label: "\u6587\u6863\u529E\u516C" },
1799
+ { value: "design creation", label: "\u8BBE\u8BA1\u521B\u4F5C" },
1800
+ { value: "automation integration", label: "\u81EA\u52A8\u5316\u96C6\u6210" },
1801
+ { value: "product management", label: "\u4EA7\u54C1\u7BA1\u7406" },
1802
+ { value: "marketing growth", label: "\u8425\u9500\u589E\u957F" },
1803
+ { value: "security compliance", label: "\u5B89\u5168\u5408\u89C4" },
1804
+ { value: "education knowledge", label: "\u6559\u80B2\u77E5\u8BC6" },
1805
+ { value: "plugin development", label: "Plugin \u5F00\u53D1" },
1806
+ { value: "skills management", label: "Skills \u7BA1\u7406" },
1807
+ { value: "others", label: "\u5176\u5B83" }
1808
+ ];
1809
+ function getClient2(getConfig2) {
1810
+ return new CliApiClient(getConfig2());
1811
+ }
1812
+ function parseTags(tags) {
1813
+ if (!tags) return void 0;
1814
+ return tags.split(",").map((item) => item.trim()).filter(Boolean).slice(0, 3);
1815
+ }
1816
+ async function runSearch2(getConfig2, query, opts) {
1817
+ const config = getConfig2();
1818
+ const client = getClient2(getConfig2);
1819
+ const response = await client.searchSkills({
1820
+ q: query,
1821
+ category: opts.category,
1822
+ source: opts.source,
1823
+ tag: opts.tag,
1824
+ sort: opts.sort,
1825
+ page_size: opts.pageSize,
1826
+ cursor: opts.cursor
1827
+ });
1828
+ if (config.json) {
1829
+ printJson(response);
1830
+ return;
1831
+ }
1832
+ if (response.items.length === 0) {
1833
+ printInfo("No skills found.");
1834
+ return;
1835
+ }
1836
+ const rows = response.items.map((item) => [
1837
+ item.skill_id,
1838
+ String(item.latest_version ?? "-"),
1839
+ String(item.category ?? "-"),
1840
+ String(item.source ?? "-")
1841
+ ]);
1842
+ printTable(["skill_id", "version", "category", "source"], rows);
1843
+ if (response.next_cursor) {
1844
+ printInfo(`Next cursor: ${response.next_cursor}`);
1845
+ }
1846
+ }
1847
+ async function runInfo2(getConfig2, skillId, opts) {
1848
+ const config = getConfig2();
1849
+ const client = getClient2(getConfig2);
1850
+ const response = await client.getSkillInfo(skillId, opts.version);
1851
+ if (config.json) {
1852
+ printJson(response);
1853
+ return;
1854
+ }
1855
+ printInfo(`Skill: ${response.skill_id}`);
1856
+ if (response.latest_version) printInfo(`Version: ${response.latest_version}`);
1857
+ if (response.scan_status) printInfo(`Scan: ${response.scan_status}`);
1858
+ if (response.install_command) printInfo(`Install: ${response.install_command}`);
1859
+ if (response.public_url) printInfo(`URL: ${response.public_url}`);
1860
+ }
1861
+ async function runVersions2(getConfig2, skillId) {
1862
+ const config = getConfig2();
1863
+ const client = getClient2(getConfig2);
1864
+ const response = await client.getSkillVersions(skillId);
1865
+ if (config.json) {
1866
+ printJson(response);
1867
+ return;
1868
+ }
1869
+ if (response.items.length === 0) {
1870
+ printInfo("No published versions.");
1871
+ return;
1872
+ }
1873
+ const rows = response.items.map((item) => [
1874
+ item.version,
1875
+ item.published_at ?? "-",
1876
+ item.deprecated ? "yes" : "no"
1877
+ ]);
1878
+ printTable(["version", "published_at", "deprecated"], rows);
1879
+ }
1880
+ async function runInstall2(getConfig2, skillId, opts) {
1881
+ const base = getConfig2();
1882
+ const { config } = await requireCredentials(base, base.platformUrl);
1883
+ const client = new CliApiClient(config);
1884
+ const intent = await client.createSkillInstallIntent(skillId, opts.version);
1885
+ if (base.json) {
1886
+ printJson(intent);
1887
+ return;
1888
+ }
1889
+ if (opts.via !== void 0 && opts.via !== "qwenpaw") {
1890
+ throw new AspError("Only --via qwenpaw is supported now.", {
1891
+ code: "INVALID_REQUEST"
1892
+ });
1893
+ }
1894
+ const installCmd = `qwenpaw skill install ${intent.download_url}`;
1895
+ printInfo(`Run to install:
1896
+ ${installCmd}`);
1897
+ if (intent.sha512) printInfo(`sha512: ${intent.sha512}`);
1898
+ if (intent.expires_at) printInfo(`URL expires at: ${intent.expires_at}`);
1899
+ const child = spawn3("qwenpaw", ["skill", "install", intent.download_url], {
1900
+ stdio: "inherit"
1901
+ });
1902
+ await new Promise((resolve, reject) => {
1903
+ child.on("exit", (code) => {
1904
+ if (code === 0) resolve();
1905
+ else reject(new AspError(`qwenpaw exited with code ${code ?? 1}`, { code: "QWENPAW_FAILED" }));
1906
+ });
1907
+ child.on("error", reject);
1908
+ });
1909
+ }
1910
+ async function uploadZipToSignedUrl2(upload, fileBuffer) {
1911
+ const response = await fetch(upload.url, {
1912
+ method: upload.method ?? "PUT",
1913
+ headers: upload.headers ?? {},
1914
+ body: fileBuffer
1915
+ });
1916
+ if (!response.ok) {
1917
+ throw new AspError(`Upload failed with HTTP ${response.status}`, {
1918
+ code: "UPLOAD_FAILED",
1919
+ statusCode: response.status
1920
+ });
1921
+ }
1922
+ }
1923
+ function buildZipBody2(filePath, fileBuffer, sha512, opts) {
1924
+ const body = appendOptionalArtifactFields(
1925
+ {
1926
+ artifact_type: "skill",
1927
+ package_source: "zip",
1928
+ category: opts.category,
1929
+ tags: parseTags(opts.tags),
1930
+ filename: basename2(filePath),
1931
+ size_bytes: fileBuffer.byteLength,
1932
+ sha512,
1933
+ publish_mode: opts.publishMode ?? "auto_after_scan",
1934
+ client: {
1935
+ name: "agentscope-platform-cli",
1936
+ version: CLI_VERSION
1937
+ }
1938
+ },
1939
+ {
1940
+ artifactId: opts.artifactId,
1941
+ version: opts.version,
1942
+ repoUrl: opts.repoUrl
1943
+ }
1944
+ );
1945
+ return body;
1946
+ }
1947
+ function buildUrlBody2(opts) {
1948
+ return appendOptionalArtifactFields(
1949
+ {
1950
+ artifact_type: "skill",
1951
+ package_source: "url",
1952
+ source_url: opts.url,
1953
+ category: opts.category,
1954
+ tags: parseTags(opts.tags),
1955
+ publish_mode: opts.publishMode ?? "auto_after_scan",
1956
+ client: {
1957
+ name: "agentscope-platform-cli",
1958
+ version: CLI_VERSION
1959
+ }
1960
+ },
1961
+ {
1962
+ artifactId: opts.artifactId,
1963
+ version: opts.version,
1964
+ repoUrl: opts.repoUrl
1965
+ }
1966
+ );
1967
+ }
1968
+ function parseCategory2(inputValue) {
1969
+ if (!inputValue) return void 0;
1970
+ const normalized = inputValue.trim().toLowerCase();
1971
+ if (SKILL_CATEGORY_OPTIONS.some((item) => item.value === normalized)) {
1972
+ return normalized;
1973
+ }
1974
+ return void 0;
1975
+ }
1976
+ async function resolvePublishCategory2(opts, jsonOutput) {
1977
+ const fromArg = parseCategory2(opts.category);
1978
+ if (fromArg) return fromArg;
1979
+ if (opts.category && !fromArg) {
1980
+ throw new AspError(`Invalid --category: ${opts.category}`, {
1981
+ code: "INVALID_REQUEST",
1982
+ hint: `Use one of: ${SKILL_CATEGORY_OPTIONS.map((item) => item.value).join(", ")}`
1983
+ });
1984
+ }
1985
+ if (jsonOutput || !input3.isTTY || !output3.isTTY) {
1986
+ throw new AspError("Skill publish requires --category.", {
1987
+ code: "INVALID_REQUEST",
1988
+ hint: `Use --category with one of: ${SKILL_CATEGORY_OPTIONS.map((item) => item.value).join(", ")}`
1989
+ });
1990
+ }
1991
+ printInfo("Select skill category:");
1992
+ for (const [index, item] of SKILL_CATEGORY_OPTIONS.entries()) {
1993
+ printInfo(`${index + 1}) ${item.value} - ${item.label}`);
1994
+ }
1995
+ const rl = createInterface3({ input: input3, output: output3 });
1996
+ try {
1997
+ while (true) {
1998
+ const answer = (await rl.question("Enter category number or value: ")).trim();
1999
+ const index = Number.parseInt(answer, 10);
2000
+ if (!Number.isNaN(index) && index >= 1 && index <= SKILL_CATEGORY_OPTIONS.length) {
2001
+ const selected = SKILL_CATEGORY_OPTIONS[index - 1].value;
2002
+ printInfo(`Selected category: ${selected}`);
2003
+ return selected;
2004
+ }
2005
+ const byValue = parseCategory2(answer);
2006
+ if (byValue) {
2007
+ printInfo(`Selected category: ${byValue}`);
2008
+ return byValue;
2009
+ }
2010
+ printWarn(`Invalid choice. Use 1-${SKILL_CATEGORY_OPTIONS.length} or category value.`);
2011
+ }
2012
+ } finally {
2013
+ rl.close();
2014
+ }
2015
+ }
2016
+ async function resolvePublishVersion(jsonOutput) {
2017
+ if (jsonOutput || !input3.isTTY || !output3.isTTY) {
2018
+ throw new AspError("Skill publish requires version.", {
2019
+ code: "INVALID_REQUEST",
2020
+ hint: "Add `version: 1.0.0` to SKILL.md frontmatter, or pass --version 1.0.0."
2021
+ });
2022
+ }
2023
+ const rl = createInterface3({ input: input3, output: output3 });
2024
+ try {
2025
+ while (true) {
2026
+ const answer = (await rl.question("Enter skill version (e.g. 1.0.0): ")).trim();
2027
+ if (answer) {
2028
+ printInfo(`Using version: ${answer}`);
2029
+ return answer;
2030
+ }
2031
+ printWarn("Version is required. Example: 1.0.0");
2032
+ }
2033
+ } finally {
2034
+ rl.close();
2035
+ }
2036
+ }
2037
+ function isMissingVersionError(err) {
2038
+ if (!(err instanceof AspError)) return false;
2039
+ if (err.code !== "INVALID_REQUEST") return false;
2040
+ const message = `${err.message} ${err.hint ?? ""}`.toLowerCase();
2041
+ return message.includes("version") || message.includes("skill.md");
2042
+ }
2043
+ function printSkillManifestPreview(manifest, jsonOutput) {
2044
+ if (jsonOutput || !manifest) return;
2045
+ if (manifest.name) {
2046
+ printInfo(`Package skill id: ${manifest.name}`);
2047
+ }
2048
+ if (manifest.version) {
2049
+ printInfo(`Package version: ${manifest.version}`);
2050
+ }
2051
+ }
2052
+ function validateExplicitSkillOverrides(opts, manifest) {
2053
+ if (opts.artifactId && manifest?.name && opts.artifactId !== manifest.name) {
2054
+ throw new AspError(
2055
+ `artifact_id mismatch: --artifact-id=${opts.artifactId}, SKILL.md name=${manifest.name}`,
2056
+ {
2057
+ code: "ARTIFACT_ID_MISMATCH",
2058
+ hint: "Remove --artifact-id or fix the value to match SKILL.md."
2059
+ }
2060
+ );
2061
+ }
2062
+ if (opts.version && manifest?.version && opts.version !== manifest.version) {
2063
+ throw new AspError(
2064
+ `version mismatch: --version=${opts.version}, SKILL.md version=${manifest.version}`,
2065
+ {
2066
+ code: "VERSION_MISMATCH",
2067
+ hint: "Remove --version or fix the value to match SKILL.md."
2068
+ }
2069
+ );
2070
+ }
2071
+ }
2072
+ function resolveSkillPublishFields(opts, manifest) {
2073
+ return {
2074
+ ...opts,
2075
+ artifactId: opts.artifactId ?? manifest?.name,
2076
+ version: opts.version ?? manifest?.version
2077
+ };
2078
+ }
2079
+ function isMissingCategoryError2(err) {
2080
+ if (!(err instanceof AspError)) return false;
2081
+ if (err.code !== "INVALID_REQUEST") return false;
2082
+ const message = `${err.message} ${err.hint ?? ""}`.toLowerCase();
2083
+ return message.includes("category") || message.includes("\u6280\u672F\u7C7B\u578B") || message.includes("\u5927\u7C7B");
2084
+ }
2085
+ function tryParseSkillIdFromUrl(urlValue) {
2086
+ if (!urlValue) return void 0;
2087
+ try {
2088
+ const parsed = new URL(urlValue);
2089
+ const segments = parsed.pathname.split("/").map((item) => item.trim()).filter(Boolean);
2090
+ const skillsIdx = segments.findIndex((item) => item.toLowerCase() === "skills");
2091
+ if (skillsIdx >= 0 && segments[skillsIdx + 1]) {
2092
+ return segments[skillsIdx + 1];
2093
+ }
2094
+ const last = segments[segments.length - 1];
2095
+ if (last?.toLowerCase().endsWith(".zip")) {
2096
+ return last.replace(/-\d+\.\d+\.\d+.*\.zip$/i, "").replace(/\.zip$/i, "");
2097
+ }
2098
+ return void 0;
2099
+ } catch {
2100
+ return void 0;
2101
+ }
2102
+ }
2103
+ async function showPublishedSkillStatus(client, skillId, jsonOutput) {
2104
+ try {
2105
+ const info = await client.getSkillInfo(skillId);
2106
+ if (jsonOutput) {
2107
+ printJson({
2108
+ skill_id: info.skill_id,
2109
+ status: "published",
2110
+ terminal: true,
2111
+ latest_version: info.latest_version,
2112
+ scan_status: info.scan_status,
2113
+ public_url: info.public_url,
2114
+ install_command: info.install_command
2115
+ });
2116
+ return;
2117
+ }
2118
+ printInfo(`Skill: ${info.skill_id}`);
2119
+ printInfo("Status: published");
2120
+ printInfo("Terminal: yes");
2121
+ if (info.latest_version) printInfo(`Version: ${info.latest_version}`);
2122
+ if (info.scan_status) printInfo(`Scan: ${info.scan_status}`);
2123
+ if (info.public_url) printInfo(`URL: ${info.public_url}`);
2124
+ if (info.install_command) printInfo(`Install: ${info.install_command}`);
2125
+ } catch (err) {
2126
+ if (err instanceof AspError && err.statusCode === 404) {
2127
+ throw new AspError(`No submission or published skill found: ${skillId}`, {
2128
+ code: "NOT_FOUND",
2129
+ hint: "If the skill is still scanning, use submission_id from publish output. Republish on this machine to cache skill_id mapping locally."
2130
+ });
2131
+ }
2132
+ throw err;
2133
+ }
2134
+ }
2135
+ async function runPublish2(getConfig2, zipPath, opts) {
2136
+ const base = getConfig2();
2137
+ const { config } = await requireCredentials(base, base.platformUrl);
2138
+ const client = new CliApiClient(config);
2139
+ let publishOpts;
2140
+ try {
2141
+ const category = await resolvePublishCategory2(opts, base.json);
2142
+ publishOpts = { ...opts, category };
2143
+ } catch (err) {
2144
+ if (!isMissingCategoryError2(err)) throw err;
2145
+ const category = await resolvePublishCategory2({ ...opts, category: void 0 }, base.json);
2146
+ publishOpts = { ...opts, category };
2147
+ }
2148
+ const useUrlMode = Boolean(opts.url);
2149
+ if (useUrlMode && zipPath) {
2150
+ throw new AspError("Do not pass zip path when using --url.", {
2151
+ code: "INVALID_REQUEST",
2152
+ hint: "Use either `asp skill publish ./skill.zip` or `asp skill publish --url <url>`."
2153
+ });
2154
+ }
2155
+ if (!useUrlMode && !zipPath) {
2156
+ throw new AspError("Zip path is required unless --url is provided.", {
2157
+ code: "INVALID_REQUEST",
2158
+ hint: "Use `asp skill publish ./skill.zip`."
2159
+ });
2160
+ }
2161
+ const manifest = useUrlMode ? void 0 : await readSkillManifestFromZip(zipPath);
2162
+ printSkillManifestPreview(manifest, base.json);
2163
+ validateExplicitSkillOverrides(publishOpts, manifest);
2164
+ publishOpts = resolveSkillPublishFields(publishOpts, manifest);
2165
+ if (!publishOpts.version) {
2166
+ publishOpts.version = await resolvePublishVersion(base.json);
2167
+ }
2168
+ let submission;
2169
+ const tryCreateSubmission = async () => {
2170
+ if (useUrlMode) {
2171
+ return client.createArtifactSubmission(buildUrlBody2(publishOpts));
2172
+ }
2173
+ const fileBuffer = await readFile3(zipPath);
2174
+ const sha512 = createHash3("sha512").update(fileBuffer).digest("hex");
2175
+ let localSubmission = await client.createArtifactSubmission(
2176
+ buildZipBody2(zipPath, fileBuffer, sha512, publishOpts)
2177
+ );
2178
+ if (!localSubmission.upload?.url) {
2179
+ throw new AspError("Missing upload URL for zip submission.", {
2180
+ code: "UPLOAD_URL_MISSING"
2181
+ });
2182
+ }
2183
+ printInfo("Uploading skill zip to object storage...");
2184
+ await uploadZipToSignedUrl2(localSubmission.upload, fileBuffer);
2185
+ localSubmission = await client.completeArtifactSubmission(localSubmission.submission_id, {
2186
+ sha512,
2187
+ size_bytes: fileBuffer.byteLength
2188
+ });
2189
+ return localSubmission;
2190
+ };
2191
+ try {
2192
+ submission = await tryCreateSubmission();
2193
+ } catch (err) {
2194
+ if (isMissingVersionError(err)) {
2195
+ if (base.json || !input3.isTTY || !output3.isTTY) {
2196
+ throw new AspError("SKILL.md in zip is missing version.", {
2197
+ code: "INVALID_REQUEST",
2198
+ hint: "Add `version: 1.0.0` to SKILL.md frontmatter (between --- markers), or pass --version 1.0.0."
2199
+ });
2200
+ }
2201
+ printWarn("Publish failed due to missing version, please enter one and retrying...");
2202
+ publishOpts.version = await resolvePublishVersion(base.json);
2203
+ submission = await tryCreateSubmission();
2204
+ } else if (!isMissingCategoryError2(err) || base.json || !input3.isTTY || !output3.isTTY) {
2205
+ throw err;
2206
+ } else {
2207
+ printWarn("Publish failed due to missing category, please choose one and retrying...");
2208
+ const category = await resolvePublishCategory2({ ...opts, category: void 0 }, base.json);
2209
+ publishOpts = { ...publishOpts, category };
2210
+ submission = await tryCreateSubmission();
2211
+ }
2212
+ }
2213
+ const urlSkillId = useUrlMode ? tryParseSkillIdFromUrl(publishOpts.url) : void 0;
2214
+ await recordSkillPublish({
2215
+ submissionId: submission.submission_id,
2216
+ platformUrl: config.platformUrl,
2217
+ skillId: publishOpts.artifactId ?? urlSkillId,
2218
+ sourceUrl: useUrlMode ? publishOpts.url : void 0
2219
+ });
2220
+ if (base.json) {
2221
+ printJson(submission);
2222
+ return;
2223
+ }
2224
+ printSuccess(`Submission created: ${submission.submission_id}`);
2225
+ printInfo(`Initial status: ${submission.status}`);
2226
+ const trackRef = publishOpts.artifactId ?? urlSkillId ?? submission.submission_id;
2227
+ if (opts.noWait) {
2228
+ printInfo(`Track progress: asp skill publish status ${trackRef}`);
2229
+ printInfo(`Show logs: asp skill publish logs ${trackRef}`);
2230
+ return;
2231
+ }
2232
+ if (!submission.terminal) {
2233
+ printSuccess("Upload submitted successfully. Scanning continues asynchronously.");
2234
+ printInfo(`Track progress: asp skill publish status ${trackRef} --watch`);
2235
+ printInfo(`Show logs: asp skill publish logs ${trackRef}`);
2236
+ return;
2237
+ }
2238
+ if (submission.status === "published") {
2239
+ printSuccess(`Published successfully (${formatPlatformLabel(config.platformUrl)})`);
2240
+ if (submission.publish?.public_url) {
2241
+ printInfo(`URL: ${submission.publish.public_url}`);
2242
+ }
2243
+ return;
2244
+ }
2245
+ printWarn(`Submission ended with status: ${submission.status}`);
2246
+ if (submission.failure?.message) {
2247
+ printWarn(`Failure: ${submission.failure.message}`);
2248
+ }
2249
+ printInfo(`Inspect logs: asp skill publish logs ${trackRef}`);
2250
+ }
2251
+ async function runPublishStatus2(getConfig2, skillRef, opts) {
2252
+ const base = getConfig2();
2253
+ const { config } = await requireCredentials(base, base.platformUrl);
2254
+ const client = new CliApiClient(config);
2255
+ if (looksLikeSubmissionId(skillRef)) {
2256
+ const state2 = opts.watch ? await pollUntilTerminal(client, skillRef, true) : await client.getArtifactSubmission(skillRef);
2257
+ if (base.json) {
2258
+ printJson(state2);
2259
+ return;
2260
+ }
2261
+ printInfo(`Submission: ${state2.submission_id}`);
2262
+ printInfo(`Status: ${state2.status}`);
2263
+ printInfo(`Terminal: ${state2.terminal ? "yes" : "no"}`);
2264
+ if (state2.scan?.status) printInfo(`Scan: ${state2.scan.status}`);
2265
+ if (state2.publish?.public_url) printInfo(`URL: ${state2.publish.public_url}`);
2266
+ if (state2.failure?.message) printWarn(`Failure: ${state2.failure.message}`);
2267
+ return;
2268
+ }
2269
+ const submissionId = await resolveSkillSubmissionId(client, skillRef, config.platformUrl);
2270
+ if (!submissionId) {
2271
+ await showPublishedSkillStatus(client, skillRef, base.json);
2272
+ return;
2273
+ }
2274
+ if (!base.json) {
2275
+ printInfo(`Resolved skill id "${skillRef}" -> submission ${submissionId}`);
2276
+ }
2277
+ const current = await client.getArtifactSubmission(submissionId);
2278
+ if (current.terminal && current.status === "published") {
2279
+ await showPublishedSkillStatus(client, skillRef, base.json);
2280
+ return;
2281
+ }
2282
+ const state = opts.watch ? await pollUntilTerminal(client, submissionId, true) : current;
2283
+ if (base.json) {
2284
+ printJson(state);
2285
+ return;
2286
+ }
2287
+ printInfo(`Submission: ${state.submission_id}`);
2288
+ printInfo(`Status: ${state.status}`);
2289
+ printInfo(`Terminal: ${state.terminal ? "yes" : "no"}`);
2290
+ if (state.scan?.status) printInfo(`Scan: ${state.scan.status}`);
2291
+ if (state.publish?.public_url) printInfo(`URL: ${state.publish.public_url}`);
2292
+ if (state.failure?.message) printWarn(`Failure: ${state.failure.message}`);
2293
+ }
2294
+ async function runPublishLogs2(getConfig2, skillRef) {
2295
+ const base = getConfig2();
2296
+ const { config } = await requireCredentials(base, base.platformUrl);
2297
+ const client = new CliApiClient(config);
2298
+ const submissionId = await resolveSkillSubmissionId(client, skillRef, config.platformUrl);
2299
+ if (!submissionId) {
2300
+ throw new AspError(`No publish logs found for skill id: ${skillRef}`, {
2301
+ code: "NOT_FOUND",
2302
+ hint: "Use submission_id from publish output, or republish on this machine to cache the mapping."
2303
+ });
2304
+ }
2305
+ if (submissionId !== skillRef && !base.json) {
2306
+ printInfo(`Resolved skill id "${skillRef}" -> submission ${submissionId}`);
2307
+ }
2308
+ const logs = await client.getArtifactSubmissionLogs(submissionId);
2309
+ if (base.json) {
2310
+ printJson(logs);
2311
+ return;
2312
+ }
2313
+ if (logs.items.length === 0) {
2314
+ printInfo("No logs yet.");
2315
+ return;
2316
+ }
2317
+ const rows = logs.items.map((item) => [item.timestamp, item.level, item.message]);
2318
+ printTable(["timestamp", "level", "message"], rows);
2319
+ }
2320
+ function registerSkillCommands(program2, getConfig2) {
2321
+ const skill = program2.command("skill").description("Skill marketplace and publish commands");
2322
+ skill.command("search [query]").description("Search skills").option("--category <code>", "Skill category code").option("--source <source>", "official | verified | community").option("--tag <tag>", "Tag filter").option("--sort <sort>", "recommended | downloads | updated_at").option("--page-size <n>", "Page size", (v) => Number.parseInt(v, 10)).option("--cursor <cursor>", "Cursor for next page").action(async (query, opts) => {
2323
+ await runSearch2(getConfig2, query, opts);
2324
+ });
2325
+ skill.command("info <skillId>").description("Show skill details").option("--version <version>", "Specify version").action(async (skillId, opts) => {
2326
+ await runInfo2(getConfig2, skillId, opts);
2327
+ });
2328
+ skill.command("versions <skillId>").description("List published versions").action(async (skillId) => {
2329
+ await runVersions2(getConfig2, skillId);
2330
+ });
2331
+ skill.command("install <skillId>").description("Install skill via qwenpaw").option("--version <version>", "Specify version").option("--via <tool>", "Install tool (qwenpaw)", "qwenpaw").action(async (skillId, opts) => {
2332
+ await runInstall2(getConfig2, skillId, opts);
2333
+ });
2334
+ const publish = skill.command("publish [zipPath]").description("Publish skill from local zip or URL").option("--url <url>", "Publish from remote zip URL").option("--artifact-id <id>", "Optional skill id override (must match SKILL.md in zip mode)").option("--version <version>", "Skill version (prompted if SKILL.md has no version)").option(
2335
+ "--category <code>",
2336
+ "Skill category code (engineering development|data research|document office|design creation|automation integration|product management|marketing growth|security compliance|education knowledge|plugin development|skills management|others)"
2337
+ ).option("--tags <csv>", "Optional tags, comma-separated, max 3").option("--repo-url <url>", "Repository URL").option("--publish-mode <mode>", "Publish mode", "auto_after_scan").option("--no-wait", "Do not wait for terminal status").action(async (zipPath, opts) => {
2338
+ await runPublish2(getConfig2, zipPath, opts);
2339
+ });
2340
+ publish.command("status <skillId>").description("Show publish status by skill id or submission id").option("--watch", "Poll until terminal status", false).action(async (skillId, opts) => {
2341
+ await runPublishStatus2(getConfig2, skillId, opts);
2342
+ });
2343
+ publish.command("logs <skillId>").description("Show publish logs by skill id or submission id").action(async (skillId) => {
2344
+ await runPublishLogs2(getConfig2, skillId);
2345
+ });
2346
+ }
2347
+
2348
+ // src/cli.ts
2349
+ var globalOpts = {};
2350
+ var program = new Command().name("asp").description("AgentScope Platform CLI").version(CLI_VERSION).option("--platform-url <url>", "Custom Platform base URL (advanced)").option("--token <token>", "Access token (overrides stored credentials)").option("-v, --verbose", "Verbose logging").option("--json", "Output JSON").hook("preAction", (thisCommand) => {
2351
+ const opts = thisCommand.opts();
2352
+ globalOpts.platformUrl = opts.platformUrl;
2353
+ globalOpts.token = opts.token;
2354
+ globalOpts.verbose = opts.verbose;
2355
+ globalOpts.json = opts.json;
2356
+ });
2357
+ var getConfig = () => resolveConfig(globalOpts);
2358
+ registerAuthCommands(program, getConfig);
2359
+ registerPluginCommands(program, getConfig);
2360
+ registerSkillCommands(program, getConfig);
2361
+ async function main() {
2362
+ try {
2363
+ await program.parseAsync(process.argv);
2364
+ } catch (err) {
2365
+ if (err instanceof AspError) {
2366
+ printError(`${err.message} [${err.code}]`);
2367
+ if (err.hint) printError(`Hint: ${err.hint}`);
2368
+ if (err.requestId) printError(`Request ID: ${err.requestId}`);
2369
+ process.exit(err.exitCode);
2370
+ }
2371
+ if (err instanceof Error) {
2372
+ printError(err.message);
2373
+ if (globalOpts.verbose && err.stack) console.error(err.stack);
2374
+ process.exit(1);
2375
+ }
2376
+ throw err;
2377
+ }
2378
+ }
2379
+ main();
2380
+ //# sourceMappingURL=cli.js.map