aiconductor-cli 1.0.1
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 +41 -0
- package/package.json +25 -0
- package/src/api.js +111 -0
- package/src/commands/publish.js +91 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require("commander");
|
|
4
|
+
const { publishCommand } = require("../src/commands/publish");
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name("aiconductor-cli")
|
|
10
|
+
.description("AIConductor CLI 工具箱")
|
|
11
|
+
.version(require("../package.json").version);
|
|
12
|
+
|
|
13
|
+
// html 子命令组
|
|
14
|
+
const htmlCmd = program
|
|
15
|
+
.command("html")
|
|
16
|
+
.description("HTML 相关操作");
|
|
17
|
+
|
|
18
|
+
htmlCmd
|
|
19
|
+
.command("publish")
|
|
20
|
+
.description("发布 HTML 代码为在线网页")
|
|
21
|
+
.argument("<html>", "HTML 代码字符串或 HTML 文件路径")
|
|
22
|
+
.option("-k, --api-key <key>", "AIConductor API Key(也可通过 AICONDUCTOR_API_KEY 环境变量或 .env 文件提供)")
|
|
23
|
+
.option("-t, --title <title>", "文档标题(用作文件名基础)")
|
|
24
|
+
.option("-f, --format <format>", "输出格式:json 或 text", "json")
|
|
25
|
+
.action((html, options) => {
|
|
26
|
+
publishCommand(html, options);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 未知命令处理
|
|
30
|
+
program.on("command:*", () => {
|
|
31
|
+
console.error(`未知命令: ${program.args.join(" ")}`);
|
|
32
|
+
console.error("使用 --help 查看可用命令。");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program.parse(process.argv);
|
|
37
|
+
|
|
38
|
+
// 没有参数时显示帮助
|
|
39
|
+
if (!process.argv.slice(2).length) {
|
|
40
|
+
program.outputHelp();
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aiconductor-cli",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "AIConductor CLI 工具箱",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"aiconductor",
|
|
7
|
+
"html",
|
|
8
|
+
"publish",
|
|
9
|
+
"deploy",
|
|
10
|
+
"cli"
|
|
11
|
+
],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"main": "src/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"aiconductor-cli": "bin/aiconductor-cli.js"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=16.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^12.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const API_HOST = "plugin.aiconductor.fun";
|
|
6
|
+
const API_PATH = "/api/html_publish";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 解析 API Key,三级优先级:命令行参数 > 系统环境变量 > .env 文件
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
function resolveApiKey(optionKey) {
|
|
13
|
+
// 1. 命令行参数
|
|
14
|
+
if (optionKey) return optionKey;
|
|
15
|
+
|
|
16
|
+
// 2. 系统环境变量
|
|
17
|
+
if (process.env.AICONDUCTOR_API_KEY) return process.env.AICONDUCTOR_API_KEY;
|
|
18
|
+
|
|
19
|
+
// 3. 当前目录 .env 文件
|
|
20
|
+
try {
|
|
21
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
22
|
+
if (fs.existsSync(envPath)) {
|
|
23
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
24
|
+
const match = content.match(/^AICONDUCTOR_API_KEY\s*=\s*(.+)$/m);
|
|
25
|
+
if (match) return match[1].trim();
|
|
26
|
+
}
|
|
27
|
+
} catch (_) {
|
|
28
|
+
// .env 不可读,忽略
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* HTTP POST 请求封装
|
|
36
|
+
* @param {string} host
|
|
37
|
+
* @param {string} path
|
|
38
|
+
* @param {object} body
|
|
39
|
+
* @returns {Promise<object>}
|
|
40
|
+
*/
|
|
41
|
+
function httpPost(host, path, body) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const data = JSON.stringify(body);
|
|
44
|
+
|
|
45
|
+
const options = {
|
|
46
|
+
hostname: host,
|
|
47
|
+
port: 80,
|
|
48
|
+
path: path,
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"Content-Length": Buffer.byteLength(data),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const req = http.request(options, (res) => {
|
|
57
|
+
let chunks = [];
|
|
58
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
59
|
+
res.on("end", () => {
|
|
60
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(raw);
|
|
64
|
+
} catch (_) {
|
|
65
|
+
parsed = { success: false, message: raw };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (res.statusCode >= 400) {
|
|
69
|
+
reject({
|
|
70
|
+
type: "http_error",
|
|
71
|
+
statusCode: res.statusCode,
|
|
72
|
+
body: parsed,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resolve(parsed);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
req.on("error", (err) => {
|
|
82
|
+
reject({ type: "network_error", message: err.message });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
req.write(data);
|
|
86
|
+
req.end();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 发布 HTML 到线上
|
|
92
|
+
* @param {string} apiKey
|
|
93
|
+
* @param {string} htmlCode
|
|
94
|
+
* @param {string} [title]
|
|
95
|
+
* @returns {Promise<{success: boolean, message: string, online_url: string}>}
|
|
96
|
+
*/
|
|
97
|
+
async function publishHtml(apiKey, htmlCode, title) {
|
|
98
|
+
const reqBody = {
|
|
99
|
+
api_key: apiKey,
|
|
100
|
+
html_code: htmlCode,
|
|
101
|
+
};
|
|
102
|
+
if (title) reqBody.title = title;
|
|
103
|
+
|
|
104
|
+
const resp = await httpPost(API_HOST, API_PATH, reqBody);
|
|
105
|
+
|
|
106
|
+
// 过滤计费信息
|
|
107
|
+
const { billing, ...safe } = resp;
|
|
108
|
+
return safe;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { resolveApiKey, publishHtml };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { resolveApiKey, publishHtml } = require("../api");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 智能解析输入:如果是已存在的文件路径则读取,否则当作原始 HTML 字符串
|
|
7
|
+
* @param {string} input
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function resolveHtmlInput(input) {
|
|
11
|
+
const trimmed = input.trim();
|
|
12
|
+
// 尝试作为文件路径
|
|
13
|
+
try {
|
|
14
|
+
const filePath = path.resolve(trimmed);
|
|
15
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
16
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
} catch (_) {
|
|
19
|
+
// 不是文件路径,按原始字符串处理
|
|
20
|
+
}
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 输出成功结果
|
|
26
|
+
* @param {object} data
|
|
27
|
+
* @param {"json"|"text"} format
|
|
28
|
+
*/
|
|
29
|
+
function outputResult(data, format) {
|
|
30
|
+
if (format === "json") {
|
|
31
|
+
process.stdout.write(JSON.stringify({ success: true, data }, null, 2) + "\n");
|
|
32
|
+
} else {
|
|
33
|
+
process.stdout.write(`✓ 发布成功\n`);
|
|
34
|
+
process.stdout.write(` URL: ${data.online_url}\n`);
|
|
35
|
+
if (data.message) {
|
|
36
|
+
process.stdout.write(` 信息: ${data.message}\n`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 输出错误
|
|
43
|
+
* @param {object} err
|
|
44
|
+
* @param {"json"|"text"} format
|
|
45
|
+
*/
|
|
46
|
+
function outputError(err, format) {
|
|
47
|
+
if (format === "json") {
|
|
48
|
+
process.stderr.write(JSON.stringify({ success: false, error: err.message || String(err) }, null, 2) + "\n");
|
|
49
|
+
} else {
|
|
50
|
+
process.stderr.write(`✗ 错误: ${err.message || err}\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* publish 命令(子命令模式)
|
|
56
|
+
* @param {string} htmlCode - HTML 代码或文件路径
|
|
57
|
+
* @param {{ apiKey?: string, format?: string, title?: string }} options
|
|
58
|
+
*/
|
|
59
|
+
async function publishCommand(htmlCode, options = {}) {
|
|
60
|
+
const format = options.format || "json";
|
|
61
|
+
const resolvedKey = resolveApiKey(options.apiKey);
|
|
62
|
+
|
|
63
|
+
if (!resolvedKey) {
|
|
64
|
+
outputError(
|
|
65
|
+
{ message: "缺少 API Key。请通过 -k 参数、AICONDUCTOR_API_KEY 环境变量或 .env 文件提供。" },
|
|
66
|
+
format
|
|
67
|
+
);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const code = resolveHtmlInput(htmlCode);
|
|
73
|
+
const result = await publishHtml(resolvedKey, code, options.title);
|
|
74
|
+
outputResult(result, format);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err.type === "http_error") {
|
|
78
|
+
outputError(
|
|
79
|
+
{ message: `API 返回错误 (HTTP ${err.statusCode}): ${err.body?.message || JSON.stringify(err.body)}` },
|
|
80
|
+
format
|
|
81
|
+
);
|
|
82
|
+
} else if (err.type === "network_error") {
|
|
83
|
+
outputError({ message: `网络错误: ${err.message}` }, format);
|
|
84
|
+
} else {
|
|
85
|
+
outputError({ message: `未知错误: ${err.message || err}` }, format);
|
|
86
|
+
}
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { publishCommand };
|