aiconductor-cli 1.0.1 → 1.2.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/CODEBUDDY.md ADDED
@@ -0,0 +1,79 @@
1
+ # CODEBUDDY.md This file provides guidance to CodeBuddy when working with code in this repository.
2
+
3
+ ## Commands
4
+
5
+ ### 本地运行(开发调试)
6
+
7
+ ```bash
8
+ node bin/aiconductor-cli.js --help # 查看所有命令
9
+ node bin/aiconductor-cli.js html publish --help # 查看某命令选项
10
+ node bin/aiconductor-cli.js search web "关键词" -n 3 -f text # 运行网页搜索(text 输出)
11
+ node bin/aiconductor-cli.js search image "关键词" -n 2 # 运行图片搜索(JSON 输出)
12
+ ```
13
+
14
+ ### 发布到 npm
15
+
16
+ ```bash
17
+ npm version patch|minor|major # 升版本号(新增功能升 minor,修复升 patch)
18
+ npm pkg fix # 修复 package.json 格式问题
19
+ npm publish # 发布(如开启 2FA 需加 --otp=<验证码>)
20
+ ```
21
+
22
+ ### 验证流程
23
+
24
+ 发布前必须对本轮新增/修改的每个命令执行真实调用,确认 exit code 0 且输出格式正确。用 `node bin/aiconductor-cli.js <command> --help` 检查选项完整性。
25
+
26
+ ## Architecture
27
+
28
+ ### 分层设计
29
+
30
+ 项目采用三层分离架构,每层职责明确:
31
+
32
+ ```
33
+ bin/aiconductor-cli.js ← 入口层:commander 注册子命令树,不含业务逻辑
34
+ src/api.js ← API 层:HTTP 请求封装 + API Key 解析
35
+ src/commands/<topic>.js ← 命令层:参数组装、调用 API、格式化输出
36
+ ```
37
+
38
+ **`bin/aiconductor-cli.js`** 是纯聚合入口,用 commander 的 `.command().addCommand()` 结构将各 topic 模块注册为子命令树。action 回调只做参数传递,不含业务逻辑。新增命令分类只需在此文件引入并注册。
39
+
40
+ **`src/api.js`** 是所有外部 API 调用的底层封装:
41
+ - `resolveApiKey(optionKey)` — 三级优先级查找 API Key:`-k` 参数 > `AICONDUCTOR_API_KEY` 环境变量 > 当前目录 `.env` 文件
42
+ - `httpPost(host, path, body, headers)` — 通用 HTTP POST 封装,支持自定义 headers,统一处理 HTTP 错误(`type: "http_error"`)和网络错误(`type: "network_error"`)
43
+ - 每个外部 API 对应一个独立函数(如 `publishHtml`、`webSearch`),函数内过滤 `billing` 字段后返回
44
+ - 新增外部 API 渠道只改此文件
45
+
46
+ **`src/commands/<topic>.js`** 是命令编排层,每个文件对应一个命令分组:
47
+ - 接收业务参数 + options 对象
48
+ - 调用 `resolveApiKey` 解析 Key(失败则 exit 1)
49
+ - 组装参数调用 `api.js` 中的函数
50
+ - 通过 `outputResult` / `outputError` 统一处理输出
51
+ - 成功 `process.exit(0)`,失败 `process.exit(1)`
52
+
53
+ ### 命令文件命名规则
54
+
55
+ `src/commands/` 下的文件名与 API 端点名一一对应:
56
+ - `/api/html_publish` → `html_publish.js`
57
+ - `/api/aic_web_search` → `web_search.js`
58
+
59
+ 新增命令时遵循同样规则:文件名 = API 端点名(snake_case)。
60
+
61
+ ### 输出格式约定
62
+
63
+ 所有命令支持 `-f json|text` 两种输出格式:
64
+ - **JSON(默认)**:成功输出 `{ success: true, data: {...} }`,失败输出 `{ success: false, error: "..." }`,便于 AI Agent 解析
65
+ - **text**:人类可读格式,成功以 `✓` 开头,失败以 `✗` 开头
66
+
67
+ ### API Key 鉴权
68
+
69
+ 不同 API 使用不同鉴权方式:
70
+ - `html_publish`:api_key 放入请求 body
71
+ - `aic_web_search`:使用 `Authorization: Bearer <api_key>` 请求头
72
+
73
+ ### 计费信息过滤
74
+
75
+ 所有 API 函数在返回前通过解构 `const { billing, ...safe } = resp` 过滤掉计费字段,不暴露给终端用户。
76
+
77
+ ### Skills
78
+
79
+ `.codebuddy/skills/` 下存放 CodeBuddy Skill 文档(SKILL.md),描述 CLI 命令的使用方法供 AI Agent 调用。每个 Skill 对应一个 CLI 命令分组,目录名使用 kebab-case。此目录已被 `.npmignore` 排除,不会发布到 npm。
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # AIConductor CLI
2
+
3
+ AIConductor 平台命令行工具箱,提供 HTML 发布和网页/图片搜索功能。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g aiconductor-cli
9
+ ```
10
+
11
+ **Node.js >= 16.0.0** 环境。
12
+
13
+ ## 配置 API Key
14
+
15
+ 前往 [https://dev.aiconductor.fun/register](https://dev.aiconductor.fun/register) 注册获取 API Key,然后配置环境变量:
16
+
17
+ **macOS / Linux:**
18
+ ```bash
19
+ echo 'export AICONDUCTOR_API_KEY="your-api-key"' >> ~/.zshrc && source ~/.zshrc
20
+ ```
21
+
22
+ **Windows PowerShell:**
23
+ ```powershell
24
+ [Environment]::SetEnvironmentVariable("AICONDUCTOR_API_KEY", "your-api-key", "User")
25
+ ```
26
+
27
+ > 也可在项目目录创建 `.env` 文件写入 `AICONDUCTOR_API_KEY=your-api-key`。
28
+
29
+ ## 命令
30
+
31
+ ### 发布 HTML 网页
32
+
33
+ ```bash
34
+ aiconductor-cli html publish <HTML代码或文件路径> -t <英文文件名>
35
+ ```
36
+
37
+ | 选项 | 说明 |
38
+ |------|------|
39
+ | `-t, --title` | 英文文件名(必填,小写+连字符) |
40
+ | `-k, --api-key` | API Key(可选,默认读取环境变量) |
41
+ | `-f, --format` | 输出格式:`json`(默认)或 `text` |
42
+
43
+ 示例:
44
+ ```bash
45
+ aiconductor-cli html publish index.html -t my-page
46
+ aiconductor-cli html publish "<h1>Hello</h1>" -t hello-page -f text
47
+ ```
48
+
49
+ ### 网页搜索
50
+
51
+ ```bash
52
+ aiconductor-cli search web <关键词> [选项]
53
+ ```
54
+
55
+ | 选项 | 说明 | 默认值 |
56
+ |------|------|--------|
57
+ | `-n, --count` | 返回条数(最多50) | 5 |
58
+ | `--time-range` | 时间范围:`OneDay`/`OneWeek`/`OneMonth`/`OneYear` | 无 |
59
+ | `--no-summary` | 不返回内容摘要 | 默认返回 |
60
+ | `--content-format` | 内容格式:`Text`/`Markdown` | Text |
61
+ | `--auth-level` | 权威过滤:0=不过滤,1=优先权威来源 | 0 |
62
+ | `--query-rewrite` | 开启 Query 改写 | 关闭 |
63
+
64
+ 示例:
65
+ ```bash
66
+ aiconductor-cli search web "北京今日天气" -n 3 --auth-level 1
67
+ ```
68
+
69
+ ### 图片搜索
70
+
71
+ ```bash
72
+ aiconductor-cli search image <关键词> [选项]
73
+ ```
74
+
75
+ | 选项 | 说明 | 默认值 |
76
+ |------|------|--------|
77
+ | `-n, --count` | 返回条数(最多5) | 5 |
78
+ | `--time-range` | 时间范围 | 无 |
79
+
80
+ 示例:
81
+ ```bash
82
+ aiconductor-cli search image "长城风景" -n 3 -f text
83
+ ```
84
+
85
+ ### 通用选项
86
+
87
+ 所有命令支持:
88
+ - `-k, --api-key <key>` — API Key(优先级:参数 > 环境变量 > .env 文件)
89
+ - `-f, --format <json|text>` — 输出格式,默认 JSON
90
+
91
+ ## 计费
92
+
93
+ - HTML 发布:0.01 元/次
94
+ - 网页/图片搜索:0.03 元/次
95
+ - 搜索失败不扣费,仅成功时计费
96
+
97
+ ## 开发
98
+
99
+ ```bash
100
+ git clone https://github.com/huyi9531/aiconductor-cli.git
101
+ cd aiconductor-cli
102
+ npm install
103
+ node bin/aiconductor-cli.js --help # 本地运行
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { Command } = require("commander");
4
- const { publishCommand } = require("../src/commands/publish");
4
+ const { publishCommand } = require("../src/commands/html_publish");
5
+ const { publishCommand: webpagePublishCommand } = require("../src/commands/md_publish");
6
+ const { searchCommand } = require("../src/commands/web_search");
5
7
 
6
8
  const program = new Command();
7
9
 
@@ -26,6 +28,57 @@ htmlCmd
26
28
  publishCommand(html, options);
27
29
  });
28
30
 
31
+ // webpage 子命令组(统一发布 Markdown/HTML 为在线网页)
32
+ const webpageCmd = program
33
+ .command("webpage")
34
+ .description("网页发布(支持 Markdown 和 HTML)");
35
+
36
+ webpageCmd
37
+ .command("publish")
38
+ .description("将 Markdown 或 HTML 发布为在线网页(自动识别类型)")
39
+ .argument("<input>", "Markdown/HTML 文本字符串或文件路径")
40
+ .option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
41
+ .option("-t, --title <title>", "文件名(不含 .html 后缀)")
42
+ .option("--type <type>", "强制指定类型:md 或 html(默认自动识别)")
43
+ .option("--copy-button", "添加代码复制按钮(仅 Markdown 类型生效)")
44
+ .option("-f, --format <format>", "输出格式:json 或 text", "json")
45
+ .action((input, options) => {
46
+ webpagePublishCommand(input, options);
47
+ });
48
+
49
+ // search 子命令组
50
+ const searchCmd = program
51
+ .command("search")
52
+ .description("网页/图片搜索");
53
+
54
+ searchCmd
55
+ .command("web")
56
+ .description("网页搜索")
57
+ .argument("<query>", "搜索关键词(1~100字符)")
58
+ .option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
59
+ .option("-n, --count <n>", "返回条数(最多50)")
60
+ .option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
61
+ .option("--no-summary", "不返回摘要")
62
+ .option("--content-format <format>", "内容格式:Text 或 Markdown", "Text")
63
+ .option("--auth-level <level>", "权威来源过滤:0=不过滤,1=优先权威来源", "0")
64
+ .option("--query-rewrite", "开启 Query 改写")
65
+ .option("-f, --format <format>", "输出格式:json 或 text", "json")
66
+ .action((query, options) => {
67
+ searchCommand(query, "web", options);
68
+ });
69
+
70
+ searchCmd
71
+ .command("image")
72
+ .description("图片搜索")
73
+ .argument("<query>", "搜索关键词(1~100字符)")
74
+ .option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
75
+ .option("-n, --count <n>", "返回条数(最多5)")
76
+ .option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
77
+ .option("-f, --format <format>", "输出格式:json 或 text", "json")
78
+ .action((query, options) => {
79
+ searchCommand(query, "image", options);
80
+ });
81
+
29
82
  // 未知命令处理
30
83
  program.on("command:*", () => {
31
84
  console.error(`未知命令: ${program.args.join(" ")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiconductor-cli",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "AIConductor CLI 工具箱",
5
5
  "keywords": [
6
6
  "aiconductor",
package/src/api.js CHANGED
@@ -4,6 +4,8 @@ const path = require("path");
4
4
 
5
5
  const API_HOST = "plugin.aiconductor.fun";
6
6
  const API_PATH = "/api/html_publish";
7
+ const MD_TO_HTML_API_PATH = "/api/md_to_html";
8
+ const SEARCH_API_PATH = "/api/aic_web_search";
7
9
 
8
10
  /**
9
11
  * 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
@@ -38,7 +40,7 @@ function resolveApiKey(optionKey) {
38
40
  * @param {object} body
39
41
  * @returns {Promise<object>}
40
42
  */
41
- function httpPost(host, path, body) {
43
+ function httpPost(host, path, body, headers = {}) {
42
44
  return new Promise((resolve, reject) => {
43
45
  const data = JSON.stringify(body);
44
46
 
@@ -50,6 +52,7 @@ function httpPost(host, path, body) {
50
52
  headers: {
51
53
  "Content-Type": "application/json",
52
54
  "Content-Length": Buffer.byteLength(data),
55
+ ...headers,
53
56
  },
54
57
  };
55
58
 
@@ -108,4 +111,53 @@ async function publishHtml(apiKey, htmlCode, title) {
108
111
  return safe;
109
112
  }
110
113
 
111
- module.exports = { resolveApiKey, publishHtml };
114
+ /**
115
+ * 网页/图片搜索
116
+ * @param {string} apiKey
117
+ * @param {{ query: string, searchType: string, count?: number, timeRange?: string, needSummary?: boolean, contentFormats?: string, authLevel?: number, queryRewrite?: boolean }} params
118
+ * @returns {Promise<object>}
119
+ */
120
+ async function webSearch(apiKey, params) {
121
+ const reqBody = {
122
+ query: params.query,
123
+ search_type: params.searchType,
124
+ };
125
+ if (params.count !== undefined) reqBody.count = params.count;
126
+ if (params.timeRange) reqBody.time_range = params.timeRange;
127
+ if (params.needSummary !== undefined) reqBody.need_summary = params.needSummary;
128
+ if (params.contentFormats) reqBody.content_formats = params.contentFormats;
129
+ if (params.authLevel !== undefined) reqBody.auth_level = params.authLevel;
130
+ if (params.queryRewrite !== undefined) reqBody.query_rewrite = params.queryRewrite;
131
+
132
+ const resp = await httpPost(API_HOST, SEARCH_API_PATH, reqBody, {
133
+ Authorization: `Bearer ${apiKey}`,
134
+ });
135
+
136
+ // 过滤计费信息
137
+ const { billing, ...safe } = resp;
138
+ return safe;
139
+ }
140
+
141
+ /**
142
+ * Markdown 转 HTML 并发布
143
+ * @param {string} apiKey
144
+ * @param {string} mdText
145
+ * @param {string} [filename]
146
+ * @param {boolean} [addCopyButton=true]
147
+ * @returns {Promise<{success: boolean, message: string, file_url: string, online_url: string}>}
148
+ */
149
+ async function publishMdToHtml(apiKey, mdText, filename, addCopyButton = false) {
150
+ const reqBody = {
151
+ api_key: apiKey,
152
+ md_text: mdText,
153
+ add_copy_button: addCopyButton,
154
+ };
155
+ if (filename) reqBody.filename = filename;
156
+
157
+ const resp = await httpPost(API_HOST, MD_TO_HTML_API_PATH, reqBody);
158
+
159
+ const { billing, ...safe } = resp;
160
+ return safe;
161
+ }
162
+
163
+ module.exports = { resolveApiKey, publishHtml, publishMdToHtml, webSearch };
@@ -0,0 +1,118 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { resolveApiKey, publishHtml, publishMdToHtml } = require("../api");
4
+
5
+ /**
6
+ * 解析输入内容:如果是已存在的文件路径则读取,否则当作原始字符串
7
+ * @param {string} input
8
+ * @returns {{ content: string, filePath: string|null }}
9
+ */
10
+ function resolveInput(input) {
11
+ const trimmed = input.trim();
12
+ try {
13
+ const filePath = path.resolve(trimmed);
14
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
15
+ return { content: fs.readFileSync(filePath, "utf-8"), filePath };
16
+ }
17
+ } catch (_) {}
18
+ return { content: trimmed, filePath: null };
19
+ }
20
+
21
+ /**
22
+ * 自动检测内容类型:Markdown 或 HTML
23
+ * @param {string} content
24
+ * @param {string|null} filePath
25
+ * @returns {"md"|"html"}
26
+ */
27
+ function detectType(content, filePath) {
28
+ // 优先用文件扩展名判断
29
+ if (filePath) {
30
+ const ext = path.extname(filePath).toLowerCase();
31
+ if (ext === ".md" || ext === ".markdown") return "md";
32
+ if (ext === ".html" || ext === ".htm") return "html";
33
+ }
34
+ // 内容特征判断
35
+ const trimmed = content.trim();
36
+ if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html") || trimmed.startsWith("<head")) {
37
+ return "html";
38
+ }
39
+ if (trimmed.startsWith("<") && /<\w+[^>]*>/.test(trimmed.substring(0, 200))) {
40
+ return "html";
41
+ }
42
+ // 默认 Markdown
43
+ return "md";
44
+ }
45
+
46
+ function outputResult(data, format) {
47
+ if (format === "json") {
48
+ process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
49
+ } else {
50
+ process.stdout.write(`✓ 发布成功\n`);
51
+ process.stdout.write(` URL: ${data.online_url}\n`);
52
+ if (data.message) {
53
+ process.stdout.write(` 信息: ${data.message}\n`);
54
+ }
55
+ }
56
+ }
57
+
58
+ function outputError(err, format) {
59
+ if (format === "json") {
60
+ process.stderr.write(JSON.stringify({ success: false, error: err.message || String(err) }, null, 2) + "\n");
61
+ } else {
62
+ process.stderr.write(`✗ 错误: ${err.message || err}\n`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * webpage publish 命令 — 统一发布 Markdown 或 HTML 为在线网页
68
+ * @param {string} input - Markdown/HTML 代码或文件路径
69
+ * @param {{ apiKey?: string, format?: string, title?: string, copyButton?: boolean, type?: string }} options
70
+ */
71
+ async function publishCommand(input, options = {}) {
72
+ const format = options.format || "json";
73
+ const resolvedKey = resolveApiKey(options.apiKey);
74
+
75
+ if (!resolvedKey) {
76
+ outputError(
77
+ { message: "缺少 API Key。请通过 -k 参数、AICONDUCTOR_API_KEY 环境变量或 .env 文件提供。" },
78
+ format
79
+ );
80
+ process.exit(1);
81
+ }
82
+
83
+ try {
84
+ const { content, filePath } = resolveInput(input);
85
+ // 确定类型:--type 显式指定优先,否则自动检测
86
+ let type = options.type;
87
+ if (!type) {
88
+ type = detectType(content, filePath);
89
+ } else if (type !== "md" && type !== "html") {
90
+ outputError({ message: `无效的 --type 值: "${type}",可选: md 或 html` }, format);
91
+ process.exit(1);
92
+ }
93
+
94
+ let result;
95
+ if (type === "html") {
96
+ result = await publishHtml(resolvedKey, content, options.title);
97
+ } else {
98
+ const addCopyButton = options.copyButton === true;
99
+ result = await publishMdToHtml(resolvedKey, content, options.title, addCopyButton);
100
+ }
101
+ outputResult(result, format);
102
+ process.exit(0);
103
+ } catch (err) {
104
+ if (err.type === "http_error") {
105
+ outputError(
106
+ { message: `API 返回错误 (HTTP ${err.statusCode}): ${err.body?.message || JSON.stringify(err.body)}` },
107
+ format
108
+ );
109
+ } else if (err.type === "network_error") {
110
+ outputError({ message: `网络错误: ${err.message}` }, format);
111
+ } else {
112
+ outputError({ message: `未知错误: ${err.message || err}` }, format);
113
+ }
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ module.exports = { publishCommand };
@@ -0,0 +1,120 @@
1
+ const { resolveApiKey, webSearch } = require("../api");
2
+
3
+ /**
4
+ * 输出成功结果
5
+ * @param {object} data
6
+ * @param {"json"|"text"} format
7
+ */
8
+ function outputResult(data, format) {
9
+ if (format === "json") {
10
+ process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
11
+ } else {
12
+ // API 响应结构:{ success, message, data: { ResultCount, TimeCost, WebResults, ImageResults } }
13
+ const inner = data.data || {};
14
+ process.stdout.write(`✓ 搜索成功\n`);
15
+ process.stdout.write(` 结果数: ${inner.ResultCount}\n`);
16
+ process.stdout.write(` 耗时: ${inner.TimeCost}ms\n\n`);
17
+
18
+ if (inner.WebResults) {
19
+ inner.WebResults.forEach((item) => {
20
+ process.stdout.write(`[${item.SortId}] ${item.Title}\n`);
21
+ if (item.SiteName) process.stdout.write(` 站点: ${item.SiteName}\n`);
22
+ if (item.Url) process.stdout.write(` URL: ${item.Url}\n`);
23
+ if (item.Snippet) process.stdout.write(` 摘要: ${item.Snippet}\n`);
24
+ if (item.Summary) process.stdout.write(` 内容摘要: ${item.Summary}\n`);
25
+ if (item.PublishTime) process.stdout.write(` 发布时间: ${item.PublishTime}\n`);
26
+ process.stdout.write(`\n`);
27
+ });
28
+ }
29
+
30
+ if (inner.ImageResults) {
31
+ inner.ImageResults.forEach((item) => {
32
+ process.stdout.write(`[${item.SortId}] ${item.Title}\n`);
33
+ if (item.SiteName) process.stdout.write(` 来源: ${item.SiteName}\n`);
34
+ if (item.Image?.Url) process.stdout.write(` 图片: ${item.Image.Url}\n`);
35
+ if (item.Image?.Width && item.Image?.Height) {
36
+ process.stdout.write(` 尺寸: ${item.Image.Width}x${item.Image.Height}\n`);
37
+ }
38
+ if (item.Url) process.stdout.write(` 来源页面: ${item.Url}\n`);
39
+ process.stdout.write(`\n`);
40
+ });
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 输出错误
47
+ * @param {object} err
48
+ * @param {"json"|"text"} format
49
+ */
50
+ function outputError(err, format) {
51
+ if (format === "json") {
52
+ process.stderr.write(JSON.stringify({ success: false, error: err.message || String(err) }, null, 2) + "\n");
53
+ } else {
54
+ process.stderr.write(`✗ 错误: ${err.message || err}\n`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * search 命令
60
+ * @param {string} query - 搜索关键词
61
+ * @param {"web"|"image"} searchType - 搜索类型
62
+ * @param {object} options - 命令行选项
63
+ */
64
+ async function searchCommand(query, searchType, options = {}) {
65
+ const format = options.format || "json";
66
+ const resolvedKey = resolveApiKey(options.apiKey);
67
+
68
+ if (!resolvedKey) {
69
+ outputError(
70
+ { message: "缺少 API Key。请通过 -k 参数、AICONDUCTOR_API_KEY 环境变量或 .env 文件提供。" },
71
+ format
72
+ );
73
+ process.exit(1);
74
+ }
75
+
76
+ const params = {
77
+ query,
78
+ searchType,
79
+ };
80
+
81
+ if (options.count !== undefined) {
82
+ params.count = parseInt(options.count, 10);
83
+ }
84
+ if (options.timeRange) {
85
+ params.timeRange = options.timeRange;
86
+ }
87
+ // need_summary 默认 true,--no-summary 时 options.summary 为 false
88
+ if (searchType === "web") {
89
+ params.needSummary = options.summary !== false;
90
+ }
91
+ if (options.contentFormat) {
92
+ params.contentFormats = options.contentFormat;
93
+ }
94
+ if (options.authLevel !== undefined) {
95
+ params.authLevel = parseInt(options.authLevel, 10);
96
+ }
97
+ if (options.queryRewrite) {
98
+ params.queryRewrite = true;
99
+ }
100
+
101
+ try {
102
+ const result = await webSearch(resolvedKey, params);
103
+ outputResult(result, format);
104
+ process.exit(0);
105
+ } catch (err) {
106
+ if (err.type === "http_error") {
107
+ outputError(
108
+ { message: `API 返回错误 (HTTP ${err.statusCode}): ${err.body?.message || JSON.stringify(err.body)}` },
109
+ format
110
+ );
111
+ } else if (err.type === "network_error") {
112
+ outputError({ message: `网络错误: ${err.message}` }, format);
113
+ } else {
114
+ outputError({ message: `未知错误: ${err.message || err}` }, format);
115
+ }
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ module.exports = { searchCommand };
File without changes