@hasna/economy 0.5.1 → 0.5.3
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 +12 -0
- package/dist/chunks/{billing-bkpxy4kv.js → billing-22az60hy.js} +2 -2
- package/dist/chunks/{config-j9mv3ehk.js → config-hsp7ndhj.js} +2 -2
- package/dist/chunks/{index-d480p2wh.js → index-1vz1arz5.js} +14 -13
- package/dist/chunks/{index-hesxq90g.js → index-9wa1f2kv.js} +1 -1
- package/dist/chunks/{index-49f12css.js → index-bn04v378.js} +2 -2
- package/dist/chunks/{index-zpvea08j.js → index-c7h2rxx1.js} +38 -12
- package/dist/chunks/{index-k540hfww.js → index-dk5rq1rq.js} +1 -1
- package/dist/chunks/{index-ez9thsr7.js → index-f5jv6q5q.js} +38 -12
- package/dist/chunks/{index-r1jv3kpy.js → index-mhrkwvtk.js} +3 -3
- package/dist/chunks/{index-xsah93s2.js → index-s9mvven6.js} +2 -2
- package/dist/chunks/{index-2hwzmfgv.js → index-vajzfzgv.js} +38 -12
- package/dist/chunks/{open-projects-6z4pq6mx.js → open-projects-8qm46m3d.js} +1 -1
- package/dist/chunks/{pricing-vf2pycmh.js → pricing-462f8xkm.js} +1 -1
- package/dist/chunks/{pricing-f80p81gr.js → pricing-k9wsjwbz.js} +1 -1
- package/dist/chunks/{pricing-y21mcjsa.js → pricing-vxejj1qa.js} +1 -1
- package/dist/chunks/{serve-2knem98x.js → serve-8yqe7nhs.js} +44 -11
- package/dist/chunks/{sqlite-store-501f32ts.js → sqlite-store-13nhr1z0.js} +2 -2
- package/dist/chunks/{sqlite-store-st99garx.js → sqlite-store-gamn65xp.js} +2 -2
- package/dist/chunks/{sqlite-store-fq4natnf.js → sqlite-store-ht6frztj.js} +2 -2
- package/dist/chunks/{tui-khmnkat2.js → tui-fe94bvp9.js} +4 -4
- package/dist/chunks/{watch-4y064yys.js → watch-ct8dvrpr.js} +5 -5
- package/dist/chunks/{webhooks-k4fh2t9c.js → webhooks-nw4daw9y.js} +20 -9
- package/dist/cli/index.js +27 -27
- package/dist/db/database.d.ts +2 -2
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/legacy-large-schema.d.ts +6 -0
- package/dist/db/legacy-large-schema.d.ts.map +1 -0
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/sync-pg.d.ts.map +1 -1
- package/dist/index.js +15 -14
- package/dist/lib/analytics.d.ts +1 -1
- package/dist/lib/analytics.d.ts.map +1 -1
- package/dist/lib/periods.d.ts +20 -1
- package/dist/lib/periods.d.ts.map +1 -1
- package/dist/lib/spikes.d.ts +3 -3
- package/dist/lib/spikes.d.ts.map +1 -1
- package/dist/mcp/index.js +18 -17
- package/dist/otel/index.js +1 -1
- package/dist/server/index.js +213 -74
- package/dist/server/legacy-limits.d.ts +51 -0
- package/dist/server/legacy-limits.d.ts.map +1 -0
- package/dist/server/legacy-operations.d.ts +2 -1
- package/dist/server/legacy-operations.d.ts.map +1 -1
- package/dist/server/legacy-plan.d.ts +2 -1
- package/dist/server/legacy-plan.d.ts.map +1 -1
- package/dist/server/legacy-preservation.d.ts +2 -1
- package/dist/server/legacy-preservation.d.ts.map +1 -1
- package/dist/server/service-operations.d.ts.map +1 -1
- package/dist/server/service-worker.js +78 -33
- package/docs/historical-data-reconciliation.md +38 -0
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Artifact schemas select a closed resource envelope. A larger envelope is
|
|
2
|
+
* explicit and restricted to one complete session; ordinary artifacts keep
|
|
3
|
+
* their original limits. These profiles confer no reconciliation authority. */
|
|
4
|
+
export declare const LEGACY_STANDARD: {
|
|
5
|
+
readonly selection: "economy.legacy-selection.v1";
|
|
6
|
+
readonly plan: "economy.legacy-plan.v1";
|
|
7
|
+
readonly capsule: "economy.legacy-preservation.v1";
|
|
8
|
+
readonly receipt: "economy.legacy-operation-receipt.v1";
|
|
9
|
+
readonly items: 100;
|
|
10
|
+
readonly sessions: 100;
|
|
11
|
+
readonly members: 10000;
|
|
12
|
+
readonly inputBytes: number;
|
|
13
|
+
readonly originalBytes: number;
|
|
14
|
+
readonly capsuleBytes: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const LEGACY_SINGLE_SESSION: {
|
|
17
|
+
readonly selection: "economy.legacy-session-selection.v1";
|
|
18
|
+
readonly plan: "economy.legacy-session-plan.v1";
|
|
19
|
+
readonly capsule: "economy.legacy-session-preservation.v1";
|
|
20
|
+
readonly receipt: "economy.legacy-session-operation-receipt.v1";
|
|
21
|
+
readonly items: 50000;
|
|
22
|
+
readonly sessions: 1;
|
|
23
|
+
readonly members: 50000;
|
|
24
|
+
readonly inputBytes: number;
|
|
25
|
+
readonly originalBytes: number;
|
|
26
|
+
readonly capsuleBytes: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function legacyMode(kind: 'selection' | 'plan' | 'capsule' | 'receipt', schema: unknown): {
|
|
29
|
+
readonly selection: "economy.legacy-selection.v1";
|
|
30
|
+
readonly plan: "economy.legacy-plan.v1";
|
|
31
|
+
readonly capsule: "economy.legacy-preservation.v1";
|
|
32
|
+
readonly receipt: "economy.legacy-operation-receipt.v1";
|
|
33
|
+
readonly items: 100;
|
|
34
|
+
readonly sessions: 100;
|
|
35
|
+
readonly members: 10000;
|
|
36
|
+
readonly inputBytes: number;
|
|
37
|
+
readonly originalBytes: number;
|
|
38
|
+
readonly capsuleBytes: number;
|
|
39
|
+
} | {
|
|
40
|
+
readonly selection: "economy.legacy-session-selection.v1";
|
|
41
|
+
readonly plan: "economy.legacy-session-plan.v1";
|
|
42
|
+
readonly capsule: "economy.legacy-session-preservation.v1";
|
|
43
|
+
readonly receipt: "economy.legacy-session-operation-receipt.v1";
|
|
44
|
+
readonly items: 50000;
|
|
45
|
+
readonly sessions: 1;
|
|
46
|
+
readonly members: 50000;
|
|
47
|
+
readonly inputBytes: number;
|
|
48
|
+
readonly originalBytes: number;
|
|
49
|
+
readonly capsuleBytes: number;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=legacy-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-limits.d.ts","sourceRoot":"","sources":["../../src/server/legacy-limits.ts"],"names":[],"mappings":"AAAA;;+EAE+E;AAC/E,eAAO,MAAM,eAAe;;;;;;;;;;;CAKlB,CAAA;AAEV,eAAO,MAAM,qBAAqB;;;;;;;;;;;CAKxB,CAAA;AAEV,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;EAI7F"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type pg from 'pg';
|
|
2
|
+
import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
|
|
2
3
|
import type { LegacyReader } from './legacy-plan.js';
|
|
3
4
|
type Action = 'apply' | 'rollback';
|
|
4
5
|
export interface LegacyIntent {
|
|
@@ -19,7 +20,7 @@ export declare function parseLegacyIntent(action: Action, value: unknown): Legac
|
|
|
19
20
|
* complements exact database/relation locators; it is not ownership approval. */
|
|
20
21
|
export declare function legacyTargetDigest(target: string | pg.PoolConfig): string;
|
|
21
22
|
interface Receipt {
|
|
22
|
-
schema:
|
|
23
|
+
schema: typeof LEGACY_STANDARD.receipt | typeof LEGACY_SINGLE_SESSION.receipt;
|
|
23
24
|
action: Action;
|
|
24
25
|
operation_key: string;
|
|
25
26
|
apply_key: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-operations.d.ts","sourceRoot":"","sources":["../../src/server/legacy-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"legacy-operations.d.ts","sourceRoot":"","sources":["../../src/server/legacy-operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAGvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAQpD,KAAK,MAAM,GAAC,OAAO,GAAC,UAAU,CAAA;AAC9B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,WAAW,EAAC,MAAM,CAAC;IAAC,cAAc,EAAC,MAAM,CAAA;IACpG,SAAS,EAAC,MAAM,CAAC;IAAC,aAAa,EAAC,MAAM,CAAC;IAAC,SAAS,CAAC,EAAC,MAAM,CAAA;CAC1D;AACD,4EAA4E;AAC5E,qBAAa,yBAA0B,SAAQ,KAAK;CAAG;AACvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO,GAAE,YAAY,CAY1E;AACD;iFACiF;AACjF,wBAAgB,kBAAkB,CAAC,MAAM,EAAC,MAAM,GAAC,EAAE,CAAC,UAAU,GAAE,MAAM,CAOrE;AAiED,UAAU,OAAO;IACf,MAAM,EAAC,OAAO,eAAe,CAAC,OAAO,GAAC,OAAO,qBAAqB,CAAC,OAAO,CAAC;IAAA,MAAM,EAAC,MAAM,CAAC;IAAA,aAAa,EAAC,MAAM,CAAC;IAAA,SAAS,EAAC,MAAM,CAAA;IAC9H,WAAW,EAAC,MAAM,CAAC;IAAA,cAAc,EAAC,MAAM,CAAC;IAAA,eAAe,EAAC,MAAM,CAAC;IAAA,kBAAkB,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,MAAM,CAAA;IACzG,mBAAmB,EAAC,CAAC,CAAC;IAAA,UAAU,EAAC,MAAM,CAAA;CACxC;AAoCD,wBAAsB,yBAAyB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GAmChH;AACD,wBAAsB,4BAA4B,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM;;;;;GAuBjG;AACD,4FAA4F;AAC5F,wBAAsB,mBAAmB,CAAC,MAAM,EAAC,YAAY,EAAC,SAAS,EAAC,MAAM;;;;;;;;;GAQ7E;AACD,wBAAgB,2BAA2B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAE;IAAC,KAAK,CAAC,EAAC,MAAM,CAAC;IAAA,OAAO,CAAC,EAAC,MAAM,CAAC;IAAA,SAAS,CAAC,EAAC,MAAM,CAAA;CAAC,CAQ1H;AACD,wBAAsB,kBAAkB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC,UAAU,CAAC,OAAO,2BAA2B,CAAC,iBAgC7G"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type LegacyFamily } from './legacy-inventory.js';
|
|
2
|
+
import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
|
|
2
3
|
export interface LegacyReader {
|
|
3
4
|
query(sql: string, values?: unknown[]): Promise<{
|
|
4
5
|
rows: Record<string, any>[];
|
|
@@ -30,7 +31,7 @@ export interface LegacyPlan {
|
|
|
30
31
|
}
|
|
31
32
|
type Disposition = 'quarantine_aggregate' | 'mark_billing_unknown';
|
|
32
33
|
export interface LegacySelection {
|
|
33
|
-
schema:
|
|
34
|
+
schema: typeof LEGACY_STANDARD.selection | typeof LEGACY_SINGLE_SESSION.selection;
|
|
34
35
|
items: Array<{
|
|
35
36
|
id: string;
|
|
36
37
|
family: LegacyFamily;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-plan.d.ts","sourceRoot":"","sources":["../../src/server/legacy-plan.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"legacy-plan.d.ts","sourceRoot":"","sources":["../../src/server/legacy-plan.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAC7F,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAEvF,MAAM,WAAW,YAAY;IAAG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE;AAClH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;IAChF,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;KAAE,CAAA;IACpG,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvE,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvG,mBAAmB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CACxE;AACD,KAAK,WAAW,GAAG,sBAAsB,GAAG,sBAAsB,CAAA;AAClE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,eAAe,CAAC,SAAS,GAAG,OAAO,qBAAqB,CAAC,SAAS,CAAA;IACjF,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC5I;AAsBD,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAmBpE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAqBrE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAG7D;AAED;mFACmF;AACnF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAEjG;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAClF,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CA0EpD;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7E"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type LegacyPlan, type LegacyReader } from './legacy-plan.js';
|
|
2
|
+
import { LEGACY_STANDARD, LEGACY_SINGLE_SESSION } from './legacy-limits.js';
|
|
2
3
|
/** Validate the complete emitted proposal before taking a connection or opening output. */
|
|
3
4
|
export declare function parseLegacyPlan(value: unknown): LegacyPlan;
|
|
4
5
|
export declare function assertLegacyPlanCurrent(expected: LegacyPlan, current: LegacyPlan): void;
|
|
@@ -7,7 +8,7 @@ interface Original {
|
|
|
7
8
|
original: string;
|
|
8
9
|
}
|
|
9
10
|
export interface LegacyCapsule {
|
|
10
|
-
schema:
|
|
11
|
+
schema: typeof LEGACY_STANDARD.capsule | typeof LEGACY_SINGLE_SESSION.capsule;
|
|
11
12
|
plan: LegacyPlan;
|
|
12
13
|
captured_at: string;
|
|
13
14
|
requests: Original[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-preservation.d.ts","sourceRoot":"","sources":["../../src/server/legacy-preservation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"legacy-preservation.d.ts","sourceRoot":"","sources":["../../src/server/legacy-preservation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACnH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAc,MAAM,oBAAoB,CAAA;AAevF,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CA8C1D;AAOD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAEvF;AAED,UAAU,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACnD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,eAAe,CAAC,OAAO,GAAG,OAAO,qBAAqB,CAAC,OAAO,CAAA;IAC7E,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA4ChE;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAkB1G;AAmBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAU,GAAG,OAAO,CAS5E;AAED;sFACsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO;;;;;;;;;;;EAwB3D;AAED;;kFAEkF;AAClF,wBAAsB,qBAAqB,CAAC,MAAM,EAAC,YAAY,EAAC,KAAK,EAAC,OAAO,EAAC,KAAK,GAAC,aAAa,GAAC,WAAyB;;;;;;;;;;GAwD1H;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,MAAM,EAAE,GAAG;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,CAM1G;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAC,MAAM,EAAC,OAAO,EAAC;IAAC,KAAK,EAAC,MAAM,CAAC;IAAA,MAAM,CAAC,EAAC,MAAM,CAAA;CAAC,iBAgB/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-operations.d.ts","sourceRoot":"","sources":["../../src/server/service-operations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAuCxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"service-operations.d.ts","sourceRoot":"","sources":["../../src/server/service-operations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAuCxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAgkB5E,gHAAgH;AAChH,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAe/G"}
|
|
@@ -1117,12 +1117,32 @@ function meteredBudgetAmounts(groups, limit, threshold) {
|
|
|
1117
1117
|
function ymd(date2) {
|
|
1118
1118
|
return date2.toISOString().substring(0, 10);
|
|
1119
1119
|
}
|
|
1120
|
-
function
|
|
1120
|
+
function resolveReportingInterval(interval, now = new Date) {
|
|
1121
1121
|
if (!Number.isFinite(now.getTime()))
|
|
1122
1122
|
throw new RangeError("Invalid reporting clock");
|
|
1123
|
-
const start = new Date(
|
|
1123
|
+
const start = new Date(now);
|
|
1124
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
1124
1125
|
const end = new Date(start);
|
|
1125
|
-
|
|
1126
|
+
if (interval.kind === "rolling") {
|
|
1127
|
+
if (!Number.isSafeInteger(interval.count) || interval.count < 1)
|
|
1128
|
+
throw new RangeError("Invalid reporting window");
|
|
1129
|
+
if (interval.unit === "hour") {
|
|
1130
|
+
start.setTime(now.getTime() - interval.count * 3600000);
|
|
1131
|
+
if (!Number.isFinite(start.getTime()))
|
|
1132
|
+
throw new RangeError("Invalid reporting window");
|
|
1133
|
+
return { time_zone: "UTC", start: start.toISOString(), end_exclusive: now.toISOString() };
|
|
1134
|
+
}
|
|
1135
|
+
if (interval.unit !== "day")
|
|
1136
|
+
throw new RangeError("Invalid reporting unit");
|
|
1137
|
+
start.setUTCDate(start.getUTCDate() - interval.count + 1);
|
|
1138
|
+
end.setUTCDate(end.getUTCDate() + 1);
|
|
1139
|
+
if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()))
|
|
1140
|
+
throw new RangeError("Invalid reporting window");
|
|
1141
|
+
return { time_zone: "UTC", start: ymd(start), end_exclusive: ymd(end) };
|
|
1142
|
+
}
|
|
1143
|
+
if (interval.kind !== "calendar")
|
|
1144
|
+
throw new RangeError("Invalid reporting interval");
|
|
1145
|
+
switch (interval.period) {
|
|
1126
1146
|
case "today":
|
|
1127
1147
|
end.setUTCDate(end.getUTCDate() + 1);
|
|
1128
1148
|
break;
|
|
@@ -1145,11 +1165,15 @@ function calendarPeriodBounds(period, now = new Date) {
|
|
|
1145
1165
|
end.setUTCFullYear(end.getUTCFullYear() + 1);
|
|
1146
1166
|
break;
|
|
1147
1167
|
case "all":
|
|
1148
|
-
return { time_zone: "UTC",
|
|
1168
|
+
return { time_zone: "UTC", start: null, end_exclusive: null };
|
|
1149
1169
|
default:
|
|
1150
1170
|
throw new RangeError("Invalid reporting period");
|
|
1151
1171
|
}
|
|
1152
|
-
return { time_zone: "UTC",
|
|
1172
|
+
return { time_zone: "UTC", start: ymd(start), end_exclusive: ymd(end) };
|
|
1173
|
+
}
|
|
1174
|
+
function calendarPeriodBounds(period, now = new Date) {
|
|
1175
|
+
const bounds = resolveReportingInterval({ kind: "calendar", period }, now);
|
|
1176
|
+
return { time_zone: bounds.time_zone, start_date: bounds.start, end_date_exclusive: bounds.end_exclusive };
|
|
1153
1177
|
}
|
|
1154
1178
|
function calendarPeriodWhere(period, column, now = new Date) {
|
|
1155
1179
|
if (!/^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)?$/i.test(column)) {
|
|
@@ -3020,20 +3044,22 @@ function queryAccountBreakdownSince(db, since) {
|
|
|
3020
3044
|
ORDER BY api_equivalent_usd DESC
|
|
3021
3045
|
`).all(since, since);
|
|
3022
3046
|
}
|
|
3023
|
-
function queryDailyBreakdown(db, days = 30, machine) {
|
|
3047
|
+
function queryDailyBreakdown(db, days = 30, machine, now = new Date) {
|
|
3048
|
+
const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
|
|
3024
3049
|
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
3025
|
-
const params = machine ? [
|
|
3050
|
+
const params = machine ? [bounds.start, bounds.end_exclusive, machine] : [bounds.start, bounds.end_exclusive];
|
|
3026
3051
|
return db.prepare(`
|
|
3027
3052
|
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
3028
3053
|
FROM economy_effective_requests
|
|
3029
|
-
WHERE timestamp >=
|
|
3054
|
+
WHERE timestamp >= ? AND timestamp < ?${machineClause}
|
|
3030
3055
|
GROUP BY DATE(timestamp), agent
|
|
3031
3056
|
ORDER BY date ASC
|
|
3032
3057
|
`).all(...params);
|
|
3033
3058
|
}
|
|
3034
|
-
function queryHourlyBreakdown(db, machine, hours) {
|
|
3035
|
-
const
|
|
3036
|
-
const
|
|
3059
|
+
function queryHourlyBreakdown(db, machine, hours, now = new Date) {
|
|
3060
|
+
const bounds = resolveReportingInterval(hours == null ? { kind: "calendar", period: "today" } : { kind: "rolling", unit: "hour", count: hours }, now);
|
|
3061
|
+
const clauses = hours == null ? ["timestamp >= ? AND timestamp < ?"] : ["STRFTIME('%Y-%m-%dT%H:%M:%fZ', timestamp) >= ? AND STRFTIME('%Y-%m-%dT%H:%M:%fZ', timestamp) < ?"];
|
|
3062
|
+
const params = [bounds.start, bounds.end_exclusive];
|
|
3037
3063
|
if (machine) {
|
|
3038
3064
|
clauses.push("machine_id = ?");
|
|
3039
3065
|
params.push(machine);
|
|
@@ -3723,12 +3749,21 @@ var init_config = __esm(() => {
|
|
|
3723
3749
|
function detectCostSpikes(dailyTotals, opts) {
|
|
3724
3750
|
const windowDays = opts?.windowDays ?? 7;
|
|
3725
3751
|
const multiplier = opts?.multiplier ?? 2;
|
|
3752
|
+
if (!Number.isSafeInteger(windowDays) || windowDays < 1)
|
|
3753
|
+
throw new RangeError("Invalid spike window");
|
|
3754
|
+
if (!Number.isFinite(multiplier) || multiplier <= 0)
|
|
3755
|
+
throw new RangeError("Invalid spike multiplier");
|
|
3726
3756
|
const sorted = [...dailyTotals].sort((a, b) => a.date.localeCompare(b.date));
|
|
3727
3757
|
const spikes = [];
|
|
3728
3758
|
for (let i = windowDays;i < sorted.length; i++) {
|
|
3729
3759
|
const window = sorted.slice(i - windowDays, i);
|
|
3730
|
-
const avg = window.reduce((s, d) => s + d.cost_usd, 0) / window.length;
|
|
3731
3760
|
const current = sorted[i];
|
|
3761
|
+
const previous = new Date(current.date + "T00:00:00.000Z");
|
|
3762
|
+
previous.setUTCDate(previous.getUTCDate() - 1);
|
|
3763
|
+
const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: windowDays }, previous);
|
|
3764
|
+
if (window[0].date !== bounds.start || window[window.length - 1].date !== previous.toISOString().slice(0, 10) || new Set(window.map((day) => day.date)).size !== windowDays)
|
|
3765
|
+
continue;
|
|
3766
|
+
const avg = window.reduce((s, d) => s + d.cost_usd, 0) / window.length;
|
|
3732
3767
|
if (avg > 0 && current.cost_usd > avg * multiplier) {
|
|
3733
3768
|
spikes.push({
|
|
3734
3769
|
date: current.date,
|
|
@@ -3740,21 +3775,23 @@ function detectCostSpikes(dailyTotals, opts) {
|
|
|
3740
3775
|
}
|
|
3741
3776
|
return spikes;
|
|
3742
3777
|
}
|
|
3743
|
-
function queryRecentSpikes(db, days = 14) {
|
|
3778
|
+
function queryRecentSpikes(db, days = 14, now = new Date) {
|
|
3779
|
+
const bounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
|
|
3744
3780
|
const rows = db.prepare(`
|
|
3745
3781
|
SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
3746
3782
|
FROM economy_effective_requests
|
|
3747
|
-
WHERE timestamp >=
|
|
3783
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
3748
3784
|
GROUP BY DATE(timestamp)
|
|
3749
3785
|
ORDER BY date ASC
|
|
3750
|
-
`).all(
|
|
3786
|
+
`).all(bounds.start, bounds.end_exclusive);
|
|
3751
3787
|
return detectCostSpikes(rows);
|
|
3752
3788
|
}
|
|
3753
|
-
function getTodaySpike(db) {
|
|
3754
|
-
const today =
|
|
3755
|
-
const spikes = queryRecentSpikes(db, 14);
|
|
3789
|
+
function getTodaySpike(db, now = new Date) {
|
|
3790
|
+
const today = resolveReportingInterval({ kind: "calendar", period: "today" }, now).start;
|
|
3791
|
+
const spikes = queryRecentSpikes(db, 14, now);
|
|
3756
3792
|
return spikes.find((s) => s.date === today) ?? null;
|
|
3757
3793
|
}
|
|
3794
|
+
var init_spikes = () => {};
|
|
3758
3795
|
|
|
3759
3796
|
// src/lib/webhooks.ts
|
|
3760
3797
|
var exports_webhooks = {};
|
|
@@ -3821,6 +3858,7 @@ async function fireWebhook(url, payload) {
|
|
|
3821
3858
|
var init_webhooks = __esm(() => {
|
|
3822
3859
|
init_config();
|
|
3823
3860
|
init_database();
|
|
3861
|
+
init_spikes();
|
|
3824
3862
|
});
|
|
3825
3863
|
|
|
3826
3864
|
// src/db/sync-pg.ts
|
|
@@ -3882,15 +3920,16 @@ function translateSqliteDates(sql) {
|
|
|
3882
3920
|
s = s.replace(/DATE\('now',\s*(\$\d+)\s*\|\|\s*' days'\)/gi, (_m, p) => isoDate(`now() + ((${p}) || ' days')::interval`));
|
|
3883
3921
|
s = s.replace(/DATETIME\('now',\s*(\$\d+)\)/gi, (_m, p) => `(now() + (${p})::interval)`);
|
|
3884
3922
|
s = s.replace(/DATETIME\('now',\s*'([^']+)'\)/gi, (_m, lit) => `(now() + interval '${lit}')`);
|
|
3923
|
+
s = s.replace(/STRFTIME\('%Y-%m-%dT%H:%M:%fZ',\s*([A-Za-z_][A-Za-z0-9_]*)\)/gi, (_m, col) => `to_char(CAST("${col}" AS timestamptz) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`);
|
|
3885
3924
|
const strftimeMap = { "%H": "HH24", "%Y": "YYYY", "%m": "MM", "%d": "DD", "%M": "MI", "%S": "SS" };
|
|
3886
3925
|
s = s.replace(/STRFTIME\('([^']+)',\s*([A-Za-z_][A-Za-z0-9_]*)\)/gi, (_m, fmt, col) => {
|
|
3887
3926
|
const pgFmt = Object.entries(strftimeMap).reduce((acc, [k, v]) => acc.split(k).join(v), fmt);
|
|
3888
|
-
return `to_char(CAST("${col}" AS timestamptz), '${pgFmt}')`;
|
|
3927
|
+
return `to_char(CAST("${col}" AS timestamptz) AT TIME ZONE 'UTC', '${pgFmt}')`;
|
|
3889
3928
|
});
|
|
3890
3929
|
s = s.replace(/\bDATE\('now'\)/gi, isoDate("now()"));
|
|
3891
3930
|
s = s.replace(/\bDATETIME\('now'\)/gi, "now()");
|
|
3892
3931
|
s = s.replace(/\bDATETIME\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/gi, (_m, col) => `CAST("${col}" AS timestamptz)`);
|
|
3893
|
-
s = s.replace(/\bDATE\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/gi, (_m, col) => `substr("${
|
|
3932
|
+
s = s.replace(/\bDATE\(\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)?)\s*\)/gi, (_m, col) => `substr(${col.split(".").map((part) => `"${part}"`).join(".")}, 1, 10)`);
|
|
3894
3933
|
return s;
|
|
3895
3934
|
}
|
|
3896
3935
|
function resolveWorkerPath() {
|
|
@@ -7105,7 +7144,8 @@ function buildBrief(db, opts = {}) {
|
|
|
7105
7144
|
}
|
|
7106
7145
|
|
|
7107
7146
|
// src/lib/analytics.ts
|
|
7108
|
-
function queryProjectDetail(db, nameOrPath) {
|
|
7147
|
+
function queryProjectDetail(db, nameOrPath, now = new Date) {
|
|
7148
|
+
const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now);
|
|
7109
7149
|
const like = `%${nameOrPath}%`;
|
|
7110
7150
|
const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
|
|
7111
7151
|
FROM economy_effective_sessions
|
|
@@ -7119,8 +7159,8 @@ function queryProjectDetail(db, nameOrPath) {
|
|
|
7119
7159
|
const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
7120
7160
|
FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
|
|
7121
7161
|
WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
|
|
7122
|
-
AND r.timestamp >=
|
|
7123
|
-
GROUP BY date ORDER BY date ASC`).all(like, like);
|
|
7162
|
+
AND r.timestamp >= ? AND r.timestamp < ?
|
|
7163
|
+
GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
|
|
7124
7164
|
const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
7125
7165
|
FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
|
|
7126
7166
|
WHERE s.project_name LIKE ? OR s.project_path LIKE ?
|
|
@@ -7159,18 +7199,17 @@ function queryRangeStats(db, from, to) {
|
|
|
7159
7199
|
return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
|
|
7160
7200
|
}
|
|
7161
7201
|
function queryForecast(db, now = new Date) {
|
|
7162
|
-
const
|
|
7163
|
-
const
|
|
7164
|
-
const
|
|
7165
|
-
const
|
|
7202
|
+
const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now);
|
|
7203
|
+
const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now);
|
|
7204
|
+
const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();
|
|
7205
|
+
const dayOfMonth = now.getUTCDate();
|
|
7206
|
+
const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(month.start, recent.end_exclusive);
|
|
7166
7207
|
const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
|
|
7167
7208
|
const projected = dailyAvg * daysInMonth;
|
|
7168
|
-
const
|
|
7169
|
-
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
7170
|
-
const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(sevenDaysAgo.toISOString().substring(0, 10));
|
|
7209
|
+
const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(recent.start, recent.end_exclusive);
|
|
7171
7210
|
const last7DailyAvg = last7.cost / 7;
|
|
7172
7211
|
const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
|
|
7173
|
-
FROM economy_effective_requests WHERE
|
|
7212
|
+
FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
|
|
7174
7213
|
return {
|
|
7175
7214
|
day_of_month: dayOfMonth,
|
|
7176
7215
|
days_in_month: daysInMonth,
|
|
@@ -7266,7 +7305,7 @@ import { randomUUID } from "crypto";
|
|
|
7266
7305
|
// package.json
|
|
7267
7306
|
var package_default = {
|
|
7268
7307
|
name: "@hasna/economy",
|
|
7269
|
-
version: "0.5.
|
|
7308
|
+
version: "0.5.3",
|
|
7270
7309
|
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
7310
|
repository: {
|
|
7272
7311
|
type: "git",
|
|
@@ -7471,7 +7510,13 @@ function createDataHandler(db, actorId) {
|
|
|
7471
7510
|
if (path === "/api/daily" && method === "GET") {
|
|
7472
7511
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
7473
7512
|
const machine = url.searchParams.get("machine") ?? undefined;
|
|
7474
|
-
|
|
7513
|
+
const now = new Date;
|
|
7514
|
+
try {
|
|
7515
|
+
resolveReportingInterval({ kind: "rolling", unit: "day", count: days }, now);
|
|
7516
|
+
} catch {
|
|
7517
|
+
return err("days must be a positive integer within the supported UTC date range");
|
|
7518
|
+
}
|
|
7519
|
+
return ok(queryDailyBreakdown(db, days, machine, now));
|
|
7475
7520
|
}
|
|
7476
7521
|
if (path === "/api/hourly" && method === "GET") {
|
|
7477
7522
|
const machine = url.searchParams.get("machine") ?? undefined;
|
|
@@ -122,6 +122,44 @@ database backup, production ownership attestation or disaster-recovery exercise.
|
|
|
122
122
|
Neither command authorizes quarantine or performs application or rollback of a
|
|
123
123
|
reconciliation.
|
|
124
124
|
|
|
125
|
+
## One large session
|
|
126
|
+
|
|
127
|
+
When one complete session exceeds the ordinary envelope, select it explicitly
|
|
128
|
+
with `schema: "economy.legacy-session-selection.v1"` in the same selection file.
|
|
129
|
+
Use the same maintained `legacy-plan`, `legacy-preserve` and `legacy-rehearse`
|
|
130
|
+
commands. Every target still needs its exact original digest and reviewed
|
|
131
|
+
disposition, reason and evidence reference. The planner requires exactly one
|
|
132
|
+
session, preserves its entire request population including unselected rows, and
|
|
133
|
+
emits `economy.legacy-session-plan.v1`. Preservation emits
|
|
134
|
+
`economy.legacy-session-preservation.v1`; neither schema can be substituted for
|
|
135
|
+
its ordinary counterpart.
|
|
136
|
+
|
|
137
|
+
This mode accepts at most 50,000 selected requests, 50,000 complete-session
|
|
138
|
+
members, a 32 MiB selection file, 64 MiB of original row text, and a 128 MiB
|
|
139
|
+
preservation file. Each bound refuses the operation without truncation. Ordinary
|
|
140
|
+
artifacts retain their 100-target, 10,000-member, 256 KiB selection, 16 MiB
|
|
141
|
+
original and 32 MiB preservation bounds. A population above the explicit
|
|
142
|
+
single-session bounds still needs another reviewed procedure; the tool never
|
|
143
|
+
silently increases them.
|
|
144
|
+
|
|
145
|
+
Apply uses one set-based insertion for the selected dispositions and commits
|
|
146
|
+
one immutable capsule and receipt for the whole operation. Its receipt schema
|
|
147
|
+
is `economy.legacy-session-operation-receipt.v1`. Use the same exact-target
|
|
148
|
+
intent, observation, explicit retry and rollback commands described below.
|
|
149
|
+
Session overlap remains prohibited: select all reviewed targets for that
|
|
150
|
+
session together, instead of repeatedly preserving the same whole session for
|
|
151
|
+
small target batches. File and row preservation, typed restoration, complete
|
|
152
|
+
membership preconditions, ingestion exclusion and rollback checks all apply.
|
|
153
|
+
Use a version supporting these explicit artifact schemas for every stage;
|
|
154
|
+
older versions refuse them. These limits do not establish source provenance or
|
|
155
|
+
authorize changing a production report.
|
|
156
|
+
|
|
157
|
+
The appended PostgreSQL migration checks conflicts once for the inserted set,
|
|
158
|
+
including conflicts between operations in that same set. This avoids repeated
|
|
159
|
+
scans of a growing operation when existing history has small or stale planner
|
|
160
|
+
statistics. The 15-second statement timeout and session/request conflict rules
|
|
161
|
+
remain in force.
|
|
162
|
+
|
|
125
163
|
## Apply, observe and roll back
|
|
126
164
|
|
|
127
165
|
Deploy the reviewed runtime integration and apply its appended PostgreSQL schema
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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",
|