@songsid/agend 2.0.11-beta.3 → 2.0.11-beta.4

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.
@@ -0,0 +1,272 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>AgEnD View</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
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; }
12
+
13
+ /* Sidebar */
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; }
15
+ #sidebar h1 { font-size: 14px; color: var(--accent); padding: 14px 16px; border-bottom: 1px solid var(--border); font-weight: 600; letter-spacing: .5px; }
16
+ .inst { display: flex; align-items: center; gap: 8px; padding: 10px 16px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,.03); font-size: 13px; }
17
+ .inst:hover { background: rgba(255,255,255,.04); }
18
+ .inst.active { background: rgba(88,166,255,.14); border-left: 3px solid var(--accent); padding-left: 13px; }
19
+ .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
20
+ .dot.running { background: #3fb950; } .dot.stopped { background: #6e7681; } .dot.crashed { background: #f85149; }
21
+ .inst .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
22
+ .inst .pct { color: var(--dim); font-size: 11px; }
23
+
24
+ /* Main */
25
+ #main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
26
+ #term { flex: 1; background: #000; overflow: auto; padding: 10px 12px; }
27
+ #term pre { font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace; line-height: 1.2; white-space: pre; color: #d0d0d0; }
28
+
29
+ /* Card */
30
+ #card { background: var(--panel); border-top: 1px solid var(--border); padding: 14px 18px; display: flex; gap: 16px; align-items: flex-start; }
31
+ #avatar { width: 56px; height: 56px; border-radius: 8px; object-fit: cover; background: #21262d; flex: 0 0 56px; }
32
+ #cardBody { flex: 1; min-width: 0; }
33
+ #cardBody .row1 { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
34
+ #cardBody .dname { font-size: 16px; font-weight: 600; }
35
+ #cardBody .role { color: var(--accent); font-size: 13px; }
36
+ #cardBody .meta { color: var(--dim); font-size: 12px; margin-top: 3px; }
37
+ #cardBody .desc { font-size: 13px; margin-top: 6px; color: #adbac7; white-space: pre-wrap; }
38
+ #editBtn { background: #21262d; color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 5px 12px; cursor: pointer; font-size: 13px; }
39
+ #editBtn:hover { border-color: var(--accent); }
40
+
41
+ /* Edit form */
42
+ #edit { display: none; flex-direction: column; gap: 8px; flex: 1; }
43
+ #edit input, #edit textarea { background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; font-size: 13px; font-family: inherit; width: 100%; }
44
+ #edit textarea { resize: vertical; min-height: 48px; }
45
+ #edit .actions { display: flex; gap: 8px; align-items: center; }
46
+ #edit button { border: none; border-radius: 6px; padding: 6px 14px; cursor: pointer; font-size: 13px; }
47
+ #saveBtn { background: #238636; color: #fff; } #cancelBtn { background: #21262d; color: var(--fg); border: 1px solid var(--border); }
48
+ #edit .hint { color: var(--dim); font-size: 11px; }
49
+ #msg { font-size: 12px; }
50
+ .err { color: #f85149; } .ok { color: #3fb950; }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <div id="sidebar"><h1>AgEnD · View</h1><div id="list"></div></div>
55
+ <div id="main">
56
+ <div id="term"><pre id="pre"></pre></div>
57
+ <div id="card">
58
+ <img id="avatar" alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E">
59
+ <div id="cardBody">
60
+ <div class="row1"><span class="dname" id="dname">—</span><span class="role" id="role"></span></div>
61
+ <div class="meta" id="meta"></div>
62
+ <div class="desc" id="desc"></div>
63
+ </div>
64
+ <button id="editBtn">✏️ Edit</button>
65
+ <div id="edit">
66
+ <input id="fDisplay" placeholder="Display name">
67
+ <input id="fRole" placeholder="Role">
68
+ <textarea id="fDesc" placeholder="Description"></textarea>
69
+ <input id="fAvatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
70
+ <input id="fToken" type="password" placeholder="web.token (required to save)">
71
+ <div class="actions">
72
+ <button id="saveBtn">Save</button>
73
+ <button id="cancelBtn">Cancel</button>
74
+ <span id="msg"></span>
75
+ </div>
76
+ <div class="hint">Read access uses the view token in the URL. Saving requires the web.token (~/.agend/web.token).</div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <script>
82
+ (() => {
83
+ const token = new URLSearchParams(location.search).get("token") || "";
84
+ const q = (id) => document.getElementById(id);
85
+ let roster = [];
86
+ let current = null;
87
+ let poll = null;
88
+ let fitted = false;
89
+
90
+ const api = (path) => `${path}${path.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}`;
91
+
92
+ // ── ANSI (SGR) → HTML ─────────────────────────────────
93
+ const BASE = ["#000000","#cd3131","#0dbc79","#e5e510","#2472c8","#bc3fbc","#11a8cd","#e5e5e5"];
94
+ const BRIGHT = ["#666666","#f14c4c","#23d18b","#f5f543","#3b8eea","#d670d6","#29b8db","#ffffff"];
95
+ const esc = (s) => s.replace(/[&<>]/g, c => ({ "&":"&amp;", "<":"&lt;", ">":"&gt;" }[c]));
96
+
97
+ function ansiToHtml(text) {
98
+ let out = "", fg = null, bg = null, bold = false, open = false;
99
+ const style = () => {
100
+ const p = [];
101
+ let f = fg;
102
+ if (bold && typeof f === "number" && f < 8) f = BRIGHT[f];
103
+ else if (typeof f === "number") f = BASE[f];
104
+ if (f) p.push("color:" + f);
105
+ if (bg) p.push("background:" + (typeof bg === "number" ? BASE[bg] : bg));
106
+ if (bold) p.push("font-weight:bold");
107
+ return p.join(";");
108
+ };
109
+ const flush = () => { if (open) { out += "</span>"; open = false; } };
110
+ const openSpan = () => { const s = style(); if (s) { out += `<span style="${s}">`; open = true; } };
111
+
112
+ const re = /\x1b\[([0-9;]*)m/g;
113
+ let last = 0, m;
114
+ const emit = (chunk) => { if (!chunk) return; if (!open) openSpan(); out += esc(chunk); };
115
+ while ((m = re.exec(text)) !== null) {
116
+ emit(text.slice(last, m.index));
117
+ last = re.lastIndex;
118
+ flush();
119
+ const codes = m[1].split(";").filter(x => x !== "").map(Number);
120
+ if (codes.length === 0) codes.push(0);
121
+ for (let i = 0; i < codes.length; i++) {
122
+ const c = codes[i];
123
+ if (c === 0) { fg = bg = null; bold = false; }
124
+ else if (c === 1) bold = true;
125
+ else if (c === 22) bold = false;
126
+ else if (c === 39) fg = null;
127
+ else if (c === 49) bg = null;
128
+ else if (c >= 30 && c <= 37) fg = c - 30;
129
+ else if (c >= 90 && c <= 97) fg = BRIGHT[c - 90];
130
+ else if (c >= 40 && c <= 47) bg = c - 40;
131
+ else if (c >= 100 && c <= 107) bg = BRIGHT[c - 100];
132
+ else if (c === 38 || c === 48) {
133
+ const target = c === 38 ? "fg" : "bg";
134
+ if (codes[i + 1] === 5) { const n = codes[i + 2]; const col = xterm256(n); if (target === "fg") fg = col; else bg = col; i += 2; }
135
+ else if (codes[i + 1] === 2) { const col = `rgb(${codes[i+2]||0},${codes[i+3]||0},${codes[i+4]||0})`; if (target === "fg") fg = col; else bg = col; i += 4; }
136
+ }
137
+ }
138
+ }
139
+ emit(text.slice(last));
140
+ flush();
141
+ return out;
142
+ }
143
+ function xterm256(n) {
144
+ if (n < 8) return BASE[n];
145
+ if (n < 16) return BRIGHT[n - 8];
146
+ if (n < 232) { n -= 16; const r = Math.floor(n/36), g = Math.floor((n%36)/6), b = n%6; const v = x => x ? x*40+55 : 0; return `rgb(${v(r)},${v(g)},${v(b)})`; }
147
+ const l = (n - 232) * 10 + 8; return `rgb(${l},${l},${l})`;
148
+ }
149
+
150
+ // ── Sidebar ───────────────────────────────────────────
151
+ function renderList() {
152
+ const list = q("list");
153
+ list.innerHTML = "";
154
+ for (const it of roster) {
155
+ const el = document.createElement("div");
156
+ el.className = "inst" + (it.instance_name === current ? " active" : "");
157
+ const label = it.display_name || it.instance_name;
158
+ el.innerHTML = `<span class="dot ${it.status}"></span><span class="nm">${esc(label)}</span><span class="pct">${it.context_pct ? Math.round(it.context_pct)+"%" : ""}</span>`;
159
+ el.onclick = () => select(it.instance_name);
160
+ list.appendChild(el);
161
+ }
162
+ }
163
+
164
+ function renderCard() {
165
+ const it = roster.find(r => r.instance_name === current);
166
+ if (!it) return;
167
+ q("dname").textContent = it.display_name || it.instance_name;
168
+ q("role").textContent = it.role || "";
169
+ q("meta").textContent = `${it.instance_name} · ${it.backend}${it.model ? " · " + it.model : ""} · ctx ${Math.round(it.context_pct || 0)}% · ${it.status}`;
170
+ q("desc").textContent = it.description || "";
171
+ const av = q("avatar");
172
+ if (it.has_avatar) av.src = api(`/api/avatar/${encodeURIComponent(it.instance_name)}`) + "&t=" + Date.now();
173
+ else av.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E";
174
+ }
175
+
176
+ async function loadRoster() {
177
+ try {
178
+ const r = await fetch(api("/api/profiles"));
179
+ if (!r.ok) throw new Error("HTTP " + r.status);
180
+ roster = await r.json();
181
+ if (!current && roster.length) current = roster[0].instance_name;
182
+ renderList(); renderCard();
183
+ } catch (e) { q("pre").textContent = "Failed to load roster: " + e.message + "\n(Check the token in the URL.)"; }
184
+ }
185
+
186
+ // ── Terminal ──────────────────────────────────────────
187
+ async function refreshPane() {
188
+ if (!current) return;
189
+ try {
190
+ const r = await fetch(api(`/api/pane/${encodeURIComponent(current)}`));
191
+ const text = await r.text();
192
+ const pre = q("pre");
193
+ pre.innerHTML = ansiToHtml(text);
194
+ if (!fitted) { fitFont(text); fitted = true; }
195
+ } catch { /* transient */ }
196
+ }
197
+
198
+ // Fit font size so the widest line fits the terminal width. Recomputed only
199
+ // on instance switch (fitted flag), not every poll.
200
+ function fitFont(text) {
201
+ const cols = text.split("\n").reduce((m, l) => Math.max(m, l.replace(/\x1b\[[0-9;]*m/g, "").length), 0);
202
+ if (cols < 1) return;
203
+ const termW = q("term").clientWidth - 24;
204
+ // monospace char width ≈ 0.6 × font-size
205
+ let size = Math.floor(termW / (cols * 0.6));
206
+ size = Math.max(8, Math.min(size, 16));
207
+ q("pre").style.fontSize = size + "px";
208
+ }
209
+
210
+ function select(name) {
211
+ if (name === current) return;
212
+ current = name; fitted = false;
213
+ exitEdit();
214
+ renderList(); renderCard(); refreshPane();
215
+ }
216
+
217
+ // ── Edit ──────────────────────────────────────────────
218
+ function enterEdit() {
219
+ const it = roster.find(r => r.instance_name === current);
220
+ if (!it) return;
221
+ q("fDisplay").value = it.display_name || "";
222
+ q("fRole").value = it.role || "";
223
+ q("fDesc").value = it.description || "";
224
+ q("fToken").value = localStorage.getItem("agend_web_token") || "";
225
+ q("msg").textContent = "";
226
+ q("cardBody").style.display = "none";
227
+ q("editBtn").style.display = "none";
228
+ q("edit").style.display = "flex";
229
+ }
230
+ function exitEdit() {
231
+ q("edit").style.display = "none";
232
+ q("cardBody").style.display = "";
233
+ q("editBtn").style.display = "";
234
+ }
235
+
236
+ async function save() {
237
+ const it = roster.find(r => r.instance_name === current);
238
+ if (!it) return;
239
+ const webToken = q("fToken").value.trim();
240
+ if (!webToken) { q("msg").className = "err"; q("msg").textContent = "web.token required"; return; }
241
+ localStorage.setItem("agend_web_token", webToken);
242
+ const name = encodeURIComponent(current);
243
+ try {
244
+ const r = await fetch(`/api/profile/${name}`, {
245
+ method: "POST",
246
+ headers: { "Content-Type": "application/json", "X-Agend-Token": webToken },
247
+ body: JSON.stringify({ display_name: q("fDisplay").value, role: q("fRole").value, description: q("fDesc").value }),
248
+ });
249
+ if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.error || ("HTTP " + r.status)); }
250
+ const file = q("fAvatar").files[0];
251
+ if (file) {
252
+ const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": file.type, "X-Agend-Token": webToken }, body: file });
253
+ if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error("avatar: " + (e.error || ar.status)); }
254
+ }
255
+ q("msg").className = "ok"; q("msg").textContent = "Saved";
256
+ await loadRoster();
257
+ setTimeout(exitEdit, 500);
258
+ } catch (e) { q("msg").className = "err"; q("msg").textContent = e.message; }
259
+ }
260
+
261
+ q("editBtn").onclick = enterEdit;
262
+ q("cancelBtn").onclick = exitEdit;
263
+ q("saveBtn").onclick = save;
264
+
265
+ // ── Boot ──────────────────────────────────────────────
266
+ loadRoster().then(() => { refreshPane(); });
267
+ poll = setInterval(refreshPane, 800);
268
+ setInterval(loadRoster, 5000); // refresh status dots / ctx%
269
+ })();
270
+ </script>
271
+ </body>
272
+ </html>
@@ -0,0 +1,20 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { FleetConfig } from "./types.js";
3
+ import type { Logger } from "./logger.js";
4
+ export interface ViewApiContext {
5
+ readonly viewToken: string | null;
6
+ readonly webToken: string | null;
7
+ readonly dataDir: string;
8
+ readonly fleetConfig: FleetConfig | null;
9
+ readonly logger: Logger;
10
+ getInstanceStatus(name: string): "running" | "stopped" | "crashed";
11
+ getUiStatus(): unknown;
12
+ }
13
+ /** True if the path belongs to the view feature (so the caller can skip the
14
+ * global web-token gate and let this module do its own token checks). */
15
+ export declare function isViewPath(path: string): boolean;
16
+ /**
17
+ * Handle a `/view` feature request. Returns true if the request was a view
18
+ * route (and has been answered), false if it isn't ours.
19
+ */
20
+ export declare function handleViewRequest(req: IncomingMessage, res: ServerResponse, url: URL, ctx: ViewApiContext): boolean;
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Read-only Web View (`/view`) — a terminal-streaming page plus editable
3
+ * instance profiles. Separate from the operator Web UI (`/ui`):
4
+ *
5
+ * GET /view → static page (view.token or web.token)
6
+ * GET /api/pane/:instance → `tmux capture-pane -ep` output (ANSI text)
7
+ * GET /api/profiles → merged roster (live status + config + profile)
8
+ * GET /api/profile/:instance → one profile row
9
+ * POST /api/profile/:instance → upsert profile (web.token only)
10
+ * GET /api/avatar/:instance → avatar image
11
+ * POST /api/avatar/:instance → upload avatar (web.token only)
12
+ *
13
+ * Auth: GET routes accept view.token OR web.token; POST routes require the
14
+ * (read-write) web.token. Instance names are whitelisted against fleet config
15
+ * and tmux is invoked via execFile (no shell) to prevent command injection.
16
+ */
17
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
18
+ import { join, dirname } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { execFile } from "node:child_process";
21
+ import { promisify } from "node:util";
22
+ import Database from "better-sqlite3";
23
+ import { getTmuxSession } from "./config.js";
24
+ import { getTmuxSocketName } from "./paths.js";
25
+ const execFileP = promisify(execFile);
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ // Lazy per-dataDir SQLite handle for instance profiles.
28
+ let _db = null;
29
+ let _dbPath = "";
30
+ function profileDb(dataDir) {
31
+ const p = join(dataDir, "profiles.db");
32
+ if (_db && _dbPath === p)
33
+ return _db;
34
+ const db = new Database(p);
35
+ db.pragma("journal_mode = WAL");
36
+ db.exec(`CREATE TABLE IF NOT EXISTS instance_profile (
37
+ instance_name TEXT PRIMARY KEY,
38
+ display_name TEXT,
39
+ avatar_path TEXT,
40
+ role TEXT,
41
+ description TEXT,
42
+ updated_at INTEGER
43
+ );`);
44
+ _db = db;
45
+ _dbPath = p;
46
+ return db;
47
+ }
48
+ const IMAGE_MIME = {
49
+ ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
50
+ ".gif": "image/gif", ".webp": "image/webp",
51
+ };
52
+ function extForMime(mime) {
53
+ if (mime.includes("png"))
54
+ return ".png";
55
+ if (mime.includes("jpeg") || mime.includes("jpg"))
56
+ return ".jpg";
57
+ if (mime.includes("gif"))
58
+ return ".gif";
59
+ if (mime.includes("webp"))
60
+ return ".webp";
61
+ return null;
62
+ }
63
+ function tokenFrom(req, url) {
64
+ const h = req.headers["x-agend-token"];
65
+ return url.searchParams.get("token") ?? (typeof h === "string" ? h : null);
66
+ }
67
+ function json(res, code, body) {
68
+ res.writeHead(code, { "Content-Type": "application/json" });
69
+ res.end(JSON.stringify(body));
70
+ }
71
+ /** Safe instance name that also exists in fleet config. */
72
+ function knownInstance(ctx, name) {
73
+ return /^[A-Za-z0-9._-]+$/.test(name) && !!ctx.fleetConfig?.instances?.[name];
74
+ }
75
+ function readBody(req, maxBytes) {
76
+ return new Promise((resolve, reject) => {
77
+ const chunks = [];
78
+ let size = 0;
79
+ req.on("data", (c) => {
80
+ size += c.length;
81
+ if (size > maxBytes) {
82
+ reject(new Error("payload too large"));
83
+ req.destroy();
84
+ return;
85
+ }
86
+ chunks.push(c);
87
+ });
88
+ req.on("end", () => resolve(Buffer.concat(chunks)));
89
+ req.on("error", reject);
90
+ });
91
+ }
92
+ /** Capture a pane's current contents (with ANSI escapes) for an instance. */
93
+ async function capturePane(ctx, name) {
94
+ const widFile = join(ctx.dataDir, "instances", name, "window-id");
95
+ if (!existsSync(widFile))
96
+ return "";
97
+ const wid = readFileSync(widFile, "utf-8").trim();
98
+ if (!wid)
99
+ return "";
100
+ const socket = getTmuxSocketName();
101
+ const args = [
102
+ ...(socket ? ["-L", socket] : []),
103
+ "capture-pane", "-p", "-e", "-t", `${getTmuxSession()}:${wid}`,
104
+ ];
105
+ const { stdout } = await execFileP("tmux", args, { maxBuffer: 8 * 1024 * 1024 });
106
+ return stdout;
107
+ }
108
+ /** True if the path belongs to the view feature (so the caller can skip the
109
+ * global web-token gate and let this module do its own token checks). */
110
+ export function isViewPath(path) {
111
+ return path === "/view"
112
+ || path.startsWith("/api/pane/")
113
+ || path === "/api/profiles"
114
+ || path.startsWith("/api/profile/")
115
+ || path.startsWith("/api/avatar/");
116
+ }
117
+ /**
118
+ * Handle a `/view` feature request. Returns true if the request was a view
119
+ * route (and has been answered), false if it isn't ours.
120
+ */
121
+ export function handleViewRequest(req, res, url, ctx) {
122
+ const path = url.pathname;
123
+ if (!isViewPath(path))
124
+ return false;
125
+ const method = req.method ?? "GET";
126
+ const token = tokenFrom(req, url);
127
+ const canRead = !!token && (token === ctx.viewToken || token === ctx.webToken);
128
+ const canWrite = !!token && token === ctx.webToken;
129
+ // ── GET /view — static page ──
130
+ if (method === "GET" && path === "/view") {
131
+ if (!canRead) {
132
+ json(res, 401, { error: "Unauthorized" });
133
+ return true;
134
+ }
135
+ try {
136
+ const html = readFileSync(join(__dirname, "ui", "view.html"), "utf-8");
137
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
138
+ res.end(html);
139
+ }
140
+ catch {
141
+ json(res, 500, { error: "view.html not found" });
142
+ }
143
+ return true;
144
+ }
145
+ // ── GET /api/pane/:instance ──
146
+ if (method === "GET" && path.startsWith("/api/pane/")) {
147
+ if (!canRead) {
148
+ json(res, 401, { error: "Unauthorized" });
149
+ return true;
150
+ }
151
+ const name = decodeURIComponent(path.slice("/api/pane/".length));
152
+ if (!knownInstance(ctx, name)) {
153
+ json(res, 404, { error: "unknown instance" });
154
+ return true;
155
+ }
156
+ capturePane(ctx, name)
157
+ .then(text => { res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end(text); })
158
+ .catch(err => { ctx.logger.debug({ err, name }, "capture-pane failed"); res.writeHead(200, { "Content-Type": "text/plain" }); res.end(""); });
159
+ return true;
160
+ }
161
+ // ── GET /api/profiles — merged roster ──
162
+ if (method === "GET" && path === "/api/profiles") {
163
+ if (!canRead) {
164
+ json(res, 401, { error: "Unauthorized" });
165
+ return true;
166
+ }
167
+ const ui = ctx.getUiStatus();
168
+ const live = new Map(ui.instances.map(i => [i.name, i]));
169
+ const db = profileDb(ctx.dataDir);
170
+ const profiles = new Map(db.prepare("SELECT * FROM instance_profile").all().map(r => [r.instance_name, r]));
171
+ const roster = Object.keys(ctx.fleetConfig?.instances ?? {}).map(name => {
172
+ const cfg = ctx.fleetConfig.instances[name];
173
+ const l = live.get(name);
174
+ const p = profiles.get(name);
175
+ return {
176
+ instance_name: name,
177
+ status: l?.status ?? ctx.getInstanceStatus(name),
178
+ context_pct: l?.context_pct ?? 0,
179
+ model: l?.model ?? "",
180
+ backend: cfg?.backend ?? "claude-code",
181
+ display_name: p?.display_name ?? null,
182
+ role: p?.role ?? null,
183
+ avatar_path: p?.avatar_path ?? null,
184
+ description: p?.description ?? cfg?.description ?? null,
185
+ has_avatar: !!p?.avatar_path,
186
+ };
187
+ });
188
+ json(res, 200, roster);
189
+ return true;
190
+ }
191
+ // ── /api/profile/:instance ──
192
+ if (path.startsWith("/api/profile/")) {
193
+ const name = decodeURIComponent(path.slice("/api/profile/".length));
194
+ if (!knownInstance(ctx, name)) {
195
+ json(res, 404, { error: "unknown instance" });
196
+ return true;
197
+ }
198
+ if (method === "GET") {
199
+ if (!canRead) {
200
+ json(res, 401, { error: "Unauthorized" });
201
+ return true;
202
+ }
203
+ const db = profileDb(ctx.dataDir);
204
+ const row = db.prepare("SELECT * FROM instance_profile WHERE instance_name = ?").get(name)
205
+ ?? { instance_name: name, display_name: null, avatar_path: null, role: null, description: null, updated_at: 0 };
206
+ json(res, 200, row);
207
+ return true;
208
+ }
209
+ if (method === "POST") {
210
+ if (!canWrite) {
211
+ json(res, 401, { error: "Unauthorized (web token required)" });
212
+ return true;
213
+ }
214
+ readBody(req, 256 * 1024).then(buf => {
215
+ let body;
216
+ try {
217
+ body = JSON.parse(buf.toString("utf-8") || "{}");
218
+ }
219
+ catch {
220
+ json(res, 400, { error: "invalid JSON" });
221
+ return;
222
+ }
223
+ const db = profileDb(ctx.dataDir);
224
+ // avatar_path is managed by the avatar upload route — don't clobber it here.
225
+ db.prepare(`INSERT INTO instance_profile (instance_name, display_name, role, description, updated_at)
226
+ VALUES (@n, @d, @r, @desc, @t)
227
+ ON CONFLICT(instance_name) DO UPDATE SET
228
+ display_name = @d, role = @r, description = @desc, updated_at = @t`)
229
+ .run({ n: name, d: body.display_name ?? null, r: body.role ?? null, desc: body.description ?? null, t: Date.now() });
230
+ json(res, 200, { ok: true });
231
+ }).catch(err => json(res, 400, { error: err.message }));
232
+ return true;
233
+ }
234
+ json(res, 405, { error: "method not allowed" });
235
+ return true;
236
+ }
237
+ // ── /api/avatar/:instance ──
238
+ if (path.startsWith("/api/avatar/")) {
239
+ const name = decodeURIComponent(path.slice("/api/avatar/".length));
240
+ if (!knownInstance(ctx, name)) {
241
+ json(res, 404, { error: "unknown instance" });
242
+ return true;
243
+ }
244
+ if (method === "GET") {
245
+ if (!canRead) {
246
+ json(res, 401, { error: "Unauthorized" });
247
+ return true;
248
+ }
249
+ const db = profileDb(ctx.dataDir);
250
+ const row = db.prepare("SELECT avatar_path FROM instance_profile WHERE instance_name = ?").get(name);
251
+ const file = row?.avatar_path;
252
+ if (!file || !existsSync(file)) {
253
+ json(res, 404, { error: "no avatar" });
254
+ return true;
255
+ }
256
+ const ext = (file.match(/\.[^.]+$/)?.[0] ?? "").toLowerCase();
257
+ try {
258
+ const data = readFileSync(file);
259
+ res.writeHead(200, { "Content-Type": IMAGE_MIME[ext] ?? "application/octet-stream", "Cache-Control": "no-cache" });
260
+ res.end(data);
261
+ }
262
+ catch {
263
+ json(res, 404, { error: "avatar unreadable" });
264
+ }
265
+ return true;
266
+ }
267
+ if (method === "POST") {
268
+ if (!canWrite) {
269
+ json(res, 401, { error: "Unauthorized (web token required)" });
270
+ return true;
271
+ }
272
+ const ext = extForMime(String(req.headers["content-type"] ?? ""));
273
+ if (!ext) {
274
+ json(res, 400, { error: "unsupported image type (png/jpeg/gif/webp)" });
275
+ return true;
276
+ }
277
+ readBody(req, 4 * 1024 * 1024).then(buf => {
278
+ if (buf.length === 0) {
279
+ json(res, 400, { error: "empty body" });
280
+ return;
281
+ }
282
+ const dir = join(ctx.dataDir, "avatars");
283
+ mkdirSync(dir, { recursive: true });
284
+ // Remove any prior avatar for this instance (different extension).
285
+ for (const f of (existsSync(dir) ? readdirSync(dir) : [])) {
286
+ if (f.startsWith(`${name}.`)) {
287
+ try {
288
+ writeFileSync(join(dir, f), "");
289
+ }
290
+ catch { /* best effort */ }
291
+ }
292
+ }
293
+ const dest = join(dir, `${name}${ext}`);
294
+ writeFileSync(dest, buf, { mode: 0o600 });
295
+ const db = profileDb(ctx.dataDir);
296
+ db.prepare(`INSERT INTO instance_profile (instance_name, avatar_path, updated_at)
297
+ VALUES (@n, @a, @t)
298
+ ON CONFLICT(instance_name) DO UPDATE SET avatar_path = @a, updated_at = @t`)
299
+ .run({ n: name, a: dest, t: Date.now() });
300
+ json(res, 200, { ok: true, avatar_path: dest });
301
+ }).catch(err => json(res, 400, { error: err.message }));
302
+ return true;
303
+ }
304
+ json(res, 405, { error: "method not allowed" });
305
+ return true;
306
+ }
307
+ return false;
308
+ }
309
+ //# sourceMappingURL=view-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-api.js","sourceRoot":"","sources":["../src/view-api.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.0.11-beta.3",
3
+ "version": "2.0.11-beta.4",
4
4
  "description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
5
5
  "type": "module",
6
6
  "bin": {