Sign In

@budgetary/mcp

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@budgetary/mcp - npm Package Compare versions

Comparing version
0.9.0
to
0.9.1
dist/chunk-XRQX5KPH.js

Sorry, the diff of this file is too big to display

+136
import {
MAX_TRANSCRIPT_BYTES,
PendingStore,
capTrace,
entryBinding,
findProvenTranscript,
isTranscriptDir,
pendingFilePath,
persistedCounts,
readTranscriptUsage,
submitActuals
} from "./chunk-XRQX5KPH.js";
// src/reconcile.ts
import { readdirSync, readFileSync, statSync } from "fs";
import { BudgetaryClient } from "@budgetary/sdk";
var RECONCILE_MAX_RETRIES = 0;
function fileContains(path, needle) {
try {
const st = statSync(path);
if (!st.isFile() || st.size > MAX_TRANSCRIPT_BYTES) return false;
return readFileSync(path, "utf8").includes(needle);
} catch {
return false;
}
}
function listDir(dir) {
try {
return readdirSync(dir);
} catch {
return [];
}
}
function mtimeMs(path) {
try {
return statSync(path).mtimeMs;
} catch {
return null;
}
}
function fingerprint(path) {
try {
const st = statSync(path);
return `${st.size}:${st.mtimeMs}`;
} catch {
return null;
}
}
async function reconcileEntry(args) {
const logger = args.logger ?? { warn: () => {
} };
const now = (args.now ?? (() => /* @__PURE__ */ new Date()))();
const binding = entryBinding(args.entry);
if (binding === null) return "no-binding";
const store = new PendingStore({
path: pendingFilePath(args.home),
logger
});
const factory = args.clientFactory ?? ((opts) => new BudgetaryClient(opts));
const client = factory({
apiKey: args.apiKey,
baseUrl: args.baseUrl,
maxRetries: RECONCILE_MAX_RETRIES
});
const persisted = persistedCounts(args.entry);
if (persisted !== null) {
return finish(await trySubmit(persisted));
}
if (!isTranscriptDir(binding.transcriptDir, args.home)) return "no-transcript";
const path = findProvenTranscript(
binding,
{ listDir, contains: fileContains, mtimeMs },
Date.parse(args.entry.created_at)
);
if (path === null) return "no-transcript";
const before = fingerprint(path);
if (before === null) return "no-transcript";
const usage = (args.readUsage ?? readTranscriptUsage)(path, { target: false });
if (fingerprint(path) !== before) return "transcript-changed";
if (usage === null) return "no-usage";
const trace = capTrace(usage.trace) ?? void 0;
const counts = {
tokensIn: usage.tokensIn,
tokensOut: usage.tokensOut,
// We submit ONLY for a session whose serving process is gone and whose
// transcript is complete and stable — i.e. one that ran to termination,
// which is the same condition the hook path encodes as success (it counts
// `clear` / `logout` / `prompt_input_exit`, the normal endings). The hook's
// conservative default of `false` is not available to us: it keys off a
// termination reason only the host can report, and defaulting to it would
// stamp `false` on EVERY reconciled run — a systematic label, not a
// measurement. See the PR for the divergence this leaves.
success: true,
// Measured, not inferred from this process's clock. The hook can bound the
// run with a real session-end moment; we cannot, so the transcript's own
// last-write time is used as the end bound. Using `now` here would report
// the age of the ENTRY (up to a full day) as the run's duration.
durationMs: durationFromTranscript(path, args.entry, now),
...trace ? { trace } : {}
};
return finish(await trySubmit(counts));
async function trySubmit(measured) {
try {
const outcome = await submitActuals({
store,
client,
entry: args.entry,
counts: measured,
logger
});
return outcome.submitted;
} catch {
return false;
}
}
function finish(submitted) {
return submitted ? "submitted" : "not-submitted";
}
}
function durationFromTranscript(path, entry, now) {
const created = Date.parse(entry.created_at);
if (!Number.isFinite(created)) return 0;
let endMs;
try {
endMs = statSync(path).mtimeMs;
} catch {
return 0;
}
if (!Number.isFinite(endMs) || endMs < created || endMs > now.getTime() + 1e3) {
return 0;
}
return Math.max(0, Math.round(endMs - created));
}
export {
reconcileEntry
};
+1
-1

@@ -11,3 +11,3 @@ import {

submitActuals
} from "./chunk-FR4HH2QS.js";
} from "./chunk-XRQX5KPH.js";
export {

@@ -14,0 +14,0 @@ MAX_ATTEMPTS,

{
"name": "@budgetary/mcp",
"version": "0.9.0",
"version": "0.9.1",
"description": "Model Context Protocol server for Budgetary: a portable pre-flight token-spend estimate tool for any MCP-capable host.",

@@ -5,0 +5,0 @@ "mcpName": "io.github.thriftell/budgetary",

Sorry, the diff of this file is too big to display

import {
MAX_TRANSCRIPT_BYTES,
PendingStore,
capTrace,
entryBinding,
findProvenTranscript,
isTranscriptDir,
pendingFilePath,
persistedCounts,
readTranscriptUsage,
submitActuals
} from "./chunk-FR4HH2QS.js";
// src/reconcile.ts
import { readdirSync, readFileSync, statSync } from "fs";
import { BudgetaryClient } from "@budgetary/sdk";
var RECONCILE_MAX_RETRIES = 0;
function fileContains(path, needle) {
try {
const st = statSync(path);
if (!st.isFile() || st.size > MAX_TRANSCRIPT_BYTES) return false;
return readFileSync(path, "utf8").includes(needle);
} catch {
return false;
}
}
function listDir(dir) {
try {
return readdirSync(dir);
} catch {
return [];
}
}
function mtimeMs(path) {
try {
return statSync(path).mtimeMs;
} catch {
return null;
}
}
function fingerprint(path) {
try {
const st = statSync(path);
return `${st.size}:${st.mtimeMs}`;
} catch {
return null;
}
}
async function reconcileEntry(args) {
const logger = args.logger ?? { warn: () => {
} };
const now = (args.now ?? (() => /* @__PURE__ */ new Date()))();
const binding = entryBinding(args.entry);
if (binding === null) return "no-binding";
const store = new PendingStore({
path: pendingFilePath(args.home),
logger
});
const factory = args.clientFactory ?? ((opts) => new BudgetaryClient(opts));
const client = factory({
apiKey: args.apiKey,
baseUrl: args.baseUrl,
maxRetries: RECONCILE_MAX_RETRIES
});
const persisted = persistedCounts(args.entry);
if (persisted !== null) {
return finish(await trySubmit(persisted));
}
if (!isTranscriptDir(binding.transcriptDir, args.home)) return "no-transcript";
const path = findProvenTranscript(
binding,
{ listDir, contains: fileContains, mtimeMs },
Date.parse(args.entry.created_at)
);
if (path === null) return "no-transcript";
const before = fingerprint(path);
if (before === null) return "no-transcript";
const usage = (args.readUsage ?? readTranscriptUsage)(path, { target: false });
if (fingerprint(path) !== before) return "transcript-changed";
if (usage === null) return "no-usage";
const trace = capTrace(usage.trace) ?? void 0;
const counts = {
tokensIn: usage.tokensIn,
tokensOut: usage.tokensOut,
// We submit ONLY for a session whose serving process is gone and whose
// transcript is complete and stable — i.e. one that ran to termination,
// which is the same condition the hook path encodes as success (it counts
// `clear` / `logout` / `prompt_input_exit`, the normal endings). The hook's
// conservative default of `false` is not available to us: it keys off a
// termination reason only the host can report, and defaulting to it would
// stamp `false` on EVERY reconciled run — a systematic label, not a
// measurement. See the PR for the divergence this leaves.
success: true,
// Measured, not inferred from this process's clock. The hook can bound the
// run with a real session-end moment; we cannot, so the transcript's own
// last-write time is used as the end bound. Using `now` here would report
// the age of the ENTRY (up to a full day) as the run's duration.
durationMs: durationFromTranscript(path, args.entry, now),
...trace ? { trace } : {}
};
return finish(await trySubmit(counts));
async function trySubmit(measured) {
try {
const outcome = await submitActuals({
store,
client,
entry: args.entry,
counts: measured,
logger
});
return outcome.submitted;
} catch {
return false;
}
}
function finish(submitted) {
return submitted ? "submitted" : "not-submitted";
}
}
function durationFromTranscript(path, entry, now) {
const created = Date.parse(entry.created_at);
if (!Number.isFinite(created)) return 0;
let endMs;
try {
endMs = statSync(path).mtimeMs;
} catch {
return 0;
}
if (!Number.isFinite(endMs) || endMs < created || endMs > now.getTime() + 1e3) {
return 0;
}
return Math.max(0, Math.round(endMs - created));
}
export {
reconcileEntry
};

Sorry, the diff of this file is too big to display