@hasna/economy 0.5.4 → 0.5.5
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 +3 -1
- package/dist/chunks/{index-9arv0b1j.js → index-61c0vz9p.js} +63 -66
- package/dist/chunks/{index-nheq2nrz.js → index-7k1xt8mj.js} +1 -1
- package/dist/chunks/{index-n1wwqxfe.js → index-pnnqdv3r.js} +1 -1
- package/dist/chunks/{index-ct3zdefz.js → index-z9psed59.js} +1 -1
- package/dist/chunks/{serve-ra46ms69.js → serve-zp5a3qzw.js} +2 -2
- package/dist/chunks/{tui-a1tvcw2j.js → tui-2yea642b.js} +3 -3
- package/dist/chunks/{watch-rvd3dn6r.js → watch-811vncjz.js} +3 -3
- package/dist/cli/index.js +10 -10
- package/dist/index.js +1 -1
- package/dist/ingest/claude.d.ts.map +1 -1
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/mcp/index.js +63 -66
- package/dist/otel/index.js +32 -39
- package/dist/server/index.js +63 -66
- package/dist/server/service-worker.js +63 -66
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -85,7 +85,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
85
85
|
// package.json
|
|
86
86
|
var package_default = {
|
|
87
87
|
name: "@hasna/economy",
|
|
88
|
-
version: "0.5.
|
|
88
|
+
version: "0.5.5",
|
|
89
89
|
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",
|
|
90
90
|
repository: {
|
|
91
91
|
type: "git",
|
|
@@ -1556,8 +1556,8 @@ function sourceTimeMillis(value) {
|
|
|
1556
1556
|
|
|
1557
1557
|
// src/ingest/claude.ts
|
|
1558
1558
|
import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync3, statSync } from "fs";
|
|
1559
|
-
import { homedir as
|
|
1560
|
-
import { join as
|
|
1559
|
+
import { homedir as homedir3 } from "os";
|
|
1560
|
+
import { join as join7, basename } from "path";
|
|
1561
1561
|
|
|
1562
1562
|
// src/lib/accounts-store.ts
|
|
1563
1563
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
@@ -2164,12 +2164,41 @@ function withAccount(record, account) {
|
|
|
2164
2164
|
};
|
|
2165
2165
|
}
|
|
2166
2166
|
|
|
2167
|
+
// src/lib/paths.ts
|
|
2168
|
+
import { homedir as homedir2 } from "os";
|
|
2169
|
+
import { join as join6 } from "path";
|
|
2170
|
+
function getHomeDir() {
|
|
2171
|
+
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
|
|
2172
|
+
}
|
|
2173
|
+
function agentPaths() {
|
|
2174
|
+
const home = getHomeDir();
|
|
2175
|
+
const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join6(home, ".claude");
|
|
2176
|
+
const codexHome = process.env["CODEX_HOME"] || join6(home, ".codex");
|
|
2177
|
+
return {
|
|
2178
|
+
claudeProjects: join6(claudeHome, "projects"),
|
|
2179
|
+
claudeCredentials: join6(claudeHome, ".credentials.json"),
|
|
2180
|
+
takumiProjects: join6(home, ".takumi", "projects"),
|
|
2181
|
+
codexDir: codexHome,
|
|
2182
|
+
codexDb: join6(codexHome, "state_5.sqlite"),
|
|
2183
|
+
codexAuth: join6(codexHome, "auth.json"),
|
|
2184
|
+
codexConfig: join6(codexHome, "config.toml"),
|
|
2185
|
+
geminiTmp: join6(home, ".gemini", "tmp"),
|
|
2186
|
+
geminiHistory: join6(home, ".gemini", "history"),
|
|
2187
|
+
opencodeMessages: join6(home, ".local", "share", "opencode", "storage", "message"),
|
|
2188
|
+
piSessions: join6(home, ".pi", "agent", "sessions"),
|
|
2189
|
+
hermesDir: join6(home, ".hermes"),
|
|
2190
|
+
hermesDb: join6(home, ".hermes", "state.db")
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2167
2194
|
// src/ingest/claude.ts
|
|
2168
2195
|
function autoDetectProject(cwd, projects) {
|
|
2169
2196
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
2170
2197
|
}
|
|
2171
|
-
var
|
|
2172
|
-
|
|
2198
|
+
var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join7(homedir3(), ".takumi", "projects");
|
|
2199
|
+
function claudeProjectsDir() {
|
|
2200
|
+
return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
|
|
2201
|
+
}
|
|
2173
2202
|
function dirNameToPath(dirName) {
|
|
2174
2203
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
2175
2204
|
}
|
|
@@ -2179,9 +2208,9 @@ function collectJsonlFiles(projectDir) {
|
|
|
2179
2208
|
try {
|
|
2180
2209
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
2181
2210
|
if (entry.isDirectory())
|
|
2182
|
-
walk(
|
|
2211
|
+
walk(join7(dir, entry.name));
|
|
2183
2212
|
else if (entry.name.endsWith(".jsonl"))
|
|
2184
|
-
files.push(
|
|
2213
|
+
files.push(join7(dir, entry.name));
|
|
2185
2214
|
}
|
|
2186
2215
|
} catch {
|
|
2187
2216
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -2190,7 +2219,7 @@ function collectJsonlFiles(projectDir) {
|
|
|
2190
2219
|
walk(projectDir);
|
|
2191
2220
|
return files;
|
|
2192
2221
|
}
|
|
2193
|
-
async function ingestClaude(db, verbose = false, projectsDir =
|
|
2222
|
+
async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
|
|
2194
2223
|
return ingestJsonlProjects(db, projectsDir, "claude", verbose);
|
|
2195
2224
|
}
|
|
2196
2225
|
async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
|
|
@@ -2212,7 +2241,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
2212
2241
|
const ingestState = loadIngestState(db, agentName);
|
|
2213
2242
|
const projectDirs = readdirSync3(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
2214
2243
|
for (const projectDirEntry of projectDirs) {
|
|
2215
|
-
const projectDirPath =
|
|
2244
|
+
const projectDirPath = join7(projectsDir, projectDirEntry.name);
|
|
2216
2245
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
2217
2246
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
2218
2247
|
for (const filePath of jsonlFiles) {
|
|
@@ -2361,24 +2390,19 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
2361
2390
|
// src/ingest/codex.ts
|
|
2362
2391
|
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
2363
2392
|
import { createHash as createHash3 } from "crypto";
|
|
2364
|
-
import {
|
|
2365
|
-
import { join as join7, basename as basename2 } from "path";
|
|
2366
|
-
var DEFAULT_CODEX_DB_PATH = join7(homedir3(), ".codex", "state_5.sqlite");
|
|
2367
|
-
var DEFAULT_CODEX_CONFIG_PATH = join7(homedir3(), ".codex", "config.toml");
|
|
2368
|
-
var DEFAULT_CODEWITH_DB_PATH = join7(homedir3(), ".codewith", "state_5.sqlite");
|
|
2369
|
-
var DEFAULT_CODEWITH_CONFIG_PATH = join7(homedir3(), ".codewith", "config.toml");
|
|
2393
|
+
import { join as join8, basename as basename2 } from "path";
|
|
2370
2394
|
var CODEX_INGEST_VERSION = "rollout-events-v4";
|
|
2371
2395
|
function codexDbPath() {
|
|
2372
|
-
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ??
|
|
2396
|
+
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
|
|
2373
2397
|
}
|
|
2374
2398
|
function codexConfigPath() {
|
|
2375
|
-
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ??
|
|
2399
|
+
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
|
|
2376
2400
|
}
|
|
2377
2401
|
function codewithDbPath() {
|
|
2378
|
-
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ??
|
|
2402
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join8(getHomeDir(), ".codewith", "state_5.sqlite");
|
|
2379
2403
|
}
|
|
2380
2404
|
function codewithConfigPath() {
|
|
2381
|
-
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ??
|
|
2405
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join8(getHomeDir(), ".codewith", "config.toml");
|
|
2382
2406
|
}
|
|
2383
2407
|
function codexSources() {
|
|
2384
2408
|
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
@@ -2390,7 +2414,7 @@ function codexSources() {
|
|
|
2390
2414
|
sessionPrefix: "codex",
|
|
2391
2415
|
stateSource: "codex"
|
|
2392
2416
|
}];
|
|
2393
|
-
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2417
|
+
if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
|
|
2394
2418
|
sources.push({
|
|
2395
2419
|
label: "Codewith",
|
|
2396
2420
|
dbPath: codewithDbPath(),
|
|
@@ -2628,9 +2652,9 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2628
2652
|
// src/ingest/gemini.ts
|
|
2629
2653
|
import { readdirSync as readdirSync4, readFileSync as readFileSync6, existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
2630
2654
|
import { homedir as homedir4 } from "os";
|
|
2631
|
-
import { join as
|
|
2632
|
-
var DEFAULT_GEMINI_TMP_DIR =
|
|
2633
|
-
var DEFAULT_GEMINI_HISTORY_DIR =
|
|
2655
|
+
import { join as join9, basename as basename3 } from "path";
|
|
2656
|
+
var DEFAULT_GEMINI_TMP_DIR = join9(homedir4(), ".gemini", "tmp");
|
|
2657
|
+
var DEFAULT_GEMINI_HISTORY_DIR = join9(homedir4(), ".gemini", "history");
|
|
2634
2658
|
function geminiTmpDir() {
|
|
2635
2659
|
return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
|
|
2636
2660
|
}
|
|
@@ -2652,7 +2676,7 @@ function listProjectDirs(...roots) {
|
|
|
2652
2676
|
try {
|
|
2653
2677
|
for (const entry of readdirSync4(root, { withFileTypes: true })) {
|
|
2654
2678
|
if (entry.isDirectory())
|
|
2655
|
-
dirs.add(
|
|
2679
|
+
dirs.add(join9(root, entry.name));
|
|
2656
2680
|
}
|
|
2657
2681
|
} catch {
|
|
2658
2682
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -2665,7 +2689,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
2665
2689
|
return chatData.projectPath;
|
|
2666
2690
|
if (chatData.project_path)
|
|
2667
2691
|
return chatData.project_path;
|
|
2668
|
-
const rootFile =
|
|
2692
|
+
const rootFile = join9(projectDir, ".project_root");
|
|
2669
2693
|
try {
|
|
2670
2694
|
if (existsSync5(rootFile))
|
|
2671
2695
|
return readFileSync6(rootFile, "utf-8").trim();
|
|
@@ -2690,12 +2714,12 @@ async function ingestGemini(db, verbose) {
|
|
|
2690
2714
|
const account = await resolveAccountForAgent("gemini");
|
|
2691
2715
|
const projectDirs = listProjectDirs(tmpDir, historyDir);
|
|
2692
2716
|
for (const projectDir of projectDirs) {
|
|
2693
|
-
const chatsDir =
|
|
2717
|
+
const chatsDir = join9(projectDir, "chats");
|
|
2694
2718
|
if (!existsSync5(chatsDir))
|
|
2695
2719
|
continue;
|
|
2696
2720
|
let chatFiles = [];
|
|
2697
2721
|
try {
|
|
2698
|
-
chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2722
|
+
chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join9(chatsDir, f));
|
|
2699
2723
|
} catch {
|
|
2700
2724
|
collectionProblem("failed", "source_unreadable");
|
|
2701
2725
|
continue;
|
|
@@ -2805,13 +2829,13 @@ async function ingestGemini(db, verbose) {
|
|
|
2805
2829
|
// src/ingest/opencode.ts
|
|
2806
2830
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
2807
2831
|
import { homedir as homedir5 } from "os";
|
|
2808
|
-
import { join as
|
|
2809
|
-
var OPENCODE_STORAGE =
|
|
2832
|
+
import { join as join10 } from "path";
|
|
2833
|
+
var OPENCODE_STORAGE = join10(homedir5(), ".local", "share", "opencode", "storage");
|
|
2810
2834
|
function walkJsonFiles(dir, acc = []) {
|
|
2811
2835
|
if (!existsSync6(dir))
|
|
2812
2836
|
return acc;
|
|
2813
2837
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
2814
|
-
const p =
|
|
2838
|
+
const p = join10(dir, entry.name);
|
|
2815
2839
|
if (entry.isDirectory())
|
|
2816
2840
|
walkJsonFiles(p, acc);
|
|
2817
2841
|
else if (entry.name.endsWith(".json"))
|
|
@@ -2831,7 +2855,7 @@ function parseSessionIdFromPath(filePath) {
|
|
|
2831
2855
|
return null;
|
|
2832
2856
|
}
|
|
2833
2857
|
async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
|
|
2834
|
-
const messageDir =
|
|
2858
|
+
const messageDir = join10(sourceDir, "message");
|
|
2835
2859
|
if (!existsSync6(messageDir))
|
|
2836
2860
|
collectionProblem("missing", "source_missing");
|
|
2837
2861
|
const files = walkJsonFiles(messageDir);
|
|
@@ -3023,13 +3047,13 @@ async function ingestCursor(db, verbose = false) {
|
|
|
3023
3047
|
// src/ingest/pi.ts
|
|
3024
3048
|
import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
3025
3049
|
import { homedir as homedir6 } from "os";
|
|
3026
|
-
import { join as
|
|
3027
|
-
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ??
|
|
3050
|
+
import { join as join11 } from "path";
|
|
3051
|
+
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join11(homedir6(), ".pi", "agent", "sessions");
|
|
3028
3052
|
function walkSessions(dir, acc = []) {
|
|
3029
3053
|
if (!existsSync7(dir))
|
|
3030
3054
|
return acc;
|
|
3031
3055
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
3032
|
-
const p =
|
|
3056
|
+
const p = join11(dir, entry.name);
|
|
3033
3057
|
if (entry.isDirectory())
|
|
3034
3058
|
walkSessions(p, acc);
|
|
3035
3059
|
else if (entry.name.endsWith(".json"))
|
|
@@ -3129,8 +3153,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
|
|
|
3129
3153
|
// src/ingest/hermes.ts
|
|
3130
3154
|
import { existsSync as existsSync8, statSync as statSync6 } from "fs";
|
|
3131
3155
|
import { homedir as homedir7 } from "os";
|
|
3132
|
-
import { join as
|
|
3133
|
-
var HERMES_DB =
|
|
3156
|
+
import { join as join12 } from "path";
|
|
3157
|
+
var HERMES_DB = join12(homedir7(), ".hermes", "state.db");
|
|
3134
3158
|
async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
|
|
3135
3159
|
if (!existsSync8(sourcePath)) {
|
|
3136
3160
|
collectionProblem("missing", "source_missing");
|
|
@@ -3198,7 +3222,7 @@ function statSyncSafe(path) {
|
|
|
3198
3222
|
// src/ingest/loops.ts
|
|
3199
3223
|
import { existsSync as existsSync9 } from "fs";
|
|
3200
3224
|
import { homedir as homedir8 } from "os";
|
|
3201
|
-
import { join as
|
|
3225
|
+
import { join as join13 } from "path";
|
|
3202
3226
|
import { homedir as pathsResolverHomedir } from "os";
|
|
3203
3227
|
import { join as pathsResolverJoin } from "path";
|
|
3204
3228
|
var PATHS_RESOLVER_KIND_ENV = {
|
|
@@ -3265,8 +3289,8 @@ function defaultLoopsDbPath() {
|
|
|
3265
3289
|
app: "loops",
|
|
3266
3290
|
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
3267
3291
|
});
|
|
3268
|
-
const legacy =
|
|
3269
|
-
return existsSync9(
|
|
3292
|
+
const legacy = join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
3293
|
+
return existsSync9(join13(resolved, "loops.db")) ? join13(resolved, "loops.db") : legacy;
|
|
3270
3294
|
}
|
|
3271
3295
|
function loopsDbPath() {
|
|
3272
3296
|
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
@@ -3450,33 +3474,6 @@ async function ingestLoops(db, verbose = false) {
|
|
|
3450
3474
|
|
|
3451
3475
|
// src/ingest/claude-quota.ts
|
|
3452
3476
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
3453
|
-
|
|
3454
|
-
// src/lib/paths.ts
|
|
3455
|
-
import { homedir as homedir9 } from "os";
|
|
3456
|
-
import { join as join13 } from "path";
|
|
3457
|
-
function getHomeDir() {
|
|
3458
|
-
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
|
|
3459
|
-
}
|
|
3460
|
-
function agentPaths() {
|
|
3461
|
-
const home = getHomeDir();
|
|
3462
|
-
return {
|
|
3463
|
-
claudeProjects: join13(home, ".claude", "projects"),
|
|
3464
|
-
claudeCredentials: join13(home, ".claude", ".credentials.json"),
|
|
3465
|
-
takumiProjects: join13(home, ".takumi", "projects"),
|
|
3466
|
-
codexDir: join13(home, ".codex"),
|
|
3467
|
-
codexDb: join13(home, ".codex", "state_5.sqlite"),
|
|
3468
|
-
codexAuth: join13(home, ".codex", "auth.json"),
|
|
3469
|
-
codexConfig: join13(home, ".codex", "config.toml"),
|
|
3470
|
-
geminiTmp: join13(home, ".gemini", "tmp"),
|
|
3471
|
-
geminiHistory: join13(home, ".gemini", "history"),
|
|
3472
|
-
opencodeMessages: join13(home, ".local", "share", "opencode", "storage", "message"),
|
|
3473
|
-
piSessions: join13(home, ".pi", "agent", "sessions"),
|
|
3474
|
-
hermesDir: join13(home, ".hermes"),
|
|
3475
|
-
hermesDb: join13(home, ".hermes", "state.db")
|
|
3476
|
-
};
|
|
3477
|
-
}
|
|
3478
|
-
|
|
3479
|
-
// src/ingest/claude-quota.ts
|
|
3480
3477
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
3481
3478
|
var OAUTH_BETA = "oauth-2025-04-20";
|
|
3482
3479
|
function readClaudeToken() {
|
package/dist/otel/index.js
CHANGED
|
@@ -2587,33 +2587,26 @@ var BUILTIN_TOOLS = [
|
|
|
2587
2587
|
];
|
|
2588
2588
|
|
|
2589
2589
|
// src/ingest/claude.ts
|
|
2590
|
-
var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join3(homedir3(), ".claude", "projects");
|
|
2591
2590
|
var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join3(homedir3(), ".takumi", "projects");
|
|
2592
2591
|
|
|
2593
2592
|
// src/ingest/codex.ts
|
|
2594
2593
|
init_database();
|
|
2595
2594
|
init_pricing();
|
|
2596
|
-
import { homedir as homedir4 } from "os";
|
|
2597
|
-
import { join as join4, basename as basename2 } from "path";
|
|
2598
|
-
var DEFAULT_CODEX_DB_PATH = join4(homedir4(), ".codex", "state_5.sqlite");
|
|
2599
|
-
var DEFAULT_CODEX_CONFIG_PATH = join4(homedir4(), ".codex", "config.toml");
|
|
2600
|
-
var DEFAULT_CODEWITH_DB_PATH = join4(homedir4(), ".codewith", "state_5.sqlite");
|
|
2601
|
-
var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir4(), ".codewith", "config.toml");
|
|
2602
2595
|
|
|
2603
2596
|
// src/ingest/gemini.ts
|
|
2604
2597
|
init_database();
|
|
2605
2598
|
init_pricing();
|
|
2606
|
-
import { homedir as
|
|
2607
|
-
import { join as
|
|
2608
|
-
var DEFAULT_GEMINI_TMP_DIR =
|
|
2609
|
-
var DEFAULT_GEMINI_HISTORY_DIR =
|
|
2599
|
+
import { homedir as homedir4 } from "os";
|
|
2600
|
+
import { join as join4, basename as basename2 } from "path";
|
|
2601
|
+
var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
|
|
2602
|
+
var DEFAULT_GEMINI_HISTORY_DIR = join4(homedir4(), ".gemini", "history");
|
|
2610
2603
|
|
|
2611
2604
|
// src/ingest/opencode.ts
|
|
2612
2605
|
init_database();
|
|
2613
2606
|
init_pricing();
|
|
2614
|
-
import { homedir as
|
|
2615
|
-
import { join as
|
|
2616
|
-
var OPENCODE_STORAGE =
|
|
2607
|
+
import { homedir as homedir5 } from "os";
|
|
2608
|
+
import { join as join5 } from "path";
|
|
2609
|
+
var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
|
|
2617
2610
|
|
|
2618
2611
|
// src/ingest/quota-observation.ts
|
|
2619
2612
|
init_database();
|
|
@@ -2621,15 +2614,15 @@ init_observation_provenance();
|
|
|
2621
2614
|
|
|
2622
2615
|
// src/ingest/pi.ts
|
|
2623
2616
|
init_database();
|
|
2624
|
-
import { homedir as
|
|
2625
|
-
import { join as
|
|
2626
|
-
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ??
|
|
2617
|
+
import { homedir as homedir6 } from "os";
|
|
2618
|
+
import { join as join6 } from "path";
|
|
2619
|
+
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
|
|
2627
2620
|
|
|
2628
2621
|
// src/ingest/hermes.ts
|
|
2629
2622
|
init_database();
|
|
2630
|
-
import { homedir as
|
|
2631
|
-
import { join as
|
|
2632
|
-
var HERMES_DB =
|
|
2623
|
+
import { homedir as homedir7 } from "os";
|
|
2624
|
+
import { join as join7 } from "path";
|
|
2625
|
+
var HERMES_DB = join7(homedir7(), ".hermes", "state.db");
|
|
2633
2626
|
|
|
2634
2627
|
// src/ingest/loops.ts
|
|
2635
2628
|
init_database();
|
|
@@ -2702,7 +2695,7 @@ init_ingest_validation();
|
|
|
2702
2695
|
init_database();
|
|
2703
2696
|
import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync as mkdirSync3, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
|
|
2704
2697
|
import { createHash as createHash4 } from "crypto";
|
|
2705
|
-
import { join as
|
|
2698
|
+
import { join as join8, resolve as resolve2 } from "path";
|
|
2706
2699
|
var MAX_COMMANDS = 1000;
|
|
2707
2700
|
var MAX_BYTES = 64 * 1024 * 1024;
|
|
2708
2701
|
var RECEIPT_RESERVE = 4096;
|
|
@@ -2730,7 +2723,7 @@ function definitiveIngestRefusal(status, body) {
|
|
|
2730
2723
|
};
|
|
2731
2724
|
}
|
|
2732
2725
|
function getIngestJournalPath() {
|
|
2733
|
-
return process.env.HASNA_ECONOMY_INGEST_JOURNAL ??
|
|
2726
|
+
return process.env.HASNA_ECONOMY_INGEST_JOURNAL ?? join8(cacheDir({ app: "economy", home: process.env.HOME || process.env.USERPROFILE }), "pending-ingest");
|
|
2734
2727
|
}
|
|
2735
2728
|
function targetId(cloud) {
|
|
2736
2729
|
const url = new URL(cloud.client.transport.baseUrl);
|
|
@@ -2833,7 +2826,7 @@ class IngestJournal {
|
|
|
2833
2826
|
constructor(cloud, path = getIngestJournalPath()) {
|
|
2834
2827
|
this.target = targetId(cloud);
|
|
2835
2828
|
secureDirectory(path);
|
|
2836
|
-
this.directory =
|
|
2829
|
+
this.directory = join8(realpathSync(path), this.target);
|
|
2837
2830
|
secureDirectory(this.directory);
|
|
2838
2831
|
}
|
|
2839
2832
|
names() {
|
|
@@ -2859,7 +2852,7 @@ class IngestJournal {
|
|
|
2859
2852
|
throw new Error("Orphaned ingest outcome requires maintenance recovery");
|
|
2860
2853
|
if (name.endsWith(".refusal.json") && set.has(name.replace(".refusal.", ".receipt.")))
|
|
2861
2854
|
throw new Error("Conflicting ingest receipt and refusal");
|
|
2862
|
-
const stat = lstatSync(
|
|
2855
|
+
const stat = lstatSync(join8(this.directory, name));
|
|
2863
2856
|
if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0)
|
|
2864
2857
|
throw new Error("Unsafe ingest journal record");
|
|
2865
2858
|
if (stat.size > MAX_INGEST_BYTES + 4096)
|
|
@@ -2871,10 +2864,10 @@ class IngestJournal {
|
|
|
2871
2864
|
return names;
|
|
2872
2865
|
}
|
|
2873
2866
|
file(key, kind) {
|
|
2874
|
-
return
|
|
2867
|
+
return join8(this.directory, `${sha(key)}.${kind}.json`);
|
|
2875
2868
|
}
|
|
2876
2869
|
intent(name) {
|
|
2877
|
-
const record = readJson(
|
|
2870
|
+
const record = readJson(join8(this.directory, name), MAX_INGEST_BYTES + 4096);
|
|
2878
2871
|
if (record.version !== 1 || record.target !== this.target || !record.command || typeof record.command.idempotency_key !== "string" || record.body_hash !== canonicalIngestBodyHash(record.command) || name !== `${sha(record.command.idempotency_key)}.intent.json`)
|
|
2879
2872
|
throw new Error("Invalid ingest journal intent");
|
|
2880
2873
|
validateIngest(record.command);
|
|
@@ -3033,7 +3026,7 @@ class IngestJournal {
|
|
|
3033
3026
|
// src/lib/ingest-journal-maintenance.ts
|
|
3034
3027
|
import { constants as constants2, closeSync as closeSync2, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
3035
3028
|
import { createHash as createHash5 } from "crypto";
|
|
3036
|
-
import { join as
|
|
3029
|
+
import { join as join9 } from "path";
|
|
3037
3030
|
var uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
3038
3031
|
var digest2 = /^[a-f0-9]{64}$/;
|
|
3039
3032
|
var sha2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
@@ -3093,7 +3086,7 @@ async function confirm(cloud, anchor, deadline) {
|
|
|
3093
3086
|
}
|
|
3094
3087
|
async function verifyJournalAuthority(journal, cloud) {
|
|
3095
3088
|
journal.assertCloud(cloud);
|
|
3096
|
-
const path =
|
|
3089
|
+
const path = join9(journal.directory, ".authority.json");
|
|
3097
3090
|
if (!exists(path)) {
|
|
3098
3091
|
const names = journal.names();
|
|
3099
3092
|
const name = names.find((n) => n.endsWith(".intent.json") && names.includes(n.replace(".intent.", ".receipt.")));
|
|
@@ -3137,8 +3130,8 @@ class IngestJournalMaintenance {
|
|
|
3137
3130
|
this.cloud = cloud;
|
|
3138
3131
|
this.factory = factory;
|
|
3139
3132
|
journal.assertCloud(cloud);
|
|
3140
|
-
this.control =
|
|
3141
|
-
this.operationRoot =
|
|
3133
|
+
this.control = join9(journal.directory, ".maintenance");
|
|
3134
|
+
this.operationRoot = join9(journal.directory, ".recovery");
|
|
3142
3135
|
}
|
|
3143
3136
|
manifest() {
|
|
3144
3137
|
secureDirectory(this.control);
|
|
@@ -3147,10 +3140,10 @@ class IngestJournalMaintenance {
|
|
|
3147
3140
|
throw new Error("Unexpected maintenance entry");
|
|
3148
3141
|
let size = 0;
|
|
3149
3142
|
for (const name of names)
|
|
3150
|
-
size += lstatSync2(
|
|
3143
|
+
size += lstatSync2(join9(this.control, name)).size;
|
|
3151
3144
|
if (size > MAINTENANCE_RESERVE)
|
|
3152
3145
|
throw new Error("Maintenance metadata exceeds reserve");
|
|
3153
|
-
const m = validAnchor(readJson(
|
|
3146
|
+
const m = validAnchor(readJson(join9(this.control, "manifest.json"), 8192), this.journal.target);
|
|
3154
3147
|
for (const part of [m.intent, m.receipt_file])
|
|
3155
3148
|
if (!part || !digest2.test(part.sha256) || !digest2.test(part.capture_sha256 ?? "") || !Number.isSafeInteger(part.sizeBytes) || part.sizeBytes < 1 || part.sizeBytes > 4198400 || part.mode !== 384 || !/^\d+$/.test(part.device) || !/^\d+$/.test(part.inode) || !Number.isFinite(part.ctime))
|
|
3156
3149
|
throw new Error("Invalid maintenance snapshot");
|
|
@@ -3175,7 +3168,7 @@ class IngestJournalMaintenance {
|
|
|
3175
3168
|
if (!operation.receipt || operation.receipt.sha256 !== expected.capture_sha256 || operation.receipt.sizeBytes !== expected.sizeBytes || operation.receipt.mode !== expected.mode)
|
|
3176
3169
|
throw new Error("Trash snapshot mismatch");
|
|
3177
3170
|
sameSource(operation.path, expected);
|
|
3178
|
-
const path =
|
|
3171
|
+
const path = join9(this.control, `${kind}.operation.json`);
|
|
3179
3172
|
createExact(path, { id: operation.id });
|
|
3180
3173
|
if (readJson(path, 1024).id !== operation.id)
|
|
3181
3174
|
throw new Error("Conflicting Trash operation");
|
|
@@ -3200,7 +3193,7 @@ class IngestJournalMaintenance {
|
|
|
3200
3193
|
async pairFile(backend, m, kind, deadline) {
|
|
3201
3194
|
await confirm(this.cloud, m, deadline);
|
|
3202
3195
|
const path = this.journal.file(m.key, kind), expected = kind === "receipt" ? m.receipt_file : m.intent;
|
|
3203
|
-
const saved =
|
|
3196
|
+
const saved = join9(this.control, `${kind}.operation.json`);
|
|
3204
3197
|
let id;
|
|
3205
3198
|
if (exists(saved))
|
|
3206
3199
|
id = readJson(saved, 1024).id;
|
|
@@ -3306,14 +3299,14 @@ class IngestJournalMaintenance {
|
|
|
3306
3299
|
key: intent.command.idempotency_key,
|
|
3307
3300
|
body_hash: intent.body_hash,
|
|
3308
3301
|
receipt,
|
|
3309
|
-
intent: snapshot(
|
|
3302
|
+
intent: snapshot(join9(this.journal.directory, name)),
|
|
3310
3303
|
receipt_file: snapshot(this.journal.file(intent.command.idempotency_key, "receipt"))
|
|
3311
3304
|
};
|
|
3312
3305
|
await confirm(this.cloud, m, deadline);
|
|
3313
3306
|
m.intent.capture_sha256 = backend.snapshot(this.journal.file(m.key, "intent")).sha256;
|
|
3314
3307
|
m.receipt_file.capture_sha256 = backend.snapshot(this.journal.file(m.key, "receipt")).sha256;
|
|
3315
3308
|
secureDirectory(this.control);
|
|
3316
|
-
if (!createExact(
|
|
3309
|
+
if (!createExact(join9(this.control, "manifest.json"), m))
|
|
3317
3310
|
throw new Error("Existing maintenance transaction");
|
|
3318
3311
|
await this.finish(backend, deadline);
|
|
3319
3312
|
retired++;
|
|
@@ -3345,7 +3338,7 @@ class IngestJournalMaintenance {
|
|
|
3345
3338
|
|
|
3346
3339
|
// src/lib/ingest-journal-lock.ts
|
|
3347
3340
|
import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync3, openSync as openSync3 } from "fs";
|
|
3348
|
-
import { join as
|
|
3341
|
+
import { join as join10 } from "path";
|
|
3349
3342
|
async function withIngestJournalLock(directory, action) {
|
|
3350
3343
|
if (process.platform !== "linux" && process.platform !== "darwin")
|
|
3351
3344
|
throw new Error("Ingest journal locking requires Linux or macOS");
|
|
@@ -3353,7 +3346,7 @@ async function withIngestJournalLock(directory, action) {
|
|
|
3353
3346
|
const library = dlopen(process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6", {
|
|
3354
3347
|
flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 }
|
|
3355
3348
|
});
|
|
3356
|
-
const path =
|
|
3349
|
+
const path = join10(directory, ".lease");
|
|
3357
3350
|
let fd;
|
|
3358
3351
|
try {
|
|
3359
3352
|
fd = openSync3(path, constants3.O_RDWR | constants3.O_CREAT | constants3.O_NOFOLLOW | constants3.O_NONBLOCK, 384);
|
|
@@ -3487,7 +3480,7 @@ async function pushIngestRows(cloud, scratch) {
|
|
|
3487
3480
|
// package.json
|
|
3488
3481
|
var package_default = {
|
|
3489
3482
|
name: "@hasna/economy",
|
|
3490
|
-
version: "0.5.
|
|
3483
|
+
version: "0.5.5",
|
|
3491
3484
|
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",
|
|
3492
3485
|
repository: {
|
|
3493
3486
|
type: "git",
|