@doubao-dev/plugin-cli 0.0.0-stage → 0.0.40-canary-dea0ddc73-20261026073950

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.

Potentially problematic release.


This version of @doubao-dev/plugin-cli might be problematic. Click here for more details.

package/dist/188.js ADDED
@@ -0,0 +1,1691 @@
1
+ import { Command, CommanderError, Option } from "commander";
2
+ import strict from "node:assert/strict";
3
+ import node_crypto, { randomUUID } from "node:crypto";
4
+ import { z } from "zod";
5
+ import node_os, { homedir } from "node:os";
6
+ import node_path, { dirname, join, resolve as external_node_path_resolve } from "node:path";
7
+ import { execFile, spawn } from "node:child_process";
8
+ import node_fs, { existsSync, promises } from "node:fs";
9
+ import { promisify, stripVTControlCharacters } from "node:util";
10
+ import { setTimeout as promises_setTimeout } from "node:timers/promises";
11
+ import { ofetch } from "ofetch";
12
+ import { once } from "node:events";
13
+ import { createRequire } from "node:module";
14
+ import { pathToFileURL } from "node:url";
15
+ import ora, { oraPromise } from "ora";
16
+ import { log } from "@clack/prompts";
17
+ import { readJsonSync } from "fs-extra/esm";
18
+ import * as __rspack_external_node_fs_1b05aee1 from "node:fs";
19
+ function errorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
22
+ async function applyPlugins(program, plugins) {
23
+ const names = new Set();
24
+ for (const { name } of plugins){
25
+ strict(name.trim() && !names.has(name), `插件名称为空或重复:${name}`);
26
+ names.add(name);
27
+ }
28
+ const hooks = {
29
+ addCommand: [],
30
+ dev: [],
31
+ modifyPackagePlan: [],
32
+ afterUpload: []
33
+ };
34
+ for (const plugin of plugins)try {
35
+ const pluginName = plugin.name;
36
+ await plugin.setup({
37
+ onAddCommand: (callback)=>{
38
+ hooks.addCommand.push({
39
+ pluginName,
40
+ callback
41
+ });
42
+ },
43
+ onDev: (callback)=>{
44
+ hooks.dev.push({
45
+ pluginName,
46
+ callback
47
+ });
48
+ },
49
+ onModifyPackagePlan: (callback)=>{
50
+ hooks.modifyPackagePlan.push({
51
+ pluginName,
52
+ callback
53
+ });
54
+ },
55
+ onAfterUpload: (callback)=>{
56
+ hooks.afterUpload.push({
57
+ pluginName,
58
+ callback
59
+ });
60
+ }
61
+ });
62
+ } catch (cause) {
63
+ throw new Error(`[${plugin.name}] 注册失败:${errorMessage(cause)}`, {
64
+ cause
65
+ });
66
+ }
67
+ for (const hook of hooks.addCommand)await callHook(hook, {
68
+ program,
69
+ hooks
70
+ });
71
+ }
72
+ async function callHook({ pluginName, callback }, ...args) {
73
+ try {
74
+ return await callback(...args);
75
+ } catch (cause) {
76
+ throw new Error(`[${pluginName}] ${errorMessage(cause)}`, {
77
+ cause
78
+ });
79
+ }
80
+ }
81
+ var package_namespaceObject = /*#__PURE__*/ JSON.parse('{"UU":"@doubao-dev/plugin-cli","rE":"0.0.40-canary-dea0ddc73-20261026073950","h_":"Doubao Plugins CLI"}');
82
+ const runtime = {
83
+ name: package_namespaceObject.UU,
84
+ version: package_namespaceObject.rE,
85
+ description: package_namespaceObject.h_,
86
+ registry: 'https://registry.npmjs.org',
87
+ appDev: {
88
+ name: '@doubao-dev/plugin-app-dev',
89
+ version: package_namespaceObject.rE,
90
+ entry: '.'
91
+ },
92
+ kit: {
93
+ name: '@doubao-dev/kit',
94
+ version: process.env.DBX_KIT_VERSION?.trim() || "0.8.0-rc.2",
95
+ entry: './sdk'
96
+ },
97
+ auth: {
98
+ baseUrl: 'https://open.doubao.com',
99
+ verificationUrl: 'https://open.doubao.com/apps-plugin/oauth/device'
100
+ },
101
+ apiUrl: 'https://open.doubao.com',
102
+ deviceApiUrl: 'https://developer.doubao.com',
103
+ consoleUrl: 'https://open.doubao.com/console/plugins/create'
104
+ };
105
+ const DEFAULT_BASE_URL = 'https://developer.doubao.com';
106
+ const DEFAULT_CLIENT_NAME = 'dbx-cli';
107
+ const DEFAULT_TOKEN_PATH = node_path.join(node_os.homedir(), '.dbx', 'token');
108
+ const LEGACY_TOKEN_PATH = node_path.join(node_os.homedir(), '.doubao', 'token');
109
+ const configured = {};
110
+ function config_resolveOptions(options = {}) {
111
+ const merged = {
112
+ ...configured,
113
+ ...options
114
+ };
115
+ const baseUrl = normalizeHttpsBaseUrl(merged.baseUrl ?? DEFAULT_BASE_URL, 'baseUrl');
116
+ const tokenPath = merged.tokenPath ?? DEFAULT_TOKEN_PATH;
117
+ return {
118
+ ...merged,
119
+ baseUrl,
120
+ verificationUrl: normalizeHttpsBaseUrl(merged.verificationUrl ?? new URL('/oauth/device', baseUrl).href, 'verificationUrl'),
121
+ allowedOrigins: normalizeAllowedOrigins(merged.allowedOrigins, baseUrl),
122
+ tokenPath,
123
+ legacyTokenPath: merged.tokenPath ? void 0 : LEGACY_TOKEN_PATH,
124
+ requestHeaders: new Headers(merged.requestHeaders)
125
+ };
126
+ }
127
+ function trimTrailingSlash(value) {
128
+ return value.replace(/\/+$/, '');
129
+ }
130
+ function normalizeHttpsBaseUrl(value, label) {
131
+ let parsed;
132
+ try {
133
+ parsed = new URL(trimTrailingSlash(value));
134
+ } catch {
135
+ throw new Error(`${label} 必须是完整的 HTTPS URL`);
136
+ }
137
+ if ('https:' !== parsed.protocol) throw new Error(`${label} 必须使用 HTTPS`);
138
+ return trimTrailingSlash(parsed.toString());
139
+ }
140
+ function normalizeAllowedOrigins(values, baseUrl) {
141
+ const origins = new Set([
142
+ new URL(baseUrl).origin
143
+ ]);
144
+ for (const value of values ?? []){
145
+ let parsed;
146
+ try {
147
+ parsed = new URL(value);
148
+ } catch {
149
+ throw new Error(`allowedOrigins 中的来源必须是完整的 HTTPS 地址:${value}`);
150
+ }
151
+ if ('https:' !== parsed.protocol) throw new Error(`allowedOrigins 中的来源必须使用 HTTPS:${value}`);
152
+ origins.add(parsed.origin);
153
+ }
154
+ return [
155
+ ...origins
156
+ ];
157
+ }
158
+ const execFileAsync = promisify(execFile);
159
+ const SERVICE_NAME = 'byted-doubao-open-dbx-cli';
160
+ const LOCAL_STORE_DIR_NAME = 'dbx-cli';
161
+ const MASTER_KEY_FILE = 'master.key';
162
+ const CREDENTIALS_DIR = 'credentials';
163
+ const AUTHORITIES_DIR = 'authorities';
164
+ const LOCAL_STORE_VERSION = 1;
165
+ const LOCAL_STORE_ALGORITHM = 'aes-256-gcm';
166
+ class CredentialStoreError extends Error {
167
+ constructor(message){
168
+ super(message);
169
+ this.name = 'CredentialStoreError';
170
+ }
171
+ }
172
+ class MissingMasterKeyError extends CredentialStoreError {
173
+ constructor(){
174
+ super('无法读取本地登录凭证:用于解密凭证的密钥文件 master.key 已丢失。请运行 dbx auth login 重新登录。');
175
+ }
176
+ }
177
+ function defaultCredentialStore() {
178
+ const encryptedFileStore = new EncryptedFileCredentialStore();
179
+ if ('darwin' === process.platform) return new CredentialStoreWithFallback(new MacOSKeychainStore(), encryptedFileStore);
180
+ if ('linux' === process.platform) return new CredentialStoreWithFallback(new SecretToolStore(), encryptedFileStore);
181
+ return encryptedFileStore;
182
+ }
183
+ class CredentialStoreWithFallback {
184
+ constructor(primary, fallback){
185
+ this.primary = primary;
186
+ this.fallback = fallback;
187
+ }
188
+ async get(account) {
189
+ if (await this.isFallbackAuthoritative(account)) return this.fallback.get(account);
190
+ let primaryError;
191
+ try {
192
+ const value = await this.primary.get(account);
193
+ if (value) return value;
194
+ } catch (error) {
195
+ primaryError = error;
196
+ }
197
+ try {
198
+ const value = await this.fallback.get(account);
199
+ if (value) return value;
200
+ } catch (fallbackError) {
201
+ if (primaryError) throw new CredentialStoreError(`从系统凭证存储读取登录凭证失败(${credential_store_errorMessage(primaryError)});从备用加密文件读取也失败:${credential_store_errorMessage(fallbackError)}`);
202
+ throw fallbackError;
203
+ }
204
+ if (primaryError) throw primaryError instanceof Error ? primaryError : new CredentialStoreError(`从系统凭证存储读取登录凭证失败:${String(primaryError)}`);
205
+ }
206
+ async set(account, value) {
207
+ let primaryError;
208
+ try {
209
+ await this.primary.set(account, value);
210
+ } catch (error) {
211
+ primaryError = error;
212
+ }
213
+ try {
214
+ await this.fallback.set(account, value);
215
+ } catch (fallbackError) {
216
+ if (primaryError) throw new CredentialStoreError(`向系统凭证存储写入登录凭证失败(${credential_store_errorMessage(primaryError)}),写入备用加密文件也失败(${credential_store_errorMessage(fallbackError)})`);
217
+ await this.removeStaleFallbackAfterPrimaryWrite(account, fallbackError);
218
+ throw fallbackError;
219
+ }
220
+ if (primaryError) await this.markFallbackAuthoritative(account);
221
+ else await this.clearFallbackAuthority(account);
222
+ }
223
+ async delete(account) {
224
+ let firstError;
225
+ try {
226
+ await this.primary.delete(account);
227
+ } catch (error) {
228
+ firstError = error;
229
+ }
230
+ try {
231
+ await this.fallback.delete(account);
232
+ } catch (error) {
233
+ if (firstError) throw new CredentialStoreError(`从系统凭证存储删除登录凭证失败(${credential_store_errorMessage(firstError)}),从备用加密文件删除也失败(${credential_store_errorMessage(error)})`);
234
+ throw error;
235
+ }
236
+ if (firstError) return void await this.markFallbackAuthoritativeOrThrow(account, firstError);
237
+ await this.clearFallbackAuthority(account);
238
+ }
239
+ async isFallbackAuthoritative(account) {
240
+ return supportsCredentialAuthority(this.fallback) ? this.fallback.isFallbackAuthoritative(account) : false;
241
+ }
242
+ async markFallbackAuthoritative(account) {
243
+ if (supportsCredentialAuthority(this.fallback)) await this.fallback.markFallbackAuthoritative(account);
244
+ }
245
+ async markFallbackAuthoritativeOrThrow(account, primaryError) {
246
+ if (!supportsCredentialAuthority(this.fallback)) throw primaryError instanceof Error ? primaryError : new CredentialStoreError(`系统凭证删除失败,且备用存储无法记录删除状态:${String(primaryError)}`);
247
+ try {
248
+ await this.fallback.markFallbackAuthoritative(account);
249
+ } catch (error) {
250
+ throw new CredentialStoreError(`系统凭证删除失败(${credential_store_errorMessage(primaryError)}),且无法记录备用凭证删除状态:${credential_store_errorMessage(error)}`);
251
+ }
252
+ }
253
+ async clearFallbackAuthority(account) {
254
+ if (supportsCredentialAuthority(this.fallback)) await this.fallback.clearFallbackAuthority(account);
255
+ }
256
+ async removeStaleFallbackAfterPrimaryWrite(account, fallbackError) {
257
+ const cleanupErrors = [];
258
+ try {
259
+ await this.fallback.delete(account);
260
+ } catch (error) {
261
+ cleanupErrors.push(error);
262
+ }
263
+ try {
264
+ await this.clearFallbackAuthority(account);
265
+ } catch (error) {
266
+ cleanupErrors.push(error);
267
+ }
268
+ if (cleanupErrors.length > 0) throw new CredentialStoreError(`系统凭证已写入,但备用加密文件写入失败(${credential_store_errorMessage(fallbackError)}),且清理旧备用凭证失败:${cleanupErrors.map(credential_store_errorMessage).join(';')}`);
269
+ }
270
+ }
271
+ class EncryptedFileCredentialStore {
272
+ constructor(options = {}){
273
+ this.baseDir = options.baseDir ?? defaultEncryptedCredentialDir();
274
+ }
275
+ async get(account) {
276
+ const filePath = this.credentialFilePath(account);
277
+ let raw;
278
+ try {
279
+ raw = await promises.readFile(filePath, 'utf8');
280
+ } catch (error) {
281
+ if (isNodeError(error) && 'ENOENT' === error.code) return;
282
+ throw new CredentialStoreError(`读取加密凭证文件失败:${credential_store_errorMessage(error)}`);
283
+ }
284
+ let record;
285
+ try {
286
+ record = JSON.parse(raw);
287
+ } catch (error) {
288
+ throw new CredentialStoreError(`加密凭证文件不是有效的 JSON:${credential_store_errorMessage(error)}`);
289
+ }
290
+ if (!isEncryptedCredentialRecord(record)) throw new CredentialStoreError('无法读取本地登录凭证:当前 CLI 不支持凭证文件中的格式或字段。');
291
+ const key = await this.readMasterKey();
292
+ try {
293
+ const decipher = node_crypto.createDecipheriv(LOCAL_STORE_ALGORITHM, key, Buffer.from(record.iv, 'base64url'));
294
+ decipher.setAAD(Buffer.from(associatedData(account), 'utf8'));
295
+ decipher.setAuthTag(Buffer.from(record.authTag, 'base64url'));
296
+ const plaintext = Buffer.concat([
297
+ decipher.update(Buffer.from(record.ciphertext, 'base64url')),
298
+ decipher.final()
299
+ ]);
300
+ return plaintext.toString('utf8');
301
+ } catch (error) {
302
+ throw new CredentialStoreError(`解密凭证文件失败:${credential_store_errorMessage(error)}`);
303
+ }
304
+ }
305
+ async set(account, value) {
306
+ const key = await this.readOrCreateMasterKey();
307
+ const iv = node_crypto.randomBytes(12);
308
+ const cipher = node_crypto.createCipheriv(LOCAL_STORE_ALGORITHM, key, iv);
309
+ cipher.setAAD(Buffer.from(associatedData(account), 'utf8'));
310
+ const ciphertext = Buffer.concat([
311
+ cipher.update(value, 'utf8'),
312
+ cipher.final()
313
+ ]);
314
+ const record = {
315
+ version: LOCAL_STORE_VERSION,
316
+ algorithm: LOCAL_STORE_ALGORITHM,
317
+ iv: iv.toString('base64url'),
318
+ authTag: cipher.getAuthTag().toString('base64url'),
319
+ ciphertext: ciphertext.toString('base64url')
320
+ };
321
+ await writePrivateFileAtomic(this.credentialFilePath(account), `${JSON.stringify(record, null, 2)}\n`);
322
+ }
323
+ async delete(account) {
324
+ try {
325
+ await promises.unlink(this.credentialFilePath(account));
326
+ } catch (error) {
327
+ if (!isNodeError(error) || 'ENOENT' !== error.code) throw new CredentialStoreError(`删除加密凭证文件失败:${credential_store_errorMessage(error)}`);
328
+ }
329
+ }
330
+ async isFallbackAuthoritative(account) {
331
+ try {
332
+ const raw = await promises.readFile(this.authorityFilePath(account), 'utf8');
333
+ const parsed = JSON.parse(raw);
334
+ return 1 === parsed.version && 'fallback' === parsed.authority;
335
+ } catch (error) {
336
+ if (isNodeError(error) && 'ENOENT' === error.code) return false;
337
+ throw new CredentialStoreError(`读取登录凭证权威副本标记失败:${credential_store_errorMessage(error)}`);
338
+ }
339
+ }
340
+ async markFallbackAuthoritative(account) {
341
+ const record = {
342
+ version: 1,
343
+ authority: 'fallback'
344
+ };
345
+ await writePrivateFileAtomic(this.authorityFilePath(account), `${JSON.stringify(record, null, 2)}\n`);
346
+ }
347
+ async clearFallbackAuthority(account) {
348
+ try {
349
+ await promises.unlink(this.authorityFilePath(account));
350
+ } catch (error) {
351
+ if (!isNodeError(error) || 'ENOENT' !== error.code) throw new CredentialStoreError(`清理登录凭证权威副本标记失败:${credential_store_errorMessage(error)}`);
352
+ }
353
+ }
354
+ masterKeyPath() {
355
+ return node_path.join(this.baseDir, MASTER_KEY_FILE);
356
+ }
357
+ credentialFilePath(account) {
358
+ const digest = node_crypto.createHash('sha256').update(account).digest('hex');
359
+ return node_path.join(this.baseDir, CREDENTIALS_DIR, `${digest}.json`);
360
+ }
361
+ authorityFilePath(account) {
362
+ const digest = node_crypto.createHash('sha256').update(account).digest('hex');
363
+ return node_path.join(this.baseDir, AUTHORITIES_DIR, `${digest}.json`);
364
+ }
365
+ async readMasterKey() {
366
+ try {
367
+ return parseMasterKey(await promises.readFile(this.masterKeyPath(), 'utf8'));
368
+ } catch (error) {
369
+ if (isNodeError(error) && 'ENOENT' === error.code) throw new MissingMasterKeyError();
370
+ if (error instanceof CredentialStoreError) throw error;
371
+ throw new CredentialStoreError(`无法读取用于解密本地登录凭证的密钥文件 master.key:${credential_store_errorMessage(error)}`);
372
+ }
373
+ }
374
+ async readOrCreateMasterKey() {
375
+ try {
376
+ return await this.readMasterKey();
377
+ } catch (error) {
378
+ if (!(error instanceof MissingMasterKeyError)) throw error;
379
+ }
380
+ const key = node_crypto.randomBytes(32);
381
+ try {
382
+ const created = await writePrivateFileCreateOnce(this.masterKeyPath(), `${key.toString('base64url')}\n`);
383
+ return created ? key : this.readMasterKey();
384
+ } catch (error) {
385
+ throw new CredentialStoreError(`无法保存用于加密本地登录凭证的密钥文件 master.key:${credential_store_errorMessage(error)}`);
386
+ }
387
+ }
388
+ }
389
+ class MacOSKeychainStore {
390
+ async get(account) {
391
+ try {
392
+ const { stdout } = await execFileAsync('security', [
393
+ 'find-generic-password',
394
+ '-a',
395
+ account,
396
+ '-s',
397
+ SERVICE_NAME,
398
+ '-w'
399
+ ]);
400
+ return String(stdout).replace(/\n$/, '');
401
+ } catch (error) {
402
+ if (isMacOSKeychainNotFound(error)) return;
403
+ throw new CredentialStoreError(`从 macOS 钥匙串读取登录凭证失败:${credential_store_errorMessage(error)}`);
404
+ }
405
+ }
406
+ async set(account, value) {
407
+ try {
408
+ await execFileAsync('security', [
409
+ 'add-generic-password',
410
+ '-a',
411
+ account,
412
+ '-s',
413
+ SERVICE_NAME,
414
+ '-w',
415
+ value,
416
+ '-U'
417
+ ]);
418
+ } catch (error) {
419
+ throw new CredentialStoreError(`向 macOS 钥匙串写入登录凭证失败:${credential_store_errorMessage(error)}`);
420
+ }
421
+ }
422
+ async delete(account) {
423
+ try {
424
+ await execFileAsync('security', [
425
+ 'delete-generic-password',
426
+ '-a',
427
+ account,
428
+ '-s',
429
+ SERVICE_NAME
430
+ ]);
431
+ } catch (error) {
432
+ if (isMacOSKeychainNotFound(error)) return;
433
+ throw new CredentialStoreError(`从 macOS 钥匙串删除登录凭证失败:${credential_store_errorMessage(error)}`);
434
+ }
435
+ }
436
+ }
437
+ class SecretToolStore {
438
+ async get(account) {
439
+ try {
440
+ const { stdout } = await execFileAsync('secret-tool', [
441
+ 'lookup',
442
+ 'service',
443
+ SERVICE_NAME,
444
+ 'account',
445
+ account
446
+ ]);
447
+ return String(stdout).replace(/\n$/, '');
448
+ } catch (error) {
449
+ if (isSecretToolNotFound(error)) return;
450
+ throw new CredentialStoreError(`从 Secret Service 读取登录凭证失败:${credential_store_errorMessage(error)}`);
451
+ }
452
+ }
453
+ async set(account, value) {
454
+ try {
455
+ await execFileWithInput('secret-tool', [
456
+ 'store',
457
+ '--label',
458
+ 'Doubao Open CLI 登录凭证',
459
+ 'service',
460
+ SERVICE_NAME,
461
+ 'account',
462
+ account
463
+ ], value);
464
+ } catch (error) {
465
+ throw new CredentialStoreError(`向 Secret Service 写入登录凭证失败:${credential_store_errorMessage(error)}`);
466
+ }
467
+ }
468
+ async delete(account) {
469
+ try {
470
+ await execFileAsync('secret-tool', [
471
+ 'clear',
472
+ 'service',
473
+ SERVICE_NAME,
474
+ 'account',
475
+ account
476
+ ]);
477
+ } catch (error) {
478
+ if (isSecretToolNotFound(error)) return;
479
+ throw new CredentialStoreError(`从 Secret Service 删除登录凭证失败:${credential_store_errorMessage(error)}`);
480
+ }
481
+ }
482
+ }
483
+ function execFileWithInput(command, args, input) {
484
+ return new Promise((resolve, reject)=>{
485
+ const child = spawn(command, args, {
486
+ stdio: [
487
+ 'pipe',
488
+ 'ignore',
489
+ 'pipe'
490
+ ]
491
+ });
492
+ let stderr = '';
493
+ child.stderr.setEncoding('utf8');
494
+ child.stderr.on('data', (chunk)=>{
495
+ stderr += String(chunk);
496
+ });
497
+ child.on('error', reject);
498
+ child.on('close', (code)=>{
499
+ if (0 === code) resolve();
500
+ else {
501
+ const error = new Error(stderr.trim() || `${command} 已退出,退出码为 ${code}`);
502
+ reject(error);
503
+ }
504
+ });
505
+ child.stdin.end(input);
506
+ });
507
+ }
508
+ async function writePrivateFileAtomic(filePath, payload) {
509
+ const dir = node_path.dirname(filePath);
510
+ await promises.mkdir(dir, {
511
+ recursive: true,
512
+ mode: 448
513
+ });
514
+ if ('win32' !== process.platform) await promises.chmod(dir, 448);
515
+ const tempPath = node_path.join(dir, `.${node_path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
516
+ try {
517
+ await promises.writeFile(tempPath, payload, {
518
+ mode: 384,
519
+ flag: 'wx'
520
+ });
521
+ if ('win32' !== process.platform) await promises.chmod(tempPath, 384);
522
+ await promises.rename(tempPath, filePath);
523
+ if ('win32' !== process.platform) await promises.chmod(filePath, 384);
524
+ } finally{
525
+ try {
526
+ await promises.unlink(tempPath);
527
+ } catch {}
528
+ }
529
+ }
530
+ async function writePrivateFileCreateOnce(filePath, payload) {
531
+ const dir = node_path.dirname(filePath);
532
+ await promises.mkdir(dir, {
533
+ recursive: true,
534
+ mode: 448
535
+ });
536
+ if ('win32' !== process.platform) await promises.chmod(dir, 448);
537
+ const tempPath = node_path.join(dir, `.${node_path.basename(filePath)}.${process.pid}.${Date.now()}.${node_crypto.randomBytes(8).toString('hex')}.tmp`);
538
+ try {
539
+ await promises.writeFile(tempPath, payload, {
540
+ mode: 384,
541
+ flag: 'wx'
542
+ });
543
+ if ('win32' !== process.platform) await promises.chmod(tempPath, 384);
544
+ try {
545
+ await promises.link(tempPath, filePath);
546
+ if ('win32' !== process.platform) await promises.chmod(filePath, 384);
547
+ return true;
548
+ } catch (error) {
549
+ if (isNodeError(error) && 'EEXIST' === error.code) return false;
550
+ throw error;
551
+ }
552
+ } finally{
553
+ try {
554
+ await promises.unlink(tempPath);
555
+ } catch {}
556
+ }
557
+ }
558
+ function supportsCredentialAuthority(store) {
559
+ const candidate = store;
560
+ return 'function' == typeof candidate.isFallbackAuthoritative && 'function' == typeof candidate.markFallbackAuthoritative && 'function' == typeof candidate.clearFallbackAuthority;
561
+ }
562
+ function parseMasterKey(raw) {
563
+ const key = Buffer.from(raw.trim(), 'base64url');
564
+ if (32 !== key.length) throw new CredentialStoreError('无法读取本地登录凭证:密钥文件 master.key 的内容无法解析为 32 字节密钥。');
565
+ return key;
566
+ }
567
+ function isEncryptedCredentialRecord(value) {
568
+ if (!value || 'object' != typeof value) return false;
569
+ const record = value;
570
+ return record.version === LOCAL_STORE_VERSION && record.algorithm === LOCAL_STORE_ALGORITHM && 'string' == typeof record.iv && 'string' == typeof record.authTag && 'string' == typeof record.ciphertext;
571
+ }
572
+ function associatedData(account) {
573
+ return `${SERVICE_NAME}:${account}`;
574
+ }
575
+ function defaultEncryptedCredentialDir() {
576
+ if ('darwin' === process.platform) return node_path.join(node_os.homedir(), 'Library', 'Application Support', LOCAL_STORE_DIR_NAME);
577
+ if ('win32' === process.platform) return node_path.join(process.env.APPDATA || node_path.join(node_os.homedir(), 'AppData', 'Roaming'), LOCAL_STORE_DIR_NAME);
578
+ return node_path.join(process.env.XDG_DATA_HOME || node_path.join(node_os.homedir(), '.local', 'share'), LOCAL_STORE_DIR_NAME);
579
+ }
580
+ function isMacOSKeychainNotFound(error) {
581
+ const code = errorCode(error);
582
+ const stderr = errorStderr(error);
583
+ return (1 === code || 44 === code) && (stderr.includes('could not be found') || stderr.includes('The specified item could not be found'));
584
+ }
585
+ function isSecretToolNotFound(error) {
586
+ const code = errorCode(error);
587
+ const stderr = errorStderr(error);
588
+ return 1 === code && (stderr.includes('could not be found') || stderr.includes('No matching items') || '' === stderr.trim());
589
+ }
590
+ function errorCode(error) {
591
+ return 'object' == typeof error && null !== error && 'code' in error ? error.code : void 0;
592
+ }
593
+ function errorStderr(error) {
594
+ return 'object' == typeof error && null !== error && 'stderr' in error ? String(error.stderr ?? '') : '';
595
+ }
596
+ function isNodeError(error) {
597
+ return error instanceof Error && 'code' in error;
598
+ }
599
+ function credential_store_errorMessage(error) {
600
+ if ('object' == typeof error && null !== error && 'stderr' in error) {
601
+ const stderr = String(error.stderr ?? '').trim();
602
+ if (stderr) return stderr;
603
+ }
604
+ if (error instanceof Error && error.message) return error.message;
605
+ return String(error);
606
+ }
607
+ class AuthError extends Error {
608
+ constructor(message, code){
609
+ super(message);
610
+ this.code = code;
611
+ this.name = 'AuthError';
612
+ }
613
+ }
614
+ class NotLoggedInError extends AuthError {
615
+ constructor(message = '尚未登录,请运行 dbx auth login 完成登录。'){
616
+ super(message, 'NOT_LOGGED_IN');
617
+ this.name = 'NotLoggedInError';
618
+ }
619
+ }
620
+ class TokenExpiredError extends AuthError {
621
+ constructor(message = '登录凭证已过期,请运行 dbx auth login 重新登录。'){
622
+ super(message, 'TOKEN_EXPIRED');
623
+ this.name = 'TokenExpiredError';
624
+ }
625
+ }
626
+ class DeviceAuthError extends AuthError {
627
+ constructor(message, code = 'DEVICE_AUTH_FAILED', options = {}){
628
+ super(message, code);
629
+ this.name = 'DeviceAuthError';
630
+ this.logId = options.logId;
631
+ }
632
+ }
633
+ function decodeJwtPayload(token) {
634
+ const part = token.split('.')[1];
635
+ if (!part) return;
636
+ try {
637
+ const json = Buffer.from(part, 'base64url').toString('utf8');
638
+ const payload = JSON.parse(json);
639
+ return payload && 'object' == typeof payload ? payload : void 0;
640
+ } catch {
641
+ return;
642
+ }
643
+ }
644
+ function getUidFromToken(token) {
645
+ const payload = decodeJwtPayload(token);
646
+ const uid = payload?.uid ?? payload?.sub;
647
+ if ('string' == typeof uid && uid.length > 0) return uid;
648
+ if ('number' == typeof uid && Number.isFinite(uid)) return String(uid);
649
+ }
650
+ const ENV_ACCESS_TOKEN_EXPIRES_AT = 4102444800;
651
+ async function readToken(tokenPath, options = {}) {
652
+ const envToken = readAccessTokenFromEnv();
653
+ if (envToken) return {
654
+ accessToken: envToken,
655
+ tokenType: 'Bearer',
656
+ expiresAt: ENV_ACCESS_TOKEN_EXPIRES_AT,
657
+ baseUrl: options.envTokenBaseUrl ?? ''
658
+ };
659
+ const raw = await readTokenFile(tokenPath);
660
+ if (void 0 === raw) return readLegacyPlaintextToken(tokenPath, options);
661
+ let parsed;
662
+ try {
663
+ parsed = JSON.parse(raw);
664
+ } catch (_error) {
665
+ return;
666
+ }
667
+ if (isStoredTokenMetadata(parsed)) {
668
+ if (!options.allowExpired && isExpired(parsed)) return void await store_deleteToken(tokenPath, options);
669
+ const accessToken = await credentialStore(options).get(parsed.credentialAccount);
670
+ if (!accessToken) throw new CredentialStoreError("已找到登录记录,但登录凭证缺失或无法访问。请在能够访问凭证存储的终端中重新运行 dbx auth login。");
671
+ return {
672
+ accessToken,
673
+ tokenType: parsed.tokenType,
674
+ expiresAt: parsed.expiresAt,
675
+ baseUrl: parsed.baseUrl
676
+ };
677
+ }
678
+ if (!isStoredToken(parsed)) return;
679
+ if (!options.allowExpired && isExpired(parsed)) return void await store_deleteToken(tokenPath, options);
680
+ await writeToken(tokenPath, parsed, options);
681
+ return parsed;
682
+ }
683
+ async function writeToken(tokenPath, token, options = {}) {
684
+ const metadata = {
685
+ version: 2,
686
+ storage: 'keychain',
687
+ credentialAccount: credentialAccountForPath(tokenPath),
688
+ tokenType: token.tokenType,
689
+ expiresAt: token.expiresAt,
690
+ baseUrl: token.baseUrl
691
+ };
692
+ await credentialStore(options).set(metadata.credentialAccount, token.accessToken);
693
+ await promises.mkdir(node_path.dirname(tokenPath), {
694
+ recursive: true,
695
+ mode: 448
696
+ });
697
+ if ('win32' !== process.platform) await promises.chmod(node_path.dirname(tokenPath), 448);
698
+ const tempPath = `${tokenPath}.${process.pid}.${Date.now()}.tmp`;
699
+ await promises.writeFile(tempPath, `${JSON.stringify(metadata, null, 2)}\n`, {
700
+ mode: 384
701
+ });
702
+ if ('win32' !== process.platform) await promises.chmod(tempPath, 384);
703
+ await promises.rename(tempPath, tokenPath);
704
+ if ('win32' !== process.platform) await promises.chmod(tokenPath, 384);
705
+ }
706
+ async function store_deleteToken(tokenPath, options = {}) {
707
+ const raw = await readTokenFile(tokenPath);
708
+ let account = credentialAccountForPath(tokenPath);
709
+ if (void 0 !== raw) try {
710
+ const parsed = JSON.parse(raw);
711
+ if (isStoredTokenMetadata(parsed)) account = parsed.credentialAccount;
712
+ } catch {}
713
+ await credentialStore(options).delete(account);
714
+ try {
715
+ await promises.unlink(tokenPath);
716
+ } catch (error) {
717
+ if (!store_isNodeError(error) || 'ENOENT' !== error.code) throw error;
718
+ }
719
+ }
720
+ function isExpired(token, nowSeconds = Math.floor(Date.now() / 1000)) {
721
+ return token.expiresAt <= nowSeconds;
722
+ }
723
+ function isStoredToken(value) {
724
+ if (!value || 'object' != typeof value) return false;
725
+ const token = value;
726
+ return 'string' == typeof token.accessToken && token.accessToken.length > 0 && 'string' == typeof token.tokenType && token.tokenType.length > 0 && 'number' == typeof token.expiresAt && Number.isFinite(token.expiresAt) && 'string' == typeof token.baseUrl && token.baseUrl.length > 0;
727
+ }
728
+ function isStoredTokenMetadata(value) {
729
+ if (!value || 'object' != typeof value) return false;
730
+ const metadata = value;
731
+ return 2 === metadata.version && 'keychain' === metadata.storage && 'string' == typeof metadata.credentialAccount && metadata.credentialAccount.length > 0 && 'string' == typeof metadata.tokenType && metadata.tokenType.length > 0 && 'number' == typeof metadata.expiresAt && Number.isFinite(metadata.expiresAt) && 'string' == typeof metadata.baseUrl && metadata.baseUrl.length > 0;
732
+ }
733
+ async function readTokenFile(tokenPath) {
734
+ try {
735
+ return await promises.readFile(tokenPath, 'utf8');
736
+ } catch (error) {
737
+ store_isNodeError(error) && error.code;
738
+ return;
739
+ }
740
+ }
741
+ async function readLegacyPlaintextToken(tokenPath, options) {
742
+ if (!options.legacyTokenPath || node_path.resolve(options.legacyTokenPath) === node_path.resolve(tokenPath)) return;
743
+ const raw = await readTokenFile(options.legacyTokenPath);
744
+ if (void 0 === raw) return;
745
+ let parsed;
746
+ try {
747
+ parsed = JSON.parse(raw);
748
+ } catch {
749
+ return;
750
+ }
751
+ if (!isStoredToken(parsed)) return;
752
+ if (!options.allowExpired && isExpired(parsed)) return void await store_deleteToken(options.legacyTokenPath, options);
753
+ await writeToken(tokenPath, parsed, options);
754
+ await store_deleteToken(options.legacyTokenPath, options);
755
+ return parsed;
756
+ }
757
+ function credentialStore(options) {
758
+ return options.credentialStore ?? defaultCredentialStore();
759
+ }
760
+ function credentialAccountForPath(tokenPath) {
761
+ const digest = node_crypto.createHash('sha256').update(node_path.resolve(tokenPath)).digest('hex').slice(0, 32);
762
+ return `token:${digest}`;
763
+ }
764
+ function readAccessTokenFromEnv() {
765
+ const value = process.env.DBX_ACCESS_TOKEN?.trim();
766
+ return value ? value : void 0;
767
+ }
768
+ function store_isNodeError(error) {
769
+ return error instanceof Error && 'code' in error;
770
+ }
771
+ var types_TokenStatus;
772
+ (function(TokenStatus) {
773
+ TokenStatus[TokenStatus["Pending"] = 1] = "Pending";
774
+ TokenStatus[TokenStatus["Authorized"] = 2] = "Authorized";
775
+ TokenStatus[TokenStatus["Expired"] = 3] = "Expired";
776
+ TokenStatus[TokenStatus["Invalid"] = 4] = "Invalid";
777
+ TokenStatus[TokenStatus["SlowDown"] = 5] = "SlowDown";
778
+ })(types_TokenStatus || (types_TokenStatus = {}));
779
+ const ISSUE_DEVICE_CODE_PATH = '/app_developer/oauth/device/code';
780
+ const EXCHANGE_DEVICE_TOKEN_PATH = '/app_developer/oauth/token';
781
+ async function login(options = {}) {
782
+ const resolved = config_resolveOptions(options);
783
+ const fetchImpl = resolved.fetch ?? fetch;
784
+ const device = await requestDeviceCode(fetchImpl, resolved.baseUrl, resolved.requestHeaders);
785
+ const verificationUrl = buildVerificationUrl(resolved.verificationUrl, device.user_code);
786
+ resolved.onVerification?.({
787
+ userCode: device.user_code,
788
+ verificationUrl,
789
+ expiresIn: device.expires_in,
790
+ interval: device.interval
791
+ });
792
+ return pollDeviceToken(device.device_code, resolved, Math.max(1, device.interval), 1000 * device.expires_in);
793
+ }
794
+ async function startLogin(options = {}) {
795
+ const resolved = config_resolveOptions(options);
796
+ const fetchImpl = resolved.fetch ?? fetch;
797
+ const device = await requestDeviceCode(fetchImpl, resolved.baseUrl, resolved.requestHeaders);
798
+ const verificationUrl = buildVerificationUrl(resolved.verificationUrl, device.user_code);
799
+ resolved.onVerification?.({
800
+ userCode: device.user_code,
801
+ verificationUrl,
802
+ expiresIn: device.expires_in,
803
+ interval: device.interval
804
+ });
805
+ return {
806
+ userCode: device.user_code,
807
+ deviceCode: device.device_code,
808
+ verificationUrl,
809
+ expiresIn: device.expires_in,
810
+ interval: device.interval
811
+ };
812
+ }
813
+ async function completeLogin(deviceCode, options = {}) {
814
+ return pollDeviceToken(deviceCode, config_resolveOptions(options), 5, 600000);
815
+ }
816
+ async function pollDeviceToken(deviceCode, resolved, initialInterval, expiresInMs) {
817
+ const fetchImpl = resolved.fetch ?? fetch;
818
+ const sleep = resolved.sleep ?? promises_setTimeout;
819
+ let intervalSeconds = initialInterval;
820
+ const deadline = Date.now() + expiresInMs;
821
+ while(Date.now() < deadline){
822
+ await sleep(1000 * intervalSeconds);
823
+ const token = await requestToken(fetchImpl, resolved.baseUrl, deviceCode, resolved.requestHeaders);
824
+ resolved.onPoll?.({
825
+ status: token.status,
826
+ interval: intervalSeconds
827
+ });
828
+ if (token.status === types_TokenStatus.Pending) continue;
829
+ if (token.status === types_TokenStatus.SlowDown) {
830
+ intervalSeconds += initialInterval;
831
+ continue;
832
+ }
833
+ if (token.status === types_TokenStatus.Expired) throw new DeviceAuthError('设备授权已过期,请运行 dbx auth login 重新登录。', 'DEVICE_AUTH_EXPIRED');
834
+ if (token.status === types_TokenStatus.Invalid) throw new DeviceAuthError('设备授权无效,请运行 dbx auth login 重新登录。', 'DEVICE_AUTH_INVALID');
835
+ if (token.status === types_TokenStatus.Authorized) {
836
+ if (!token.access_token) throw new DeviceAuthError('无法完成登录:认证服务已确认授权,但未返回 access_token。');
837
+ const stored = toStoredToken(token, resolved.baseUrl);
838
+ await writeToken(resolved.tokenPath, stored, {
839
+ credentialStore: resolved.credentialStore
840
+ });
841
+ return {
842
+ accessToken: stored.accessToken,
843
+ tokenType: stored.tokenType,
844
+ expiresAt: stored.expiresAt,
845
+ baseUrl: stored.baseUrl,
846
+ uid: getUidFromToken(stored.accessToken),
847
+ tokenPath: resolved.tokenPath
848
+ };
849
+ }
850
+ throw new DeviceAuthError(`无法完成登录:认证服务返回了无法识别的授权状态 status=${String(token.status)}`);
851
+ }
852
+ throw new DeviceAuthError('等待设备授权超时,请运行 dbx auth login 重新发起登录。', 'DEVICE_AUTH_EXPIRED');
853
+ }
854
+ async function requestDeviceCode(fetchImpl, baseUrl, requestHeaders) {
855
+ const response = await fetchImpl(`${baseUrl}${ISSUE_DEVICE_CODE_PATH}`, {
856
+ method: 'POST',
857
+ headers: withJsonHeaders(requestHeaders),
858
+ body: JSON.stringify({
859
+ client_name: DEFAULT_CLIENT_NAME
860
+ })
861
+ });
862
+ const data = await readJson(response);
863
+ const payload = unwrapData(data);
864
+ if (!payload || !payload.device_code || !payload.user_code || 'number' != typeof payload.expires_in || 'number' != typeof payload.interval) throw new DeviceAuthError(`无法获取登录授权码:认证服务返回的数据缺少必要字段或字段类型不正确(HTTP ${response.status}):${JSON.stringify(data)}`);
865
+ return payload;
866
+ }
867
+ async function requestToken(fetchImpl, baseUrl, deviceCode, requestHeaders) {
868
+ const response = await fetchImpl(`${baseUrl}${EXCHANGE_DEVICE_TOKEN_PATH}`, {
869
+ method: 'POST',
870
+ headers: withJsonHeaders(requestHeaders),
871
+ body: JSON.stringify({
872
+ device_code: deviceCode
873
+ })
874
+ });
875
+ const data = await readJson(response);
876
+ const payload = unwrapData(data);
877
+ if (payload?.access_token) return {
878
+ status: types_TokenStatus.Authorized,
879
+ access_token: payload.access_token,
880
+ token_type: payload.token_type,
881
+ expires_in: payload.expires_in,
882
+ expires_at: payload.expires_at
883
+ };
884
+ if (!payload || 'number' != typeof payload.status) throw new DeviceAuthError(`无法获取登录凭证:认证服务返回的数据缺少有效的 status 字段(HTTP ${response.status}):${JSON.stringify(data)}`);
885
+ return payload;
886
+ }
887
+ async function readJson(response) {
888
+ const headerLogId = response.headers.get('x-tt-logid') ?? void 0;
889
+ let data;
890
+ try {
891
+ data = await response.json();
892
+ } catch {
893
+ throw new DeviceAuthError(`无法读取登录结果:认证服务返回的内容不是有效的 JSON(HTTP ${response.status})${formatLogId(headerLogId)}`, 'DEVICE_AUTH_FAILED', {
894
+ logId: headerLogId
895
+ });
896
+ }
897
+ const bodyLogId = extractLogId(data);
898
+ const logId = bodyLogId ?? headerLogId;
899
+ if (!response.ok) throw new DeviceAuthError(`认证服务返回 HTTP ${response.status}:${JSON.stringify(data)}${formatLogId(logId)}`, 'DEVICE_AUTH_FAILED', {
900
+ logId
901
+ });
902
+ const apiError = extractApiError(data);
903
+ if (apiError) throw new DeviceAuthError(`认证服务出错(错误码 ${apiError.code}):${apiError.message}${formatLogId(apiError.logId ?? logId)}`, 'DEVICE_AUTH_FAILED', {
904
+ logId: apiError.logId ?? logId
905
+ });
906
+ return data;
907
+ }
908
+ function extractApiError(data) {
909
+ if (data && 'object' == typeof data) {
910
+ const d = data;
911
+ if ('number' == typeof d.code && 0 !== d.code) return {
912
+ code: d.code,
913
+ message: 'string' == typeof d.message ? d.message : '未知错误',
914
+ logId: 'string' == typeof d.log_id ? d.log_id : void 0
915
+ };
916
+ }
917
+ return null;
918
+ }
919
+ function extractLogId(data) {
920
+ if (!data || 'object' != typeof data) return;
921
+ const d = data;
922
+ return 'string' == typeof d.log_id && d.log_id ? d.log_id : void 0;
923
+ }
924
+ function formatLogId(logId) {
925
+ return logId ? ` [log_id: ${logId}]` : '';
926
+ }
927
+ function unwrapData(data) {
928
+ if (data && 'object' == typeof data && 'data' in data) return data.data;
929
+ return data;
930
+ }
931
+ function withJsonHeaders(requestHeaders) {
932
+ const headers = new Headers(requestHeaders);
933
+ headers.set('Content-Type', 'application/json');
934
+ return headers;
935
+ }
936
+ function toStoredToken(response, baseUrl) {
937
+ const now = Math.floor(Date.now() / 1000);
938
+ return {
939
+ accessToken: response.access_token ?? '',
940
+ tokenType: response.token_type ?? 'Bearer',
941
+ expiresAt: 'number' == typeof response.expires_at ? response.expires_at : now + (response.expires_in ?? 0),
942
+ baseUrl
943
+ };
944
+ }
945
+ function buildVerificationUrl(verificationUrl, userCode) {
946
+ const url = new URL(verificationUrl);
947
+ url.searchParams.set('user_code', userCode);
948
+ return url.toString();
949
+ }
950
+ function getAuthHeaders(input, headers, options = {}) {
951
+ return resolveAuthHeaders(input, headers, config_resolveOptions(options));
952
+ }
953
+ async function resolveAuthHeaders(input, initHeaders, resolved) {
954
+ assertAllowedAuthenticatedRequest(input, resolved.allowedOrigins);
955
+ const token = await readToken(resolved.tokenPath, {
956
+ allowExpired: true,
957
+ envTokenBaseUrl: resolved.baseUrl,
958
+ credentialStore: resolved.credentialStore,
959
+ legacyTokenPath: resolved.legacyTokenPath
960
+ });
961
+ if (!token) throw new NotLoggedInError();
962
+ if (isExpired(token)) throw new TokenExpiredError();
963
+ const headers = createMergedHeaders(input, initHeaders);
964
+ resolved.requestHeaders.forEach((value, key)=>{
965
+ headers.set(key, value);
966
+ });
967
+ headers.set('Authorization', `${token.tokenType || 'Bearer'} ${token.accessToken}`);
968
+ return headers;
969
+ }
970
+ function createMergedHeaders(input, initHeaders) {
971
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
972
+ new Headers(initHeaders).forEach((value, key)=>{
973
+ headers.set(key, value);
974
+ });
975
+ return headers;
976
+ }
977
+ function assertAllowedAuthenticatedRequest(input, allowedOrigins) {
978
+ const url = requestUrl(input);
979
+ if ('https:' !== url.protocol) throw new Error(`携带登录凭证的请求必须使用 HTTPS:${url.toString()}`);
980
+ if (!allowedOrigins.includes(url.origin)) throw new Error(`无法向不受信任的来源发送 Authorization 请求头:${url.origin}`);
981
+ }
982
+ function requestUrl(input) {
983
+ try {
984
+ if ('string' == typeof input) return new URL(input);
985
+ if (input instanceof URL) return input;
986
+ return new URL(input.url);
987
+ } catch {
988
+ throw new Error('携带登录凭证的请求必须使用完整的 HTTPS URL');
989
+ }
990
+ }
991
+ async function whoami(options = {}) {
992
+ const resolved = config_resolveOptions(options);
993
+ let token;
994
+ try {
995
+ token = await readToken(resolved.tokenPath, {
996
+ envTokenBaseUrl: resolved.baseUrl,
997
+ credentialStore: resolved.credentialStore,
998
+ legacyTokenPath: resolved.legacyTokenPath
999
+ });
1000
+ } catch (error) {
1001
+ if (error instanceof CredentialStoreError) return {
1002
+ loggedIn: false,
1003
+ tokenPath: resolved.tokenPath,
1004
+ baseUrl: resolved.baseUrl,
1005
+ credentialError: {
1006
+ code: 'CREDENTIAL_STORE_UNAVAILABLE',
1007
+ message: error.message
1008
+ }
1009
+ };
1010
+ throw error;
1011
+ }
1012
+ if (!token) return {
1013
+ loggedIn: false,
1014
+ tokenPath: resolved.tokenPath
1015
+ };
1016
+ const user = await fetchCurrentUser(options.fetch ?? fetch, resolved.baseUrl, token, resolved.requestHeaders);
1017
+ if (!user) return {
1018
+ loggedIn: false,
1019
+ expiresAt: token.expiresAt,
1020
+ tokenPath: resolved.tokenPath,
1021
+ baseUrl: resolved.baseUrl
1022
+ };
1023
+ return {
1024
+ loggedIn: true,
1025
+ uid: user.userId,
1026
+ name: user.name,
1027
+ avatar: user.avatar,
1028
+ expiresAt: token.expiresAt,
1029
+ tokenPath: resolved.tokenPath,
1030
+ baseUrl: resolved.baseUrl
1031
+ };
1032
+ }
1033
+ async function fetchCurrentUser(fetchImpl, baseUrl, token, requestHeaders) {
1034
+ const headers = new Headers(requestHeaders);
1035
+ headers.set('Authorization', `${token.tokenType || 'Bearer'} ${token.accessToken}`);
1036
+ const response = await fetchImpl(`${baseUrl.replace(/\/+$/, '')}/app_developer/api/user/basic`, {
1037
+ method: 'GET',
1038
+ headers
1039
+ });
1040
+ if (!response.ok) return;
1041
+ const result = await response.json();
1042
+ if (0 !== result.code) return;
1043
+ const userId = toStringId(result.data?.user_id);
1044
+ return {
1045
+ userId,
1046
+ name: result.data?.name,
1047
+ avatar: result.data?.avatar
1048
+ };
1049
+ }
1050
+ function toStringId(value) {
1051
+ if ('string' == typeof value && value.length > 0) return value;
1052
+ if ('number' == typeof value && Number.isFinite(value)) return String(value);
1053
+ }
1054
+ const baseURL = `${process.env.DBX_BUILD_API_BASE_URL || runtime.apiUrl}/platform/api/v1`;
1055
+ const http_request = ofetch.create({
1056
+ baseURL: baseURL,
1057
+ headers: runtime.apiHeaders,
1058
+ method: 'GET',
1059
+ timeout: 30000,
1060
+ retry: 0,
1061
+ redirect: 'error',
1062
+ responseType: 'json',
1063
+ ignoreResponseError: true,
1064
+ async onRequest ({ request, options }) {
1065
+ const target = 'string' == typeof request ? new URL(request, options.baseURL) : request;
1066
+ options.headers = await getAuthHeaders(target, options.headers, {
1067
+ baseUrl: options.baseURL
1068
+ });
1069
+ if (options.signal && options.timeout) options.signal = AbortSignal.any([
1070
+ options.signal,
1071
+ AbortSignal.timeout(options.timeout)
1072
+ ]);
1073
+ },
1074
+ onResponse ({ response }) {
1075
+ const result = response._data;
1076
+ if (!response.ok || result?.code !== 0) {
1077
+ const message = result?.msg || result?.message || result?.statusText || `接口请求失败(HTTP ${response.status})`;
1078
+ const details = [];
1079
+ if (response.headers.has('x-tt-logid')) details.push(`请求日志 ID:${response.headers.get('x-tt-logid')}`);
1080
+ throw new Error([
1081
+ message,
1082
+ '',
1083
+ ...details
1084
+ ].join('\n'));
1085
+ }
1086
+ response._data = result.data;
1087
+ }
1088
+ });
1089
+ async function createDeviceTicket(params, signal) {
1090
+ const data = await http_request(`/app_developer/check_api/apps/${encodeURIComponent(params.appKey)}/debug/ticket`, {
1091
+ baseURL: runtime.deviceApiUrl,
1092
+ method: 'POST',
1093
+ signal,
1094
+ body: {
1095
+ debug_context: JSON.stringify({
1096
+ appletName: params.appKey,
1097
+ appletHost: params.hostUrl,
1098
+ appletType: 0,
1099
+ isSigned: false,
1100
+ isEncrypted: false,
1101
+ defaultDebugTagState: true,
1102
+ wifiConnectUrl: params.wifiConnectUrl,
1103
+ ...params.versionTag ? {
1104
+ versionTag: params.versionTag
1105
+ } : {}
1106
+ })
1107
+ }
1108
+ });
1109
+ const ticket = z.object({
1110
+ debug_token: z.string().trim().min(1),
1111
+ encrypted_debug_payload: z.string().trim().min(1)
1112
+ }).parse(data);
1113
+ return {
1114
+ debugToken: ticket.debug_token,
1115
+ qrCodeValue: JSON.stringify({
1116
+ action: 'start_real_device_debug',
1117
+ ...ticket
1118
+ })
1119
+ };
1120
+ }
1121
+ function getAppInfo(params, signal) {
1122
+ return http_request('/devtool/plugin-mini-app-base-info', {
1123
+ query: {
1124
+ ...params
1125
+ },
1126
+ signal
1127
+ });
1128
+ }
1129
+ function uploadPackage(fileName, packageContent, signal) {
1130
+ return http_request('/devtool/plugin-package-uploads', {
1131
+ method: 'POST',
1132
+ query: {
1133
+ file_name: fileName
1134
+ },
1135
+ headers: {
1136
+ 'Content-Type': 'application/zip'
1137
+ },
1138
+ body: packageContent,
1139
+ signal
1140
+ });
1141
+ }
1142
+ function validateMaterial(params) {
1143
+ return http_request('/devtool/plugin-material-validations', {
1144
+ method: 'POST',
1145
+ body: params
1146
+ });
1147
+ }
1148
+ function listPluginCategories() {
1149
+ return http_request('/devtool/plugin-categories');
1150
+ }
1151
+ function getPluginDraft(name) {
1152
+ return http_request('/devtool/plugin-drafts', {
1153
+ query: {
1154
+ name
1155
+ }
1156
+ });
1157
+ }
1158
+ function upsertPluginDraft(params) {
1159
+ return http_request('/devtool/plugin-drafts', {
1160
+ method: 'POST',
1161
+ body: params
1162
+ });
1163
+ }
1164
+ function savePluginVersion(params) {
1165
+ return http_request('/devtool/plugin-versions', {
1166
+ method: 'POST',
1167
+ body: params
1168
+ });
1169
+ }
1170
+ async function listPluginVersions(plugin, signal) {
1171
+ const identity = plugin.id ? {
1172
+ ability_id: plugin.id
1173
+ } : {
1174
+ name: plugin.name
1175
+ };
1176
+ const versions = [];
1177
+ const tokens = new Set();
1178
+ let pageToken;
1179
+ do {
1180
+ signal?.throwIfAborted();
1181
+ const result = await http_request('/devtool/plugin-versions', {
1182
+ query: {
1183
+ ...identity,
1184
+ page_size: 50,
1185
+ page_token: pageToken
1186
+ },
1187
+ signal
1188
+ });
1189
+ versions.push(...result.items ?? []);
1190
+ pageToken = result.page?.next_page_token;
1191
+ if (pageToken && tokens.has(pageToken)) throw new Error('版本列表分页异常,请稍后重试');
1192
+ if (pageToken) tokens.add(pageToken);
1193
+ }while (pageToken);
1194
+ return versions.sort((left, right)=>(right.version ?? '').localeCompare(left.version ?? '', 'en', {
1195
+ numeric: true
1196
+ }));
1197
+ }
1198
+ function getBuildStatus(params, signal) {
1199
+ return http_request('/devtool/plugin-versions/build-status', {
1200
+ query: {
1201
+ ...params
1202
+ },
1203
+ signal
1204
+ });
1205
+ }
1206
+ function createPreview(params, signal) {
1207
+ return http_request('/devtool/plugin-previews', {
1208
+ method: 'POST',
1209
+ body: params,
1210
+ signal
1211
+ });
1212
+ }
1213
+ function updateUserPlugin(params, signal) {
1214
+ return http_request('/devtool/plugin-operations', {
1215
+ method: 'POST',
1216
+ body: params,
1217
+ signal
1218
+ });
1219
+ }
1220
+ function submitApproval(name, version) {
1221
+ return http_request('/devtool/plugin-approvals/submit', {
1222
+ method: 'POST',
1223
+ body: {
1224
+ name,
1225
+ version
1226
+ }
1227
+ });
1228
+ }
1229
+ function withdrawApproval(approvalId) {
1230
+ return http_request('/devtool/plugin-approvals/withdraw', {
1231
+ method: 'POST',
1232
+ body: {
1233
+ approval_id: approvalId
1234
+ }
1235
+ });
1236
+ }
1237
+ const PLUGIN_MANIFEST_FILENAME = 'plugin.json';
1238
+ const MCP_CONFIG_FILENAME = 'mcp.json';
1239
+ const SKILL_FILENAME = 'SKILL.md';
1240
+ const APP_MANIFEST_FILENAME = 'manifest.yaml';
1241
+ const DOUBAO_APP_DIRECTORY = 'com.doubao/app';
1242
+ const APP_GENERATED_DIRECTORIES = [
1243
+ 'dist',
1244
+ 'node_modules/.doubao'
1245
+ ];
1246
+ const PROJECT_EXCLUDED_DIRECTORIES = [
1247
+ '.git',
1248
+ '.dbx'
1249
+ ];
1250
+ const PROJECT_EXCLUDED_FILES = [
1251
+ '.gitkeep',
1252
+ '.gitignore',
1253
+ '.DS_Store'
1254
+ ];
1255
+ const TEMPLATES_DIRECTORY = 'templates';
1256
+ const STANDARD_TEMPLATE_DIRECTORY = 'standard';
1257
+ const TEMPLATE_SUFFIX = '.template';
1258
+ const PLUGIN_NAME_PLACEHOLDER = '__PLUGIN_NAME__';
1259
+ const PACKAGE_CACHE_DIRECTORY = '.dbx/tools';
1260
+ const SKILL_NAME = 'doubao-plugin-cli';
1261
+ const APP_SKILL_NAME = 'doubao-plugin-app';
1262
+ const PROJECT_SKILL_PATH = `.agents/skills/${SKILL_NAME}`;
1263
+ function isInteractive() {
1264
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
1265
+ }
1266
+ function writeStdout(message) {
1267
+ console.log(stripVTControlCharacters(message));
1268
+ }
1269
+ function writeStderr(message) {
1270
+ console.error(stripVTControlCharacters(message));
1271
+ }
1272
+ const terminal_log = isInteractive() ? log : {
1273
+ info: writeStdout,
1274
+ step: writeStdout,
1275
+ success: writeStdout,
1276
+ warn: writeStderr,
1277
+ error: writeStderr
1278
+ };
1279
+ function resolveEntry(require, spec) {
1280
+ try {
1281
+ const manifestPath = require.resolve(`${spec.name}/package.json`);
1282
+ const manifest = JSON.parse(node_fs.readFileSync(manifestPath, 'utf8'));
1283
+ if (manifest.version !== spec.version) return;
1284
+ const exported = manifest.exports?.[spec.entry];
1285
+ const relativeEntry = 'string' == typeof exported ? exported : exported?.import;
1286
+ if ('string' != typeof relativeEntry || !relativeEntry) return;
1287
+ const entry = node_path.resolve(node_path.dirname(manifestPath), relativeEntry);
1288
+ if (node_fs.existsSync(entry)) return entry;
1289
+ } catch (error) {
1290
+ if ('MODULE_NOT_FOUND' !== error.code) throw error;
1291
+ }
1292
+ }
1293
+ async function loadPackage(spec, signal, output) {
1294
+ signal.throwIfAborted();
1295
+ const cache = node_path.join(homedir(), PACKAGE_CACHE_DIRECTORY, spec.name, spec.version);
1296
+ const requireCache = createRequire(node_path.join(cache, 'package.json'));
1297
+ let entry = resolveEntry(createRequire(import.meta.url), spec) ?? resolveEntry(requireCache, spec);
1298
+ if (!entry) {
1299
+ node_fs.mkdirSync(node_path.dirname(cache), {
1300
+ recursive: true
1301
+ });
1302
+ const staging = node_fs.mkdtempSync(`${cache}-`);
1303
+ const label = spec.name === runtime.kit.name ? '应用编译与模拟器工具' : '应用开发工具';
1304
+ const progress = output || !isInteractive() ? void 0 : ora({
1305
+ text: ` 正在安装${label}`,
1306
+ stream: process.stdout,
1307
+ discardStdin: false
1308
+ }).start();
1309
+ try {
1310
+ if (!progress) (output?.log ?? terminal_log).info(`正在安装${label}`);
1311
+ const child = spawn('npm', [
1312
+ 'install',
1313
+ `${spec.name}@${spec.version}`,
1314
+ '--prefix',
1315
+ staging,
1316
+ '--registry',
1317
+ runtime.registry,
1318
+ '--no-audit',
1319
+ '--no-fund',
1320
+ '--loglevel',
1321
+ 'error'
1322
+ ], {
1323
+ stdio: [
1324
+ 'ignore',
1325
+ 'ignore',
1326
+ 'pipe'
1327
+ ],
1328
+ signal
1329
+ });
1330
+ let installError = '';
1331
+ child.stderr.on('data', (data)=>{
1332
+ installError += data.toString();
1333
+ });
1334
+ const [code] = await once(child, 'close');
1335
+ if (0 !== code) throw new Error(`${spec.name} 安装失败,退出码:${code}\n${installError.trim()}`);
1336
+ signal.throwIfAborted();
1337
+ if (!resolveEntry(createRequire(node_path.join(staging, 'package.json')), spec)) throw new Error(`${spec.name} 的版本或入口不正确`);
1338
+ try {
1339
+ node_fs.renameSync(staging, cache);
1340
+ } catch {
1341
+ if (!resolveEntry(requireCache, spec)) {
1342
+ node_fs.rmSync(cache, {
1343
+ recursive: true,
1344
+ force: true
1345
+ });
1346
+ node_fs.renameSync(staging, cache);
1347
+ }
1348
+ }
1349
+ entry = resolveEntry(requireCache, spec);
1350
+ progress?.succeed(` ${label}已安装`);
1351
+ } catch (error) {
1352
+ progress?.fail(signal.aborted ? ` 已取消安装${label}` : ` ${label}安装失败`);
1353
+ signal.throwIfAborted();
1354
+ throw error;
1355
+ } finally{
1356
+ node_fs.rmSync(staging, {
1357
+ recursive: true,
1358
+ force: true
1359
+ });
1360
+ }
1361
+ }
1362
+ signal.throwIfAborted();
1363
+ if (!entry) throw new Error(`${spec.name} 的入口不存在`);
1364
+ return pathToFileURL(entry).href;
1365
+ }
1366
+ function withProgress(message, action) {
1367
+ if (!isInteractive()) {
1368
+ terminal_log.info(message);
1369
+ return action();
1370
+ }
1371
+ return oraPromise(action, {
1372
+ text: ` ${message}`,
1373
+ successText: ` ${message}完成`,
1374
+ stream: process.stdout,
1375
+ discardStdin: false
1376
+ });
1377
+ }
1378
+ let installing;
1379
+ async function loadAppModule(entry, signal = new AbortController().signal, output) {
1380
+ const main = await (installing ??= loadPackage(runtime.appDev, signal, output));
1381
+ signal.throwIfAborted();
1382
+ const url = '.' === entry ? main : await loadPackage({
1383
+ ...runtime.appDev,
1384
+ entry
1385
+ }, signal, output);
1386
+ return import(url);
1387
+ }
1388
+ const appPlugin = {
1389
+ name: 'doubao-app',
1390
+ setup (api) {
1391
+ api.onAddCommand(({ program })=>{
1392
+ program.commands.find((command)=>'dev' === command.name()).option('--app', '同时启动应用调试(App)');
1393
+ });
1394
+ api.onDev(()=>[
1395
+ {
1396
+ name: 'app',
1397
+ title: '应用调试(App)',
1398
+ option: 'app',
1399
+ watch: [
1400
+ MCP_CONFIG_FILENAME
1401
+ ],
1402
+ appliesTo: ({ project })=>Boolean(project.app),
1403
+ async start (ctx) {
1404
+ const { startApp } = await loadAppModule('.', ctx.signal, ctx);
1405
+ ctx.signal.throwIfAborted();
1406
+ await startApp(ctx, {
1407
+ kitVersion: runtime.kit.version,
1408
+ quiet: !ctx.interactive,
1409
+ loadKit: (output)=>loadPackage(runtime.kit, ctx.signal, output),
1410
+ queryAppInfo: getAppInfo,
1411
+ requestTicket: createDeviceTicket,
1412
+ queryVersions: ()=>listPluginVersions(ctx.project.plugin, ctx.signal)
1413
+ });
1414
+ },
1415
+ async createUI (ctx, onExit) {
1416
+ const { createAppUI } = await loadAppModule('./ui', ctx.signal);
1417
+ ctx.signal.throwIfAborted();
1418
+ return createAppUI({
1419
+ name: ctx.project.plugin.name,
1420
+ version: runtime.version,
1421
+ metadata: {
1422
+ Kit: runtime.kit.version
1423
+ },
1424
+ onExit
1425
+ });
1426
+ }
1427
+ }
1428
+ ]);
1429
+ api.onModifyPackagePlan((_ctx, plan)=>({
1430
+ ...plan,
1431
+ exclude: [
1432
+ ...plan.exclude ?? [],
1433
+ `${DOUBAO_APP_DIRECTORY}/node_modules`
1434
+ ]
1435
+ }));
1436
+ api.onAfterUpload(async (ctx)=>{
1437
+ if (!ctx.project.app) return;
1438
+ const { version } = await withProgress('保存预览版本', ()=>savePluginVersion({
1439
+ ...ctx.project.plugin.id ? {
1440
+ ability_id: ctx.project.plugin.id
1441
+ } : {
1442
+ name: ctx.project.plugin.name
1443
+ },
1444
+ request_id: randomUUID()
1445
+ }));
1446
+ if (!version) throw new Error('服务端未返回保存的版本号,请到平台草稿页查看。');
1447
+ const { showPreview } = await loadAppModule('./preview');
1448
+ await showPreview(ctx.project, version, {
1449
+ withProgress: withProgress,
1450
+ queryBuildStatus: getBuildStatus,
1451
+ createPreview: createPreview
1452
+ });
1453
+ });
1454
+ }
1455
+ };
1456
+ function findProjectRoot(cwd) {
1457
+ let root = external_node_path_resolve(cwd);
1458
+ while(!existsSync(join(root, PLUGIN_MANIFEST_FILENAME))){
1459
+ const parent = dirname(root);
1460
+ if (parent === root) throw new Error(`未找到 plugin.json,请在应用插件项目内运行:${cwd}`);
1461
+ root = parent;
1462
+ }
1463
+ return root;
1464
+ }
1465
+ function findApp(projectRoot) {
1466
+ const root = join(projectRoot, DOUBAO_APP_DIRECTORY);
1467
+ if (existsSync(join(root, APP_MANIFEST_FILENAME)) && findProjectRoot(root) === projectRoot) return {
1468
+ root
1469
+ };
1470
+ }
1471
+ const projectNameMessage = '项目名称须为 1 至 64 个小写字母、数字、连字符或点号,以字母或数字开头结尾,且不能包含连续连字符或连续点号。';
1472
+ const projectNameSchema = z.string().max(64, projectNameMessage).regex(/^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/, projectNameMessage);
1473
+ const versionSchema = z.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/, '版本号须为 x.y.z 格式,不能包含前导零');
1474
+ const jsonObjectSchema = z.record(z.string(), z.unknown());
1475
+ const pluginManifestSchema = z.looseObject({
1476
+ $schema: z.string().optional(),
1477
+ name: projectNameSchema,
1478
+ id: z.string().optional(),
1479
+ displayName: z.string().optional(),
1480
+ description: z.string().optional(),
1481
+ extensions: jsonObjectSchema.optional()
1482
+ });
1483
+ const mcpConfigSchema = z.looseObject({
1484
+ mcpServers: z.record(z.string(), jsonObjectSchema)
1485
+ });
1486
+ function currentSkillsRoot(packageName = runtime.name) {
1487
+ const source = packageName.replace(/^@/, '').replace(/[\\/]/g, '+');
1488
+ return join(homedir(), '.dbx', 'skills', source, 'current');
1489
+ }
1490
+ function refreshSkills() {
1491
+ const bundledRoot = external_node_path_resolve(import.meta.dirname, 'skills');
1492
+ if (!__rspack_external_node_fs_1b05aee1.statSync(join(bundledRoot, SKILL_NAME, 'SKILL.md')).isFile()) throw new Error('CLI 安装包中的开发 Skill 不完整,请重新安装 CLI。');
1493
+ replaceDirectoryLink(bundledRoot, currentSkillsRoot());
1494
+ }
1495
+ async function syncProjectSkill(cwd) {
1496
+ const projectRoot = findProjectRoot(cwd);
1497
+ pluginManifestSchema.parse(readJsonSync(join(projectRoot, 'plugin.json')));
1498
+ const hasApp = Boolean(findApp(projectRoot));
1499
+ const sources = [
1500
+ [
1501
+ SKILL_NAME,
1502
+ currentSkillsRoot()
1503
+ ]
1504
+ ];
1505
+ if (hasApp) {
1506
+ const { skillsRoot } = await loadAppModule('./assets');
1507
+ const appSkillsRoot = currentSkillsRoot(runtime.appDev.name);
1508
+ replaceDirectoryLink(skillsRoot, appSkillsRoot);
1509
+ sources.push([
1510
+ APP_SKILL_NAME,
1511
+ appSkillsRoot
1512
+ ]);
1513
+ }
1514
+ for (const [name, source] of sources){
1515
+ const skillPath = `.agents/skills/${name}`;
1516
+ replaceDirectoryLink(join(source, name), join(projectRoot, skillPath));
1517
+ const ignorePath = join(projectRoot, '.gitignore');
1518
+ const ignore = __rspack_external_node_fs_1b05aee1.existsSync(ignorePath) ? __rspack_external_node_fs_1b05aee1.readFileSync(ignorePath, 'utf8') : '';
1519
+ const pattern = `/${skillPath}`;
1520
+ if (!ignore.split(/\r?\n/).includes(pattern)) appendText(ignorePath, ignore, `${pattern}\n`);
1521
+ }
1522
+ const guidePath = join(projectRoot, 'AGENTS.md');
1523
+ const guide = __rspack_external_node_fs_1b05aee1.existsSync(guidePath) ? __rspack_external_node_fs_1b05aee1.readFileSync(guidePath, 'utf8') : '';
1524
+ if (!guide.includes(`${PROJECT_SKILL_PATH}/SKILL.md`)) {
1525
+ const template = __rspack_external_node_fs_1b05aee1.readFileSync(join(import.meta.dirname, 'templates/standard/AGENTS.md'), 'utf8');
1526
+ appendText(guidePath, guide, template);
1527
+ }
1528
+ const appSkillPath = `.agents/skills/${APP_SKILL_NAME}/SKILL.md`;
1529
+ if (hasApp && !guide.includes(appSkillPath)) appendText(guidePath, __rspack_external_node_fs_1b05aee1.readFileSync(guidePath, 'utf8'), `\n仅开发豆包 App 的 Page、Widget 或 Manifest 时,先阅读 [应用开发 Skill](${appSkillPath})。普通 MCP 和 MCP Apps 不使用 App Manifest。\n`);
1530
+ }
1531
+ function appendText(path, existing, text) {
1532
+ const separator = existing && !existing.endsWith('\n') ? '\n' : '';
1533
+ __rspack_external_node_fs_1b05aee1.writeFileSync(path, `${existing}${separator}${text}`);
1534
+ }
1535
+ function replaceDirectoryLink(source, target) {
1536
+ const existing = __rspack_external_node_fs_1b05aee1.lstatSync(target, {
1537
+ throwIfNoEntry: false
1538
+ });
1539
+ if (existing?.isSymbolicLink() && external_node_path_resolve(dirname(target), __rspack_external_node_fs_1b05aee1.readlinkSync(target)) === source) return;
1540
+ if (existing && !existing.isSymbolicLink()) throw new Error(`开发 Skill 路径已存在且不是软链接,请先确认并移走该路径:${target}`);
1541
+ __rspack_external_node_fs_1b05aee1.mkdirSync(dirname(target), {
1542
+ recursive: true
1543
+ });
1544
+ __rspack_external_node_fs_1b05aee1.rmSync(target, {
1545
+ recursive: true,
1546
+ force: true
1547
+ });
1548
+ __rspack_external_node_fs_1b05aee1.symlinkSync(source, target, 'win32' === process.platform ? 'junction' : 'dir');
1549
+ }
1550
+ const builtinPlugin = {
1551
+ name: 'builtin',
1552
+ setup (api) {
1553
+ api.onAddCommand(({ program, hooks })=>{
1554
+ const cwdOption = new Option('--cwd <path>', '项目根目录').argParser((value)=>external_node_path_resolve(value)).default(process.cwd());
1555
+ program.command('init').description('创建应用插件项目').argument('[name]', '项目名称,同时作为目录名').option('--app', '添加应用前端(App)').option('--no-install-deps', '创建 App 后不安装前端依赖').action(async (name, options)=>{
1556
+ const { runInit } = await import("./command~3.js");
1557
+ return runInit(name, options);
1558
+ });
1559
+ program.command('upload').description('上传应用插件包到开放平台').addOption(cwdOption).option('--version <version>', '应用插件版本,格式为 x.y.z').action(withProject(async (project, options)=>{
1560
+ const { runUpload } = await import("./command~4.js");
1561
+ await runUpload(project, options, hooks);
1562
+ }));
1563
+ program.command('dev').addOption(new Option('--plugin', '仅调试 Skill / MCP').conflicts('app')).option('--no-tui', '使用普通日志,不询问、不显示交互界面或自动发起登录').description('启动本地开发调试,监听目录变化并同步插件开发包').addOption(cwdOption).action(withProject(async (project, options)=>{
1564
+ const { runSession } = await import("./session.js");
1565
+ await runSession(project, options, hooks.dev);
1566
+ }));
1567
+ program.command('uninstall').description('从当前用户卸载插件开发包').addOption(cwdOption).action(withProject(async (project)=>{
1568
+ const { runUninstall } = await import("./uninstall.js");
1569
+ await runUninstall(project);
1570
+ }));
1571
+ program.command('categories').description('查询审核类目,输出包含 category_id 和 name 的 JSON 列表').action(async ()=>{
1572
+ const { runCategories } = await import("./categories.js");
1573
+ await runCategories();
1574
+ });
1575
+ program.command('audit').description('完善应用插件草稿、保存版本并提交审核').option('--input <path>', '审核材料 JSON 文件;相对路径按项目根目录解析,跳过交互提示').addOption(cwdOption).action(withProject(async (project, options)=>{
1576
+ const { runAudit } = await import("./command.js");
1577
+ await runAudit(project, options);
1578
+ }));
1579
+ program.command('cancel-audit').description('取消应用插件提审').addOption(cwdOption).action(withProject(async (project)=>{
1580
+ const { runCancelAudit } = await import("./command~2.js");
1581
+ await runCancelAudit(project);
1582
+ }));
1583
+ program.command('skills').description('配置项目开发 Skill').addOption(cwdOption).action(async ({ cwd })=>{
1584
+ await syncProjectSkill(cwd);
1585
+ terminal_log.success('项目开发 Skill 已配置');
1586
+ });
1587
+ const auth = program.command('auth').description('认证管理');
1588
+ auth.command('login').description('登录').addOption(new Option('--begin', '发起设备授权并显示操作说明').conflicts('complete')).addOption(new Option('--complete <device-code>', '使用指定设备码完成授权').conflicts('begin')).action(async (options)=>{
1589
+ const { runLogin } = await import("./command~1.js");
1590
+ await runLogin(options);
1591
+ });
1592
+ auth.command('status').description('查看登录状态').action(async ()=>{
1593
+ const { runStatus } = await import("./command~1.js");
1594
+ await runStatus();
1595
+ });
1596
+ });
1597
+ }
1598
+ };
1599
+ function withProject(run) {
1600
+ return async ({ cwd, ...options })=>{
1601
+ const { PluginProject } = await import("./plugin.js");
1602
+ await run(new PluginProject(cwd), options);
1603
+ };
1604
+ }
1605
+ const platformService = {
1606
+ name: 'platform',
1607
+ title: '插件调试(Skill / MCP)',
1608
+ watch: [
1609
+ "skills",
1610
+ MCP_CONFIG_FILENAME,
1611
+ PLUGIN_MANIFEST_FILENAME
1612
+ ],
1613
+ appliesTo: ()=>true,
1614
+ async start (ctx) {
1615
+ const sync = async ()=>{
1616
+ try {
1617
+ ctx.report({
1618
+ status: 'pending',
1619
+ message: '正在打包开发项目'
1620
+ });
1621
+ const content = await ctx.project.pack({
1622
+ exclude: [
1623
+ DOUBAO_APP_DIRECTORY
1624
+ ]
1625
+ });
1626
+ ctx.signal.throwIfAborted();
1627
+ ctx.report({
1628
+ status: 'pending',
1629
+ message: '正在上传开发包'
1630
+ });
1631
+ const { package_id } = await uploadPackage(`${ctx.project.plugin.name}-${Date.now()}.zip`, content, ctx.signal);
1632
+ if (!package_id) throw new Error('上传失败:服务端未返回 package_id');
1633
+ ctx.signal.throwIfAborted();
1634
+ ctx.report({
1635
+ status: 'pending',
1636
+ message: '正在安装应用插件到当前用户'
1637
+ });
1638
+ await updateUserPlugin({
1639
+ package_id,
1640
+ action: 'Install'
1641
+ }, ctx.signal);
1642
+ ctx.report({
1643
+ status: 'success',
1644
+ message: '已同步'
1645
+ });
1646
+ } catch (error) {
1647
+ if (!ctx.signal.aborted) {
1648
+ ctx.report({
1649
+ status: 'error',
1650
+ message: '同步失败'
1651
+ });
1652
+ ctx.log.error(`平台同步失败:${errorMessage(error)}`);
1653
+ }
1654
+ }
1655
+ };
1656
+ ctx.onFileChange(sync);
1657
+ await sync();
1658
+ }
1659
+ };
1660
+ const platformPlugin = {
1661
+ name: 'platform',
1662
+ setup (api) {
1663
+ api.onDev(()=>[
1664
+ platformService
1665
+ ]);
1666
+ }
1667
+ };
1668
+ async function main_main(argv = process.argv, options = {}) {
1669
+ const dbx = new Command().name('dbx').description(runtime.description).version(runtime.version, '-V, --version').enablePositionalOptions().configureOutput({
1670
+ outputError: (message)=>terminal_log.error(message.trimEnd())
1671
+ }).exitOverride();
1672
+ try {
1673
+ await applyPlugins(dbx, [
1674
+ builtinPlugin,
1675
+ platformPlugin,
1676
+ appPlugin,
1677
+ ...options.plugins ?? []
1678
+ ]);
1679
+ dbx.action(()=>dbx.outputHelp());
1680
+ refreshSkills();
1681
+ await dbx.parseAsync(argv);
1682
+ } catch (error) {
1683
+ if (error instanceof CommanderError) {
1684
+ process.exitCode = error.exitCode;
1685
+ return;
1686
+ }
1687
+ terminal_log.error(error instanceof Error ? error.message : String(error));
1688
+ process.exitCode = 1;
1689
+ }
1690
+ }
1691
+ export { APP_GENERATED_DIRECTORIES, DOUBAO_APP_DIRECTORY, MCP_CONFIG_FILENAME, NotLoggedInError, PLUGIN_MANIFEST_FILENAME, PLUGIN_NAME_PLACEHOLDER, PROJECT_EXCLUDED_DIRECTORIES, PROJECT_EXCLUDED_FILES, SKILL_FILENAME, STANDARD_TEMPLATE_DIRECTORY, TEMPLATES_DIRECTORY, TEMPLATE_SUFFIX, callHook, completeLogin, errorMessage, findApp, findProjectRoot, getPluginDraft, isInteractive, listPluginCategories, listPluginVersions, loadAppModule, loadPackage, login, main_main as main, mcpConfigSchema, pluginManifestSchema, projectNameSchema, runtime, savePluginVersion, startLogin, submitApproval, syncProjectSkill, terminal_log, updateUserPlugin, uploadPackage, upsertPluginDraft, validateMaterial, versionSchema, whoami, withProgress, withdrawApproval };