opencode-usage 0.3.1 → 0.3.3

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/dist/cli.d.ts CHANGED
@@ -9,9 +9,7 @@ export type CliArgs = {
9
9
  json?: boolean;
10
10
  monthly?: boolean;
11
11
  watch?: boolean;
12
- dashboard?: boolean;
13
- codexToken?: string;
14
- config?: "set-codex-token" | "show";
15
- configToken?: string;
12
+ stats?: boolean;
13
+ config?: "show";
16
14
  };
17
15
  export declare function parseArgs(): CliArgs;
@@ -1,6 +1,10 @@
1
1
  import type { QuotaSnapshot } from "./types.js";
2
2
  /**
3
- * Fetch Codex usage quota from ChatGPT API
4
- * Requires a valid session token passed via --codex-token
3
+ * Resolve Codex token: explicit override > ~/.codex/auth.json auto-read
4
+ */
5
+ export declare function resolveCodexToken(explicitToken?: string): Promise<string | undefined>;
6
+ /**
7
+ * Fetch Codex usage quota from ChatGPT API.
8
+ * Auto-reads token from ~/.codex/auth.json if not provided explicitly.
5
9
  */
6
10
  export declare function loadCodexQuota(token?: string): Promise<QuotaSnapshot[]>;
@@ -1,5 +1 @@
1
- /**
2
- * Config management commands
3
- */
4
- export declare function setCodexToken(token: string): Promise<void>;
5
1
  export declare function showConfig(): Promise<void>;
package/dist/config.d.ts CHANGED
@@ -1,9 +1 @@
1
- /**
2
- * Configuration file loader for opencode-usage
3
- */
4
- export type Config = {
5
- codexToken?: string;
6
- };
7
1
  export declare function getConfigPath(): string;
8
- export declare function loadConfig(): Promise<Config>;
9
- export declare function saveConfig(config: Config): Promise<void>;
@@ -1,5 +1,4 @@
1
1
  type DashboardProps = {
2
- codexToken?: string;
3
2
  providerFilter?: string;
4
3
  initialDays?: number;
5
4
  refreshInterval?: number;
@@ -2,7 +2,6 @@
2
2
  * Dashboard orchestrator - unified multi-source usage view
3
3
  */
4
4
  export type DashboardOptions = {
5
- codexToken?: string;
6
5
  refreshInterval?: number;
7
6
  providerFilter?: string;
8
7
  daysFilter?: number;
package/dist/index.d.ts CHANGED
@@ -3,12 +3,9 @@
3
3
  * OpenCode Usage - CLI tool for tracking OpenCode AI usage and costs
4
4
  *
5
5
  * Usage:
6
- * bunx opencode-usage
7
- * bunx opencode-usage --provider anthropic
8
- * bunx opencode-usage --days 30
9
- * bunx opencode-usage --since 20251201 --until 20251231
10
- * bunx opencode-usage --monthly --json
11
- * bunx opencode-usage --watch
12
- * bunx opencode-usage --dashboard
6
+ * bunx opencode-usage (dashboard, default)
7
+ * bunx opencode-usage --stats (table mode)
8
+ * bunx opencode-usage --stats -d 30
9
+ * bunx opencode-usage --stats --monthly --json
13
10
  */
14
11
  export {};
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var __require = import.meta.require;
4
3
 
5
4
  // src/cli.ts
6
5
  import { parseArgs as nodeParseArgs } from "util";
@@ -47,10 +46,8 @@ function parseArgs() {
47
46
  json: { type: "boolean", short: "j" },
48
47
  monthly: { type: "boolean", short: "m" },
49
48
  watch: { type: "boolean", short: "w" },
50
- dashboard: { type: "boolean", short: "D" },
51
- "codex-token": { type: "string" },
49
+ stats: { type: "boolean", short: "S" },
52
50
  config: { type: "string" },
53
- token: { type: "string" },
54
51
  help: { type: "boolean", short: "h" }
55
52
  },
56
53
  strict: true
@@ -67,10 +64,8 @@ function parseArgs() {
67
64
  json: values.json,
68
65
  monthly: values.monthly,
69
66
  watch: values.watch,
70
- dashboard: values.dashboard,
71
- codexToken: values["codex-token"],
72
- config: values.config,
73
- configToken: values.token
67
+ stats: values.stats,
68
+ config: values.config
74
69
  };
75
70
  } catch (error) {
76
71
  if (error instanceof Error && error.message.includes("Unknown option")) {
@@ -88,43 +83,33 @@ opencode-usage - Track OpenCode AI coding assistant usage and costs
88
83
  Usage:
89
84
  bunx opencode-usage [options]
90
85
 
86
+ Modes:
87
+ (default) Interactive dashboard (Bun only)
88
+ -S, --stats Stats table mode (works with Node.js too)
89
+
91
90
  Options:
92
91
  -p, --provider <name> Filter by provider (anthropic, openai, google, opencode)
93
92
  -d, --days <n> Show only last N days
94
93
  -s, --since <date> Start date (YYYYMMDD, YYYY-MM-DD, or 7d/1w/1m)
95
94
  -u, --until <date> End date (YYYYMMDD, YYYY-MM-DD, or 7d/1w/1m)
96
- -j, --json Output as JSON
97
- -m, --monthly Aggregate by month instead of day
98
- -w, --watch Watch mode - refresh every 5 minutes
99
- -D, --dashboard Dashboard mode - unified multi-source view (Bun only)
100
- --codex-token <t> Codex API token for quota display in dashboard
101
- --config <cmd> Config commands: set-codex-token, show
95
+ -j, --json Output as JSON (stats mode only)
96
+ -m, --monthly Aggregate by month (stats mode only)
97
+ -w, --watch Watch mode - refresh every 5 minutes (stats mode only)
98
+ --config show Show current configuration
102
99
  -h, --help Show this help message
103
100
 
104
- Config Commands:
105
- --config show Show current configuration
106
- --config set-codex-token --token <tok> Save Codex API token to config
101
+ Codex Quota:
102
+ Dashboard auto-reads Codex auth from ~/.codex/auth.json.
103
+ Run 'codex login' to authenticate.
107
104
 
108
105
  Examples:
109
106
  bunx opencode-usage
110
- bunx opencode-usage --provider anthropic
111
- bunx opencode-usage -p openai -d 30
112
- bunx opencode-usage --since 20251201 --until 20251231
113
- bunx opencode-usage --since 7d
114
- bunx opencode-usage --monthly --json
115
- bunx opencode-usage --watch
116
- bunx opencode-usage -w -d 1
117
- bunx opencode-usage --dashboard
118
- bunx opencode-usage --dashboard --codex-token <token>
107
+ bunx opencode-usage --stats
108
+ bunx opencode-usage --stats --provider anthropic
109
+ bunx opencode-usage --stats -p openai -d 30
110
+ bunx opencode-usage --stats --since 7d --monthly --json
111
+ bunx opencode-usage --stats -w -d 1
119
112
  bunx opencode-usage --config show
120
- bunx opencode-usage --config set-codex-token --token sk-...
121
-
122
- How to get Codex token:
123
- 1. Open chatgpt.com in browser
124
- 2. Open DevTools (F12 or Cmd+Option+I)
125
- 3. Go to Network tab and reload page
126
- 4. Find request to 'backend-api/wham/usage'
127
- 5. Copy 'Authorization' header value (starts with 'Bearer ')
128
113
  `);
129
114
  }
130
115
 
@@ -28302,15 +28287,38 @@ async function loadAntigravityQuota() {
28302
28287
  }
28303
28288
 
28304
28289
  // src/codex-client.ts
28290
+ import { readFile as readFile3 } from "fs/promises";
28291
+ import { homedir as homedir3 } from "os";
28292
+ import { join as join4 } from "path";
28293
+ var isBun3 = typeof globalThis.Bun !== "undefined";
28305
28294
  var CODEX_API_URL = "https://chatgpt.com/backend-api/wham/usage";
28295
+ var CODEX_AUTH_PATH = join4(homedir3(), ".codex", "auth.json");
28296
+ async function readCodexAuthToken() {
28297
+ try {
28298
+ const content = isBun3 ? await Bun.file(CODEX_AUTH_PATH).text() : await readFile3(CODEX_AUTH_PATH, "utf-8");
28299
+ const auth = JSON.parse(content);
28300
+ if (auth.OPENAI_API_KEY) {
28301
+ return auth.OPENAI_API_KEY;
28302
+ }
28303
+ return auth.tokens?.access_token ?? undefined;
28304
+ } catch {
28305
+ return;
28306
+ }
28307
+ }
28308
+ async function resolveCodexToken(explicitToken) {
28309
+ if (explicitToken)
28310
+ return explicitToken;
28311
+ return readCodexAuthToken();
28312
+ }
28306
28313
  async function loadCodexQuota(token) {
28307
- if (!token) {
28314
+ const resolvedToken = await resolveCodexToken(token);
28315
+ if (!resolvedToken) {
28308
28316
  return [
28309
28317
  {
28310
28318
  source: "codex",
28311
28319
  label: "Codex",
28312
28320
  used: 0,
28313
- error: "No --codex-token provided"
28321
+ error: "Not logged in. Run: codex login"
28314
28322
  }
28315
28323
  ];
28316
28324
  }
@@ -28318,17 +28326,18 @@ async function loadCodexQuota(token) {
28318
28326
  const response = await fetch(CODEX_API_URL, {
28319
28327
  method: "GET",
28320
28328
  headers: {
28321
- Authorization: `Bearer ${token}`,
28329
+ Authorization: `Bearer ${resolvedToken}`,
28322
28330
  "Content-Type": "application/json"
28323
28331
  }
28324
28332
  });
28325
28333
  if (!response.ok) {
28334
+ const hint = response.status === 401 ? " (token expired? Run: codex login)" : "";
28326
28335
  return [
28327
28336
  {
28328
28337
  source: "codex",
28329
28338
  label: "Codex",
28330
28339
  used: 0,
28331
- error: `API error: ${response.status}`
28340
+ error: `API error: ${response.status}${hint}`
28332
28341
  }
28333
28342
  ];
28334
28343
  }
@@ -29052,18 +29061,16 @@ function Dashboard(props) {
29052
29061
  error: `Load error: ${err}`
29053
29062
  });
29054
29063
  }
29055
- if (props.codexToken) {
29056
- try {
29057
- const codex = await loadCodexQuota(props.codexToken);
29058
- results.push(...codex);
29059
- } catch (err) {
29060
- results.push({
29061
- source: "codex",
29062
- label: "Codex",
29063
- used: 0,
29064
- error: `Load error: ${err}`
29065
- });
29066
- }
29064
+ try {
29065
+ const codex = await loadCodexQuota();
29066
+ results.push(...codex);
29067
+ } catch (err) {
29068
+ results.push({
29069
+ source: "codex",
29070
+ label: "Codex",
29071
+ used: 0,
29072
+ error: `Load error: ${err}`
29073
+ });
29067
29074
  }
29068
29075
  setQuotas(results);
29069
29076
  };
@@ -29199,9 +29206,6 @@ function Dashboard(props) {
29199
29206
  }
29200
29207
  async function runSolidDashboard(options) {
29201
29208
  await render(() => createComponent2(Dashboard, {
29202
- get codexToken() {
29203
- return options.codexToken;
29204
- },
29205
29209
  get providerFilter() {
29206
29210
  return options.providerFilter;
29207
29211
  },
@@ -29218,57 +29222,32 @@ async function runSolidDashboard(options) {
29218
29222
  });
29219
29223
  }
29220
29224
 
29225
+ // src/config-commands.ts
29226
+ import { readFile as readFile4 } from "fs/promises";
29227
+ import { homedir as homedir5 } from "os";
29228
+ import { join as join6 } from "path";
29229
+
29221
29230
  // src/config.ts
29222
- import { readFile as readFile3 } from "fs/promises";
29223
- import { homedir as homedir3 } from "os";
29224
- import { join as join4 } from "path";
29225
- var isBun3 = typeof globalThis.Bun !== "undefined";
29231
+ import { homedir as homedir4 } from "os";
29232
+ import { join as join5 } from "path";
29226
29233
  function getConfigPath() {
29227
- const configDir = process.env.XDG_CONFIG_HOME ?? join4(homedir3(), ".config");
29228
- return join4(configDir, "opencode-usage", "config.json");
29229
- }
29230
- async function loadConfig() {
29231
- try {
29232
- const configPath = getConfigPath();
29233
- const content = isBun3 ? await Bun.file(configPath).text() : await readFile3(configPath, "utf-8");
29234
- return JSON.parse(content);
29235
- } catch {
29236
- return {};
29237
- }
29238
- }
29239
- async function saveConfig(config) {
29240
- const configPath = getConfigPath();
29241
- const configDir = join4(configPath, "..");
29242
- const { mkdir, writeFile } = await import("fs/promises");
29243
- await mkdir(configDir, { recursive: true });
29244
- const content = JSON.stringify(config, null, 2) + `
29245
- `;
29246
- if (isBun3) {
29247
- await Bun.write(configPath, content);
29248
- } else {
29249
- await writeFile(configPath, content, "utf-8");
29250
- }
29234
+ const configDir = process.env.XDG_CONFIG_HOME ?? join5(homedir4(), ".config");
29235
+ return join5(configDir, "opencode-usage", "config.json");
29251
29236
  }
29252
29237
 
29253
29238
  // src/config-commands.ts
29254
- async function setCodexToken(token) {
29255
- const config = await loadConfig();
29256
- config.codexToken = token;
29257
- await saveConfig(config);
29258
- console.log(`\u2713 Codex token saved to ${getConfigPath()}`);
29259
- }
29239
+ var CODEX_AUTH_PATH2 = join6(homedir5(), ".codex", "auth.json");
29260
29240
  async function showConfig() {
29261
- const config = await loadConfig();
29262
29241
  const configPath = getConfigPath();
29263
29242
  console.log(`
29264
- Configuration file: ${configPath}
29265
- `);
29266
- if (config.codexToken) {
29267
- const maskedToken = config.codexToken.substring(0, 8) + "..." + config.codexToken.substring(config.codexToken.length - 4);
29268
- console.log(` codexToken: ${maskedToken}`);
29269
- } else {
29270
- console.log(` codexToken: (not set)`);
29271
- }
29243
+ Configuration file: ${configPath}`);
29244
+ let hasCodexAuth = false;
29245
+ try {
29246
+ const content = await readFile4(CODEX_AUTH_PATH2, "utf-8");
29247
+ const auth = JSON.parse(content);
29248
+ hasCodexAuth = !!auth.tokens?.access_token;
29249
+ } catch {}
29250
+ console.log(` Codex auth: ${hasCodexAuth ? "~/.codex/auth.json (auto)" : "(not found \u2014 run: codex login)"}`);
29272
29251
  console.log();
29273
29252
  }
29274
29253
 
@@ -29323,37 +29302,13 @@ Loading OpenCode usage data from: ${options.storagePath}`);
29323
29302
  }
29324
29303
  }
29325
29304
  async function main2() {
29326
- const {
29327
- provider,
29328
- days,
29329
- since,
29330
- until,
29331
- json,
29332
- monthly,
29333
- watch,
29334
- dashboard,
29335
- codexToken,
29336
- config,
29337
- configToken
29338
- } = parseArgs();
29305
+ const { provider, days, since, until, json, monthly, watch, stats, config } = parseArgs();
29339
29306
  if (config === "show") {
29340
29307
  await showConfig();
29341
29308
  return;
29342
29309
  }
29343
- if (config === "set-codex-token") {
29344
- if (!configToken) {
29345
- console.error("Error: --config set-codex-token requires --token <value>");
29346
- console.error("Usage: opencode-usage --config set-codex-token --token <token>");
29347
- process.exit(1);
29348
- }
29349
- await setCodexToken(configToken);
29350
- return;
29351
- }
29352
- const configData = await loadConfig();
29353
- const effectiveCodexToken = codexToken ?? configData.codexToken;
29354
- if (dashboard) {
29310
+ if (!stats) {
29355
29311
  await runSolidDashboard({
29356
- codexToken: effectiveCodexToken,
29357
29312
  refreshInterval: 300,
29358
29313
  providerFilter: provider,
29359
29314
  initialDays: days
@@ -29391,4 +29346,4 @@ async function main2() {
29391
29346
  }
29392
29347
  main2().catch(console.error);
29393
29348
 
29394
- //# debugId=2D8D06802834C82864756E2164756E21
29349
+ //# debugId=4F3AFA8CCC597E5564756E2164756E21