@khalilgharbaoui/opencode-claude-code-plugin 0.54.2 → 0.56.0
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 +9 -1
- package/dist/index.d.ts +159 -10
- package/dist/index.js +2177 -603
- package/dist/index.js.map +1 -1
- package/dist/tui.js +206 -8
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +105 -9
package/dist/tui.js
CHANGED
|
@@ -271,6 +271,157 @@ function createSpawnRecordReader(file = spawnRecordStorePath) {
|
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// src/account-usage.ts
|
|
275
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
276
|
+
import { homedir as homedir2 } from "os";
|
|
277
|
+
import { join as join2 } from "path";
|
|
278
|
+
var UNTIMED_READING_TTL_MS = 5 * 60 * 60 * 1e3;
|
|
279
|
+
var USAGE_READING_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
280
|
+
var NAME = /^[a-z0-9_-]{1,64}$/;
|
|
281
|
+
var STATUS = /^[a-z_]{1,32}$/;
|
|
282
|
+
var MAX_RESETS_TEXT = 80;
|
|
283
|
+
var pathOverride2 = null;
|
|
284
|
+
function accountUsageStorePath() {
|
|
285
|
+
if (pathOverride2) return pathOverride2;
|
|
286
|
+
const stateRoot = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
|
|
287
|
+
return join2(stateRoot, "opencode-claude-code-plugin", "account-usage.json");
|
|
288
|
+
}
|
|
289
|
+
function finite(value) {
|
|
290
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
291
|
+
}
|
|
292
|
+
function validEntry(value) {
|
|
293
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
294
|
+
const entry = value;
|
|
295
|
+
if (!finite(entry.observedAt)) return null;
|
|
296
|
+
if (typeof entry.status !== "string" || !STATUS.test(entry.status)) return null;
|
|
297
|
+
if (entry.utilization !== null && !finite(entry.utilization)) return null;
|
|
298
|
+
if (entry.resetsAt !== void 0 && !finite(entry.resetsAt)) return null;
|
|
299
|
+
if (entry.resetsText !== void 0 && (typeof entry.resetsText !== "string" || entry.resetsText.length > MAX_RESETS_TEXT)) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const noted = Array.isArray(entry.noted) ? [...new Set(entry.noted.filter((t) => finite(t) && t > 0 && t <= 100))].sort(
|
|
303
|
+
(a, b) => a - b
|
|
304
|
+
) : [];
|
|
305
|
+
return {
|
|
306
|
+
utilization: entry.utilization,
|
|
307
|
+
...entry.resetsAt === void 0 ? {} : { resetsAt: entry.resetsAt },
|
|
308
|
+
...entry.resetsText === void 0 ? {} : { resetsText: entry.resetsText },
|
|
309
|
+
status: entry.status,
|
|
310
|
+
observedAt: entry.observedAt,
|
|
311
|
+
noted
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function readStore(file, now) {
|
|
315
|
+
const out = /* @__PURE__ */ new Map();
|
|
316
|
+
try {
|
|
317
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
318
|
+
const accounts = parsed && typeof parsed === "object" ? parsed.accounts : null;
|
|
319
|
+
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return out;
|
|
320
|
+
const cutoff = now - USAGE_READING_MAX_AGE_MS;
|
|
321
|
+
for (const [account, windows] of Object.entries(accounts)) {
|
|
322
|
+
if (!NAME.test(account) || !windows || typeof windows !== "object") continue;
|
|
323
|
+
const kept = /* @__PURE__ */ new Map();
|
|
324
|
+
for (const [window, raw] of Object.entries(windows)) {
|
|
325
|
+
if (!NAME.test(window)) continue;
|
|
326
|
+
const entry = validEntry(raw);
|
|
327
|
+
if (entry && entry.observedAt >= cutoff) kept.set(window, entry);
|
|
328
|
+
}
|
|
329
|
+
if (kept.size > 0) out.set(account, kept);
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function isReadingCurrent(entry, now = Date.now()) {
|
|
336
|
+
if (entry.resetsAt !== void 0) return entry.resetsAt > now;
|
|
337
|
+
return now - entry.observedAt < UNTIMED_READING_TTL_MS;
|
|
338
|
+
}
|
|
339
|
+
function usagePercent(utilization) {
|
|
340
|
+
return Math.round(utilization * 1e3) / 10;
|
|
341
|
+
}
|
|
342
|
+
function createAccountUsageReader(file = accountUsageStorePath) {
|
|
343
|
+
let identity = "";
|
|
344
|
+
let usage = /* @__PURE__ */ new Map();
|
|
345
|
+
return {
|
|
346
|
+
read() {
|
|
347
|
+
const path = file();
|
|
348
|
+
let next = "";
|
|
349
|
+
try {
|
|
350
|
+
const stats = statSync2(path);
|
|
351
|
+
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
352
|
+
} catch {
|
|
353
|
+
next = "missing";
|
|
354
|
+
}
|
|
355
|
+
if (next !== identity) {
|
|
356
|
+
identity = next;
|
|
357
|
+
usage = next === "missing" ? /* @__PURE__ */ new Map() : readStore(path, Date.now());
|
|
358
|
+
}
|
|
359
|
+
return usage;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/account-sidebar.ts
|
|
365
|
+
var WARNING_PERCENT = 75;
|
|
366
|
+
var SHORT_WINDOWS = {
|
|
367
|
+
five_hour: "5h",
|
|
368
|
+
seven_day: "7d",
|
|
369
|
+
seven_day_opus: "7d opus",
|
|
370
|
+
seven_day_sonnet: "7d sonnet",
|
|
371
|
+
seven_day_overage_included: "7d extra",
|
|
372
|
+
overage: "extra"
|
|
373
|
+
};
|
|
374
|
+
function shortWindowName(window) {
|
|
375
|
+
if (window === "unknown") return "";
|
|
376
|
+
return SHORT_WINDOWS[window] ?? window.replace(/_/g, " ");
|
|
377
|
+
}
|
|
378
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
379
|
+
function formatResetClock(resetsAt, now) {
|
|
380
|
+
const when = new Date(resetsAt);
|
|
381
|
+
const today = new Date(now);
|
|
382
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
383
|
+
const clock = `${pad(when.getHours())}:${pad(when.getMinutes())}`;
|
|
384
|
+
const sameDay = when.getFullYear() === today.getFullYear() && when.getMonth() === today.getMonth() && when.getDate() === today.getDate();
|
|
385
|
+
return sameDay ? clock : `${MONTHS[when.getMonth()]} ${when.getDate()} ${clock}`;
|
|
386
|
+
}
|
|
387
|
+
function rank(entry) {
|
|
388
|
+
if (entry.status === "rejected") return 1e3;
|
|
389
|
+
if (entry.utilization === null) return -1;
|
|
390
|
+
return usagePercent(entry.utilization);
|
|
391
|
+
}
|
|
392
|
+
function pressingWindow(windows, now) {
|
|
393
|
+
let best;
|
|
394
|
+
for (const [window, entry] of windows) {
|
|
395
|
+
if (!isReadingCurrent(entry, now)) continue;
|
|
396
|
+
if (!best || rank(entry) > rank(best[1])) best = [window, entry];
|
|
397
|
+
}
|
|
398
|
+
return best;
|
|
399
|
+
}
|
|
400
|
+
function accountUsageLines(usage, now, width = SIDEBAR_ROW_WIDTH) {
|
|
401
|
+
const lines = [];
|
|
402
|
+
for (const account of [...usage.keys()].sort()) {
|
|
403
|
+
const pressing = pressingWindow(usage.get(account), now);
|
|
404
|
+
if (!pressing) continue;
|
|
405
|
+
const [window, entry] = pressing;
|
|
406
|
+
const state = entry.status === "rejected" ? "limited" : entry.utilization !== null && usagePercent(entry.utilization) >= WARNING_PERCENT ? "warning" : "ok";
|
|
407
|
+
const short = shortWindowName(window);
|
|
408
|
+
const level = state === "limited" ? "limited" : entry.utilization === null ? `${short ? `${short} ` : ""}ok` : `${short ? `${short} ` : ""}${Math.round(usagePercent(entry.utilization))}%`;
|
|
409
|
+
const resets = entry.resetsAt !== void 0 ? ` \xB7 resets ${formatResetClock(entry.resetsAt, now)}` : entry.resetsText ? ` \xB7 resets ${entry.resetsText}` : "";
|
|
410
|
+
const name = truncate(account, Math.max(1, Math.floor(width / 2)));
|
|
411
|
+
const rest = ` ${truncate(`\xB7 ${level}${resets}`, Math.max(0, width - Array.from(name).length - 1))}`;
|
|
412
|
+
lines.push({
|
|
413
|
+
account,
|
|
414
|
+
window,
|
|
415
|
+
state,
|
|
416
|
+
segments: [
|
|
417
|
+
{ text: name, role: "agent" },
|
|
418
|
+
{ text: rest, role: state === "limited" ? "error" : state === "warning" ? "effort" : "muted" }
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return lines;
|
|
423
|
+
}
|
|
424
|
+
|
|
274
425
|
// src/tui.ts
|
|
275
426
|
var SUBAGENTS_TUI_PLUGIN_ID = "opencode-claude-code-plugin.subagents";
|
|
276
427
|
var SECTION_ORDER = 900;
|
|
@@ -320,8 +471,12 @@ function createSidebar(host) {
|
|
|
320
471
|
const records = createSpawnRecordReader();
|
|
321
472
|
let recordMap = attempt(() => records.read(), /* @__PURE__ */ new Map());
|
|
322
473
|
const [recordVersion, setRecordVersion] = createSignal(0);
|
|
474
|
+
const usageReader = createAccountUsageReader();
|
|
475
|
+
const [usage, setUsage] = createSignal(attempt(() => usageReader.read(), /* @__PURE__ */ new Map()));
|
|
476
|
+
const [usageNow, setUsageNow] = createSignal(Date.now());
|
|
323
477
|
const sectionsRunning = /* @__PURE__ */ new Set();
|
|
324
478
|
const sectionsShowing = /* @__PURE__ */ new Set();
|
|
479
|
+
const sectionsMounted = /* @__PURE__ */ new Set();
|
|
325
480
|
let spinner;
|
|
326
481
|
let refresh;
|
|
327
482
|
const syncClocks = () => {
|
|
@@ -331,16 +486,22 @@ function createSidebar(host) {
|
|
|
331
486
|
clearInterval(spinner);
|
|
332
487
|
spinner = void 0;
|
|
333
488
|
}
|
|
334
|
-
|
|
489
|
+
const wanted = sectionsShowing.size > 0 || sectionsMounted.size > 0;
|
|
490
|
+
if (wanted && !refresh) {
|
|
335
491
|
refresh = setInterval(() => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
recordMap
|
|
339
|
-
|
|
492
|
+
if (sectionsShowing.size > 0) {
|
|
493
|
+
const next = attempt(() => records.read(), recordMap);
|
|
494
|
+
if (next !== recordMap) {
|
|
495
|
+
recordMap = next;
|
|
496
|
+
setRecordVersion((value) => value + 1);
|
|
497
|
+
}
|
|
498
|
+
setNow(Date.now());
|
|
340
499
|
}
|
|
341
|
-
|
|
500
|
+
setUsage(attempt(() => usageReader.read(), usage()));
|
|
501
|
+
const minute = Math.floor(Date.now() / 6e4) * 6e4;
|
|
502
|
+
if (minute !== usageNow()) setUsageNow(minute);
|
|
342
503
|
}, REFRESH_INTERVAL_MS);
|
|
343
|
-
} else if (
|
|
504
|
+
} else if (!wanted && refresh) {
|
|
344
505
|
clearInterval(refresh);
|
|
345
506
|
refresh = void 0;
|
|
346
507
|
}
|
|
@@ -424,9 +585,35 @@ function createSidebar(host) {
|
|
|
424
585
|
insertNode(section, body);
|
|
425
586
|
return section;
|
|
426
587
|
};
|
|
588
|
+
const buildAccounts = (lines) => {
|
|
589
|
+
const section = createElement("box");
|
|
590
|
+
const title = createElement("text");
|
|
591
|
+
const bold = createElement("b");
|
|
592
|
+
insertNode(bold, createTextNode("Accounts"));
|
|
593
|
+
insertNode(title, bold);
|
|
594
|
+
effect(() => setProp(title, "fg", host.colors().text));
|
|
595
|
+
insertNode(section, title);
|
|
596
|
+
for (const line of lines) {
|
|
597
|
+
const text2 = createElement("text");
|
|
598
|
+
setProp(text2, "wrapMode", "none");
|
|
599
|
+
for (const segment of line.segments) {
|
|
600
|
+
const span = createElement("span");
|
|
601
|
+
insertNode(span, createTextNode(segment.text));
|
|
602
|
+
effect(() => setProp(span, "style", { fg: roleColor(host.colors(), segment.role) }));
|
|
603
|
+
insertNode(text2, span);
|
|
604
|
+
}
|
|
605
|
+
insertNode(section, text2);
|
|
606
|
+
}
|
|
607
|
+
return section;
|
|
608
|
+
};
|
|
427
609
|
const render = (sessionID) => {
|
|
428
610
|
const root = createElement("box");
|
|
611
|
+
const subagentsBox = createElement("box");
|
|
612
|
+
const accountsBox = createElement("box");
|
|
613
|
+
insertNode(root, subagentsBox);
|
|
614
|
+
insertNode(root, accountsBox);
|
|
429
615
|
const key = /* @__PURE__ */ Symbol("subagents-section");
|
|
616
|
+
sectionsMounted.add(key);
|
|
430
617
|
const [expanded, setExpanded] = createSignal(true);
|
|
431
618
|
const rows = createMemo(() => {
|
|
432
619
|
const parentID = sessionID();
|
|
@@ -444,18 +631,29 @@ function createSidebar(host) {
|
|
|
444
631
|
onCleanup(() => {
|
|
445
632
|
sectionsRunning.delete(key);
|
|
446
633
|
sectionsShowing.delete(key);
|
|
634
|
+
sectionsMounted.delete(key);
|
|
447
635
|
syncClocks();
|
|
448
636
|
});
|
|
449
|
-
insert(
|
|
637
|
+
insert(subagentsBox, () => {
|
|
450
638
|
const list = rows();
|
|
451
639
|
if (list.length === 0) return null;
|
|
452
640
|
return attempt(() => buildSection(list, expanded, setExpanded), null);
|
|
453
641
|
});
|
|
642
|
+
const accountLines = createMemo(
|
|
643
|
+
() => attempt(() => accountUsageLines(usage(), usageNow()), [])
|
|
644
|
+
);
|
|
645
|
+
insert(accountsBox, () => {
|
|
646
|
+
const lines = accountLines();
|
|
647
|
+
if (lines.length === 0) return null;
|
|
648
|
+
return attempt(() => buildAccounts(lines), null);
|
|
649
|
+
});
|
|
650
|
+
syncClocks();
|
|
454
651
|
return root;
|
|
455
652
|
};
|
|
456
653
|
const dispose = () => {
|
|
457
654
|
sectionsRunning.clear();
|
|
458
655
|
sectionsShowing.clear();
|
|
656
|
+
sectionsMounted.clear();
|
|
459
657
|
syncClocks();
|
|
460
658
|
};
|
|
461
659
|
return { render, dispose };
|