tokentracker-cli 0.3.0 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dashboard/dist/assets/DashboardPage-C025vDYN.js +12 -0
- package/dashboard/dist/assets/LandingExtras-BDqTHI3x.js +1 -0
- package/dashboard/dist/assets/{LeaderboardPage-T0DeP6QE.js → LeaderboardPage-BWJsZZGo.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-DeXNLaq5.js → LeaderboardProfilePage-Fm0S6umm.js} +1 -1
- package/dashboard/dist/assets/MatrixRain-_cKLHK44.js +1 -0
- package/dashboard/dist/assets/MatrixShell-DyMWapvD.js +1 -0
- package/dashboard/dist/assets/main-ByQlanyj.css +1 -0
- package/dashboard/dist/assets/{main-CvjKTqMk.js → main-DSjEYl46.js} +25 -25
- package/dashboard/dist/assets/vibeusage-api-B9iVLrJk.js +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/sync.js +28 -2
- package/src/lib/rollout.js +200 -0
- package/dashboard/dist/assets/DashboardPage-CdsMxGgP.js +0 -12
- package/dashboard/dist/assets/LandingExtras-BPCBL0OB.js +0 -1
- package/dashboard/dist/assets/MatrixRain-DNvnkted.js +0 -1
- package/dashboard/dist/assets/MatrixShell-p6MtAS33.js +0 -1
- package/dashboard/dist/assets/main-DJvWJCv5.css +0 -1
- package/dashboard/dist/assets/vibeusage-api-By1xP_lg.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as p,al as G,d as ie,am as j,an as U,ao as ce,ap as ue,aq as le,ar as fe,as as de,at as ge,au as ke,a6 as pe,av as me,aw as ye,ax as he}from"./main-DSjEYl46.js";const be="Backend runtime unavailable (InsForge). Please retry later.",g={usageSummary:"vibeusage-usage-summary",usageDaily:"vibeusage-usage-daily",usageHourly:"vibeusage-usage-hourly",usageMonthly:"vibeusage-usage-monthly",usageHeatmap:"vibeusage-usage-heatmap",usageModelBreakdown:"vibeusage-usage-model-breakdown",projectUsageSummary:"vibeusage-project-usage-summary",leaderboard:"vibeusage-leaderboard",leaderboardProfile:"vibeusage-leaderboard-profile",userStatus:"vibeusage-user-status",linkCodeInit:"vibeusage-link-code-init",publicViewProfile:"vibeusage-public-view-profile",publicVisibility:"vibeusage-public-visibility"},J="/functions",we="/api/functions",y={business:"business",probe:"probe"};let z=null;async function d(e){return await ie(e)}async function xe({baseUrl:e,accessToken:t,signal:s}={}){const a=await d(t),n=he(new Date);return await m({baseUrl:e,accessToken:a,slug:g.usageSummary,params:{from:n,to:n},fetchOptions:{cache:"no-store",signal:s},retry:!1,requestKind:y.probe}),{status:200}}async function Ee({baseUrl:e,accessToken:t,from:s,to:a,source:n,model:r,timeZone:l,tzOffsetMinutes:o,rolling:c=!1}={}){const u=await d(t);if(p())return ue({from:s,to:a,seed:u,rolling:c});const i=M({timeZone:l,tzOffsetMinutes:o}),f=A({source:n,model:r}),_=c?{rolling:"1"}:{};return m({baseUrl:e,accessToken:u,slug:g.usageSummary,params:{from:s,to:a,...f,...i,..._}})}async function Re({baseUrl:e,accessToken:t,from:s,to:a,source:n,limit:r,timeZone:l,tzOffsetMinutes:o}={}){const c=await d(t);if(p())return fe({seed:c,limit:r});const u=M({timeZone:l,tzOffsetMinutes:o}),f={...A({source:n}),...u};return s&&(f.from=s),a&&(f.to=a),r!=null&&(f.limit=String(r)),m({baseUrl:e,accessToken:c,slug:g.projectUsageSummary,params:f})}async function Ce({baseUrl:e,accessToken:t,period:s,metric:a,limit:n,offset:r}={}){const l=await d(t);if(p())return G({seed:l,period:s,metric:a,limit:n,offset:r});const c=(typeof s=="string"?s:"week").trim().toLowerCase(),i={period:c==="month"||c==="total"||c==="week"?c:"week"};return a&&(i.metric=String(a)),n!=null&&(i.limit=String(n)),r!=null&&(i.offset=String(r)),m({baseUrl:e,accessToken:l,slug:g.leaderboard,params:i})}async function De({baseUrl:e,accessToken:t}={}){const s=await d(t);return p()?{enabled:!1,updated_at:null,share_token:null}:m({baseUrl:e,accessToken:s,slug:g.publicVisibility})}async function ze({baseUrl:e,accessToken:t,enabled:s}={}){const a=await d(t);return p()?{enabled:!!s,updated_at:new Date().toISOString(),share_token:s?"pv1-mock-token":null}:X({baseUrl:e,accessToken:a,slug:g.publicVisibility,body:{enabled:!!s}})}async function Ue({baseUrl:e,accessToken:t,userId:s,period:a}={}){const n=await d(t);if(p()){const r=G({seed:n,period:a,metric:"all",limit:250,offset:0}),o=(Array.isArray(r?.entries)?r.entries:[]).find(c=>c?.user_id===s)||null;return{period:r?.period??"week",from:r?.from??null,to:r?.to??null,generated_at:r?.generated_at??new Date().toISOString(),entry:o?{user_id:o.user_id??null,display_name:o.display_name??null,avatar_url:o.avatar_url??null,rank:o.rank??null,gpt_tokens:o.gpt_tokens??"0",claude_tokens:o.claude_tokens??"0",other_tokens:o.other_tokens??"0",total_tokens:o.total_tokens??"0"}:null}}return m({baseUrl:e,accessToken:n,slug:g.leaderboardProfile,params:{user_id:String(s||""),period:String(a||"")}})}async function Ie({baseUrl:e,accessToken:t}={}){const s=await d(t);if(p()){const a=new Date().toISOString();return{user_id:"mock-user",created_at:a,pro:{active:!0,sources:["mock"],expires_at:null,partial:!1,as_of:a},subscriptions:{partial:!1,as_of:a,items:[{tool:"codex",provider:"openai",product:"chatgpt",plan_type:"pro",updated_at:a},{tool:"claude",provider:"anthropic",product:"subscription",plan_type:"max",rate_limit_tier:"default_claude_max_5x",updated_at:a}]},install:{partial:!1,as_of:a,has_active_device_token:!1,has_active_device:!1,active_device_tokens:0,active_devices:0,latest_token_activity_at:null,latest_device_seen_at:null}}}return m({baseUrl:e,accessToken:s,slug:g.userStatus})}async function je({signal:e}={}){const t=await fetch("/functions/vibeusage-local-sync",{method:"POST",headers:{Accept:"application/json"},cache:"no-store",signal:e}),s=await t.json().catch(()=>({ok:!1,error:`Local sync request failed with HTTP ${t.status}`}));if(!t.ok||s?.ok===!1){const a=s?.error||s?.message||`Local sync request failed with HTTP ${t.status}`,n=new Error(a);throw n.status=t.status,n.statusCode=t.status,n.payload=s,n}return s}async function He({baseUrl:e,accessToken:t,from:s,to:a,source:n,timeZone:r,tzOffsetMinutes:l}={}){const o=await d(t);if(p())return le({from:s,to:a,seed:o});const c=M({timeZone:r,tzOffsetMinutes:l}),u=A({source:n});return m({baseUrl:e,accessToken:o,slug:g.usageModelBreakdown,params:{from:s,to:a,...u,...c}})}async function Le({baseUrl:e,accessToken:t,from:s,to:a,source:n,model:r,timeZone:l,tzOffsetMinutes:o}={}){const c=await d(t);if(p())return ce({from:s,to:a,seed:c});const u=M({timeZone:l,tzOffsetMinutes:o}),i=A({source:n,model:r});return m({baseUrl:e,accessToken:c,slug:g.usageDaily,params:{from:s,to:a,...i,...u}})}async function qe({baseUrl:e,accessToken:t,day:s,source:a,model:n,timeZone:r,tzOffsetMinutes:l}={}){const o=await d(t);if(p())return de({day:s,seed:o});const c=M({timeZone:r,tzOffsetMinutes:l}),u=A({source:a,model:n});return m({baseUrl:e,accessToken:o,slug:g.usageHourly,params:s?{day:s,...u,...c}:{...u,...c}})}async function Be({baseUrl:e,accessToken:t,months:s,to:a,source:n,model:r,timeZone:l,tzOffsetMinutes:o}={}){const c=await d(t);if(p())return ge({months:s,to:a,seed:c});const u=M({timeZone:l,tzOffsetMinutes:o}),i=A({source:n,model:r});return m({baseUrl:e,accessToken:c,slug:g.usageMonthly,params:{...s?{months:String(s)}:{},...a?{to:a}:{},...i,...u}})}async function Fe({baseUrl:e,accessToken:t,weeks:s,to:a,weekStartsOn:n,source:r,model:l,timeZone:o,tzOffsetMinutes:c}={}){const u=await d(t);if(p())return ke({weeks:s,to:a,weekStartsOn:n,seed:u});const i=M({timeZone:o,tzOffsetMinutes:c}),f=A({source:r,model:l});return m({baseUrl:e,accessToken:u,slug:g.usageHeatmap,params:{weeks:String(s),to:a,week_starts_on:n,...f,...i}})}async function Ne({baseUrl:e,accessToken:t}={}){const s=await d(t);return p()?{link_code:"mock_link_code",expires_at:new Date(Date.now()+10*6e4).toISOString()}:X({baseUrl:e,accessToken:s,slug:g.linkCodeInit,body:{}})}async function Ve({baseUrl:e,accessToken:t}={}){const s=await d(t);return m({baseUrl:e,accessToken:s,slug:g.publicViewProfile})}function M({timeZone:e,tzOffsetMinutes:t}={}){const s={},a=typeof e=="string"?e.trim():"";return a&&(s.tz=a),Number.isFinite(t)&&(s.tz_offset_minutes=String(Math.trunc(t))),s}function A({source:e,model:t}={}){const s={},a=typeof e=="string"?e.trim().toLowerCase():"";a&&(s.source=a);const n=typeof t=="string"?t.trim():"";return n&&(s.model=n),s}async function m({baseUrl:e,accessToken:t,slug:s,params:a,fetchOptions:n,errorPrefix:r,retry:l,requestKind:o=y.business,skipSessionExpiry:c=!1,allowRefresh:u=!0}={}){let i=await d(t),f=x(i),_=j({baseUrl:e,accessToken:i??void 0}).getHttpClient();const E=te(l,"GET"),T=c?y.probe:o;let v=0;const{primaryPath:R,fallbackPath:C}=O(s);for(;;)try{const S=await $({http:_,primaryPath:R,fallbackPath:C,params:a,fetchOptions:n});return H({hadAccessToken:f,accessToken:i}),S}catch(S){const h=S;if(h?.name==="AbortError")throw S;let b=null;const L=h?.statusCode??h?.status;if(u&&Z({status:L,requestKind:T,hadAccessToken:f,accessToken:i})){const k=(await ee())?.accessToken??null;if(x(k)){const D=j({baseUrl:e,accessToken:k}).getHttpClient();i=k,f=!0,_=D;try{const w=await $({http:D,primaryPath:R,fallbackPath:C,params:a,fetchOptions:n});return H({hadAccessToken:!0,accessToken:k}),w}catch(w){const B=w?.statusCode??w?.status;V({status:B,hadAccessToken:!0,accessToken:k,skipSessionExpiry:T===y.probe})&&U(),b=P(w,{errorPrefix:r,hadAccessToken:!0,accessToken:k,skipSessionExpiry:!0})}}else I({hadAccessToken:f,accessToken:i,skipSessionExpiry:T===y.probe})&&U();b??=P(h,{errorPrefix:r,hadAccessToken:f,accessToken:i,skipSessionExpiry:!0})}if(b??=P(h,{errorPrefix:r,hadAccessToken:f,accessToken:i,skipSessionExpiry:T===y.probe}),!se({err:b,attempt:v,retryOptions:E}))throw b;const q=ae({retryOptions:E,attempt:v});await ne(q),v+=1}}async function X({baseUrl:e,accessToken:t,slug:s,body:a,fetchOptions:n,errorPrefix:r,retry:l,requestKind:o=y.business,skipSessionExpiry:c=!1,allowRefresh:u=!0}={}){let i=await d(t),f=x(i),_=j({baseUrl:e,accessToken:i??void 0}).getHttpClient();const E=te(l,"POST"),T=c?y.probe:o;let v=0;const{primaryPath:R,fallbackPath:C}=O(s);for(;;)try{const S=await K({http:_,primaryPath:R,fallbackPath:C,body:a,fetchOptions:n});return H({hadAccessToken:f,accessToken:i}),S}catch(S){const h=S;if(h?.name==="AbortError")throw S;let b=null;const L=h?.statusCode??h?.status;if(u&&Z({status:L,requestKind:T,hadAccessToken:f,accessToken:i})){const k=(await ee())?.accessToken??null;if(x(k)){const D=j({baseUrl:e,accessToken:k}).getHttpClient();i=k,f=!0,_=D;try{const w=await K({http:D,primaryPath:R,fallbackPath:C,body:a,fetchOptions:n});return H({hadAccessToken:!0,accessToken:k}),w}catch(w){const B=w?.statusCode??w?.status;V({status:B,hadAccessToken:!0,accessToken:k,skipSessionExpiry:T===y.probe})&&U(),b=P(w,{errorPrefix:r,hadAccessToken:!0,accessToken:k,skipSessionExpiry:!0})}}else I({hadAccessToken:f,accessToken:i,skipSessionExpiry:T===y.probe})&&U();b??=P(h,{errorPrefix:r,hadAccessToken:f,accessToken:i,skipSessionExpiry:!0})}if(b??=P(h,{errorPrefix:r,hadAccessToken:f,accessToken:i,skipSessionExpiry:T===y.probe}),!se({err:b,attempt:v,retryOptions:E}))throw b;const q=ae({retryOptions:E,attempt:v});await ne(q),v+=1}}function O(e){const t=Te(e),s=`${N(J)}/${t}`,a=`${N(we)}/${t}`;return{primaryPath:s,fallbackPath:a}}function Te(e){return(typeof e=="string"?e.trim():"").replace(/^\/+/,"")}function N(e){const t=typeof e=="string"?e.trim():"";return t.endsWith("/")?t.slice(0,-1):t}async function $({http:e,primaryPath:t,fallbackPath:s,params:a,fetchOptions:n}={}){try{return await e.get(t,{params:a,...n||{}})}catch(r){if(!Q(r,t))throw r;return await e.get(s,{params:a,...n||{}})}}async function K({http:e,primaryPath:t,fallbackPath:s,body:a,fetchOptions:n}={}){try{return await W({http:e,path:t,body:a,fetchOptions:n})}catch(r){if(!Q(r,t))throw r;return await W({http:e,path:s,body:a,fetchOptions:n})}}async function W({http:e,path:t,body:s,fetchOptions:a}={}){return await e.post(t,s,{...a||{}})}function Q(e,t){return!t||!t.startsWith(`${N(J)}/`)?!1:(e?.statusCode??e?.status)===404}function P(e,{errorPrefix:t,hadAccessToken:s,accessToken:a,skipSessionExpiry:n}={}){const r=typeof e?.message=="string"?e.message.trim():"",l=typeof e?.error=="string"?e.error.trim():"",o=r||l||String(e||"Unknown error"),c=_e(o),u=new Error(t?`${t}: ${c}`:c);u.cause=e;const i=e?.statusCode??e?.status;return V({status:i,hadAccessToken:s,accessToken:a,skipSessionExpiry:n})&&U(),typeof i=="number"&&(u.status=i,u.statusCode=i),u.retryable=ve(i)||Me(o),c!==o&&(u.originalMessage=o),e?.nextActions&&(u.nextActions=e.nextActions),e?.error&&(u.error=e.error),u}function I({hadAccessToken:e,accessToken:t,skipSessionExpiry:s}={}){return s||!e||!x(t)?!1:Ae(t)}function V({status:e,hadAccessToken:t,accessToken:s,skipSessionExpiry:a}={}){return e!==401?!1:I({hadAccessToken:t,accessToken:s,skipSessionExpiry:a})}function Se({hadAccessToken:e,accessToken:t}={}){return I({hadAccessToken:e,accessToken:t})}function H({hadAccessToken:e,accessToken:t}={}){Se({hadAccessToken:e,accessToken:t})&&me()}function _e(e){return Y(e)?be:String(e||"Unknown error")}function Y(e){const t=String(e||"").toLowerCase();return t?!!(t.includes("deno:")||t.includes("deno")||t.includes("econnreset")||t.includes("econnrefused")||t.includes("etimedout")||t.includes("timeout")&&t.includes("request")||t.includes("upstream")&&(t.includes("deno")||t.includes("connect"))):!1}function Z({status:e,requestKind:t,hadAccessToken:s,accessToken:a}={}){return e!==401||t!==y.business?!1:I({hadAccessToken:s,accessToken:a})}async function ee(){return z||(z=ye.auth.getCurrentSession().then(({data:e})=>e?.session??null).catch(()=>null).finally(()=>{z=null}),z)}function ve(e){return e===502||e===503||e===504}function Me(e){const t=String(e||"").toLowerCase();return t?!!(Y(t)||t.includes("econnreset")||t.includes("econnrefused")||t.includes("etimedout")||t.includes("timeout")||t.includes("networkerror")||t.includes("failed to fetch")||t.includes("socket hang up")||t.includes("connection reset")):!1}function te(e,t){const a=(t||"GET").toUpperCase()==="GET"?{maxRetries:2,baseDelayMs:300,maxDelayMs:1500,jitterRatio:.2}:{maxRetries:0,baseDelayMs:0,maxDelayMs:0,jitterRatio:0};if(e==null)return a;if(e===!1)return{maxRetries:0,baseDelayMs:0,maxDelayMs:0,jitterRatio:0};const n=F(e.maxRetries??a.maxRetries,0,10),r=F(e.baseDelayMs??a.baseDelayMs,50,6e4),l=F(e.maxDelayMs??a.maxDelayMs,r,12e4),o=typeof e.jitterRatio=="number"?Math.max(0,Math.min(.5,e.jitterRatio)):a.jitterRatio;return{maxRetries:n,baseDelayMs:r,maxDelayMs:l,jitterRatio:o}}function x(e){return!!pe(e)}function Ae(e){if(!x(e))return!1;const t=e.split(".");return t.length!==3?!1:t.every(s=>/^[A-Za-z0-9_-]+$/.test(s))}function se({err:e,attempt:t,retryOptions:s}={}){return!s||s.maxRetries<=0||t>=s.maxRetries?!1:!!(e&&e.retryable)}function ae({retryOptions:e,attempt:t}={}){if(!e||e.maxRetries<=0)return 0;const s=e.baseDelayMs*Math.pow(2,t),a=Math.min(e.maxDelayMs,s),n=a*e.jitterRatio*Math.random();return Math.round(a+n)}function F(e,t,s){const a=Number(e);return Number.isFinite(a)?Math.min(s,Math.max(t,Math.floor(a))):t}function ne(e){return!e||e<=0?Promise.resolve():new Promise(t=>setTimeout(t,e))}export{Le as a,Re as b,qe as c,Be as d,Ee as e,He as f,Fe as g,De as h,Ve as i,Ie as j,Ce as k,Ue as l,xe as p,Ne as r,ze as s,je as t};
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
</script>
|
|
110
|
-
<script type="module" crossorigin src="/assets/main-
|
|
111
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
110
|
+
<script type="module" crossorigin src="/assets/main-DSjEYl46.js"></script>
|
|
111
|
+
<link rel="stylesheet" crossorigin href="/assets/main-ByQlanyj.css">
|
|
112
112
|
</head>
|
|
113
113
|
<body>
|
|
114
114
|
<main class="aeo-seed-content" aria-label="Token Tracker AI-readable summary">
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
55
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-DSjEYl46.js"></script>
|
|
55
|
+
<link rel="stylesheet" crossorigin href="/assets/main-ByQlanyj.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
|
58
58
|
<main class="aeo-seed-content" aria-label="Token Tracker share page summary">
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, OpenCode, OpenClaw)",
|
|
3
|
+
"version": "0.3.5",
|
|
4
|
+
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Kiro, Gemini, OpenCode, OpenClaw)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tracker": "bin/tracker.js",
|
package/src/commands/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require("node:os");
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
|
+
const fssync = require("node:fs");
|
|
4
5
|
const cp = require("node:child_process");
|
|
5
6
|
|
|
6
7
|
const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
|
|
@@ -10,6 +11,7 @@ const {
|
|
|
10
11
|
listGeminiSessionFiles,
|
|
11
12
|
listOpencodeMessageFiles,
|
|
12
13
|
readOpencodeDbMessages,
|
|
14
|
+
resolveKiroDbPath,
|
|
13
15
|
parseRolloutIncremental,
|
|
14
16
|
parseClaudeIncremental,
|
|
15
17
|
parseGeminiIncremental,
|
|
@@ -17,6 +19,7 @@ const {
|
|
|
17
19
|
parseOpencodeDbIncremental,
|
|
18
20
|
parseOpenclawIncremental,
|
|
19
21
|
parseCursorApiIncremental,
|
|
22
|
+
parseKiroIncremental,
|
|
20
23
|
} = require("../lib/rollout");
|
|
21
24
|
const { drainQueueToCloud } = require("../lib/uploader");
|
|
22
25
|
const { collectLocalSubscriptions } = require("../lib/subscriptions");
|
|
@@ -299,6 +302,27 @@ async function cmdSync(argv) {
|
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
|
|
305
|
+
// ── Kiro (SQLite-based) ──
|
|
306
|
+
let kiroResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
307
|
+
const kiroDbPath = resolveKiroDbPath();
|
|
308
|
+
if (fssync.existsSync(kiroDbPath)) {
|
|
309
|
+
if (progress?.enabled) {
|
|
310
|
+
progress.start(`Parsing Kiro ${renderBar(0)} | buckets 0`);
|
|
311
|
+
}
|
|
312
|
+
kiroResult = await parseKiroIncremental({
|
|
313
|
+
dbPath: kiroDbPath,
|
|
314
|
+
cursors,
|
|
315
|
+
queuePath,
|
|
316
|
+
onProgress: (p) => {
|
|
317
|
+
if (!progress?.enabled) return;
|
|
318
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
319
|
+
progress.update(
|
|
320
|
+
`Parsing Kiro ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} records | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
321
|
+
);
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
302
326
|
if (cursors?.projectHourly?.projects && projectQueuePath && projectQueueStatePath) {
|
|
303
327
|
for (const [projectKey, meta] of Object.entries(cursors.projectHourly.projects)) {
|
|
304
328
|
if (!meta || typeof meta !== "object") continue;
|
|
@@ -473,14 +497,16 @@ async function cmdSync(argv) {
|
|
|
473
497
|
claudeResult.filesProcessed +
|
|
474
498
|
geminiResult.filesProcessed +
|
|
475
499
|
opencodeResult.filesProcessed +
|
|
476
|
-
cursorResult.recordsProcessed
|
|
500
|
+
cursorResult.recordsProcessed +
|
|
501
|
+
kiroResult.recordsProcessed;
|
|
477
502
|
const totalBuckets =
|
|
478
503
|
parseResult.bucketsQueued +
|
|
479
504
|
openclawResult.bucketsQueued +
|
|
480
505
|
claudeResult.bucketsQueued +
|
|
481
506
|
geminiResult.bucketsQueued +
|
|
482
507
|
opencodeResult.bucketsQueued +
|
|
483
|
-
cursorResult.bucketsQueued
|
|
508
|
+
cursorResult.bucketsQueued +
|
|
509
|
+
kiroResult.bucketsQueued;
|
|
484
510
|
process.stdout.write(
|
|
485
511
|
[
|
|
486
512
|
"Sync finished:",
|
package/src/lib/rollout.js
CHANGED
|
@@ -2596,12 +2596,211 @@ async function parseCursorApiIncremental({
|
|
|
2596
2596
|
return { recordsProcessed: total, eventsAggregated, bucketsQueued };
|
|
2597
2597
|
}
|
|
2598
2598
|
|
|
2599
|
+
// ---------------------------------------------------------------------------
|
|
2600
|
+
// Kiro token tracking (reads from devdata.sqlite)
|
|
2601
|
+
// ---------------------------------------------------------------------------
|
|
2602
|
+
|
|
2603
|
+
function resolveKiroBasePath() {
|
|
2604
|
+
const home = require("node:os").homedir();
|
|
2605
|
+
return path.join(
|
|
2606
|
+
home,
|
|
2607
|
+
"Library",
|
|
2608
|
+
"Application Support",
|
|
2609
|
+
"Kiro",
|
|
2610
|
+
"User",
|
|
2611
|
+
"globalStorage",
|
|
2612
|
+
"kiro.kiroagent",
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
function resolveKiroDbPath() {
|
|
2617
|
+
return path.join(resolveKiroBasePath(), "dev_data", "devdata.sqlite");
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
function readKiroDbTokens(dbPath, sinceId) {
|
|
2621
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
2622
|
+
const minId = Number.isFinite(sinceId) && sinceId > 0 ? sinceId : 0;
|
|
2623
|
+
const sql = `SELECT id, model, provider, tokens_prompt, tokens_generated, timestamp FROM tokens_generated WHERE id > ${minId} ORDER BY id ASC`;
|
|
2624
|
+
let raw;
|
|
2625
|
+
try {
|
|
2626
|
+
raw = cp.execFileSync("sqlite3", ["-json", dbPath, sql], {
|
|
2627
|
+
encoding: "utf8",
|
|
2628
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2629
|
+
timeout: 15_000,
|
|
2630
|
+
});
|
|
2631
|
+
} catch (_e) {
|
|
2632
|
+
return [];
|
|
2633
|
+
}
|
|
2634
|
+
if (!raw || !raw.trim()) return [];
|
|
2635
|
+
let rows;
|
|
2636
|
+
try {
|
|
2637
|
+
rows = JSON.parse(raw);
|
|
2638
|
+
} catch (_e) {
|
|
2639
|
+
return [];
|
|
2640
|
+
}
|
|
2641
|
+
return Array.isArray(rows) ? rows : [];
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// Build a sorted timeline of model usage from Kiro .chat metadata files
|
|
2645
|
+
function buildKiroModelTimeline(basePath) {
|
|
2646
|
+
const timeline = []; // [{ startMs, endMs, model }]
|
|
2647
|
+
if (!basePath || !fssync.existsSync(basePath)) return timeline;
|
|
2648
|
+
let dirs;
|
|
2649
|
+
try {
|
|
2650
|
+
dirs = fssync.readdirSync(basePath, { withFileTypes: true });
|
|
2651
|
+
} catch (_e) {
|
|
2652
|
+
return timeline;
|
|
2653
|
+
}
|
|
2654
|
+
for (const entry of dirs) {
|
|
2655
|
+
if (!entry.isDirectory()) continue;
|
|
2656
|
+
const dirPath = path.join(basePath, entry.name);
|
|
2657
|
+
let files;
|
|
2658
|
+
try {
|
|
2659
|
+
files = fssync.readdirSync(dirPath).filter((f) => f.endsWith(".chat"));
|
|
2660
|
+
} catch (_e) {
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
for (const file of files) {
|
|
2664
|
+
try {
|
|
2665
|
+
const raw = fssync.readFileSync(path.join(dirPath, file), "utf8");
|
|
2666
|
+
const data = JSON.parse(raw);
|
|
2667
|
+
const meta = data?.metadata;
|
|
2668
|
+
if (!meta?.modelId || !meta?.startTime) continue;
|
|
2669
|
+
timeline.push({
|
|
2670
|
+
startMs: meta.startTime,
|
|
2671
|
+
endMs: meta.endTime || meta.startTime,
|
|
2672
|
+
model: String(meta.modelId),
|
|
2673
|
+
});
|
|
2674
|
+
} catch (_e) {}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
timeline.sort((a, b) => a.startMs - b.startMs);
|
|
2678
|
+
return timeline;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// Find the model for a given UTC timestamp string using the .chat timeline
|
|
2682
|
+
function resolveKiroModel(timeline, utcTimestamp) {
|
|
2683
|
+
if (!timeline.length || !utcTimestamp) return null;
|
|
2684
|
+
const ts = new Date(utcTimestamp).getTime();
|
|
2685
|
+
if (!Number.isFinite(ts)) return null;
|
|
2686
|
+
|
|
2687
|
+
// Binary search for the closest .chat entry
|
|
2688
|
+
let lo = 0;
|
|
2689
|
+
let hi = timeline.length - 1;
|
|
2690
|
+
let best = null;
|
|
2691
|
+
let bestDist = Infinity;
|
|
2692
|
+
while (lo <= hi) {
|
|
2693
|
+
const mid = (lo + hi) >>> 1;
|
|
2694
|
+
const entry = timeline[mid];
|
|
2695
|
+
// Check if timestamp falls within the .chat execution window
|
|
2696
|
+
if (ts >= entry.startMs && ts <= entry.endMs) return entry.model;
|
|
2697
|
+
const dist = Math.min(Math.abs(ts - entry.startMs), Math.abs(ts - entry.endMs));
|
|
2698
|
+
if (dist < bestDist) {
|
|
2699
|
+
bestDist = dist;
|
|
2700
|
+
best = entry.model;
|
|
2701
|
+
}
|
|
2702
|
+
if (ts < entry.startMs) hi = mid - 1;
|
|
2703
|
+
else lo = mid + 1;
|
|
2704
|
+
}
|
|
2705
|
+
// Only match if within 10 minutes
|
|
2706
|
+
return bestDist < 10 * 60 * 1000 ? best : null;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// Normalize Kiro internal model IDs to readable names
|
|
2710
|
+
// e.g. "CLAUDE_SONNET_4_20250514_V1_0" → "claude-sonnet-4"
|
|
2711
|
+
function normalizeKiroModelName(raw) {
|
|
2712
|
+
if (!raw || typeof raw !== "string") return null;
|
|
2713
|
+
let name = raw.trim();
|
|
2714
|
+
if (!name) return null;
|
|
2715
|
+
// Already lowercase with dashes (e.g. "claude-opus-4.5") → keep as-is
|
|
2716
|
+
if (name === name.toLowerCase() && name.includes("-")) return name;
|
|
2717
|
+
// UPPER_SNAKE_CASE internal names: strip date/version suffixes, convert to lowercase-dash
|
|
2718
|
+
name = name
|
|
2719
|
+
.replace(/_\d{8}_V\d+_\d+$/i, "") // remove _20250514_V1_0
|
|
2720
|
+
.replace(/_V\d+$/i, "") // remove _V1
|
|
2721
|
+
.toLowerCase()
|
|
2722
|
+
.replace(/_/g, "-");
|
|
2723
|
+
return name || null;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
async function parseKiroIncremental({ dbPath, cursors, queuePath, onProgress }) {
|
|
2727
|
+
await ensureDir(path.dirname(queuePath));
|
|
2728
|
+
const kiroState = cursors.kiro && typeof cursors.kiro === "object" ? cursors.kiro : {};
|
|
2729
|
+
const lastId = typeof kiroState.lastId === "number" ? kiroState.lastId : 0;
|
|
2730
|
+
|
|
2731
|
+
const resolvedDbPath = dbPath || resolveKiroDbPath();
|
|
2732
|
+
const rows = readKiroDbTokens(resolvedDbPath, lastId);
|
|
2733
|
+
if (rows.length === 0) {
|
|
2734
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// Build model timeline from .chat files for model name resolution
|
|
2738
|
+
const basePath = resolveKiroBasePath();
|
|
2739
|
+
const modelTimeline = buildKiroModelTimeline(basePath);
|
|
2740
|
+
|
|
2741
|
+
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
2742
|
+
const touchedBuckets = new Set();
|
|
2743
|
+
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
2744
|
+
let eventsAggregated = 0;
|
|
2745
|
+
let maxId = lastId;
|
|
2746
|
+
|
|
2747
|
+
for (let i = 0; i < rows.length; i++) {
|
|
2748
|
+
const row = rows[i];
|
|
2749
|
+
const inputTokens = toNonNegativeInt(row.tokens_prompt);
|
|
2750
|
+
const outputTokens = toNonNegativeInt(row.tokens_generated);
|
|
2751
|
+
if (inputTokens === 0 && outputTokens === 0) continue;
|
|
2752
|
+
|
|
2753
|
+
// timestamp format: "2026-01-09 15:25:30" (UTC from SQLite DEFAULT CURRENT_TIMESTAMP)
|
|
2754
|
+
const ts = row.timestamp ? row.timestamp.replace(" ", "T") + "Z" : null;
|
|
2755
|
+
const bucketStart = ts ? toUtcHalfHourStart(ts) : null;
|
|
2756
|
+
if (!bucketStart) continue;
|
|
2757
|
+
|
|
2758
|
+
// Resolve actual model from .chat timeline, fallback to "kiro-agent"
|
|
2759
|
+
const resolvedModel = resolveKiroModel(modelTimeline, ts);
|
|
2760
|
+
const model = normalizeKiroModelName(resolvedModel) || "kiro-agent";
|
|
2761
|
+
|
|
2762
|
+
const delta = {
|
|
2763
|
+
input_tokens: inputTokens,
|
|
2764
|
+
cached_input_tokens: 0,
|
|
2765
|
+
output_tokens: outputTokens,
|
|
2766
|
+
reasoning_output_tokens: 0,
|
|
2767
|
+
total_tokens: inputTokens + outputTokens,
|
|
2768
|
+
conversation_count: 1,
|
|
2769
|
+
};
|
|
2770
|
+
|
|
2771
|
+
const bucket = getHourlyBucket(hourlyState, "kiro", model, bucketStart);
|
|
2772
|
+
addTotals(bucket.totals, delta);
|
|
2773
|
+
touchedBuckets.add(bucketKey("kiro", model, bucketStart));
|
|
2774
|
+
eventsAggregated++;
|
|
2775
|
+
|
|
2776
|
+
if (row.id && row.id > maxId) maxId = row.id;
|
|
2777
|
+
|
|
2778
|
+
if (cb) {
|
|
2779
|
+
cb({
|
|
2780
|
+
index: i + 1,
|
|
2781
|
+
total: rows.length,
|
|
2782
|
+
recordsProcessed: i + 1,
|
|
2783
|
+
eventsAggregated,
|
|
2784
|
+
bucketsQueued: touchedBuckets.size,
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
|
|
2790
|
+
hourlyState.updatedAt = new Date().toISOString();
|
|
2791
|
+
cursors.hourly = hourlyState;
|
|
2792
|
+
cursors.kiro = { lastId: maxId, updatedAt: new Date().toISOString() };
|
|
2793
|
+
|
|
2794
|
+
return { recordsProcessed: rows.length, eventsAggregated, bucketsQueued };
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2599
2797
|
module.exports = {
|
|
2600
2798
|
listRolloutFiles,
|
|
2601
2799
|
listClaudeProjectFiles,
|
|
2602
2800
|
listGeminiSessionFiles,
|
|
2603
2801
|
listOpencodeMessageFiles,
|
|
2604
2802
|
readOpencodeDbMessages,
|
|
2803
|
+
resolveKiroDbPath,
|
|
2605
2804
|
parseRolloutIncremental,
|
|
2606
2805
|
parseClaudeIncremental,
|
|
2607
2806
|
parseGeminiIncremental,
|
|
@@ -2609,4 +2808,5 @@ module.exports = {
|
|
|
2609
2808
|
parseOpencodeDbIncremental,
|
|
2610
2809
|
parseOpenclawIncremental,
|
|
2611
2810
|
parseCursorApiIncremental,
|
|
2811
|
+
parseKiroIncremental,
|
|
2612
2812
|
};
|