@hasna/economy 0.5.2 → 0.5.4
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/README.md +4 -2
- 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-npsjjv96.js → index-9arv0b1j.js} +162 -36
- package/dist/chunks/{index-hesxq90g.js → index-9wa1f2kv.js} +1 -1
- package/dist/chunks/{index-zpvea08j.js → index-c7h2rxx1.js} +38 -12
- package/dist/chunks/{index-9d2aky06.js → index-ct3zdefz.js} +3 -3
- 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-dj3tctqh.js → index-n1wwqxfe.js} +14 -13
- package/dist/chunks/{index-kzwqw3ve.js → index-nheq2nrz.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-p2yarhm8.js → serve-ra46ms69.js} +18 -10
- package/dist/chunks/{sqlite-store-st99garx.js → sqlite-store-13nhr1z0.js} +2 -2
- package/dist/chunks/{sqlite-store-501f32ts.js → sqlite-store-gamn65xp.js} +2 -2
- package/dist/chunks/{sqlite-store-fq4natnf.js → sqlite-store-ht6frztj.js} +2 -2
- package/dist/chunks/{tui-x64hjfrh.js → tui-a1tvcw2j.js} +4 -4
- package/dist/chunks/{watch-bxstjpbx.js → watch-rvd3dn6r.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/sync-pg.d.ts.map +1 -1
- package/dist/index.js +15 -14
- package/dist/lib/accounts-store.d.ts +25 -1
- package/dist/lib/accounts-store.d.ts.map +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- 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/lib/subscriptions-registry.d.ts +26 -0
- package/dist/lib/subscriptions-registry.d.ts.map +1 -0
- package/dist/lib/test-keychain-fixture.d.ts +3 -2
- package/dist/lib/test-keychain-fixture.d.ts.map +1 -1
- package/dist/mcp/index.js +245 -118
- package/dist/otel/index.js +56 -27
- package/dist/server/index.js +246 -77
- package/dist/server/service-operations.d.ts.map +1 -1
- package/dist/server/service-worker.js +237 -68
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
queryZeroCostTokenizedModels,
|
|
57
57
|
requireCurrentBillingSummary,
|
|
58
58
|
requireCurrentCostSummary,
|
|
59
|
+
resolveReportingInterval,
|
|
59
60
|
rollupSession,
|
|
60
61
|
scanReceiptId,
|
|
61
62
|
sessionOverlapWhere,
|
|
@@ -74,7 +75,7 @@ import {
|
|
|
74
75
|
upsertUsageSnapshot,
|
|
75
76
|
usageSnapshotFilterForPeriod,
|
|
76
77
|
validateIngest
|
|
77
|
-
} from "../chunks/index-
|
|
78
|
+
} from "../chunks/index-vajzfzgv.js";
|
|
78
79
|
import {
|
|
79
80
|
__require
|
|
80
81
|
} from "../chunks/index-p35cyhc4.js";
|
|
@@ -84,7 +85,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
84
85
|
// package.json
|
|
85
86
|
var package_default = {
|
|
86
87
|
name: "@hasna/economy",
|
|
87
|
-
version: "0.5.
|
|
88
|
+
version: "0.5.4",
|
|
88
89
|
description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
|
|
89
90
|
repository: {
|
|
90
91
|
type: "git",
|
|
@@ -1506,7 +1507,7 @@ function registerIngestJournalTool(server) {
|
|
|
1506
1507
|
}
|
|
1507
1508
|
|
|
1508
1509
|
// src/mcp/server.ts
|
|
1509
|
-
import { z as
|
|
1510
|
+
import { z as z7 } from "zod";
|
|
1510
1511
|
|
|
1511
1512
|
// src/ingest/collection-outcome.ts
|
|
1512
1513
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1562,34 +1563,141 @@ import { join as join6, basename } from "path";
|
|
|
1562
1563
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1563
1564
|
import { homedir } from "os";
|
|
1564
1565
|
import { join as join5 } from "path";
|
|
1565
|
-
import { z as
|
|
1566
|
+
import { z as z6 } from "zod";
|
|
1566
1567
|
import { resolveClientTransport, resolveCredential } from "@hasna/contracts/client";
|
|
1567
|
-
|
|
1568
|
-
|
|
1568
|
+
|
|
1569
|
+
// src/lib/subscriptions-registry.ts
|
|
1570
|
+
import { execFile } from "child_process";
|
|
1571
|
+
import { promisify } from "util";
|
|
1572
|
+
import { isAbsolute } from "path";
|
|
1573
|
+
import { z as z5 } from "zod";
|
|
1574
|
+
var execute = promisify(execFile);
|
|
1575
|
+
var readSubscriptionsMetadata = async (args, env) => {
|
|
1576
|
+
let stdout;
|
|
1577
|
+
try {
|
|
1578
|
+
const result = await execute("subscriptions", args, {
|
|
1579
|
+
env,
|
|
1580
|
+
encoding: "utf8",
|
|
1581
|
+
timeout: 30000,
|
|
1582
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
1583
|
+
windowsHide: true
|
|
1584
|
+
});
|
|
1585
|
+
stdout = result.stdout;
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
const code = error?.code;
|
|
1588
|
+
if (code === "ENOENT")
|
|
1589
|
+
throw new Error("subscriptions: CLI not found on PATH; install the Subscriptions CLI to resolve station account metadata");
|
|
1590
|
+
const status = typeof code === "number" && Number.isInteger(code) ? `exit ${code}` : "execution limit or transport failure";
|
|
1591
|
+
throw new Error(`subscriptions: read-only metadata command failed (${status}); verify the installed CLI and its hosted credentials`);
|
|
1592
|
+
}
|
|
1593
|
+
try {
|
|
1594
|
+
return JSON.parse(stdout);
|
|
1595
|
+
} catch {
|
|
1596
|
+
throw new Error("subscriptions: metadata command returned invalid JSON");
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
var nonempty = z5.string().min(1);
|
|
1569
1600
|
var profileSchema = z5.object({
|
|
1570
|
-
name:
|
|
1571
|
-
tool:
|
|
1601
|
+
name: nonempty,
|
|
1602
|
+
tool: nonempty,
|
|
1572
1603
|
email: z5.string().optional(),
|
|
1573
1604
|
displayName: z5.string().optional(),
|
|
1574
|
-
dir:
|
|
1605
|
+
dir: nonempty.refine(isAbsolute).refine((value) => !/[\0\r\n]/.test(value)),
|
|
1606
|
+
createdAt: nonempty,
|
|
1607
|
+
lastUsedAt: z5.string().optional(),
|
|
1575
1608
|
description: z5.string().optional(),
|
|
1576
|
-
|
|
1577
|
-
|
|
1609
|
+
active: z5.boolean(),
|
|
1610
|
+
applied: z5.boolean(),
|
|
1611
|
+
switchedAway: z5.object({ profile: nonempty, email: z5.string().optional() }).optional()
|
|
1612
|
+
});
|
|
1613
|
+
var toolsSchema = z5.array(z5.object({
|
|
1614
|
+
id: nonempty,
|
|
1615
|
+
label: nonempty,
|
|
1616
|
+
envVar: nonempty,
|
|
1617
|
+
defaultDir: nonempty,
|
|
1618
|
+
bin: nonempty
|
|
1619
|
+
}));
|
|
1620
|
+
function unique(rows, identity) {
|
|
1621
|
+
if (new Set(rows.map(identity)).size !== rows.length) {
|
|
1622
|
+
throw new Error("subscriptions: duplicate identity in metadata response");
|
|
1623
|
+
}
|
|
1624
|
+
return rows;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
class SubscriptionsRegistry {
|
|
1628
|
+
read;
|
|
1629
|
+
transport = "api";
|
|
1630
|
+
registry = "subscriptions";
|
|
1631
|
+
env;
|
|
1632
|
+
profiles;
|
|
1633
|
+
tools;
|
|
1634
|
+
constructor(baseUrl, env, read = readSubscriptionsMetadata) {
|
|
1635
|
+
this.read = read;
|
|
1636
|
+
this.env = { ...env, HASNA_SUBSCRIPTIONS_API_URL: baseUrl.replace(/\/v1\/?$/, "") };
|
|
1637
|
+
}
|
|
1638
|
+
allProfiles() {
|
|
1639
|
+
return this.profiles ??= this.read(["list", "--json"], this.env).then((raw) => {
|
|
1640
|
+
const parsed = z5.array(profileSchema).safeParse(raw);
|
|
1641
|
+
if (!parsed.success)
|
|
1642
|
+
throw new Error("subscriptions: invalid profile metadata response");
|
|
1643
|
+
return unique(parsed.data, (row) => JSON.stringify([row.tool, row.name]));
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
async listProfiles(tool) {
|
|
1647
|
+
const rows = await this.allProfiles();
|
|
1648
|
+
return rows.filter((row) => !tool || row.tool === tool);
|
|
1649
|
+
}
|
|
1650
|
+
async findProfile(name, tool) {
|
|
1651
|
+
const matches = (await this.listProfiles(tool)).filter((row) => row.name === name);
|
|
1652
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
1653
|
+
}
|
|
1654
|
+
async appliedProfile(tool) {
|
|
1655
|
+
const matches = (await this.allProfiles()).filter((row) => row.tool === tool && row.applied);
|
|
1656
|
+
if (matches.length > 1)
|
|
1657
|
+
throw new Error("subscriptions: ambiguous station applied-profile binding");
|
|
1658
|
+
return matches[0];
|
|
1659
|
+
}
|
|
1660
|
+
async currentProfile(_tool) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
async listTools() {
|
|
1664
|
+
return this.tools ??= this.read(["tools", "list", "--json"], this.env).then((raw) => {
|
|
1665
|
+
const parsed = toolsSchema.safeParse(raw);
|
|
1666
|
+
if (!parsed.success)
|
|
1667
|
+
throw new Error("subscriptions: invalid tool metadata response");
|
|
1668
|
+
return unique(parsed.data, (row) => row.id);
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// src/lib/accounts-store.ts
|
|
1674
|
+
var ACCOUNTS_APP = "accounts";
|
|
1675
|
+
var profileNameSchema = z6.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase alphanumeric/hyphen and start with a letter or digit");
|
|
1676
|
+
var profileSchema2 = z6.object({
|
|
1677
|
+
name: z6.string(),
|
|
1678
|
+
tool: z6.string(),
|
|
1679
|
+
email: z6.string().optional(),
|
|
1680
|
+
displayName: z6.string().optional(),
|
|
1681
|
+
dir: z6.string(),
|
|
1682
|
+
description: z6.string().optional(),
|
|
1683
|
+
createdAt: z6.string(),
|
|
1684
|
+
lastUsedAt: z6.string().optional(),
|
|
1685
|
+
switchedAway: z6.object({ profile: z6.string().min(1), email: z6.string().optional() }).optional()
|
|
1578
1686
|
});
|
|
1579
|
-
var toolDefSchema =
|
|
1580
|
-
id:
|
|
1581
|
-
label:
|
|
1582
|
-
envVar:
|
|
1583
|
-
defaultDir:
|
|
1584
|
-
bin:
|
|
1687
|
+
var toolDefSchema = z6.object({
|
|
1688
|
+
id: z6.string(),
|
|
1689
|
+
label: z6.string(),
|
|
1690
|
+
envVar: z6.string(),
|
|
1691
|
+
defaultDir: z6.string(),
|
|
1692
|
+
bin: z6.string()
|
|
1585
1693
|
});
|
|
1586
|
-
var storeSchema =
|
|
1587
|
-
version:
|
|
1588
|
-
current:
|
|
1589
|
-
applied:
|
|
1590
|
-
toolLocks:
|
|
1591
|
-
profiles:
|
|
1592
|
-
tools:
|
|
1694
|
+
var storeSchema = z6.object({
|
|
1695
|
+
version: z6.literal(1),
|
|
1696
|
+
current: z6.record(z6.string(), z6.string()).default({}),
|
|
1697
|
+
applied: z6.record(z6.string(), z6.string()).default({}),
|
|
1698
|
+
toolLocks: z6.record(z6.string(), z6.string()).default({}),
|
|
1699
|
+
profiles: z6.array(profileSchema2).default([]),
|
|
1700
|
+
tools: z6.array(toolDefSchema).default([])
|
|
1593
1701
|
});
|
|
1594
1702
|
function localRegistryAllowed(env = process.env) {
|
|
1595
1703
|
return LOCAL_STORAGE_OPT_IN_KEYS.some((key) => {
|
|
@@ -1871,25 +1979,34 @@ class ApiStore {
|
|
|
1871
1979
|
}
|
|
1872
1980
|
}
|
|
1873
1981
|
function resolveStore(env = process.env, options = {}) {
|
|
1982
|
+
const legacy = resolveRegistryTransport(ACCOUNTS_APP, env, options.credentials);
|
|
1983
|
+
if (legacy)
|
|
1984
|
+
return new ApiStore(legacy.baseUrl, legacy.apiKey);
|
|
1985
|
+
const current = resolveRegistryTransport("subscriptions", env, options.subscriptionsCredentials);
|
|
1986
|
+
if (current)
|
|
1987
|
+
return new SubscriptionsRegistry(current.baseUrl, env, options.readSubscriptionsMetadata);
|
|
1988
|
+
return unconfiguredStore(env);
|
|
1989
|
+
}
|
|
1990
|
+
function resolveRegistryTransport(app, env, credentials) {
|
|
1874
1991
|
const clientEnv = env;
|
|
1875
|
-
const credential = resolveCredential(
|
|
1992
|
+
const credential = resolveCredential(app, clientEnv, credentials);
|
|
1876
1993
|
if (credential === null) {
|
|
1877
1994
|
try {
|
|
1878
|
-
resolveClientTransport(
|
|
1995
|
+
resolveClientTransport(app, clientEnv, credentials ? { credentials } : {});
|
|
1879
1996
|
} catch (err) {
|
|
1880
1997
|
if (isNoAccountsCredentialConfigurationError(err))
|
|
1881
|
-
return
|
|
1998
|
+
return null;
|
|
1882
1999
|
throw err;
|
|
1883
2000
|
}
|
|
1884
|
-
return
|
|
2001
|
+
return null;
|
|
1885
2002
|
}
|
|
1886
2003
|
if (credential.tier === "pointer") {
|
|
1887
|
-
throw new Error(
|
|
2004
|
+
throw new Error(`${app}: ${credential.source} names a secrets-vault item, and the registry adapter resolves ` + "credentials synchronously, so it cannot complete the pointer per request. It is a deliberate " + "selection and is not resolved around. Use a literal tier instead \u2014 the Keychain item " + `hasna.credentials.${app}.api-key, ~/.hasna/${app}/config/credentials, or HASNA_${app.toUpperCase()}_API_KEY.`);
|
|
1888
2005
|
}
|
|
1889
|
-
const resolution = resolveClientTransport(
|
|
1890
|
-
credentials: { ...
|
|
2006
|
+
const resolution = resolveClientTransport(app, clientEnv, {
|
|
2007
|
+
credentials: { ...credentials, apiKey: credential.apiKey }
|
|
1891
2008
|
});
|
|
1892
|
-
return
|
|
2009
|
+
return { baseUrl: resolution.baseUrl, apiKey: credential.apiKey };
|
|
1893
2010
|
}
|
|
1894
2011
|
function isNoAccountsCredentialConfigurationError(error) {
|
|
1895
2012
|
if (typeof error !== "object" || error === null)
|
|
@@ -1906,10 +2023,10 @@ function unconfiguredStore(env) {
|
|
|
1906
2023
|
return new NoRegistryStore;
|
|
1907
2024
|
}
|
|
1908
2025
|
function accountsLocalModeNotice() {
|
|
1909
|
-
return `accounts: LOCAL mode (${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1) \u2014
|
|
2026
|
+
return `accounts: LOCAL mode (${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1) \u2014 neither Accounts nor Subscriptions credentials resolved, so ` + "attribution reads the on-box JSON registry (~/.hasna/accounts/accounts.json) instead of accounts-serve. " + "For hosted attribution, configure the Subscriptions CLI through its normal sign-in or credential resolver.";
|
|
1910
2027
|
}
|
|
1911
2028
|
function accountsRefusalNotice() {
|
|
1912
|
-
return "accounts:
|
|
2029
|
+
return "accounts: neither Accounts nor Subscriptions credentials resolved (legacy hasna.credentials.accounts.api-key, " + "~/.hasna/accounts/config/credentials, HASNA_ACCOUNTS_API_KEY; current hasna.credentials.subscriptions.api-key, " + "~/.hasna/subscriptions/config/credentials, HASNA_SUBSCRIPTIONS_API_KEY), so spend attribution is omitted. The " + `on-box registry (~/.hasna/accounts/accounts.json) is NOT read without ${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1 \u2014 ` + "economy fails closed instead of attributing hosted spend from an on-box file.";
|
|
1913
2030
|
}
|
|
1914
2031
|
var accountsLocalNoticePrinted = false;
|
|
1915
2032
|
function announceAccountsLocalMode() {
|
|
@@ -1997,7 +2114,10 @@ async function profileForEnvDir(store, tool, env) {
|
|
|
1997
2114
|
if (!configuredDir)
|
|
1998
2115
|
return null;
|
|
1999
2116
|
const normalized = normalizeDir(configuredDir);
|
|
2000
|
-
|
|
2117
|
+
const matches = (await store.listProfiles(tool.id)).filter((profile) => normalizeDir(profile.dir) === normalized);
|
|
2118
|
+
if (matches.length > 1)
|
|
2119
|
+
throw new Error("account attribution: multiple profiles match the configured directory");
|
|
2120
|
+
return matches[0] ?? null;
|
|
2001
2121
|
}
|
|
2002
2122
|
async function resolveAccountForAgent(agent, env = process.env, options = {}) {
|
|
2003
2123
|
const override = envOverride(agent, env);
|
|
@@ -2011,8 +2131,15 @@ async function resolveAccountForAgent(agent, env = process.env, options = {}) {
|
|
|
2011
2131
|
continue;
|
|
2012
2132
|
const envProfile = await profileForEnvDir(store, tool, env);
|
|
2013
2133
|
if (envProfile)
|
|
2014
|
-
return fromProfile(envProfile, "env");
|
|
2015
|
-
|
|
2134
|
+
return envProfile.switchedAway ? null : fromProfile(envProfile, "env");
|
|
2135
|
+
if (store.registry === "subscriptions" && env[tool.envVar])
|
|
2136
|
+
return null;
|
|
2137
|
+
if (store.appliedProfile) {
|
|
2138
|
+
const applied = await store.appliedProfile(toolId);
|
|
2139
|
+
if (applied)
|
|
2140
|
+
return applied.switchedAway ? null : fromProfile(applied, "applied");
|
|
2141
|
+
}
|
|
2142
|
+
const appliedName = store.registry === "subscriptions" ? undefined : appliedProfileName(toolId);
|
|
2016
2143
|
if (appliedName) {
|
|
2017
2144
|
const applied = await store.findProfile(appliedName, toolId);
|
|
2018
2145
|
if (applied)
|
|
@@ -3579,7 +3706,7 @@ function backfillMachineId(db) {
|
|
|
3579
3706
|
return { requests: Number(reqCount.changes), sessions: Number(sessCount.changes) };
|
|
3580
3707
|
}
|
|
3581
3708
|
async function recalculateZeroCostRequests(db) {
|
|
3582
|
-
const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-
|
|
3709
|
+
const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-vxejj1qa.js");
|
|
3583
3710
|
const zeroRows = db.prepare(`SELECT id, model, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, cache_create_5m_tokens, cache_create_1h_tokens
|
|
3584
3711
|
FROM requests
|
|
3585
3712
|
WHERE cost_usd = 0
|
|
@@ -3788,7 +3915,7 @@ async function postIngest(cloud, body) {
|
|
|
3788
3915
|
}
|
|
3789
3916
|
async function syncAllToCloud(cloud, opts = {}) {
|
|
3790
3917
|
await runIngestJournalOperation(cloud, { action: "reconcile" });
|
|
3791
|
-
const { openDatabase } = await import("../chunks/sqlite-store-
|
|
3918
|
+
const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
|
|
3792
3919
|
const scratch = openDatabase(":memory:", true);
|
|
3793
3920
|
ensurePricingSeeded(scratch);
|
|
3794
3921
|
const cachePath = opts.cachePath ?? getIngestCachePath();
|
|
@@ -4714,7 +4841,8 @@ function buildBrief(db, opts = {}) {
|
|
|
4714
4841
|
}
|
|
4715
4842
|
|
|
4716
4843
|
// src/lib/analytics.ts
|
|
4717
|
-
function queryProjectDetail(db, nameOrPath) {
|
|
4844
|
+
function queryProjectDetail(db, nameOrPath, now2 = new Date) {
|
|
4845
|
+
const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now2);
|
|
4718
4846
|
const like = `%${nameOrPath}%`;
|
|
4719
4847
|
const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
|
|
4720
4848
|
FROM economy_effective_sessions
|
|
@@ -4728,8 +4856,8 @@ function queryProjectDetail(db, nameOrPath) {
|
|
|
4728
4856
|
const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
4729
4857
|
FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
|
|
4730
4858
|
WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
|
|
4731
|
-
AND r.timestamp >=
|
|
4732
|
-
GROUP BY date ORDER BY date ASC`).all(like, like);
|
|
4859
|
+
AND r.timestamp >= ? AND r.timestamp < ?
|
|
4860
|
+
GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
|
|
4733
4861
|
const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
4734
4862
|
FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
|
|
4735
4863
|
WHERE s.project_name LIKE ? OR s.project_path LIKE ?
|
|
@@ -4768,18 +4896,17 @@ function queryRangeStats(db, from, to) {
|
|
|
4768
4896
|
return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
|
|
4769
4897
|
}
|
|
4770
4898
|
function queryForecast(db, now2 = new Date) {
|
|
4771
|
-
const
|
|
4772
|
-
const
|
|
4773
|
-
const
|
|
4774
|
-
const
|
|
4899
|
+
const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now2);
|
|
4900
|
+
const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now2);
|
|
4901
|
+
const daysInMonth = new Date(Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth() + 1, 0)).getUTCDate();
|
|
4902
|
+
const dayOfMonth = now2.getUTCDate();
|
|
4903
|
+
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);
|
|
4775
4904
|
const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
|
|
4776
4905
|
const projected = dailyAvg * daysInMonth;
|
|
4777
|
-
const
|
|
4778
|
-
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
4779
|
-
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));
|
|
4906
|
+
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);
|
|
4780
4907
|
const last7DailyAvg = last7.cost / 7;
|
|
4781
4908
|
const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
|
|
4782
|
-
FROM economy_effective_requests WHERE
|
|
4909
|
+
FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
|
|
4783
4910
|
return {
|
|
4784
4911
|
day_of_month: dayOfMonth,
|
|
4785
4912
|
days_in_month: daysInMonth,
|
|
@@ -4830,7 +4957,7 @@ class LocalStore2 {
|
|
|
4830
4957
|
_db;
|
|
4831
4958
|
async db() {
|
|
4832
4959
|
if (!this._db) {
|
|
4833
|
-
const { openDatabase } = await import("../chunks/sqlite-store-
|
|
4960
|
+
const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
|
|
4834
4961
|
this._db = openDatabase();
|
|
4835
4962
|
ensurePricingSeeded(this._db);
|
|
4836
4963
|
}
|
|
@@ -5193,7 +5320,7 @@ function buildServer() {
|
|
|
5193
5320
|
let _db;
|
|
5194
5321
|
const localDb = async () => {
|
|
5195
5322
|
if (!_db) {
|
|
5196
|
-
const { openDatabase } = await import("../chunks/sqlite-store-
|
|
5323
|
+
const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
|
|
5197
5324
|
_db = openDatabase();
|
|
5198
5325
|
ensurePricingSeeded(_db);
|
|
5199
5326
|
}
|
|
@@ -5331,17 +5458,17 @@ function buildServer() {
|
|
|
5331
5458
|
function textError(message) {
|
|
5332
5459
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
5333
5460
|
}
|
|
5334
|
-
server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query:
|
|
5461
|
+
server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z7.string().optional() }, async ({ query }) => {
|
|
5335
5462
|
const q2 = query?.toLowerCase();
|
|
5336
5463
|
const matches = q2 ? TOOL_NAMES.filter((name) => name.includes(q2)) : [...TOOL_NAMES];
|
|
5337
5464
|
return text(matches.join(", "));
|
|
5338
5465
|
});
|
|
5339
|
-
server.tool("describe_tools", "Get param hints for specific tools by name.", { names:
|
|
5466
|
+
server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z7.array(z7.string()) }, async ({ names }) => {
|
|
5340
5467
|
const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
|
|
5341
5468
|
`);
|
|
5342
5469
|
return text(result);
|
|
5343
5470
|
});
|
|
5344
|
-
server.tool("get_cost_summary", "Stored usage-value summary with pricing coverage; never a paid-spend or complete-source claim. period: today|week|month|year|all. machine: filter by hostname.", { period:
|
|
5471
|
+
server.tool("get_cost_summary", "Stored usage-value summary with pricing coverage; never a paid-spend or complete-source claim. period: today|week|month|year|all. machine: filter by hostname.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), machine: z7.string().optional() }, async ({ period, machine }) => {
|
|
5345
5472
|
const resolved = period ?? "today";
|
|
5346
5473
|
const s = await store.summary(resolved, machine);
|
|
5347
5474
|
const machineLabel = machine ? ` on ${machine}` : "";
|
|
@@ -5357,11 +5484,11 @@ function buildServer() {
|
|
|
5357
5484
|
`));
|
|
5358
5485
|
});
|
|
5359
5486
|
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
|
|
5360
|
-
agent:
|
|
5361
|
-
project:
|
|
5362
|
-
account:
|
|
5363
|
-
machine:
|
|
5364
|
-
limit:
|
|
5487
|
+
agent: z7.enum(AGENTS).optional(),
|
|
5488
|
+
project: z7.string().optional(),
|
|
5489
|
+
account: z7.string().optional(),
|
|
5490
|
+
machine: z7.string().optional(),
|
|
5491
|
+
limit: z7.number().int().positive().max(100).optional()
|
|
5365
5492
|
}, async ({ agent, project, account, machine, limit }) => {
|
|
5366
5493
|
const sessions = await store.sessions({ agent, project, account, machine, limit: limit ?? 20 });
|
|
5367
5494
|
const lines = ["id agent cost tokens project"];
|
|
@@ -5371,8 +5498,8 @@ function buildServer() {
|
|
|
5371
5498
|
`));
|
|
5372
5499
|
});
|
|
5373
5500
|
server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
|
|
5374
|
-
n:
|
|
5375
|
-
agent:
|
|
5501
|
+
n: z7.number().int().positive().max(100).optional(),
|
|
5502
|
+
agent: z7.enum(AGENTS).optional()
|
|
5376
5503
|
}, async ({ n, agent }) => {
|
|
5377
5504
|
const sessions = await store.topSessions(n ?? 10, agent);
|
|
5378
5505
|
const lines = ["rank id agent cost tokens project"];
|
|
@@ -5380,7 +5507,7 @@ function buildServer() {
|
|
|
5380
5507
|
return text(lines.join(`
|
|
5381
5508
|
`));
|
|
5382
5509
|
});
|
|
5383
|
-
server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit:
|
|
5510
|
+
server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
|
|
5384
5511
|
const rows = await store.modelBreakdown();
|
|
5385
5512
|
if (json)
|
|
5386
5513
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5395,7 +5522,7 @@ function buildServer() {
|
|
|
5395
5522
|
return text(lines.join(`
|
|
5396
5523
|
`));
|
|
5397
5524
|
});
|
|
5398
|
-
server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period:
|
|
5525
|
+
server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ period, limit, verbose, json }) => {
|
|
5399
5526
|
const rows = await store.projectBreakdown({ period: period ?? "all" });
|
|
5400
5527
|
if (json)
|
|
5401
5528
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5411,7 +5538,7 @@ function buildServer() {
|
|
|
5411
5538
|
return text(lines.join(`
|
|
5412
5539
|
`));
|
|
5413
5540
|
});
|
|
5414
|
-
server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period:
|
|
5541
|
+
server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
5415
5542
|
const rows = await store.agentBreakdown({ period: period ?? "all" });
|
|
5416
5543
|
if (rows.length === 0)
|
|
5417
5544
|
return text("No agent usage yet.");
|
|
@@ -5422,7 +5549,7 @@ function buildServer() {
|
|
|
5422
5549
|
return text(lines.join(`
|
|
5423
5550
|
`));
|
|
5424
5551
|
});
|
|
5425
|
-
server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period:
|
|
5552
|
+
server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ period, limit, verbose, json }) => {
|
|
5426
5553
|
const rows = await store.accountBreakdown({ period: period ?? "all" });
|
|
5427
5554
|
if (json)
|
|
5428
5555
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5441,12 +5568,12 @@ function buildServer() {
|
|
|
5441
5568
|
`));
|
|
5442
5569
|
});
|
|
5443
5570
|
server.tool("get_cost_center_breakdown", "Cost per cost center. Params: period, kind, cursor(0), limit(20), verbose, json. json=true preserves the complete legacy array.", {
|
|
5444
|
-
period:
|
|
5445
|
-
kind:
|
|
5446
|
-
cursor:
|
|
5447
|
-
limit:
|
|
5448
|
-
verbose:
|
|
5449
|
-
json:
|
|
5571
|
+
period: z7.enum(["today", "week", "month", "year", "all"]).optional(),
|
|
5572
|
+
kind: z7.enum(["loop", "app", "repo", "service", "team"]).optional(),
|
|
5573
|
+
cursor: z7.number().int().nonnegative().optional(),
|
|
5574
|
+
limit: z7.number().int().positive().max(100).optional(),
|
|
5575
|
+
verbose: z7.boolean().optional(),
|
|
5576
|
+
json: z7.boolean().optional()
|
|
5450
5577
|
}, async ({ period, kind, cursor, limit, verbose, json }) => {
|
|
5451
5578
|
const rows = await store.costCenterBreakdown({ period: period ?? "all", kind });
|
|
5452
5579
|
if (json)
|
|
@@ -5469,7 +5596,7 @@ function buildServer() {
|
|
|
5469
5596
|
return text(lines.join(`
|
|
5470
5597
|
`));
|
|
5471
5598
|
});
|
|
5472
|
-
server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit:
|
|
5599
|
+
server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
|
|
5473
5600
|
const budgets = await store.listBudgets();
|
|
5474
5601
|
if (json)
|
|
5475
5602
|
return text(JSON.stringify(budgets, null, 2));
|
|
@@ -5490,12 +5617,12 @@ function buildServer() {
|
|
|
5490
5617
|
`));
|
|
5491
5618
|
});
|
|
5492
5619
|
server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
|
|
5493
|
-
period:
|
|
5494
|
-
limit_usd:
|
|
5495
|
-
project_path:
|
|
5496
|
-
agent:
|
|
5497
|
-
cost_center_id:
|
|
5498
|
-
alert_at_percent:
|
|
5620
|
+
period: z7.enum(["daily", "weekly", "monthly"]),
|
|
5621
|
+
limit_usd: z7.number().positive(),
|
|
5622
|
+
project_path: z7.string().optional(),
|
|
5623
|
+
agent: z7.enum(AGENTS).optional(),
|
|
5624
|
+
cost_center_id: z7.string().optional(),
|
|
5625
|
+
alert_at_percent: z7.number().positive().max(100).optional()
|
|
5499
5626
|
}, async ({ period, limit_usd, project_path, agent, cost_center_id, alert_at_percent }) => {
|
|
5500
5627
|
const id = await store.setBudget({
|
|
5501
5628
|
project_path: project_path ?? null,
|
|
@@ -5507,11 +5634,11 @@ function buildServer() {
|
|
|
5507
5634
|
});
|
|
5508
5635
|
return text(`Budget set: ${id}`);
|
|
5509
5636
|
});
|
|
5510
|
-
server.tool("remove_budget", "Delete a budget by id.", { id:
|
|
5637
|
+
server.tool("remove_budget", "Delete a budget by id.", { id: z7.string() }, async ({ id }) => {
|
|
5511
5638
|
await store.removeBudget(id);
|
|
5512
5639
|
return text("Budget removed.");
|
|
5513
5640
|
});
|
|
5514
|
-
server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit:
|
|
5641
|
+
server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
|
|
5515
5642
|
const rows = await store.listPricing();
|
|
5516
5643
|
if (json)
|
|
5517
5644
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5527,13 +5654,13 @@ function buildServer() {
|
|
|
5527
5654
|
`));
|
|
5528
5655
|
});
|
|
5529
5656
|
server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
|
|
5530
|
-
model:
|
|
5531
|
-
input_per_1m:
|
|
5532
|
-
output_per_1m:
|
|
5533
|
-
cache_read_per_1m:
|
|
5534
|
-
cache_write_per_1m:
|
|
5535
|
-
cache_write_1h_per_1m:
|
|
5536
|
-
cache_storage_per_1m_hour:
|
|
5657
|
+
model: z7.string().min(1),
|
|
5658
|
+
input_per_1m: z7.number().nonnegative(),
|
|
5659
|
+
output_per_1m: z7.number().nonnegative(),
|
|
5660
|
+
cache_read_per_1m: z7.number().nonnegative().optional(),
|
|
5661
|
+
cache_write_per_1m: z7.number().nonnegative().optional(),
|
|
5662
|
+
cache_write_1h_per_1m: z7.number().nonnegative().optional(),
|
|
5663
|
+
cache_storage_per_1m_hour: z7.number().nonnegative().optional()
|
|
5537
5664
|
}, async (input) => {
|
|
5538
5665
|
const model = input.model.trim();
|
|
5539
5666
|
if (!model)
|
|
@@ -5549,11 +5676,11 @@ function buildServer() {
|
|
|
5549
5676
|
});
|
|
5550
5677
|
return text(`Pricing set: ${model}`);
|
|
5551
5678
|
});
|
|
5552
|
-
server.tool("remove_pricing", "Delete a model pricing row by model id.", { model:
|
|
5679
|
+
server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z7.string() }, async ({ model }) => {
|
|
5553
5680
|
await store.removePricing(model);
|
|
5554
5681
|
return text("Pricing removed.");
|
|
5555
5682
|
});
|
|
5556
|
-
server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days:
|
|
5683
|
+
server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z7.number().int().positive().max(365).optional() }, async ({ days }) => {
|
|
5557
5684
|
const rows = await store.daily(days ?? 30);
|
|
5558
5685
|
const byDate = new Map;
|
|
5559
5686
|
for (const row of rows) {
|
|
@@ -5577,7 +5704,7 @@ function buildServer() {
|
|
|
5577
5704
|
return text(lines.join(`
|
|
5578
5705
|
`));
|
|
5579
5706
|
});
|
|
5580
|
-
server.tool("get_billing_summary", "Recorded provider billing-row subtotals; complete account/source coverage is unverified. Params: period(today|yesterday|week|month|year|all)", { period:
|
|
5707
|
+
server.tool("get_billing_summary", "Recorded provider billing-row subtotals; complete account/source coverage is unverified. Params: period(today|yesterday|week|month|year|all)", { period: z7.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
|
|
5581
5708
|
const summary = await store.billingSummary(period ?? "month");
|
|
5582
5709
|
const lines = ["provider recorded subtotal"];
|
|
5583
5710
|
for (const [provider, cost] of Object.entries(summary.by_provider)) {
|
|
@@ -5588,7 +5715,7 @@ function buildServer() {
|
|
|
5588
5715
|
return text(lines.join(`
|
|
5589
5716
|
`));
|
|
5590
5717
|
});
|
|
5591
|
-
server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id:
|
|
5718
|
+
server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id: z7.string(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional() }, async ({ session_id, limit, verbose }) => {
|
|
5592
5719
|
const detail = await store.sessionDetail(session_id);
|
|
5593
5720
|
if (!detail)
|
|
5594
5721
|
return textError(`Session not found: ${session_id}`);
|
|
@@ -5612,7 +5739,7 @@ function buildServer() {
|
|
|
5612
5739
|
return text(lines.join(`
|
|
5613
5740
|
`));
|
|
5614
5741
|
});
|
|
5615
|
-
server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")} ('loops' reads another app's on-box store and is refused for a hosted client). Set json=true for the full result object.`, { sources:
|
|
5742
|
+
server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")} ('loops' reads another app's on-box store and is refused for a hosted client). Set json=true for the full result object.`, { sources: z7.enum([...SYNC_SOURCES]).optional(), json: z7.boolean().optional() }, async ({ sources, json }) => {
|
|
5616
5743
|
const selected = sources ?? "all";
|
|
5617
5744
|
const opts = selected === "all" ? {} : { [selected]: true };
|
|
5618
5745
|
if (isCloudStore()) {
|
|
@@ -5669,7 +5796,7 @@ function buildServer() {
|
|
|
5669
5796
|
return { ...text(lines.join(`
|
|
5670
5797
|
`)), isError: failed };
|
|
5671
5798
|
});
|
|
5672
|
-
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period:
|
|
5799
|
+
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), agent: z7.enum(AGENTS).optional(), limit: z7.number().int().positive().max(100).optional(), json: z7.boolean().optional() }, async ({ period, agent, limit, json }) => {
|
|
5673
5800
|
const p = period ?? "month";
|
|
5674
5801
|
const { snapshots: snaps, summary } = await store.usage(p, agent);
|
|
5675
5802
|
if (json)
|
|
@@ -5692,7 +5819,7 @@ function buildServer() {
|
|
|
5692
5819
|
return text(lines.join(`
|
|
5693
5820
|
`));
|
|
5694
5821
|
});
|
|
5695
|
-
server.tool("get_savings", "Estimated subscription-included usage benefit versus mapped current plan fees; unavailable when evidence is missing. Paid API cost is not savings. Params: period, agent, limit(20), json.", { period:
|
|
5822
|
+
server.tool("get_savings", "Estimated subscription-included usage benefit versus mapped current plan fees; unavailable when evidence is missing. Paid API cost is not savings. Params: period, agent, limit(20), json.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), agent: z7.enum(AGENTS).optional(), limit: z7.number().int().positive().max(100).optional(), json: z7.boolean().optional() }, async ({ period, agent, limit, json }) => {
|
|
5696
5823
|
const savings = await store.savings(period ?? "month", agent);
|
|
5697
5824
|
if (json)
|
|
5698
5825
|
return text(JSON.stringify(savings, null, 2));
|
|
@@ -5733,17 +5860,17 @@ function buildServer() {
|
|
|
5733
5860
|
`));
|
|
5734
5861
|
});
|
|
5735
5862
|
server.tool("set_subscription", "Configure an account subscription plan. Explicit amounts are configured observations; omitted amounts remain unknown.", {
|
|
5736
|
-
id:
|
|
5737
|
-
provider:
|
|
5738
|
-
plan:
|
|
5739
|
-
account_key:
|
|
5740
|
-
agent:
|
|
5741
|
-
monthly_fee_usd:
|
|
5742
|
-
included_usage_usd:
|
|
5743
|
-
billing_cycle_start:
|
|
5744
|
-
reset_policy:
|
|
5745
|
-
active:
|
|
5746
|
-
json:
|
|
5863
|
+
id: z7.string().optional(),
|
|
5864
|
+
provider: z7.string().min(1),
|
|
5865
|
+
plan: z7.string().min(1),
|
|
5866
|
+
account_key: z7.string().min(1).max(256),
|
|
5867
|
+
agent: z7.enum(AGENTS).optional(),
|
|
5868
|
+
monthly_fee_usd: z7.number().nonnegative().optional(),
|
|
5869
|
+
included_usage_usd: z7.number().nonnegative().optional(),
|
|
5870
|
+
billing_cycle_start: z7.string().optional(),
|
|
5871
|
+
reset_policy: z7.string().optional(),
|
|
5872
|
+
active: z7.boolean().optional(),
|
|
5873
|
+
json: z7.boolean().optional()
|
|
5747
5874
|
}, async (input) => {
|
|
5748
5875
|
const row = await store.setSubscription({
|
|
5749
5876
|
id: input.id,
|
|
@@ -5767,15 +5894,15 @@ agent: ${row.agent ?? "unallocated"}
|
|
|
5767
5894
|
fee: ${money.fee}
|
|
5768
5895
|
included: ${money.included}`);
|
|
5769
5896
|
});
|
|
5770
|
-
server.tool("remove_subscription", "Delete a subscription plan by id.", { id:
|
|
5897
|
+
server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z7.string() }, async ({ id }) => {
|
|
5771
5898
|
await store.removeSubscription(id);
|
|
5772
5899
|
return text("Subscription removed.");
|
|
5773
5900
|
});
|
|
5774
|
-
server.tool("estimate_cost", "Pre-flight usage-value estimate for token counts; missing model pricing returns MODEL_PRICING_UNAVAILABLE, never a free estimate.", { model:
|
|
5901
|
+
server.tool("estimate_cost", "Pre-flight usage-value estimate for token counts; missing model pricing returns MODEL_PRICING_UNAVAILABLE, never a free estimate.", { model: z7.string().min(1), input_tokens: z7.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), output_tokens: z7.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional() }, async ({ model, input_tokens, output_tokens }) => {
|
|
5775
5902
|
const cost = await store.estimate({ model, inputTokens: input_tokens ?? 0, outputTokens: output_tokens ?? 0 });
|
|
5776
5903
|
return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
|
|
5777
5904
|
});
|
|
5778
|
-
server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit:
|
|
5905
|
+
server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
|
|
5779
5906
|
const goals = await store.listGoals();
|
|
5780
5907
|
if (json)
|
|
5781
5908
|
return text(JSON.stringify(goals, null, 2));
|
|
@@ -5796,10 +5923,10 @@ included: ${money.included}`);
|
|
|
5796
5923
|
`));
|
|
5797
5924
|
});
|
|
5798
5925
|
server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
|
|
5799
|
-
period:
|
|
5800
|
-
limit_usd:
|
|
5801
|
-
project_path:
|
|
5802
|
-
agent:
|
|
5926
|
+
period: z7.enum(["day", "week", "month", "year"]),
|
|
5927
|
+
limit_usd: z7.number().positive(),
|
|
5928
|
+
project_path: z7.string().optional(),
|
|
5929
|
+
agent: z7.string().optional()
|
|
5803
5930
|
}, async ({ period, limit_usd, project_path, agent }) => {
|
|
5804
5931
|
await store.setGoal({
|
|
5805
5932
|
period,
|
|
@@ -5809,11 +5936,11 @@ included: ${money.included}`);
|
|
|
5809
5936
|
});
|
|
5810
5937
|
return text(`Goal set: ${period} $${limit_usd}`);
|
|
5811
5938
|
});
|
|
5812
|
-
server.tool("remove_goal", "Delete a goal by id.", { id:
|
|
5939
|
+
server.tool("remove_goal", "Delete a goal by id.", { id: z7.string() }, async ({ id }) => {
|
|
5813
5940
|
await store.removeGoal(id);
|
|
5814
5941
|
return text("Goal removed.");
|
|
5815
5942
|
});
|
|
5816
|
-
server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit:
|
|
5943
|
+
server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
|
|
5817
5944
|
const machines = await store.machines();
|
|
5818
5945
|
if (json)
|
|
5819
5946
|
return text(JSON.stringify(machines, null, 2));
|
|
@@ -5835,9 +5962,9 @@ current machine: ${getMachineId()}`);
|
|
|
5835
5962
|
registerAgentTools(server, { service: "economy" });
|
|
5836
5963
|
registerIngestJournalTool(server);
|
|
5837
5964
|
server.tool("send_feedback", "Send feedback about this service.", {
|
|
5838
|
-
message:
|
|
5839
|
-
email:
|
|
5840
|
-
category:
|
|
5965
|
+
message: z7.string(),
|
|
5966
|
+
email: z7.string().optional(),
|
|
5967
|
+
category: z7.enum(["bug", "feature", "general"]).optional()
|
|
5841
5968
|
}, async ({ message, email, category }) => {
|
|
5842
5969
|
try {
|
|
5843
5970
|
await store.sendFeedback({ message, email, category });
|