@hydra-acp/budgeter 0.1.7 → 0.1.8

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.
@@ -1 +1 @@
1
- import{relative as G,resolve as w}from"node:path";import{homedir as W}from"node:os";import{realpathSync as E}from"node:fs";function P(c){const d=c.match(/^(\d+)\s*([dhmwy])$/i);if(d!==null){const a=d[1],h=d[2];if(a===void 0||h===void 0)throw new Error(`invalid since spec: ${c}`);const k=parseInt(a,10),r=h.toLowerCase(),u=new Date;return r==="d"?u.setDate(u.getDate()-k):r==="h"?u.setHours(u.getHours()-k):r==="w"?u.setDate(u.getDate()-k*7):r==="m"?u.setMonth(u.getMonth()-k):r==="y"&&u.setFullYear(u.getFullYear()-k),u}const n=new Date(c);if(Number.isNaN(n.getTime()))throw new Error(`invalid since spec: ${c}`);return n}const v=new Map;function C(c){if(v.has(c))return v.get(c);try{const d=E(c);return v.set(c,d),d}catch{return v.set(c,c),c}}function F(c,d){let n=c;if(d.since!==void 0){const r=d.since.getTime(),u=[];for(const f of n)new Date(f.updatedAt).getTime()<r||u.push(f);n=u}if(d.dir!==void 0){const r=C(w(d.dir)),u=[];for(const f of n)f.cwd===void 0||f.cwd===""||(f.cwd.startsWith(r+"/")||f.cwd===r)&&u.push(f);n=u}if(d.interactive!==void 0){const r=d.interactive,u=[];for(const f of n)f.interactive===r&&u.push(f);n=u}const a=d.min??0,h=d.minMetric==="tokens",k=[];for(const r of n)(h?r.contextTokens:r.costAmount)>a&&k.push(r);return k}function O(c){return c.dir!==void 0?w(c.dir):w(W())}function L(c,d,n){if(c===void 0||c==="")return"<unknown>";const a=C(w(c));if(d===void 0){const p=C(w(W()));return a===p?"~":a.startsWith(p+"/")?"~/"+a.slice(p.length+1):a}const h=C(w(n));let k=G(h,a);if(k===""||k===".")return"<root>";if(k.startsWith(".."))return"<unknown>";const r=k.split("/");if(r.length===0)return"<root>";const u=Math.min(d,r.length),f=r.slice(0,u);return f.length===0?"<root>":f.join("/")}function Y(c,d,n={}){let a=n.since;if(a===void 0&&n.bucket!==void 0){const e=new Date;n.bucket==="hour"?(e.setHours(e.getHours()-24),a=e):n.bucket==="day"?(e.setDate(e.getDate()-30),a=e):n.bucket==="week"?(e.setMonth(e.getMonth()-6),a=e):n.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),a=e)}if(!(a!==void 0||n.bucket!==void 0||n.tokens===!0||n.by!==void 0||n.dir!==void 0||n.interactive!==void 0)){let e=0;for(const i of c)e+=i.costAmount;let s="";for(const i of c){s=i.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:c.length},currency:s}}const k={since:a,dir:n.dir,interactive:n.interactive,min:n.min,minMetric:n.tokens===!0?"tokens":"cost"},r=F(c,k),u=new Map;if(d!==void 0)for(const e of d){const s=u.get(e.sessionId);s===void 0?u.set(e.sessionId,[e]):s.push(e)}let f="";if(d!==void 0&&d.length>0)for(const e of d){f=e.currency;break}else if(r.length>0)for(const e of r){f=e.costCurrency;break}const p=e=>n.by==="dir"?L(e.cwd,n.depth,O(n)):n.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:n.by==="model"?e.model===""?"<unknown>":e.model:n.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",R=e=>{const s=new Date(e);if(Number.isNaN(s.getTime()))return"<invalid>";if(n.bucket==="hour"){const i=s.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),o=String(s.getHours()).padStart(2,"0");return`${i} ${o}:00`}if(n.bucket==="day")return s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(n.bucket==="week"){const i=new Date(s),o=i.getDay(),t=i.getDate()-o+(o===0?-6:1);return i.setDate(t),i.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return n.bucket==="month"?s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})};if(n.by===void 0&&n.bucket===void 0){let e=0,s=0,i=0,o=0,t=0,l=0;for(const b of r){e+=b.costAmount;const T=u.get(b.sessionId);if(T!==void 0)for(const m of T)s+=m.deltaCost,n.tokens===!0&&(m.inputTokens!==void 0&&(i+=m.inputTokens),m.outputTokens!==void 0&&(o+=m.outputTokens),m.cacheReadTokens!==void 0&&(t+=m.cacheReadTokens),m.cacheWriteTokens!==void 0&&(l+=m.cacheWriteTokens))}const g={label:"All sessions",costAmount:e,deltaCost:s,sessionCount:r.length};return n.tokens===!0&&(g.inputTokens=i,g.outputTokens=o,g.cacheReadTokens=t,g.cacheWriteTokens=l),{kind:"total",row:g,currency:f}}if(n.by!==void 0&&n.bucket===void 0){const e=new Map;for(const i of r){const o=p(i);let t=e.get(o);t===void 0&&(t={label:o,rows:{label:o,costAmount:0,deltaCost:0,sessionCount:0}},e.set(o,t)),t.rows.costAmount+=i.costAmount,t.rows.sessionCount=(t.rows.sessionCount??0)+1,n.tokens===!0&&(t.rows.inputTokens===void 0&&(t.rows.inputTokens=0),t.rows.inputTokens+=i.contextTokens);const l=u.get(i.sessionId);if(l!==void 0)for(const g of l)t.rows.deltaCost+=g.deltaCost,n.tokens===!0&&(t.rows.inputTokens===void 0&&(t.rows.inputTokens=0),t.rows.outputTokens===void 0&&(t.rows.outputTokens=0),t.rows.cacheReadTokens===void 0&&(t.rows.cacheReadTokens=0),t.rows.cacheWriteTokens===void 0&&(t.rows.cacheWriteTokens=0),g.inputTokens!==void 0&&(t.rows.inputTokens+=g.inputTokens),g.outputTokens!==void 0&&(t.rows.outputTokens+=g.outputTokens),g.cacheReadTokens!==void 0&&(t.rows.cacheReadTokens+=g.cacheReadTokens),g.cacheWriteTokens!==void 0&&(t.rows.cacheWriteTokens+=g.cacheWriteTokens))}const s=[];for(const i of e.values())s.push({label:i.label,items:[i.rows]});return{kind:"grouped",groups:s,currency:f}}const _=(e,s)=>{n.tokens===!0&&(e.inputTokens===void 0&&(e.inputTokens=0),e.inputTokens+=s.contextTokens)},M=new Map;for(const e of r)M.set(e.sessionId,e);const y=new Map;if(d!==void 0){for(const e of d){if(!M.has(e.sessionId))continue;const s=y.get(e.sessionId);s===void 0?y.set(e.sessionId,[e]):s.push(e)}for(const e of y.values())e.sort((s,i)=>s.ts.localeCompare(i.ts))}const D=(e,s)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(s),e.sessionCount=e._sessions.size},B=e=>{delete e._sessions},I=(e,s,i)=>{const o=R(s.ts);let t=e.get(o);t===void 0&&(t={bucket:o,costAmount:0,deltaCost:0,sessionCount:0},e.set(o,t)),t.costAmount+=i,t.deltaCost+=i,D(t,s.sessionId),n.tokens===!0&&(t.inputTokens===void 0||(s.inputTokens??0)>t.inputTokens)&&(t.inputTokens=s.inputTokens??t.inputTokens??0)},x=(e,s)=>{const i=R(s.updatedAt);let o=e.get(i);o===void 0&&(o={bucket:i,costAmount:0,deltaCost:0,sessionCount:0},e.set(i,o)),o.costAmount+=s.costAmount,o.deltaCost+=s.costAmount,D(o,s.sessionId),_(o,s)};if(n.by!==void 0&&n.bucket!==void 0){const e=new Map,s=o=>{const t=p(o);let l=e.get(t);return l===void 0&&(l={label:t,buckets:new Map},e.set(t,l)),l.buckets};for(const o of r){const t=s(o),l=y.get(o.sessionId);if(l!==void 0&&l.length>0){let g=l[0].cumulativeCost;for(let b=1;b<l.length;b++){const T=l[b],m=Math.max(0,T.cumulativeCost-g);g=T.cumulativeCost,!(a!==void 0&&new Date(T.ts)<a)&&I(t,T,m)}}else x(t,o)}const i=[];for(const o of e.values()){const t=Array.from(o.buckets.values());for(const l of t)B(l);t.sort((l,g)=>l.bucket.localeCompare(g.bucket)),t.length>0&&i.push({label:o.label,items:t})}return{kind:"timeSeriesGrouped",groups:i,currency:f}}const S=new Map;for(const e of r){const s=y.get(e.sessionId);if(s!==void 0&&s.length>0){let i=s[0].cumulativeCost;for(let o=1;o<s.length;o++){const t=s[o],l=Math.max(0,t.cumulativeCost-i);i=t.cumulativeCost,!(a!==void 0&&new Date(t.ts)<a)&&I(S,t,l)}}else x(S,e)}const A=Array.from(S.values());for(const e of A)B(e);return A.sort((e,s)=>e.bucket.localeCompare(s.bucket)),{kind:"timeSeries",timeSeries:A,currency:f}}export{Y as aggregate,F as applyFilters,P as parseSince};
1
+ import{relative as W,resolve as w}from"node:path";import{homedir as B}from"node:os";import{realpathSync as _}from"node:fs";function H(a){const u=a.match(/^(\d+)\s*([dhmwy])$/i);if(u!==null){const f=u[1],h=u[2];if(f===void 0||h===void 0)throw new Error(`invalid since spec: ${a}`);const k=parseInt(f,10),o=h.toLowerCase(),i=new Date;return o==="d"?i.setDate(i.getDate()-k):o==="h"?i.setHours(i.getHours()-k):o==="w"?i.setDate(i.getDate()-k*7):o==="m"?i.setMonth(i.getMonth()-k):o==="y"&&i.setFullYear(i.getFullYear()-k),i}const n=new Date(a);if(Number.isNaN(n.getTime()))throw new Error(`invalid since spec: ${a}`);return n}const S=new Map;function v(a){if(S.has(a))return S.get(a);try{const u=_(a);return S.set(a,u),u}catch{return S.set(a,a),a}}function F(a,u){let n=a;if(u.since!==void 0){const o=u.since.getTime(),i=[];for(const c of n)new Date(c.updatedAt).getTime()<o||i.push(c);n=i}if(u.dir!==void 0){const o=v(w(u.dir)),i=[];for(const c of n)c.cwd===void 0||c.cwd===""||(c.cwd.startsWith(o+"/")||c.cwd===o)&&i.push(c);n=i}if(u.host!==void 0&&u.host!=="all"){const o=u.host,i=[];for(const c of n)o==="local"?(!c.importedFromMachine||c.upstreamSessionId)&&i.push(c):c.importedFromMachine===o&&!c.upstreamSessionId&&i.push(c);n=i}if(u.interactive!==void 0){const o=u.interactive,i=[];for(const c of n)c.interactive===o&&i.push(c);n=i}const f=u.min??0,h=u.minMetric==="tokens",k=[];for(const o of n)(h?o.contextTokens:o.costAmount)>f&&k.push(o);return k}function G(a){return a.dir!==void 0?w(a.dir):w(B())}function E(a,u,n){if(a===void 0||a==="")return"<unknown>";const f=v(w(a));if(u===void 0){const p=v(w(B()));return f===p?"~":f.startsWith(p+"/")?"~/"+f.slice(p.length+1):f}const h=v(w(n));let k=W(h,f);if(k===""||k===".")return"<root>";if(k.startsWith(".."))return"<unknown>";const o=k.split("/");if(o.length===0)return"<root>";const i=Math.min(u,o.length),c=o.slice(0,i);return c.length===0?"<root>":c.join("/")}function $(a,u,n={}){let f=n.since;if(f===void 0&&n.bucket!==void 0){const e=new Date;n.bucket==="hour"?(e.setHours(e.getHours()-24),f=e):n.bucket==="day"?(e.setDate(e.getDate()-30),f=e):n.bucket==="week"?(e.setMonth(e.getMonth()-6),f=e):n.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),f=e)}if(!(f!==void 0||n.bucket!==void 0||n.tokens===!0||n.by!==void 0||n.dir!==void 0||n.interactive!==void 0)){let e=0;for(const r of a)e+=r.costAmount;let s="";for(const r of a){s=r.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:a.length},currency:s}}const k={since:f,dir:n.dir,interactive:n.interactive,min:n.min,minMetric:n.tokens===!0?"tokens":"cost"},o=F(a,k),i=new Map;if(u!==void 0)for(const e of u){const s=i.get(e.sessionId);s===void 0?i.set(e.sessionId,[e]):s.push(e)}let c="";if(u!==void 0&&u.length>0)for(const e of u){c=e.currency;break}else if(o.length>0)for(const e of o){c=e.costCurrency;break}const p=e=>n.by==="dir"?E(e.cwd,n.depth,G(n)):n.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:n.by==="model"?e.model===""?"<unknown>":e.model:n.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",I=e=>{const s=new Date(e);if(Number.isNaN(s.getTime()))return"<invalid>";if(n.bucket==="hour"){const r=s.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),d=String(s.getHours()).padStart(2,"0");return`${r} ${d}:00`}if(n.bucket==="day")return s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(n.bucket==="week"){const r=new Date(s),d=r.getDay(),t=r.getDate()-d+(d===0?-6:1);return r.setDate(t),r.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return n.bucket==="month"?s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})};if(n.by===void 0&&n.bucket===void 0){let e=0,s=0,r=0,d=0,t=0,g=0;for(const b of o){e+=b.costAmount;const T=i.get(b.sessionId);if(T!==void 0)for(const m of T)s+=m.deltaCost,n.tokens===!0&&(m.inputTokens!==void 0&&(r+=m.inputTokens),m.outputTokens!==void 0&&(d+=m.outputTokens),m.cacheReadTokens!==void 0&&(t+=m.cacheReadTokens),m.cacheWriteTokens!==void 0&&(g+=m.cacheWriteTokens))}const l={label:"All sessions",costAmount:e,deltaCost:s,sessionCount:o.length};return n.tokens===!0&&(l.inputTokens=r,l.outputTokens=d,l.cacheReadTokens=t,l.cacheWriteTokens=g),{kind:"total",row:l,currency:c}}if(n.by!==void 0&&n.bucket===void 0){const e=new Map;for(const r of o){const d=p(r);let t=e.get(d);t===void 0&&(t={label:d,rows:{label:d,costAmount:0,deltaCost:0,sessionCount:0}},e.set(d,t)),t.rows.costAmount+=r.costAmount,t.rows.sessionCount=(t.rows.sessionCount??0)+1,n.tokens===!0&&(t.rows.inputTokens===void 0&&(t.rows.inputTokens=0),t.rows.inputTokens+=r.contextTokens);const g=i.get(r.sessionId);if(g!==void 0)for(const l of g)t.rows.deltaCost+=l.deltaCost,n.tokens===!0&&(t.rows.inputTokens===void 0&&(t.rows.inputTokens=0),t.rows.outputTokens===void 0&&(t.rows.outputTokens=0),t.rows.cacheReadTokens===void 0&&(t.rows.cacheReadTokens=0),t.rows.cacheWriteTokens===void 0&&(t.rows.cacheWriteTokens=0),l.inputTokens!==void 0&&(t.rows.inputTokens+=l.inputTokens),l.outputTokens!==void 0&&(t.rows.outputTokens+=l.outputTokens),l.cacheReadTokens!==void 0&&(t.rows.cacheReadTokens+=l.cacheReadTokens),l.cacheWriteTokens!==void 0&&(t.rows.cacheWriteTokens+=l.cacheWriteTokens))}const s=[];for(const r of e.values())s.push({label:r.label,items:[r.rows]});return{kind:"grouped",groups:s,currency:c}}const M=new Map;for(const e of o)M.set(e.sessionId,e);const y=new Map;if(u!==void 0){for(const e of u){if(!M.has(e.sessionId))continue;const s=y.get(e.sessionId);s===void 0?y.set(e.sessionId,[e]):s.push(e)}for(const e of y.values())e.sort((s,r)=>s.ts.localeCompare(r.ts))}const x=(e,s)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(s),e.sessionCount=e._sessions.size},R=e=>{delete e._sessions},A=(e,s,r)=>{const d=I(s.ts);let t=e.get(d);t===void 0&&(t={bucket:d,costAmount:0,deltaCost:0,sessionCount:0},e.set(d,t)),t.costAmount+=r,t.deltaCost+=r,x(t,s.sessionId),n.tokens===!0&&(t.inputTokens===void 0||(s.inputTokens??0)>t.inputTokens)&&(t.inputTokens=s.inputTokens??t.inputTokens??0)};if(n.by!==void 0&&n.bucket!==void 0){const e=new Map,s=d=>{const t=p(d);let g=e.get(t);return g===void 0&&(g={label:t,buckets:new Map},e.set(t,g)),g.buckets};for(const d of o){const t=y.get(d.sessionId);if(t===void 0||t.length===0)continue;const g=s(d);let l=t[0].cumulativeCost;for(let b=1;b<t.length;b++){const T=t[b],m=Math.max(0,T.cumulativeCost-l);l=T.cumulativeCost,!(f!==void 0&&new Date(T.ts)<f)&&A(g,T,m)}}const r=[];for(const d of e.values()){const t=Array.from(d.buckets.values());for(const g of t)R(g);t.sort((g,l)=>g.bucket.localeCompare(l.bucket)),t.length>0&&r.push({label:d.label,items:t})}return{kind:"timeSeriesGrouped",groups:r,currency:c}}const D=new Map;for(const e of o){const s=y.get(e.sessionId);if(s===void 0||s.length===0)continue;let r=s[0].cumulativeCost;for(let d=1;d<s.length;d++){const t=s[d],g=Math.max(0,t.cumulativeCost-r);r=t.cumulativeCost,!(f!==void 0&&new Date(t.ts)<f)&&A(D,t,g)}}const C=Array.from(D.values());for(const e of C)R(e);return C.sort((e,s)=>e.bucket.localeCompare(s.bucket)),{kind:"timeSeries",timeSeries:C,currency:c}}export{$ as aggregate,F as applyFilters,H as parseSince};
@@ -1,2 +1,2 @@
1
- import{readFileSync as m}from"node:fs";import{homedir as y}from"node:os";import{resolve as g}from"node:path";import{logger as E}from"../util/log.js";const i=E("cost/daemon-client");function b(){if(process.env.HYDRA_ACP_TOKEN)return process.env.HYDRA_ACP_TOKEN;const e=g(process.env.HYDRA_ACP_HOME??g(y(),".hydra-acp"),"auth-token");try{const t=m(e,"utf8").trim();if(t.length>0)return t}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`auth-token read failed: ${n.message}`)}}function R(){if(process.env.HYDRA_ACP_DAEMON_URL)return process.env.HYDRA_ACP_DAEMON_URL;const e=g(process.env.HYDRA_ACP_HOME??g(y(),".hydra-acp"),"daemon.pid");try{const t=m(e,"utf8"),n=JSON.parse(t);if(typeof n.host=="string"&&typeof n.port=="number")return`http://${n.host}:${n.port}`}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`daemon.pid read failed: ${n.message}`)}return"http://127.0.0.1:8765"}function l(){const e=b();if(e!==void 0)return{daemonUrl:R(),token:e}}function C(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0;if(t===void 0)return;const n=typeof e.updatedAt=="string"?e.updatedAt:"",s=(e._meta??{})["hydra-acp"]??{},d=typeof e.cwd=="string"?e.cwd:typeof s.cwd=="string"?s.cwd:void 0,o=typeof e.agentId=="string"?e.agentId:typeof s.agentId=="string"?s.agentId:"",u=typeof e.currentModel=="string"?e.currentModel:typeof s.currentModel=="string"?s.currentModel:"",f=typeof e.title=="string"?e.title:"",c=e.interactive===!0||s.interactive===!0,p=e.currentUsage,h=s.currentUsage,a=p??h,k=typeof a?.costAmount=="number"?a.costAmount:0,A=typeof a?.costCurrency=="string"?a.costCurrency:"",v=typeof a?.used=="number"?a.used:0;return{sessionId:t,cwd:d,agentId:o,model:u,interactive:c,costAmount:k,costCurrency:A,contextTokens:v,title:f,createdAt:"",updatedAt:n}}async function T(){const e=l();if(e===void 0){i.debug("no daemon token resolved; skipping daemon fetch");return}const t=`${e.daemonUrl.replace(/\/$/,"")}/v1/sessions?includeNonInteractive=1`;let n;try{n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}})}catch(o){i.debug(`daemon fetch failed (${t}): ${o.message}`);return}if(!n.ok){i.debug(`daemon returned HTTP ${n.status} for ${t}`);return}let r;try{r=await n.json()}catch(o){i.debug(`daemon response not JSON: ${o.message}`);return}const s=r;if(!Array.isArray(s.sessions)){i.debug("daemon response missing sessions[]");return}const d=[];for(const o of s.sessions)if(o&&typeof o=="object"&&!Array.isArray(o)){const u=C(o);u!==void 0&&d.push(u)}return d}function $(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0,n=typeof e.ts=="string"?e.ts:void 0;if(t===void 0||n===void 0)return;const r=e.update??void 0,s=typeof r?.cost?.amount=="number"?r.cost.amount:0,d=typeof r?.cost?.currency=="string"?r.cost.currency:"USD",o=typeof r?.used=="number"?r.used:0;return{sessionId:t,ts:n,costCumulative:s,costCurrency:d,contextTokens:o}}async function I(e){const t=l();if(t===void 0)return;const n=new URLSearchParams({kinds:"usage_update"});e!==void 0&&n.set("since",e.toISOString());const r=`${t.daemonUrl.replace(/\/$/,"")}/v1/sessions/events?${n.toString()}`;let s;try{s=await fetch(r,{headers:{Authorization:`Bearer ${t.token}`}})}catch(u){i.debug(`events fetch failed: ${u.message}`);return}if(!s.ok){i.debug(`events endpoint HTTP ${s.status}`);return}const d=await s.text(),o=[];for(const u of d.split(`
2
- `)){const f=u.trim();if(f.length===0)continue;let c;try{c=JSON.parse(f)}catch{continue}if(c&&typeof c=="object"&&!Array.isArray(c)){const p=$(c);p!==void 0&&o.push(p)}}return o}export{I as fetchUsageEventsFromDaemon,T as listSessionsFromDaemon};
1
+ import{readFileSync as y}from"node:fs";import{homedir as l}from"node:os";import{resolve as m}from"node:path";import{logger as C}from"../util/log.js";const i=C("cost/daemon-client");function I(){if(process.env.HYDRA_ACP_TOKEN)return process.env.HYDRA_ACP_TOKEN;const e=m(process.env.HYDRA_ACP_HOME??m(l(),".hydra-acp"),"auth-token");try{const t=y(e,"utf8").trim();if(t.length>0)return t}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`auth-token read failed: ${n.message}`)}}function $(){if(process.env.HYDRA_ACP_DAEMON_URL)return process.env.HYDRA_ACP_DAEMON_URL;const e=m(process.env.HYDRA_ACP_HOME??m(l(),".hydra-acp"),"daemon.pid");try{const t=y(e,"utf8"),n=JSON.parse(t);if(typeof n.host=="string"&&typeof n.port=="number")return`http://${n.host}:${n.port}`}catch(t){const n=t;n.code!=="ENOENT"&&i.debug(`daemon.pid read failed: ${n.message}`)}return"http://127.0.0.1:8765"}function h(){const e=I();if(e!==void 0)return{daemonUrl:$(),token:e}}function U(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0;if(t===void 0)return;const n=typeof e.updatedAt=="string"?e.updatedAt:"",s=(e._meta??{})["hydra-acp"]??{},d=typeof e.cwd=="string"?e.cwd:typeof s.cwd=="string"?s.cwd:void 0,o=typeof e.agentId=="string"?e.agentId:typeof s.agentId=="string"?s.agentId:"",c=typeof e.currentModel=="string"?e.currentModel:typeof s.currentModel=="string"?s.currentModel:"",p=typeof e.title=="string"?e.title:"",u=e.interactive===!0||s.interactive===!0,f=typeof e.importedFromMachine=="string"?e.importedFromMachine:typeof s.importedFromMachine=="string"?s.importedFromMachine:"",k=f.length>0?f:void 0,g=typeof e.upstreamSessionId=="string"?e.upstreamSessionId:typeof s.upstreamSessionId=="string"?s.upstreamSessionId:"",A=g.length>0?g:void 0,v=e.currentUsage,R=s.currentUsage,a=v??R,E=typeof a?.costAmount=="number"?a.costAmount:0,b=typeof a?.costCurrency=="string"?a.costCurrency:"",S=typeof a?.used=="number"?a.used:0;return{sessionId:t,cwd:d,agentId:o,model:c,interactive:u,costAmount:E,costCurrency:b,contextTokens:S,title:p,createdAt:"",updatedAt:n,importedFromMachine:k,upstreamSessionId:A}}async function P(){const e=h();if(e===void 0){i.debug("no daemon token resolved; skipping daemon fetch");return}const t=`${e.daemonUrl.replace(/\/$/,"")}/v1/sessions?includeNonInteractive=1`;let n;try{n=await fetch(t,{headers:{Authorization:`Bearer ${e.token}`}})}catch(o){i.debug(`daemon fetch failed (${t}): ${o.message}`);return}if(!n.ok){i.debug(`daemon returned HTTP ${n.status} for ${t}`);return}let r;try{r=await n.json()}catch(o){i.debug(`daemon response not JSON: ${o.message}`);return}const s=r;if(!Array.isArray(s.sessions)){i.debug("daemon response missing sessions[]");return}const d=[];for(const o of s.sessions)if(o&&typeof o=="object"&&!Array.isArray(o)){const c=U(o);c!==void 0&&d.push(c)}return d}function _(e){const t=typeof e.sessionId=="string"?e.sessionId:void 0,n=typeof e.ts=="string"?e.ts:void 0;if(t===void 0||n===void 0)return;const r=e.update??void 0,s=typeof r?.cost?.amount=="number"?r.cost.amount:0,d=typeof r?.cost?.currency=="string"?r.cost.currency:"USD",o=typeof r?.used=="number"?r.used:0;return{sessionId:t,ts:n,costCumulative:s,costCurrency:d,contextTokens:o}}async function x(e){const t=h();if(t===void 0)return;const n=new URLSearchParams({kinds:"usage_update"});e!==void 0&&n.set("since",e.toISOString());const r=`${t.daemonUrl.replace(/\/$/,"")}/v1/sessions/events?${n.toString()}`;let s;try{s=await fetch(r,{headers:{Authorization:`Bearer ${t.token}`}})}catch(c){i.debug(`events fetch failed: ${c.message}`);return}if(!s.ok){i.debug(`events endpoint HTTP ${s.status}`);return}const d=await s.text(),o=[];for(const c of d.split(`
2
+ `)){const p=c.trim();if(p.length===0)continue;let u;try{u=JSON.parse(p)}catch{continue}if(u&&typeof u=="object"&&!Array.isArray(u)){const f=_(u);f!==void 0&&o.push(f)}}return o}export{x as fetchUsageEventsFromDaemon,P as listSessionsFromDaemon};
@@ -1 +1 @@
1
- import{readdirSync as C,readFileSync as I,realpathSync as R,statSync as h}from"node:fs";import{isAbsolute as j,resolve as a}from"node:path";import{homedir as x}from"node:os";import{logger as E}from"../util/log.js";const d=E("cost/session-store"),f=new Map;function $(n){if(f.has(n))return f.get(n);try{const s=R(n);return f.set(n,s),s}catch{f.set(n,void 0);return}}function N(){const n=process.env.HYDRA_ACP_HOME??a(x(),".hydra-acp");return a(n,"sessions")}function J(n){const s=a(n,"meta.json");let i;try{i=I(s,"utf8")}catch(g){const p=g;if(p.code==="ENOENT")return;d.debug(`skipping ${n}: read meta.json failed: ${p.message}`);return}let t;try{t=JSON.parse(i)}catch{d.debug(`skipping ${n}: meta.json is not valid JSON`);return}if(!t||typeof t!="object"||Array.isArray(t)){d.debug(`skipping ${n}: meta.json is not an object`);return}const e=t,c=typeof e.sessionId=="string"?e.sessionId:void 0;if(c===void 0){d.debug(`skipping ${n}: missing sessionId`);return}let r;const u=typeof e.cwd=="string"?e.cwd:void 0;u!==void 0&&(j(u)?r=$(u):r=u);const y=typeof e.agentId=="string"?e.agentId:"",m=typeof e.currentModel=="string"?e.currentModel:"",l=e.interactive===!0,o=e.currentUsage??void 0,A=typeof o?.costAmount=="number"?o.costAmount:0,w=typeof o?.costCurrency=="string"?o.costCurrency:"",b=typeof o?.used=="number"?o.used:0,S=typeof e.title=="string"?e.title:"",k=typeof e.createdAt=="string"?e.createdAt:"",v=typeof e.updatedAt=="string"?e.updatedAt:"";return{sessionId:c,cwd:r,agentId:y,model:m,interactive:l,costAmount:A,costCurrency:w,contextTokens:b,title:S,createdAt:k,updatedAt:v}}function U(){const n=N();let s;try{s=C(n)}catch(t){const e=t;return d.debug(`session store not found at ${n}: ${e.message}`),[]}const i=[];for(const t of s){const e=a(n,t);let c;try{c=h(e)}catch{continue}if(!c.isDirectory())continue;const r=J(e);r!==void 0&&i.push(r)}return i}export{U as scanSessions,N as sessionsDir};
1
+ import{readdirSync as M,readFileSync as C,realpathSync as R,statSync as j}from"node:fs";import{isAbsolute as x,resolve as a}from"node:path";import{homedir as E}from"node:os";import{logger as $}from"../util/log.js";const c=$("cost/session-store"),f=new Map;function F(n){if(f.has(n))return f.get(n);try{const s=R(n);return f.set(n,s),s}catch{f.set(n,void 0);return}}function N(){const n=process.env.HYDRA_ACP_HOME??a(E(),".hydra-acp");return a(n,"sessions")}function J(n){const s=a(n,"meta.json");let i;try{i=C(s,"utf8")}catch(g){const p=g;if(p.code==="ENOENT")return;c.debug(`skipping ${n}: read meta.json failed: ${p.message}`);return}let t;try{t=JSON.parse(i)}catch{c.debug(`skipping ${n}: meta.json is not valid JSON`);return}if(!t||typeof t!="object"||Array.isArray(t)){c.debug(`skipping ${n}: meta.json is not an object`);return}const e=t,d=typeof e.sessionId=="string"?e.sessionId:void 0;if(d===void 0){c.debug(`skipping ${n}: missing sessionId`);return}let r;const u=typeof e.cwd=="string"?e.cwd:void 0;u!==void 0&&(x(u)?r=F(u):r=u);const m=typeof e.agentId=="string"?e.agentId:"",y=typeof e.currentModel=="string"?e.currentModel:"",l=e.interactive===!0,o=e.currentUsage??void 0,S=typeof o?.costAmount=="number"?o.costAmount:0,A=typeof o?.costCurrency=="string"?o.costCurrency:"",w=typeof o?.used=="number"?o.used:0,h=typeof e.title=="string"?e.title:"",b=typeof e.createdAt=="string"?e.createdAt:"",I=typeof e.updatedAt=="string"?e.updatedAt:"",k=typeof e.importedFromMachine=="string"&&e.importedFromMachine.length>0?e.importedFromMachine:void 0,v=typeof e.upstreamSessionId=="string"&&e.upstreamSessionId.length>0?e.upstreamSessionId:void 0;return{sessionId:d,cwd:r,agentId:m,model:y,interactive:l,costAmount:S,costCurrency:A,contextTokens:w,title:h,createdAt:b,updatedAt:I,importedFromMachine:k,upstreamSessionId:v}}function _(){const n=N();let s;try{s=M(n)}catch(t){const e=t;return c.debug(`session store not found at ${n}: ${e.message}`),[]}const i=[];for(const t of s){const e=a(n,t);let d;try{d=j(e)}catch{continue}if(!d.isDirectory())continue;const r=J(e);r!==void 0&&i.push(r)}return i}export{_ as scanSessions,N as sessionsDir};
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as C}from"node:fs";import{dirname as F,resolve as R}from"node:path";import{fileURLToPath as U}from"node:url";import{loadConfig as $}from"./config.js";import{BudgeterBridge as L}from"./bridge.js";import{stateFilePath as v}from"./paths.js";import{DEFAULT_RULE as P}from"./rule.js";import{deleteStateFile as N}from"./tracker.js";import{logger as x,setDebug as G}from"./util/log.js";import{scanSessions as M}from"./cost/session-store.js";import{listSessionsFromDaemon as j,fetchUsageEventsFromDaemon as H}from"./cost/daemon-client.js";import{aggregate as A,applyFilters as _,parseSince as J}from"./cost/aggregate.js";import{renderText as W,renderJson as Y}from"./cost/format.js";const k=x("main");function S(){try{const e=F(U(import.meta.url));return JSON.parse(C(R(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function B(){N(v()),process.stdout.write(`hydra-acp-budgeter accumulated cost reset
3
- `)}const V=`Usage: hydra budgeter usage [OPTIONS]
2
+ import{readFileSync as C}from"node:fs";import{dirname as R,resolve as U}from"node:path";import{fileURLToPath as $}from"node:url";import{loadConfig as L}from"./config.js";import{BudgeterBridge as P}from"./bridge.js";import{stateFilePath as k}from"./paths.js";import{DEFAULT_RULE as N}from"./rule.js";import{deleteStateFile as x}from"./tracker.js";import{logger as G,setDebug as M}from"./util/log.js";import{scanSessions as j}from"./cost/session-store.js";import{listSessionsFromDaemon as H,fetchUsageEventsFromDaemon as A}from"./cost/daemon-client.js";import{aggregate as W,applyFilters as _,parseSince as J}from"./cost/aggregate.js";import{renderText as Y,renderJson as B}from"./cost/format.js";const S=G("main");function E(){try{const e=R($(import.meta.url));return JSON.parse(C(U(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function V(){x(k()),process.stdout.write(`hydra-acp-budgeter accumulated cost reset
3
+ `)}const K=`Usage: hydra budgeter usage [OPTIONS]
4
4
 
5
5
  Options:
6
6
  --since <date|duration> Only include sessions updated after this date (e.g. 7d, 2024-01-01)
@@ -9,16 +9,20 @@ Options:
9
9
  --depth <N> Depth for --by dir grouping (default: 1)
10
10
  --dir <path> Only include sessions under this directory prefix
11
11
  --interactive Only include interactive sessions (default: include both)
12
+ --host <name|local|all> Filter sessions by host. "local" (default) shows sessions
13
+ created here plus imports attached locally; "all" includes
14
+ every session; <name> shows passive mirrors imported from
15
+ that host.
12
16
  --min <N> Drop sessions whose active-metric value is <= N (default: 0)
13
17
  --histogram Show an ASCII histogram bar next to each row (implies --bucket week if no bucket given)
14
18
  --metric <cost|tokens> Display metric (default: cost)
15
19
  --json Output as JSON
16
- --help Show this help message`;async function E(e){if(e.includes("--help")){process.stdout.write(V+`
17
- `);return}let r,s,d,a,c,l=!1,f,u=!1,o,m=!1;for(let t=0;t<e.length;t++){const n=e[t];if(n!==void 0){if(n==="--since")t+=1,r=e[t];else if(n==="--bucket")t+=1,s=e[t];else if(n==="--by")t+=1,d=e[t];else if(n==="--depth")t+=1,a=e[t];else if(n==="--dir")t+=1,c=e[t];else if(n==="--interactive")l=!0;else if(n==="--min")t+=1,f=e[t];else if(n==="--histogram")u=!0;else if(n==="--metric")t+=1,o=e[t];else if(n==="--json")m=!0;else if(n.startsWith("--"))throw new Error(`Unknown option: ${n}
20
+ --help Show this help message`;async function T(e){if(e.includes("--help")){process.stdout.write(K+`
21
+ `);return}let r,n,a,d,c,m=!1,l,u=!1,o,h=!1,f;for(let t=0;t<e.length;t++){const s=e[t];if(s!==void 0){if(s==="--since")t+=1,r=e[t];else if(s==="--bucket")t+=1,n=e[t];else if(s==="--by")t+=1,a=e[t];else if(s==="--depth")t+=1,d=e[t];else if(s==="--dir")t+=1,c=e[t];else if(s==="--interactive")m=!0;else if(s==="--min")t+=1,l=e[t];else if(s==="--histogram")u=!0;else if(s==="--metric")t+=1,o=e[t];else if(s==="--json")h=!0;else if(s==="--host")t+=1,f=e[t];else if(s.startsWith("--host="))f=s.slice(7);else if(s.startsWith("--"))throw new Error(`Unknown option: ${s}
18
22
  Run "hydra budgeter usage --help" for usage.`)}}if(o!==void 0&&o!=="cost"&&o!=="tokens")throw new Error(`Invalid --metric value. Must be 'cost' or 'tokens'.
19
- Run "hydra budgeter usage --help" for usage.`);let p;if(r!==void 0)try{p=J(r)}catch(t){const n=t;throw new Error(`Invalid --since value: ${n.message}
20
- Run "hydra budgeter usage --help" for usage.`)}e.length===0&&(s="hour",u=!0),u&&s===void 0&&(s="week");let i=p;if(i===void 0&&s!==void 0){const t=new Date;s==="hour"?(t.setHours(t.getHours()-24),i=t):s==="day"?(t.setDate(t.getDate()-30),i=t):s==="week"?(t.setMonth(t.getMonth()-6),i=t):s==="month"&&(t.setFullYear(t.getFullYear()-2),i=t)}const h=l?!0:void 0,g=o==="tokens",y=f!==void 0?parseFloat(f):void 0,T=await j()??M(),D=_(T,{since:i,dir:c,interactive:h,min:y,minMetric:g?"tokens":"cost"});let w;if(s!==void 0){const t=await H();t!==void 0&&(w=t.map(n=>({sessionId:n.sessionId,ts:n.ts,deltaCost:0,cumulativeCost:n.costCumulative,currency:n.costCurrency,inputTokens:n.contextTokens})))}const I=a!==void 0?parseInt(a,10):void 0,b=A(D,w,{by:d,depth:I,bucket:s,since:i,interactive:h,dir:c,tokens:g,min:y});if(m)process.stdout.write(Y(b)+`
21
- `);else{const t=W(b,{histogram:u,tokens:o==="tokens"});process.stdout.write(t)}}async function K(){const e=$();G(e.debug);const r=v(),s=new L({daemonWsUrl:e.hydraWsUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:P,statePath:r});s.start();const d=a=>{k.info(`${a} received \u2014 shutting down`),s.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>d("SIGINT")),process.on("SIGTERM",()=>d("SIGTERM")),k.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=${r}`)}function Z(){process.stdout.write(`hydra-acp-budgeter ${S()}
23
+ Run "hydra budgeter usage --help" for usage.`);let p;if(r!==void 0)try{p=J(r)}catch(t){const s=t;throw new Error(`Invalid --since value: ${s.message}
24
+ Run "hydra budgeter usage --help" for usage.`)}e.length===0&&(n="hour",u=!0),u&&n===void 0&&(n="week");let i=p;if(i===void 0&&n!==void 0){const t=new Date;n==="hour"?(t.setHours(t.getHours()-24),i=t):n==="day"?(t.setDate(t.getDate()-30),i=t):n==="week"?(t.setMonth(t.getMonth()-6),i=t):n==="month"&&(t.setFullYear(t.getFullYear()-2),i=t)}const g=m?!0:void 0,y=o==="tokens",w=l!==void 0?parseFloat(l):void 0,D=await H()??j(),I=_(D,{since:i,dir:c,interactive:g,min:w,minMetric:y?"tokens":"cost",host:f??"local"});let b;if(n!==void 0){const t=await A();t!==void 0&&(b=t.map(s=>({sessionId:s.sessionId,ts:s.ts,deltaCost:0,cumulativeCost:s.costCumulative,currency:s.costCurrency,inputTokens:s.contextTokens})))}const O=d!==void 0?parseInt(d,10):void 0,v=W(I,b,{by:a,depth:O,bucket:n,since:i,interactive:g,dir:c,tokens:y,min:w});if(h)process.stdout.write(B(v)+`
25
+ `);else{const t=Y(v,{histogram:u,tokens:o==="tokens"});process.stdout.write(t)}}async function Z(){const e=L();M(e.debug);const r=k(),n=new P({daemonWsUrl:e.hydraWsUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:N,statePath:r});n.start();const a=d=>{S.info(`${d} received \u2014 shutting down`),n.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>a("SIGINT")),process.on("SIGTERM",()=>a("SIGTERM")),S.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=${r}`)}function q(){process.stdout.write(`hydra-acp-budgeter ${E()}
22
26
 
23
27
  Usage:
24
28
  hydra budgeter [usage] <flags> Report historical cost/usage across sessions
@@ -27,6 +31,6 @@ Usage:
27
31
  Flags:
28
32
  -v, --version Print version and exit
29
33
  -h, --help Show this help
30
- `)}async function q(){const e=process.argv.slice(2);if(e.includes("--version")||e.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${S()}
31
- `);return}if((e[0]==="help"||e.includes("--help")||e.includes("-h"))&&e[0]!=="usage"&&e[0]!=="cost"){Z();return}if(e[0]==="reset"){B();return}if(e[0]==="usage"||e[0]==="cost"){await E(e.slice(1));return}if(e[0]==="run"||process.env.HYDRA_ACP_TOKEN){await K();return}await E(e)}q().catch(e=>{process.stderr.write(`hydra-acp-budgeter: ${e.message}
34
+ `)}async function z(){const e=process.argv.slice(2);if(e.includes("--version")||e.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${E()}
35
+ `);return}if((e[0]==="help"||e.includes("--help")||e.includes("-h"))&&e[0]!=="usage"&&e[0]!=="cost"){q();return}if(e[0]==="reset"){V();return}if(e[0]==="usage"||e[0]==="cost"){await T(e.slice(1));return}if(e[0]==="run"||process.env.HYDRA_ACP_TOKEN){await Z();return}await T(e)}z().catch(e=>{process.stderr.write(`hydra-acp-budgeter: ${e.message}
32
36
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/budgeter",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Cost-budget transformer extension for hydra-acp — warns on soft limit, rejects further prompts/sessions on hard limit.",
5
5
  "license": "MIT",
6
6
  "type": "module",