@offerpilot/axiomruntime 0.0.1 → 0.0.3
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 +18 -11
- package/dist/cli/commands/set.js +121 -0
- package/dist/cli/commands/telegram.js +4 -3
- package/dist/cli/index.js +11 -1
- package/dist/cli/sanitize-args.js +6 -0
- package/dist/core/integrations/codex-provider-config.js +540 -0
- package/dist/core/runner/claude-permission-policy.js +19 -0
- package/dist/core/runner/engine-registry.js +4 -4
- package/dist/core/runner/tool-runner.js +46 -9
- package/dist/core/status/doctor-service.js +17 -0
- package/dist/telegram/bot-registry.js +17 -4
- package/dist/telegram/engine/claude-engine.js +23 -6
- package/dist/telegram/engine/codex-engine.js +0 -2
- package/dist/telegram/engine/engine-utils.js +14 -3
- package/docs/USAGE.html +41 -9
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -26,33 +26,34 @@ AI 与维护者使用的英文权威文档位于 `AGENTS.md`、`docs/AI_RUNTIME_
|
|
|
26
26
|
|
|
27
27
|
## 通过 npm 安装
|
|
28
28
|
|
|
29
|
-
运行环境需要 Node.js `22.19.0`
|
|
29
|
+
运行环境需要 Node.js `22.19.0` 或更高版本。先确认版本,再执行全局安装;npm 会把本包注册为 `ai` 命令:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
+
node --version
|
|
32
33
|
npm install --global @offerpilot/axiomruntime
|
|
33
34
|
ai --version
|
|
34
35
|
ai setup
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
如果 npm 显示 `EBADENGINE`,说明当前 Node.js 版本不受支持。请先升级到 `22.19.0+`,不要继续使用警告状态下安装的 CLI。
|
|
39
|
+
|
|
37
40
|
`ai --version` 用于验证 npm 包和命令安装成功;`ai setup` 初始化 Runtime 配置,并可在确认后安装缺失的 Claude Code/Codex。之后可直接运行 `ai`。
|
|
38
41
|
|
|
42
|
+
可用 root 完成全局安装,但日常执行 `ai` 和常驻服务应使用专用非 root 用户。若仍以 root 启动 Claude,Runtime 会把 `auto`/`bypassPermissions` 安全约束为默认权限、输出告警,并且绝不会传递 Claude Code 禁止 root 使用的 `--dangerously-skip-permissions`。
|
|
43
|
+
|
|
39
44
|
## 通过 Homebrew 安装
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
维护者发布新版本时可直接双击仓库根目录的 `发布.command`,交互选择 npm、Homebrew 或发布条件检查,无需记发布命令。npm 发布成功后,再选择 Homebrew;助手会读取 npm Registry 的真实 tarball 与 SHA256,并把 `Formula/axiomruntime.rb` 同步到 GitLab 项目 `linlangli/axiom`。
|
|
47
|
+
|
|
48
|
+
用户首次安装需要显式提供 GitLab 地址:
|
|
42
49
|
|
|
43
50
|
```bash
|
|
44
|
-
brew tap
|
|
45
|
-
brew install axiomruntime
|
|
51
|
+
brew tap linlangli/axiom https://gitlab.com/linlangli/axiom.git
|
|
52
|
+
brew install linlangli/axiom/axiomruntime
|
|
46
53
|
ai --version
|
|
47
54
|
ai setup
|
|
48
55
|
```
|
|
49
56
|
|
|
50
|
-
如果 Tap 不托管在 GitHub,需要在首次添加时显式提供公开 Git 地址:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
brew tap offerpilot/tap <homebrew-tap-public-git-url>
|
|
54
|
-
```
|
|
55
|
-
|
|
56
57
|
## 安装开发依赖
|
|
57
58
|
|
|
58
59
|
运行和开发需要 Node.js `22.19.0` 或更高版本。该下限与锁定依赖的运行时要求一致;Node.js 20 不受支持。
|
|
@@ -103,6 +104,8 @@ AI_GATEWAY_SERVER_TOKEN='replace-me' npm run server
|
|
|
103
104
|
|
|
104
105
|
默认分支提交通过测试后,GitLab CI 会构建以 commit SHA 标记的镜像,并由锁定当前项目的受保护 Runner(标签 `axiomruntime-prod`)部署到 `154.201.73.41:/opt/AxiomRuntime`。部署只操作 `axiomruntime` Compose 项目;健康检查失败时恢复上一镜像,不清理服务器上的其他容器或镜像。
|
|
105
106
|
|
|
107
|
+
镜像构建会在一次性 build stage 中编译 `better-sqlite3` 原生 addon,再把裁掉开发依赖后的 production dependency tree 复制到相同 Node.js 版本的最终镜像;最终镜像不包含编译器。
|
|
108
|
+
|
|
106
109
|
首次部署前,服务器必须已有 `/opt/AxiomRuntime/config/providers.json` 和 `/opt/AxiomRuntime/secrets/server-token`,且容器用户 `10001:10001` 可以读写配置目录。CI 只校验这些文件,不生成或覆盖凭据。
|
|
107
110
|
|
|
108
111
|
Telegram 容器默认不启动,以免与现有 long polling 进程争抢消息。完成迁移并停止旧 Bot 后,在服务器 `/opt/AxiomRuntime/.env` 中设置:
|
|
@@ -130,6 +133,10 @@ ai provider add
|
|
|
130
133
|
ai provider list
|
|
131
134
|
ai provider edit
|
|
132
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
|
|
133
140
|
ai telegram
|
|
134
141
|
ai telegram list
|
|
135
142
|
ai telegram start --bot ops
|
|
@@ -172,7 +179,7 @@ Telegram Bot 集成说明:
|
|
|
172
179
|
|
|
173
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`。
|
|
174
181
|
- `ai telegram config set <key> <value> --bot <name>` 只修改指定 Bot;旧位置参数写法继续兼容。
|
|
175
|
-
- 如果本机不能直连 Telegram API,可设置 `ai telegram config set proxyUrl http://127.0.0.1:7890 --bot <name
|
|
182
|
+
- 如果本机不能直连 Telegram API,可设置 `ai telegram config set proxyUrl http://127.0.0.1:7890 --bot <name>`;代理地址需包含协议,交互式新增或编辑 Bot 时也会显示该完整示例。
|
|
176
183
|
- Bot 首次 `/start` 或 `/reset` 会先选择工作路径,再选择 Claude Code/Codex,并展示当前会话、provider 和模型卡片。
|
|
177
184
|
- Bot 会记录选择过的工作路径;自定义路径会按 `~/输入名称` 创建或复用。
|
|
178
185
|
- Telegram 默认命令菜单只保留 `/claude`、`/codex`、`/reset`、`/cwd` 和 `/help`;选择 CLI 后,会在当前 chat 的 `/` 菜单追加该 CLI 的 slash 命令映射。
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { writeLog } from "../../core/logs/log-service.js";
|
|
2
|
+
import { readCache } from "../../core/config/cache-store.js";
|
|
3
|
+
import { listAvailableCodexGptModels, resolveCodexProviderModel, setCodexProvider, setCodexProxy } from "../../core/integrations/codex-provider-config.js";
|
|
4
|
+
import { listProviders } from "../../core/providers/provider-service.js";
|
|
5
|
+
import { promptSelect, promptText } from "../prompts/prompt.js";
|
|
6
|
+
const SET_CODEX_USAGE = "Usage: ai set codex [chatgpt | provider [name] [model-id] | proxy <host:port|url|off>]";
|
|
7
|
+
export async function runSetCommand(args) {
|
|
8
|
+
const [targetArg, modeArg, providerNameArg, modelArg, ...extra] = args;
|
|
9
|
+
const target = targetArg ?? "codex";
|
|
10
|
+
if (target !== "codex") {
|
|
11
|
+
throw new Error(SET_CODEX_USAGE);
|
|
12
|
+
}
|
|
13
|
+
if (extra.length) {
|
|
14
|
+
throw new Error(SET_CODEX_USAGE);
|
|
15
|
+
}
|
|
16
|
+
const mode = await getMode(modeArg);
|
|
17
|
+
if (mode === "proxy") {
|
|
18
|
+
if (modelArg)
|
|
19
|
+
throw new Error(SET_CODEX_USAGE);
|
|
20
|
+
const proxyValue = providerNameArg ?? await promptText("Codex proxy (host:port, URL, or off)");
|
|
21
|
+
const result = await setCodexProxy(proxyValue);
|
|
22
|
+
console.log(result.proxyUrl ? `Codex proxy set to: ${result.proxyUrl}` : "Codex proxy cleared.");
|
|
23
|
+
console.log(`Updated: ${result.configPath}`);
|
|
24
|
+
if (!result.environment.applied && result.environment.reason) {
|
|
25
|
+
console.log(result.environment.reason);
|
|
26
|
+
}
|
|
27
|
+
printRestartResult(result.restart);
|
|
28
|
+
await writeLog({
|
|
29
|
+
category: "provider",
|
|
30
|
+
action: "codex_proxy_set",
|
|
31
|
+
message: result.proxyUrl ? "Codex proxy enabled." : "Codex proxy cleared.",
|
|
32
|
+
metadata: {
|
|
33
|
+
enabled: Boolean(result.proxyUrl),
|
|
34
|
+
desktopEnvironmentApplied: result.environment.applied,
|
|
35
|
+
restartScheduled: result.restart.scheduled
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (mode === "chatgpt" && (providerNameArg || modelArg)) {
|
|
41
|
+
throw new Error("Usage: ai set codex chatgpt");
|
|
42
|
+
}
|
|
43
|
+
const selection = mode === "chatgpt"
|
|
44
|
+
? { type: "chatgpt" }
|
|
45
|
+
: { type: "provider", ...await getProviderAndModel(providerNameArg, modelArg) };
|
|
46
|
+
if (mode === "chatgpt") {
|
|
47
|
+
console.log("Opening ChatGPT login in your browser...");
|
|
48
|
+
}
|
|
49
|
+
const result = await setCodexProvider(selection);
|
|
50
|
+
console.log(`Codex provider set to: ${result.providerName}`);
|
|
51
|
+
if (result.model)
|
|
52
|
+
console.log(`Codex model set to: ${result.model}`);
|
|
53
|
+
console.log(`Updated: ${result.configPath}`);
|
|
54
|
+
console.log(`Updated: ${result.authPath}`);
|
|
55
|
+
printRestartResult(result.restart);
|
|
56
|
+
await writeLog({
|
|
57
|
+
category: "provider",
|
|
58
|
+
action: "codex_provider_set",
|
|
59
|
+
message: `Codex provider set to ${result.providerName}.`,
|
|
60
|
+
metadata: {
|
|
61
|
+
selection: result.selection,
|
|
62
|
+
provider: result.providerName,
|
|
63
|
+
providerId: result.providerId,
|
|
64
|
+
model: result.model,
|
|
65
|
+
restartScheduled: result.restart.scheduled
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function getMode(value) {
|
|
70
|
+
if (value === "chatgpt" || value === "provider" || value === "proxy")
|
|
71
|
+
return value;
|
|
72
|
+
if (value) {
|
|
73
|
+
throw new Error(`Unknown Codex setting: ${value}`);
|
|
74
|
+
}
|
|
75
|
+
return promptSelect("Set Codex", [
|
|
76
|
+
{ label: "ChatGPT account", value: "chatgpt" },
|
|
77
|
+
{ label: "Configured provider", value: "provider" },
|
|
78
|
+
{ label: "Proxy", value: "proxy" }
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
function printRestartResult(restart) {
|
|
82
|
+
if (restart.scheduled) {
|
|
83
|
+
console.log(`Restarting ${restart.appPath ?? "Codex app"}...`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(`Restart Codex app manually. ${restart.reason ?? ""}`.trim());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function getProviderAndModel(name, requestedModel) {
|
|
90
|
+
const [providers, cache] = await Promise.all([listProviders(), readCache()]);
|
|
91
|
+
return selectCodexProviderAndModel(providers, cache.providers, name, requestedModel);
|
|
92
|
+
}
|
|
93
|
+
export async function selectCodexProviderAndModel(providers, providerCache, name, requestedModel, select = promptSelect) {
|
|
94
|
+
if (!providers.length) {
|
|
95
|
+
throw new Error("No providers configured. Run `ai provider add` first.");
|
|
96
|
+
}
|
|
97
|
+
if (!name && !providers.some((provider) => providerGptModels(provider.name, providerCache).length)) {
|
|
98
|
+
throw new Error("No configured provider has available GPT models. Run `ai status` to refresh model lists.");
|
|
99
|
+
}
|
|
100
|
+
const providerName = name ?? await select("Select provider for Codex", providers.map((provider) => ({
|
|
101
|
+
label: `${provider.name} / ${providerGptModels(provider.name, providerCache).length} GPT models`,
|
|
102
|
+
value: provider.name
|
|
103
|
+
})));
|
|
104
|
+
const provider = providers.find((item) => item.name === providerName);
|
|
105
|
+
if (!provider) {
|
|
106
|
+
throw new Error(`Provider not found: ${providerName}`);
|
|
107
|
+
}
|
|
108
|
+
const availableModels = providerGptModels(provider.name, providerCache);
|
|
109
|
+
if (!availableModels.length) {
|
|
110
|
+
resolveCodexProviderModel(provider.name, availableModels);
|
|
111
|
+
}
|
|
112
|
+
const model = requestedModel
|
|
113
|
+
? resolveCodexProviderModel(provider.name, availableModels, requestedModel)
|
|
114
|
+
: name
|
|
115
|
+
? resolveCodexProviderModel(provider.name, availableModels)
|
|
116
|
+
: await select(`Select GPT model for ${provider.name}`, availableModels.map((availableModel) => ({ label: availableModel, value: availableModel })));
|
|
117
|
+
return { provider, model };
|
|
118
|
+
}
|
|
119
|
+
function providerGptModels(providerName, providerCache) {
|
|
120
|
+
return listAvailableCodexGptModels(providerCache[providerName]?.models ?? []);
|
|
121
|
+
}
|
|
@@ -8,6 +8,7 @@ import { botExists, isBotRunning, isValidBotName, listBots, migrateLegacyConfig,
|
|
|
8
8
|
import { ensureTelegramLogFile, readRecentTelegramLogLines } from "../../telegram/log.js";
|
|
9
9
|
import { checkTelegramApi, getTelegramProxyUrl } from "../../telegram/network.js";
|
|
10
10
|
import { promptConfirm, promptSelect, promptText } from "../prompts/prompt.js";
|
|
11
|
+
export const TELEGRAM_PROXY_URL_PROMPT = "Proxy URL (include protocol, e.g. http://127.0.0.1:7890)";
|
|
11
12
|
export async function runTelegramCommand(args) {
|
|
12
13
|
const target = parseTelegramTargetArgs(args);
|
|
13
14
|
const [subcommand, ...rest] = target.args;
|
|
@@ -202,7 +203,7 @@ async function addBot(nameArg) {
|
|
|
202
203
|
if (!token.trim())
|
|
203
204
|
throw new Error("Token is required.");
|
|
204
205
|
const allowedUserIds = await promptText("Allowed user IDs (comma-separated, empty for allowAll)");
|
|
205
|
-
const proxyUrl = await promptText(
|
|
206
|
+
const proxyUrl = await promptText(`${TELEGRAM_PROXY_URL_PROMPT} (empty to skip)`, { defaultValue: "" });
|
|
206
207
|
const config = {
|
|
207
208
|
token: token.trim(),
|
|
208
209
|
...(proxyUrl.trim() ? { proxyUrl: proxyUrl.trim() } : {})
|
|
@@ -494,7 +495,7 @@ async function editBotConfigInteractive(name) {
|
|
|
494
495
|
const current = String(config[field] ?? "");
|
|
495
496
|
const value = field === "token"
|
|
496
497
|
? await promptText("token (leave empty to keep current)") || current
|
|
497
|
-
: await promptText(field, { defaultValue: current });
|
|
498
|
+
: await promptText(field === "proxyUrl" ? TELEGRAM_PROXY_URL_PROMPT : field, { defaultValue: current });
|
|
498
499
|
config = await setBotConfigValue(name, field, value);
|
|
499
500
|
}
|
|
500
501
|
}
|
|
@@ -539,7 +540,7 @@ async function editTelegramConfigInteractive() {
|
|
|
539
540
|
const current = String(config[field] ?? "");
|
|
540
541
|
const value = field === "token"
|
|
541
542
|
? await promptText("token (leave empty to keep current)") || current
|
|
542
|
-
: await promptText(field, { defaultValue: current });
|
|
543
|
+
: await promptText(field === "proxyUrl" ? TELEGRAM_PROXY_URL_PROMPT : field, { defaultValue: current });
|
|
543
544
|
config = await setTelegramConfigValue(field, value);
|
|
544
545
|
}
|
|
545
546
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { runMemoryCommand } from "./commands/memory.js";
|
|
|
10
10
|
import { runReportCommand } from "./commands/report.js";
|
|
11
11
|
import { runLastCommand, runUseCommand } from "./commands/use.js";
|
|
12
12
|
import { runSessionCommand } from "./commands/session.js";
|
|
13
|
+
import { runSetCommand } from "./commands/set.js";
|
|
13
14
|
import { runSetupCommand } from "./commands/setup.js";
|
|
14
15
|
import { runStatusCommand } from "./commands/status.js";
|
|
15
16
|
import { runTelegramCommand } from "./commands/telegram.js";
|
|
@@ -17,6 +18,7 @@ import { runVersionCommand } from "./commands/version.js";
|
|
|
17
18
|
import { writeLog } from "../core/logs/log-service.js";
|
|
18
19
|
import { promptSelect } from "./prompts/prompt.js";
|
|
19
20
|
import { getCliCommand, registerCliCommand } from "./registry.js";
|
|
21
|
+
import { redactCodexProxyCommandArgs } from "./sanitize-args.js";
|
|
20
22
|
registerBuiltInCommands();
|
|
21
23
|
async function main() {
|
|
22
24
|
const [, , command, ...args] = process.argv;
|
|
@@ -52,6 +54,9 @@ async function main() {
|
|
|
52
54
|
process.exitCode = 1;
|
|
53
55
|
}
|
|
54
56
|
function sanitizeArgs(command, args) {
|
|
57
|
+
const redactedProxyArgs = redactCodexProxyCommandArgs(command, args);
|
|
58
|
+
if (redactedProxyArgs)
|
|
59
|
+
return redactedProxyArgs;
|
|
55
60
|
if (command === "add" && args.length >= 3) {
|
|
56
61
|
return [args[0], args[1], maskSecretArg(args[2])];
|
|
57
62
|
}
|
|
@@ -131,7 +136,7 @@ function maskSecretArg(value) {
|
|
|
131
136
|
return `${value.slice(0, 4)}****${value.slice(-4)}`;
|
|
132
137
|
}
|
|
133
138
|
async function runInteractive() {
|
|
134
|
-
const interactiveCommandNames = ["setup", "use", "add", "memory", "telegram", "status", "session", "report", "doctor", "log", "context", "help", "last"];
|
|
139
|
+
const interactiveCommandNames = ["setup", "use", "set", "add", "memory", "telegram", "status", "session", "report", "doctor", "log", "context", "help", "last"];
|
|
135
140
|
const command = await promptSelect("AI Gateway", [
|
|
136
141
|
...interactiveCommandNames
|
|
137
142
|
.flatMap((name) => {
|
|
@@ -250,6 +255,11 @@ function registerBuiltInCommands() {
|
|
|
250
255
|
description: "use",
|
|
251
256
|
run: (args) => runUseCommand(args)
|
|
252
257
|
});
|
|
258
|
+
registerCliCommand({
|
|
259
|
+
name: "set",
|
|
260
|
+
description: "set Codex provider or proxy",
|
|
261
|
+
run: (args) => runSetCommand(args)
|
|
262
|
+
});
|
|
253
263
|
registerCliCommand({
|
|
254
264
|
name: "last",
|
|
255
265
|
description: "last",
|