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
+77 -11
View File
@@ -397,6 +397,7 @@ import type {
Lane,
LaneChangeRange,
LaneChanges,
LaneBlame,
LaneCommit,
LaneFileContent,
LaneTreeEntry,
@@ -673,8 +674,42 @@ export const api = {
users: {
/** GET /api/users — every account with its recent usage (admin/analyst). */
list: () => request<{ users: ServerUserUsage[] }>("/users"),
/** GET /api/users/audit — staff reads of members' sessions, newest first. */
/** GET /api/users/audit — staff reads of members' sessions and admins'
* account changes, newest first. */
audit: () => request<{ entries: AuditEntry[] }>("/users/audit"),
// Account changes (admin only). Each carries the admin's OWN password
// again (`currentPassword`): the server refuses without it (EREAUTH).
/** POST /api/users — a new account. */
create: (
body: { username: string; password: string; role: AuthUser["role"] },
currentPassword: string
) =>
request<{ user: AuthUser & { disabled: boolean } }>("/users", {
method: "POST",
body: JSON.stringify({ ...body, currentPassword }),
}),
/** PATCH /api/users/:id — role and/or disabled (never your own). */
update: (
id: number,
body: { role?: AuthUser["role"]; disabled?: boolean },
currentPassword: string
) =>
request<{ user: AuthUser & { disabled: boolean } }>(`/users/${id}`, {
method: "PATCH",
body: JSON.stringify({ ...body, currentPassword }),
}),
/** POST /api/users/:id/password — a new password; that user's logins end. */
setPassword: (id: number, password: string, currentPassword: string) =>
request<{ user: AuthUser }>(`/users/${id}/password`, {
method: "POST",
body: JSON.stringify({ password, currentPassword }),
}),
/** POST /api/users/:id/workspaces — give that user a workspace. */
assignWorkspace: (id: number, laneId: number, currentPassword: string) =>
request<{ user: AuthUser }>(`/users/${id}/workspaces`, {
method: "POST",
body: JSON.stringify({ laneId, currentPassword }),
}),
},
// ───────────────────────── Updates / self-update API ─────────────────────────
/** Self-update status: whether this install is a git clone and, if so,
@@ -1113,7 +1148,13 @@ export const api = {
};
load_stats: { m5: number; m15: number; h1: number };
};
hooks: { installed: boolean; path: string; hooks: Record<string, boolean> };
hooks: {
installed: boolean;
path: string;
hooks: Record<string, boolean>;
/** "plugin" when the enabled agenthub plugin provides the hooks. */
source?: "plugin";
};
server: {
version: string;
uptime: number;
@@ -1197,6 +1238,21 @@ export const api = {
body: JSON.stringify({ enabled }),
}),
},
/** AgentHub's icon in the OS status area — on by default; a change starts
* or stops the icon process, never the server. */
tray: {
/** GET /api/settings/tray */
get: () => request<TrayStatus>("/settings/tray"),
/** PUT /api/settings/tray */
set: (enabled: boolean) =>
request<TrayStatus>("/settings/tray", {
method: "PUT",
body: JSON.stringify({ enabled }),
}),
},
/** POST /api/settings/quit — close every workspace terminal and stop the
* server until `agenthub start` (or a boot/login with autostart on). */
quit: () => request<{ ok: true; killed: number }>("/settings/quit", { method: "POST" }),
/** Opt-in secret redaction of stored payloads and the folders whose hooks
* are dropped (`server/lib/privacy.js`). */
privacy: {
@@ -2891,6 +2947,9 @@ export const api = {
* @param id The lane id.
* @param opts `file` narrows the list to the commits touching one path.
*/
/** GET /api/lanes/:id/git/blame — Annotate: each line's last commit. */
gitBlame: (id: GitTarget, path: string) =>
request<LaneBlame>(gitUrl(id, "/blame", new URLSearchParams({ path }))),
gitLog: (id: GitTarget, opts: { limit?: number; file?: string; ref?: string } = {}) => {
const q = new URLSearchParams();
if (opts.limit) q.set("limit", String(opts.limit));
@@ -2978,8 +3037,11 @@ export const api = {
* POST /api/lanes/:id/git/commit-message — a message for what is staged,
* written by the user's `claude` CLI (no tools, nothing persisted).
*/
suggestCommitMessage: (id: GitTarget) =>
request<{ message: string }>(gitUrl(id, "/commit-message"), { method: "POST" }),
suggestCommitMessage: (id: GitTarget, paths?: string[]) =>
request<{ message: string }>(gitUrl(id, "/commit-message"), {
method: "POST",
...(paths ? { body: JSON.stringify({ paths }) } : {}),
}),
/**
* POST /api/lanes/:id/git/:action — one git write on the lane's worktree.
* @param id The lane id.
@@ -3016,7 +3078,8 @@ export const api = {
| "cherry-pick"
| "revert"
| "undo-commit"
| "amend",
| "amend"
| "ignore",
body: {
/** cherry-pick / revert: the commit. */
sha?: string;
@@ -3278,12 +3341,7 @@ function requestCodexBackupsHelper(params?: {
/** Kind of Claude Code config artifact manageable via `api.ccConfig.write`/
* `delete` - each maps to a distinct on-disk location under `.claude/`. */
export type CcArtifactType =
| "skills"
| "agents"
| "commands"
| "output-styles"
| "memory"
| "auto-memory";
"skills" | "agents" | "commands" | "output-styles" | "memory" | "auto-memory";
/** Body for PUT /api/cc-config/file - create or overwrite one artifact. */
export interface CcWriteArgs {
@@ -4436,6 +4494,14 @@ export interface AutostartStatus {
foreign: boolean;
}
/** GET /api/settings/tray. `available` false: server mode, or no graphical
* session on this Linux machine. */
export interface TrayStatus {
available: boolean;
enabled: boolean;
running: boolean;
}
/** GET /api/settings/privacy. A field the environment set is `env_owned`
* and cannot be saved from the dashboard. */
export interface PrivacySettings {