@hydra-acp/budgeter 0.1.13 → 0.1.14

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 j,resolve as A}from"node:path";import{homedir as O}from"node:os";import{realpathSync as N}from"node:fs";function Q(g){const c=g.match(/^(\d+)\s*([dhmwy])$/i);if(c!==null){const T=c[1],a=c[2];if(T===void 0||a===void 0)throw new Error(`invalid since spec: ${g}`);const k=parseInt(T,10),l=a.toLowerCase(),i=new Date;return l==="d"?i.setDate(i.getDate()-k):l==="h"?i.setHours(i.getHours()-k):l==="w"?i.setDate(i.getDate()-k*7):l==="m"?i.setMonth(i.getMonth()-k):l==="y"&&i.setFullYear(i.getFullYear()-k),i}const t=new Date(g);if(Number.isNaN(t.getTime()))throw new Error(`invalid since spec: ${g}`);return t}function H(g){const c=g.locByFiletype;if(c===void 0)return 0;let t=0;for(const T of Object.keys(c)){const a=c[T];a!==void 0&&(t+=a.added-a.removed)}return t}const B=new Map;function I(g){if(B.has(g))return B.get(g);try{const c=N(g);return B.set(g,c),c}catch{return B.set(g,g),g}}function $(g,c){let t=g;if(c.since!==void 0){const k=c.since.getTime(),l=[];for(const i of t)new Date(i.updatedAt).getTime()<k||l.push(i);t=l}if(c.dir!==void 0){const k=I(A(c.dir)),l=[];for(const i of t)i.cwd===void 0||i.cwd===""||(i.cwd.startsWith(k+"/")||i.cwd===k)&&l.push(i);t=l}if(c.host!==void 0&&c.host!=="all"){const k=c.host,l=[];for(const i of t)k==="local"?(!i.importedFromMachine||i.upstreamSessionId)&&l.push(i):i.importedFromMachine===k&&!i.upstreamSessionId&&l.push(i);t=l}if(c.interactive!==void 0){const k=c.interactive,l=[];for(const i of t)i.interactive===k&&l.push(i);t=l}const T=c.min??0,a=[];for(const k of t){let l;c.minMetric==="tokens"?l=k.contextTokens:c.minMetric==="loc"?l=H(k):l=k.costAmount,l>T&&a.push(k)}return a}function q(g){return g.dir!==void 0?A(g.dir):A(O())}function P(g,c,t){if(g===void 0||g==="")return"<unknown>";const T=I(A(g));if(c===void 0){const y=I(A(O()));return T===y?"~":T.startsWith(y+"/")?"~/"+T.slice(y.length+1):T}const a=I(A(t));let k=j(a,T);if(k===""||k===".")return"<root>";if(k.startsWith(".."))return"<unknown>";const l=k.split("/");if(l.length===0)return"<root>";const i=Math.min(c,l.length),C=l.slice(0,i);return C.length===0?"<root>":C.join("/")}function U(g,c,t={},T){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"?(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 r of g)e+=r.costAmount;let o="";for(const r of g){o=r.costCurrency;break}return{kind:"total",row:{label:"All sessions",costAmount:e,sessionCount:g.length},currency:o}}const l={since:a,dir:t.dir,interactive:t.interactive,min:t.min,minMetric:t.loc===!0?"loc":t.tokens===!0?"tokens":"cost"},i=$(g,l),C=new Map;if(c!==void 0)for(const e of c){const o=C.get(e.sessionId);o===void 0?C.set(e.sessionId,[e]):o.push(e)}let y="";if(c!==void 0&&c.length>0)for(const e of c){y=e.currency;break}else if(i.length>0)for(const e of i){y=e.costCurrency;break}if(t.by==="filetype"&&t.bucket===void 0){const e=new Map,o=new Set;for(const u of i){const n=u.locByFiletype;if(n===void 0)continue;let f=!1;for(const d of Object.keys(n)){const w=n[d];if(w===void 0)continue;let h=e.get(d);h===void 0&&(h={added:0,removed:0,sessions:new Set},e.set(d,h)),h.added+=w.added,h.removed+=w.removed,(w.added>0||w.removed>0)&&(h.sessions.add(u.sessionId),f=!0)}f&&o.add(u.sessionId)}const r=[];for(const[u,n]of e){const f={label:u,costAmount:0,sessionCount:n.sessions.size,linesAdded:n.added,linesRemoved:n.removed};r.push({label:u,items:[f],sessionCount:n.sessions.size})}return{kind:"grouped",groups:r,currency:y,totalSessions:o.size}}const D=e=>t.by==="dir"?P(e.cwd,t.depth,q(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>",M=e=>{const o=new Date(e);if(Number.isNaN(o.getTime()))return"<invalid>";if(t.bucket==="hour"){const r=o.toLocaleDateString(void 0,{month:"2-digit",day:"2-digit"}),u=String(o.getHours()).padStart(2,"0");return`${r} ${u}:00`}if(t.bucket==="day")return o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"});if(t.bucket==="week"){const r=new Date(o),u=r.getDay(),n=r.getDate()-u+(u===0?-6:1);return r.setDate(n),r.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})}return t.bucket==="month"?o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit"}):o.toLocaleDateString(void 0,{year:"numeric",month:"2-digit",day:"2-digit"})};if(t.by===void 0&&t.bucket===void 0){let e=0,o=0,r=0,u=0,n=0,f=0;for(const w of i){e+=w.costAmount;const h=C.get(w.sessionId);if(h!==void 0)for(const s of h)o+=s.deltaCost,t.tokens===!0&&(s.inputTokens!==void 0&&(r+=s.inputTokens),s.outputTokens!==void 0&&(u+=s.outputTokens),s.cacheReadTokens!==void 0&&(n+=s.cacheReadTokens),s.cacheWriteTokens!==void 0&&(f+=s.cacheWriteTokens))}const d={label:"All sessions",costAmount:e,deltaCost:o,sessionCount:i.length};if(t.tokens===!0&&(d.inputTokens=r,d.outputTokens=u,d.cacheReadTokens=n,d.cacheWriteTokens=f),t.loc===!0){let w=0,h=0;for(const s of i){const m=s.locByFiletype;if(m!==void 0)for(const p of Object.keys(m)){const b=m[p];b!==void 0&&(w+=b.added,h+=b.removed)}}d.linesAdded=w,d.linesRemoved=h}return{kind:"total",row:d,currency:y}}if(t.by!==void 0&&t.bucket===void 0){const e=new Map;for(const r of i){const u=D(r);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+=r.costAmount,n.rows.sessionCount=(n.rows.sessionCount??0)+1,t.tokens===!0&&(n.rows.inputTokens===void 0&&(n.rows.inputTokens=0),n.rows.inputTokens+=r.contextTokens),t.loc===!0){const d=r.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 w of Object.keys(d)){const h=d[w];h!==void 0&&(n.rows.linesAdded+=h.added,n.rows.linesRemoved+=h.removed)}}}const f=C.get(r.sessionId);if(f!==void 0)for(const d of f)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 o=[];for(const r of e.values())o.push({label:r.label,items:[r.rows],sessionCount:r.rows.sessionCount});return{kind:"grouped",groups:o,currency:y}}const _=new Map;for(const e of i)_.set(e.sessionId,e);const R=new Map;if(c!==void 0){for(const e of c){if(!_.has(e.sessionId))continue;const o=R.get(e.sessionId);o===void 0?R.set(e.sessionId,[e]):o.push(e)}for(const e of R.values())e.sort((o,r)=>o.ts.localeCompare(r.ts))}const L=(e,o)=>{e._sessions===void 0&&(e._sessions=new Set),e._sessions.add(o),e.sessionCount=e._sessions.size},F=e=>{delete e._sessions},G=(e,o,r)=>{const u=M(o.ts);let n=e.get(u);n===void 0&&(n={bucket:u,costAmount:0,deltaCost:0,sessionCount:0},e.set(u,n)),n.costAmount+=r,n.deltaCost+=r,L(n,o.sessionId),t.tokens===!0&&(n.inputTokens===void 0||(o.inputTokens??0)>n.inputTokens)&&(n.inputTokens=o.inputTokens??n.inputTokens??0)};if(t.loc===!0&&t.bucket!==void 0){const e=new Set;for(const s of i)e.add(s.sessionId);const o=new Map;for(const s of i)o.set(s.sessionId,s);const r=(s,m)=>{let p=s.get(m);return p===void 0&&(p={bucket:m,costAmount:0,deltaCost:0,sessionCount:0,linesAdded:0,linesRemoved:0},s.set(m,p)),(p.linesAdded??void 0)===void 0&&(p.linesAdded=0),(p.linesRemoved??void 0)===void 0&&(p.linesRemoved=0),p},u=(s,m)=>{s.linesAdded=(s.linesAdded??0)+m.linesAdded,s.linesRemoved=(s.linesRemoved??0)+m.linesRemoved,s._sessions===void 0&&(s._sessions=new Set),s._sessions.add(m.sessionId),s.sessionCount=s._sessions.size},n=s=>{delete s._sessions},f=T??[];if(t.by==="filetype"){const s=new Map,m=new Set;for(const b of f){if(!e.has(b.sessionId)||a!==void 0&&new Date(b.ts)<a)continue;let v=s.get(b.filetype);v===void 0&&(v={label:b.filetype,buckets:new Map,sessions:new Set},s.set(b.filetype,v));const S=r(v.buckets,M(b.ts));u(S,b),v.sessions.add(b.sessionId),m.add(b.sessionId)}const p=[];for(const b of s.values()){const v=Array.from(b.buckets.values());for(const S of v)n(S);v.sort((S,E)=>S.bucket.localeCompare(E.bucket)),v.length>0&&p.push({label:b.label,items:v,sessionCount:b.sessions.size})}return{kind:"timeSeriesGrouped",groups:p,currency:y,totalSessions:m.size}}if(t.by!==void 0){const s=new Map;for(const p of f){if(!e.has(p.sessionId)||a!==void 0&&new Date(p.ts)<a)continue;const b=o.get(p.sessionId);if(b===void 0)continue;const v=D(b);let S=s.get(v);S===void 0&&(S={label:v,buckets:new Map,sessions:new Set},s.set(v,S));const E=r(S.buckets,M(p.ts));u(E,p),S.sessions.add(p.sessionId)}const m=[];for(const p of s.values()){const b=Array.from(p.buckets.values());for(const v of b)n(v);b.sort((v,S)=>v.bucket.localeCompare(S.bucket)),b.length>0&&m.push({label:p.label,items:b,sessionCount:p.sessions.size})}return{kind:"timeSeriesGrouped",groups:m,currency:y}}const d=new Map,w=new Set;for(const s of f){if(!e.has(s.sessionId)||a!==void 0&&new Date(s.ts)<a)continue;const m=r(d,M(s.ts));u(m,s),w.add(s.sessionId)}const h=Array.from(d.values());for(const s of h)n(s);return h.sort((s,m)=>s.bucket.localeCompare(m.bucket)),{kind:"timeSeries",timeSeries:h,currency:y,totalSessions:w.size}}if(t.by!==void 0&&t.bucket!==void 0){const e=new Map,o=u=>{const n=D(u);let f=e.get(n);return f===void 0&&(f={label:n,buckets:new Map,sessions:new Set},e.set(n,f)),f};for(const u of i){const n=R.get(u.sessionId);if(n===void 0||n.length===0)continue;const f=o(u),d=f.buckets;f.sessions.add(u.sessionId);const w=n[0];if(w===void 0)continue;let h=w.cumulativeCost;for(let s=1;s<n.length;s++){const m=n[s];if(m===void 0)continue;const p=Math.max(0,m.cumulativeCost-h);h=m.cumulativeCost,!(a!==void 0&&new Date(m.ts)<a)&&G(d,m,p)}}const r=[];for(const u of e.values()){const n=Array.from(u.buckets.values());for(const f of n)F(f);n.sort((f,d)=>f.bucket.localeCompare(d.bucket)),n.length>0&&r.push({label:u.label,items:n,sessionCount:u.sessions.size})}return{kind:"timeSeriesGrouped",groups:r,currency:y}}const W=new Map,z=new Set;for(const e of i){const o=R.get(e.sessionId);if(o===void 0||o.length===0)continue;const r=o[0];if(r===void 0)continue;let u=r.cumulativeCost,n=!1;for(let f=1;f<o.length;f++){const d=o[f];if(d===void 0)continue;const w=Math.max(0,d.cumulativeCost-u);u=d.cumulativeCost,!(a!==void 0&&new Date(d.ts)<a)&&(G(W,d,w),n=!0)}n&&z.add(e.sessionId)}const x=Array.from(W.values());for(const e of x)F(e);return x.sort((e,o)=>e.bucket.localeCompare(o.bucket)),{kind:"timeSeries",timeSeries:x,currency:y,totalSessions:z.size}}export{U as aggregate,$ as applyFilters,Q as parseSince};
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};
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
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 P}from"node:url";import{loadConfig as H}from"./config.js";import{BudgeterBridge as M}from"./bridge.js";import{DEFAULT_RULE as W}from"./rule.js";import{logger as j,setDebug as G}from"./util/log.js";import{scanSessions as _,enrichSessionsWithLoc as B}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 K}from"./cost/aggregate.js";import{renderText as Z,renderJson as z}from"./cost/format.js";const D=j("main");function L(){try{const e=F(P(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 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 H}from"./bridge.js";import{DEFAULT_RULE as W}from"./rule.js";import{logger as j,setDebug as G}from"./util/log.js";import{scanSessions as _,enrichSessionsWithLoc as B}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 K}from"./cost/aggregate.js";import{renderText as Z,renderJson as z}from"./cost/format.js";const D=j("main");function L(){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.
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
5
  `),process.exit(2)}const X=`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)
9
- --bucket <hour|day|week|month> Group results into time buckets (implies --since 24h/30d/6m/2y)
9
+ --bucket <hour|day|week|month|weekday> Group results into time buckets (implies
10
+ --since 24h/30d/6m/2y; weekday collapses the last 30 days
11
+ into Mon-Sun)
10
12
  --by <dir|session|model|agent|filetype> Group by dimension (filetype requires --metric loc)
11
13
  --depth <N> Depth for --by dir grouping (default: 1)
12
14
  --dir <path> Only include sessions under this directory prefix
@@ -22,13 +24,13 @@ Options:
22
24
  from Edit/Write tool diffs in session history.
23
25
  --json Output as JSON
24
26
  --help Show this help message`;async function T(e){if(e.includes("--help")){process.stdout.write(X+`
25
- `);return}let r,n="hour",i,d,l,w=!1,f,u=!0,o,b=!1,h,m=!1,p=!1;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],m=!0;else if(s==="--by")t+=1,i=e[t];else if(s==="--depth")t+=1,d=e[t];else if(s==="--dir")t+=1,l=e[t];else if(s==="--interactive")w=!0;else if(s==="--min")t+=1,f=e[t];else if(s==="--histogram")u=!0,p=!0;else if(s==="--no-histogram")u=!1,p=!0;else if(s==="--metric")t+=1,o=e[t];else if(s==="--json")b=!0;else if(s==="--host")t+=1,h=e[t];else if(s.startsWith("--host="))h=s.slice(7);else if(s.startsWith("--"))throw new Error(`Unknown option: ${s}
27
+ `);return}let r,s="hour",i,d,l,w=!1,f,u=!0,o,b=!1,h,m=!1,p=!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],m=!0;else if(n==="--by")t+=1,i=e[t];else if(n==="--depth")t+=1,d=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,p=!0;else if(n==="--no-histogram")u=!1,p=!0;else if(n==="--metric")t+=1,o=e[t];else if(n==="--json")b=!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
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'.
27
29
  Run "hydra budgeter usage --help" for usage.`);const a=o==="loc";if(i!==void 0&&!new Set(["dir","session","model","agent","filetype"]).has(i))throw new Error(`Invalid --by value: ${i}. Must be one of: dir, session, model, agent, filetype.
28
30
  Run "hydra budgeter usage --help" for usage.`);if(i==="filetype"&&!a)throw new Error(`--by filetype requires --metric loc.
29
- Run "hydra budgeter usage --help" for usage.`);let g;if(r!==void 0)try{g=K(r)}catch(t){const s=t;throw new Error(`Invalid --since value: ${s.message}
30
- Run "hydra budgeter usage --help" for usage.`)}i!==void 0&&(m||(n=void 0),p||(u=!1)),!u&&!m&&(n=void 0);let c;if(g!==void 0)c=g;else{const t=new Date;n==="day"?t.setDate(t.getDate()-30):n==="week"?t.setMonth(t.getMonth()-6):n==="month"?t.setFullYear(t.getFullYear()-2):t.setHours(t.getHours()-24),c=t}const v=w?!0:void 0,y=o==="tokens",k=f!==void 0?parseFloat(f):void 0,E=await A()??_();(a||i==="filetype")&&await B(E);const S=V(E,{since:c,dir:l,interactive:v,min:k,minMetric:a?"loc":y?"tokens":"cost",host:h??"local"});let R;if(n!==void 0){const t=await J();t!==void 0&&(R=t.map(s=>({sessionId:s.sessionId,ts:s.ts,deltaCost:0,cumulativeCost:s.costCumulative,currency:s.costCurrency,inputTokens:s.contextTokens})))}let I;if(a&&n!==void 0){const t=[];for await(const s of Y(S))t.push(s);I=t}const C=d!==void 0?parseInt(d,10):void 0,$=q(S,R,{by:i,depth:C,bucket:n,since:c,interactive:v,dir:l,tokens:y,loc:a,min:k},I),x=ee({sinceRaw:r,effectiveSince:c,bucket:n});if(b)process.stdout.write(z($)+`
31
- `);else{const t=Z($,{histogram:u,tokens:y,loc:a,windowLabel:x});process.stdout.write(t)}}function ee(e){if(e.sinceRaw!==void 0){const r=te(e.sinceRaw);return r!==void 0?`last ${r}`:`since ${e.sinceRaw}`}return e.bucket==="day"?"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 te(e){const r=e.match(/^(\d+)\s*([dhmwy])$/i);if(r===null)return;let n=parseInt(r[1]??"0",10),i=(r[2]??"").toLowerCase();i==="h"&&n%24===0&&(n=n/24,i="d"),i==="d"&&n%7===0&&n<30&&(n=n/7,i="w"),i==="m"&&n%12===0&&(n=n/12,i="y");const d={h:"hour",d:"day",w:"week",m:"month",y:"year"}[i];if(d!==void 0)return`${n} ${d}${n===1?"":"s"}`}async function se(){const e=H();G(e.debug);const r=new M({daemonWsUrl:e.hydraWsUrl,daemonHttpBase:e.hydraDaemonUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:W});r.start();const n=i=>{D.info(`${i} received \u2014 shutting down`),r.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>n("SIGINT")),process.on("SIGTERM",()=>n("SIGTERM")),D.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=per-session (extension_state)`)}function ne(){process.stdout.write(`hydra-acp-budgeter ${L()}
31
+ Run "hydra budgeter usage --help" for usage.`);let g;if(r!==void 0)try{g=K(r)}catch(t){const n=t;throw new Error(`Invalid --since value: ${n.message}
32
+ Run "hydra budgeter usage --help" for usage.`)}i!==void 0&&(m||(s=void 0),p||(u=!1)),!u&&!m&&(s=void 0);let c;if(g!==void 0)c=g;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 v=w?!0:void 0,y=o==="tokens",k=f!==void 0?parseFloat(f):void 0,E=await A()??_();(a||i==="filetype")&&await B(E);const S=V(E,{since:c,dir:l,interactive:v,min:k,minMetric:a?"loc":y?"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 I;if(a&&s!==void 0){const t=[];for await(const n of Y(S))t.push(n);I=t}const C=d!==void 0?parseInt(d,10):void 0,$=q(S,R,{by:i,depth:C,bucket:s,since:c,interactive:v,dir:l,tokens:y,loc:a,min:k},I),x=ee({sinceRaw:r,effectiveSince:c,bucket:s});if(b)process.stdout.write(z($)+`
33
+ `);else{const t=Z($,{histogram:u,tokens:y,loc:a,windowLabel:x});process.stdout.write(t)}}function ee(e){if(e.sinceRaw!==void 0){const r=te(e.sinceRaw);return r!==void 0?`last ${r}`:`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 te(e){const r=e.match(/^(\d+)\s*([dhmwy])$/i);if(r===null)return;let s=parseInt(r[1]??"0",10),i=(r[2]??"").toLowerCase();i==="h"&&s%24===0&&(s=s/24,i="d"),i==="d"&&s%7===0&&s<30&&(s=s/7,i="w"),i==="m"&&s%12===0&&(s=s/12,i="y");const d={h:"hour",d:"day",w:"week",m:"month",y:"year"}[i];if(d!==void 0)return`${s} ${d}${s===1?"":"s"}`}async function se(){const e=P();G(e.debug);const r=new H({daemonWsUrl:e.hydraWsUrl,daemonHttpBase:e.hydraDaemonUrl,token:e.hydraToken,softLimit:e.softLimit,hardLimit:e.hardLimit,currency:e.currency,rule:W});r.start();const s=i=>{D.info(`${i} received \u2014 shutting down`),r.stop(),setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM")),D.info(`hydra-acp-budgeter up; daemon=${e.hydraDaemonUrl} soft=${e.softLimit} hard=${e.hardLimit} ${e.currency} state=per-session (extension_state)`)}function ne(){process.stdout.write(`hydra-acp-budgeter ${L()}
32
34
 
33
35
  Usage:
34
36
  hydra budgeter [usage] <flags> Report historical cost/usage across sessions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/budgeter",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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",