@hasna/economy 0.5.4 → 0.5.6
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 +18 -0
- package/README.md +3 -1
- package/dist/chunks/{index-n1wwqxfe.js → index-m12xttw1.js} +3 -1
- package/dist/chunks/{index-9arv0b1j.js → index-qvcafabj.js} +63 -66
- package/dist/chunks/{index-nheq2nrz.js → index-zqsx461z.js} +1 -1
- package/dist/chunks/{index-ct3zdefz.js → index-zs5dba3t.js} +1 -1
- package/dist/chunks/{serve-ra46ms69.js → serve-ept6cfbe.js} +2 -2
- package/dist/chunks/{tui-a1tvcw2j.js → tui-4bh067a3.js} +3 -3
- package/dist/chunks/{watch-rvd3dn6r.js → watch-227kxmra.js} +3 -3
- package/dist/cli/index.js +10 -10
- package/dist/index.js +3 -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 +65 -66
- package/dist/otel/index.js +34 -39
- package/dist/server/index.js +119 -71
- package/dist/server/legacy-preservation.d.ts.map +1 -1
- package/dist/server/service-worker.js +65 -66
- package/native/README.md +131 -0
- package/native/Tools/pair-qr.py +131 -0
- package/native/Tools/pair-qr.swift +223 -0
- package/package.json +3 -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.6",
|
|
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",
|
|
@@ -118,6 +118,8 @@ var package_default = {
|
|
|
118
118
|
files: [
|
|
119
119
|
"dist",
|
|
120
120
|
"docs",
|
|
121
|
+
"native/Tools/pair-qr.py",
|
|
122
|
+
"native/Tools/pair-qr.swift",
|
|
121
123
|
"postinstall.js",
|
|
122
124
|
"LICENSE",
|
|
123
125
|
"README.md",
|
|
@@ -1556,8 +1558,8 @@ function sourceTimeMillis(value) {
|
|
|
1556
1558
|
|
|
1557
1559
|
// src/ingest/claude.ts
|
|
1558
1560
|
import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync3, statSync } from "fs";
|
|
1559
|
-
import { homedir as
|
|
1560
|
-
import { join as
|
|
1561
|
+
import { homedir as homedir3 } from "os";
|
|
1562
|
+
import { join as join7, basename } from "path";
|
|
1561
1563
|
|
|
1562
1564
|
// src/lib/accounts-store.ts
|
|
1563
1565
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
@@ -2164,12 +2166,41 @@ function withAccount(record, account) {
|
|
|
2164
2166
|
};
|
|
2165
2167
|
}
|
|
2166
2168
|
|
|
2169
|
+
// src/lib/paths.ts
|
|
2170
|
+
import { homedir as homedir2 } from "os";
|
|
2171
|
+
import { join as join6 } from "path";
|
|
2172
|
+
function getHomeDir() {
|
|
2173
|
+
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
|
|
2174
|
+
}
|
|
2175
|
+
function agentPaths() {
|
|
2176
|
+
const home = getHomeDir();
|
|
2177
|
+
const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join6(home, ".claude");
|
|
2178
|
+
const codexHome = process.env["CODEX_HOME"] || join6(home, ".codex");
|
|
2179
|
+
return {
|
|
2180
|
+
claudeProjects: join6(claudeHome, "projects"),
|
|
2181
|
+
claudeCredentials: join6(claudeHome, ".credentials.json"),
|
|
2182
|
+
takumiProjects: join6(home, ".takumi", "projects"),
|
|
2183
|
+
codexDir: codexHome,
|
|
2184
|
+
codexDb: join6(codexHome, "state_5.sqlite"),
|
|
2185
|
+
codexAuth: join6(codexHome, "auth.json"),
|
|
2186
|
+
codexConfig: join6(codexHome, "config.toml"),
|
|
2187
|
+
geminiTmp: join6(home, ".gemini", "tmp"),
|
|
2188
|
+
geminiHistory: join6(home, ".gemini", "history"),
|
|
2189
|
+
opencodeMessages: join6(home, ".local", "share", "opencode", "storage", "message"),
|
|
2190
|
+
piSessions: join6(home, ".pi", "agent", "sessions"),
|
|
2191
|
+
hermesDir: join6(home, ".hermes"),
|
|
2192
|
+
hermesDb: join6(home, ".hermes", "state.db")
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2167
2196
|
// src/ingest/claude.ts
|
|
2168
2197
|
function autoDetectProject(cwd, projects) {
|
|
2169
2198
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
2170
2199
|
}
|
|
2171
|
-
var
|
|
2172
|
-
|
|
2200
|
+
var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join7(homedir3(), ".takumi", "projects");
|
|
2201
|
+
function claudeProjectsDir() {
|
|
2202
|
+
return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
|
|
2203
|
+
}
|
|
2173
2204
|
function dirNameToPath(dirName) {
|
|
2174
2205
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
2175
2206
|
}
|
|
@@ -2179,9 +2210,9 @@ function collectJsonlFiles(projectDir) {
|
|
|
2179
2210
|
try {
|
|
2180
2211
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
2181
2212
|
if (entry.isDirectory())
|
|
2182
|
-
walk(
|
|
2213
|
+
walk(join7(dir, entry.name));
|
|
2183
2214
|
else if (entry.name.endsWith(".jsonl"))
|
|
2184
|
-
files.push(
|
|
2215
|
+
files.push(join7(dir, entry.name));
|
|
2185
2216
|
}
|
|
2186
2217
|
} catch {
|
|
2187
2218
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -2190,7 +2221,7 @@ function collectJsonlFiles(projectDir) {
|
|
|
2190
2221
|
walk(projectDir);
|
|
2191
2222
|
return files;
|
|
2192
2223
|
}
|
|
2193
|
-
async function ingestClaude(db, verbose = false, projectsDir =
|
|
2224
|
+
async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
|
|
2194
2225
|
return ingestJsonlProjects(db, projectsDir, "claude", verbose);
|
|
2195
2226
|
}
|
|
2196
2227
|
async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
|
|
@@ -2212,7 +2243,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
2212
2243
|
const ingestState = loadIngestState(db, agentName);
|
|
2213
2244
|
const projectDirs = readdirSync3(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
2214
2245
|
for (const projectDirEntry of projectDirs) {
|
|
2215
|
-
const projectDirPath =
|
|
2246
|
+
const projectDirPath = join7(projectsDir, projectDirEntry.name);
|
|
2216
2247
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
2217
2248
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
2218
2249
|
for (const filePath of jsonlFiles) {
|
|
@@ -2361,24 +2392,19 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
2361
2392
|
// src/ingest/codex.ts
|
|
2362
2393
|
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
2363
2394
|
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");
|
|
2395
|
+
import { join as join8, basename as basename2 } from "path";
|
|
2370
2396
|
var CODEX_INGEST_VERSION = "rollout-events-v4";
|
|
2371
2397
|
function codexDbPath() {
|
|
2372
|
-
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ??
|
|
2398
|
+
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
|
|
2373
2399
|
}
|
|
2374
2400
|
function codexConfigPath() {
|
|
2375
|
-
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ??
|
|
2401
|
+
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
|
|
2376
2402
|
}
|
|
2377
2403
|
function codewithDbPath() {
|
|
2378
|
-
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ??
|
|
2404
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join8(getHomeDir(), ".codewith", "state_5.sqlite");
|
|
2379
2405
|
}
|
|
2380
2406
|
function codewithConfigPath() {
|
|
2381
|
-
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ??
|
|
2407
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join8(getHomeDir(), ".codewith", "config.toml");
|
|
2382
2408
|
}
|
|
2383
2409
|
function codexSources() {
|
|
2384
2410
|
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
@@ -2390,7 +2416,7 @@ function codexSources() {
|
|
|
2390
2416
|
sessionPrefix: "codex",
|
|
2391
2417
|
stateSource: "codex"
|
|
2392
2418
|
}];
|
|
2393
|
-
if (!explicitCodexPath || explicitCodewithPath) {
|
|
2419
|
+
if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
|
|
2394
2420
|
sources.push({
|
|
2395
2421
|
label: "Codewith",
|
|
2396
2422
|
dbPath: codewithDbPath(),
|
|
@@ -2628,9 +2654,9 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2628
2654
|
// src/ingest/gemini.ts
|
|
2629
2655
|
import { readdirSync as readdirSync4, readFileSync as readFileSync6, existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
2630
2656
|
import { homedir as homedir4 } from "os";
|
|
2631
|
-
import { join as
|
|
2632
|
-
var DEFAULT_GEMINI_TMP_DIR =
|
|
2633
|
-
var DEFAULT_GEMINI_HISTORY_DIR =
|
|
2657
|
+
import { join as join9, basename as basename3 } from "path";
|
|
2658
|
+
var DEFAULT_GEMINI_TMP_DIR = join9(homedir4(), ".gemini", "tmp");
|
|
2659
|
+
var DEFAULT_GEMINI_HISTORY_DIR = join9(homedir4(), ".gemini", "history");
|
|
2634
2660
|
function geminiTmpDir() {
|
|
2635
2661
|
return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
|
|
2636
2662
|
}
|
|
@@ -2652,7 +2678,7 @@ function listProjectDirs(...roots) {
|
|
|
2652
2678
|
try {
|
|
2653
2679
|
for (const entry of readdirSync4(root, { withFileTypes: true })) {
|
|
2654
2680
|
if (entry.isDirectory())
|
|
2655
|
-
dirs.add(
|
|
2681
|
+
dirs.add(join9(root, entry.name));
|
|
2656
2682
|
}
|
|
2657
2683
|
} catch {
|
|
2658
2684
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -2665,7 +2691,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
2665
2691
|
return chatData.projectPath;
|
|
2666
2692
|
if (chatData.project_path)
|
|
2667
2693
|
return chatData.project_path;
|
|
2668
|
-
const rootFile =
|
|
2694
|
+
const rootFile = join9(projectDir, ".project_root");
|
|
2669
2695
|
try {
|
|
2670
2696
|
if (existsSync5(rootFile))
|
|
2671
2697
|
return readFileSync6(rootFile, "utf-8").trim();
|
|
@@ -2690,12 +2716,12 @@ async function ingestGemini(db, verbose) {
|
|
|
2690
2716
|
const account = await resolveAccountForAgent("gemini");
|
|
2691
2717
|
const projectDirs = listProjectDirs(tmpDir, historyDir);
|
|
2692
2718
|
for (const projectDir of projectDirs) {
|
|
2693
|
-
const chatsDir =
|
|
2719
|
+
const chatsDir = join9(projectDir, "chats");
|
|
2694
2720
|
if (!existsSync5(chatsDir))
|
|
2695
2721
|
continue;
|
|
2696
2722
|
let chatFiles = [];
|
|
2697
2723
|
try {
|
|
2698
|
-
chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2724
|
+
chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join9(chatsDir, f));
|
|
2699
2725
|
} catch {
|
|
2700
2726
|
collectionProblem("failed", "source_unreadable");
|
|
2701
2727
|
continue;
|
|
@@ -2805,13 +2831,13 @@ async function ingestGemini(db, verbose) {
|
|
|
2805
2831
|
// src/ingest/opencode.ts
|
|
2806
2832
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
2807
2833
|
import { homedir as homedir5 } from "os";
|
|
2808
|
-
import { join as
|
|
2809
|
-
var OPENCODE_STORAGE =
|
|
2834
|
+
import { join as join10 } from "path";
|
|
2835
|
+
var OPENCODE_STORAGE = join10(homedir5(), ".local", "share", "opencode", "storage");
|
|
2810
2836
|
function walkJsonFiles(dir, acc = []) {
|
|
2811
2837
|
if (!existsSync6(dir))
|
|
2812
2838
|
return acc;
|
|
2813
2839
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
2814
|
-
const p =
|
|
2840
|
+
const p = join10(dir, entry.name);
|
|
2815
2841
|
if (entry.isDirectory())
|
|
2816
2842
|
walkJsonFiles(p, acc);
|
|
2817
2843
|
else if (entry.name.endsWith(".json"))
|
|
@@ -2831,7 +2857,7 @@ function parseSessionIdFromPath(filePath) {
|
|
|
2831
2857
|
return null;
|
|
2832
2858
|
}
|
|
2833
2859
|
async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
|
|
2834
|
-
const messageDir =
|
|
2860
|
+
const messageDir = join10(sourceDir, "message");
|
|
2835
2861
|
if (!existsSync6(messageDir))
|
|
2836
2862
|
collectionProblem("missing", "source_missing");
|
|
2837
2863
|
const files = walkJsonFiles(messageDir);
|
|
@@ -3023,13 +3049,13 @@ async function ingestCursor(db, verbose = false) {
|
|
|
3023
3049
|
// src/ingest/pi.ts
|
|
3024
3050
|
import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
3025
3051
|
import { homedir as homedir6 } from "os";
|
|
3026
|
-
import { join as
|
|
3027
|
-
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ??
|
|
3052
|
+
import { join as join11 } from "path";
|
|
3053
|
+
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join11(homedir6(), ".pi", "agent", "sessions");
|
|
3028
3054
|
function walkSessions(dir, acc = []) {
|
|
3029
3055
|
if (!existsSync7(dir))
|
|
3030
3056
|
return acc;
|
|
3031
3057
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
3032
|
-
const p =
|
|
3058
|
+
const p = join11(dir, entry.name);
|
|
3033
3059
|
if (entry.isDirectory())
|
|
3034
3060
|
walkSessions(p, acc);
|
|
3035
3061
|
else if (entry.name.endsWith(".json"))
|
|
@@ -3129,8 +3155,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
|
|
|
3129
3155
|
// src/ingest/hermes.ts
|
|
3130
3156
|
import { existsSync as existsSync8, statSync as statSync6 } from "fs";
|
|
3131
3157
|
import { homedir as homedir7 } from "os";
|
|
3132
|
-
import { join as
|
|
3133
|
-
var HERMES_DB =
|
|
3158
|
+
import { join as join12 } from "path";
|
|
3159
|
+
var HERMES_DB = join12(homedir7(), ".hermes", "state.db");
|
|
3134
3160
|
async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
|
|
3135
3161
|
if (!existsSync8(sourcePath)) {
|
|
3136
3162
|
collectionProblem("missing", "source_missing");
|
|
@@ -3198,7 +3224,7 @@ function statSyncSafe(path) {
|
|
|
3198
3224
|
// src/ingest/loops.ts
|
|
3199
3225
|
import { existsSync as existsSync9 } from "fs";
|
|
3200
3226
|
import { homedir as homedir8 } from "os";
|
|
3201
|
-
import { join as
|
|
3227
|
+
import { join as join13 } from "path";
|
|
3202
3228
|
import { homedir as pathsResolverHomedir } from "os";
|
|
3203
3229
|
import { join as pathsResolverJoin } from "path";
|
|
3204
3230
|
var PATHS_RESOLVER_KIND_ENV = {
|
|
@@ -3265,8 +3291,8 @@ function defaultLoopsDbPath() {
|
|
|
3265
3291
|
app: "loops",
|
|
3266
3292
|
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
3267
3293
|
});
|
|
3268
|
-
const legacy =
|
|
3269
|
-
return existsSync9(
|
|
3294
|
+
const legacy = join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
3295
|
+
return existsSync9(join13(resolved, "loops.db")) ? join13(resolved, "loops.db") : legacy;
|
|
3270
3296
|
}
|
|
3271
3297
|
function loopsDbPath() {
|
|
3272
3298
|
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
@@ -3450,33 +3476,6 @@ async function ingestLoops(db, verbose = false) {
|
|
|
3450
3476
|
|
|
3451
3477
|
// src/ingest/claude-quota.ts
|
|
3452
3478
|
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
3479
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
3481
3480
|
var OAUTH_BETA = "oauth-2025-04-20";
|
|
3482
3481
|
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.6",
|
|
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",
|
|
@@ -3520,6 +3513,8 @@ var package_default = {
|
|
|
3520
3513
|
files: [
|
|
3521
3514
|
"dist",
|
|
3522
3515
|
"docs",
|
|
3516
|
+
"native/Tools/pair-qr.py",
|
|
3517
|
+
"native/Tools/pair-qr.swift",
|
|
3523
3518
|
"postinstall.js",
|
|
3524
3519
|
"LICENSE",
|
|
3525
3520
|
"README.md",
|