Skip to content

Commit 26a1539

Browse files
committed
Init commit
0 parents  commit 26a1539

File tree

22 files changed

+1420
-0
lines changed

22 files changed

+1420
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
dist/
3+
.DS_Store
4+
*.log
5+
bun.lockb
6+
.npm-cache

README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# agent-monitor
2+
3+
Terminal dashboard to monitor Claude Code, Codex, and Gemini CLI usage limits.
4+
5+
## Install
6+
7+
### Via npm (requires Bun)
8+
9+
```bash
10+
npm install -g agent-monitor
11+
```
12+
13+
### Standalone Binary (no dependencies)
14+
15+
Download from [GitHub Releases](https://github.com/AgentWorkforce/monitor/releases):
16+
17+
```bash
18+
# Apple Silicon
19+
curl -L https://github.com/AgentWorkforce/monitor/releases/latest/download/agent-monitor-darwin-arm64 -o /usr/local/bin/agent-monitor
20+
chmod +x /usr/local/bin/agent-monitor
21+
22+
# Intel Mac
23+
curl -L https://github.com/AgentWorkforce/monitor/releases/latest/download/agent-monitor-darwin-x64 -o /usr/local/bin/agent-monitor
24+
chmod +x /usr/local/bin/agent-monitor
25+
```
26+
27+
## Quick Start
28+
29+
```bash
30+
agent-monitor usage
31+
```
32+
33+
## CLI
34+
35+
| Command | Description |
36+
|---------|-------------|
37+
| `agent-monitor usage` | Show usage dashboard |
38+
| `agent-monitor help` | Show help message |
39+
40+
## Dashboard Controls
41+
42+
| Key | Action |
43+
|-----|--------|
44+
| `q` | Quit |
45+
| `r` | Refresh |
46+
47+
## Features
48+
49+
- Real-time usage tracking for Claude Code, Codex, and Gemini CLI
50+
- Trajectory markers showing if you're ahead or behind your usage pace
51+
- Auto-refresh every 60 seconds
52+
- Color-coded usage indicators
53+
54+
## Supported Providers
55+
56+
| Provider | Status | Data Source |
57+
|----------|--------|-------------|
58+
| Claude Code | Full support | macOS Keychain + Anthropic API |
59+
| Codex | Full support | `~/.codex/auth.json` + OpenAI API |
60+
| Gemini CLI | Static limits | `~/.gemini/settings.json` |
61+
62+
## Development
63+
64+
```bash
65+
git clone https://github.com/AgentWorkforce/monitor.git
66+
cd monitor
67+
bun install
68+
```
69+
70+
Run in development mode with hot reload:
71+
72+
```bash
73+
bun run dev
74+
```
75+
76+
Run directly:
77+
78+
```bash
79+
bun run start
80+
```
81+
82+
> **Note:** In dev mode, use `q` to quit cleanly. If you Ctrl-C and see garbled output, run `reset` to restore your terminal.
83+
84+
### Building Standalone Binaries
85+
86+
Build binaries that don't require Bun:
87+
88+
```bash
89+
# Build for all macOS architectures
90+
bun run build
91+
92+
# Build for specific architecture
93+
bun run build:arm64 # Apple Silicon
94+
bun run build:x64 # Intel
95+
```
96+
97+
Binaries are output to `dist/`.
98+
99+
## How It Works
100+
101+
agent-monitor reads credentials from standard locations:
102+
103+
- **Claude Code**: macOS Keychain (`Claude Code-credentials`)
104+
- **Codex**: `~/.codex/auth.json`
105+
- **Gemini**: `~/.gemini/settings.json`
106+
107+
It then fetches usage data from each provider's API and displays it in a unified dashboard.
108+
109+
### Trajectory Indicator
110+
111+
Each progress bar shows a `|` marker indicating where your usage "should be" based on time elapsed in the reset period:
112+
113+
```
114+
[███████░░░░|░░░░░░░░░] 30% ↓12%
115+
^ you should be at 42%, but you're at 30% (12% under pace)
116+
```
117+
118+
- `↓X%` (green) = under pace, you have headroom
119+
- `↑X%` (red) = over pace, might hit limits early
120+
121+
## Requirements
122+
123+
- macOS (uses Keychain for credential storage)
124+
- Active CLI authentication for providers you want to monitor
125+
126+
## License
127+
128+
MIT

bin/cli.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env bun
2+
/** @jsxImportSource @opentui/react */
3+
4+
import { createRoot } from "@opentui/react";
5+
import { App } from "../src/App";
6+
7+
function resetTerminal() {
8+
process.stdout.write("\x1b[?1049l");
9+
process.stdout.write("\x1b[?25h");
10+
process.stdout.write("\x1b[0m");
11+
process.stdout.write("\x1b[?1000l");
12+
process.stdout.write("\x1b[?1002l");
13+
process.stdout.write("\x1b[?1003l");
14+
process.stdout.write("\x1b[?1006l");
15+
process.stdout.write("\x1b[?2004l");
16+
}
17+
18+
const command = process.argv[2];
19+
20+
if (command === "usage") {
21+
let root: ReturnType<typeof createRoot> | null = null;
22+
23+
const cleanup = () => {
24+
if (root) {
25+
root.unmount();
26+
}
27+
resetTerminal();
28+
process.exit(0);
29+
};
30+
31+
process.on("SIGINT", cleanup);
32+
process.on("SIGTERM", cleanup);
33+
process.on("exit", resetTerminal);
34+
35+
root = createRoot(process.stdout, { hideCursor: true });
36+
root.render(<App onExit={cleanup} />);
37+
} else if (command === "help" || command === "--help" || command === "-h" || !command) {
38+
console.log(`
39+
agent-monitor
40+
41+
Monitor AI agent CLI usage limits in real-time.
42+
43+
Install:
44+
npm install -g agent-monitor
45+
46+
Quick Start:
47+
agent-monitor usage
48+
49+
CLI:
50+
agent-monitor usage Show usage dashboard
51+
agent-monitor help Show this help message
52+
53+
Dashboard Controls:
54+
q Quit
55+
r Refresh
56+
`);
57+
} else {
58+
console.error(`Unknown command: ${command}`);
59+
console.error(`Run 'agent-monitor help' for usage.`);
60+
process.exit(1);
61+
}

bun.lock

Lines changed: 232 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"name": "agent-monitor",
3+
"version": "0.1.0",
4+
"description": "Terminal dashboard to monitor Claude Code, Codex, and other agent usage limits",
5+
"type": "module",
6+
"main": "src/index.tsx",
7+
"bin": {
8+
"agent-monitor": "./bin/cli.tsx"
9+
},
10+
"scripts": {
11+
"start": "bun run src/index.tsx",
12+
"dev": "bun --watch run src/index.tsx",
13+
"build": "bun build --compile --target=bun-darwin-arm64 ./bin/cli.tsx --outfile=dist/agent-monitor-darwin-arm64 && bun build --compile --target=bun-darwin-x64 ./bin/cli.tsx --outfile=dist/agent-monitor-darwin-x64",
14+
"build:arm64": "bun build --compile --target=bun-darwin-arm64 ./bin/cli.tsx --outfile=dist/agent-monitor-darwin-arm64",
15+
"build:x64": "bun build --compile --target=bun-darwin-x64 ./bin/cli.tsx --outfile=dist/agent-monitor-darwin-x64"
16+
},
17+
"files": [
18+
"bin",
19+
"src"
20+
],
21+
"keywords": [
22+
"cli",
23+
"terminal",
24+
"dashboard",
25+
"claude",
26+
"codex",
27+
"gemini",
28+
"ai",
29+
"agent",
30+
"usage",
31+
"limits",
32+
"monitor",
33+
"tui"
34+
],
35+
"author": "Will Washburn",
36+
"license": "MIT",
37+
"repository": {
38+
"type": "git",
39+
"url": "git+https://github.com/AgentWorkforce/monitor.git"
40+
},
41+
"bugs": {
42+
"url": "https://github.com/AgentWorkforce/monitor.git/issues"
43+
},
44+
"homepage": "https://github.com/AgentWorkforce/monitor.git",
45+
"engines": {
46+
"bun": ">=1.0.0"
47+
},
48+
"os": [
49+
"darwin"
50+
],
51+
"dependencies": {
52+
"@opentui/core": "^0.1.67",
53+
"@opentui/react": "^0.1.63",
54+
"react": "^19.0.0"
55+
},
56+
"devDependencies": {
57+
"@types/bun": "latest",
58+
"@types/react": "^19.0.0",
59+
"typescript": "^5.0.0"
60+
}
61+
}

src/App.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useEffect, useState, useCallback } from "react";
2+
import { useKeyboard } from "@opentui/react";
3+
import { Header, Footer, Dashboard } from "./components";
4+
import { fetchAllProviders, type ProviderStatus } from "./providers";
5+
6+
const REFRESH_INTERVAL = 60000;
7+
8+
interface AppProps {
9+
onExit?: () => void;
10+
}
11+
12+
export function App({ onExit }: AppProps) {
13+
const [providers, setProviders] = useState<ProviderStatus[]>([
14+
{ provider: "claude", status: "loading", metrics: [] },
15+
{ provider: "codex", status: "loading", metrics: [] },
16+
{ provider: "gemini", status: "loading", metrics: [] },
17+
]);
18+
const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
19+
const [isLoading, setIsLoading] = useState(true);
20+
21+
const refresh = useCallback(async () => {
22+
setIsLoading(true);
23+
try {
24+
const results = await fetchAllProviders();
25+
setProviders(results);
26+
setLastRefresh(new Date());
27+
} catch (err) {
28+
console.error("Failed to fetch providers:", err);
29+
} finally {
30+
setIsLoading(false);
31+
}
32+
}, []);
33+
34+
useEffect(() => {
35+
refresh();
36+
const interval = setInterval(refresh, REFRESH_INTERVAL);
37+
return () => clearInterval(interval);
38+
}, [refresh]);
39+
40+
useKeyboard((key) => {
41+
if (key.name === "q" || (key.ctrl && key.name === "c")) {
42+
onExit?.();
43+
}
44+
45+
if (key.name === "r") {
46+
refresh();
47+
}
48+
});
49+
50+
return (
51+
<box
52+
style={{
53+
width: "100%",
54+
height: "100%",
55+
flexDirection: "column",
56+
backgroundColor: "#0a0a0f",
57+
}}
58+
>
59+
<Header lastRefresh={lastRefresh} isLoading={isLoading} />
60+
<Dashboard providers={providers} />
61+
<Footer />
62+
</box>
63+
);
64+
}

src/components/Dashboard.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ProviderStatus } from "../providers/types";
2+
import { ProviderCard } from "./ProviderCard";
3+
4+
interface DashboardProps {
5+
providers: ProviderStatus[];
6+
}
7+
8+
export function Dashboard({ providers }: DashboardProps) {
9+
return (
10+
<box
11+
style={{
12+
flexGrow: 1,
13+
flexDirection: "row",
14+
gap: 1,
15+
padding: 1,
16+
}}
17+
>
18+
{providers.map((provider) => (
19+
<ProviderCard key={provider.provider} data={provider} />
20+
))}
21+
</box>
22+
);
23+
}

src/components/Footer.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export function Footer() {
2+
return (
3+
<box
4+
style={{
5+
width: "100%",
6+
height: 1,
7+
flexDirection: "row",
8+
paddingLeft: 2,
9+
paddingRight: 2,
10+
}}
11+
>
12+
<text fg="#6b7280">
13+
<span fg="#9ca3af">q</span>: quit{" "}
14+
<span fg="#9ca3af">r</span>: refresh{" "}
15+
<span fg="#9ca3af">?</span>: help
16+
</text>
17+
</box>
18+
);
19+
}

0 commit comments

Comments
 (0)