claudemon 0.2.2 → 0.3.0

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
@@ -75,6 +75,37 @@ Config file: `~/.config/claudemon/config.toml`
75
75
 
76
76
  </details>
77
77
 
78
+ ## Pi Integration
79
+
80
+ Claudemon is also available as a [pi](https://github.com/badlogic/pi-mono) extension, so you can check your Claude quota without leaving your coding session.
81
+
82
+ ### Install
83
+
84
+ ```sh
85
+ # From npm
86
+ pi install npm:claudemon
87
+
88
+ # From git
89
+ pi install https://github.com/anistark/claudemon
90
+
91
+ # Try without installing
92
+ pi -e npm:claudemon
93
+ ```
94
+
95
+ ### Usage
96
+
97
+ | Command | Description |
98
+ |---------|-------------|
99
+ | `/claudemon` | Show quota usage inline (5-hour, 7-day windows, per-model breakdown) |
100
+ | `/claudemon --tui` | Launch the full TUI dashboard |
101
+ | *"Check my Claude usage"* | The LLM calls the `claudemon` tool automatically |
102
+
103
+ ### Uninstall
104
+
105
+ ```sh
106
+ pi remove npm:claudemon
107
+ ```
108
+
78
109
  ## Development
79
110
 
80
111
  ```sh
package/dist/api.d.ts CHANGED
@@ -9,4 +9,15 @@ export declare class AuthenticationError extends QuotaFetchError {
9
9
  constructor(message: string);
10
10
  }
11
11
  export declare function fetchQuota(oauthToken: string): Promise<QuotaData>;
12
+ /**
13
+ * Quick health-check: verifies that an OAuth token exists and is accepted
14
+ * by the API. Returns `{ ok: true }` on success, or `{ ok: false, reason }`
15
+ * on failure.
16
+ */
17
+ export declare function validateToken(): Promise<{
18
+ ok: true;
19
+ } | {
20
+ ok: false;
21
+ reason: string;
22
+ }>;
12
23
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,aAAa,CAAC;AAG/E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,eAAe;gBAC1C,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAuCvE"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,aAAa,CAAC;AAG/E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,eAAe;gBAC1C,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAuCvE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAC5C;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAC7C,CAmDA"}
package/dist/api.js CHANGED
@@ -46,6 +46,59 @@ export async function fetchQuota(oauthToken) {
46
46
  const data = (await resp.json());
47
47
  return parseQuotaResponse(data);
48
48
  }
49
+ /**
50
+ * Quick health-check: verifies that an OAuth token exists and is accepted
51
+ * by the API. Returns `{ ok: true }` on success, or `{ ok: false, reason }`
52
+ * on failure.
53
+ */
54
+ export async function validateToken() {
55
+ const { getOAuthToken } = await import("./auth.js");
56
+ const token = getOAuthToken();
57
+ if (!token) {
58
+ return {
59
+ ok: false,
60
+ reason: "No OAuth token found. Please run 'claudemon setup' first.",
61
+ };
62
+ }
63
+ const config = loadConfig();
64
+ const usageUrl = config["oauth_usage_url"];
65
+ const betaHeader = config["oauth_beta_header"];
66
+ try {
67
+ const resp = await fetch(usageUrl, {
68
+ headers: {
69
+ Authorization: `Bearer ${token}`,
70
+ "anthropic-beta": betaHeader,
71
+ },
72
+ signal: AbortSignal.timeout(10000),
73
+ });
74
+ if (resp.status === 401) {
75
+ return {
76
+ ok: false,
77
+ reason: "OAuth token is invalid or expired. Please run 'claudemon setup --re' to re-authenticate.",
78
+ };
79
+ }
80
+ if (resp.status === 403) {
81
+ return {
82
+ ok: false,
83
+ reason: "Access denied. Your token may lack the required permissions. Run 'claudemon setup --re'.",
84
+ };
85
+ }
86
+ if (resp.status !== 200) {
87
+ const text = await resp.text();
88
+ return {
89
+ ok: false,
90
+ reason: `API returned status ${resp.status}: ${text}`,
91
+ };
92
+ }
93
+ return { ok: true };
94
+ }
95
+ catch (e) {
96
+ return {
97
+ ok: false,
98
+ reason: `Network error while validating token: ${e}`,
99
+ };
100
+ }
101
+ }
49
102
  function parseQuotaResponse(data) {
50
103
  const quota = createQuotaData();
51
104
  // Parse 5-hour window
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
6
6
  import { createRequire } from "node:module";
7
7
  import { render } from "ink";
8
8
  import { App } from "./app.js";
9
+ import { validateToken } from "./api.js";
9
10
  const require = createRequire(import.meta.url);
10
11
  const { version: VERSION } = require("../package.json");
11
12
  const LOGO = `
@@ -42,7 +43,7 @@ const LOGO = `
42
43
  ----===----
43
44
  ---===---
44
45
  `;
45
- function main() {
46
+ async function main() {
46
47
  const args = process.argv.slice(2);
47
48
  if (args.includes("--help") || args.includes("-h")) {
48
49
  printHelp();
@@ -55,9 +56,17 @@ function main() {
55
56
  }
56
57
  if (args[0] === "setup") {
57
58
  const forceReauth = args.includes("--re");
58
- runSetup(forceReauth);
59
+ await runSetup(forceReauth);
59
60
  return;
60
61
  }
62
+ // Validate token before launching the TUI
63
+ console.log("Validating OAuth token...");
64
+ const result = await validateToken();
65
+ if (!result.ok) {
66
+ console.error(`\n✗ ${result.reason}\n`);
67
+ process.exit(1);
68
+ }
69
+ console.log("✓ Token is valid.\n");
61
70
  // Launch TUI (full-screen alternate screen)
62
71
  process.stdout.write("\x1b[?1049h"); // enter alternate screen
63
72
  process.stdout.write("\x1b[2J\x1b[H"); // clear + home
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;AAEA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAExD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCZ,CAAC;AAEF,SAAS,IAAI;IACX,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1C,QAAQ,CAAC,WAAW,CAAC,CAAC;QACtB,OAAO;IACT,CAAC;IAED,4CAA4C;IAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,yBAAyB;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAC,GAAG,IAAC,OAAO,EAAE,OAAO,GAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,sBAAsB;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,WAAW,GAAG,KAAK;IACzC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IACvD,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;iBAcG,CAAC,CAAC;AACnB,CAAC;AAED,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;AAEA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAExD,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCZ,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,0CAA0C;IAC1C,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAEnC,4CAA4C;IAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,yBAAyB;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAC,GAAG,IAAC,OAAO,EAAE,OAAO,GAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,sBAAsB;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,WAAW,GAAG,KAAK;IACzC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IACvD,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;iBAcG,CAAC,CAAC;AACnB,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Pi extension for claudemon - Claude Usage Monitor.
3
+ *
4
+ * Registers:
5
+ * /claudemon - Show quota usage inline (or launch TUI with --tui)
6
+ * claudemon tool - LLM-callable tool to check Claude quota
7
+ */
8
+
9
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
+ import { Type } from "@sinclair/typebox";
11
+ import { execFileSync } from "node:child_process";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { homedir, platform } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Token resolution (same logic as claudemon's auth.ts)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function readKeychainCredentials(): Record<string, unknown> | null {
21
+ if (platform() !== "darwin") return null;
22
+ try {
23
+ const raw = execFileSync(
24
+ "/usr/bin/security",
25
+ ["find-generic-password", "-s", "Claude Code-credentials", "-w"],
26
+ { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
27
+ );
28
+ if (!raw.trim()) return null;
29
+ return JSON.parse(raw.trim()) as Record<string, unknown>;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function readFileCredentials(): Record<string, unknown> | null {
36
+ const credFile = join(homedir(), ".claude", ".credentials.json");
37
+ if (!existsSync(credFile)) return null;
38
+ try {
39
+ return JSON.parse(readFileSync(credFile, "utf-8")) as Record<string, unknown>;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ function readClaudemonToken(): string | null {
46
+ const tokenFile = join(homedir(), ".config", "claudemon", "token.json");
47
+ if (!existsSync(tokenFile)) return null;
48
+ try {
49
+ const data = JSON.parse(readFileSync(tokenFile, "utf-8"));
50
+ if (data.expires_at && data.expires_at < Date.now()) return null;
51
+ return data.oauth_token ?? null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ function getOAuthToken(): string | null {
58
+ // Prefer Claude Code's live credentials
59
+ for (const reader of [readKeychainCredentials, readFileCredentials]) {
60
+ const data = reader();
61
+ if (data) {
62
+ const oauth = data["claudeAiOauth"] as { accessToken?: string; expiresAt?: number } | undefined;
63
+ if (oauth?.accessToken) {
64
+ if (oauth.expiresAt && oauth.expiresAt < Date.now()) continue;
65
+ return oauth.accessToken;
66
+ }
67
+ }
68
+ }
69
+ // Fallback to claudemon's own stored token
70
+ return readClaudemonToken();
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Quota fetch (minimal inline version)
75
+ // ---------------------------------------------------------------------------
76
+
77
+ interface ModelQuota {
78
+ modelName: string;
79
+ usagePct: number;
80
+ }
81
+
82
+ interface QuotaData {
83
+ fiveHourUsagePct: number;
84
+ fiveHourResetTime: Date | null;
85
+ sevenDayUsagePct: number;
86
+ sevenDayResetTime: Date | null;
87
+ modelQuotas: ModelQuota[];
88
+ planType: string;
89
+ }
90
+
91
+ async function fetchQuota(token: string): Promise<QuotaData> {
92
+ const resp = await fetch("https://api.anthropic.com/api/oauth/usage", {
93
+ headers: {
94
+ Authorization: `Bearer ${token}`,
95
+ "anthropic-beta": "oauth-2025-04-20",
96
+ },
97
+ signal: AbortSignal.timeout(15000),
98
+ });
99
+
100
+ if (resp.status === 401 || resp.status === 403) {
101
+ throw new Error("OAuth token expired. Run `claudemon setup` to re-authenticate.");
102
+ }
103
+ if (resp.status !== 200) {
104
+ throw new Error(`API returned status ${resp.status}`);
105
+ }
106
+
107
+ const data = (await resp.json()) as Record<string, unknown>;
108
+ return parseQuotaResponse(data);
109
+ }
110
+
111
+ function parseQuotaResponse(data: Record<string, unknown>): QuotaData {
112
+ const fiveHour = (data["five_hour"] ?? data["fiveHour"] ?? {}) as Record<string, unknown>;
113
+ const sevenDay = (data["seven_day"] ?? data["sevenDay"] ?? {}) as Record<string, unknown>;
114
+ const models = (data["models"] ?? data["model_quotas"] ?? []) as Array<Record<string, unknown>>;
115
+
116
+ const fiveHourReset = (fiveHour["resets_at"] ?? fiveHour["reset_at"] ?? fiveHour["resetAt"]) as string | undefined;
117
+ const sevenDayReset = (sevenDay["resets_at"] ?? sevenDay["reset_at"] ?? sevenDay["resetAt"]) as string | undefined;
118
+
119
+ return {
120
+ fiveHourUsagePct: (fiveHour["utilization"] as number) ?? (fiveHour["usage_pct"] as number) ?? 0,
121
+ fiveHourResetTime: fiveHourReset ? new Date(fiveHourReset) : null,
122
+ sevenDayUsagePct: (sevenDay["utilization"] as number) ?? (sevenDay["usage_pct"] as number) ?? 0,
123
+ sevenDayResetTime: sevenDayReset ? new Date(sevenDayReset) : null,
124
+ modelQuotas: models.map((m) => ({
125
+ modelName: (m["model"] as string) ?? (m["name"] as string) ?? "unknown",
126
+ usagePct: (m["utilization"] as number) ?? (m["usage_pct"] as number) ?? 0,
127
+ })),
128
+ planType: (data["plan_type"] as string) ?? (data["planType"] as string) ?? "pro",
129
+ };
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Formatting helpers
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function formatCountdown(totalSeconds: number): string {
137
+ if (totalSeconds <= 0) return "now";
138
+ const days = Math.floor(totalSeconds / 86400);
139
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
140
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
141
+ if (days > 0) return `${days}d ${hours}h`;
142
+ if (hours > 0) return `${hours}h ${minutes}m`;
143
+ return `${minutes}m`;
144
+ }
145
+
146
+ function usageBar(pct: number, width = 20): string {
147
+ const filled = Math.round((pct / 100) * width);
148
+ const empty = width - filled;
149
+ return `[${"█".repeat(filled)}${"░".repeat(empty)}] ${pct.toFixed(1)}%`;
150
+ }
151
+
152
+ function formatQuota(q: QuotaData): string {
153
+ const lines: string[] = [];
154
+ lines.push(`📊 Claude Usage (${q.planType.toUpperCase()} plan)`);
155
+ lines.push("");
156
+
157
+ // 5-hour window
158
+ const fiveHourReset = q.fiveHourResetTime
159
+ ? formatCountdown(Math.max(0, (q.fiveHourResetTime.getTime() - Date.now()) / 1000))
160
+ : "—";
161
+ lines.push(`5-hour: ${usageBar(q.fiveHourUsagePct)} resets in ${fiveHourReset}`);
162
+
163
+ // 7-day window
164
+ const sevenDayReset = q.sevenDayResetTime
165
+ ? formatCountdown(Math.max(0, (q.sevenDayResetTime.getTime() - Date.now()) / 1000))
166
+ : "—";
167
+ lines.push(`7-day: ${usageBar(q.sevenDayUsagePct)} resets in ${sevenDayReset}`);
168
+
169
+ // Per-model breakdown
170
+ if (q.modelQuotas.length > 0) {
171
+ lines.push("");
172
+ lines.push("Per model:");
173
+ for (const m of q.modelQuotas) {
174
+ lines.push(` ${m.modelName.padEnd(30)} ${usageBar(m.usagePct)}`);
175
+ }
176
+ }
177
+
178
+ return lines.join("\n");
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Extension entry point
183
+ // ---------------------------------------------------------------------------
184
+
185
+ export default function (pi: ExtensionAPI) {
186
+ // /claudemon command — show quota inline or launch TUI
187
+ pi.registerCommand("claudemon", {
188
+ description: "Show Claude usage quota (--tui to launch dashboard)",
189
+ handler: async (args, ctx) => {
190
+ const trimmed = (args ?? "").trim();
191
+
192
+ // --tui flag: launch the full TUI dashboard
193
+ if (trimmed === "--tui" || trimmed === "-t") {
194
+ const result = await pi.exec("npx", ["claudemon"], { timeout: 300000 });
195
+ if (result.code !== 0 && result.stderr) {
196
+ ctx.ui.notify(`claudemon exited with error: ${result.stderr.slice(0, 200)}`, "error");
197
+ }
198
+ return;
199
+ }
200
+
201
+ // Default: fetch and display inline
202
+ const token = getOAuthToken();
203
+ if (!token) {
204
+ ctx.ui.notify(
205
+ "Not authenticated. Run `claudemon setup` or `npx claudemon setup` first.",
206
+ "error",
207
+ );
208
+ return;
209
+ }
210
+
211
+ try {
212
+ ctx.ui.setStatus("claudemon", "Fetching quota…");
213
+ const quota = await fetchQuota(token);
214
+ ctx.ui.setStatus("claudemon", undefined);
215
+ ctx.ui.notify(formatQuota(quota), "info");
216
+ } catch (err) {
217
+ ctx.ui.setStatus("claudemon", undefined);
218
+ ctx.ui.notify(`Failed to fetch quota: ${err instanceof Error ? err.message : err}`, "error");
219
+ }
220
+ },
221
+ });
222
+
223
+ // LLM-callable tool
224
+ pi.registerTool({
225
+ name: "claudemon",
226
+ label: "Claude Usage Monitor",
227
+ description:
228
+ "Check the user's Claude Pro/Max plan quota usage including 5-hour and 7-day windows and per-model breakdown. Use when the user asks about their Claude usage, quota, or remaining capacity.",
229
+ parameters: Type.Object({}),
230
+ async execute(_toolCallId, _params, signal) {
231
+ const token = getOAuthToken();
232
+ if (!token) {
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text" as const,
237
+ text: "Not authenticated. The user needs to run `claudemon setup` or `npx claudemon setup` to authenticate first.",
238
+ },
239
+ ],
240
+ isError: true,
241
+ };
242
+ }
243
+
244
+ try {
245
+ const quota = await fetchQuota(token);
246
+ return {
247
+ content: [{ type: "text" as const, text: formatQuota(quota) }],
248
+ details: {
249
+ fiveHourUsagePct: quota.fiveHourUsagePct,
250
+ sevenDayUsagePct: quota.sevenDayUsagePct,
251
+ planType: quota.planType,
252
+ modelQuotas: quota.modelQuotas,
253
+ },
254
+ };
255
+ } catch (err) {
256
+ return {
257
+ content: [
258
+ {
259
+ type: "text" as const,
260
+ text: `Failed to fetch quota: ${err instanceof Error ? err.message : err}`,
261
+ },
262
+ ],
263
+ isError: true,
264
+ };
265
+ }
266
+ },
267
+ });
268
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemon",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Usage Monitor TUI - monitor your Claude usage in real-time",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,15 +8,22 @@
8
8
  "claudemon": "dist/index.js"
9
9
  },
10
10
  "files": [
11
- "dist"
11
+ "dist",
12
+ "extensions"
12
13
  ],
13
14
  "keywords": [
14
15
  "claude",
15
16
  "anthropic",
16
17
  "quota",
17
18
  "tui",
18
- "cli"
19
+ "cli",
20
+ "pi-package"
19
21
  ],
22
+ "pi": {
23
+ "extensions": [
24
+ "./extensions"
25
+ ]
26
+ },
20
27
  "license": "MIT",
21
28
  "dependencies": {
22
29
  "chalk": "^5.3.0",
@@ -24,6 +31,18 @@
24
31
  "react": "^18.3.1",
25
32
  "smol-toml": "^1.3.1"
26
33
  },
34
+ "peerDependencies": {
35
+ "@mariozechner/pi-coding-agent": "*",
36
+ "@sinclair/typebox": "*"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@mariozechner/pi-coding-agent": {
40
+ "optional": true
41
+ },
42
+ "@sinclair/typebox": {
43
+ "optional": true
44
+ }
45
+ },
27
46
  "devDependencies": {
28
47
  "@types/node": "^25.2.3",
29
48
  "@types/react": "^18.3.12",