nomoreide 0.1.64 → 0.1.65
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/core/usage-history.d.ts +81 -0
- package/dist/core/usage-history.js +149 -0
- package/dist/core/usage-history.js.map +1 -0
- package/dist/web/client/assets/{code-editor-BGMboZmn.js → code-editor-DPTOoTni.js} +1 -1
- package/dist/web/client/assets/index-BylTX_ZJ.css +1 -0
- package/dist/web/client/assets/{index-BTz_ITED.js → index-CuhlHDjR.js} +97 -97
- package/dist/web/client/index.html +2 -2
- package/dist/web/routes/agent-routes.js +8 -0
- package/dist/web/routes/agent-routes.js.map +1 -1
- package/dist/web/routes/context.d.ts +2 -0
- package/dist/web/routes/context.js.map +1 -1
- package/dist/web/server.js +21 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/client/assets/index-Ca7-Vu2E.css +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal shape this store needs from a usage reading. The web layer's
|
|
3
|
+
* `UsageInfo` is structurally assignable to it, so core never imports the web
|
|
4
|
+
* layer (no dependency cycle).
|
|
5
|
+
*/
|
|
6
|
+
export interface UsageSnapshotInput {
|
|
7
|
+
claude?: {
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
costUSD: number;
|
|
10
|
+
inputTokens: number;
|
|
11
|
+
outputTokens: number;
|
|
12
|
+
models?: Array<{
|
|
13
|
+
model: string;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
codex?: {
|
|
17
|
+
timestamp?: string;
|
|
18
|
+
inputTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export type UsageSource = "claude" | "codex";
|
|
24
|
+
export interface UsageHistoryEntry {
|
|
25
|
+
at: string;
|
|
26
|
+
source: UsageSource;
|
|
27
|
+
/** Claude: the agent session id. Codex: the reading's source timestamp. */
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
inputTokens: number;
|
|
30
|
+
outputTokens: number;
|
|
31
|
+
totalTokens: number;
|
|
32
|
+
/** Real per-session cost for Claude; 0 for Codex (no honest price available). */
|
|
33
|
+
costUSD: number;
|
|
34
|
+
models?: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface UsageDayBucket {
|
|
37
|
+
date: string;
|
|
38
|
+
costUSD: number;
|
|
39
|
+
runs: number;
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
}
|
|
42
|
+
export interface UsageHistorySummary {
|
|
43
|
+
/** Distinct Claude sessions recorded — i.e. agent runs. */
|
|
44
|
+
runs: number;
|
|
45
|
+
totalCostUSD: number;
|
|
46
|
+
totalInputTokens: number;
|
|
47
|
+
totalOutputTokens: number;
|
|
48
|
+
totalTokens: number;
|
|
49
|
+
firstAt?: string;
|
|
50
|
+
lastAt?: string;
|
|
51
|
+
byDay: UsageDayBucket[];
|
|
52
|
+
/** Latest cumulative Codex token total (Codex reports lifetime totals, not per-run). */
|
|
53
|
+
codexTotalTokens: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Append-only token/cost history at `.nomoreide/usage-history.jsonl`. A sampler
|
|
57
|
+
* calls {@link record} with the latest usage reading; a row is appended only
|
|
58
|
+
* when a source's totals actually change, so an idle agent doesn't grow the file
|
|
59
|
+
* each tick. Claude's `costUSD` is a real per-session figure (it resets and the
|
|
60
|
+
* session id changes on a new run), so the summary groups by session to answer
|
|
61
|
+
* "this work cost ~$X over N runs".
|
|
62
|
+
*/
|
|
63
|
+
export declare class UsageHistory {
|
|
64
|
+
private readonly filePath;
|
|
65
|
+
private seeded;
|
|
66
|
+
private readonly lastKey;
|
|
67
|
+
constructor(options: {
|
|
68
|
+
filePath: string;
|
|
69
|
+
});
|
|
70
|
+
/** Append a row per source whose totals changed since the last recorded row. */
|
|
71
|
+
record(input: UsageSnapshotInput): Promise<UsageHistoryEntry[]>;
|
|
72
|
+
list(options?: {
|
|
73
|
+
since?: string;
|
|
74
|
+
}): Promise<UsageHistoryEntry[]>;
|
|
75
|
+
summary(options?: {
|
|
76
|
+
since?: string;
|
|
77
|
+
}): Promise<UsageHistorySummary>;
|
|
78
|
+
/** Prime dedup keys from the last recorded row per source so a restart doesn't re-append. */
|
|
79
|
+
private seed;
|
|
80
|
+
private loadEntries;
|
|
81
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Append-only token/cost history at `.nomoreide/usage-history.jsonl`. A sampler
|
|
5
|
+
* calls {@link record} with the latest usage reading; a row is appended only
|
|
6
|
+
* when a source's totals actually change, so an idle agent doesn't grow the file
|
|
7
|
+
* each tick. Claude's `costUSD` is a real per-session figure (it resets and the
|
|
8
|
+
* session id changes on a new run), so the summary groups by session to answer
|
|
9
|
+
* "this work cost ~$X over N runs".
|
|
10
|
+
*/
|
|
11
|
+
export class UsageHistory {
|
|
12
|
+
filePath;
|
|
13
|
+
seeded = false;
|
|
14
|
+
lastKey = {};
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.filePath = options.filePath;
|
|
17
|
+
}
|
|
18
|
+
/** Append a row per source whose totals changed since the last recorded row. */
|
|
19
|
+
async record(input) {
|
|
20
|
+
if (!this.seeded)
|
|
21
|
+
await this.seed();
|
|
22
|
+
const at = new Date().toISOString();
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (input.claude && hasClaudeData(input.claude)) {
|
|
25
|
+
candidates.push({
|
|
26
|
+
at,
|
|
27
|
+
source: "claude",
|
|
28
|
+
sessionId: input.claude.sessionId,
|
|
29
|
+
inputTokens: input.claude.inputTokens,
|
|
30
|
+
outputTokens: input.claude.outputTokens,
|
|
31
|
+
totalTokens: input.claude.inputTokens + input.claude.outputTokens,
|
|
32
|
+
costUSD: input.claude.costUSD,
|
|
33
|
+
models: input.claude.models?.map((m) => m.model),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (input.codex && input.codex.totalTokens > 0) {
|
|
37
|
+
candidates.push({
|
|
38
|
+
at,
|
|
39
|
+
source: "codex",
|
|
40
|
+
sessionId: input.codex.timestamp,
|
|
41
|
+
inputTokens: input.codex.inputTokens,
|
|
42
|
+
outputTokens: input.codex.outputTokens,
|
|
43
|
+
totalTokens: input.codex.totalTokens,
|
|
44
|
+
costUSD: 0,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const appended = [];
|
|
48
|
+
for (const entry of candidates) {
|
|
49
|
+
const key = entryKey(entry);
|
|
50
|
+
if (this.lastKey[entry.source] === key)
|
|
51
|
+
continue;
|
|
52
|
+
this.lastKey[entry.source] = key;
|
|
53
|
+
appended.push(entry);
|
|
54
|
+
}
|
|
55
|
+
if (appended.length > 0) {
|
|
56
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
57
|
+
await appendFile(this.filePath, `${appended.map((e) => JSON.stringify(e)).join("\n")}\n`);
|
|
58
|
+
}
|
|
59
|
+
return appended;
|
|
60
|
+
}
|
|
61
|
+
async list(options = {}) {
|
|
62
|
+
const entries = await this.loadEntries();
|
|
63
|
+
const since = options.since;
|
|
64
|
+
return since ? entries.filter((entry) => entry.at >= since) : entries;
|
|
65
|
+
}
|
|
66
|
+
async summary(options = {}) {
|
|
67
|
+
const entries = await this.list(options);
|
|
68
|
+
const codex = entries.filter((e) => e.source === "codex");
|
|
69
|
+
// Each Claude session contributes its final (max-cost) snapshot once.
|
|
70
|
+
const bySession = new Map();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.source !== "claude")
|
|
73
|
+
continue;
|
|
74
|
+
const key = entry.sessionId ?? entry.at;
|
|
75
|
+
const prev = bySession.get(key);
|
|
76
|
+
if (!prev || entry.costUSD >= prev.costUSD)
|
|
77
|
+
bySession.set(key, entry);
|
|
78
|
+
}
|
|
79
|
+
const sessions = [...bySession.values()];
|
|
80
|
+
let totalCostUSD = 0;
|
|
81
|
+
let totalInputTokens = 0;
|
|
82
|
+
let totalOutputTokens = 0;
|
|
83
|
+
let totalTokens = 0;
|
|
84
|
+
const dayMap = new Map();
|
|
85
|
+
for (const session of sessions) {
|
|
86
|
+
totalCostUSD += session.costUSD;
|
|
87
|
+
totalInputTokens += session.inputTokens;
|
|
88
|
+
totalOutputTokens += session.outputTokens;
|
|
89
|
+
totalTokens += session.totalTokens;
|
|
90
|
+
const date = session.at.slice(0, 10);
|
|
91
|
+
const bucket = dayMap.get(date) ?? { date, costUSD: 0, runs: 0, totalTokens: 0 };
|
|
92
|
+
bucket.costUSD += session.costUSD;
|
|
93
|
+
bucket.runs += 1;
|
|
94
|
+
bucket.totalTokens += session.totalTokens;
|
|
95
|
+
dayMap.set(date, bucket);
|
|
96
|
+
}
|
|
97
|
+
const ats = entries.map((e) => e.at).sort();
|
|
98
|
+
return {
|
|
99
|
+
runs: sessions.length,
|
|
100
|
+
totalCostUSD,
|
|
101
|
+
totalInputTokens,
|
|
102
|
+
totalOutputTokens,
|
|
103
|
+
totalTokens,
|
|
104
|
+
firstAt: ats[0],
|
|
105
|
+
lastAt: ats[ats.length - 1],
|
|
106
|
+
byDay: [...dayMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
107
|
+
codexTotalTokens: codex.length > 0 ? Math.max(...codex.map((e) => e.totalTokens)) : 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Prime dedup keys from the last recorded row per source so a restart doesn't re-append. */
|
|
111
|
+
async seed() {
|
|
112
|
+
this.seeded = true;
|
|
113
|
+
for (const entry of await this.loadEntries()) {
|
|
114
|
+
this.lastKey[entry.source] = entryKey(entry);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async loadEntries() {
|
|
118
|
+
let raw;
|
|
119
|
+
try {
|
|
120
|
+
raw = await readFile(this.filePath, "utf8");
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
const entries = [];
|
|
126
|
+
for (const line of raw.split("\n")) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed)
|
|
129
|
+
continue;
|
|
130
|
+
try {
|
|
131
|
+
entries.push(JSON.parse(trimmed));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// skip a corrupt line
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return entries;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function hasClaudeData(claude) {
|
|
141
|
+
return claude.costUSD > 0 || claude.inputTokens > 0 || claude.outputTokens > 0;
|
|
142
|
+
}
|
|
143
|
+
function entryKey(entry) {
|
|
144
|
+
if (entry.source === "codex") {
|
|
145
|
+
return `codex:${entry.sessionId ?? ""}:${entry.totalTokens}`;
|
|
146
|
+
}
|
|
147
|
+
return `claude:${entry.sessionId ?? ""}:${entry.costUSD}:${entry.inputTokens}:${entry.outputTokens}`;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=usage-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-history.js","sourceRoot":"","sources":["../../src/core/usage-history.ts"],"names":[],"mappings":"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"}
|