opencode-usage-coach 0.13.0 → 0.13.2
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 +38 -12
- package/dist/cli.js +61 -4
- package/dist/index.js +81 -64
- package/dist/tui.js +4 -4
- package/package.json +5 -13
package/README.md
CHANGED
|
@@ -25,32 +25,52 @@ See **[docs/architecture.md](docs/architecture.md)** for the full design.
|
|
|
25
25
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
|
+
### 3 steps to get running
|
|
29
|
+
|
|
30
|
+
**Step 1 — Install globally (provides the `usage-coach` CLI):**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g opencode-usage-coach
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Step 2 — Add the server plugin to opencode:**
|
|
37
|
+
|
|
28
38
|
```jsonc
|
|
29
|
-
// ~/.config/opencode/opencode.json
|
|
39
|
+
// ~/.config/opencode/opencode.json
|
|
30
40
|
{ "plugin": ["opencode-usage-coach"] }
|
|
31
|
-
|
|
32
|
-
// ~/.config/opencode/tui.json — sidebar panel (point at built dist/tui.js)
|
|
33
|
-
{ "$schema": "https://opencode.ai/tui.json", "plugin": ["opencode-usage-coach/tui"] }
|
|
34
41
|
```
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
**Step 3 — Run setup (auto-configures everything else):**
|
|
37
44
|
|
|
38
45
|
```bash
|
|
39
|
-
usage-coach setup
|
|
40
|
-
usage-coach setup --json # machine-readable output (for scripts/CI)
|
|
46
|
+
usage-coach setup
|
|
41
47
|
```
|
|
42
48
|
|
|
43
|
-
This
|
|
49
|
+
This single command:
|
|
50
|
+
- Creates `~/.config/opencode-usage-coach/harness.config.json` (model config)
|
|
51
|
+
- Copies the harness agent file to `~/.config/opencode/agents/`
|
|
52
|
+
- **Auto-configures `~/.config/opencode/tui.json`** with the correct TUI plugin path
|
|
53
|
+
- Detects whether `codexbar` is installed
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
Restart opencode — you're done. The sidebar panel appears (toggle with `Alt+H`).
|
|
56
|
+
|
|
57
|
+
> **Without `npm install -g`:** The server plugin still works (opencode auto-installs it from npm), but `usage-coach setup` and the `usage-coach` CLI won't be available. You'd need to manually create `harness.config.json` and configure `tui.json` yourself.
|
|
58
|
+
|
|
59
|
+
### Quota sensing (optional but recommended)
|
|
60
|
+
|
|
61
|
+
The plugin works in **GO-only mode** out of the box (no quota sensing). To enable real-time quota monitoring, install [codexbar](https://github.com/nicepkg/codexbar):
|
|
46
62
|
|
|
47
63
|
```bash
|
|
64
|
+
# macOS / Linux
|
|
65
|
+
curl -fsSL https://raw.githubusercontent.com/nicepkg/codexbar/main/install.sh | bash
|
|
66
|
+
|
|
67
|
+
# Then configure your provider API key:
|
|
48
68
|
printf '%s' "$YOUR_PROVIDER_API_KEY" | codexbar config set-api-key --provider <id> --stdin
|
|
49
69
|
```
|
|
50
70
|
|
|
51
|
-
|
|
71
|
+
codexbar supports any provider with a usage API (OpenAI, Anthropic, Google, z.ai, etc.). Check `codexbar providers` for the full list.
|
|
52
72
|
|
|
53
|
-
|
|
73
|
+
Without codexbar, the plugin simply doesn't sense quota — all other features (harness loop, learning, domain knowledge) work normally.
|
|
54
74
|
|
|
55
75
|
## Configuration
|
|
56
76
|
|
|
@@ -89,7 +109,13 @@ See **[docs/architecture.md](docs/architecture.md)** for details on the loop, NE
|
|
|
89
109
|
|
|
90
110
|
## Troubleshooting
|
|
91
111
|
|
|
92
|
-
|
|
112
|
+
**opencode crashes on startup after adding the plugin** — Make sure you're on v0.13.1 or later. Earlier versions had a bug where opencode would crash due to named exports. Run `npm install -g opencode-usage-coach@latest`.
|
|
113
|
+
|
|
114
|
+
**TUI sidebar not showing** — Run `usage-coach setup` again. It auto-detects the TUI path and writes `tui.json`. If you installed via `npm install -g`, the path is `$(npm root -g)/opencode-usage-coach/dist/tui.js`. Never install `solid-js` in the opencode config directory.
|
|
115
|
+
|
|
116
|
+
**`usage-coach: command not found`** — You need `npm install -g opencode-usage-coach` for the CLI. The server plugin works without it, but setup and status commands require the global install.
|
|
117
|
+
|
|
118
|
+
**Model selection errors** — `generator` is required in `harness.config.json`. Run `usage-coach setup` to create it, then edit the model or use `/coach-config` at runtime.
|
|
93
119
|
|
|
94
120
|
See **[docs/troubleshooting.md](docs/troubleshooting.md)** for full diagnoses and fixes.
|
|
95
121
|
|
package/dist/cli.js
CHANGED
|
@@ -279,8 +279,8 @@ function formatStatus(r) {
|
|
|
279
279
|
const model = q.model ? ` ${q.model.split("/").pop()}` : "";
|
|
280
280
|
lines.push(`usage-coach [${tag}]${model}`);
|
|
281
281
|
if (!q.isFree) {
|
|
282
|
-
lines.push(` 5h ${bar(q.fiveHour)} ${q.fiveHour}%`);
|
|
283
|
-
lines.push(` 1w ${bar(q.weekly)} ${q.weekly}%`);
|
|
282
|
+
if (q.fiveHour >= 0) lines.push(` 5h ${bar(q.fiveHour)} ${q.fiveHour}%`);
|
|
283
|
+
if (q.weekly >= 0) lines.push(` 1w ${bar(q.weekly)} ${q.weekly}%`);
|
|
284
284
|
}
|
|
285
285
|
if (q.advice) lines.push(` ${q.advice}`);
|
|
286
286
|
if (r.harness?.active) {
|
|
@@ -345,6 +345,7 @@ function formatDomain(r) {
|
|
|
345
345
|
}
|
|
346
346
|
var GLOBAL_CONFIG_DIR = join(homedir(), ".config", "opencode-usage-coach");
|
|
347
347
|
var OPENCODE_AGENTS_DIR = join(homedir(), ".config", "opencode", "agents");
|
|
348
|
+
var OPENCODE_TUI_CONFIG = join(homedir(), ".config", "opencode", "tui.json");
|
|
348
349
|
var DEFAULT_HARNESS_CONFIG = {
|
|
349
350
|
generator: "opencode/deepseek-v4-flash-free",
|
|
350
351
|
grader: "opencode/mimo-v2.5-free",
|
|
@@ -355,6 +356,42 @@ function resolveAgentSourceFile() {
|
|
|
355
356
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
356
357
|
return join(scriptDir, "..", "agents", "usage-coach-harness.md");
|
|
357
358
|
}
|
|
359
|
+
function resolveTuiPath(scriptDir) {
|
|
360
|
+
const dir = scriptDir ?? dirname(fileURLToPath(import.meta.url));
|
|
361
|
+
return join(dir, "tui.js");
|
|
362
|
+
}
|
|
363
|
+
function configureTui(tuiPath, tuiConfigPath) {
|
|
364
|
+
if (!existsSync(tuiPath)) {
|
|
365
|
+
return { action: "not-found", path: tuiPath };
|
|
366
|
+
}
|
|
367
|
+
let config = {};
|
|
368
|
+
if (existsSync(tuiConfigPath)) {
|
|
369
|
+
try {
|
|
370
|
+
config = JSON.parse(readFileSync(tuiConfigPath, "utf8"));
|
|
371
|
+
} catch {
|
|
372
|
+
config = {};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const plugins = Array.isArray(config["plugin"]) ? config["plugin"] : [];
|
|
376
|
+
if (plugins.includes(tuiPath)) {
|
|
377
|
+
return { action: "exists", path: tuiConfigPath };
|
|
378
|
+
}
|
|
379
|
+
const filtered = plugins.filter(
|
|
380
|
+
(p) => !p.includes("opencode-usage-coach")
|
|
381
|
+
);
|
|
382
|
+
filtered.push(tuiPath);
|
|
383
|
+
config["plugin"] = filtered;
|
|
384
|
+
if (!config["$schema"]) {
|
|
385
|
+
config["$schema"] = "https://opencode.ai/tui.json";
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
mkdirSync(dirname(tuiConfigPath), { recursive: true });
|
|
389
|
+
writeFileSync(tuiConfigPath, JSON.stringify(config, null, 2) + "\n");
|
|
390
|
+
return { action: "configured", path: tuiConfigPath };
|
|
391
|
+
} catch {
|
|
392
|
+
return { action: "write-error", path: tuiConfigPath };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
358
395
|
function detectCodexbar() {
|
|
359
396
|
try {
|
|
360
397
|
const r = spawnSync("codexbar", ["--version"], { timeout: 5e3 });
|
|
@@ -370,6 +407,7 @@ function doSetup(opts = {}) {
|
|
|
370
407
|
const configDir = opts.configDir ?? GLOBAL_CONFIG_DIR;
|
|
371
408
|
const agentsDir = opts.agentsDir ?? OPENCODE_AGENTS_DIR;
|
|
372
409
|
const agentSource = opts.agentSourceFile ?? resolveAgentSourceFile();
|
|
410
|
+
const tuiConfigPath = opts.tuiConfigPath ?? OPENCODE_TUI_CONFIG;
|
|
373
411
|
const configPath = join(configDir, "harness.config.json");
|
|
374
412
|
let configAction;
|
|
375
413
|
if (existsSync(configPath)) {
|
|
@@ -394,10 +432,13 @@ function doSetup(opts = {}) {
|
|
|
394
432
|
copyFileSync(agentSource, agentDestPath);
|
|
395
433
|
agentAction = "copied";
|
|
396
434
|
}
|
|
435
|
+
const tuiPath = resolveTuiPath(opts.scriptDir);
|
|
436
|
+
const tuiResult = configureTui(tuiPath, tuiConfigPath);
|
|
397
437
|
return {
|
|
398
438
|
harnessConfig: { action: configAction, path: configPath },
|
|
399
439
|
codexbar,
|
|
400
|
-
agentFile: { action: agentAction, path: agentDestPath }
|
|
440
|
+
agentFile: { action: agentAction, path: agentDestPath },
|
|
441
|
+
tuiConfig: tuiResult
|
|
401
442
|
};
|
|
402
443
|
}
|
|
403
444
|
function formatSetup(r) {
|
|
@@ -433,6 +474,20 @@ function formatSetup(r) {
|
|
|
433
474
|
lines.push(` Expected: ${r.agentFile.path}`);
|
|
434
475
|
}
|
|
435
476
|
lines.push("");
|
|
477
|
+
if (r.tuiConfig.action === "configured") {
|
|
478
|
+
lines.push(` \u2705 TUI plugin configured`);
|
|
479
|
+
lines.push(` ${r.tuiConfig.path}`);
|
|
480
|
+
} else if (r.tuiConfig.action === "exists") {
|
|
481
|
+
lines.push(` \u2705 TUI plugin already configured`);
|
|
482
|
+
lines.push(` ${r.tuiConfig.path}`);
|
|
483
|
+
} else if (r.tuiConfig.action === "not-found") {
|
|
484
|
+
lines.push(` \u26A0\uFE0F TUI plugin (dist/tui.js) not found`);
|
|
485
|
+
lines.push(` Run: npm install -g opencode-usage-coach`);
|
|
486
|
+
} else {
|
|
487
|
+
lines.push(` \u26A0\uFE0F Could not write TUI config`);
|
|
488
|
+
lines.push(` Manually add to ${r.tuiConfig.path}`);
|
|
489
|
+
}
|
|
490
|
+
lines.push("");
|
|
436
491
|
lines.push("Setup complete. Restart opencode to apply changes.");
|
|
437
492
|
return lines.join("\n");
|
|
438
493
|
}
|
|
@@ -551,6 +606,7 @@ if (isDirectRun) {
|
|
|
551
606
|
main();
|
|
552
607
|
}
|
|
553
608
|
export {
|
|
609
|
+
configureTui,
|
|
554
610
|
doSetup,
|
|
555
611
|
parseArgs,
|
|
556
612
|
projectStateDir,
|
|
@@ -560,5 +616,6 @@ export {
|
|
|
560
616
|
readRules,
|
|
561
617
|
readStatus,
|
|
562
618
|
resolveAgentSourceFile,
|
|
563
|
-
resolveStateDir
|
|
619
|
+
resolveStateDir,
|
|
620
|
+
resolveTuiPath
|
|
564
621
|
};
|
package/dist/index.js
CHANGED
|
@@ -1534,11 +1534,11 @@ async function fetchEnabledProviders() {
|
|
|
1534
1534
|
}
|
|
1535
1535
|
function providerAdvice(h5, wk) {
|
|
1536
1536
|
const S5H = STOP_5H, SWK = STOP_WK, T5H = THR_5H, TWK = THR_WK;
|
|
1537
|
-
if (h5 >= S5H || wk >= SWK) return "STOP \u2014 finish current only";
|
|
1538
|
-
if (h5 >= T5H && wk >= TWK) return "small tasks only \u2014 big ones will hit both limits";
|
|
1539
|
-
if (h5 >= T5H) return "small tasks only \u2014 5h window nearly full, big tasks after reset";
|
|
1540
|
-
if (wk >= TWK) return "small tasks only \u2014 big ones will strain late-week";
|
|
1541
|
-
if (h5 >= 50 || wk >= 50) return "moderate tasks OK \u2014 save big ones for headroom";
|
|
1537
|
+
if (h5 !== null && h5 >= S5H || wk !== null && wk >= SWK) return "STOP \u2014 finish current only";
|
|
1538
|
+
if (h5 !== null && wk !== null && h5 >= T5H && wk >= TWK) return "small tasks only \u2014 big ones will hit both limits";
|
|
1539
|
+
if (h5 !== null && h5 >= T5H) return "small tasks only \u2014 5h window nearly full, big tasks after reset";
|
|
1540
|
+
if (wk !== null && wk >= TWK) return "small tasks only \u2014 big ones will strain late-week";
|
|
1541
|
+
if (h5 !== null && h5 >= 50 || wk !== null && wk >= 50) return "moderate tasks OK \u2014 save big ones for headroom";
|
|
1542
1542
|
return "big tasks OK \u2014 short & long limits comfortable";
|
|
1543
1543
|
}
|
|
1544
1544
|
async function fetchProvidersCoach() {
|
|
@@ -1546,17 +1546,17 @@ async function fetchProvidersCoach() {
|
|
|
1546
1546
|
const results = await Promise.all(ids.map(async (id) => {
|
|
1547
1547
|
try {
|
|
1548
1548
|
const out = await captureStdout(["usage", "--provider", id, "--json"]);
|
|
1549
|
-
const
|
|
1550
|
-
if (!
|
|
1551
|
-
const h5 = Math.round(
|
|
1552
|
-
const wk = Math.round(
|
|
1549
|
+
const q = parseQuotaResponse(out);
|
|
1550
|
+
if (!q) return null;
|
|
1551
|
+
const h5 = q.fiveHour ? Math.round(q.fiveHour.usedPercent ?? 0) : null;
|
|
1552
|
+
const wk = q.weekly ? Math.round(q.weekly.usedPercent ?? 0) : null;
|
|
1553
1553
|
return {
|
|
1554
1554
|
id,
|
|
1555
1555
|
name: id,
|
|
1556
|
-
fiveHour: h5,
|
|
1557
|
-
weekly: wk,
|
|
1558
|
-
fiveHourReset: humanRemaining(
|
|
1559
|
-
weeklyReset: humanRemaining(
|
|
1556
|
+
fiveHour: h5 ?? -1,
|
|
1557
|
+
weekly: wk ?? -1,
|
|
1558
|
+
fiveHourReset: q.fiveHour ? humanRemaining(q.fiveHour.resetsAt) : "",
|
|
1559
|
+
weeklyReset: q.weekly ? humanRemaining(q.weekly.resetsAt) : "",
|
|
1560
1560
|
advice: providerAdvice(h5, wk)
|
|
1561
1561
|
};
|
|
1562
1562
|
} catch {
|
|
@@ -1571,11 +1571,36 @@ function parseQuotaResponse(rawText) {
|
|
|
1571
1571
|
if (!text || text === "[]") return null;
|
|
1572
1572
|
const u = JSON.parse(text)[0]?.usage;
|
|
1573
1573
|
if (!u) return null;
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1574
|
+
const result = { weekly: null, monthly: null, fiveHour: null };
|
|
1575
|
+
const entries = [
|
|
1576
|
+
{ key: "primary", slot: u.primary },
|
|
1577
|
+
{ key: "secondary", slot: u.secondary },
|
|
1578
|
+
{ key: "tertiary", slot: u.tertiary }
|
|
1579
|
+
].filter((e) => e.slot != null);
|
|
1580
|
+
for (const { key, slot } of entries) {
|
|
1581
|
+
const mins = slot.windowMinutes ?? 0;
|
|
1582
|
+
const desc = String(slot.resetDescription ?? "").toLowerCase();
|
|
1583
|
+
if (mins === 300 || desc.includes("5 hour") || desc.includes("5h")) {
|
|
1584
|
+
result.fiveHour = slot;
|
|
1585
|
+
} else if (mins === 10080 || desc.includes("week")) {
|
|
1586
|
+
result.weekly = slot;
|
|
1587
|
+
} else if (desc.includes("month")) {
|
|
1588
|
+
result.monthly = slot;
|
|
1589
|
+
} else if (mins > 0 && mins <= 360) {
|
|
1590
|
+
result.fiveHour = slot;
|
|
1591
|
+
} else if (mins > 360 && mins <= 14400) {
|
|
1592
|
+
result.weekly = slot;
|
|
1593
|
+
} else if (mins > 14400) {
|
|
1594
|
+
result.monthly = slot;
|
|
1595
|
+
} else if (key === "primary") {
|
|
1596
|
+
result.weekly = slot;
|
|
1597
|
+
} else if (key === "secondary") {
|
|
1598
|
+
result.monthly = slot;
|
|
1599
|
+
} else if (key === "tertiary") {
|
|
1600
|
+
result.fiveHour = slot;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return result;
|
|
1579
1604
|
} catch {
|
|
1580
1605
|
return null;
|
|
1581
1606
|
}
|
|
@@ -1617,17 +1642,31 @@ async function fetchQuotaWithRetry(provider, maxRetries = 3) {
|
|
|
1617
1642
|
}
|
|
1618
1643
|
function coach(q, lighter) {
|
|
1619
1644
|
if (!q) return { decision: "GO", advice: "quota unavailable \u2014 retrying. proceeding cautiously.", weekly: -2, monthly: -2, fiveHour: -2 };
|
|
1620
|
-
const wk =
|
|
1621
|
-
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1645
|
+
const wk = q.weekly ? Math.round(q.weekly.usedPercent ?? 0) : null;
|
|
1646
|
+
const mo = q.monthly ? Math.round(q.monthly.usedPercent ?? 0) : null;
|
|
1647
|
+
const h5 = q.fiveHour ? Math.round(q.fiveHour.usedPercent ?? 0) : null;
|
|
1648
|
+
if (wk !== null && Number.isNaN(wk) || mo !== null && Number.isNaN(mo) || h5 !== null && Number.isNaN(h5)) {
|
|
1649
|
+
const sWk = wk !== null && !Number.isNaN(wk) ? wk : 0;
|
|
1650
|
+
const sMo = mo !== null && !Number.isNaN(mo) ? mo : 0;
|
|
1651
|
+
const sH5 = h5 !== null && !Number.isNaN(h5) ? h5 : 0;
|
|
1652
|
+
return { decision: "THROTTLE", advice: "invalid quota data \u2014 proceeding with caution. switch to lighter model if available.", weekly: sWk, monthly: sMo, fiveHour: sH5 };
|
|
1653
|
+
}
|
|
1654
|
+
const wkR = q.weekly ? humanRemaining(q.weekly.resetsAt) : "";
|
|
1655
|
+
const h5R = q.fiveHour ? humanRemaining(q.fiveHour.resetsAt) : "";
|
|
1656
|
+
const stop = (r) => ({ decision: "STOP", advice: `STOP recommend \u2014 ${r}. window nearly exhausted. stop now or it will be force-blocked.`, weekly: wk ?? -1, monthly: mo ?? -1, fiveHour: h5 ?? -1 });
|
|
1657
|
+
const thr = (r) => ({ decision: "THROTTLE", advice: `Throttle recommend \u2014 ${r}. switch to lighter model (${lighter}) or wait for window reset.`, weekly: wk ?? -1, monthly: mo ?? -1, fiveHour: h5 ?? -1 });
|
|
1658
|
+
if (h5 !== null && h5 >= STOP_5H) return stop(`5h window ${h5}% (${h5R})`);
|
|
1659
|
+
if (wk !== null && wk >= STOP_WK) return stop(`weekly ${wk}% (${wkR})`);
|
|
1660
|
+
if (mo !== null && mo >= STOP_MO) return stop(`monthly ${mo}%`);
|
|
1661
|
+
if (h5 !== null && h5 >= THR_5H) return thr(`5h window ${h5}% (${h5R})`);
|
|
1662
|
+
if (wk !== null && wk >= THR_WK) return thr(`weekly ${wk}% (${wkR})`);
|
|
1663
|
+
const parts = [];
|
|
1664
|
+
if (wk !== null) parts.push(`weekly ${wk}%`);
|
|
1665
|
+
if (h5 !== null) parts.push(`5h ${h5}%`);
|
|
1666
|
+
if (mo !== null) parts.push(`monthly ${mo}%`);
|
|
1667
|
+
const summary = parts.length > 0 ? parts.join(" \xB7 ") : "no quota limits detected";
|
|
1668
|
+
const resetInfo = h5R ? ` 5h window ${h5R}.` : wkR ? ` weekly ${wkR}.` : "";
|
|
1669
|
+
return { decision: "GO", advice: `Comfortable \u2014 ${summary}. proceed.${resetInfo}`, weekly: wk ?? -1, monthly: mo ?? -1, fiveHour: h5 ?? -1 };
|
|
1631
1670
|
}
|
|
1632
1671
|
var agentCache = /* @__PURE__ */ new Map();
|
|
1633
1672
|
var currentModel = "";
|
|
@@ -1731,9 +1770,12 @@ async function UsageCoachPlugin(input) {
|
|
|
1731
1770
|
providers = await fetchProvidersCoach();
|
|
1732
1771
|
} catch {
|
|
1733
1772
|
}
|
|
1734
|
-
if (providers.length > 0 && last.weekly < 0) {
|
|
1773
|
+
if (providers.length > 0 && last.weekly < 0 && last.fiveHour < 0) {
|
|
1735
1774
|
const p0 = providers[0];
|
|
1736
|
-
|
|
1775
|
+
const newWeekly = p0.weekly >= 0 ? p0.weekly : last.weekly;
|
|
1776
|
+
const newFiveHour = p0.fiveHour >= 0 ? p0.fiveHour : last.fiveHour;
|
|
1777
|
+
const newDecision = newWeekly >= STOP_WK || newFiveHour >= STOP_5H ? "STOP" : newWeekly >= THR_WK || newFiveHour >= THR_5H ? "THROTTLE" : "GO";
|
|
1778
|
+
last = { ...last, weekly: newWeekly, fiveHour: newFiveHour, advice: p0.advice, decision: newDecision };
|
|
1737
1779
|
}
|
|
1738
1780
|
writeState({ ...last, providers, model: currentModel, provider: currentProvider, isFree: false, agent: currentAgent, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1739
1781
|
log(`${last.decision} | weekly=${last.weekly}% 5h=${last.fiveHour}% | providers=${providers.length}`);
|
|
@@ -1807,7 +1849,13 @@ async function UsageCoachPlugin(input) {
|
|
|
1807
1849
|
let instruction = "";
|
|
1808
1850
|
if (c.decision === "STOP") instruction = `[${PLUGIN_NAME}] QUOTA limit exceeded. ${c.advice} Stop making further tool calls, finish the in-progress work, then report the quota status to the user.`;
|
|
1809
1851
|
else if (c.decision === "THROTTLE") instruction = `[${PLUGIN_NAME}] ${c.advice} Hold off on long/heavy tasks.`;
|
|
1810
|
-
else if (c.weekly >= 0
|
|
1852
|
+
else if (c.weekly >= 0 || c.fiveHour >= 0) {
|
|
1853
|
+
const parts = [];
|
|
1854
|
+
if (c.weekly >= 0) parts.push(`weekly ${c.weekly}%`);
|
|
1855
|
+
if (c.fiveHour >= 0) parts.push(`5h ${c.fiveHour}%`);
|
|
1856
|
+
if (c.monthly >= 0) parts.push(`monthly ${c.monthly}%`);
|
|
1857
|
+
instruction = `[${PLUGIN_NAME}] quota ok \u2014 ${parts.join(" \xB7 ")}.`;
|
|
1858
|
+
}
|
|
1811
1859
|
if (instruction) output.system.push(instruction);
|
|
1812
1860
|
} catch (e) {
|
|
1813
1861
|
log(`system.transform err: ${String(e)}`);
|
|
@@ -2671,35 +2719,4 @@ Note: Changes take effect immediately for new generate/grade calls.`,
|
|
|
2671
2719
|
return NOOP_HOOKS;
|
|
2672
2720
|
}
|
|
2673
2721
|
}
|
|
2674
|
-
export {
|
|
2675
|
-
__resetCodexbarMissing,
|
|
2676
|
-
buildGapPrompt,
|
|
2677
|
-
buildScanSummary,
|
|
2678
|
-
checkScanGate,
|
|
2679
|
-
clearSubSession,
|
|
2680
|
-
coach,
|
|
2681
|
-
UsageCoachPlugin as default,
|
|
2682
|
-
detectLanguage,
|
|
2683
|
-
extractImplNotes,
|
|
2684
|
-
extractKeywords,
|
|
2685
|
-
fetchQuotaWithRetry,
|
|
2686
|
-
findActiveTaskId,
|
|
2687
|
-
formatReport,
|
|
2688
|
-
humanRemaining,
|
|
2689
|
-
isCodexbarMissing,
|
|
2690
|
-
isFreeModel,
|
|
2691
|
-
isHarnessAgent,
|
|
2692
|
-
parseFileList,
|
|
2693
|
-
parseGapAnalysis,
|
|
2694
|
-
parseQuotaResponse,
|
|
2695
|
-
providerAdvice,
|
|
2696
|
-
providerToCodexbar,
|
|
2697
|
-
readHarness,
|
|
2698
|
-
readHarnessCfg,
|
|
2699
|
-
readRules,
|
|
2700
|
-
UsageCoachPlugin as server,
|
|
2701
|
-
setStateDir,
|
|
2702
|
-
updateSubSession,
|
|
2703
|
-
writeHarness,
|
|
2704
|
-
writeHarnessCfg
|
|
2705
|
-
};
|
|
2722
|
+
export { UsageCoachPlugin as server, UsageCoachPlugin as default };
|
package/dist/tui.js
CHANGED
|
@@ -324,7 +324,7 @@ function initializeTui(api, disposeRoot) {
|
|
|
324
324
|
}
|
|
325
325
|
if (s.providers && s.providers.length > 0) {
|
|
326
326
|
for (const p of s.providers) {
|
|
327
|
-
nodes.push((() => {
|
|
327
|
+
if (p.fiveHour >= 0) nodes.push((() => {
|
|
328
328
|
var _el$16 = _$createElement("box"), _el$17 = _$createElement("text"), _el$19 = _$createElement("text"), _el$20 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` `), _el$23 = _$createTextNode(`% `);
|
|
329
329
|
_$insertNode(_el$16, _el$17);
|
|
330
330
|
_$insertNode(_el$16, _el$19);
|
|
@@ -349,7 +349,7 @@ function initializeTui(api, disposeRoot) {
|
|
|
349
349
|
});
|
|
350
350
|
return _el$16;
|
|
351
351
|
})());
|
|
352
|
-
nodes.push((() => {
|
|
352
|
+
if (p.weekly >= 0) nodes.push((() => {
|
|
353
353
|
var _el$24 = _$createElement("box"), _el$25 = _$createElement("text"), _el$27 = _$createElement("text"), _el$28 = _$createElement("text"), _el$29 = _$createElement("text"), _el$30 = _$createTextNode(` `), _el$31 = _$createTextNode(`% `);
|
|
354
354
|
_$insertNode(_el$24, _el$25);
|
|
355
355
|
_$insertNode(_el$24, _el$27);
|
|
@@ -376,7 +376,7 @@ function initializeTui(api, disposeRoot) {
|
|
|
376
376
|
})());
|
|
377
377
|
}
|
|
378
378
|
} else {
|
|
379
|
-
nodes.push((() => {
|
|
379
|
+
if (s.fiveHour >= 0) nodes.push((() => {
|
|
380
380
|
var _el$32 = _$createElement("box"), _el$33 = _$createElement("text"), _el$35 = _$createElement("text"), _el$36 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createTextNode(` `), _el$39 = _$createTextNode(`%`);
|
|
381
381
|
_$insertNode(_el$32, _el$33);
|
|
382
382
|
_$insertNode(_el$32, _el$35);
|
|
@@ -400,7 +400,7 @@ function initializeTui(api, disposeRoot) {
|
|
|
400
400
|
});
|
|
401
401
|
return _el$32;
|
|
402
402
|
})());
|
|
403
|
-
nodes.push((() => {
|
|
403
|
+
if (s.weekly >= 0) nodes.push((() => {
|
|
404
404
|
var _el$40 = _$createElement("box"), _el$41 = _$createElement("text"), _el$43 = _$createElement("text"), _el$44 = _$createElement("text"), _el$45 = _$createElement("text"), _el$46 = _$createTextNode(` `), _el$47 = _$createTextNode(`%`);
|
|
405
405
|
_$insertNode(_el$40, _el$41);
|
|
406
406
|
_$insertNode(_el$40, _el$43);
|
package/package.json
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-coach",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "opencode closed-loop usage coach — quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"./tui": {
|
|
14
|
-
"types": "./dist/tui.d.ts",
|
|
15
|
-
"import": "./dist/tui.js"
|
|
16
|
-
},
|
|
17
|
-
"./cli": {
|
|
18
|
-
"types": "./dist/cli.d.ts",
|
|
19
|
-
"import": "./dist/cli.js"
|
|
20
|
-
}
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./tui": "./dist/tui.js",
|
|
11
|
+
"./cli": "./dist/cli.js"
|
|
21
12
|
},
|
|
22
13
|
"bin": {
|
|
23
14
|
"usage-coach": "./dist/cli.js"
|
|
24
15
|
},
|
|
25
16
|
"scripts": {
|
|
26
17
|
"build": "tsup",
|
|
18
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test",
|
|
27
19
|
"typecheck": "tsc --noEmit",
|
|
28
20
|
"lint": "eslint .",
|
|
29
21
|
"lint:fix": "eslint . --fix",
|