随着 Agent 辅助编写文章越来越方便,内容生产的入口正在从纯手工编辑器,逐渐扩展到“人提出方向,AI 生成、修改、整理和发布”的工作流。CLI 工具天然适合 AI 调用:参数明确、输出结构化、执行路径稳定,也容易嵌入自动化流程。因此我为 Visionary 搭建了一套 CLI 工具,让 AI 不只负责写文章,也能通过命令完成创建草稿、更新内容、发布文章和管理专栏等操作。
现在这套工具已经整理成 npm 包,可以直接全局安装使用:
npm install -g visionary-cli
GitHub 仓库地址:
https://github.com/PassingTraveller111/visionary-cli
这篇文章记录 Visionary CLI 当前的架构、设计取舍和功能边界。
目标:把发布动作变成稳定的命令
Visionary CLI 的核心目标很明确:让内容发布和内容管理可以通过命令完成。
安装后,可以直接在任意目录执行:
visionary --help
visionary auth login --username <username> --password <password>
visionary draft create --title <title> --content-file ./post.md --summary <text> --tags <a,b>
visionary draft publish --id <id> --confirm
相比原来的项目内脚本,全局 npm 包有几个明显优势:
- 不需要进入 Web 项目目录。
- 可以在写作目录、笔记目录、自动化脚本里直接调用。
- 命令形态更稳定,后续可以独立版本化、发布和迭代。
- 更适合被 Agent、CI、批处理脚本调用。
- 安装方式标准化,其他机器也可以通过 npm 快速使用。
项目结构
核心结构如下:
visionary-cli/
package.json
README.md
LICENSE
bin/
visionary.mjs
index.mjs
api.mjs
shared.mjs
auth.mjs
draft.mjs
article.mjs
column.mjs
这个结构有一个明显特点:入口很薄,业务按命令域拆分。
bin/visionary.mjs 只负责作为全局命令入口:
#!/usr/bin/env node
import { run } from '../index.mjs';
run();
真正的命令分发放在 index.mjs 中。它解析用户输入,然后把请求转交给对应模块:
if (command === 'auth') return handleAuthCommand(subcommand, options);
if (command === 'draft') return handleDraftCommand(subcommand, options);
if (command === 'article') return handleArticleCommand(subcommand, options);
if (command === 'column') return handleColumnCommand(subcommand, options);
这样设计的好处是:新增命令时不需要重写入口,只要新增一个模块,再在入口里注册一个分发分支即可。
npm 包设计
CLI 能像普通命令一样运行,关键在 package.json 的 bin 字段:
{
"name": "visionary-cli",
"version": "0.1.1",
"type": "module",
"bin": {
"visionary": "bin/visionary.mjs",
"visionary-cli": "bin/visionary.mjs"
},
"engines": {
"node": ">=20"
}
}
包名是 visionary-cli,全局安装方式是:
npm install -g visionary-cli
安装后会暴露两个命令:
visionary
visionary-cli
日常使用推荐 visionary,visionary-cli 作为别名保留。
本地开发阶段仍然可以使用 npm link:
npm link
因为是软链接,后续修改源码后不需要重新安装,命令会直接使用当前目录下的最新代码。
设计原则一:CLI 只做客户端,不占用端口
Visionary CLI 不启动服务,不监听端口,也不常驻后台。
它的角色是一个 HTTP 客户端:用户执行命令后,CLI 读取参数和配置,向 Visionary 服务端 API 发请求,然后把结果以 JSON 输出。
默认服务地址是:
https://visionaryblog.cn
也可以通过参数或环境变量覆盖:
visionary article list --base-url http://localhost:3000
VISIONARY_BASE_URL=http://localhost:3000 visionary article list
这意味着端口管理仍然属于 Web 服务本身,CLI 不需要处理端口冲突、进程守护、服务重启等问题。
设计原则二:配置分层,命令可复用
CLI 的配置读取顺序是:
- 命令行参数:
--base-url、--token、--cookie - 环境变量:
VISIONARY_BASE_URL、VISIONARY_TOKEN、VISIONARY_COOKIE - 本地保存配置:
~/.visionary-cli/config.json - 默认服务:
https://visionaryblog.cn
这个顺序兼顾了几种场景:
- 临时调试时,用命令参数覆盖。
- 自动化脚本中,用环境变量注入敏感信息。
- 个人日常使用时,登录一次后复用本地配置。
- 没有任何配置时,默认访问线上服务。
登录命令会把服务地址和 token 保存到本地:
visionary auth login --username <username> --password <password>
配置文件位置是:
~/.visionary-cli/config.json
保存时会创建 ~/.visionary-cli 目录,并用较保守的文件权限写入配置,避免 token 被无意暴露。
设计原则三:输出统一 JSON
Visionary CLI 的输出统一走 output():
export function output(data) {
console.log(JSON.stringify(data, null, 2));
}
失败时也输出结构化 JSON:
export function fail(message, details) {
output({ success: false, error: message, details });
process.exitCode = 1;
}
这让 CLI 不只是给人看,也适合给脚本和 Agent 消费。例如创建草稿后,可以从返回结果里读取 draftId,再自动调用发布命令。
发布相关命令还会返回关键地址,例如:
{
"draftUrl": "https://visionaryblog.cn/editor/draft/v2/666",
"reviewUrl": "https://visionaryblog.cn/reader/review/500",
"articleUrl": "https://visionaryblog.cn/reader/426"
}
对 AI 来说,这类结构化返回非常重要。它可以在发布后继续把草稿地址、审核预览地址和正式文章地址反馈给用户。
功能模块
当前 Visionary CLI 分为四个命令域。
auth:登录与本地凭证
auth 模块目前提供登录能力:
visionary auth login --username <username> --password <password>
登录成功后会解析服务端返回的 set-cookie,提取 token,并写入本地配置。后续命令会自动把 token 转成 Cookie 请求头:
Cookie: token=<token>
draft:草稿生命周期
draft 是发布文章的核心模块,支持:
visionary draft create
visionary draft get
visionary draft update
visionary draft publish
创建草稿时可以直接传入 Markdown 内容,也可以传入文件:
visionary draft create --title "标题" --content-file ./post.md --summary "摘要" --tags "CLI,Visionary"
一个细节是,默认会去掉内容开头的一级标题:
# 标题
正文...
这样可以避免文章标题和正文里的 H1 重复。如果确实希望保留正文里的 H1,可以加:
--keep-title
发布草稿时必须显式加 --confirm:
visionary draft publish --id <id> --confirm
这是一个简单但有效的安全阀,避免脚本误操作导致文章被直接发布。
article:文章查询、删除与上传
article 模块覆盖已发布文章的管理能力:
visionary article list
visionary article public-list
visionary article search --keyword <text>
visionary article get --id <id>
visionary article delete --id <id> --confirm
visionary article cover-upload --file <path>
visionary article image-upload --file <path>
删除文章同样要求 --confirm,这是命令行工具里值得保留的防误触设计。
上传能力基于 Node 20 的原生 FormData 和 Blob,不需要额外引入第三方依赖。
column:专栏管理
column 模块用于管理专栏:
visionary column list
visionary column get --id <id>
visionary column create --name <name> --description <text>
visionary column update --id <id>
visionary column delete --id <id> --confirm
visionary column articles --id <id>
visionary column set-articles --id <id> --article-ids <id,id>
visionary column candidates
visionary column cover-upload --file <path>
其中 set-articles 支持逗号分隔和 JSON 数组两种输入方式,方便人工输入,也方便脚本生成。
为什么没有引入 Commander 或 Yargs
当前版本没有引入 Commander、Yargs 这类 CLI 框架,而是自己实现了一个轻量的 parseArgs()。
原因很直接:当前命令参数还比较简单,主要是 --key value 和少量布尔参数。手写解析器可以做到:
- 零运行时依赖。
- 可控的错误信息。
- 更容易保持 JSON 输出稳定。
- 包体积更小,调试路径更短。
这不是说永远不应该引入框架。当命令开始出现子命令嵌套、交互式输入、自动补全、复杂校验时,再引入成熟 CLI 框架会更合适。
当前阶段,最小实现反而更稳。
这个 CLI 适合什么场景
Visionary CLI 适合以下几类场景:
- 本地写 Markdown,然后一条命令创建草稿。
- Agent 自动生成文章后直接发布到 Visionary。
- 批量查询、搜索、删除文章。
- 管理专栏和专栏文章关系。
- 在 CI 或定时任务里做内容同步。
- 在不同机器上通过 npm 安装后快速复用同一套发布能力。
它本质上把 Web 后台里的内容操作抽象成了稳定命令。
后续可以继续演进的方向
当前版本已经完成了从“项目内脚本”到“独立 npm 包”的关键迁移。后续可以继续增强:
- 增加
auth status和auth logout。 - 增加
config get/set/list,让配置管理更显式。 - 增加
draft from-file或publish file,把创建草稿和发布串成一个命令。 - 增加
--format table,在人类阅读场景下输出更友好。 - 增加测试,覆盖参数解析、配置优先级和关键 API 请求构造。
- 增加 GitHub Actions,自动测试并发布 npm 包。
总结
Visionary CLI 的设计并不复杂,但它抓住了 CLI 工具最重要的几个点:
- 全局可用。
- npm 标准安装。
- 不常驻、不占端口。
- 配置分层清晰。
- 输出结构化。
- 命令域按业务拆分。
- 危险操作需要显式确认。
它不是一个“大而全”的命令行框架,而是一个围绕 Visionary 内容生产流程定制的工具。正因为边界清晰,它可以很自然地嵌入日常写作、自动化脚本和 Agent 工作流中。
从这个角度看,CLI 不只是一个发布入口,更是 Visionary 内容系统对外暴露的一层稳定操作协议。