aiconductor-cli 1.0.1 → 1.1.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/bin/aiconductor-cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { Command } = require("commander");
|
|
4
|
-
const { publishCommand } = require("../src/commands/
|
|
4
|
+
const { publishCommand } = require("../src/commands/html_publish");
|
|
5
|
+
const { searchCommand } = require("../src/commands/web_search");
|
|
5
6
|
|
|
6
7
|
const program = new Command();
|
|
7
8
|
|
|
@@ -26,6 +27,39 @@ htmlCmd
|
|
|
26
27
|
publishCommand(html, options);
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
// search 子命令组
|
|
31
|
+
const searchCmd = program
|
|
32
|
+
.command("search")
|
|
33
|
+
.description("网页/图片搜索");
|
|
34
|
+
|
|
35
|
+
searchCmd
|
|
36
|
+
.command("web")
|
|
37
|
+
.description("网页搜索")
|
|
38
|
+
.argument("<query>", "搜索关键词(1~100字符)")
|
|
39
|
+
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
40
|
+
.option("-n, --count <n>", "返回条数(最多50)")
|
|
41
|
+
.option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
|
|
42
|
+
.option("--no-summary", "不返回摘要")
|
|
43
|
+
.option("--content-format <format>", "内容格式:Text 或 Markdown", "Text")
|
|
44
|
+
.option("--auth-level <level>", "权威来源过滤:0=不过滤,1=优先权威来源", "0")
|
|
45
|
+
.option("--query-rewrite", "开启 Query 改写")
|
|
46
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
47
|
+
.action((query, options) => {
|
|
48
|
+
searchCommand(query, "web", options);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
searchCmd
|
|
52
|
+
.command("image")
|
|
53
|
+
.description("图片搜索")
|
|
54
|
+
.argument("<query>", "搜索关键词(1~100字符)")
|
|
55
|
+
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
56
|
+
.option("-n, --count <n>", "返回条数(最多5)")
|
|
57
|
+
.option("--time-range <range>", "时间范围:OneDay/OneWeek/OneMonth/OneYear 或 YYYY-MM-DD..YYYY-MM-DD")
|
|
58
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
59
|
+
.action((query, options) => {
|
|
60
|
+
searchCommand(query, "image", options);
|
|
61
|
+
});
|
|
62
|
+
|
|
29
63
|
// 未知命令处理
|
|
30
64
|
program.on("command:*", () => {
|
|
31
65
|
console.error(`未知命令: ${program.args.join(" ")}`);
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -4,6 +4,7 @@ const path = require("path");
|
|
|
4
4
|
|
|
5
5
|
const API_HOST = "plugin.aiconductor.fun";
|
|
6
6
|
const API_PATH = "/api/html_publish";
|
|
7
|
+
const SEARCH_API_PATH = "/api/aic_web_search";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
|
|
@@ -38,7 +39,7 @@ function resolveApiKey(optionKey) {
|
|
|
38
39
|
* @param {object} body
|
|
39
40
|
* @returns {Promise<object>}
|
|
40
41
|
*/
|
|
41
|
-
function httpPost(host, path, body) {
|
|
42
|
+
function httpPost(host, path, body, headers = {}) {
|
|
42
43
|
return new Promise((resolve, reject) => {
|
|
43
44
|
const data = JSON.stringify(body);
|
|
44
45
|
|
|
@@ -50,6 +51,7 @@ function httpPost(host, path, body) {
|
|
|
50
51
|
headers: {
|
|
51
52
|
"Content-Type": "application/json",
|
|
52
53
|
"Content-Length": Buffer.byteLength(data),
|
|
54
|
+
...headers,
|
|
53
55
|
},
|
|
54
56
|
};
|
|
55
57
|
|
|
@@ -108,4 +110,31 @@ async function publishHtml(apiKey, htmlCode, title) {
|
|
|
108
110
|
return safe;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
/**
|
|
114
|
+
* 网页/图片搜索
|
|
115
|
+
* @param {string} apiKey
|
|
116
|
+
* @param {{ query: string, searchType: string, count?: number, timeRange?: string, needSummary?: boolean, contentFormats?: string, authLevel?: number, queryRewrite?: boolean }} params
|
|
117
|
+
* @returns {Promise<object>}
|
|
118
|
+
*/
|
|
119
|
+
async function webSearch(apiKey, params) {
|
|
120
|
+
const reqBody = {
|
|
121
|
+
query: params.query,
|
|
122
|
+
search_type: params.searchType,
|
|
123
|
+
};
|
|
124
|
+
if (params.count !== undefined) reqBody.count = params.count;
|
|
125
|
+
if (params.timeRange) reqBody.time_range = params.timeRange;
|
|
126
|
+
if (params.needSummary !== undefined) reqBody.need_summary = params.needSummary;
|
|
127
|
+
if (params.contentFormats) reqBody.content_formats = params.contentFormats;
|
|
128
|
+
if (params.authLevel !== undefined) reqBody.auth_level = params.authLevel;
|
|
129
|
+
if (params.queryRewrite !== undefined) reqBody.query_rewrite = params.queryRewrite;
|
|
130
|
+
|
|
131
|
+
const resp = await httpPost(API_HOST, SEARCH_API_PATH, reqBody, {
|
|
132
|
+
Authorization: `Bearer ${apiKey}`,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 过滤计费信息
|
|
136
|
+
const { billing, ...safe } = resp;
|
|
137
|
+
return safe;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { resolveApiKey, publishHtml, webSearch };
|
|
@@ -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
|