@hasna/economy 0.3.26 → 0.3.28
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 +15 -0
- package/README.md +3 -3
- package/dist/cli/index.js +189 -48
- package/dist/db/database.d.ts +39 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +142 -14
- package/dist/ingest/loops.d.ts +10 -0
- package/dist/ingest/loops.d.ts.map +1 -1
- package/dist/lib/model-config.d.ts +1 -1
- package/dist/lib/model-config.d.ts.map +1 -1
- package/dist/lib/package-metadata.d.ts.map +1 -1
- package/dist/mcp/index.js +170 -34
- package/dist/otel/index.js +142 -14
- package/dist/server/index.js +161 -25
- package/package.json +8 -2
- package/postinstall.js +31 -0
package/dist/index.js
CHANGED
|
@@ -593,7 +593,8 @@ import { execFileSync } from "child_process";
|
|
|
593
593
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
594
594
|
import { hostname, platform } from "os";
|
|
595
595
|
import { homedir } from "os";
|
|
596
|
-
import { join } from "path";
|
|
596
|
+
import { join, resolve } from "path";
|
|
597
|
+
import { dataDir } from "@hasna/paths";
|
|
597
598
|
function normalizeMachineId(value) {
|
|
598
599
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
599
600
|
return id && id.length > 0 ? id : null;
|
|
@@ -624,21 +625,48 @@ function getMachineId() {
|
|
|
624
625
|
return macHostMachineId() ?? hostMachine;
|
|
625
626
|
return hostMachine;
|
|
626
627
|
}
|
|
627
|
-
function
|
|
628
|
+
function getHomeDir() {
|
|
628
629
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
630
|
+
if (!home)
|
|
631
|
+
throw new Error("Could not resolve the user home directory");
|
|
632
|
+
return home;
|
|
633
|
+
}
|
|
634
|
+
function getResolverDataRoot() {
|
|
635
|
+
return dataDir({
|
|
636
|
+
app: "economy",
|
|
637
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function getLegacyDataRoot() {
|
|
641
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
642
|
+
}
|
|
643
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
644
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
645
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
646
|
+
return true;
|
|
647
|
+
return existsSync(join(resolved, "economy.db"));
|
|
648
|
+
}
|
|
649
|
+
function getExactDataRoot() {
|
|
650
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
651
|
+
if (dir && dir.trim())
|
|
652
|
+
return dir.trim();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
function getDataDir() {
|
|
656
|
+
const exact = getExactDataRoot();
|
|
657
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
658
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
659
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
660
|
+
mkdirSync(effective, { recursive: true });
|
|
633
661
|
for (const file of readdirSync(oldDir)) {
|
|
634
662
|
const oldPath = join(oldDir, file);
|
|
635
663
|
if (statSync(oldPath).isFile()) {
|
|
636
|
-
copyFileSync(oldPath, join(
|
|
664
|
+
copyFileSync(oldPath, join(effective, file));
|
|
637
665
|
}
|
|
638
666
|
}
|
|
639
667
|
}
|
|
640
|
-
mkdirSync(
|
|
641
|
-
return
|
|
668
|
+
mkdirSync(effective, { recursive: true });
|
|
669
|
+
return effective;
|
|
642
670
|
}
|
|
643
671
|
function getDbPath() {
|
|
644
672
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -1990,18 +2018,118 @@ function isAgent(value) {
|
|
|
1990
2018
|
init_database();
|
|
1991
2019
|
init_pricing();
|
|
1992
2020
|
import { randomUUID } from "crypto";
|
|
2021
|
+
// package.json
|
|
2022
|
+
var package_default = {
|
|
2023
|
+
name: "@hasna/economy",
|
|
2024
|
+
version: "0.3.28",
|
|
2025
|
+
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API + web dashboard for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
2026
|
+
repository: {
|
|
2027
|
+
type: "git",
|
|
2028
|
+
url: "git+https://github.com/hasna/economy.git"
|
|
2029
|
+
},
|
|
2030
|
+
bugs: {
|
|
2031
|
+
url: "https://github.com/hasna/economy/issues"
|
|
2032
|
+
},
|
|
2033
|
+
homepage: "https://github.com/hasna/economy#readme",
|
|
2034
|
+
type: "module",
|
|
2035
|
+
main: "dist/index.js",
|
|
2036
|
+
types: "dist/index.d.ts",
|
|
2037
|
+
bin: {
|
|
2038
|
+
economy: "dist/cli/index.js",
|
|
2039
|
+
"economy-mcp": "dist/mcp/index.js",
|
|
2040
|
+
"economy-serve": "dist/server/index.js",
|
|
2041
|
+
"economy-otel": "dist/otel/index.js"
|
|
2042
|
+
},
|
|
2043
|
+
exports: {
|
|
2044
|
+
".": {
|
|
2045
|
+
types: "./dist/index.d.ts",
|
|
2046
|
+
import: "./dist/index.js"
|
|
2047
|
+
},
|
|
2048
|
+
"./sdk": {
|
|
2049
|
+
types: "./dist/index.d.ts",
|
|
2050
|
+
import: "./dist/index.js"
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
files: [
|
|
2054
|
+
"dist",
|
|
2055
|
+
"docs",
|
|
2056
|
+
"dashboard/dist",
|
|
2057
|
+
"postinstall.js",
|
|
2058
|
+
"LICENSE",
|
|
2059
|
+
"README.md",
|
|
2060
|
+
"CHANGELOG.md",
|
|
2061
|
+
"SECURITY.md",
|
|
2062
|
+
"CONTRIBUTING.md",
|
|
2063
|
+
"CODE_OF_CONDUCT.md"
|
|
2064
|
+
],
|
|
2065
|
+
scripts: {
|
|
2066
|
+
build: "cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.ts --outdir dist/cli --target bun --packages external && bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external && bun build src/server/index.ts --outdir dist/server --target bun --packages external && bun build src/db/pg-sync-worker.ts --outdir dist/server --target bun --packages external && bun build src/otel/index.ts --outdir dist/otel --target bun --packages external && bun build src/index.ts --outdir dist --target bun --packages external && tsc --emitDeclarationOnly --outDir dist",
|
|
2067
|
+
"build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
|
|
2068
|
+
"build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
|
|
2069
|
+
"build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
|
|
2070
|
+
"build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
|
|
2071
|
+
"build:dashboard": "cd dashboard && bun run build",
|
|
2072
|
+
"artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
|
|
2073
|
+
prepack: "bun run build && bun run artifact-scan",
|
|
2074
|
+
typecheck: "tsc --noEmit",
|
|
2075
|
+
test: "bun test",
|
|
2076
|
+
"dev:cli": "bun run src/cli/index.ts",
|
|
2077
|
+
"dev:mcp": "bun run src/mcp/index.ts",
|
|
2078
|
+
"dev:serve": "bun run src/server/index.ts",
|
|
2079
|
+
"dev:otel": "bun run src/otel/index.ts",
|
|
2080
|
+
postinstall: "node postinstall.js"
|
|
2081
|
+
},
|
|
2082
|
+
keywords: [
|
|
2083
|
+
"economy",
|
|
2084
|
+
"cost",
|
|
2085
|
+
"ai",
|
|
2086
|
+
"claude",
|
|
2087
|
+
"codex",
|
|
2088
|
+
"gemini",
|
|
2089
|
+
"mcp",
|
|
2090
|
+
"cli",
|
|
2091
|
+
"budget",
|
|
2092
|
+
"tracking"
|
|
2093
|
+
],
|
|
2094
|
+
author: "hasna",
|
|
2095
|
+
license: "Apache-2.0",
|
|
2096
|
+
publishConfig: {
|
|
2097
|
+
registry: "https://registry.npmjs.org",
|
|
2098
|
+
access: "public"
|
|
2099
|
+
},
|
|
2100
|
+
dependencies: {
|
|
2101
|
+
"@hasna/accounts": "0.2.23",
|
|
2102
|
+
"@hasna/agent-registry": "^0.1.0",
|
|
2103
|
+
"@hasna/contracts": "0.13.4",
|
|
2104
|
+
"@hasna/events": "^0.1.16",
|
|
2105
|
+
"@hasna/mcp-harness": "^0.1.0",
|
|
2106
|
+
"@hasna/paths": "0.1.0",
|
|
2107
|
+
"@hasna/projects": "1.0.0",
|
|
2108
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
2109
|
+
chalk: "^5.4.1",
|
|
2110
|
+
commander: "^13.1.0",
|
|
2111
|
+
pg: "8.13.1",
|
|
2112
|
+
zod: "^3.24.2"
|
|
2113
|
+
},
|
|
2114
|
+
devDependencies: {
|
|
2115
|
+
"@types/bun": "latest",
|
|
2116
|
+
"@types/pg": "^8.20.0",
|
|
2117
|
+
"bun-types": "latest",
|
|
2118
|
+
typescript: "^5.7.2"
|
|
2119
|
+
},
|
|
2120
|
+
trustedDependencies: [
|
|
2121
|
+
"@hasna/projects"
|
|
2122
|
+
]
|
|
2123
|
+
};
|
|
1993
2124
|
|
|
1994
2125
|
// src/lib/package-metadata.ts
|
|
1995
|
-
import { readFileSync } from "fs";
|
|
1996
2126
|
var cachedMetadata = null;
|
|
1997
2127
|
function getPackageMetadata() {
|
|
1998
2128
|
if (cachedMetadata)
|
|
1999
2129
|
return cachedMetadata;
|
|
2000
|
-
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
|
|
2001
|
-
const parsed = JSON.parse(raw);
|
|
2002
2130
|
cachedMetadata = {
|
|
2003
|
-
name:
|
|
2004
|
-
version:
|
|
2131
|
+
name: package_default.name ?? "@hasna/economy",
|
|
2132
|
+
version: package_default.version ?? "0.0.0"
|
|
2005
2133
|
};
|
|
2006
2134
|
return cachedMetadata;
|
|
2007
2135
|
}
|
package/dist/ingest/loops.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* The default OpenLoops store path, resolved through the @hasna/paths resolver
|
|
4
|
+
* (XDG / macOS home layout): the resolver data root for `loops` once its store
|
|
5
|
+
* has been migrated there (`loops.db` present), otherwise the legacy
|
|
6
|
+
* `~/.hasna/loops/loops.db` — a legacy-read fallback, so economy keeps reading
|
|
7
|
+
* loops' store whichever side of the XDG migration loops is on. The exact-app
|
|
8
|
+
* override `HASNA_ECONOMY_LOOPS_DB_PATH` still wins unconditionally.
|
|
9
|
+
*/
|
|
10
|
+
export declare function defaultLoopsDbPath(): string;
|
|
11
|
+
export declare function loopsDbPath(): string;
|
|
2
12
|
export declare function ingestLoops(db: Database, verbose?: boolean): Promise<{
|
|
3
13
|
sessions: number;
|
|
4
14
|
requests: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loops.d.ts","sourceRoot":"","sources":["../../src/ingest/loops.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loops.d.ts","sourceRoot":"","sources":["../../src/ingest/loops.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAiCxE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAc3C;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAwGD,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+FhH"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const DEFAULT_MODEL = "gpt-4o-mini";
|
|
2
2
|
/** Returns the active fine-tuned model ID, or DEFAULT_MODEL if none set. */
|
|
3
3
|
export declare function getActiveModel(): string;
|
|
4
|
-
/** Persists the active fine-tuned model ID to
|
|
4
|
+
/** Persists the active fine-tuned model ID to the economy data root (config.json). */
|
|
5
5
|
export declare function setActiveModel(id: string): void;
|
|
6
6
|
/** Clears the active model, falling back to DEFAULT_MODEL. */
|
|
7
7
|
export declare function clearActiveModel(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/lib/model-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/lib/model-config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AA8B1C,4EAA4E;AAC5E,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-metadata.d.ts","sourceRoot":"","sources":["../../src/lib/package-metadata.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,kBAAkB,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"package-metadata.d.ts","sourceRoot":"","sources":["../../src/lib/package-metadata.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,kBAAkB,IAAI,eAAe,CASpD;AAED,eAAO,MAAM,eAAe,iBAAuB,CAAA"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -595,7 +595,8 @@ import { execFileSync } from "child_process";
|
|
|
595
595
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
596
596
|
import { hostname, platform } from "os";
|
|
597
597
|
import { homedir } from "os";
|
|
598
|
-
import { join } from "path";
|
|
598
|
+
import { join, resolve } from "path";
|
|
599
|
+
import { dataDir } from "@hasna/paths";
|
|
599
600
|
function normalizeMachineId(value) {
|
|
600
601
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
601
602
|
return id && id.length > 0 ? id : null;
|
|
@@ -626,21 +627,48 @@ function getMachineId() {
|
|
|
626
627
|
return macHostMachineId() ?? hostMachine;
|
|
627
628
|
return hostMachine;
|
|
628
629
|
}
|
|
629
|
-
function
|
|
630
|
+
function getHomeDir() {
|
|
630
631
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
632
|
+
if (!home)
|
|
633
|
+
throw new Error("Could not resolve the user home directory");
|
|
634
|
+
return home;
|
|
635
|
+
}
|
|
636
|
+
function getResolverDataRoot() {
|
|
637
|
+
return dataDir({
|
|
638
|
+
app: "economy",
|
|
639
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function getLegacyDataRoot() {
|
|
643
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
644
|
+
}
|
|
645
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
646
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
647
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
648
|
+
return true;
|
|
649
|
+
return existsSync(join(resolved, "economy.db"));
|
|
650
|
+
}
|
|
651
|
+
function getExactDataRoot() {
|
|
652
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
653
|
+
if (dir && dir.trim())
|
|
654
|
+
return dir.trim();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
function getDataDir() {
|
|
658
|
+
const exact = getExactDataRoot();
|
|
659
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
660
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
661
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
662
|
+
mkdirSync(effective, { recursive: true });
|
|
635
663
|
for (const file of readdirSync(oldDir)) {
|
|
636
664
|
const oldPath = join(oldDir, file);
|
|
637
665
|
if (statSync(oldPath).isFile()) {
|
|
638
|
-
copyFileSync(oldPath, join(
|
|
666
|
+
copyFileSync(oldPath, join(effective, file));
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
669
|
}
|
|
642
|
-
mkdirSync(
|
|
643
|
-
return
|
|
670
|
+
mkdirSync(effective, { recursive: true });
|
|
671
|
+
return effective;
|
|
644
672
|
}
|
|
645
673
|
function getDbPath() {
|
|
646
674
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -2082,18 +2110,118 @@ var init_database = __esm(() => {
|
|
|
2082
2110
|
|
|
2083
2111
|
// src/mcp/index.ts
|
|
2084
2112
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2113
|
+
// package.json
|
|
2114
|
+
var package_default = {
|
|
2115
|
+
name: "@hasna/economy",
|
|
2116
|
+
version: "0.3.28",
|
|
2117
|
+
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API + web dashboard for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
2118
|
+
repository: {
|
|
2119
|
+
type: "git",
|
|
2120
|
+
url: "git+https://github.com/hasna/economy.git"
|
|
2121
|
+
},
|
|
2122
|
+
bugs: {
|
|
2123
|
+
url: "https://github.com/hasna/economy/issues"
|
|
2124
|
+
},
|
|
2125
|
+
homepage: "https://github.com/hasna/economy#readme",
|
|
2126
|
+
type: "module",
|
|
2127
|
+
main: "dist/index.js",
|
|
2128
|
+
types: "dist/index.d.ts",
|
|
2129
|
+
bin: {
|
|
2130
|
+
economy: "dist/cli/index.js",
|
|
2131
|
+
"economy-mcp": "dist/mcp/index.js",
|
|
2132
|
+
"economy-serve": "dist/server/index.js",
|
|
2133
|
+
"economy-otel": "dist/otel/index.js"
|
|
2134
|
+
},
|
|
2135
|
+
exports: {
|
|
2136
|
+
".": {
|
|
2137
|
+
types: "./dist/index.d.ts",
|
|
2138
|
+
import: "./dist/index.js"
|
|
2139
|
+
},
|
|
2140
|
+
"./sdk": {
|
|
2141
|
+
types: "./dist/index.d.ts",
|
|
2142
|
+
import: "./dist/index.js"
|
|
2143
|
+
}
|
|
2144
|
+
},
|
|
2145
|
+
files: [
|
|
2146
|
+
"dist",
|
|
2147
|
+
"docs",
|
|
2148
|
+
"dashboard/dist",
|
|
2149
|
+
"postinstall.js",
|
|
2150
|
+
"LICENSE",
|
|
2151
|
+
"README.md",
|
|
2152
|
+
"CHANGELOG.md",
|
|
2153
|
+
"SECURITY.md",
|
|
2154
|
+
"CONTRIBUTING.md",
|
|
2155
|
+
"CODE_OF_CONDUCT.md"
|
|
2156
|
+
],
|
|
2157
|
+
scripts: {
|
|
2158
|
+
build: "cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.ts --outdir dist/cli --target bun --packages external && bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external && bun build src/server/index.ts --outdir dist/server --target bun --packages external && bun build src/db/pg-sync-worker.ts --outdir dist/server --target bun --packages external && bun build src/otel/index.ts --outdir dist/otel --target bun --packages external && bun build src/index.ts --outdir dist --target bun --packages external && tsc --emitDeclarationOnly --outDir dist",
|
|
2159
|
+
"build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
|
|
2160
|
+
"build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
|
|
2161
|
+
"build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
|
|
2162
|
+
"build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
|
|
2163
|
+
"build:dashboard": "cd dashboard && bun run build",
|
|
2164
|
+
"artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
|
|
2165
|
+
prepack: "bun run build && bun run artifact-scan",
|
|
2166
|
+
typecheck: "tsc --noEmit",
|
|
2167
|
+
test: "bun test",
|
|
2168
|
+
"dev:cli": "bun run src/cli/index.ts",
|
|
2169
|
+
"dev:mcp": "bun run src/mcp/index.ts",
|
|
2170
|
+
"dev:serve": "bun run src/server/index.ts",
|
|
2171
|
+
"dev:otel": "bun run src/otel/index.ts",
|
|
2172
|
+
postinstall: "node postinstall.js"
|
|
2173
|
+
},
|
|
2174
|
+
keywords: [
|
|
2175
|
+
"economy",
|
|
2176
|
+
"cost",
|
|
2177
|
+
"ai",
|
|
2178
|
+
"claude",
|
|
2179
|
+
"codex",
|
|
2180
|
+
"gemini",
|
|
2181
|
+
"mcp",
|
|
2182
|
+
"cli",
|
|
2183
|
+
"budget",
|
|
2184
|
+
"tracking"
|
|
2185
|
+
],
|
|
2186
|
+
author: "hasna",
|
|
2187
|
+
license: "Apache-2.0",
|
|
2188
|
+
publishConfig: {
|
|
2189
|
+
registry: "https://registry.npmjs.org",
|
|
2190
|
+
access: "public"
|
|
2191
|
+
},
|
|
2192
|
+
dependencies: {
|
|
2193
|
+
"@hasna/accounts": "0.2.23",
|
|
2194
|
+
"@hasna/agent-registry": "^0.1.0",
|
|
2195
|
+
"@hasna/contracts": "0.13.4",
|
|
2196
|
+
"@hasna/events": "^0.1.16",
|
|
2197
|
+
"@hasna/mcp-harness": "^0.1.0",
|
|
2198
|
+
"@hasna/paths": "0.1.0",
|
|
2199
|
+
"@hasna/projects": "1.0.0",
|
|
2200
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
2201
|
+
chalk: "^5.4.1",
|
|
2202
|
+
commander: "^13.1.0",
|
|
2203
|
+
pg: "8.13.1",
|
|
2204
|
+
zod: "^3.24.2"
|
|
2205
|
+
},
|
|
2206
|
+
devDependencies: {
|
|
2207
|
+
"@types/bun": "latest",
|
|
2208
|
+
"@types/pg": "^8.20.0",
|
|
2209
|
+
"bun-types": "latest",
|
|
2210
|
+
typescript: "^5.7.2"
|
|
2211
|
+
},
|
|
2212
|
+
trustedDependencies: [
|
|
2213
|
+
"@hasna/projects"
|
|
2214
|
+
]
|
|
2215
|
+
};
|
|
2085
2216
|
|
|
2086
2217
|
// src/lib/package-metadata.ts
|
|
2087
|
-
import { readFileSync } from "fs";
|
|
2088
2218
|
var cachedMetadata = null;
|
|
2089
2219
|
function getPackageMetadata() {
|
|
2090
2220
|
if (cachedMetadata)
|
|
2091
2221
|
return cachedMetadata;
|
|
2092
|
-
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
|
|
2093
|
-
const parsed = JSON.parse(raw);
|
|
2094
2222
|
cachedMetadata = {
|
|
2095
|
-
name:
|
|
2096
|
-
version:
|
|
2223
|
+
name: package_default.name ?? "@hasna/economy",
|
|
2224
|
+
version: package_default.version ?? "0.0.0"
|
|
2097
2225
|
};
|
|
2098
2226
|
return cachedMetadata;
|
|
2099
2227
|
}
|
|
@@ -2108,7 +2236,7 @@ import { z } from "zod";
|
|
|
2108
2236
|
// src/ingest/claude.ts
|
|
2109
2237
|
init_database();
|
|
2110
2238
|
init_pricing();
|
|
2111
|
-
import { readdirSync as readdirSync2, readFileSync
|
|
2239
|
+
import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
2112
2240
|
import { homedir as homedir2 } from "os";
|
|
2113
2241
|
import { join as join2, basename } from "path";
|
|
2114
2242
|
|
|
@@ -2437,7 +2565,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
2437
2565
|
continue;
|
|
2438
2566
|
let lines;
|
|
2439
2567
|
try {
|
|
2440
|
-
lines =
|
|
2568
|
+
lines = readFileSync(filePath, "utf-8").split(`
|
|
2441
2569
|
`).filter((l) => l.trim());
|
|
2442
2570
|
} catch {
|
|
2443
2571
|
continue;
|
|
@@ -2557,7 +2685,7 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
2557
2685
|
// src/ingest/codex.ts
|
|
2558
2686
|
init_database();
|
|
2559
2687
|
init_pricing();
|
|
2560
|
-
import { existsSync as existsSync3, readFileSync as
|
|
2688
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
2561
2689
|
import { homedir as homedir3 } from "os";
|
|
2562
2690
|
import { join as join3, basename as basename2 } from "path";
|
|
2563
2691
|
import { Database as BunDatabase } from "bun:sqlite";
|
|
@@ -2609,7 +2737,7 @@ function readCodexModel(configPath = codexConfigPath()) {
|
|
|
2609
2737
|
if (!existsSync3(configPath))
|
|
2610
2738
|
return "gpt-5-codex";
|
|
2611
2739
|
try {
|
|
2612
|
-
const content =
|
|
2740
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
2613
2741
|
const match = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
2614
2742
|
return match?.[1] ?? "gpt-5-codex";
|
|
2615
2743
|
} catch {
|
|
@@ -2652,7 +2780,7 @@ function readTokenEvents(rolloutPath) {
|
|
|
2652
2780
|
const fallbackUsages = new Map;
|
|
2653
2781
|
let fallbackTimestamp;
|
|
2654
2782
|
let aggregate = null;
|
|
2655
|
-
for (const line of
|
|
2783
|
+
for (const line of readFileSync2(rolloutPath, "utf-8").split(`
|
|
2656
2784
|
`)) {
|
|
2657
2785
|
if (!line.trim())
|
|
2658
2786
|
continue;
|
|
@@ -2809,7 +2937,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2809
2937
|
// src/ingest/gemini.ts
|
|
2810
2938
|
init_database();
|
|
2811
2939
|
init_pricing();
|
|
2812
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2940
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
|
|
2813
2941
|
import { homedir as homedir4 } from "os";
|
|
2814
2942
|
import { join as join4, basename as basename3 } from "path";
|
|
2815
2943
|
var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
|
|
@@ -2849,7 +2977,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
2849
2977
|
const rootFile = join4(projectDir, ".project_root");
|
|
2850
2978
|
try {
|
|
2851
2979
|
if (existsSync4(rootFile))
|
|
2852
|
-
return
|
|
2980
|
+
return readFileSync3(rootFile, "utf-8").trim();
|
|
2853
2981
|
} catch {}
|
|
2854
2982
|
return "";
|
|
2855
2983
|
}
|
|
@@ -2890,7 +3018,7 @@ async function ingestGemini(db, verbose) {
|
|
|
2890
3018
|
continue;
|
|
2891
3019
|
let chatData;
|
|
2892
3020
|
try {
|
|
2893
|
-
chatData = JSON.parse(
|
|
3021
|
+
chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
2894
3022
|
} catch {
|
|
2895
3023
|
continue;
|
|
2896
3024
|
}
|
|
@@ -2968,7 +3096,7 @@ async function ingestGemini(db, verbose) {
|
|
|
2968
3096
|
// src/ingest/opencode.ts
|
|
2969
3097
|
init_database();
|
|
2970
3098
|
init_pricing();
|
|
2971
|
-
import { existsSync as existsSync5, readFileSync as
|
|
3099
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
2972
3100
|
import { homedir as homedir5 } from "os";
|
|
2973
3101
|
import { join as join5 } from "path";
|
|
2974
3102
|
var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
|
|
@@ -3011,7 +3139,7 @@ async function ingestOpenCode(db, verbose = false) {
|
|
|
3011
3139
|
continue;
|
|
3012
3140
|
let parsed;
|
|
3013
3141
|
try {
|
|
3014
|
-
parsed = JSON.parse(
|
|
3142
|
+
parsed = JSON.parse(readFileSync4(file, "utf-8"));
|
|
3015
3143
|
} catch {
|
|
3016
3144
|
continue;
|
|
3017
3145
|
}
|
|
@@ -3198,7 +3326,7 @@ async function ingestCursor(db, verbose = false) {
|
|
|
3198
3326
|
|
|
3199
3327
|
// src/ingest/pi.ts
|
|
3200
3328
|
init_database();
|
|
3201
|
-
import { existsSync as existsSync6, readFileSync as
|
|
3329
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
3202
3330
|
import { homedir as homedir6 } from "os";
|
|
3203
3331
|
import { join as join6 } from "path";
|
|
3204
3332
|
var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
|
|
@@ -3228,7 +3356,7 @@ async function ingestPi(db, verbose = false) {
|
|
|
3228
3356
|
continue;
|
|
3229
3357
|
let data;
|
|
3230
3358
|
try {
|
|
3231
|
-
data = JSON.parse(
|
|
3359
|
+
data = JSON.parse(readFileSync5(file, "utf-8"));
|
|
3232
3360
|
} catch {
|
|
3233
3361
|
continue;
|
|
3234
3362
|
}
|
|
@@ -3388,10 +3516,18 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
3388
3516
|
import { homedir as homedir8 } from "os";
|
|
3389
3517
|
import { join as join8 } from "path";
|
|
3390
3518
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
3391
|
-
|
|
3519
|
+
import { dataDir as dataDir2 } from "@hasna/paths";
|
|
3392
3520
|
var LOOPS_INGEST_VERSION = "goal-runs-orchestration-v1";
|
|
3521
|
+
function defaultLoopsDbPath() {
|
|
3522
|
+
const resolved = dataDir2({
|
|
3523
|
+
app: "loops",
|
|
3524
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
3525
|
+
});
|
|
3526
|
+
const legacy = join8(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
3527
|
+
return existsSync8(join8(resolved, "loops.db")) ? join8(resolved, "loops.db") : legacy;
|
|
3528
|
+
}
|
|
3393
3529
|
function loopsDbPath() {
|
|
3394
|
-
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ??
|
|
3530
|
+
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
3395
3531
|
}
|
|
3396
3532
|
function loopsModel() {
|
|
3397
3533
|
return process.env["HASNA_ECONOMY_LOOPS_MODEL"] ?? process.env["ECONOMY_LOOPS_MODEL"] ?? "gpt-5.3-codex";
|
|
@@ -3570,16 +3706,16 @@ async function ingestLoops(db, verbose = false) {
|
|
|
3570
3706
|
|
|
3571
3707
|
// src/ingest/claude-quota.ts
|
|
3572
3708
|
init_database();
|
|
3573
|
-
import { existsSync as existsSync9, readFileSync as
|
|
3709
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3574
3710
|
|
|
3575
3711
|
// src/lib/paths.ts
|
|
3576
3712
|
import { homedir as homedir9 } from "os";
|
|
3577
3713
|
import { join as join9 } from "path";
|
|
3578
|
-
function
|
|
3714
|
+
function getHomeDir2() {
|
|
3579
3715
|
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
|
|
3580
3716
|
}
|
|
3581
3717
|
function agentPaths() {
|
|
3582
|
-
const home =
|
|
3718
|
+
const home = getHomeDir2();
|
|
3583
3719
|
return {
|
|
3584
3720
|
claudeProjects: join9(home, ".claude", "projects"),
|
|
3585
3721
|
claudeCredentials: join9(home, ".claude", ".credentials.json"),
|
|
@@ -3608,7 +3744,7 @@ function readClaudeToken() {
|
|
|
3608
3744
|
if (!existsSync9(CREDENTIALS_PATH))
|
|
3609
3745
|
return null;
|
|
3610
3746
|
try {
|
|
3611
|
-
const creds = JSON.parse(
|
|
3747
|
+
const creds = JSON.parse(readFileSync6(CREDENTIALS_PATH, "utf-8"));
|
|
3612
3748
|
const oauth = creds.claudeAiOauth;
|
|
3613
3749
|
if (!oauth?.accessToken)
|
|
3614
3750
|
return null;
|
|
@@ -3744,7 +3880,7 @@ async function ingestClaudeQuota(db, verbose = false) {
|
|
|
3744
3880
|
|
|
3745
3881
|
// src/ingest/codex-quota.ts
|
|
3746
3882
|
init_database();
|
|
3747
|
-
import { existsSync as existsSync10, readFileSync as
|
|
3883
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
3748
3884
|
var WHAM_USAGE_URL = process.env["CODEX_USAGE_URL"] ?? "https://chatgpt.com/backend-api/wham/usage";
|
|
3749
3885
|
function readCodexAuth() {
|
|
3750
3886
|
const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
|
|
@@ -3754,7 +3890,7 @@ function readCodexAuth() {
|
|
|
3754
3890
|
if (!existsSync10(authPath))
|
|
3755
3891
|
return null;
|
|
3756
3892
|
try {
|
|
3757
|
-
const auth = JSON.parse(
|
|
3893
|
+
const auth = JSON.parse(readFileSync7(authPath, "utf-8"));
|
|
3758
3894
|
const token = auth.tokens?.access_token;
|
|
3759
3895
|
if (!token)
|
|
3760
3896
|
return null;
|