aieracard 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +64 -19
  3. package/package.json +13 -4
package/README.md CHANGED
@@ -11,7 +11,9 @@ The CLI collects usage **locally** from:
11
11
  - **Claude Code** — local JSONL logs (`~/.claude/projects`), fully automatic
12
12
  - **Cursor** — all-time usage via your own cursor.com session (token resolved
13
13
  locally, sent only to cursor.com); CSV export as fallback
14
- - **OpenRouter** — via your API key (read-only endpoints)
14
+ - **OpenRouter** — management API key (read-only `/credits` + `/activity`).
15
+ Tokens are last 30 days; all-time spend stays on the source object and is
16
+ **not** rolled into the card's aggregate "compute spent"
15
17
 
16
18
  It then shows you the **exact JSON** that would be uploaded — aggregate
17
19
  numbers only: token counts, cost totals, active days, streaks, model names.
package/dist/index.js CHANGED
@@ -4084,8 +4084,11 @@ var claudeCodeSourceSchema = external_exports.object({
4084
4084
  models
4085
4085
  });
4086
4086
  var openRouterSourceSchema = external_exports.object({
4087
+ // Tokens/requests/models are last-30-day (/activity). totalCostUsd is
4088
+ // all-time spend from /credits — nullable if that endpoint fails.
4089
+ // Aggregate card totals intentionally omit this cost (mixed windows).
4087
4090
  totalTokens: tokens,
4088
- totalCostUsd: costUsd,
4091
+ totalCostUsd: costUsd.nullable(),
4089
4092
  requestCount: count,
4090
4093
  activeDays: count,
4091
4094
  windowDays: external_exports.literal(30),
@@ -4339,41 +4342,69 @@ async function validateOpenRouterKey(key) {
4339
4342
  return false;
4340
4343
  }
4341
4344
  }
4345
+ function parseCostUsd(credits) {
4346
+ if (!credits || typeof credits !== "object") return null;
4347
+ const data = credits.data;
4348
+ const raw = data?.total_usage;
4349
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? Math.round(raw * 100) / 100 : null;
4350
+ }
4351
+ function parseActivityRows(activity) {
4352
+ if (!activity || typeof activity !== "object") return [];
4353
+ const data = activity.data;
4354
+ return Array.isArray(data) ? data : [];
4355
+ }
4342
4356
  async function collectOpenRouter(key) {
4343
- const [credits, activity] = await Promise.all([
4344
- get("/credits", key).catch(() => null),
4345
- get("/activity", key).catch(() => null)
4346
- ]);
4347
- const totalCostUsd = credits?.data?.total_usage ?? credits?.data?.usage ?? 0;
4348
- const rows = activity?.data ?? [];
4357
+ const warnings = [];
4358
+ let credits = null;
4359
+ try {
4360
+ credits = await get("/credits", key);
4361
+ } catch (e) {
4362
+ warnings.push(
4363
+ `/credits failed (${e.message}) \u2014 all-time spend unknown; tokens still from last 30 days`
4364
+ );
4365
+ }
4366
+ let activity;
4367
+ try {
4368
+ activity = await get("/activity", key);
4369
+ } catch (e) {
4370
+ throw new Error(
4371
+ `OpenRouter /activity failed (${e.message}). Need a management API key with activity access.`
4372
+ );
4373
+ }
4374
+ const totalCostUsd = parseCostUsd(credits);
4375
+ if (credits != null && totalCostUsd == null) {
4376
+ warnings.push("/credits returned an unexpected shape \u2014 spend left unknown");
4377
+ }
4378
+ const rows = parseActivityRows(activity);
4349
4379
  let totalTokens = 0;
4350
4380
  let requestCount = 0;
4351
4381
  const activeDates = /* @__PURE__ */ new Set();
4352
4382
  const models2 = /* @__PURE__ */ new Set();
4353
4383
  for (const row of rows) {
4354
- totalTokens += (row.prompt_tokens ?? 0) + (row.completion_tokens ?? 0) + (row.reasoning_tokens ?? 0);
4355
- requestCount += row.requests ?? 0;
4384
+ totalTokens += (Number(row.prompt_tokens) || 0) + (Number(row.completion_tokens) || 0) + (Number(row.reasoning_tokens) || 0);
4385
+ requestCount += Number(row.requests) || 0;
4356
4386
  if (typeof row.date === "string") {
4357
4387
  const d = row.date.slice(0, 10);
4358
4388
  if (/^\d{4}-\d{2}-\d{2}$/.test(d)) activeDates.add(d);
4359
4389
  }
4360
4390
  if (typeof row.model === "string" && row.model) models2.add(row.model);
4361
- if (typeof row.model_permaslug === "string" && row.model_permaslug)
4391
+ else if (typeof row.model_permaslug === "string" && row.model_permaslug)
4362
4392
  models2.add(row.model_permaslug);
4363
4393
  }
4364
- if (totalTokens === 0 && totalCostUsd === 0 && requestCount === 0) {
4394
+ if (totalTokens === 0 && requestCount === 0 && totalCostUsd == null) {
4365
4395
  return null;
4366
4396
  }
4367
4397
  return {
4368
4398
  source: {
4369
4399
  totalTokens: Math.round(totalTokens),
4370
- totalCostUsd: Math.round(totalCostUsd * 100) / 100,
4400
+ totalCostUsd,
4371
4401
  requestCount,
4372
4402
  activeDays: activeDates.size,
4373
4403
  windowDays: 30,
4374
4404
  models: [...models2].sort().slice(0, 50)
4375
4405
  },
4376
- activeDates
4406
+ activeDates,
4407
+ warnings
4377
4408
  };
4378
4409
  }
4379
4410
 
@@ -4629,7 +4660,7 @@ async function collectCursorApi(cookie, onProgress) {
4629
4660
  }
4630
4661
 
4631
4662
  // src/merge.ts
4632
- var CLI_VERSION = "0.1.0";
4663
+ var CLI_VERSION = "0.1.1";
4633
4664
  function buildPayload(opts) {
4634
4665
  const { claudeCode, openrouter, cursor, handle } = opts;
4635
4666
  const allDates = /* @__PURE__ */ new Set();
@@ -4639,7 +4670,6 @@ function buildPayload(opts) {
4639
4670
  const totalTokens = (claudeCode?.source.totalTokens ?? 0) + (openrouter?.source.totalTokens ?? 0) + (cursor?.source.totalTokens ?? 0);
4640
4671
  const costs = [
4641
4672
  claudeCode?.source.estimatedCostUsd,
4642
- openrouter?.source.totalCostUsd,
4643
4673
  cursor?.source.totalCostUsd
4644
4674
  ].filter((c) => typeof c === "number");
4645
4675
  const totalCostUsd = costs.length > 0 ? Math.round(costs.reduce((a, b) => a + b, 0) * 100) / 100 : null;
@@ -4775,12 +4805,27 @@ Options:
4775
4805
  s2.start("Validating OpenRouter key");
4776
4806
  if (await validateOpenRouterKey(key)) {
4777
4807
  s2.message("Fetching OpenRouter usage");
4778
- openrouter = await collectOpenRouter(key);
4808
+ try {
4809
+ openrouter = await collectOpenRouter(key);
4810
+ for (const w of openrouter?.warnings ?? []) {
4811
+ p.log.warn(`OpenRouter: ${w}`);
4812
+ }
4813
+ if (openrouter) {
4814
+ const spend = openrouter.source.totalCostUsd != null ? `$${openrouter.source.totalCostUsd} all-time spend (not rolled into card total)` : "spend unknown";
4815
+ s2.stop(
4816
+ `OpenRouter: ${fmt(openrouter.source.totalTokens)} tokens (last 30 days), ${spend}`
4817
+ );
4818
+ } else {
4819
+ s2.stop("OpenRouter: no usage found \u2014 skipping");
4820
+ }
4821
+ } catch (e) {
4822
+ openrouter = null;
4823
+ s2.stop(`OpenRouter: ${e.message} \u2014 skipping`);
4824
+ }
4825
+ } else {
4779
4826
  s2.stop(
4780
- openrouter ? `OpenRouter: $${openrouter.source.totalCostUsd} all-time, ${fmt(openrouter.source.totalTokens)} tokens (last 30 days)` : "OpenRouter: no usage found \u2014 skipping"
4827
+ "OpenRouter: key invalid or not a management key \u2014 skipping"
4781
4828
  );
4782
- } else {
4783
- s2.stop("OpenRouter: key invalid \u2014 skipping");
4784
4829
  }
4785
4830
  }
4786
4831
  }
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "aieracard",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Generate a shareable card from your AI tool usage. Local parsing, aggregate numbers only.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "aieracard": "./dist/index.js"
7
+ "aieracard": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
11
  "README.md"
12
12
  ],
13
13
  "license": "MIT",
14
+ "homepage": "https://ai-era-card.vercel.app",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/sinsmilex/ai-era-card.git",
18
+ "directory": "apps/cli"
19
+ },
20
+ "bugs": "https://github.com/sinsmilex/ai-era-card/issues",
14
21
  "keywords": [
15
22
  "ai",
16
23
  "usage",
@@ -27,7 +34,8 @@
27
34
  "scripts": {
28
35
  "build": "tsup",
29
36
  "start": "node dist/index.js",
30
- "dev": "tsup --watch"
37
+ "dev": "tsup --watch",
38
+ "test": "vitest run"
31
39
  },
32
40
  "dependencies": {
33
41
  "@clack/prompts": "^0.11.0",
@@ -37,6 +45,7 @@
37
45
  "@aieracard/schema": "workspace:*",
38
46
  "@types/node": "^22",
39
47
  "tsup": "^8.3.5",
40
- "typescript": "^5.6.3"
48
+ "typescript": "^5.6.3",
49
+ "vitest": "^2.1.9"
41
50
  }
42
51
  }