baiduwangpan-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NkAntony777
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,263 @@
1
+ <div align="center">
2
+
3
+ # baiduwangpan-cli
4
+
5
+ **百度网盘 CLI — 全盘读写 · 免下载读取 · 群聊文件浏览**
6
+
7
+ 专为 AI Agent 设计的百度网盘命令行工具
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen)](https://nodejs.org)
11
+ [![npm version](https://img.shields.io/npm/v/baiduwangpan-cli)](https://www.npmjs.com/package/baiduwangpan-cli)
12
+ [![npm downloads](https://img.shields.io/npm/dm/baiduwangpan-cli)](https://www.npmjs.com/package/baiduwangpan-cli)
13
+ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-blue)]()
14
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## ✨ 特性
21
+
22
+ - **🗂️ 全盘访问** — 不受 `/apps/` 目录限制,读写网盘任意路径
23
+ - **📄 免下载读取** — 通过 HTTP Range 直接读取文件内容到 stdout,无需下载整个文件
24
+ - **👥 群聊文件浏览** — 逆向 mbox API,列出群组、浏览群内分享库、搜索群文件
25
+ - **🤖 Agent 友好** — 所有命令支持 `--json` 结构化输出,零解析成本
26
+ - **🔧 双模式** — 既是 CLI 工具,也可作为 Node.js 库 `require()` 使用
27
+ - **🔐 凭证安全** — 配置存储在 `~/.bdp/config.json`,不写入代码或日志
28
+
29
+ ## 📌 能力一览
30
+
31
+ | 能力 | 命令 | 说明 |
32
+ |:-----|:------|:-----|
33
+ | 列目录 | `bdp ls /` | 全盘任意目录 |
34
+ | 搜索文件名 | `bdp search "报告"` | 递归全盘搜索 |
35
+ | **读取文件内容** | `bdp cat /文档/data.json` | 免下载,限 1MB |
36
+ | **读取前 N 行** | `bdp head -n 50 /文档/log.txt` | 免下载 |
37
+ | **读取后 N 行** | `bdp tail -n 30 /文档/log.txt` | 免下载 |
38
+ | **搜索文件内容** | `bdp grep "关键词" /文档/file.txt` | 免下载 |
39
+ | 预览文件 | `bdp peek /文档/report.pdf` | 类型检测 + 前 10 行 |
40
+ | 下载 | `bdp get /文档/file.zip` | 保存到本地 |
41
+ | 上传 | `bdp put ./file.txt /文档/` | 全盘任意位置 |
42
+ | **列出群组** | `bdp groups` | 所有群聊 + gid |
43
+ | **群内分享库** | `bdp gshares <gid>` | 顶层分享目录 |
44
+ | **浏览群文件** | `bdp gls <gid> <fs_id>` | 分页 + 递归 |
45
+ | **搜索群文件** | `bdp gsearch <gid> "关键词"` | 按文件名搜索 |
46
+
47
+ ## 🚀 快速开始
48
+
49
+ ### 1. 安装
50
+
51
+ ```bash
52
+ npm install -g baiduwangpan-cli
53
+ ```
54
+
55
+ 安装时会**自动下载** BaiduPCS-Go 引擎(支持 GitHub 镜像加速,国内也能装)。也支持手动克隆:
56
+
57
+ ```bash
58
+ git clone https://github.com/NkAntony777/baiduwangpan-cli.git
59
+ cd baiduwangpan-cli
60
+ npm install # postinstall 自动下载引擎
61
+ npm link # 全局注册 bdp 命令
62
+ ```
63
+
64
+ > **依赖**: Node.js 14+ 和 `curl`(已在 Windows 10+ / macOS / 大多数 Linux 预装)
65
+
66
+ ### 2. 配置认证
67
+
68
+ ```bash
69
+ bdp login --bduss <你的BDUSS> --stoken <你的STOKEN>
70
+ bdp whoami # 验证
71
+ ```
72
+
73
+ <details>
74
+ <summary>📋 如何获取 BDUSS 和 STOKEN</summary>
75
+
76
+ 1. 浏览器打开 [pan.baidu.com](https://pan.baidu.com) 并登录
77
+ 2. 按 `F12` → **Application** → **Cookies** → `https://pan.baidu.com`
78
+ 3. 找到 **BDUSS** 和 **STOKEN** 的值,复制
79
+
80
+ > ⚠️ STOKEN 必须在百度网盘页面获取(不是百度首页),值中应包含大写字母。
81
+
82
+ </details>
83
+
84
+ ### 3. 开始使用
85
+
86
+ ```bash
87
+ # 浏览网盘
88
+ bdp ls /
89
+ bdp search "中医"
90
+
91
+ # 免下载读取文件
92
+ bdp head -n 10 /文档/data.csv
93
+ bdp cat /文档/config.json
94
+
95
+ # 浏览群聊文件
96
+ bdp groups
97
+ bdp gshares 539478953581833690
98
+ bdp gls 539478953581833690 742474845517885
99
+ bdp gsearch 539478953581833690 "倪海厦"
100
+ ```
101
+
102
+ ## 📖 完整命令参考
103
+
104
+ ### 网盘文件操作
105
+
106
+ ```
107
+ bdp ls [path] 列出目录
108
+ bdp search <keyword> [-p dir] 搜索文件名
109
+ bdp cat <path> 读取文件内容 (免下载, 限 1MB)
110
+ bdp head [-n N] <path> 读取前 N 行 (默认 20)
111
+ bdp tail [-n N] <path> 读取后 N 行 (默认 20)
112
+ bdp grep <pattern> <path> 搜索文件内容
113
+ bdp peek <path> 预览文件信息 + 前 10 行
114
+ bdp get <path> [-o dir] 下载文件
115
+ bdp put <local> <remote> 上传文件
116
+ bdp mkdir <path> 创建目录
117
+ bdp rm <path> 删除文件/目录
118
+ ```
119
+
120
+ ### 群聊文件操作
121
+
122
+ ```
123
+ bdp groups 列出所有群组
124
+ bdp gshares <gid> 列出群内分享库
125
+ bdp gls <gid> <fs_id> [--page N] 浏览分享库内容
126
+ bdp gsearch <gid> <keyword> 搜索群文件名
127
+ ```
128
+
129
+ ### 通用选项
130
+
131
+ ```
132
+ --json JSON 结构化输出 (Agent 友好)
133
+ -n <N> 行数 (head/tail, 默认 20)
134
+ -p <path> 搜索路径 (search, 默认 /)
135
+ -i 忽略大小写 (grep)
136
+ -N 显示行号 (cat/grep)
137
+ -o <dir> 输出目录 (get)
138
+ --page <N> 页码 (gls)
139
+ ```
140
+
141
+ ## 🤖 Agent 集成示例
142
+
143
+ 所有命令支持 `--json` 输出,适合 AI Agent 直接调用和解析:
144
+
145
+ ```bash
146
+ # Agent 获取群组列表 (JSON)
147
+ bdp groups --json
148
+ # → [{"gid":"539478953581833690","name":"国学文化SVIP13",...}]
149
+
150
+ # Agent 读取文件内容 (JSON)
151
+ bdp head -n 5 /文档/data.csv --json
152
+ # → {"path":"/文档/data.csv","lines":5,"content":["header","row1","row2",...]}
153
+
154
+ # Agent 搜索群文件 (JSON)
155
+ bdp gsearch 539478953581833690 "倪海厦" --json
156
+ # → [{"name":"N 倪海厦","path":"/全店svip200位大师课(共16T)/N 倪海厦","isDir":true,...}]
157
+ ```
158
+
159
+ ### 作为 Node.js 库使用
160
+
161
+ ```javascript
162
+ const { pan, group } = require('baiduwangpan-cli');
163
+
164
+ // 免下载读取文件
165
+ const content = pan.cat('/文档/data.json');
166
+
167
+ // 搜索群聊文件
168
+ const results = await group.searchFiles('539478953581833690', '倪海厦');
169
+ ```
170
+
171
+ ## 🏗️ 架构
172
+
173
+ ```
174
+ ┌─────────────────────────────────────────────────────┐
175
+ │ Agent / 用户 │
176
+ ├─────────────┬───────────────────────────────────────┤
177
+ │ CLI 模式 │ 库模式 (require) │
178
+ │ bdp <cmd> │ const {pan, group} = require() │
179
+ ├─────────────┴───────────────────────────────────────┤
180
+ │ bin/bdp.js │
181
+ │ (统一命令解析 + 路由) │
182
+ ├──────────────────┬──────────────────────────────────┤
183
+ │ lib/pan.js │ lib/group.js │
184
+ │ 全盘文件操作 │ 群聊文件浏览 │
185
+ ├──────────────────┼──────────────────────────────────┤
186
+ │ BaiduPCS-Go │ lib/http.js (curl) │
187
+ │ (BDUSS 认证) │ mbox API (逆向) │
188
+ │ locate → dlink │ /mbox/group/listshare │
189
+ │ upload/download │ /mbox/msg/shareinfo │
190
+ ├──────────────────┴──────────────────────────────────┤
191
+ │ 百度网盘 CDN / API │
192
+ └─────────────────────────────────────────────────────┘
193
+ ```
194
+
195
+ ### 免下载读取原理
196
+
197
+ ```
198
+ bdp cat /文档/报告.txt
199
+ │
200
+ ├── BaiduPCS-Go locate /文档/报告.txt
201
+ │ → 获取 CDN 直链 dlink (有效期 8h)
202
+ │
203
+ ├── curl -r 0-1048575 <dlink>
204
+ │ → HTTP Range 请求,只读取前 1MB
205
+ │ → 百度 CDN 返回 206 Partial Content
206
+ │
207
+ └── stdout 输出文件内容
208
+ → Agent 直接读取,无需下载整个文件
209
+ ```
210
+
211
+ ### 群聊 API 链路(逆向 `/mbox/`)
212
+
213
+ ```
214
+ bdp groups bdp gshares <gid>
215
+ │ │
216
+ ├── /mbox/group/list ├── /mbox/group/listshare
217
+ │ → 获取全部群组 (gid) │ → 获取分享库 (msg_id + from_uk + fs_id)
218
+ │ │
219
+ └───────────────────────────────────┘
220
+ ↓
221
+ bdp gls <gid> <fs_id>
222
+ │
223
+ ├── /mbox/msg/shareinfo
224
+ │ → 浏览文件/子目录 (分页)
225
+ │ → 递归遍历目录树
226
+ ↓
227
+ 文件列表 (名称/大小/类型/fs_id)
228
+ ```
229
+
230
+ ## 📁 项目结构
231
+
232
+ ```
233
+ baiduwangpan-cli/
234
+ ├── package.json # npm 配置 (bin: bdp)
235
+ ├── bin/
236
+ │ └── bdp.js # CLI 入口 (统一命令解析)
237
+ ├── lib/
238
+ │ ├── index.js # 库导出
239
+ │ ├── config.js # 配置管理 (~/.bdp/config.json)
240
+ │ ├── http.js # HTTP 客户端 (curl 封装)
241
+ │ ├── pan.js # 网盘操作 (BaiduPCS-Go 桥接)
242
+ │ └── group.js # 群聊操作 (逆向 mbox API)
243
+ ├── bdp.js # 早期独立版 (保留参考)
244
+ ├── bdp-group.js # 早期独立版 (保留参考)
245
+ └── bdp.py # Python 版 (备用)
246
+ ```
247
+
248
+ ## ⚠️ 注意事项
249
+
250
+ - **BDUSS 有效期**:Cookie 会过期,失效后重新执行 `bdp login`
251
+ - **下载限速**:非 SVIP 用户下载受百度限速策略限制
252
+ - **cat 限制**:为防止误读超大文件,`cat` 默认限制 1MB,可通过 `BDP_MAX_CAT_BYTES` 环境变量调整
253
+ - **群聊文件**:群聊 API 为逆向获取,可能随百度服务端变更而失效
254
+
255
+ ## 🤝 致谢
256
+
257
+ - [BaiduPCS-Go](https://github.com/qjfoidnh/BaiduPCS-Go) — 强大的百度网盘 Go 客户端,本工具的底层引擎
258
+ - [hustuhao/bdntoy](https://github.com/hustuhao/bdntoy) — mbox API 逆向参考
259
+ - 所有百度网盘逆向工程社区的贡献者
260
+
261
+ ## 📄 License
262
+
263
+ [MIT](LICENSE)
package/bin/bdp.js ADDED
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * bdp — 百度网盘统一 CLI 工具
5
+ *
6
+ * 网盘文件操作:
7
+ * bdp ls [path] 列出目录
8
+ * bdp search <keyword> [-p dir] 全盘搜索
9
+ * bdp cat <path> 读取文件内容(免下载)
10
+ * bdp head [-n N] <path> 读取前 N 行
11
+ * bdp tail [-n N] <path> 读取后 N 行
12
+ * bdp grep <pattern> <path> 搜索文件内容
13
+ * bdp peek <path> 预览文件信息
14
+ * bdp get <path> [-o dir] 下载文件
15
+ * bdp put <local> <remote> 上传文件
16
+ * bdp mkdir <path> 创建目录
17
+ * bdp rm <path> 删除文件
18
+ *
19
+ * 群聊文件操作:
20
+ * bdp groups 列出所有群组
21
+ * bdp gshares <gid> 列出群内分享库
22
+ * bdp gls <gid> <fs_id> 浏览分享库内容
23
+ * bdp gsearch <gid> <keyword> 搜索群文件
24
+ *
25
+ * 配置:
26
+ * bdp login --bduss X --stoken Y 设置认证
27
+ * bdp whoami 查看当前状态
28
+ * bdp config 查看配置
29
+ *
30
+ * 全局选项:
31
+ * --json JSON 输出(Agent 友好)
32
+ */
33
+
34
+ const { config } = require("../lib");
35
+ const pan = require("../lib/pan");
36
+ const group = require("../lib/group");
37
+
38
+ // ── Utils ──────────────────────────────────────────────
39
+
40
+ function formatSize(bytes) {
41
+ if (!bytes || bytes === 0) return "-";
42
+ const units = ["B", "KB", "MB", "GB", "TB"];
43
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
44
+ return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
45
+ }
46
+
47
+ function output(data, jsonFlag, formatter) {
48
+ if (jsonFlag) {
49
+ console.log(JSON.stringify(data, null, 2));
50
+ } else if (formatter) {
51
+ formatter(data);
52
+ }
53
+ }
54
+
55
+ function parseArgs(argv) {
56
+ const opts = { _: [], flags: {} };
57
+ for (let i = 0; i < argv.length; i++) {
58
+ const arg = argv[i];
59
+ if (arg === "--json") {
60
+ opts.flags.json = true;
61
+ } else if (arg === "-n" || arg === "--lines") {
62
+ opts.flags.n = parseInt(argv[++i], 10) || 20;
63
+ } else if (arg === "-p" || arg === "--path") {
64
+ opts.flags.p = argv[++i];
65
+ } else if (arg === "-k" || arg === "--keyword") {
66
+ opts.flags.k = argv[++i];
67
+ } else if (arg === "-i" || arg === "--ignore-case") {
68
+ opts.flags.i = true;
69
+ } else if (arg === "--number" || arg === "-N") {
70
+ opts.flags.number = true;
71
+ } else if (arg === "-o" || arg === "--output") {
72
+ opts.flags.o = argv[++i];
73
+ } else if (arg === "--bduss") {
74
+ opts.flags.bduss = argv[++i];
75
+ } else if (arg === "--stoken") {
76
+ opts.flags.stoken = argv[++i];
77
+ } else if (arg === "--page") {
78
+ opts.flags.page = parseInt(argv[++i], 10) || 1;
79
+ } else if (arg.startsWith("-")) {
80
+ opts.flags[arg.replace(/^-+/, "")] = true;
81
+ } else {
82
+ opts._.push(arg);
83
+ }
84
+ }
85
+ return opts;
86
+ }
87
+
88
+ // ── Commands ───────────────────────────────────────────
89
+
90
+ const cmds = {};
91
+
92
+ 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
+ );
101
+ };
102
+
103
+ cmds.whoami = async (args, json) => {
104
+ const cfg = config.get();
105
+ output(
106
+ {
107
+ loggedIn: !!(cfg.bduss && cfg.stoken),
108
+ bduss: cfg.bduss ? "***set***" : "missing",
109
+ stoken: cfg.stoken ? "***set***" : "missing",
110
+ pcsPath: cfg.pcsPath,
111
+ configFile: config.CONFIG_FILE,
112
+ },
113
+ json,
114
+ (d) => {
115
+ console.log(`Status: ${d.loggedIn ? "✅ Logged in" : "❌ Not logged in"}`);
116
+ console.log(`BDUSS: ${d.bduss}`);
117
+ console.log(`STOKEN: ${d.stoken}`);
118
+ console.log(`PCS Path: ${d.pcsPath}`);
119
+ console.log(`Config: ${d.configFile}`);
120
+ }
121
+ );
122
+ };
123
+
124
+ cmds.config = async (args, json) => {
125
+ const cfg = config.get();
126
+ output(cfg, json, (d) => {
127
+ console.log("Configuration:");
128
+ console.log(` bduss: ${d.bduss ? d.bduss.substring(0, 10) + "..." : "(not set)"}`);
129
+ console.log(` stoken: ${d.stoken ? d.stoken.substring(0, 10) + "..." : "(not set)"}`);
130
+ console.log(` pcsPath: ${d.pcsPath}`);
131
+ console.log(` ua: ${d.ua.substring(0, 40)}...`);
132
+ console.log(` maxBytes: ${d.maxBytes}`);
133
+ console.log(` file: ${config.CONFIG_FILE}`);
134
+ });
135
+ };
136
+
137
+ cmds.ls = async (args, json) => {
138
+ const dirPath = args._[0] || "/";
139
+ const raw = pan.ls(dirPath);
140
+ if (json) {
141
+ console.log(JSON.stringify({ path: dirPath, raw }));
142
+ } else {
143
+ console.log(raw);
144
+ }
145
+ };
146
+
147
+ cmds.search = async (args, json) => {
148
+ const keyword = args.flags.k || args._[0] || "";
149
+ const searchPath = args.flags.p || "/";
150
+ if (!keyword) { console.error("Usage: bdp search <keyword> [-p /path]"); process.exit(1); }
151
+ const raw = pan.search(keyword, searchPath);
152
+ if (json) {
153
+ console.log(JSON.stringify({ keyword, path: searchPath, raw }));
154
+ } else {
155
+ console.log(raw);
156
+ }
157
+ };
158
+
159
+ cmds.cat = async (args, json) => {
160
+ const filePath = args._[0];
161
+ if (!filePath) { console.error("Usage: bdp cat <path>"); process.exit(1); }
162
+ const text = pan.cat(filePath);
163
+ if (json) {
164
+ console.log(JSON.stringify({ path: filePath, content: text }));
165
+ } else if (args.flags.number) {
166
+ text.split("\n").forEach((line, i) => {
167
+ console.log(`${String(i + 1).padStart(6)} ${line}`);
168
+ });
169
+ } else {
170
+ process.stdout.write(text);
171
+ }
172
+ };
173
+
174
+ cmds.head = async (args, json) => {
175
+ const filePath = args._[0];
176
+ if (!filePath) { console.error("Usage: bdp head [-n N] <path>"); process.exit(1); }
177
+ const n = args.flags.n || 20;
178
+ const lines = pan.head(filePath, n);
179
+ if (json) {
180
+ console.log(JSON.stringify({ path: filePath, lines: n, content: lines }));
181
+ } else {
182
+ lines.forEach((l) => console.log(l));
183
+ }
184
+ };
185
+
186
+ cmds.tail = async (args, json) => {
187
+ const filePath = args._[0];
188
+ if (!filePath) { console.error("Usage: bdp tail [-n N] <path>"); process.exit(1); }
189
+ const n = args.flags.n || 20;
190
+ const lines = pan.tail(filePath, n);
191
+ if (json) {
192
+ console.log(JSON.stringify({ path: filePath, lines: n, content: lines }));
193
+ } else {
194
+ lines.forEach((l) => console.log(l));
195
+ }
196
+ };
197
+
198
+ cmds.grep = async (args, json) => {
199
+ const pattern = args._[0];
200
+ const filePath = args._[1];
201
+ if (!pattern || !filePath) { console.error("Usage: bdp grep <pattern> <path>"); process.exit(1); }
202
+ const matches = pan.grep(pattern, filePath, { ignoreCase: args.flags.i });
203
+ if (json) {
204
+ console.log(JSON.stringify({ path: filePath, pattern, matches }));
205
+ } else {
206
+ matches.forEach((m) => {
207
+ if (args.flags.number) console.log(`${filePath}:${m.line}:${m.content}`);
208
+ else console.log(m.content);
209
+ });
210
+ }
211
+ };
212
+
213
+ cmds.peek = async (args, json) => {
214
+ const filePath = args._[0];
215
+ if (!filePath) { console.error("Usage: bdp peek <path>"); process.exit(1); }
216
+ const info = pan.peek(filePath);
217
+ output(info, json, (d) => {
218
+ console.log(`Path: ${d.path}`);
219
+ console.log(`Size: ${d.size ? formatSize(d.size) : "unknown"}`);
220
+ console.log(`Type: ${d.type || (d.isDir ? "directory" : "unknown")}`);
221
+ console.log(`Dlink: ${d.dlink ? "✅" : "❌"}`);
222
+ if (d.preview) {
223
+ console.log(`\n── Preview ──`);
224
+ d.preview.forEach((l) => console.log(l));
225
+ }
226
+ });
227
+ };
228
+
229
+ cmds.get = async (args, json) => {
230
+ const filePath = args._[0];
231
+ if (!filePath) { console.error("Usage: bdp get <path> [-o dir]"); process.exit(1); }
232
+ const saveTo = args.flags.o;
233
+ const raw = pan.download(filePath, saveTo);
234
+ output({ path: filePath, savedTo: saveTo || "default", raw }, json, () => console.log(raw));
235
+ };
236
+
237
+ cmds.put = async (args, json) => {
238
+ const local = args._[0];
239
+ const remote = args._[1];
240
+ if (!local || !remote) { console.error("Usage: bdp put <local> <remote>"); process.exit(1); }
241
+ const raw = pan.upload(local, remote);
242
+ output({ local, remote, raw }, json, () => console.log(raw));
243
+ };
244
+
245
+ cmds.mkdir = async (args, json) => {
246
+ const dirPath = args._[0];
247
+ if (!dirPath) { console.error("Usage: bdp mkdir <path>"); process.exit(1); }
248
+ const raw = pan.mkdir(dirPath);
249
+ output({ path: dirPath, raw }, json, () => console.log(raw));
250
+ };
251
+
252
+ cmds.rm = async (args, json) => {
253
+ const filePath = args._[0];
254
+ if (!filePath) { console.error("Usage: bdp rm <path>"); process.exit(1); }
255
+ const raw = pan.remove(filePath);
256
+ output({ path: filePath, raw }, json, () => console.log(raw));
257
+ };
258
+
259
+ // ── Group Commands ─────────────────────────────────────
260
+
261
+ cmds.groups = async (args, json) => {
262
+ const groups = await group.listGroups();
263
+ output(groups, json, (list) => {
264
+ console.log(`${list.length} groups:\n`);
265
+ list.forEach((g, i) => {
266
+ console.log(` [${i}] ${g.name} (gid: ${g.gid})`);
267
+ });
268
+ });
269
+ };
270
+
271
+ cmds.gshares = async (args, json) => {
272
+ const gid = args._[0];
273
+ if (!gid) { console.error("Usage: bdp gshares <gid>"); process.exit(1); }
274
+ const shares = await group.listShares(gid);
275
+ output(shares, json, (list) => {
276
+ console.log(`${list.length} shares in group ${gid}:\n`);
277
+ list.forEach((s, i) => {
278
+ console.log(` [${i}] ${s.name} ${s.isDir ? "[DIR]" : ""} ${formatSize(s.size)} fs_id:${s.fsId}`);
279
+ console.log(` shared by: ${s.fromUser} msg_id:${s.msgId}`);
280
+ });
281
+ });
282
+ };
283
+
284
+ cmds.gls = async (args, json) => {
285
+ const gid = args._[0];
286
+ 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) => {
292
+ const dir = f.isDir ? "[DIR] " : " ";
293
+ console.log(` ${dir}${f.name} ${formatSize(f.size)} fs_id:${f.fsId}`);
294
+ });
295
+ });
296
+ };
297
+
298
+ cmds.gsearch = async (args, json) => {
299
+ const gid = args._[0];
300
+ 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) => {
306
+ const dir = r.isDir ? "[DIR] " : " ";
307
+ console.log(` ${dir}${r.path || r.name} ${formatSize(r.size)} fs_id:${r.fsId}`);
308
+ });
309
+ });
310
+ };
311
+
312
+ // ── Main ───────────────────────────────────────────────
313
+
314
+ const HELP = `bdp — 百度网盘 CLI
315
+
316
+ USAGE
317
+ bdp <command> [options] [arguments]
318
+
319
+ PAN FILE OPERATIONS (全盘)
320
+ ls [path] List directory
321
+ search <keyword> [-p dir] Search files by name
322
+ cat <path> Read file content (no download, max 1MB)
323
+ head [-n N] <path> Read first N lines (default 20)
324
+ tail [-n N] <path> Read last N lines (default 20)
325
+ grep <pattern> <path> Search inside file content
326
+ peek <path> Preview file info + first lines
327
+ get <path> [-o dir] Download file
328
+ put <local> <remote> Upload file
329
+ mkdir <path> Create directory
330
+ rm <path> Delete file/dir
331
+
332
+ GROUP CHAT OPERATIONS (群聊)
333
+ groups List all groups
334
+ gshares <gid> List share libraries in group
335
+ gls <gid> <fs_id> Browse files in a share
336
+ gsearch <gid> <keyword> Search group file names
337
+
338
+ CONFIGURATION
339
+ login --bduss X --stoken Y Save credentials
340
+ whoami Check login status
341
+ config Show configuration
342
+
343
+ OPTIONS
344
+ --json Output JSON (for Agent consumption)
345
+ -n <N> Number of lines (head/tail)
346
+ -p <path> Search path (search)
347
+ -i Ignore case (grep)
348
+ -N Show line numbers (cat/grep)
349
+ -o <dir> Output directory (get)
350
+ --page <N> Page number (gls)
351
+
352
+ EXAMPLES
353
+ bdp login --bduss abc123 --stoken def456
354
+ bdp ls /
355
+ bdp search "报告"
356
+ bdp head -n 50 /文档/日志.txt
357
+ bdp groups --json
358
+ bdp gshares 539478953581833690
359
+ bdp gls 539478953581833690 742474845517885 --json
360
+ bdp gsearch 539478953581833690 "倪海厦"`;
361
+
362
+ async function main() {
363
+ const argv = process.argv.slice(2);
364
+ const command = argv[0];
365
+ const rest = argv.slice(1);
366
+ const args = parseArgs(rest);
367
+ const json = args.flags.json || false;
368
+
369
+ const handler = cmds[command];
370
+ if (!handler) {
371
+ console.log(HELP);
372
+ process.exit(0);
373
+ }
374
+
375
+ try {
376
+ await handler(args, json);
377
+ } catch (e) {
378
+ if (json) {
379
+ console.log(JSON.stringify({ error: e.message }));
380
+ } else {
381
+ console.error(`[ERROR] ${e.message}`);
382
+ }
383
+ process.exit(1);
384
+ }
385
+ }
386
+
387
+ main();