@bd7pil/ocrc 0.12.2 → 0.12.4

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,338 +1,186 @@
1
1
  # ocrc — remote control for opencode
2
2
 
3
- > **ocrc is a fork of [agentjoey/opencode-remote-control](https://github.com/agentjoey/opencode-remote-control) (OCRC, MIT).**
4
- > Upstream's architecture — the in-process plugin, CardBus, transport
5
- > interface, Telegram + Web PWA — is kept intact. This fork exists for four
6
- > reasons, all additive on top of upstream's design:
7
- >
8
- > 1. **Dual-host** — one default export loads on opencode **V1 (server hook) and
9
- > V2 (setup hook)**; no plugin beta dependency, local structural V2 types.
10
- > 2. **More channels** — Feishu (official outbound WebSocket) and WeChat (official
11
- > iLink bot API) as new transports under the same Transport interface; the
12
- > relay core stays untouched.
13
- > 3. **Telegram UX at grinev depth** — the interaction model of
14
- > [@grinev/opencode-telegram-bot](https://github.com/grinev/opencode-telegram-bot)
15
- > (MIT): interaction mutex, grouped permission requests, background-session
16
- > notifications, Chinese i18n — transplanted onto ocrc's relay pipeline.
17
- > 4. **LAN-first web** — binds `0.0.0.0:4099` by default (token gate mandatory,
18
- > no cloud relay needed); dual light/dark "paper-ink" theme, auto-switched.
19
- >
20
- > Fork identity: npm `@bd7pil/ocrc`, config home `~/.ocrc/`, CLI `ocrc`.
21
- > Functional behaviour otherwise follows upstream's original design — this fork
22
- > changes identity, defaults and skins, not the interaction model.
23
-
24
- # OCRC — opencode-remote-control (upstream README follows)
25
-
26
- > **Drive your local opencode from your phone or browser.** An [opencode](https://opencode.ai)
27
- > **plugin** that runs a **Telegram bot + a Web PWA** in-process — fire off a prompt from
28
- > anywhere and watch the assistant code in real time, even when you're away from your desk.
29
- > Now also drives any [ACP](https://agentclientprotocol.com) agent (e.g. Kimi) in standalone host mode.
30
-
31
- [![Release](https://img.shields.io/github/v/release/agentjoey/opencode-remote-control?color=10b981)](https://github.com/BD7PIL/ocrc/releases)
32
- [![License: MIT](https://img.shields.io/github/license/agentjoey/opencode-remote-control?color=10b981)](LICENSE)
33
- [![CI](https://img.shields.io/github/actions/workflow/status/agentjoey/opencode-remote-control/ci.yml?branch=main&label=CI)](https://github.com/BD7PIL/ocrc/actions)
34
- [![opencode plugin](https://img.shields.io/badge/opencode-plugin-10b981)](https://opencode.ai)
35
- [![tests](https://img.shields.io/badge/tests-413%20backend%20%2B%2093%20web-10b981)](./CHANGELOG.md)
3
+ > **Drive your local opencode from your phone or browser** — a Telegram bot
4
+ > plus a scan-to-pair Web PWA, running as an **in-process opencode plugin**.
5
+ > One install; both surfaces stream the same live sessions.
6
+
7
+ [![Release](https://img.shields.io/github/v/release/BD7PIL/ocrc?color=10b981)](https://github.com/BD7PIL/ocrc/releases)
8
+ [![License: MIT](https://img.shields.io/github/license/BD7PIL/ocrc?color=10b981)](LICENSE)
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/BD7PIL/ocrc/ci.yml?branch=main&label=CI)](https://github.com/BD7PIL/ocrc/actions)
10
+ [![tests](https://img.shields.io/badge/tests-479%20backend%20%2B%20120%20web-10b981)](CHANGELOG.md)
11
+
12
+ A fork of [agentjoey/opencode-remote-control](https://github.com/agentjoey/opencode-remote-control)
13
+ (MIT); the Telegram interaction model is inspired by
14
+ [@grinev/opencode-telegram-bot](https://github.com/grinev/opencode-telegram-bot).
15
+ Full attribution in [NOTICE](NOTICE). npm `@bd7pil/ocrc` · config `~/.ocrc/` · CLI `ocrc`.
36
16
 
37
17
  <p align="center">
38
- <img src="docs/assets/ocrc-web.png" width="840" alt="OCRC — the Web PWA driving a live opencode session (sessions, live chat, task & cost inspector)">
18
+ <img src="docs/assets/ocrc-web.png" width="840" alt="ocrc — the Web PWA driving a live opencode session (sessions, live chat, task & cost inspector)">
39
19
  </p>
40
20
 
41
- Install once; it auto-starts in-process with opencode. Same sessions stream live to
42
- **Telegram** and a desktop **PWA** at the same time — switch surfaces mid-task without
43
- losing context.
21
+ ## What it is
44
22
 
45
- ## ⚡ Quick start (5 minutes)
23
+ - **One process.** The plugin loads inside opencode — no daemon, no extra
24
+ services. Telegram and the Web PWA start with opencode and die with it
25
+ (an optional supervisor is available, see [Lifecycle](#lifecycle)).
26
+ - **Two surfaces, one session.** Prompts in from either side; streaming
27
+ output, tool calls, diffs, todos, costs mirror to both in real time.
28
+ - **Local-first, single-user.** Runs on your machine against your local
29
+ opencode server. One allowlisted Telegram user; the web panel is gated by a
30
+ device token. No cloud, no shared backend.
31
+ - **Honest channel status.** Telegram and Web are implemented and stable.
32
+ Feishu / WeChat have a configuration panel pre-wired but the transports
33
+ themselves are **not implemented yet**.
46
34
 
47
- Requires **opencode 1.17+** and **Node 20+** (or Bun). Install from npm —
48
- `npm i -g @bd7pil/ocrc` — or build from source below. (The
49
- `opencode-remote-control` name on npm is an unrelated package — don't `npx` it.)
35
+ ## Quick start
50
36
 
51
- There are two ways to run it — pick one:
52
-
53
- ### Mode A — Plugin (default; controls opencode)
37
+ Requires **opencode 1.17+** (verified on 1.18.32) and **Node 20+**.
54
38
 
55
39
  ```bash
56
- # 1. Install globally, then run the interactive installer
40
+ # 1. Install
57
41
  npm i -g @bd7pil/ocrc
58
- ocrc install # paste your Telegram bot token + user id
42
+ ocrc install # interactive: Telegram bot token + your user id
43
+ # (the `opencode-remote-control` name on npm is an
44
+ # unrelated package — don't npx it)
59
45
 
60
- # 2. Run opencode from any directory — the plugin auto-starts
61
- opencode
62
- ```
46
+ # 2. Start (supervised: crash auto-restart)
47
+ ocrc start --watch /path/to/your/project
63
48
 
64
- Prefer building from source?
49
+ # 3. Talk to it
50
+ # Telegram: send "hello" to your bot
51
+ # Web: open http://<host>:4099 and pair (see below)
52
+ ```
65
53
 
66
- ```bash
67
- git clone https://github.com/BD7PIL/ocrc
68
- cd ocrc
69
- npm install && npm run build:all
70
- node dist/cli/install.js # interactive — paste your Telegram bot token + user id
54
+ Upgrades: `npm i -g @bd7pil/ocrc@<version>` + restart. The opencode binary
55
+ itself is yours to manage (`opencode upgrade <version>` — pin the version).
71
56
 
72
- opencode
73
- ```
57
+ ## Pairing a device
74
58
 
75
- `install.js` writes a plugin bridge to `~/.config/opencode/plugins/` and your
76
- config to `.env`. The plugin then loads in-process whenever opencode runs.
59
+ Web access is gated by a device token. Two ways to pair — **Telegram is not
60
+ required**:
77
61
 
78
- - **Telegram:** make a bot with [@BotFather](https://t.me/BotFather); get your numeric id from [@userinfobot](https://t.me/userinfobot) (the installer asks for both). Send "hello" → the assistant replies.
79
- - **Web PWA:** enabled by default. Run `ocrc pair` (or send `/pair` in Telegram) → open the URL/QR it prints. Auth is a device **token** (persisted at `~/.ocrc/token`) — no Cloudflare Access needed.
80
- - **From another device:** the web binds to `localhost`, so expose it over a tunnel or VPN — e.g. `tailscale serve 17081`. See [Remote access without a domain](#remote-access-without-a-domain).
62
+ 1. **From the host terminal** (works always, no Telegram):
63
+ ```bash
64
+ ocrc pair # prints a QR + link — scan or open on the device
65
+ ```
66
+ 2. **From Telegram** (once the bot is running): send `/pair`, open the link.
81
67
 
82
- ### Mode B — Standalone multi-backend host (opencode + ACP agents)
68
+ Links carry a *pending* token — valid **5 minutes, single use**, and issuing
69
+ a new one invalidates the old (refresh = new code). The device exchanges it
70
+ for the real access token on first open; the token never appears in a URL
71
+ again. Already-paired sessions can onboard further devices from the web
72
+ panel's QR (机器人面板 → 配对新设备).
83
73
 
84
- Run OCRC as its own process serving **multiple agents at once** (opencode + any
85
- [ACP](https://agentclientprotocol.com) agent like Kimi) with an in-UI switcher —
86
- no opencode plugin needed. Requires the ACP agent to be installed and logged in
87
- (e.g. `kimi login`).
74
+ ## Lifecycle
88
75
 
89
76
  ```bash
90
- git clone https://github.com/BD7PIL/ocrc
91
- cd ocrc
92
- npm install && npm run build:all
93
-
94
- cp .env.acp.example .env.acp # set WEB_TOKEN; OCRC_BACKENDS="opencode, kimi=kimi acp"
95
- scripts/run-acp-host.sh # run in a real terminal (needs full PATH to spawn agents)
77
+ ocrc start <dir> # start (detached, no supervision)
78
+ ocrc start --watch <dir> # start under the supervisor (recommended)
79
+ ocrc status # server / watcher / web panel at a glance
80
+ ocrc stop # graceful stop (watcher first, then the instance)
81
+ ocrc restart [dir] # restart, keeping the current mode
82
+ ocrc restore # re-launch the last instance under --watch
96
83
  ```
97
84
 
98
- For an always-on service (auto-start + crash-restart), install the launchd unit
99
- from `deploy/com.ocrc.host.plist`. Full runbook: [`docs/OPS.md`](docs/OPS.md).
100
-
101
- > Note: `ocrc` isn't on your PATH by default — use `node dist/cli/index.js <cmd>`
102
- > (e.g. `node dist/cli/index.js pair`), or `npm link` to get the `ocrc` shim.
103
-
104
- ## How we're different
105
-
106
- - **Runs as an opencode plugin, in-process.** One install, no extra process, no
107
- daemon to babysit — it starts and stops with `opencode` itself.
108
- - **Telegram + Web from a single codebase.** The same sessions stream live to
109
- Telegram and a desktop PWA simultaneously; switch surfaces mid-task without
110
- losing context.
111
- - **SDK-native.** Built on `@opencode-ai/sdk` and the opencode plugin event
112
- hook — it speaks opencode's own protocol rather than scraping a UI, so agent /
113
- model overrides, approvals, diffs, and cost/token metadata all come through
114
- first-class.
115
- - **Transport-agnostic core.** A channel-neutral `CardBus` carries structured
116
- cards; each transport renders them independently. Adding a new channel
117
- (Discord, Slack, …) doesn't touch the relay core.
118
- - **Local-first & single-user.** It runs on your machine against your local
119
- opencode server, stores state in a local file, and answers to one allowlisted
120
- user. No cloud, no shared backend.
121
-
122
- ## Architecture
85
+ `--watch` is a foreground supervisor with octg-proven semantics: adopts an
86
+ already-running instance, restarts the child after a crash (default 5 s,
87
+ `OCRC_WATCH_DELAY`), and treats SIGKILL / segfaults as crashes — an OOM kill
88
+ self-heals. SIGTERM or the stop file means "stop for real". Boot-time
89
+ recovery is opt-in:
123
90
 
124
91
  ```
125
- ┌──────────────────────────────────────────────────────┐
126
- │ opencode (single process) │
127
- │ │
128
- │ ┌──────────────────┐ ┌───────────────────────────┐ │
129
- │ │ AI engine :4096 │ │ plugin: remote-control │ │
130
- │ │ │ │ ├─ Telegraf (Telegram) │ │
131
- │ │ event hook ────┼──┼─►├─ Hono + WS (Web PWA) │ │
132
- │ │ │ │ └─ relay + CardBus │ │
133
- │ └──────────────────┘ └──────────┬────────────────┘ │
134
- │ ▼ │
135
- │ Telegram / Web (PWA) │
136
- └──────────────────────────────────────────────────────┘
92
+ @reboot sleep 60 && ocrc restore >> ~/.ocrc/prod.log 2>&1
137
93
  ```
138
94
 
139
- The plugin loads inside opencode and is driven by the plugin **event hook**: it
140
- submits prompts via the SDK, consumes streaming events, and renders structured
141
- cards to whichever transports are enabled. A TUI, if you run one, is just
142
- another client of the same opencode server.
143
-
144
- Full deep-dive: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
145
-
146
- ## Quick Start (Telegram)
147
-
148
- 1. **Create a bot** with [@BotFather](https://t.me/BotFather), get a token.
149
- 2. **Find your user ID** — message [@userinfobot](https://t.me/userinfobot).
150
- 3. **Build and install the plugin** (opencode 1.17+):
151
- ```bash
152
- npm install && npm run build
153
- node dist/cli/install.js
154
- ```
155
- The installer writes a plugin bridge to `~/.config/opencode/plugins/`
156
- (opencode 1.17 loads local plugins from there — directory paths in
157
- `opencode.json` no longer work), ensures that dir's `package.json` has
158
- `"type": "module"`, and saves `TELEGRAM_BOT_TOKEN` / `ALLOWED_USER_IDS` /
159
- `WEB_ENABLED` / `WEB_PORT` to the repo's `.env`.
160
- 4. **Run opencode** from any directory — the plugin loads globally:
161
- ```bash
162
- opencode
163
- ```
164
- The plugin auto-starts. For an always-on remote-control hub, run it from a
165
- small/empty directory (e.g. `~/ocrc-hub`) so opencode's file watcher stays
166
- fast. You can run several opencode instances — they elect one **PRIMARY**
167
- (atomic lock at `~/.ocrc/primary.lock`) to own the web (`:4099`) and
168
- Telegram singletons; the rest stand down PASSIVE. The web/bot can switch
169
- between the workspaces of the running instances.
170
- 5. **Send "hello"** in Telegram → the assistant responds.
171
-
172
- ## Web UI (PWA)
95
+ ## Telegram
173
96
 
174
- The Web UI runs alongside Telegram and shows the same sessions in real time
175
- (full streaming), installable as a desktop/mobile PWA.
97
+ ~34 commands, grouped: sessions (`/sessions /session /new /rename /workspaces
98
+ /projects /cleanup`), running work (`/skills /ls /open /worktree /diff /todo
99
+ /context /subs`), controls (`/agent /model /mode /task /tasks /tasklist
100
+ /taskdel /mcps /commands /messages /detach`), ops (`/start /status /version
101
+ /pair /channels /help`), plus `/abort` and plain text relay. Send any text to
102
+ drive the agent; approvals and interactive questions arrive as buttons.
176
103
 
177
- 1. Set `WEB_ENABLED=true` (and `WEB_PORT` — default `17081`, since opencode's
178
- own server occupies `7081`).
179
- 2. Build the web app: `cd web && npm run build`.
180
- 3. The plugin serves the PWA at `http://127.0.0.1:17081`.
104
+ ## Web panel
181
105
 
182
- ### Auth — pair a device (default, no Cloudflare Access needed)
106
+ PWA (installable), token-gated, with a live inspector per session: todos,
107
+ MCP servers, schedules, usage/cost, context, working-dir diff, skills, file
108
+ browser, worktrees — plus a floating plan HUD for subagent jumps. The bot
109
+ channels panel configures reply granularity, workspace scope, and shows the
110
+ pairing QR.
183
111
 
184
- Auth defaults to an app **token** (`WEB_AUTH=token`). Onboard a device with
185
- `ocrc pair` (or Telegram `/pair`): it prints a URL + QR with the token in the
186
- fragment (`https://<host>/#token=…`). Open it once — the app stores the token
187
- and attaches it to every request thereafter. The token is persisted at
188
- `~/.ocrc/token`, so it survives restarts and re-installs.
112
+ ## Remote access
189
113
 
190
- > Behind a tunnel, keep `WEB_CF_ACCESS_DEV_BYPASS=false`: `cloudflared` connects
191
- > from loopback, so a loopback bypass would trust all tunnel traffic.
192
-
193
- Prefer Cloudflare Access instead? Set `WEB_AUTH=cf-access` with
194
- `WEB_CF_ACCESS_TEAM` / `WEB_CF_ACCESS_AUD` — see [`docs/OPS.md`](docs/OPS.md).
195
-
196
- ### Remote access without a domain
197
-
198
- A PWA install needs a **secure context** (HTTPS, or `http://localhost`). To
199
- reach the hub from another machine without owning a domain:
114
+ The web binds `0.0.0.0:4099` by default (token-gated). For a PWA install you
115
+ need a secure context:
200
116
 
201
117
  | Method | Command | Notes |
202
118
  |---|---|---|
203
- | **Tailscale** (recommended) | `tailscale serve 17081` | Stable `https://<host>.ts.net`, device-level auth, survives restarts |
204
- | **cloudflared quick tunnel** | `cloudflared tunnel --url http://localhost:17081` | Free `https://*.trycloudflare.com`; URL changes each run |
205
- | **SSH port-forward** | `ssh -L 17081:localhost:17081 <host>` | Then open `http://127.0.0.1:17081` (localhost = secure context) |
206
-
207
- Set `WEB_PUBLIC_URL` to the resulting HTTPS URL so `/pair` emits the right
208
- links. (If you already run a `cloudflared` tunnel to `:4099`, `/pair`
209
- auto-detects its hostname from `~/.cloudflared`.) Plain `http://<LAN-IP>` is
210
- **not** a secure context — Chrome won't install it as an app.
211
-
212
- ### Install as an app
213
-
214
- In Chrome: the omnibox install icon, or **⋮ → Cast, save, and share → Install
215
- page as app…**. The installed PWA reuses the browser's stored token, so it stays
216
- signed in.
217
-
218
- ## Commands
219
-
220
- | Command | Description |
221
- |---|---|
222
- | `/start` | Handshake + health check |
223
- | `/status` | Server health, session count, pinned session |
224
- | `/sessions` | List all sessions with pin buttons |
225
- | `/session <id>` | Pin a specific session |
226
- | `/workspaces` | List known workspaces (directories) |
227
- | `/new` | Start a new session in the active workspace |
228
- | `/rename <title>` | Rename the pinned session |
229
- | `/files` | Files touched in the last session |
230
- | `/diff` | Pending git diff for the session |
231
- | `/todo` | Session todo list |
232
- | `/context` | Tokens + cost + model for the session |
233
- | `/agent` | Set next agent (sticky until cleared) |
234
- | `/model` | Set next model (sticky until cleared) |
235
- | `/current` | Show pinned session |
236
- | `/abort` | Stop the current generation |
237
- | `/pair` | Pair a device (URL + QR with token) for the Web PWA |
238
- | `/version` | Plugin version + uptime |
239
- | `/help` | Show this list |
240
-
241
- Send any text to relay it into opencode.
242
-
243
- ## Push notifications
244
-
245
- The plugin watches opencode sessions and proactively pushes summaries:
246
-
247
- | Trigger | When | Content |
248
- |---|---|---|
249
- | Session finished | >60s run completes | Duration + assistant text summary (first 300 chars) |
250
- | Test failure | Bash output contains FAIL/FAILED | Last 200 chars of output |
119
+ | **LAN, plain HTTP** | open `http://<lan-ip>:4099` | Works in-browser; PWA install needs HTTPS |
120
+ | **Tailscale** | `tailscale serve 4099` | Stable `https://<host>.ts.net`, device auth |
121
+ | **cloudflared** | `cloudflared tunnel --url http://localhost:4099` | Free, URL rotates; set `OCRC_WEB_PUBLIC_URL` |
251
122
 
252
- Rate limits: max 10 notifications/hour, 5-min cooldown per session. A session
253
- the foreground UI just delivered is skipped (no double-ping).
123
+ ## Security model
124
+
125
+ - One allowlisted Telegram user; web devices hold a token generated at first
126
+ start (persisted `0600` at `~/.ocrc/token`), verified with constant-time
127
+ compare on HTTP and WS.
128
+ - Pairing QR links carry a pending token (5 min, single use) — never the
129
+ permanent credential.
130
+ - If the opencode server itself runs with `OPENCODE_SERVER_PASSWORD`, ocrc
131
+ authenticates its server calls with HTTP Basic (same env, no extra config).
132
+ - No cloud. Everything stays on the machine except Telegram API traffic.
254
133
 
255
- ## Multi-transport
134
+ ## Configuration
256
135
 
257
- Telegram and Web run simultaneously and share the same opencode session state,
258
- relaying output to every connected channel in real time.
136
+ Settings live in `~/.ocrc/config.env` (`0600`; `KEY=VALUE`). Highlights:
259
137
 
260
- | Transport | Status | Notes |
138
+ | Key | Default | Notes |
261
139
  |---|---|---|
262
- | Telegram | ✅ Stable | Final-result delivery, pagination, approvals |
263
- | Web (PWA) | ✅ Stable | SvelteKit, full streaming, token auth (default) / Cloudflare Access |
140
+ | `TELEGRAM_BOT_TOKEN` | — | required for the Telegram surface |
141
+ | `ALLOWED_USER_IDS` | — | comma-separated Telegram user ids |
142
+ | `OCRC_WEB_ENABLED` | `true` | web panel on/off |
143
+ | `OCRC_WEB_PORT` | `4099` | web panel port |
144
+ | `OCRC_WEB_HOST` | `0.0.0.0` | bind address |
145
+ | `OCRC_SERVER_PORT` | `4096` | opencode server port (lifecycle commands) |
146
+ | `OCRC_SERVER_BIN` | `~/.local/bin/opencode` | binary used by `ocrc start` |
147
+ | `OCRC_WATCH_DELAY` | `5` | supervisor restart delay (s) |
148
+ | `OPENCODE_SERVER_PASSWORD` | — | enables HTTP Basic for server calls |
149
+ | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
150
+
151
+ Legacy `WEB_*` names are honored as fallbacks. Full list:
152
+ [`docs/OPS.md`](docs/OPS.md).
264
153
 
265
- To add another channel, see
266
- [`docs/transports/CONTRIBUTING-NEW-TRANSPORT.md`](docs/transports/CONTRIBUTING-NEW-TRANSPORT.md).
154
+ ## Architecture
267
155
 
268
- ## Security model
156
+ ```
157
+ ┌────────────────────────────────────────────────────┐
158
+ │ opencode (single process) │
159
+ │ │
160
+ │ ┌─────────────────┐ ┌──────────────────────────┐ │
161
+ │ │ AI engine :4096 │ │ plugin: ocrc │ │
162
+ │ │ │ │ ├─ grammY (Telegram) │ │
163
+ │ │ event hook ────┼──┼─►├─ Hono + WS (Web PWA) │ │
164
+ │ │ │ │ └─ relay + CardBus │ │
165
+ │ └─────────────────┘ └─────────┬────────────────┘ │
166
+ │ ▼ │
167
+ │ Telegram / Web (PWA) │
168
+ └────────────────────────────────────────────────────┘
169
+ ```
269
170
 
270
- - **Single-user per install.** Only the Telegram IDs in `ALLOWED_USER_IDS` can
271
- interact with the bot.
272
- - **Local-first.** Runs on your machine, talks to your local opencode server,
273
- stores state in a local JSON file (`data/state.json`).
274
- - **No secrets in repo.** `.env` is gitignored; `.env.example` documents every
275
- variable.
276
- - **Web auth is pluggable.** Default is an app **token** (auto-generated,
277
- persisted `0600` at `~/.ocrc/token`), verified on HTTP and WS with a
278
- constant-time compare; `WEB_AUTH=cf-access` switches to Cloudflare Access. The
279
- dev bypass only trusts a real loopback peer (never the bind address) and is
280
- **off by default**.
281
-
282
- ## Environment variables
283
-
284
- | Variable | Default | Description |
285
- |---|---|---|
286
- | `TELEGRAM_BOT_TOKEN` | — (required) | Telegram bot token from @BotFather |
287
- | `ALLOWED_USER_IDS` | — (required) | Comma-separated allowed Telegram user IDs |
288
- | `OPENCODE_BASE_URL` | — | opencode server URL. Unused in plugin mode (the SDK client is injected); legacy/sidecar only |
289
- | `CHAT_TIMEOUT_MS` | `600000` | Per-message timeout (ms) |
290
- | `TUI_VISIBLE` | `true` | Navigate the TUI to the target session; `false` = pure direct API |
291
- | `STATE_PATH` | `./data/state.json` | Persistent state file |
292
- | `LOG_LEVEL` | `info` | `debug` \| `info` \| `warn` \| `error` |
293
- | `TG_CHUNK_SOFT_LIMIT` | `3500` | Telegram message pagination soft limit |
294
- | **Web** |||
295
- | `WEB_ENABLED` | `false` | Enable the Web transport |
296
- | `WEB_HOST` | `127.0.0.1` | Web bind address (keep loopback; front with a tunnel) |
297
- | `WEB_PORT` | `17081` | Web port (opencode 1.17's own server occupies `7081`) |
298
- | `WEB_AUTH` | `token` | Auth strategy: `token` (app token) or `cf-access` |
299
- | `WEB_TOKEN` | auto | App token; auto-generated and persisted `0600` at `~/.ocrc/token` if unset |
300
- | `WEB_PUBLIC_URL` | — | Public URL for pairing/QR; falls back to LAN, then loopback |
301
- | `WEB_STATIC_ROOT` | `<repo>/web/dist` | Built PWA path (resolved from the plugin dir, cwd-independent) |
302
- | `WEB_SESSION_CACHE_SIZE` | `100` | Per-session card ring-buffer size |
303
- | `WEB_CF_ACCESS_TEAM` | — | Cloudflare Access team name (when `WEB_AUTH=cf-access`) |
304
- | `WEB_CF_ACCESS_AUD` | — | Cloudflare Access app AUD tag (when `WEB_AUTH=cf-access`) |
305
- | `WEB_CF_ACCESS_DEV_BYPASS` | `false` | Bypass auth **only for a loopback socket peer** |
306
-
307
- ## opencode 1.17+ notes
308
-
309
- opencode 1.17 changed plugin loading; this project accounts for all of it:
310
-
311
- - **Local plugins load from `~/.config/opencode/plugins/`**, not from directory
312
- paths in `opencode.json`. The installer writes a bridge file there that
313
- re-invokes the built `dist/` (the source of truth — rebuild + restart to
314
- update). 1.17 also only calls functions *defined* in the loaded module, so the
315
- bridge wraps the plugin in a local function rather than re-exporting it.
316
- - **Plugins run in a worker thread.** An unhandled rejection would otherwise
317
- crash the worker (`Worker has been terminated`), taking down the web server.
318
- The plugin installs absorbing guards so it survives.
319
- - **Web runs on `17081`** because opencode's own server occupies `7081`. Point
320
- your tunnel ingress at `17081`.
321
- - **PRIMARY election.** Web (`:4099`) and the Telegram bot are global
322
- singletons. Multiple opencode instances elect one PRIMARY (atomic lock at
323
- `~/.ocrc/primary.lock`) to own them; the others stand down PASSIVE.
324
- Run the hub from a small/empty directory so the file watcher stays fast.
325
-
326
- ## Testing
171
+ Deep dive: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
172
+
173
+ ## Development
327
174
 
328
175
  ```bash
329
- npm test # backend unit tests
330
- npx tsc --noEmit # backend type-check
331
- cd web && npm run check # web type-check (svelte-check)
332
- cd web && npm test # web unit tests
333
- cd web && npm run build # build PWA
176
+ npm install && npm run build:all # plugin + web
177
+ npm test # backend (vitest)
178
+ cd web && npm test # web (vitest)
179
+ bash scripts/spike-restart.sh # isolated dev instance (never touches prod)
334
180
  ```
335
181
 
336
- ## License
182
+ ## Credits & license
337
183
 
338
- MIT — see [LICENSE](LICENSE).
184
+ MIT — see [LICENSE](LICENSE) and [NOTICE](NOTICE): forked from
185
+ [agentjoey/opencode-remote-control](https://github.com/agentjoey/opencode-remote-control);
186
+ Telegram UX model from [@grinev/opencode-telegram-bot](https://github.com/grinev/opencode-telegram-bot).
package/dist/cli/index.js CHANGED
@@ -14,7 +14,7 @@ const VERSION = (() => {
14
14
  }
15
15
  })();
16
16
  const HELP = `
17
- ocrc v${VERSION} (fork of agentjoey/opencode-remote-control)
17
+ ocrc v${VERSION}
18
18
 
19
19
  USAGE:
20
20
  ocrc <command>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bd7pil/ocrc",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "OpenCode plugin — remote-control your local opencode from Telegram and a mobile web console (channels, schedules, skills, files, worktrees).",
5
5
  "type": "module",
6
6
  "main": "dist/plugin/entry.js",