aiconductor-cli 1.3.2 → 1.3.3
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 +19 -9
- package/bin/aiconductor-cli.js +19 -9
- package/package.json +4 -2
- package/src/api.js +57 -21
- package/src/api.test.js +98 -0
- package/src/commands/web_search.js +24 -9
package/README.md
CHANGED
|
@@ -115,18 +115,23 @@ aiconductor-cli search web <关键词> [选项]
|
|
|
115
115
|
|
|
116
116
|
| 选项 | 说明 | 默认值 |
|
|
117
117
|
|------|------|--------|
|
|
118
|
-
| `-n, --count` | 返回条数(最多50) |
|
|
118
|
+
| `-n, --count` | 返回条数(最多50) | 10 |
|
|
119
119
|
| `--time-range` | 时间范围:预设 `OneDay`/`OneWeek`/`OneMonth`/`OneYear`,或自定义 `YYYY-MM-DD..YYYY-MM-DD` | 无 |
|
|
120
|
-
| `--
|
|
121
|
-
| `--
|
|
122
|
-
| `--
|
|
120
|
+
| `--need-content` | 仅返回包含正文的结果 | 关闭 |
|
|
121
|
+
| `--need-url` | 仅返回带原文链接的结果 | 关闭 |
|
|
122
|
+
| `--sites` | 限定搜索站点,完整域名以 `\|` 分隔,最多20个 | 无 |
|
|
123
|
+
| `--block-hosts` | 屏蔽站点,完整域名以 `\|` 分隔,最多5个 | 无 |
|
|
124
|
+
| `--auth-info-level` | 权威度过滤:0=不限制,1=仅非常权威内容 | 0 |
|
|
125
|
+
| `--auth-level` | `--auth-info-level` 的兼容别名 | 无 |
|
|
126
|
+
| `--content-format` | 正文格式:`text` 或 `markdown` | text |
|
|
127
|
+
| `--industry` | 行业搜索:`finance`、`game` 或 `gov` | 无 |
|
|
123
128
|
| `--query-rewrite` | 开启 Query 改写 | 关闭 |
|
|
124
129
|
| `-f, --format` | 输出格式:`json` 或 `text` | json |
|
|
125
130
|
|
|
126
131
|
示例:
|
|
127
132
|
```bash
|
|
128
|
-
aiconductor-cli search web "北京今日天气" -n 3 --auth-level 1
|
|
129
|
-
aiconductor-cli search web "AI 最新进展" --time-range OneWeek --content-format
|
|
133
|
+
aiconductor-cli search web "北京今日天气" -n 3 --auth-info-level 1 --need-url
|
|
134
|
+
aiconductor-cli search web "AI 最新进展" --time-range OneWeek --content-format markdown --need-content
|
|
130
135
|
aiconductor-cli search web "技术文章" --time-range 2025-01-01..2025-06-30 -n 10
|
|
131
136
|
```
|
|
132
137
|
|
|
@@ -139,12 +144,17 @@ aiconductor-cli search image <关键词> [选项]
|
|
|
139
144
|
| 选项 | 说明 | 默认值 |
|
|
140
145
|
|------|------|--------|
|
|
141
146
|
| `-n, --count` | 返回条数(最多5) | 5 |
|
|
142
|
-
| `--
|
|
147
|
+
| `--image-width-min` | 图片最小宽度 | 无 |
|
|
148
|
+
| `--image-width-max` | 图片最大宽度 | 无 |
|
|
149
|
+
| `--image-height-min` | 图片最小高度 | 无 |
|
|
150
|
+
| `--image-height-max` | 图片最大高度 | 无 |
|
|
151
|
+
| `--image-shapes` | 图片形状,以 `\|` 分隔:`横长方形`、`竖长方形`、`方形` | 无 |
|
|
152
|
+
| `--query-rewrite` | 开启 Query 改写 | 关闭 |
|
|
143
153
|
| `-f, --format` | 输出格式:`json` 或 `text` | json |
|
|
144
154
|
|
|
145
155
|
示例:
|
|
146
156
|
```bash
|
|
147
|
-
aiconductor-cli search image "长城风景" -n 3 -f text
|
|
157
|
+
aiconductor-cli search image "长城风景" -n 3 --image-width-min 1200 --image-shapes "横长方形|方形" -f text
|
|
148
158
|
```
|
|
149
159
|
|
|
150
160
|
### 抖音数据工具箱
|
|
@@ -309,7 +319,7 @@ aiconductor-cli xhs note-publish video \
|
|
|
309
319
|
|------|------|
|
|
310
320
|
| HTML 发布 | 0.01 元/次 |
|
|
311
321
|
| Markdown/HTML 网页发布 | 0.01 元/次 |
|
|
312
|
-
|
|
|
322
|
+
| 豆包网页/图片搜索 | 0.02 元/次 |
|
|
313
323
|
| 抖音作品详情 | 0.01 元/次 |
|
|
314
324
|
| 抖音作品评论 | 0.01 元/次 |
|
|
315
325
|
| 抖音用户视频列表 | 0.03 元/页 |
|
package/bin/aiconductor-cli.js
CHANGED
|
@@ -95,12 +95,17 @@ searchCmd
|
|
|
95
95
|
.description("网页搜索")
|
|
96
96
|
.argument("<query>", "搜索关键词(1~100字符)")
|
|
97
97
|
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
98
|
-
.option("-n, --count <n>", "返回条数(最多50)")
|
|
99
|
-
.option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
|
|
100
|
-
.option("--
|
|
101
|
-
.option("--
|
|
102
|
-
.option("--
|
|
103
|
-
.option("--
|
|
98
|
+
.option("-n, --count <n>", "返回条数(最多50)")
|
|
99
|
+
.option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
|
|
100
|
+
.option("--need-content", "仅返回包含正文的结果")
|
|
101
|
+
.option("--need-url", "仅返回带原文链接的结果")
|
|
102
|
+
.option("--sites <domains>", "限定搜索站点,完整域名以 | 分隔,最多20个")
|
|
103
|
+
.option("--block-hosts <domains>", "屏蔽站点,完整域名以 | 分隔,最多5个")
|
|
104
|
+
.option("--auth-info-level <level>", "权威度过滤:0=不限制,1=仅非常权威内容")
|
|
105
|
+
.option("--auth-level <level>", "兼容别名,等同 --auth-info-level")
|
|
106
|
+
.option("--content-format <format>", "正文格式:text 或 markdown", "text")
|
|
107
|
+
.option("--industry <type>", "行业搜索:finance、game 或 gov")
|
|
108
|
+
.option("--query-rewrite", "开启 Query 改写")
|
|
104
109
|
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
105
110
|
.action((query, options) => {
|
|
106
111
|
searchCommand(query, "web", options);
|
|
@@ -110,9 +115,14 @@ searchCmd
|
|
|
110
115
|
.command("image")
|
|
111
116
|
.description("图片搜索")
|
|
112
117
|
.argument("<query>", "搜索关键词(1~100字符)")
|
|
113
|
-
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
114
|
-
.option("-n, --count <n>", "返回条数(最多5)")
|
|
115
|
-
.option("--
|
|
118
|
+
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
119
|
+
.option("-n, --count <n>", "返回条数(最多5)")
|
|
120
|
+
.option("--image-width-min <px>", "图片最小宽度")
|
|
121
|
+
.option("--image-width-max <px>", "图片最大宽度")
|
|
122
|
+
.option("--image-height-min <px>", "图片最小高度")
|
|
123
|
+
.option("--image-height-max <px>", "图片最大高度")
|
|
124
|
+
.option("--image-shapes <shapes>", "图片形状,以 | 分隔:横长方形、竖长方形、方形")
|
|
125
|
+
.option("--query-rewrite", "开启 Query 改写")
|
|
116
126
|
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
117
127
|
.action((query, options) => {
|
|
118
128
|
searchCommand(query, "image", options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiconductor-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "AIConductor CLI 工具箱",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aiconductor",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=18.0.0"
|
|
23
23
|
},
|
|
24
|
-
"scripts": {
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test"
|
|
26
|
+
},
|
|
25
27
|
"dependencies": {
|
|
26
28
|
"commander": "^12.1.0"
|
|
27
29
|
}
|
package/src/api.js
CHANGED
|
@@ -7,7 +7,7 @@ const API_PATH = "/api/html_publish";
|
|
|
7
7
|
const MD_TO_HTML_API_PATH = "/api/md_to_html";
|
|
8
8
|
const PUBLISHED_WEBPAGES_API_PATH = "/api/published_webpages";
|
|
9
9
|
const DELETE_PUBLISHED_WEBPAGE_API_PATH = "/api/delete_published_webpage";
|
|
10
|
-
const SEARCH_API_PATH = "/api/
|
|
10
|
+
const SEARCH_API_PATH = "/api/doubao_search";
|
|
11
11
|
const DOUYIN_VIDEO_INFO_PATH = "/api/douyin_video_info";
|
|
12
12
|
const DOUYIN_VIDEO_COMMENTS_PATH = "/api/douyin_video_comments";
|
|
13
13
|
const DOUYIN_USER_VIDEOS_PATH = "/api/douyin_user_videos";
|
|
@@ -273,26 +273,62 @@ async function publishHtml(apiKey, htmlCode, title) {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
/**
|
|
276
|
-
*
|
|
277
|
-
* @param {string} apiKey
|
|
278
|
-
* @param {{ query: string, searchType:
|
|
279
|
-
* @returns {Promise<object>}
|
|
280
|
-
*/
|
|
281
|
-
async function webSearch(apiKey, params) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
276
|
+
* 豆包网页/图片搜索
|
|
277
|
+
* @param {string} apiKey
|
|
278
|
+
* @param {{ query: string, searchType: "web"|"image", count?: number, queryRewrite?: boolean, needContent?: boolean, needUrl?: boolean, sites?: string, blockHosts?: string, authInfoLevel?: number, timeRange?: string, contentFormats?: string, industry?: string, imageWidthMin?: number, imageWidthMax?: number, imageHeightMin?: number, imageHeightMax?: number, imageShapes?: string[]|string }} params
|
|
279
|
+
* @returns {Promise<object>}
|
|
280
|
+
*/
|
|
281
|
+
async function webSearch(apiKey, params) {
|
|
282
|
+
const maxCount = params.searchType === "web" ? 50 : 5;
|
|
283
|
+
if (params.searchType !== "web" && params.searchType !== "image") {
|
|
284
|
+
throw Object.assign(new Error("searchType 只能是 web 或 image"), { type: "validation_error" });
|
|
285
|
+
}
|
|
286
|
+
const count = params.count === undefined ? undefined : validatePositiveInt(params.count, "count", maxCount);
|
|
287
|
+
validateInt(params.authInfoLevel, "authInfoLevel");
|
|
288
|
+
validateInt(params.imageWidthMin, "imageWidthMin");
|
|
289
|
+
validateInt(params.imageWidthMax, "imageWidthMax");
|
|
290
|
+
validateInt(params.imageHeightMin, "imageHeightMin");
|
|
291
|
+
validateInt(params.imageHeightMax, "imageHeightMax");
|
|
292
|
+
|
|
293
|
+
const authInfoLevel = params.authInfoLevel === undefined ? undefined : Number(params.authInfoLevel);
|
|
294
|
+
const imageWidthMin = params.imageWidthMin === undefined ? undefined : Number(params.imageWidthMin);
|
|
295
|
+
const imageWidthMax = params.imageWidthMax === undefined ? undefined : Number(params.imageWidthMax);
|
|
296
|
+
const imageHeightMin = params.imageHeightMin === undefined ? undefined : Number(params.imageHeightMin);
|
|
297
|
+
const imageHeightMax = params.imageHeightMax === undefined ? undefined : Number(params.imageHeightMax);
|
|
298
|
+
|
|
299
|
+
if (authInfoLevel !== undefined && ![0, 1].includes(authInfoLevel)) {
|
|
300
|
+
throw Object.assign(new Error("authInfoLevel 只能是 0 或 1"), { type: "validation_error" });
|
|
301
|
+
}
|
|
302
|
+
if (imageWidthMin !== undefined && imageWidthMax !== undefined && imageWidthMin > imageWidthMax) {
|
|
303
|
+
throw Object.assign(new Error("imageWidthMin 不能大于 imageWidthMax"), { type: "validation_error" });
|
|
304
|
+
}
|
|
305
|
+
if (imageHeightMin !== undefined && imageHeightMax !== undefined && imageHeightMin > imageHeightMax) {
|
|
306
|
+
throw Object.assign(new Error("imageHeightMin 不能大于 imageHeightMax"), { type: "validation_error" });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const reqBody = {
|
|
310
|
+
query: params.query,
|
|
311
|
+
search_type: params.searchType,
|
|
312
|
+
};
|
|
313
|
+
if (count !== undefined) reqBody.count = count;
|
|
314
|
+
if (params.queryRewrite !== undefined) reqBody.query_rewrite = params.queryRewrite;
|
|
315
|
+
|
|
316
|
+
if (params.searchType === "web") {
|
|
317
|
+
if (params.needContent !== undefined) reqBody.need_content = params.needContent;
|
|
318
|
+
if (params.needUrl !== undefined) reqBody.need_url = params.needUrl;
|
|
319
|
+
if (params.sites) reqBody.sites = params.sites;
|
|
320
|
+
if (params.blockHosts) reqBody.block_hosts = params.blockHosts;
|
|
321
|
+
if (authInfoLevel !== undefined) reqBody.auth_info_level = authInfoLevel;
|
|
322
|
+
if (params.timeRange) reqBody.time_range = params.timeRange;
|
|
323
|
+
if (params.contentFormats) reqBody.content_formats = params.contentFormats;
|
|
324
|
+
if (params.industry) reqBody.industry = params.industry;
|
|
325
|
+
} else {
|
|
326
|
+
if (imageWidthMin !== undefined) reqBody.image_width_min = imageWidthMin;
|
|
327
|
+
if (imageWidthMax !== undefined) reqBody.image_width_max = imageWidthMax;
|
|
328
|
+
if (imageHeightMin !== undefined) reqBody.image_height_min = imageHeightMin;
|
|
329
|
+
if (imageHeightMax !== undefined) reqBody.image_height_max = imageHeightMax;
|
|
330
|
+
if (params.imageShapes !== undefined) reqBody.image_shapes = params.imageShapes;
|
|
331
|
+
}
|
|
296
332
|
|
|
297
333
|
const resp = await httpPost(API_HOST, SEARCH_API_PATH, reqBody, {
|
|
298
334
|
Authorization: `Bearer ${apiKey}`,
|
package/src/api.test.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const { EventEmitter } = require("node:events");
|
|
3
|
+
const https = require("node:https");
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
|
|
6
|
+
const { webSearch } = require("./api");
|
|
7
|
+
|
|
8
|
+
function captureRequest(callback) {
|
|
9
|
+
const originalRequest = https.request;
|
|
10
|
+
let captured;
|
|
11
|
+
https.request = (options, responseCallback) => {
|
|
12
|
+
const request = new EventEmitter();
|
|
13
|
+
const chunks = [];
|
|
14
|
+
request.write = (chunk) => chunks.push(Buffer.from(chunk));
|
|
15
|
+
request.end = () => {
|
|
16
|
+
captured = { options, body: JSON.parse(Buffer.concat(chunks).toString("utf-8")) };
|
|
17
|
+
const response = new EventEmitter();
|
|
18
|
+
response.statusCode = 200;
|
|
19
|
+
responseCallback(response);
|
|
20
|
+
response.emit("data", Buffer.from(JSON.stringify({ success: true, billing: {} })));
|
|
21
|
+
response.emit("end");
|
|
22
|
+
};
|
|
23
|
+
return request;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return callback().then(() => captured).finally(() => {
|
|
27
|
+
https.request = originalRequest;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("webSearch maps current web search options to the Doubao endpoint", async () => {
|
|
32
|
+
const request = await captureRequest(() => webSearch("test-key", {
|
|
33
|
+
query: "最新的北京游玩攻略",
|
|
34
|
+
searchType: "web",
|
|
35
|
+
count: 10,
|
|
36
|
+
queryRewrite: true,
|
|
37
|
+
needContent: true,
|
|
38
|
+
needUrl: true,
|
|
39
|
+
sites: "example.com|volcengine.com",
|
|
40
|
+
blockHosts: "spam.example",
|
|
41
|
+
authInfoLevel: 1,
|
|
42
|
+
timeRange: "OneYear",
|
|
43
|
+
contentFormats: "markdown",
|
|
44
|
+
industry: "finance",
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
assert.equal(request.options.path, "/api/doubao_search");
|
|
48
|
+
assert.equal(request.options.headers.Authorization, "Bearer test-key");
|
|
49
|
+
assert.deepEqual(request.body, {
|
|
50
|
+
query: "最新的北京游玩攻略",
|
|
51
|
+
search_type: "web",
|
|
52
|
+
count: 10,
|
|
53
|
+
query_rewrite: true,
|
|
54
|
+
need_content: true,
|
|
55
|
+
need_url: true,
|
|
56
|
+
sites: "example.com|volcengine.com",
|
|
57
|
+
block_hosts: "spam.example",
|
|
58
|
+
auth_info_level: 1,
|
|
59
|
+
time_range: "OneYear",
|
|
60
|
+
content_formats: "markdown",
|
|
61
|
+
industry: "finance",
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("webSearch maps current image filters and rejects invalid count", async () => {
|
|
66
|
+
const request = await captureRequest(() => webSearch("test-key", {
|
|
67
|
+
query: "长城风景",
|
|
68
|
+
searchType: "image",
|
|
69
|
+
count: 5,
|
|
70
|
+
queryRewrite: true,
|
|
71
|
+
imageWidthMin: 1200,
|
|
72
|
+
imageWidthMax: 1920,
|
|
73
|
+
imageHeightMin: 800,
|
|
74
|
+
imageHeightMax: 1080,
|
|
75
|
+
imageShapes: "横长方形|方形",
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
assert.deepEqual(request.body, {
|
|
79
|
+
query: "长城风景",
|
|
80
|
+
search_type: "image",
|
|
81
|
+
count: 5,
|
|
82
|
+
query_rewrite: true,
|
|
83
|
+
image_width_min: 1200,
|
|
84
|
+
image_width_max: 1920,
|
|
85
|
+
image_height_min: 800,
|
|
86
|
+
image_height_max: 1080,
|
|
87
|
+
image_shapes: "横长方形|方形",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await assert.rejects(
|
|
91
|
+
webSearch("test-key", { query: "长城", searchType: "image", count: 6 }),
|
|
92
|
+
{ type: "validation_error" }
|
|
93
|
+
);
|
|
94
|
+
await assert.rejects(
|
|
95
|
+
webSearch("test-key", { query: "长城", searchType: "image", imageWidthMin: "invalid" }),
|
|
96
|
+
{ type: "validation_error" }
|
|
97
|
+
);
|
|
98
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { webSearch } = require("../api");
|
|
2
|
-
const { outputResult, outputError, resolveKeyOrExit, handleApiError
|
|
2
|
+
const { outputResult, outputError, resolveKeyOrExit, handleApiError } = require("../lib/cli-utils");
|
|
3
3
|
|
|
4
4
|
/** text 模式的自定义格式化 */
|
|
5
5
|
function formatSearchText(data) {
|
|
@@ -49,19 +49,34 @@ async function searchCommand(query, searchType, options = {}) {
|
|
|
49
49
|
const params = { query, searchType };
|
|
50
50
|
|
|
51
51
|
if (options.count !== undefined) {
|
|
52
|
-
params.count =
|
|
52
|
+
params.count = options.count;
|
|
53
53
|
}
|
|
54
54
|
if (options.timeRange) {
|
|
55
55
|
params.timeRange = options.timeRange;
|
|
56
56
|
}
|
|
57
57
|
if (searchType === "web") {
|
|
58
|
-
params.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
params.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
if (options.needContent) params.needContent = true;
|
|
59
|
+
if (options.needUrl) params.needUrl = true;
|
|
60
|
+
if (options.sites) params.sites = options.sites;
|
|
61
|
+
if (options.blockHosts) params.blockHosts = options.blockHosts;
|
|
62
|
+
const authInfoLevel = options.authInfoLevel ?? options.authLevel;
|
|
63
|
+
if (authInfoLevel !== undefined) {
|
|
64
|
+
params.authInfoLevel = authInfoLevel;
|
|
65
|
+
}
|
|
66
|
+
if (options.contentFormat) params.contentFormats = options.contentFormat;
|
|
67
|
+
if (options.industry) params.industry = options.industry;
|
|
68
|
+
} else {
|
|
69
|
+
for (const [optionName, paramName] of [
|
|
70
|
+
["imageWidthMin", "imageWidthMin"],
|
|
71
|
+
["imageWidthMax", "imageWidthMax"],
|
|
72
|
+
["imageHeightMin", "imageHeightMin"],
|
|
73
|
+
["imageHeightMax", "imageHeightMax"],
|
|
74
|
+
]) {
|
|
75
|
+
if (options[optionName] !== undefined) {
|
|
76
|
+
params[paramName] = options[optionName];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (options.imageShapes) params.imageShapes = options.imageShapes;
|
|
65
80
|
}
|
|
66
81
|
if (options.queryRewrite) {
|
|
67
82
|
params.queryRewrite = true;
|