tokentracker-cli 0.24.6 → 0.24.7
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.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dashboard/dist/assets/{Card-BfayTmBt.js → Card-Cy2qG2yw.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-C_ExwqoB.js → DashboardPage-CsG_Mq6k.js} +2 -2
- package/dashboard/dist/assets/{DevicePage-Bzc-tOQ7.js → DevicePage-B1I6_Kxp.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-C1nCEQAI.js → FadeIn-xb0lBDdT.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CalgbIws.js → HeaderGithubStar-BNxToTPs.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-C2_FdWEa.js → IpCheckPage-CCysfQ-C.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Cab2gSJk.js → LandingPage-D7x7fS1R.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BoWSvv8E.js → LeaderboardPage-DpT-Wb4Y.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-BxvvRB0p.js → LeaderboardProfilePage-BFx3cfEn.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-JTd7ZYkv.js → LimitsPage-CSXwXe-W.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-fU320alu.js → LoginPage-BJckej8m.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-C76-ba7G.js → PopoverPopup-BKcg8Ouw.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-xv4cUgTy.js → ProviderIcon-BRpynAI7.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-UtWH1_mF.js → SettingsPage-tXhwdSnC.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-9W2jGGps.js → SkillsPage-CSybau5n.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BedujTKv.js → WidgetsPage-DZS8HDJi.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Bms62oTH.js → WrappedPage-CUWY3_0R.js} +1 -1
- package/dashboard/dist/assets/check-qNB7C4Ej.js +1 -0
- package/dashboard/dist/assets/{chevron-down-g6db-hJJ.js → chevron-down-C8arMd0V.js} +1 -1
- package/dashboard/dist/assets/{download-UDqrzLfH.js → download-CoTKvd8q.js} +1 -1
- package/dashboard/dist/assets/{info-BB9X3uhm.js → info-tb06m6pM.js} +1 -1
- package/dashboard/dist/assets/{leaderboard-columns-CTGzd-uH.js → leaderboard-columns-45nSY60u.js} +1 -1
- package/dashboard/dist/assets/{main-QPJFCBQm.js → main-DDbFansE.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-DccYvUNZ.js → use-limits-display-prefs-IJ650iD0.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-DAbSUv-n.js → use-native-settings-B0Civ1m0.js} +1 -1
- package/dashboard/dist/assets/{use-reduced-motion-B_GaKtWC.js → use-reduced-motion-DnF5Mq2H.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-3pTcFcoF.js → use-usage-limits-Ig-0cZyA.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CLZ2MqvV.js → useCurrency-kbOycXmD.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/serve.js +114 -14
- package/src/commands/status.js +13 -0
- package/src/commands/sync.js +35 -2
- package/src/lib/pricing/matcher.js +54 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +474 -0
- package/dashboard/dist/assets/check-DHKWR9eH.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a}from"./main-
|
|
1
|
+
import{r as a}from"./main-DDbFansE.js";const d=["claude","codex","cursor","gemini","kimi","kiro","copilot","antigravity"],S={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},v={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kimi:"/brand-logos/kimi.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function w(){if(typeof window>"u")return[...d];try{const i=window.localStorage.getItem(l);if(!i)return[...d];const s=JSON.parse(i);if(!Array.isArray(s))return[...d];const r=s.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function y(){const i=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return i;try{const s=window.localStorage.getItem(g);if(!s)return i;const r=JSON.parse(s);if(!r||typeof r!="object")return i;const c={...i};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return i}}function C(){const[i,s]=a.useState(w),[r,c]=a.useState(y);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(i))}catch{}},[i]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(w()),t.key===g&&c(y())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const n=[...t];return[n[e-1],n[e]]=[n[e],n[e-1]],n})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const n=[...t];return[n[e],n[e+1]]=[n[e+1],n[e]],n})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const n=e.indexOf(o),m=e.indexOf(t);if(n<0||m<0)return e;const f=[...e],[I]=f.splice(n,1);return f.splice(m,0,I),f})},[]),k=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),x=a.useMemo(()=>i.filter(o=>r[o]!==!1),[i,r]);return{order:i,visibility:r,visibleOrdered:x,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:k}}export{v as L,S as a,C as u};
|
package/dashboard/dist/assets/{use-native-settings-DAbSUv-n.js → use-native-settings-B0Civ1m0.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as a,a7 as x,r as n,aH as g,aI as m,aJ as b,aK as u,aL as f,aF as h}from"./main-
|
|
1
|
+
import{C as a,a7 as x,r as n,aH as g,aI as m,aJ as b,aK as u,aL as f,aF as h}from"./main-DDbFansE.js";import{C as y}from"./Card-Cy2qG2yw.js";function p({checked:s,onChange:t,disabled:e,ariaLabel:i}){return a.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,onClick:t,disabled:e,className:x("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500 disabled:opacity-50 disabled:cursor-not-allowed",s?"bg-oai-brand-500":"bg-oai-gray-300 dark:bg-oai-gray-700"),children:a.jsx("span",{className:x("inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",s?"translate-x-[18px]":"translate-x-[3px]")})})}function j({label:s,hint:t,control:e}){return a.jsxs("div",{className:"flex items-center justify-between gap-4 py-3",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("div",{className:"text-sm text-oai-gray-900 dark:text-oai-gray-200",children:s}),t?a.jsx("div",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),a.jsx("div",{className:"shrink-0",children:e})]})}function N({title:s,subtitle:t,action:e,children:i}){return a.jsxs(y,{children:[a.jsxs("div",{className:"mb-3 flex items-start justify-between gap-4",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("h2",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide",children:s}),t?a.jsx("p",{className:"mt-1 truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),e?a.jsx("div",{className:"shrink-0",children:e}):null]}),a.jsx("div",{className:"-mb-3 divide-y divide-oai-gray-200/60 dark:divide-oai-gray-800/60",children:i})]})}function w({options:s,value:t,onChange:e}){return a.jsx("div",{className:"inline-flex items-center rounded-lg border border-oai-gray-200 bg-oai-gray-50 p-0.5 dark:border-oai-gray-800 dark:bg-oai-gray-900",children:s.map(({value:i,label:d,Icon:l})=>{const r=t===i;return a.jsxs("button",{type:"button",onClick:()=>e(i),"aria-pressed":r,className:x("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",r?"bg-white text-oai-black shadow-sm dark:bg-oai-gray-800 dark:text-white":"text-oai-gray-500 hover:text-oai-black dark:text-oai-gray-400 dark:hover:text-white"),children:[l?a.jsx(l,{className:"h-3.5 w-3.5","aria-hidden":!0}):null,a.jsx("span",{children:d})]},i)})})}function S(){const[s,t]=n.useState(null),e=g()&&m();n.useEffect(()=>{if(!e)return;const r=b(o=>t(o));return u(),r},[e]);const i=n.useCallback((r,o)=>{e&&(t(c=>c&&{...c,[r]:o}),f(r,o))},[e]),d=n.useCallback(r=>{e&&h(r)},[e]),l=n.useCallback(()=>{e&&u()},[e]);return{available:e,settings:s,setSetting:i,runAction:d,refresh:l}}export{N as S,p as T,j as a,w as b,S as u};
|
package/dashboard/dist/assets/{use-reduced-motion-B_GaKtWC.js → use-reduced-motion-DnF5Mq2H.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{aM as t,aN as o,r,aO as s}from"./main-
|
|
1
|
+
import{aM as t,aN as o,r,aO as s}from"./main-DDbFansE.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r,al as l}from"./main-
|
|
1
|
+
import{r,al as l}from"./main-DDbFansE.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e,aA as t,aB as r,I as c}from"./main-
|
|
1
|
+
import{r as e,aA as t,aB as r,I as c}from"./main-DDbFansE.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
]
|
|
211
211
|
}
|
|
212
212
|
</script>
|
|
213
|
-
<script type="module" crossorigin src="/assets/main-
|
|
213
|
+
<script type="module" crossorigin src="/assets/main-DDbFansE.js"></script>
|
|
214
214
|
<link rel="stylesheet" crossorigin href="/assets/main-C8k06i2w.css">
|
|
215
215
|
</head>
|
|
216
216
|
<body>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-DDbFansE.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-C8k06i2w.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.7",
|
|
4
4
|
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, Grok Build, oh-my-pi, pi, Craft Agents, Kilo CLI, Kilo Code, Roo Code, Zed Agent, Goose)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/commands/serve.js
CHANGED
|
@@ -11,6 +11,7 @@ const { serveStaticFile } = require("../lib/static-server");
|
|
|
11
11
|
const { openInBrowser } = require("../lib/browser-auth");
|
|
12
12
|
|
|
13
13
|
const DEFAULT_PORT = 7680;
|
|
14
|
+
const DEFAULT_MAX_PORT_ATTEMPTS = 20;
|
|
14
15
|
const NPM_PACKAGE_NAME = "tokentracker-cli";
|
|
15
16
|
const LOCAL_BIND_HOST = "127.0.0.1";
|
|
16
17
|
|
|
@@ -18,6 +19,10 @@ function buildPortInUseHint(port) {
|
|
|
18
19
|
return `Port ${port} is still in use after cleanup. Try: npx ${NPM_PACKAGE_NAME} serve --port ${port + 1}\n`;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
function isPortUnavailableError(error) {
|
|
23
|
+
return error?.code === "EADDRINUSE" || error?.code === "EACCES" || error?.code === "EPERM";
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
function getLocalServerUrl(port) {
|
|
22
27
|
return `http://${LOCAL_BIND_HOST}:${port}`;
|
|
23
28
|
}
|
|
@@ -127,10 +132,28 @@ async function cmdServe(argv) {
|
|
|
127
132
|
}
|
|
128
133
|
});
|
|
129
134
|
|
|
130
|
-
// 4. Listen
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
// 4. Listen. Default startup follows README behavior and picks the next
|
|
136
|
+
// available port; an explicit --port/PORT remains strict.
|
|
137
|
+
let port;
|
|
138
|
+
try {
|
|
139
|
+
port = await listenOnAvailablePort(server, opts.port, {
|
|
140
|
+
allowFallback: !opts.portExplicit,
|
|
141
|
+
ensurePortFreeFn: opts.portExplicit ? ensurePortFree : null,
|
|
142
|
+
onRetry: (failedPort) => {
|
|
143
|
+
process.stdout.write(`Port ${failedPort} unavailable, trying ${failedPort + 1}...\n`);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
} catch (e) {
|
|
147
|
+
if (isPortUnavailableError(e)) {
|
|
148
|
+
process.stderr.write(buildPortInUseHint(opts.port));
|
|
149
|
+
} else {
|
|
150
|
+
process.stderr.write(`Server error: ${e.message}\n`);
|
|
151
|
+
}
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
{
|
|
134
157
|
const url = getLocalServerUrl(port);
|
|
135
158
|
process.stdout.write(
|
|
136
159
|
[
|
|
@@ -148,14 +171,10 @@ async function cmdServe(argv) {
|
|
|
148
171
|
if (opts.open) {
|
|
149
172
|
openInBrowser(url);
|
|
150
173
|
}
|
|
151
|
-
}
|
|
174
|
+
}
|
|
152
175
|
|
|
153
176
|
server.on("error", (e) => {
|
|
154
|
-
|
|
155
|
-
process.stderr.write(buildPortInUseHint(port));
|
|
156
|
-
} else {
|
|
157
|
-
process.stderr.write(`Server error: ${e.message}\n`);
|
|
158
|
-
}
|
|
177
|
+
process.stderr.write(`Server error: ${e.message}\n`);
|
|
159
178
|
process.exitCode = 1;
|
|
160
179
|
});
|
|
161
180
|
|
|
@@ -213,6 +232,70 @@ async function ensurePortFree(port) {
|
|
|
213
232
|
await new Promise((r) => setTimeout(r, 500));
|
|
214
233
|
}
|
|
215
234
|
|
|
235
|
+
function listenOnce(server, port, host) {
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
let settled = false;
|
|
238
|
+
|
|
239
|
+
const cleanup = () => {
|
|
240
|
+
server.off("listening", onListening);
|
|
241
|
+
server.off("error", onError);
|
|
242
|
+
};
|
|
243
|
+
const finish = (fn, value) => {
|
|
244
|
+
if (settled) return;
|
|
245
|
+
settled = true;
|
|
246
|
+
cleanup();
|
|
247
|
+
fn(value);
|
|
248
|
+
};
|
|
249
|
+
const onListening = () => finish(resolve);
|
|
250
|
+
const onError = (error) => finish(reject, error);
|
|
251
|
+
|
|
252
|
+
server.once("listening", onListening);
|
|
253
|
+
server.once("error", onError);
|
|
254
|
+
try {
|
|
255
|
+
server.listen(port, host);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
finish(reject, error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function listenOnAvailablePort(
|
|
263
|
+
server,
|
|
264
|
+
startPort,
|
|
265
|
+
{
|
|
266
|
+
host = LOCAL_BIND_HOST,
|
|
267
|
+
allowFallback = false,
|
|
268
|
+
maxAttempts = DEFAULT_MAX_PORT_ATTEMPTS,
|
|
269
|
+
ensurePortFreeFn = null,
|
|
270
|
+
onRetry = null,
|
|
271
|
+
} = {},
|
|
272
|
+
) {
|
|
273
|
+
const attempts = allowFallback ? Math.max(1, maxAttempts) : 1;
|
|
274
|
+
let port = startPort;
|
|
275
|
+
let lastError = null;
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < attempts && port < 65536; i++, port++) {
|
|
278
|
+
if (ensurePortFreeFn) {
|
|
279
|
+
await ensurePortFreeFn(port);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
await listenOnce(server, port, host);
|
|
284
|
+
return port;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
lastError = error;
|
|
287
|
+
if (!allowFallback || !isPortUnavailableError(error) || port >= 65535) {
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
if (typeof onRetry === "function") {
|
|
291
|
+
onRetry(port, error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
throw lastError || new Error(`No available port found from ${startPort}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
216
299
|
function resolveDashboardDir() {
|
|
217
300
|
const candidates = [
|
|
218
301
|
path.resolve(__dirname, "../../dashboard/dist"),
|
|
@@ -224,13 +307,27 @@ function resolveDashboardDir() {
|
|
|
224
307
|
return null;
|
|
225
308
|
}
|
|
226
309
|
|
|
227
|
-
function
|
|
228
|
-
const
|
|
310
|
+
function parsePort(value) {
|
|
311
|
+
const n = parseInt(value, 10);
|
|
312
|
+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseArgs(argv, env = process.env) {
|
|
316
|
+
const envPort = parsePort(env.PORT);
|
|
317
|
+
const opts = {
|
|
318
|
+
port: envPort || DEFAULT_PORT,
|
|
319
|
+
portExplicit: Boolean(envPort),
|
|
320
|
+
open: true,
|
|
321
|
+
sync: true,
|
|
322
|
+
};
|
|
229
323
|
for (let i = 0; i < argv.length; i++) {
|
|
230
324
|
const arg = argv[i];
|
|
231
325
|
if (arg === "--port" && i + 1 < argv.length) {
|
|
232
|
-
const n =
|
|
233
|
-
if (
|
|
326
|
+
const n = parsePort(argv[++i]);
|
|
327
|
+
if (n) {
|
|
328
|
+
opts.port = n;
|
|
329
|
+
opts.portExplicit = true;
|
|
330
|
+
}
|
|
234
331
|
} else if (arg === "--no-open") {
|
|
235
332
|
opts.open = false;
|
|
236
333
|
} else if (arg === "--no-sync") {
|
|
@@ -245,5 +342,8 @@ module.exports = {
|
|
|
245
342
|
buildPortInUseHint,
|
|
246
343
|
NPM_PACKAGE_NAME,
|
|
247
344
|
LOCAL_BIND_HOST,
|
|
345
|
+
isPortUnavailableError,
|
|
346
|
+
listenOnAvailablePort,
|
|
248
347
|
getLocalServerUrl,
|
|
348
|
+
parseArgs,
|
|
249
349
|
};
|
package/src/commands/status.js
CHANGED
|
@@ -51,6 +51,8 @@ const {
|
|
|
51
51
|
resolveRoocodeTaskFiles,
|
|
52
52
|
resolveZedDbPath,
|
|
53
53
|
resolveGooseDbPath,
|
|
54
|
+
listDroidSettingsFiles,
|
|
55
|
+
resolveDroidSessionsDir,
|
|
54
56
|
resolveGrokBuildSessions,
|
|
55
57
|
} = require("../lib/rollout");
|
|
56
58
|
const { probeGrokHookState, resolveGrokHome } = require("../lib/grok-hook");
|
|
@@ -231,6 +233,11 @@ async function cmdStatus(argv = []) {
|
|
|
231
233
|
const gooseDbPath = resolveGooseDbPath(process.env);
|
|
232
234
|
const gooseInstalled = fssync.existsSync(gooseDbPath);
|
|
233
235
|
|
|
236
|
+
// Droid (Factory CLI) — passive cumulative-delta read of *.settings.json.
|
|
237
|
+
const droidSessionsDir = resolveDroidSessionsDir(process.env);
|
|
238
|
+
const droidSettingsFiles = listDroidSettingsFiles(process.env);
|
|
239
|
+
const droidInstalled = droidSettingsFiles.length > 0;
|
|
240
|
+
|
|
234
241
|
// Grok Build (xAI TUI)
|
|
235
242
|
const grokHookState = await probeGrokHookState({ home, trackerDir, env: process.env });
|
|
236
243
|
const grokSessions = grokHookState.hasGrokInstall || grokHookState.sessionsDir
|
|
@@ -324,6 +331,9 @@ async function cmdStatus(argv = []) {
|
|
|
324
331
|
goose: gooseInstalled
|
|
325
332
|
? { installed: true, detail: gooseDbPath }
|
|
326
333
|
: { installed: false },
|
|
334
|
+
droid: droidInstalled
|
|
335
|
+
? { installed: true, files: droidSettingsFiles.length, detail: droidSessionsDir }
|
|
336
|
+
: { installed: false },
|
|
327
337
|
grok_build: grokInstalled
|
|
328
338
|
? {
|
|
329
339
|
installed: true,
|
|
@@ -408,6 +418,9 @@ async function cmdStatus(argv = []) {
|
|
|
408
418
|
gooseInstalled
|
|
409
419
|
? `- Goose (Block): passive reader (sessions.db, cumulative-delta)`
|
|
410
420
|
: null,
|
|
421
|
+
droidInstalled
|
|
422
|
+
? `- Droid (Factory): passive reader (${droidSettingsFiles.length} session${droidSettingsFiles.length !== 1 ? "s" : ""} in ${droidSessionsDir}, cumulative-delta)`
|
|
423
|
+
: null,
|
|
411
424
|
...(() => {
|
|
412
425
|
const passive = passiveProviders.filter((p) => p.passive);
|
|
413
426
|
if (passive.length === 0) return [];
|
package/src/commands/sync.js
CHANGED
|
@@ -52,6 +52,8 @@ const {
|
|
|
52
52
|
parseZedIncremental,
|
|
53
53
|
resolveGooseDbPath,
|
|
54
54
|
parseGooseIncremental,
|
|
55
|
+
listDroidSettingsFiles,
|
|
56
|
+
parseDroidIncremental,
|
|
55
57
|
bucketKey,
|
|
56
58
|
totalsKey,
|
|
57
59
|
claudeMessageDedupKey,
|
|
@@ -456,6 +458,35 @@ async function cmdSync(argv) {
|
|
|
456
458
|
});
|
|
457
459
|
}
|
|
458
460
|
|
|
461
|
+
// ── Droid (Factory CLI) — passive reader for ~/.factory/sessions/*.settings.json ──
|
|
462
|
+
const droidSettingsFiles = listDroidSettingsFiles(process.env);
|
|
463
|
+
let droidResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
464
|
+
if (droidSettingsFiles.length > 0) {
|
|
465
|
+
if (progress?.enabled) {
|
|
466
|
+
progress.start(
|
|
467
|
+
`Parsing Droid ${renderBar(0)} 0/${formatNumber(droidSettingsFiles.length)} sessions | buckets 0`,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
droidResult = await parseDroidIncremental({
|
|
471
|
+
settingsFiles: droidSettingsFiles,
|
|
472
|
+
cursors,
|
|
473
|
+
queuePath,
|
|
474
|
+
// Full-scan sync: drop cursor entries for any session whose
|
|
475
|
+
// settings.json has disappeared off disk so cursors.droid stays
|
|
476
|
+
// bounded by the actual on-disk session count.
|
|
477
|
+
prune: true,
|
|
478
|
+
onProgress: (p) => {
|
|
479
|
+
if (!progress?.enabled) return;
|
|
480
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
481
|
+
progress.update(
|
|
482
|
+
`Parsing Droid ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
483
|
+
p.total,
|
|
484
|
+
)} sessions | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
485
|
+
);
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
459
490
|
// ── Zed Agent (hosted models only; cumulative-delta over SQLite threads) ──
|
|
460
491
|
const zedDbPath = resolveZedDbPath(process.env);
|
|
461
492
|
let zedResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -948,7 +979,8 @@ async function cmdSync(argv) {
|
|
|
948
979
|
kilocodeResult.recordsProcessed +
|
|
949
980
|
roocodeResult.recordsProcessed +
|
|
950
981
|
zedResult.recordsProcessed +
|
|
951
|
-
gooseResult.recordsProcessed
|
|
982
|
+
gooseResult.recordsProcessed +
|
|
983
|
+
droidResult.recordsProcessed;
|
|
952
984
|
const totalBuckets =
|
|
953
985
|
parseResult.bucketsQueued +
|
|
954
986
|
openclawResult.bucketsQueued +
|
|
@@ -971,7 +1003,8 @@ async function cmdSync(argv) {
|
|
|
971
1003
|
kilocodeResult.bucketsQueued +
|
|
972
1004
|
roocodeResult.bucketsQueued +
|
|
973
1005
|
zedResult.bucketsQueued +
|
|
974
|
-
gooseResult.bucketsQueued
|
|
1006
|
+
gooseResult.bucketsQueued +
|
|
1007
|
+
droidResult.bucketsQueued;
|
|
975
1008
|
process.stdout.write(
|
|
976
1009
|
[
|
|
977
1010
|
"Sync finished:",
|
|
@@ -72,6 +72,33 @@ function getSortedKeys(litellm) {
|
|
|
72
72
|
return cached;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function buildDotRestoredModel(model) {
|
|
76
|
+
if (typeof model !== "string") return "";
|
|
77
|
+
const lower = model.toLowerCase();
|
|
78
|
+
const restored = lower.replace(/(\d+)-(\d+)/g, "$1.$2");
|
|
79
|
+
return restored === lower ? "" : restored;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function lookupExactCaseInsensitive(table, model) {
|
|
83
|
+
if (!table || !model) return null;
|
|
84
|
+
if (table[model]) return table[model];
|
|
85
|
+
const lower = model.toLowerCase();
|
|
86
|
+
for (const key of Object.keys(table)) {
|
|
87
|
+
if (key.toLowerCase() === lower) return table[key];
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function lookupContainedExactCaseInsensitive(table, model) {
|
|
93
|
+
if (!table || !model) return null;
|
|
94
|
+
const lower = model.toLowerCase();
|
|
95
|
+
const keys = Object.keys(table).sort((a, b) => b.length - a.length);
|
|
96
|
+
for (const key of keys) {
|
|
97
|
+
if (lower.includes(key.toLowerCase())) return table[key];
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
76
103
|
if (!model || typeof model !== "string") {
|
|
77
104
|
return { hit: false, source: "empty", value: null };
|
|
@@ -80,16 +107,29 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
80
107
|
? normalizeAntigravityModel(model)
|
|
81
108
|
: model;
|
|
82
109
|
const lower = lookupModel.toLowerCase();
|
|
110
|
+
const dotForm = buildDotRestoredModel(lookupModel);
|
|
83
111
|
|
|
84
112
|
// 1. CURATED exact
|
|
85
113
|
if (curated.exact && curated.exact[lookupModel]) {
|
|
86
114
|
return { hit: true, source: "curated:exact", value: curated.exact[lookupModel] };
|
|
87
115
|
}
|
|
116
|
+
const curatedDotExact = lookupExactCaseInsensitive(curated.exact, dotForm);
|
|
117
|
+
if (curatedDotExact) {
|
|
118
|
+
return { hit: true, source: "curated:exact-dot", value: curatedDotExact };
|
|
119
|
+
}
|
|
120
|
+
const curatedDotContainedExact = lookupContainedExactCaseInsensitive(curated.exact, dotForm);
|
|
121
|
+
if (curatedDotContainedExact) {
|
|
122
|
+
return { hit: true, source: "curated:exact-dot", value: curatedDotContainedExact };
|
|
123
|
+
}
|
|
88
124
|
|
|
89
125
|
// 2. LiteLLM exact
|
|
90
126
|
if (litellm && litellm[lookupModel]) {
|
|
91
127
|
return { hit: true, source: "litellm:exact", value: litellm[lookupModel] };
|
|
92
128
|
}
|
|
129
|
+
const litellmDotExact = lookupExactCaseInsensitive(litellm, dotForm);
|
|
130
|
+
if (litellmDotExact) {
|
|
131
|
+
return { hit: true, source: "litellm:exact-dot", value: litellmDotExact };
|
|
132
|
+
}
|
|
93
133
|
|
|
94
134
|
// 3. CURATED alias (literal mapping like "auto" -> "composer-1")
|
|
95
135
|
if (curated.alias && curated.alias[lookupModel] && curated.exact[curated.alias[lookupModel]]) {
|
|
@@ -100,11 +140,22 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
100
140
|
};
|
|
101
141
|
}
|
|
102
142
|
|
|
103
|
-
// 4. CURATED fuzzy substring
|
|
143
|
+
// 4. CURATED fuzzy substring. Also try a dot-restored variant of the input
|
|
144
|
+
// (digits separated by `-` rejoined as `.`) so providers that dash-normalize
|
|
145
|
+
// numeric segments — Droid emits `glm-5-1-0` for upstream `GLM-5.1` — still
|
|
146
|
+
// resolve against dot-keyed curated entries like `glm-5.1`, `glm-4.6`, etc.
|
|
147
|
+
// The regex only fires on digit-dash-digit, so `claude-3-7-sonnet`,
|
|
148
|
+
// `gpt-5-codex`, `gemini-2-5-pro` are unaffected (no digit-pair to rejoin or
|
|
149
|
+
// no matching curated key).
|
|
104
150
|
if (Array.isArray(curated.fuzzy)) {
|
|
105
151
|
for (const { match, ref } of curated.fuzzy) {
|
|
106
152
|
if (!match || !ref) continue;
|
|
107
|
-
|
|
153
|
+
const needle = match.toLowerCase();
|
|
154
|
+
if (!curated.exact[ref]) continue;
|
|
155
|
+
if (lower.includes(needle)) {
|
|
156
|
+
return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
|
|
157
|
+
}
|
|
158
|
+
if (dotForm && dotForm.includes(needle)) {
|
|
108
159
|
return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
|
|
109
160
|
}
|
|
110
161
|
}
|
|
@@ -125,7 +176,7 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
125
176
|
const keyLower = key.toLowerCase();
|
|
126
177
|
// Only accept if model is a superset of key (model contains key), to
|
|
127
178
|
// avoid e.g. "gpt-5" matching "gpt-5-pro" in the wrong direction.
|
|
128
|
-
if (lower.includes(keyLower)) {
|
|
179
|
+
if (lower.includes(keyLower) || (dotForm && dotForm.includes(keyLower))) {
|
|
129
180
|
return { hit: true, source: "litellm:fuzzy", value: litellm[key] };
|
|
130
181
|
}
|
|
131
182
|
}
|