aieracard 0.1.1 → 0.1.2
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 +3 -2
- package/dist/index.js +249 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ npx aieracard
|
|
|
9
9
|
The CLI collects usage **locally** from:
|
|
10
10
|
|
|
11
11
|
- **Claude Code** — local JSONL logs (`~/.claude/projects`), fully automatic
|
|
12
|
+
- **Codex** — local JSONL under `~/.codex/sessions` (override with `CODEX_HOME`)
|
|
12
13
|
- **Cursor** — all-time usage via your own cursor.com session (token resolved
|
|
13
14
|
locally, sent only to cursor.com); CSV export as fallback
|
|
14
|
-
- **OpenRouter** — management API key (
|
|
15
|
+
- **OpenRouter** — optional management API key (`/credits` + `/activity`).
|
|
15
16
|
Tokens are last 30 days; all-time spend stays on the source object and is
|
|
16
17
|
**not** rolled into the card's aggregate "compute spent"
|
|
17
18
|
|
|
@@ -33,7 +34,7 @@ that unfurls into a stats card when shared.
|
|
|
33
34
|
--openrouter-key <k> OpenRouter API key (or OPENROUTER_API_KEY env)
|
|
34
35
|
--cursor-cookie <t> Cursor web session token (auto-detected if omitted)
|
|
35
36
|
--cursor-csv <path> usage CSV export (fallback if the API path fails)
|
|
36
|
-
--no-claude-code / --no-openrouter / --no-cursor
|
|
37
|
+
--no-claude-code / --no-codex / --no-openrouter / --no-cursor
|
|
37
38
|
--handle <name> display name on the card (unverified)
|
|
38
39
|
--open open the card URL in your browser
|
|
39
40
|
```
|
package/dist/index.js
CHANGED
|
@@ -4102,13 +4102,28 @@ var cursorSourceSchema = external_exports.object({
|
|
|
4102
4102
|
dateRange: external_exports.object({ from: dateOnly, to: dateOnly }),
|
|
4103
4103
|
models
|
|
4104
4104
|
});
|
|
4105
|
+
var codexSourceSchema = external_exports.object({
|
|
4106
|
+
tokensIn: tokens,
|
|
4107
|
+
tokensOut: tokens,
|
|
4108
|
+
cacheReadTokens: tokens,
|
|
4109
|
+
reasoningTokens: tokens,
|
|
4110
|
+
totalTokens: tokens,
|
|
4111
|
+
estimatedCostUsd: costUsd.nullable(),
|
|
4112
|
+
costConfidence: external_exports.enum(["estimated", "partial"]),
|
|
4113
|
+
sessionCount: count,
|
|
4114
|
+
activeDays: count,
|
|
4115
|
+
longestStreakDays: count,
|
|
4116
|
+
firstActivityDate: dateOnly,
|
|
4117
|
+
lastActivityDate: dateOnly,
|
|
4118
|
+
models
|
|
4119
|
+
});
|
|
4105
4120
|
var aggregateSchema = external_exports.object({
|
|
4106
4121
|
totalTokens: tokens,
|
|
4107
4122
|
totalCostUsd: costUsd.nullable(),
|
|
4108
4123
|
totalActiveDays: count,
|
|
4109
4124
|
longestStreakDays: count,
|
|
4110
4125
|
distinctModels: models,
|
|
4111
|
-
sourceCount: external_exports.number().int().min(1).max(
|
|
4126
|
+
sourceCount: external_exports.number().int().min(1).max(4),
|
|
4112
4127
|
firstActivityDate: dateOnly,
|
|
4113
4128
|
lastActivityDate: dateOnly
|
|
4114
4129
|
});
|
|
@@ -4119,9 +4134,10 @@ var snapshotPayloadSchema = external_exports.object({
|
|
|
4119
4134
|
sources: external_exports.object({
|
|
4120
4135
|
claudeCode: claudeCodeSourceSchema.optional(),
|
|
4121
4136
|
openrouter: openRouterSourceSchema.optional(),
|
|
4122
|
-
cursor: cursorSourceSchema.optional()
|
|
4137
|
+
cursor: cursorSourceSchema.optional(),
|
|
4138
|
+
codex: codexSourceSchema.optional()
|
|
4123
4139
|
}).refine(
|
|
4124
|
-
(s) => s.claudeCode || s.openrouter || s.cursor,
|
|
4140
|
+
(s) => s.claudeCode || s.openrouter || s.cursor || s.codex,
|
|
4125
4141
|
"at least one source is required"
|
|
4126
4142
|
),
|
|
4127
4143
|
aggregate: aggregateSchema,
|
|
@@ -4163,11 +4179,28 @@ var MODEL_PRICING = {
|
|
|
4163
4179
|
"claude-3-5-sonnet": rates(3, 15),
|
|
4164
4180
|
"claude-haiku-4-5": rates(1, 5),
|
|
4165
4181
|
"claude-3-5-haiku": rates(0.8, 4),
|
|
4166
|
-
"claude-3-haiku": rates(0.25, 1.25)
|
|
4182
|
+
"claude-3-haiku": rates(0.25, 1.25),
|
|
4183
|
+
// OpenAI / Codex CLI (USD per 1M; cache read ≈ 0.1× input via rates())
|
|
4184
|
+
"gpt-5.4": rates(2.5, 15),
|
|
4185
|
+
"gpt-5.3": rates(2.5, 15),
|
|
4186
|
+
"gpt-5.2": rates(2.5, 15),
|
|
4187
|
+
"gpt-5.1": rates(2.5, 15),
|
|
4188
|
+
"gpt-5-mini": rates(0.4, 1.6),
|
|
4189
|
+
"gpt-5-nano": rates(0.1, 0.4),
|
|
4190
|
+
"gpt-5-codex": rates(2.5, 15),
|
|
4191
|
+
"gpt-5": rates(2.5, 15),
|
|
4192
|
+
"gpt-4.1-mini": rates(0.4, 1.6),
|
|
4193
|
+
"gpt-4.1-nano": rates(0.1, 0.4),
|
|
4194
|
+
"gpt-4.1": rates(2, 8),
|
|
4195
|
+
"gpt-4o-mini": rates(0.15, 0.6),
|
|
4196
|
+
"gpt-4o": rates(2.5, 10),
|
|
4197
|
+
"o4-mini": rates(1.1, 4.4),
|
|
4198
|
+
"o3-mini": rates(1.1, 4.4),
|
|
4199
|
+
"o3": rates(2, 8)
|
|
4167
4200
|
};
|
|
4168
4201
|
var sortedKeys = Object.keys(MODEL_PRICING).sort((a, b) => b.length - a.length);
|
|
4169
4202
|
function ratesFor(modelId) {
|
|
4170
|
-
const normalized = modelId.replace(/^(anthropic\.|us\.anthropic\.)/, "");
|
|
4203
|
+
const normalized = modelId.replace(/^(anthropic\.|us\.anthropic\.|openai\/|openai\.)/, "").replace(/^models\//, "");
|
|
4171
4204
|
for (const key of sortedKeys) {
|
|
4172
4205
|
if (normalized.startsWith(key)) return MODEL_PRICING[key];
|
|
4173
4206
|
}
|
|
@@ -4659,24 +4692,204 @@ async function collectCursorApi(cookie, onProgress) {
|
|
|
4659
4692
|
};
|
|
4660
4693
|
}
|
|
4661
4694
|
|
|
4695
|
+
// src/collectors/codex.ts
|
|
4696
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
4697
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
4698
|
+
import { createInterface as createInterface2 } from "readline";
|
|
4699
|
+
import { homedir as homedir3 } from "os";
|
|
4700
|
+
import { join as join3, relative } from "path";
|
|
4701
|
+
function codexHome() {
|
|
4702
|
+
return process.env.CODEX_HOME?.split(",")[0]?.trim() || join3(homedir3(), ".codex");
|
|
4703
|
+
}
|
|
4704
|
+
function codexSessionsRoots(home = codexHome()) {
|
|
4705
|
+
return [join3(home, "sessions"), join3(home, "archived_sessions")];
|
|
4706
|
+
}
|
|
4707
|
+
function asBucket(v) {
|
|
4708
|
+
if (!v || typeof v !== "object") return null;
|
|
4709
|
+
const o = v;
|
|
4710
|
+
const n = (k) => typeof o[k] === "number" && Number.isFinite(o[k]) ? o[k] : 0;
|
|
4711
|
+
return {
|
|
4712
|
+
input_tokens: n("input_tokens"),
|
|
4713
|
+
cached_input_tokens: n("cached_input_tokens"),
|
|
4714
|
+
output_tokens: n("output_tokens"),
|
|
4715
|
+
reasoning_output_tokens: n("reasoning_output_tokens"),
|
|
4716
|
+
total_tokens: n("total_tokens")
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
function delta(cur, prev) {
|
|
4720
|
+
if (!prev) return cur;
|
|
4721
|
+
return {
|
|
4722
|
+
input_tokens: Math.max(0, cur.input_tokens - prev.input_tokens),
|
|
4723
|
+
cached_input_tokens: Math.max(
|
|
4724
|
+
0,
|
|
4725
|
+
cur.cached_input_tokens - prev.cached_input_tokens
|
|
4726
|
+
),
|
|
4727
|
+
output_tokens: Math.max(0, cur.output_tokens - prev.output_tokens),
|
|
4728
|
+
reasoning_output_tokens: Math.max(
|
|
4729
|
+
0,
|
|
4730
|
+
cur.reasoning_output_tokens - prev.reasoning_output_tokens
|
|
4731
|
+
),
|
|
4732
|
+
total_tokens: Math.max(0, cur.total_tokens - prev.total_tokens)
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4735
|
+
function isZero(b) {
|
|
4736
|
+
return b.input_tokens === 0 && b.cached_input_tokens === 0 && b.output_tokens === 0 && b.reasoning_output_tokens === 0 && b.total_tokens === 0;
|
|
4737
|
+
}
|
|
4738
|
+
async function listJsonlFiles(root) {
|
|
4739
|
+
const out = [];
|
|
4740
|
+
async function walk(dir) {
|
|
4741
|
+
let entries;
|
|
4742
|
+
try {
|
|
4743
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
4744
|
+
} catch {
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
for (const e of entries) {
|
|
4748
|
+
const p2 = join3(dir, e.name);
|
|
4749
|
+
if (e.isDirectory()) await walk(p2);
|
|
4750
|
+
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(p2);
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
await walk(root);
|
|
4754
|
+
return out;
|
|
4755
|
+
}
|
|
4756
|
+
async function collectCodex(home = codexHome()) {
|
|
4757
|
+
const sessionRoot = join3(home, "sessions");
|
|
4758
|
+
const archivedRoot = join3(home, "archived_sessions");
|
|
4759
|
+
const activeFiles = await listJsonlFiles(sessionRoot);
|
|
4760
|
+
const archivedFiles = await listJsonlFiles(archivedRoot);
|
|
4761
|
+
const seenRel = new Set(
|
|
4762
|
+
activeFiles.map((f) => relative(sessionRoot, f).replace(/\\/g, "/"))
|
|
4763
|
+
);
|
|
4764
|
+
const files = [
|
|
4765
|
+
...activeFiles,
|
|
4766
|
+
...archivedFiles.filter((f) => {
|
|
4767
|
+
const rel = relative(archivedRoot, f).replace(/\\/g, "/");
|
|
4768
|
+
return !seenRel.has(rel);
|
|
4769
|
+
})
|
|
4770
|
+
];
|
|
4771
|
+
if (files.length === 0) return null;
|
|
4772
|
+
let tokensIn = 0;
|
|
4773
|
+
let tokensOut = 0;
|
|
4774
|
+
let cacheRead = 0;
|
|
4775
|
+
let reasoning = 0;
|
|
4776
|
+
let costUsd2 = 0;
|
|
4777
|
+
let hasUnpricedModel = false;
|
|
4778
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
4779
|
+
const activeDates = /* @__PURE__ */ new Set();
|
|
4780
|
+
const models2 = /* @__PURE__ */ new Set();
|
|
4781
|
+
let firstDate = null;
|
|
4782
|
+
let lastDate = null;
|
|
4783
|
+
let filesParsed = 0;
|
|
4784
|
+
for (const file of files) {
|
|
4785
|
+
filesParsed++;
|
|
4786
|
+
sessions.add(file);
|
|
4787
|
+
let prevTotals = null;
|
|
4788
|
+
let currentModel = null;
|
|
4789
|
+
const rl = createInterface2({
|
|
4790
|
+
input: createReadStream2(file),
|
|
4791
|
+
crlfDelay: Infinity
|
|
4792
|
+
});
|
|
4793
|
+
for await (const line of rl) {
|
|
4794
|
+
if (!line.includes("token_count") && !line.includes("turn_context") && !line.includes("session_meta")) {
|
|
4795
|
+
continue;
|
|
4796
|
+
}
|
|
4797
|
+
let entry;
|
|
4798
|
+
try {
|
|
4799
|
+
entry = JSON.parse(line);
|
|
4800
|
+
} catch {
|
|
4801
|
+
continue;
|
|
4802
|
+
}
|
|
4803
|
+
const ts = typeof entry.timestamp === "string" ? toDateOnly(entry.timestamp) : null;
|
|
4804
|
+
if (entry.type === "turn_context") {
|
|
4805
|
+
const model = entry.payload?.model ?? entry.payload?.info?.model ?? entry.model;
|
|
4806
|
+
if (typeof model === "string" && model) currentModel = model;
|
|
4807
|
+
continue;
|
|
4808
|
+
}
|
|
4809
|
+
if (entry.type === "session_meta") {
|
|
4810
|
+
const id = entry.payload?.id ?? entry.payload?.session_id;
|
|
4811
|
+
if (typeof id === "string" && id) {
|
|
4812
|
+
sessions.delete(file);
|
|
4813
|
+
sessions.add(id);
|
|
4814
|
+
}
|
|
4815
|
+
continue;
|
|
4816
|
+
}
|
|
4817
|
+
if (entry.type !== "event_msg") continue;
|
|
4818
|
+
if (entry.payload?.type !== "token_count") continue;
|
|
4819
|
+
const total = asBucket(entry.payload?.info?.total_token_usage);
|
|
4820
|
+
if (!total) continue;
|
|
4821
|
+
const d = delta(total, prevTotals);
|
|
4822
|
+
prevTotals = total;
|
|
4823
|
+
if (isZero(d)) continue;
|
|
4824
|
+
const cached = d.cached_input_tokens;
|
|
4825
|
+
const nonCachedIn = Math.max(0, d.input_tokens - cached);
|
|
4826
|
+
const outTok = d.output_tokens;
|
|
4827
|
+
const reasonTok = d.reasoning_output_tokens;
|
|
4828
|
+
tokensIn += d.input_tokens;
|
|
4829
|
+
cacheRead += cached;
|
|
4830
|
+
tokensOut += outTok;
|
|
4831
|
+
reasoning += reasonTok;
|
|
4832
|
+
if (currentModel) {
|
|
4833
|
+
models2.add(currentModel);
|
|
4834
|
+
const r = ratesFor(currentModel);
|
|
4835
|
+
if (r) {
|
|
4836
|
+
costUsd2 += (nonCachedIn * r.input + cached * r.cacheRead + outTok * r.output) / 1e6;
|
|
4837
|
+
} else {
|
|
4838
|
+
hasUnpricedModel = true;
|
|
4839
|
+
}
|
|
4840
|
+
} else {
|
|
4841
|
+
hasUnpricedModel = true;
|
|
4842
|
+
}
|
|
4843
|
+
if (ts) {
|
|
4844
|
+
activeDates.add(ts);
|
|
4845
|
+
if (!firstDate || ts < firstDate) firstDate = ts;
|
|
4846
|
+
if (!lastDate || ts > lastDate) lastDate = ts;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
const totalTokens = tokensIn + tokensOut;
|
|
4851
|
+
if (totalTokens === 0 || !firstDate || !lastDate) return null;
|
|
4852
|
+
return {
|
|
4853
|
+
source: {
|
|
4854
|
+
tokensIn,
|
|
4855
|
+
tokensOut,
|
|
4856
|
+
cacheReadTokens: cacheRead,
|
|
4857
|
+
reasoningTokens: reasoning,
|
|
4858
|
+
totalTokens,
|
|
4859
|
+
estimatedCostUsd: Math.round(costUsd2 * 100) / 100,
|
|
4860
|
+
costConfidence: hasUnpricedModel ? "partial" : "estimated",
|
|
4861
|
+
sessionCount: sessions.size,
|
|
4862
|
+
activeDays: activeDates.size,
|
|
4863
|
+
longestStreakDays: longestStreak(activeDates),
|
|
4864
|
+
firstActivityDate: firstDate,
|
|
4865
|
+
lastActivityDate: lastDate,
|
|
4866
|
+
models: [...models2].sort().slice(0, 50)
|
|
4867
|
+
},
|
|
4868
|
+
activeDates,
|
|
4869
|
+
filesParsed
|
|
4870
|
+
};
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4662
4873
|
// src/merge.ts
|
|
4663
|
-
var CLI_VERSION = "0.1.
|
|
4874
|
+
var CLI_VERSION = "0.1.2";
|
|
4664
4875
|
function buildPayload(opts) {
|
|
4665
|
-
const { claudeCode, openrouter, cursor, handle } = opts;
|
|
4876
|
+
const { claudeCode, openrouter, cursor, codex, handle } = opts;
|
|
4666
4877
|
const allDates = /* @__PURE__ */ new Set();
|
|
4667
|
-
for (const r of [claudeCode, openrouter, cursor]) {
|
|
4878
|
+
for (const r of [claudeCode, openrouter, cursor, codex]) {
|
|
4668
4879
|
if (r) for (const d of r.activeDates) allDates.add(d);
|
|
4669
4880
|
}
|
|
4670
|
-
const totalTokens = (claudeCode?.source.totalTokens ?? 0) + (openrouter?.source.totalTokens ?? 0) + (cursor?.source.totalTokens ?? 0);
|
|
4881
|
+
const totalTokens = (claudeCode?.source.totalTokens ?? 0) + (openrouter?.source.totalTokens ?? 0) + (cursor?.source.totalTokens ?? 0) + (codex?.source.totalTokens ?? 0);
|
|
4671
4882
|
const costs = [
|
|
4672
4883
|
claudeCode?.source.estimatedCostUsd,
|
|
4673
|
-
cursor?.source.totalCostUsd
|
|
4884
|
+
cursor?.source.totalCostUsd,
|
|
4885
|
+
codex?.source.estimatedCostUsd
|
|
4674
4886
|
].filter((c) => typeof c === "number");
|
|
4675
4887
|
const totalCostUsd = costs.length > 0 ? Math.round(costs.reduce((a, b) => a + b, 0) * 100) / 100 : null;
|
|
4676
4888
|
const distinctModels = /* @__PURE__ */ new Set([
|
|
4677
4889
|
...claudeCode?.source.models ?? [],
|
|
4678
4890
|
...openrouter?.source.models ?? [],
|
|
4679
|
-
...cursor?.source.models ?? []
|
|
4891
|
+
...cursor?.source.models ?? [],
|
|
4892
|
+
...codex?.source.models ?? []
|
|
4680
4893
|
]);
|
|
4681
4894
|
let first = null;
|
|
4682
4895
|
let last = null;
|
|
@@ -4684,6 +4897,10 @@ function buildPayload(opts) {
|
|
|
4684
4897
|
first = minDate(first, claudeCode.source.firstActivityDate);
|
|
4685
4898
|
last = maxDate(last, claudeCode.source.lastActivityDate);
|
|
4686
4899
|
}
|
|
4900
|
+
if (codex) {
|
|
4901
|
+
first = minDate(first, codex.source.firstActivityDate);
|
|
4902
|
+
last = maxDate(last, codex.source.lastActivityDate);
|
|
4903
|
+
}
|
|
4687
4904
|
if (cursor) {
|
|
4688
4905
|
first = minDate(first, cursor.source.dateRange.from);
|
|
4689
4906
|
last = maxDate(last, cursor.source.dateRange.to);
|
|
@@ -4700,7 +4917,8 @@ function buildPayload(opts) {
|
|
|
4700
4917
|
sources: {
|
|
4701
4918
|
...claudeCode ? { claudeCode: claudeCode.source } : {},
|
|
4702
4919
|
...openrouter ? { openrouter: openrouter.source } : {},
|
|
4703
|
-
...cursor ? { cursor: cursor.source } : {}
|
|
4920
|
+
...cursor ? { cursor: cursor.source } : {},
|
|
4921
|
+
...codex ? { codex: codex.source } : {}
|
|
4704
4922
|
},
|
|
4705
4923
|
aggregate: {
|
|
4706
4924
|
totalTokens,
|
|
@@ -4708,7 +4926,7 @@ function buildPayload(opts) {
|
|
|
4708
4926
|
totalActiveDays: allDates.size,
|
|
4709
4927
|
longestStreakDays: longestStreak(allDates),
|
|
4710
4928
|
distinctModels: [...distinctModels].sort().slice(0, 50),
|
|
4711
|
-
sourceCount: [claudeCode, openrouter, cursor].filter(Boolean).length,
|
|
4929
|
+
sourceCount: [claudeCode, openrouter, cursor, codex].filter(Boolean).length,
|
|
4712
4930
|
firstActivityDate: first ?? today,
|
|
4713
4931
|
lastActivityDate: last ?? today
|
|
4714
4932
|
},
|
|
@@ -4730,6 +4948,7 @@ var { values: args } = parseArgs({
|
|
|
4730
4948
|
"no-claude-code": { type: "boolean", default: false },
|
|
4731
4949
|
"no-openrouter": { type: "boolean", default: false },
|
|
4732
4950
|
"no-cursor": { type: "boolean", default: false },
|
|
4951
|
+
"no-codex": { type: "boolean", default: false },
|
|
4733
4952
|
handle: { type: "string" },
|
|
4734
4953
|
endpoint: { type: "string" },
|
|
4735
4954
|
open: { type: "boolean", default: false },
|
|
@@ -4764,6 +4983,7 @@ Options:
|
|
|
4764
4983
|
--cursor-cookie <t> Cursor web session token (auto-detected if omitted)
|
|
4765
4984
|
--cursor-csv <path> usage CSV export (fallback if the API path fails)
|
|
4766
4985
|
--no-claude-code skip Claude Code logs
|
|
4986
|
+
--no-codex skip OpenAI Codex CLI logs
|
|
4767
4987
|
--no-openrouter skip OpenRouter
|
|
4768
4988
|
--no-cursor skip Cursor
|
|
4769
4989
|
--handle <name> display name on the card (unverified)
|
|
@@ -4789,6 +5009,19 @@ Options:
|
|
|
4789
5009
|
s2.stop("Claude Code: no local logs found \u2014 skipping");
|
|
4790
5010
|
}
|
|
4791
5011
|
}
|
|
5012
|
+
let codex = null;
|
|
5013
|
+
if (!args["no-codex"]) {
|
|
5014
|
+
const s2 = p.spinner();
|
|
5015
|
+
s2.start(`Scanning Codex logs (${codexSessionsRoots().join(", ")})`);
|
|
5016
|
+
codex = await collectCodex();
|
|
5017
|
+
if (codex) {
|
|
5018
|
+
s2.stop(
|
|
5019
|
+
`Codex: ${fmt(codex.source.totalTokens)} tokens across ${codex.source.sessionCount} sessions (${codex.filesParsed} log files)`
|
|
5020
|
+
);
|
|
5021
|
+
} else {
|
|
5022
|
+
s2.stop("Codex: no local session logs found \u2014 skipping");
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
4792
5025
|
let openrouter = null;
|
|
4793
5026
|
if (!args["no-openrouter"]) {
|
|
4794
5027
|
let key = args["openrouter-key"] ?? process.env.OPENROUTER_API_KEY ?? "";
|
|
@@ -4876,9 +5109,9 @@ Options:
|
|
|
4876
5109
|
}
|
|
4877
5110
|
}
|
|
4878
5111
|
}
|
|
4879
|
-
if (!claudeCode && !openrouter && !cursor) {
|
|
5112
|
+
if (!claudeCode && !openrouter && !cursor && !codex) {
|
|
4880
5113
|
bail(
|
|
4881
|
-
"No usage sources found or supplied. Nothing to build a card from.\nTry --openrouter-key or --cursor-csv
|
|
5114
|
+
"No usage sources found or supplied. Nothing to build a card from.\nTry Claude Code, Codex CLI, --openrouter-key, or --cursor-csv."
|
|
4882
5115
|
);
|
|
4883
5116
|
}
|
|
4884
5117
|
let handle = args.handle ?? null;
|
|
@@ -4893,6 +5126,7 @@ Options:
|
|
|
4893
5126
|
claudeCode,
|
|
4894
5127
|
openrouter,
|
|
4895
5128
|
cursor,
|
|
5129
|
+
codex,
|
|
4896
5130
|
handle
|
|
4897
5131
|
});
|
|
4898
5132
|
const parsed = snapshotPayloadSchema.safeParse(payload);
|