@hasna/economy 0.5.10 → 0.5.11
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/CHANGELOG.md +6 -0
- package/README.md +22 -0
- package/dist/chunks/{index-w4r6xs02.js → index-akxt4n8x.js} +1 -1
- package/dist/chunks/{index-4v5r3c22.js → index-b36bq51x.js} +1 -1
- package/dist/chunks/{index-t777mc83.js → index-mgnzgf1m.js} +1 -1
- package/dist/chunks/{index-twn8ncdp.js → index-v35tm3xg.js} +1 -1
- package/dist/chunks/index-yv28hyk7.js +1064 -0
- package/dist/chunks/{serve-bqn5j46v.js → serve-kk3n0p7j.js} +2 -2
- package/dist/chunks/{tui-5s5w6yjd.js → tui-hxjczmv5.js} +3 -3
- package/dist/chunks/watch-ewed2srf.js +307 -0
- package/dist/cli/commands/watch.d.ts +2 -0
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/index.js +37 -1071
- package/dist/index.js +1 -1
- package/dist/lib/watch-collector.d.ts +11 -0
- package/dist/lib/watch-collector.d.ts.map +1 -0
- package/dist/lib/watch-paths.d.ts +2 -1
- package/dist/lib/watch-paths.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/otel/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/service-worker.js +1 -1
- package/docs/cli.md +1 -1
- package/package.json +1 -1
- package/dist/chunks/watch-7pf8xj1q.js +0 -196
package/dist/index.js
CHANGED
|
@@ -111,7 +111,7 @@ import { randomUUID } from "crypto";
|
|
|
111
111
|
// package.json
|
|
112
112
|
var package_default = {
|
|
113
113
|
name: "@hasna/economy",
|
|
114
|
-
version: "0.5.
|
|
114
|
+
version: "0.5.11",
|
|
115
115
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
116
116
|
repository: {
|
|
117
117
|
type: "git",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** One collector owns its timers and never overlaps durable ingestion calls. */
|
|
2
|
+
export interface WatchCollectorOptions {
|
|
3
|
+
intervalMs: number;
|
|
4
|
+
collect: () => Promise<void>;
|
|
5
|
+
onError: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWatchCollector(options: WatchCollectorOptions): {
|
|
8
|
+
request: () => void;
|
|
9
|
+
stop(): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=watch-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-collector.d.ts","sourceRoot":"","sources":["../../src/lib/watch-collector.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB;mBA6B7C,IAAI;YAaR,OAAO,CAAC,IAAI,CAAC;EAW9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-paths.d.ts","sourceRoot":"","sources":["../../src/lib/watch-paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"watch-paths.d.ts","sourceRoot":"","sources":["../../src/lib/watch-paths.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAExC,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,CASrD"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -86,7 +86,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
86
86
|
// package.json
|
|
87
87
|
var package_default = {
|
|
88
88
|
name: "@hasna/economy",
|
|
89
|
-
version: "0.5.
|
|
89
|
+
version: "0.5.11",
|
|
90
90
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
91
91
|
repository: {
|
|
92
92
|
type: "git",
|
package/dist/otel/index.js
CHANGED
|
@@ -3523,7 +3523,7 @@ async function pushIngestRows(cloud, scratch) {
|
|
|
3523
3523
|
// package.json
|
|
3524
3524
|
var package_default = {
|
|
3525
3525
|
name: "@hasna/economy",
|
|
3526
|
-
version: "0.5.
|
|
3526
|
+
version: "0.5.11",
|
|
3527
3527
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
3528
3528
|
repository: {
|
|
3529
3529
|
type: "git",
|
package/dist/server/index.js
CHANGED
|
@@ -5578,7 +5578,7 @@ function isAuthorizedRequest(req, path) {
|
|
|
5578
5578
|
// package.json
|
|
5579
5579
|
var package_default = {
|
|
5580
5580
|
name: "@hasna/economy",
|
|
5581
|
-
version: "0.5.
|
|
5581
|
+
version: "0.5.11",
|
|
5582
5582
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
5583
5583
|
repository: {
|
|
5584
5584
|
type: "git",
|
|
@@ -7454,7 +7454,7 @@ import { randomUUID } from "crypto";
|
|
|
7454
7454
|
// package.json
|
|
7455
7455
|
var package_default = {
|
|
7456
7456
|
name: "@hasna/economy",
|
|
7457
|
-
version: "0.5.
|
|
7457
|
+
version: "0.5.11",
|
|
7458
7458
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
7459
7459
|
repository: {
|
|
7460
7460
|
type: "git",
|
package/docs/cli.md
CHANGED
|
@@ -29,7 +29,7 @@ The supported coding-agent values are `claude`, `takumi`, `codex`, `gemini`, `op
|
|
|
29
29
|
| `economy brief` | Fleet brief with `--since <24h|7d|ISO-date>`, `--machine <id|all>`, or `--json`. |
|
|
30
30
|
| `economy usage [period]` | Quota/usage snapshots with `--agent`, `--limit`, `--verbose`, or `--json`. |
|
|
31
31
|
| `economy savings [period]` | Subscription-versus-API-equivalent savings with `--agent` or `--json`. |
|
|
32
|
-
| `economy watch` |
|
|
32
|
+
| `economy watch` | Stream stored costs. Explicit `--daemon` also collects local provider files through the configured store, including the hosted API. `--sync-interval` bounds collection frequency and periodic fallback; `--interval` controls report refresh. Also accepts `--agent`, `--notify`, and `--no-notifications`. Ordinary hosted watch remains read-only. This foreground command does not install a startup service. |
|
|
33
33
|
| `economy status` | Print one-line spend, fleet, storage, top-agent, and available quota status. |
|
|
34
34
|
| `economy doctor` | Check source paths/token availability, storage mode, pricing gaps, deduplication, and billing drift. |
|
|
35
35
|
| `economy transport` | Report the resolved client transport and credential SOURCE (never the key): the `/v1` authority, the URL/key source names, and the credential tier from the `@hasna/contracts` chain; `--json` for the full report. Exits 0 even when unconfigured — the refusal is the report. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
agentPaths,
|
|
4
|
-
syncAll
|
|
5
|
-
} from "./index-t777mc83.js";
|
|
6
|
-
import {
|
|
7
|
-
getStore,
|
|
8
|
-
isCloudStore
|
|
9
|
-
} from "./index-twn8ncdp.js";
|
|
10
|
-
import"./index-4v5r3c22.js";
|
|
11
|
-
import {
|
|
12
|
-
formatSummaryValue
|
|
13
|
-
} from "./index-zgvmrqyc.js";
|
|
14
|
-
import {
|
|
15
|
-
__require
|
|
16
|
-
} from "./index-p35cyhc4.js";
|
|
17
|
-
|
|
18
|
-
// src/cli/commands/watch.ts
|
|
19
|
-
import chalk from "chalk";
|
|
20
|
-
import { watch } from "fs";
|
|
21
|
-
|
|
22
|
-
// src/lib/watch-paths.ts
|
|
23
|
-
import { existsSync } from "fs";
|
|
24
|
-
function getWatchPaths() {
|
|
25
|
-
const p = agentPaths();
|
|
26
|
-
const candidates = [
|
|
27
|
-
p.claudeProjects,
|
|
28
|
-
p.takumiProjects,
|
|
29
|
-
p.codexDir,
|
|
30
|
-
p.geminiTmp,
|
|
31
|
-
p.geminiHistory,
|
|
32
|
-
p.opencodeMessages,
|
|
33
|
-
p.piSessions,
|
|
34
|
-
p.hermesDir
|
|
35
|
-
];
|
|
36
|
-
return candidates.filter((path) => existsSync(path));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/cli/commands/notification.ts
|
|
40
|
-
import { execFileSync } from "child_process";
|
|
41
|
-
function appleScriptString(value) {
|
|
42
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
43
|
-
}
|
|
44
|
-
function notificationScript(title, body) {
|
|
45
|
-
return `display notification ${appleScriptString(body)} with title ${appleScriptString(title)}`;
|
|
46
|
-
}
|
|
47
|
-
function sendNotification(title, body, execFile = execFileSync) {
|
|
48
|
-
try {
|
|
49
|
-
execFile("osascript", ["-e", notificationScript(title, body)], { stdio: "ignore" });
|
|
50
|
-
} catch {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/cli/commands/watch.ts
|
|
54
|
-
function fmt(usd) {
|
|
55
|
-
return chalk.green(`$${usd.toFixed(4)}`);
|
|
56
|
-
}
|
|
57
|
-
function renderHeader(today, week, mode) {
|
|
58
|
-
process.stdout.write("\x1B[H\x1B[2J");
|
|
59
|
-
console.log(chalk.bold.cyan(" economy watch") + chalk.dim(` \u2014 stored usage stream (${mode})`));
|
|
60
|
-
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
61
|
-
console.log(` Today: ${formatSummaryValue(today)} Week: ${formatSummaryValue(week)}`);
|
|
62
|
-
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
63
|
-
console.log(" UTC; selected stored records only; source coverage unverified");
|
|
64
|
-
console.log(chalk.dim(" [agent] cost model tokens project"));
|
|
65
|
-
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
66
|
-
}
|
|
67
|
-
function agentLabel(agent) {
|
|
68
|
-
if (agent === "claude")
|
|
69
|
-
return chalk.blue("[claude]");
|
|
70
|
-
if (agent === "codex")
|
|
71
|
-
return chalk.yellow("[codex] ");
|
|
72
|
-
if (agent === "gemini")
|
|
73
|
-
return chalk.green("[gemini]");
|
|
74
|
-
if (agent === "takumi")
|
|
75
|
-
return chalk.magenta("[takumi]");
|
|
76
|
-
if (agent === "opencode")
|
|
77
|
-
return chalk.cyan("[opncde]");
|
|
78
|
-
if (agent === "cursor")
|
|
79
|
-
return chalk.white("[cursor]");
|
|
80
|
-
if (agent === "pi")
|
|
81
|
-
return chalk.white("[pi ]");
|
|
82
|
-
if (agent === "hermes")
|
|
83
|
-
return chalk.white("[hermes]");
|
|
84
|
-
return chalk.gray(`[${agent.slice(0, 6).padEnd(6)}]`);
|
|
85
|
-
}
|
|
86
|
-
async function watchCosts(opts) {
|
|
87
|
-
const store = getStore();
|
|
88
|
-
const cloud = isCloudStore();
|
|
89
|
-
const db = cloud ? null : (await import("./sqlite-store-r219qrjt.js")).openDatabase();
|
|
90
|
-
let lastCheck = new Date(Date.now() - opts.interval * 1000).toISOString();
|
|
91
|
-
const lines = [];
|
|
92
|
-
const MAX_LINES = 20;
|
|
93
|
-
let sessionCumulativeCost = 0;
|
|
94
|
-
let notifyThresholdFired = 0;
|
|
95
|
-
let ingestPending = false;
|
|
96
|
-
let ingestTimer = null;
|
|
97
|
-
const daemon = opts.daemon && !cloud;
|
|
98
|
-
const mode = cloud ? "cloud" : daemon ? "daemon" : "poll";
|
|
99
|
-
const initialSummaryToday = await store.summary("today");
|
|
100
|
-
const initialSummaryWeek = await store.summary("week");
|
|
101
|
-
renderHeader(initialSummaryToday, initialSummaryWeek, mode);
|
|
102
|
-
if (daemon) {
|
|
103
|
-
const paths = getWatchPaths();
|
|
104
|
-
console.log(chalk.dim(`
|
|
105
|
-
Watching ${paths.length} paths \u2014 sync on change, refresh every ${opts.interval}s
|
|
106
|
-
`));
|
|
107
|
-
for (const p of paths) {
|
|
108
|
-
try {
|
|
109
|
-
watch(p, { recursive: true }, () => scheduleIngest());
|
|
110
|
-
} catch {
|
|
111
|
-
try {
|
|
112
|
-
watch(p, () => scheduleIngest());
|
|
113
|
-
} catch {}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} else if (cloud) {
|
|
117
|
-
console.log(chalk.dim(`
|
|
118
|
-
Streaming from cloud API every ${opts.interval}s \u2014 Ctrl+C to exit
|
|
119
|
-
`));
|
|
120
|
-
} else {
|
|
121
|
-
console.log(chalk.dim(`
|
|
122
|
-
Polling every ${opts.interval}s \u2014 Ctrl+C to exit
|
|
123
|
-
`));
|
|
124
|
-
}
|
|
125
|
-
function scheduleIngest() {
|
|
126
|
-
ingestPending = true;
|
|
127
|
-
if (ingestTimer)
|
|
128
|
-
return;
|
|
129
|
-
ingestTimer = setTimeout(() => {
|
|
130
|
-
ingestTimer = null;
|
|
131
|
-
poll();
|
|
132
|
-
}, 500);
|
|
133
|
-
}
|
|
134
|
-
async function poll() {
|
|
135
|
-
const now = new Date().toISOString();
|
|
136
|
-
const shouldSync = !daemon || ingestPending;
|
|
137
|
-
ingestPending = false;
|
|
138
|
-
if (shouldSync && db)
|
|
139
|
-
await syncAll(db);
|
|
140
|
-
const newRequests = await store.recentRequests(lastCheck);
|
|
141
|
-
lastCheck = now;
|
|
142
|
-
for (const req of newRequests) {
|
|
143
|
-
if (opts.agent && req.agent !== opts.agent)
|
|
144
|
-
continue;
|
|
145
|
-
const tokens = req.input_tokens + req.output_tokens;
|
|
146
|
-
const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
|
|
147
|
-
const line = ` ${agentLabel(req.agent)} ${fmt(req.cost_usd).padEnd(14)}${req.model.substring(0, 24).padEnd(26)}${tokStr.padEnd(10)}${req.session_id.substring(0, 12)}`;
|
|
148
|
-
lines.push(line);
|
|
149
|
-
if (lines.length > MAX_LINES)
|
|
150
|
-
lines.shift();
|
|
151
|
-
if (req.cost_usd > 1) {
|
|
152
|
-
sendNotification("economy: high cost", `$${req.cost_usd.toFixed(2)} on ${req.model}`);
|
|
153
|
-
}
|
|
154
|
-
if (opts.notify && opts.notify > 0) {
|
|
155
|
-
sessionCumulativeCost += req.cost_usd;
|
|
156
|
-
const crossedThresholds = Math.floor(sessionCumulativeCost / opts.notify);
|
|
157
|
-
if (crossedThresholds > notifyThresholdFired) {
|
|
158
|
-
notifyThresholdFired = crossedThresholds;
|
|
159
|
-
sendNotification("Cost Alert", `Economy: $${sessionCumulativeCost.toFixed(2)} spent this session`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const today = await store.summary("today");
|
|
164
|
-
const week = await store.summary("week");
|
|
165
|
-
renderHeader(today, week, mode);
|
|
166
|
-
for (const line of lines)
|
|
167
|
-
console.log(line);
|
|
168
|
-
if (lines.length === 0)
|
|
169
|
-
console.log(chalk.dim(" Waiting for new requests..."));
|
|
170
|
-
const suffix = daemon ? `file watch + ${opts.interval}s refresh` : cloud ? `cloud stream every ${opts.interval}s` : `polling every ${opts.interval}s`;
|
|
171
|
-
console.log(chalk.dim(`
|
|
172
|
-
Last updated: ${new Date().toLocaleTimeString()} \u2014 ${suffix} \u2014 Ctrl+C to exit`));
|
|
173
|
-
}
|
|
174
|
-
if (daemon)
|
|
175
|
-
ingestPending = true;
|
|
176
|
-
await poll();
|
|
177
|
-
const timer = setInterval(() => {
|
|
178
|
-
poll().catch((e) => {
|
|
179
|
-
console.log(chalk.dim(`
|
|
180
|
-
Poll error: ${e instanceof Error ? e.message : String(e)} \u2014 retrying next tick`));
|
|
181
|
-
});
|
|
182
|
-
}, opts.interval * 1000);
|
|
183
|
-
process.on("SIGINT", () => {
|
|
184
|
-
clearInterval(timer);
|
|
185
|
-
if (ingestTimer)
|
|
186
|
-
clearTimeout(ingestTimer);
|
|
187
|
-
console.log(chalk.dim(`
|
|
188
|
-
|
|
189
|
-
Stopped watching.`));
|
|
190
|
-
process.exit(0);
|
|
191
|
-
});
|
|
192
|
-
await new Promise(() => {});
|
|
193
|
-
}
|
|
194
|
-
export {
|
|
195
|
-
watchCosts
|
|
196
|
-
};
|