kiosko-plugin-agents 0.0.0-stage → 0.2.0

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -2
  3. package/dist/index.js +1485 -0
  4. package/package.json +52 -4
package/dist/index.js ADDED
@@ -0,0 +1,1485 @@
1
+ import { createRequire as __pluginRequire } from 'node:module'; const require = __pluginRequire(import.meta.url);
2
+
3
+ // src/index.ts
4
+ import { defineInterlude, definePlugin, defineSource, defineWidget, t } from "@kiosko/sdk";
5
+
6
+ // src/demo.ts
7
+ var s = (n) => n * 1e3;
8
+ var min = (n) => n * 6e4;
9
+ function rec(spec, now) {
10
+ return {
11
+ schemaVersion: 1,
12
+ session_id: spec.id,
13
+ provider: spec.provider ?? "claude",
14
+ state: spec.state,
15
+ detail: spec.detail,
16
+ project: spec.project,
17
+ cwd: `/home/user/dev/${spec.project}`,
18
+ since: now - spec.ago,
19
+ updated: now - (spec.updatedAgo ?? spec.ago),
20
+ event: "PreToolUse",
21
+ ...spec.dirty !== void 0 ? { dirty: spec.dirty } : {}
22
+ };
23
+ }
24
+ function scenario(name, now) {
25
+ const r = (spec) => rec(spec, now);
26
+ switch (name) {
27
+ case "empty":
28
+ return [];
29
+ case "one":
30
+ return [r({ id: "a1b2c3d4-1111", project: "api-gateway", state: "active", detail: "Edit src/routes/orders.ts", ago: s(12), dirty: 4 })];
31
+ case "two":
32
+ return [
33
+ r({ id: "a1b2c3d4-1111", project: "web-app", state: "permission", detail: "Bash: npm publish", ago: min(2) + s(18), dirty: 3 }),
34
+ r({ id: "b2c3d4e5-2222", project: "api-gateway", state: "active", detail: "Read src/adapters/payments/client.ts", ago: s(24), dirty: 0 })
35
+ ];
36
+ case "threeClaim":
37
+ return [
38
+ r({ id: "a1b2c3d4-1111", project: "infra", state: "permission", detail: "Bash: sudo systemctl restart nginx --no-block", ago: min(3) + s(41), dirty: 2 }),
39
+ r({ id: "b2c3d4e5-2222", project: "web-app", state: "waiting", detail: "Waiting for your input", ago: min(18) + s(7), dirty: 11, provider: "gemini" }),
40
+ r({ id: "c3d4e5f6-3333", project: "api-gateway", state: "ready", detail: "Stop", ago: min(1) + s(4), dirty: 0, provider: "gemini" }),
41
+ r({ id: "d4e5f6a7-4444", project: "docs-site", state: "active", detail: 'Grep "createServer"', ago: s(9), dirty: 1 })
42
+ ];
43
+ case "quiet":
44
+ return [
45
+ r({ id: "b8c9d0e1-8888", project: "api-gateway", state: "active", detail: "Bash: npm run build", ago: s(31), dirty: 6 }),
46
+ r({ id: "c9d0e1f2-9999", project: "web-app", state: "idle", detail: "SessionStart", ago: min(42), dirty: 0, provider: "gemini" })
47
+ ];
48
+ case "longNames":
49
+ return [
50
+ r({ id: "e5f6a7b8-5555", project: "infrastructure-terraform-production", state: "permission", detail: "Write /etc/systemd/system/app-worker.service with a rather long unit file that will not fit", ago: min(7) + s(22), dirty: 37 }),
51
+ r({ id: "f6a7b8c9-6666", project: "frontend-monorepo-customer-portal", state: "ready", detail: "SubagentStop", ago: min(2), dirty: 0, provider: "gemini" }),
52
+ r({ id: "a7b8c9d0-7777", project: "x", state: "active", detail: "Read package.json", ago: s(3), dirty: 0 })
53
+ ];
54
+ case "overflow":
55
+ return [
56
+ r({ id: "d0e1f2a3-aaaa", project: "infra", state: "permission", detail: "Bash: kubectl rollout restart deploy/api", ago: min(5) + s(13), dirty: 2 }),
57
+ r({ id: "e1f2a3b4-bbbb", project: "web-app", state: "waiting", detail: "idle_prompt", ago: min(11), dirty: 11, provider: "gemini" }),
58
+ r({ id: "f2a3b4c5-cccc", project: "api-gateway", state: "waiting", detail: "permission needed for MCP", ago: min(4) + s(51), dirty: 3 }),
59
+ r({ id: "a3b4c5d6-dddd", project: "docs-site", state: "ready", detail: "Stop", ago: s(48), dirty: 1, provider: "gemini" }),
60
+ r({ id: "b4c5d6e7-eeee", project: "notes", state: "ready", detail: "Stop", ago: min(3), dirty: 0 }),
61
+ r({ id: "c5d6e7f8-ffff", project: "scraper", state: "active", detail: "WebFetch example.com", ago: s(6), dirty: 0, provider: "gemini" }),
62
+ r({ id: "d6e7f8a9-0001", project: "bench", state: "active", detail: "Bash: pytest -q", ago: s(22), dirty: 4 }),
63
+ r({ id: "e7f8a9b0-0002", project: "archive", state: "idle", detail: "SessionStart", ago: min(30), dirty: 0, provider: "gemini" })
64
+ ];
65
+ case "meeting":
66
+ return [
67
+ {
68
+ schemaVersion: 1,
69
+ session_id: "meeting-1",
70
+ provider: "calendar",
71
+ state: "waiting",
72
+ detail: "",
73
+ project: "Architecture review",
74
+ since: now + min(4),
75
+ updated: now,
76
+ event: "Meeting",
77
+ startsAt: now + min(4),
78
+ endsAt: now + min(64)
79
+ },
80
+ r({ id: "a1b2c3d4-1111", project: "web-app", state: "permission", detail: "Bash: npm publish", ago: min(2), dirty: 3 }),
81
+ r({ id: "b2c3d4e5-2222", project: "api-gateway", state: "active", detail: "Read src/transport.ts", ago: s(24), dirty: 0 })
82
+ ];
83
+ }
84
+ }
85
+ var DEMO_VITALS = {
86
+ cpuTemp: 61,
87
+ cpuUsage: 0.23,
88
+ mem: { used: 44 * 1024 ** 3, total: 62 * 1024 ** 3 }
89
+ };
90
+
91
+ // ../../kiosko/packages/agent-sessions/src/protocol.ts
92
+ var SCHEMA_VERSION = 1;
93
+ var SESSION_STATES = ["permission", "waiting", "ready", "active", "idle"];
94
+ var STATE_PRIORITY = {
95
+ permission: 4,
96
+ waiting: 3,
97
+ ready: 2,
98
+ active: 1,
99
+ idle: 0
100
+ };
101
+ var CLAIMS = /* @__PURE__ */ new Set(["permission", "waiting", "ready"]);
102
+ function isSessionRecord(x) {
103
+ if (typeof x !== "object" || x === null) return false;
104
+ const r = x;
105
+ return r.schemaVersion === SCHEMA_VERSION && typeof r.session_id === "string" && typeof r.provider === "string" && SESSION_STATES.includes(r.state) && typeof r.detail === "string" && typeof r.project === "string" && typeof r.since === "number" && typeof r.updated === "number" && typeof r.event === "string";
106
+ }
107
+
108
+ // ../../kiosko/packages/agent-sessions/src/paths.ts
109
+ import { homedir } from "node:os";
110
+ import { join } from "node:path";
111
+ function defaultSessionsDir(env = process.env) {
112
+ return env.AGENT_SESSIONS_DIR ?? join(env.AIMONITOR_DIR ?? join(homedir(), ".aimonitor"), "sessions");
113
+ }
114
+ function sessionFile(dir, sessionId) {
115
+ return join(dir, `${sessionId}.json`);
116
+ }
117
+
118
+ // ../../kiosko/packages/agent-sessions/src/reader.ts
119
+ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
120
+ import { join as join2 } from "node:path";
121
+
122
+ // ../../kiosko/packages/agent-sessions/src/select.ts
123
+ function claims(r) {
124
+ return CLAIMS.has(r.state);
125
+ }
126
+ function pruneZombies(records, now, zombieMs, staleMs) {
127
+ return records.filter((r) => now - r.updated <= (r.state === "active" ? zombieMs : staleMs));
128
+ }
129
+ function order(records) {
130
+ return [...records].sort((a, b) => {
131
+ const ca = claims(a) ? 1 : 0;
132
+ const cb = claims(b) ? 1 : 0;
133
+ if (ca !== cb) return cb - ca;
134
+ const pa = STATE_PRIORITY[a.state];
135
+ const pb = STATE_PRIORITY[b.state];
136
+ if (pa !== pb) return pb - pa;
137
+ if (ca === 1) return a.since - b.since;
138
+ return b.updated - a.updated;
139
+ });
140
+ }
141
+
142
+ // ../../kiosko/packages/agent-sessions/src/reader.ts
143
+ function readSessions(dir = defaultSessionsDir()) {
144
+ if (!existsSync(dir)) return [];
145
+ const out = [];
146
+ for (const name of readdirSync(dir)) {
147
+ if (!name.endsWith(".json")) continue;
148
+ try {
149
+ const rec2 = JSON.parse(readFileSync(join2(dir, name), "utf8"));
150
+ if (isSessionRecord(rec2)) out.push(rec2);
151
+ } catch {
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+ function reap(records, now, o, dir = defaultSessionsDir(), onReap = () => {
157
+ }) {
158
+ const alive = pruneZombies(records, now, o.zombieMs, o.staleMs);
159
+ if (alive.length === records.length) return alive;
160
+ const keep = new Set(alive.map((r) => r.session_id));
161
+ for (const r of records) {
162
+ if (keep.has(r.session_id)) continue;
163
+ try {
164
+ rmSync(sessionFile(dir, r.session_id), { force: true });
165
+ onReap(r);
166
+ } catch {
167
+ }
168
+ }
169
+ return alive;
170
+ }
171
+
172
+ // ../../kiosko/packages/agent-sessions/src/craft.ts
173
+ function longestDefinition(text) {
174
+ const lines = text.split("\n");
175
+ const START = /^(export\s+)?(default\s+)?(async\s+)?(function\b|def\s|fn\s|func\s|class\s|const\s+\w+\s*=\s*(async\s*)?\(|(public|private|protected|static)\s)/;
176
+ let best = 0, from = -1;
177
+ lines.forEach((l, i) => {
178
+ if (START.test(l)) {
179
+ if (from >= 0) best = Math.max(best, i - from);
180
+ from = i;
181
+ }
182
+ });
183
+ if (from >= 0) best = Math.max(best, lines.length - from);
184
+ return best;
185
+ }
186
+ var SPAGHETTI_LINES = 500;
187
+
188
+ // ../../kiosko/packages/agent-sessions/src/providers.ts
189
+ var ALIASES = {};
190
+ function generic(provider) {
191
+ return (data, cwd) => {
192
+ const raw = data.hook_event_name || data.event || data.type || "";
193
+ return {
194
+ event: ALIASES[provider]?.[raw] ?? raw,
195
+ sessionId: data.session_id || data.sessionId || "global",
196
+ cwd: data.cwd || cwd,
197
+ tool: String(data.tool_name ?? data.tool?.name ?? data.tool ?? ""),
198
+ toolInput: data.tool_input ?? data.tool?.arguments,
199
+ toolResponse: data.tool_response,
200
+ source: String(data.source ?? data.session_start_type ?? ""),
201
+ notification: String(data.notification_type ?? data.notificationType ?? data.message ?? ""),
202
+ prompt: String(data.prompt ?? ""),
203
+ lastMessage: String(data.last_assistant_message ?? ""),
204
+ fallback: String(data.detail ?? data.message ?? data.command ?? "")
205
+ };
206
+ };
207
+ }
208
+ var PROVIDERS = {
209
+ claude: { name: "claude", normalize: generic("claude") },
210
+ gemini: { name: "gemini", normalize: generic("gemini") }
211
+ };
212
+
213
+ // src/format.ts
214
+ function elapsed(fmt, ms, nowWord) {
215
+ return ms < 6e4 ? nowWord : fmt.duration(ms);
216
+ }
217
+ function shortId(sessionId) {
218
+ return sessionId.replace(/-/g, "").slice(0, 6).toUpperCase();
219
+ }
220
+ function gigabytes(bytes) {
221
+ return `${Math.round(bytes / 1024 ** 3)}GB`;
222
+ }
223
+
224
+ // src/select.ts
225
+ function assignWeights(ordered) {
226
+ const claiming = ordered.filter(claims);
227
+ const winner = claiming.find((r) => r.startsAt !== void 0) ?? claiming[0];
228
+ return ordered.map((record) => {
229
+ if (!claims(record)) return { record, weight: "quiet" };
230
+ return { record, weight: record === winner ? "solid" : "marked" };
231
+ });
232
+ }
233
+ function plan(records, available, o) {
234
+ const { min: min2, max, gap, summaryWidth } = o;
235
+ const all = assignWeights(order(records));
236
+ const fits = (n, reserve2) => (available - reserve2 - (n - 1) * gap) / n >= min2;
237
+ let visible = o.maxTiles && o.maxTiles > 0 ? Math.min(all.length, Math.floor(o.maxTiles)) : all.length;
238
+ let reserve = visible < all.length ? summaryWidth + gap : 0;
239
+ while (visible > 1 && !fits(visible, reserve)) {
240
+ visible--;
241
+ reserve = summaryWidth + gap;
242
+ }
243
+ if (visible < all.length && reserve === 0) reserve = summaryWidth + gap;
244
+ const cap = visible < all.length ? Infinity : max;
245
+ const tileWidth = Math.max(0, Math.min(cap, Math.floor((available - reserve - (visible - 1) * gap) / Math.max(1, visible))));
246
+ return {
247
+ tiles: all.slice(0, visible),
248
+ overflow: all.slice(visible).map((t2) => t2.record),
249
+ tileWidth,
250
+ summaryWidth: visible < all.length ? summaryWidth : 0
251
+ };
252
+ }
253
+ function usedWidth(p, gap) {
254
+ const tiles = p.tiles.length * p.tileWidth + Math.max(0, p.tiles.length - 1) * gap;
255
+ return tiles + (p.summaryWidth ? (p.tiles.length ? gap : 0) + p.summaryWidth : 0);
256
+ }
257
+ function spareWidth(p, available, gap) {
258
+ const used = p.tiles.length * p.tileWidth + Math.max(0, p.tiles.length - 1) * gap;
259
+ return available - used - (p.summaryWidth ? p.summaryWidth + gap : 0);
260
+ }
261
+
262
+ // src/texts.ts
263
+ var STATE_LABEL = {
264
+ permission: { en: "Permission", es: "Permiso" },
265
+ waiting: { en: "Waiting", es: "Espera" },
266
+ ready: { en: "Ready", es: "Listo" },
267
+ active: { en: "Active", es: "Activa" },
268
+ idle: { en: "Idle", es: "Inactiva" }
269
+ };
270
+ var TEXT = {
271
+ now: { en: "now", es: "ahora" },
272
+ meeting: { en: "Meeting", es: "Reuni\xF3n" },
273
+ gitClean: { en: "git clean", es: "git limpio" },
274
+ gitDirty: { en: "git {n} uncommitted", es: "git {n} sin commit" },
275
+ more: { en: "+{n} more", es: "+{n} m\xE1s" },
276
+ consoles: { en: "{n} consoles", es: "{n} consolas" },
277
+ console: { en: "1 console", es: "1 consola" },
278
+ waitingMany: { en: "{n} waiting for you", es: "{n} te esperan" },
279
+ waitingOne: { en: "1 waiting for you", es: "1 te espera" },
280
+ nobody: { en: "nobody waiting", es: "nadie espera" },
281
+ noSessions: { en: "No agent sessions", es: "Sin sesiones de agentes" }
282
+ };
283
+ function fill(text, vars) {
284
+ return text.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
285
+ }
286
+
287
+ // src/types.ts
288
+ import { matchFilter } from "@kiosko/sdk";
289
+ var SESSIONS_SOURCE = "agents/sessions";
290
+ function sessionsOf(sources) {
291
+ const data = sources[SESSIONS_SOURCE];
292
+ return Array.isArray(data?.sessions) ? data.sessions : [];
293
+ }
294
+ function filterSessions(sessions, f) {
295
+ const hidden = new Set(f.hideStates ?? []);
296
+ return sessions.filter((s2) => {
297
+ if (hidden.has(s2.state)) return false;
298
+ if (s2.startsAt !== void 0) return true;
299
+ return matchFilter([s2.project, s2.repo, s2.cwd].filter((v) => !!v), f.projects);
300
+ });
301
+ }
302
+ function needsPermission(sessions) {
303
+ return sessions.some((s2) => s2.state === "permission");
304
+ }
305
+
306
+ // src/draw.ts
307
+ var REF_H = 374;
308
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
309
+ function stateColor(state, c) {
310
+ switch (state) {
311
+ case "permission":
312
+ return c.alert;
313
+ case "waiting":
314
+ return c.warn;
315
+ case "ready":
316
+ return c.info;
317
+ case "active":
318
+ return c.ok;
319
+ default:
320
+ return c.inkDim;
321
+ }
322
+ }
323
+ function alpha(kit, color, a) {
324
+ return /^#[0-9a-f]{6}$/i.test(color) ? kit.css(kit.hex(color), a) : color;
325
+ }
326
+ function inks(kit, weight, state, c) {
327
+ if (weight === "solid") {
328
+ return { strong: c.bg, mid: alpha(kit, c.bg, 0.78), faint: alpha(kit, c.bg, 0.55), rule: alpha(kit, c.bg, 0.28), accent: c.bg };
329
+ }
330
+ if (weight === "marked") return { strong: c.ink, mid: c.inkDim, faint: c.inkFaint, rule: c.surfaceEdge, accent: stateColor(state, c) };
331
+ return { strong: c.inkDim, mid: c.inkFaint, faint: c.inkFaint, rule: c.surfaceEdge, accent: c.inkFaint };
332
+ }
333
+ function hairline(g, x, y, w, color) {
334
+ g.fillStyle = color;
335
+ g.fillRect(x, Math.round(y), w, 1);
336
+ }
337
+ function drawTile(g, ctx, tile, x, y, w, h, st) {
338
+ const { kit, theme, now, t: t2, fmt } = ctx;
339
+ const c = theme.colors;
340
+ const f = theme.fonts;
341
+ const { record: r, weight } = tile;
342
+ const color = stateColor(r.state, c);
343
+ const ink = inks(kit, weight, r.state, c);
344
+ const s2 = st.s;
345
+ const fs = clamp(Math.min(s2, s2 * w / st.designW), 0.4, 2);
346
+ const spine = Math.max(3, Math.round(st.spineWidth * fs));
347
+ if (weight === "solid") {
348
+ g.fillStyle = color;
349
+ g.fillRect(x, y, w, h);
350
+ } else {
351
+ g.fillStyle = c.surface;
352
+ g.fillRect(x, y, w, h);
353
+ g.strokeStyle = c.surfaceEdge;
354
+ g.lineWidth = 1;
355
+ g.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);
356
+ if (weight === "marked") {
357
+ g.fillStyle = color;
358
+ g.fillRect(x, y, spine, h);
359
+ }
360
+ }
361
+ const pad = Math.round(18 * fs);
362
+ const padL = weight === "marked" ? spine + Math.round(15 * fs) : pad;
363
+ const cx = x + padL;
364
+ const cw = Math.max(10, w - padL - pad);
365
+ const meeting = r.startsAt !== void 0;
366
+ const labelOpts = { size: Math.max(11, Math.round(21 * fs)), family: f.sansBold, tracking: 3.6 * fs, smallCaps: true, color: ink.accent };
367
+ const label = kit.truncate(g, t2(meeting ? TEXT.meeting : STATE_LABEL[r.state]), cw, labelOpts);
368
+ const labelW = kit.drawTracked(g, label, cx, y + 32 * s2, labelOpts);
369
+ const idOpts = { size: Math.max(10, Math.round(15 * fs)), family: f.mono, color: ink.faint, align: "right" };
370
+ const ids = meeting ? [fmt.time(r.startsAt)] : st.showId ? [`${String(r.provider).toUpperCase()} \xB7 ${shortId(r.session_id)}`, shortId(r.session_id)] : [];
371
+ const id = ids.find((txt) => kit.measureTracked(g, txt, idOpts) <= cw - labelW - 12 * fs);
372
+ if (id) kit.drawTracked(g, id, x + w - pad, y + 31 * s2, idOpts);
373
+ hairline(g, cx, y + 46 * s2, cw, ink.rule);
374
+ const footerBase = y + h - 16 * s2;
375
+ const elapsedSize = Math.round(52 * fs);
376
+ const elapsedBase = footerBase - 26 * s2;
377
+ const big = !meeting ? elapsed(fmt, now - r.since, t2(TEXT.now)) : r.startsAt <= now ? t2(TEXT.now) : fmt.duration(r.startsAt - now, { round: "up" });
378
+ const bigOpts = { size: elapsedSize, family: f.monoBold, color: ink.strong, tracking: -1 * fs };
379
+ kit.drawClipped(g, big, cx, elapsedBase, cw, bigOpts);
380
+ const detailOpts = { size: Math.max(10, Math.round(17 * fs)), family: f.mono, color: ink.mid };
381
+ const lead = Math.round(detailOpts.size * 1.35);
382
+ const detailBase = elapsedBase - elapsedSize * 0.8 - 14 * s2;
383
+ const midTop = y + 54 * s2;
384
+ const projMax = Math.round(48 * fs);
385
+ const projMin = Math.max(12, Math.round(26 * fs));
386
+ let maxLines = Math.max(0, st.detailLines);
387
+ while (maxLines > 1 && detailBase - (maxLines - 1) * lead - 24 * s2 - midTop < projMin * 0.9) maxLines--;
388
+ const lines = maxLines > 0 && r.detail ? kit.wrapLines(g, r.detail, cw, maxLines, detailOpts) : [];
389
+ lines.forEach((line, i) => kit.drawTracked(g, line, cx, detailBase - (lines.length - 1 - i) * lead, detailOpts));
390
+ const detailTop = detailBase - Math.max(0, lines.length - 1) * lead;
391
+ const midBottom = maxLines > 0 ? detailTop - 24 * s2 : detailBase;
392
+ const projOpts = { size: projMax, family: f.sansBold, color: ink.strong, tracking: 0.5 * fs };
393
+ projOpts.size = Math.min(kit.fitSize(g, r.project, cw, projOpts, projMin), Math.max(projMin, Math.floor((midBottom - midTop) / 0.8)));
394
+ const projBase = Math.round((midTop + midBottom) / 2 + projOpts.size * 0.36);
395
+ kit.drawClipped(g, r.project, cx, projBase, cw, projOpts);
396
+ if (st.showGit && r.dirty !== void 0) {
397
+ const txt = r.dirty === 0 ? t2(TEXT.gitClean) : fill(t2(TEXT.gitDirty), { n: r.dirty });
398
+ kit.drawClipped(g, txt, cx, footerBase, cw, { size: Math.max(10, Math.round(16 * fs)), family: f.mono, color: ink.faint });
399
+ }
400
+ }
401
+ function drawOverflow(g, ctx, rest, x, y, w, h, s2) {
402
+ const { kit, theme, t: t2 } = ctx;
403
+ const c = theme.colors;
404
+ const f = theme.fonts;
405
+ g.fillStyle = c.surface;
406
+ g.fillRect(x, y, w, h);
407
+ g.strokeStyle = c.surfaceEdge;
408
+ g.lineWidth = 1;
409
+ g.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);
410
+ const pad = Math.round(16 * s2);
411
+ const cx = x + pad;
412
+ const cw = Math.max(10, w - 2 * pad);
413
+ const headOpts = { size: Math.max(11, Math.round(21 * s2)), family: f.sansBold, tracking: 3.6 * s2, smallCaps: true, color: c.inkDim };
414
+ kit.drawClipped(g, fill(t2(TEXT.more), { n: rest.length }), cx, y + 32 * s2, cw, headOpts);
415
+ hairline(g, cx, y + 46 * s2, cw, c.surfaceEdge);
416
+ const counts = /* @__PURE__ */ new Map();
417
+ for (const r of rest) counts.set(r.state, (counts.get(r.state) ?? 0) + 1);
418
+ const box = Math.max(5, Math.round(9 * s2));
419
+ const countOpts = { size: Math.max(10, Math.round(17 * s2)), family: f.monoBold, color: c.inkDim, align: "right" };
420
+ const labelOpts = { size: Math.max(10, Math.round(16 * s2)), family: f.sansBold, tracking: 2 * s2, smallCaps: true, color: c.inkDim };
421
+ let ry = y + 78 * s2;
422
+ for (const [state, n] of [...counts].sort((a, b) => b[1] - a[1])) {
423
+ if (ry > y + h - 6 * s2) break;
424
+ g.fillStyle = stateColor(state, c);
425
+ g.fillRect(cx, ry - box, box, box);
426
+ const countW = kit.drawTracked(g, String(n), x + w - pad, ry, countOpts);
427
+ const lx = cx + box + Math.round(8 * s2);
428
+ kit.drawClipped(g, t2(STATE_LABEL[state]), lx, ry, Math.max(0, x + w - pad - countW - 6 * s2 - lx), labelOpts);
429
+ ry += 28 * s2;
430
+ }
431
+ }
432
+ function drawQuiet(g, ctx, text) {
433
+ const { box, kit, theme, t: t2 } = ctx;
434
+ const size = clamp(Math.round(box.h * 0.08), 12, 26);
435
+ const o = { size, family: theme.fonts.sansBold, tracking: size * 0.15, smallCaps: true, color: theme.colors.inkFaint, align: "center" };
436
+ kit.drawTracked(g, kit.truncate(g, t2(text), box.w, o), box.x + box.w / 2, box.y + box.h / 2 + size * 0.36, o);
437
+ }
438
+ function drawSessions(g, ctx, o) {
439
+ const { box } = ctx;
440
+ const sessions = filterSessions(sessionsOf(ctx.sources), o);
441
+ if (!sessions.length) {
442
+ drawQuiet(g, ctx, TEXT.noSessions);
443
+ return;
444
+ }
445
+ const s2 = clamp(box.h / REF_H, 0.5, 1.6);
446
+ const gap = Math.max(2, Math.round(o.gap * s2));
447
+ const designW = o.tileMin * s2;
448
+ const max = o.tileMax > 0 ? Math.max(o.tileMin, o.tileMax) * s2 : Infinity;
449
+ const p = plan(sessions, box.w, { min: designW, max, gap, summaryWidth: Math.round(o.summaryWidth * s2), maxTiles: o.maxTiles });
450
+ const free = box.w - usedWidth(p, gap);
451
+ let x = Math.round(box.x + (o.align === "center" ? free / 2 : o.align === "right" ? free : 0));
452
+ const st = { s: s2, designW, spineWidth: o.spineWidth, detailLines: o.detailLines, showGit: o.showGit, showId: o.showId };
453
+ for (const tile of p.tiles) {
454
+ drawTile(g, ctx, tile, x, box.y, p.tileWidth, box.h, st);
455
+ x += p.tileWidth + gap;
456
+ }
457
+ if (p.overflow.length) drawOverflow(g, ctx, p.overflow, x, box.y, p.summaryWidth, box.h, s2);
458
+ }
459
+ function drawSummaryStrip(g, ctx, o) {
460
+ const { box, kit, theme, now, t: t2, fmt, sources } = ctx;
461
+ const c = theme.colors;
462
+ const f = theme.fonts;
463
+ const sessions = filterSessions(sessionsOf(sources), o);
464
+ const s2 = clamp(Math.min(box.h / 56, box.w / 900), 0.4, 2);
465
+ const ruleH = o.showRule ? 1 : 0;
466
+ const mid = box.y + (box.h - ruleH) / 2;
467
+ let right = box.x + box.w;
468
+ if (o.showClock) {
469
+ const cs = Math.max(10, Math.round(30 * s2));
470
+ const cw = kit.drawTracked(g, fmt.time(now), right, mid + cs * 0.36, { size: cs, family: f.monoBold, color: c.ink, align: "right" });
471
+ right -= cw + Math.round(24 * s2);
472
+ }
473
+ const parts = [];
474
+ const mem = sources["system/vitals"]?.mem;
475
+ if (o.showRam && mem && mem.total > 0) {
476
+ parts.push({ text: `RAM: ${gigabytes(mem.used)}/${gigabytes(mem.total)}`, color: c.ink, keep: 0 });
477
+ }
478
+ const n = sessions.length;
479
+ if (o.showConsoles) parts.push({ text: n === 1 ? t2(TEXT.console) : fill(t2(TEXT.consoles), { n }), color: c.inkDim, keep: 1 });
480
+ const waiting = sessions.filter(claims);
481
+ const urgent = [...waiting].sort((a, b) => STATE_PRIORITY[b.state] - STATE_PRIORITY[a.state])[0];
482
+ const waitText = waiting.length === 0 ? t2(TEXT.nobody) : waiting.length === 1 ? t2(TEXT.waitingOne) : fill(t2(TEXT.waitingMany), { n: waiting.length });
483
+ parts.push({ text: waitText, color: urgent ? stateColor(urgent.state, c) : c.inkFaint, keep: 2 });
484
+ const dot = " \xB7 ";
485
+ let size = Math.max(10, Math.round(22 * s2));
486
+ const label = () => ({ size, family: f.sansBold, tracking: 3.4 * (size / 22), smallCaps: true });
487
+ const width = (ps) => ps.reduce((a, p, i) => a + kit.measureTracked(g, p.text, label()) + (i ? kit.measureTracked(g, dot, label()) : 0), 0);
488
+ const room = right - box.x;
489
+ while (parts.length > 1 && width(parts) > room) parts.splice(parts.findIndex((p) => p.keep === Math.min(...parts.map((q) => q.keep))), 1);
490
+ while (size > 10 && width(parts) > room) size--;
491
+ let x = box.x;
492
+ const base = mid + size * 0.36;
493
+ parts.forEach((p, i) => {
494
+ if (i) x += kit.drawTracked(g, dot, x, base, { ...label(), color: c.inkFaint });
495
+ x += kit.drawTracked(g, kit.truncate(g, p.text, Math.max(0, right - x), label()), x, base, { ...label(), color: p.color });
496
+ });
497
+ if (o.showRule) hairline(g, box.x, box.y + box.h - 1, box.w, c.surfaceEdge);
498
+ }
499
+
500
+ // src/platformer.ts
501
+ var T = 66;
502
+ var ROWS = 7;
503
+ var WORLD_H = ROWS * T;
504
+ var GROUND_ROW = 6;
505
+ var MAP = [
506
+ // 1 2 3 4 5 6 7 8 9
507
+ //234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
508
+ " ",
509
+ " C ",
510
+ " B?B?B ",
511
+ " B?B ",
512
+ " F ",
513
+ " e PP e PP e F ",
514
+ "####################################### ###################### ################# ###############"
515
+ ];
516
+ var SPEED = 24.75;
517
+ var GRAVITY = 16;
518
+ var JUMP_V = 88;
519
+ var STOMP_V = 46;
520
+ function measureJumpReach() {
521
+ let y = GROUND_ROW * T;
522
+ let vy = -JUMP_V;
523
+ let frames = 0;
524
+ do {
525
+ vy += GRAVITY;
526
+ y += vy;
527
+ frames++;
528
+ } while (!(vy > 0 && y >= GROUND_ROW * T));
529
+ return frames * SPEED;
530
+ }
531
+ var JUMP_REACH = measureJumpReach();
532
+ var BUG_SPEED = 4;
533
+ var COLS = MAP[0].length;
534
+ var LEVEL_WIDTH = COLS * T;
535
+ var CASTLE_W = 5 * T;
536
+ var CASTLE_TOP = 2.3 * T;
537
+ var SKY = "#5C94FC";
538
+ var SKY_DEEP = "#2A5FC8";
539
+ function parseLevel() {
540
+ const blocks = [];
541
+ const pipes = [];
542
+ const bugs = [];
543
+ const gaps = /* @__PURE__ */ new Set();
544
+ let flagCol = -1;
545
+ let castleCol = -1;
546
+ for (let row = 0; row < ROWS; row++) {
547
+ const line = MAP[row].padEnd(COLS, " ");
548
+ for (let col = 0; col < COLS; col++) {
549
+ const ch = line[col];
550
+ if (ch === "#") blocks.push({ col, row, kind: "#" });
551
+ else if (ch === "B" || ch === "?") blocks.push({ col, row, kind: ch });
552
+ else if (ch === "P") {
553
+ if (line[col - 1] !== "P" && (MAP[row - 1]?.[col] ?? " ") !== "P") pipes.push({ col, top: row });
554
+ } else if (ch === "e") bugs.push({ col });
555
+ else if (ch === "F" && flagCol < 0) flagCol = col;
556
+ else if (ch === "C" && castleCol < 0) castleCol = col;
557
+ }
558
+ }
559
+ for (let col = 0; col < COLS; col++) {
560
+ if (MAP[GROUND_ROW].padEnd(COLS, " ")[col] !== "#") gaps.add(col);
561
+ }
562
+ return { blocks, pipes, bugs, gaps, flagCol, castleCol };
563
+ }
564
+ var LEVEL = parseLevel();
565
+ function gapGroups() {
566
+ const cols = [...LEVEL.gaps].sort((a, b) => a - b);
567
+ const out = [];
568
+ for (const c of cols) {
569
+ const last = out[out.length - 1];
570
+ if (last && c === last[1] + 1) last[1] = c;
571
+ else out.push([c, c]);
572
+ }
573
+ return out;
574
+ }
575
+ function surfaceAt(x) {
576
+ const col = Math.floor(x / T);
577
+ if (col < 0 || col >= COLS) return GROUND_ROW * T;
578
+ for (const p of LEVEL.pipes) {
579
+ if (col >= p.col && col < p.col + 2) return p.top * T;
580
+ }
581
+ if (LEVEL.gaps.has(col)) return Number.POSITIVE_INFINITY;
582
+ return GROUND_ROW * T;
583
+ }
584
+ function jumpTriggers() {
585
+ const t2 = [];
586
+ for (const p of LEVEL.pipes) t2.push(p.col * T + 2.6 * T - JUMP_REACH);
587
+ for (const [from, to] of gapGroups()) {
588
+ t2.push(Math.max(from * T - 0.6 * T, (to + 1) * T + 0.4 * T - JUMP_REACH));
589
+ }
590
+ return t2.sort((a, b) => a - b);
591
+ }
592
+ var TRIGGERS = jumpTriggers();
593
+ var GAP_TRIGGER = new Map(gapGroups().map(([from, to]) => [from, Math.max(from * T - 0.6 * T, (to + 1) * T + 0.4 * T - JUMP_REACH)]));
594
+ function fateFor(seed) {
595
+ const stumbles = [
596
+ // The first obstacles are skipped: falling two seconds in leaves no time
597
+ // to even notice there is an animation.
598
+ ...gapGroups().map(([col]) => col).filter((c) => c > 20).map((col) => ({ kind: "gap", col })),
599
+ ...LEVEL.bugs.map((_b, index) => ({ kind: "bug", index })).filter((_, i) => LEVEL.bugs[i].col > 20)
600
+ ];
601
+ if (stumbles.length === 0) return { kind: "complete" };
602
+ const n = stumbles.length + 2;
603
+ const pick = (seed % n + n) % n;
604
+ return pick < 2 ? { kind: "complete" } : stumbles[pick - 2];
605
+ }
606
+ var SLIDE_FRAMES = 12;
607
+ var WALK_FRAMES = 60;
608
+ var HOLD_FRAMES = 14;
609
+ var CARD_FRAMES = 16;
610
+ var DEATH_V = 58;
611
+ function simulate(width, seed = 0) {
612
+ const fate = fateFor(seed);
613
+ const frames = [];
614
+ const flagX = LEVEL.flagCol * T;
615
+ const doorX = LEVEL.castleCol * T + CASTLE_W / 2;
616
+ let x = 2 * T;
617
+ let y = GROUND_ROW * T;
618
+ let vy = 0;
619
+ let airborne = false;
620
+ let nextTrigger = 0;
621
+ let coins = 0;
622
+ const squashed = /* @__PURE__ */ new Map();
623
+ const bugX = LEVEL.bugs.map((b) => b.col * T);
624
+ const attempted = /* @__PURE__ */ new Set();
625
+ const maxCamera = Math.max(0, LEVEL_WIDTH - width);
626
+ const cameraFor = (hx) => Math.max(0, Math.min(maxCamera, hx - width * 0.34));
627
+ let phase = "run";
628
+ let index = 0;
629
+ let phaseFrame = 0;
630
+ let castleFlag = 0;
631
+ let camera = cameraFor(x);
632
+ for (let guard = 0; guard < 4e3; guard++) {
633
+ if (phase === "run") {
634
+ x += SPEED;
635
+ const closing = JUMP_REACH * ((SPEED + BUG_SPEED) / SPEED);
636
+ let bugAhead = false;
637
+ for (let i = 0; i < bugX.length; i++) {
638
+ if (squashed.has(i) || attempted.has(i)) continue;
639
+ if (bugX[i] - x > closing) continue;
640
+ attempted.add(i);
641
+ if (fate.kind === "bug" && fate.index === i) continue;
642
+ bugAhead = true;
643
+ }
644
+ let staticTrigger = false;
645
+ while (nextTrigger < TRIGGERS.length && x >= TRIGGERS[nextTrigger]) {
646
+ const skip = fate.kind === "gap" && TRIGGERS[nextTrigger] === GAP_TRIGGER.get(fate.col);
647
+ if (!skip) staticTrigger = true;
648
+ nextTrigger++;
649
+ }
650
+ if (!airborne && (staticTrigger || bugAhead)) {
651
+ vy = -JUMP_V;
652
+ airborne = true;
653
+ }
654
+ if (airborne) {
655
+ vy += GRAVITY;
656
+ y += vy;
657
+ if (vy > 0) {
658
+ for (let i = 0; i < bugX.length; i++) {
659
+ if (squashed.has(i)) continue;
660
+ if (Math.abs(bugX[i] - x) < T * 0.8 && y >= GROUND_ROW * T - T * 0.6) {
661
+ squashed.set(i, index);
662
+ vy = -STOMP_V;
663
+ coins += 100;
664
+ }
665
+ }
666
+ }
667
+ const floor = surfaceAt(x);
668
+ if (vy > 0 && y >= floor) {
669
+ y = floor;
670
+ vy = 0;
671
+ airborne = false;
672
+ }
673
+ }
674
+ if (!airborne) {
675
+ for (let i = 0; i < bugX.length; i++) {
676
+ if (squashed.has(i)) continue;
677
+ if (Math.abs(bugX[i] - x) < T * 0.55) {
678
+ phase = "dies";
679
+ vy = -DEATH_V;
680
+ phaseFrame = 0;
681
+ }
682
+ }
683
+ }
684
+ if (!airborne && y < surfaceAt(x)) {
685
+ airborne = true;
686
+ vy = 0;
687
+ }
688
+ if (y > ROWS * T) {
689
+ phase = "dies";
690
+ phaseFrame = 0;
691
+ }
692
+ for (let i = 0; i < bugX.length; i++) {
693
+ if (!squashed.has(i) && bugX[i] - x < width * 0.7) bugX[i] = bugX[i] - BUG_SPEED;
694
+ }
695
+ if (phase === "run" && x >= flagX) {
696
+ x = flagX;
697
+ y = 2.2 * T;
698
+ phase = "slide";
699
+ phaseFrame = 0;
700
+ coins += 500;
701
+ }
702
+ camera = cameraFor(x);
703
+ } else if (phase === "dies") {
704
+ vy += GRAVITY;
705
+ y += vy;
706
+ phaseFrame++;
707
+ if (y > ROWS * T + 260) {
708
+ phase = "retry";
709
+ phaseFrame = 0;
710
+ }
711
+ } else if (phase === "retry") {
712
+ if (++phaseFrame > CARD_FRAMES) break;
713
+ } else if (phase === "slide") {
714
+ y = 2.2 * T + (GROUND_ROW * T - 2.2 * T) * (phaseFrame / SLIDE_FRAMES);
715
+ camera = cameraFor(flagX + width * 0.2);
716
+ if (++phaseFrame > SLIDE_FRAMES) {
717
+ phase = "walk";
718
+ phaseFrame = 0;
719
+ y = GROUND_ROW * T;
720
+ }
721
+ } else if (phase === "walk") {
722
+ x += SPEED * 0.75;
723
+ camera = cameraFor(flagX + width * 0.2);
724
+ castleFlag = Math.min(1, (x - flagX) / (doorX - flagX));
725
+ if (x >= doorX || ++phaseFrame > WALK_FRAMES) {
726
+ phase = "hold";
727
+ phaseFrame = 0;
728
+ }
729
+ } else {
730
+ castleFlag = 1;
731
+ if (++phaseFrame > HOLD_FRAMES) break;
732
+ }
733
+ frames.push({ camera, heroX: x, heroY: y, airborne, phase, squashed: new Map(squashed), bugX: [...bugX], castleFlag, coins, index });
734
+ index++;
735
+ }
736
+ return frames;
737
+ }
738
+ var DEFAULT_TEXTS = { title: "KIOSKO", world: "RELEASE", time: "TIME", retry: "TRY AGAIN", level: "V1.0" };
739
+ function levelFor(seed) {
740
+ const n = Math.abs(Math.floor(seed));
741
+ return `V${n % 9 + 1}.${Math.floor(n / 9) % 10}`;
742
+ }
743
+ var GLYPHS = {
744
+ A: "01110 10001 10001 11111 10001 10001 10001",
745
+ B: "11110 10001 10001 11110 10001 10001 11110",
746
+ C: "01110 10001 10000 10000 10000 10001 01110",
747
+ D: "11110 10001 10001 10001 10001 10001 11110",
748
+ E: "11111 10000 10000 11110 10000 10000 11111",
749
+ F: "11111 10000 10000 11110 10000 10000 10000",
750
+ G: "01110 10001 10000 10111 10001 10001 01111",
751
+ H: "10001 10001 10001 11111 10001 10001 10001",
752
+ I: "11111 00100 00100 00100 00100 00100 11111",
753
+ J: "00111 00010 00010 00010 00010 10010 01100",
754
+ K: "10001 10010 10100 11000 10100 10010 10001",
755
+ L: "10000 10000 10000 10000 10000 10000 11111",
756
+ M: "10001 11011 10101 10101 10001 10001 10001",
757
+ N: "10001 11001 10101 10011 10001 10001 10001",
758
+ O: "01110 10001 10001 10001 10001 10001 01110",
759
+ P: "11110 10001 10001 11110 10000 10000 10000",
760
+ Q: "01110 10001 10001 10001 10101 10010 01101",
761
+ R: "11110 10001 10001 11110 10100 10010 10001",
762
+ S: "01111 10000 10000 01110 00001 00001 11110",
763
+ T: "11111 00100 00100 00100 00100 00100 00100",
764
+ U: "10001 10001 10001 10001 10001 10001 01110",
765
+ V: "10001 10001 10001 10001 10001 01010 00100",
766
+ W: "10001 10001 10001 10101 10101 11011 10001",
767
+ X: "10001 10001 01010 00100 01010 10001 10001",
768
+ Y: "10001 10001 01010 00100 00100 00100 00100",
769
+ Z: "11111 00001 00010 00100 01000 10000 11111",
770
+ "0": "01110 10011 10101 10101 11001 10001 01110",
771
+ "1": "00100 01100 00100 00100 00100 00100 01110",
772
+ "2": "01110 10001 00001 00110 01000 10000 11111",
773
+ "3": "11111 00010 00100 00010 00001 10001 01110",
774
+ "4": "00010 00110 01010 10010 11111 00010 00010",
775
+ "5": "11111 10000 11110 00001 00001 10001 01110",
776
+ "6": "00110 01000 10000 11110 10001 10001 01110",
777
+ "7": "11111 00001 00010 00100 01000 01000 01000",
778
+ "8": "01110 10001 10001 01110 10001 10001 01110",
779
+ "9": "01110 10001 10001 01111 00001 00010 01100",
780
+ "?": "01110 10001 00001 00110 00100 00000 00100",
781
+ "<": "00010 00100 01000 10000 01000 00100 00010",
782
+ ">": "01000 00100 00010 00001 00010 00100 01000",
783
+ "/": "00001 00010 00010 00100 01000 01000 10000",
784
+ "!": "00100 00100 00100 00100 00100 00000 00100",
785
+ ".": "00000 00000 00000 00000 00000 00000 00100",
786
+ "-": "00000 00000 00000 11111 00000 00000 00000",
787
+ " ": "00000 00000 00000 00000 00000 00000 00000"
788
+ };
789
+ function pixelSafe(text) {
790
+ return text.normalize("NFD").replace(/[̀-ͯ]/g, "").toUpperCase();
791
+ }
792
+ var SEAM = 0.4;
793
+ var pixelWidth = (text, px) => [...text].length * 6 * px - px;
794
+ function drawPixelText(g, text, x, y, px, color) {
795
+ g.fillStyle = color;
796
+ let cx = x;
797
+ for (const ch of pixelSafe(text)) {
798
+ const rows = (GLYPHS[ch] ?? "11111 10001 10001 10001 10001 10001 11111").split(" ");
799
+ for (let r = 0; r < rows.length; r++) {
800
+ const row = rows[r];
801
+ for (let c = 0; c < row.length; c++) if (row[c] === "1") g.fillRect(cx + c * px, y + r * px, px + SEAM, px + SEAM);
802
+ }
803
+ cx += 6 * px;
804
+ }
805
+ }
806
+ var HERO_COLORS = {
807
+ A: "#FFB020",
808
+ // cap and shirt
809
+ S: "#F2C9A0",
810
+ // skin
811
+ K: "#2A1A12",
812
+ // eye
813
+ O: "#2E8A82",
814
+ // overalls
815
+ N: "#1F6259",
816
+ // overalls shade
817
+ B: "#4A2E1E",
818
+ // boots
819
+ H: "#8A5A2B"
820
+ // hair
821
+ };
822
+ var HERO_BODY = ["....AAAAA...", "...AAAAAAAA.", "...HHSSSKS..", "...HSSSSKSS.", "...HSSSSSSS.", "....SSSSSS..", ".....SSSS...", "..AAAAAAAA..", ".AAAOAAAOAA.", "AAAAOOOOAAAA", "AAAAOOOOAAAA"];
823
+ var HERO_LEGS = [
824
+ [".AAAOOOOAAA.", "...OOOOOO...", "...OO..OO...", "..BBB..BBB..", "..BBB..BBB.."],
825
+ [".AAAOOOOAAA.", "...OOOOOO...", "..OO....OO..", ".BBB......BB", ".BBB......BB"],
826
+ [".AAAOOOOAAA.", "...OOOOOO...", "....OOOO....", "...BBBBBB...", "...BBBBBB..."]
827
+ ];
828
+ var HERO_LEGS_AIR = [".AAAOOOOAAA.", "..NOOOOOON..", ".OO......OO.", "BBB.......BB", "BBB........."];
829
+ var HERO_DEAD = ["A..........A", "AA.......AA.", ".AA.AAAAA.A.", "..AAAAAAAA..", "..SKSSSSKS..", "..SSKSSKSS..", "..SKSSSSKS..", "...SSSSSS...", "..AAAAAAAA..", ".AAAOOOOAAA.", ".AAAOOOOAAA.", "..OOOOOOOO..", "..OO....OO..", "..BB....BB..", ".BBB....BBB.", "............"];
830
+ var HERO_PX = 7;
831
+ var HERO_W = 12 * HERO_PX;
832
+ var HERO_H = 16 * HERO_PX;
833
+ function drawSprite(g, rows, x, y, px, colors) {
834
+ for (let r = 0; r < rows.length; r++) {
835
+ const row = rows[r];
836
+ for (let c = 0; c < row.length; c++) {
837
+ const color = colors[row[c]];
838
+ if (!color) continue;
839
+ g.fillStyle = color;
840
+ g.fillRect(x + c * px, y + r * px, px + SEAM, px + SEAM);
841
+ }
842
+ }
843
+ }
844
+ function drawHero(g, f) {
845
+ if (f.phase === "hold") return;
846
+ const x = Math.round(f.heroX - f.camera - HERO_W / 2);
847
+ const y = Math.round(f.heroY - HERO_H);
848
+ if (f.phase === "dies") {
849
+ drawSprite(g, HERO_DEAD, x, y, HERO_PX, HERO_COLORS);
850
+ return;
851
+ }
852
+ const legs = f.airborne || f.phase === "slide" ? HERO_LEGS_AIR : HERO_LEGS[Math.floor(f.index / 2) % HERO_LEGS.length];
853
+ drawSprite(g, HERO_BODY, x, y, HERO_PX, HERO_COLORS);
854
+ drawSprite(g, legs, x, y + HERO_BODY.length * HERO_PX, HERO_PX, HERO_COLORS);
855
+ }
856
+ function drawRetryCard(g, width, height, texts) {
857
+ g.fillStyle = "#000000";
858
+ g.fillRect(0, 0, width, height);
859
+ const cx = width / 2;
860
+ const fit = (text, px2) => {
861
+ while (px2 > 1 && pixelWidth(pixelSafe(text), px2) > width * 0.9) px2--;
862
+ return px2;
863
+ };
864
+ const head = `${texts.world} ${texts.level ?? DEFAULT_TEXTS.level}`;
865
+ const px = fit(head, 6);
866
+ drawPixelText(g, head, cx - pixelWidth(pixelSafe(head), px) / 2, height * 0.26, px, "#FFFFFF");
867
+ const headPx = 7;
868
+ const lives = "X 2";
869
+ const groupW = 12 * headPx + 30 + pixelWidth(lives, px);
870
+ const left = cx - groupW / 2;
871
+ drawSprite(g, HERO_BODY.slice(0, 7), left, height * 0.48, headPx, HERO_COLORS);
872
+ drawPixelText(g, lives, left + 12 * headPx + 30, height * 0.52, px, "#FFFFFF");
873
+ const rpx = fit(texts.retry, 3);
874
+ drawPixelText(g, texts.retry, cx - pixelWidth(pixelSafe(texts.retry), rpx) / 2, height * 0.82, rpx, "#627180");
875
+ }
876
+ var BUG_COLORS = { X: "#B84A3A", D: "#2A1A12", W: "#EFEFEF", F: "#7A2E22" };
877
+ var BUG = ["..XXXXXX..", ".XXXXXXXX.", "XXWDXXDWXX", "XXWDXXDWXX", "XXXXXXXXXX", ".XXXXXXXX.", "..FF..FF..", ".FFF..FFF."];
878
+ var BUG_FLAT = ["..........", "..........", "..........", "..........", "..XXXXXX..", ".XXXXXXXX.", "XXXXXXXXXX", ".FF....FF."];
879
+ var BUG_PX = 6;
880
+ function drawBugs(g, f, width) {
881
+ for (let i = 0; i < f.bugX.length; i++) {
882
+ const squashedAt = f.squashed.get(i);
883
+ if (squashedAt !== void 0 && f.index - squashedAt > 8) continue;
884
+ const rows = squashedAt !== void 0 ? BUG_FLAT : BUG;
885
+ const x = Math.round(f.bugX[i] - f.camera - BUG[0].length * BUG_PX / 2);
886
+ if (x < -100 || x > width + 100) continue;
887
+ drawSprite(g, rows, x, GROUND_ROW * T - rows.length * BUG_PX, BUG_PX, BUG_COLORS);
888
+ }
889
+ }
890
+ function drawBlock(g, b, sx, y, frameIndex) {
891
+ if (b.kind === "#") {
892
+ g.fillStyle = "#C84C0C";
893
+ g.fillRect(sx, y, T + 1, T + 1);
894
+ g.fillStyle = "#8A3308";
895
+ for (let r = 0; r < 2; r++) {
896
+ g.fillRect(sx, y + r * (T / 2) + T / 2 - 3, T, 3);
897
+ g.fillRect(sx + (r % 2 ? T / 4 : T * 3 / 4) - 1.5, y + r * (T / 2), 3, T / 2);
898
+ }
899
+ return;
900
+ }
901
+ if (b.kind === "B") {
902
+ g.fillStyle = "#B5651D";
903
+ g.fillRect(sx, y, T + 1, T);
904
+ g.fillStyle = "#7A3E10";
905
+ g.fillRect(sx, y, T, 4);
906
+ g.fillRect(sx, y + T / 2 - 2, T, 4);
907
+ g.fillRect(sx + T / 2 - 2, y + 4, 4, T / 2 - 6);
908
+ g.fillRect(sx + T / 4 - 2, y + T / 2 + 2, 4, T / 2 - 2);
909
+ g.fillRect(sx + T * 3 / 4 - 2, y + T / 2 + 2, 4, T / 2 - 2);
910
+ return;
911
+ }
912
+ const lit = Math.floor(frameIndex / 3) % 4;
913
+ g.fillStyle = ["#7C5CFF", "#8F74FF", "#7C5CFF", "#6346E0"][lit];
914
+ g.fillRect(sx, y, T + 1, T);
915
+ g.fillStyle = "#2E1A7A";
916
+ g.fillRect(sx, y, T, 4);
917
+ g.fillRect(sx, y + T - 4, T, 4);
918
+ g.fillRect(sx, y, 4, T);
919
+ g.fillRect(sx + T - 4, y, 4, T);
920
+ const tag = "</>";
921
+ drawPixelText(g, tag, sx + (T - pixelWidth(tag, 3)) / 2, y + T / 2 - 10, 3, "#EDE9FF");
922
+ }
923
+ function drawPipe(g, p, sx) {
924
+ const top = p.top * T;
925
+ const h = (GROUND_ROW - p.top) * T;
926
+ g.fillStyle = "#1E293B";
927
+ g.fillRect(sx - 4, top - 4, 2 * T + 8, h + 4);
928
+ g.fillStyle = "#64748B";
929
+ g.fillRect(sx + 6, top + T * 0.42, 2 * T - 12, h - T * 0.42);
930
+ g.fillRect(sx, top, 2 * T, T * 0.42);
931
+ g.fillStyle = "#94A3B8";
932
+ g.fillRect(sx + 12, top + 6, 14, T * 0.42 - 12);
933
+ g.fillRect(sx + 18, top + T * 0.42, 12, h - T * 0.42);
934
+ g.fillStyle = "#334155";
935
+ g.fillRect(sx + 2 * T - 16, top, 10, T * 0.42);
936
+ g.fillRect(sx + 2 * T - 22, top + T * 0.42, 10, h - T * 0.42);
937
+ g.fillStyle = "#1E293B";
938
+ for (let i = 0; i < 4; i++) g.fillRect(sx + 34 + i * 18, top + 10, 10, 4);
939
+ g.fillStyle = "#34D399";
940
+ g.fillRect(sx + 2 * T - 34, top + 9, 6, 6);
941
+ }
942
+ function drawBackdrop(g, camera, width) {
943
+ const grad = g.createLinearGradient(0, 0, 0, ROWS * T);
944
+ grad.addColorStop(0, SKY_DEEP);
945
+ grad.addColorStop(0.55, SKY);
946
+ g.fillStyle = grad;
947
+ g.fillRect(0, 0, width, ROWS * T);
948
+ const par = camera * 0.35;
949
+ g.fillStyle = "#1E7C34";
950
+ for (let i = 0; i < 14; i++) {
951
+ const x = ((i * 640 - par) % (LEVEL_WIDTH * 0.6) + LEVEL_WIDTH * 0.6) % (LEVEL_WIDTH * 0.6) - 200;
952
+ const r = i % 2 ? 150 : 96;
953
+ g.beginPath();
954
+ g.arc(x, GROUND_ROW * T, r, Math.PI, 0);
955
+ g.arc(x + r * 0.75, GROUND_ROW * T, r * 0.7, Math.PI, 0);
956
+ g.fill();
957
+ }
958
+ const cpar = camera * 0.18;
959
+ g.fillStyle = "#FFFFFF";
960
+ for (let i = 0; i < 12; i++) {
961
+ const x = ((i * 730 + 120 - cpar) % (LEVEL_WIDTH * 0.5) + LEVEL_WIDTH * 0.5) % (LEVEL_WIDTH * 0.5) - 150;
962
+ const y = 40 + i % 3 * 46;
963
+ const s2 = i % 2 ? 1 : 0.75;
964
+ for (const [dx, dy, r] of [
965
+ [0, 0, 26],
966
+ [30, -12, 34],
967
+ [64, 0, 26],
968
+ [32, 10, 30]
969
+ ]) {
970
+ g.beginPath();
971
+ g.arc(x + dx * s2, y + dy * s2, r * s2, 0, Math.PI * 2);
972
+ g.fill();
973
+ }
974
+ }
975
+ }
976
+ function drawFlagAndCastle(g, f, width) {
977
+ const poleX = LEVEL.flagCol * T - f.camera + T * 0.4;
978
+ if (poleX > -80 && poleX < width + 80) {
979
+ const top = 1.6 * T;
980
+ const bottom = GROUND_ROW * T;
981
+ g.fillStyle = "#94A3B8";
982
+ g.fillRect(poleX - 6, top, 4, bottom - top);
983
+ g.fillRect(poleX + 10, top, 4, bottom - top);
984
+ g.fillStyle = "#64748B";
985
+ for (let y = top + 10; y < bottom; y += 26) {
986
+ g.fillRect(poleX - 6, y, 20, 3);
987
+ g.save();
988
+ g.translate(poleX - 4, y + 3);
989
+ g.rotate(Math.atan2(23, 16));
990
+ g.fillRect(0, 0, 28, 2);
991
+ g.restore();
992
+ }
993
+ g.fillStyle = f.index % 8 < 4 ? "#F5453D" : "#7A1F1B";
994
+ g.fillRect(poleX - 4, top - 14, 16, 14);
995
+ const drop = f.phase === "run" ? 0 : Math.min(1, (f.heroY - 2.2 * T) / (GROUND_ROW * T - 2.2 * T));
996
+ const fy = 1.9 * T + drop * (GROUND_ROW * T - 2.9 * T);
997
+ g.fillStyle = "#22D3EE";
998
+ g.beginPath();
999
+ g.moveTo(poleX - 6, fy);
1000
+ g.lineTo(poleX - 66, fy + 22);
1001
+ g.lineTo(poleX - 6, fy + 44);
1002
+ g.closePath();
1003
+ g.fill();
1004
+ }
1005
+ const cx = LEVEL.castleCol * T - f.camera;
1006
+ if (cx > -400 && cx < width + 400) {
1007
+ const cw = CASTLE_W;
1008
+ const top = CASTLE_TOP;
1009
+ const ground = GROUND_ROW * T;
1010
+ g.fillStyle = "#1E293B";
1011
+ g.fillRect(cx, top, cw, ground - top);
1012
+ g.fillStyle = "#334155";
1013
+ g.fillRect(cx - 8, top - 16, cw + 16, 16);
1014
+ for (let row = 0; row < 3; row++) {
1015
+ for (let col = 0; col < 6; col++) {
1016
+ const x = cx + 18 + col * ((cw - 36) / 6);
1017
+ const y = top + 24 + row * 34;
1018
+ g.fillStyle = "#0F172A";
1019
+ g.fillRect(x, y, (cw - 36) / 6 - 8, 22);
1020
+ const on = (col * 7 + row * 3 + Math.floor(f.index / 3)) % 5;
1021
+ g.fillStyle = on === 0 ? "#FFB020" : on < 3 ? "#34D399" : "#14532D";
1022
+ g.fillRect(x + 6, y + 8, 8, 6);
1023
+ }
1024
+ }
1025
+ g.fillStyle = "#0F172A";
1026
+ g.fillRect(cx + cw / 2 - T * 0.35, ground - T * 1.15, T * 0.7, T * 1.15);
1027
+ g.fillStyle = "#38BDF8";
1028
+ g.fillRect(cx + cw / 2 + T * 0.2, ground - T * 0.6, 5, 5);
1029
+ const px = cx + cw / 2;
1030
+ g.fillStyle = "#94A3B8";
1031
+ g.fillRect(px - 3, top - 112, 6, 96);
1032
+ if (f.castleFlag > 0) {
1033
+ const fy = top - 34 - f.castleFlag * 58;
1034
+ g.fillStyle = "#8B5CF6";
1035
+ g.fillRect(px + 3, fy, 54, 34);
1036
+ g.fillStyle = "#EDE9FF";
1037
+ g.fillRect(px + 14, fy + 15, 32, 4);
1038
+ }
1039
+ }
1040
+ }
1041
+ function hud(g, text, x, y, px) {
1042
+ drawPixelText(g, text, x + px, y + px, px, "rgba(0,0,0,0.45)");
1043
+ drawPixelText(g, text, x, y, px, "#FFFFFF");
1044
+ }
1045
+ function drawHud(g, f, total, width, texts) {
1046
+ const y = 22;
1047
+ const coins = String(f.coins).padStart(6, "0");
1048
+ const left = Math.max(0, Math.ceil((total - f.index) / total * 400));
1049
+ const time = String(left).padStart(3, "0");
1050
+ const title = pixelSafe(texts.title);
1051
+ const world = pixelSafe(texts.world);
1052
+ const timeLabel = pixelSafe(texts.time);
1053
+ for (const px of [4, 3, 2]) {
1054
+ const leftW = Math.max(pixelWidth(title, px), pixelWidth(coins, px));
1055
+ const rightW = Math.max(pixelWidth(timeLabel, px), pixelWidth(time, px));
1056
+ const level = pixelSafe(texts.level ?? DEFAULT_TEXTS.level);
1057
+ const worldW = Math.max(pixelWidth(world, px), pixelWidth(level, px));
1058
+ const margin = 10 * px;
1059
+ const rightC = width - margin - rightW / 2;
1060
+ const withWorld = margin + leftW + 2 * margin + worldW + 2 * margin + rightW + margin <= width;
1061
+ if (!withWorld && margin + leftW + 2 * margin + rightW + margin > width && px > 2) continue;
1062
+ const line2 = y + 9 * px;
1063
+ const centre = (t2, cx) => cx - pixelWidth(t2, px) / 2;
1064
+ hud(g, title, margin, y, px);
1065
+ hud(g, coins, margin, line2, px);
1066
+ if (withWorld) {
1067
+ hud(g, world, centre(world, width / 2), y, px);
1068
+ hud(g, level, centre(level, width / 2), line2, px);
1069
+ }
1070
+ hud(g, timeLabel, centre(timeLabel, rightC), y, px);
1071
+ hud(g, time, centre(time, rightC), line2, px);
1072
+ return;
1073
+ }
1074
+ }
1075
+ function drawFrame(g, f, total, width, texts = DEFAULT_TEXTS) {
1076
+ if (f.phase === "retry") {
1077
+ drawRetryCard(g, width, WORLD_H, texts);
1078
+ return;
1079
+ }
1080
+ drawBackdrop(g, f.camera, width);
1081
+ for (const b of LEVEL.blocks) {
1082
+ const sx = b.col * T - f.camera;
1083
+ if (sx < -T || sx > width) continue;
1084
+ drawBlock(g, b, sx, b.row * T, f.index);
1085
+ }
1086
+ for (const p of LEVEL.pipes) {
1087
+ const sx = p.col * T - f.camera;
1088
+ if (sx < -2 * T || sx > width) continue;
1089
+ drawPipe(g, p, sx);
1090
+ }
1091
+ drawFlagAndCastle(g, f, width);
1092
+ drawBugs(g, f, width);
1093
+ drawHero(g, f);
1094
+ drawHud(g, f, total, width, texts);
1095
+ }
1096
+ function runnerPlayer(width, height, seed, texts = DEFAULT_TEXTS) {
1097
+ const k = height / WORLD_H;
1098
+ const view = Math.max(1, Math.ceil(width / k));
1099
+ const frames = simulate(view, seed);
1100
+ return {
1101
+ frames: frames.length,
1102
+ fate: fateFor(seed),
1103
+ draw(g, i) {
1104
+ const f = frames[Math.max(0, Math.min(frames.length - 1, i))];
1105
+ g.fillStyle = "#000000";
1106
+ g.fillRect(0, 0, width, height);
1107
+ g.save();
1108
+ g.scale(k, k);
1109
+ drawFrame(g, f, frames.length, view, texts);
1110
+ g.restore();
1111
+ }
1112
+ };
1113
+ }
1114
+
1115
+ // src/source.ts
1116
+ import { existsSync as existsSync3, watch } from "node:fs";
1117
+ import { homedir as homedir2 } from "node:os";
1118
+ import { join as join3 } from "node:path";
1119
+
1120
+ // ../../kiosko/packages/agent-sessions/src/git.ts
1121
+ import { execFile } from "node:child_process";
1122
+ import { readFileSync as readFileSync2, statSync, existsSync as existsSync2 } from "node:fs";
1123
+ import { basename } from "node:path";
1124
+ import { promisify } from "node:util";
1125
+ var execFileAsync = promisify(execFile);
1126
+ var NEGATIVE_TTL_MS = 10 * 6e4;
1127
+ function repoRootCache(run, now = Date.now) {
1128
+ const known = /* @__PURE__ */ new Map();
1129
+ return async (cwd) => {
1130
+ const hit = known.get(cwd);
1131
+ if (hit && (hit.root !== null || now() - hit.at < NEGATIVE_TTL_MS)) return hit.root;
1132
+ const out = (await run(cwd))?.trim() || null;
1133
+ known.set(cwd, { root: out, at: now() });
1134
+ return out;
1135
+ };
1136
+ }
1137
+ function parseHead(out) {
1138
+ const [ct, refs = ""] = out.trim().split("\n");
1139
+ const secs = Number(ct);
1140
+ if (!ct || !Number.isFinite(secs)) return null;
1141
+ return { detached: !/\bHEAD -> /.test(refs), lastCommit: secs * 1e3 };
1142
+ }
1143
+ function headCache(run, ttlMs = 6e4, now = Date.now) {
1144
+ const known = /* @__PURE__ */ new Map();
1145
+ return async (root) => {
1146
+ const hit = known.get(root);
1147
+ if (hit && now() - hit.at < ttlMs) return hit.info;
1148
+ const out = await run(root);
1149
+ const info = out ? parseHead(out) : null;
1150
+ known.set(root, { info, at: now() });
1151
+ return info;
1152
+ };
1153
+ }
1154
+ var git = (args, timeout = 2e3) => async (cwd) => {
1155
+ try {
1156
+ return (await execFileAsync("git", args, { cwd, timeout })).stdout;
1157
+ } catch {
1158
+ return null;
1159
+ }
1160
+ };
1161
+ function parseStatus(out) {
1162
+ const lines = out.split("\n").filter((l) => l.trim().length > 0);
1163
+ const files = lines.map((l) => l.slice(3).split(" -> ").pop().replace(/^"|"$/g, ""));
1164
+ return { count: lines.length, files };
1165
+ }
1166
+ function createGitEnricher(run = (args) => git(args)) {
1167
+ const repoRoot = repoRootCache(run(["rev-parse", "--show-toplevel"]));
1168
+ const headOf = headCache(run(["log", "-1", "--format=%ct%n%D"]));
1169
+ const born = /* @__PURE__ */ new Map();
1170
+ const long = /* @__PURE__ */ new Map();
1171
+ async function bornOf(root) {
1172
+ const hit = born.get(root);
1173
+ if (hit && Date.now() - hit.at < 6 * 36e5) return hit.born;
1174
+ let value = null;
1175
+ if ((await run(["rev-parse", "--is-shallow-repository"])(root))?.trim() === "false") {
1176
+ const out = await run(["log", "--max-parents=0", "--format=%at", "HEAD"])(root);
1177
+ const times = (out ?? "").split("\n").map(Number).filter((n) => n > 0);
1178
+ if (times.length) value = Math.min(...times) * 1e3;
1179
+ }
1180
+ born.set(root, { born: value, at: Date.now() });
1181
+ return value;
1182
+ }
1183
+ function isSpaghetti(path) {
1184
+ if (!/\.(ts|tsx|js|jsx|mjs|py|go|rs|java|kt|cs|php|rb|c|cc|cpp|h|swift)$/.test(path)) return false;
1185
+ try {
1186
+ const st = statSync(path);
1187
+ const hit = long.get(path);
1188
+ if (hit && hit.mtime === st.mtimeMs) return hit.long;
1189
+ const value = st.size < 2e6 && longestDefinition(readFileSync2(path, "utf8")) > SPAGHETTI_LINES;
1190
+ if (long.size > 500) long.clear();
1191
+ long.set(path, { mtime: st.mtimeMs, long: value });
1192
+ return value;
1193
+ } catch {
1194
+ return false;
1195
+ }
1196
+ }
1197
+ return async function enrich(rec2) {
1198
+ const out = { ...rec2 };
1199
+ if (!rec2.cwd || !existsSync2(rec2.cwd)) return out;
1200
+ const root = await repoRoot(rec2.cwd);
1201
+ if (root) {
1202
+ out.repo = root;
1203
+ out.project = basename(root);
1204
+ const head = await headOf(root);
1205
+ if (head?.detached) out.detached = true;
1206
+ if (head?.lastCommit) out.lastCommit = head.lastCommit;
1207
+ const b = await bornOf(root);
1208
+ if (b) out.repoBorn = b;
1209
+ }
1210
+ if (rec2.files?.some(isSpaghetti)) out.spaghetti = true;
1211
+ const status = await run(["status", "--porcelain"])(rec2.cwd);
1212
+ if (status !== null) {
1213
+ const s2 = parseStatus(status);
1214
+ out.dirty = s2.count;
1215
+ out.dirtyFiles = s2.files;
1216
+ }
1217
+ return out;
1218
+ };
1219
+ }
1220
+
1221
+ // src/source.ts
1222
+ function resolveDir(dir, home = homedir2()) {
1223
+ const d = dir.trim();
1224
+ if (!d) return defaultSessionsDir();
1225
+ if (d === "~") return home;
1226
+ if (d.startsWith("~/")) return join3(home, d.slice(2));
1227
+ return d;
1228
+ }
1229
+ async function loadSessions(o, enrich, now = Date.now(), onReap) {
1230
+ const records = readSessions(o.dir);
1231
+ const alive = o.reap ? reap(records, now, o, o.dir, onReap) : pruneZombies(records, now, o.zombieMs, o.staleMs);
1232
+ if (!enrich) return alive;
1233
+ return Promise.all(alive.map((r) => enrich(r).catch(() => r)));
1234
+ }
1235
+ function watchSessions(o, h) {
1236
+ const enrich = o.git ? createGitEnricher() : void 0;
1237
+ const now = h.now ?? Date.now;
1238
+ let stopped = false;
1239
+ let running = false;
1240
+ let again = false;
1241
+ let debounce;
1242
+ let watcher;
1243
+ const reload = async () => {
1244
+ if (stopped) return;
1245
+ if (running) {
1246
+ again = true;
1247
+ return;
1248
+ }
1249
+ running = true;
1250
+ try {
1251
+ const t2 = now();
1252
+ const sessions = await loadSessions(o, enrich, t2, (r) => h.log(`dropped dead session ${r.project} (${r.state}, silent for ${Math.round((t2 - r.updated) / 6e4)} min)`));
1253
+ if (!stopped) h.emit({ sessions, zombieMs: o.zombieMs });
1254
+ } catch (err) {
1255
+ h.log(`reading ${o.dir} failed: ${err instanceof Error ? err.message : String(err)}`);
1256
+ } finally {
1257
+ running = false;
1258
+ if (again && !stopped) {
1259
+ again = false;
1260
+ void reload();
1261
+ }
1262
+ }
1263
+ };
1264
+ const schedule = () => {
1265
+ if (debounce) clearTimeout(debounce);
1266
+ debounce = setTimeout(() => void reload(), o.debounceMs);
1267
+ };
1268
+ const arm = () => {
1269
+ if (watcher || stopped || !existsSync3(o.dir)) return;
1270
+ try {
1271
+ const w = watch(o.dir, (_event, name) => {
1272
+ if (!name || String(name).endsWith(".json")) schedule();
1273
+ });
1274
+ w.on("error", () => {
1275
+ w.close();
1276
+ if (watcher === w) watcher = void 0;
1277
+ });
1278
+ watcher = w;
1279
+ } catch (err) {
1280
+ h.log(`cannot watch ${o.dir}: ${err instanceof Error ? err.message : String(err)}; polling only`);
1281
+ }
1282
+ };
1283
+ arm();
1284
+ void reload();
1285
+ const poll = setInterval(() => {
1286
+ arm();
1287
+ void reload();
1288
+ }, Math.max(1e3, o.pollMs));
1289
+ poll.unref?.();
1290
+ const stop = () => {
1291
+ stopped = true;
1292
+ if (debounce) clearTimeout(debounce);
1293
+ clearInterval(poll);
1294
+ watcher?.close();
1295
+ watcher = void 0;
1296
+ };
1297
+ h.signal?.addEventListener("abort", stop, { once: true });
1298
+ return stop;
1299
+ }
1300
+
1301
+ // src/index.ts
1302
+ var sessionsSource = defineSource({
1303
+ id: "sessions",
1304
+ name: { en: "AI agent sessions", es: "Sesiones de agentes IA" },
1305
+ description: {
1306
+ en: "Claude Code, Gemini CLI\u2026 sessions as written by the agent-sessions hook, with git status.",
1307
+ es: "Las sesiones de Claude Code, Gemini CLI\u2026 que escribe el hook de agent-sessions, con el estado de git."
1308
+ },
1309
+ options: t.object({
1310
+ dir: t.string({
1311
+ default: "~/.aimonitor/sessions",
1312
+ format: "path",
1313
+ risk: "path",
1314
+ label: { en: "Sessions folder", es: "Carpeta de sesiones" },
1315
+ help: { en: "Where the hook writes one JSON file per session. Empty: $AGENT_SESSIONS_DIR or ~/.aimonitor/sessions.", es: "Donde el hook escribe un JSON por sesi\xF3n. Vac\xEDo: $AGENT_SESSIONS_DIR o ~/.aimonitor/sessions." }
1316
+ }),
1317
+ git: t.boolean({
1318
+ default: true,
1319
+ label: { en: "Read git status", es: "Leer el estado de git" },
1320
+ help: { en: "Repository name and uncommitted files of each session (runs git).", es: "Nombre del repositorio y ficheros sin commit de cada sesi\xF3n (ejecuta git)." }
1321
+ }),
1322
+ zombieMs: t.duration({
1323
+ default: 15 * 6e4,
1324
+ min: 6e4,
1325
+ label: { en: "Drop silent working sessions after", es: "Descartar sesiones activas calladas tras" },
1326
+ help: {
1327
+ en: "A session that says it is working and writes nothing for this long is dead. The cats room turns it into Schr\xF6dinger's box at half of this.",
1328
+ es: "Una sesi\xF3n que dice estar trabajando y no escribe nada en este tiempo est\xE1 muerta. La habitaci\xF3n de gatos la convierte en la caja de Schr\xF6dinger a la mitad."
1329
+ }
1330
+ }),
1331
+ staleMs: t.duration({
1332
+ default: 2 * 36e5,
1333
+ min: 6e4,
1334
+ label: { en: "Drop any silent session after", es: "Descartar cualquier sesi\xF3n callada tras" },
1335
+ help: { en: "The net under SessionEnd, which never arrives if the terminal is killed. Raise it if you leave permission prompts waiting for hours.", es: "La red bajo SessionEnd, que no llega si se mata el terminal. S\xFAbelo si dejas permisos esperando horas." }
1336
+ }),
1337
+ reap: t.boolean({
1338
+ default: true,
1339
+ advanced: true,
1340
+ label: { en: "Delete files of dead sessions", es: "Borrar los ficheros de sesiones muertas" },
1341
+ help: { en: "Safe: a live session writes its file again on its next event.", es: "Es seguro: una sesi\xF3n viva vuelve a escribir su fichero en el siguiente evento." }
1342
+ }),
1343
+ debounceMs: t.integer({ default: 200, min: 0, max: 5e3, unit: "ms", advanced: true, label: { en: "Wait for a burst of events", es: "Esperar a que acabe una r\xE1faga de eventos" } }),
1344
+ pollMs: t.duration({ default: 3e4, min: 5e3, advanced: true, label: { en: "Re-read every", es: "Releer cada" }, help: { en: "Fallback to file watching; also refreshes the git counts.", es: "Respaldo de la vigilancia de ficheros; tambi\xE9n refresca los contadores de git." } })
1345
+ }),
1346
+ watch(ctx, emit) {
1347
+ const o = ctx.options;
1348
+ return watchSessions({ ...o, dir: resolveDir(o.dir) }, { emit, log: ctx.log, signal: ctx.signal });
1349
+ },
1350
+ demo: ({ now }) => ({ sessions: scenario("threeClaim", now), zombieMs: 15 * 6e4 })
1351
+ });
1352
+ var STATES = ["active", "idle", "ready", "waiting", "permission"];
1353
+ var projectsField = () => t.filter({
1354
+ label: { en: "Projects", es: "Proyectos" },
1355
+ help: {
1356
+ en: "Empty: all. Project, repository or folder names or patterns, comma-separated: * matches anything, a leading ! leaves out (web-app, acme-*, !scratch).",
1357
+ es: "Vac\xEDo: todos. Nombres o patrones de proyecto, repositorio o carpeta, separados por comas: * vale por cualquier cosa y un ! delante excluye (web-app, acme-*, !pruebas)."
1358
+ }
1359
+ });
1360
+ var sessionsOptions = t.object({
1361
+ projects: projectsField(),
1362
+ hideStates: t.list(t.enum(STATES, { labels: STATE_LABEL }), {
1363
+ label: { en: "Hide sessions that are", es: "Ocultar las sesiones en estado" },
1364
+ help: { en: "E.g. Active and Idle, to see only the ones that claim you.", es: "Por ejemplo, Activa e Inactiva, para ver s\xF3lo las que te reclaman." }
1365
+ }),
1366
+ maxTiles: t.integer({
1367
+ default: 0,
1368
+ min: 0,
1369
+ max: 12,
1370
+ label: { en: "Most tiles", es: "Casillas como mucho" },
1371
+ help: { en: "0 = as many as fit. The rest go to the summary column, so the tiles get wider.", es: "0 = las que quepan. El resto va a la columna de resumen, as\xED que las casillas se ensanchan." }
1372
+ }),
1373
+ tileMin: t.integer({ default: 320, min: 120, max: 1e3, unit: "px", label: { en: "Narrowest tile", es: "Casilla m\xE1s estrecha" }, help: { en: "At a 374 px tall widget; scales with the height. Sessions that would need narrower tiles go to the summary column.", es: "Para un widget de 374 px de alto; escala con la altura. Las sesiones que no quepan van a la columna de resumen." } }),
1374
+ tileMax: t.integer({ default: 430, min: 0, max: 2e3, unit: "px", label: { en: "Widest tile", es: "Casilla m\xE1s ancha" }, help: { en: "So a single session doesn\u2019t stretch across the panel. 0 = no limit: the tiles fill the width.", es: "Para que una sola sesi\xF3n no se estire a todo el panel. 0 = sin l\xEDmite: las casillas llenan el ancho." } }),
1375
+ gap: t.integer({ default: 12, min: 0, max: 60, unit: "px", label: { en: "Gap between tiles", es: "Separaci\xF3n entre casillas" } }),
1376
+ detailLines: t.integer({ default: 3, min: 0, max: 6, slider: true, label: { en: "Detail lines", es: "L\xEDneas de detalle" }, help: { en: "Lines of the last tool call or message before cutting with an ellipsis. 0 hides it.", es: "L\xEDneas de la \xFAltima herramienta o mensaje antes de cortar con puntos suspensivos. 0 lo oculta." } }),
1377
+ spineWidth: t.integer({ default: 7, min: 2, max: 30, unit: "px", advanced: true, label: { en: "Colour spine width", es: "Ancho de la espina de color" } }),
1378
+ summaryWidth: t.integer({ default: 150, min: 80, max: 400, unit: "px", advanced: true, label: { en: "Summary column width", es: "Ancho de la columna de resumen" } }),
1379
+ align: t.enum(["left", "center", "right"], { label: { en: "Alignment", es: "Alineaci\xF3n" }, labels: { left: { en: "Left", es: "Izquierda" }, center: { en: "Centre", es: "Centro" }, right: { en: "Right", es: "Derecha" } } }),
1380
+ showGit: t.boolean({ default: true, label: { en: "Show uncommitted files", es: "Mostrar ficheros sin commit" } }),
1381
+ showId: t.boolean({ default: true, label: { en: "Show the console id", es: "Mostrar el id de la consola" }, help: { en: "PROVIDER \xB7 ABC123 in the corner, to tell two consoles of one project apart.", es: "PROVEEDOR \xB7 ABC123 en la esquina, para distinguir dos consolas del mismo proyecto." } })
1382
+ });
1383
+ var sessionsWidget = defineWidget({
1384
+ id: "sessions",
1385
+ name: { en: "Agent sessions", es: "Sesiones de agentes" },
1386
+ description: {
1387
+ en: "One tile per AI coding session. The one that needs you most turns solid, the others that wait get a colour spine, and working ones stay dark.",
1388
+ es: "Una casilla por sesi\xF3n de IA. La que m\xE1s te necesita se enciende entera, las dem\xE1s que esperan llevan una espina de color y las que trabajan quedan a oscuras."
1389
+ },
1390
+ icon: "bot",
1391
+ category: "agents",
1392
+ tags: ["claude", "gemini", "ai", "sessions", "git"],
1393
+ chrome: "bare",
1394
+ size: { min: { w: 400, h: 200 }, preferred: { w: 1500, h: 390 } },
1395
+ options: sessionsOptions,
1396
+ sources: [SESSIONS_SOURCE],
1397
+ redraw: "minute",
1398
+ render(g, ctx) {
1399
+ drawSessions(g, ctx, ctx.options);
1400
+ },
1401
+ urgent: ({ sources, options }) => needsPermission(filterSessions(sessionsOf(sources), options)),
1402
+ idle: ({ sources, options }) => filterSessions(sessionsOf(sources), options).length === 0,
1403
+ empty: ({ sources, options }) => filterSessions(sessionsOf(sources), options).length === 0,
1404
+ demo: ({ now }) => ({ sources: { [SESSIONS_SOURCE]: { sessions: scenario("threeClaim", now) } } })
1405
+ });
1406
+ var summaryWidget = defineWidget({
1407
+ id: "summary",
1408
+ name: { en: "Agents summary strip", es: "Franja de resumen de agentes" },
1409
+ description: {
1410
+ en: "A thin header line: RAM, how many consoles are open, how many wait for you (tinted by the most urgent) and the clock.",
1411
+ es: "Una l\xEDnea fina de cabecera: RAM, cu\xE1ntas consolas hay abiertas, cu\xE1ntas te esperan (te\xF1ido por la m\xE1s urgente) y la hora."
1412
+ },
1413
+ icon: "activity",
1414
+ category: "agents",
1415
+ tags: ["claude", "gemini", "header"],
1416
+ chrome: "bare",
1417
+ size: { min: { w: 400, h: 32 }, preferred: { w: 1900, h: 60 } },
1418
+ options: t.object({
1419
+ projects: projectsField(),
1420
+ showRam: t.boolean({ default: true, label: { en: "Show RAM", es: "Mostrar la RAM" }, help: { en: "Needs the system vitals source.", es: "Necesita la fuente de vitales del sistema." } }),
1421
+ showConsoles: t.boolean({ default: true, label: { en: "Show how many consoles", es: "Mostrar cu\xE1ntas consolas" }, help: { en: "Off: only how many wait for you, for short strips.", es: "Apagado: s\xF3lo cu\xE1ntas te esperan, para franjas cortas." } }),
1422
+ showClock: t.boolean({ default: true, label: { en: "Show the clock", es: "Mostrar la hora" } }),
1423
+ showRule: t.boolean({ default: true, label: { en: "Rule underneath", es: "Raya debajo" } })
1424
+ }),
1425
+ sources: [SESSIONS_SOURCE, "system/vitals"],
1426
+ redraw: "minute",
1427
+ render(g, ctx) {
1428
+ drawSummaryStrip(g, ctx, ctx.options);
1429
+ },
1430
+ idle: ({ sources, options }) => filterSessions(sessionsOf(sources), options).length === 0,
1431
+ demo: ({ now }) => ({ sources: { [SESSIONS_SOURCE]: { sessions: scenario("threeClaim", now) }, "system/vitals": DEMO_VITALS } })
1432
+ });
1433
+ var runner = defineInterlude({
1434
+ id: "runner",
1435
+ name: { en: "8-bit runner", es: "Corredor de 8 bits" },
1436
+ description: {
1437
+ en: "A side-scrolling platformer run through a server room, in flat colours. Every slot plays a different ending; now and then the runner ships the release.",
1438
+ es: "Una carrera de plataformas de scroll lateral por una sala de servidores, en colores planos. Cada franja acaba distinto; de vez en cuando el corredor saca la versi\xF3n."
1439
+ },
1440
+ icon: "gamepad",
1441
+ options: t.object({
1442
+ title: t.string({ default: DEFAULT_TEXTS.title, maxLength: 12, label: { en: "Name in the score", es: "Nombre en el marcador" }, help: { en: "Letters, digits and - . ! ?", es: "Letras, cifras y - . ! ?" } })
1443
+ }),
1444
+ sources: [SESSIONS_SOURCE],
1445
+ fps: 12,
1446
+ async prepare(ctx) {
1447
+ if (ctx.width < 8 || ctx.height < 8) return null;
1448
+ const texts = {
1449
+ title: ctx.options.title.trim() || DEFAULT_TEXTS.title,
1450
+ world: ctx.t({ en: "RELEASE", es: "VERSION" }),
1451
+ level: levelFor(ctx.seed),
1452
+ time: ctx.t({ en: "TIME", es: "TIEMPO" }),
1453
+ retry: ctx.t({ en: "TRY AGAIN", es: "OTRA VEZ SERA" })
1454
+ };
1455
+ const p = runnerPlayer(ctx.width, ctx.height, ctx.seed, texts);
1456
+ return { frames: p.frames, draw: p.draw };
1457
+ },
1458
+ // A session blocked on a permission prompt is no time to play.
1459
+ skip: ({ sources }) => needsPermission(sessionsOf(sources))
1460
+ });
1461
+ var index_default = definePlugin({
1462
+ id: "agents",
1463
+ name: { en: "AI agents", es: "Agentes IA" },
1464
+ version: "0.2.0",
1465
+ description: {
1466
+ en: "Claude Code and Gemini CLI sessions at a glance: who waits for you, for how long, and git status. Ported from aimonitor.",
1467
+ es: "Las sesiones de Claude Code y Gemini CLI de un vistazo: qui\xE9n te espera, desde cu\xE1ndo y el estado de git. Portado de aimonitor."
1468
+ },
1469
+ author: "Kiosko",
1470
+ license: "MIT",
1471
+ icon: "bot",
1472
+ widgets: [sessionsWidget, summaryWidget],
1473
+ sources: [sessionsSource],
1474
+ interludes: [runner]
1475
+ });
1476
+ export {
1477
+ assignWeights,
1478
+ index_default as default,
1479
+ plan,
1480
+ runner,
1481
+ sessionsSource,
1482
+ sessionsWidget,
1483
+ spareWidth,
1484
+ summaryWidget
1485
+ };