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
+8 -6
View File
@@ -247,7 +247,7 @@ export function analyseHunk(hunk: LaneDiffHunk): Analysed {
if (hit) return hit;
const lines = hunk.lines;
const emph = new Map<number, [number, number]>();
for (let i = 0; i < lines.length; ) {
for (let i = 0; i < lines.length;) {
if (lines[i]!.type === "ctx") {
i += 1;
continue;
@@ -440,7 +440,8 @@ const startAtRow = (hunk: LaneDiffHunk, i: number) =>
* block's last line. A context rather than a prop because lines render from
* four places; `changes` is what the bar's "n of N" and its arrows walk. */
const HunkActions = createContext<{
actions: (change: LaneDiffChange) => ReactNode;
/** null = no bar; the anchors still mark each block for navigation. */
actions: ((change: LaneDiffChange) => ReactNode) | null;
changes: LaneDiffChange[];
go: (index: number) => void;
} | null>(null);
@@ -457,7 +458,7 @@ function useAnchor(change?: LaneDiffChange) {
function HunkBar({ change }: { change: LaneDiffChange }) {
const { t } = useTranslation(["lanes"]);
const ctx = useContext(HunkActions);
if (!ctx) return null;
if (!ctx?.actions) return null;
const index = ctx.changes.indexOf(change);
const total = ctx.changes.length;
const nav =
@@ -700,8 +701,7 @@ export function clampScroll(v: number, scrollSize: number, clientSize: number) {
}
type Axis =
| ["scrollLeft", "scrollWidth", "clientWidth"]
| ["scrollTop", "scrollHeight", "clientHeight"];
["scrollLeft", "scrollWidth", "clientWidth"] | ["scrollTop", "scrollHeight", "clientHeight"];
const AXES: Axis[] = [
["scrollLeft", "scrollWidth", "clientWidth"],
["scrollTop", "scrollHeight", "clientHeight"],
@@ -931,7 +931,9 @@ export function LaneDiffView({
const at = items.findIndex((it) => it.hunk === target.hunk && it.i === target.firstLine);
if (at >= 0 && layout === "unified") el.scrollTop = at * ROW_PX;
};
const review = hunkActions && hunks ? { actions: hunkActions, changes, go } : null;
// Provided without actions too: every block's first row carries its
// `data-change-index`, which the panel's F7 / Shift+F7 walk in any range.
const review = hunks ? { actions: hunkActions ?? null, changes, go } : null;
if (file.binary)
return <p className="px-2 py-1 text-[10px] text-fg-muted">{t("changes.binary")}</p>;