codemax-cli 0.1.4 → 0.1.5

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
@@ -5,7 +5,7 @@
5
5
  <p align="center"><b>Every AI coding plan you pay for. One terminal.</b></p>
6
6
 
7
7
  <p align="center">
8
- Claude Code · Codex · OpenCode · Grok · Cursor · Gemini CLI
8
+ Claude Code · Codex · OpenCode · Grok · Cursor · Antigravity CLI
9
9
  </p>
10
10
 
11
11
  ---
@@ -79,21 +79,21 @@ cd codemax-cli && bun install && bun link
79
79
  | OpenCode | Your OpenCode login | Supported |
80
80
  | Grok | SuperGrok | Supported |
81
81
  | Cursor Agent | Cursor plans | Supported (needs Cursor Agent 2026.10 or newer). Changing its model with `/model` also changes Cursor's own default. |
82
- | Gemini CLI | Google AI plans | Early, untested |
82
+ | Antigravity CLI | Google AI plans | Supported. It has no ACP mode of its own, so codemax offers to download Google's official ACP server for it (about 110 MB, once, from dl.google.com) the first time you switch to it, and signs in with your Google account (a browser may open once). It reports context size but not token counts, so `/usage` shows input only. |
83
83
  | Copilot CLI | GitHub Copilot plans | In its own UI via `codemax --tui` |
84
84
 
85
85
  ## Everyday keys
86
86
 
87
87
  | | |
88
88
  |---|---|
89
+ | **↑** on an empty prompt | Edit a queued message (↑ again for the one before); Enter puts it back, clear it and Enter to remove it, Esc leaves it as it was |
89
90
  | `/switch` or **Ctrl+^** | Move to another agent, conversation included |
90
91
  | `/ask codex <msg>` | One question to another agent |
91
92
  | `@file` | Attach a file to your message |
92
93
  | `/task` | The shared task: plan, who did what, where it stopped, files changed |
93
94
  | `/update` | Update codemax when a new version is out (also `codemax update`) |
94
95
  | `/config` | Every setting in one panel: theme, keep awake, side agent and its model, handoffs, snapshots… |
95
- | `/side` | Pick a side agent and its model; it reviews each turn and leaves a note when something needs attention (`/side codex <model> auto` forwards notes itself) |
96
- | `/forward`, `/notes`, `/review` | Send the side agent's note to the main agent; list notes; ask for a review now. How often it reviews is in `/config`; each review uses the side agent's plan, so a different subscription than the main agent is best |
96
+ | Side agent | Turn it on in `/config` and pick its agent and model. It reviews the work in the background and speaks up only when something needs attention: its note waits above the prompt, where **1** expands it, **2** sends it to the main agent to discuss, **0** dismisses it. It never uses the main agent's plan unless you allow that in `/config` |
97
97
  | `/awake on`, `/awake lid` | Keep the machine from sleeping while agents work; `lid` also with the lid closed |
98
98
  | `/undo` | Revert the last turn's changes |
99
99
  | `/usage`, `/budget` | Usage and spend per agent |
@@ -111,7 +111,7 @@ cd codemax-cli && bun install && bun link
111
111
 
112
112
  | Key | Default | What it does |
113
113
  |---|---|---|
114
- | `order` | `["claude", "codex", "opencode", "cursor", "grok", "gemini", "copilot"]` | Which agent comes next on a handoff |
114
+ | `order` | `["claude", "codex", "opencode", "cursor", "grok", "antigravity", "copilot"]` | Which agent comes next on a handoff |
115
115
  | `budgets` | `{}` | Daily limit per agent, e.g. `{"claude": 20, "all": 50}` |
116
116
  | `routes` | `[]` | Send matching messages to a specific agent |
117
117
  | `checkpoints` | `true` | Snapshots for `/undo` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemax-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "One terminal for every coding agent you pay for: drive Claude Code, Codex, OpenCode, Grok and Gemini CLI from one chat, on your subscriptions, with context that survives switching agents.",
5
5
  "keywords": [
6
6
  "acp",
@@ -1,16 +1,45 @@
1
1
  import type { DetectResult } from "./types.ts";
2
2
  import { platformArgv } from "../core/spawn.ts";
3
3
 
4
- export async function detectBin(bin: string, versionArgs: string[] = ["--version"]): Promise<DetectResult> {
5
- const path = Bun.which(bin);
4
+ export interface DetectDeps {
5
+ which: (bin: string) => string | null;
6
+ run: (argv: string[], timeoutMs: number) => Promise<{ code: number | null; out: string; timedOut?: boolean }>;
7
+ }
8
+
9
+ const defaultDeps: DetectDeps = {
10
+ which: (b) => Bun.which(b),
11
+ run: async (argv, timeoutMs) => {
12
+ const p = Bun.spawn(platformArgv(argv), { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
13
+ let timedOut = false;
14
+ const timer = setTimeout(() => {
15
+ timedOut = true;
16
+ p.kill();
17
+ }, timeoutMs);
18
+ const [out, err] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
19
+ const code = await p.exited;
20
+ clearTimeout(timer);
21
+ return { code, out: `${out}\n${err}`.trim(), timedOut };
22
+ },
23
+ };
24
+
25
+ /**
26
+ * An agent counts as installed only when its CLI actually runs: `<bin> --version`
27
+ * must exit 0. A file on PATH alone is not enough; on Windows an uninstalled npm
28
+ * CLI can leave shims behind that no longer start, and those must not be offered.
29
+ */
30
+ export async function detectBin(bin: string, versionArgs: string[] = ["--version"], deps: DetectDeps = defaultDeps, timeoutMs = 15000): Promise<DetectResult> {
31
+ const path = deps.which(bin);
6
32
  if (!path) return { installed: false };
7
33
  try {
8
- const p = Bun.spawn(platformArgv([path, ...versionArgs]), { stdout: "pipe", stderr: "pipe" });
9
- const text = (await new Response(p.stdout).text()).trim();
10
- await p.exited;
11
- const m = text.match(/\d+\.\d+(\.\d+)?/);
12
- return { installed: true, path, version: m?.[0] ?? text.split("\n")[0] };
13
- } catch {
14
- return { installed: true, path };
34
+ const r = await deps.run([path, ...versionArgs], timeoutMs);
35
+ const lines = r.out.split("\n").map((l) => l.trim()).filter(Boolean);
36
+ // Node prints a stack location first; the line naming the error is the useful one.
37
+ const first = lines.find((l) => /error|not found|cannot|denied/i.test(l)) ?? lines[0] ?? "";
38
+ if (r.timedOut) return { installed: false, path, problem: `${bin} was found at ${path} but did not answer ${versionArgs.join(" ")} within ${Math.round(timeoutMs / 1000)}s` };
39
+ if (r.code !== 0) return { installed: false, path, problem: `${bin} was found at ${path} but does not run${first ? `: ${first.slice(0, 160)}` : ` (exit ${r.code})`}` };
40
+ const m = r.out.match(/\d+\.\d+(\.\d+)?/);
41
+ return { installed: true, path, version: m?.[0] ?? first };
42
+ } catch (e: any) {
43
+ return { installed: false, path, problem: `${bin} was found at ${path} but could not be started: ${String(e?.message ?? e).slice(0, 160)}` };
15
44
  }
16
45
  }
@@ -1,9 +1,9 @@
1
1
  import type { Adapter, AgentId } from "./types.ts";
2
2
  import { claude } from "./claude.ts";
3
3
  import { codex } from "./codex.ts";
4
- import { opencode, cursor, grok, gemini, copilot, fake } from "./simple.ts";
4
+ import { opencode, cursor, grok, antigravity, copilot, fake } from "./simple.ts";
5
5
 
6
- const real: Adapter[] = [claude, codex, opencode, cursor, grok, gemini, copilot];
6
+ const real: Adapter[] = [claude, codex, opencode, cursor, grok, antigravity, copilot];
7
7
 
8
8
  /**
9
9
  * The adapters, read at call time: tests set CODEMAX_FAKE_AGENT after another
@@ -63,12 +63,13 @@ export const grok = simple({
63
63
  apiKeyEnv: ["XAI_API_KEY", "GROK_API_KEY"],
64
64
  });
65
65
 
66
- export const gemini = simple({
67
- id: "gemini",
68
- name: "Gemini CLI",
69
- bin: "gemini",
70
- promptPositional: false,
71
- patterns: [/quota exceeded/i, /RESOURCE_EXHAUSTED/],
66
+ /** Google's coding agent for Google AI plans (Antigravity CLI replaced Gemini CLI). */
67
+ export const antigravity = simple({
68
+ id: "antigravity",
69
+ name: "Antigravity CLI",
70
+ bin: "agy",
71
+ resumeArgs: (id) => ["--conversation", id],
72
+ patterns: [/quota exceeded/i, /RESOURCE_EXHAUSTED/, /rate limit/i, /usage limit/i],
72
73
  apiKeyEnv: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
73
74
  });
74
75
 
@@ -1,4 +1,4 @@
1
- export type AgentId = "claude" | "codex" | "opencode" | "cursor" | "grok" | "gemini" | "copilot" | "fake";
1
+ export type AgentId = "claude" | "codex" | "opencode" | "cursor" | "grok" | "antigravity" | "copilot" | "fake";
2
2
 
3
3
  export interface SessionRef {
4
4
  agent: AgentId;
@@ -60,6 +60,8 @@ export interface DetectResult {
60
60
  installed: boolean;
61
61
  path?: string;
62
62
  version?: string;
63
+ /** Why a CLI found on PATH does not count as installed (it failed to run). */
64
+ problem?: string;
63
65
  }
64
66
 
65
67
  export interface Adapter {