@lsddream/platform-cli 0.1.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,1255 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CLIENT_ID,
4
+ CLI_API_PREFIX,
5
+ CLI_VERSION,
6
+ DEFAULT_SCOPE,
7
+ EXCLUDED_PATTERNS,
8
+ PRE_PLATFORM_URL,
9
+ PROD_PLATFORM_URL,
10
+ QWENPAW_API_PREFIX,
11
+ SENSITIVE_REASONS,
12
+ SYNC_DEFAULT_FILES,
13
+ clearCredentials,
14
+ credentialsFromTokenResponse,
15
+ getQwenpawWorkspacesRoot,
16
+ isTokenExpired,
17
+ loadCredentials,
18
+ resolveConfig,
19
+ saveCredentials
20
+ } from "./chunk-2RAWITF3.js";
21
+
22
+ // src/cli.ts
23
+ import { Command } from "commander";
24
+
25
+ // src/utils/errors.ts
26
+ var AspError = class _AspError extends Error {
27
+ code;
28
+ hint;
29
+ requestId;
30
+ statusCode;
31
+ exitCode;
32
+ constructor(message, options = {}) {
33
+ super(message, { cause: options.cause });
34
+ this.name = "AspError";
35
+ this.code = options.code ?? "UNKNOWN";
36
+ this.hint = options.hint;
37
+ this.requestId = options.requestId;
38
+ this.statusCode = options.statusCode;
39
+ this.exitCode = options.exitCode ?? 1;
40
+ }
41
+ static fromResponse(status, body) {
42
+ const parsed = body;
43
+ if (parsed?.error) {
44
+ const exitCode = mapErrorCodeToExit(parsed.error.code, status);
45
+ let hint = parsed.error.hint ?? parsed.error.detail;
46
+ if (parsed.error.code === "ASP.COMM.NOT_FOUND") {
47
+ hint = hint ?? "The API endpoint is not available on this Platform environment. Pre-release may not have /api/qwenpaw/v1 yet; try production `asp qwenpaw remote list` after `asp auth login`.";
48
+ }
49
+ return new _AspError(parsed.error.message, {
50
+ code: parsed.error.code,
51
+ hint,
52
+ requestId: parsed.request_id,
53
+ statusCode: status,
54
+ exitCode
55
+ });
56
+ }
57
+ return new _AspError(`HTTP ${status}`, {
58
+ code: "HTTP_ERROR",
59
+ statusCode: status,
60
+ exitCode: status >= 500 ? 2 : 1
61
+ });
62
+ }
63
+ };
64
+ function mapErrorCodeToExit(code, status) {
65
+ if (code === "UNAUTHORIZED" || code === "UNAUTHENTICATED" || code === "ASP.AUTH.UNAUTHORIZED" || code === "ASP.AUTH.SESSION_INVALID" || code === "SESSION_INVALID") {
66
+ return 3;
67
+ }
68
+ if (code === "FORBIDDEN") return 4;
69
+ if (status >= 500) return 2;
70
+ return 1;
71
+ }
72
+ function maskSecrets(text) {
73
+ 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]"');
74
+ }
75
+
76
+ // src/auth/session.ts
77
+ function isSessionInvalidError(err) {
78
+ if (!(err instanceof AspError)) return false;
79
+ return err.code === "ASP.AUTH.SESSION_INVALID" || err.code === "SESSION_INVALID";
80
+ }
81
+ async function refreshStoredSession(config, refreshToken, platformUrl) {
82
+ const client = new CliApiClient({
83
+ ...config,
84
+ token: void 0,
85
+ platformUrl: platformUrl.replace(/\/$/, "")
86
+ });
87
+ const refreshed = await client.refresh(refreshToken);
88
+ const creds = credentialsFromTokenResponse(platformUrl, refreshed);
89
+ await saveCredentials(creds);
90
+ return creds;
91
+ }
92
+ async function finalizeLogin(config, loginResponse) {
93
+ if (!loginResponse.refresh_token) {
94
+ const creds = credentialsFromTokenResponse(config.platformUrl, loginResponse);
95
+ await saveCredentials(creds);
96
+ return creds;
97
+ }
98
+ try {
99
+ return await refreshStoredSession(
100
+ config,
101
+ loginResponse.refresh_token,
102
+ config.platformUrl
103
+ );
104
+ } catch {
105
+ const creds = credentialsFromTokenResponse(config.platformUrl, loginResponse);
106
+ await saveCredentials(creds);
107
+ return creds;
108
+ }
109
+ }
110
+
111
+ // src/api/client.ts
112
+ var HttpClient = class {
113
+ constructor(config) {
114
+ this.config = config;
115
+ }
116
+ config;
117
+ async request(opts) {
118
+ try {
119
+ return await this.doRequest(opts);
120
+ } catch (err) {
121
+ if (opts.auth !== false && !opts._retried && isSessionInvalidError(err) && !this.config.token) {
122
+ const creds = await loadCredentials();
123
+ if (creds?.refresh_token) {
124
+ await refreshStoredSession(configWithPlatform(this.config, creds), creds.refresh_token, creds.platform_url);
125
+ return this.doRequest({ ...opts, _retried: true });
126
+ }
127
+ }
128
+ throw err;
129
+ }
130
+ }
131
+ async doRequest(opts) {
132
+ const creds = opts.auth !== false && !this.config.token ? await loadCredentials() : null;
133
+ const platformUrl = creds?.platform_url ?? this.config.platformUrl;
134
+ const prefix = opts.prefix ?? "";
135
+ const url = `${platformUrl}${prefix}${opts.path}`;
136
+ const headers = {
137
+ Accept: "application/json",
138
+ ...opts.headers
139
+ };
140
+ if (opts.body !== void 0) {
141
+ headers["Content-Type"] = "application/json";
142
+ }
143
+ if (opts.auth !== false) {
144
+ const token = await this.resolveAccessToken(creds);
145
+ if (token) {
146
+ headers.Authorization = `Bearer ${token}`;
147
+ }
148
+ }
149
+ if (this.config.verbose) {
150
+ console.error(`[verbose] ${opts.method ?? "GET"} ${url}`);
151
+ if (opts.body) console.error(maskSecrets(JSON.stringify(opts.body)));
152
+ }
153
+ const res = await fetch(url, {
154
+ method: opts.method ?? "GET",
155
+ headers,
156
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
157
+ });
158
+ const text = await res.text();
159
+ let data = {};
160
+ if (text) {
161
+ try {
162
+ data = JSON.parse(text);
163
+ } catch {
164
+ data = { raw: text };
165
+ }
166
+ }
167
+ if (this.config.verbose) {
168
+ console.error(`[verbose] ${res.status}`, maskSecrets(text.slice(0, 2e3)));
169
+ }
170
+ if (!res.ok) {
171
+ throw AspError.fromResponse(res.status, data);
172
+ }
173
+ return data;
174
+ }
175
+ async resolveAccessToken(existing) {
176
+ if (this.config.token) return this.config.token;
177
+ const creds = existing ?? await loadCredentials();
178
+ if (!creds) return void 0;
179
+ if (isTokenExpired(creds) && creds.refresh_token) {
180
+ const refreshed = await refreshStoredSession(
181
+ configWithPlatform(this.config, creds),
182
+ creds.refresh_token,
183
+ creds.platform_url
184
+ );
185
+ return refreshed.access_token;
186
+ }
187
+ return creds.access_token;
188
+ }
189
+ };
190
+ function configWithPlatform(config, creds) {
191
+ return { ...config, platformUrl: creds.platform_url || config.platformUrl };
192
+ }
193
+
194
+ // src/api/cli-api.ts
195
+ var CliApiClient = class {
196
+ http;
197
+ constructor(config) {
198
+ this.http = new HttpClient(config);
199
+ }
200
+ req(opts) {
201
+ return this.http.request({ ...opts, prefix: CLI_API_PREFIX });
202
+ }
203
+ getMeta() {
204
+ return this.req({ path: "/meta", auth: false });
205
+ }
206
+ getMe() {
207
+ return this.req({ path: "/me" });
208
+ }
209
+ login(account, password) {
210
+ return this.req({
211
+ method: "POST",
212
+ path: "/auth/login",
213
+ auth: false,
214
+ body: { account, password }
215
+ });
216
+ }
217
+ refresh(refreshToken) {
218
+ return this.req({
219
+ method: "POST",
220
+ path: "/auth/refresh",
221
+ auth: false,
222
+ body: { refresh_token: refreshToken }
223
+ });
224
+ }
225
+ oauthToken(body) {
226
+ return this.req({ method: "POST", path: "/oauth/token", auth: false, body });
227
+ }
228
+ oauthDeviceCode(body) {
229
+ return this.req({ method: "POST", path: "/oauth/device/code", auth: false, body });
230
+ }
231
+ oauthDeviceToken(body) {
232
+ return this.req({ method: "POST", path: "/oauth/device/token", auth: false, body });
233
+ }
234
+ oauthRevoke(token, tokenTypeHint = "refresh_token") {
235
+ return this.req({
236
+ method: "POST",
237
+ path: "/oauth/revoke",
238
+ body: { token, token_type_hint: tokenTypeHint }
239
+ });
240
+ }
241
+ async logout() {
242
+ try {
243
+ return await this.req({ method: "POST", path: "/auth/logout" });
244
+ } catch {
245
+ return this.oauthRevoke("", "access_token");
246
+ }
247
+ }
248
+ };
249
+
250
+ // src/auth/browser-login.ts
251
+ import { createServer } from "http";
252
+ import open from "open";
253
+
254
+ // src/auth/pkce.ts
255
+ import { randomBytes, createHash } from "crypto";
256
+ function base64Url(buf) {
257
+ return buf.toString("base64url");
258
+ }
259
+ function generatePkce() {
260
+ const codeVerifier = base64Url(randomBytes(32));
261
+ const codeChallenge = base64Url(createHash("sha256").update(codeVerifier).digest());
262
+ const state = `state_${base64Url(randomBytes(16))}`;
263
+ const nonce = `nonce_${base64Url(randomBytes(16))}`;
264
+ return { codeVerifier, codeChallenge, state, nonce };
265
+ }
266
+
267
+ // src/utils/output.ts
268
+ import chalk from "chalk";
269
+ function printJson(data) {
270
+ console.log(JSON.stringify(data, null, 2));
271
+ }
272
+ function printSuccess(message) {
273
+ console.log(chalk.green("\u2713"), message);
274
+ }
275
+ function printInfo(message) {
276
+ console.log(chalk.blue("\u2139"), message);
277
+ }
278
+ function printError(message) {
279
+ console.error(chalk.red("\u2717"), message);
280
+ }
281
+ function printTable(headers, rows) {
282
+ const widths = headers.map(
283
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
284
+ );
285
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
286
+ console.log(chalk.bold(headerLine));
287
+ for (const row of rows) {
288
+ console.log(row.map((c, i) => (c ?? "").padEnd(widths[i])).join(" "));
289
+ }
290
+ }
291
+ function formatBytes(bytes) {
292
+ if (bytes < 1024) return `${bytes} B`;
293
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
294
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
295
+ }
296
+
297
+ // src/auth/browser-login.ts
298
+ var LOGIN_TIMEOUT_MS = 3e5;
299
+ async function browserPkceLogin(config) {
300
+ const pkce = generatePkce();
301
+ const port = await pickPort();
302
+ const redirectUri = `http://127.0.0.1:${port}/callback/${pkce.nonce}`;
303
+ const loginUrl = new URL(`${config.platformUrl}/cli/login`);
304
+ loginUrl.searchParams.set("client_id", CLIENT_ID);
305
+ loginUrl.searchParams.set("redirect_uri", redirectUri);
306
+ loginUrl.searchParams.set("response_type", "code");
307
+ loginUrl.searchParams.set("code_challenge", pkce.codeChallenge);
308
+ loginUrl.searchParams.set("code_challenge_method", "S256");
309
+ loginUrl.searchParams.set("state", pkce.state);
310
+ loginUrl.searchParams.set("scope", DEFAULT_SCOPE);
311
+ const code = await waitForCallback(port, pkce.nonce, pkce.state, loginUrl.toString());
312
+ const client = new CliApiClient(config);
313
+ const token = await client.oauthToken({
314
+ grant_type: "authorization_code",
315
+ client_id: CLIENT_ID,
316
+ code,
317
+ code_verifier: pkce.codeVerifier,
318
+ redirect_uri: redirectUri
319
+ });
320
+ await finalizeLogin(config, token);
321
+ printSuccess(`Logged in as ${token.user?.email ?? token.user?.display_name ?? "user"}`);
322
+ }
323
+ async function pickPort() {
324
+ return new Promise((resolve2, reject) => {
325
+ const server = createServer();
326
+ server.listen(0, "127.0.0.1", () => {
327
+ const addr = server.address();
328
+ if (!addr || typeof addr === "string") {
329
+ server.close();
330
+ reject(new Error("Failed to bind callback port"));
331
+ return;
332
+ }
333
+ const port = addr.port;
334
+ server.close(() => resolve2(port));
335
+ });
336
+ server.on("error", reject);
337
+ });
338
+ }
339
+ function waitForCallback(port, nonce, expectedState, loginUrl) {
340
+ return new Promise((resolve2, reject) => {
341
+ const server = createServer(async (req, res) => {
342
+ try {
343
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
344
+ if (url.pathname !== `/callback/${nonce}`) {
345
+ res.writeHead(404);
346
+ res.end("Not found");
347
+ return;
348
+ }
349
+ const code = url.searchParams.get("code");
350
+ const state = url.searchParams.get("state");
351
+ if (!code || state !== expectedState) {
352
+ res.writeHead(400);
353
+ res.end("Invalid callback");
354
+ reject(new Error("Invalid OAuth callback: state mismatch or missing code"));
355
+ server.close();
356
+ return;
357
+ }
358
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
359
+ res.end("<html><body><h2>Login successful. You can close this window.</h2></body></html>");
360
+ server.close();
361
+ resolve2(code);
362
+ } catch (err) {
363
+ server.close();
364
+ reject(err);
365
+ }
366
+ });
367
+ server.listen(port, "127.0.0.1", () => {
368
+ printInfo(`Opening browser for login...`);
369
+ void open(loginUrl).catch(() => {
370
+ printInfo(`Open this URL manually: ${loginUrl}`);
371
+ });
372
+ });
373
+ setTimeout(() => {
374
+ server.close();
375
+ reject(new Error("Login timed out"));
376
+ }, LOGIN_TIMEOUT_MS);
377
+ });
378
+ }
379
+
380
+ // src/auth/device.ts
381
+ import open2 from "open";
382
+ async function deviceCodeLogin(config) {
383
+ const client = new CliApiClient(config);
384
+ const device = await client.oauthDeviceCode({
385
+ client_id: CLIENT_ID,
386
+ scope: DEFAULT_SCOPE
387
+ });
388
+ printInfo(`Visit: ${device.verification_uri_complete ?? device.verification_uri}`);
389
+ printInfo(`Enter code: ${device.user_code}`);
390
+ await open2(device.verification_uri_complete ?? device.verification_uri).catch(() => {
391
+ });
392
+ const interval = (device.interval ?? 5) * 1e3;
393
+ const deadline = Date.now() + device.expires_in * 1e3;
394
+ while (Date.now() < deadline) {
395
+ await sleep(interval);
396
+ try {
397
+ const token = await client.oauthDeviceToken({
398
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
399
+ client_id: CLIENT_ID,
400
+ device_code: device.device_code
401
+ });
402
+ await finalizeLogin(config, token);
403
+ printSuccess(`Logged in as ${token.user?.email ?? token.user?.display_name ?? "user"}`);
404
+ return;
405
+ } catch (err) {
406
+ const code = err.code;
407
+ if (code === "AUTHORIZATION_PENDING" || code === "SLOW_DOWN") continue;
408
+ throw err;
409
+ }
410
+ }
411
+ throw new Error("Device code login timed out");
412
+ }
413
+ function sleep(ms) {
414
+ return new Promise((r) => setTimeout(r, ms));
415
+ }
416
+
417
+ // src/utils/prompt.ts
418
+ import { createInterface } from "readline/promises";
419
+ import { stdin as input, stdout as output } from "process";
420
+ async function promptLine(message) {
421
+ const rl = createInterface({ input, output });
422
+ try {
423
+ const value = (await rl.question(message)).trim();
424
+ if (!value) {
425
+ throw new Error("Input is required.");
426
+ }
427
+ return value;
428
+ } finally {
429
+ rl.close();
430
+ }
431
+ }
432
+ async function promptPassword(message = "Password: ") {
433
+ return new Promise((resolve2, reject) => {
434
+ const stdin = process.stdin;
435
+ const wasRaw = stdin.isRaw;
436
+ const wasPaused = stdin.isPaused();
437
+ if (!stdin.isTTY) {
438
+ reject(new Error("Password prompt requires an interactive terminal."));
439
+ return;
440
+ }
441
+ output.write(message);
442
+ stdin.setRawMode(true);
443
+ stdin.resume();
444
+ stdin.setEncoding("utf8");
445
+ let password = "";
446
+ const cleanup = () => {
447
+ stdin.removeListener("data", onData);
448
+ stdin.setRawMode(wasRaw ?? false);
449
+ if (wasPaused) stdin.pause();
450
+ else stdin.resume();
451
+ };
452
+ const onData = (char) => {
453
+ switch (char) {
454
+ case "\n":
455
+ case "\r":
456
+ case "":
457
+ cleanup();
458
+ output.write("\n");
459
+ if (!password) {
460
+ reject(new Error("Password is required."));
461
+ return;
462
+ }
463
+ resolve2(password);
464
+ break;
465
+ case "":
466
+ cleanup();
467
+ output.write("\n");
468
+ reject(new Error("Login cancelled."));
469
+ break;
470
+ case "\x7F":
471
+ case "\b":
472
+ if (password.length > 0) {
473
+ password = password.slice(0, -1);
474
+ output.write("\b \b");
475
+ }
476
+ break;
477
+ default:
478
+ if (char < " " && char !== " ") return;
479
+ password += char;
480
+ output.write("*");
481
+ break;
482
+ }
483
+ };
484
+ stdin.on("data", onData);
485
+ });
486
+ }
487
+ async function promptCredentials(options) {
488
+ const account = options.account ?? await promptLine("Email: ");
489
+ const password = options.password ?? await promptPassword();
490
+ return { account, password };
491
+ }
492
+
493
+ // src/utils/platform.ts
494
+ function detectPlatformEnv(url) {
495
+ const normalized = url.replace(/\/$/, "");
496
+ if (normalized === PROD_PLATFORM_URL) return "prod";
497
+ if (normalized === PRE_PLATFORM_URL) return "pre";
498
+ return "custom";
499
+ }
500
+ function formatPlatformLabel(url) {
501
+ const env = detectPlatformEnv(url);
502
+ if (env === "prod") return `prod (${PROD_PLATFORM_URL})`;
503
+ if (env === "pre") return `pre (${PRE_PLATFORM_URL})`;
504
+ return url;
505
+ }
506
+ function withPlatformUrl(config, platformUrl) {
507
+ return { ...config, platformUrl: platformUrl.replace(/\/$/, "") };
508
+ }
509
+ function resolveCommandPlatformUrl(config, options = {}) {
510
+ if (options.pre) return PRE_PLATFORM_URL;
511
+ if (options.prod) return PROD_PLATFORM_URL;
512
+ return config.platformUrl;
513
+ }
514
+ async function requireCredentials(config, targetPlatformUrl) {
515
+ if (config.token) {
516
+ return {
517
+ config: withPlatformUrl(config, targetPlatformUrl),
518
+ creds: {
519
+ access_token: config.token,
520
+ token_type: "Bearer",
521
+ platform_url: targetPlatformUrl
522
+ }
523
+ };
524
+ }
525
+ const creds = await loadCredentials();
526
+ if (!creds?.access_token) {
527
+ const hint = detectPlatformEnv(targetPlatformUrl) === "pre" ? "Run `asp auth login-pre`." : "Run `asp auth login`.";
528
+ throw new AspError("Not logged in.", {
529
+ code: "UNAUTHORIZED",
530
+ hint,
531
+ exitCode: 3
532
+ });
533
+ }
534
+ const credsUrl = creds.platform_url.replace(/\/$/, "");
535
+ const targetUrl = targetPlatformUrl.replace(/\/$/, "");
536
+ if (credsUrl !== targetUrl) {
537
+ const loggedIn = formatPlatformLabel(credsUrl);
538
+ const target = formatPlatformLabel(targetUrl);
539
+ const hint = detectPlatformEnv(targetUrl) === "pre" ? `You are logged in to ${loggedIn}. Run \`asp auth login-pre\` or use the matching -pre command.` : `You are logged in to ${loggedIn}. Run \`asp auth login\` or use production commands without -pre.`;
540
+ throw new AspError(`Credential environment mismatch: logged in to ${loggedIn}, command targets ${target}.`, {
541
+ code: "ENV_MISMATCH",
542
+ hint,
543
+ exitCode: 1
544
+ });
545
+ }
546
+ return {
547
+ config: withPlatformUrl(config, targetUrl),
548
+ creds
549
+ };
550
+ }
551
+ async function resolveAuthContext(getConfig2, options = {}) {
552
+ const base = getConfig2();
553
+ const platformUrl = resolveCommandPlatformUrl(base, options);
554
+ return requireCredentials(base, platformUrl);
555
+ }
556
+
557
+ // src/commands/auth.ts
558
+ var LOGIN_OPTIONS = [
559
+ ["--device", "Use device code flow (headless)"],
560
+ ["--browser", "Use browser PKCE login"],
561
+ ["--token <token>", "Save and validate an existing access token"],
562
+ ["--account <email>", "Account email (non-interactive)"],
563
+ ["--password <password>", "Account password (non-interactive)"]
564
+ ];
565
+ async function runLogin(config, opts) {
566
+ const client = new CliApiClient(config);
567
+ if (opts.token) {
568
+ await saveTokenLogin(config, opts.token);
569
+ return;
570
+ }
571
+ const meta = await client.getMeta().catch(() => null);
572
+ if (opts.device) {
573
+ if (meta?.features?.device_login === false) {
574
+ throw new AspError("Device login is not enabled on this platform.", {
575
+ code: "FEATURE_DISABLED",
576
+ hint: "Use email/password login instead."
577
+ });
578
+ }
579
+ await deviceCodeLogin(config);
580
+ return;
581
+ }
582
+ if (opts.browser) {
583
+ if (meta?.features?.browser_pkce_login === false) {
584
+ throw new AspError("Browser PKCE login is not enabled on this platform.", {
585
+ code: "FEATURE_DISABLED",
586
+ hint: "Use email/password login instead."
587
+ });
588
+ }
589
+ await browserPkceLogin(config);
590
+ return;
591
+ }
592
+ const { account, password } = await promptCredentials({
593
+ account: opts.account,
594
+ password: opts.password
595
+ });
596
+ const loginResponse = await client.login(account, password);
597
+ await finalizeLogin(config, loginResponse);
598
+ const me = await new CliApiClient(config).getMe();
599
+ const display = me.user?.email ?? me.email ?? me.user?.display_name ?? me.account ?? account;
600
+ printSuccess(`Logged in as ${display} (${formatPlatformLabel(config.platformUrl)})`);
601
+ }
602
+ async function saveTokenLogin(config, token) {
603
+ const creds = credentialsFromTokenResponse(config.platformUrl, {
604
+ access_token: token,
605
+ token_type: "Bearer"
606
+ });
607
+ await saveCredentials(creds);
608
+ const me = await new CliApiClient({ ...config, token }).getMe();
609
+ const display = me.user?.email ?? me.email ?? me.user?.display_name ?? me.account ?? me.user_id;
610
+ printSuccess(`Token saved. Logged in as ${display} (${formatPlatformLabel(config.platformUrl)})`);
611
+ }
612
+ function registerLoginCommand(auth, getConfig2, name, description, platformUrl) {
613
+ let cmd = auth.command(name).description(description);
614
+ for (const [flags, desc] of LOGIN_OPTIONS) {
615
+ cmd = cmd.option(flags, desc);
616
+ }
617
+ cmd.action(async (opts) => {
618
+ const config = withPlatformUrl(getConfig2(), platformUrl);
619
+ await runLogin(config, opts);
620
+ });
621
+ }
622
+ async function runStatus(getConfig2, env) {
623
+ const base = getConfig2();
624
+ const platformUrl = resolveCommandPlatformUrl(base, env);
625
+ const creds = await loadCredentials();
626
+ if (!base.token && !creds) {
627
+ printInfo("Not logged in.");
628
+ printInfo(" prod: `asp auth login`");
629
+ printInfo(" pre: `asp auth login-pre`");
630
+ return;
631
+ }
632
+ const { config: apiConfig } = await requireCredentials(base, platformUrl);
633
+ const client = new CliApiClient(apiConfig);
634
+ const me = await client.getMe();
635
+ const latestCreds = await loadCredentials() ?? creds;
636
+ if (base.json) {
637
+ printJson({
638
+ logged_in: true,
639
+ environment: formatPlatformLabel(platformUrl),
640
+ user: me.user ?? {
641
+ id: me.user_id,
642
+ email: me.email,
643
+ display_name: me.username ?? me.account
644
+ },
645
+ qwenpaw_instance: me.qwenpaw_instance,
646
+ scopes: me.scopes,
647
+ expires_at: latestCreds?.expires_at,
648
+ platform_url: platformUrl
649
+ });
650
+ return;
651
+ }
652
+ const user = me.user ?? {
653
+ id: me.user_id ?? "",
654
+ email: me.email,
655
+ display_name: me.username ?? me.account
656
+ };
657
+ printInfo(`Environment: ${formatPlatformLabel(platformUrl)}`);
658
+ printInfo(`User: ${user.display_name ?? user.email ?? user.id}`);
659
+ if (user.email) printInfo(`Email: ${user.email}`);
660
+ if (me.qwenpaw_instance) {
661
+ printInfo(`QwenPaw instance: ${me.qwenpaw_instance.status} (${me.qwenpaw_instance.id})`);
662
+ printInfo(`Cloud URL: ${me.qwenpaw_instance.cloud_url}`);
663
+ }
664
+ if (me.scopes?.length) printInfo(`Scopes: ${me.scopes.join(", ")}`);
665
+ if (latestCreds?.expires_at) printInfo(`Token expires: ${latestCreds.expires_at}`);
666
+ }
667
+ async function runRefresh(getConfig2, env) {
668
+ const base = getConfig2();
669
+ const platformUrl = resolveCommandPlatformUrl(base, env);
670
+ const { config, creds } = await requireCredentials(base, platformUrl);
671
+ if (!creds.refresh_token) {
672
+ throw new AspError("No refresh token available.", {
673
+ code: "UNAUTHORIZED",
674
+ hint: env.pre ? "Run `asp auth login-pre`." : "Run `asp auth login`.",
675
+ exitCode: 3
676
+ });
677
+ }
678
+ await refreshStoredSession(config, creds.refresh_token, platformUrl);
679
+ printSuccess(`Token refreshed (${formatPlatformLabel(platformUrl)}).`);
680
+ }
681
+ async function runLogout(getConfig2, env) {
682
+ const base = getConfig2();
683
+ const platformUrl = resolveCommandPlatformUrl(base, env);
684
+ const creds = await loadCredentials();
685
+ if (!creds) {
686
+ printInfo(`Not logged in to ${formatPlatformLabel(platformUrl)}.`);
687
+ return;
688
+ }
689
+ const credsUrl = creds.platform_url.replace(/\/$/, "");
690
+ const targetUrl = platformUrl.replace(/\/$/, "");
691
+ if (credsUrl !== targetUrl) {
692
+ throw new AspError(
693
+ `Not logged in to ${formatPlatformLabel(targetUrl)}.`,
694
+ {
695
+ code: "ENV_MISMATCH",
696
+ hint: env.pre ? `Current session is ${formatPlatformLabel(credsUrl)}. Use \`asp auth logout-pre\` after \`asp auth login-pre\`.` : `Current session is ${formatPlatformLabel(credsUrl)}. Use \`asp auth logout-pre\` for pre, or re-login with \`asp auth login\`.`,
697
+ exitCode: 1
698
+ }
699
+ );
700
+ }
701
+ if (creds.refresh_token) {
702
+ const client = new CliApiClient({ ...base, platformUrl: targetUrl });
703
+ await client.logout().catch(() => {
704
+ });
705
+ }
706
+ await clearCredentials();
707
+ printSuccess(`Logged out from ${formatPlatformLabel(platformUrl)}.`);
708
+ }
709
+ function registerEnvAuthCommand(auth, getConfig2, baseName, description, env, handler) {
710
+ auth.command(baseName).description(description).action(async () => {
711
+ await handler(getConfig2, env);
712
+ });
713
+ }
714
+ function registerAuthCommands(program2, getConfig2) {
715
+ const auth = program2.command("auth").description("Authentication commands");
716
+ registerLoginCommand(
717
+ auth,
718
+ getConfig2,
719
+ "login",
720
+ `Log in to production (${PROD_PLATFORM_URL})`,
721
+ PROD_PLATFORM_URL
722
+ );
723
+ registerLoginCommand(
724
+ auth,
725
+ getConfig2,
726
+ "login-pre",
727
+ `Log in to pre-release (${PRE_PLATFORM_URL})`,
728
+ PRE_PLATFORM_URL
729
+ );
730
+ registerEnvAuthCommand(
731
+ auth,
732
+ getConfig2,
733
+ "status",
734
+ `Show login status (production, ${PROD_PLATFORM_URL})`,
735
+ { prod: true },
736
+ runStatus
737
+ );
738
+ registerEnvAuthCommand(
739
+ auth,
740
+ getConfig2,
741
+ "status-pre",
742
+ `Show login status (pre-release, ${PRE_PLATFORM_URL})`,
743
+ { pre: true },
744
+ runStatus
745
+ );
746
+ registerEnvAuthCommand(
747
+ auth,
748
+ getConfig2,
749
+ "refresh",
750
+ `Refresh access token (production, ${PROD_PLATFORM_URL})`,
751
+ { prod: true },
752
+ runRefresh
753
+ );
754
+ registerEnvAuthCommand(
755
+ auth,
756
+ getConfig2,
757
+ "refresh-pre",
758
+ `Refresh access token (pre-release, ${PRE_PLATFORM_URL})`,
759
+ { pre: true },
760
+ runRefresh
761
+ );
762
+ registerEnvAuthCommand(
763
+ auth,
764
+ getConfig2,
765
+ "logout",
766
+ `Log out from production (${PROD_PLATFORM_URL})`,
767
+ { prod: true },
768
+ runLogout
769
+ );
770
+ registerEnvAuthCommand(
771
+ auth,
772
+ getConfig2,
773
+ "logout-pre",
774
+ `Log out from pre-release (${PRE_PLATFORM_URL})`,
775
+ { pre: true },
776
+ runLogout
777
+ );
778
+ }
779
+
780
+ // src/qwenpaw/workspace.ts
781
+ import { readFile, access, readdir, stat as stat2 } from "fs/promises";
782
+ import { join as join2 } from "path";
783
+
784
+ // src/utils/hash.ts
785
+ import { createHash as createHash2 } from "crypto";
786
+ import { createReadStream } from "fs";
787
+ import { stat } from "fs/promises";
788
+ import { hostname, userInfo } from "os";
789
+ import { basename, join, posix, relative, resolve } from "path";
790
+ async function sha256File(filePath) {
791
+ return new Promise((resolvePromise, reject) => {
792
+ const hash = createHash2("sha256");
793
+ const stream = createReadStream(filePath);
794
+ stream.on("data", (chunk) => hash.update(chunk));
795
+ stream.on("end", () => resolvePromise(hash.digest("hex")));
796
+ stream.on("error", reject);
797
+ });
798
+ }
799
+ function toPosixPath(p) {
800
+ return p.split("\\").join("/");
801
+ }
802
+ function guessContentType(filePath) {
803
+ const ext = basename(filePath).split(".").pop()?.toLowerCase();
804
+ const map = {
805
+ json: "application/json",
806
+ md: "text/markdown",
807
+ txt: "text/plain",
808
+ csv: "text/csv",
809
+ pdf: "application/pdf",
810
+ zip: "application/zip",
811
+ png: "image/png",
812
+ jpg: "image/jpeg",
813
+ jpeg: "image/jpeg"
814
+ };
815
+ return map[ext ?? ""] ?? "application/octet-stream";
816
+ }
817
+ async function fileStat(filePath) {
818
+ const s = await stat(filePath);
819
+ return {
820
+ bytes: s.size,
821
+ mtime: s.mtime.toISOString()
822
+ };
823
+ }
824
+ function remoteAgentId(localAgentId, custom) {
825
+ if (custom) return custom;
826
+ return `${localAgentId}_remote`;
827
+ }
828
+ function validateAgentId(agentId, allowRemoteSource = false) {
829
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(agentId)) {
830
+ throw new Error(`Invalid agent id: ${agentId}`);
831
+ }
832
+ if (!allowRemoteSource && agentId.endsWith("_remote")) {
833
+ throw new Error(
834
+ `Agent id '${agentId}' ends with '_remote'. Use --allow-remote-source to override.`
835
+ );
836
+ }
837
+ }
838
+ function getMachineLabel() {
839
+ try {
840
+ const user = userInfo().username;
841
+ return `${user} ${hostname()}`;
842
+ } catch {
843
+ return "unknown";
844
+ }
845
+ }
846
+
847
+ // src/qwenpaw/workspace.ts
848
+ function getAgentWorkspace(agentId) {
849
+ return join2(getQwenpawWorkspacesRoot(), agentId);
850
+ }
851
+ async function listLocalAgentIds() {
852
+ const root = getQwenpawWorkspacesRoot();
853
+ try {
854
+ const entries = await readdir(root, { withFileTypes: true });
855
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
856
+ } catch {
857
+ return [];
858
+ }
859
+ }
860
+ async function ensureAgentWorkspace(agentId) {
861
+ const ws = getAgentWorkspace(agentId);
862
+ const root = getQwenpawWorkspacesRoot();
863
+ try {
864
+ await access(ws);
865
+ return ws;
866
+ } catch {
867
+ const agents = await listLocalAgentIds();
868
+ if (agents.length === 0) {
869
+ throw new AspError(`Local QwenPaw workspace directory not found: ${root}`, {
870
+ code: "WORKSPACE_NOT_FOUND",
871
+ hint: "Install and create a local QwenPaw agent first, or set QWENPAW_WORKSPACES_DIR to your workspaces path."
872
+ });
873
+ }
874
+ throw new AspError(`Local agent '${agentId}' not found under ${root}`, {
875
+ code: "AGENT_NOT_FOUND",
876
+ hint: `Available agents: ${agents.join(", ")}. Run \`asp qwenpaw agents list\`.`
877
+ });
878
+ }
879
+ }
880
+ async function readAgentJson(agentId) {
881
+ const ws = await ensureAgentWorkspace(agentId);
882
+ const raw = await readFile(join2(ws, "agent.json"), "utf8");
883
+ return JSON.parse(raw);
884
+ }
885
+ function isExcluded(relPath) {
886
+ const posix2 = toPosixPath(relPath);
887
+ for (const pattern of EXCLUDED_PATTERNS) {
888
+ if (posix2 === pattern.replace(/\/$/, "") || posix2.startsWith(pattern)) {
889
+ return SENSITIVE_REASONS[pattern] ?? "excluded";
890
+ }
891
+ }
892
+ return null;
893
+ }
894
+ async function collectFilesRecursive(workspaceRoot, relDir, files, excluded) {
895
+ const absDir = join2(workspaceRoot, relDir);
896
+ let entries;
897
+ try {
898
+ entries = await readdir(absDir, { withFileTypes: true });
899
+ } catch {
900
+ return;
901
+ }
902
+ for (const entry of entries) {
903
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
904
+ const posix2 = toPosixPath(rel);
905
+ const reason = isExcluded(posix2);
906
+ if (reason) {
907
+ if (!excluded.some((e) => e.path === posix2 || posix2.startsWith(`${e.path}/`))) {
908
+ excluded.push({ path: posix2.endsWith("/") ? posix2 : `${posix2}/`, reason });
909
+ }
910
+ continue;
911
+ }
912
+ if (entry.isDirectory()) {
913
+ await collectFilesRecursive(workspaceRoot, rel, files, excluded);
914
+ } else if (entry.isFile()) {
915
+ const abs = join2(workspaceRoot, rel);
916
+ const sha256 = await sha256File(abs);
917
+ const st = await fileStat(abs);
918
+ files.push({
919
+ path: posix2,
920
+ sha256,
921
+ bytes: st.bytes,
922
+ role: inferRole(posix2),
923
+ content_type: guessContentType(posix2),
924
+ mtime: st.mtime
925
+ });
926
+ }
927
+ }
928
+ }
929
+ function inferRole(path) {
930
+ if (path === "agent.json") return "agent_config";
931
+ if (path.endsWith(".md") && ["AGENTS.md", "SOUL.md", "PROFILE.md", "MEMORY.md"].includes(path))
932
+ return "persona";
933
+ if (path === "skill.json" || path.startsWith("skills/")) return "skill";
934
+ if (path.startsWith("memory/")) return "memory";
935
+ if (path === "jobs.json") return "jobs";
936
+ return "user_file";
937
+ }
938
+ async function buildSyncManifest(agentId, options = {}) {
939
+ const ws = await ensureAgentWorkspace(agentId);
940
+ const files = [];
941
+ const excluded = [];
942
+ for (const f of SYNC_DEFAULT_FILES) {
943
+ const abs = join2(ws, f);
944
+ try {
945
+ await stat2(abs);
946
+ const reason = isExcluded(f);
947
+ if (reason) {
948
+ excluded.push({ path: f, reason });
949
+ continue;
950
+ }
951
+ const sha256 = await sha256File(abs);
952
+ const st = await fileStat(abs);
953
+ files.push({
954
+ path: f,
955
+ sha256,
956
+ bytes: st.bytes,
957
+ role: inferRole(f),
958
+ content_type: guessContentType(f),
959
+ mtime: st.mtime
960
+ });
961
+ } catch {
962
+ }
963
+ }
964
+ if (options.includeMemoryDir !== false) {
965
+ await collectFilesRecursive(ws, "skills", files, excluded);
966
+ await collectFilesRecursive(ws, "memory", files, excluded);
967
+ }
968
+ if (options.includeJobs) {
969
+ const jobs = "jobs.json";
970
+ try {
971
+ await stat2(join2(ws, jobs));
972
+ const sha256 = await sha256File(join2(ws, jobs));
973
+ const st = await fileStat(join2(ws, jobs));
974
+ files.push({
975
+ path: jobs,
976
+ sha256,
977
+ bytes: st.bytes,
978
+ role: "jobs",
979
+ content_type: "application/json",
980
+ mtime: st.mtime
981
+ });
982
+ } catch {
983
+ }
984
+ }
985
+ const seen = /* @__PURE__ */ new Set();
986
+ const deduped = files.filter((f) => {
987
+ if (seen.has(f.path)) return false;
988
+ seen.add(f.path);
989
+ return true;
990
+ });
991
+ return { files: deduped, excluded };
992
+ }
993
+
994
+ // src/commands/qwenpaw/agents.ts
995
+ function registerAgentsCommands(qwenpaw, getConfig2) {
996
+ const agents = qwenpaw.command("agents").description("Local QwenPaw agents");
997
+ agents.command("list").description("List local QwenPaw agent workspaces").action(async () => {
998
+ const config = getConfig2();
999
+ const root = getQwenpawWorkspacesRoot();
1000
+ const ids = await listLocalAgentIds();
1001
+ if (config.json) {
1002
+ printJson({ workspaces_root: root, agents: ids });
1003
+ return;
1004
+ }
1005
+ printInfo(`Workspaces root: ${root}`);
1006
+ if (ids.length === 0) {
1007
+ printInfo("No local agents found.");
1008
+ printInfo("Create an agent in QwenPaw first, or set QWENPAW_WORKSPACES_DIR.");
1009
+ return;
1010
+ }
1011
+ printTable(["AGENT ID"], ids.map((id) => [id]));
1012
+ });
1013
+ }
1014
+
1015
+ // src/api/qwenpaw-api.ts
1016
+ var QwenpawApiClient = class {
1017
+ http;
1018
+ constructor(config) {
1019
+ this.http = new HttpClient(config);
1020
+ }
1021
+ req(opts) {
1022
+ return this.http.request({ ...opts, prefix: QWENPAW_API_PREFIX });
1023
+ }
1024
+ listRemoteAgents(params = {}) {
1025
+ const qs = new URLSearchParams(params).toString();
1026
+ return this.req({ path: `/remote-agents${qs ? `?${qs}` : ""}` });
1027
+ }
1028
+ createRemoteAgent(body) {
1029
+ return this.req({
1030
+ method: "POST",
1031
+ path: "/remote-agents",
1032
+ body
1033
+ });
1034
+ }
1035
+ };
1036
+
1037
+ // src/commands/qwenpaw/remote.ts
1038
+ async function runRemoteList(getConfig2, options, opts) {
1039
+ const { config } = await resolveAuthContext(getConfig2, options);
1040
+ const api = new QwenpawApiClient(config);
1041
+ const params = { page_size: opts.pageSize };
1042
+ if (opts.query) params.q = opts.query;
1043
+ if (opts.status) params.status = opts.status;
1044
+ if (opts.cursor) params.cursor = opts.cursor;
1045
+ const res = await api.listRemoteAgents(params);
1046
+ if (config.json) {
1047
+ printJson({
1048
+ platform_url: config.platformUrl,
1049
+ environment: formatPlatformLabel(config.platformUrl),
1050
+ ...res
1051
+ });
1052
+ return;
1053
+ }
1054
+ printInfo(`Environment: ${formatPlatformLabel(config.platformUrl)}`);
1055
+ if (res.items.length === 0) {
1056
+ printInfo("No remote agents found.");
1057
+ return;
1058
+ }
1059
+ printTable(
1060
+ ["REMOTE ID", "LOCAL ID", "STATUS", "UPDATED"],
1061
+ res.items.map((a) => [
1062
+ a.remote_agent_id,
1063
+ a.local_agent_id ?? "-",
1064
+ a.status,
1065
+ a.latest_sync?.completed_at ?? a.created_at ?? "-"
1066
+ ])
1067
+ );
1068
+ if (res.next_cursor) {
1069
+ const cmd = options.pre ? "asp qwenpaw remote list-pre" : "asp qwenpaw remote list";
1070
+ printInfo(`More results available. Use: ${cmd} --cursor ${res.next_cursor}`);
1071
+ }
1072
+ }
1073
+ function registerListCommand(remote, getConfig2, name, description, env) {
1074
+ remote.command(name).description(description).option("-q, --query <q>", "Search query").option("--status <status>", "Filter by status: creating|ready|syncing|error|archived").option("--page-size <n>", "Page size", "50").option("--cursor <cursor>", "Pagination cursor from previous response").action(async (opts) => {
1075
+ await runRemoteList(getConfig2, env, opts);
1076
+ });
1077
+ }
1078
+ function registerRemoteCommands(qwenpaw, getConfig2) {
1079
+ const remote = qwenpaw.command("remote").description("Remote agent registry");
1080
+ registerListCommand(
1081
+ remote,
1082
+ getConfig2,
1083
+ "list",
1084
+ `List remote agents on production (${PROD_PLATFORM_URL})`,
1085
+ { prod: true }
1086
+ );
1087
+ registerListCommand(
1088
+ remote,
1089
+ getConfig2,
1090
+ "list-pre",
1091
+ `List remote agents on pre-release (${PRE_PLATFORM_URL})`,
1092
+ { pre: true }
1093
+ );
1094
+ }
1095
+
1096
+ // src/qwenpaw/sync.ts
1097
+ async function ensureRemoteAgent(config, localAgentId, customRemoteId) {
1098
+ const api = new QwenpawApiClient(config);
1099
+ const rid = remoteAgentId(localAgentId, customRemoteId);
1100
+ const existing = await api.listRemoteAgents({ q: rid });
1101
+ const found = existing.items.find((a) => a.remote_agent_id === rid);
1102
+ if (found) {
1103
+ return { remoteAgentId: rid, created: false };
1104
+ }
1105
+ const agent = await readAgentJson(localAgentId).catch(() => ({ name: void 0 }));
1106
+ const agentName = agent.name ?? localAgentId;
1107
+ await api.createRemoteAgent({
1108
+ local_agent_id: localAgentId,
1109
+ local_agent_name: agentName,
1110
+ remote_agent_id: rid,
1111
+ remote_agent_name: `${agentName}_remote`,
1112
+ source: {
1113
+ kind: "local_qwenpaw",
1114
+ qwenpaw_version: process.env.QWENPAW_VERSION ?? "unknown",
1115
+ machine_label: getMachineLabel()
1116
+ },
1117
+ create_if_missing: true,
1118
+ update_if_exists: false
1119
+ });
1120
+ return { remoteAgentId: rid, created: true };
1121
+ }
1122
+ async function runSyncAgent(config, localAgentId, options = {}) {
1123
+ const manifest = await buildSyncManifest(localAgentId, {
1124
+ includeJobs: options.includeJobs,
1125
+ includeMemoryDir: options.includeMemoryDir
1126
+ });
1127
+ if (options.dryRun) {
1128
+ return { manifest };
1129
+ }
1130
+ const { remoteAgentId: rid, created } = await ensureRemoteAgent(
1131
+ config,
1132
+ localAgentId,
1133
+ options.remoteId
1134
+ );
1135
+ return { manifest, remoteAgentId: rid, created };
1136
+ }
1137
+
1138
+ // src/commands/qwenpaw/sync.ts
1139
+ async function runSyncAgentCommand(getConfig2, agentId, env, opts) {
1140
+ validateAgentId(agentId, opts.allowRemoteSource);
1141
+ const syncOptions = {
1142
+ dryRun: opts.dryRun,
1143
+ includeJobs: opts.includeJobs,
1144
+ includeMemoryDir: opts.memoryDir !== false,
1145
+ remoteId: opts.remoteId
1146
+ };
1147
+ if (opts.dryRun) {
1148
+ const result2 = await runSyncAgent(getConfig2(), agentId, syncOptions);
1149
+ printSyncResult(getConfig2(), agentId, opts, result2, null);
1150
+ return;
1151
+ }
1152
+ const { config } = await resolveAuthContext(getConfig2, env);
1153
+ const result = await runSyncAgent(config, agentId, syncOptions);
1154
+ printSyncResult(config, agentId, opts, result, config.platformUrl);
1155
+ }
1156
+ function printSyncResult(config, agentId, opts, result, platformUrl) {
1157
+ if (config.json) {
1158
+ printJson({
1159
+ agent_id: agentId,
1160
+ platform_url: platformUrl,
1161
+ environment: platformUrl ? formatPlatformLabel(platformUrl) : "local",
1162
+ dry_run: !!opts.dryRun,
1163
+ ...result
1164
+ });
1165
+ return;
1166
+ }
1167
+ if (opts.dryRun) {
1168
+ printInfo(`Sync preview for agent: ${agentId} (local only, no upload)`);
1169
+ if (result.manifest.files.length > 0) {
1170
+ printInfo("\nWill upload:");
1171
+ for (const f of result.manifest.files) {
1172
+ console.log(` ${f.path} (${formatBytes(f.bytes)})`);
1173
+ }
1174
+ } else {
1175
+ printInfo("\nNo files to upload.");
1176
+ }
1177
+ if (result.manifest.excluded.length > 0) {
1178
+ printInfo("\nExcluded:");
1179
+ for (const e of result.manifest.excluded) {
1180
+ console.log(` ${e.path.padEnd(20)} ${e.reason}`);
1181
+ }
1182
+ }
1183
+ return;
1184
+ }
1185
+ printInfo(`Environment: ${formatPlatformLabel(platformUrl)}`);
1186
+ const rid = result.remoteAgentId;
1187
+ if (result.created) {
1188
+ printSuccess(`Created remote agent: ${rid}`);
1189
+ } else {
1190
+ printSuccess(`Remote agent already exists: ${rid}`);
1191
+ }
1192
+ }
1193
+ function registerSyncAgentCommand(sync, getConfig2, name, description, env) {
1194
+ sync.command(name).description(description).argument("<agent_id>", "Local QwenPaw agent id").option("--dry-run", "Preview files to sync without uploading").option("--remote-id <id>", "Custom remote agent id").option("--include-jobs", "Include jobs.json in sync manifest").option("--no-memory-dir", "Exclude memory/ directory from manifest").option("--allow-remote-source", "Allow agent ids ending with _remote").action(async (agentId, opts) => {
1195
+ await runSyncAgentCommand(getConfig2, agentId, env, opts);
1196
+ });
1197
+ }
1198
+ function registerSyncCommands(qwenpaw, getConfig2) {
1199
+ const sync = qwenpaw.command("sync").description("Sync local agent to cloud");
1200
+ registerSyncAgentCommand(
1201
+ sync,
1202
+ getConfig2,
1203
+ "agent",
1204
+ `Sync agent to production remote (${PROD_PLATFORM_URL})`,
1205
+ { prod: true }
1206
+ );
1207
+ registerSyncAgentCommand(
1208
+ sync,
1209
+ getConfig2,
1210
+ "agent-pre",
1211
+ `Sync agent to pre-release remote (${PRE_PLATFORM_URL})`,
1212
+ { pre: true }
1213
+ );
1214
+ }
1215
+
1216
+ // src/commands/qwenpaw/index.ts
1217
+ function registerQwenpawCommands(program2, getConfig2) {
1218
+ const qwenpaw = program2.command("qwenpaw").description("QwenPaw cloud sync commands");
1219
+ registerAgentsCommands(qwenpaw, getConfig2);
1220
+ registerRemoteCommands(qwenpaw, getConfig2);
1221
+ registerSyncCommands(qwenpaw, getConfig2);
1222
+ }
1223
+
1224
+ // src/cli.ts
1225
+ var globalOpts = {};
1226
+ 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) => {
1227
+ const opts = thisCommand.opts();
1228
+ globalOpts.platformUrl = opts.platformUrl;
1229
+ globalOpts.token = opts.token;
1230
+ globalOpts.verbose = opts.verbose;
1231
+ globalOpts.json = opts.json;
1232
+ });
1233
+ var getConfig = () => resolveConfig(globalOpts);
1234
+ registerAuthCommands(program, getConfig);
1235
+ registerQwenpawCommands(program, getConfig);
1236
+ async function main() {
1237
+ try {
1238
+ await program.parseAsync(process.argv);
1239
+ } catch (err) {
1240
+ if (err instanceof AspError) {
1241
+ printError(`${err.message} [${err.code}]`);
1242
+ if (err.hint) printError(`Hint: ${err.hint}`);
1243
+ if (err.requestId) printError(`Request ID: ${err.requestId}`);
1244
+ process.exit(err.exitCode);
1245
+ }
1246
+ if (err instanceof Error) {
1247
+ printError(err.message);
1248
+ if (globalOpts.verbose && err.stack) console.error(err.stack);
1249
+ process.exit(1);
1250
+ }
1251
+ throw err;
1252
+ }
1253
+ }
1254
+ main();
1255
+ //# sourceMappingURL=cli.js.map