roger-roger 0.1.3 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +1 -1
- package/skills/roger-roger/SKILL.md +7 -4
- package/skills/roger-roger/herdr-plugin.toml +1 -1
- package/skills/roger-roger/scripts/activity.mjs +92 -0
- package/skills/roger-roger/scripts/audio.mjs +123 -19
- package/skills/roger-roger/scripts/daemon.mjs +5 -3
- package/skills/roger-roger/scripts/decisions.mjs +9 -0
- package/skills/roger-roger/scripts/handlers.mjs +29 -10
- package/skills/roger-roger/scripts/help.mjs +3 -2
- package/skills/roger-roger/scripts/herdr.mjs +47 -2
- package/skills/roger-roger/scripts/inbox.mjs +16 -0
- package/skills/roger-roger/scripts/install.mjs +130 -2
- package/skills/roger-roger/scripts/lib.mjs +57 -2
- package/skills/roger-roger/scripts/roger-roger.mjs +1 -0
- package/skills/roger-roger/scripts/tray.mjs +5 -0
- package/skills/roger-roger/tray/main.mjs +5 -1
- package/skills/roger-roger/tray/panel.html +167 -2
|
@@ -141,6 +141,40 @@
|
|
|
141
141
|
.slider input[type="range"] { flex: 1; accent-color: var(--accent); padding: 0; border: 0; background: transparent; }
|
|
142
142
|
.slider output { width: 40px; text-align: right; font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
|
|
143
143
|
.flash.is-ok { border-color: #7fd1a0; }
|
|
144
|
+
|
|
145
|
+
/* The log: who said what, newest first. The agent's colour runs down the left edge. */
|
|
146
|
+
.row.is-agent { cursor: pointer; }
|
|
147
|
+
.filters { display: flex; gap: 4px; flex-wrap: wrap; padding: 6px 4px 8px; }
|
|
148
|
+
.entry { position: relative; padding: 8px 8px 8px 12px; border-bottom: 1px solid var(--border-subtle); cursor: pointer; }
|
|
149
|
+
.entry::before { content: ""; position: absolute; left: 0; top: 8px; bottom: 8px; width: 2px; background: var(--edge, var(--text-muted)); }
|
|
150
|
+
.entry.from-user::before { background: var(--accent); }
|
|
151
|
+
.entry:hover { background: color-mix(in srgb, var(--bg-section) 60%, transparent); }
|
|
152
|
+
.entry .top { gap: 6px; font-size: 11px; color: var(--text-dim); }
|
|
153
|
+
.entry .name { font-weight: 600; color: var(--text); }
|
|
154
|
+
.entry .arrow { color: var(--text-muted); }
|
|
155
|
+
.entry .kind { padding: 0 5px; border: 1px solid var(--border-subtle); font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted); }
|
|
156
|
+
.entry .kind.k-blocked, .entry .kind.k-error, .entry .kind.k-question { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
|
|
157
|
+
.entry .kind.k-done, .entry .kind.k-answer { color: #7fd1a0; border-color: #7fd1a055; }
|
|
158
|
+
.entry .text { margin-top: 4px; line-height: 1.4; color: var(--text); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
|
|
159
|
+
.entry.is-open .text { display: block; }
|
|
160
|
+
.entry .detail { margin-top: 3px; font-size: 11px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
161
|
+
.entry.is-open .detail { white-space: normal; }
|
|
162
|
+
.day { padding: 12px 8px 4px; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); }
|
|
163
|
+
|
|
164
|
+
/* On air: shown only while a voice or sound plays, with the one thing you might want — stop it. */
|
|
165
|
+
.onair { display: flex; align-items: center; gap: 10px; padding: 8px 10px 8px 14px; border-bottom: 1px solid var(--border-subtle); background: color-mix(in srgb, var(--accent) 12%, transparent); }
|
|
166
|
+
.onair[hidden] { display: none; }
|
|
167
|
+
.onair .bars { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
|
|
168
|
+
.onair .bars i { width: 3px; background: var(--accent); animation: level 900ms ease-in-out infinite; }
|
|
169
|
+
.onair .bars i:nth-child(2) { animation-delay: -300ms; }
|
|
170
|
+
.onair .bars i:nth-child(3) { animation-delay: -600ms; }
|
|
171
|
+
@keyframes level { 0%, 100% { height: 4px; } 50% { height: 14px; } }
|
|
172
|
+
@media (prefers-reduced-motion: reduce) { .onair .bars i { animation: none; height: 10px; } }
|
|
173
|
+
.onair .label { flex: 1; min-width: 0; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
174
|
+
.onair .label strong { font-weight: 600; }
|
|
175
|
+
.onair button { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 11px; font-weight: 600; color: #101317; background: var(--accent); border-color: var(--accent); }
|
|
176
|
+
.onair button:hover { filter: brightness(1.08); }
|
|
177
|
+
.onair button svg { width: 11px; height: 11px; fill: currentColor; stroke: none; }
|
|
144
178
|
svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
|
145
179
|
</style>
|
|
146
180
|
</head>
|
|
@@ -151,6 +185,9 @@
|
|
|
151
185
|
<span class="count" id="count"></span>
|
|
152
186
|
<span class="spacer"></span>
|
|
153
187
|
<span class="badge" id="badge" hidden></span>
|
|
188
|
+
<button class="icon-btn icon-btn-sm" id="log" title="Log: who said what">
|
|
189
|
+
<svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path><path d="M8 9h8"></path><path d="M8 13h5"></path></svg>
|
|
190
|
+
</button>
|
|
154
191
|
<button class="icon-btn icon-btn-sm" id="pin" title="Keep open when I click elsewhere">
|
|
155
192
|
<svg viewBox="0 0 24 24"><path d="M12 17v5"></path><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"></path></svg>
|
|
156
193
|
</button>
|
|
@@ -158,6 +195,13 @@
|
|
|
158
195
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6h.09A1.65 1.65 0 0 0 10 3.09V3a2 2 0 1 1 4 0v.09A1.65 1.65 0 0 0 15 4.6a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
159
196
|
</button>
|
|
160
197
|
</header>
|
|
198
|
+
<div class="onair" id="onair" hidden>
|
|
199
|
+
<span class="bars" aria-hidden="true"><i></i><i></i><i></i></span>
|
|
200
|
+
<span class="label" id="onair-label"></span>
|
|
201
|
+
<button data-do="hush" title="Stop this announcement (the Slack message stays)">
|
|
202
|
+
<svg viewBox="0 0 24 24"><rect x="5" y="5" width="14" height="14"></rect></svg>Stop
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
161
205
|
<main id="body"></main>
|
|
162
206
|
<div class="flash" id="flash" hidden></div>
|
|
163
207
|
<footer id="actions"></footer>
|
|
@@ -168,9 +212,15 @@
|
|
|
168
212
|
const plural = (n, one, many) => n + " " + (n === 1 ? one : many);
|
|
169
213
|
const send = (body) => window.ipc.postMessage(JSON.stringify(body));
|
|
170
214
|
|
|
171
|
-
let latest = { agents: [], questions: [], working: [] };
|
|
215
|
+
let latest = { agents: [], questions: [], working: [], log: [] };
|
|
172
216
|
let settingsOpen = false;
|
|
173
217
|
let advancedOpen = false;
|
|
218
|
+
// The log view, which agent it is narrowed to (null: everyone), and which entries are opened out.
|
|
219
|
+
let logOpen = false;
|
|
220
|
+
let logFilter = null;
|
|
221
|
+
// Stop was pressed: hide the strip at once rather than waiting for the daemon to say it stopped.
|
|
222
|
+
let hushedAt = 0;
|
|
223
|
+
const openEntries = new Set();
|
|
174
224
|
|
|
175
225
|
function ago(ms) {
|
|
176
226
|
const s = Math.round((ms ?? 0) / 1000);
|
|
@@ -197,7 +247,7 @@
|
|
|
197
247
|
if (a.queued) bits.push(plural(a.queued, "message", "messages") + " unread");
|
|
198
248
|
if (a.owes) bits.push(plural(a.owes, "answer", "answers") + " to collect");
|
|
199
249
|
return (
|
|
200
|
-
'<div class="row"><div class="top">' +
|
|
250
|
+
'<div class="row is-agent" data-agent="' + esc(a.nickname) + '" title="Show what ' + esc(a.nickname) + ' said"><div class="top">' +
|
|
201
251
|
'<span class="status-dot"' + colour + "></span>" +
|
|
202
252
|
'<span class="who">' + esc(a.nickname) + "</span>" +
|
|
203
253
|
'<span class="state">' + esc(a.state) + "</span>" +
|
|
@@ -217,6 +267,70 @@
|
|
|
217
267
|
);
|
|
218
268
|
}
|
|
219
269
|
|
|
270
|
+
// ---------------------------------------------------------------- log
|
|
271
|
+
|
|
272
|
+
const KIND_LABEL = { done: "done", blocked: "blocked", input: "needs you", milestone: "milestone", error: "error", info: "info", question: "question", answer: "answer", message: "message", cancelled: "cancelled", expired: "expired" };
|
|
273
|
+
|
|
274
|
+
function clock(iso) {
|
|
275
|
+
const d = new Date(iso);
|
|
276
|
+
return isFinite(d) ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function dayOf(iso) {
|
|
280
|
+
const d = new Date(iso);
|
|
281
|
+
if (!isFinite(d)) return "";
|
|
282
|
+
const today = new Date();
|
|
283
|
+
const yesterday = new Date(today.getTime() - 864e5);
|
|
284
|
+
if (d.toDateString() === today.toDateString()) return "Today";
|
|
285
|
+
if (d.toDateString() === yesterday.toDateString()) return "Yesterday";
|
|
286
|
+
return d.toLocaleDateString([], { weekday: "long", day: "numeric", month: "short" });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function entryRow(e, i) {
|
|
290
|
+
const name = (a) => '<span class="name">' + esc(a ? (a.swatch ? a.swatch + " " : "") + a.nickname : "an agent") + "</span>";
|
|
291
|
+
const who = e.from === "user"
|
|
292
|
+
? '<span class="name">you</span>' + (e.agent ? '<span class="arrow">→</span>' + name(e.agent) : "")
|
|
293
|
+
: name(e.agent) + '<span class="arrow">→</span><span class="name">you</span>';
|
|
294
|
+
const key = e.at + "|" + i;
|
|
295
|
+
const edge = e.agent && e.agent.colour ? ' style="--edge:' + esc(e.agent.colour) + '"' : "";
|
|
296
|
+
const detail = e.kind === "answer" && e.detail ? "to: " + e.detail : e.detail;
|
|
297
|
+
return (
|
|
298
|
+
'<div class="entry' + (e.from === "user" ? " from-user" : "") + (openEntries.has(key) ? " is-open" : "") + '" data-entry="' + esc(key) + '"' + edge + ">" +
|
|
299
|
+
'<div class="top">' + who +
|
|
300
|
+
'<span class="kind k-' + esc(e.kind) + '">' + esc(KIND_LABEL[e.kind] || e.kind) + "</span>" +
|
|
301
|
+
'<span class="age" title="' + esc(new Date(e.at).toLocaleString()) + '">' + clock(e.at) + "</span></div>" +
|
|
302
|
+
(e.text ? '<div class="text">' + esc(e.text) + "</div>" : "") +
|
|
303
|
+
(detail ? '<div class="detail">' + esc(detail) + "</div>" : "") +
|
|
304
|
+
"</div>"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function logView(log) {
|
|
309
|
+
const names = [...new Set(log.map((e) => e.agent && e.agent.nickname).filter(Boolean))];
|
|
310
|
+
const shown = logFilter ? log.filter((e) => e.agent && e.agent.nickname === logFilter) : log;
|
|
311
|
+
const parts = [];
|
|
312
|
+
if (names.length > 1 || logFilter) {
|
|
313
|
+
parts.push('<div class="filters">' +
|
|
314
|
+
'<button class="chip' + (logFilter ? "" : " is-active") + '" data-filter="">Everyone</button>' +
|
|
315
|
+
names.map((n) => '<button class="chip' + (logFilter === n ? " is-active" : "") + '" data-filter="' + esc(n) + '">' + esc(n) + "</button>").join("") +
|
|
316
|
+
"</div>");
|
|
317
|
+
}
|
|
318
|
+
if (!shown.length) {
|
|
319
|
+
parts.push('<p class="empty">' + (logFilter ? "Nothing from " + esc(logFilter) + " yet." : "Nothing said yet. Notifications, questions, your answers and your messages show up here.") + "</p>");
|
|
320
|
+
return parts.join("");
|
|
321
|
+
}
|
|
322
|
+
let day = "";
|
|
323
|
+
shown.forEach((e, i) => {
|
|
324
|
+
const d = dayOf(e.at);
|
|
325
|
+
if (d !== day) {
|
|
326
|
+
day = d;
|
|
327
|
+
parts.push('<p class="day">' + esc(d) + "</p>");
|
|
328
|
+
}
|
|
329
|
+
parts.push(entryRow(e, i));
|
|
330
|
+
});
|
|
331
|
+
return parts.join("");
|
|
332
|
+
}
|
|
333
|
+
|
|
220
334
|
// ---------------------------------------------------------------- settings
|
|
221
335
|
|
|
222
336
|
const setting = (label, control, hint) =>
|
|
@@ -363,6 +477,8 @@
|
|
|
363
477
|
|
|
364
478
|
document.getElementById("count").textContent = settingsOpen
|
|
365
479
|
? "settings"
|
|
480
|
+
: logOpen
|
|
481
|
+
? "log" + (logFilter ? " · " + logFilter : "")
|
|
366
482
|
: agents.length
|
|
367
483
|
? plural(agents.length, "agent", "agents") + (questions.length ? " · " + plural(questions.length, "question", "questions") : "")
|
|
368
484
|
: "nothing running";
|
|
@@ -371,12 +487,27 @@
|
|
|
371
487
|
badge.hidden = !snoozed && !quiet;
|
|
372
488
|
badge.textContent = snoozed ? "snoozed" : "quiet hours";
|
|
373
489
|
document.getElementById("gear").classList.toggle("is-active", settingsOpen);
|
|
490
|
+
document.getElementById("log").classList.toggle("is-active", logOpen && !settingsOpen);
|
|
491
|
+
|
|
492
|
+
// Who is speaking, and the Stop button, only while something is.
|
|
493
|
+
const speaking = state.speaking;
|
|
494
|
+
const onair = document.getElementById("onair");
|
|
495
|
+
onair.hidden = !speaking || hushedAt > Date.now() - 2500;
|
|
496
|
+
if (speaking) {
|
|
497
|
+
const who = speaking.who;
|
|
498
|
+
const agent = agents.find((a) => a.nickname === who);
|
|
499
|
+
document.getElementById("onair-label").innerHTML = who && agent
|
|
500
|
+
? "<strong>" + esc((agent.swatch ? agent.swatch + " " : "") + who) + "</strong> is speaking"
|
|
501
|
+
: who ? "Playing a " + esc(who) : "Speaking";
|
|
502
|
+
}
|
|
374
503
|
|
|
375
504
|
const parts = [];
|
|
376
505
|
// The tray says so when the file it reads has stopped being kept up to date.
|
|
377
506
|
if (state.stale) parts.push('<p class="stale">Daemon not running — state may be old.</p>');
|
|
378
507
|
if (settingsOpen) {
|
|
379
508
|
parts.push(settingsView(state.settings, state.options || {}));
|
|
509
|
+
} else if (logOpen) {
|
|
510
|
+
parts.push(logView(state.log ?? []));
|
|
380
511
|
} else if (!agents.length && !questions.length && !working.length) {
|
|
381
512
|
parts.push('<p class="empty">Nothing is running.</p>');
|
|
382
513
|
} else {
|
|
@@ -455,10 +586,40 @@
|
|
|
455
586
|
|
|
456
587
|
document.getElementById("gear").addEventListener("click", () => {
|
|
457
588
|
settingsOpen = !settingsOpen;
|
|
589
|
+
if (settingsOpen) logOpen = false;
|
|
590
|
+
draw();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
document.getElementById("log").addEventListener("click", () => {
|
|
594
|
+
logOpen = settingsOpen ? true : !logOpen;
|
|
595
|
+
settingsOpen = false;
|
|
596
|
+
logFilter = null;
|
|
597
|
+
document.getElementById("body").scrollTop = 0;
|
|
458
598
|
draw();
|
|
459
599
|
});
|
|
460
600
|
|
|
461
601
|
document.addEventListener("click", (e) => {
|
|
602
|
+
// An agent's row opens its log; a filter narrows the log; an entry opens out to its full text.
|
|
603
|
+
const agent = e.target.closest("[data-agent]");
|
|
604
|
+
if (agent) {
|
|
605
|
+
logOpen = true;
|
|
606
|
+
logFilter = agent.dataset.agent;
|
|
607
|
+
document.getElementById("body").scrollTop = 0;
|
|
608
|
+
return draw();
|
|
609
|
+
}
|
|
610
|
+
const filter = e.target.closest("[data-filter]");
|
|
611
|
+
if (filter) {
|
|
612
|
+
logFilter = filter.dataset.filter || null;
|
|
613
|
+
return draw();
|
|
614
|
+
}
|
|
615
|
+
const entry = e.target.closest("[data-entry]");
|
|
616
|
+
if (entry) {
|
|
617
|
+
const key = entry.dataset.entry;
|
|
618
|
+
if (openEntries.has(key)) openEntries.delete(key);
|
|
619
|
+
else openEntries.add(key);
|
|
620
|
+
entry.classList.toggle("is-open");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
462
623
|
const action = e.target.closest("[data-do]");
|
|
463
624
|
if (action) {
|
|
464
625
|
e.preventDefault();
|
|
@@ -466,6 +627,10 @@
|
|
|
466
627
|
action.disabled = true;
|
|
467
628
|
action.textContent = "Speaking…";
|
|
468
629
|
}
|
|
630
|
+
if (action.dataset.do === "hush") {
|
|
631
|
+
hushedAt = Date.now();
|
|
632
|
+
document.getElementById("onair").hidden = true;
|
|
633
|
+
}
|
|
469
634
|
return send({ do: action.dataset.do, value: action.dataset.value });
|
|
470
635
|
}
|
|
471
636
|
const chip = e.target.closest("[data-chip]");
|