Skip to content
Merged
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,39 @@ automatically. Point `ZCODE_BIN` at the bundled `zcode.cjs`:
## Standalone Quota CLI

Besides the ACP server, the package ships a `zcode-quota` bin that queries
your GLM Coding Plan usage **from the terminal** — no editor or running server
needed. It reads the same `~/.zcode/v2/config.json` for credentials.
your usage **from the terminal** — no editor or running server needed. By
default it shows both **GLM Coding Plan** and **Opencode Go** in one card;
pass a provider to focus on one.

GLM credentials are read from `~/.zcode/v2/config.json`. Opencode Go
credentials come from environment variables (the dashboard needs a browser
cookie — see [Opencode Go setup](#opencode-go-setup) below).

```bash
# One-shot: print the card and exit
# Both providers (default): GLM + Opencode Go in one card
zcode-quota

# Focus on one provider
zcode-quota glm # GLM Coding Plan only
zcode-quota go # Opencode Go only (rolling + weekly + monthly)

# Live monitor: clear the screen and refresh every 30s (default)
zcode-quota -w
zcode-quota go -w # watch Opencode Go only

# Refresh at a custom interval (seconds; minimum 10)
zcode-quota --watch --interval 60

# Plain monochrome bars (color is the default on a terminal)
zcode-quota --plain
```

By default the CLI renders heat-colored (green→yellow→red) progress bars with
the usage numbers overlaid inside the bar, so each line stays short. Pass
`--plain` (or `-p`) for the classic monochrome `█`/`░` layout. Color is also
disabled automatically when stdout is piped or redirected, so captured output
stays clean.

The watch mode clears and redraws the card in place, like `top`/`htop`. Press
`Ctrl-C` to exit. The 10s minimum exists because the quota API is cached for
10s internally — a shorter interval would just keep returning the stale cached
Expand All @@ -116,6 +135,38 @@ When the package isn't globally installed, run the built file directly:
node dist/bin/quota.js -w
```

### Opencode Go setup

Opencode Go has no JSON API for subscription usage — the CLI scrapes the
authenticated dashboard at `opencode.ai/workspace/<id>/go`, so it needs your
browser `auth` cookie. Credentials are read from two sources, **merged
field-by-field with environment variables taking precedence** over the config
file:

- **Config file**: `~/.pi/agent/opencode-go.json` — same convention as the
`@beyona/pi-zai-usage` Pi extension, so if you already configured it there
you're done.
```json
{ "workspaceId": "wrk_your_workspace_id", "authCookie": "Fe26.2**your_cookie_value" }
```
- **Environment variables** (override the matching file field):
```bash
export OPENCODE_GO_WORKSPACE_ID="wrk_your_workspace_id"
export OPENCODE_GO_AUTH_COOKIE="Fe26.2**your_cookie_value"
```

How to get the values:

1. **Workspace ID** — open `https://opencode.ai`, navigate to your Go
workspace, and copy the `wrk_…` id from the URL
(`https://opencode.ai/workspace/<wrk_…>/go`).
2. **Auth cookie** — open browser DevTools (F12) → Application → Cookies →
`opencode.ai` → copy the value of the cookie named `auth` (it starts with
`Fe26.2**`).

Without credentials, the default dual-provider mode silently shows GLM only
(no error). Running `zcode-quota go` without credentials prints a setup hint.

## ACP Registry

This server is compatible with the [ACP Registry](https://agentclientprotocol.com/get-started/registry). It advertises a single `agent`-type auth method at `initialize` time — the GLM API key is read from `~/.zcode/v2/config.json` by the ZCode backend, so **no editor-side credentials are required**.
Expand Down
43 changes: 40 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,35 @@ ZCode CLI 内置于桌面应用中,默认不会加到 `PATH`。用 `ZCODE_BIN`
## 独立配额查询 CLI(zcode-quota)

除了 ACP server,本包还附带一个 `zcode-quota` 命令,可在**终端**里直接查询
GLM Coding Plan 用量——无需编辑器,也无需 server 运行。它读取同一个
`~/.zcode/v2/config.json` 获取凭证。
用量——无需编辑器,也无需 server 运行。默认在一张卡片里同时显示
**GLM Coding Plan** 和 **Opencode Go**;传入 provider 参数可只看其中一个。

GLM 凭证读取自 `~/.zcode/v2/config.json`。Opencode Go 凭证来自环境变量
(dashboard 需要浏览器 cookie——见下方 [Opencode Go 配置](#opencode-go-配置))。

```bash
# 一次性:打印卡片后退出
# 双平台(默认):GLM + Opencode Go 合并为一张卡片
zcode-quota

# 只看某一个 provider
zcode-quota glm # 仅 GLM Coding Plan
zcode-quota go # 仅 Opencode Go(rolling + weekly + monthly 三窗口)

# 常驻监控:清屏并每 30s 刷新(默认)
zcode-quota -w
zcode-quota go -w # 只监控 Opencode Go

# 自定义刷新间隔(秒,最小 10)
zcode-quota --watch --interval 60

# 纯文本单色进度条(终端默认是彩色)
zcode-quota --plain
```

默认情况下 CLI 会渲染热力配色(绿→黄→红)的进度条,并把用量数字叠在条内,
这样每行更紧凑。传 `--plain`(或 `-p`)切回经典的 `█`/`░` 单色布局。当 stdout
被管道或重定向时,彩色也会自动关闭,保证捕获到的输出干净。

watch 模式会原地清屏重绘卡片,效果类似 `top`/`htop`。按 `Ctrl-C` 退出。
之所以设最小间隔 10s,是因为配额 API 内部有 10s 缓存——更短的间隔只会一直
返回过期的缓存值,没有意义。
Expand All @@ -114,6 +129,28 @@ watch 模式会原地清屏重绘卡片,效果类似 `top`/`htop`。按 `Ctrl-
node dist/bin/quota.js -w
```

### Opencode Go 配置

Opencode Go 订阅用量没有 JSON API——CLI 抓取的是登录后的 dashboard 页面
`opencode.ai/workspace/<id>/go`,因此需要你的浏览器 `auth` cookie。设置两个
环境变量:

```bash
export OPENCODE_GO_WORKSPACE_ID="wrk_你的工作区id"
export OPENCODE_GO_AUTH_COOKIE="Fe26.2**你的cookie值"
```

获取方式:

1. **Workspace ID**——打开 `https://opencode.ai`,进入你的 Go 工作区,从 URL
里复制 `wrk_…` id(`https://opencode.ai/workspace/<wrk_…>/go`)。
2. **Auth cookie**——打开浏览器开发者工具(F12)→ Application → Cookies →
`opencode.ai` → 复制名为 `auth` 的 cookie 值(以 `Fe26.2**` 开头)。

未设置这两个变量时,默认的双平台模式会**静默退化为只显示 GLM**(不报错)。
若明确运行 `zcode-quota go` 但未配置,会打印一条配置提示。把它们加到 shell
配置文件(`~/.zshrc` / `~/.bashrc`)即可持久化。

## ACP Registry

本服务端兼容 [ACP Registry](https://agentclientprotocol.com/get-started/registry)。它在 `initialize` 时声明一个 `agent` 类型的认证方法——GLM API key 由 ZCode 后端从 `~/.zcode/v2/config.json` 读取,**编辑器侧无需配置任何凭据**。
Expand Down
13 changes: 12 additions & 1 deletion src/backend/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ export class EventStreamListener {
const isTimeout = resp.error.message === "timeout";
if (!isTimeout || attempt === MAX_ATTEMPTS) {
if (isTimeout) {
warn(`subscribe: all ${MAX_ATTEMPTS} attempts timed out (backend unresponsive for ~${Math.round((MAX_ATTEMPTS * 5000 + 500) / 1000)}s)`);
warn(
`subscribe: all ${MAX_ATTEMPTS} attempts timed out (backend unresponsive for ~${Math.round((MAX_ATTEMPTS * 5000 + 500) / 1000)}s)`,
);
}
throw new Error(formatSubscribeError(resp));
}
Expand Down Expand Up @@ -126,6 +128,15 @@ export class EventStreamListener {
}
}

/**
* True if events are queued waiting for a poll (non-destructive). Lets the
* turn loop check liveness without consuming an event — used by the stall
* reconciliation to confirm a turn is still alive before ending it.
*/
hasQueuedEvents(): boolean {
return this.queue.length > 0;
}

/**
* Wait for the next event, resolving once one arrives or `timeoutMs` elapses
* (resolves null on timeout). Events arriving with no active waiter are
Expand Down
Loading
Loading