@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 CHANGED
@@ -26,33 +26,34 @@ AI 与维护者使用的英文权威文档位于 `AGENTS.md`、`docs/AI_RUNTIME_
26
26
 
27
27
  ## 通过 npm 安装
28
28
 
29
- 运行环境需要 Node.js `22.19.0` 或更高版本。全局安装后,npm 会把本包注册为 `ai` 命令:
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
- 发布流程会在 npm 发布成功后把 `Formula/axiomruntime.rb` 同步到 `offerpilot/homebrew-tap`。Tap 建立后可使用:
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 offerpilot/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("Proxy URL (empty to skip)", { defaultValue: "" });
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",
@@ -0,0 +1,6 @@
1
+ export function redactCodexProxyCommandArgs(command, args) {
2
+ if (command !== "set" || args[0] !== "codex" || args[1] !== "proxy" || args.length < 3) {
3
+ return null;
4
+ }
5
+ return [args[0], args[1], "[proxy-redacted]", ...args.slice(3)];
6
+ }