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 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 — server plugin
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
- Then run setup:
43
+ **Step 3 — Run setup (auto-configures everything else):**
37
44
 
38
45
  ```bash
39
- usage-coach setup # auto-generates harness.config.json + copies agent file
40
- usage-coach setup --json # machine-readable output (for scripts/CI)
46
+ usage-coach setup
41
47
  ```
42
48
 
43
- This creates `~/.config/opencode-usage-coach/harness.config.json` (edit `generator`/`grader` or use `/coach-config` at runtime) and copies `agents/usage-coach-harness.md` to `~/.config/opencode/agents/`.
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
- If you use `codexbar` for quota sensing:
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
- Without codexbar, the plugin runs in GO-only mode (no quota sensing).
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
- For local dev without npm: `bun install && bun run build`, then point both configs at the `dist/` files.
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
- Common issues: TUI panel missing (check `tui.json` points at `dist/tui.js`, never install `solid-js` in the config dir), model selection errors (`generator` is required), and tool-abort timeouts (platform limit split large tasks).
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 u = JSON.parse(out)[0]?.usage;
1550
- if (!u) return null;
1551
- const h5 = Math.round(u.tertiary?.usedPercent ?? 0);
1552
- const wk = Math.round(u.primary?.usedPercent ?? 0);
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(u.tertiary?.resetsAt),
1559
- weeklyReset: humanRemaining(u.primary?.resetsAt),
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
- return {
1575
- weekly: u.primary ?? { usedPercent: 0 },
1576
- monthly: u.secondary ?? { usedPercent: 0 },
1577
- fiveHour: u.tertiary ?? { usedPercent: 0 }
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 = Math.round(q.weekly?.usedPercent ?? 0), mo = Math.round(q.monthly?.usedPercent ?? 0), h5 = Math.round(q.fiveHour?.usedPercent ?? 0);
1621
- if (Number.isNaN(wk) || Number.isNaN(mo) || Number.isNaN(h5)) return { decision: "THROTTLE", advice: "invalid quota data \u2014 proceeding with caution. switch to lighter model if available.", weekly: Number.isNaN(wk) ? 0 : wk, monthly: Number.isNaN(mo) ? 0 : mo, fiveHour: Number.isNaN(h5) ? 0 : h5 };
1622
- const wkR = humanRemaining(q.weekly?.resetsAt), h5R = humanRemaining(q.fiveHour?.resetsAt);
1623
- const stop = (r) => ({ decision: "STOP", advice: `STOP recommend \u2014 ${r}. window nearly exhausted. stop now or it will be force-blocked.`, weekly: wk, monthly: mo, fiveHour: h5 });
1624
- const thr = (r) => ({ decision: "THROTTLE", advice: `Throttle recommend \u2014 ${r}. switch to lighter model (${lighter}) or wait for window reset.`, weekly: wk, monthly: mo, fiveHour: h5 });
1625
- if (h5 >= STOP_5H) return stop(`5h window ${h5}% (${h5R})`);
1626
- if (wk >= STOP_WK) return stop(`weekly ${wk}% (${wkR})`);
1627
- if (mo >= STOP_MO) return stop(`monthly ${mo}%`);
1628
- if (h5 >= THR_5H) return thr(`5h window ${h5}% (${h5R})`);
1629
- if (wk >= THR_WK) return thr(`weekly ${wk}% (${wkR})`);
1630
- return { decision: "GO", advice: `Comfortable \u2014 weekly ${wk}% \xB7 5h ${h5}% \xB7 monthly ${mo}%. proceed. 5h window ${h5R}.`, weekly: wk, monthly: mo, fiveHour: h5 };
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
- last = { ...last, weekly: p0.weekly, fiveHour: p0.fiveHour, monthly: p0.weekly >= 0 ? 0 : -1, advice: p0.advice, decision: p0.weekly >= STOP_WK ? "STOP" : p0.weekly >= THR_WK ? "THROTTLE" : "GO" };
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) instruction = `[${PLUGIN_NAME}] quota ok \u2014 weekly ${c.weekly}% \xB7 5h ${c.fiveHour}% \xB7 monthly ${c.monthly}%.`;
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.0",
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
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
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",