@hydra-acp/budgeter 0.1.18 → 0.1.19

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 H,resolve as A}from"node:path";import{homedir as L}from"node:os";import{realpathSync as $}from"node:fs";function V(l){const c=l.match(/^(\d+)\s*([dhmwy])$/i);if(c!==null){const T=c[1],f=c[2];if(T===void 0||f===void 0)throw new Error(`invalid since spec: ${l}`);const m=parseInt(T,10),a=f.toLowerCase(),r=new Date;return a==="d"?r.setDate(r.getDate()-m):a==="h"?r.setHours(r.getHours()-m):a==="w"?r.setDate(r.getDate()-m*7):a==="m"?r.setMonth(r.getMonth()-m):a==="y"&&r.setFullYear(r.getFullYear()-m),r}const t=new Date(l);if(Number.isNaN(t.getTime()))throw new Error(`invalid since spec: ${l}`);return t}function q(l){const c=l.locByFiletype;if(c===void 0)return 0;let t=0;for(const T of Object.keys(c)){const f=c[T];f!==void 0&&(t+=f.added-f.removed)}return t}const I=new Map;function D(l){if(I.has(l))return I.get(l);try{const c=$(l);return I.set(l,c),c}catch{return I.set(l,l),l}}function P(l,c){let t=l;if(c.since!==void 0){const m=c.since.getTime(),a=[];for(const r of t)new Date(r.updatedAt).getTime()<m||a.push(r);t=a}if(c.dir!==void 0){const m=D(A(c.dir)),a=[];for(const r of t)r.cwd===void 0||r.cwd===""||(r.cwd.startsWith(m+"/")||r.cwd===m)&&a.push(r);t=a}if(c.host!==void 0&&c.host!=="all"){const m=c.host,a=[];for(const r of t)m==="local"?(!r.importedFromMachine||r.upstreamSessionId)&&a.push(r):r.importedFromMachine===m&&!r.upstreamSessionId&&a.push(r);t=a}if(c.interactive!==void 0){const m=c.interactive,a=[];for(const r of t)r.interactive===m&&a.push(r);t=a}const T=c.min??0,f=[];for(const m of t){let a;c.minMetric==="tokens"?a=m.contextTokens:c.minMetric==="loc"?a=q(m):a=m.costAmount,a>T&&f.push(m)}return f}function Y(l){return l.dir!==void 0?A(l.dir):A(L())}function K(l,c,t){if(l===void 0||l==="")return"<unknown>";const T=D(A(l));if(c===void 0){const v=D(A(L()));return T===v?"~":T.startsWith(v+"/")?"~/"+T.slice(v.length+1):T}const f=D(A(t));let m=H(f,T);if(m===""||m===".")return"<root>";if(m.startsWith(".."))return"<unknown>";const a=m.split("/");if(a.length===0)return"<root>";const r=Math.min(c,a.length),S=a.slice(0,r);return S.length===0?"<root>":S.join("/")}function X(l,c,t={},T){let f=t.since;if(f===void 0&&t.bucket!==void 0){const e=new Date;t.bucket==="hour"?(e.setHours(e.getHours()-24),f=e):t.bucket==="day"||t.bucket==="weekday"?(e.setDate(e.getDate()-30),f=e):t.bucket==="week"?(e.setMonth(e.getMonth()-6),f=e):t.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),f=e)}if(!(f!==void 0||t.bucket!==void 0||t.tokens===!0||t.loc===!0||t.by!==void 0||t.dir!==void 0||t.interactive!==void 0)){let e=0;for(const o of l)e+=o.costAmount;let s="";for(const o of l){s=o.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:l.length},currency:s}}const a={since:f,dir:t.dir,interactive:t.interactive,min:t.min,minMetric:t.loc===!0?"loc":t.tokens===!0?"tokens":"cost"},r=P(l,a),S=new Map;if(c!==void 0)for(const e of c){const s=S.get(e.sessionId);s===void 0?S.set(e.sessionId,[e]):s.push(e)}let v="";if(c!==void 0&&c.length>0)for(const e of c){v=e.currency;break}else if(r.length>0)for(const e of r){v=e.costCurrency;break}if(t.by==="filetype"&&t.bucket===void 0){const e=new Map,s=new Set;for(const u of r){const n=u.locByFiletype;if(n===void 0)continue;let g=!1;for(const d of Object.keys(n)){const h=n[d];if(h===void 0)continue;let w=e.get(d);w===void 0&&(w={added:0,removed:0,sessions:new Set},e.set(d,w)),w.added+=h.added,w.removed+=h.removed,(h.added>0||h.removed>0)&&(w.sessions.add(u.sessionId),g=!0)}g&&s.add(u.sessionId)}const o=[];for(const[u,n]of e){const g={label:u,costAmount:0,sessionCount:n.sessions.size,linesAdded:n.added,linesRemoved:n.removed};o.push({label:u,items:[g],sessionCount:n.sessions.size})}return{kind:"grouped",groups:o,currency:v,totalSessions:s.size}}const x=e=>t.by==="dir"?K(e.cwd,t.depth,Y(t)):t.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:t.by==="model"?e.model===""?"<unknown>":e.model:t.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",B=e=>{const s=new Date(e);if(Number.isNaN(s.getTime()))return"<invalid>";if(t.bucket==="hour"){const o=s.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),u=String(s.getHours()).padStart(2,"0");return`${o} ${u}:00`}if(t.bucket==="day")return s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(t.bucket==="weekday")return s.toLocaleDateString(void 0,{weekday:"short"});if(t.bucket==="week"){const o=new Date(s),u=o.getDay(),n=o.getDate()-u+(u===0?-6:1);return o.setDate(n),o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return t.bucket==="month"?s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})},E=new Map;if(t.bucket==="weekday"){const e=new Date(2024,0,1);for(let s=0;s<7;s++){const o=new Date(e);o.setDate(e.getDate()+s),E.set(o.toLocaleDateString(void 0,{weekday:"short"}),s)}}const R=(e,s)=>{if(t.bucket==="weekday"){const o=E.get(e.bucket)??7,u=E.get(s.bucket)??7;if(o!==u)return o-u}return e.bucket.localeCompare(s.bucket)};if(t.by===void 0&&t.bucket===void 0){let e=0,s=0,o=0,u=0,n=0,g=0;for(const h of r){e+=h.costAmount;const w=S.get(h.sessionId);if(w!==void 0)for(const i of w)s+=i.deltaCost,t.tokens===!0&&(i.inputTokens!==void 0&&(o+=i.inputTokens),i.outputTokens!==void 0&&(u+=i.outputTokens),i.cacheReadTokens!==void 0&&(n+=i.cacheReadTokens),i.cacheWriteTokens!==void 0&&(g+=i.cacheWriteTokens))}const d={label:"All sessions",costAmount:e,deltaCost:s,sessionCount:r.length};if(t.tokens===!0&&(d.inputTokens=o,d.outputTokens=u,d.cacheReadTokens=n,d.cacheWriteTokens=g),t.loc===!0){let h=0,w=0;for(const i of r){const b=i.locByFiletype;if(b!==void 0)for(const k of Object.keys(b)){const p=b[k];p!==void 0&&(h+=p.added,w+=p.removed)}}d.linesAdded=h,d.linesRemoved=w}return{kind:"total",row:d,currency:v}}if(t.by!==void 0&&t.bucket===void 0){const e=new Map;for(const o of r){const u=x(o);let n=e.get(u);if(n===void 0&&(n={label:u,rows:{label:u,costAmount:0,deltaCost:0,sessionCount:0}},e.set(u,n)),n.rows.costAmount+=o.costAmount,n.rows.sessionCount=(n.rows.sessionCount??0)+1,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.inputTokens+=o.contextTokens),t.loc===!0){const d=o.locByFiletype;if(d!==void 0){n.rows.linesAdded===void 0&&(n.rows.linesAdded=0),n.rows.linesRemoved===void 0&&(n.rows.linesRemoved=0);for(const h of Object.keys(d)){const w=d[h];w!==void 0&&(n.rows.linesAdded+=w.added,n.rows.linesRemoved+=w.removed)}}}const g=S.get(o.sessionId);if(g!==void 0)for(const d of g)n.rows.deltaCost+=d.deltaCost,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.outputTokens===void 0&&(n.rows.outputTokens=0),n.rows.cacheReadTokens===void 0&&(n.rows.cacheReadTokens=0),n.rows.cacheWriteTokens===void 0&&(n.rows.cacheWriteTokens=0),d.inputTokens!==void 0&&(n.rows.inputTokens+=d.inputTokens),d.outputTokens!==void 0&&(n.rows.outputTokens+=d.outputTokens),d.cacheReadTokens!==void 0&&(n.rows.cacheReadTokens+=d.cacheReadTokens),d.cacheWriteTokens!==void 0&&(n.rows.cacheWriteTokens+=d.cacheWriteTokens))}const s=[];for(const o of e.values())s.push({label:o.label,items:[o.rows],sessionCount:o.rows.sessionCount});return{kind:"grouped",groups:s,currency:v}}const F=new Map;for(const e of r)F.set(e.sessionId,e);const M=new Map;if(c!==void 0){for(const e of c){if(!F.has(e.sessionId))continue;const s=M.get(e.sessionId);s===void 0?M.set(e.sessionId,[e]):s.push(e)}for(const e of M.values())e.sort((s,o)=>s.ts.localeCompare(o.ts))}const j=(e,s)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(s),e.sessionCount=e._sessions.size},G=e=>{delete e._sessions},W=(e,s,o)=>{const u=B(s.ts);let n=e.get(u);n===void 0&&(n={bucket:u,costAmount:0,deltaCost:0,sessionCount:0},e.set(u,n)),n.costAmount+=o,n.deltaCost+=o,j(n,s.sessionId),t.tokens===!0&&(n.inputTokens===void 0||(s.inputTokens??0)>n.inputTokens)&&(n.inputTokens=s.inputTokens??n.inputTokens??0)};if(t.loc===!0&&t.bucket!==void 0){const e=new Set;for(const i of r)e.add(i.sessionId);const s=new Map;for(const i of r)s.set(i.sessionId,i);const o=(i,b)=>{let k=i.get(b);return k===void 0&&(k={bucket:b,costAmount:0,deltaCost:0,sessionCount:0,linesAdded:0,linesRemoved:0},i.set(b,k)),(k.linesAdded??void 0)===void 0&&(k.linesAdded=0),(k.linesRemoved??void 0)===void 0&&(k.linesRemoved=0),k},u=(i,b)=>{i.linesAdded=(i.linesAdded??0)+b.linesAdded,i.linesRemoved=(i.linesRemoved??0)+b.linesRemoved,i._sessions===void 0&&(i._sessions=new Set),i._sessions.add(b.sessionId),i.sessionCount=i._sessions.size},n=i=>{delete i._sessions},g=T??[];if(t.by==="filetype"){const i=new Map,b=new Set;for(const p of g){if(!e.has(p.sessionId)||f!==void 0&&new Date(p.ts)<f)continue;let y=i.get(p.filetype);y===void 0&&(y={label:p.filetype,buckets:new Map,sessions:new Set},i.set(p.filetype,y));const C=o(y.buckets,B(p.ts));u(C,p),y.sessions.add(p.sessionId),b.add(p.sessionId)}const k=[];for(const p of i.values()){const y=Array.from(p.buckets.values());for(const C of y)n(C);y.sort(R),y.length>0&&k.push({label:p.label,items:y,sessionCount:p.sessions.size})}return{kind:"timeSeriesGrouped",groups:k,currency:v,totalSessions:b.size}}if(t.by!==void 0){const i=new Map;for(const k of g){if(!e.has(k.sessionId)||f!==void 0&&new Date(k.ts)<f)continue;const p=s.get(k.sessionId);if(p===void 0)continue;const y=x(p);let C=i.get(y);C===void 0&&(C={label:y,buckets:new Map,sessions:new Set},i.set(y,C));const N=o(C.buckets,B(k.ts));u(N,k),C.sessions.add(k.sessionId)}const b=[];for(const k of i.values()){const p=Array.from(k.buckets.values());for(const y of p)n(y);p.sort(R),p.length>0&&b.push({label:k.label,items:p,sessionCount:k.sessions.size})}return{kind:"timeSeriesGrouped",groups:b,currency:v}}const d=new Map,h=new Set;for(const i of g){if(!e.has(i.sessionId)||f!==void 0&&new Date(i.ts)<f)continue;const b=o(d,B(i.ts));u(b,i),h.add(i.sessionId)}const w=Array.from(d.values());for(const i of w)n(i);return w.sort(R),{kind:"timeSeries",timeSeries:w,currency:v,totalSessions:h.size}}if(t.by!==void 0&&t.bucket!==void 0){const e=new Map,s=u=>{const n=x(u);let g=e.get(n);return g===void 0&&(g={label:n,buckets:new Map,sessions:new Set},e.set(n,g)),g};for(const u of r){const n=M.get(u.sessionId);if(n===void 0||n.length===0)continue;const g=s(u),d=g.buckets;g.sessions.add(u.sessionId);const h=n[0];if(h===void 0)continue;let w=h.cumulativeCost;for(let i=1;i<n.length;i++){const b=n[i];if(b===void 0)continue;const k=Math.max(0,b.cumulativeCost-w);w=b.cumulativeCost,!(f!==void 0&&new Date(b.ts)<f)&&W(d,b,k)}}const o=[];for(const u of e.values()){const n=Array.from(u.buckets.values());for(const g of n)G(g);n.sort(R),n.length>0&&o.push({label:u.label,items:n,sessionCount:u.sessions.size})}return{kind:"timeSeriesGrouped",groups:o,currency:v}}const O=new Map,z=new Set;for(const e of r){const s=M.get(e.sessionId);if(s===void 0||s.length===0)continue;const o=s[0];if(o===void 0)continue;let u=o.cumulativeCost,n=!1;for(let g=1;g<s.length;g++){const d=s[g];if(d===void 0)continue;const h=Math.max(0,d.cumulativeCost-u);u=d.cumulativeCost,!(f!==void 0&&new Date(d.ts)<f)&&(W(O,d,h),n=!0)}n&&z.add(e.sessionId)}const _=Array.from(O.values());for(const e of _)G(e);return _.sort(R),{kind:"timeSeries",timeSeries:_,currency:v,totalSessions:z.size}}export{X as aggregate,P as applyFilters,V as parseSince};
1
+ import{relative as H,resolve as R}from"node:path";import{homedir as L}from"node:os";import{realpathSync as $}from"node:fs";function V(w){const c=w.match(/^(\d+)\s*([dhmwy])$/i);if(c!==null){const y=c[1],a=c[2];if(y===void 0||a===void 0)throw new Error(`invalid since spec: ${w}`);const h=parseInt(y,10),k=a.toLowerCase(),r=new Date;return k==="d"?r.setDate(r.getDate()-h):k==="h"?r.setHours(r.getHours()-h):k==="w"?r.setDate(r.getDate()-h*7):k==="m"?r.setMonth(r.getMonth()-h):k==="y"&&r.setFullYear(r.getFullYear()-h),r}const t=new Date(w);if(Number.isNaN(t.getTime()))throw new Error(`invalid since spec: ${w}`);return t}function q(w){const c=w.locByFiletype;if(c===void 0)return 0;let t=0;for(const y of Object.keys(c)){const a=c[y];a!==void 0&&(t+=a.added-a.removed)}return t}const I=new Map;function D(w){if(I.has(w))return I.get(w);try{const c=$(w);return I.set(w,c),c}catch{return I.set(w,w),w}}function P(w,c){let t=w;if(c.since!==void 0){const h=c.since.getTime(),k=[];for(const r of t)new Date(r.updatedAt).getTime()<h||k.push(r);t=k}if(c.dir!==void 0){const h=D(R(c.dir)),k=[];for(const r of t)r.cwd===void 0||r.cwd===""||(r.cwd.startsWith(h+"/")||r.cwd===h)&&k.push(r);t=k}if(c.host!==void 0&&c.host!=="all"){const h=c.host,k=[];for(const r of t)h==="local"?(!r.importedFromMachine||r.upstreamSessionId)&&k.push(r):r.importedFromMachine===h&&!r.upstreamSessionId&&k.push(r);t=k}if(c.interactive!==void 0){const h=c.interactive,k=[];for(const r of t)r.interactive===h&&k.push(r);t=k}const y=c.min??0,a=[];for(const h of t){let k;c.minMetric==="tokens"?k=h.contextTokens:c.minMetric==="loc"?k=q(h):k=h.costAmount,k>y&&a.push(h)}return a}function Y(w){return w.dir!==void 0?R(w.dir):R(L())}function K(w,c,t){if(w===void 0||w==="")return"<unknown>";const y=D(R(w));if(c===void 0){const S=D(R(L()));return y===S?"~":y.startsWith(S+"/")?"~/"+y.slice(S.length+1):y}const a=D(R(t));let h=H(a,y);if(h===""||h===".")return"<root>";if(h.startsWith(".."))return"<unknown>";const k=h.split("/");if(k.length===0)return"<root>";const r=Math.min(c,k.length),C=k.slice(0,r);return C.length===0?"<root>":C.join("/")}function X(w,c,t={},y){let a=t.since;if(a===void 0&&t.bucket!==void 0){const e=new Date;t.bucket==="hour"?(e.setHours(e.getHours()-24),a=e):t.bucket==="day"||t.bucket==="weekday"?(e.setDate(e.getDate()-30),a=e):t.bucket==="week"?(e.setMonth(e.getMonth()-6),a=e):t.bucket==="month"&&(e.setFullYear(e.getFullYear()-2),a=e)}if(!(a!==void 0||t.bucket!==void 0||t.tokens===!0||t.loc===!0||t.by!==void 0||t.dir!==void 0||t.interactive!==void 0)){let e=0;for(const i of w)e+=i.costAmount;let s="";for(const i of w){s=i.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:w.length},currency:s}}const k={since:a,dir:t.dir,interactive:t.interactive,min:t.min,minMetric:t.loc===!0?"loc":t.tokens===!0?"tokens":"cost"},r=P(w,k),C=new Map;if(c!==void 0)for(const e of c){const s=C.get(e.sessionId);s===void 0?C.set(e.sessionId,[e]):s.push(e)}let S="";if(c!==void 0&&c.length>0)for(const e of c){S=e.currency;break}else if(r.length>0)for(const e of r){S=e.costCurrency;break}if(t.by==="filetype"&&t.bucket===void 0){const e=new Map,s=new Set;for(const u of r){const n=u.locByFiletype;if(n===void 0)continue;let l=!1;for(const m of Object.keys(n)){const f=n[m];if(f===void 0)continue;let b=e.get(m);b===void 0&&(b={added:0,removed:0,sessions:new Set},e.set(m,b)),b.added+=f.added,b.removed+=f.removed,(f.added>0||f.removed>0)&&(b.sessions.add(u.sessionId),l=!0)}l&&s.add(u.sessionId)}const i=[];for(const[u,n]of e){const l={label:u,costAmount:0,sessionCount:n.sessions.size,linesAdded:n.added,linesRemoved:n.removed};i.push({label:u,items:[l],sessionCount:n.sessions.size})}return{kind:"grouped",groups:i,currency:S,totalSessions:s.size}}const E=e=>t.by==="dir"?K(e.cwd,t.depth,Y(t)):t.by==="session"?e.sessionId.startsWith("hydra_session_")?e.sessionId.slice(14):e.sessionId:t.by==="model"?e.model===""?"<unknown>":e.model:t.by==="agent"?e.agentId===""?"<unknown>":e.agentId:"<all>",B=e=>{const s=new Date(e);if(Number.isNaN(s.getTime()))return"<invalid>";if(t.bucket==="hour"){const i=s.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),u=String(s.getHours()).padStart(2,"0");return`${i} ${u}:00`}if(t.bucket==="day")return s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(t.bucket==="weekday")return s.toLocaleDateString(void 0,{weekday:"short"});if(t.bucket==="week"){const i=new Date(s),u=i.getDay(),n=i.getDate()-u+(u===0?-6:1);return i.setDate(n),i.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return t.bucket==="month"?s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):s.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})},x=new Map;if(t.bucket==="weekday"){const e=new Date(2024,0,1);for(let s=0;s<7;s++){const i=new Date(e);i.setDate(e.getDate()+s),x.set(i.toLocaleDateString(void 0,{weekday:"short"}),s)}}const M=(e,s)=>{if(t.bucket==="weekday"){const i=x.get(e.bucket)??7,u=x.get(s.bucket)??7;if(i!==u)return i-u}return e.bucket.localeCompare(s.bucket)},F=new Map;for(const e of r)F.set(e.sessionId,e);const A=new Map;if(c!==void 0){for(const e of c){if(!F.has(e.sessionId))continue;const s=A.get(e.sessionId);s===void 0?A.set(e.sessionId,[e]):s.push(e)}for(const e of A.values())e.sort((s,i)=>s.ts.localeCompare(i.ts))}if(t.by===void 0&&t.bucket===void 0){let e=0,s=0,i=0,u=0,n=0,l=0;for(const f of r){const b=A.get(f.sessionId);if(b===void 0||b.length<2)e+=f.costAmount,l++;else{let d=b[0].cumulativeCost,g=!1;for(let p=1;p<b.length;p++){const v=b[p];if(v===void 0)continue;const T=Math.max(0,v.cumulativeCost-d);d=v.cumulativeCost,!(a!==void 0&&new Date(v.ts)<a)&&(e+=T,g=!0)}g&&l++}if(t.tokens===!0){const o=C.get(f.sessionId);if(o!==void 0)for(const d of o)d.inputTokens!==void 0&&(s+=d.inputTokens),d.outputTokens!==void 0&&(i+=d.outputTokens),d.cacheReadTokens!==void 0&&(u+=d.cacheReadTokens),d.cacheWriteTokens!==void 0&&(n+=d.cacheWriteTokens)}}const m={label:"All sessions",costAmount:e,deltaCost:e,sessionCount:l};if(t.tokens===!0&&(m.inputTokens=s,m.outputTokens=i,m.cacheReadTokens=u,m.cacheWriteTokens=n),t.loc===!0){let f=0,b=0;for(const o of r){const d=o.locByFiletype;if(d!==void 0)for(const g of Object.keys(d)){const p=d[g];p!==void 0&&(f+=p.added,b+=p.removed)}}m.linesAdded=f,m.linesRemoved=b}return{kind:"total",row:m,currency:S}}if(t.by!==void 0&&t.bucket===void 0){const e=new Map;for(const i of r){const u=E(i);let n=e.get(u);if(n===void 0&&(n={label:u,rows:{label:u,costAmount:0,deltaCost:0,sessionCount:0},sessions:new Set},e.set(u,n)),t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.inputTokens+=i.contextTokens),t.loc===!0){const m=i.locByFiletype;if(m!==void 0){n.rows.linesAdded===void 0&&(n.rows.linesAdded=0),n.rows.linesRemoved===void 0&&(n.rows.linesRemoved=0);for(const f of Object.keys(m)){const b=m[f];b!==void 0&&(n.rows.linesAdded+=b.added,n.rows.linesRemoved+=b.removed)}}}const l=A.get(i.sessionId);if(l===void 0||l.length<2)n.rows.costAmount+=i.costAmount,n.rows.deltaCost+=i.costAmount,n.sessions.add(i.sessionId);else{let f=l[0].cumulativeCost,b=!1;for(let o=1;o<l.length;o++){const d=l[o];if(d===void 0)continue;const g=Math.max(0,d.cumulativeCost-f);f=d.cumulativeCost,!(a!==void 0&&new Date(d.ts)<a)&&(n.rows.costAmount+=g,n.rows.deltaCost+=g,b=!0)}b&&n.sessions.add(i.sessionId)}if(t.tokens===!0){const m=C.get(i.sessionId);if(m!==void 0)for(const f of m)n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.outputTokens===void 0&&(n.rows.outputTokens=0),n.rows.cacheReadTokens===void 0&&(n.rows.cacheReadTokens=0),n.rows.cacheWriteTokens===void 0&&(n.rows.cacheWriteTokens=0),f.inputTokens!==void 0&&(n.rows.inputTokens+=f.inputTokens),f.outputTokens!==void 0&&(n.rows.outputTokens+=f.outputTokens),f.cacheReadTokens!==void 0&&(n.rows.cacheReadTokens+=f.cacheReadTokens),f.cacheWriteTokens!==void 0&&(n.rows.cacheWriteTokens+=f.cacheWriteTokens)}}const s=[];for(const i of e.values())i.rows.sessionCount=i.sessions.size,s.push({label:i.label,items:[i.rows],sessionCount:i.sessions.size});return{kind:"grouped",groups:s,currency:S}}const j=(e,s)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(s),e.sessionCount=e._sessions.size},G=e=>{delete e._sessions},W=(e,s,i)=>{const u=B(s.ts);let n=e.get(u);n===void 0&&(n={bucket:u,costAmount:0,deltaCost:0,sessionCount:0},e.set(u,n)),n.costAmount+=i,n.deltaCost+=i,j(n,s.sessionId),t.tokens===!0&&(n.inputTokens===void 0||(s.inputTokens??0)>n.inputTokens)&&(n.inputTokens=s.inputTokens??n.inputTokens??0)};if(t.loc===!0&&t.bucket!==void 0){const e=new Set;for(const o of r)e.add(o.sessionId);const s=new Map;for(const o of r)s.set(o.sessionId,o);const i=(o,d)=>{let g=o.get(d);return g===void 0&&(g={bucket:d,costAmount:0,deltaCost:0,sessionCount:0,linesAdded:0,linesRemoved:0},o.set(d,g)),(g.linesAdded??void 0)===void 0&&(g.linesAdded=0),(g.linesRemoved??void 0)===void 0&&(g.linesRemoved=0),g},u=(o,d)=>{o.linesAdded=(o.linesAdded??0)+d.linesAdded,o.linesRemoved=(o.linesRemoved??0)+d.linesRemoved,o._sessions===void 0&&(o._sessions=new Set),o._sessions.add(d.sessionId),o.sessionCount=o._sessions.size},n=o=>{delete o._sessions},l=y??[];if(t.by==="filetype"){const o=new Map,d=new Set;for(const p of l){if(!e.has(p.sessionId)||a!==void 0&&new Date(p.ts)<a)continue;let v=o.get(p.filetype);v===void 0&&(v={label:p.filetype,buckets:new Map,sessions:new Set},o.set(p.filetype,v));const T=i(v.buckets,B(p.ts));u(T,p),v.sessions.add(p.sessionId),d.add(p.sessionId)}const g=[];for(const p of o.values()){const v=Array.from(p.buckets.values());for(const T of v)n(T);v.sort(M),v.length>0&&g.push({label:p.label,items:v,sessionCount:p.sessions.size})}return{kind:"timeSeriesGrouped",groups:g,currency:S,totalSessions:d.size}}if(t.by!==void 0){const o=new Map;for(const g of l){if(!e.has(g.sessionId)||a!==void 0&&new Date(g.ts)<a)continue;const p=s.get(g.sessionId);if(p===void 0)continue;const v=E(p);let T=o.get(v);T===void 0&&(T={label:v,buckets:new Map,sessions:new Set},o.set(v,T));const N=i(T.buckets,B(g.ts));u(N,g),T.sessions.add(g.sessionId)}const d=[];for(const g of o.values()){const p=Array.from(g.buckets.values());for(const v of p)n(v);p.sort(M),p.length>0&&d.push({label:g.label,items:p,sessionCount:g.sessions.size})}return{kind:"timeSeriesGrouped",groups:d,currency:S}}const m=new Map,f=new Set;for(const o of l){if(!e.has(o.sessionId)||a!==void 0&&new Date(o.ts)<a)continue;const d=i(m,B(o.ts));u(d,o),f.add(o.sessionId)}const b=Array.from(m.values());for(const o of b)n(o);return b.sort(M),{kind:"timeSeries",timeSeries:b,currency:S,totalSessions:f.size}}if(t.by!==void 0&&t.bucket!==void 0){const e=new Map,s=u=>{const n=E(u);let l=e.get(n);return l===void 0&&(l={label:n,buckets:new Map,sessions:new Set},e.set(n,l)),l};for(const u of r){const n=A.get(u.sessionId);if(n===void 0||n.length===0)continue;const l=s(u),m=l.buckets;l.sessions.add(u.sessionId);const f=n[0];if(f===void 0)continue;let b=f.cumulativeCost;for(let o=1;o<n.length;o++){const d=n[o];if(d===void 0)continue;const g=Math.max(0,d.cumulativeCost-b);b=d.cumulativeCost,!(a!==void 0&&new Date(d.ts)<a)&&W(m,d,g)}}const i=[];for(const u of e.values()){const n=Array.from(u.buckets.values());for(const l of n)G(l);n.sort(M),n.length>0&&i.push({label:u.label,items:n,sessionCount:u.sessions.size})}return{kind:"timeSeriesGrouped",groups:i,currency:S}}const z=new Map,O=new Set;for(const e of r){const s=A.get(e.sessionId);if(s===void 0||s.length===0)continue;const i=s[0];if(i===void 0)continue;let u=i.cumulativeCost,n=!1;for(let l=1;l<s.length;l++){const m=s[l];if(m===void 0)continue;const f=Math.max(0,m.cumulativeCost-u);u=m.cumulativeCost,!(a!==void 0&&new Date(m.ts)<a)&&(W(z,m,f),n=!0)}n&&O.add(e.sessionId)}const _=Array.from(z.values());for(const e of _)G(e);return _.sort(M),{kind:"timeSeries",timeSeries:_,currency:S,totalSessions:O.size}}export{X as aggregate,P as applyFilters,V as parseSince};
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as U}from"node:fs";import{dirname as F,resolve as N}from"node:path";import{fileURLToPath as M}from"node:url";import{loadConfig as P}from"./config.js";import{BudgeterBridge as _}from"./bridge.js";import{DEFAULT_RULE as H}from"./rule.js";import{logger as W,setDebug as j}from"./util/log.js";import{scanSessions as B,enrichSessionsWithLoc as G}from"./cost/session-store.js";import{listSessionsFromDaemon as A,fetchUsageEventsFromDaemon as J}from"./cost/daemon-client.js";import{streamHistoryEditEvents as Y}from"./cost/history-stream.js";import{aggregate as q,applyFilters as V,parseSince as z}from"./cost/aggregate.js";import{renderText as K,renderJson as Z}from"./cost/format.js";const L=W("main");function C(){try{const e=F(M(import.meta.url));return JSON.parse(U(N(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function Q(){process.stderr.write(`hydra-acp-budgeter: 'reset' as a CLI subcommand is no longer supported.
2
+ import{readFileSync as F}from"node:fs";import{dirname as N,resolve as M}from"node:path";import{fileURLToPath as P}from"node:url";import{loadConfig as _}from"./config.js";import{BudgeterBridge as H}from"./bridge.js";import{DEFAULT_RULE as W}from"./rule.js";import{logger as j,setDebug as B}from"./util/log.js";import{scanSessions as G,enrichSessionsWithLoc as A}from"./cost/session-store.js";import{listSessionsFromDaemon as J,fetchUsageEventsFromDaemon as Y}from"./cost/daemon-client.js";import{streamHistoryEditEvents as q}from"./cost/history-stream.js";import{aggregate as V,applyFilters as z,parseSince as K}from"./cost/aggregate.js";import{renderText as Z,renderJson as Q}from"./cost/format.js";const C=j("main");function D(){try{const e=N(P(import.meta.url));return JSON.parse(F(M(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}function X(){process.stderr.write(`hydra-acp-budgeter: 'reset' as a CLI subcommand is no longer supported.
3
3
  Run \`/hydra hydra-acp-budgeter reset\` in a live hydra session instead.
4
4
  Cost state is now persisted per-session in meta.json (extension_state).
5
- `),process.exit(2)}const X=`Usage: hydra budgeter usage [OPTIONS]
5
+ `),process.exit(2)}const ee=`Usage: hydra budgeter usage [OPTIONS]
6
6
 
7
7
  Options:
8
8
  --since <date|duration> Only include sessions updated after this date (e.g. 7d, 2024-01-01)
@@ -23,14 +23,14 @@ Options:
23
23
  --metric <cost|tokens|loc> Display metric (default: cost). "loc" counts net lines of code
24
24
  from Edit/Write tool diffs in session history.
25
25
  --json Output as JSON
26
- --help Show this help message`;async function D(e){if(e.includes("--help")){process.stdout.write(X+`
27
- `);return}let i,s="hour",r,a,l,w=!1,f,u=!0,o,k=!1,h,p=!1,m=!1;for(let t=0;t<e.length;t++){const n=e[t];if(n!==void 0){if(n==="--since")t+=1,i=e[t];else if(n==="--bucket")t+=1,s=ee(e[t]),p=!0;else if(n==="--by")t+=1,r=e[t];else if(n==="--depth")t+=1,a=e[t];else if(n==="--dir")t+=1,l=e[t];else if(n==="--interactive")w=!0;else if(n==="--min")t+=1,f=e[t];else if(n==="--histogram")u=!0,m=!0;else if(n==="--no-histogram")u=!1,m=!0;else if(n==="--metric")t+=1,o=e[t];else if(n==="--json")k=!0;else if(n==="--host")t+=1,h=e[t];else if(n.startsWith("--host="))h=n.slice(7);else if(n.startsWith("--"))throw new Error(`Unknown option: ${n}
26
+ --help Show this help message`;async function T(e){if(e.includes("--help")){process.stdout.write(ee+`
27
+ `);return}let i,s="hour",r,a,l,w=!1,f,u=!0,o,k=!1,h,p=!1,m=!1;for(let t=0;t<e.length;t++){const n=e[t];if(n!==void 0){if(n==="--since")t+=1,i=e[t];else if(n==="--bucket")t+=1,s=te(e[t]),p=!0;else if(n==="--by")t+=1,r=e[t];else if(n==="--depth")t+=1,a=e[t];else if(n==="--dir")t+=1,l=e[t];else if(n==="--interactive")w=!0;else if(n==="--min")t+=1,f=e[t];else if(n==="--histogram")u=!0,m=!0;else if(n==="--no-histogram")u=!1,m=!0;else if(n==="--metric")t+=1,o=e[t];else if(n==="--json")k=!0;else if(n==="--host")t+=1,h=e[t];else if(n.startsWith("--host="))h=n.slice(7);else if(n.startsWith("--"))throw new Error(`Unknown option: ${n}
28
28
  Run "hydra budgeter usage --help" for usage.`)}}if(o!==void 0&&o!=="cost"&&o!=="tokens"&&o!=="loc")throw new Error(`Invalid --metric value. Must be 'cost', 'tokens', or 'loc'.
29
29
  Run "hydra budgeter usage --help" for usage.`);const d=o==="loc";if(r!==void 0&&!new Set(["dir","session","model","agent","filetype"]).has(r))throw new Error(`Invalid --by value: ${r}. Must be one of: dir, session, model, agent, filetype.
30
30
  Run "hydra budgeter usage --help" for usage.`);if(r==="filetype"&&!d)throw new Error(`--by filetype requires --metric loc.
31
- Run "hydra budgeter usage --help" for usage.`);let y;if(i!==void 0)try{y=z(i)}catch(t){const n=t;throw new Error(`Invalid --since value: ${n.message}
32
- Run "hydra budgeter usage --help" for usage.`)}r!==void 0&&(p||(s=void 0),m||(u=!1)),!u&&!p&&(s=void 0);let c;if(y!==void 0)c=y;else{const t=new Date;s==="day"||s==="weekday"?t.setDate(t.getDate()-30):s==="week"?t.setMonth(t.getMonth()-6):s==="month"?t.setFullYear(t.getFullYear()-2):t.setHours(t.getHours()-24),c=t}const b=w?!0:void 0,g=o==="tokens",v=f!==void 0?parseFloat(f):void 0,E=await A()??B();(d||r==="filetype")&&await G(E);const S=V(E,{since:c,dir:l,interactive:b,min:v,minMetric:d?"loc":g?"tokens":"cost",host:h??"local"});let R;if(s!==void 0){const t=await J();t!==void 0&&(R=t.map(n=>({sessionId:n.sessionId,ts:n.ts,deltaCost:0,cumulativeCost:n.costCumulative,currency:n.costCurrency,inputTokens:n.contextTokens})))}let $;if(d&&s!==void 0){const t=[];for await(const n of Y(S))t.push(n);$=t}const T=a!==void 0?parseInt(a,10):void 0,I=q(S,R,{by:r,depth:T,bucket:s,since:c,interactive:b,dir:l,tokens:g,loc:d,min:v},$),x=te({sinceRaw:i,effectiveSince:c,bucket:s});if(k)process.stdout.write(Z(I)+`
33
- `);else{const t=K(I,{histogram:u,tokens:g,loc:d,windowLabel:x});process.stdout.write(t)}}function ee(e){const i=(e??"").toLowerCase();switch(i){case"weekday":case"dayofweek":case"day-of-week":case"day_of_week":case"dow":return"weekday";case"hour":case"day":case"week":case"month":return i;default:throw new Error(`unknown --bucket value ${JSON.stringify(e??"")} (expected hour, day, week, month, or weekday)`)}}function te(e){if(e.sinceRaw!==void 0){const i=se(e.sinceRaw);return i!==void 0?`last ${i}`:`since ${e.sinceRaw}`}return e.bucket==="day"||e.bucket==="weekday"?"last 30 days":e.bucket==="week"?"last 6 months":e.bucket==="month"?"last 2 years":e.bucket==="hour"?"last 24 hours":"last 24 hours (default; pass --since to widen)"}function se(e){const i=e.match(/^(\d+)\s*([dhmwy])$/i);if(i===null)return;let s=parseInt(i[1]??"0",10),r=(i[2]??"").toLowerCase();r==="h"&&s%24===0&&(s=s/24,r="d"),r==="d"&&s%7===0&&s<30&&(s=s/7,r="w"),r==="m"&&s%12===0&&(s=s/12,r="y");const a={h:"hour",d:"day",w:"week",m:"month",y:"year"}[r];if(a!==void 0)return`${s} ${a}${s===1?"":"s"}`}async function ne(){const e=P();j(e.debug);const i=new _({daemonWsUrl:e.hydraWsUrl,daemonHttpBase:e.hydraDaemonUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:H});i.start();const s=r=>{L.info(`${r} received \u2014 shutting down`),i.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM")),L.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=per-session (extension_state)`)}function ie(){process.stdout.write(`hydra-acp-budgeter ${C()}
31
+ Run "hydra budgeter usage --help" for usage.`);let y;if(i!==void 0)try{y=K(i)}catch(t){const n=t;throw new Error(`Invalid --since value: ${n.message}
32
+ Run "hydra budgeter usage --help" for usage.`)}r!==void 0&&(p||(s=void 0),m||(u=!1)),!u&&!p&&(s=void 0);let c;if(y!==void 0)c=y;else{const t=new Date;s==="day"||s==="weekday"?t.setDate(t.getDate()-30):s==="week"?t.setMonth(t.getMonth()-6):s==="month"?t.setFullYear(t.getFullYear()-2):t.setHours(t.getHours()-24),c=t}const b=w?!0:void 0,g=o==="tokens",v=f!==void 0?parseFloat(f):void 0,E=await J()??G();(d||r==="filetype")&&await A(E);const S=z(E,{since:c,dir:l,interactive:b,min:v,minMetric:d?"loc":g?"tokens":"cost",host:h??"local"});let R;const $=await Y();$!==void 0&&(R=$.map(t=>({sessionId:t.sessionId,ts:t.ts,deltaCost:0,cumulativeCost:t.costCumulative,currency:t.costCurrency,inputTokens:t.contextTokens})));let I;if(d&&s!==void 0){const t=[];for await(const n of q(S))t.push(n);I=t}const x=a!==void 0?parseInt(a,10):void 0,L=V(S,R,{by:r,depth:x,bucket:s,since:c,interactive:b,dir:l,tokens:g,loc:d,min:v},I),O=se({sinceRaw:i,effectiveSince:c,bucket:s});if(k)process.stdout.write(Q(L)+`
33
+ `);else{const t=Z(L,{histogram:u,tokens:g,loc:d,windowLabel:O});process.stdout.write(t)}}function te(e){const i=(e??"").toLowerCase();switch(i){case"weekday":case"dayofweek":case"day-of-week":case"day_of_week":case"dow":return"weekday";case"hour":case"day":case"week":case"month":return i;default:throw new Error(`unknown --bucket value ${JSON.stringify(e??"")} (expected hour, day, week, month, or weekday)`)}}function se(e){if(e.sinceRaw!==void 0){const i=ne(e.sinceRaw);return i!==void 0?`last ${i}`:`since ${e.sinceRaw}`}return e.bucket==="day"||e.bucket==="weekday"?"last 30 days":e.bucket==="week"?"last 6 months":e.bucket==="month"?"last 2 years":e.bucket==="hour"?"last 24 hours":"last 24 hours (default; pass --since to widen)"}function ne(e){const i=e.match(/^(\d+)\s*([dhmwy])$/i);if(i===null)return;let s=parseInt(i[1]??"0",10),r=(i[2]??"").toLowerCase();r==="h"&&s%24===0&&(s=s/24,r="d"),r==="d"&&s%7===0&&s<30&&(s=s/7,r="w"),r==="m"&&s%12===0&&(s=s/12,r="y");const a={h:"hour",d:"day",w:"week",m:"month",y:"year"}[r];if(a!==void 0)return`${s} ${a}${s===1?"":"s"}`}async function ie(){const e=_();B(e.debug);const i=new H({daemonWsUrl:e.hydraWsUrl,daemonHttpBase:e.hydraDaemonUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:W});i.start();const s=r=>{C.info(`${r} received \u2014 shutting down`),i.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM")),C.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=per-session (extension_state)`)}function re(){process.stdout.write(`hydra-acp-budgeter ${D()}
34
34
 
35
35
  Usage:
36
36
  hydra budgeter [usage] <flags> Report historical cost/usage across sessions
@@ -40,6 +40,6 @@ Flags:
40
40
  -v, --version Print version and exit
41
41
  repair Reconcile recorded cost against the agent ledger
42
42
  -h, --help Show this help
43
- `)}async function re(){const e=process.argv.slice(2);if(e.includes("--version")||e.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${C()}
44
- `);return}if((e[0]==="help"||e.includes("--help")||e.includes("-h"))&&e[0]!=="usage"&&e[0]!=="cost"&&e[0]!=="repair"){ie();return}if(e[0]==="reset"){Q();return}if(e[0]==="repair"){const{runRepair:i}=await import("./cost/repair-cmd.js");await i(e.slice(1));return}if(e[0]==="usage"||e[0]==="cost"){await D(e.slice(1));return}if(e[0]==="run"||process.env.HYDRA_ACP_TOKEN){await ne();return}await D(e)}re().catch(e=>{process.stderr.write(`hydra-acp-budgeter: ${e.message}
43
+ `)}async function oe(){const e=process.argv.slice(2);if(e.includes("--version")||e.includes("-v")){process.stdout.write(`hydra-acp-budgeter ${D()}
44
+ `);return}if((e[0]==="help"||e.includes("--help")||e.includes("-h"))&&e[0]!=="usage"&&e[0]!=="cost"&&e[0]!=="repair"){re();return}if(e[0]==="reset"){X();return}if(e[0]==="repair"){const{runRepair:i}=await import("./cost/repair-cmd.js");await i(e.slice(1));return}if(e[0]==="usage"||e[0]==="cost"){await T(e.slice(1));return}if(e[0]==="run"||process.env.HYDRA_ACP_TOKEN){await ie();return}await T(e)}oe().catch(e=>{process.stderr.write(`hydra-acp-budgeter: ${e.message}
45
45
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/budgeter",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
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",