@offerpilot/axiomruntime 0.0.3 → 0.0.5

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 CHANGED
@@ -1,192 +1,305 @@
1
- # AI Runtime(当前阶段:AI Gateway)
1
+ # AxiomRuntime
2
2
 
3
- 本项目的长期目标是构建一套企业级 AI Runtime:让模型、Agent、记忆、知识、技能、工作流和工具在统一的运行、治理与学习体系中协作,并持续把个人经验沉淀为组织能力。
3
+ > 一条命令管理 Claude Code 和 Codex CLI 的多 Provider 运行环境。
4
4
 
5
- Runtime 才是产品。Memory、Agent LLM 都是可以替换和组合的能力模块。
5
+ AxiomRuntime 是一个以 CLI 为核心的本地 AI Runtime 基础层。它不提供模型,也不替代 Claude Code 或 Codex CLI,而是把多个 Provider(官方 API、中转站或其他兼容服务)集中管理,再以**每次进程独立注入**的方式启动原生 CLI。
6
6
 
7
- 当前可用版本是 Runtime Gateway 基础层:通过统一的 `ai` 命令管理 Claude Code Codex CLI 的 provider、模型、优先级、健康度、故障转移、用量、会话、诊断与 Telegram 入口。后续能力会在保持现有命令稳定的基础上,逐步演进到统一执行、受治理学习、Skill/Capability 沉淀和组织级共享。
7
+ 它最适合这样的工作方式:同时开多个终端窗口,每个窗口使用不同的 Provider 或模型;需要时再从 Telegram 远程启动任务。窗口之间互不覆盖配置,AxiomRuntime 也不会接管你的原生 Claude Code / Codex 配置。
8
8
 
9
- 北极星目标:**每一次交互,都让 Runtime 更有能力。**
9
+ ## 先看实现状态
10
10
 
11
- ## 文档导航
11
+ README 需要区分“已经能用”和“只是规划”。下面的状态以当前仓库代码和 npm `0.0.5` 为准:
12
12
 
13
- - [中文文档总览](./docs/README.md)
14
- - [doc_2.0 文档层总览](./docs/README.md)
15
- - [中文使用指南](./docs/usage.md)
16
- - [HTML 命令手册](./docs/USAGE.html)
17
- - [整体架构与当前实现快照](./docs/00-overview.md)
13
+ | 标记 | 含义 |
14
+ |---|---|
15
+ | ✅ | 已实现,并随 npm `0.0.5` 提供 |
16
+ | 🟡 | 有可用实现,但能力范围或接入面仍有限制 |
17
+ | 🚧 | 尚未实现,仅代表后续方向 |
18
18
 
19
- AI 与维护者使用的英文权威文档位于 `AGENTS.md`、`docs/AI_RUNTIME_ARCHITECTURE.md` `docs/CONVENTIONS.md`;产品规范、宪法摘要、记忆/学习与 Telegram 设计等构成 `doc_2.0` 文档层(现已展开在 `docs/` `docs/modules/` 下,索引见 `docs/README.md`)。
19
+ | 能力 | npm `0.0.5` | 当前源码 | 说明 |
20
+ |---|---:|---:|---|
21
+ | Provider 增删改查、模型发现、优先级 | ✅ | ✅ | `ai provider`、`ai status` |
22
+ | Claude Code / Codex 启动与运行时 fallback | ✅ | ✅ | 按健康度、兼容模型和 `level` 选择 |
23
+ | 多窗口使用不同 Provider,互不影响 | ✅ | ✅ | 配置只注入对应子进程 |
24
+ | 不修改原生 Claude / Codex 配置 | ✅ | ✅ | `ai use` 的默认行为 |
25
+ | 用量统计、成本展示、会话和报告 | ✅ | ✅ | 记录新产生的请求,不补录历史 |
26
+ | Telegram 远程执行与多 Bot 隔离 | ✅ | ✅ | 带进度、审批、附件和 Provider 管理 |
27
+ | 图片生成 | ✅ | ✅ | OpenAI-compatible Images API |
28
+ | `ai set codex` 持久配置 Codex App | ✅ | ✅ | 这是显式修改原生配置的命令 |
29
+ | 本地 HTTP Runtime API | ✅ | ✅ | Bearer 鉴权,适合本机集成 |
30
+ | Telegram 显式记忆 | 🟡 | 🟡 | `/remember` 等命令可用,普通聊天不会自动记忆 |
31
+ | 自动记忆、向量检索、终端记忆 | 🚧 | 🚧 | embedding 配置已就绪,入库和召回尚未接入 |
32
+ | 加密凭证、Skill 沉淀、学习治理、插件和 Web 界面 | 🚧 | 🚧 | 仍是后续路线,不要按已实现能力使用 |
20
33
 
21
- ## 当前边界
34
+ 如果你通过 npm 安装,实际可用的是 npm `0.0.5` 那一列;README 会随功能发布同步更新。
22
35
 
23
- - 已交付:本地 Provider Gateway、Claude/Codex 启动与 fallback、Telegram、会话、用量、诊断和早期 Memory。
24
- - 演进中:统一 Runtime 执行上下文、自动学习与记忆治理、Skill/Capability 生命周期、插件体系。
25
- - 长期方向:组织与租户隔离、能力注册与共享、企业集成、Web/Desktop/API 治理界面。
36
+ ## 它解决什么问题
26
37
 
27
- ## 通过 npm 安装
38
+ ### 1. Provider 管理从“手改配置”变成一条命令
28
39
 
29
- 运行环境需要 Node.js `22.19.0` 或更高版本。先确认版本,再执行全局安装;npm 会把本包注册为 `ai` 命令:
40
+ 你可以把多个 API Provider 放进统一配置中,记录名称、Base URL、API Key、默认模型、模式和优先级:
30
41
 
31
42
  ```bash
32
- node --version
33
- npm install --global @offerpilot/axiomruntime
34
- ai --version
35
- ai setup
43
+ ai provider add main https://api.example.com sk-xxx
44
+ ai provider add backup https://backup.example.com sk-yyy
45
+ ai provider list
46
+ ai status
36
47
  ```
37
48
 
38
- 如果 npm 显示 `EBADENGINE`,说明当前 Node.js 版本不受支持。请先升级到 `22.19.0+`,不要继续使用警告状态下安装的 CLI
49
+ 添加时会测试连通性、探测模型并建立缓存。`ai use` 会根据 Provider 健康状态、当前 CLI 可兼容的模型和 `level` 顺序选择候选;实际子进程非零退出时,还会继续尝试下一个 Provider
50
+
51
+ ### 2. 多开窗口,每个窗口使用不同 Provider
52
+
53
+ 这是 AxiomRuntime 的核心使用体验:
54
+
55
+ ```bash
56
+ # 窗口 A:主力 Provider
57
+ ai use claude main
58
+
59
+ # 窗口 B:备用 Provider + 指定模型
60
+ ai use claude backup claude-opus-4-6
61
+
62
+ # 窗口 C:Codex 使用另一家 Provider
63
+ ai use codex fenno gpt-5.5
64
+ ```
65
+
66
+ 每次 `ai use` 都为对应子进程单独准备路由和凭证。不同窗口的 Provider、模型和运行状态不会互相覆盖;并行数量只受本机资源和上游额度影响。
67
+
68
+ ### 3. 与 Claude Code、Codex 的原生配置保持独立
69
+
70
+ `ai use` 是临时注入,不是全局切换:
71
+
72
+ - **Claude Code**:生成临时 `--settings` 文件,注入 `ANTHROPIC_BASE_URL`、`ANTHROPIC_API_KEY` 和模型,进程结束后清理;不修改 `~/.claude/settings.json`。
73
+ - **Codex CLI**:通过本次进程的 `-c model_provider=...`、Provider 的 `base_url` 和 `env_key`,配合进程级 `OPENAI_API_KEY` 环境变量完成路由;不修改全局 `auth.json` 或 `config.toml`。Codex 本身也支持环境变量,AxiomRuntime 只是把它们限制在当前进程内。
74
+ - **直接运行原生命令**:你仍然可以直接执行 `claude` 或 `codex`,它们继续使用自己的原生配置,不受 `ai use` 影响。
75
+
76
+ 只有明确执行 `ai set codex` 时,AxiomRuntime 才会按你的选择持久写入 Codex 配置。这个命令与日常的临时启动是两条不同路径。
77
+
78
+ ### 4. Telegram 远程控制
79
+
80
+ 在手机上发一条消息,让本机的 Claude Code 或 Codex 执行任务,并把进度、结果、文件和图片发回 Telegram。它不是完成通知,而是一个真正的远程执行入口:
81
+
82
+ - 多 Bot 配置、进程、日志、会话数据库相互隔离
83
+ - 默认白名单鉴权;高风险 `/terminal` 命令必须二次确认
84
+ - 长任务编辑同一条进度消息,显示阶段、工具和回答预览
85
+ - 支持附件、图片、最近对话、引用和固定上下文
86
+ - `/provider` 可直接在手机上查看、添加、编辑、删除和检查 Provider
87
+ - Telegram 使用同一套顶层 Provider 配置和用量账本,不维护另一套路由规则
88
+
89
+ ### 5. 看见用量,而不是凭感觉猜成本
39
90
 
40
- `ai --version` 用于验证 npm 包和命令安装成功;`ai setup` 初始化 Runtime 配置,并可在确认后安装缺失的 Claude Code/Codex。之后可直接运行 `ai`。
91
+ Claude Code Codex 的请求会经过本次执行专属的本地用量代理。Runtime 读取 Provider 返回的真实 token 和成本,写入同一份 `usage.json`:
41
92
 
42
- 可用 root 完成全局安装,但日常执行 `ai` 和常驻服务应使用专用非 root 用户。若仍以 root 启动 Claude,Runtime 会把 `auto`/`bypassPermissions` 安全约束为默认权限、输出告警,并且绝不会传递 Claude Code 禁止 root 使用的 `--dangerously-skip-permissions`。
93
+ - Provider 返回的 `cost_usd` 时优先使用真实成本
94
+ - 只有 token、没有成本时,才按 `model-pricing.json` 估算
95
+ - 没有价格配置时显示未知,不伪造精确数字
96
+ - 安装 AxiomRuntime 之前产生的历史请求不会被扫描补录
43
97
 
44
- ## 通过 Homebrew 安装
98
+ ## 已发布功能
45
99
 
46
- 维护者发布新版本时可直接双击仓库根目录的 `发布.command`,交互选择 npm、Homebrew 或发布条件检查,无需记发布命令。npm 发布成功后,再选择 Homebrew;助手会读取 npm Registry 的真实 tarball 与 SHA256,并把 `Formula/axiomruntime.rb` 同步到 GitLab 项目 `linlangli/axiom`。
100
+ ### 安装与初始化
47
101
 
48
- 用户首次安装需要显式提供 GitLab 地址:
102
+ 运行环境要求 Node.js `22.19.0` 或更高版本,Node.js 20 不支持。Claude Code 和 Codex CLI 是外部依赖,不会被打包进 AxiomRuntime。
49
103
 
50
104
  ```bash
51
- brew tap linlangli/axiom https://gitlab.com/linlangli/axiom.git
52
- brew install linlangli/axiom/axiomruntime
105
+ node --version
106
+ npm install --global @offerpilot/axiomruntime
53
107
  ai --version
54
108
  ai setup
55
109
  ```
56
110
 
57
- ## 安装开发依赖
111
+ `ai setup` 会创建 `~/.ai-gateway/`,引导添加第一个 Provider,测试连通性、探测模型,并询问是否安装缺失的 Claude Code / Codex。
58
112
 
59
- 运行和开发需要 Node.js `22.19.0` 或更高版本。该下限与锁定依赖的运行时要求一致;Node.js 20 不受支持。
113
+ 无人值守场景可以使用:
60
114
 
61
115
  ```bash
62
- npm install
116
+ ai setup --yes --skip-tools
117
+ ai setup --yes --install-tools
118
+ ai setup --skip-provider
63
119
  ```
64
120
 
65
- ## 本机 CLI 前置条件
121
+ 如果 npm `EBADENGINE`,请先升级 Node.js,再重新安装,不要忽略警告继续运行。
66
122
 
67
- AI Gateway 不内置 Claude Code 或 Codex。它们作为彼此独立的外部 CLI 使用,需要先在本机安装并能通过 `PATH` 或常见安装路径找到。
123
+ ### Provider、启动与诊断
68
124
 
69
- 可用下面的命令初始化和检查:
125
+ ```bash
126
+ ai add # 交互式添加 Provider
127
+ ai provider add <name> <base-url> <api-key>
128
+ ai provider list # 只读配置,不联网
129
+ ai provider edit
130
+ ai provider delete <name>
131
+
132
+ ai status # 刷新健康度、模型缓存和用量
133
+ ai use claude [provider] [model]
134
+ ai use codex [provider] [model]
135
+ ai last # 沿用上次选择
136
+ ai doctor # 检查并安全修复内部状态
137
+ ```
138
+
139
+ `ai status` 默认只做连通性和模型检查,不发真实对话请求。`ai doctor` 可以重建缺失或损坏的缓存、用量和会话文件,但 Provider 配置无效时只报告,不会覆盖凭证。
140
+
141
+ ### 会话、日志、上下文与图片
70
142
 
71
143
  ```bash
72
- ai setup
73
- ai doctor
144
+ ai session list
145
+ ai session resume <session-id>
146
+ ai session clear
147
+ ai report
148
+ ai report --print <session-id>
149
+ ai log --print 100
150
+ ai context --print
151
+
152
+ ai image # 交互选择 Provider、模型和提示词
153
+ ai image <provider> "一只戴墨镜的猫"
154
+ ai image <provider> <model> "数字插画"
74
155
  ```
75
156
 
76
- ## 构建
157
+ 图片生成调用已配置 Provider 的 OpenAI-compatible `/v1/images/generations`。`b64_json` 会以 `0600` 权限保存到当前目录;远程 `https` 图片只输出 URL,不会自动下载不受信任的地址。图片请求最长等待 120 秒,失败后会尝试下一个兼容 Provider。
158
+
159
+ ### Codex App 的持久配置
160
+
161
+ 如果需要让 Codex App 或直接执行的 `codex` 持续使用某个 Provider,显式运行:
77
162
 
78
163
  ```bash
79
- npm run build
164
+ ai set codex provider <provider> <gpt-model>
165
+ ai set codex provider <provider>
166
+ ai set codex chatgpt
167
+ ai set codex proxy 127.0.0.1:7890
168
+ ai set codex proxy off
80
169
  ```
81
170
 
82
- ## 本地测试
171
+ 这条命令会原子更新 Codex 的 `config.toml` / `auth.json`,保留无关配置,失败时回滚;macOS 上会尝试重启 Codex App,并清理重启进程继承的 `OPENAI_API_KEY`,避免旧凭证覆盖刚选择的 Provider。切换到自定义 Provider 会替换当前 ChatGPT token,切回官方账号时需要重新登录。
172
+
173
+ ### Telegram Bot
83
174
 
84
175
  ```bash
85
- npm test
176
+ ai telegram add ops
177
+ ai telegram config set token <bot-token> --bot ops
178
+ ai telegram config set allowedUserIds <telegram-user-id> --bot ops
179
+ ai telegram start --bot ops
86
180
  ```
87
181
 
88
- ## 内网 Runtime 服务
182
+ 常用管理命令:
89
183
 
90
- 常驻服务为其他应用提供两类受鉴权接口,同时保留现有 `ai` CLI 和 Telegram 行为:
184
+ ```bash
185
+ ai telegram list
186
+ ai telegram status --bot ops
187
+ ai telegram logs --bot ops -f
188
+ ai telegram edit --bot ops
189
+ ai telegram stop --bot ops
190
+ ai telegram delete --bot ops
191
+ ```
91
192
 
92
- - `POST /v1/chat/completions`、`/v1/responses`、`/v1/embeddings`:OpenAI 兼容模型调用,按 provider 优先级、模型目录、健康度和熔断状态自动切换。
93
- - `POST /v1/agent/runs`:复用 Telegram 背后的 Claude/Codex Engine。只接受受控 `workspace_id`,默认 `read_only`,服务端限制并发和超时。
94
- - `GET /health/live`、`/health/ready`:不含 Secret 的存活与就绪检查;其他接口必须使用 Bearer Token。
193
+ Bot 中可使用 `/claude`、`/codex`、`/reset`、`/fresh`、`/cwd`、`/config`、`/provider`、`/usage`、`/remember`、`/forget`、`/memories`、`/pin`、`/unpin`、`/terminal`、`/restart` `/help`。如果本机无法直连 Telegram API,可设置:
95
194
 
96
195
  ```bash
97
- npm run build
98
- AI_GATEWAY_SERVER_TOKEN='replace-me' npm run server
196
+ ai telegram config set proxyUrl http://127.0.0.1:7890 --bot ops
99
197
  ```
100
198
 
101
- 生产 Compose 位于 `deploy/docker-compose.prod.yml`,默认仅映射宿主机 `127.0.0.1:8020`,并加入外部 Docker 网络 `ai-runtime`。Provider 配置挂载到 `/var/lib/ai-gateway`,服务 Token 通过只读文件挂载,不写入镜像或仓库。
199
+ ## 🟡 记忆:现在能做什么、不能做什么
200
+
201
+ 当前记忆能力只接入 Telegram,且以**显式操作**为主:
102
202
 
103
- ### GitLab 自动部署
203
+ ```text
204
+ /remember 记住:这个项目使用 pnpm
205
+ /memories
206
+ /forget <memory-id>
207
+ ```
104
208
 
105
- 默认分支提交通过测试后,GitLab CI 会构建以 commit SHA 标记的镜像,并由锁定当前项目的受保护 Runner(标签 `axiomruntime-prod`)部署到 `154.201.73.41:/opt/AxiomRuntime`。部署只操作 `axiomruntime` Compose 项目;健康检查失败时恢复上一镜像,不清理服务器上的其他容器或镜像。
209
+ 已经实现:
106
210
 
107
- 镜像构建会在一次性 build stage 中编译 `better-sqlite3` 原生 addon,再把裁掉开发依赖后的 production dependency tree 复制到相同 Node.js 版本的最终镜像;最终镜像不包含编译器。
211
+ - Telegram 中的 `global` / `bot` 两级 scope
212
+ - 显式新增、查看、删除和固定记忆
213
+ - 最近会话与可见记忆注入当前 Telegram prompt
214
+ - SQLite 存储和短期记忆 TTL
215
+ - embedding Provider 的配置和连通性检查
108
216
 
109
- 首次部署前,服务器必须已有 `/opt/AxiomRuntime/config/providers.json` 和 `/opt/AxiomRuntime/secrets/server-token`,且容器用户 `10001:10001` 可以读写配置目录。CI 只校验这些文件,不生成或覆盖凭据。
217
+ 尚未实现:
110
218
 
111
- Telegram 容器默认不启动,以免与现有 long polling 进程争抢消息。完成迁移并停止旧 Bot 后,在服务器 `/opt/AxiomRuntime/.env` 中设置:
219
+ - 普通聊天自动抽取和自动写入
220
+ - 终端 `ai use` 的记忆接入
221
+ - 向量入库、向量召回和语义搜索
222
+ - Claim / Evidence 权威账本、多源召回、冲突与时序处理
223
+ - credential 类记忆的安全存储
112
224
 
113
- ```dotenv
114
- COMPOSE_PROFILES=telegram
115
- ```
225
+ 因此,`ai memory embedding` 目前只是为未来检索准备模型配置;它不会让当前记忆自动变成向量搜索。
116
226
 
117
- 启用前还要把各 Bot 的 `defaultCwd` 改为容器内的 `/workspace/...`,对应宿主机 `/opt/AxiomRuntime/workspaces/...`。之后的自动部署会同时更新 Runtime API 和所有位于 `config/telegram/bots/*.json` 的 Telegram Bot。资源上限和工作目录可参考 `.env.server.example` 调整。
227
+ ## 本地 Runtime API
118
228
 
119
- ## 本机注册 `ai` 命令
229
+ 从源码构建后,可以启动带 Bearer 鉴权的本地 HTTP 服务:
120
230
 
121
231
  ```bash
122
- npm link
232
+ npm install
233
+ npm run build
234
+ AI_GATEWAY_SERVER_TOKEN='replace-me' npm run server
123
235
  ```
124
236
 
125
- 注册后可直接使用:
237
+ 已实现路由:
126
238
 
127
- ```bash
128
- ai
129
- ai --version
130
- ai setup
131
- ai help
132
- ai provider add
133
- ai provider list
134
- ai provider edit
135
- ai provider delete
136
- ai set codex chatgpt
137
- ai set codex provider 51talk-cq gpt-5.6-sol
138
- ai set codex provider 51talk-cq
139
- ai set codex proxy 127.0.0.1:7890
140
- ai telegram
141
- ai telegram list
142
- ai telegram start --bot ops
143
- ai telegram status --bot ops
144
- ai status
145
- ai log
146
- ai log --print
147
- ai context
148
- ai context --print
149
- ai session
150
- ai session list
151
- ai session clear
152
- ai report
153
- ai doctor
154
- ```
239
+ | 路由 | 用途 |
240
+ |---|---|
241
+ | `POST /v1/chat/completions` | OpenAI 兼容对话 |
242
+ | `POST /v1/responses` | OpenAI Responses 协议 |
243
+ | `POST /v1/embeddings` | 向量化接口 |
244
+ | `GET /v1/models` | 当前模型列表 |
245
+ | `POST /v1/agent/runs` | 受控的 Claude/Codex Agent Run |
246
+
247
+ 服务只接受受控 `workspace_id`,默认只读,限制请求体、并发和执行时长;不提供任意目录访问,也不提供 `bypassPermissions`。
155
248
 
156
- ## 配置文件
249
+ ## 配置文件与安全边界
157
250
 
158
- 默认从全局配置目录读取:
251
+ 默认配置目录是 `~/.ai-gateway/`,可以用 `AI_GATEWAY_HOME` 覆盖:
159
252
 
160
253
  ```text
161
- ~/.ai-gateway/providers.json
162
- ~/.ai-gateway/.ai-cache.json
163
- ~/.ai-gateway/usage.json
164
- ~/.ai-gateway/model-pricing.json
165
- ~/.ai-gateway/sessions.json
166
- ~/.ai-gateway/telegram.json
167
- ~/.ai-gateway/telegram.sqlite
168
- ~/.ai-gateway/telegram.log
169
- ~/.ai-gateway/reports/
170
- ~/.ai-gateway/exports/
171
- ~/.ai-gateway/.ai-gateway.log
254
+ ~/.ai-gateway/providers.json Provider 配置
255
+ ~/.ai-gateway/.ai-cache.json 模型缓存、健康状态和上次选择
256
+ ~/.ai-gateway/usage.json 用量账本
257
+ ~/.ai-gateway/model-pricing.json 本地成本估算价格
258
+ ~/.ai-gateway/sessions.json CLI 会话记录
259
+ ~/.ai-gateway/memory.json embedding 配置
260
+ ~/.ai-gateway/memory.sqlite Telegram 记忆
261
+ ~/.ai-gateway/telegram/bots/ 多 Bot 配置、PID、日志和数据库
262
+ ~/.ai-gateway/reports/ 会话报告
263
+ ~/.ai-gateway/.ai-gateway.log 结构化日志
172
264
  ```
173
265
 
174
- 可通过 `AI_GATEWAY_HOME` 覆盖配置目录。
266
+ 请注意:
267
+
268
+ - `providers.json` 和 `memory.json` 中的 API Key 当前仍是明文保存。请限制目录权限,绝不要提交到仓库;加密凭证库尚未实现。
269
+ - `ai use` 的临时注入文件会在进程结束后清理;`ai set codex` 是例外,因为它就是明确的持久配置操作。
270
+ - 配置写入使用文件锁、临时文件和原子重命名,降低 CLI 与 Telegram 并发写入时损坏文件的风险。
271
+ - 日志不会记录完整 API Key,但会记录脱敏指纹;Provider 名称、模型和部分运行元数据会进入日志。
272
+ - root 用户运行 Claude 的自动权限模式时,Runtime 会收紧权限,不会传递官方禁止 root 使用的危险参数。
273
+ - Telegram 默认只允许白名单用户;如果显式开启 `allowAllUsers`,任何能访问 Bot 的人都可能在你的机器上执行命令。
274
+
275
+ ## 🚧 尚未实现与未来路线
175
276
 
176
- `providers.json` 包含 provider 配置和 SecretStore 引用,不要提交到仓库。
277
+ 以下内容不要按当前功能使用:
177
278
 
178
- Telegram Bot 集成说明:
279
+ 1. **更完整的记忆**:自动抽取、向量检索、终端接入、Claim / Evidence、冲突和时序治理。
280
+ 2. **可复用能力沉淀**:把经过验证的重复流程沉淀为 Skill 或 Workflow,并支持评测、激活和回滚。
281
+ 3. **统一 Runtime 契约**:统一 CLI、Telegram、HTTP 的身份、权限、事件、取消、幂等和恢复语义。
282
+ 4. **更完整的安全能力**:加密凭证、登录态托管、细粒度策略和可审计的权限决策。
283
+ 5. **更多接入面**:Web、桌面、调度和插件体系。当前 `ai web` 只是占位命令。
179
284
 
180
- - Bot 使用 `ai telegram add <name>` 创建,并以统一的 `--bot <name>` 选择目标;例如 `ai telegram edit --bot ops`、`start|stop|status --bot ops`、`delete --bot ops`。裸 `ai telegram` 会先选操作再选 Bot,删除有二次确认;批量生命周期操作使用 `--all`。
181
- - `ai telegram config set <key> <value> --bot <name>` 只修改指定 Bot;旧位置参数写法继续兼容。
182
- - 如果本机不能直连 Telegram API,可设置 `ai telegram config set proxyUrl http://127.0.0.1:7890 --bot <name>`;代理地址需包含协议,交互式新增或编辑 Bot 时也会显示该完整示例。
183
- - Bot 首次 `/start` 或 `/reset` 会先选择工作路径,再选择 Claude Code/Codex,并展示当前会话、provider 和模型卡片。
184
- - Bot 会记录选择过的工作路径;自定义路径会按 `~/输入名称` 创建或复用。
185
- - Telegram 默认命令菜单只保留 `/claude`、`/codex`、`/reset`、`/cwd` 和 `/help`;选择 CLI 后,会在当前 chat 的 `/` 菜单追加该 CLI 的 slash 命令映射。
186
- - Telegram 远程执行复用 AI Gateway 顶层 provider,不维护另一套 provider 配置。
187
- - Telegram Codex 执行会自动走本地 OpenAI 兼容代理记录 provider usage;`/usage` 有 provider cost 时直接展示,否则按 `model-pricing.json` 估算。
285
+ 这些是产品路线,不是已经发布的功能。README、使用指南和产品文档会继续按 🟢 / 🟡 / 🔴 状态同步。
286
+
287
+ ## 开发
288
+
289
+ ```bash
290
+ npm install
291
+ npm run build
292
+ npm test
293
+ npm link
294
+ ```
295
+
296
+ 完整命令说明见:
297
+
298
+ - [使用指南](./docs/usage.md)
299
+ - [技术架构](./docs/architecture.md)
300
+ - [产品需求文档](./docs/prd.md)
301
+ - [HTML 命令手册](./docs/USAGE.html)
188
302
 
189
- 更多命令用法见:
303
+ ## 许可
190
304
 
191
- - [docs/usage.md](./docs/usage.md)
192
- - [docs/USAGE.html](./docs/USAGE.html)
305
+ 本项目当前未在 README 中声明开源许可证。使用前请以仓库中的许可证文件和项目发布说明为准。
@@ -0,0 +1,75 @@
1
+ import { readCache } from "../../core/config/cache-store.js";
2
+ import { generateImage } from "../../core/images/image-generation-service.js";
3
+ import { listProviders, refreshAllProviders } from "../../core/providers/provider-service.js";
4
+ import { promptSelect, promptText } from "../prompts/prompt.js";
5
+ export async function runImageCommand(args) {
6
+ await refreshAllProviders();
7
+ const [providers, cache] = await Promise.all([listProviders(), readCache()]);
8
+ const input = await resolveImageCommandInput(args, providers, cache.providers);
9
+ const result = await generateImage(input);
10
+ console.log(`Generated with ${result.provider} / ${result.model}`);
11
+ if (result.filePath)
12
+ console.log(`Image saved: ${result.filePath}`);
13
+ if (result.url)
14
+ console.log(`Image URL: ${result.url}`);
15
+ if (result.revisedPrompt)
16
+ console.log(`Revised prompt: ${result.revisedPrompt}`);
17
+ }
18
+ export async function resolveImageCommandInput(args, providers, providerCache, select = promptSelect, text = promptText) {
19
+ if (!providers.length) {
20
+ throw new Error("No providers configured. Run `ai add` first.");
21
+ }
22
+ const target = args[0]?.trim();
23
+ const namedProvider = target ? providers.find((provider) => provider.name === target) : undefined;
24
+ if (target && !namedProvider) {
25
+ const prompt = args.slice(1).join(" ").trim() || await text("Image prompt");
26
+ return { target, prompt: requirePrompt(prompt) };
27
+ }
28
+ const provider = namedProvider ?? await selectImageProvider(providers, providerCache, select);
29
+ const models = getProviderImageModels(provider, providerCache);
30
+ const secondArg = args[1]?.trim();
31
+ const hasExplicitModel = Boolean(secondArg && models.includes(secondArg));
32
+ const model = hasExplicitModel
33
+ ? secondArg
34
+ : target
35
+ ? getDefaultImageModel(provider, models)
36
+ : await selectImageModel(provider, models, select);
37
+ const promptArgs = hasExplicitModel ? args.slice(2) : args.slice(1);
38
+ const prompt = promptArgs.join(" ").trim() || await text("Image prompt");
39
+ return { provider: provider.name, model, prompt: requirePrompt(prompt) };
40
+ }
41
+ async function selectImageProvider(providers, providerCache, select) {
42
+ const availableProviders = providers.filter((provider) => getProviderImageModels(provider, providerCache).length);
43
+ if (!availableProviders.length) {
44
+ throw new Error("No configured provider has available image models. Run `ai status` to refresh model lists.");
45
+ }
46
+ const providerName = await select("Select image provider", availableProviders.map((provider) => ({
47
+ label: `${provider.name} / ${getProviderImageModels(provider, providerCache).length} models`,
48
+ value: provider.name
49
+ })));
50
+ return availableProviders.find((provider) => provider.name === providerName);
51
+ }
52
+ async function selectImageModel(provider, models, select) {
53
+ if (!models.length) {
54
+ throw new Error(`Provider ${provider.name} has no available image models. Run \`ai status\` first.`);
55
+ }
56
+ return select(`Select image model for ${provider.name}`, models.map((model) => ({ label: model, value: model })));
57
+ }
58
+ function getProviderImageModels(provider, providerCache) {
59
+ const cachedModels = providerCache[provider.name]?.models ?? [];
60
+ const models = cachedModels.length ? [...new Set(cachedModels)] : provider.model ? [provider.model] : [];
61
+ return provider.model && models.includes(provider.model)
62
+ ? [provider.model, ...models.filter((model) => model !== provider.model)]
63
+ : models;
64
+ }
65
+ function getDefaultImageModel(provider, models) {
66
+ if (provider.model && models.includes(provider.model))
67
+ return provider.model;
68
+ throw new Error(`Provider ${provider.name} has no usable default image model. Specify one of: ${models.join(", ") || "(none)"}.`);
69
+ }
70
+ function requirePrompt(value) {
71
+ const prompt = value.trim();
72
+ if (!prompt)
73
+ throw new Error("Image prompt is required.");
74
+ return prompt;
75
+ }
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ import { runContextCommand } from "./commands/context.js";
4
4
  import { runDoctorCommand } from "./commands/doctor.js";
5
5
  import { runProviderDeleteCommand, runProviderEditCommand } from "./commands/edit.js";
6
6
  import { runHelpCommand } from "./commands/help.js";
7
+ import { runImageCommand } from "./commands/image.js";
7
8
  import { runListCommand } from "./commands/list.js";
8
9
  import { runLogCommand } from "./commands/log.js";
9
10
  import { runMemoryCommand } from "./commands/memory.js";
@@ -53,7 +54,7 @@ async function main() {
53
54
  console.error("Run `ai help` for usage.");
54
55
  process.exitCode = 1;
55
56
  }
56
- function sanitizeArgs(command, args) {
57
+ export function sanitizeArgs(command, args) {
57
58
  const redactedProxyArgs = redactCodexProxyCommandArgs(command, args);
58
59
  if (redactedProxyArgs)
59
60
  return redactedProxyArgs;
@@ -69,6 +70,9 @@ function sanitizeArgs(command, args) {
69
70
  if (command === "telegram") {
70
71
  return maskTelegramConfigSecretArgs(args);
71
72
  }
73
+ if (command === "image" && args.length > 1) {
74
+ return [args[0], `[prompt ${args.slice(1).join(" ").length} chars]`];
75
+ }
72
76
  return args.map((arg, index) => {
73
77
  const previous = args[index - 1]?.toLowerCase();
74
78
  if (previous === "--api-key" || previous === "--apikey" || previous === "--key") {
@@ -136,7 +140,7 @@ function maskSecretArg(value) {
136
140
  return `${value.slice(0, 4)}****${value.slice(-4)}`;
137
141
  }
138
142
  async function runInteractive() {
139
- const interactiveCommandNames = ["setup", "use", "set", "add", "memory", "telegram", "status", "session", "report", "doctor", "log", "context", "help", "last"];
143
+ const interactiveCommandNames = ["setup", "use", "image", "set", "add", "memory", "telegram", "status", "session", "report", "doctor", "log", "context", "help", "last"];
140
144
  const command = await promptSelect("AI Gateway", [
141
145
  ...interactiveCommandNames
142
146
  .flatMap((name) => {
@@ -255,6 +259,11 @@ function registerBuiltInCommands() {
255
259
  description: "use",
256
260
  run: (args) => runUseCommand(args)
257
261
  });
262
+ registerCliCommand({
263
+ name: "image",
264
+ description: "generate image",
265
+ run: (args) => runImageCommand(args)
266
+ });
258
267
  registerCliCommand({
259
268
  name: "set",
260
269
  description: "set Codex provider or proxy",
@@ -28,9 +28,9 @@ const DOC_CANDIDATES = [
28
28
  "README.md",
29
29
  "AGENTS.md",
30
30
  "CLAUDE.md",
31
- "docs/00-overview.md",
32
- "docs/CONVENTIONS.md",
33
- "docs/usage.md"
31
+ "docs/usage.md",
32
+ "docs/architecture.md",
33
+ "docs/prd.md"
34
34
  ];
35
35
  export async function generateProjectContext(root = getProjectRoot()) {
36
36
  const content = [