@hasna/economy 0.5.3 → 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 +6 -0
- package/README.md +4 -2
- package/dist/chunks/{index-s9mvven6.js → index-9arv0b1j.js} +161 -35
- package/dist/chunks/{index-mhrkwvtk.js → index-ct3zdefz.js} +1 -1
- package/dist/chunks/{index-1vz1arz5.js → index-n1wwqxfe.js} +1 -1
- package/dist/chunks/{index-bn04v378.js → index-nheq2nrz.js} +1 -1
- package/dist/chunks/{serve-8yqe7nhs.js → serve-ra46ms69.js} +2 -2
- package/dist/chunks/{tui-fe94bvp9.js → tui-a1tvcw2j.js} +3 -3
- package/dist/chunks/{watch-ct8dvrpr.js → watch-rvd3dn6r.js} +3 -3
- package/dist/cli/index.js +10 -10
- package/dist/index.js +1 -1
- 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/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 +228 -102
- package/dist/otel/index.js +56 -27
- package/dist/server/index.js +169 -45
- package/dist/server/service-worker.js +160 -36
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -85,7 +85,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
85
85
|
// package.json
|
|
86
86
|
var package_default = {
|
|
87
87
|
name: "@hasna/economy",
|
|
88
|
-
version: "0.5.
|
|
88
|
+
version: "0.5.4",
|
|
89
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",
|
|
90
90
|
repository: {
|
|
91
91
|
type: "git",
|
|
@@ -1507,7 +1507,7 @@ function registerIngestJournalTool(server) {
|
|
|
1507
1507
|
}
|
|
1508
1508
|
|
|
1509
1509
|
// src/mcp/server.ts
|
|
1510
|
-
import { z as
|
|
1510
|
+
import { z as z7 } from "zod";
|
|
1511
1511
|
|
|
1512
1512
|
// src/ingest/collection-outcome.ts
|
|
1513
1513
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1563,34 +1563,141 @@ import { join as join6, basename } from "path";
|
|
|
1563
1563
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1564
1564
|
import { homedir } from "os";
|
|
1565
1565
|
import { join as join5 } from "path";
|
|
1566
|
-
import { z as
|
|
1566
|
+
import { z as z6 } from "zod";
|
|
1567
1567
|
import { resolveClientTransport, resolveCredential } from "@hasna/contracts/client";
|
|
1568
|
-
|
|
1569
|
-
|
|
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);
|
|
1570
1600
|
var profileSchema = z5.object({
|
|
1571
|
-
name:
|
|
1572
|
-
tool:
|
|
1601
|
+
name: nonempty,
|
|
1602
|
+
tool: nonempty,
|
|
1573
1603
|
email: z5.string().optional(),
|
|
1574
1604
|
displayName: z5.string().optional(),
|
|
1575
|
-
dir:
|
|
1605
|
+
dir: nonempty.refine(isAbsolute).refine((value) => !/[\0\r\n]/.test(value)),
|
|
1606
|
+
createdAt: nonempty,
|
|
1607
|
+
lastUsedAt: z5.string().optional(),
|
|
1576
1608
|
description: z5.string().optional(),
|
|
1577
|
-
|
|
1578
|
-
|
|
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()
|
|
1579
1686
|
});
|
|
1580
|
-
var toolDefSchema =
|
|
1581
|
-
id:
|
|
1582
|
-
label:
|
|
1583
|
-
envVar:
|
|
1584
|
-
defaultDir:
|
|
1585
|
-
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()
|
|
1586
1693
|
});
|
|
1587
|
-
var storeSchema =
|
|
1588
|
-
version:
|
|
1589
|
-
current:
|
|
1590
|
-
applied:
|
|
1591
|
-
toolLocks:
|
|
1592
|
-
profiles:
|
|
1593
|
-
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([])
|
|
1594
1701
|
});
|
|
1595
1702
|
function localRegistryAllowed(env = process.env) {
|
|
1596
1703
|
return LOCAL_STORAGE_OPT_IN_KEYS.some((key) => {
|
|
@@ -1872,25 +1979,34 @@ class ApiStore {
|
|
|
1872
1979
|
}
|
|
1873
1980
|
}
|
|
1874
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) {
|
|
1875
1991
|
const clientEnv = env;
|
|
1876
|
-
const credential = resolveCredential(
|
|
1992
|
+
const credential = resolveCredential(app, clientEnv, credentials);
|
|
1877
1993
|
if (credential === null) {
|
|
1878
1994
|
try {
|
|
1879
|
-
resolveClientTransport(
|
|
1995
|
+
resolveClientTransport(app, clientEnv, credentials ? { credentials } : {});
|
|
1880
1996
|
} catch (err) {
|
|
1881
1997
|
if (isNoAccountsCredentialConfigurationError(err))
|
|
1882
|
-
return
|
|
1998
|
+
return null;
|
|
1883
1999
|
throw err;
|
|
1884
2000
|
}
|
|
1885
|
-
return
|
|
2001
|
+
return null;
|
|
1886
2002
|
}
|
|
1887
2003
|
if (credential.tier === "pointer") {
|
|
1888
|
-
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.`);
|
|
1889
2005
|
}
|
|
1890
|
-
const resolution = resolveClientTransport(
|
|
1891
|
-
credentials: { ...
|
|
2006
|
+
const resolution = resolveClientTransport(app, clientEnv, {
|
|
2007
|
+
credentials: { ...credentials, apiKey: credential.apiKey }
|
|
1892
2008
|
});
|
|
1893
|
-
return
|
|
2009
|
+
return { baseUrl: resolution.baseUrl, apiKey: credential.apiKey };
|
|
1894
2010
|
}
|
|
1895
2011
|
function isNoAccountsCredentialConfigurationError(error) {
|
|
1896
2012
|
if (typeof error !== "object" || error === null)
|
|
@@ -1907,10 +2023,10 @@ function unconfiguredStore(env) {
|
|
|
1907
2023
|
return new NoRegistryStore;
|
|
1908
2024
|
}
|
|
1909
2025
|
function accountsLocalModeNotice() {
|
|
1910
|
-
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.";
|
|
1911
2027
|
}
|
|
1912
2028
|
function accountsRefusalNotice() {
|
|
1913
|
-
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.";
|
|
1914
2030
|
}
|
|
1915
2031
|
var accountsLocalNoticePrinted = false;
|
|
1916
2032
|
function announceAccountsLocalMode() {
|
|
@@ -1998,7 +2114,10 @@ async function profileForEnvDir(store, tool, env) {
|
|
|
1998
2114
|
if (!configuredDir)
|
|
1999
2115
|
return null;
|
|
2000
2116
|
const normalized = normalizeDir(configuredDir);
|
|
2001
|
-
|
|
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;
|
|
2002
2121
|
}
|
|
2003
2122
|
async function resolveAccountForAgent(agent, env = process.env, options = {}) {
|
|
2004
2123
|
const override = envOverride(agent, env);
|
|
@@ -2012,8 +2131,15 @@ async function resolveAccountForAgent(agent, env = process.env, options = {}) {
|
|
|
2012
2131
|
continue;
|
|
2013
2132
|
const envProfile = await profileForEnvDir(store, tool, env);
|
|
2014
2133
|
if (envProfile)
|
|
2015
|
-
return fromProfile(envProfile, "env");
|
|
2016
|
-
|
|
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);
|
|
2017
2143
|
if (appliedName) {
|
|
2018
2144
|
const applied = await store.findProfile(appliedName, toolId);
|
|
2019
2145
|
if (applied)
|
|
@@ -5332,17 +5458,17 @@ function buildServer() {
|
|
|
5332
5458
|
function textError(message) {
|
|
5333
5459
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
5334
5460
|
}
|
|
5335
|
-
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 }) => {
|
|
5336
5462
|
const q2 = query?.toLowerCase();
|
|
5337
5463
|
const matches = q2 ? TOOL_NAMES.filter((name) => name.includes(q2)) : [...TOOL_NAMES];
|
|
5338
5464
|
return text(matches.join(", "));
|
|
5339
5465
|
});
|
|
5340
|
-
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 }) => {
|
|
5341
5467
|
const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
|
|
5342
5468
|
`);
|
|
5343
5469
|
return text(result);
|
|
5344
5470
|
});
|
|
5345
|
-
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 }) => {
|
|
5346
5472
|
const resolved = period ?? "today";
|
|
5347
5473
|
const s = await store.summary(resolved, machine);
|
|
5348
5474
|
const machineLabel = machine ? ` on ${machine}` : "";
|
|
@@ -5358,11 +5484,11 @@ function buildServer() {
|
|
|
5358
5484
|
`));
|
|
5359
5485
|
});
|
|
5360
5486
|
server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
|
|
5361
|
-
agent:
|
|
5362
|
-
project:
|
|
5363
|
-
account:
|
|
5364
|
-
machine:
|
|
5365
|
-
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()
|
|
5366
5492
|
}, async ({ agent, project, account, machine, limit }) => {
|
|
5367
5493
|
const sessions = await store.sessions({ agent, project, account, machine, limit: limit ?? 20 });
|
|
5368
5494
|
const lines = ["id agent cost tokens project"];
|
|
@@ -5372,8 +5498,8 @@ function buildServer() {
|
|
|
5372
5498
|
`));
|
|
5373
5499
|
});
|
|
5374
5500
|
server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
|
|
5375
|
-
n:
|
|
5376
|
-
agent:
|
|
5501
|
+
n: z7.number().int().positive().max(100).optional(),
|
|
5502
|
+
agent: z7.enum(AGENTS).optional()
|
|
5377
5503
|
}, async ({ n, agent }) => {
|
|
5378
5504
|
const sessions = await store.topSessions(n ?? 10, agent);
|
|
5379
5505
|
const lines = ["rank id agent cost tokens project"];
|
|
@@ -5381,7 +5507,7 @@ function buildServer() {
|
|
|
5381
5507
|
return text(lines.join(`
|
|
5382
5508
|
`));
|
|
5383
5509
|
});
|
|
5384
|
-
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 }) => {
|
|
5385
5511
|
const rows = await store.modelBreakdown();
|
|
5386
5512
|
if (json)
|
|
5387
5513
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5396,7 +5522,7 @@ function buildServer() {
|
|
|
5396
5522
|
return text(lines.join(`
|
|
5397
5523
|
`));
|
|
5398
5524
|
});
|
|
5399
|
-
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 }) => {
|
|
5400
5526
|
const rows = await store.projectBreakdown({ period: period ?? "all" });
|
|
5401
5527
|
if (json)
|
|
5402
5528
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5412,7 +5538,7 @@ function buildServer() {
|
|
|
5412
5538
|
return text(lines.join(`
|
|
5413
5539
|
`));
|
|
5414
5540
|
});
|
|
5415
|
-
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 }) => {
|
|
5416
5542
|
const rows = await store.agentBreakdown({ period: period ?? "all" });
|
|
5417
5543
|
if (rows.length === 0)
|
|
5418
5544
|
return text("No agent usage yet.");
|
|
@@ -5423,7 +5549,7 @@ function buildServer() {
|
|
|
5423
5549
|
return text(lines.join(`
|
|
5424
5550
|
`));
|
|
5425
5551
|
});
|
|
5426
|
-
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 }) => {
|
|
5427
5553
|
const rows = await store.accountBreakdown({ period: period ?? "all" });
|
|
5428
5554
|
if (json)
|
|
5429
5555
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5442,12 +5568,12 @@ function buildServer() {
|
|
|
5442
5568
|
`));
|
|
5443
5569
|
});
|
|
5444
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.", {
|
|
5445
|
-
period:
|
|
5446
|
-
kind:
|
|
5447
|
-
cursor:
|
|
5448
|
-
limit:
|
|
5449
|
-
verbose:
|
|
5450
|
-
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()
|
|
5451
5577
|
}, async ({ period, kind, cursor, limit, verbose, json }) => {
|
|
5452
5578
|
const rows = await store.costCenterBreakdown({ period: period ?? "all", kind });
|
|
5453
5579
|
if (json)
|
|
@@ -5470,7 +5596,7 @@ function buildServer() {
|
|
|
5470
5596
|
return text(lines.join(`
|
|
5471
5597
|
`));
|
|
5472
5598
|
});
|
|
5473
|
-
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 }) => {
|
|
5474
5600
|
const budgets = await store.listBudgets();
|
|
5475
5601
|
if (json)
|
|
5476
5602
|
return text(JSON.stringify(budgets, null, 2));
|
|
@@ -5491,12 +5617,12 @@ function buildServer() {
|
|
|
5491
5617
|
`));
|
|
5492
5618
|
});
|
|
5493
5619
|
server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
|
|
5494
|
-
period:
|
|
5495
|
-
limit_usd:
|
|
5496
|
-
project_path:
|
|
5497
|
-
agent:
|
|
5498
|
-
cost_center_id:
|
|
5499
|
-
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()
|
|
5500
5626
|
}, async ({ period, limit_usd, project_path, agent, cost_center_id, alert_at_percent }) => {
|
|
5501
5627
|
const id = await store.setBudget({
|
|
5502
5628
|
project_path: project_path ?? null,
|
|
@@ -5508,11 +5634,11 @@ function buildServer() {
|
|
|
5508
5634
|
});
|
|
5509
5635
|
return text(`Budget set: ${id}`);
|
|
5510
5636
|
});
|
|
5511
|
-
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 }) => {
|
|
5512
5638
|
await store.removeBudget(id);
|
|
5513
5639
|
return text("Budget removed.");
|
|
5514
5640
|
});
|
|
5515
|
-
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 }) => {
|
|
5516
5642
|
const rows = await store.listPricing();
|
|
5517
5643
|
if (json)
|
|
5518
5644
|
return text(JSON.stringify(rows, null, 2));
|
|
@@ -5528,13 +5654,13 @@ function buildServer() {
|
|
|
5528
5654
|
`));
|
|
5529
5655
|
});
|
|
5530
5656
|
server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
|
|
5531
|
-
model:
|
|
5532
|
-
input_per_1m:
|
|
5533
|
-
output_per_1m:
|
|
5534
|
-
cache_read_per_1m:
|
|
5535
|
-
cache_write_per_1m:
|
|
5536
|
-
cache_write_1h_per_1m:
|
|
5537
|
-
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()
|
|
5538
5664
|
}, async (input) => {
|
|
5539
5665
|
const model = input.model.trim();
|
|
5540
5666
|
if (!model)
|
|
@@ -5550,11 +5676,11 @@ function buildServer() {
|
|
|
5550
5676
|
});
|
|
5551
5677
|
return text(`Pricing set: ${model}`);
|
|
5552
5678
|
});
|
|
5553
|
-
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 }) => {
|
|
5554
5680
|
await store.removePricing(model);
|
|
5555
5681
|
return text("Pricing removed.");
|
|
5556
5682
|
});
|
|
5557
|
-
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 }) => {
|
|
5558
5684
|
const rows = await store.daily(days ?? 30);
|
|
5559
5685
|
const byDate = new Map;
|
|
5560
5686
|
for (const row of rows) {
|
|
@@ -5578,7 +5704,7 @@ function buildServer() {
|
|
|
5578
5704
|
return text(lines.join(`
|
|
5579
5705
|
`));
|
|
5580
5706
|
});
|
|
5581
|
-
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 }) => {
|
|
5582
5708
|
const summary = await store.billingSummary(period ?? "month");
|
|
5583
5709
|
const lines = ["provider recorded subtotal"];
|
|
5584
5710
|
for (const [provider, cost] of Object.entries(summary.by_provider)) {
|
|
@@ -5589,7 +5715,7 @@ function buildServer() {
|
|
|
5589
5715
|
return text(lines.join(`
|
|
5590
5716
|
`));
|
|
5591
5717
|
});
|
|
5592
|
-
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 }) => {
|
|
5593
5719
|
const detail = await store.sessionDetail(session_id);
|
|
5594
5720
|
if (!detail)
|
|
5595
5721
|
return textError(`Session not found: ${session_id}`);
|
|
@@ -5613,7 +5739,7 @@ function buildServer() {
|
|
|
5613
5739
|
return text(lines.join(`
|
|
5614
5740
|
`));
|
|
5615
5741
|
});
|
|
5616
|
-
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 }) => {
|
|
5617
5743
|
const selected = sources ?? "all";
|
|
5618
5744
|
const opts = selected === "all" ? {} : { [selected]: true };
|
|
5619
5745
|
if (isCloudStore()) {
|
|
@@ -5670,7 +5796,7 @@ function buildServer() {
|
|
|
5670
5796
|
return { ...text(lines.join(`
|
|
5671
5797
|
`)), isError: failed };
|
|
5672
5798
|
});
|
|
5673
|
-
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 }) => {
|
|
5674
5800
|
const p = period ?? "month";
|
|
5675
5801
|
const { snapshots: snaps, summary } = await store.usage(p, agent);
|
|
5676
5802
|
if (json)
|
|
@@ -5693,7 +5819,7 @@ function buildServer() {
|
|
|
5693
5819
|
return text(lines.join(`
|
|
5694
5820
|
`));
|
|
5695
5821
|
});
|
|
5696
|
-
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 }) => {
|
|
5697
5823
|
const savings = await store.savings(period ?? "month", agent);
|
|
5698
5824
|
if (json)
|
|
5699
5825
|
return text(JSON.stringify(savings, null, 2));
|
|
@@ -5734,17 +5860,17 @@ function buildServer() {
|
|
|
5734
5860
|
`));
|
|
5735
5861
|
});
|
|
5736
5862
|
server.tool("set_subscription", "Configure an account subscription plan. Explicit amounts are configured observations; omitted amounts remain unknown.", {
|
|
5737
|
-
id:
|
|
5738
|
-
provider:
|
|
5739
|
-
plan:
|
|
5740
|
-
account_key:
|
|
5741
|
-
agent:
|
|
5742
|
-
monthly_fee_usd:
|
|
5743
|
-
included_usage_usd:
|
|
5744
|
-
billing_cycle_start:
|
|
5745
|
-
reset_policy:
|
|
5746
|
-
active:
|
|
5747
|
-
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()
|
|
5748
5874
|
}, async (input) => {
|
|
5749
5875
|
const row = await store.setSubscription({
|
|
5750
5876
|
id: input.id,
|
|
@@ -5768,15 +5894,15 @@ agent: ${row.agent ?? "unallocated"}
|
|
|
5768
5894
|
fee: ${money.fee}
|
|
5769
5895
|
included: ${money.included}`);
|
|
5770
5896
|
});
|
|
5771
|
-
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 }) => {
|
|
5772
5898
|
await store.removeSubscription(id);
|
|
5773
5899
|
return text("Subscription removed.");
|
|
5774
5900
|
});
|
|
5775
|
-
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 }) => {
|
|
5776
5902
|
const cost = await store.estimate({ model, inputTokens: input_tokens ?? 0, outputTokens: output_tokens ?? 0 });
|
|
5777
5903
|
return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
|
|
5778
5904
|
});
|
|
5779
|
-
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 }) => {
|
|
5780
5906
|
const goals = await store.listGoals();
|
|
5781
5907
|
if (json)
|
|
5782
5908
|
return text(JSON.stringify(goals, null, 2));
|
|
@@ -5797,10 +5923,10 @@ included: ${money.included}`);
|
|
|
5797
5923
|
`));
|
|
5798
5924
|
});
|
|
5799
5925
|
server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
|
|
5800
|
-
period:
|
|
5801
|
-
limit_usd:
|
|
5802
|
-
project_path:
|
|
5803
|
-
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()
|
|
5804
5930
|
}, async ({ period, limit_usd, project_path, agent }) => {
|
|
5805
5931
|
await store.setGoal({
|
|
5806
5932
|
period,
|
|
@@ -5810,11 +5936,11 @@ included: ${money.included}`);
|
|
|
5810
5936
|
});
|
|
5811
5937
|
return text(`Goal set: ${period} $${limit_usd}`);
|
|
5812
5938
|
});
|
|
5813
|
-
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 }) => {
|
|
5814
5940
|
await store.removeGoal(id);
|
|
5815
5941
|
return text("Goal removed.");
|
|
5816
5942
|
});
|
|
5817
|
-
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 }) => {
|
|
5818
5944
|
const machines = await store.machines();
|
|
5819
5945
|
if (json)
|
|
5820
5946
|
return text(JSON.stringify(machines, null, 2));
|
|
@@ -5836,9 +5962,9 @@ current machine: ${getMachineId()}`);
|
|
|
5836
5962
|
registerAgentTools(server, { service: "economy" });
|
|
5837
5963
|
registerIngestJournalTool(server);
|
|
5838
5964
|
server.tool("send_feedback", "Send feedback about this service.", {
|
|
5839
|
-
message:
|
|
5840
|
-
email:
|
|
5841
|
-
category:
|
|
5965
|
+
message: z7.string(),
|
|
5966
|
+
email: z7.string().optional(),
|
|
5967
|
+
category: z7.enum(["bug", "feature", "general"]).optional()
|
|
5842
5968
|
}, async ({ message, email, category }) => {
|
|
5843
5969
|
try {
|
|
5844
5970
|
await store.sendFeedback({ message, email, category });
|