aiconductor-cli 1.2.1 → 1.2.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/CLAUDE.md +76 -0
- package/README.md +266 -108
- package/package.json +4 -3
- package/src/api.js +42 -26
- package/src/commands/douyin.js +109 -151
- package/src/commands/html_publish.js +54 -91
- package/src/commands/md_publish.js +87 -118
- package/src/commands/web_search.js +84 -120
- package/src/lib/cli-utils.js +80 -0
- package/AGENTS.md +0 -79
- package/AIC /346/212/226/351/237/263/347/263/273/345/210/227API/346/216/245/345/217/243.txt" +0 -417
- package/CODEBUDDY.md +0 -79
|
@@ -1,120 +1,84 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
process.exit(
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 };
|
|
1
|
+
const { webSearch } = require("../api");
|
|
2
|
+
const { outputResult, outputError, resolveKeyOrExit, handleApiError, safeParseInt } = require("../lib/cli-utils");
|
|
3
|
+
|
|
4
|
+
/** text 模式的自定义格式化 */
|
|
5
|
+
function formatSearchText(data) {
|
|
6
|
+
const inner = data.data || {};
|
|
7
|
+
let out = "✓ 搜索成功\n";
|
|
8
|
+
out += ` 结果数: ${inner.ResultCount}\n`;
|
|
9
|
+
out += ` 耗时: ${inner.TimeCost}ms\n\n`;
|
|
10
|
+
|
|
11
|
+
if (inner.WebResults) {
|
|
12
|
+
inner.WebResults.forEach((item) => {
|
|
13
|
+
out += `[${item.SortId}] ${item.Title}\n`;
|
|
14
|
+
if (item.SiteName) out += ` 站点: ${item.SiteName}\n`;
|
|
15
|
+
if (item.Url) out += ` URL: ${item.Url}\n`;
|
|
16
|
+
if (item.Snippet) out += ` 摘要: ${item.Snippet}\n`;
|
|
17
|
+
if (item.Summary) out += ` 内容摘要: ${item.Summary}\n`;
|
|
18
|
+
if (item.PublishTime) out += ` 发布时间: ${item.PublishTime}\n`;
|
|
19
|
+
out += `\n`;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (inner.ImageResults) {
|
|
24
|
+
inner.ImageResults.forEach((item) => {
|
|
25
|
+
out += `[${item.SortId}] ${item.Title}\n`;
|
|
26
|
+
if (item.SiteName) out += ` 来源: ${item.SiteName}\n`;
|
|
27
|
+
if (item.Image?.Url) out += ` 图片: ${item.Image.Url}\n`;
|
|
28
|
+
if (item.Image?.Width && item.Image?.Height) {
|
|
29
|
+
out += ` 尺寸: ${item.Image.Width}x${item.Image.Height}\n`;
|
|
30
|
+
}
|
|
31
|
+
if (item.Url) out += ` 来源页面: ${item.Url}\n`;
|
|
32
|
+
out += `\n`;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return out.trimEnd();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* search 命令
|
|
41
|
+
* @param {string} query - 搜索关键词
|
|
42
|
+
* @param {"web"|"image"} searchType - 搜索类型
|
|
43
|
+
* @param {object} options - 命令行选项
|
|
44
|
+
*/
|
|
45
|
+
async function searchCommand(query, searchType, options = {}) {
|
|
46
|
+
const format = options.format || "json";
|
|
47
|
+
const apiKey = resolveKeyOrExit(options.apiKey, format);
|
|
48
|
+
|
|
49
|
+
const params = { query, searchType };
|
|
50
|
+
|
|
51
|
+
if (options.count !== undefined) {
|
|
52
|
+
params.count = safeParseInt(options.count, undefined);
|
|
53
|
+
}
|
|
54
|
+
if (options.timeRange) {
|
|
55
|
+
params.timeRange = options.timeRange;
|
|
56
|
+
}
|
|
57
|
+
if (searchType === "web") {
|
|
58
|
+
params.needSummary = options.summary !== false;
|
|
59
|
+
}
|
|
60
|
+
if (options.contentFormat) {
|
|
61
|
+
params.contentFormats = options.contentFormat;
|
|
62
|
+
}
|
|
63
|
+
if (options.authLevel !== undefined) {
|
|
64
|
+
params.authLevel = safeParseInt(options.authLevel, undefined);
|
|
65
|
+
}
|
|
66
|
+
if (options.queryRewrite) {
|
|
67
|
+
params.queryRewrite = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const result = await webSearch(apiKey, params);
|
|
72
|
+
outputResult(result, format, formatSearchText);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.type === "validation_error") {
|
|
76
|
+
outputError(err, format);
|
|
77
|
+
} else {
|
|
78
|
+
handleApiError(err, format);
|
|
79
|
+
}
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { searchCommand };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const { resolveApiKey } = require("../api");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 统一成功输出
|
|
5
|
+
* @param {object} data - API 返回数据
|
|
6
|
+
* @param {"json"|"text"} format
|
|
7
|
+
* @param {function} [textFormatter] - text 模式的自定义格式化函数,接收 data 返回字符串
|
|
8
|
+
*/
|
|
9
|
+
function outputResult(data, format, textFormatter) {
|
|
10
|
+
if (format === "json") {
|
|
11
|
+
process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
|
|
12
|
+
} else {
|
|
13
|
+
const text = textFormatter ? textFormatter(data) : JSON.stringify(data, null, 2);
|
|
14
|
+
process.stdout.write(text + "\n");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 统一错误输出
|
|
20
|
+
* @param {object} err - 错误对象(需有 message 属性)
|
|
21
|
+
* @param {"json"|"text"} format
|
|
22
|
+
*/
|
|
23
|
+
function outputError(err, format) {
|
|
24
|
+
const msg = err.message || String(err);
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
process.stderr.write(JSON.stringify({ success: false, error: msg }, null, 2) + "\n");
|
|
27
|
+
} else {
|
|
28
|
+
process.stderr.write(`✗ 错误: ${msg}\n`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 解析 API Key,失败时输出错误并退出进程
|
|
34
|
+
* @param {string} [optionKey] - 命令行传入的 key
|
|
35
|
+
* @param {"json"|"text"} format
|
|
36
|
+
* @returns {string} 解析到的 API Key
|
|
37
|
+
*/
|
|
38
|
+
function resolveKeyOrExit(optionKey, format) {
|
|
39
|
+
const key = resolveApiKey(optionKey);
|
|
40
|
+
if (!key) {
|
|
41
|
+
outputError(
|
|
42
|
+
{ message: "缺少 API Key。请通过 -k 参数、AICONDUCTOR_API_KEY 环境变量或 .env 文件提供。" },
|
|
43
|
+
format
|
|
44
|
+
);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
return key;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 统一 API 错误处理(不退出,由调用方决定退出码)
|
|
52
|
+
* @param {object} err - 错误对象
|
|
53
|
+
* @param {"json"|"text"} format
|
|
54
|
+
*/
|
|
55
|
+
function handleApiError(err, format) {
|
|
56
|
+
if (err.type === "http_error") {
|
|
57
|
+
outputError(
|
|
58
|
+
{ message: `API 返回错误 (HTTP ${err.statusCode}): ${err.body?.message || JSON.stringify(err.body)}` },
|
|
59
|
+
format
|
|
60
|
+
);
|
|
61
|
+
} else if (err.type === "network_error") {
|
|
62
|
+
outputError({ message: `网络错误: ${err.message}` }, format);
|
|
63
|
+
} else {
|
|
64
|
+
outputError({ message: `未知错误: ${err.message || err}` }, format);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 安全解析整数,无效时返回 defaultValue
|
|
70
|
+
* @param {*} value
|
|
71
|
+
* @param {number} defaultValue
|
|
72
|
+
* @returns {number}
|
|
73
|
+
*/
|
|
74
|
+
function safeParseInt(value, defaultValue) {
|
|
75
|
+
if (value === undefined || value === null) return defaultValue;
|
|
76
|
+
const n = parseInt(value, 10);
|
|
77
|
+
return Number.isNaN(n) ? defaultValue : n;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { outputResult, outputError, resolveKeyOrExit, handleApiError, safeParseInt };
|
package/AGENTS.md
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# AGENTS.md This file provides guidance to Agent 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。
|