@hasna/statusline 0.0.7 → 0.0.9
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 +98 -31
- package/dist/cli.js +429 -62
- package/dist/context-window.d.ts +22 -0
- package/dist/disk.d.ts +51 -0
- package/dist/format.d.ts +7 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +435 -63
- package/dist/mcp/index.js +19939 -6483
- package/dist/providers/codex.d.ts +19 -0
- package/dist/providers/opencode.d.ts +16 -0
- package/dist/providers/types.d.ts +10 -3
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
3
33
|
var __returnValue = (v) => v;
|
|
4
34
|
function __exportSetter(name, newValue) {
|
|
5
35
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -29,6 +59,7 @@ function defaultConfig() {
|
|
|
29
59
|
"loc",
|
|
30
60
|
"model-context",
|
|
31
61
|
"context-remaining",
|
|
62
|
+
"cache-rate",
|
|
32
63
|
"cost"
|
|
33
64
|
]
|
|
34
65
|
};
|
|
@@ -94,7 +125,7 @@ function numberOr(v) {
|
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
// src/segments/index.ts
|
|
97
|
-
import { hostname } from "os";
|
|
128
|
+
import { homedir as homedir5, hostname } from "os";
|
|
98
129
|
|
|
99
130
|
// src/format.ts
|
|
100
131
|
function compactAge(seconds) {
|
|
@@ -125,6 +156,23 @@ function compactNum(n) {
|
|
|
125
156
|
return `${trimmed(n / 1000)}k`;
|
|
126
157
|
return `${n}`;
|
|
127
158
|
}
|
|
159
|
+
var BYTE_UNITS = ["K", "M", "G", "T", "P"];
|
|
160
|
+
function compactBytes(bytes) {
|
|
161
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
162
|
+
return "";
|
|
163
|
+
if (bytes < 1024)
|
|
164
|
+
return `${Math.floor(bytes)}B`;
|
|
165
|
+
let value = bytes / 1024;
|
|
166
|
+
let unit = 0;
|
|
167
|
+
while (unit < BYTE_UNITS.length - 1) {
|
|
168
|
+
const rounded = Number(value.toFixed(1));
|
|
169
|
+
if (rounded < 1024)
|
|
170
|
+
break;
|
|
171
|
+
value /= 1024;
|
|
172
|
+
unit += 1;
|
|
173
|
+
}
|
|
174
|
+
return `${trimmed(value)}${BYTE_UNITS[unit]}`;
|
|
175
|
+
}
|
|
128
176
|
function money(n) {
|
|
129
177
|
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
130
178
|
}
|
|
@@ -134,10 +182,10 @@ function compactDuration(ms) {
|
|
|
134
182
|
return `${s}s`;
|
|
135
183
|
if (s < 3600) {
|
|
136
184
|
const rem2 = s % 60;
|
|
137
|
-
return rem2 ? `${Math.floor(s / 60)}m${rem2}s` : `${Math.floor(s / 60)}m`;
|
|
185
|
+
return rem2 ? `${Math.floor(s / 60)}m ${rem2}s` : `${Math.floor(s / 60)}m`;
|
|
138
186
|
}
|
|
139
187
|
const rem = Math.floor(s % 3600 / 60);
|
|
140
|
-
return rem ? `${Math.floor(s / 3600)}h${rem}m` : `${Math.floor(s / 3600)}h`;
|
|
188
|
+
return rem ? `${Math.floor(s / 3600)}h ${rem}m` : `${Math.floor(s / 3600)}h`;
|
|
141
189
|
}
|
|
142
190
|
|
|
143
191
|
// src/git.ts
|
|
@@ -194,14 +242,145 @@ function trackedLineCount(cwd) {
|
|
|
194
242
|
}
|
|
195
243
|
|
|
196
244
|
// src/context-window.ts
|
|
197
|
-
import { existsSync as
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
246
|
+
|
|
247
|
+
// src/providers/codex.ts
|
|
248
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
249
|
+
import { homedir as homedir2 } from "os";
|
|
250
|
+
import { join as join2 } from "path";
|
|
251
|
+
function latestCodexSessionPath(home = homedir2()) {
|
|
252
|
+
const root = join2(home, ".codex", "sessions");
|
|
253
|
+
if (!existsSync2(root))
|
|
254
|
+
return null;
|
|
255
|
+
let newest = null;
|
|
256
|
+
let newestMtime = 0;
|
|
257
|
+
try {
|
|
258
|
+
for (const year of readdirSync(root)) {
|
|
259
|
+
const yearDir = join2(root, year);
|
|
260
|
+
if (!statSync(yearDir, { throwIfNoEntry: false })?.isDirectory())
|
|
261
|
+
continue;
|
|
262
|
+
for (const month of readdirSync(yearDir)) {
|
|
263
|
+
const monthDir = join2(yearDir, month);
|
|
264
|
+
if (!statSync(monthDir, { throwIfNoEntry: false })?.isDirectory())
|
|
265
|
+
continue;
|
|
266
|
+
for (const day of readdirSync(monthDir)) {
|
|
267
|
+
const dayDir = join2(monthDir, day);
|
|
268
|
+
if (!statSync(dayDir, { throwIfNoEntry: false })?.isDirectory())
|
|
269
|
+
continue;
|
|
270
|
+
for (const file of readdirSync(dayDir)) {
|
|
271
|
+
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl"))
|
|
272
|
+
continue;
|
|
273
|
+
const full = join2(dayDir, file);
|
|
274
|
+
const mtime = statSync(full, { throwIfNoEntry: false })?.mtimeMs ?? 0;
|
|
275
|
+
if (mtime > newestMtime) {
|
|
276
|
+
newestMtime = mtime;
|
|
277
|
+
newest = full;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return newest;
|
|
287
|
+
}
|
|
288
|
+
function codexCacheRate(sessionPath) {
|
|
289
|
+
const path = sessionPath ?? latestCodexSessionPath();
|
|
200
290
|
if (!path || !existsSync2(path))
|
|
201
291
|
return null;
|
|
202
292
|
let lines;
|
|
203
293
|
try {
|
|
204
294
|
lines = readFileSync2(path, "utf8").split(`
|
|
295
|
+
`);
|
|
296
|
+
} catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
let usage = null;
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
if (!line || !line.includes("token_count"))
|
|
302
|
+
continue;
|
|
303
|
+
try {
|
|
304
|
+
const entry = JSON.parse(line);
|
|
305
|
+
if (entry?.type !== "event_msg")
|
|
306
|
+
continue;
|
|
307
|
+
if (entry?.payload?.type !== "token_count")
|
|
308
|
+
continue;
|
|
309
|
+
const info = entry?.payload?.info?.total_token_usage;
|
|
310
|
+
if (info && typeof info === "object")
|
|
311
|
+
usage = info;
|
|
312
|
+
} catch {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!usage)
|
|
317
|
+
return null;
|
|
318
|
+
const read = usage.cached_input_tokens || 0;
|
|
319
|
+
const input = usage.input_tokens || 0;
|
|
320
|
+
const write = usage.cache_write_input_tokens || 0;
|
|
321
|
+
const divisor = input + read + write;
|
|
322
|
+
if (divisor <= 0)
|
|
323
|
+
return null;
|
|
324
|
+
return Math.min(1, Math.max(0, read / divisor));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/providers/opencode.ts
|
|
328
|
+
import { existsSync as existsSync3 } from "fs";
|
|
329
|
+
import { homedir as homedir3 } from "os";
|
|
330
|
+
import { join as join3 } from "path";
|
|
331
|
+
import { Database } from "bun:sqlite";
|
|
332
|
+
function opencodeCacheRate(opts = {}) {
|
|
333
|
+
const dbPath = opts.dbPath ?? join3(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
334
|
+
if (!existsSync3(dbPath))
|
|
335
|
+
return null;
|
|
336
|
+
let db;
|
|
337
|
+
try {
|
|
338
|
+
db = new Database(dbPath, { readonly: true });
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
let row = tokenRow(db, "session", opts.sessionId);
|
|
344
|
+
if (!row)
|
|
345
|
+
row = tokenRow(db, "session_v2", opts.sessionId);
|
|
346
|
+
if (!row)
|
|
347
|
+
return null;
|
|
348
|
+
const input = row.tokens_input ?? 0;
|
|
349
|
+
const read = row.tokens_cache_read ?? 0;
|
|
350
|
+
const write = row.tokens_cache_write ?? 0;
|
|
351
|
+
const divisor = input + read + write;
|
|
352
|
+
if (divisor <= 0)
|
|
353
|
+
return null;
|
|
354
|
+
return Math.min(1, Math.max(0, read / divisor));
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
} finally {
|
|
358
|
+
db.close();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function tokenRow(db, table, sessionId) {
|
|
362
|
+
try {
|
|
363
|
+
if (sessionId) {
|
|
364
|
+
return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
|
|
365
|
+
FROM ${table} WHERE id = ? LIMIT 1`).get(sessionId);
|
|
366
|
+
}
|
|
367
|
+
return db.query(`SELECT tokens_input, tokens_cache_read, tokens_cache_write
|
|
368
|
+
FROM ${table}
|
|
369
|
+
WHERE tokens_input > 0 OR tokens_cache_read > 0 OR tokens_cache_write > 0
|
|
370
|
+
ORDER BY time_updated DESC LIMIT 1`).get();
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/context-window.ts
|
|
377
|
+
function lastUsageBlock(ctx) {
|
|
378
|
+
const path = ctx.transcriptPath;
|
|
379
|
+
if (!path || !existsSync4(path))
|
|
380
|
+
return null;
|
|
381
|
+
let lines;
|
|
382
|
+
try {
|
|
383
|
+
lines = readFileSync3(path, "utf8").split(`
|
|
205
384
|
`);
|
|
206
385
|
} catch {
|
|
207
386
|
return null;
|
|
@@ -215,26 +394,137 @@ function contextUsage(ctx) {
|
|
|
215
394
|
const usage = entry?.message?.usage;
|
|
216
395
|
if (!usage || typeof usage.input_tokens !== "number")
|
|
217
396
|
continue;
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
used,
|
|
221
|
-
output: usage.output_tokens || 0,
|
|
222
|
-
window: windowSize(ctx)
|
|
223
|
-
};
|
|
397
|
+
return usage;
|
|
224
398
|
} catch {
|
|
225
399
|
continue;
|
|
226
400
|
}
|
|
227
401
|
}
|
|
228
402
|
return null;
|
|
229
403
|
}
|
|
404
|
+
function contextUsage(ctx) {
|
|
405
|
+
const usage = lastUsageBlock(ctx);
|
|
406
|
+
if (!usage)
|
|
407
|
+
return null;
|
|
408
|
+
const used = usage.input_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
|
409
|
+
return {
|
|
410
|
+
used,
|
|
411
|
+
output: usage.output_tokens || 0,
|
|
412
|
+
window: windowSize(ctx)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function cacheRate(ctx) {
|
|
416
|
+
if (ctx.provider === "codex")
|
|
417
|
+
return codexCacheRate();
|
|
418
|
+
if (ctx.provider === "opencode")
|
|
419
|
+
return opencodeCacheRate();
|
|
420
|
+
const usage = lastUsageBlock(ctx);
|
|
421
|
+
if (!usage)
|
|
422
|
+
return null;
|
|
423
|
+
const read = usage.cache_read_input_tokens || 0;
|
|
424
|
+
const creation = usage.cache_creation_input_tokens || 0;
|
|
425
|
+
const input = usage.input_tokens || 0;
|
|
426
|
+
const divisor = input + creation + read;
|
|
427
|
+
if (divisor <= 0)
|
|
428
|
+
return null;
|
|
429
|
+
return read / divisor;
|
|
430
|
+
}
|
|
230
431
|
function windowSize(ctx) {
|
|
231
432
|
return ctx.model?.id?.includes("[1m]") ? 1e6 : 200000;
|
|
232
433
|
}
|
|
233
434
|
|
|
435
|
+
// src/disk.ts
|
|
436
|
+
import { statfsSync } from "fs";
|
|
437
|
+
var DISK_MEMO = Symbol("statusline.disk-free");
|
|
438
|
+
var DISK_WARN_PERCENT = 20;
|
|
439
|
+
var DISK_CRITICAL_PERCENT = 10;
|
|
440
|
+
function finiteNonNegative(value) {
|
|
441
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
442
|
+
}
|
|
443
|
+
function readDiskStats(path) {
|
|
444
|
+
if (typeof statfsSync !== "function")
|
|
445
|
+
return null;
|
|
446
|
+
try {
|
|
447
|
+
const stats = statfsSync(path);
|
|
448
|
+
if (!stats)
|
|
449
|
+
return null;
|
|
450
|
+
return {
|
|
451
|
+
bsize: Number(stats.bsize),
|
|
452
|
+
blocks: Number(stats.blocks),
|
|
453
|
+
bavail: Number(stats.bavail)
|
|
454
|
+
};
|
|
455
|
+
} catch {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function diskFree(path, reader = readDiskStats) {
|
|
460
|
+
let stats;
|
|
461
|
+
try {
|
|
462
|
+
stats = reader(path);
|
|
463
|
+
} catch {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
if (!stats)
|
|
467
|
+
return null;
|
|
468
|
+
const bsize = finiteNonNegative(stats.bsize);
|
|
469
|
+
const blocks = finiteNonNegative(stats.blocks);
|
|
470
|
+
const bavail = finiteNonNegative(stats.bavail);
|
|
471
|
+
if (bsize === null || blocks === null || bavail === null)
|
|
472
|
+
return null;
|
|
473
|
+
if (bsize <= 0 || blocks <= 0)
|
|
474
|
+
return null;
|
|
475
|
+
const available = Math.min(bavail, blocks);
|
|
476
|
+
const totalBytes = bsize * blocks;
|
|
477
|
+
const freeBytes = bsize * available;
|
|
478
|
+
if (!Number.isFinite(totalBytes) || !Number.isFinite(freeBytes))
|
|
479
|
+
return null;
|
|
480
|
+
return {
|
|
481
|
+
freeBytes,
|
|
482
|
+
totalBytes,
|
|
483
|
+
freePercent: available / blocks * 100
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function diskFreeFor(path, memo, reader = readDiskStats) {
|
|
487
|
+
if (!memo)
|
|
488
|
+
return diskFree(path, reader);
|
|
489
|
+
const hit = memo.get(DISK_MEMO);
|
|
490
|
+
if (hit && hit.path === path)
|
|
491
|
+
return hit.value;
|
|
492
|
+
const value = diskFree(path, reader);
|
|
493
|
+
memo.set(DISK_MEMO, { path, value });
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
function diskLevel(freePercent) {
|
|
497
|
+
if (!Number.isFinite(freePercent))
|
|
498
|
+
return null;
|
|
499
|
+
if (freePercent < DISK_CRITICAL_PERCENT)
|
|
500
|
+
return "critical";
|
|
501
|
+
if (freePercent < DISK_WARN_PERCENT)
|
|
502
|
+
return "low";
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
function formatDiskFree(disk) {
|
|
506
|
+
const value = compactBytes(disk.freeBytes);
|
|
507
|
+
if (!value)
|
|
508
|
+
return "";
|
|
509
|
+
return diskLevel(disk.freePercent) ? `disk ${value} low` : `disk ${value}`;
|
|
510
|
+
}
|
|
511
|
+
function diskColor(disk) {
|
|
512
|
+
if (!disk)
|
|
513
|
+
return null;
|
|
514
|
+
switch (diskLevel(disk.freePercent)) {
|
|
515
|
+
case "critical":
|
|
516
|
+
return "red";
|
|
517
|
+
case "low":
|
|
518
|
+
return "yellow";
|
|
519
|
+
default:
|
|
520
|
+
return "dim";
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
234
524
|
// src/accounts.ts
|
|
235
|
-
import { existsSync as
|
|
236
|
-
import { homedir as
|
|
237
|
-
import { join as
|
|
525
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
526
|
+
import { homedir as homedir4 } from "os";
|
|
527
|
+
import { join as join4, resolve, sep } from "path";
|
|
238
528
|
var CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
|
|
239
529
|
var DEFAULT_CONFIG_SUBDIR = ".claude";
|
|
240
530
|
var STORE_PATH_ENV = "ACCOUNTS_STORE_PATH";
|
|
@@ -258,10 +548,10 @@ function str(value) {
|
|
|
258
548
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
259
549
|
}
|
|
260
550
|
function home(env) {
|
|
261
|
-
return str(env.HOME) ??
|
|
551
|
+
return str(env.HOME) ?? homedir4();
|
|
262
552
|
}
|
|
263
553
|
function expandHome(path, env) {
|
|
264
|
-
return path === "~" || path.startsWith(`~${sep}`) ?
|
|
554
|
+
return path === "~" || path.startsWith(`~${sep}`) ? join4(home(env), path.slice(1)) : path;
|
|
265
555
|
}
|
|
266
556
|
function canonical(path, env) {
|
|
267
557
|
const abs = resolve(expandHome(path, env));
|
|
@@ -273,7 +563,7 @@ function canonical(path, env) {
|
|
|
273
563
|
}
|
|
274
564
|
function readJson(path) {
|
|
275
565
|
try {
|
|
276
|
-
return JSON.parse(
|
|
566
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
277
567
|
} catch {
|
|
278
568
|
return null;
|
|
279
569
|
}
|
|
@@ -294,14 +584,14 @@ async function accountsPaths(env) {
|
|
|
294
584
|
return { store: storage.storePath(), profiles: storage.profilesDir() };
|
|
295
585
|
} catch {}
|
|
296
586
|
}
|
|
297
|
-
const base = homeOverride ??
|
|
587
|
+
const base = homeOverride ?? join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
|
|
298
588
|
return {
|
|
299
|
-
store: storeOverride ??
|
|
300
|
-
profiles:
|
|
589
|
+
store: storeOverride ?? join4(base, STORE_FILE),
|
|
590
|
+
profiles: join4(base, PROFILES_SUBDIR)
|
|
301
591
|
};
|
|
302
592
|
}
|
|
303
593
|
function registryEntry(store, configDir, env, tool) {
|
|
304
|
-
if (!
|
|
594
|
+
if (!existsSync5(store))
|
|
305
595
|
return null;
|
|
306
596
|
const entries = readJson(store)?.profiles;
|
|
307
597
|
if (!Array.isArray(entries))
|
|
@@ -322,7 +612,7 @@ function registryEntry(store, configDir, env, tool) {
|
|
|
322
612
|
return tool ? untyped : first;
|
|
323
613
|
}
|
|
324
614
|
function layoutProfile(profilesRoot, configDir, env) {
|
|
325
|
-
if (!
|
|
615
|
+
if (!existsSync5(configDir))
|
|
326
616
|
return null;
|
|
327
617
|
const prefix = canonical(profilesRoot, env) + sep;
|
|
328
618
|
if (!configDir.startsWith(prefix))
|
|
@@ -336,7 +626,7 @@ function layoutProfile(profilesRoot, configDir, env) {
|
|
|
336
626
|
return { configDir, profile, tool, source: "layout" };
|
|
337
627
|
}
|
|
338
628
|
function switchedOccupant(configDir) {
|
|
339
|
-
const raw = readJson(
|
|
629
|
+
const raw = readJson(join4(configDir, AUTH_STATE_SUBDIR, SWITCHED_ACCOUNT_MARKER_FILE));
|
|
340
630
|
if (!raw)
|
|
341
631
|
return null;
|
|
342
632
|
const profile = str(raw.profile);
|
|
@@ -344,17 +634,17 @@ function switchedOccupant(configDir) {
|
|
|
344
634
|
return profile || email ? { profile, email } : null;
|
|
345
635
|
}
|
|
346
636
|
function sessionConfigDir(env = process.env) {
|
|
347
|
-
return canonical(str(env[CONFIG_DIR_ENV]) ??
|
|
637
|
+
return canonical(str(env[CONFIG_DIR_ENV]) ?? join4(home(env), DEFAULT_CONFIG_SUBDIR), env);
|
|
348
638
|
}
|
|
349
639
|
function sessionStateFile(env = process.env) {
|
|
350
640
|
const base = canonical(str(env[CONFIG_DIR_ENV]) ?? home(env), env);
|
|
351
|
-
const preferred =
|
|
352
|
-
if (
|
|
641
|
+
const preferred = join4(base, STATE_FILE_PREFERRED);
|
|
642
|
+
if (existsSync5(preferred))
|
|
353
643
|
return preferred;
|
|
354
644
|
const variants = str(env[CUSTOM_OAUTH_ENV]) ? ["-custom-oauth", ...OAUTH_VARIANTS] : OAUTH_VARIANTS;
|
|
355
645
|
for (const variant of variants) {
|
|
356
|
-
const candidate =
|
|
357
|
-
if (
|
|
646
|
+
const candidate = join4(base, `${STATE_FILE_PREFIX}${variant}${STATE_FILE_SUFFIX}`);
|
|
647
|
+
if (existsSync5(candidate))
|
|
358
648
|
return candidate;
|
|
359
649
|
}
|
|
360
650
|
return null;
|
|
@@ -402,7 +692,7 @@ async function sessionAccountEmail(env = process.env, tool) {
|
|
|
402
692
|
}
|
|
403
693
|
function accountsHomeDir(env) {
|
|
404
694
|
const override = str(env[ACCOUNTS_HOME_ENV]);
|
|
405
|
-
return override ? expandHome(override, env) :
|
|
695
|
+
return override ? expandHome(override, env) : join4(home(env), ...DEFAULT_ACCOUNTS_HOME);
|
|
406
696
|
}
|
|
407
697
|
function sessionAccountUuid(env = process.env) {
|
|
408
698
|
try {
|
|
@@ -475,8 +765,8 @@ function sessionUsage(env = process.env, now = new Date) {
|
|
|
475
765
|
const uuid = sessionAccountUuid(env);
|
|
476
766
|
if (!uuid)
|
|
477
767
|
return empty;
|
|
478
|
-
const path =
|
|
479
|
-
if (!
|
|
768
|
+
const path = join4(accountsHomeDir(env), CACHE_SUBDIR, USAGE_CACHE_SUBDIR, `${uuid}.json`);
|
|
769
|
+
if (!existsSync5(path))
|
|
480
770
|
return empty;
|
|
481
771
|
const entry = readJson(path);
|
|
482
772
|
if (!entry || entry.accountUuid !== uuid)
|
|
@@ -491,11 +781,11 @@ function sessionUsage(env = process.env, now = new Date) {
|
|
|
491
781
|
}
|
|
492
782
|
|
|
493
783
|
// src/settings.ts
|
|
494
|
-
import { readFileSync as
|
|
495
|
-
import { join as
|
|
784
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
785
|
+
import { join as join5 } from "path";
|
|
496
786
|
function sessionFastMode(env = process.env) {
|
|
497
787
|
try {
|
|
498
|
-
const raw =
|
|
788
|
+
const raw = readFileSync5(join5(sessionConfigDir(env), "settings.json"), "utf8");
|
|
499
789
|
return JSON.parse(raw)?.fastMode === true;
|
|
500
790
|
} catch {
|
|
501
791
|
return false;
|
|
@@ -519,6 +809,29 @@ function friendlyModel(id) {
|
|
|
519
809
|
return { name: name || mid, tag };
|
|
520
810
|
}
|
|
521
811
|
var dir = (ctx) => ctx.cwd || process.cwd();
|
|
812
|
+
function abbreviateHome(path) {
|
|
813
|
+
const home2 = homedir5();
|
|
814
|
+
if (!home2 || home2 === "/")
|
|
815
|
+
return path;
|
|
816
|
+
if (path === home2)
|
|
817
|
+
return "~";
|
|
818
|
+
return path.startsWith(`${home2}/`) ? `~${path.slice(home2.length)}` : path;
|
|
819
|
+
}
|
|
820
|
+
var CWD_MAX = 40;
|
|
821
|
+
function truncateLeft(path, max) {
|
|
822
|
+
return path.length <= max ? path : `\u2026${path.slice(path.length - (max - 1))}`;
|
|
823
|
+
}
|
|
824
|
+
function contextColor(ctx) {
|
|
825
|
+
const u = contextUsage(ctx);
|
|
826
|
+
if (!u || !u.window)
|
|
827
|
+
return null;
|
|
828
|
+
const pct = Math.min(100, Math.round(u.used / u.window * 100));
|
|
829
|
+
if (pct >= 80)
|
|
830
|
+
return "red";
|
|
831
|
+
if (pct >= 50)
|
|
832
|
+
return "yellow";
|
|
833
|
+
return "green";
|
|
834
|
+
}
|
|
522
835
|
function modelName(ctx) {
|
|
523
836
|
if (ctx.model?.displayName)
|
|
524
837
|
return ctx.model.displayName.toLowerCase();
|
|
@@ -527,6 +840,12 @@ function modelName(ctx) {
|
|
|
527
840
|
function limitPercent(pct) {
|
|
528
841
|
return typeof pct === "number" ? pct : 0;
|
|
529
842
|
}
|
|
843
|
+
function anthropicSession(ctx) {
|
|
844
|
+
if (ctx.rateLimits)
|
|
845
|
+
return true;
|
|
846
|
+
const id = ctx.model?.id;
|
|
847
|
+
return typeof id === "string" && /claude/i.test(id);
|
|
848
|
+
}
|
|
530
849
|
function usageLabel(prefix, headroom) {
|
|
531
850
|
return headroom === null ? `${prefix} \u2014` : `${prefix} ${headroom}%`;
|
|
532
851
|
}
|
|
@@ -590,16 +909,39 @@ var segments = [
|
|
|
590
909
|
return n ? compactNum(n) : null;
|
|
591
910
|
}
|
|
592
911
|
},
|
|
912
|
+
{
|
|
913
|
+
id: "disk-free",
|
|
914
|
+
description: "Free space on the volume holding the working directory",
|
|
915
|
+
defaultEnabled: false,
|
|
916
|
+
color: (ctx, memo) => diskColor(diskFreeFor(dir(ctx), memo)),
|
|
917
|
+
render(ctx, memo) {
|
|
918
|
+
const disk = diskFreeFor(dir(ctx), memo);
|
|
919
|
+
return disk ? formatDiskFree(disk) || null : null;
|
|
920
|
+
}
|
|
921
|
+
},
|
|
593
922
|
{
|
|
594
923
|
id: "current-dir",
|
|
595
924
|
description: "Current working directory (basename)",
|
|
596
925
|
defaultEnabled: false,
|
|
597
926
|
render: (ctx) => dir(ctx).split("/").pop() || null
|
|
598
927
|
},
|
|
928
|
+
{
|
|
929
|
+
id: "cwd",
|
|
930
|
+
description: "Current working directory (full path)",
|
|
931
|
+
defaultEnabled: false,
|
|
932
|
+
color: "blue",
|
|
933
|
+
render(ctx) {
|
|
934
|
+
const path = ctx.cwd;
|
|
935
|
+
if (typeof path !== "string" || !path)
|
|
936
|
+
return null;
|
|
937
|
+
return truncateLeft(abbreviateHome(path), CWD_MAX);
|
|
938
|
+
}
|
|
939
|
+
},
|
|
599
940
|
{
|
|
600
941
|
id: "model",
|
|
601
942
|
description: "Current model name",
|
|
602
943
|
defaultEnabled: false,
|
|
944
|
+
color: "dim",
|
|
603
945
|
render(ctx) {
|
|
604
946
|
if (!ctx.model?.id)
|
|
605
947
|
return null;
|
|
@@ -610,6 +952,7 @@ var segments = [
|
|
|
610
952
|
id: "model-context",
|
|
611
953
|
description: "Model name with context-size tag (e.g. [1m])",
|
|
612
954
|
defaultEnabled: true,
|
|
955
|
+
color: "dim",
|
|
613
956
|
render(ctx) {
|
|
614
957
|
if (!ctx.model?.id)
|
|
615
958
|
return null;
|
|
@@ -621,7 +964,7 @@ var segments = [
|
|
|
621
964
|
id: "model-with-reasoning",
|
|
622
965
|
description: "Model name with its reasoning effort (or thinking, when on)",
|
|
623
966
|
defaultEnabled: false,
|
|
624
|
-
color: "
|
|
967
|
+
color: "dim",
|
|
625
968
|
render(ctx) {
|
|
626
969
|
const name = modelName(ctx);
|
|
627
970
|
if (!name)
|
|
@@ -681,15 +1024,15 @@ var segments = [
|
|
|
681
1024
|
id: "usage-session",
|
|
682
1025
|
description: "Session (5-hour) usage remaining for this pane's own account, from the local accounts usage cache",
|
|
683
1026
|
defaultEnabled: false,
|
|
684
|
-
color: () => usageColor(sessionUsage(process.env).sessionHeadroom),
|
|
685
|
-
render: () => usageLabel("5h", sessionUsage(process.env).sessionHeadroom)
|
|
1027
|
+
color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).sessionHeadroom) : null,
|
|
1028
|
+
render: (ctx) => anthropicSession(ctx) ? usageLabel("5h", sessionUsage(process.env).sessionHeadroom) : null
|
|
686
1029
|
},
|
|
687
1030
|
{
|
|
688
1031
|
id: "usage-weekly",
|
|
689
1032
|
description: "Weekly usage remaining for this pane's own account \u2014 the binding limit \u2014 from the local accounts usage cache",
|
|
690
1033
|
defaultEnabled: false,
|
|
691
|
-
color: () => usageColor(sessionUsage(process.env).weeklyHeadroom, true),
|
|
692
|
-
render: () => usageLabel("7d", sessionUsage(process.env).weeklyHeadroom)
|
|
1034
|
+
color: (ctx) => anthropicSession(ctx) ? usageColor(sessionUsage(process.env).weeklyHeadroom, true) : null,
|
|
1035
|
+
render: (ctx) => anthropicSession(ctx) ? usageLabel("7d", sessionUsage(process.env).weeklyHeadroom) : null
|
|
693
1036
|
},
|
|
694
1037
|
{
|
|
695
1038
|
id: "thread-title",
|
|
@@ -702,17 +1045,19 @@ var segments = [
|
|
|
702
1045
|
id: "context-used",
|
|
703
1046
|
description: "Percentage of context window used (omitted when unknown)",
|
|
704
1047
|
defaultEnabled: false,
|
|
1048
|
+
color: contextColor,
|
|
705
1049
|
render(ctx) {
|
|
706
1050
|
const u = contextUsage(ctx);
|
|
707
1051
|
if (!u)
|
|
708
1052
|
return null;
|
|
709
|
-
return
|
|
1053
|
+
return `ctx ${Math.min(100, Math.round(u.used / u.window * 100))}%`;
|
|
710
1054
|
}
|
|
711
1055
|
},
|
|
712
1056
|
{
|
|
713
1057
|
id: "context-remaining",
|
|
714
1058
|
description: "Percentage of context window remaining (omitted when unknown)",
|
|
715
1059
|
defaultEnabled: true,
|
|
1060
|
+
color: contextColor,
|
|
716
1061
|
render(ctx) {
|
|
717
1062
|
const u = contextUsage(ctx);
|
|
718
1063
|
if (!u)
|
|
@@ -731,10 +1076,20 @@ var segments = [
|
|
|
731
1076
|
return `${compactNum(u.used + u.output)} tok`;
|
|
732
1077
|
}
|
|
733
1078
|
},
|
|
1079
|
+
{
|
|
1080
|
+
id: "cache-rate",
|
|
1081
|
+
description: "Percentage of input tokens served from cache (omitted when unknown)",
|
|
1082
|
+
defaultEnabled: true,
|
|
1083
|
+
render(ctx) {
|
|
1084
|
+
const r = cacheRate(ctx);
|
|
1085
|
+
return r === null ? null : `${Math.round(r * 100)}% cached`;
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
734
1088
|
{
|
|
735
1089
|
id: "cost",
|
|
736
1090
|
description: "Session cost in USD (omitted when zero)",
|
|
737
1091
|
defaultEnabled: true,
|
|
1092
|
+
color: "dim",
|
|
738
1093
|
render(ctx) {
|
|
739
1094
|
const c = ctx.cost?.totalCostUsd;
|
|
740
1095
|
return c ? money(c) : null;
|
|
@@ -744,6 +1099,7 @@ var segments = [
|
|
|
744
1099
|
id: "duration",
|
|
745
1100
|
description: "Session wall-clock duration",
|
|
746
1101
|
defaultEnabled: false,
|
|
1102
|
+
color: "dim",
|
|
747
1103
|
render(ctx) {
|
|
748
1104
|
const ms = ctx.cost?.totalDurationMs;
|
|
749
1105
|
return ms ? compactDuration(ms) : null;
|
|
@@ -810,13 +1166,14 @@ function colorsEnabled(cfg, env = process.env) {
|
|
|
810
1166
|
return false;
|
|
811
1167
|
return cfg.colors !== false;
|
|
812
1168
|
}
|
|
813
|
-
function paint(segment, text, ctx) {
|
|
814
|
-
const color = typeof segment.color === "function" ? segment.color(ctx) : segment.color;
|
|
1169
|
+
function paint(segment, text, ctx, memo) {
|
|
1170
|
+
const color = typeof segment.color === "function" ? segment.color(ctx, memo) : segment.color;
|
|
815
1171
|
const code = color ? ANSI[color] : undefined;
|
|
816
1172
|
return code ? `${code}${text}${RESET}` : text;
|
|
817
1173
|
}
|
|
818
1174
|
async function renderLine(ctx, cfg) {
|
|
819
1175
|
const colorize = colorsEnabled(cfg);
|
|
1176
|
+
const memo = new Map;
|
|
820
1177
|
const rows = [[]];
|
|
821
1178
|
for (const id of cfg.segments) {
|
|
822
1179
|
if (id === "newline") {
|
|
@@ -827,9 +1184,9 @@ async function renderLine(ctx, cfg) {
|
|
|
827
1184
|
if (!segment)
|
|
828
1185
|
continue;
|
|
829
1186
|
try {
|
|
830
|
-
const out = await segment.render(ctx);
|
|
1187
|
+
const out = await segment.render(ctx, memo);
|
|
831
1188
|
if (out)
|
|
832
|
-
rows[rows.length - 1].push(colorize ? paint(segment, out, ctx) : out);
|
|
1189
|
+
rows[rows.length - 1].push(colorize ? paint(segment, out, ctx, memo) : out);
|
|
833
1190
|
} catch {}
|
|
834
1191
|
}
|
|
835
1192
|
return rows.map((parts) => parts.join(cfg.separator)).filter((row) => row.length > 0).join(`
|
|
@@ -838,7 +1195,7 @@ async function renderLine(ctx, cfg) {
|
|
|
838
1195
|
// package.json
|
|
839
1196
|
var package_default = {
|
|
840
1197
|
name: "@hasna/statusline",
|
|
841
|
-
version: "0.0.
|
|
1198
|
+
version: "0.0.9",
|
|
842
1199
|
description: "Tiny composable statusline CLI for AI coding agents \u2014 pre-built toggleable segments for Claude Code, with a provider model for others",
|
|
843
1200
|
license: "Apache-2.0",
|
|
844
1201
|
type: "module",
|
|
@@ -877,11 +1234,12 @@ var package_default = {
|
|
|
877
1234
|
},
|
|
878
1235
|
repository: {
|
|
879
1236
|
type: "git",
|
|
880
|
-
url: "
|
|
1237
|
+
url: "https://github.com/hasna/apps.git",
|
|
1238
|
+
directory: "apps/statusline"
|
|
881
1239
|
},
|
|
882
|
-
homepage: "https://github.com/hasna/statusline#readme",
|
|
1240
|
+
homepage: "https://github.com/hasna/apps/tree/main/apps/statusline#readme",
|
|
883
1241
|
bugs: {
|
|
884
|
-
url: "https://github.com/hasna/
|
|
1242
|
+
url: "https://github.com/hasna/apps/issues"
|
|
885
1243
|
},
|
|
886
1244
|
keywords: [
|
|
887
1245
|
"statusline",
|
|
@@ -898,36 +1256,37 @@ var package_default = {
|
|
|
898
1256
|
bun: ">=1.0.0"
|
|
899
1257
|
},
|
|
900
1258
|
dependencies: {
|
|
901
|
-
"@modelcontextprotocol/sdk": "1.
|
|
1259
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
902
1260
|
zod: "3.25.76"
|
|
903
1261
|
},
|
|
904
1262
|
devDependencies: {
|
|
905
|
-
"@types/bun": "
|
|
906
|
-
typescript: "5.9.3"
|
|
1263
|
+
"@types/bun": "1.3.14",
|
|
1264
|
+
typescript: "5.9.3",
|
|
1265
|
+
"bun-types": "1.3.14"
|
|
907
1266
|
}
|
|
908
1267
|
};
|
|
909
1268
|
// src/install.ts
|
|
910
|
-
import { copyFileSync, existsSync as
|
|
911
|
-
import { homedir as
|
|
912
|
-
import { dirname as dirname2, join as
|
|
1269
|
+
import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1270
|
+
import { homedir as homedir6 } from "os";
|
|
1271
|
+
import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
|
|
913
1272
|
function defaultSettingsPath(env) {
|
|
914
|
-
return
|
|
1273
|
+
return join6(env.HOME || homedir6(), ".claude", "settings.json");
|
|
915
1274
|
}
|
|
916
1275
|
function claudeSettingsPath(env = process.env) {
|
|
917
|
-
const home2 = env.HOME ||
|
|
1276
|
+
const home2 = env.HOME || homedir6();
|
|
918
1277
|
const raw = env.CLAUDE_CONFIG_DIR?.trim();
|
|
919
1278
|
if (!raw)
|
|
920
1279
|
return defaultSettingsPath(env);
|
|
921
|
-
const configDir = raw === "~" || raw.startsWith("~/") ?
|
|
922
|
-
return
|
|
1280
|
+
const configDir = raw === "~" || raw.startsWith("~/") ? join6(home2, raw.slice(1)) : raw;
|
|
1281
|
+
return join6(configDir, "settings.json");
|
|
923
1282
|
}
|
|
924
1283
|
function installClaude(settingsPath, env = process.env) {
|
|
925
1284
|
const target = settingsPath ?? claudeSettingsPath(env);
|
|
926
1285
|
const binary = Bun.which("statusline") || "statusline";
|
|
927
1286
|
let settings = {};
|
|
928
|
-
if (
|
|
1287
|
+
if (existsSync6(target)) {
|
|
929
1288
|
try {
|
|
930
|
-
settings = JSON.parse(
|
|
1289
|
+
settings = JSON.parse(readFileSync6(target, "utf8"));
|
|
931
1290
|
} catch (error) {
|
|
932
1291
|
throw new Error(`${target} is not valid JSON \u2014 fix or move it first`);
|
|
933
1292
|
}
|
|
@@ -935,7 +1294,7 @@ function installClaude(settingsPath, env = process.env) {
|
|
|
935
1294
|
}
|
|
936
1295
|
settings.statusLine = { ...settings.statusLine, type: "command", command: `${binary} render` };
|
|
937
1296
|
const configDir = dirname2(target);
|
|
938
|
-
if (!
|
|
1297
|
+
if (!existsSync6(configDir)) {
|
|
939
1298
|
if (resolve2(target) !== resolve2(defaultSettingsPath(env))) {
|
|
940
1299
|
throw new Error(`config dir ${configDir} does not exist \u2014 create the profile first`);
|
|
941
1300
|
}
|
|
@@ -1002,6 +1361,14 @@ function resetStatuslineConfig() {
|
|
|
1002
1361
|
return defaultConfig();
|
|
1003
1362
|
}
|
|
1004
1363
|
async function renderStatusline(input = {}, config = loadConfig()) {
|
|
1364
|
+
const provider = process.env.STATUSLINE_PROVIDER;
|
|
1365
|
+
if (provider === "codex" || provider === "opencode") {
|
|
1366
|
+
const ctx = {
|
|
1367
|
+
provider,
|
|
1368
|
+
cwd: typeof input.cwd === "string" ? input.cwd : process.cwd()
|
|
1369
|
+
};
|
|
1370
|
+
return renderLine(ctx, config);
|
|
1371
|
+
}
|
|
1005
1372
|
return renderLine(parseClaudeInput(input), config);
|
|
1006
1373
|
}
|
|
1007
1374
|
function sampleClaudeInput(cwd = process.cwd()) {
|
|
@@ -1044,9 +1411,12 @@ export {
|
|
|
1044
1411
|
previewStatusline,
|
|
1045
1412
|
parseClaudeInput,
|
|
1046
1413
|
orderSegments,
|
|
1414
|
+
opencodeCacheRate,
|
|
1047
1415
|
money,
|
|
1048
1416
|
loadConfig,
|
|
1049
1417
|
listSegments,
|
|
1418
|
+
latestCodexSessionPath,
|
|
1419
|
+
lastUsageBlock,
|
|
1050
1420
|
lastCommitEpoch,
|
|
1051
1421
|
installClaude,
|
|
1052
1422
|
gitRoot,
|
|
@@ -1061,5 +1431,7 @@ export {
|
|
|
1061
1431
|
compactNum,
|
|
1062
1432
|
compactDuration,
|
|
1063
1433
|
compactAge,
|
|
1064
|
-
|
|
1434
|
+
codexCacheRate,
|
|
1435
|
+
claudeSettingsPath,
|
|
1436
|
+
cacheRate
|
|
1065
1437
|
};
|