@hasna/statusline 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -31
- package/dist/cli.js +308 -58
- package/dist/context-window.d.ts +22 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +314 -59
- package/dist/mcp/index.js +19823 -6484
- package/dist/providers/codex.d.ts +19 -0
- package/dist/providers/opencode.d.ts +16 -0
- package/dist/providers/types.d.ts +1 -1
- package/package.json +10 -8
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
34
|
var __returnValue = (v) => v;
|
|
5
35
|
function __exportSetter(name, newValue) {
|
|
6
36
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -30,6 +60,7 @@ function defaultConfig() {
|
|
|
30
60
|
"loc",
|
|
31
61
|
"model-context",
|
|
32
62
|
"context-remaining",
|
|
63
|
+
"cache-rate",
|
|
33
64
|
"cost"
|
|
34
65
|
]
|
|
35
66
|
};
|
|
@@ -95,7 +126,7 @@ function numberOr(v) {
|
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
// src/segments/index.ts
|
|
98
|
-
import { hostname } from "os";
|
|
129
|
+
import { homedir as homedir5, hostname } from "os";
|
|
99
130
|
|
|
100
131
|
// src/format.ts
|
|
101
132
|
function compactAge(seconds) {
|
|
@@ -135,10 +166,10 @@ function compactDuration(ms) {
|
|
|
135
166
|
return `${s}s`;
|
|
136
167
|
if (s < 3600) {
|
|
137
168
|
const rem2 = s % 60;
|
|
138
|
-
return rem2 ? `${Math.floor(s / 60)}m${rem2}s` : `${Math.floor(s / 60)}m`;
|
|
169
|
+
return rem2 ? `${Math.floor(s / 60)}m ${rem2}s` : `${Math.floor(s / 60)}m`;
|
|
139
170
|
}
|
|
140
171
|
const rem = Math.floor(s % 3600 / 60);
|
|
141
|
-
return rem ? `${Math.floor(s / 3600)}h${rem}m` : `${Math.floor(s / 3600)}h`;
|
|
172
|
+
return rem ? `${Math.floor(s / 3600)}h ${rem}m` : `${Math.floor(s / 3600)}h`;
|
|
142
173
|
}
|
|
143
174
|
|
|
144
175
|
// src/git.ts
|
|
@@ -195,14 +226,145 @@ function trackedLineCount(cwd) {
|
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
// src/context-window.ts
|
|
198
|
-
import { existsSync as
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
230
|
+
|
|
231
|
+
// src/providers/codex.ts
|
|
232
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
233
|
+
import { homedir as homedir2 } from "os";
|
|
234
|
+
import { join as join2 } from "path";
|
|
235
|
+
function latestCodexSessionPath(home = homedir2()) {
|
|
236
|
+
const root = join2(home, ".codex", "sessions");
|
|
237
|
+
if (!existsSync2(root))
|
|
238
|
+
return null;
|
|
239
|
+
let newest = null;
|
|
240
|
+
let newestMtime = 0;
|
|
241
|
+
try {
|
|
242
|
+
for (const year of readdirSync(root)) {
|
|
243
|
+
const yearDir = join2(root, year);
|
|
244
|
+
if (!statSync(yearDir, { throwIfNoEntry: false })?.isDirectory())
|
|
245
|
+
continue;
|
|
246
|
+
for (const month of readdirSync(yearDir)) {
|
|
247
|
+
const monthDir = join2(yearDir, month);
|
|
248
|
+
if (!statSync(monthDir, { throwIfNoEntry: false })?.isDirectory())
|
|
249
|
+
continue;
|
|
250
|
+
for (const day of readdirSync(monthDir)) {
|
|
251
|
+
const dayDir = join2(monthDir, day);
|
|
252
|
+
if (!statSync(dayDir, { throwIfNoEntry: false })?.isDirectory())
|
|
253
|
+
continue;
|
|
254
|
+
for (const file of readdirSync(dayDir)) {
|
|
255
|
+
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl"))
|
|
256
|
+
continue;
|
|
257
|
+
const full = join2(dayDir, file);
|
|
258
|
+
const mtime = statSync(full, { throwIfNoEntry: false })?.mtimeMs ?? 0;
|
|
259
|
+
if (mtime > newestMtime) {
|
|
260
|
+
newestMtime = mtime;
|
|
261
|
+
newest = full;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return newest;
|
|
271
|
+
}
|
|
272
|
+
function codexCacheRate(sessionPath) {
|
|
273
|
+
const path = sessionPath ?? latestCodexSessionPath();
|
|
201
274
|
if (!path || !existsSync2(path))
|
|
202
275
|
return null;
|
|
203
276
|
let lines;
|
|
204
277
|
try {
|
|
205
278
|
lines = readFileSync2(path, "utf8").split(`
|
|
279
|
+
`);
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
let usage = null;
|
|
284
|
+
for (const line of lines) {
|
|
285
|
+
if (!line || !line.includes("token_count"))
|
|
286
|
+
continue;
|
|
287
|
+
try {
|
|
288
|
+
const entry = JSON.parse(line);
|
|
289
|
+
if (entry?.type !== "event_msg")
|
|
290
|
+
continue;
|
|
291
|
+
if (entry?.payload?.type !== "token_count")
|
|
292
|
+
continue;
|
|
293
|
+
const info = entry?.payload?.info?.total_token_usage;
|
|
294
|
+
if (info && typeof info === "object")
|
|
295
|
+
usage = info;
|
|
296
|
+
} catch {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!usage)
|
|
301
|
+
return null;
|
|
302
|
+
const read = usage.cached_input_tokens || 0;
|
|
303
|
+
const input = usage.input_tokens || 0;
|
|
304
|
+
const write = usage.cache_write_input_tokens || 0;
|
|
305
|
+
const divisor = input + read + write;
|
|
306
|
+
if (divisor <= 0)
|
|
307
|
+
return null;
|
|
308
|
+
return Math.min(1, Math.max(0, read / divisor));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/providers/opencode.ts
|
|
312
|
+
import { existsSync as existsSync3 } from "fs";
|
|
313
|
+
import { homedir as homedir3 } from "os";
|
|
314
|
+
import { join as join3 } from "path";
|
|
315
|
+
import { Database } from "bun:sqlite";
|
|
316
|
+
function opencodeCacheRate(opts = {}) {
|
|
317
|
+
const dbPath = opts.dbPath ?? join3(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
318
|
+
if (!existsSync3(dbPath))
|
|
319
|
+
return null;
|
|
320
|
+
let db;
|
|
321
|
+
try {
|
|
322
|
+
db = new Database(dbPath, { readonly: true });
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
let row = tokenRow(db, "session", opts.sessionId);
|
|
328
|
+
if (!row)
|
|
329
|
+
row = tokenRow(db, "session_v2", opts.sessionId);
|
|
330
|
+
if (!row)
|
|
331
|
+
return null;
|
|
332
|
+
const input = row.tokens_input ?? 0;
|
|
333
|
+
const read = row.tokens_cache_read ?? 0;
|
|
334
|
+
const write = row.tokens_cache_write ?? 0;
|
|
335
|
+
const divisor = input + read + write;
|
|
336
|
+
if (divisor <= 0)
|
|
337
|
+
return null;
|
|
338
|
+
return Math.min(1, Math.max(0, read / divisor));
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
} finally {
|
|
342
|
+
db.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function tokenRow(db, table, sessionId) {
|
|
346
|
+
try {
|
|
347
|
+
if (sessionId) {
|
|
348
|
+
return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
|
|
349
|
+
FROM ${table} WHERE id = ? LIMIT 1`).get(sessionId);
|
|
350
|
+
}
|
|
351
|
+
return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
|
|
352
|
+
FROM ${table}
|
|
353
|
+
WHERE tokens_input > 0 OR tokens_cache_read > 0 OR tokens_cache_write > 0
|
|
354
|
+
ORDER BY time_updated DESC LIMIT 1`).get();
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/context-window.ts
|
|
361
|
+
function lastUsageBlock(ctx) {
|
|
362
|
+
const path = ctx.transcriptPath;
|
|
363
|
+
if (!path || !existsSync4(path))
|
|
364
|
+
return null;
|
|
365
|
+
let lines;
|
|
366
|
+
try {
|
|
367
|
+
lines = readFileSync3(path, "utf8").split(`
|
|
206
368
|
`);
|
|
207
369
|
} catch {
|
|
208
370
|
return null;
|
|
@@ -216,26 +378,48 @@ function contextUsage(ctx) {
|
|
|
216
378
|
const usage = entry?.message?.usage;
|
|
217
379
|
if (!usage || typeof usage.input_tokens !== "number")
|
|
218
380
|
continue;
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
used,
|
|
222
|
-
output: usage.output_tokens || 0,
|
|
223
|
-
window: windowSize(ctx)
|
|
224
|
-
};
|
|
381
|
+
return usage;
|
|
225
382
|
} catch {
|
|
226
383
|
continue;
|
|
227
384
|
}
|
|
228
385
|
}
|
|
229
386
|
return null;
|
|
230
387
|
}
|
|
388
|
+
function contextUsage(ctx) {
|
|
389
|
+
const usage = lastUsageBlock(ctx);
|
|
390
|
+
if (!usage)
|
|
391
|
+
return null;
|
|
392
|
+
const used = usage.input_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
|
393
|
+
return {
|
|
394
|
+
used,
|
|
395
|
+
output: usage.output_tokens || 0,
|
|
396
|
+
window: windowSize(ctx)
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function cacheRate(ctx) {
|
|
400
|
+
if (ctx.provider === "codex")
|
|
401
|
+
return codexCacheRate();
|
|
402
|
+
if (ctx.provider === "opencode")
|
|
403
|
+
return opencodeCacheRate();
|
|
404
|
+
const usage = lastUsageBlock(ctx);
|
|
405
|
+
if (!usage)
|
|
406
|
+
return null;
|
|
407
|
+
const read = usage.cache_read_input_tokens || 0;
|
|
408
|
+
const creation = usage.cache_creation_input_tokens || 0;
|
|
409
|
+
const input = usage.input_tokens || 0;
|
|
410
|
+
const divisor = input + creation + read;
|
|
411
|
+
if (divisor <= 0)
|
|
412
|
+
return null;
|
|
413
|
+
return read / divisor;
|
|
414
|
+
}
|
|
231
415
|
function windowSize(ctx) {
|
|
232
416
|
return ctx.model?.id?.includes("[1m]") ? 1e6 : 200000;
|
|
233
417
|
}
|
|
234
418
|
|
|
235
419
|
// src/accounts.ts
|
|
236
|
-
import { existsSync as
|
|
237
|
-
import { homedir as
|
|
238
|
-
import { join as
|
|
420
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
421
|
+
import { homedir as homedir4 } from "os";
|
|
422
|
+
import { join as join4, resolve, sep } from "path";
|
|
239
423
|
var CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
|
|
240
424
|
var DEFAULT_CONFIG_SUBDIR = ".claude";
|
|
241
425
|
var STORE_PATH_ENV = "ACCOUNTS_STORE_PATH";
|
|
@@ -259,10 +443,10 @@ function str(value) {
|
|
|
259
443
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
260
444
|
}
|
|
261
445
|
function home(env) {
|
|
262
|
-
return str(env.HOME) ??
|
|
446
|
+
return str(env.HOME) ?? homedir4();
|
|
263
447
|
}
|
|
264
448
|
function expandHome(path, env) {
|
|
265
|
-
return path === "~" || path.startsWith(`~${sep}`) ?
|
|
449
|
+
return path === "~" || path.startsWith(`~${sep}`) ? join4(home(env), path.slice(1)) : path;
|
|
266
450
|
}
|
|
267
451
|
function canonical(path, env) {
|
|
268
452
|
const abs = resolve(expandHome(path, env));
|
|
@@ -274,7 +458,7 @@ function canonical(path, env) {
|
|
|
274
458
|
}
|
|
275
459
|
function readJson(path) {
|
|
276
460
|
try {
|
|
277
|
-
return JSON.parse(
|
|
461
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
278
462
|
} catch {
|
|
279
463
|
return null;
|
|
280
464
|
}
|
|
@@ -295,14 +479,14 @@ async function accountsPaths(env) {
|
|
|
295
479
|
return { store: storage.storePath(), profiles: storage.profilesDir() };
|
|
296
480
|
} catch {}
|
|
297
481
|
}
|
|
298
|
-
const base = homeOverride ??
|
|
482
|
+
const base = homeOverride ?? join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
|
|
299
483
|
return {
|
|
300
|
-
store: storeOverride ??
|
|
301
|
-
profiles:
|
|
484
|
+
store: storeOverride ?? join4(base, STORE_FILE),
|
|
485
|
+
profiles: join4(base, PROFILES_SUBDIR)
|
|
302
486
|
};
|
|
303
487
|
}
|
|
304
488
|
function registryEntry(store, configDir, env, tool) {
|
|
305
|
-
if (!
|
|
489
|
+
if (!existsSync5(store))
|
|
306
490
|
return null;
|
|
307
491
|
const entries = readJson(store)?.profiles;
|
|
308
492
|
if (!Array.isArray(entries))
|
|
@@ -323,7 +507,7 @@ function registryEntry(store, configDir, env, tool) {
|
|
|
323
507
|
return tool ? untyped : first;
|
|
324
508
|
}
|
|
325
509
|
function layoutProfile(profilesRoot, configDir, env) {
|
|
326
|
-
if (!
|
|
510
|
+
if (!existsSync5(configDir))
|
|
327
511
|
return null;
|
|
328
512
|
const prefix = canonical(profilesRoot, env) + sep;
|
|
329
513
|
if (!configDir.startsWith(prefix))
|
|
@@ -337,7 +521,7 @@ function layoutProfile(profilesRoot, configDir, env) {
|
|
|
337
521
|
return { configDir, profile, tool, source: "layout" };
|
|
338
522
|
}
|
|
339
523
|
function switchedOccupant(configDir) {
|
|
340
|
-
const raw = readJson(
|
|
524
|
+
const raw = readJson(join4(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
|
|
341
525
|
if (!raw)
|
|
342
526
|
return null;
|
|
343
527
|
const profile = str(raw.profile);
|
|
@@ -345,17 +529,17 @@ function switchedOccupant(configDir) {
|
|
|
345
529
|
return profile || email ? { profile, email } : null;
|
|
346
530
|
}
|
|
347
531
|
function sessionConfigDir(env = process.env) {
|
|
348
|
-
return canonical(str(env[CONFIG_DIR_ENV]) ??
|
|
532
|
+
return canonical(str(env[CONFIG_DIR_ENV]) ?? join4(home(env), DEFAULT_CONFIG_SUBDIR), env);
|
|
349
533
|
}
|
|
350
534
|
function sessionStateFile(env = process.env) {
|
|
351
535
|
const base = canonical(str(env[CONFIG_DIR_ENV]) ?? home(env), env);
|
|
352
|
-
const preferred =
|
|
353
|
-
if (
|
|
536
|
+
const preferred = join4(base, STATE_FILE_PREFERRED);
|
|
537
|
+
if (existsSync5(preferred))
|
|
354
538
|
return preferred;
|
|
355
539
|
const variants = str(env[CUSTOM_OAUTH_ENV]) ? ["-custom-oauth", ...OAUTH_VARIANTS] : OAUTH_VARIANTS;
|
|
356
540
|
for (const variant of variants) {
|
|
357
|
-
const candidate =
|
|
358
|
-
if (
|
|
541
|
+
const candidate = join4(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
|
|
542
|
+
if (existsSync5(candidate))
|
|
359
543
|
return candidate;
|
|
360
544
|
}
|
|
361
545
|
return null;
|
|
@@ -403,7 +587,7 @@ async function sessionAccountEmail(env = process.env, tool) {
|
|
|
403
587
|
}
|
|
404
588
|
function accountsHomeDir(env) {
|
|
405
589
|
const override = str(env[ACCOUNTS_HOME_ENV]);
|
|
406
|
-
return override ? expandHome(override, env) :
|
|
590
|
+
return override ? expandHome(override, env) : join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
|
|
407
591
|
}
|
|
408
592
|
function sessionAccountUuid(env = process.env) {
|
|
409
593
|
try {
|
|
@@ -476,8 +660,8 @@ function sessionUsage(env = process.env, now = new Date) {
|
|
|
476
660
|
const uuid = sessionAccountUuid(env);
|
|
477
661
|
if (!uuid)
|
|
478
662
|
return empty;
|
|
479
|
-
const path =
|
|
480
|
-
if (!
|
|
663
|
+
const path = join4(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
|
|
664
|
+
if (!existsSync5(path))
|
|
481
665
|
return empty;
|
|
482
666
|
const entry = readJson(path);
|
|
483
667
|
if (!entry || entry.accountUuid !== uuid)
|
|
@@ -492,11 +676,11 @@ function sessionUsage(env = process.env, now = new Date) {
|
|
|
492
676
|
}
|
|
493
677
|
|
|
494
678
|
// src/settings.ts
|
|
495
|
-
import { readFileSync as
|
|
496
|
-
import { join as
|
|
679
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
680
|
+
import { join as join5 } from "path";
|
|
497
681
|
function sessionFastMode(env = process.env) {
|
|
498
682
|
try {
|
|
499
|
-
const raw =
|
|
683
|
+
const raw = readFileSync5(join5(sessionConfigDir(env), "settings.json"), "utf8");
|
|
500
684
|
return JSON.parse(raw)?.fastMode === true;
|
|
501
685
|
} catch {
|
|
502
686
|
return false;
|
|
@@ -520,6 +704,29 @@ function friendlyModel(id) {
|
|
|
520
704
|
return { name: name || mid, tag };
|
|
521
705
|
}
|
|
522
706
|
var dir = (ctx) => ctx.cwd || process.cwd();
|
|
707
|
+
function abbreviateHome(path) {
|
|
708
|
+
const home2 = homedir5();
|
|
709
|
+
if (!home2 || home2 === "/")
|
|
710
|
+
return path;
|
|
711
|
+
if (path === home2)
|
|
712
|
+
return "~";
|
|
713
|
+
return path.startsWith(`${home2}/`) ? `~${path.slice(home2.length)}` : path;
|
|
714
|
+
}
|
|
715
|
+
var CWD_MAX = 40;
|
|
716
|
+
function truncateLeft(path, max) {
|
|
717
|
+
return path.length <= max ? path : `\u2026${path.slice(path.length - (max - 1))}`;
|
|
718
|
+
}
|
|
719
|
+
function contextColor(ctx) {
|
|
720
|
+
const u = contextUsage(ctx);
|
|
721
|
+
if (!u || !u.window)
|
|
722
|
+
return null;
|
|
723
|
+
const pct = Math.min(100, Math.round(u.used / u.window * 100));
|
|
724
|
+
if (pct >= 80)
|
|
725
|
+
return "red";
|
|
726
|
+
if (pct >= 50)
|
|
727
|
+
return "yellow";
|
|
728
|
+
return "green";
|
|
729
|
+
}
|
|
523
730
|
function modelName(ctx) {
|
|
524
731
|
if (ctx.model?.displayName)
|
|
525
732
|
return ctx.model.displayName.toLowerCase();
|
|
@@ -528,6 +735,12 @@ function modelName(ctx) {
|
|
|
528
735
|
function limitPercent(pct) {
|
|
529
736
|
return typeof pct === "number" ? pct : 0;
|
|
530
737
|
}
|
|
738
|
+
function anthropicSession(ctx) {
|
|
739
|
+
if (ctx.rateLimits)
|
|
740
|
+
return true;
|
|
741
|
+
const id = ctx.model?.id;
|
|
742
|
+
return typeof id === "string" && /claude/i.test(id);
|
|
743
|
+
}
|
|
531
744
|
function usageLabel(prefix, headroom) {
|
|
532
745
|
return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
|
|
533
746
|
}
|
|
@@ -597,10 +810,23 @@ var segments = [
|
|
|
597
810
|
defaultEnabled: false,
|
|
598
811
|
render: (ctx) => dir(ctx).split("/").pop() || null
|
|
599
812
|
},
|
|
813
|
+
{
|
|
814
|
+
id: "cwd",
|
|
815
|
+
description: "Current working directory (full path)",
|
|
816
|
+
defaultEnabled: false,
|
|
817
|
+
color: "blue",
|
|
818
|
+
render(ctx) {
|
|
819
|
+
const path = ctx.cwd;
|
|
820
|
+
if (typeof path !== "string" || !path)
|
|
821
|
+
return null;
|
|
822
|
+
return truncateLeft(abbreviateHome(path), CWD_MAX);
|
|
823
|
+
}
|
|
824
|
+
},
|
|
600
825
|
{
|
|
601
826
|
id: "model",
|
|
602
827
|
description: "Current model name",
|
|
603
828
|
defaultEnabled: false,
|
|
829
|
+
color: "dim",
|
|
604
830
|
render(ctx) {
|
|
605
831
|
if (!ctx.model?.id)
|
|
606
832
|
return null;
|
|
@@ -611,6 +837,7 @@ var segments = [
|
|
|
611
837
|
id: "model-context",
|
|
612
838
|
description: "Model name with context-size tag (e.g. [1m])",
|
|
613
839
|
defaultEnabled: true,
|
|
840
|
+
color: "dim",
|
|
614
841
|
render(ctx) {
|
|
615
842
|
if (!ctx.model?.id)
|
|
616
843
|
return null;
|
|
@@ -622,7 +849,7 @@ var segments = [
|
|
|
622
849
|
id: "model-with-reasoning",
|
|
623
850
|
description: "Model name with its reasoning effort (or thinking, when on)",
|
|
624
851
|
defaultEnabled: false,
|
|
625
|
-
color: "
|
|
852
|
+
color: "dim",
|
|
626
853
|
render(ctx) {
|
|
627
854
|
const name = modelName(ctx);
|
|
628
855
|
if (!name)
|
|
@@ -682,15 +909,15 @@ var segments = [
|
|
|
682
909
|
id: "usage-session",
|
|
683
910
|
description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
|
|
684
911
|
defaultEnabled: false,
|
|
685
|
-
color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
|
|
686
|
-
render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
|
|
912
|
+
color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).sessionHeadroom) : null,
|
|
913
|
+
render: (ctx) => anthropicSession(ctx) ? usageLabel("5h", sessionUsage(process.env).sessionHeadroom) : null
|
|
687
914
|
},
|
|
688
915
|
{
|
|
689
916
|
id: "usage-weekly",
|
|
690
917
|
description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
|
|
691
918
|
defaultEnabled: false,
|
|
692
|
-
color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
|
|
693
|
-
render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
|
|
919
|
+
color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).weeklyHeadroom, true) : null,
|
|
920
|
+
render: (ctx) => anthropicSession(ctx) ? usageLabel("7d", sessionUsage(process.env).weeklyHeadroom) : null
|
|
694
921
|
},
|
|
695
922
|
{
|
|
696
923
|
id: "thread-title",
|
|
@@ -703,17 +930,19 @@ var segments = [
|
|
|
703
930
|
id: "context-used",
|
|
704
931
|
description: "Percentage of context window used (omitted when unknown)",
|
|
705
932
|
defaultEnabled: false,
|
|
933
|
+
color: contextColor,
|
|
706
934
|
render(ctx) {
|
|
707
935
|
const u = contextUsage(ctx);
|
|
708
936
|
if (!u)
|
|
709
937
|
return null;
|
|
710
|
-
return
|
|
938
|
+
return `ctx ${Math.min(100, Math.round(u.used / u.window * 100))}%`;
|
|
711
939
|
}
|
|
712
940
|
},
|
|
713
941
|
{
|
|
714
942
|
id: "context-remaining",
|
|
715
943
|
description: "Percentage of context window remaining (omitted when unknown)",
|
|
716
944
|
defaultEnabled: true,
|
|
945
|
+
color: contextColor,
|
|
717
946
|
render(ctx) {
|
|
718
947
|
const u = contextUsage(ctx);
|
|
719
948
|
if (!u)
|
|
@@ -732,10 +961,20 @@ var segments = [
|
|
|
732
961
|
return `${compactNum(u.used + u.output)} tok`;
|
|
733
962
|
}
|
|
734
963
|
},
|
|
964
|
+
{
|
|
965
|
+
id: "cache-rate",
|
|
966
|
+
description: "Percentage of input tokens served from cache (omitted when unknown)",
|
|
967
|
+
defaultEnabled: true,
|
|
968
|
+
render(ctx) {
|
|
969
|
+
const r = cacheRate(ctx);
|
|
970
|
+
return r === null ? null : `${Math.round(r * 100)}% cached`;
|
|
971
|
+
}
|
|
972
|
+
},
|
|
735
973
|
{
|
|
736
974
|
id: "cost",
|
|
737
975
|
description: "Session cost in USD (omitted when zero)",
|
|
738
976
|
defaultEnabled: true,
|
|
977
|
+
color: "dim",
|
|
739
978
|
render(ctx) {
|
|
740
979
|
const c = ctx.cost?.totalCostUsd;
|
|
741
980
|
return c ? money(c) : null;
|
|
@@ -745,6 +984,7 @@ var segments = [
|
|
|
745
984
|
id: "duration",
|
|
746
985
|
description: "Session wall-clock duration",
|
|
747
986
|
defaultEnabled: false,
|
|
987
|
+
color: "dim",
|
|
748
988
|
render(ctx) {
|
|
749
989
|
const ms = ctx.cost?.totalDurationMs;
|
|
750
990
|
return ms ? compactDuration(ms) : null;
|
|
@@ -839,7 +1079,7 @@ async function renderLine(ctx, cfg) {
|
|
|
839
1079
|
// package.json
|
|
840
1080
|
var package_default = {
|
|
841
1081
|
name: "@hasna/statusline",
|
|
842
|
-
version: "0.0.
|
|
1082
|
+
version: "0.0.8",
|
|
843
1083
|
description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
|
|
844
1084
|
license: "Apache-2.0",
|
|
845
1085
|
type: "module",
|
|
@@ -878,11 +1118,12 @@ var package_default = {
|
|
|
878
1118
|
},
|
|
879
1119
|
repository: {
|
|
880
1120
|
type: "git",
|
|
881
|
-
url: "
|
|
1121
|
+
url: "https://github.com/hasna/apps.git",
|
|
1122
|
+
directory: "apps/statusline"
|
|
882
1123
|
},
|
|
883
|
-
homepage: "https://github.com/hasna/statusline#readme",
|
|
1124
|
+
homepage: "https://github.com/hasna/apps/tree/main/apps/statusline#readme",
|
|
884
1125
|
bugs: {
|
|
885
|
-
url: "https://github.com/hasna/
|
|
1126
|
+
url: "https://github.com/hasna/apps/issues"
|
|
886
1127
|
},
|
|
887
1128
|
keywords: [
|
|
888
1129
|
"statusline",
|
|
@@ -899,36 +1140,37 @@ var package_default = {
|
|
|
899
1140
|
bun: ">=1.0.0"
|
|
900
1141
|
},
|
|
901
1142
|
dependencies: {
|
|
902
|
-
"@modelcontextprotocol/sdk": "1.
|
|
1143
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
903
1144
|
zod: "3.25.76"
|
|
904
1145
|
},
|
|
905
1146
|
devDependencies: {
|
|
906
|
-
"@types/bun": "
|
|
907
|
-
typescript: "5.9.3"
|
|
1147
|
+
"@types/bun": "1.3.14",
|
|
1148
|
+
typescript: "5.9.3",
|
|
1149
|
+
"bun-types": "1.3.14"
|
|
908
1150
|
}
|
|
909
1151
|
};
|
|
910
1152
|
// src/install.ts
|
|
911
|
-
import { copyFileSync, existsSync as
|
|
912
|
-
import { homedir as
|
|
913
|
-
import { dirname as dirname2, join as
|
|
1153
|
+
import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1154
|
+
import { homedir as homedir6 } from "os";
|
|
1155
|
+
import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
|
|
914
1156
|
function defaultSettingsPath(env) {
|
|
915
|
-
return
|
|
1157
|
+
return join6(env.HOME || homedir6(), ".claude", "settings.json");
|
|
916
1158
|
}
|
|
917
1159
|
function claudeSettingsPath(env = process.env) {
|
|
918
|
-
const home2 = env.HOME ||
|
|
1160
|
+
const home2 = env.HOME || homedir6();
|
|
919
1161
|
const raw = env.CLAUDE_CONFIG_DIR?.trim();
|
|
920
1162
|
if (!raw)
|
|
921
1163
|
return defaultSettingsPath(env);
|
|
922
|
-
const configDir = raw === "~" || raw.startsWith("~/") ?
|
|
923
|
-
return
|
|
1164
|
+
const configDir = raw === "~" || raw.startsWith("~/") ? join6(home2, raw.slice(1)) : raw;
|
|
1165
|
+
return join6(configDir, "settings.json");
|
|
924
1166
|
}
|
|
925
1167
|
function installClaude(settingsPath, env = process.env) {
|
|
926
1168
|
const target = settingsPath ?? claudeSettingsPath(env);
|
|
927
1169
|
const binary = Bun.which("statusline") || "statusline";
|
|
928
1170
|
let settings = {};
|
|
929
|
-
if (
|
|
1171
|
+
if (existsSync6(target)) {
|
|
930
1172
|
try {
|
|
931
|
-
settings = JSON.parse(
|
|
1173
|
+
settings = JSON.parse(readFileSync6(target, "utf8"));
|
|
932
1174
|
} catch (error) {
|
|
933
1175
|
throw new Error(`${target} is not valid JSON \u2014 fix or move it first`);
|
|
934
1176
|
}
|
|
@@ -936,7 +1178,7 @@ function installClaude(settingsPath, env = process.env) {
|
|
|
936
1178
|
}
|
|
937
1179
|
settings.statusLine = { ...settings.statusLine, type: "command", command: `${binary} render` };
|
|
938
1180
|
const configDir = dirname2(target);
|
|
939
|
-
if (!
|
|
1181
|
+
if (!existsSync6(configDir)) {
|
|
940
1182
|
if (resolve2(target) !== resolve2(defaultSettingsPath(env))) {
|
|
941
1183
|
throw new Error(`config dir ${configDir} does not exist \u2014 create the profile first`);
|
|
942
1184
|
}
|
|
@@ -1003,6 +1245,14 @@ function resetStatuslineConfig() {
|
|
|
1003
1245
|
return defaultConfig();
|
|
1004
1246
|
}
|
|
1005
1247
|
async function renderStatusline(input = {}, config = loadConfig()) {
|
|
1248
|
+
const provider = process.env.STATUSLINE_PROVIDER;
|
|
1249
|
+
if (provider === "codex" || provider === "opencode") {
|
|
1250
|
+
const ctx = {
|
|
1251
|
+
provider,
|
|
1252
|
+
cwd: typeof input.cwd === "string" ? input.cwd : process.cwd()
|
|
1253
|
+
};
|
|
1254
|
+
return renderLine(ctx, config);
|
|
1255
|
+
}
|
|
1006
1256
|
return renderLine(parseClaudeInput(input), config);
|
|
1007
1257
|
}
|
|
1008
1258
|
function sampleClaudeInput(cwd = process.cwd()) {
|
package/dist/context-window.d.ts
CHANGED
|
@@ -7,8 +7,30 @@ export interface ContextUsage {
|
|
|
7
7
|
/** Context window size in tokens. */
|
|
8
8
|
window: number;
|
|
9
9
|
}
|
|
10
|
+
/** The raw `message.usage` object of the last assistant entry that carries one. */
|
|
11
|
+
export interface UsageBlock {
|
|
12
|
+
input_tokens: number;
|
|
13
|
+
cache_creation_input_tokens?: number;
|
|
14
|
+
cache_read_input_tokens?: number;
|
|
15
|
+
output_tokens?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Scan the session transcript (JSONL) from the end and return the raw usage
|
|
19
|
+
* block of the last assistant entry that carries one — the block that
|
|
20
|
+
* reflects what the context window currently holds. Lines that are malformed
|
|
21
|
+
* or whose usage block lacks a numeric `input_tokens` are skipped, exactly
|
|
22
|
+
* like `contextUsage` used to skip them inline.
|
|
23
|
+
*/
|
|
24
|
+
export declare function lastUsageBlock(ctx: StatusContext): UsageBlock | null;
|
|
10
25
|
/**
|
|
11
26
|
* Derive context usage from the session transcript (JSONL). The last
|
|
12
27
|
* assistant entry's usage block reflects what the context window holds.
|
|
13
28
|
*/
|
|
14
29
|
export declare function contextUsage(ctx: StatusContext): ContextUsage | null;
|
|
30
|
+
/**
|
|
31
|
+
* Fraction of input-side tokens served from cache, dispatched on the
|
|
32
|
+
* context's provider: the Claude transcript usage block, the Codex session
|
|
33
|
+
* rollout's latest token_count, or the OpenCode sessions DB. Null when the
|
|
34
|
+
* source is absent or the divisor is zero — never a wrong number.
|
|
35
|
+
*/
|
|
36
|
+
export declare function cacheRate(ctx: StatusContext): number | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type StatuslineConfig } from "./config.js";
|
|
2
2
|
export { sessionAccount, sessionAccountEmail, sessionConfigDir, sessionStateFile, type SessionAccount, } from "./accounts.js";
|
|
3
3
|
export { defaultConfig, loadConfig, saveConfig, configPath, type StatuslineConfig } from "./config.js";
|
|
4
|
-
export { contextUsage, type ContextUsage } from "./context-window.js";
|
|
4
|
+
export { cacheRate, contextUsage, lastUsageBlock, type ContextUsage, type UsageBlock } from "./context-window.js";
|
|
5
5
|
export { compactAge, compactDuration, compactNum, money } from "./format.js";
|
|
6
6
|
export { gitBranch, gitProjectName, gitRoot, lastCommitEpoch, trackedLineCount } from "./git.js";
|
|
7
7
|
export { claudeSettingsPath, installClaude } from "./install.js";
|
|
8
|
+
export { codexCacheRate, latestCodexSessionPath, type TotalTokenUsage } from "./providers/codex.js";
|
|
9
|
+
export { opencodeCacheRate, type OpenCodeCacheRateOptions } from "./providers/opencode.js";
|
|
8
10
|
export { parseClaudeInput } from "./providers/claude.js";
|
|
9
11
|
export type { Segment, SegmentColor, StatusContext } from "./providers/types.js";
|
|
10
12
|
export { renderLine } from "./render.js";
|