baiduwangpan-cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,13 +65,25 @@ npm link # 全局注册 bdp 命令
65
65
 
66
66
  ### 2. 配置认证
67
67
 
68
+ **方式一:浏览器自动登录(推荐)**
69
+
70
+ ```bash
71
+ bdp login
72
+ ```
73
+
74
+ 自动启动 Chrome/Edge → 打开百度网盘登录页 → **手机扫码** → 在页面会话内验证登录 → 自动同步引擎。无需 F12。
75
+
76
+ 扫码产生的新会话可能被百度限制为只能在原浏览器上下文中使用。因此 `bdp` 会保留专用 profile,并在登录后将浏览器最小化;PCS API 继续使用 BDUSS/STOKEN,网页和群聊 API 通过该浏览器的 CDP 会话调用。浏览器被关闭后,下次网页 API 调用会使用同一 profile 自动重启。
77
+
78
+ **方式二:手动凭证**
79
+
68
80
  ```bash
69
81
  bdp login --bduss <你的BDUSS> --stoken <你的STOKEN>
70
82
  bdp whoami # 验证
71
83
  ```
72
84
 
73
85
  <details>
74
- <summary>📋 如何获取 BDUSS 和 STOKEN</summary>
86
+ <summary>📋 如何获取 BDUSS 和 STOKEN(手动方式)</summary>
75
87
 
76
88
  1. 浏览器打开 [pan.baidu.com](https://pan.baidu.com) 并登录
77
89
  2. 按 `F12` → **Application** → **Cookies** → `https://pan.baidu.com`
@@ -79,6 +91,8 @@ bdp whoami # 验证
79
91
 
80
92
  > ⚠️ STOKEN 必须在百度网盘页面获取(不是百度首页),值中应包含大写字母。
81
93
 
94
+ > 💡 推荐直接运行 `bdp login`,全程自动化,无需手动操作。
95
+
82
96
  </details>
83
97
 
84
98
  ### 3. 开始使用
@@ -183,7 +197,7 @@ const results = await group.searchFiles('539478953581833690', '倪海厦');
183
197
  │ lib/pan.js │ lib/group.js │
184
198
  │ 全盘文件操作 │ 群聊文件浏览 │
185
199
  ├──────────────────┼──────────────────────────────────┤
186
- │ BaiduPCS-Go │ lib/http.js (curl) │
200
+ │ BaiduPCS-Go │ lib/http.js (curl / browser) │
187
201
  │ (BDUSS 认证) │ mbox API (逆向) │
188
202
  │ locate → dlink │ /mbox/group/listshare │
189
203
  │ upload/download │ /mbox/msg/shareinfo │
@@ -237,14 +251,63 @@ baiduwangpan-cli/
237
251
  ├── lib/
238
252
  │ ├── index.js # 库导出
239
253
  │ ├── config.js # 配置管理 (~/.bdp/config.json)
240
- │ ├── http.js # HTTP 客户端 (curl 封装)
254
+ │ ├── http.js # 网页 API 双传输 (curl / browser)
255
+ │ ├── browser-login.js # 浏览器登录、持久 profile 与 CDP 请求
241
256
  │ ├── pan.js # 网盘操作 (BaiduPCS-Go 桥接)
242
257
  │ └── group.js # 群聊操作 (逆向 mbox API)
258
+ ├── test/ # Node.js 回归测试
259
+ ├── skills/
260
+ │ └── baiduwangpan/ # Agent Skill 源文件 (SKILL.md + reference + scripts)
261
+ ├── baiduwangpan-skill.zip # Agent Skill 打包 (Releases 附件)
243
262
  ├── bdp.js # 早期独立版 (保留参考)
244
263
  ├── bdp-group.js # 早期独立版 (保留参考)
245
264
  └── bdp.py # Python 版 (备用)
246
265
  ```
247
266
 
267
+ ## 🤖 Agent Skill 使用
268
+
269
+ 本仓库附带一个标准格式的 Agent Skill,让 Claude Code / Codex / OpenClaw / Cursor 等 Agent 通过自然语言操作百度网盘。
270
+
271
+ ### 安装 Skill
272
+
273
+ 从 [Releases](https://github.com/NkAntony777/baiduwangpan-cli/releases) 下载 `baiduwangpan-skill.zip`,或直接复制仓库 `skills/baiduwangpan/` 文件夹:
274
+
275
+ ```bash
276
+ # 各平台 skills 目录
277
+ ~/.claude/skills/ # Claude Code
278
+ ~/.codex/skills/ # Codex CLI
279
+ ~/.config/opencode/skills/ # OpenCode
280
+ # 或对应 Agent 的 skills 目录
281
+ ```
282
+
283
+ ### Skill 使用
284
+
285
+ Agent 加载 skill 后,自然语言即可驱动(skill 会自动触发):
286
+
287
+ ```
288
+ 用户: 看看我网盘里有什么
289
+ 用户: 在群里找倪海厦的资料
290
+ 用户: 读一下 /文档/data.csv 前几行
291
+ 用户: 搜索网盘里所有报告文件
292
+ ```
293
+
294
+ skill 会自动执行 `bdp` 命令并返回结构化结果(含 `--json` 输出)。
295
+
296
+ ### Skill 内容
297
+
298
+ ```
299
+ skills/baiduwangpan/
300
+ ├── SKILL.md # 技能定义(触发规则 + 命令规范 + 安全边界)
301
+ ├── reference/
302
+ │ ├── commands.md # 完整命令参考
303
+ │ ├── authentication.md # 凭证获取与配置
304
+ │ ├── examples.md # Agent 使用示例 + JSON 输出格式
305
+ │ └── troubleshooting.md # 故障排查
306
+ └── scripts/
307
+ ├── setup.sh # macOS/Linux 自动安装
308
+ └── setup.ps1 # Windows 自动安装
309
+ ```
310
+
248
311
  ## ⚠️ 注意事项
249
312
 
250
313
  - **BDUSS 有效期**:Cookie 会过期,失效后重新执行 `bdp login`
package/bin/bdp.js CHANGED
@@ -52,12 +52,38 @@ function output(data, jsonFlag, formatter) {
52
52
  }
53
53
  }
54
54
 
55
+ function emitJson(data, args) {
56
+ const json = JSON.stringify(data, null, 2);
57
+ if (args.flags.jsonFile) {
58
+ require("fs").writeFileSync(args.flags.jsonFile, json, "utf-8");
59
+ console.log(args.flags.jsonFile);
60
+ } else {
61
+ console.log(json);
62
+ }
63
+ }
64
+
65
+ function verboseLog(args, msg) {
66
+ if (args.flags.verbose) process.stderr.write(`[verbose] ${msg}\n`);
67
+ }
68
+
55
69
  function parseArgs(argv) {
56
70
  const opts = { _: [], flags: {} };
57
71
  for (let i = 0; i < argv.length; i++) {
58
72
  const arg = argv[i];
59
73
  if (arg === "--json") {
60
74
  opts.flags.json = true;
75
+ } else if (arg === "--legacy-json") {
76
+ opts.flags.legacyJson = true;
77
+ } else if (arg === "--json-file") {
78
+ opts.flags.jsonFile = argv[++i];
79
+ } else if (arg === "--verbose") {
80
+ opts.flags.verbose = true;
81
+ } else if (arg === "--unique") {
82
+ opts.flags.unique = true;
83
+ } else if (arg === "--no-unique") {
84
+ opts.flags.unique = false;
85
+ } else if (arg === "--all") {
86
+ opts.flags.all = true;
61
87
  } else if (arg === "-n" || arg === "--lines") {
62
88
  opts.flags.n = parseInt(argv[++i], 10) || 20;
63
89
  } else if (arg === "-p" || arg === "--path") {
@@ -76,6 +102,22 @@ function parseArgs(argv) {
76
102
  opts.flags.stoken = argv[++i];
77
103
  } else if (arg === "--page") {
78
104
  opts.flags.page = parseInt(argv[++i], 10) || 1;
105
+ } else if (arg === "--page-size") {
106
+ opts.flags.pageSize = parseInt(argv[++i], 10) || 50;
107
+ } else if (arg === "--limit") {
108
+ opts.flags.limit = parseInt(argv[++i], 10) || 50;
109
+ } else if (arg === "--concurrency") {
110
+ opts.flags.concurrency = parseInt(argv[++i], 10) || 4;
111
+ } else if (arg === "--depth") {
112
+ opts.flags.depth = parseInt(argv[++i], 10) || 2;
113
+ } else if (arg === "--max-nodes") {
114
+ opts.flags.maxNodes = parseInt(argv[++i], 10) || 2000;
115
+ } else if (arg === "--from-uk") {
116
+ opts.flags.fromUk = argv[++i];
117
+ } else if (arg === "--msg-id") {
118
+ opts.flags.msgId = argv[++i];
119
+ } else if (arg === "--parent-fs-id") {
120
+ opts.flags.parentFsId = argv[++i];
79
121
  } else if (arg.startsWith("-")) {
80
122
  opts.flags[arg.replace(/^-+/, "")] = true;
81
123
  } else {
@@ -90,14 +132,77 @@ function parseArgs(argv) {
90
132
  const cmds = {};
91
133
 
92
134
  cmds.login = async (args, json) => {
93
- if (args.flags.bduss) config.set("bduss", args.flags.bduss);
94
- if (args.flags.stoken) config.set("stoken", args.flags.stoken);
95
- const cfg = config.get();
96
- output(
97
- { success: true, bduss: cfg.bduss ? "***set***" : "missing", stoken: cfg.stoken ? "***set***" : "missing" },
98
- json,
99
- () => console.log("Credentials saved to " + config.CONFIG_FILE)
100
- );
135
+ // 方式 1: 手动传入凭证
136
+ if (args.flags.bduss || args.flags.stoken) {
137
+ if (!args.flags.bduss || !args.flags.stoken) {
138
+ throw new Error("Manual login requires both --bduss and --stoken");
139
+ }
140
+ config.setAuth({
141
+ bduss: args.flags.bduss,
142
+ stoken: args.flags.stoken,
143
+ webTransport: "curl",
144
+ });
145
+ const cfg = config.get();
146
+ output(
147
+ { success: true, bduss: cfg.bduss ? "***set***" : "missing", stoken: cfg.stoken ? "***set***" : "missing" },
148
+ json,
149
+ () => console.log("Credentials saved to " + config.CONFIG_FILE)
150
+ );
151
+ return;
152
+ }
153
+
154
+ // 方式 2: 浏览器自动登录(扫码,Agent 代劳)
155
+ const { browserLogin } = require("../lib/browser-login");
156
+ const status = (msg) => {
157
+ if (!json) console.log(msg);
158
+ };
159
+
160
+ status("🔐 bdp login: 未提供 --bduss/--stoken,启动浏览器扫码登录...");
161
+ try {
162
+ const previous = config.get();
163
+ const creds = await browserLogin({
164
+ onStatus: status,
165
+ profileDir: previous.browserProfile || undefined,
166
+ port: previous.webTransport === "browser" ? previous.browserPort : undefined,
167
+ });
168
+ config.setAuth({
169
+ bduss: creds.bduss,
170
+ stoken: creds.stoken,
171
+ webTransport: "browser",
172
+ browserProfile: creds.browserProfile,
173
+ browserPort: creds.browserPort,
174
+ });
175
+ status("✅ 登录成功,凭证已保存到 " + config.CONFIG_FILE);
176
+
177
+ // 同步登录 BaiduPCS-Go 引擎(全盘操作依赖)
178
+ try {
179
+ const { spawnSync } = require("child_process");
180
+ const cfg = config.get();
181
+ const loginResult = spawnSync(cfg.pcsPath, ["login", `-bduss=${creds.bduss}`, `-stoken=${creds.stoken}`], {
182
+ encoding: "utf-8",
183
+ timeout: 30000,
184
+ });
185
+ if (loginResult.stdout && loginResult.stdout.includes("登录成功")) {
186
+ status("✅ BaiduPCS-Go 引擎已同步登录");
187
+ } else {
188
+ status("⚠️ BaiduPCS-Go 引擎登录失败: " + (loginResult.stdout || loginResult.stderr || "未知错误").trim().substring(0, 100));
189
+ }
190
+ } catch (e) {
191
+ status("⚠️ BaiduPCS-Go 引擎登录异常: " + e.message);
192
+ }
193
+
194
+ if (json) {
195
+ console.log(JSON.stringify({ success: true, method: "browser-qr", bduss: "***set***", stoken: "***set***" }));
196
+ }
197
+ } catch (e) {
198
+ if (json) {
199
+ console.log(JSON.stringify({ error: e.message }));
200
+ } else {
201
+ console.error(`[ERROR] ${e.message}`);
202
+ console.error(" 提示: 也可手动方式 bdp login --bduss <值> --stoken <值>");
203
+ }
204
+ process.exit(1);
205
+ }
101
206
  };
102
207
 
103
208
  cmds.whoami = async (args, json) => {
@@ -107,6 +212,7 @@ cmds.whoami = async (args, json) => {
107
212
  loggedIn: !!(cfg.bduss && cfg.stoken),
108
213
  bduss: cfg.bduss ? "***set***" : "missing",
109
214
  stoken: cfg.stoken ? "***set***" : "missing",
215
+ webTransport: cfg.webTransport,
110
216
  pcsPath: cfg.pcsPath,
111
217
  configFile: config.CONFIG_FILE,
112
218
  },
@@ -115,6 +221,7 @@ cmds.whoami = async (args, json) => {
115
221
  console.log(`Status: ${d.loggedIn ? "✅ Logged in" : "❌ Not logged in"}`);
116
222
  console.log(`BDUSS: ${d.bduss}`);
117
223
  console.log(`STOKEN: ${d.stoken}`);
224
+ console.log(`Web API: ${d.webTransport}`);
118
225
  console.log(`PCS Path: ${d.pcsPath}`);
119
226
  console.log(`Config: ${d.configFile}`);
120
227
  }
@@ -127,6 +234,7 @@ cmds.config = async (args, json) => {
127
234
  console.log("Configuration:");
128
235
  console.log(` bduss: ${d.bduss ? d.bduss.substring(0, 10) + "..." : "(not set)"}`);
129
236
  console.log(` stoken: ${d.stoken ? d.stoken.substring(0, 10) + "..." : "(not set)"}`);
237
+ console.log(` web API: ${d.webTransport}`);
130
238
  console.log(` pcsPath: ${d.pcsPath}`);
131
239
  console.log(` ua: ${d.ua.substring(0, 40)}...`);
132
240
  console.log(` maxBytes: ${d.maxBytes}`);
@@ -284,29 +392,119 @@ cmds.gshares = async (args, json) => {
284
392
  cmds.gls = async (args, json) => {
285
393
  const gid = args._[0];
286
394
  const fsId = args._[1];
287
- if (!gid || !fsId) { console.error("Usage: bdp gls <gid> <fs_id>"); process.exit(1); }
288
- const result = await group.listFiles(gid, fsId, { page: args.flags.page || 1 });
289
- output(result, json, (d) => {
290
- console.log(`${d.files.length} items${d.hasMore ? " (has more — use --page)" : ""}:\n`);
291
- d.files.forEach((f) => {
395
+ if (!gid || !fsId) { console.error("Usage: bdp gls <gid> <fs_id> [--page N] [--page-size N]"); process.exit(1); }
396
+
397
+ const opts = {
398
+ page: args.flags.page || 1,
399
+ pageSize: args.flags.pageSize || 50,
400
+ };
401
+ if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
402
+ if (args.flags.msgId) opts.msgId = args.flags.msgId;
403
+ if (args.flags.parentFsId) opts.parentFsId = args.flags.parentFsId;
404
+
405
+ verboseLog(args, `gls gid=${gid} fsId=${fsId} page=${opts.page} pageSize=${opts.pageSize}`);
406
+ const result = await group.listFiles(gid, fsId, opts);
407
+
408
+ if (args.flags.legacyJson) {
409
+ emitJson(result.files, args);
410
+ return;
411
+ }
412
+
413
+ if (result.fallback) {
414
+ verboseLog(args, `fallback: ${result.fallback.reason} -> ${result.fallback.resolvedFsId} (${result.fallback.level})`);
415
+ }
416
+
417
+ if (args.flags.jsonFile || json) {
418
+ emitJson(result, args);
419
+ } else {
420
+ if (result.fallback) {
421
+ console.log(`⚠️ fallback: ${result.fallback.reason} — 已回退到 ${result.fallback.resolvedFsId} (${result.fallback.level})`);
422
+ }
423
+ console.log(`${result.files.length} items${result.hasMore ? " (has more — use --page)" : ""}:\n`);
424
+ result.files.forEach((f) => {
292
425
  const dir = f.isDir ? "[DIR] " : " ";
293
426
  console.log(` ${dir}${f.name} ${formatSize(f.size)} fs_id:${f.fsId}`);
294
427
  });
295
- });
428
+ }
429
+ };
430
+
431
+ cmds.gtree = async (args, json) => {
432
+ const gid = args._[0];
433
+ if (!gid) { console.error("Usage: bdp gtree <gid> [--depth N] [--concurrency N] [--max-nodes N]"); process.exit(1); }
434
+
435
+ const opts = {
436
+ depth: args.flags.depth || 2,
437
+ concurrency: args.flags.concurrency || 4,
438
+ maxNodes: args.flags.maxNodes || 2000,
439
+ };
440
+ verboseLog(args, `gtree gid=${gid} depth=${opts.depth} concurrency=${opts.concurrency} maxNodes=${opts.maxNodes}`);
441
+ const result = await group.treeFiles(gid, opts);
442
+
443
+ if (args.flags.jsonFile || json) {
444
+ emitJson(result, args);
445
+ } else {
446
+ console.log(`${result.tree.length} nodes (depth ${result.depth}, ${result.failed.length} failed${result.truncated ? ", truncated" : ""}):\n`);
447
+ result.tree.forEach((n) => {
448
+ const dir = n.isDir ? "[DIR] " : " ";
449
+ console.log(` ${dir}${n.path || n.name} ${formatSize(n.size)} fs_id:${n.fsId}`);
450
+ });
451
+ }
296
452
  };
297
453
 
298
454
  cmds.gsearch = async (args, json) => {
299
455
  const gid = args._[0];
300
456
  const keyword = args._[1];
301
- if (!gid || !keyword) { console.error("Usage: bdp gsearch <gid> <keyword>"); process.exit(1); }
302
- const results = await group.searchFiles(gid, keyword);
303
- output(results, json, (list) => {
304
- console.log(`${list.length} matches for "${keyword}":\n`);
305
- list.forEach((r) => {
457
+ if (!gid || !keyword) { console.error("Usage: bdp gsearch <gid> <keyword> [--page N] [--limit N]"); process.exit(1); }
458
+
459
+ const opts = {
460
+ page: args.flags.page || 1,
461
+ limit: args.flags.limit || 50,
462
+ concurrency: args.flags.concurrency || 4,
463
+ unique: args.flags.unique !== false,
464
+ all: args.flags.all === true,
465
+ depth: args.flags.depth || 1,
466
+ };
467
+
468
+ verboseLog(args, `gsearch gid=${gid} keyword="${keyword}" page=${opts.page} limit=${opts.limit} concurrency=${opts.concurrency} unique=${opts.unique} all=${opts.all}`);
469
+ const result = await group.searchFiles(gid, keyword, opts);
470
+
471
+ if (result.partial) {
472
+ verboseLog(args, `partial: ${result.failedShares}/${result.totalShares} shares failed`);
473
+ }
474
+
475
+ if (args.flags.legacyJson) {
476
+ emitJson(result.results, args);
477
+ return;
478
+ }
479
+
480
+ if (args.flags.jsonFile || json) {
481
+ emitJson(result, args);
482
+ } else {
483
+ console.log(`${result.returned} matches for "${keyword}"${result.hasMore ? " (has more — use --page " + (result.nextPage || "?") + ")" : ""}:\n`);
484
+ result.results.forEach((r) => {
306
485
  const dir = r.isDir ? "[DIR] " : " ";
307
486
  console.log(` ${dir}${r.path || r.name} ${formatSize(r.size)} fs_id:${r.fsId}`);
308
487
  });
309
- });
488
+ if (result.partial) {
489
+ console.error(`[verbose] ${result.failedShares} shares failed (partial result)`);
490
+ }
491
+ }
492
+ };
493
+
494
+ cmds.error = async (args, json) => {
495
+ const code = args._[0] || "-3";
496
+ const ERRORS = {
497
+ "-3": "群分享 API 拒绝请求。已观察到大目录、错误的 msgId/fromUk、过期分享都可能触发。\n" +
498
+ "处理:减小 --page-size(自动重试 50→20→10),或携带 gsearch 返回的 fromUk/msgId/parentFsId 信息重试;\n" +
499
+ " 若回退到父目录(fallback level=parent),Agent 应从父目录换路径继续遍历。",
500
+ "-6": "接口鉴权失败。通常为 access_token/cookie 失效,请重新执行 bdp login。",
501
+ };
502
+ const msg = ERRORS[code] || `未知错误码 ${code},请提交 Issue: https://github.com/NkAntony777/baiduwangpan-cli/issues`;
503
+ if (args.flags.jsonFile || json) {
504
+ emitJson({ code, description: msg }, args);
505
+ } else {
506
+ console.log(msg);
507
+ }
310
508
  };
311
509
 
312
510
  // ── Main ───────────────────────────────────────────────
@@ -332,22 +530,38 @@ PAN FILE OPERATIONS (全盘)
332
530
  GROUP CHAT OPERATIONS (群聊)
333
531
  groups List all groups
334
532
  gshares <gid> List share libraries in group
533
+ gtree <gid> Build directory tree (BFS)
534
+ [--depth N] [--concurrency N] [--max-nodes N]
335
535
  gls <gid> <fs_id> Browse files in a share
536
+ [--page N] [--page-size N] [--from-uk X] [--msg-id Y] [--parent-fs-id Z]
336
537
  gsearch <gid> <keyword> Search group file names
538
+ [--page N] [--limit N] [--depth N] [--concurrency N] [--no-unique] [--all]
539
+ error <code> Explain an error code (e.g. -3)
337
540
 
338
541
  CONFIGURATION
339
- login --bduss X --stoken Y Save credentials
542
+ login Browser QR login (auto-detect cookies)
543
+ login --bduss X --stoken Y Save credentials manually
340
544
  whoami Check login status
341
545
  config Show configuration
342
546
 
343
547
  OPTIONS
344
548
  --json Output JSON (for Agent consumption)
549
+ --json-file <path> Write JSON to file (UTF-8, bypasses console encoding)
550
+ --legacy-json Output bare array (gls/gsearch legacy format)
551
+ --verbose Write progress/log to stderr only
345
552
  -n <N> Number of lines (head/tail)
346
553
  -p <path> Search path (search)
347
554
  -i Ignore case (grep)
348
555
  -N Show line numbers (cat/grep)
349
556
  -o <dir> Output directory (get)
350
- --page <N> Page number (gls)
557
+ --page <N> Page number (gls/gsearch)
558
+ --page-size <N> Page size for gls (default 50, max 100)
559
+ --limit <N> Page size for gsearch (default 50)
560
+ --depth <N> Recursive search/tree depth (gsearch default 1, gtree default 2)
561
+ --max-nodes <N> Tree node cap (gtree default 2000)
562
+ --concurrency <N> Parallel share scans for gsearch/gtree (default 4)
563
+ --no-unique Keep duplicates from different msgId (gsearch)
564
+ --all Fetch all results, no paging (gsearch, not Agent default)
351
565
 
352
566
  EXAMPLES
353
567
  bdp login --bduss abc123 --stoken def456
@@ -357,7 +571,10 @@ EXAMPLES
357
571
  bdp groups --json
358
572
  bdp gshares 539478953581833690
359
573
  bdp gls 539478953581833690 742474845517885 --json
360
- bdp gsearch 539478953581833690 "倪海厦"`;
574
+ bdp gls 539478953581833690 292608024165826 --from-uk 2642611875 --msg-id 5069931974377329661 --json
575
+ bdp gsearch 539478953581833690 "倪海厦" --limit 20 --json
576
+ bdp gsearch 539478953581833690 "古籍" --page 2 --limit 20 --json-file result.json
577
+ bdp error -3`;
361
578
 
362
579
  async function main() {
363
580
  const argv = process.argv.slice(2);
@@ -372,6 +589,7 @@ async function main() {
372
589
  process.exit(0);
373
590
  }
374
591
 
592
+ let exitCode = 0;
375
593
  try {
376
594
  await handler(args, json);
377
595
  } catch (e) {
@@ -380,8 +598,13 @@ async function main() {
380
598
  } else {
381
599
  console.error(`[ERROR] ${e.message}`);
382
600
  }
383
- process.exit(1);
601
+ exitCode = 1;
602
+ } finally {
603
+ try {
604
+ require("../lib/browser-login").disconnectBrowserSession();
605
+ } catch {}
384
606
  }
607
+ process.exit(exitCode);
385
608
  }
386
609
 
387
610
  main();