@hasna/economy 0.2.17 → 0.2.18
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/LICENSE +186 -13
- package/README.md +186 -13
- package/dashboard/dist/assets/index-5mUN0CPj.css +1 -0
- package/dashboard/dist/assets/index-L1FgNQ4t.js +93 -0
- package/dashboard/dist/index.html +14 -0
- package/dashboard/dist/logo.jpg +0 -0
- package/dashboard/dist/vite.svg +1 -0
- package/dist/cli/index.js +134 -783
- package/dist/db/database.d.ts +1 -23
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/index.js +54 -206
- package/dist/ingest/claude.d.ts +1 -6
- package/dist/ingest/claude.d.ts.map +1 -1
- package/dist/ingest/codex.d.ts +1 -1
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/ingest/gemini.d.ts +1 -1
- package/dist/ingest/gemini.d.ts.map +1 -1
- package/dist/lib/pricing.d.ts +1 -1
- package/dist/lib/pricing.d.ts.map +1 -1
- package/dist/lib/webhooks.d.ts +1 -1
- package/dist/lib/webhooks.d.ts.map +1 -1
- package/dist/mcp/index.js +384 -600
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +63 -357
- package/dist/server/serve.d.ts +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -4
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +7 -3
- package/dist/ingest/billing.d.ts +0 -18
- package/dist/ingest/billing.d.ts.map +0 -1
- package/dist/lib/package-metadata.d.ts +0 -8
- package/dist/lib/package-metadata.d.ts.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
1
|
// @bun
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __returnValue = (v) => v;
|
|
@@ -79,7 +78,6 @@ var DEFAULT_PRICING;
|
|
|
79
78
|
var init_pricing = __esm(() => {
|
|
80
79
|
init_database();
|
|
81
80
|
DEFAULT_PRICING = {
|
|
82
|
-
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
83
81
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
84
82
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
85
83
|
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
@@ -90,55 +88,28 @@ var init_pricing = __esm(() => {
|
|
|
90
88
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
91
89
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
92
90
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
93
|
-
"gemini-3.1-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
94
|
-
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
|
-
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
96
91
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
92
|
+
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
93
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
98
94
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
100
|
-
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
101
|
-
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
102
95
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
103
|
-
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
104
96
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
105
97
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
106
|
-
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
107
|
-
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
108
98
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
109
99
|
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
110
100
|
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
111
101
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
112
102
|
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
113
103
|
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
114
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
115
|
-
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
116
|
-
"qwen3.6": { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
117
|
-
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
118
|
-
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
119
|
-
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
120
|
-
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
121
|
-
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
122
|
-
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
123
|
-
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
124
|
-
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
104
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
125
105
|
};
|
|
126
106
|
});
|
|
127
107
|
|
|
128
108
|
// src/db/database.ts
|
|
129
109
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
130
110
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
131
|
-
import { hostname } from "os";
|
|
132
111
|
import { homedir } from "os";
|
|
133
112
|
import { join } from "path";
|
|
134
|
-
function getMachineId() {
|
|
135
|
-
if (process.env["ECONOMY_MACHINE_ID"])
|
|
136
|
-
return process.env["ECONOMY_MACHINE_ID"];
|
|
137
|
-
const h = hostname().toLowerCase();
|
|
138
|
-
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
139
|
-
return h.split(".")[0];
|
|
140
|
-
return h.split(".")[0];
|
|
141
|
-
}
|
|
142
113
|
function getDataDir() {
|
|
143
114
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
144
115
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -171,7 +142,6 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
171
142
|
}
|
|
172
143
|
const db = new Database(path);
|
|
173
144
|
db.exec("PRAGMA journal_mode = WAL");
|
|
174
|
-
db.exec("PRAGMA busy_timeout = 5000");
|
|
175
145
|
db.exec("PRAGMA foreign_keys = ON");
|
|
176
146
|
initSchema(db);
|
|
177
147
|
if (!skipSeed) {
|
|
@@ -193,8 +163,7 @@ function initSchema(db) {
|
|
|
193
163
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
194
164
|
duration_ms INTEGER DEFAULT 0,
|
|
195
165
|
timestamp TEXT NOT NULL,
|
|
196
|
-
source_request_id TEXT
|
|
197
|
-
machine_id TEXT DEFAULT ''
|
|
166
|
+
source_request_id TEXT
|
|
198
167
|
);
|
|
199
168
|
|
|
200
169
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -206,8 +175,7 @@ function initSchema(db) {
|
|
|
206
175
|
ended_at TEXT,
|
|
207
176
|
total_cost_usd REAL DEFAULT 0,
|
|
208
177
|
total_tokens INTEGER DEFAULT 0,
|
|
209
|
-
request_count INTEGER DEFAULT 0
|
|
210
|
-
machine_id TEXT DEFAULT ''
|
|
178
|
+
request_count INTEGER DEFAULT 0
|
|
211
179
|
);
|
|
212
180
|
|
|
213
181
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -272,27 +240,6 @@ function initSchema(db) {
|
|
|
272
240
|
machine_id TEXT,
|
|
273
241
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
274
242
|
);
|
|
275
|
-
|
|
276
|
-
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
277
|
-
date TEXT NOT NULL,
|
|
278
|
-
provider TEXT NOT NULL,
|
|
279
|
-
description TEXT DEFAULT '',
|
|
280
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
281
|
-
updated_at TEXT NOT NULL,
|
|
282
|
-
PRIMARY KEY (date, provider, description)
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
286
|
-
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
287
|
-
`);
|
|
288
|
-
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
289
|
-
if (!cols.some((c) => c.name === "machine_id")) {
|
|
290
|
-
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
291
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
292
|
-
}
|
|
293
|
-
db.exec(`
|
|
294
|
-
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
295
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
296
243
|
`);
|
|
297
244
|
}
|
|
298
245
|
function periodWhere(period) {
|
|
@@ -302,11 +249,11 @@ function periodWhere(period) {
|
|
|
302
249
|
case "yesterday":
|
|
303
250
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
304
251
|
case "week":
|
|
305
|
-
return `timestamp >= DATE('now', '
|
|
252
|
+
return `timestamp >= DATE('now', '-7 days')`;
|
|
306
253
|
case "month":
|
|
307
|
-
return `timestamp >= DATE('now', '
|
|
254
|
+
return `timestamp >= DATE('now', '-30 days')`;
|
|
308
255
|
case "year":
|
|
309
|
-
return `timestamp >= DATE('now', '
|
|
256
|
+
return `timestamp >= DATE('now', '-365 days')`;
|
|
310
257
|
case "all":
|
|
311
258
|
return "1=1";
|
|
312
259
|
}
|
|
@@ -318,11 +265,11 @@ function sessionPeriodWhere(period) {
|
|
|
318
265
|
case "yesterday":
|
|
319
266
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
320
267
|
case "week":
|
|
321
|
-
return `started_at >= DATE('now', '
|
|
268
|
+
return `started_at >= DATE('now', '-7 days')`;
|
|
322
269
|
case "month":
|
|
323
|
-
return `started_at >= DATE('now', '
|
|
270
|
+
return `started_at >= DATE('now', '-30 days')`;
|
|
324
271
|
case "year":
|
|
325
|
-
return `started_at >= DATE('now', '
|
|
272
|
+
return `started_at >= DATE('now', '-365 days')`;
|
|
326
273
|
case "all":
|
|
327
274
|
return "1=1";
|
|
328
275
|
}
|
|
@@ -332,17 +279,17 @@ function upsertRequest(db, req) {
|
|
|
332
279
|
INSERT OR REPLACE INTO requests
|
|
333
280
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
334
281
|
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
335
|
-
timestamp, source_request_id
|
|
336
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
337
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id
|
|
282
|
+
timestamp, source_request_id)
|
|
283
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
284
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
338
285
|
}
|
|
339
286
|
function upsertSession(db, session) {
|
|
340
287
|
db.prepare(`
|
|
341
288
|
INSERT OR REPLACE INTO sessions
|
|
342
289
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
343
|
-
total_cost_usd, total_tokens, request_count
|
|
344
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
345
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count
|
|
290
|
+
total_cost_usd, total_tokens, request_count)
|
|
291
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
292
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
346
293
|
}
|
|
347
294
|
function rollupSession(db, sessionId) {
|
|
348
295
|
db.prepare(`
|
|
@@ -372,10 +319,6 @@ function querySessions(db, filter = {}) {
|
|
|
372
319
|
conditions.push("started_at >= ?");
|
|
373
320
|
params.push(filter.since);
|
|
374
321
|
}
|
|
375
|
-
if (filter.machine) {
|
|
376
|
-
conditions.push("machine_id = ?");
|
|
377
|
-
params.push(filter.machine);
|
|
378
|
-
}
|
|
379
322
|
if (filter.search) {
|
|
380
323
|
const q = `%${filter.search}%`;
|
|
381
324
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -394,25 +337,24 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
394
337
|
}
|
|
395
338
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
396
339
|
}
|
|
397
|
-
function querySummary(db, period
|
|
340
|
+
function querySummary(db, period) {
|
|
398
341
|
const rWhere = periodWhere(period);
|
|
399
342
|
const sWhere = sessionPeriodWhere(period);
|
|
400
|
-
const machineClause = machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
401
343
|
const r = db.prepare(`
|
|
402
344
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
403
345
|
COUNT(*) as requests,
|
|
404
346
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
405
|
-
FROM requests WHERE ${rWhere}
|
|
347
|
+
FROM requests WHERE ${rWhere}
|
|
406
348
|
`).get();
|
|
407
349
|
const codexTotals = db.prepare(`
|
|
408
350
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
409
351
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
410
352
|
COUNT(*) as sessions
|
|
411
353
|
FROM sessions
|
|
412
|
-
WHERE ${sWhere}
|
|
354
|
+
WHERE ${sWhere}
|
|
413
355
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
414
356
|
`).get();
|
|
415
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}
|
|
357
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
416
358
|
return {
|
|
417
359
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
418
360
|
requests: r.requests,
|
|
@@ -432,66 +374,23 @@ function queryModelBreakdown(db) {
|
|
|
432
374
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
433
375
|
`).all();
|
|
434
376
|
}
|
|
435
|
-
function labelForPath(projectPath, projectName) {
|
|
436
|
-
if (projectName && projectName.trim() !== "")
|
|
437
|
-
return projectName;
|
|
438
|
-
if (!projectPath)
|
|
439
|
-
return "";
|
|
440
|
-
const segments = projectPath.split("/").filter(Boolean);
|
|
441
|
-
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
442
|
-
for (const seg of segments) {
|
|
443
|
-
if (projectPrefix.test(seg))
|
|
444
|
-
return seg;
|
|
445
|
-
}
|
|
446
|
-
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
447
|
-
for (let i = segments.length - 1;i >= 0; i--) {
|
|
448
|
-
if (!generic.has(segments[i].toLowerCase()))
|
|
449
|
-
return segments[i];
|
|
450
|
-
}
|
|
451
|
-
return segments[segments.length - 1] ?? projectPath;
|
|
452
|
-
}
|
|
453
377
|
function queryProjectBreakdown(db) {
|
|
454
|
-
|
|
455
|
-
SELECT
|
|
456
|
-
|
|
457
|
-
|
|
378
|
+
return db.prepare(`
|
|
379
|
+
SELECT
|
|
380
|
+
s.project_path,
|
|
381
|
+
COALESCE(p.name, s.project_name) as project_name,
|
|
382
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
383
|
+
COUNT(r.id) as requests,
|
|
384
|
+
COALESCE(SUM(r.cost_usd), COALESCE(SUM(s.total_cost_usd), 0)) as cost_usd,
|
|
385
|
+
COALESCE(SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens), 0) as total_tokens,
|
|
386
|
+
MAX(s.started_at) as last_active
|
|
387
|
+
FROM sessions s
|
|
388
|
+
LEFT JOIN projects p ON p.path = s.project_path OR p.name = s.project_name
|
|
389
|
+
LEFT JOIN requests r ON r.session_id = s.id
|
|
390
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
391
|
+
GROUP BY s.project_path
|
|
392
|
+
ORDER BY cost_usd DESC
|
|
458
393
|
`).all();
|
|
459
|
-
const groups = new Map;
|
|
460
|
-
for (const s of sessions) {
|
|
461
|
-
const label = labelForPath(s.project_path, s.project_name);
|
|
462
|
-
if (!label)
|
|
463
|
-
continue;
|
|
464
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
465
|
-
g.sessionIds.push(s.id);
|
|
466
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
467
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
468
|
-
g.lastActive = s.started_at;
|
|
469
|
-
if (!g.samplePath)
|
|
470
|
-
g.samplePath = s.project_path;
|
|
471
|
-
groups.set(label, g);
|
|
472
|
-
}
|
|
473
|
-
const result = [];
|
|
474
|
-
for (const [label, g] of groups.entries()) {
|
|
475
|
-
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
476
|
-
const reqStats = placeholders.length ? db.prepare(`
|
|
477
|
-
SELECT
|
|
478
|
-
COUNT(*) as requests,
|
|
479
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
480
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
481
|
-
FROM requests WHERE session_id IN (${placeholders})
|
|
482
|
-
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
483
|
-
result.push({
|
|
484
|
-
project_path: g.samplePath,
|
|
485
|
-
project_name: label,
|
|
486
|
-
sessions: g.sessionIds.length,
|
|
487
|
-
requests: reqStats.requests,
|
|
488
|
-
total_tokens: reqStats.total_tokens,
|
|
489
|
-
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
490
|
-
last_active: g.lastActive
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
494
|
-
return result;
|
|
495
394
|
}
|
|
496
395
|
function queryDailyBreakdown(db, days = 30) {
|
|
497
396
|
return db.prepare(`
|
|
@@ -600,20 +499,6 @@ function getIngestState(db, source, key) {
|
|
|
600
499
|
function setIngestState(db, source, key, value) {
|
|
601
500
|
db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES (?, ?, ?)`).run(source, key, value);
|
|
602
501
|
}
|
|
603
|
-
function listMachines(db) {
|
|
604
|
-
return db.prepare(`
|
|
605
|
-
SELECT
|
|
606
|
-
s.machine_id,
|
|
607
|
-
COUNT(DISTINCT s.id) as sessions,
|
|
608
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
609
|
-
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
610
|
-
MAX(s.started_at) as last_active
|
|
611
|
-
FROM sessions s
|
|
612
|
-
WHERE s.machine_id != ''
|
|
613
|
-
GROUP BY s.machine_id
|
|
614
|
-
ORDER BY total_cost_usd DESC
|
|
615
|
-
`).all();
|
|
616
|
-
}
|
|
617
502
|
function upsertModelPricing(db, p) {
|
|
618
503
|
db.prepare(`
|
|
619
504
|
INSERT OR REPLACE INTO model_pricing
|
|
@@ -631,11 +516,11 @@ function deleteModelPricing(db, model) {
|
|
|
631
516
|
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
632
517
|
}
|
|
633
518
|
function seedModelPricing(db, defaults) {
|
|
634
|
-
const existing =
|
|
519
|
+
const existing = db.prepare(`SELECT COUNT(*) as count FROM model_pricing`).get();
|
|
520
|
+
if (existing.count > 0)
|
|
521
|
+
return;
|
|
635
522
|
const now = new Date().toISOString();
|
|
636
523
|
for (const [model, p] of Object.entries(defaults)) {
|
|
637
|
-
if (existing.has(model))
|
|
638
|
-
continue;
|
|
639
524
|
upsertModelPricing(db, {
|
|
640
525
|
model,
|
|
641
526
|
input_per_1m: p.inputPer1M,
|
|
@@ -660,8 +545,7 @@ import { join as join2, basename } from "path";
|
|
|
660
545
|
function autoDetectProject(cwd, projects) {
|
|
661
546
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
662
547
|
}
|
|
663
|
-
var
|
|
664
|
-
var TAKUMI_PROJECTS_DIR = join2(homedir2(), ".takumi", "projects");
|
|
548
|
+
var PROJECTS_DIR = join2(homedir2(), ".claude", "projects");
|
|
665
549
|
function dirNameToPath(dirName) {
|
|
666
550
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
667
551
|
}
|
|
@@ -681,36 +565,29 @@ function collectJsonlFiles(projectDir) {
|
|
|
681
565
|
return files;
|
|
682
566
|
}
|
|
683
567
|
async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
async function ingestTakumi(db, verbose = false) {
|
|
687
|
-
return ingestJsonlProjects(db, TAKUMI_PROJECTS_DIR, "takumi", verbose);
|
|
688
|
-
}
|
|
689
|
-
async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
|
|
690
|
-
if (!existsSync2(projectsDir)) {
|
|
568
|
+
if (!existsSync2(PROJECTS_DIR)) {
|
|
691
569
|
if (verbose)
|
|
692
|
-
console.log(
|
|
570
|
+
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
693
571
|
return { files: 0, requests: 0, sessions: 0 };
|
|
694
572
|
}
|
|
695
|
-
const machineId = getMachineId();
|
|
696
573
|
let totalFiles = 0;
|
|
697
574
|
let totalRequests = 0;
|
|
698
575
|
const touchedSessions = new Set;
|
|
699
576
|
const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
|
|
700
|
-
const projectDirs = readdirSync2(
|
|
577
|
+
const projectDirs = readdirSync2(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
701
578
|
for (const projectDirEntry of projectDirs) {
|
|
702
|
-
const projectDirPath = join2(
|
|
579
|
+
const projectDirPath = join2(PROJECTS_DIR, projectDirEntry.name);
|
|
703
580
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
704
581
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
705
582
|
for (const filePath of jsonlFiles) {
|
|
706
|
-
const stateKey = filePath.replace(
|
|
583
|
+
const stateKey = filePath.replace(PROJECTS_DIR, "");
|
|
707
584
|
let fileMtime = "0";
|
|
708
585
|
try {
|
|
709
586
|
fileMtime = statSync2(filePath).mtimeMs.toString();
|
|
710
587
|
} catch {
|
|
711
588
|
continue;
|
|
712
589
|
}
|
|
713
|
-
const processed = getIngestState(db,
|
|
590
|
+
const processed = getIngestState(db, "claude", stateKey);
|
|
714
591
|
if (processed === fileMtime)
|
|
715
592
|
continue;
|
|
716
593
|
let lines;
|
|
@@ -751,10 +628,10 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
751
628
|
if (inputTokens + outputTokens + cacheWriteTokens === 0)
|
|
752
629
|
continue;
|
|
753
630
|
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens);
|
|
754
|
-
const reqId =
|
|
631
|
+
const reqId = `claude-${sessionId}-${timestamp}`;
|
|
755
632
|
upsertRequest(db, {
|
|
756
633
|
id: reqId,
|
|
757
|
-
agent:
|
|
634
|
+
agent: "claude",
|
|
758
635
|
session_id: sessionId,
|
|
759
636
|
model,
|
|
760
637
|
input_tokens: inputTokens,
|
|
@@ -764,8 +641,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
764
641
|
cost_usd: costUsd,
|
|
765
642
|
duration_ms: 0,
|
|
766
643
|
timestamp,
|
|
767
|
-
source_request_id: reqId
|
|
768
|
-
machine_id: machineId
|
|
644
|
+
source_request_id: reqId
|
|
769
645
|
});
|
|
770
646
|
if (!touchedSessions.has(sessionId)) {
|
|
771
647
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -774,15 +650,14 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
774
650
|
const detectedProject = autoDetectProject(effectiveCwd, registeredProjects);
|
|
775
651
|
const session = {
|
|
776
652
|
id: sessionId,
|
|
777
|
-
agent:
|
|
653
|
+
agent: "claude",
|
|
778
654
|
project_path: detectedProject ? detectedProject.path : effectiveCwd,
|
|
779
655
|
project_name: detectedProject ? detectedProject.name : "",
|
|
780
656
|
started_at: timestamp,
|
|
781
657
|
ended_at: null,
|
|
782
658
|
total_cost_usd: 0,
|
|
783
659
|
total_tokens: 0,
|
|
784
|
-
request_count: 0
|
|
785
|
-
machine_id: machineId
|
|
660
|
+
request_count: 0
|
|
786
661
|
};
|
|
787
662
|
upsertSession(db, session);
|
|
788
663
|
}
|
|
@@ -790,7 +665,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
790
665
|
}
|
|
791
666
|
totalRequests++;
|
|
792
667
|
}
|
|
793
|
-
setIngestState(db,
|
|
668
|
+
setIngestState(db, "claude", stateKey, fileMtime);
|
|
794
669
|
totalFiles++;
|
|
795
670
|
}
|
|
796
671
|
}
|
|
@@ -805,7 +680,7 @@ init_database();
|
|
|
805
680
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
806
681
|
import { homedir as homedir3 } from "os";
|
|
807
682
|
import { join as join3, basename as basename2 } from "path";
|
|
808
|
-
import { Database as
|
|
683
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
809
684
|
var CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
|
|
810
685
|
var CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
|
|
811
686
|
async function ingestCodex(db, verbose = false) {
|
|
@@ -814,11 +689,10 @@ async function ingestCodex(db, verbose = false) {
|
|
|
814
689
|
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
815
690
|
return { sessions: 0 };
|
|
816
691
|
}
|
|
817
|
-
const machineId = getMachineId();
|
|
818
692
|
let codexDb = null;
|
|
819
693
|
let ingested = 0;
|
|
820
694
|
try {
|
|
821
|
-
codexDb = new
|
|
695
|
+
codexDb = new Database2(CODEX_DB_PATH, { readonly: true });
|
|
822
696
|
const threads = codexDb.prepare(`SELECT id, cwd, created_at, updated_at, tokens_used, title FROM threads WHERE tokens_used > 0`).all();
|
|
823
697
|
for (const thread of threads) {
|
|
824
698
|
const stateKey = thread.id;
|
|
@@ -839,8 +713,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
839
713
|
ended_at: endedAt,
|
|
840
714
|
total_cost_usd: costUsd,
|
|
841
715
|
total_tokens: thread.tokens_used,
|
|
842
|
-
request_count: 1
|
|
843
|
-
machine_id: machineId
|
|
716
|
+
request_count: 1
|
|
844
717
|
});
|
|
845
718
|
setIngestState(db, "codex", stateKey, "done");
|
|
846
719
|
ingested++;
|
|
@@ -853,85 +726,6 @@ async function ingestCodex(db, verbose = false) {
|
|
|
853
726
|
return { sessions: ingested };
|
|
854
727
|
}
|
|
855
728
|
|
|
856
|
-
// src/ingest/gemini.ts
|
|
857
|
-
init_database();
|
|
858
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
859
|
-
import { homedir as homedir4 } from "os";
|
|
860
|
-
import { join as join4 } from "path";
|
|
861
|
-
var GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
|
|
862
|
-
async function ingestGemini(db, verbose) {
|
|
863
|
-
if (!existsSync4(GEMINI_TMP_DIR)) {
|
|
864
|
-
if (verbose)
|
|
865
|
-
console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
|
|
866
|
-
return { sessions: 0 };
|
|
867
|
-
}
|
|
868
|
-
const machineId = getMachineId();
|
|
869
|
-
let totalSessions = 0;
|
|
870
|
-
const touchedSessions = new Set;
|
|
871
|
-
let projectHashDirs = [];
|
|
872
|
-
try {
|
|
873
|
-
projectHashDirs = readdirSync3(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join4(GEMINI_TMP_DIR, d.name));
|
|
874
|
-
} catch {
|
|
875
|
-
return { sessions: 0 };
|
|
876
|
-
}
|
|
877
|
-
for (const projectDir of projectHashDirs) {
|
|
878
|
-
const chatsDir = join4(projectDir, "chats");
|
|
879
|
-
if (!existsSync4(chatsDir))
|
|
880
|
-
continue;
|
|
881
|
-
let chatFiles = [];
|
|
882
|
-
try {
|
|
883
|
-
chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join4(chatsDir, f));
|
|
884
|
-
} catch {
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
for (const filePath of chatFiles) {
|
|
888
|
-
const stateKey = filePath.replace(homedir4(), "~");
|
|
889
|
-
let fileMtime = "0";
|
|
890
|
-
try {
|
|
891
|
-
fileMtime = statSync3(filePath).mtimeMs.toString();
|
|
892
|
-
} catch {
|
|
893
|
-
continue;
|
|
894
|
-
}
|
|
895
|
-
const processed = getIngestState(db, "gemini", stateKey);
|
|
896
|
-
if (processed === fileMtime)
|
|
897
|
-
continue;
|
|
898
|
-
let chatData;
|
|
899
|
-
try {
|
|
900
|
-
chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
901
|
-
} catch {
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
const sessionId = chatData.sessionId;
|
|
905
|
-
if (!sessionId)
|
|
906
|
-
continue;
|
|
907
|
-
const startTime = chatData.startTime ?? new Date().toISOString();
|
|
908
|
-
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
909
|
-
if (!existing) {
|
|
910
|
-
const session = {
|
|
911
|
-
id: sessionId,
|
|
912
|
-
agent: "gemini",
|
|
913
|
-
project_path: "",
|
|
914
|
-
project_name: "",
|
|
915
|
-
started_at: startTime,
|
|
916
|
-
ended_at: chatData.lastUpdated ?? null,
|
|
917
|
-
total_cost_usd: 0,
|
|
918
|
-
total_tokens: 0,
|
|
919
|
-
request_count: 0,
|
|
920
|
-
machine_id: machineId
|
|
921
|
-
};
|
|
922
|
-
upsertSession(db, session);
|
|
923
|
-
touchedSessions.add(sessionId);
|
|
924
|
-
totalSessions++;
|
|
925
|
-
}
|
|
926
|
-
setIngestState(db, "gemini", stateKey, fileMtime);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
for (const sessionId of touchedSessions) {
|
|
930
|
-
rollupSession(db, sessionId);
|
|
931
|
-
}
|
|
932
|
-
return { sessions: totalSessions };
|
|
933
|
-
}
|
|
934
|
-
|
|
935
729
|
// src/server/serve.ts
|
|
936
730
|
init_pricing();
|
|
937
731
|
import { randomUUID } from "crypto";
|
|
@@ -952,20 +746,6 @@ function ok(data, meta) {
|
|
|
952
746
|
function err(message, status = 400) {
|
|
953
747
|
return json({ error: message }, status);
|
|
954
748
|
}
|
|
955
|
-
function normalizeBudgetPeriod(value) {
|
|
956
|
-
switch (value) {
|
|
957
|
-
case "day":
|
|
958
|
-
case "daily":
|
|
959
|
-
return "daily";
|
|
960
|
-
case "week":
|
|
961
|
-
case "weekly":
|
|
962
|
-
return "weekly";
|
|
963
|
-
case "month":
|
|
964
|
-
case "monthly":
|
|
965
|
-
default:
|
|
966
|
-
return "monthly";
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
749
|
function applyFields(obj, fields) {
|
|
970
750
|
if (!fields || fields.length === 0)
|
|
971
751
|
return obj;
|
|
@@ -982,11 +762,7 @@ function createHandler(db) {
|
|
|
982
762
|
return ok({ status: "ok", ts: new Date().toISOString() });
|
|
983
763
|
if (path === "/api/summary" && method === "GET") {
|
|
984
764
|
const period = url.searchParams.get("period") ?? "today";
|
|
985
|
-
|
|
986
|
-
return ok(querySummary(db, period, machine));
|
|
987
|
-
}
|
|
988
|
-
if (path === "/api/machines" && method === "GET") {
|
|
989
|
-
return ok(listMachines(db), { current_machine: getMachineId() });
|
|
765
|
+
return ok(querySummary(db, period));
|
|
990
766
|
}
|
|
991
767
|
if (path === "/api/daily" && method === "GET") {
|
|
992
768
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
@@ -995,22 +771,12 @@ function createHandler(db) {
|
|
|
995
771
|
if (path === "/api/sessions" && method === "GET") {
|
|
996
772
|
const agent = url.searchParams.get("agent");
|
|
997
773
|
const project = url.searchParams.get("project") ?? undefined;
|
|
998
|
-
const search = url.searchParams.get("search") ?? undefined;
|
|
999
|
-
const machine = url.searchParams.get("machine") ?? undefined;
|
|
1000
774
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
1001
775
|
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
1002
776
|
const since = url.searchParams.get("since") ?? undefined;
|
|
1003
777
|
const fieldsParam = url.searchParams.get("fields");
|
|
1004
778
|
const fields = fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
1005
|
-
const sessions = querySessions(db, {
|
|
1006
|
-
agent: agent ?? undefined,
|
|
1007
|
-
project,
|
|
1008
|
-
search,
|
|
1009
|
-
machine,
|
|
1010
|
-
limit,
|
|
1011
|
-
offset,
|
|
1012
|
-
since
|
|
1013
|
-
});
|
|
779
|
+
const sessions = querySessions(db, { agent: agent ?? undefined, project, limit, offset, since });
|
|
1014
780
|
return ok(fields ? sessions.map((s) => applyFields(s, fields)) : sessions, { limit, offset });
|
|
1015
781
|
}
|
|
1016
782
|
if (path === "/api/top" && method === "GET") {
|
|
@@ -1038,7 +804,7 @@ function createHandler(db) {
|
|
|
1038
804
|
id: randomUUID(),
|
|
1039
805
|
project_path: body["project_path"] ?? null,
|
|
1040
806
|
agent: body["agent"] ?? null,
|
|
1041
|
-
period:
|
|
807
|
+
period: body["period"] ?? "monthly",
|
|
1042
808
|
limit_usd: Number(body["limit_usd"]),
|
|
1043
809
|
alert_at_percent: Number(body["alert_at_percent"] ?? 80),
|
|
1044
810
|
created_at: now,
|
|
@@ -1099,12 +865,8 @@ function createHandler(db) {
|
|
|
1099
865
|
const results = {};
|
|
1100
866
|
if (sources === "all" || sources === "claude")
|
|
1101
867
|
results["claude"] = await ingestClaude(db);
|
|
1102
|
-
if (sources === "all" || sources === "takumi")
|
|
1103
|
-
results["takumi"] = await ingestTakumi(db);
|
|
1104
868
|
if (sources === "all" || sources === "codex")
|
|
1105
869
|
results["codex"] = await ingestCodex(db);
|
|
1106
|
-
if (sources === "all" || sources === "gemini")
|
|
1107
|
-
results["gemini"] = await ingestGemini(db);
|
|
1108
870
|
return ok(results);
|
|
1109
871
|
}
|
|
1110
872
|
const sessionRequestsMatch = path.match(/^\/api\/sessions\/([^/]+)\/requests$/);
|
|
@@ -1154,15 +916,15 @@ function startServer(port = 3456) {
|
|
|
1154
916
|
return apiHandler(req);
|
|
1155
917
|
}
|
|
1156
918
|
try {
|
|
1157
|
-
const { existsSync:
|
|
1158
|
-
if (
|
|
919
|
+
const { existsSync: existsSync4 } = await import("fs");
|
|
920
|
+
if (existsSync4(dashboardDir)) {
|
|
1159
921
|
let filePath = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
1160
922
|
const fullPath = dashboardDir + filePath;
|
|
1161
|
-
if (
|
|
923
|
+
if (existsSync4(fullPath)) {
|
|
1162
924
|
return new Response(Bun.file(fullPath));
|
|
1163
925
|
}
|
|
1164
926
|
const indexPath = dashboardDir + "/index.html";
|
|
1165
|
-
if (
|
|
927
|
+
if (existsSync4(indexPath)) {
|
|
1166
928
|
return new Response(Bun.file(indexPath));
|
|
1167
929
|
}
|
|
1168
930
|
}
|
|
@@ -1173,62 +935,6 @@ function startServer(port = 3456) {
|
|
|
1173
935
|
console.log(`economy-serve listening on http://localhost:${port}`);
|
|
1174
936
|
}
|
|
1175
937
|
|
|
1176
|
-
// src/lib/package-metadata.ts
|
|
1177
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
1178
|
-
var cachedMetadata = null;
|
|
1179
|
-
function getPackageMetadata() {
|
|
1180
|
-
if (cachedMetadata)
|
|
1181
|
-
return cachedMetadata;
|
|
1182
|
-
const raw = readFileSync4(new URL("../../package.json", import.meta.url), "utf8");
|
|
1183
|
-
const parsed = JSON.parse(raw);
|
|
1184
|
-
cachedMetadata = {
|
|
1185
|
-
name: parsed.name ?? "@hasna/economy",
|
|
1186
|
-
version: parsed.version ?? "0.0.0"
|
|
1187
|
-
};
|
|
1188
|
-
return cachedMetadata;
|
|
1189
|
-
}
|
|
1190
|
-
var packageMetadata = getPackageMetadata();
|
|
1191
|
-
|
|
1192
938
|
// src/server/index.ts
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
REST API server for ${packageMetadata.name}
|
|
1197
|
-
|
|
1198
|
-
Options:
|
|
1199
|
-
-p, --port <port> Port to bind (default: ECONOMY_PORT or 3456)
|
|
1200
|
-
-V, --version output the version number
|
|
1201
|
-
-h, --help display help for command`);
|
|
1202
|
-
}
|
|
1203
|
-
function resolvePort(argv) {
|
|
1204
|
-
for (let i = 0;i < argv.length; i++) {
|
|
1205
|
-
const arg = argv[i];
|
|
1206
|
-
if ((arg === "--port" || arg === "-p") && argv[i + 1]) {
|
|
1207
|
-
const value2 = Number(argv[i + 1]);
|
|
1208
|
-
if (!Number.isFinite(value2) || value2 <= 0) {
|
|
1209
|
-
throw new Error(`Invalid port: ${argv[i + 1]}`);
|
|
1210
|
-
}
|
|
1211
|
-
return value2;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
const value = Number(process.env["ECONOMY_PORT"] ?? 3456);
|
|
1215
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
1216
|
-
throw new Error(`Invalid ECONOMY_PORT: ${process.env["ECONOMY_PORT"]}`);
|
|
1217
|
-
}
|
|
1218
|
-
return value;
|
|
1219
|
-
}
|
|
1220
|
-
var args = process.argv.slice(2);
|
|
1221
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
1222
|
-
printHelp();
|
|
1223
|
-
process.exit(0);
|
|
1224
|
-
}
|
|
1225
|
-
if (args.includes("--version") || args.includes("-V")) {
|
|
1226
|
-
console.log(packageMetadata.version);
|
|
1227
|
-
process.exit(0);
|
|
1228
|
-
}
|
|
1229
|
-
try {
|
|
1230
|
-
startServer(resolvePort(args));
|
|
1231
|
-
} catch (error) {
|
|
1232
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
1233
|
-
process.exit(1);
|
|
1234
|
-
}
|
|
939
|
+
var port = Number(process.env["ECONOMY_PORT"] ?? 3456);
|
|
940
|
+
startServer(port);
|