opencode-usage-coach 0.13.0 → 0.13.1
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/dist/cli.js +2 -2
- package/dist/index.js +81 -64
- package/dist/tui.js +4 -4
- package/package.json +1 -1
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) {
|
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