@hasna/economy 0.5.0 → 0.5.1
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 +7 -0
- package/dist/chunks/{index-kn5572kd.js → index-49f12css.js} +1 -1
- package/dist/chunks/{index-axh32d30.js → index-d480p2wh.js} +11 -2
- package/dist/chunks/{index-kgy9b6bf.js → index-r1jv3kpy.js} +1 -1
- package/dist/chunks/{index-emvs5ph2.js → index-xsah93s2.js} +1 -1
- package/dist/chunks/{serve-0k69e4h9.js → serve-2knem98x.js} +2 -2
- package/dist/chunks/{tui-7f8f951w.js → tui-khmnkat2.js} +3 -3
- package/dist/chunks/{watch-mm1j39vy.js → watch-4y064yys.js} +3 -3
- package/dist/cli/index.js +10 -10
- package/dist/db/pg-legacy-schema.d.ts +12 -0
- package/dist/db/pg-legacy-schema.d.ts.map +1 -0
- package/dist/index.js +11 -2
- package/dist/mcp/index.js +11 -2
- package/dist/otel/index.js +11 -2
- package/dist/server/index.js +225 -12
- package/dist/server/service-worker.js +11 -2
- package/docs/postgresql-legacy-schema-repair.md +30 -0
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Update the hosted container to the pinned Alpine runtime and age-eligible security fixes, with exact standalone dependency overrides and reproducible lockfile resolution.
|
|
8
|
+
- Repair missing legacy PostgreSQL cost-center schema during explicit migration, preserving original rows and migration history with an atomic, validated completion receipt.
|
|
9
|
+
|
|
3
10
|
## 0.5.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
// package.json
|
|
20
20
|
var package_default = {
|
|
21
21
|
name: "@hasna/economy",
|
|
22
|
-
version: "0.5.
|
|
22
|
+
version: "0.5.1",
|
|
23
23
|
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",
|
|
24
24
|
repository: {
|
|
25
25
|
type: "git",
|
|
@@ -114,7 +114,16 @@ var package_default = {
|
|
|
114
114
|
},
|
|
115
115
|
trustedDependencies: [
|
|
116
116
|
"@hasna/projects"
|
|
117
|
-
]
|
|
117
|
+
],
|
|
118
|
+
overrides: {
|
|
119
|
+
"fast-uri": "3.1.8",
|
|
120
|
+
hono: "4.13.7",
|
|
121
|
+
"ip-address": "10.7.1",
|
|
122
|
+
nanoid: "5.1.16",
|
|
123
|
+
"path-to-regexp": "8.4.0",
|
|
124
|
+
undici: "6.28.1",
|
|
125
|
+
"express-rate-limit": "8.7.0"
|
|
126
|
+
}
|
|
118
127
|
};
|
|
119
128
|
|
|
120
129
|
// src/lib/package-metadata.ts
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./index-w3bvv0ns.js";
|
|
6
6
|
import {
|
|
7
7
|
syncAll
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-xsah93s2.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-d480p2wh.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-49f12css.js";
|
|
10
10
|
import"./index-w3bvv0ns.js";
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
11
|
+
import"./index-r1jv3kpy.js";
|
|
12
|
+
import"./index-d480p2wh.js";
|
|
13
13
|
import"./index-ez9thsr7.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-xsah93s2.js";
|
|
6
6
|
import {
|
|
7
7
|
getStore,
|
|
8
8
|
isCloudStore
|
|
9
|
-
} from "./index-
|
|
10
|
-
import"./index-
|
|
9
|
+
} from "./index-r1jv3kpy.js";
|
|
10
|
+
import"./index-d480p2wh.js";
|
|
11
11
|
import {
|
|
12
12
|
formatSummaryValue
|
|
13
13
|
} from "./index-ez9thsr7.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-49f12css.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-xsah93s2.js";
|
|
17
17
|
import {
|
|
18
18
|
getStore,
|
|
19
19
|
isCloudStore
|
|
20
|
-
} from "../chunks/index-
|
|
20
|
+
} from "../chunks/index-r1jv3kpy.js";
|
|
21
21
|
import {
|
|
22
22
|
billingDriftCheck,
|
|
23
23
|
economyCloudStorage,
|
|
24
24
|
economyTransportReport,
|
|
25
25
|
packageMetadata
|
|
26
|
-
} from "../chunks/index-
|
|
26
|
+
} from "../chunks/index-d480p2wh.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-khmnkat2.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-khmnkat2.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-khmnkat2.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-khmnkat2.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-4y064yys.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-2knem98x.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) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LegacySchemaRepairResult {
|
|
2
|
+
id: string;
|
|
3
|
+
status: 'not_needed' | 'applied' | 'already_applied';
|
|
4
|
+
added: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Repair one known additive schema gap before the existing numbered migration
|
|
8
|
+
* sequence. Only the explicit migration command calls this; server startup
|
|
9
|
+
* never repairs a store. Originals and their migration markers are untouched.
|
|
10
|
+
*/
|
|
11
|
+
export declare function repairLegacyCostCenterSchema(dsn: string): Promise<LegacySchemaRepairResult>;
|
|
12
|
+
//# sourceMappingURL=pg-legacy-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-legacy-schema.d.ts","sourceRoot":"","sources":["../../src/db/pg-legacy-schema.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,iBAAiB,CAAA;IACpD,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AA6HD;;;;GAIG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CA8DjG"}
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import { randomUUID } from "crypto";
|
|
|
61
61
|
// package.json
|
|
62
62
|
var package_default = {
|
|
63
63
|
name: "@hasna/economy",
|
|
64
|
-
version: "0.5.
|
|
64
|
+
version: "0.5.1",
|
|
65
65
|
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",
|
|
66
66
|
repository: {
|
|
67
67
|
type: "git",
|
|
@@ -156,7 +156,16 @@ var package_default = {
|
|
|
156
156
|
},
|
|
157
157
|
trustedDependencies: [
|
|
158
158
|
"@hasna/projects"
|
|
159
|
-
]
|
|
159
|
+
],
|
|
160
|
+
overrides: {
|
|
161
|
+
"fast-uri": "3.1.8",
|
|
162
|
+
hono: "4.13.7",
|
|
163
|
+
"ip-address": "10.7.1",
|
|
164
|
+
nanoid: "5.1.16",
|
|
165
|
+
"path-to-regexp": "8.4.0",
|
|
166
|
+
undici: "6.28.1",
|
|
167
|
+
"express-rate-limit": "8.7.0"
|
|
168
|
+
}
|
|
160
169
|
};
|
|
161
170
|
|
|
162
171
|
// src/lib/package-metadata.ts
|
package/dist/mcp/index.js
CHANGED
|
@@ -84,7 +84,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
84
84
|
// package.json
|
|
85
85
|
var package_default = {
|
|
86
86
|
name: "@hasna/economy",
|
|
87
|
-
version: "0.5.
|
|
87
|
+
version: "0.5.1",
|
|
88
88
|
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",
|
|
89
89
|
repository: {
|
|
90
90
|
type: "git",
|
|
@@ -179,7 +179,16 @@ var package_default = {
|
|
|
179
179
|
},
|
|
180
180
|
trustedDependencies: [
|
|
181
181
|
"@hasna/projects"
|
|
182
|
-
]
|
|
182
|
+
],
|
|
183
|
+
overrides: {
|
|
184
|
+
"fast-uri": "3.1.8",
|
|
185
|
+
hono: "4.13.7",
|
|
186
|
+
"ip-address": "10.7.1",
|
|
187
|
+
nanoid: "5.1.16",
|
|
188
|
+
"path-to-regexp": "8.4.0",
|
|
189
|
+
undici: "6.28.1",
|
|
190
|
+
"express-rate-limit": "8.7.0"
|
|
191
|
+
}
|
|
183
192
|
};
|
|
184
193
|
|
|
185
194
|
// src/lib/package-metadata.ts
|
package/dist/otel/index.js
CHANGED
|
@@ -3458,7 +3458,7 @@ async function pushIngestRows(cloud, scratch) {
|
|
|
3458
3458
|
// package.json
|
|
3459
3459
|
var package_default = {
|
|
3460
3460
|
name: "@hasna/economy",
|
|
3461
|
-
version: "0.5.
|
|
3461
|
+
version: "0.5.1",
|
|
3462
3462
|
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",
|
|
3463
3463
|
repository: {
|
|
3464
3464
|
type: "git",
|
|
@@ -3553,7 +3553,16 @@ var package_default = {
|
|
|
3553
3553
|
},
|
|
3554
3554
|
trustedDependencies: [
|
|
3555
3555
|
"@hasna/projects"
|
|
3556
|
-
]
|
|
3556
|
+
],
|
|
3557
|
+
overrides: {
|
|
3558
|
+
"fast-uri": "3.1.8",
|
|
3559
|
+
hono: "4.13.7",
|
|
3560
|
+
"ip-address": "10.7.1",
|
|
3561
|
+
nanoid: "5.1.16",
|
|
3562
|
+
"path-to-regexp": "8.4.0",
|
|
3563
|
+
undici: "6.28.1",
|
|
3564
|
+
"express-rate-limit": "8.7.0"
|
|
3565
|
+
}
|
|
3557
3566
|
};
|
|
3558
3567
|
|
|
3559
3568
|
// src/lib/package-metadata.ts
|
package/dist/server/index.js
CHANGED
|
@@ -4164,6 +4164,208 @@ var init_pg_migrate = __esm(() => {
|
|
|
4164
4164
|
init_pg_connection();
|
|
4165
4165
|
});
|
|
4166
4166
|
|
|
4167
|
+
// src/db/pg-legacy-schema.ts
|
|
4168
|
+
var exports_pg_legacy_schema = {};
|
|
4169
|
+
__export(exports_pg_legacy_schema, {
|
|
4170
|
+
repairLegacyCostCenterSchema: () => repairLegacyCostCenterSchema
|
|
4171
|
+
});
|
|
4172
|
+
import { createHash as createHash5 } from "crypto";
|
|
4173
|
+
import pg4 from "pg";
|
|
4174
|
+
async function assertReceiptTable(client, schema) {
|
|
4175
|
+
await client.query("LOCK TABLE " + quote2(schema) + ".economy_schema_repairs IN SHARE ROW EXCLUSIVE MODE");
|
|
4176
|
+
const shape = await relation(client, schema, "economy_schema_repairs");
|
|
4177
|
+
if (!shape || shape.columns.length !== 3 || !checkColumn("economy_schema_repairs", shape.columns.find((c) => c.name === "id"), true) || !checkColumn("economy_schema_repairs", shape.columns.find((c) => c.name === "sql_sha256"), true)) {
|
|
4178
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected repair ledger columns");
|
|
4179
|
+
}
|
|
4180
|
+
const completed = shape.columns.find((c) => c.name === "completed_at");
|
|
4181
|
+
if (!completed || completed.type !== "timestamp with time zone" || !completed.required || completed.generated || completed.identity) {
|
|
4182
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected repair ledger timestamp");
|
|
4183
|
+
}
|
|
4184
|
+
const meta = await client.query(`
|
|
4185
|
+
SELECT c.relpersistence='p' AND NOT c.relrowsecurity AND NOT c.relforcerowsecurity
|
|
4186
|
+
AND NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid=c.oid AND NOT tgisinternal)
|
|
4187
|
+
AND NOT EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class=c.oid)
|
|
4188
|
+
AND NOT EXISTS (SELECT 1 FROM pg_inherits WHERE inhrelid=c.oid OR inhparent=c.oid)
|
|
4189
|
+
AND NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid=c.oid AND (contype<>'p' OR condeferrable))
|
|
4190
|
+
AS ordinary,
|
|
4191
|
+
ARRAY(SELECT a.attname::text FROM pg_index i
|
|
4192
|
+
CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum,ordinality)
|
|
4193
|
+
JOIN pg_attribute a ON a.attrelid=c.oid AND a.attnum=k.attnum
|
|
4194
|
+
WHERE i.indrelid=c.oid AND i.indisprimary AND i.indisvalid AND i.indisready
|
|
4195
|
+
ORDER BY k.ordinality) AS primary_columns,
|
|
4196
|
+
(SELECT pg_get_expr(d.adbin,d.adrelid) FROM pg_attrdef d JOIN pg_attribute a
|
|
4197
|
+
ON a.attrelid=d.adrelid AND a.attnum=d.adnum
|
|
4198
|
+
WHERE d.adrelid=c.oid AND a.attname='completed_at') AS clock_default
|
|
4199
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
4200
|
+
WHERE n.nspname=$1 AND c.relname='economy_schema_repairs'`, [schema]);
|
|
4201
|
+
const row = meta.rows[0];
|
|
4202
|
+
if (!row?.ordinary || JSON.stringify(row.primary_columns) !== '["id"]' || row.clock_default !== "clock_timestamp()") {
|
|
4203
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected repair ledger behavior");
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
async function relation(client, schema, name) {
|
|
4207
|
+
const result = await client.query(`
|
|
4208
|
+
SELECT c.relkind AS kind, a.attname AS name, a.atttypid::regtype::text AS type,
|
|
4209
|
+
a.attnotnull AS required, a.attgenerated AS generated, a.attidentity AS identity
|
|
4210
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
4211
|
+
LEFT JOIN pg_attribute a ON a.attrelid=c.oid AND a.attnum>0 AND NOT a.attisdropped
|
|
4212
|
+
WHERE n.nspname=$1 AND c.relname=$2 ORDER BY a.attnum`, [schema, name]);
|
|
4213
|
+
if (!result.rows.length)
|
|
4214
|
+
return null;
|
|
4215
|
+
if (result.rows[0].kind !== "r")
|
|
4216
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: expected ordinary table " + name);
|
|
4217
|
+
return { kind: result.rows[0].kind, columns: result.rows.filter((row) => row.name) };
|
|
4218
|
+
}
|
|
4219
|
+
function checkColumn(table, actual, required3) {
|
|
4220
|
+
if (!actual)
|
|
4221
|
+
return false;
|
|
4222
|
+
if (actual.type !== "text" || actual.required !== required3 || actual.generated || actual.identity) {
|
|
4223
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected column " + table + "." + actual.name);
|
|
4224
|
+
}
|
|
4225
|
+
return true;
|
|
4226
|
+
}
|
|
4227
|
+
async function missingStructure(client, schema) {
|
|
4228
|
+
const missing = [];
|
|
4229
|
+
for (const table of ["requests", "sessions", "budgets"]) {
|
|
4230
|
+
const found = await relation(client, schema, table);
|
|
4231
|
+
if (!found)
|
|
4232
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: missing core table " + table);
|
|
4233
|
+
if (!checkColumn(table, found.columns.find((column) => column.name === "cost_center_id"), false)) {
|
|
4234
|
+
missing.push(table + ".cost_center_id");
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
const centers = await relation(client, schema, "cost_centers");
|
|
4238
|
+
if (!centers)
|
|
4239
|
+
missing.push("cost_centers");
|
|
4240
|
+
else {
|
|
4241
|
+
for (const [name, required3] of [
|
|
4242
|
+
["id", true],
|
|
4243
|
+
["kind", true],
|
|
4244
|
+
["name", true],
|
|
4245
|
+
["repo_path", false],
|
|
4246
|
+
["labels_json", false],
|
|
4247
|
+
["created_at", true],
|
|
4248
|
+
["updated_at", true]
|
|
4249
|
+
]) {
|
|
4250
|
+
if (!checkColumn("cost_centers", centers.columns.find((column) => column.name === name), required3)) {
|
|
4251
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: incomplete cost_centers table");
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
const primary = await client.query(`
|
|
4255
|
+
SELECT array_agg(a.attname::text ORDER BY k.ordinality) AS columns
|
|
4256
|
+
FROM pg_index i JOIN pg_class c ON c.oid=i.indrelid JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
4257
|
+
CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum,ordinality)
|
|
4258
|
+
JOIN pg_attribute a ON a.attrelid=c.oid AND a.attnum=k.attnum
|
|
4259
|
+
WHERE n.nspname=$1 AND c.relname='cost_centers' AND i.indisprimary
|
|
4260
|
+
GROUP BY i.indexrelid`, [schema]);
|
|
4261
|
+
if (JSON.stringify(primary.rows.map((row) => row.columns)) !== '[["id"]]') {
|
|
4262
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected cost_centers primary key");
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
for (const [name, table, column] of INDEXES) {
|
|
4266
|
+
const result = await client.query(`
|
|
4267
|
+
SELECT t.relname AS table_name, am.amname AS method,
|
|
4268
|
+
i.indisunique AS unique, i.indisvalid AS valid, i.indisready AS ready,
|
|
4269
|
+
i.indpred IS NULL AS unfiltered, i.indexprs IS NULL AS simple,
|
|
4270
|
+
i.indnatts=i.indnkeyatts AS no_includes,
|
|
4271
|
+
ARRAY(SELECT a.attname::text FROM unnest(i.indkey) WITH ORDINALITY k(attnum,ordinality)
|
|
4272
|
+
JOIN pg_attribute a ON a.attrelid=t.oid AND a.attnum=k.attnum
|
|
4273
|
+
ORDER BY k.ordinality) AS columns
|
|
4274
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
4275
|
+
LEFT JOIN pg_index i ON i.indexrelid=c.oid LEFT JOIN pg_class t ON t.oid=i.indrelid
|
|
4276
|
+
LEFT JOIN pg_am am ON am.oid=c.relam WHERE n.nspname=$1 AND c.relname=$2`, [schema, name]);
|
|
4277
|
+
if (!result.rows.length) {
|
|
4278
|
+
missing.push(name);
|
|
4279
|
+
continue;
|
|
4280
|
+
}
|
|
4281
|
+
const actual = result.rows[0];
|
|
4282
|
+
if (actual.table_name !== table || actual.method !== "btree" || actual.unique || !actual.valid || !actual.ready || !actual.unfiltered || !actual.simple || !actual.no_includes || JSON.stringify(actual.columns) !== JSON.stringify([column])) {
|
|
4283
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: unexpected index " + name);
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
return missing;
|
|
4287
|
+
}
|
|
4288
|
+
async function repairLegacyCostCenterSchema(dsn) {
|
|
4289
|
+
const result = { id: REPAIR_ID, status: "not_needed", added: [] };
|
|
4290
|
+
const pool = new pg4.Pool({ ...resolvePgConnection(dsn), max: 1, connectionTimeoutMillis: 1e4 });
|
|
4291
|
+
pool.on("error", () => {});
|
|
4292
|
+
let client;
|
|
4293
|
+
try {
|
|
4294
|
+
client = await pool.connect();
|
|
4295
|
+
client.on("error", () => {});
|
|
4296
|
+
await client.query("SET statement_timeout='15min'; SET lock_timeout='15s'");
|
|
4297
|
+
await client.query("SELECT pg_advisory_lock(hashtext('economy.pg-migrations.v1'), hashtext(current_database() || ':' || current_schema()))");
|
|
4298
|
+
const schema = (await client.query("SELECT current_schema() AS name")).rows[0]?.name;
|
|
4299
|
+
if (typeof schema !== "string" || !schema)
|
|
4300
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: missing migration schema");
|
|
4301
|
+
const ledger = await relation(client, schema, "_pg_migrations");
|
|
4302
|
+
if (!ledger)
|
|
4303
|
+
return result;
|
|
4304
|
+
const history = await client.query("SELECT version FROM " + quote2(schema) + "._pg_migrations WHERE version>=0 AND version<85 ORDER BY version");
|
|
4305
|
+
if (history.rows.length !== 85 || history.rows.some((row, index) => row.version !== index)) {
|
|
4306
|
+
return result;
|
|
4307
|
+
}
|
|
4308
|
+
await client.query("BEGIN");
|
|
4309
|
+
try {
|
|
4310
|
+
await client.query("SELECT set_config('search_path', $1, true)", [quote2(schema)]);
|
|
4311
|
+
const missing = await missingStructure(client, schema);
|
|
4312
|
+
const receiptTable = await relation(client, schema, "economy_schema_repairs");
|
|
4313
|
+
if (receiptTable) {
|
|
4314
|
+
await assertReceiptTable(client, schema);
|
|
4315
|
+
const receipt = await client.query("SELECT sql_sha256 FROM economy_schema_repairs WHERE id=$1", [REPAIR_ID]);
|
|
4316
|
+
if (receipt.rows.length) {
|
|
4317
|
+
if (receipt.rows.length !== 1 || receipt.rows[0].sql_sha256 !== REPAIR_HASH || missing.length) {
|
|
4318
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: completed repair receipt or schema has changed");
|
|
4319
|
+
}
|
|
4320
|
+
await client.query("COMMIT");
|
|
4321
|
+
return { ...result, status: "already_applied" };
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
if (!missing.length) {
|
|
4325
|
+
await client.query("COMMIT");
|
|
4326
|
+
return result;
|
|
4327
|
+
}
|
|
4328
|
+
await client.query(`CREATE TABLE IF NOT EXISTS economy_schema_repairs (
|
|
4329
|
+
id TEXT PRIMARY KEY, sql_sha256 TEXT NOT NULL,
|
|
4330
|
+
completed_at TIMESTAMPTZ NOT NULL DEFAULT pg_catalog.clock_timestamp()
|
|
4331
|
+
)`);
|
|
4332
|
+
await assertReceiptTable(client, schema);
|
|
4333
|
+
for (const sql of REPAIR_SQL)
|
|
4334
|
+
await client.query(sql);
|
|
4335
|
+
if ((await missingStructure(client, schema)).length)
|
|
4336
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: repair postcondition failed");
|
|
4337
|
+
const inserted = await client.query("INSERT INTO economy_schema_repairs (id,sql_sha256) VALUES ($1,$2) RETURNING id,sql_sha256", [REPAIR_ID, REPAIR_HASH]);
|
|
4338
|
+
const readback = await client.query("SELECT id,sql_sha256 FROM economy_schema_repairs WHERE id=$1", [REPAIR_ID]);
|
|
4339
|
+
for (const receipt of [inserted, readback]) {
|
|
4340
|
+
if (receipt.rowCount !== 1 || receipt.rows[0]?.id !== REPAIR_ID || receipt.rows[0]?.sql_sha256 !== REPAIR_HASH) {
|
|
4341
|
+
throw Error("LEGACY_SCHEMA_INCOMPATIBLE: repair receipt readback failed");
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
await client.query("COMMIT");
|
|
4345
|
+
return { id: REPAIR_ID, status: "applied", added: missing };
|
|
4346
|
+
} catch (error) {
|
|
4347
|
+
await client.query("ROLLBACK").catch(() => {});
|
|
4348
|
+
throw error;
|
|
4349
|
+
}
|
|
4350
|
+
} finally {
|
|
4351
|
+
client?.release();
|
|
4352
|
+
await pool.end().catch(() => {});
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
var REPAIR_ID = "legacy-cost-centers-v1", REPAIR_SQL, REPAIR_HASH, INDEXES, quote2 = (name) => '"' + name.replaceAll('"', '""') + '"';
|
|
4356
|
+
var init_pg_legacy_schema = __esm(() => {
|
|
4357
|
+
init_pg_connection();
|
|
4358
|
+
init_pg_migrations();
|
|
4359
|
+
REPAIR_SQL = [0, 38, 47, 48, 15, 54, 55, 56].map((index) => PG_MIGRATIONS[index]);
|
|
4360
|
+
REPAIR_HASH = createHash5("sha256").update(JSON.stringify(REPAIR_SQL)).digest("hex");
|
|
4361
|
+
INDEXES = [
|
|
4362
|
+
["idx_cost_centers_kind", "cost_centers", "kind"],
|
|
4363
|
+
["idx_requests_cost_center", "requests", "cost_center_id"],
|
|
4364
|
+
["idx_sessions_cost_center", "sessions", "cost_center_id"],
|
|
4365
|
+
["idx_budgets_cost_center", "budgets", "cost_center_id"]
|
|
4366
|
+
];
|
|
4367
|
+
});
|
|
4368
|
+
|
|
4167
4369
|
// src/server/legacy-inventory.ts
|
|
4168
4370
|
var exports_legacy_inventory = {};
|
|
4169
4371
|
__export(exports_legacy_inventory, {
|
|
@@ -4316,7 +4518,7 @@ __export(exports_legacy_plan, {
|
|
|
4316
4518
|
parseLegacySelection: () => parseLegacySelection,
|
|
4317
4519
|
parseLegacyPlanOptions: () => parseLegacyPlanOptions
|
|
4318
4520
|
});
|
|
4319
|
-
import { createHash as
|
|
4521
|
+
import { createHash as createHash6 } from "crypto";
|
|
4320
4522
|
import { closeSync, constants, fstatSync, openSync, readSync } from "fs";
|
|
4321
4523
|
import { isAbsolute } from "path";
|
|
4322
4524
|
function object(value, fields, label) {
|
|
@@ -4505,7 +4707,7 @@ async function runLegacyPlan(selection) {
|
|
|
4505
4707
|
await pool.end();
|
|
4506
4708
|
}
|
|
4507
4709
|
}
|
|
4508
|
-
var INPUT_MAX, MEMBERS_MAX = 1e4, sha256 = (text) =>
|
|
4710
|
+
var INPUT_MAX, MEMBERS_MAX = 1e4, sha256 = (text) => createHash6("sha256").update(text).digest("hex");
|
|
4509
4711
|
var init_legacy_plan = __esm(() => {
|
|
4510
4712
|
init_legacy_inventory();
|
|
4511
4713
|
INPUT_MAX = 256 * 1024;
|
|
@@ -4524,7 +4726,7 @@ __export(exports_legacy_preservation, {
|
|
|
4524
4726
|
parseLegacyCapsule: () => parseLegacyCapsule,
|
|
4525
4727
|
assertLegacyPlanCurrent: () => assertLegacyPlanCurrent
|
|
4526
4728
|
});
|
|
4527
|
-
import { createHash as
|
|
4729
|
+
import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
|
|
4528
4730
|
import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, fsyncSync, lstatSync, openSync as openSync2, readSync as readSync2, realpathSync, writeSync } from "fs";
|
|
4529
4731
|
import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
|
|
4530
4732
|
function parseLegacyPlan(value) {
|
|
@@ -4774,8 +4976,8 @@ async function rehearseLegacyRestore(client, input, scope = "transaction") {
|
|
|
4774
4976
|
const table = tables[i], expected = capsule.plan.snapshot.tables[i];
|
|
4775
4977
|
if (table.table_name !== expected.table_name || table.columns_sha256 !== expected.columns_sha256)
|
|
4776
4978
|
fail("Restore target schema differs from the preserved schema");
|
|
4777
|
-
const source =
|
|
4778
|
-
const temp =
|
|
4979
|
+
const source = quote3(table.schema_name) + "." + quote3(table.table_name);
|
|
4980
|
+
const temp = quote3("economy_restore_" + randomUUID3().replaceAll("-", ""));
|
|
4779
4981
|
await client.query("CREATE TEMP TABLE " + temp + " (LIKE " + source + " INCLUDING ALL) ON COMMIT DROP");
|
|
4780
4982
|
const originals = table.table_name === "requests" ? capsule.requests : capsule.sessions;
|
|
4781
4983
|
restoreTables.push({ source, temp, originals });
|
|
@@ -4835,12 +5037,12 @@ async function runLegacyPreservation(command, options) {
|
|
|
4835
5037
|
await pool.end();
|
|
4836
5038
|
}
|
|
4837
5039
|
}
|
|
4838
|
-
var MAX_BYTES, MAX_MEMBERS = 1e4, sha2 = (s) =>
|
|
5040
|
+
var MAX_BYTES, MAX_MEMBERS = 1e4, sha2 = (s) => createHash7("sha256").update(s).digest("hex"), fail = (message) => {
|
|
4839
5041
|
throw Error(message);
|
|
4840
5042
|
}, record = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : fail("Expected a JSON object"), hash = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v) ? v : fail("Expected a lowercase SHA-256 digest"), identifier3 = (v) => typeof v === "string" && v.length > 0 && v.length <= 1024 && !v.includes("\x00") ? v : fail("Expected a nonempty identifier of at most 1024 characters without NUL"), fields = (o, names) => {
|
|
4841
5043
|
if (Object.keys(o).sort().join(",") !== [...names].sort().join(","))
|
|
4842
5044
|
fail("Unexpected or missing preservation fields");
|
|
4843
|
-
}, cOrder = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)), same = (a, b) => JSON.stringify(a) === JSON.stringify(b),
|
|
5045
|
+
}, cOrder = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)), same = (a, b) => JSON.stringify(a) === JSON.stringify(b), quote3 = (s) => '"' + s.replaceAll('"', '""') + '"';
|
|
4844
5046
|
var init_legacy_preservation = __esm(() => {
|
|
4845
5047
|
init_legacy_plan();
|
|
4846
5048
|
MAX_BYTES = 32 * 1024 * 1024;
|
|
@@ -4858,7 +5060,7 @@ __export(exports_legacy_operations, {
|
|
|
4858
5060
|
applyLegacyReconciliation: () => applyLegacyReconciliation,
|
|
4859
5061
|
LegacyOperationInputError: () => LegacyOperationInputError
|
|
4860
5062
|
});
|
|
4861
|
-
import { createHash as
|
|
5063
|
+
import { createHash as createHash8 } from "crypto";
|
|
4862
5064
|
import { isAbsolute as isAbsolute3 } from "path";
|
|
4863
5065
|
function parseLegacyIntent(action, value) {
|
|
4864
5066
|
try {
|
|
@@ -5150,7 +5352,7 @@ async function runLegacyOperation(command, options) {
|
|
|
5150
5352
|
await pool.end();
|
|
5151
5353
|
}
|
|
5152
5354
|
}
|
|
5153
|
-
var sha3 = (s) =>
|
|
5355
|
+
var sha3 = (s) => createHash8("sha256").update(s).digest("hex"), fail2 = (s) => {
|
|
5154
5356
|
throw Error(s);
|
|
5155
5357
|
}, digest3 = (v, label = "digest") => typeof v === "string" && /^[a-f0-9]{64}$/.test(v) ? v : fail2(label + " must be a lowercase SHA-256 digest"), key = (v, label = "operation_key") => typeof v === "string" && /^[A-Za-z0-9._:-]{1,200}$/.test(v) ? v : fail2(label + " must contain 1-200 letters, digits, dot, underscore, colon or hyphen"), reference = (v, label) => typeof v === "string" && /^[A-Za-z0-9:/._-]{1,512}$/.test(v) ? v : fail2(label + " must contain 1-512 letters, digits, colon, slash, dot, underscore or hyphen"), LegacyOperationInputError, TABLES_SQL = `SELECT c.relname AS table_name,n.nspname AS schema_name,c.oid::text AS relation_oid,
|
|
5156
5358
|
encode(sha256(convert_to(json_agg(json_build_array(a.attnum,a.attname,a.atttypid::text,a.atttypmod,a.attnotnull,a.attcollation::text) ORDER BY a.attnum)::text,'UTF8')),'hex') AS columns_sha256
|
|
@@ -5194,7 +5396,7 @@ function isAuthorizedRequest(req, path) {
|
|
|
5194
5396
|
// package.json
|
|
5195
5397
|
var package_default = {
|
|
5196
5398
|
name: "@hasna/economy",
|
|
5197
|
-
version: "0.5.
|
|
5399
|
+
version: "0.5.1",
|
|
5198
5400
|
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",
|
|
5199
5401
|
repository: {
|
|
5200
5402
|
type: "git",
|
|
@@ -5289,7 +5491,16 @@ var package_default = {
|
|
|
5289
5491
|
},
|
|
5290
5492
|
trustedDependencies: [
|
|
5291
5493
|
"@hasna/projects"
|
|
5292
|
-
]
|
|
5494
|
+
],
|
|
5495
|
+
overrides: {
|
|
5496
|
+
"fast-uri": "3.1.8",
|
|
5497
|
+
hono: "4.13.7",
|
|
5498
|
+
"ip-address": "10.7.1",
|
|
5499
|
+
nanoid: "5.1.16",
|
|
5500
|
+
"path-to-regexp": "8.4.0",
|
|
5501
|
+
undici: "6.28.1",
|
|
5502
|
+
"express-rate-limit": "8.7.0"
|
|
5503
|
+
}
|
|
5293
5504
|
};
|
|
5294
5505
|
|
|
5295
5506
|
// src/lib/package-metadata.ts
|
|
@@ -12457,10 +12668,12 @@ function parsePort(raw, label) {
|
|
|
12457
12668
|
async function runMigrate() {
|
|
12458
12669
|
const { getCloudDatabaseUrl: getCloudDatabaseUrl2, createCloudPool: createCloudPool2, authClientFromPool: authClientFromPool2, cloudMigrations: cloudMigrations2 } = await Promise.resolve().then(() => (init_cloud(), exports_cloud));
|
|
12459
12670
|
const { applyPgMigrations: applyPgMigrations2 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
|
|
12671
|
+
const { repairLegacyCostCenterSchema: repairLegacyCostCenterSchema2 } = await Promise.resolve().then(() => (init_pg_legacy_schema(), exports_pg_legacy_schema));
|
|
12460
12672
|
const { ApiKeyStore: ApiKeyStore2 } = await import("@hasna/contracts/auth");
|
|
12461
12673
|
const dsn = getCloudDatabaseUrl2();
|
|
12462
12674
|
if (!dsn)
|
|
12463
12675
|
throw new Error("migrate requires a Postgres DSN: set HASNA_ECONOMY_DATABASE_URL (or ECONOMY_DATABASE_URL / DATABASE_URL)");
|
|
12676
|
+
const repair = await repairLegacyCostCenterSchema2(dsn);
|
|
12464
12677
|
const result = await applyPgMigrations2(dsn, cloudMigrations2(), "economy");
|
|
12465
12678
|
const pool = createCloudPool2(dsn);
|
|
12466
12679
|
try {
|
|
@@ -12468,7 +12681,7 @@ async function runMigrate() {
|
|
|
12468
12681
|
} finally {
|
|
12469
12682
|
await pool.end().catch(() => {});
|
|
12470
12683
|
}
|
|
12471
|
-
console.log(JSON.stringify({ evt: "migrate", service: result.service, applied: result.applied, alreadyApplied: result.alreadyApplied, errors: result.errors, api_keys: "ensured" }));
|
|
12684
|
+
console.log(JSON.stringify({ evt: "migrate", service: result.service, applied: result.applied, alreadyApplied: result.alreadyApplied, errors: result.errors, repair, api_keys: "ensured" }));
|
|
12472
12685
|
if (result.errors.length)
|
|
12473
12686
|
process.exit(1);
|
|
12474
12687
|
}
|
|
@@ -7266,7 +7266,7 @@ import { randomUUID } from "crypto";
|
|
|
7266
7266
|
// package.json
|
|
7267
7267
|
var package_default = {
|
|
7268
7268
|
name: "@hasna/economy",
|
|
7269
|
-
version: "0.5.
|
|
7269
|
+
version: "0.5.1",
|
|
7270
7270
|
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",
|
|
7271
7271
|
repository: {
|
|
7272
7272
|
type: "git",
|
|
@@ -7361,7 +7361,16 @@ var package_default = {
|
|
|
7361
7361
|
},
|
|
7362
7362
|
trustedDependencies: [
|
|
7363
7363
|
"@hasna/projects"
|
|
7364
|
-
]
|
|
7364
|
+
],
|
|
7365
|
+
overrides: {
|
|
7366
|
+
"fast-uri": "3.1.8",
|
|
7367
|
+
hono: "4.13.7",
|
|
7368
|
+
"ip-address": "10.7.1",
|
|
7369
|
+
nanoid: "5.1.16",
|
|
7370
|
+
"path-to-regexp": "8.4.0",
|
|
7371
|
+
undici: "6.28.1",
|
|
7372
|
+
"express-rate-limit": "8.7.0"
|
|
7373
|
+
}
|
|
7365
7374
|
};
|
|
7366
7375
|
|
|
7367
7376
|
// src/lib/package-metadata.ts
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Legacy PostgreSQL cost-center schema repair
|
|
2
|
+
|
|
3
|
+
Some existing databases record migrations 0–84 while missing `cost_centers`
|
|
4
|
+
and the nullable `cost_center_id` columns on `requests`, `sessions` and
|
|
5
|
+
`budgets`. Migration 102 requires those columns.
|
|
6
|
+
|
|
7
|
+
The explicit `economy-serve migrate` command checks this known layout before
|
|
8
|
+
running the numbered migrations. It adds the missing table, columns and four
|
|
9
|
+
indexes using their unchanged original definitions. It does not infer cost
|
|
10
|
+
center assignments, change existing row values or rewrite migration history.
|
|
11
|
+
Fresh and already complete databases need no compatibility repair.
|
|
12
|
+
|
|
13
|
+
The repair serializes with the existing migration runner. Its DDL and one
|
|
14
|
+
`legacy-cost-centers-v1` receipt commit in one transaction. The receipt records
|
|
15
|
+
the exact repair SQL hash. An existing receipt ledger must have the canonical
|
|
16
|
+
permanent shape, with no triggers, rewrite rules or row security. A completed
|
|
17
|
+
receipt with changed schema or hash is refused. Incompatible existing tables,
|
|
18
|
+
columns or indexes are also refused without replacing them.
|
|
19
|
+
|
|
20
|
+
Preserve and restore-test the database before using the normal deployment
|
|
21
|
+
migration operation. Server startup does not run this repair. If a connection
|
|
22
|
+
is lost during commit, keep the outcome uncertain and inspect the database
|
|
23
|
+
before an explicit retry; the command never retries automatically. A subsequent
|
|
24
|
+
invocation recognizes a committed matching receipt as `already_applied`.
|
|
25
|
+
Numbered migrations retain their separate atomic completion markers.
|
|
26
|
+
|
|
27
|
+
The migration JSON includes `repair.id`, `repair.status` and `repair.added`.
|
|
28
|
+
Possible statuses are `not_needed`, `applied` and `already_applied`. Retain that
|
|
29
|
+
result with the deployment and preservation records. Rolling application code
|
|
30
|
+
back does not remove the added schema or repair receipt.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -95,5 +95,14 @@
|
|
|
95
95
|
},
|
|
96
96
|
"trustedDependencies": [
|
|
97
97
|
"@hasna/projects"
|
|
98
|
-
]
|
|
98
|
+
],
|
|
99
|
+
"overrides": {
|
|
100
|
+
"fast-uri": "3.1.8",
|
|
101
|
+
"hono": "4.13.7",
|
|
102
|
+
"ip-address": "10.7.1",
|
|
103
|
+
"nanoid": "5.1.16",
|
|
104
|
+
"path-to-regexp": "8.4.0",
|
|
105
|
+
"undici": "6.28.1",
|
|
106
|
+
"express-rate-limit": "8.7.0"
|
|
107
|
+
}
|
|
99
108
|
}
|