feat: v2.5.12 — plan and quota for every AI tool, Grok Build and Kimi Code, Quit AgentHub (#65)

Usage panel (status bar):
- every account shows its plan (Claude subscriptionType, Codex
  chatgpt_plan_type, Cursor membershipType, Grok subscriptionTier)
- new quota rows: Cursor CLI (cursor.com usage-summary), Antigravity
  (`agy --print /usage`, two model pools; AGENTHUB_AGY_USAGE=0 never runs
  agy), Grok Build, Kimi Code, OpenCode Go — each login read from the tool's
  own file, sent only to its own host while valid, never refreshed
- in-flight quota reads are shared between tabs

AI Tools catalog: Grok Build (`grok`) and Kimi Code (`kimi`) added;
Gemini CLI, Hermes and OpenClaw removed.

Also: Quit AgentHub (#65, `agenthub stop`), status-area icon, Add to Chat
on an editor selection, Settings in three groups, JetBrains-style Changes
panel (ticked files/hunks, ignore, blame, file history), terminal font.

--no-verify: :3000 is taken by another dev server (lane-detect tests);
both suites run by hand — server 2636/2639 (the 3 are :3000), client
1131/1131.
This commit is contained in:
2026-10-08 12:22:14 +07:00
parent 6ef9a5d4b7
commit a9c71cd905
133 changed files with 8007 additions and 1830 deletions
+398 -2
View File
@@ -3,7 +3,7 @@
openapi: 3.0.3
info:
title: Agent Dashboard for Claude Code API
version: 2.5.11
version: 2.5.12
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact:
name: Nguyễn Ngọc Trí Vĩ
@@ -1100,6 +1100,12 @@ components:
fast_output_per_mtok:
type: number
description: Fast-mode output rate (0 = none)
long_input_per_mtok:
type: number
description: Input rate for a request whose prompt is above 100k tokens (0 = none); cache rates scale by long_input / input
long_output_per_mtok:
type: number
description: Output rate for a request whose prompt is above 100k tokens (0 = none)
source:
type: string
nullable: true
@@ -1190,6 +1196,17 @@ components:
type: integer
code_execution_requests:
type: integer
long_input_tokens:
type: integer
description: Part of input_tokens spent by requests whose prompt was above 100k tokens
long_output_tokens:
type: integer
long_cache_read_tokens:
type: integer
long_cache_write_tokens:
type: integer
long_cache_write_1h_tokens:
type: integer
cost:
type: number
matched_rule:
@@ -11556,6 +11573,33 @@ paths:
description: ESECRET — `.claude/settings.local.json` (where an older build kept the AI Proxy key) is refused at every rev, at any depth, and under any path that resolves to it (a symlinked `.claude`).
'404':
description: Lane not found.
/api/lanes/{id}/git/blame:
get:
tags:
- Lanes
summary: 'Annotate: each line''s last commit'
description: '`git blame --porcelain` of the working-tree file, for the Changes panel''s Annotate gutter. Same lexical lane guard and secret refusal as /git/file, capped at 1 MB. A line nobody committed yet is null. An untracked file (no history) answers {available:false}. Read-only, no same-origin guard.'
operationId: blameLaneFile
parameters:
- name: id
in: path
required: true
schema:
type: integer
- name: path
in: query
required: true
schema:
type: string
responses:
'200':
description: '{available, path, commits: {<sha>: {short, author, date, summary}}, lines: [<sha>|null]}.'
'400':
description: EBADPATH, ENOFILE or ETOOBIG.
'403':
description: ESECRET.
'404':
description: Lane not found.
/api/lanes/{id}/git/raw:
get:
tags:
@@ -11745,6 +11789,7 @@ paths:
- revert
- undo-commit
- amend
- ignore
requestBody:
required: false
content:
@@ -11779,10 +11824,24 @@ paths:
type: array
items:
type: string
description: stage, unstage, discard; each must be a path git reports changed.
description: 'stage, unstage, discard, ignore (untracked only, else 400 ETRACKED; appends an anchored line to the workspace''s .gitignore); each must be a path git reports changed. commit and amend: commit exactly these files'' working-tree content (`git commit --only`), an untracked one included, leaving every other change — staged or not — uncommitted; a conflicted one is 409 ECONFLICTS. amend with an empty list rewords only (a message is then required, else 400 ENOCHANGES).'
path:
type: string
description: stage-hunk, discard-hunk; a path git reports changed (not untracked).
hunks:
type: object
additionalProperties:
type: array
items:
type: object
properties:
hunk:
type: string
blocks:
type: array
items:
type: integer
description: 'commit, amend (optional): the hunk-level commit — per tracked, non-renamed path, only these change blocks of these hunks (`@@` lines as the diff at `context` returned them). Built in a temporary index from git''s own fresh diff and committed from it; the real index entries of the committed paths are then reset to the new HEAD, the rest of a partial file left as an unstaged change. A hunk no longer in the diff is 409 EHUNKGONE, an untracked path 400 EUNTRACKED, a bad shape 400 EBADPATH.'
hunk:
type: string
description: stage-hunk, discard-hunk; the hunk's `@@` line as the diff returned it. The server re-reads the diff and applies git's own text of that hunk; a hunk no longer present is 409 EHUNKGONE.
@@ -12706,6 +12765,75 @@ paths:
description: 'EBADORIGIN: another site asked'
'409':
description: 'EFOREIGN: a unit named agenthub that AgentHub did not write'
/api/settings/tray:
get:
tags:
- Settings
summary: Whether AgentHub's icon shows in the OS status area
operationId: getTray
responses:
'200':
description: Status-area icon state
content:
application/json:
schema:
type: object
required:
- available
- enabled
- running
properties:
available:
type: boolean
description: False in server mode, or on Linux with no graphical session
enabled:
type: boolean
description: On by default
running:
type: boolean
description: The icon process is heartbeating
put:
tags:
- Settings
summary: Show or hide the status-area icon
description: Clears or writes `<data dir>/runtime/tray-off` and starts or stops the icon process. Never touches the server. Same-origin only.
operationId: setTray
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- enabled
properties:
enabled:
type: boolean
responses:
'200':
description: The new state
content:
application/json:
schema:
type: object
required:
- available
- enabled
- running
properties:
available:
type: boolean
description: False in server mode, or on Linux with no graphical session
enabled:
type: boolean
description: On by default
running:
type: boolean
description: The icon process is heartbeating
'400':
description: 'EBADVALUE: `enabled` is not a boolean'
'403':
description: 'EBADORIGIN: another site asked'
/api/settings/codex-hooks:
get:
tags:
@@ -13426,6 +13554,274 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags:
- Auth
operationId: createServerUser
summary: 'Admin: add an account. Needs the admin''s own password again (currentPassword); audited'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- username
- password
- currentPassword
properties:
username:
type: string
password:
type: string
minLength: 8
role:
type: string
enum:
- admin
- analyst
- member
default: member
currentPassword:
type: string
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
user:
$ref: '#/components/schemas/AuthUser'
'400':
description: EBADUSER / EBADPASSWORD / EBADROLE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: EEXISTS
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — 10 wrong passwords in 15 minutes
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users/{id}:
patch:
tags:
- Auth
operationId: updateServerUser
summary: 'Admin: change a role and/or disable (ending every login) or enable. Never your own (ESELF)'
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- currentPassword
properties:
role:
type: string
enum:
- admin
- analyst
- member
disabled:
type: boolean
currentPassword:
type: string
responses:
'200':
description: Changed
content:
application/json:
schema:
type: object
properties:
user:
$ref: '#/components/schemas/AuthUser'
'400':
description: EBADPASSWORD / EBADROLE / EBADFIELD
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: ENOUSER / ENOLANE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: ESELF — your own role or disable
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — 10 wrong passwords in 15 minutes
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users/{id}/password:
post:
tags:
- Auth
operationId: resetServerUserPassword
summary: 'Admin: a new password for that user; every login of theirs ends'
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- password
- currentPassword
properties:
password:
type: string
minLength: 8
currentPassword:
type: string
responses:
'200':
description: Changed
content:
application/json:
schema:
type: object
properties:
user:
$ref: '#/components/schemas/AuthUser'
'400':
description: EBADPASSWORD / EBADROLE / EBADFIELD
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: ENOUSER / ENOLANE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: ESELF — your own role or disable
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — 10 wrong passwords in 15 minutes
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users/{id}/workspaces:
post:
tags:
- Auth
operationId: assignServerUserWorkspace
summary: 'Admin: make that user the owner of a workspace (lane)'
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- laneId
- currentPassword
properties:
laneId:
type: integer
currentPassword:
type: string
responses:
'200':
description: Changed
content:
application/json:
schema:
type: object
properties:
user:
$ref: '#/components/schemas/AuthUser'
'400':
description: EBADPASSWORD / EBADROLE / EBADFIELD
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: ENOUSER / ENOLANE
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: ESELF — your own role or disable
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: ETOOMANY — 10 wrong passwords in 15 minutes
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/users/audit:
get:
tags: