@songsid/agend 2.1.0-beta.17 → 2.1.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -121,6 +121,7 @@ graph LR
121
121
  | OpenCode | `curl -fsSL https://opencode.ai/install \| bash` | `opencode` (configure provider) |
122
122
  | Kiro CLI | `curl -fsSL https://cli.kiro.dev/install | bash` | `kiro-cli login` (AWS Builder ID) |
123
123
  | Antigravity CLI | `curl -fsSL https://antigravity.google/cli/install.sh \| bash` | `agy` (Google Sign-In) |
124
+ | Grok Build | `curl -fsSL https://x.ai/cli/install.sh \| bash` | `grok` (x.ai OAuth device flow) ⚠️ Experimental |
124
125
 
125
126
  ## Requirements
126
127
 
@@ -4,6 +4,7 @@ import { CodexBackend } from "./codex.js";
4
4
  import { OpenCodeBackend } from "./opencode.js";
5
5
  import { KiroBackend } from "./kiro.js";
6
6
  import { AntigravityBackend } from "./antigravity.js";
7
+ import { GrokBackend } from "./grok.js";
7
8
  import { MockBackend } from "./mock.js";
8
9
  export function createBackend(name, instanceDir) {
9
10
  switch (name) {
@@ -20,10 +21,13 @@ export function createBackend(name, instanceDir) {
20
21
  return new KiroBackend(instanceDir);
21
22
  case "antigravity":
22
23
  return new AntigravityBackend(instanceDir);
24
+ case "grok":
25
+ console.warn("⚠️ grok backend is experimental — some patterns may need tuning.");
26
+ return new GrokBackend(instanceDir);
23
27
  case "mock":
24
28
  return new MockBackend(instanceDir);
25
29
  default:
26
- throw new Error(`Unknown backend: ${name}. Available: claude-code, gemini-cli, codex, opencode, kiro-cli, antigravity, mock`);
30
+ throw new Error(`Unknown backend: ${name}. Available: claude-code, gemini-cli, codex, opencode, kiro-cli, antigravity, grok, mock`);
27
31
  }
28
32
  }
29
33
  //# sourceMappingURL=factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backend/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,WAAmB;IAC7D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,OAAO,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC5C,KAAK,YAAY;YACf,OAAO,CAAC,IAAI,CAAC,6FAA6F,CAAC,CAAC;YAC5G,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3C,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC;QACvC,KAAK,UAAU;YACb,OAAO,IAAI,eAAe,CAAC,WAAW,CAAC,CAAC;QAC1C,KAAK,UAAU;YACb,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;QACtC,KAAK,aAAa;YAChB,OAAO,IAAI,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAC7C,KAAK,MAAM;YACT,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;QACtC;YACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,oFAAoF,CAAC,CAAC;IAClI,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backend/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,WAAmB;IAC7D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa;YAChB,OAAO,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC5C,KAAK,YAAY;YACf,OAAO,CAAC,IAAI,CAAC,6FAA6F,CAAC,CAAC;YAC5G,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC3C,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC;QACvC,KAAK,UAAU;YACb,OAAO,IAAI,eAAe,CAAC,WAAW,CAAC,CAAC;QAC1C,KAAK,UAAU;YACb,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;QACtC,KAAK,aAAa;YAChB,OAAO,IAAI,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAC7C,KAAK,MAAM;YACT,OAAO,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;YACjF,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;QACtC,KAAK,MAAM;YACT,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;QACtC;YACE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,0FAA0F,CAAC,CAAC;IACxI,CAAC;AACH,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { type CliBackend, type CliBackendConfig, type ErrorPattern, type RuntimeDialog, type StartupDialog } from "./types.js";
2
+ /**
3
+ * Grok Build (xAI) — https://docs.x.ai/build/cli
4
+ *
5
+ * IMPORTANT — why the interactive TUI, not `grok agent stdio`:
6
+ * Grok Build exposes three surfaces: the interactive TUI (`grok`), headless
7
+ * (`grok -p "<prompt>"`), and an ACP agent (`grok agent stdio`) that speaks
8
+ * JSON-RPC over stdin/stdout. AgEnD drives a backend through a tmux PTY — it
9
+ * scans human-readable pane text for ready/error patterns and injects
10
+ * keystrokes (cancel key, slash commands). An ACP JSON-RPC agent has no
11
+ * human-readable prompt, ready banner, or slash commands, so the CliBackend
12
+ * interface does not map onto it. The interactive TUI is the correct surface,
13
+ * the same choice made for codex/opencode/kiro.
14
+ *
15
+ * Phase 2: ready pattern, cancel/quit keys, /compact, context format, and the
16
+ * device-flow login dialog were confirmed against a live grok session. Error
17
+ * strings beyond the cancellation notice are still best-effort and may need
18
+ * tuning — the backend is registered as experimental (see factory.ts).
19
+ */
20
+ export declare class GrokBackend implements CliBackend {
21
+ private instanceDir;
22
+ readonly binaryName = "grok";
23
+ private binaryPath;
24
+ private workingDirectory?;
25
+ constructor(instanceDir: string);
26
+ buildCommand(config: CliBackendConfig): string;
27
+ /** The daemon's persisted session id (written from getSessionId()), for --resume. */
28
+ private storedSessionId;
29
+ writeConfig(config: CliBackendConfig): void;
30
+ getReadyPattern(): RegExp;
31
+ getErrorPatterns(): ErrorPattern[];
32
+ getStartupDialogs(): StartupDialog[];
33
+ getContextUsage(): number | null;
34
+ getSessionId(): string | null;
35
+ getRuntimeDialogs(): RuntimeDialog[];
36
+ getQuitCommand(): string | null;
37
+ getQuitKey(): string;
38
+ getCompactCommand(): string;
39
+ getCancelKey(): string;
40
+ cleanup(config: CliBackendConfig): void;
41
+ }
@@ -0,0 +1,243 @@
1
+ import { join } from "node:path";
2
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { isModelCompatible, resolveBinary, validateModel } from "./types.js";
5
+ import { appendWithMarker, removeMarker } from "./marker-utils.js";
6
+ /** Session ids are UUIDs (e.g. "019f82d4-…"); guard before shell interpolation. */
7
+ const SESSION_ID_RE = /^[A-Za-z0-9-]+$/;
8
+ /**
9
+ * Grok Build (xAI) — https://docs.x.ai/build/cli
10
+ *
11
+ * IMPORTANT — why the interactive TUI, not `grok agent stdio`:
12
+ * Grok Build exposes three surfaces: the interactive TUI (`grok`), headless
13
+ * (`grok -p "<prompt>"`), and an ACP agent (`grok agent stdio`) that speaks
14
+ * JSON-RPC over stdin/stdout. AgEnD drives a backend through a tmux PTY — it
15
+ * scans human-readable pane text for ready/error patterns and injects
16
+ * keystrokes (cancel key, slash commands). An ACP JSON-RPC agent has no
17
+ * human-readable prompt, ready banner, or slash commands, so the CliBackend
18
+ * interface does not map onto it. The interactive TUI is the correct surface,
19
+ * the same choice made for codex/opencode/kiro.
20
+ *
21
+ * Phase 2: ready pattern, cancel/quit keys, /compact, context format, and the
22
+ * device-flow login dialog were confirmed against a live grok session. Error
23
+ * strings beyond the cancellation notice are still best-effort and may need
24
+ * tuning — the backend is registered as experimental (see factory.ts).
25
+ */
26
+ export class GrokBackend {
27
+ instanceDir;
28
+ binaryName = "grok";
29
+ binaryPath;
30
+ // Cached from buildCommand/writeConfig so getSessionId() (which takes no args)
31
+ // can scope to grok's per-cwd session directory.
32
+ workingDirectory;
33
+ constructor(instanceDir) {
34
+ this.instanceDir = instanceDir;
35
+ this.binaryPath = resolveBinary("grok");
36
+ }
37
+ buildCommand(config) {
38
+ this.workingDirectory = config.workingDirectory;
39
+ // Flags: -m/--model, --always-approve, -r/--resume <ID>, -p/--single, --no-auto-update.
40
+ // --no-auto-update: an auto-update prompt on launch would corrupt ready detection.
41
+ let cmd = `${this.binaryPath} --no-auto-update`;
42
+ // --always-approve auto-approves tool executions (documented flag). If a grok
43
+ // build ignores it, getRuntimeDialogs() auto-approves the prompt as a net.
44
+ if (config.skipPermissions !== false)
45
+ cmd += " --always-approve";
46
+ // Resume by explicit session id (verified: grok prints
47
+ // "Resume this session with: grok --resume <uuid>" on exit — there is no
48
+ // bare --continue that reliably resumes). The id is persisted by the daemon
49
+ // from getSessionId(); skip on crash recovery (skipResume) for a clean start.
50
+ if (!config.skipResume) {
51
+ const sid = this.storedSessionId();
52
+ if (sid)
53
+ cmd += ` --resume ${sid}`;
54
+ }
55
+ if (config.model) {
56
+ if (isModelCompatible("grok", config.model)) {
57
+ cmd += ` --model ${validateModel(config.model)}`;
58
+ }
59
+ else {
60
+ console.warn(`[agend] model "${config.model}" is not compatible with grok — skipping --model, using the CLI's default`);
61
+ }
62
+ }
63
+ return cmd;
64
+ }
65
+ /** The daemon's persisted session id (written from getSessionId()), for --resume. */
66
+ storedSessionId() {
67
+ try {
68
+ const sid = readFileSync(join(this.instanceDir, "session-id"), "utf-8").trim();
69
+ return SESSION_ID_RE.test(sid) ? sid : null;
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ writeConfig(config) {
76
+ this.workingDirectory = config.workingDirectory;
77
+ // Grok Build merges MCP servers from a project-level .mcp.json (loaded below
78
+ // ~/.grok/config.toml in priority). Using the project file means we never
79
+ // clobber the user's global config. Standard mcpServers format: { command, args, env }.
80
+ const mcpPath = join(config.workingDirectory, ".mcp.json");
81
+ let root = {};
82
+ try {
83
+ root = JSON.parse(readFileSync(mcpPath, "utf-8"));
84
+ }
85
+ catch { /* new file */ }
86
+ const servers = (root.mcpServers ?? {});
87
+ // Drop stale agend entries whose command binary no longer exists.
88
+ for (const [key, val] of Object.entries(servers)) {
89
+ if (key.startsWith("agend-")) {
90
+ const cmd = val?.command;
91
+ if (typeof cmd === "string" && !existsSync(cmd))
92
+ delete servers[key];
93
+ }
94
+ }
95
+ // Namespace each server by instance so multiple instances can share a working dir.
96
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
97
+ const instanceKey = `${name}-${config.instanceName}`;
98
+ servers[instanceKey] = {
99
+ command: entry.command,
100
+ args: entry.args,
101
+ env: { ...entry.env, AGEND_INSTANCE_NAME: config.instanceName },
102
+ };
103
+ }
104
+ // Clean up any legacy non-namespaced key.
105
+ delete servers["agend"];
106
+ root.mcpServers = servers;
107
+ writeFileSync(mcpPath, JSON.stringify(root, null, 2));
108
+ // Fleet instructions → AGENTS.md marker block (Grok reads AGENTS.md project docs,
109
+ // same convention as Codex). Additive + idempotent via the AGEND marker.
110
+ if (config.instructions) {
111
+ try {
112
+ appendWithMarker(join(config.workingDirectory, "AGENTS.md"), config.instanceName, config.instructions);
113
+ }
114
+ catch { /* best effort */ }
115
+ }
116
+ }
117
+ getReadyPattern() {
118
+ // Verified: `❯` is the idle input prompt inside the TUI box; the Grok Build
119
+ // header ("Grok Build" / "Grok <n>") also identifies the ready screen.
120
+ return /❯|Grok \d|Grok Build/m;
121
+ }
122
+ getErrorPatterns() {
123
+ // NOTE: "Turn cancelled by user" is NORMAL behaviour (user interrupt), not an
124
+ // error — none of the patterns below match it, and none should be added that do.
125
+ // Error strings below are best-effort generic API shapes; tune once observed live.
126
+ return [
127
+ { pattern: /rate.?limit|too many requests|\b429\b/i, type: "rate_limit", action: "failover", message: "Grok rate limit reached" },
128
+ { pattern: /unauthorized|authentication (failed|error)|\b401\b/i, type: "auth_error", action: "pause", message: "Grok authentication error" },
129
+ { pattern: /quota|insufficient credits|out of credits/i, type: "quota", action: "notify", message: "Grok quota/credits exhausted" },
130
+ ];
131
+ }
132
+ getStartupDialogs() {
133
+ return [
134
+ // Device-flow login is BLOCKING and cannot be auto-dismissed — the user must
135
+ // approve externally. Empty keys => the daemon sends nothing but treats the
136
+ // screen as "not ready yet" and keeps polling, so the login screen is never
137
+ // mistaken for the idle prompt. (The "[Click here to Upgrade]" banner is
138
+ // non-blocking and is intentionally NOT listed — nothing to dismiss.)
139
+ { pattern: /Waiting for approval|Log in to continue|device.*approval/i, keys: [], description: "Grok device-flow login — wait for user authorization (no auto-dismiss)" },
140
+ ];
141
+ }
142
+ getContextUsage() {
143
+ // Grok shows context as "12K / 500K" (used / total) in the TUI, not in a file.
144
+ // getContextUsage() has no pane access, so parsing lives in the pane scanners
145
+ // (parseContextPercent in topic-commands.ts + defaultParser in cli.ts), which
146
+ // is what /ctx and `agend ls` use. Nothing file-based to report here.
147
+ return null;
148
+ }
149
+ getSessionId() {
150
+ // grok stores sessions per working directory (verified):
151
+ // ~/.grok/sessions/<encodeURIComponent(cwd)>/<session-uuid>/{events.jsonl,…}
152
+ // Scoping by cwd means multiple concurrent grok instances (distinct cwds)
153
+ // never pick up each other's session. Return the UUID of the most-recently
154
+ // ACTIVE session in this instance's cwd for the daemon to persist (--resume).
155
+ if (!this.workingDirectory)
156
+ return null;
157
+ try {
158
+ const base = join(homedir(), ".grok", "sessions", encodeURIComponent(this.workingDirectory));
159
+ let newestId = null;
160
+ let newestMtime = -1;
161
+ for (const name of readdirSync(base)) {
162
+ // Session dirs are named by UUID; skip prompt_history.jsonl etc.
163
+ if (!SESSION_ID_RE.test(name) || name.length < 8)
164
+ continue;
165
+ const sessionDir = join(base, name);
166
+ let st;
167
+ try {
168
+ st = statSync(sessionDir);
169
+ }
170
+ catch {
171
+ continue;
172
+ }
173
+ if (!st.isDirectory())
174
+ continue;
175
+ // Activity = latest INNER-file mtime. The dir's own mtime doesn't move on
176
+ // append AND is misleadingly recent for a just-created dir, so it must NOT
177
+ // outweigh a resumed older session whose logs were just appended. Only fall
178
+ // back to dir mtime when the session has no inner files yet.
179
+ let activity = -1;
180
+ try {
181
+ for (const f of readdirSync(sessionDir)) {
182
+ try {
183
+ const m = statSync(join(sessionDir, f)).mtimeMs;
184
+ if (m > activity)
185
+ activity = m;
186
+ }
187
+ catch { /* skip */ }
188
+ }
189
+ }
190
+ catch { /* unreadable dir */ }
191
+ if (activity < 0)
192
+ activity = st.mtimeMs;
193
+ if (activity > newestMtime) {
194
+ newestMtime = activity;
195
+ newestId = name;
196
+ }
197
+ }
198
+ return newestId;
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ }
204
+ getRuntimeDialogs() {
205
+ return [
206
+ // Mid-task tool-approval prompt ("1. Yes, always 2. Yes 3. No"). Select
207
+ // option 1 so the fleet runs unattended. Primary mechanism is
208
+ // --always-approve at launch; this is the net if that flag is absent/ignored.
209
+ // ⚠️ key needs live confirmation — assumes a numeric hotkey (types "1"+Enter);
210
+ // if grok uses an arrow-select cursor instead, switch to ["Enter"] or nav keys.
211
+ { pattern: /1\.\s*Yes,?\s*always|Yes,?\s*always[\s\S]{0,40}\bNo\b/i, keys: ["1"], description: "Grok tool-approval prompt — select 'Yes, always'" },
212
+ ];
213
+ }
214
+ // grok has no slash quit command — it quits via the Ctrl+Q key chord (getQuitKey).
215
+ getQuitCommand() { return null; }
216
+ getQuitKey() { return "C-q"; }
217
+ getCompactCommand() { return "/compact"; } // verified present
218
+ // Verified: grok interrupts generation on Ctrl+C.
219
+ getCancelKey() { return "C-c"; }
220
+ cleanup(config) {
221
+ // Remove only this instance's namespaced MCP entries — a non-namespaced key
222
+ // may belong to another instance sharing the working directory.
223
+ try {
224
+ const mcpPath = join(config.workingDirectory, ".mcp.json");
225
+ if (existsSync(mcpPath)) {
226
+ const root = JSON.parse(readFileSync(mcpPath, "utf-8"));
227
+ if (root.mcpServers) {
228
+ for (const name of Object.keys(config.mcpServers)) {
229
+ delete root.mcpServers[`${name}-${config.instanceName}`];
230
+ }
231
+ writeFileSync(mcpPath, JSON.stringify(root, null, 2));
232
+ }
233
+ }
234
+ }
235
+ catch { /* best effort */ }
236
+ // Remove fleet instructions marker block from AGENTS.md.
237
+ try {
238
+ removeMarker(join(config.workingDirectory, "AGENTS.md"), config.instanceName);
239
+ }
240
+ catch { /* best effort */ }
241
+ }
242
+ }
243
+ //# sourceMappingURL=grok.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok.js","sourceRoot":"","sources":["../../src/backend/grok.ts"],"names":[],"mappings":"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"}
@@ -92,8 +92,18 @@ export interface CliBackend {
92
92
  preTrust?(workingDirectory: string): void;
93
93
  /** Resolve working directory (e.g. create symlink to avoid hidden paths). Returns resolved path. */
94
94
  resolveWorkingDirectory?(workingDirectory: string, instanceName?: string): string;
95
- /** Command to gracefully quit the CLI (e.g. "/exit", "/quit"). */
96
- getQuitCommand(): string;
95
+ /**
96
+ * Slash/text command to gracefully quit the CLI (e.g. "/exit", "/quit"),
97
+ * typed then followed by Enter. Return null when the CLI has no quit command
98
+ * and quits via a key chord instead — implement getQuitKey() in that case.
99
+ */
100
+ getQuitCommand(): string | null;
101
+ /**
102
+ * The tmux key chord that quits the CLI, for backends whose quit is a keypress
103
+ * rather than a typed command (e.g. grok Ctrl+Q). Consulted only when
104
+ * getQuitCommand() returns null. Values are tmux send-keys names (e.g. "C-q").
105
+ */
106
+ getQuitKey?(): string;
97
107
  /**
98
108
  * In-session command to compact/reset the conversation context. Most CLIs use
99
109
  * "/compact"; some (e.g. Codex) use "/compact" too, OpenCode uses "/compact".
@@ -34,6 +34,7 @@ const BACKEND_MODEL_PATTERNS = {
34
34
  "gemini-cli": /^gemini/i,
35
35
  "opencode": /./, // opencode accepts anything (provider-dependent)
36
36
  "antigravity": / /, // agy models always contain spaces (display names like "Gemini 3.5 Flash (High)")
37
+ "grok": /^grok/i, // grok-4.5, grok-4.3, grok-code, grok-build-0.1
37
38
  };
38
39
  /** Check if a model name is compatible with the given backend. */
39
40
  export function isModelCompatible(backendName, model) {
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AA0IlD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAiB,aAAa,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,0GAA0G;AAC1G,MAAM,sBAAsB,GAA2B;IACrD,aAAa,EAAE,4CAA4C;IAC3D,UAAU,EAAE,8BAA8B;IAC1C,OAAO,EAAE,wBAAwB;IACjC,YAAY,EAAE,UAAU;IACxB,UAAU,EAAE,GAAG,EAAG,iDAAiD;IACnE,aAAa,EAAE,GAAG,EAAG,kFAAkF;CACxG,CAAC;AAEF,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,WAAmB,EAAE,KAAa;IAClE,MAAM,OAAO,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,CAAC,iCAAiC;IAC5D,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,KAAa;IACpE,IAAI,CAAC,iBAAiB,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,kBAAkB,KAAK,uCAAuC,WAAW,0BAA0B,CAAC,CAAC;IACpH,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAqJlD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAC5C,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAiB,aAAa,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,0GAA0G;AAC1G,MAAM,sBAAsB,GAA2B;IACrD,aAAa,EAAE,4CAA4C;IAC3D,UAAU,EAAE,8BAA8B;IAC1C,OAAO,EAAE,wBAAwB;IACjC,YAAY,EAAE,UAAU;IACxB,UAAU,EAAE,GAAG,EAAG,iDAAiD;IACnE,aAAa,EAAE,GAAG,EAAG,kFAAkF;IACvG,MAAM,EAAE,QAAQ,EAAG,gDAAgD;CACpE,CAAC;AAEF,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,WAAmB,EAAE,KAAa;IAClE,MAAM,OAAO,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,CAAC,iCAAiC;IAC5D,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,KAAa;IACpE,IAAI,CAAC,iBAAiB,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,kBAAkB,KAAK,uCAAuC,WAAW,0BAA0B,CAAC,CAAC;IACpH,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}
package/dist/cli.js CHANGED
@@ -1938,6 +1938,7 @@ function formatTimeSince(isoStr) {
1938
1938
  * kiro classic "8% !>" / TUI "◔ 1%" / bracket "[8%]" / prompt "8% ❯"
1939
1939
  * codex "Context N% left" (remaining → 100-N) or "Context N% used"
1940
1940
  * opencode "1.2K (6%)"
1941
+ * grok "12K / 500K" (used tokens / context window)
1941
1942
  * Duplicated (not imported) so this CLI entry point stays free of the daemon's
1942
1943
  * dependency graph. All returned values are context USED (low % = fresh).
1943
1944
  */
@@ -1948,6 +1949,18 @@ const defaultParser = (output) => {
1948
1949
  const left = line.match(/Context\s+(\d+)%\s+left/i);
1949
1950
  if (left)
1950
1951
  return 100 - parseInt(left[1], 10);
1952
+ const ratio = line.match(/(\d+\.?\d*[KkMm]?)\s*\/\s*(\d+\.?\d*[KkMm]?)/);
1953
+ if (ratio) {
1954
+ const tokenCount = (value) => {
1955
+ const suffix = value.at(-1)?.toLowerCase();
1956
+ const multiplier = suffix === "k" ? 1_000 : suffix === "m" ? 1_000_000 : 1;
1957
+ return parseFloat(value) * multiplier;
1958
+ };
1959
+ const used = tokenCount(ratio[1]);
1960
+ const total = tokenCount(ratio[2]);
1961
+ if (Number.isFinite(used) && Number.isFinite(total) && total > 0)
1962
+ return used / total * 100;
1963
+ }
1951
1964
  const m = line.match(/(\d+)%.*[!❯>]/)
1952
1965
  || line.match(/◔\s*(\d+)%/)
1953
1966
  || line.match(/\[(\d+)%\]/)