@suzuke/agend 0.0.1 → 1.0.2
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 +78 -0
- package/README.zh-TW.md +79 -0
- package/dist/access-path.d.ts +7 -0
- package/dist/access-path.js +12 -0
- package/dist/access-path.js.map +1 -0
- package/dist/backend/claude-code.d.ts +13 -0
- package/dist/backend/claude-code.js +114 -0
- package/dist/backend/claude-code.js.map +1 -0
- package/dist/backend/codex.d.ts +10 -0
- package/dist/backend/codex.js +58 -0
- package/dist/backend/codex.js.map +1 -0
- package/dist/backend/factory.d.ts +2 -0
- package/dist/backend/factory.js +19 -0
- package/dist/backend/factory.js.map +1 -0
- package/dist/backend/gemini-cli.d.ts +10 -0
- package/dist/backend/gemini-cli.js +68 -0
- package/dist/backend/gemini-cli.js.map +1 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/opencode.d.ts +10 -0
- package/dist/backend/opencode.js +63 -0
- package/dist/backend/opencode.js.map +1 -0
- package/dist/backend/types.d.ts +26 -0
- package/dist/backend/types.js +2 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/channel/access-manager.d.ts +18 -0
- package/dist/channel/access-manager.js +149 -0
- package/dist/channel/access-manager.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +45 -0
- package/dist/channel/adapters/discord.js +366 -0
- package/dist/channel/adapters/discord.js.map +1 -0
- package/dist/channel/adapters/telegram.d.ts +58 -0
- package/dist/channel/adapters/telegram.js +569 -0
- package/dist/channel/adapters/telegram.js.map +1 -0
- package/dist/channel/attachment-handler.d.ts +15 -0
- package/dist/channel/attachment-handler.js +55 -0
- package/dist/channel/attachment-handler.js.map +1 -0
- package/dist/channel/factory.d.ts +12 -0
- package/dist/channel/factory.js +38 -0
- package/dist/channel/factory.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +26 -0
- package/dist/channel/ipc-bridge.js +170 -0
- package/dist/channel/ipc-bridge.js.map +1 -0
- package/dist/channel/mcp-server.d.ts +10 -0
- package/dist/channel/mcp-server.js +196 -0
- package/dist/channel/mcp-server.js.map +1 -0
- package/dist/channel/mcp-tools.d.ts +909 -0
- package/dist/channel/mcp-tools.js +346 -0
- package/dist/channel/mcp-tools.js.map +1 -0
- package/dist/channel/message-bus.d.ts +17 -0
- package/dist/channel/message-bus.js +86 -0
- package/dist/channel/message-bus.js.map +1 -0
- package/dist/channel/message-queue.d.ts +39 -0
- package/dist/channel/message-queue.js +248 -0
- package/dist/channel/message-queue.js.map +1 -0
- package/dist/channel/tool-router.d.ts +6 -0
- package/dist/channel/tool-router.js +69 -0
- package/dist/channel/tool-router.js.map +1 -0
- package/dist/channel/tool-tracker.d.ts +13 -0
- package/dist/channel/tool-tracker.js +58 -0
- package/dist/channel/tool-tracker.js.map +1 -0
- package/dist/channel/types.d.ts +116 -0
- package/dist/channel/types.js +2 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +782 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/context-guardian.d.ts +29 -0
- package/dist/context-guardian.js +123 -0
- package/dist/context-guardian.js.map +1 -0
- package/dist/cost-guard.d.ts +21 -0
- package/dist/cost-guard.js +113 -0
- package/dist/cost-guard.js.map +1 -0
- package/dist/daemon-entry.d.ts +1 -0
- package/dist/daemon-entry.js +29 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/daemon.d.ts +88 -0
- package/dist/daemon.js +821 -0
- package/dist/daemon.js.map +1 -0
- package/dist/daily-summary.d.ts +13 -0
- package/dist/daily-summary.js +55 -0
- package/dist/daily-summary.js.map +1 -0
- package/dist/event-log.d.ts +22 -0
- package/dist/event-log.js +66 -0
- package/dist/event-log.js.map +1 -0
- package/dist/export-import.d.ts +2 -0
- package/dist/export-import.js +110 -0
- package/dist/export-import.js.map +1 -0
- package/dist/fleet-context.d.ts +36 -0
- package/dist/fleet-context.js +4 -0
- package/dist/fleet-context.js.map +1 -0
- package/dist/fleet-manager.d.ts +115 -0
- package/dist/fleet-manager.js +1739 -0
- package/dist/fleet-manager.js.map +1 -0
- package/dist/fleet-system-prompt.d.ts +11 -0
- package/dist/fleet-system-prompt.js +60 -0
- package/dist/fleet-system-prompt.js.map +1 -0
- package/dist/hang-detector.d.ts +16 -0
- package/dist/hang-detector.js +53 -0
- package/dist/hang-detector.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
- package/dist/scheduler/db.d.ts +16 -0
- package/dist/scheduler/db.js +132 -0
- package/dist/scheduler/db.js.map +1 -0
- package/dist/scheduler/db.test.d.ts +1 -0
- package/dist/scheduler/db.test.js +92 -0
- package/dist/scheduler/db.test.js.map +1 -0
- package/dist/scheduler/index.d.ts +4 -0
- package/dist/scheduler/index.js +4 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +25 -0
- package/dist/scheduler/scheduler.js +119 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/scheduler.test.d.ts +1 -0
- package/dist/scheduler/scheduler.test.js +119 -0
- package/dist/scheduler/scheduler.test.js.map +1 -0
- package/dist/scheduler/types.d.ts +47 -0
- package/dist/scheduler/types.js +7 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service-installer.d.ts +14 -0
- package/dist/service-installer.js +91 -0
- package/dist/service-installer.js.map +1 -0
- package/dist/setup-wizard.d.ts +14 -0
- package/dist/setup-wizard.js +517 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/stt.d.ts +10 -0
- package/dist/stt.js +33 -0
- package/dist/stt.js.map +1 -0
- package/dist/tmux-manager.d.ts +22 -0
- package/dist/tmux-manager.js +132 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/topic-commands.d.ts +22 -0
- package/dist/topic-commands.js +176 -0
- package/dist/topic-commands.js.map +1 -0
- package/dist/transcript-monitor.d.ts +21 -0
- package/dist/transcript-monitor.js +149 -0
- package/dist/transcript-monitor.js.map +1 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-emitter.d.ts +15 -0
- package/dist/webhook-emitter.js +41 -0
- package/dist/webhook-emitter.js.map +1 -0
- package/package.json +58 -4
- package/templates/launchd.plist.ejs +29 -0
- package/templates/systemd.service.ejs +15 -0
- package/index.js +0 -1
package/README.md
CHANGED
|
@@ -1 +1,79 @@
|
|
|
1
1
|
# AgEnD
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@suzuke/agend)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**Agent Engineering Daemon** — run a fleet of AI coding agents from your phone.
|
|
8
|
+
|
|
9
|
+
One Telegram bot, multiple CLI backends (Claude Code, Gemini CLI, Codex, OpenCode), unlimited projects. Each Forum Topic is an independent agent session with crash recovery and zero babysitting.
|
|
10
|
+
|
|
11
|
+
[繁體中文](README.zh-TW.md)
|
|
12
|
+
|
|
13
|
+
> **⚠️** All CLI backends run with `--dangerously-skip-permissions` (or equivalent). See [Security](SECURITY.md).
|
|
14
|
+
|
|
15
|
+
## Problems agend solves
|
|
16
|
+
|
|
17
|
+
| Without agend | With agend |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Close the terminal, agent goes offline | Runs as a system service — survives reboots |
|
|
20
|
+
| One terminal = one project | One bot, unlimited projects running in parallel |
|
|
21
|
+
| Long-running sessions accumulate stale context | Auto-rotates sessions by max age to stay fresh |
|
|
22
|
+
| No idea what your agents are doing overnight | Daily cost reports + hang detection alerts |
|
|
23
|
+
| Cron tasks disappear when the session ends | Persistent schedules backed by SQLite |
|
|
24
|
+
| Rate limited on one model, everything stops | Auto-failover to backup models |
|
|
25
|
+
| Can't approve tool use from your phone | Inline Telegram buttons with countdown + Always Allow |
|
|
26
|
+
| Agents work in silos, can't coordinate | Peer-to-peer collaboration via MCP tools |
|
|
27
|
+
| Runaway costs from unattended sessions | Per-instance daily spending limits with auto-pause |
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
brew install tmux # macOS (prerequisite)
|
|
33
|
+
npm install -g @suzuke/agend # install
|
|
34
|
+
agend init # interactive setup
|
|
35
|
+
agend fleet start # launch the fleet
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Fleet mode** — one bot, N projects, each in its own Telegram Forum Topic
|
|
41
|
+
- **Persistent schedules** — cron-based tasks that survive restarts (SQLite-backed)
|
|
42
|
+
- **Context rotation** — auto-restart long-running sessions to keep context fresh (max-age based)
|
|
43
|
+
- **Peer-to-peer collaboration** — agents discover, wake, and message each other via MCP tools
|
|
44
|
+
- **General Topic** — natural language dispatcher that routes tasks to the right agent
|
|
45
|
+
- **Permission relay** — inline Telegram buttons for Allow/Deny with countdown + Always Allow
|
|
46
|
+
- **Voice messages** — Groq Whisper transcription, talk to your agents
|
|
47
|
+
- **Cost guard** — per-instance daily spending limits with auto-pause
|
|
48
|
+
- **Hang detection** — auto-detect stuck sessions, notify with restart buttons
|
|
49
|
+
- **Model failover** — auto-switch to backup model on rate limits
|
|
50
|
+
- **Daily summary** — fleet cost report posted to Telegram
|
|
51
|
+
- **External sessions** — connect local Claude Code to the fleet via IPC
|
|
52
|
+
- **Discord adapter** — use Discord instead of (or alongside) Telegram
|
|
53
|
+
- **Health endpoint** — HTTP API for external monitoring
|
|
54
|
+
- **Webhook notifications** — push events to Slack or custom endpoints
|
|
55
|
+
- **System service** — one command to install as launchd/systemd service
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- Node.js >= 20
|
|
60
|
+
- tmux
|
|
61
|
+
- Claude Code CLI (`claude`)
|
|
62
|
+
- Telegram bot token ([@BotFather](https://t.me/BotFather))
|
|
63
|
+
- Groq API key (optional, for voice)
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
- [Features](docs/features.md) — detailed feature documentation
|
|
68
|
+
- [CLI Reference](docs/cli.md) — all commands and options
|
|
69
|
+
- [Configuration](docs/configuration.md) — fleet.yaml, .env, data directory
|
|
70
|
+
- [Security](SECURITY.md) — trust model and hardening
|
|
71
|
+
|
|
72
|
+
## Known limitations
|
|
73
|
+
|
|
74
|
+
- macOS (launchd) and Linux (systemd) supported; Windows is not
|
|
75
|
+
- Official Telegram plugin in global `enabledPlugins` causes 409 polling conflicts
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/README.zh-TW.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# AgEnD
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@suzuke/agend)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**Agent Engineering Daemon** — 用手機管理一整個 AI coding agent 團隊。
|
|
8
|
+
|
|
9
|
+
一個 Telegram bot,多種 CLI 後端(Claude Code、Gemini CLI、Codex、OpenCode),無限專案。每個 Forum Topic 就是一個獨立的 agent session,crash 自動恢復,不用顧。
|
|
10
|
+
|
|
11
|
+
[English](README.md)
|
|
12
|
+
|
|
13
|
+
> **⚠️** 所有 CLI 後端都以 `--dangerously-skip-permissions`(或等效參數)執行。詳見 [Security](SECURITY.md)。
|
|
14
|
+
|
|
15
|
+
## agend 解決什麼問題
|
|
16
|
+
|
|
17
|
+
| 沒有 agend | 有 agend |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 關掉終端機,agent 就斷線 | 系統服務常駐,重開機也不怕 |
|
|
20
|
+
| 一個終端機 = 一個專案 | 一個 bot,無限專案同時跑 |
|
|
21
|
+
| 長時間 session 累積過時 context | 依 max age 自動輪替 session,保持新鮮 |
|
|
22
|
+
| 不知道 agent 半夜在幹嘛 | 每日花費報告 + 卡住偵測通知 |
|
|
23
|
+
| 排程任務隨 session 結束消失 | 持久化排程,SQLite 儲存 |
|
|
24
|
+
| 某個 model 被限速,全部停擺 | 自動切換備用 model |
|
|
25
|
+
| 沒辦法從手機核准工具使用 | Telegram inline 按鈕,倒數計時 + Always Allow |
|
|
26
|
+
| Agent 各做各的,無法協作 | 點對點協作,透過 MCP tools |
|
|
27
|
+
| 無人看管時帳單暴增 | 每個 instance 每日花費上限,自動暫停 |
|
|
28
|
+
|
|
29
|
+
## 開始用
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
brew install tmux # macOS(前置需求)
|
|
33
|
+
npm install -g @suzuke/agend # 安裝
|
|
34
|
+
agend init # 互動式設定
|
|
35
|
+
agend fleet start # 啟動 fleet
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 功能
|
|
39
|
+
|
|
40
|
+
- **Fleet 模式** — 一個 bot、N 個專案,各自獨立的 Telegram Forum Topic
|
|
41
|
+
- **持久化排程** — cron 排程任務,重啟不遺失(SQLite 儲存)
|
|
42
|
+
- **Context 輪替** — 長時間 session 依 max age 自動重啟,保持 context 新鮮
|
|
43
|
+
- **點對點協作** — agent 之間透過 MCP tools 互相發現、喚醒、傳訊
|
|
44
|
+
- **General Topic** — 自然語言調度器,把任務路由到對的 agent
|
|
45
|
+
- **權限轉發** — Telegram inline 按鈕 Allow/Deny,倒數計時 + Always Allow
|
|
46
|
+
- **語音訊息** — Groq Whisper 轉文字,用說的跟 agent 溝通
|
|
47
|
+
- **花費上限** — 每個 instance 每日限額,超過自動暫停
|
|
48
|
+
- **卡住偵測** — 自動偵測無回應的 session,通知並提供重啟按鈕
|
|
49
|
+
- **Model Failover** — 被限速時自動切換備用 model
|
|
50
|
+
- **每日摘要** — Fleet 花費報告,發到 Telegram
|
|
51
|
+
- **外部 Session** — 本地 Claude Code 透過 IPC 連入 fleet
|
|
52
|
+
- **Discord Adapter** — 用 Discord 取代(或同時使用)Telegram
|
|
53
|
+
- **Health Endpoint** — HTTP API 供外部監控
|
|
54
|
+
- **Webhook 通知** — 推送事件到 Slack 或自訂 endpoint
|
|
55
|
+
- **系統服務** — 一行指令裝成 launchd/systemd 服務
|
|
56
|
+
|
|
57
|
+
## 系統需求
|
|
58
|
+
|
|
59
|
+
- Node.js >= 20
|
|
60
|
+
- tmux
|
|
61
|
+
- Claude Code CLI(`claude`)
|
|
62
|
+
- Telegram bot token([@BotFather](https://t.me/BotFather))
|
|
63
|
+
- Groq API key(選用,語音轉文字用)
|
|
64
|
+
|
|
65
|
+
## 文件
|
|
66
|
+
|
|
67
|
+
- [Features](docs/features.md) — 功能詳細說明
|
|
68
|
+
- [CLI Reference](docs/cli.md) — 所有指令與選項
|
|
69
|
+
- [Configuration](docs/configuration.md) — fleet.yaml、.env、資料目錄
|
|
70
|
+
- [Security](SECURITY.md) — 信任模型與安全強化
|
|
71
|
+
|
|
72
|
+
## 已知限制
|
|
73
|
+
|
|
74
|
+
- 支援 macOS(launchd)和 Linux(systemd),不支援 Windows
|
|
75
|
+
- 全域 `enabledPlugins` 裡有官方 Telegram plugin 會造成 409 polling 衝突
|
|
76
|
+
|
|
77
|
+
## 授權
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the access.json path for an instance.
|
|
3
|
+
* Topic mode uses fleet-level access; otherwise per-instance.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveAccessPathFromConfig(dataDir: string, _instance: string, fleetChannel: {
|
|
6
|
+
mode?: string;
|
|
7
|
+
} | undefined): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the access.json path for an instance.
|
|
4
|
+
* Topic mode uses fleet-level access; otherwise per-instance.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAccessPathFromConfig(dataDir, _instance, fleetChannel) {
|
|
7
|
+
if (fleetChannel?.mode === "topic") {
|
|
8
|
+
return join(dataDir, "access", "access.json");
|
|
9
|
+
}
|
|
10
|
+
return join(dataDir, "access", "access.json");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=access-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-path.js","sourceRoot":"","sources":["../src/access-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAAe,EACf,SAAiB,EACjB,YAA2C;IAE3C,IAAI,YAAY,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class ClaudeCodeBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(_config: CliBackendConfig): void;
|
|
10
|
+
/** Pre-approve ANTHROPIC_API_KEY in ~/.claude.json to skip the interactive prompt */
|
|
11
|
+
private preApproveApiKey;
|
|
12
|
+
private writeStatusLineScript;
|
|
13
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
export class ClaudeCodeBackend {
|
|
5
|
+
instanceDir;
|
|
6
|
+
constructor(instanceDir) {
|
|
7
|
+
this.instanceDir = instanceDir;
|
|
8
|
+
}
|
|
9
|
+
buildCommand(config) {
|
|
10
|
+
const settingsPath = join(this.instanceDir, "claude-settings.json");
|
|
11
|
+
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
12
|
+
// Forward Anthropic env vars to the CLI process (tmux shell doesn't inherit daemon's env)
|
|
13
|
+
const envPrefix = ["CMUX_CLAUDE_HOOKS_DISABLED=1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"];
|
|
14
|
+
for (const key of ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY"]) {
|
|
15
|
+
if (process.env[key])
|
|
16
|
+
envPrefix.push(`${key}=${process.env[key]}`);
|
|
17
|
+
}
|
|
18
|
+
let cmd = `${envPrefix.join(" ")} claude --settings ${settingsPath} --mcp-config ${mcpConfigPath} --dangerously-skip-permissions`;
|
|
19
|
+
const sessionIdFile = join(this.instanceDir, "session-id");
|
|
20
|
+
if (existsSync(sessionIdFile)) {
|
|
21
|
+
const sid = readFileSync(sessionIdFile, "utf-8").trim();
|
|
22
|
+
if (sid && /^[a-zA-Z0-9_-]+$/.test(sid))
|
|
23
|
+
cmd += ` --resume ${sid}`;
|
|
24
|
+
}
|
|
25
|
+
if (config.model) {
|
|
26
|
+
cmd += ` --model ${config.model}`;
|
|
27
|
+
}
|
|
28
|
+
if (config.systemPrompt) {
|
|
29
|
+
const promptPath = join(this.instanceDir, "system-prompt.md");
|
|
30
|
+
writeFileSync(promptPath, config.systemPrompt);
|
|
31
|
+
cmd += ` --system-prompt "${promptPath}"`;
|
|
32
|
+
}
|
|
33
|
+
return cmd;
|
|
34
|
+
}
|
|
35
|
+
writeConfig(config) {
|
|
36
|
+
// 1. Write mcp-config.json to instance dir (loaded via --mcp-config)
|
|
37
|
+
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
38
|
+
const mcpConfig = { mcpServers: config.mcpServers };
|
|
39
|
+
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
40
|
+
// 2. Write statusline script
|
|
41
|
+
const statusLineCommand = this.writeStatusLineScript();
|
|
42
|
+
// 3. Write claude-settings.json (permissions handled by --dangerously-skip-permissions)
|
|
43
|
+
const settings = {
|
|
44
|
+
statusLine: {
|
|
45
|
+
type: "command",
|
|
46
|
+
command: statusLineCommand,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
writeFileSync(join(this.instanceDir, "claude-settings.json"), JSON.stringify(settings));
|
|
50
|
+
// 4. Pre-approve API key to skip interactive prompt on startup
|
|
51
|
+
this.preApproveApiKey(config);
|
|
52
|
+
}
|
|
53
|
+
getContextUsage() {
|
|
54
|
+
try {
|
|
55
|
+
const sf = join(this.instanceDir, "statusline.json");
|
|
56
|
+
const data = JSON.parse(readFileSync(sf, "utf-8"));
|
|
57
|
+
return data.context_window?.used_percentage ?? null;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// File may not exist yet during startup — return null to signal unavailable
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
getSessionId() {
|
|
65
|
+
try {
|
|
66
|
+
const sf = join(this.instanceDir, "statusline.json");
|
|
67
|
+
const data = JSON.parse(readFileSync(sf, "utf-8"));
|
|
68
|
+
return data.session_id ?? null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
cleanup(_config) {
|
|
75
|
+
// mcp-config.json is in instance dir, cleaned up when instance is deleted
|
|
76
|
+
}
|
|
77
|
+
/** Pre-approve ANTHROPIC_API_KEY in ~/.claude.json to skip the interactive prompt */
|
|
78
|
+
preApproveApiKey(_config) {
|
|
79
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
80
|
+
if (!apiKey)
|
|
81
|
+
return;
|
|
82
|
+
const fingerprint = apiKey.length > 20 ? apiKey.slice(-20) : apiKey;
|
|
83
|
+
const claudeJsonPath = join(homedir(), ".claude.json");
|
|
84
|
+
let claudeCfg = {};
|
|
85
|
+
try {
|
|
86
|
+
claudeCfg = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
|
|
87
|
+
}
|
|
88
|
+
catch { /* new file or parse error */ }
|
|
89
|
+
const existing = claudeCfg.customApiKeyResponses;
|
|
90
|
+
const approved = existing?.approved ?? [];
|
|
91
|
+
if (!approved.includes(fingerprint)) {
|
|
92
|
+
claudeCfg.customApiKeyResponses = {
|
|
93
|
+
approved: [...approved, fingerprint],
|
|
94
|
+
rejected: existing?.rejected ?? [],
|
|
95
|
+
};
|
|
96
|
+
writeFileSync(claudeJsonPath, JSON.stringify(claudeCfg, null, 2));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
writeStatusLineScript() {
|
|
100
|
+
const statusFile = join(this.instanceDir, "statusline.json");
|
|
101
|
+
// Use a Node.js script instead of bash to avoid shell injection via statusFile path
|
|
102
|
+
const script = [
|
|
103
|
+
"#!/usr/bin/env node",
|
|
104
|
+
"const fs = require('fs');",
|
|
105
|
+
"let input = '';",
|
|
106
|
+
"process.stdin.on('data', d => input += d);",
|
|
107
|
+
`process.stdin.on('end', () => { fs.writeFileSync(${JSON.stringify(statusFile)}, input); console.log('ok'); });`,
|
|
108
|
+
].join("\n");
|
|
109
|
+
const scriptPath = join(this.instanceDir, "statusline.js");
|
|
110
|
+
writeFileSync(scriptPath, script, { mode: 0o755 });
|
|
111
|
+
return scriptPath;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/backend/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class CodexBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(config: CliBackendConfig): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
export class CodexBackend {
|
|
4
|
+
instanceDir;
|
|
5
|
+
constructor(instanceDir) {
|
|
6
|
+
this.instanceDir = instanceDir;
|
|
7
|
+
}
|
|
8
|
+
buildCommand(config) {
|
|
9
|
+
let cmd = "codex --full-auto";
|
|
10
|
+
if (config.model) {
|
|
11
|
+
cmd += ` -c model="${config.model}"`;
|
|
12
|
+
}
|
|
13
|
+
if (config.systemPrompt) {
|
|
14
|
+
const promptPath = join(this.instanceDir, "system-prompt.md");
|
|
15
|
+
writeFileSync(promptPath, config.systemPrompt);
|
|
16
|
+
cmd += ` --system-prompt-file "${promptPath}"`;
|
|
17
|
+
}
|
|
18
|
+
return cmd;
|
|
19
|
+
}
|
|
20
|
+
writeConfig(config) {
|
|
21
|
+
// Codex uses codex mcp add (global config), write a setup script
|
|
22
|
+
// that registers MCP servers on first launch
|
|
23
|
+
const setupScript = Object.entries(config.mcpServers).map(([name, entry]) => {
|
|
24
|
+
const args = entry.args.map(a => `"${a}"`).join(" ");
|
|
25
|
+
const envFlags = Object.entries(entry.env || {}).map(([k, v]) => `-e ${k}="${v}"`).join(" ");
|
|
26
|
+
return `codex mcp add ${name} ${entry.command} ${args} ${envFlags} 2>/dev/null || true`;
|
|
27
|
+
}).join("\n");
|
|
28
|
+
writeFileSync(join(this.instanceDir, "setup-mcp.sh"), setupScript, { mode: 0o755 });
|
|
29
|
+
// Run setup immediately
|
|
30
|
+
const { execSync } = require("node:child_process");
|
|
31
|
+
try {
|
|
32
|
+
execSync(`bash ${join(this.instanceDir, "setup-mcp.sh")}`, { stdio: "ignore" });
|
|
33
|
+
}
|
|
34
|
+
catch { /* best effort */ }
|
|
35
|
+
}
|
|
36
|
+
getContextUsage() {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
getSessionId() {
|
|
40
|
+
try {
|
|
41
|
+
const f = join(this.instanceDir, "session-id");
|
|
42
|
+
return readFileSync(f, "utf-8").trim() || null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
cleanup(config) {
|
|
49
|
+
const { execSync } = require("node:child_process");
|
|
50
|
+
for (const name of Object.keys(config.mcpServers)) {
|
|
51
|
+
try {
|
|
52
|
+
execSync(`codex mcp remove ${name}`, { stdio: "ignore" });
|
|
53
|
+
}
|
|
54
|
+
catch { /* best effort */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/backend/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ClaudeCodeBackend } from "./claude-code.js";
|
|
2
|
+
import { GeminiCliBackend } from "./gemini-cli.js";
|
|
3
|
+
import { CodexBackend } from "./codex.js";
|
|
4
|
+
import { OpenCodeBackend } from "./opencode.js";
|
|
5
|
+
export function createBackend(name, instanceDir) {
|
|
6
|
+
switch (name) {
|
|
7
|
+
case "claude-code":
|
|
8
|
+
return new ClaudeCodeBackend(instanceDir);
|
|
9
|
+
case "gemini-cli":
|
|
10
|
+
return new GeminiCliBackend(instanceDir);
|
|
11
|
+
case "codex":
|
|
12
|
+
return new CodexBackend(instanceDir);
|
|
13
|
+
case "opencode":
|
|
14
|
+
return new OpenCodeBackend(instanceDir);
|
|
15
|
+
default:
|
|
16
|
+
throw new Error(`Unknown backend: ${name}. Available: claude-code, gemini-cli, codex, opencode`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backend/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,WAAmB;IAC7D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,OAAO,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC5C,KAAK,YAAY;YACf,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3C,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC;QACvC,KAAK,UAAU;YACb,OAAO,IAAI,eAAe,CAAC,WAAW,CAAC,CAAC;QAC1C;YACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,uDAAuD,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class GeminiCliBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(config: CliBackendConfig): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
export class GeminiCliBackend {
|
|
4
|
+
instanceDir;
|
|
5
|
+
constructor(instanceDir) {
|
|
6
|
+
this.instanceDir = instanceDir;
|
|
7
|
+
}
|
|
8
|
+
buildCommand(config) {
|
|
9
|
+
let cmd = "gemini --yolo";
|
|
10
|
+
const sessionIdFile = join(this.instanceDir, "session-id");
|
|
11
|
+
if (existsSync(sessionIdFile)) {
|
|
12
|
+
const sid = readFileSync(sessionIdFile, "utf-8").trim();
|
|
13
|
+
if (sid)
|
|
14
|
+
cmd += ` --resume ${sid}`;
|
|
15
|
+
}
|
|
16
|
+
if (config.model) {
|
|
17
|
+
cmd += ` --model ${config.model}`;
|
|
18
|
+
}
|
|
19
|
+
return cmd;
|
|
20
|
+
}
|
|
21
|
+
writeConfig(config) {
|
|
22
|
+
// Gemini uses .gemini/settings.json for MCP servers
|
|
23
|
+
const geminiDir = join(config.workingDirectory, ".gemini");
|
|
24
|
+
mkdirSync(geminiDir, { recursive: true });
|
|
25
|
+
const settingsPath = join(geminiDir, "settings.json");
|
|
26
|
+
let settings = {};
|
|
27
|
+
try {
|
|
28
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
catch { /* new file */ }
|
|
31
|
+
settings.mcpServers = config.mcpServers;
|
|
32
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
33
|
+
// System prompt via GEMINI.md
|
|
34
|
+
if (config.systemPrompt) {
|
|
35
|
+
writeFileSync(join(geminiDir, "GEMINI.md"), config.systemPrompt);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getContextUsage() {
|
|
39
|
+
// Gemini CLI doesn't expose context usage via a file
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
getSessionId() {
|
|
43
|
+
try {
|
|
44
|
+
const f = join(this.instanceDir, "session-id");
|
|
45
|
+
return readFileSync(f, "utf-8").trim() || null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
cleanup(config) {
|
|
52
|
+
// Clean up .gemini/settings.json MCP entries
|
|
53
|
+
try {
|
|
54
|
+
const settingsPath = join(config.workingDirectory, ".gemini", "settings.json");
|
|
55
|
+
if (existsSync(settingsPath)) {
|
|
56
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
57
|
+
if (settings.mcpServers) {
|
|
58
|
+
for (const name of Object.keys(config.mcpServers)) {
|
|
59
|
+
delete settings.mcpServers[name];
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch { /* best effort */ }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=gemini-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-cli.js","sourceRoot":"","sources":["../../src/backend/gemini-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { CliBackend, CliBackendConfig, McpServerEntry } from "./types.js";
|
|
2
|
+
export { ClaudeCodeBackend } from "./claude-code.js";
|
|
3
|
+
export { GeminiCliBackend } from "./gemini-cli.js";
|
|
4
|
+
export { CodexBackend } from "./codex.js";
|
|
5
|
+
export { OpenCodeBackend } from "./opencode.js";
|
|
6
|
+
export { createBackend } from "./factory.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ClaudeCodeBackend } from "./claude-code.js";
|
|
2
|
+
export { GeminiCliBackend } from "./gemini-cli.js";
|
|
3
|
+
export { CodexBackend } from "./codex.js";
|
|
4
|
+
export { OpenCodeBackend } from "./opencode.js";
|
|
5
|
+
export { createBackend } from "./factory.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CliBackend, CliBackendConfig } from "./types.js";
|
|
2
|
+
export declare class OpenCodeBackend implements CliBackend {
|
|
3
|
+
private instanceDir;
|
|
4
|
+
constructor(instanceDir: string);
|
|
5
|
+
buildCommand(_config: CliBackendConfig): string;
|
|
6
|
+
writeConfig(config: CliBackendConfig): void;
|
|
7
|
+
getContextUsage(): number | null;
|
|
8
|
+
getSessionId(): string | null;
|
|
9
|
+
cleanup(config: CliBackendConfig): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
export class OpenCodeBackend {
|
|
4
|
+
instanceDir;
|
|
5
|
+
constructor(instanceDir) {
|
|
6
|
+
this.instanceDir = instanceDir;
|
|
7
|
+
}
|
|
8
|
+
buildCommand(_config) {
|
|
9
|
+
return "opencode";
|
|
10
|
+
}
|
|
11
|
+
writeConfig(config) {
|
|
12
|
+
// OpenCode uses opencode.json in the working directory
|
|
13
|
+
const configPath = join(config.workingDirectory, "opencode.json");
|
|
14
|
+
let oc = {};
|
|
15
|
+
try {
|
|
16
|
+
oc = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
17
|
+
}
|
|
18
|
+
catch { /* new file */ }
|
|
19
|
+
// MCP servers
|
|
20
|
+
oc.mcp = {};
|
|
21
|
+
for (const [name, entry] of Object.entries(config.mcpServers)) {
|
|
22
|
+
oc.mcp[name] = {
|
|
23
|
+
type: "local",
|
|
24
|
+
command: [entry.command, ...entry.args],
|
|
25
|
+
env: entry.env,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// System prompt via instructions
|
|
29
|
+
if (config.systemPrompt) {
|
|
30
|
+
oc.instructions = config.systemPrompt;
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(configPath, JSON.stringify(oc, null, 2));
|
|
33
|
+
}
|
|
34
|
+
getContextUsage() {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
getSessionId() {
|
|
38
|
+
try {
|
|
39
|
+
const f = join(this.instanceDir, "session-id");
|
|
40
|
+
return readFileSync(f, "utf-8").trim() || null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
cleanup(config) {
|
|
47
|
+
// Clean up opencode.json MCP entries
|
|
48
|
+
try {
|
|
49
|
+
const configPath = join(config.workingDirectory, "opencode.json");
|
|
50
|
+
if (existsSync(configPath)) {
|
|
51
|
+
const oc = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
52
|
+
if (oc.mcp) {
|
|
53
|
+
for (const name of Object.keys(config.mcpServers)) {
|
|
54
|
+
delete oc.mcp[name];
|
|
55
|
+
}
|
|
56
|
+
writeFileSync(configPath, JSON.stringify(oc, null, 2));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch { /* best effort */ }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=opencode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/backend/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGlE,MAAM,OAAO,eAAe;IACN;IAApB,YAAoB,WAAmB;QAAnB,gBAAW,GAAX,WAAW,CAAQ;IAAG,CAAC;IAE3C,YAAY,CAAC,OAAyB;QACpC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,WAAW,CAAC,MAAwB;QAClC,uDAAuD;QACvD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;QAClE,IAAI,EAAE,GAA4B,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;QAE1B,cAAc;QACd,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7D,EAAE,CAAC,GAA+B,CAAC,IAAI,CAAC,GAAG;gBAC1C,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC;gBACvC,GAAG,EAAE,KAAK,CAAC,GAAG;aACf,CAAC;QACJ,CAAC;QAED,iCAAiC;QACjC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,EAAE,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;QACxC,CAAC;QAED,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,YAAY;QACV,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;YAC/C,OAAO,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,MAAwB;QAC9B,qCAAqC;QACrC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;YAClE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBACzD,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;oBACX,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;wBAClD,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACtB,CAAC;oBACD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface McpServerEntry {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
env: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface CliBackendConfig {
|
|
7
|
+
workingDirectory: string;
|
|
8
|
+
instanceDir: string;
|
|
9
|
+
instanceName: string;
|
|
10
|
+
mcpServers: Record<string, McpServerEntry>;
|
|
11
|
+
systemPrompt?: string;
|
|
12
|
+
skipPermissions?: boolean;
|
|
13
|
+
model?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CliBackend {
|
|
16
|
+
/** Build the shell command string to launch the CLI in a tmux window. */
|
|
17
|
+
buildCommand(config: CliBackendConfig): string;
|
|
18
|
+
/** Write all config files the CLI needs before launch. */
|
|
19
|
+
writeConfig(config: CliBackendConfig): void;
|
|
20
|
+
/** Read context window usage percentage (0-100). Returns null if unavailable. */
|
|
21
|
+
getContextUsage(): number | null;
|
|
22
|
+
/** Read session ID for resume capability. Returns null if unavailable. */
|
|
23
|
+
getSessionId(): string | null;
|
|
24
|
+
/** Clean up config files on shutdown. */
|
|
25
|
+
cleanup?(config: CliBackendConfig): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":""}
|