md2any 1.0.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/README.md +136 -0
- package/SKILL.md +80 -0
- package/bin/md2any.js +227 -0
- package/lib/actions.js +262 -0
- package/lib/converter.js +864 -0
- package/lib/extract.js +195 -0
- package/lib/llm.js +281 -0
- package/lib/social.js +264 -0
- package/lib/store.js +70 -0
- package/lib/themes.js +588 -0
- package/lib/zhihu.js +666 -0
- package/package.json +65 -0
- package/scripts/social_worker.js +774 -0
- package/scripts/xhs_screenshot.js +260 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# md2any CLI
|
|
2
|
+
|
|
3
|
+
> **一篇 Markdown,发到微信 / 知乎 / 小红书 / Twitter。**
|
|
4
|
+
> [Markdown2Anything](https://github.com/marsggbo/markdown2anything) 的命令行版 —— 也可以作为 **Agent Skill** 使用。
|
|
5
|
+
|
|
6
|
+
VS Code 插件把这套能力做成了"编辑器里的一个按钮"。CLI 把它变成**任何脚本、任何 Agent 都能调用的一种能力**。
|
|
7
|
+
|
|
8
|
+
核心逻辑(Markdown 渲染、长图切片、LLM 文案、Playwright 发布)**完全复用主仓库的 `lib/` 和 `scripts/`**,没有重写。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone https://github.com/marsggbo/markdown2anything.git
|
|
16
|
+
cd markdown2anything && npm install # 主仓库依赖(CLI 直接复用)
|
|
17
|
+
npm link ./cli # 让 md2any 进 PATH
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
首次使用发布/截图功能前,下载一次浏览器:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
md2any install-browser # Chromium,约 150MB,只需一次
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 1) 登录(弹出真实浏览器,扫码/输密码,Cookie 存本地)
|
|
32
|
+
md2any login xhs
|
|
33
|
+
md2any login twitter
|
|
34
|
+
md2any login zhihu
|
|
35
|
+
md2any status # 看登录态 + Cookie 剩余有效期
|
|
36
|
+
|
|
37
|
+
# 2) 把文章渲染成长图(小红书和 Twitter 的配图都来自这里)
|
|
38
|
+
md2any images ./post.md
|
|
39
|
+
|
|
40
|
+
# 3) 生成平台文案
|
|
41
|
+
md2any gen ./post.md --to xhs # 用 LLM
|
|
42
|
+
md2any gen ./post.md --to twitter --local # 用本地关键词提取,不需要 API Key
|
|
43
|
+
|
|
44
|
+
# 4) 发布(可一次发多个平台)
|
|
45
|
+
md2any publish ./post.md --to xhs,twitter
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> 默认**停在「发布」按钮前**,浏览器保持打开,由你核对后自己点。
|
|
49
|
+
> 加 `--auto` 才连最后一下也自动点(不推荐:小红书有二次确认弹窗,容易发失败)。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 命令
|
|
54
|
+
|
|
55
|
+
| 命令 | 说明 |
|
|
56
|
+
|------|------|
|
|
57
|
+
| `md2any login <平台>` | 浏览器登录并保存 Cookie(`xhs` / `twitter` / `zhihu`) |
|
|
58
|
+
| `md2any status` | 各平台登录态与 Cookie 剩余有效期 |
|
|
59
|
+
| `md2any logout <平台>` | 清除某平台 Cookie |
|
|
60
|
+
| `md2any images <file.md>` | 渲染成小红书长图(智能分片,尽量在空白行处切) |
|
|
61
|
+
| `md2any gen <file.md> --to <平台>` | 生成文案(`--local` 用本地算法,不调 LLM) |
|
|
62
|
+
| `md2any publish <file.md> --to <平台..>` | 发布,可逗号分隔多个平台 |
|
|
63
|
+
| `md2any copy <file.md> --to <目标>` | 输出可粘贴的 HTML 到 stdout(`wechat` / `zhihu` / `xhs`) |
|
|
64
|
+
| `md2any config llm --base-url … --model …` | 配置 LLM |
|
|
65
|
+
| `md2any install-browser` | 下载 Chromium |
|
|
66
|
+
|
|
67
|
+
常用参数:`--to` `--local` `--auto` `--headless` `--json`
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
md2any copy ./post.md --to wechat | pbcopy # 直接进剪贴板,粘到公众号
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## LLM 配置
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
md2any config llm --base-url https://api.deepseek.com/v1 --model deepseek-chat --api-key sk-xxx
|
|
79
|
+
md2any config show
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
也可用环境变量:`MD2ANY_LLM_BASE_URL` / `MD2ANY_LLM_MODEL` / `MD2ANY_LLM_API_KEY`
|
|
83
|
+
|
|
84
|
+
**不配也能用** —— `md2any gen --local` 走内置的本地关键词提取(中英混合 n-gram + 停用词过滤 + 长词吸收短词),零配置零成本。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 给 Agent 用
|
|
89
|
+
|
|
90
|
+
CLI 的输出约定是**为脚本和 Agent 设计的**:
|
|
91
|
+
|
|
92
|
+
- **stdout 只输出结果,stderr 输出进度日志** → 可以安全地 pipe 和解析
|
|
93
|
+
- **`--json` 给结构化输出**
|
|
94
|
+
- 退出码:`0` 成功,`1` 失败
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
md2any gen ./post.md --to twitter --json 2>/dev/null | jq '.twitter.tweets'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
仓库里的 [`SKILL.md`](./SKILL.md) 就是给 Agent 读的 skill 定义(Claude Code / OpenClaw 等可直接挂载):把 `cli/` 目录作为 skill 装进去,Agent 就能听懂"把这篇文章发到小红书和推特"。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## ⚠️ 已知限制
|
|
105
|
+
|
|
106
|
+
**知乎的图片必须人工粘贴。**
|
|
107
|
+
|
|
108
|
+
知乎编辑器**不会**自动抓取粘贴进来的图片(base64 和远程 URL 都不行),只有剪贴板里是**真正的图片位图**时它才会上传到自己的图床。
|
|
109
|
+
|
|
110
|
+
所以 `md2any publish --to zhihu` 只会自动填好**标题、正文、代码块(带语言高亮)、公式**,图片位置留空。
|
|
111
|
+
补图的办法:用 VS Code 插件预览区图片上的「📋 复制图片」按钮逐张粘贴。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🔐 安全
|
|
116
|
+
|
|
117
|
+
- Cookie 和 API Key 只存本地:`~/.config/md2any/`,权限 **0600**
|
|
118
|
+
- **唯一的网络去向是平台自己 + 你自己配的 LLM 端点**,不经过任何第三方服务
|
|
119
|
+
- 发布走 **Playwright 真实浏览器 + Cookie 注入**(模拟真人正常操作),而不是伪造签名的裸 HTTP 请求
|
|
120
|
+
- 默认不替你点最后那下「发布」
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 文件产物
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
your-post/
|
|
128
|
+
post.md
|
|
129
|
+
post_xhs/ ← 长图
|
|
130
|
+
xhs_01.png …
|
|
131
|
+
post_social.json ← 文案 + 版本历史(每次生成存一版,旧版保留)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`_social.json` **和 VS Code 插件完全互通** —— 插件里生成的文案,CLI 能读;CLI 生成的,插件里也能用 `◀ ▶` 切换查看。
|
|
135
|
+
|
|
136
|
+
MIT © [marsggbo](https://github.com/marsggbo)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: md2any
|
|
3
|
+
description: 把一篇 Markdown 文章发布到微信公众号 / 知乎 / 小红书 / Twitter(X)。能自动把文章渲染成长图、用 LLM 生成各平台的文案与串推、注入 Cookie 用真实浏览器自动填内容并传图。当用户说"把这篇文章发到小红书/推特/知乎"、"帮我导出长图"、"生成小红书文案"时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# md2any — 一篇 Markdown 发到所有平台
|
|
7
|
+
|
|
8
|
+
## 这个 Skill 能做什么
|
|
9
|
+
|
|
10
|
+
| 平台 | 自动化程度 |
|
|
11
|
+
|------|-----------|
|
|
12
|
+
| 小红书 | **全自动**:截长图 → 写文案 → 传图 → 填标题/正文/标签(停在发布前) |
|
|
13
|
+
| Twitter(X) | **全自动**:生成中文串推、按图切块配图、自动填好(停在发帖前) |
|
|
14
|
+
| 知乎 | **半自动**:自动填标题/正文/代码块/公式;**图片需人工粘贴**(见「已知限制」) |
|
|
15
|
+
| 微信公众号 | 输出可直接粘贴的内联样式 HTML |
|
|
16
|
+
|
|
17
|
+
## 前置条件
|
|
18
|
+
|
|
19
|
+
**首次使用必须先做两件事**,缺一不可:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
md2any install-browser # 下载 Chromium(约 150MB,只需一次)
|
|
23
|
+
md2any login xhs # 浏览器扫码登录,Cookie 存本地
|
|
24
|
+
md2any login twitter
|
|
25
|
+
md2any login zhihu
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
登录会**弹出真实浏览器窗口让用户自己登录**——这一步无法自动化,必须让用户操作。
|
|
29
|
+
用 `md2any status` 检查登录态和 Cookie 剩余有效期。
|
|
30
|
+
|
|
31
|
+
## 典型流程
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 1) 把文章渲染成长图(小红书/Twitter 的配图都来自这里)
|
|
35
|
+
md2any images ./post.md
|
|
36
|
+
|
|
37
|
+
# 2) 生成平台文案(LLM);加 --local 则用本地关键词提取,不需要 API Key
|
|
38
|
+
md2any gen ./post.md --to xhs
|
|
39
|
+
md2any gen ./post.md --to twitter
|
|
40
|
+
|
|
41
|
+
# 3) 发布(可一次发多个平台)
|
|
42
|
+
md2any publish ./post.md --to xhs,twitter
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**默认停在「发布」按钮前**,浏览器保持打开,由用户核对后自己点发布。
|
|
46
|
+
加 `--auto` 才会连最后一下也自动点(**不推荐**:小红书有二次确认弹窗,容易发失败)。
|
|
47
|
+
|
|
48
|
+
## 给 Agent 的重要约定
|
|
49
|
+
|
|
50
|
+
- **stdout 只输出结果,stderr 输出进度日志** → 可以安全地 pipe 和解析
|
|
51
|
+
- **加 `--json` 得到结构化输出**,优先用这个
|
|
52
|
+
- 退出码:`0` 成功,`1` 失败
|
|
53
|
+
- 生成的文案会存到文章同目录的 `<文章名>_social.json`,**每次生成存一个新版本,旧版保留**,重复调用不会丢历史
|
|
54
|
+
|
|
55
|
+
## LLM 配置
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
md2any config llm --base-url https://api.deepseek.com/v1 --model deepseek-chat --api-key sk-xxx
|
|
59
|
+
md2any config show
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
也可用环境变量:`MD2ANY_LLM_BASE_URL` / `MD2ANY_LLM_MODEL` / `MD2ANY_LLM_API_KEY`。
|
|
63
|
+
|
|
64
|
+
**不配也能用**:`md2any gen --local` 走内置的本地关键词提取(中英混合 n-gram + 停用词 + 长词吸收),零成本零配置。
|
|
65
|
+
|
|
66
|
+
## ⚠️ 已知限制(务必如实告诉用户,别假装能做到)
|
|
67
|
+
|
|
68
|
+
**知乎的图片必须人工粘贴。** 知乎编辑器**不会**自动抓取粘贴进来的图片(base64 和远程 URL 都不行),只有剪贴板里是**真正的图片位图**时它才会上传到自己的图床。
|
|
69
|
+
|
|
70
|
+
所以 `md2any publish --to zhihu` 只会自动填好**标题、正文、代码块(带语言高亮)、公式**,图片位置会留空。
|
|
71
|
+
请告诉用户:用 VS Code 插件预览区图片上的「📋 复制图片」按钮逐张粘贴,或在知乎编辑器里手动上传。
|
|
72
|
+
|
|
73
|
+
**不要向用户承诺知乎图片能自动上传。**
|
|
74
|
+
|
|
75
|
+
## 安全
|
|
76
|
+
|
|
77
|
+
- Cookie 和 API Key 只存本地(`~/.config/md2any/`,权限 0600)
|
|
78
|
+
- 唯一的网络去向是**平台自己**和**用户自己配的 LLM 端点**,不经过任何第三方
|
|
79
|
+
- 发布走真实浏览器 + Cookie 注入(模拟真人操作),而非伪造签名的 HTTP 请求
|
|
80
|
+
- **发布是对外可见的操作**:除非用户明确要求,否则不要加 `--auto`,把最后一步留给用户
|
package/bin/md2any.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* md2any — 把一篇 Markdown 发到微信 / 知乎 / 小红书 / Twitter 的命令行工具。
|
|
6
|
+
*
|
|
7
|
+
* 设计上对 Agent 友好:
|
|
8
|
+
* - 所有「结果」走 stdout,所有「进度/日志」走 stderr → 可以安全地 pipe / 解析
|
|
9
|
+
* - 加 --json 输出结构化结果
|
|
10
|
+
* - 退出码:0 成功,1 失败
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const A = require('../lib/actions');
|
|
16
|
+
const store = require('../lib/store');
|
|
17
|
+
|
|
18
|
+
const log = (...a) => console.error(...a); // 进度 → stderr
|
|
19
|
+
const emit = (s) => process.stdout.write(s + '\n'); // 结果 → stdout
|
|
20
|
+
|
|
21
|
+
// ─── 极简参数解析 ────────────────────────────────────────────
|
|
22
|
+
function parse(argv) {
|
|
23
|
+
const pos = [];
|
|
24
|
+
const flags = {};
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const a = argv[i];
|
|
27
|
+
if (a.startsWith('--')) {
|
|
28
|
+
const k = a.slice(2);
|
|
29
|
+
if (k.includes('=')) { const [kk, vv] = k.split(/=(.*)/); flags[kk] = vv; }
|
|
30
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith('-')) flags[k] = argv[++i];
|
|
31
|
+
else flags[k] = true;
|
|
32
|
+
} else pos.push(a);
|
|
33
|
+
}
|
|
34
|
+
return { pos, flags };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HELP = `
|
|
38
|
+
md2any — 一篇 Markdown,发到微信 / 知乎 / 小红书 / Twitter
|
|
39
|
+
|
|
40
|
+
用法:
|
|
41
|
+
md2any <命令> [参数]
|
|
42
|
+
|
|
43
|
+
命令:
|
|
44
|
+
login <平台> 浏览器登录并保存 Cookie(平台:xhs | twitter | zhihu)
|
|
45
|
+
status 查看各平台登录态与有效期
|
|
46
|
+
logout <平台> 清除某平台的 Cookie
|
|
47
|
+
|
|
48
|
+
images <file.md> 把文章渲染成小红书长图(自动智能分片)
|
|
49
|
+
gen <file.md> --to <平台> 生成文案(默认 LLM;--local 用本地算法,不需要 API Key)
|
|
50
|
+
publish <file.md> --to <平台..> 发布(可逗号分隔多个平台)
|
|
51
|
+
copy <file.md> --to <目标> 输出可粘贴的 HTML 到 stdout(目标:wechat | zhihu | xhs)
|
|
52
|
+
|
|
53
|
+
config llm --base-url <url> --model <名> [--api-key <key>]
|
|
54
|
+
config show
|
|
55
|
+
install-browser 下载 Chromium(首次使用发布/截图前需要)
|
|
56
|
+
|
|
57
|
+
常用参数:
|
|
58
|
+
--to <平台> xhs | twitter | zhihu(可逗号分隔)
|
|
59
|
+
--local 文案用本地关键词提取,不调用 LLM
|
|
60
|
+
--auto 连最后的「发布」按钮也自动点(默认停在发布前,由你确认)
|
|
61
|
+
--headless 隐藏浏览器窗口
|
|
62
|
+
--json 以 JSON 输出结果(给 Agent 用)
|
|
63
|
+
|
|
64
|
+
示例:
|
|
65
|
+
md2any login xhs
|
|
66
|
+
md2any images ./post.md
|
|
67
|
+
md2any gen ./post.md --to twitter
|
|
68
|
+
md2any publish ./post.md --to xhs,twitter
|
|
69
|
+
md2any copy ./post.md --to wechat | pbcopy
|
|
70
|
+
|
|
71
|
+
环境变量(也可用 config 命令写入 ~/.config/md2any/):
|
|
72
|
+
MD2ANY_LLM_BASE_URL OpenAI 兼容接口地址,如 https://api.deepseek.com/v1
|
|
73
|
+
MD2ANY_LLM_MODEL 模型名,如 deepseek-chat
|
|
74
|
+
MD2ANY_LLM_API_KEY API Key(本地端点如 Ollama 可不填)
|
|
75
|
+
`.trim();
|
|
76
|
+
|
|
77
|
+
// ─── 命令 ────────────────────────────────────────────────────
|
|
78
|
+
async function main() {
|
|
79
|
+
const { pos, flags } = parse(process.argv.slice(2));
|
|
80
|
+
const cmd = pos[0];
|
|
81
|
+
const json = !!flags.json;
|
|
82
|
+
|
|
83
|
+
if (!cmd || cmd === 'help' || flags.help) { emit(HELP); return; }
|
|
84
|
+
|
|
85
|
+
const needMd = (i = 1) => {
|
|
86
|
+
const f = pos[i];
|
|
87
|
+
if (!f) throw new Error('缺少 markdown 文件路径');
|
|
88
|
+
const abs = path.resolve(f);
|
|
89
|
+
if (!fs.existsSync(abs)) throw new Error(`文件不存在:${abs}`);
|
|
90
|
+
return abs;
|
|
91
|
+
};
|
|
92
|
+
const targets = () => String(flags.to || '').split(',').map(s => A.normPlatform(s.trim())).filter(Boolean);
|
|
93
|
+
|
|
94
|
+
switch (cmd) {
|
|
95
|
+
|
|
96
|
+
case 'login': {
|
|
97
|
+
const p = A.normPlatform(pos[1]);
|
|
98
|
+
if (!A.PLATFORMS.includes(p)) throw new Error(`未知平台:${pos[1]}(可选 ${A.PLATFORMS.join(' / ')})`);
|
|
99
|
+
log(`正在打开浏览器登录 ${p} …`);
|
|
100
|
+
await A.login(p, { log });
|
|
101
|
+
const st = A.status().find(s => s.platform === p);
|
|
102
|
+
log('✅ 登录成功');
|
|
103
|
+
emit(json ? JSON.stringify(st) : `已登录 ${st.name}` + (st.daysLeft != null ? `(剩 ${st.daysLeft} 天)` : ''));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case 'status': {
|
|
108
|
+
const st = A.status();
|
|
109
|
+
if (json) { emit(JSON.stringify(st, null, 2)); return; }
|
|
110
|
+
for (const s of st) {
|
|
111
|
+
const tag = s.loggedIn
|
|
112
|
+
? (s.daysLeft != null ? `已登录 · 剩 ${s.daysLeft} 天` : '已登录')
|
|
113
|
+
: (s.state === 'expired' ? '已过期' : '未登录');
|
|
114
|
+
emit(`${s.loggedIn ? '✅' : '❌'} ${s.name.padEnd(8)} ${tag}`);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'logout': {
|
|
120
|
+
const p = A.normPlatform(pos[1]);
|
|
121
|
+
const social = require(path.join(A.ROOT, 'lib', 'social'));
|
|
122
|
+
social.clearCookies(p, store.cookieStorage());
|
|
123
|
+
emit(`已清除 ${p} 的 Cookie`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'images': {
|
|
128
|
+
const md = needMd();
|
|
129
|
+
log('正在渲染长图…');
|
|
130
|
+
const imgs = await A.exportImages(md, { theme: flags.theme || 'zhihu', log });
|
|
131
|
+
log(`✅ 已导出 ${imgs.length} 张`);
|
|
132
|
+
emit(json ? JSON.stringify({ count: imgs.length, images: imgs }, null, 2) : imgs.join('\n'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case 'gen': {
|
|
137
|
+
const md = needMd();
|
|
138
|
+
const ps = targets();
|
|
139
|
+
if (!ps.length) throw new Error('请用 --to 指定平台,如 --to twitter');
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const p of ps) {
|
|
142
|
+
if (p === 'zhihu') { log('知乎无需生成文案(直接用文章正文),跳过'); continue; }
|
|
143
|
+
let copy;
|
|
144
|
+
if (flags.local) {
|
|
145
|
+
copy = A.localCopy(md, p);
|
|
146
|
+
log(`✅ [${p}] 本地算法提取完成(未调用模型)`);
|
|
147
|
+
} else {
|
|
148
|
+
if (!A.listImages(md).length && p === 'twitter') {
|
|
149
|
+
log(' Twitter 的串推条数要按长图张数算,正在自动导出长图…');
|
|
150
|
+
await A.exportImages(md, { log });
|
|
151
|
+
}
|
|
152
|
+
log(`正在用 LLM 生成 ${p} 文案…`);
|
|
153
|
+
copy = await A.llmCopy(md, p, { instruction: flags.prompt });
|
|
154
|
+
log(`✅ [${p}] 生成完成`);
|
|
155
|
+
}
|
|
156
|
+
A.addVersion(md, p, copy, flags.local ? 'local' : 'llm', A.readMeta(md).link);
|
|
157
|
+
out[p] = copy;
|
|
158
|
+
}
|
|
159
|
+
emit(json ? JSON.stringify(out, null, 2) : JSON.stringify(out, null, 2));
|
|
160
|
+
log(`已保存到 ${path.basename(A.socialFile(md))}(旧版本保留,可回切)`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'publish': {
|
|
165
|
+
const md = needMd();
|
|
166
|
+
const ps = targets();
|
|
167
|
+
if (!ps.length) throw new Error('请用 --to 指定平台,如 --to xhs,twitter');
|
|
168
|
+
const results = {};
|
|
169
|
+
for (const p of ps) {
|
|
170
|
+
log(`\n▶ 发布到 ${p} …`);
|
|
171
|
+
const r = await A.publish(md, p, {
|
|
172
|
+
mode: flags.auto ? 'auto' : 'prepare',
|
|
173
|
+
headless: !!flags.headless,
|
|
174
|
+
log,
|
|
175
|
+
});
|
|
176
|
+
results[p] = r.status;
|
|
177
|
+
log(r.status === 'ready'
|
|
178
|
+
? `✅ [${p}] 内容已填好,浏览器保持打开 —— 请核对后自己点「发布」`
|
|
179
|
+
: `✅ [${p}] 已提交发布`);
|
|
180
|
+
}
|
|
181
|
+
emit(json ? JSON.stringify(results, null, 2) : Object.entries(results).map(([k, v]) => `${k}: ${v}`).join('\n'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
case 'copy': {
|
|
186
|
+
const md = needMd();
|
|
187
|
+
const t = String(flags.to || 'wechat');
|
|
188
|
+
emit(A.toHtml(md, A.normPlatform(t) === 'xiaohongshu' ? 'xhs' : t, flags.theme || 'wechat'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'config': {
|
|
193
|
+
const sub = pos[1];
|
|
194
|
+
if (sub === 'show') {
|
|
195
|
+
const c = store.getLlmConfig();
|
|
196
|
+
emit(JSON.stringify({ baseUrl: c.baseUrl, model: c.model, apiKey: c.apiKey ? '***已配置***' : '' }, null, 2));
|
|
197
|
+
emit(`配置文件:${store.CONFIG_FILE}`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (sub === 'llm') {
|
|
201
|
+
const patch = {};
|
|
202
|
+
if (flags['base-url']) patch.baseUrl = flags['base-url'];
|
|
203
|
+
if (flags.model) patch.model = flags.model;
|
|
204
|
+
if (flags['api-key']) patch.apiKey = flags['api-key'];
|
|
205
|
+
if (!Object.keys(patch).length) throw new Error('请提供 --base-url / --model / --api-key');
|
|
206
|
+
store.setLlmConfig(patch);
|
|
207
|
+
emit(`✅ 已保存到 ${store.CONFIG_FILE}(权限 0600)`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
throw new Error('用法:md2any config show | md2any config llm --base-url ... --model ...');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case 'install-browser': {
|
|
214
|
+
await A.installBrowser({ log });
|
|
215
|
+
emit('✅ Chromium 已就绪');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
default:
|
|
220
|
+
throw new Error(`未知命令:${cmd}\n\n${HELP}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
main().catch(err => {
|
|
225
|
+
console.error('❌ ' + (err && err.message ? err.message : err));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
});
|