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:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @file Settings.test.tsx
|
||||
* @description The install-wide Settings pages: General and Notifications never
|
||||
* @description The install-wide Settings pages: System shows the server's
|
||||
* runtime, install and database facts; General and Notifications never
|
||||
* read `/api/settings/info` (it COUNTs whole tables), Data reads it once and
|
||||
* coalesces a burst of activity instead of re-reading per event, a purge takes
|
||||
* a whole number of days and runs only after a dry-run count is confirmed, and
|
||||
@@ -38,9 +39,21 @@ vi.mock("../../lib/api", async (importOriginal) => {
|
||||
...actual,
|
||||
api: {
|
||||
pricing: { totalCost: vi.fn(async () => ({ total_cost: 1 })) },
|
||||
updates: {
|
||||
status: vi.fn(async () => ({
|
||||
git_repo: false,
|
||||
update_available: true,
|
||||
install_kind: "plugin",
|
||||
repo_root: "/home/u/.claude/plugins/cache/agenthub/9.9.8",
|
||||
local_version: "9.9.8",
|
||||
latest_release: "v9.9.9",
|
||||
})),
|
||||
},
|
||||
settings: {
|
||||
info: (...a: unknown[]) => info(...(a as [])),
|
||||
cleanup: (p: { dry_run?: boolean }) => cleanup(p),
|
||||
autostart: { get: vi.fn(async () => ({ available: false })) },
|
||||
tray: { get: vi.fn(async () => ({ available: false })) },
|
||||
clearData: () => clearData(),
|
||||
gc: vi.fn(async () => ({ reaped: [], capped: [] })),
|
||||
exportData: () => "/api/settings/export",
|
||||
@@ -81,6 +94,47 @@ describe("Settings", () => {
|
||||
expect(info).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows the server's runtime, install and database facts on System", async () => {
|
||||
info.mockImplementationOnce(
|
||||
async () =>
|
||||
({
|
||||
db: {
|
||||
path: "/data/dashboard.db",
|
||||
size: 5 * 1024 * 1024,
|
||||
counts: {},
|
||||
pragmas: { journal_mode: "wal" },
|
||||
load_stats: { m5: 3, m15: 9, h1: 40 },
|
||||
},
|
||||
hooks: { installed: true, path: "", hooks: {} },
|
||||
server: {
|
||||
version: "1",
|
||||
uptime: 3700,
|
||||
node_version: "v26.0.0",
|
||||
platform: "linux",
|
||||
arch: "x64",
|
||||
cpus: 8,
|
||||
cpu_load: [0.5, 0.25, 0.125],
|
||||
total_mem: 16 * 1024 ** 3,
|
||||
free_mem: 4 * 1024 ** 3,
|
||||
memory: { rss: 200 * 1024 ** 2, heapUsed: 50 * 1024 ** 2, heapLimit: 4 * 1024 ** 3 },
|
||||
ws_connections: 2,
|
||||
},
|
||||
}) as never
|
||||
);
|
||||
render(<Settings section="about" />);
|
||||
expect(await screen.findByText("/data/dashboard.db")).toBeInTheDocument();
|
||||
expect(screen.getByText("1h 1m")).toBeInTheDocument();
|
||||
expect(screen.getByText(/linux · x64 · Node.js v26.0.0/)).toBeInTheDocument();
|
||||
expect(screen.getByText("8 cores · load 0.50 / 0.25 / 0.13")).toBeInTheDocument();
|
||||
expect(screen.getByText("200.0 MB RSS · heap 50.0 MB of 4.0 GB")).toBeInTheDocument();
|
||||
expect(screen.getByText("12.0 GB used of 16.0 GB")).toBeInTheDocument();
|
||||
expect(screen.getByText("wal")).toBeInTheDocument();
|
||||
expect(screen.getByText("3 in 5 min · 9 in 15 min · 40 in 1 h")).toBeInTheDocument();
|
||||
expect(await screen.findByText("Claude Code plugin")).toBeInTheDocument();
|
||||
expect(screen.getByText("v9.9.9")).toBeInTheDocument();
|
||||
expect(screen.getByText("Update available")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reads info once on Data and coalesces a burst of activity", async () => {
|
||||
render(<Settings section="data" />);
|
||||
await screen.findByText("/tmp/x.db");
|
||||
|
||||
Reference in New Issue
Block a user