Configuration reference¶
All of Beacon's configuration lives in JSON files under %APPDATA%\Beacon\. What the settings page changes and what import packages carry is the content of these files. This page lists every setting's key name, type, default, and actual effect.
Configuration files and when they take effect¶
| File | Contents | After manual edit | After settings-page change |
|---|---|---|---|
| config.json | Global settings, appearance, notification rules | After restart | Immediate (except theme and floating mode, which need a restart) |
| connections.json | Connection definitions and credential references | After restart | Immediate (saving a connection rebuilds the registry) |
| widgets.json | Widget instances and field configuration | After restart | Immediate |
| pins.json | Pinned layout and floating window positions | With widget loading | Immediate |
| actions.json | Custom action library | After restart | Immediate |
| notifications.json | Notification records (read-only cache, deletable) | Rehydrated on restart | No editor provided |
Read failures are never silent: when a main file is corrupt it first tries the .bak backup, then falls back to defaults and archives *.corrupt, with an explanation in both the tray bubble and the logs. Diagnostic logs live at %APPDATA%\Beacon\logs\beacon-*.log.
Global settings (config.json)¶
| Key | Type | Default | Effect | Takes effect |
|---|---|---|---|---|
| hotkey | string | Ctrl+Alt+B |
Global hotkey for the quick panel. Changeable in the settings page "Global hotkey"; a registration failure (already taken) shows a tray bubble | Immediate |
| launchOnStartup | bool | true |
Auto-start on login. Written to the registry Run key, kept in sync with the settings page "Launch on startup" switch | Immediate |
| theme | system / light / dark |
system |
Light / dark / follow system. The WinUI theme can only be set at process start (2026-10-10 decision to keep; see the decision record in mvp-issues) | Restart |
| uiOpacity | number | 1.0 |
Unified opacity for the four floating surfaces (capsule, host panel, standalone floating window, quick panel). Settings slider range 0.2–1.0 in 0.05 steps (matching the runtime floor); below 0.2 is treated as 0.2 | Immediate |
| showCapsule | bool | false |
Show/hide the L1 status capsule (the always-resident capsule in the bottom-right corner). Off by default per the 2026-10-09 decision; older configs on upgrade are flattened once | Immediate |
| keepQuickPanelOpen | bool | false |
Keep the L2 quick panel resident (the "Keep Panel Open" check item in the tray right-click menu, 2026-10-11 user order). On = the panel no longer auto-hides on focus loss and stays on the desktop (Esc or clicking the tray again still closes it; the checkmark survives restart); off = Flyout transient semantics (closes when you click outside) | Immediate |
| pinDisplayMode | panel / floating |
panel |
L0 floating form: panel is a single window with multiple tiles; floating gives each pinned widget its own window, draggable anywhere on the desktop (2026-10-10 decision kept; takes effect on restart) |
Restart |
| numericFloatingEnabled | bool | true |
Whether numeric / quota widgets (AI quota cards etc.) may go on standalone floating windows. On by default (flipped by the 2026-10-11 user order); when off, such pins still show in the host panel | Immediate |
| pollIntervalSeconds | int (seconds) | 0 |
Global check frequency; 0 = each widget's refresh tier uses its default period. Settings options: 15 s / 30 s / 1 min / 5 min. A widget with its own "检测间隔" overrides this |
Immediate (rebuilds the refresh schedule) |
| settingsSidebarWidth | number (DIP) | 264 |
Settings window left-column width. Dragging the splitter between the columns (turns blue on hover) persists automatically and restores on launch; clamped to [200, 420] | Restored on restart (saved right after the drag) |
| settingsWindowWidth / settingsWindowHeight | number (DIP) | 0 |
Settings window size memory; 0 = use the default 880×640. Resizing the window edge persists after 0.8 s at rest and restores on launch |
Restored on restart |
| notificationRules | rule array | [] |
Custom notification rules; an empty array uses the four built-in defaults. See the next section | Immediate (import-package path); manual file edits need a restart |
| appearance | object | see next section | Severity color overrides, motion, and density | Immediate |
| configVersion | int | 1 |
Configuration structure version, used by program migrations — do not edit by hand | Maintained by the program |
| numericFloatingResetDone | bool | false |
Deprecated (the one-time set-to-off migration was removed when the 2026-10-11 numeric floating window switched to on by default); the field is kept only for old-config compatibility — do not edit by hand | Maintained by the program |
Appearance (appearance)¶
Severity colors apply to the tray dot, the capsule, and widget status lights. When a key is absent the default palette is used and tray / capsule / L0 all follow it.
| Key | Type | Default | Effect |
|---|---|---|---|
| appearance.severityColors.info | #RRGGBB |
#58a6ff |
Info color |
| appearance.severityColors.success | #RRGGBB |
#3fb950 |
Success color |
| appearance.severityColors.warning | #RRGGBB |
#d29922 |
Warning color |
| appearance.severityColors.error | #RRGGBB |
#f85149 |
Error color |
| appearance.severityColors.critical | #RRGGBB |
#ff3b30 |
Critical color |
| appearance.severityColors.offline | #RRGGBB |
#8b949e |
Connection offline color |
| appearance.motion.mode | full / reduced / off |
reduced |
Motion tier: full / moderate / off. On off, every animation is still |
| appearance.motion.intensity | number 0.5–2.0 | 1.0 |
Scales motion duration and magnitude. Settings slider steps by 0.1 |
| appearance.progressStyle | "bar" / "pool" |
"bar" |
How tiles show quota usage: progress bar (2026-10-11 redesign default: a 4 DIP rounded bar at the bottom + an NN% readout, ≥80% warning, ≥98% Error red) / circular pool fill (the legacy effect: water level = progress, full = Error red). Takes effect live, no restart needed |
| appearance.density | "regular" / "compact" / "spacious" |
"regular" |
Component spacing density tier: Regular (identity) / Compact (spacing ×0.75) / Spacious (spacing ×1.25), rounded half away from zero. Surfaces pick the tier up when rebuilt (tiles and the panel on their next render, detail and quick panel on next open); unknown values fall back to Regular |
Color changes take effect immediately (tray / capsule / L0 re-render with the new palette). Formats #RRGGBB and #AARRGGBB are accepted; invalid values report an error and are not saved. A widget can additionally override its own light via colorOverride in widgets.json.
Notification rules (notificationRules)¶
Rule semantics: when a widget's state reaches a threshold band, fire a Toast / sound, with no repeat inside the cooldown. Fields:
| Field | Type | Default | Description |
|---|---|---|---|
| id | string | required | Rule identifier, used for record de-duplication |
| widgetType | string | null |
Widget type to match; null = all types |
| widgetId | string | null |
Match one specific widget instance |
| severityAtLeast | enum | warning |
Minimum triggering severity, one of success/info/warning/error/critical |
| severityAtMost | enum | null |
Maximum severity, forming a band with the floor (e.g. "warning only"); null = no ceiling |
| toast | bool | true |
Pop a system Toast |
| sound | bool | false |
Play a sound |
| cooldown | hh:mm:ss |
null |
Cooldown between repeat alerts (e.g. 00:30:00). null = alert once per threshold episode |
| enabled | bool | true |
Rule switch |
When the list is empty, built-in defaults apply:
| Rule id | Matches | Trigger | Cooldown |
|---|---|---|---|
| builtin.ci-failed | github.actions.runs | ≥ error | Once per threshold |
| builtin.ci-stuck | github.actions.runs | warning only (stuck runs) | 30 minutes |
| builtin.pr-review | github.pull_requests | ≥ warning (a review is waiting on you) | 30 minutes |
| builtin.connection-degraded | all widgets (refresh failure) | ≥ warning | 15 minutes |
The settings page "Advanced → Notification rules" shows the currently effective rule set (including the expanded built-in defaults).
Connections (connections.json)¶
| Field | Type | Default | Description |
|---|---|---|---|
| id | string | required | Unique connection name. Credentials are stored in DPAPI under conn:{id} |
| type | string | required | Connection type, see the table below |
| endpoint | string | null |
Service endpoint; null = the type's default endpoint. Overridable (self-hosted proxy / region / acceptance mock) |
| credentialRef | string | per type | DPAPI credential reference. The secret plaintext goes only into DPAPI; the JSON holds only the reference |
| enabled | bool | true |
Module on/off. When false, its widgets stop refreshing, the UI drops their state, and pins leave the desktop |
| settings | object | {} |
Type-specific keys, see the per-type notes |
Per-type credentials and endpoints¶
The settings-page token box hints per type; secrets are always written to DPAPI only:
| Type | Default endpoint | What goes in the token box | Connectivity test |
|---|---|---|---|
| github | api.github.com | GitHub PAT | GET /rate_limit (does not consume core quota) |
| jenkins | required instance base URL (e.g. jenkins.example.com) | user:apiToken (empty = anonymous read; create under Manage Jenkins → Users → API Token, Basic auth) | GET /api/json (2xx and JSON = connected; guards against reverse-proxy false positives) |
| machine | none (leave empty) | none (local machine data, zero credentials zero network) | Not applicable: the connection is always up (the machine is itself) |
| herald | Herald base URL (required, e.g. http://127.0.0.1:8080) |
API Key (empty = anonymous; required when auth.enabled is on; X-API-Key header, written to DPAPI only) |
GET /api/v1/status (always public): 2xx and JSON = connected; a reverse-proxy HTML shell counts as Degraded |
| bigmodel | open.bigmodel.cn | Zhipu API key (the monitoring endpoint takes the raw key directly, no Bearer prefix) | GET quota/limit |
| ark | open.volcengineapi.com | Volcengine AccessKey:SecretKey, colon-joined, pasted at once (created in the console under "API 访问密钥"; the inference ARK_API_KEY cannot call the control plane) |
V4-signed call to GetCodingPlanUsage |
| claude | none (reads local machine) | leave empty | Local session directory readable |
| kimi | api.kimi.com/coding/v1/usages (the community GET endpoint, default). Console endpoint www.kimi.com/apiv2/kimi.gateway.billing.v1.BillingService/GetUsages: paste the whole string as endpoint + add http_method=POST, auth_header=Cookie in Settings (supply http_body per F12 → Network → that request) |
Either one: Kimi For Coding key (sk-kimi- prefix), or a browser Cookie (F12 → Cookies, copy the value and paste). Beacon does no QR-scan / login flow — the Cookie credential is copied from the browser by the user, stored encrypted in DPAPI, and replayed verbatim on requests; after session expiry (browser logout / expiry) just record it once more. Separate accounts from the Moonshot open platform | GET usages / POST GetUsages (2xx = connected) |
| moonshot | api.moonshot.cn/v1/users/me/balance | Moonshot open-platform API key (Bearer; separate accounts from Kimi For Coding) | GET balance (response must contain balance data) |
| sensenova | platform.sensenova.cn/lite/console/v1/tokenplan/pool-usage | SenseNova console JWT (Bearer). This is a console session token, not an API key — copy it after logging into the platform.sensenova.cn console; it carries an exp and expires |
GET pool-usage (response must contain credit-pool data) |
| deepseek | api.deepseek.com | DeepSeek API key | GET /user/balance |
| mimo | token-plan-cn.xiaomimimo.com/v1 | Xiaomi API key (Bearer; only authorizes model calls — the quota additionally needs the console Cookie) | GET /models |
| qwen | token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1 | Bailian Token Plan key (sk-sp- prefix) | GET /models |
| codex | none (reads local machine) | leave empty | Local sessions directory readable |
| copilot | api.github.com/copilot_internal/user | GitHub PAT (copilot scope required) | Hits the quota endpoint directly with the same request headers |
| opencode | opencode.ai/console/api/v2/budgets/members | OpenCode key (oc_sk prefix; reading Budgets needs the All permission) | GET budgets/members |
| http | required | API key (optional) | GET Endpoint, 2xx = healthy |
Common Settings keys¶
Connection types that go through the unified HTTP channel (HttpEndpoint.FetchAsync, src/Beacon.Connections/HttpEndpoint.cs) — bigmodel/deepseek/kimi/moonshot/sensenova/http — support:
copilot/qwen/opencode/mimo do not use the unified channel (each builds its own requests, with its own hard-coded endpoints and headers): they only honor the connection-level
endpoint(copilot/opencode) or the Settingsusage_endpoint(qwen/mimo) plus credentials, not the four channel keys in the table below.
| Key | Default | Description |
|---|---|---|
| auth_header | Authorization |
Auth header name (for Cookie credentials, set Cookie) |
| auth_prefix | Bearer |
Value prefix; can be explicitly emptied (the bigmodel raw key and verbatim Cookie replay are the set-empty cases) |
| http_method | GET |
Request method (POST etc.; the kimi console GetUsages is a POST endpoint) |
| http_body | empty | Request body (JSON text, sent only when configured; content per the request payload in the browser's F12 → Network) |
Type-specific Settings keys¶
| Type | Key | Description |
|---|---|---|
| ark | region | Signing region, default cn-beijing |
| qwen | usage_endpoint | Custom usage endpoint (GET + Bearer). When empty, the model catalog is shown with an honest note that the official quota endpoint is not open |
| mimo | usage_endpoint | Custom usage endpoint (GET + Bearer): a counter-shaped response yields "本机累计 N 次"; a percentage shape (used/total etc.) yields used / percent; an unrecognized shape goes explicitly Degraded with the raw response attached (never passed through to the card) |
| mimo | litellm_spend_url | LiteLLM gateway accounting endpoint (e.g. http://127.0.0.1:4000/spend/logs). Empty by default = nothing is sent out (localhost 127.0.0.1 accounting does not count as outgoing); requests carry the month's start/end start_date/end_date + summarize=true (the gateway's openapi parameter names) |
| mimo | litellm_key_ref | Credential reference for the LiteLLM gateway key, default mimo:litellm (Bearer when present, plain call otherwise — local accounting usually needs no auth); when the gateway has no DB the spend side returns no_db_connection throughout and the card shows Degraded with "未接数据库" |
| mimo | console_usage_url | Console plan-usage API path (default /api/v1/tokenPlan/usage, GET + api-platform_ph Cookie + plan-manage referer) |
| mimo | console_credential_ref | Credential reference for the console Cookie, default mimo:console (Cookie value = the browser F12 → Cookies → api-platform_ph value; pasting the whole Cookie line also works — api-platform_ph is picked out automatically; paste + DPAPI replay, no QR-scan login; re-record on session expiry) |
| http | dsl_path | Path to a Provider DSL declaration file (YAML, see the "Provider DSL" section below). Not configured = zero change; when configured, mapping supplies data-extraction defaults (widget.Config instance values still win) and actions enter the widget detail page's action area. A missing / unparseable file → the affected cards go Degraded with an explicit reason, never silently |
Provider DSL (declarative mapping and actions for http connections, 批3-补42)¶
Connection type http can point Settings dsl_path at a YAML declaration file (zero third-party dependencies; a tiny built-in YAML subset: indented mappings / - lists / quotes / inline comments; flow {} [], block scalars, Tab indentation, and multi-document are unsupported and fail loudly with a line number):
provider: company-packager # required; used by logs and validation
mapping: # optional: data-extraction defaults (keys per the table below; unknown keys error)
status_path: $.job.state
success_values: green,passed
actions: # optional: declared actions (into the detail page action area, through the existing executor chain)
- id: restart # required
type: http # open.url / http / webhook
name: 重启打包任务
url: https://packer.local/api/jobs/42/restart # required; supports {var} templates
method: POST # http defaults to GET
body: '{"force": true}' # optional (quote literals containing { })
confirm: true # default: open.url=false, http/webhook=true
retry: # optional; default 1 attempt (no retry)
max_attempts: 3 # 1–10
backoff_seconds: 5 # 0–300
| mapping key | Applies to | Default |
|---|---|---|
| status_path / summary_path / url_path | http.status status / summary / detail-link extraction | $.status / empty / empty |
| success_values / warning_values / error_values | http.status status vocabularies (comma-separated) | see the http.status notes |
| total_path / used_path / remaining_path / reset_path | http.quota numeric extraction | empty (total stays required) |
| unit | http.quota unit | empty |
Conventions: the declaration layer only supplies data-extraction defaults — widget.Config instance values win (the declaration layer never takes over instance overrides); label/icon/warn_percent/error_percent belong to the widget instance and are not in the DSL. Action ids automatically get the dsl.{connection}.{id} prefix and appear only on the detail pages of that connection's widgets (connection_id scoping); retry happens at the ActionRunner layer — the default 1 attempt = no retry (byte-for-byte equal to existing behavior), retries share one total timeout budget, user cancel / timeout never retries, and a message that still fails after several attempts carries a "已重试 N 次" annotation.
Per-vendor quota conventions and data sources¶
| Connection | Widget types | Quota convention | Data source and degradation behavior |
|---|---|---|---|
| ark | ark.usage | Used percentage across a 5 h rolling window + weekly window + monthly window; worst window wins (2026-10-13 user order): severity / progress bar / main value take the highest-used window and label the window name; warn/error thresholds still apply to the worst window's used % | Control-plane OpenAPI (V4 signing). Any window at 100% shows "额度用尽(窗名,重置时间)" for the whole card; a 429 quota-exhausted code (AccountQuotaExceeded etc.) is treated directly as that window spent with the reset time; a pure rate-limit 429 does not flip |
| bigmodel | bigmodel.usage | Used percentage across a 5 h rolling + weekly + monthly window (the windows have no absolute token figure; the monitoring endpoint gives percentages only); worst window wins (2026-10-13 user order): severity / progress bar / main value take the highest-used window and label the window name; thresholds still apply to the worst window's used % | Monitoring endpoint quota/limit. The main value no longer falls back through rolling → weekly → monthly availability; it takes the highest-used window |
| claude | claude.usage | Tokens and cost in the window (cost only when costUSD is present; Beacon does not price by itself) | Reads local ~/.claude/projects/**/*.jsonl, zero credentials zero network. Only when daily_cost_limit is set does usage raise the severity |
| codex | codex.usage | Tokens over the last N days (per session file, the last cumulative entry is summed; honestly labeled "本地统计,非官方口径") | Reads local ~/.codex/sessions/**/rollout-*.jsonl. The official endpoint needs OAuth, so this is the local-statistics tier |
| kimi | kimi.coding | Card / progress bar = used % (remaining derived); severity still judged on remaining (0% remaining shows "已用尽"); worst window wins (2026-10-13 user order): severity / progress bar / main value take the lowest-remaining (= highest-used) window and label the window name; thresholds still on remaining percentage | usages[] (scope=FEATURE_CODING) entries: detail = period window, in-entry limits[] = 5 h window, totalQuota into payload; values are strings (captured live 2026-10-11; parsing tolerates). The old shape (top-level usage + limits[]) remains a compatible fallback |
| moonshot | moonshot.balance | Balance amount (¥); balance ≤ warn_below (default 20) → Warning, ≤ error_below (default 5) → Error | GET /v1/users/me/balance (Bearer). A response without balance data goes Degraded |
| sensenova | sensenova.tokenplan | Console Token Plan credit-pool used percentage (multiple pools × 5 h/7 d double windows); worst window of the worst pool wins: main value / progress bar / severity take the highest-used window and label pool and window names; thresholds (warn_percent 80 / error_percent 95) apply to the worst window's used %; the summary shows all pools; bonus-expiry warning (nearest expiry within 7 days, or expiring-balance ≤10% of that pool's bonus) raises the severity floor to Warning | Console pool-level endpoint (Bearer JWT). JWT-expired 401 → Degraded explicitly saying "控制台 token 已过期,请重新获取", never silent empty data; no pools / no window data is a legal state (an honest Info about the subscription, no fake red); values are decimal strings (with thousands separators) and parsing tolerates; multiple accounts = multiple connections (one console JWT each, each with its own pools) |
| deepseek | deepseek.balance | Balance amount (CNY/USD); severity judged on total ≤ threshold | GET /user/balance. is_available=false goes straight to Error |
| mimo | mimo.usage | Plan used / total / percentage (shown once the console Cookie is recorded); no Cookie but litellm_spend_url set → "本月已用 $X(网关记账)" (the gateway has no quota denominator, so only the used amount is shown — honest, not invented); otherwise the local counter; otherwise an honest "无额度口" | Four descending tiers: console API → LiteLLM gateway accounting → local counter → model-catalog count (the model count is a catalog, not a quota — it does not pretend). Console 401/403 shows "Cookie 已失效或未录制" on the card + a re-record guide (Degraded, not a silent downgrade). The true console response, captured 2026-10-11: data.monthUsage / usage blocks + items[], where the percent field is a 0–1 ratio (used/limit computes the exact percentage; ≥90% Warn / ≥100% Error). The LiteLLM response tolerates four shapes: root array / response[] / results[] / summarize rollup object |
| qwen | qwen.usage | Token Plan used percentage + reset day (personal: a fixed 7-day window; team: monthly) | Four-stage probing: official usage API absent → custom usage_endpoint → quota probe (when exhausted, any POST gets a 429 AllocationQuota carrying the reset time; non-exhaustion 429s do not flip) → model catalog fallback |
| copilot | copilot.usage | premium_interactions slot used percentage (chat/completions unlimited, not counted); quota reset day shown | The same quota endpoint the official client uses, requiring the Editor-Version / Copilot-Integration-Id headers |
| opencode | opencode.usage | Monthly budget (amount in micro-cents); with a cap set, card / progress bar = cap-converted used % (Progress = used share); multi-member aggregation: spending summed, any member uncapped = uncapped, any overspender = over | Console Budgets API. exceeded → Error (full bar); uncapped → Success baseline, no invented percentage |
| github | github.pull_requests / github.actions.runs | Not a quota: PR-list state / workflow-run state | REST + ETag conditional requests, rate-limit aware |
| jenkins | jenkins.build | Not a quota: latest build state of a job (success / failure / unstable / building / not built / disabled) | Open-source standard JSON API (works for any instance): the job endpoint escapes each job/<name> segment for Folder support; one request judges severity by color (job-level lastBuild lacks result — measured); the _anime suffix = building |
| machine | machine.net / machine.git / machine.local | Not a quota: local network (IP / live ↑↓ speed / cumulative traffic) + local Git repositories (↑↓ · changed · stash, one card per repository) + local resources (CPU / memory / disk, three percentages in a row) | Network: reads only the local NetworkInterface (IPv4 send/receive counters accumulated since boot); speed = the difference between adjacent samples ÷ the time delta (machine tier 15 s; the first refresh has no baseline and shows only IP and cumulative values); a counter reset / NIC change re-baselines; cumulative traffic lives in the L3 detail. Git: three read-only git CLI calls (status --porcelain -b / rev-list / stash list, zero network zero credentials; repositories with no upstream do not show ↑↓); behind>0 or diverged turns yellow (you need to pull / rebase); a preview of the first 5 changed files lives in L3. Resources: Linux reads /proc (first line of stat + meminfo), Windows goes through GetSystemTimes + GlobalMemoryStatusEx, disk uniformly DriveInfo (empty = system drive / root partition; a named-but-missing drive fails loudly); CPU uses the same differencing (no baseline on first refresh, no CPU shown); the three thresholds default to 90%, over the line turns yellow (compared against the rounded display value — what you see is what warns). Optional display items (2026-10-12): the widget config show is a comma-separated multi-select — machine.local can pick cpu_percent / mem_percent / disk_percent, machine.net can pick down_bps / up_bps / ip; empty = show all (the default layout unchanged); unselected items leave the floating tile value slot, the quick panel Summary, and the detail page payload (all three share one source), while raw ledger keys (mem_used / total_sent etc.) are not selectable and always stay in the detail page. The floating tile width is auto-derived from the worst value row of the selected items (TileSizing, floor 168); changing show takes effect on the next refresh, no restart. Everything reads the local machine only and never touches the frozen remote machine management |
| http | http.status / http.quota | Custom: a status-word mapping or a JSONPath numeric card | Any HTTP JSON endpoint; all field paths configurable |
| herald | herald.incidents / herald.feed / herald.deliveries | Not a quota: the incident ledger (open / acknowledged / resolved / all counts + the latest one) + the feed (RSS entries / unread count) + delivery failures (failed count + the latest one's destination and error) | incidents: /api/v1/incidents?status=… (default open; a nonsense value fails loudly), open>0 yellow, containing critical red, retrospective views (acked/resolved) always Info; feed: public category /feeds/{category}.xml (slug validated against injection) or private /feeds/private/{token}.xml (the token is the credential, goes to DPAPI, never into widgets.json nor the payload; category wins); unread = guids new since the last refresh (baseline kept per widget; the first refresh only records the baseline, no unread); deliveries: /api/v1/logs?status=failed&limit=200[&provider=X], failed>0 yellow (undelivered mail needs a human; retries exhausted dead is also written failed at the log layer, not distinguished; shadow is naturally excluded by status=failed, pending is transient and not reported), the count comes from the response total (the filtered total over the whole window), filterable by provider; RFC1123Z timestamps are parsed with the weekday name stripped (invariant-globalization compatible) |
Multi-window quota display (2026-10-12; unified to the used-amount convention by the 2026-10-13 user order): when more than one window of bigmodel/ark/kimi has values at the same time, the tile value slot renders the 5%/5h 70%/w 80%/M form (all-used convention, no prefix; only windows that exist are shown; window order 5 h → weekly → monthly; for kimi, used = derived from remaining). Progress bars are likewise the used share everywhere (provider Progress is the same source); the kimi severity convention is unchanged — thresholds still on the remaining percentage. In the multi-window form the tile collapses the duplicate "NN%" progress readout (the value text already carries it); the single-window form is unchanged. Tile width follows each type's worst value-row text budget (TileSizing); the value column is never truncated. sensenova is the same family but a different form: the tile value slot takes the worst window of the worst pool (pool name + window name + used %, e.g. "Flash-Lite池 5h 已用100%"), and the summary shows every pool (each "short name 5h X% · 7d Y%", pools separated by " / ").
Widgets (widgets.json)¶
| Field | Type | Default | Description |
|---|---|---|---|
| id | string | required | Unique widget instance name |
| type | string | required | Widget type, e.g. ark.usage (see the widget-type column above; also github.pull_requests etc.) |
| connectionId | string | "" |
The bound connection id |
| config | object | {} |
Type-specific fields, see the next section; common keys: label (tile display name), icon (override the brand glyph), progressStyle (pool = quota progress switches to the legacy circular-pool-fill effect; absent = follow the appearance setting's "额度显示方式") |
| refreshTier | string | default |
Refresh tier, see the tier table |
| refreshIntervalSeconds | int | null |
Detection-interval override (seconds). null = the refresh tier's period; settings options 30 s / 1 / 2 / 5 / 15 min / 1 hour |
| pinned | bool | false |
Whether pinned to the desktop (L0). The context menu, the settings page, and the quick panel all write this one field |
| colorOverride | #RRGGBB |
null |
Status-light color override; priority: widget > global palette; invalid values fall back to the default |
| pinLayout | object | null |
Layout convenience reference (pins.json is authoritative; both are written together) |
Refresh tiers (refreshTier)¶
| Tier | Period | Applies to |
|---|---|---|
| pr | 60 s | GitHub PRs |
| ci | 15 s | CI runs, AI quota widgets |
| machine | 15 s | Machine state |
| agent | 10 s | Agent activity |
| workflow | 10 s | Workflow events |
| static | no auto refresh | Manual / event-triggered |
| default | 60 s | Fallback |
Failure backoff: multiplies the tier period, capped at 10×; the period carries 20% jitter.
Widget config fields (by type)¶
Common fields (most quota widgets have them):
| Field | Applies to | Default | Description |
|---|---|---|---|
| label | all | per type | Display name, on the tile and the card title |
| icon | types with a brand slot | the connection's brand | Icon (icon picker) |
| warn_percent | used-% quota widgets | 60/80 per type | Turns Warning when used reaches this |
| error_percent | used-% quota widgets | 90/95 per type | Turns Error when used reaches this |
github.pull_requests:
| Field | Default | Description |
|---|---|---|
| repo | required | owner/repo |
| warnOnReviewRequested | — | When true, alerts on "PRs waiting for your review" |
| warnOnRedCi | false |
When true, a PR's red CI (combined status failure) counts toward the alert. CI state is remembered per PR: terminal and unchanged-commit PRs reuse the memory (steady state, zero extra requests); probes go out only for new pushes or in-flight PRs; the first sweep after enabling costs one request per PR — expensive on huge repositories at first refresh |
github.actions.runs:
| Field | Default | Description |
|---|---|---|
| repo | required | owner/repo |
| workflow | required | Workflow file name (e.g. ci.yml) |
| branch | repository default branch | Branch filter |
claude.usage:
| Field | Default | Description |
|---|---|---|
| days | 1 |
Statistics window (days) |
| daily_cost_limit | none | USD per window. Only when set does usage raise severity; otherwise always Success |
| warn_percent / error_percent | 60 / 90 | Pairs with the cost cap |
codex.usage:
| Field | Default | Description |
|---|---|---|
| days | 7 |
Statistics window (days) |
kimi.coding (thresholds judge the remaining amount; card / progress bar show used %):
| Field | Default | Description |
|---|---|---|
| warn_percent | 30 |
Warns when remaining drops below this |
| error_percent | 10 |
Errors when remaining drops below this |
deepseek.balance (balance convention):
| Field | Default | Description |
|---|---|---|
| warn_below | 20 |
Warns when the balance drops below this (in the balance's currency) |
| error_below | 5 |
Errors when the balance drops below this |
http.status:
| Field | Default | Description |
|---|---|---|
| status_path | required | Status field JSONPath (e.g. $.status) |
| summary_path | — | Summary field path |
| url_path | — | Detail-link path |
| success_values | success,ok,done,passed,healthy,finished |
Success status words (comma-separated) |
| warning_values | running,building,pending,queued,in_progress,deploying,starting |
In-progress status words |
| error_values | failed,failure,error,critical,broken |
Failure status words; empty / unknown words count as Warning (better to warn than to miss) |
http.quota:
| Field | Default | Description |
|---|---|---|
| total_path | required | Total field path |
| used_path | one of the two | Used field path |
| remaining_path | one of the two | Remaining field path |
| reset_path | — | Reset-time path (epoch seconds / milliseconds / ISO auto-detected) |
| unit | — | Unit label (e.g. tokens) |
| warn_percent / error_percent | 60 / 90 | Thresholds; missing data counts as Warning |
ark.usage / bigmodel.usage / qwen.usage / copilot.usage / opencode.usage: common fields only (label, warn_percent, error_percent); each vendor's default thresholds are in the quota-convention table (bigmodel/ark/qwen 60/90, copilot/opencode 80/95). mimo.usage has only label (thresholds fixed at 90/100, not configurable).
Pinned layout (pins.json)¶
| Field | Type | Description |
|---|---|---|
| tiles[].widgetId | string | The corresponding widget id |
| tiles[].layout.monitor | string | Monitor identifier (device name + resolution); restored by anchor after hot-plug |
| tiles[].layout.anchor | enum | Anchor corner: topLeft/topRight/bottomLeft/bottomRight |
| tiles[].layout.offsetDips | object | Offset from the anchor corner (DIP, scales with DPI) |
| tiles[].layout.collapsed | bool | Edge-collapsed state (strip / dot; hover expands) |
| tiles[].layout.floatingX | int | Window X in standalone-floating mode (physical pixels, kept per widget; null = never dragged, cascades in order) |
| tiles[].layout.floatingY | int | Window Y in standalone-floating mode |
Custom actions (actions.json)¶
| Field | Type | Default | Description |
|---|---|---|---|
| id | string | required | Unique action name |
| type | enum | required | open.url / http / gh.workflow_dispatch / local.command / webhook |
| name | string | null |
Display name |
| requireConfirmation | bool | true |
Confirm before executing dangerous operations; can be turned off per action |
| parameters | object | {} |
Parameters per type (URL / command / workflow file etc.) |
Actions appear in the L3 detail window's ACTIONS area filtered by widgetType; the editor lives in settings "Advanced → Custom actions".
Configuration import and export¶
Export packs config/connections/widgets/pins/actions into one JSON package (settings "Advanced → Import/Export"):
{
"version": 1,
"exportedAt": "2026-10-10T12:00:00+08:00",
"app": { "hotkey": "Ctrl+Alt+B", "...": "全局设置与外观、通知规则" },
"connections": [ { "id": "ark-main", "credentialRef": "conn:ark-main", "...": "" } ],
"widgets": [],
"pins": { "tiles": [] },
"actions": []
}
Red line: secrets never enter the package. The exported file contains no token anywhere; connections carry only credentialRef references. After import, each credentialRef in the package prompts once to re-enter the secret (the new machine's DPAPI has no matching plaintext). The actions field was added later; an older package without it still imports (empty action library, no error). Import is a wholesale replacement: what is in the package lands; keys absent from the package fall back to defaults.
Floating display policy¶
Three switches decide where a widget finally appears:
| Switch | Location | Effect |
|---|---|---|
| pinDisplayMode | Settings → General | L0 form, one of two: host panel (one window, multiple tiles) / standalone floating windows (one window per widget). Switching needs a restart |
| numericFloatingEnabled | Settings → General | Whether numeric / quota widgets may go on standalone floating windows. On by default (2026-10-11 user order); off means such pins show only in the host panel, data unaffected |
| pinned (per widget) | Context menu / settings page / quick panel | Whether the widget is pinned to the desktop |
Decision rules: information-dense widgets (trend charts / light groups) are not governed by the numeric floating master switch; numeric / quota widgets (FloatingOptIn — all AI quota cards, HTTP numeric cards) are. In floating mode, a widget already shown in a floating window does not repeat in the host panel; in panel mode the panel is the only L0 and the master switch gates only the standalone-floating route. A widget whose connection is disabled (enabled=false) leaves the desktop, and its state and events are cleared together — no stale light is left behind.