@songsid/agend 2.1.1-beta.2 → 2.1.1-beta.21
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 +1 -0
- package/README.zh-TW.md +1 -0
- package/dist/access-path.js +15 -6
- package/dist/access-path.js.map +1 -1
- package/dist/agent-cli-instructions.md +9 -0
- package/dist/backend/antigravity.d.ts +4 -1
- package/dist/backend/antigravity.js +24 -13
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +37 -0
- package/dist/backend/claude-code.js +47 -8
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +17 -0
- package/dist/backend/codex.js +279 -62
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.js +4 -7
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/grok.d.ts +24 -0
- package/dist/backend/grok.js +57 -8
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +27 -0
- package/dist/backend/kiro.js +96 -15
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.js +4 -7
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +44 -0
- package/dist/backend/types.js +3 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +15 -0
- package/dist/channel/adapters/discord.js +94 -6
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +21 -0
- package/dist/channel/adapters/telegram.js +132 -15
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +17 -1
- package/dist/channel/ipc-bridge.js +53 -17
- 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 +30 -14
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +28 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.d.ts +1 -0
- package/dist/channel/message-queue.js +49 -17
- package/dist/channel/message-queue.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 +148 -60
- 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 +27 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +6 -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 +230 -13
- package/dist/daemon.js +975 -335
- 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-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +209 -4
- package/dist/fleet-manager.js +813 -84
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +22 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +14 -23
- package/dist/general-knowledge/skills/session-management/SKILL.md +15 -34
- 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.d.ts +21 -1
- package/dist/instance-lifecycle.js +141 -7
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +5 -0
- package/dist/instructions.js +10 -5
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +3 -0
- package/dist/locale.js.map +1 -1
- package/dist/logger.js +14 -0
- package/dist/logger.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 +16 -0
- package/dist/outbound-handlers.js +121 -27
- 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 +45 -0
- package/dist/sd-notify.js +74 -3
- 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 +107 -13
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +47 -2
- package/dist/tmux-manager.js +103 -8
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +43 -1
- package/dist/topic-commands.js +196 -66
- package/dist/topic-commands.js.map +1 -1
- package/dist/tui-glyphs.d.ts +25 -0
- package/dist/tui-glyphs.js +26 -0
- package/dist/tui-glyphs.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/ui/view.html +291 -33
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +44 -0
- package/dist/update-check.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.d.ts +10 -0
- package/dist/view-api.js +67 -14
- 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/templates/systemd.service.ejs +9 -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/fleet-system-prompt.d.ts +0 -11
- package/dist/fleet-system-prompt.js +0 -61
- package/dist/fleet-system-prompt.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -47,6 +47,17 @@ export interface HangDetectorConfig {
|
|
|
47
47
|
enabled: boolean;
|
|
48
48
|
timeout_minutes: number;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Stable logical terminal geometry for an instance's tmux window.
|
|
52
|
+
*
|
|
53
|
+
* `enabled: false` is the compatibility escape hatch: the window is pinned to
|
|
54
|
+
* tmux's historical 80x24 geometry rather than inheriting the larger defaults.
|
|
55
|
+
*/
|
|
56
|
+
export interface TerminalConfig {
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
columns?: number;
|
|
59
|
+
rows?: number;
|
|
60
|
+
}
|
|
50
61
|
export interface DailySummaryConfig {
|
|
51
62
|
enabled: boolean;
|
|
52
63
|
hour: number;
|
|
@@ -130,6 +141,8 @@ export interface InstanceConfig {
|
|
|
130
141
|
agent_mode?: "mcp" | "cli";
|
|
131
142
|
/** Hang detector override for this instance. */
|
|
132
143
|
hang_detector?: HangDetectorConfig;
|
|
144
|
+
/** Logical terminal size. Defaults to 120x36; set enabled=false for 80x24 compatibility. */
|
|
145
|
+
terminal?: TerminalConfig;
|
|
133
146
|
/** Command to paste raw before each user message (e.g. "/chat load base.json") */
|
|
134
147
|
pre_task_command?: string;
|
|
135
148
|
}
|
|
@@ -219,6 +232,11 @@ export interface FleetTemplate {
|
|
|
219
232
|
team?: boolean;
|
|
220
233
|
instances: Record<string, TemplateInstanceDef>;
|
|
221
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
|
+
}
|
|
222
240
|
export interface FleetConfig {
|
|
223
241
|
channel?: ChannelConfig;
|
|
224
242
|
channels?: ChannelConfig[];
|
|
@@ -229,6 +247,7 @@ export interface FleetConfig {
|
|
|
229
247
|
templates?: Record<string, FleetTemplate>;
|
|
230
248
|
profiles?: Record<string, ProfileConfig>;
|
|
231
249
|
health_port?: number;
|
|
250
|
+
web?: WebConfig;
|
|
232
251
|
}
|
|
233
252
|
/**
|
|
234
253
|
* User-authored fleet.yaml before defaults are merged into instances.
|
package/dist/ui/view.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
9
|
:root { --bg:#0d1117; --panel:#161b22; --border:#21262d; --fg:#c9d1d9; --dim:#8b949e; --accent:#58a6ff; }
|
|
10
|
-
html, body { height: 100%; }
|
|
11
|
-
body { background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; display: flex; height: 100vh; overflow: hidden; }
|
|
10
|
+
html, body { width: 100%; height: 100%; }
|
|
11
|
+
body { background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; display: flex; width: 100vw; min-width: 0; height: 100vh; overflow: hidden; }
|
|
12
12
|
|
|
13
13
|
/* Sidebar */
|
|
14
14
|
#sidebar { width: 240px; flex: 0 0 240px; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow-y: auto; }
|
|
@@ -31,21 +31,35 @@
|
|
|
31
31
|
|
|
32
32
|
/* Main */
|
|
33
33
|
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#cardBody
|
|
46
|
-
#cardBody .
|
|
47
|
-
#cardBody .
|
|
48
|
-
#
|
|
34
|
+
/* Padding kept at 5/6px: enough that glyphs don't touch the edge, small
|
|
35
|
+
enough that the terminal keeps essentially the whole main area. */
|
|
36
|
+
#term { flex: 1; background: #000; overflow: auto; scrollbar-gutter: stable; padding: 5px 6px; }
|
|
37
|
+
#term pre { min-width: 100%; min-height: 100%; font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace; line-height: 1.2; white-space: pre; color: #d0d0d0; }
|
|
38
|
+
|
|
39
|
+
/* Card — a single compact bar by default so the terminal keeps the height.
|
|
40
|
+
`.expanded` restores the tall layout with avatar + description. */
|
|
41
|
+
#card { background: var(--panel); border-top: 1px solid var(--border); padding: 5px 10px; display: flex; gap: 10px; align-items: center; }
|
|
42
|
+
#avatarBox { width: 24px; height: 24px; flex: 0 0 24px; }
|
|
43
|
+
.avatar-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
|
|
44
|
+
.avatar-ph { width: 100%; height: 100%; border-radius: 50%; background: #30363d; color: var(--dim); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; text-transform: uppercase; user-select: none; }
|
|
45
|
+
#cardBody { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 10px; overflow: hidden; }
|
|
46
|
+
#cardBody .row1 { display: flex; align-items: baseline; gap: 10px; flex: 0 0 auto; }
|
|
47
|
+
#cardBody .dname { font-size: 13px; font-weight: 600; white-space: nowrap; }
|
|
48
|
+
#cardBody .role { color: var(--accent); font-size: 12px; white-space: nowrap; }
|
|
49
|
+
#cardBody .meta { color: var(--dim); font-size: 11px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
50
|
+
#cardBody .desc { display: none; }
|
|
51
|
+
#cardToggle { background: none; border: none; color: var(--dim); cursor: pointer; font-size: 12px; padding: 2px 4px; flex: 0 0 auto; }
|
|
52
|
+
#cardToggle:hover { color: var(--fg); }
|
|
53
|
+
|
|
54
|
+
#card.expanded { align-items: flex-start; padding: 12px 14px; }
|
|
55
|
+
#card.expanded #avatarBox { width: 48px; height: 48px; flex: 0 0 48px; }
|
|
56
|
+
#card.expanded .avatar-ph { font-size: 20px; }
|
|
57
|
+
#card.expanded #cardBody { display: block; }
|
|
58
|
+
#card.expanded #cardBody .dname { font-size: 16px; }
|
|
59
|
+
#card.expanded #cardBody .role { font-size: 13px; }
|
|
60
|
+
#card.expanded #cardBody .meta { font-size: 12px; margin-top: 3px; white-space: normal; overflow: visible; }
|
|
61
|
+
#card.expanded #cardBody .desc { display: block; font-size: 13px; margin-top: 6px; color: #adbac7; white-space: pre-wrap; }
|
|
62
|
+
#editBtn { background: #21262d; color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; cursor: pointer; font-size: 12px; flex: 0 0 auto; }
|
|
49
63
|
#editBtn:hover { border-color: var(--accent); }
|
|
50
64
|
|
|
51
65
|
/* Edit form */
|
|
@@ -68,9 +82,43 @@
|
|
|
68
82
|
.help-panel ul { margin: 8px 0 0 18px; }
|
|
69
83
|
.help-panel li { font-size: 13px; margin: 6px 0; color: var(--fg); }
|
|
70
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
|
+
|
|
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
|
+
|
|
110
|
+
#densityBtn { font-size: 14px; font-weight: 600; letter-spacing: -.5px; }
|
|
111
|
+
@media (max-width: 700px) {
|
|
112
|
+
#sidebar { width: 180px; flex-basis: 180px; }
|
|
113
|
+
/* The compact bar must stay one line; only the expanded form wraps. */
|
|
114
|
+
#card.expanded { flex-wrap: wrap; padding: 10px 12px; }
|
|
115
|
+
#edit { flex-basis: 100%; }
|
|
116
|
+
}
|
|
71
117
|
</style>
|
|
72
118
|
</head>
|
|
73
119
|
<body>
|
|
120
|
+
<button class="help-btn" id="usageBtn" title="AI usage" style="right:140px; display:none">📊</button>
|
|
121
|
+
<button class="help-btn" id="densityBtn" title="Font density" style="right:98px">⤢</button>
|
|
74
122
|
<button class="help-btn" id="langBtn" title="Language" style="right:56px">🌐</button>
|
|
75
123
|
<button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
|
|
76
124
|
<div class="help-backdrop" id="helpBackdrop"></div>
|
|
@@ -78,6 +126,10 @@
|
|
|
78
126
|
<button class="close" id="helpClose" title="Close">×</button>
|
|
79
127
|
<div id="helpContent"></div>
|
|
80
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>
|
|
81
133
|
<div id="sidebar"><h1 data-i18n="viewTitle">AgEnD · View</h1><div id="list"></div></div>
|
|
82
134
|
<div id="main">
|
|
83
135
|
<div id="term"><pre id="pre"></pre></div>
|
|
@@ -88,6 +140,7 @@
|
|
|
88
140
|
<div class="meta" id="meta"></div>
|
|
89
141
|
<div class="desc" id="desc"></div>
|
|
90
142
|
</div>
|
|
143
|
+
<button id="cardToggle" title="Expand profile" aria-expanded="false">▲</button>
|
|
91
144
|
<button id="editBtn" data-i18n="editProfile">✏️ Edit</button>
|
|
92
145
|
<div id="edit">
|
|
93
146
|
<input id="fDisplay" data-i18n-ph="displayName" placeholder="Display name">
|
|
@@ -121,13 +174,23 @@
|
|
|
121
174
|
saveHint: "Viewing is open (read-only). Saving requires the web.token (~/.agend/web.token).", helpTitle: "📖 Help",
|
|
122
175
|
help1: "Pick an instance on the left → its live terminal shows on the right",
|
|
123
176
|
help2: "Click a group header ▼/▶ to collapse/expand", help3: "✏️ Edit changes display name, role, description",
|
|
124
|
-
help4: "Drag to reorder (needs web.token permission)"
|
|
177
|
+
help4: "Drag to reorder (needs web.token permission)",
|
|
178
|
+
help5: "Click ⤢ to cycle font density: Fit → Comfortable → Compact",
|
|
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." },
|
|
125
183
|
"zh-TW": { viewTitle: "AgEnD · 檢視", editProfile: "✏️ 編輯", save: "儲存", cancel: "取消",
|
|
126
184
|
displayName: "顯示名稱", role: "角色", description: "描述", tokenPh: "web.token(儲存時需要)",
|
|
127
185
|
saveHint: "檢視為公開(唯讀)。儲存需要 web.token(~/.agend/web.token)。", helpTitle: "📖 使用說明",
|
|
128
186
|
help1: "左側選 instance → 右側顯示即時 terminal 畫面",
|
|
129
187
|
help2: "點 group header ▼/▶ 收合/展開", help3: "✏️ Edit 可修改顯示名稱、角色、描述",
|
|
130
|
-
help4: "拖曳可調整順序(需 web.token 權限)"
|
|
188
|
+
help4: "拖曳可調整順序(需 web.token 權限)",
|
|
189
|
+
help5: "點 ⤢ 循環切換字級密度:剛好填滿 → 舒適 → 精簡",
|
|
190
|
+
densityFit: "剛好填滿", densityComfortable: "舒適", densityCompact: "精簡", densityLabel: "字級密度",
|
|
191
|
+
usageTitle: "📊 AI 訂閱用量", usageEmpty: "本機沒有偵測到已登入的 AI CLI。",
|
|
192
|
+
usageUpdated: "更新於", usageRefresh: "重新整理", usageHigh: "偏高", usageNear: "接近上限",
|
|
193
|
+
usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。" },
|
|
131
194
|
};
|
|
132
195
|
let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
|
|
133
196
|
if (!I18N[lang]) lang = "en";
|
|
@@ -137,14 +200,28 @@
|
|
|
137
200
|
document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", T(e.getAttribute("data-i18n-ph"))); });
|
|
138
201
|
const hc = q("helpContent");
|
|
139
202
|
if (hc) { hc.innerHTML = ""; const h3 = document.createElement("h3"); h3.textContent = T("helpTitle"); hc.append(h3);
|
|
140
|
-
const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
|
|
203
|
+
const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4","help5"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
|
|
204
|
+
renderDensityBtn();
|
|
141
205
|
}
|
|
142
206
|
|
|
143
207
|
let roster = [];
|
|
144
208
|
let rosterByName = new Map();
|
|
145
209
|
let current = null;
|
|
146
210
|
let poll = null;
|
|
147
|
-
|
|
211
|
+
// Pane grid in cells, reported by /api/pane's X-Pane-Cols / X-Pane-Rows.
|
|
212
|
+
// This — not the captured text — is the font-size input.
|
|
213
|
+
let paneCols = 0, paneRows = 0;
|
|
214
|
+
let lastFitKey = ""; // skip redundant recalcs
|
|
215
|
+
let appliedSize = 0; // last font-size actually written to #pre
|
|
216
|
+
let resizeTimer = null;
|
|
217
|
+
// Font density — the manual escape hatch. Once the tmux grid is large
|
|
218
|
+
// (120x32), the auto-fit size is necessarily small, so the user needs a way
|
|
219
|
+
// to trade "everything visible" against "readable glyphs".
|
|
220
|
+
const DENSITY = { fit: 1, comfortable: 1.25, compact: 0.8 };
|
|
221
|
+
const DENSITY_GLYPH = { fit: "⤢", comfortable: "A+", compact: "A−" };
|
|
222
|
+
const DENSITY_ORDER = ["fit", "comfortable", "compact"];
|
|
223
|
+
let density = localStorage.getItem("agend_view_density");
|
|
224
|
+
if (!DENSITY[density]) density = "fit";
|
|
148
225
|
const collapsed = new Set(); // group names the user has collapsed
|
|
149
226
|
// Drag-sort state
|
|
150
227
|
let sortGroups = new Map(); // groupName -> sort_index (persisted override)
|
|
@@ -456,32 +533,114 @@
|
|
|
456
533
|
}
|
|
457
534
|
const text = await r.text();
|
|
458
535
|
if (target !== current) return;
|
|
536
|
+
// Pane grid comes from tmux, so a sparse frame can't distort the fit.
|
|
537
|
+
const c = Number(r.headers.get("X-Pane-Cols")), rw = Number(r.headers.get("X-Pane-Rows"));
|
|
538
|
+
if (c >= 1 && rw >= 1 && (c !== paneCols || rw !== paneRows)) { paneCols = c; paneRows = rw; lastFitKey = ""; }
|
|
459
539
|
q("pre").innerHTML = ansiToHtml(text);
|
|
460
|
-
|
|
540
|
+
fitFont();
|
|
461
541
|
} catch { /* transient network error — keep last frame */ }
|
|
462
542
|
}
|
|
463
543
|
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
544
|
+
// ── Font sizing ───────────────────────────────────────
|
|
545
|
+
// Measured advance width of one monospace cell as a fraction of font-size.
|
|
546
|
+
// The old hardcoded .6em was only an estimate and drifts per resolved font.
|
|
547
|
+
// The ratio is font-size independent, so measuring once is enough.
|
|
548
|
+
let cellRatio = 0;
|
|
549
|
+
function glyphRatio() {
|
|
550
|
+
if (cellRatio) return cellRatio;
|
|
551
|
+
const probe = document.createElement("span");
|
|
552
|
+
probe.style.cssText = "position:absolute;visibility:hidden;white-space:pre;left:-9999px;top:0;font-size:100px";
|
|
553
|
+
probe.style.fontFamily = getComputedStyle(q("pre")).fontFamily;
|
|
554
|
+
probe.textContent = "0".repeat(100);
|
|
555
|
+
document.body.appendChild(probe);
|
|
556
|
+
const w = probe.getBoundingClientRect().width;
|
|
557
|
+
probe.remove();
|
|
558
|
+
if (w > 0) cellRatio = w / 100 / 100; // ÷100 chars, ÷100px font-size
|
|
559
|
+
return cellRatio || 0.6; // headless / odd env → old estimate
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// The removed 48px ceiling was what made an 80x24 pane render at ~33px on a
|
|
563
|
+
// 1080p screen — the "font is huge" complaint. A single desktop range is
|
|
564
|
+
// enough; /view is not targeting phones.
|
|
565
|
+
const MIN_PX = 12, MAX_PX = 22;
|
|
566
|
+
|
|
567
|
+
// Order: fit → clamp → user density → absolute readability bounds. When the
|
|
568
|
+
// clamped size can't show all `cols`, #term scrolls rather than shrinking
|
|
569
|
+
// below the floor.
|
|
570
|
+
function fitFont() {
|
|
571
|
+
if (paneCols < 1 || paneRows < 1) return; // no trustworthy size yet
|
|
572
|
+
const term = q("term"), pre = q("pre");
|
|
573
|
+
const cs = getComputedStyle(term);
|
|
574
|
+
const padX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
|
|
575
|
+
const padY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
|
576
|
+
const innerW = Math.max(1, term.clientWidth - (padX || 0));
|
|
577
|
+
const innerH = Math.max(1, term.clientHeight - (padY || 0));
|
|
578
|
+
const key = `${paneCols}x${paneRows}@${Math.round(innerW)}x${Math.round(innerH)}/${density}`;
|
|
579
|
+
if (key === lastFitKey) return;
|
|
580
|
+
const pcs = getComputedStyle(pre);
|
|
581
|
+
const lhRatio = parseFloat(pcs.lineHeight) / parseFloat(pcs.fontSize);
|
|
582
|
+
const lineH = Number.isFinite(lhRatio) && lhRatio > 0 ? lhRatio : 1.2;
|
|
583
|
+
const widthFit = innerW / (paneCols * glyphRatio());
|
|
584
|
+
const heightFit = innerH / (paneRows * lineH);
|
|
585
|
+
// 0.5% slack keeps an exact fit from rounding into a scrollbar (paired with
|
|
586
|
+
// scrollbar-gutter:stable, that's what stops resize→scrollbar→resize loops).
|
|
587
|
+
let size = Math.min(widthFit, heightFit) * 0.995;
|
|
588
|
+
size = Math.max(MIN_PX, Math.min(size, MAX_PX));
|
|
589
|
+
size *= DENSITY[density] || 1;
|
|
590
|
+
size = Math.max(8, Math.min(size, 32));
|
|
591
|
+
lastFitKey = key;
|
|
592
|
+
// Sub-pixel churn (a scrollbar appearing shrinks the box, which would shrink
|
|
593
|
+
// the font, which would hide the scrollbar again) resolves to a no-op here.
|
|
594
|
+
if (Math.abs(size - appliedSize) < 0.75) return;
|
|
595
|
+
appliedSize = size;
|
|
596
|
+
pre.style.fontSize = size.toFixed(2) + "px";
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function scheduleFit() {
|
|
600
|
+
clearTimeout(resizeTimer);
|
|
601
|
+
resizeTimer = setTimeout(() => { lastFitKey = ""; fitFont(); }, 80);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function renderDensityBtn() {
|
|
605
|
+
const b = q("densityBtn");
|
|
606
|
+
if (!b) return;
|
|
607
|
+
b.textContent = DENSITY_GLYPH[density];
|
|
608
|
+
b.title = `${T("densityLabel")}: ${T("density" + density.charAt(0).toUpperCase() + density.slice(1))}`;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function cycleDensity() {
|
|
612
|
+
density = DENSITY_ORDER[(DENSITY_ORDER.indexOf(density) + 1) % DENSITY_ORDER.length];
|
|
613
|
+
localStorage.setItem("agend_view_density", density);
|
|
614
|
+
renderDensityBtn();
|
|
615
|
+
lastFitKey = "";
|
|
616
|
+
fitFont();
|
|
474
617
|
}
|
|
475
618
|
|
|
476
619
|
function select(name) {
|
|
477
620
|
if (name === current) return;
|
|
478
|
-
current = name;
|
|
621
|
+
current = name;
|
|
622
|
+
lastFitKey = ""; // force a refit once the new pane's size lands
|
|
479
623
|
exitEdit();
|
|
480
624
|
q("pre").textContent = ""; // blank the old instance's output immediately
|
|
481
625
|
renderList(); renderCard(); refreshPane();
|
|
482
626
|
}
|
|
483
627
|
|
|
484
628
|
// ── Edit ──────────────────────────────────────────────
|
|
629
|
+
// The card is a one-line bar by default; expanding it (or editing) is what
|
|
630
|
+
// borrows height back from the terminal.
|
|
631
|
+
let cardExpanded = localStorage.getItem("agend_view_card_expanded") === "1";
|
|
632
|
+
function applyCardExpanded(on) {
|
|
633
|
+
q("card").classList.toggle("expanded", on);
|
|
634
|
+
const t = q("cardToggle");
|
|
635
|
+
t.textContent = on ? "▼" : "▲";
|
|
636
|
+
t.setAttribute("aria-expanded", on ? "true" : "false");
|
|
637
|
+
scheduleFit(); // the terminal just gained or lost height
|
|
638
|
+
}
|
|
639
|
+
function setCardExpanded(on, persist) {
|
|
640
|
+
if (persist) { cardExpanded = on; localStorage.setItem("agend_view_card_expanded", on ? "1" : "0"); }
|
|
641
|
+
applyCardExpanded(on);
|
|
642
|
+
}
|
|
643
|
+
|
|
485
644
|
function enterEdit() {
|
|
486
645
|
const it = roster.find(r => r.instance_name === current);
|
|
487
646
|
if (!it) return;
|
|
@@ -490,14 +649,18 @@
|
|
|
490
649
|
q("fDesc").value = it.description || "";
|
|
491
650
|
q("fToken").value = localStorage.getItem("agend_web_token") || "";
|
|
492
651
|
q("msg").textContent = "";
|
|
652
|
+
applyCardExpanded(true); // form needs the room; don't persist it
|
|
493
653
|
q("cardBody").style.display = "none";
|
|
654
|
+
q("cardToggle").style.display = "none";
|
|
494
655
|
q("editBtn").style.display = "none";
|
|
495
656
|
q("edit").style.display = "flex";
|
|
496
657
|
}
|
|
497
658
|
function exitEdit() {
|
|
498
659
|
q("edit").style.display = "none";
|
|
499
660
|
q("cardBody").style.display = "";
|
|
661
|
+
q("cardToggle").style.display = "";
|
|
500
662
|
q("editBtn").style.display = "";
|
|
663
|
+
applyCardExpanded(cardExpanded); // back to the user's own preference
|
|
501
664
|
}
|
|
502
665
|
|
|
503
666
|
async function save() {
|
|
@@ -529,13 +692,108 @@
|
|
|
529
692
|
q("editBtn").onclick = enterEdit;
|
|
530
693
|
q("cancelBtn").onclick = exitEdit;
|
|
531
694
|
q("saveBtn").onclick = save;
|
|
695
|
+
q("cardToggle").onclick = () => setCardExpanded(!q("card").classList.contains("expanded"), true);
|
|
532
696
|
|
|
533
697
|
const setHelp = (open) => { q("helpPanel").classList.toggle("open", open); q("helpBackdrop").classList.toggle("open", open); };
|
|
534
698
|
q("helpBtn").onclick = () => setHelp(!q("helpPanel").classList.contains("open"));
|
|
535
699
|
q("helpClose").onclick = () => setHelp(false);
|
|
536
700
|
q("helpBackdrop").onclick = () => setHelp(false);
|
|
537
701
|
q("langBtn").onclick = () => { lang = lang === "en" ? "zh-TW" : "en"; localStorage.setItem("agend_lang", lang); applyI18n(); };
|
|
702
|
+
q("densityBtn").onclick = cycleDensity;
|
|
703
|
+
window.addEventListener("keydown", (e) => { if (e.key === "Escape") setHelp(false); });
|
|
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
|
+
|
|
791
|
+
window.addEventListener("resize", scheduleFit);
|
|
792
|
+
// Catches container changes the window resize event doesn't see — chiefly the
|
|
793
|
+
// profile card expanding or collapsing.
|
|
794
|
+
if (window.ResizeObserver) new ResizeObserver(scheduleFit).observe(q("term"));
|
|
538
795
|
applyI18n();
|
|
796
|
+
applyCardExpanded(cardExpanded);
|
|
539
797
|
|
|
540
798
|
// ── Boot ──────────────────────────────────────────────
|
|
541
799
|
loadRoster().then(() => { refreshPane(); });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
export interface UpdateVersionOptions {
|
|
3
|
+
version?: string;
|
|
4
|
+
beta?: boolean;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Resolve the npm selector used by `agend update`. */
|
|
8
|
+
export declare function getUpdateSelector(opts: UpdateVersionOptions): string;
|
|
9
|
+
/**
|
|
10
|
+
* Query the registry without making update availability a hard dependency.
|
|
11
|
+
* A failed lookup deliberately returns null so the existing install flow can
|
|
12
|
+
* continue during registry/network outages.
|
|
13
|
+
*/
|
|
14
|
+
export declare function lookupTargetVersion(selector: string, run?: typeof execFileSync): string | null;
|
|
15
|
+
export declare function shouldSkipUpdate(currentVersion: string, targetVersion: string | null, force?: boolean): boolean;
|
|
16
|
+
interface UpdateOutput {
|
|
17
|
+
log(message: string): void;
|
|
18
|
+
error(message: string): void;
|
|
19
|
+
}
|
|
20
|
+
/** Report restart status and let the CLI translate failure into exit code 1. */
|
|
21
|
+
export declare function reportUpdateRestart(status: number | null, output?: UpdateOutput): boolean;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
/** Resolve the npm selector used by `agend update`. */
|
|
3
|
+
export function getUpdateSelector(opts) {
|
|
4
|
+
return opts.version ?? (opts.beta ? "beta" : "latest");
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Query the registry without making update availability a hard dependency.
|
|
8
|
+
* A failed lookup deliberately returns null so the existing install flow can
|
|
9
|
+
* continue during registry/network outages.
|
|
10
|
+
*/
|
|
11
|
+
export function lookupTargetVersion(selector, run = execFileSync) {
|
|
12
|
+
try {
|
|
13
|
+
const output = run("npm", ["view", `@songsid/agend@${selector}`, "version"], {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16
|
+
timeout: 15_000,
|
|
17
|
+
});
|
|
18
|
+
const version = String(output).trim();
|
|
19
|
+
return version || null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function normalizeVersion(version) {
|
|
26
|
+
return version.trim().replace(/^v/i, "");
|
|
27
|
+
}
|
|
28
|
+
export function shouldSkipUpdate(currentVersion, targetVersion, force = false) {
|
|
29
|
+
return !force
|
|
30
|
+
&& targetVersion !== null
|
|
31
|
+
&& normalizeVersion(currentVersion) === normalizeVersion(targetVersion);
|
|
32
|
+
}
|
|
33
|
+
/** Report restart status and let the CLI translate failure into exit code 1. */
|
|
34
|
+
export function reportUpdateRestart(status, output = console) {
|
|
35
|
+
if (status === 0) {
|
|
36
|
+
output.log(" ✓ Service restarted");
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
output.error("\n ✗ Auto-restart FAILED. Fleet may be stopped.");
|
|
40
|
+
output.error(" Run: agend start");
|
|
41
|
+
output.error(" Service status: agend status\n");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,IAA0B;IAC1D,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,MAA2B,YAAY;IAEvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAChB,KAAK,EACL,CAAC,MAAM,EAAE,kBAAkB,QAAQ,EAAE,EAAE,SAAS,CAAC,EACjD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO,EAAE,MAAM;SAChB,CACF,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,cAAsB,EACtB,aAA4B,EAC5B,KAAK,GAAG,KAAK;IAEb,OAAO,CAAC,KAAK;WACR,aAAa,KAAK,IAAI;WACtB,gBAAgB,CAAC,cAAc,CAAC,KAAK,gBAAgB,CAAC,aAAa,CAAC,CAAC;AAC5E,CAAC;AAOD,gFAAgF;AAChF,MAAM,UAAU,mBAAmB,CACjC,MAAqB,EACrB,SAAuB,OAAO;IAE9B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACjE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -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 {};
|