@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Transfer preserved original rows and temporary restore rehearsals in bounded queries while retaining complete-session checks and exact PostgreSQL row bytes.
|
|
8
|
+
- Accept current Apple iPhone development profiles that list iOS alongside xrOS and visionOS, while retaining exact signing identity, bundle, device and expiry checks.
|
|
9
|
+
- Connect iPhone with QR scanning or secure Paste, and approve a Mac's expiring QR from the phone. Store independent Economy-only device sessions and retain workspace, scope and revocation checks.
|
|
10
|
+
|
|
11
|
+
Include the Terminal pairing launcher and renderer in the installed package so an agent can open the owner's QR window using a Secrets reference.
|
|
12
|
+
|
|
13
|
+
- Pass the exact Apple team and bundle verification requirement as literal codesign source for native release checks.
|
|
14
|
+
|
|
15
|
+
## 0.5.5
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Read Codex and Claude usage and quota credentials from their selected native profile directories while preserving explicit Economy source overrides. A missing selected profile stays missing instead of reading another account's default directory. An explicitly selected Codex profile includes the legacy Codewith reader only when its source is explicitly configured too.
|
|
20
|
+
|
|
3
21
|
## 0.5.4
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -128,7 +128,9 @@ economy sync --recalculate
|
|
|
128
128
|
economy sync --backfill-machine
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Full sync also imports active project metadata from `@hasna/projects` when the registry is available.
|
|
131
|
+
Full sync also imports active project metadata from `@hasna/projects` when the registry is available. Codex reads `CODEX_HOME/state_5.sqlite` and Claude reads `CLAUDE_CONFIG_DIR/projects` when those native profile directories are set; otherwise they use `~/.codex/state_5.sqlite` and `~/.claude/projects`. Explicit `HASNA_ECONOMY_CODEX_DB_PATH` and `HASNA_ECONOMY_CLAUDE_PROJECTS_DIR` overrides take precedence. Codex configuration follows `CODEX_HOME/config.toml` unless `HASNA_ECONOMY_CODEX_CONFIG_PATH` is set. Quota credentials follow those same native profile directories, retaining the existing OAuth-token environment overrides. A missing selected source or credential is reported as missing, without falling back to another profile.
|
|
132
|
+
|
|
133
|
+
The existing Codewith compatibility reader uses `~/.codewith/state_5.sqlite`, with `HASNA_ECONOMY_CODEWITH_DB_PATH` as its explicit override. When `CODEX_HOME` or `HASNA_ECONOMY_CODEX_DB_PATH` explicitly selects a Codex source, the compatibility reader runs only if `HASNA_ECONOMY_CODEWITH_DB_PATH` is also set.
|
|
132
134
|
|
|
133
135
|
Account attribution uses the hosted Subscriptions registry through the installed `subscriptions` CLI and its own credential resolver. Economy reads `subscriptions list --json` and `subscriptions tools list --json` once per resolution, retaining the owning package's station-local paths and applied bindings. It does not activate a profile or copy credentials. A matching explicit agent directory takes precedence; an unmatched or ambiguous directory cannot inherit another profile. With no explicit directory, a station's applied binding can supply attribution; the shared registry's current selection alone cannot identify the local account.
|
|
134
136
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
// package.json
|
|
21
21
|
var package_default = {
|
|
22
22
|
name: "@hasna/economy",
|
|
23
|
-
version: "0.5.
|
|
23
|
+
version: "0.5.6",
|
|
24
24
|
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",
|
|
25
25
|
repository: {
|
|
26
26
|
type: "git",
|
|
@@ -53,6 +53,8 @@ var package_default = {
|
|
|
53
53
|
files: [
|
|
54
54
|
"dist",
|
|
55
55
|
"docs",
|
|
56
|
+
"native/Tools/pair-qr.py",
|
|
57
|
+
"native/Tools/pair-qr.swift",
|
|
56
58
|
"postinstall.js",
|
|
57
59
|
"LICENSE",
|
|
58
60
|
"README.md",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
LOCAL_STORAGE_OPT_IN_KEYS
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-m12xttw1.js";
|
|
5
5
|
import {
|
|
6
6
|
assessCostFromDb,
|
|
7
7
|
getIngestState,
|
|
@@ -71,8 +71,8 @@ function sourceTimeMillis(value) {
|
|
|
71
71
|
|
|
72
72
|
// src/ingest/claude.ts
|
|
73
73
|
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2, statSync } from "fs";
|
|
74
|
-
import { homedir as
|
|
75
|
-
import { join as
|
|
74
|
+
import { homedir as homedir3 } from "os";
|
|
75
|
+
import { join as join3, basename } from "path";
|
|
76
76
|
|
|
77
77
|
// src/lib/accounts-store.ts
|
|
78
78
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -679,12 +679,41 @@ function withAccount(record, account) {
|
|
|
679
679
|
};
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
// src/lib/paths.ts
|
|
683
|
+
import { homedir as homedir2 } from "os";
|
|
684
|
+
import { join as join2 } from "path";
|
|
685
|
+
function getHomeDir() {
|
|
686
|
+
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
|
|
687
|
+
}
|
|
688
|
+
function agentPaths() {
|
|
689
|
+
const home = getHomeDir();
|
|
690
|
+
const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join2(home, ".claude");
|
|
691
|
+
const codexHome = process.env["CODEX_HOME"] || join2(home, ".codex");
|
|
692
|
+
return {
|
|
693
|
+
claudeProjects: join2(claudeHome, "projects"),
|
|
694
|
+
claudeCredentials: join2(claudeHome, ".credentials.json"),
|
|
695
|
+
takumiProjects: join2(home, ".takumi", "projects"),
|
|
696
|
+
codexDir: codexHome,
|
|
697
|
+
codexDb: join2(codexHome, "state_5.sqlite"),
|
|
698
|
+
codexAuth: join2(codexHome, "auth.json"),
|
|
699
|
+
codexConfig: join2(codexHome, "config.toml"),
|
|
700
|
+
geminiTmp: join2(home, ".gemini", "tmp"),
|
|
701
|
+
geminiHistory: join2(home, ".gemini", "history"),
|
|
702
|
+
opencodeMessages: join2(home, ".local", "share", "opencode", "storage", "message"),
|
|
703
|
+
piSessions: join2(home, ".pi", "agent", "sessions"),
|
|
704
|
+
hermesDir: join2(home, ".hermes"),
|
|
705
|
+
hermesDb: join2(home, ".hermes", "state.db")
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
682
709
|
// src/ingest/claude.ts
|
|
683
710
|
function autoDetectProject(cwd, projects) {
|
|
684
711
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
685
712
|
}
|
|
686
|
-
var
|
|
687
|
-
|
|
713
|
+
var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join3(homedir3(), ".takumi", "projects");
|
|
714
|
+
function claudeProjectsDir() {
|
|
715
|
+
return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
|
|
716
|
+
}
|
|
688
717
|
function dirNameToPath(dirName) {
|
|
689
718
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
690
719
|
}
|
|
@@ -694,9 +723,9 @@ function collectJsonlFiles(projectDir) {
|
|
|
694
723
|
try {
|
|
695
724
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
696
725
|
if (entry.isDirectory())
|
|
697
|
-
walk(
|
|
726
|
+
walk(join3(dir, entry.name));
|
|
698
727
|
else if (entry.name.endsWith(".jsonl"))
|
|
699
|
-
files.push(
|
|
728
|
+
files.push(join3(dir, entry.name));
|
|
700
729
|
}
|
|
701
730
|
} catch {
|
|
702
731
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -705,7 +734,7 @@ function collectJsonlFiles(projectDir) {
|
|
|
705
734
|
walk(projectDir);
|
|
706
735
|
return files;
|
|
707
736
|
}
|
|
708
|
-
async function ingestClaude(db, verbose = false, projectsDir =
|
|
737
|
+
async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
|
|
709
738
|
return ingestJsonlProjects(db, projectsDir, "claude", verbose);
|
|
710
739
|
}
|
|
711
740
|
async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
|
|
@@ -727,7 +756,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
727
756
|
const ingestState = loadIngestState(db, agentName);
|
|
728
757
|
const projectDirs = readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
729
758
|
for (const projectDirEntry of projectDirs) {
|
|
730
|
-
const projectDirPath =
|
|
759
|
+
const projectDirPath = join3(projectsDir, projectDirEntry.name);
|
|
731
760
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
732
761
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
733
762
|
for (const filePath of jsonlFiles) {
|
|
@@ -876,24 +905,19 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
876
905
|
// src/ingest/codex.ts
|
|
877
906
|
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
878
907
|
import { createHash } from "crypto";
|
|
879
|
-
import {
|
|
880
|
-
import { join as join3, basename as basename2 } from "path";
|
|
881
|
-
var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
|
|
882
|
-
var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
|
|
883
|
-
var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
|
|
884
|
-
var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
|
|
908
|
+
import { join as join4, basename as basename2 } from "path";
|
|
885
909
|
var CODEX_INGEST_VERSION = "rollout-events-v4";
|
|
886
910
|
function codexDbPath() {
|
|
887
|
-
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ??
|
|
911
|
+
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
|
|
888
912
|
}
|
|
889
913
|
function codexConfigPath() {
|
|
890
|
-
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ??
|
|
914
|
+
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
|
|
891
915
|
}
|
|
892
916
|
function codewithDbPath() {
|
|
893
|
-
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ??
|
|
917
|
+
return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join4(getHomeDir(), ".codewith", "state_5.sqlite");
|
|
894
918
|
}
|
|
895
919
|
function codewithConfigPath() {
|
|
896
|
-
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ??
|
|
920
|
+
return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join4(getHomeDir(), ".codewith", "config.toml");
|
|
897
921
|
}
|
|
898
922
|
function codexSources() {
|
|
899
923
|
const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
|
|
@@ -905,7 +929,7 @@ function codexSources() {
|
|
|
905
929
|
sessionPrefix: "codex",
|
|
906
930
|
stateSource: "codex"
|
|
907
931
|
}];
|
|
908
|
-
if (!explicitCodexPath || explicitCodewithPath) {
|
|
932
|
+
if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
|
|
909
933
|
sources.push({
|
|
910
934
|
label: "Codewith",
|
|
911
935
|
dbPath: codewithDbPath(),
|
|
@@ -1143,9 +1167,9 @@ async function ingestCodex(db, verbose = false) {
|
|
|
1143
1167
|
// src/ingest/gemini.ts
|
|
1144
1168
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
1145
1169
|
import { homedir as homedir4 } from "os";
|
|
1146
|
-
import { join as
|
|
1147
|
-
var DEFAULT_GEMINI_TMP_DIR =
|
|
1148
|
-
var DEFAULT_GEMINI_HISTORY_DIR =
|
|
1170
|
+
import { join as join5, basename as basename3 } from "path";
|
|
1171
|
+
var DEFAULT_GEMINI_TMP_DIR = join5(homedir4(), ".gemini", "tmp");
|
|
1172
|
+
var DEFAULT_GEMINI_HISTORY_DIR = join5(homedir4(), ".gemini", "history");
|
|
1149
1173
|
function geminiTmpDir() {
|
|
1150
1174
|
return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
|
|
1151
1175
|
}
|
|
@@ -1167,7 +1191,7 @@ function listProjectDirs(...roots) {
|
|
|
1167
1191
|
try {
|
|
1168
1192
|
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
1169
1193
|
if (entry.isDirectory())
|
|
1170
|
-
dirs.add(
|
|
1194
|
+
dirs.add(join5(root, entry.name));
|
|
1171
1195
|
}
|
|
1172
1196
|
} catch {
|
|
1173
1197
|
collectionProblem("failed", "source_unreadable");
|
|
@@ -1180,7 +1204,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
1180
1204
|
return chatData.projectPath;
|
|
1181
1205
|
if (chatData.project_path)
|
|
1182
1206
|
return chatData.project_path;
|
|
1183
|
-
const rootFile =
|
|
1207
|
+
const rootFile = join5(projectDir, ".project_root");
|
|
1184
1208
|
try {
|
|
1185
1209
|
if (existsSync4(rootFile))
|
|
1186
1210
|
return readFileSync4(rootFile, "utf-8").trim();
|
|
@@ -1205,12 +1229,12 @@ async function ingestGemini(db, verbose) {
|
|
|
1205
1229
|
const account = await resolveAccountForAgent("gemini");
|
|
1206
1230
|
const projectDirs = listProjectDirs(tmpDir, historyDir);
|
|
1207
1231
|
for (const projectDir of projectDirs) {
|
|
1208
|
-
const chatsDir =
|
|
1232
|
+
const chatsDir = join5(projectDir, "chats");
|
|
1209
1233
|
if (!existsSync4(chatsDir))
|
|
1210
1234
|
continue;
|
|
1211
1235
|
let chatFiles = [];
|
|
1212
1236
|
try {
|
|
1213
|
-
chatFiles = readdirSync2(chatsDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
1237
|
+
chatFiles = readdirSync2(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join5(chatsDir, f));
|
|
1214
1238
|
} catch {
|
|
1215
1239
|
collectionProblem("failed", "source_unreadable");
|
|
1216
1240
|
continue;
|
|
@@ -1320,13 +1344,13 @@ async function ingestGemini(db, verbose) {
|
|
|
1320
1344
|
// src/ingest/opencode.ts
|
|
1321
1345
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
1322
1346
|
import { homedir as homedir5 } from "os";
|
|
1323
|
-
import { join as
|
|
1324
|
-
var OPENCODE_STORAGE =
|
|
1347
|
+
import { join as join6 } from "path";
|
|
1348
|
+
var OPENCODE_STORAGE = join6(homedir5(), ".local", "share", "opencode", "storage");
|
|
1325
1349
|
function walkJsonFiles(dir, acc = []) {
|
|
1326
1350
|
if (!existsSync5(dir))
|
|
1327
1351
|
return acc;
|
|
1328
1352
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1329
|
-
const p =
|
|
1353
|
+
const p = join6(dir, entry.name);
|
|
1330
1354
|
if (entry.isDirectory())
|
|
1331
1355
|
walkJsonFiles(p, acc);
|
|
1332
1356
|
else if (entry.name.endsWith(".json"))
|
|
@@ -1346,7 +1370,7 @@ function parseSessionIdFromPath(filePath) {
|
|
|
1346
1370
|
return null;
|
|
1347
1371
|
}
|
|
1348
1372
|
async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
|
|
1349
|
-
const messageDir =
|
|
1373
|
+
const messageDir = join6(sourceDir, "message");
|
|
1350
1374
|
if (!existsSync5(messageDir))
|
|
1351
1375
|
collectionProblem("missing", "source_missing");
|
|
1352
1376
|
const files = walkJsonFiles(messageDir);
|
|
@@ -1538,13 +1562,13 @@ async function ingestCursor(db, verbose = false) {
|
|
|
1538
1562
|
// src/ingest/pi.ts
|
|
1539
1563
|
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
1540
1564
|
import { homedir as homedir6 } from "os";
|
|
1541
|
-
import { join as
|
|
1542
|
-
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ??
|
|
1565
|
+
import { join as join7 } from "path";
|
|
1566
|
+
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join7(homedir6(), ".pi", "agent", "sessions");
|
|
1543
1567
|
function walkSessions(dir, acc = []) {
|
|
1544
1568
|
if (!existsSync6(dir))
|
|
1545
1569
|
return acc;
|
|
1546
1570
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
1547
|
-
const p =
|
|
1571
|
+
const p = join7(dir, entry.name);
|
|
1548
1572
|
if (entry.isDirectory())
|
|
1549
1573
|
walkSessions(p, acc);
|
|
1550
1574
|
else if (entry.name.endsWith(".json"))
|
|
@@ -1644,8 +1668,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
|
|
|
1644
1668
|
// src/ingest/hermes.ts
|
|
1645
1669
|
import { existsSync as existsSync7, statSync as statSync6 } from "fs";
|
|
1646
1670
|
import { homedir as homedir7 } from "os";
|
|
1647
|
-
import { join as
|
|
1648
|
-
var HERMES_DB =
|
|
1671
|
+
import { join as join8 } from "path";
|
|
1672
|
+
var HERMES_DB = join8(homedir7(), ".hermes", "state.db");
|
|
1649
1673
|
async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
|
|
1650
1674
|
if (!existsSync7(sourcePath)) {
|
|
1651
1675
|
collectionProblem("missing", "source_missing");
|
|
@@ -1713,7 +1737,7 @@ function statSyncSafe(path) {
|
|
|
1713
1737
|
// src/ingest/loops.ts
|
|
1714
1738
|
import { existsSync as existsSync8 } from "fs";
|
|
1715
1739
|
import { homedir as homedir8 } from "os";
|
|
1716
|
-
import { join as
|
|
1740
|
+
import { join as join9 } from "path";
|
|
1717
1741
|
import { homedir as pathsResolverHomedir } from "os";
|
|
1718
1742
|
import { join as pathsResolverJoin } from "path";
|
|
1719
1743
|
var PATHS_RESOLVER_KIND_ENV = {
|
|
@@ -1780,8 +1804,8 @@ function defaultLoopsDbPath() {
|
|
|
1780
1804
|
app: "loops",
|
|
1781
1805
|
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
1782
1806
|
});
|
|
1783
|
-
const legacy =
|
|
1784
|
-
return existsSync8(
|
|
1807
|
+
const legacy = join9(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
1808
|
+
return existsSync8(join9(resolved, "loops.db")) ? join9(resolved, "loops.db") : legacy;
|
|
1785
1809
|
}
|
|
1786
1810
|
function loopsDbPath() {
|
|
1787
1811
|
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
@@ -1965,33 +1989,6 @@ async function ingestLoops(db, verbose = false) {
|
|
|
1965
1989
|
|
|
1966
1990
|
// src/ingest/claude-quota.ts
|
|
1967
1991
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1968
|
-
|
|
1969
|
-
// src/lib/paths.ts
|
|
1970
|
-
import { homedir as homedir9 } from "os";
|
|
1971
|
-
import { join as join9 } from "path";
|
|
1972
|
-
function getHomeDir() {
|
|
1973
|
-
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
|
|
1974
|
-
}
|
|
1975
|
-
function agentPaths() {
|
|
1976
|
-
const home = getHomeDir();
|
|
1977
|
-
return {
|
|
1978
|
-
claudeProjects: join9(home, ".claude", "projects"),
|
|
1979
|
-
claudeCredentials: join9(home, ".claude", ".credentials.json"),
|
|
1980
|
-
takumiProjects: join9(home, ".takumi", "projects"),
|
|
1981
|
-
codexDir: join9(home, ".codex"),
|
|
1982
|
-
codexDb: join9(home, ".codex", "state_5.sqlite"),
|
|
1983
|
-
codexAuth: join9(home, ".codex", "auth.json"),
|
|
1984
|
-
codexConfig: join9(home, ".codex", "config.toml"),
|
|
1985
|
-
geminiTmp: join9(home, ".gemini", "tmp"),
|
|
1986
|
-
geminiHistory: join9(home, ".gemini", "history"),
|
|
1987
|
-
opencodeMessages: join9(home, ".local", "share", "opencode", "storage", "message"),
|
|
1988
|
-
piSessions: join9(home, ".pi", "agent", "sessions"),
|
|
1989
|
-
hermesDir: join9(home, ".hermes"),
|
|
1990
|
-
hermesDb: join9(home, ".hermes", "state.db")
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
// src/ingest/claude-quota.ts
|
|
1995
1992
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
1996
1993
|
var OAUTH_BETA = "oauth-2025-04-20";
|
|
1997
1994
|
function readClaudeToken() {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./index-w3bvv0ns.js";
|
|
6
6
|
import {
|
|
7
7
|
syncAll
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-qvcafabj.js";
|
|
9
9
|
import {
|
|
10
10
|
buildBrief,
|
|
11
11
|
packageMetadata,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
queryProjectDetail,
|
|
17
17
|
queryRangeStats,
|
|
18
18
|
querySavingsSummary
|
|
19
|
-
} from "./index-
|
|
19
|
+
} from "./index-m12xttw1.js";
|
|
20
20
|
import {
|
|
21
21
|
AGENTS,
|
|
22
22
|
INGEST_SCHEMA_SQL,
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
printStatusLine,
|
|
7
7
|
printWaybarJson,
|
|
8
8
|
runTui
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-zqsx461z.js";
|
|
10
10
|
import"./index-w3bvv0ns.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-zs5dba3t.js";
|
|
12
|
+
import"./index-m12xttw1.js";
|
|
13
13
|
import"./index-f5jv6q5q.js";
|
|
14
14
|
import"./index-p35cyhc4.js";
|
|
15
15
|
export {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
agentPaths,
|
|
4
4
|
syncAll
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-qvcafabj.js";
|
|
6
6
|
import {
|
|
7
7
|
getStore,
|
|
8
8
|
isCloudStore
|
|
9
|
-
} from "./index-
|
|
10
|
-
import"./index-
|
|
9
|
+
} from "./index-zs5dba3t.js";
|
|
10
|
+
import"./index-m12xttw1.js";
|
|
11
11
|
import {
|
|
12
12
|
formatSummaryValue
|
|
13
13
|
} from "./index-f5jv6q5q.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -8,22 +8,22 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
buildStatusLine,
|
|
10
10
|
gatherStatusData
|
|
11
|
-
} from "../chunks/index-
|
|
11
|
+
} from "../chunks/index-zqsx461z.js";
|
|
12
12
|
import"../chunks/index-w3bvv0ns.js";
|
|
13
13
|
import {
|
|
14
14
|
agentPaths,
|
|
15
15
|
syncAll
|
|
16
|
-
} from "../chunks/index-
|
|
16
|
+
} from "../chunks/index-qvcafabj.js";
|
|
17
17
|
import {
|
|
18
18
|
getStore,
|
|
19
19
|
isCloudStore
|
|
20
|
-
} from "../chunks/index-
|
|
20
|
+
} from "../chunks/index-zs5dba3t.js";
|
|
21
21
|
import {
|
|
22
22
|
billingDriftCheck,
|
|
23
23
|
economyCloudStorage,
|
|
24
24
|
economyTransportReport,
|
|
25
25
|
packageMetadata
|
|
26
|
-
} from "../chunks/index-
|
|
26
|
+
} from "../chunks/index-m12xttw1.js";
|
|
27
27
|
import {
|
|
28
28
|
AGENTS,
|
|
29
29
|
MAX_INGEST_BYTES,
|
|
@@ -1357,23 +1357,23 @@ function registerExtendedCommands(program) {
|
|
|
1357
1357
|
console.log();
|
|
1358
1358
|
});
|
|
1359
1359
|
program.command("tui").description("Terminal status dashboard (Linux/SSH friendly)").option("--watch", "Live refresh").option("--interval <seconds>", "Refresh interval", "30").action(async (opts) => {
|
|
1360
|
-
const { runTui } = await import("../chunks/tui-
|
|
1360
|
+
const { runTui } = await import("../chunks/tui-4bh067a3.js");
|
|
1361
1361
|
await runTui({
|
|
1362
1362
|
watch: Boolean(opts.watch),
|
|
1363
1363
|
interval: Number(opts.interval ?? 30)
|
|
1364
1364
|
});
|
|
1365
1365
|
});
|
|
1366
1366
|
program.command("waybar").description("Print waybar-compatible JSON status line").action(async () => {
|
|
1367
|
-
const { printWaybarJson } = await import("../chunks/tui-
|
|
1367
|
+
const { printWaybarJson } = await import("../chunks/tui-4bh067a3.js");
|
|
1368
1368
|
await printWaybarJson();
|
|
1369
1369
|
});
|
|
1370
1370
|
const barCmd = program.command("bar").description("Status bar helpers (Linux)");
|
|
1371
1371
|
barCmd.command("tui").description("Alias for economy tui").option("--watch", "Live refresh").action(async (opts) => {
|
|
1372
|
-
const { runTui } = await import("../chunks/tui-
|
|
1372
|
+
const { runTui } = await import("../chunks/tui-4bh067a3.js");
|
|
1373
1373
|
await runTui({ watch: Boolean(opts.watch) });
|
|
1374
1374
|
});
|
|
1375
1375
|
barCmd.command("waybar").description("Alias for economy waybar").action(async () => {
|
|
1376
|
-
const { printWaybarJson } = await import("../chunks/tui-
|
|
1376
|
+
const { printWaybarJson } = await import("../chunks/tui-4bh067a3.js");
|
|
1377
1377
|
await printWaybarJson();
|
|
1378
1378
|
});
|
|
1379
1379
|
program.command("estimate").description("Estimate cost for token counts").requiredOption("--model <model>", "Model name").option("--input <n>", "Input tokens", "0").option("--output <n>", "Output tokens", "0").action(async (opts) => {
|
|
@@ -3198,7 +3198,7 @@ program.command("accounts [period]").description("List account usage by email ad
|
|
|
3198
3198
|
console.log();
|
|
3199
3199
|
});
|
|
3200
3200
|
program.command("watch").description("Live stream of incoming costs").option("--interval <seconds>", "Poll interval in seconds", "10").option("--daemon", "Watch agent data directories and sync on change").option("--agent <agent>", `Filter by agent (${AGENTS.join("|")})`).option("--notify <amount>", "Fire macOS notification when cumulative cost crosses this USD threshold").action(async (opts) => {
|
|
3201
|
-
const { watchCosts } = await import("../chunks/watch-
|
|
3201
|
+
const { watchCosts } = await import("../chunks/watch-227kxmra.js");
|
|
3202
3202
|
await watchCosts({
|
|
3203
3203
|
interval: parsePositiveCliInteger(opts.interval ?? "10", "--interval"),
|
|
3204
3204
|
daemon: Boolean(opts.daemon),
|
|
@@ -3434,7 +3434,7 @@ pricingCmd.command("remove <model>").description("Remove pricing for a model").a
|
|
|
3434
3434
|
});
|
|
3435
3435
|
program.command("serve").description("Start the REST API server").option("-p, --port <port>", "Port", "3456").action(async (opts) => {
|
|
3436
3436
|
const port = parseCliPort(opts.port ?? "3456", "--port");
|
|
3437
|
-
const { startServer } = await import("../chunks/serve-
|
|
3437
|
+
const { startServer } = await import("../chunks/serve-ept6cfbe.js");
|
|
3438
3438
|
startServer(port);
|
|
3439
3439
|
});
|
|
3440
3440
|
program.command("mcp").description("Show MCP server install commands").option("--claude", "Install into Claude Code").option("--codex", "Install into Codex").option("--gemini", "Install into Gemini CLI").option("--all", "Install into all agents").action(async (opts) => {
|
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ import { randomUUID } from "crypto";
|
|
|
62
62
|
// package.json
|
|
63
63
|
var package_default = {
|
|
64
64
|
name: "@hasna/economy",
|
|
65
|
-
version: "0.5.
|
|
65
|
+
version: "0.5.6",
|
|
66
66
|
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",
|
|
67
67
|
repository: {
|
|
68
68
|
type: "git",
|
|
@@ -95,6 +95,8 @@ var package_default = {
|
|
|
95
95
|
files: [
|
|
96
96
|
"dist",
|
|
97
97
|
"docs",
|
|
98
|
+
"native/Tools/pair-qr.py",
|
|
99
|
+
"native/Tools/pair-qr.swift",
|
|
98
100
|
"postinstall.js",
|
|
99
101
|
"LICENSE",
|
|
100
102
|
"README.md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/ingest/claude.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAWxE,OAAO,KAAK,EAAkB,KAAK,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/ingest/claude.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAWxE,OAAO,KAAK,EAAkB,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAsE9D,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,UAAQ,EACf,WAAW,SAAsB,GAChC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEhE;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,UAAQ,EACf,WAAW,SAAsB,GAChC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEhE;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,KAAK,EAChB,OAAO,UAAQ,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoJhE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAsBxE,UAAU,eAAe;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,eAAe,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAuDD,iBAAS,cAAc,CAAC,UAAU,SAAoB,GAAG,MAAM,CAS9D;AAkCD;;uEAEuE;AACvE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CA0CpG;AAED,MAAM,WAAW,gBAAgB;IAAG,IAAI,EAAE,qCAAqC,GAAG,0BAA0B,GAAG,yBAAyB,GAAG,mBAAmB,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE;AACrL,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAmG7I;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/lib/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAIA,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,UAAU;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAIA,sEAAsE;AACtE,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,UAAU;;;;;;;;;;;;;;EAmBzB;AAED,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C"}
|