@songsid/agend 2.1.2-beta.15 → 2.1.2-beta.17
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/access-path.js +15 -6
- package/dist/access-path.js.map +1 -1
- package/dist/backend/claude-code.d.ts +37 -0
- package/dist/backend/claude-code.js +43 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +22 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.d.ts +23 -0
- package/dist/backend/grok.js +25 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +20 -0
- package/dist/backend/kiro.js +59 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +27 -0
- package/dist/backend/types.js +1 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +15 -0
- package/dist/channel/adapters/discord.js +91 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +21 -0
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +9 -1
- package/dist/channel/ipc-bridge.js +12 -3
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/ipc-timeouts.d.ts +40 -0
- package/dist/channel/ipc-timeouts.js +58 -0
- package/dist/channel/ipc-timeouts.js.map +1 -0
- package/dist/channel/mcp-server.js +25 -14
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +4 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/reconnect-backoff.d.ts +17 -0
- package/dist/channel/reconnect-backoff.js +21 -0
- package/dist/channel/reconnect-backoff.js.map +1 -0
- package/dist/channel/types.d.ts +29 -0
- package/dist/classic-channel-manager.js +1 -5
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +129 -50
- package/dist/cli.js.map +1 -1
- package/dist/completion.d.ts +27 -0
- package/dist/completion.js +121 -0
- package/dist/completion.js.map +1 -0
- package/dist/config-validator.js +9 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/cost-guard.d.ts +3 -1
- package/dist/cost-guard.js +3 -1
- package/dist/cost-guard.js.map +1 -1
- package/dist/daemon.d.ts +164 -5
- package/dist/daemon.js +711 -290
- package/dist/daemon.js.map +1 -1
- package/dist/event-log.d.ts +31 -0
- package/dist/event-log.js +96 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-manager.d.ts +188 -1
- package/dist/fleet-manager.js +713 -61
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +1 -1
- package/dist/hang-detector.d.ts +19 -13
- package/dist/hang-detector.js +19 -49
- package/dist/hang-detector.js.map +1 -1
- package/dist/instance-lifecycle.js +25 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +2 -1
- package/dist/locale.js.map +1 -1
- package/dist/mcp-liveness.d.ts +21 -0
- package/dist/mcp-liveness.js +27 -0
- package/dist/mcp-liveness.js.map +1 -0
- package/dist/outbound-handlers.d.ts +7 -0
- package/dist/outbound-handlers.js +79 -5
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +7 -2
- package/dist/outbound-schemas.js +13 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-write-lock.d.ts +48 -0
- package/dist/pane-write-lock.js +73 -0
- package/dist/pane-write-lock.js.map +1 -0
- package/dist/process-memory.d.ts +31 -0
- package/dist/process-memory.js +79 -0
- package/dist/process-memory.js.map +1 -0
- package/dist/quickstart.js +17 -16
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.js +3 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/sd-notify.d.ts +27 -0
- package/dist/sd-notify.js +33 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/secret-file.d.ts +33 -0
- package/dist/secret-file.js +36 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/setup-wizard.js +9 -7
- package/dist/setup-wizard.js.map +1 -1
- package/dist/tmux-control.d.ts +58 -5
- package/dist/tmux-control.js +102 -14
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +31 -1
- package/dist/tmux-manager.js +40 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +30 -11
- package/dist/topic-commands.js +164 -66
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/ui/view.html +123 -2
- package/dist/usage/format-rich.d.ts +11 -0
- package/dist/usage/format-rich.js +108 -0
- package/dist/usage/format-rich.js.map +1 -0
- package/dist/usage/providers.d.ts +50 -0
- package/dist/usage/providers.js +611 -0
- package/dist/usage/providers.js.map +1 -0
- package/dist/usage/usage-api.d.ts +50 -0
- package/dist/usage/usage-api.js +111 -0
- package/dist/usage/usage-api.js.map +1 -0
- package/dist/view-api.js +1 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.js +5 -2
- package/dist/web-api.js.map +1 -1
- package/package.json +4 -1
- package/dist/channel/tool-tracker.d.ts +0 -13
- package/dist/channel/tool-tracker.js +0 -58
- package/dist/channel/tool-tracker.js.map +0 -1
- package/dist/daemon-entry.d.ts +0 -1
- package/dist/daemon-entry.js +0 -30
- package/dist/daemon-entry.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -232,6 +232,11 @@ export interface FleetTemplate {
|
|
|
232
232
|
team?: boolean;
|
|
233
233
|
instances: Record<string, TemplateInstanceDef>;
|
|
234
234
|
}
|
|
235
|
+
/** Web UI feature toggles (fleet.yaml `web:` section). */
|
|
236
|
+
export interface WebConfig {
|
|
237
|
+
/** Show the AI subscription usage panel on /view and serve /api/ai-usage (default true). */
|
|
238
|
+
usage_panel?: boolean;
|
|
239
|
+
}
|
|
235
240
|
export interface FleetConfig {
|
|
236
241
|
channel?: ChannelConfig;
|
|
237
242
|
channels?: ChannelConfig[];
|
|
@@ -242,6 +247,7 @@ export interface FleetConfig {
|
|
|
242
247
|
templates?: Record<string, FleetTemplate>;
|
|
243
248
|
profiles?: Record<string, ProfileConfig>;
|
|
244
249
|
health_port?: number;
|
|
250
|
+
web?: WebConfig;
|
|
245
251
|
}
|
|
246
252
|
/**
|
|
247
253
|
* User-authored fleet.yaml before defaults are merged into instances.
|
package/dist/ui/view.html
CHANGED
|
@@ -83,6 +83,30 @@
|
|
|
83
83
|
.help-panel li { font-size: 13px; margin: 6px 0; color: var(--fg); }
|
|
84
84
|
.help-panel .close { position: absolute; top: 14px; right: 16px; cursor: pointer; color: var(--dim); font-size: 22px; line-height: 1; background: none; border: none; }
|
|
85
85
|
|
|
86
|
+
/* AI usage panel (shares .help-panel chrome) */
|
|
87
|
+
.u-provider { margin: 14px 0 4px; padding-top: 10px; border-top: 1px solid var(--border); }
|
|
88
|
+
.u-provider:first-of-type { border-top: none; padding-top: 0; }
|
|
89
|
+
.u-head { display: flex; align-items: baseline; gap: 8px; }
|
|
90
|
+
.u-head .u-name { font-size: 14px; font-weight: 600; }
|
|
91
|
+
.u-head .u-plan { color: var(--dim); font-size: 11px; }
|
|
92
|
+
.u-metric { margin: 8px 0; }
|
|
93
|
+
.u-row { display: flex; align-items: baseline; gap: 6px; font-size: 12px; }
|
|
94
|
+
.u-row .u-label { color: var(--dim); flex: 1; min-width: 0; }
|
|
95
|
+
.u-row .u-val { font-variant-numeric: tabular-nums; }
|
|
96
|
+
.u-row .u-status { font-size: 11px; font-weight: 600; }
|
|
97
|
+
.u-status.warn { color: #d29922; } .u-status.crit { color: #f85149; }
|
|
98
|
+
.u-meter { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; margin-top: 4px; }
|
|
99
|
+
.u-fill { height: 100%; border-radius: 3px; background: var(--accent); min-width: 2px; }
|
|
100
|
+
.u-fill.warn { background: #d29922; } .u-fill.crit { background: #f85149; }
|
|
101
|
+
.u-sub { color: var(--dim); font-size: 11px; margin-top: 2px; font-variant-numeric: tabular-nums; }
|
|
102
|
+
.u-err { color: #d29922; font-size: 12px; margin: 6px 0; }
|
|
103
|
+
/* Neutral note (subscription plan, or "sign-in needed") — deliberately not the
|
|
104
|
+
amber error colour, since neither state is a failure. */
|
|
105
|
+
.u-note { color: var(--dim); font-size: 12px; margin: 6px 0; }
|
|
106
|
+
.u-foot { color: var(--dim); font-size: 11px; margin-top: 14px; display: flex; gap: 8px; align-items: baseline; }
|
|
107
|
+
.u-foot button { background: none; border: 1px solid var(--border); border-radius: 5px; color: var(--dim); cursor: pointer; font-size: 11px; padding: 2px 8px; }
|
|
108
|
+
.u-foot button:hover { color: var(--fg); border-color: var(--accent); }
|
|
109
|
+
|
|
86
110
|
#densityBtn { font-size: 14px; font-weight: 600; letter-spacing: -.5px; }
|
|
87
111
|
@media (max-width: 700px) {
|
|
88
112
|
#sidebar { width: 180px; flex-basis: 180px; }
|
|
@@ -93,6 +117,7 @@
|
|
|
93
117
|
</style>
|
|
94
118
|
</head>
|
|
95
119
|
<body>
|
|
120
|
+
<button class="help-btn" id="usageBtn" title="AI usage" style="right:140px; display:none">📊</button>
|
|
96
121
|
<button class="help-btn" id="densityBtn" title="Font density" style="right:98px">⤢</button>
|
|
97
122
|
<button class="help-btn" id="langBtn" title="Language" style="right:56px">🌐</button>
|
|
98
123
|
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
@@ -101,6 +126,10 @@
|
|
|
101
126
|
<button class="close" id="helpClose" title="Close">×</button>
|
|
102
127
|
<div id="helpContent"></div>
|
|
103
128
|
</aside>
|
|
129
|
+
<aside class="help-panel" id="usagePanel">
|
|
130
|
+
<button class="close" id="usageClose" title="Close">×</button>
|
|
131
|
+
<div id="usageContent"></div>
|
|
132
|
+
</aside>
|
|
104
133
|
<div id="sidebar"><h1 data-i18n="viewTitle">AgEnD · View</h1><div id="list"></div></div>
|
|
105
134
|
<div id="main">
|
|
106
135
|
<div id="term"><pre id="pre"></pre></div>
|
|
@@ -147,7 +176,10 @@
|
|
|
147
176
|
help2: "Click a group header ▼/▶ to collapse/expand", help3: "✏️ Edit changes display name, role, description",
|
|
148
177
|
help4: "Drag to reorder (needs web.token permission)",
|
|
149
178
|
help5: "Click ⤢ to cycle font density: Fit → Comfortable → Compact",
|
|
150
|
-
densityFit: "Fit", densityComfortable: "Comfortable", densityCompact: "Compact", densityLabel: "Font density"
|
|
179
|
+
densityFit: "Fit", densityComfortable: "Comfortable", densityCompact: "Compact", densityLabel: "Font density",
|
|
180
|
+
usageTitle: "📊 AI Usage", usageEmpty: "No AI CLI logins found on this machine.",
|
|
181
|
+
usageUpdated: "updated", usageRefresh: "Refresh", usageHigh: "high", usageNear: "near limit",
|
|
182
|
+
usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data." },
|
|
151
183
|
"zh-TW": { viewTitle: "AgEnD · 檢視", editProfile: "✏️ 編輯", save: "儲存", cancel: "取消",
|
|
152
184
|
displayName: "顯示名稱", role: "角色", description: "描述", tokenPh: "web.token(儲存時需要)",
|
|
153
185
|
saveHint: "檢視為公開(唯讀)。儲存需要 web.token(~/.agend/web.token)。", helpTitle: "📖 使用說明",
|
|
@@ -155,7 +187,10 @@
|
|
|
155
187
|
help2: "點 group header ▼/▶ 收合/展開", help3: "✏️ Edit 可修改顯示名稱、角色、描述",
|
|
156
188
|
help4: "拖曳可調整順序(需 web.token 權限)",
|
|
157
189
|
help5: "點 ⤢ 循環切換字級密度:剛好填滿 → 舒適 → 精簡",
|
|
158
|
-
densityFit: "剛好填滿", densityComfortable: "舒適", densityCompact: "精簡", densityLabel: "字級密度"
|
|
190
|
+
densityFit: "剛好填滿", densityComfortable: "舒適", densityCompact: "精簡", densityLabel: "字級密度",
|
|
191
|
+
usageTitle: "📊 AI 訂閱用量", usageEmpty: "本機沒有偵測到已登入的 AI CLI。",
|
|
192
|
+
usageUpdated: "更新於", usageRefresh: "重新整理", usageHigh: "偏高", usageNear: "接近上限",
|
|
193
|
+
usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。" },
|
|
159
194
|
};
|
|
160
195
|
let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
161
196
|
if (!I18N[lang]) lang = "en";
|
|
@@ -667,6 +702,92 @@
|
|
|
667
702
|
q("densityBtn").onclick = cycleDensity;
|
|
668
703
|
window.addEventListener("keydown", (e) => { if (e.key === "Escape") setHelp(false); });
|
|
669
704
|
|
|
705
|
+
// ── AI usage panel ────────────────────────────────────
|
|
706
|
+
let usageData = null;
|
|
707
|
+
let usageTimer = null;
|
|
708
|
+
|
|
709
|
+
function usageResetText(iso) {
|
|
710
|
+
if (!iso) return "";
|
|
711
|
+
const ms = new Date(iso).getTime() - Date.now();
|
|
712
|
+
if (isNaN(ms)) return "";
|
|
713
|
+
if (ms <= 0) return T("usageSoon");
|
|
714
|
+
const h = Math.floor(ms / 3600000), m = Math.floor((ms % 3600000) / 60000);
|
|
715
|
+
const t = h >= 48 ? `${Math.floor(h / 24)}d ${h % 24}h` : h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
716
|
+
return T("usageResets").replace("{t}", t);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function usageMetricHtml(m) {
|
|
720
|
+
if (m.type === "percent") {
|
|
721
|
+
const pct = Math.min(100, Math.max(0, m.used ?? 0));
|
|
722
|
+
const cls = pct >= 90 ? "crit" : pct >= 70 ? "warn" : "";
|
|
723
|
+
const word = pct >= 90 ? T("usageNear") : pct >= 70 ? T("usageHigh") : "";
|
|
724
|
+
const sub = [m.note, usageResetText(m.resetsAt)].filter(Boolean).join(" · ");
|
|
725
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span>` +
|
|
726
|
+
(word ? `<span class="u-status ${cls}">${esc(word)}</span>` : "") +
|
|
727
|
+
`<span class="u-val">${pct.toFixed(0)}%</span></div>` +
|
|
728
|
+
`<div class="u-meter"><div class="u-fill ${cls}" style="width:${pct}%"></div></div>` +
|
|
729
|
+
(sub ? `<div class="u-sub">${esc(sub)}</div>` : "") + `</div>`;
|
|
730
|
+
}
|
|
731
|
+
if (m.type === "dollars") {
|
|
732
|
+
const val = m.limit ? `$${(m.used ?? 0).toFixed(2)} / $${m.limit.toFixed(2)}` : `$${(m.used ?? 0).toFixed(2)}`;
|
|
733
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span><span class="u-val">${esc(val)}</span></div></div>`;
|
|
734
|
+
}
|
|
735
|
+
const val = `${m.value ?? ""} ${m.unit ?? ""}`.trim();
|
|
736
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span><span class="u-val">${esc(val)}</span></div>` +
|
|
737
|
+
(m.note ? `<div class="u-sub">${esc(m.note)}</div>` : "") + `</div>`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function renderUsage() {
|
|
741
|
+
const box = q("usageContent");
|
|
742
|
+
if (!usageData || !Array.isArray(usageData.providers)) { box.innerHTML = `<h3>${T("usageTitle")}</h3><div class="u-err">${T("usageFail")}</div>`; return; }
|
|
743
|
+
let html = `<h3>${T("usageTitle")}</h3>`;
|
|
744
|
+
if (!usageData.providers.length) html += `<div class="u-err">${T("usageEmpty")}</div>`;
|
|
745
|
+
for (const p of usageData.providers) {
|
|
746
|
+
html += `<div class="u-provider"><div class="u-head"><span class="u-name">${esc(p.name)}</span>` +
|
|
747
|
+
(p.plan ? `<span class="u-plan">${esc(p.plan)}</span>` : "") + `</div>`;
|
|
748
|
+
if (p.status === "error") html += `<div class="u-err">${esc(p.error || "error")}</div>`;
|
|
749
|
+
else {
|
|
750
|
+
html += (p.metrics || []).map(usageMetricHtml).join("");
|
|
751
|
+
// A provider with nothing to meter (subscription plan) or one awaiting
|
|
752
|
+
// sign-in carries a hint instead of metrics. Without this the panel
|
|
753
|
+
// rendered an empty box, so those states looked broken.
|
|
754
|
+
if (p.hint) html += `<div class="u-note">${esc(p.hint)}</div>`;
|
|
755
|
+
}
|
|
756
|
+
html += `</div>`;
|
|
757
|
+
}
|
|
758
|
+
const at = new Date(usageData.fetchedAt).toLocaleTimeString();
|
|
759
|
+
html += `<div class="u-foot"><span>${T("usageUpdated")} ${esc(at)}</span><button id="usageRefresh">${T("usageRefresh")}</button></div>`;
|
|
760
|
+
box.innerHTML = html;
|
|
761
|
+
const rb = q("usageRefresh");
|
|
762
|
+
if (rb) rb.onclick = () => loadUsage(true);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async function loadUsage(force) {
|
|
766
|
+
try {
|
|
767
|
+
const r = await fetch(`/api/ai-usage${force ? "?force=1" : ""}`);
|
|
768
|
+
if (r.status === 404) { q("usageBtn").style.display = "none"; return false; }
|
|
769
|
+
usageData = await r.json();
|
|
770
|
+
} catch { usageData = null; }
|
|
771
|
+
renderUsage();
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const setUsage = (open) => {
|
|
776
|
+
q("usagePanel").classList.toggle("open", open);
|
|
777
|
+
q("helpBackdrop").classList.toggle("open", open);
|
|
778
|
+
if (open) { setHelp(false); loadUsage(false); if (!usageTimer) usageTimer = setInterval(() => loadUsage(false), 60_000); }
|
|
779
|
+
else if (usageTimer) { clearInterval(usageTimer); usageTimer = null; }
|
|
780
|
+
};
|
|
781
|
+
q("usageBtn").onclick = () => setUsage(!q("usagePanel").classList.contains("open"));
|
|
782
|
+
q("usageClose").onclick = () => setUsage(false);
|
|
783
|
+
q("helpBackdrop").addEventListener("click", () => setUsage(false));
|
|
784
|
+
q("helpBtn").addEventListener("click", () => setUsage(false));
|
|
785
|
+
window.addEventListener("keydown", (e) => { if (e.key === "Escape") setUsage(false); });
|
|
786
|
+
q("langBtn").addEventListener("click", () => { if (usageData) renderUsage(); });
|
|
787
|
+
// Probe once at boot: shows the 📊 button only when the endpoint is enabled,
|
|
788
|
+
// and warms the server-side 5-minute cache so the first open is instant.
|
|
789
|
+
loadUsage(false).then(ok => { if (ok !== false) q("usageBtn").style.display = ""; });
|
|
790
|
+
|
|
670
791
|
window.addEventListener("resize", scheduleFit);
|
|
671
792
|
// Catches container changes the window resize event doesn't see — chiefly the
|
|
672
793
|
// profile card expanding or collapsing.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UsagePayload } from "./usage-api.js";
|
|
2
|
+
/** `████░░░░░░` for 40%. Clamped — a vendor "105% used" must not overflow. */
|
|
3
|
+
export declare function usageBar(percent: number): string;
|
|
4
|
+
/** Discord: native Markdown in plain message content. */
|
|
5
|
+
export declare function renderUsageMarkdown(payload: UsagePayload): string;
|
|
6
|
+
/**
|
|
7
|
+
* Telegram: HTML parse mode. Every payload-derived string is entity-escaped —
|
|
8
|
+
* provider errors and metric labels come from vendor responses, and one stray
|
|
9
|
+
* `<` would make Telegram reject the whole message.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderUsageHtml(payload: UsagePayload): string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const BAR_WIDTH = 10;
|
|
2
|
+
/** `████░░░░░░` for 40%. Clamped — a vendor "105% used" must not overflow. */
|
|
3
|
+
export function usageBar(percent) {
|
|
4
|
+
const filled = Math.round(Math.min(100, Math.max(0, percent)) / 100 * BAR_WIDTH);
|
|
5
|
+
return "█".repeat(filled) + "░".repeat(BAR_WIDTH - filled);
|
|
6
|
+
}
|
|
7
|
+
function statusDot(p) {
|
|
8
|
+
if (p.status === "error")
|
|
9
|
+
return "🔴";
|
|
10
|
+
if (p.status === "no-credentials")
|
|
11
|
+
return "⚪";
|
|
12
|
+
// Highest metric drives the colour: the reader scans for "which one is hot".
|
|
13
|
+
const hottest = Math.max(0, ...p.metrics.map(metricPercent).filter((v) => v !== null));
|
|
14
|
+
return hottest >= 90 ? "🔴" : hottest >= 70 ? "🟡" : "🟢";
|
|
15
|
+
}
|
|
16
|
+
/** A metric's fill percentage, when one can honestly be computed. */
|
|
17
|
+
function metricPercent(m) {
|
|
18
|
+
if (m.type === "percent" && typeof m.used === "number")
|
|
19
|
+
return m.used;
|
|
20
|
+
if (m.type === "dollars" && typeof m.used === "number" && m.limit)
|
|
21
|
+
return (m.used / m.limit) * 100;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function resetSuffix(resetsAt) {
|
|
25
|
+
if (!resetsAt)
|
|
26
|
+
return "";
|
|
27
|
+
const at = new Date(resetsAt);
|
|
28
|
+
if (Number.isNaN(at.getTime()))
|
|
29
|
+
return "";
|
|
30
|
+
const hours = Math.round((at.getTime() - Date.now()) / 3_600_000);
|
|
31
|
+
if (hours <= 0)
|
|
32
|
+
return "";
|
|
33
|
+
return hours >= 48 ? ` · resets in ${Math.round(hours / 24)}d` : ` · resets in ${hours}h`;
|
|
34
|
+
}
|
|
35
|
+
function metricLine(m) {
|
|
36
|
+
const pct = metricPercent(m);
|
|
37
|
+
switch (m.type) {
|
|
38
|
+
case "percent":
|
|
39
|
+
return { bar: usageBar(pct ?? 0), text: `${Math.round(m.used ?? 0)}% ${m.label}${resetSuffix(m.resetsAt)}` };
|
|
40
|
+
case "dollars": {
|
|
41
|
+
const used = `$${(m.used ?? 0).toFixed(2)}`;
|
|
42
|
+
return m.limit
|
|
43
|
+
? { bar: usageBar(pct ?? 0), text: `${used}/$${m.limit.toFixed(2)} ${m.label}` }
|
|
44
|
+
: { bar: null, text: `${used} ${m.label}` };
|
|
45
|
+
}
|
|
46
|
+
case "count":
|
|
47
|
+
return { bar: null, text: `${m.label}: ${m.value ?? "?"}${m.unit ? ` ${m.unit}` : ""}` };
|
|
48
|
+
case "text":
|
|
49
|
+
return m.value != null ? { bar: null, text: `${m.label}: ${m.value}` } : null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function toBlocks(payload) {
|
|
53
|
+
return payload.providers.map(p => ({
|
|
54
|
+
dot: statusDot(p),
|
|
55
|
+
name: p.name,
|
|
56
|
+
plan: p.plan ?? null,
|
|
57
|
+
note: p.status === "no-credentials" ? "not logged in"
|
|
58
|
+
: p.status === "error" ? `⚠️ ${p.error ?? "error"}`
|
|
59
|
+
: null,
|
|
60
|
+
lines: p.status === "ok" ? p.metrics.map(metricLine).filter((l) => l !== null) : [],
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/** Discord: native Markdown in plain message content. */
|
|
64
|
+
export function renderUsageMarkdown(payload) {
|
|
65
|
+
const out = ["📊 **AI Subscription Usage**"];
|
|
66
|
+
for (const b of toBlocks(payload)) {
|
|
67
|
+
out.push("", `${b.dot} **${b.name}**${b.plan ? ` (${b.plan})` : ""}`);
|
|
68
|
+
if (b.note) {
|
|
69
|
+
out.push(`> ${b.note}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (b.lines.length === 0) {
|
|
73
|
+
out.push("> no data");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const l of b.lines) {
|
|
77
|
+
out.push(l.bar ? `\`${l.bar}\` ${l.text}` : l.text);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function escapeHtml(s) {
|
|
83
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Telegram: HTML parse mode. Every payload-derived string is entity-escaped —
|
|
87
|
+
* provider errors and metric labels come from vendor responses, and one stray
|
|
88
|
+
* `<` would make Telegram reject the whole message.
|
|
89
|
+
*/
|
|
90
|
+
export function renderUsageHtml(payload) {
|
|
91
|
+
const out = ["📊 <b>AI Subscription Usage</b>"];
|
|
92
|
+
for (const b of toBlocks(payload)) {
|
|
93
|
+
out.push("", `${b.dot} <b>${escapeHtml(b.name)}</b>${b.plan ? ` (${escapeHtml(b.plan)})` : ""}`);
|
|
94
|
+
if (b.note) {
|
|
95
|
+
out.push(escapeHtml(b.note));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (b.lines.length === 0) {
|
|
99
|
+
out.push("no data");
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const l of b.lines) {
|
|
103
|
+
out.push(l.bar ? `<code>${l.bar}</code> ${escapeHtml(l.text)}` : escapeHtml(l.text));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out.join("\n");
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=format-rich.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-rich.js","sourceRoot":"","sources":["../../src/usage/format-rich.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface UsageMetric {
|
|
2
|
+
label: string;
|
|
3
|
+
type: "percent" | "dollars" | "count" | "text";
|
|
4
|
+
used?: number;
|
|
5
|
+
limit?: number;
|
|
6
|
+
value?: number | string;
|
|
7
|
+
unit?: string;
|
|
8
|
+
note?: string;
|
|
9
|
+
resetsAt?: string | null;
|
|
10
|
+
windowMs?: number | null;
|
|
11
|
+
}
|
|
12
|
+
export interface ProviderUsage {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
status: "ok" | "error" | "no-credentials";
|
|
16
|
+
plan?: string | null;
|
|
17
|
+
error?: string;
|
|
18
|
+
hint?: string;
|
|
19
|
+
metrics: UsageMetric[];
|
|
20
|
+
}
|
|
21
|
+
interface KiroToken {
|
|
22
|
+
access_token?: string;
|
|
23
|
+
expires_at?: string;
|
|
24
|
+
region?: string;
|
|
25
|
+
profile_arn?: string;
|
|
26
|
+
/** IAM Identity Center portal URL — present only for Q Developer Pro logins. */
|
|
27
|
+
start_url?: string;
|
|
28
|
+
/** Social login provider (google/github/…) — present only for free-tier logins. */
|
|
29
|
+
provider?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Which Kiro login is in use. Verified against a real kiro-cli auth store:
|
|
33
|
+
* - IAM Identity Center (Amazon Q Developer Pro) tokens carry `start_url`
|
|
34
|
+
* (e.g. `https://d-xxxx.awsapps.com/start/`) and no social `provider`.
|
|
35
|
+
* - Free-tier social logins (Builder ID / Google / GitHub) carry `provider`
|
|
36
|
+
* and a `profile_arn`.
|
|
37
|
+
* Q Developer Pro is a seat-licensed subscription, so the credit/usage endpoint
|
|
38
|
+
* that Builder ID reports against does not describe it.
|
|
39
|
+
*/
|
|
40
|
+
export type KiroAuthKind = "q-developer-pro" | "builder-id";
|
|
41
|
+
export declare function kiroAuthKind(token: KiroToken): KiroAuthKind;
|
|
42
|
+
/** Exported for tests: the Q-Pro and expired paths return without any network call. */
|
|
43
|
+
export declare function fetchKiroUsage(): Promise<Omit<ProviderUsage, "id" | "name">>;
|
|
44
|
+
/** Fetch every provider in parallel. Providers without local credentials are
|
|
45
|
+
* skipped (per the panel's contract: absent CLIs simply don't show). */
|
|
46
|
+
export declare function fetchAllUsage(): Promise<{
|
|
47
|
+
fetchedAt: string;
|
|
48
|
+
providers: ProviderUsage[];
|
|
49
|
+
}>;
|
|
50
|
+
export {};
|