@sickr/replay 0.4.6 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +143 -40
- package/dist/render.js +109 -0
- package/dist/share.js +4 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,8 +6,8 @@ import { join, dirname } from 'node:path';
|
|
|
6
6
|
import { spawn, execFileSync } from 'node:child_process';
|
|
7
7
|
import { appendEvent, loadRun, runsDir, latestRunId } from './recorder.js';
|
|
8
8
|
import { mergeHooks, removeHooks } from './hookConfig.js';
|
|
9
|
-
import { renderRunHtml } from './render.js';
|
|
10
|
-
import { buildSharePayload, publish, PublishError } from './share.js';
|
|
9
|
+
import { renderRunHtml, renderCombinedHtml } from './render.js';
|
|
10
|
+
import { buildSharePayload, buildCombinedPayload, publish, PublishError } from './share.js';
|
|
11
11
|
const REPLAY_ENDPOINT = process.env.SICKR_REPLAY_ENDPOINT ?? 'https://sickr.ai/api/replay';
|
|
12
12
|
const COMMANDS = ['init', 'record', 'open', 'list', 'share', 'stop', 'clear', 'help'];
|
|
13
13
|
export function parseCommand(argv) {
|
|
@@ -32,14 +32,17 @@ Commands:
|
|
|
32
32
|
Claude Code (.claude/settings.json)
|
|
33
33
|
--no-name label your prompts "Human" instead of your
|
|
34
34
|
login/git name (default is your login name)
|
|
35
|
-
open [run] Render a run to a local HTML timeline and open it
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
open [run] Render a run to a local HTML timeline and open it. 100% local.
|
|
36
|
+
Defaults to the newest run; pass a run id, or --codex/--claude
|
|
37
|
+
for the newest run of that agent. Combine across agents with a
|
|
38
|
+
window: --today, --since <2h|30m|1d>, or --all (interleaved,
|
|
39
|
+
filterable by agent, sortable by prompt/response time).
|
|
39
40
|
share [run] Redact and publish ONE run to a public sickr.ai/r/<id> link
|
|
40
41
|
(shows a preview and asks first). Links expire after 24h.
|
|
41
|
-
--open
|
|
42
|
-
--yes
|
|
42
|
+
--open also open the published link in your browser
|
|
43
|
+
--yes skip the confirmation prompt
|
|
44
|
+
Or publish a COMBINED multi-agent view with a window:
|
|
45
|
+
--today / --since <2h|30m|1d> / --all (+ --claude/--codex).
|
|
43
46
|
list List recorded runs, newest first.
|
|
44
47
|
stop Stop recording — removes SICKR's hooks from this project.
|
|
45
48
|
Your recorded runs are kept; run \`init\` to start again.
|
|
@@ -238,6 +241,69 @@ function handleOpen(runId, provider) {
|
|
|
238
241
|
`→ ${out} (newest run; use \`list\` to see others, \`open <id>\` to pick one)\n`);
|
|
239
242
|
openInBrowser(out);
|
|
240
243
|
}
|
|
244
|
+
function parseDur(s) {
|
|
245
|
+
const m = /^(\d+)(m|h|d)$/.exec(String(s ?? ''));
|
|
246
|
+
if (!m)
|
|
247
|
+
return null;
|
|
248
|
+
return Number(m[1]) * (m[2] === 'm' ? 60_000 : m[2] === 'h' ? 3_600_000 : 86_400_000);
|
|
249
|
+
}
|
|
250
|
+
/** Select runs by window flag (--today/--since <dur>/--all) + optional agent. Null if no window flag. */
|
|
251
|
+
export function selectWindow(rest) {
|
|
252
|
+
let pred = null;
|
|
253
|
+
let label = '';
|
|
254
|
+
const now = Date.now();
|
|
255
|
+
if (rest.includes('--all')) {
|
|
256
|
+
pred = () => true;
|
|
257
|
+
label = 'all runs';
|
|
258
|
+
}
|
|
259
|
+
else if (rest.includes('--today')) {
|
|
260
|
+
const d = new Date();
|
|
261
|
+
d.setHours(0, 0, 0, 0);
|
|
262
|
+
const t = d.getTime();
|
|
263
|
+
pred = (m) => m >= t;
|
|
264
|
+
label = 'today';
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const i = rest.indexOf('--since');
|
|
268
|
+
if (i >= 0) {
|
|
269
|
+
const ms = parseDur(rest[i + 1]);
|
|
270
|
+
if (ms == null) {
|
|
271
|
+
process.stderr.write('sickr: --since takes a duration like 2h, 30m, or 1d.\n');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
pred = (m) => m >= now - ms;
|
|
276
|
+
label = `last ${rest[i + 1]}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!pred)
|
|
280
|
+
return null;
|
|
281
|
+
const dir = runsDir();
|
|
282
|
+
const wantAgent = rest.includes('--codex') ? 'Codex' : rest.includes('--claude') ? 'Claude' : null;
|
|
283
|
+
let ids = existsSync(dir)
|
|
284
|
+
? readdirSync(dir).filter((f) => f.endsWith('.ndjson')).filter((f) => pred(statSync(join(dir, f)).mtimeMs)).map((f) => f.replace(/\.ndjson$/, ''))
|
|
285
|
+
: [];
|
|
286
|
+
if (wantAgent) {
|
|
287
|
+
ids = ids.filter((id) => runSummary(id).agent === wantAgent);
|
|
288
|
+
label += ` · ${wantAgent}`;
|
|
289
|
+
}
|
|
290
|
+
return { ids, label };
|
|
291
|
+
}
|
|
292
|
+
function handleOpenCombined(sel) {
|
|
293
|
+
const runs = sel.ids
|
|
294
|
+
.map((id) => ({ id, events: loadRun(id).events }))
|
|
295
|
+
.filter((r) => r.events.length)
|
|
296
|
+
.map((r) => ({ agent: runSummary(r.id).agent, events: r.events }));
|
|
297
|
+
if (runs.length === 0) {
|
|
298
|
+
process.stdout.write(`sickr: no runs in ${sel.label}.\n`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const out = join(homedir(), '.sickr', 'last.html');
|
|
302
|
+
mkdirSync(join(homedir(), '.sickr'), { recursive: true });
|
|
303
|
+
writeFileSync(out, renderCombinedHtml(runs, sel.label));
|
|
304
|
+
process.stdout.write(`sickr: opened combined replay (${sel.label}) · ${runs.length} runs → ${out}\n`);
|
|
305
|
+
openInBrowser(out);
|
|
306
|
+
}
|
|
241
307
|
function handleList(provider) {
|
|
242
308
|
const dir = runsDir();
|
|
243
309
|
let files = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.ndjson')) : [];
|
|
@@ -259,56 +325,80 @@ function handleList(provider) {
|
|
|
259
325
|
process.stdout.write(`${id} ${s.agent.padEnd(7)} ${String(s.events).padStart(4)} ev ${when}${snippet}\n`);
|
|
260
326
|
});
|
|
261
327
|
}
|
|
262
|
-
async function
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
328
|
+
async function confirmPublish(yes, what) {
|
|
329
|
+
if (yes)
|
|
330
|
+
return true;
|
|
331
|
+
if (!process.stdin.isTTY) {
|
|
332
|
+
process.stderr.write('sickr: re-run with --yes to publish non-interactively.\n');
|
|
266
333
|
process.exit(1);
|
|
267
|
-
return;
|
|
334
|
+
return false;
|
|
268
335
|
}
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!process.stdin.isTTY) {
|
|
275
|
-
process.stderr.write('sickr: re-run with --yes to publish non-interactively.\n');
|
|
276
|
-
process.exit(1);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
process.stdout.write('Publish this run publicly? [y/N] ');
|
|
280
|
-
const answer = await promptLine();
|
|
281
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
282
|
-
process.stdout.write('sickr: cancelled.\n');
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
336
|
+
process.stdout.write(`Publish ${what} publicly? [y/N] `);
|
|
337
|
+
const a = await promptLine();
|
|
338
|
+
if (a !== 'y' && a !== 'yes') {
|
|
339
|
+
process.stdout.write('sickr: cancelled.\n');
|
|
340
|
+
return false;
|
|
285
341
|
}
|
|
286
|
-
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
/** Publish with a single friendly retry on 429 (the WAF allows ~1/10s). Returns the URL. */
|
|
345
|
+
async function publishWithRetry(payload) {
|
|
287
346
|
try {
|
|
288
|
-
(
|
|
347
|
+
return (await publish(payload, REPLAY_ENDPOINT)).url;
|
|
289
348
|
}
|
|
290
349
|
catch (err) {
|
|
291
350
|
if (err instanceof PublishError && err.status === 429) {
|
|
292
351
|
process.stdout.write('sickr: rate-limited — you can publish about once every 10s. Waiting to retry once...\n');
|
|
293
352
|
await new Promise((r) => setTimeout(r, 11_000));
|
|
294
353
|
try {
|
|
295
|
-
(
|
|
354
|
+
return (await publish(payload, REPLAY_ENDPOINT)).url;
|
|
296
355
|
}
|
|
297
356
|
catch (retryErr) {
|
|
298
357
|
if (retryErr instanceof PublishError && retryErr.status === 429) {
|
|
299
358
|
process.stderr.write('sickr: still rate-limited. Give it a minute and run `share` again.\n');
|
|
300
359
|
process.exit(1);
|
|
301
|
-
return;
|
|
302
360
|
}
|
|
303
361
|
throw retryErr;
|
|
304
362
|
}
|
|
305
363
|
}
|
|
306
|
-
|
|
307
|
-
throw err;
|
|
308
|
-
}
|
|
364
|
+
throw err;
|
|
309
365
|
}
|
|
310
|
-
|
|
311
|
-
|
|
366
|
+
}
|
|
367
|
+
async function handleShare(runId, yes, open) {
|
|
368
|
+
const id = runId ?? latestRunId();
|
|
369
|
+
if (!id) {
|
|
370
|
+
process.stderr.write('sickr: no runs to share. Use Claude Code or Codex first.\n');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const payload = buildSharePayload(loadRun(id));
|
|
375
|
+
process.stdout.write(`sickr: about to publish run "${id}" (${payload.run.events.length} events, secrets already redacted) to ${REPLAY_ENDPOINT}\n` +
|
|
376
|
+
`sickr: tip — run \`npx @sickr/replay open ${id}\` to review the full timeline locally before sharing.\n`);
|
|
377
|
+
if (!(await confirmPublish(yes, 'this run')))
|
|
378
|
+
return;
|
|
379
|
+
const url = await publishWithRetry(payload);
|
|
380
|
+
process.stdout.write(`sickr: published → ${url}\nsickr: this link expires in 24h.\n`);
|
|
381
|
+
if (open)
|
|
382
|
+
openInBrowser(url);
|
|
383
|
+
}
|
|
384
|
+
async function handleShareCombined(sel, yes, open) {
|
|
385
|
+
const runs = sel.ids
|
|
386
|
+
.map((id) => ({ id, events: loadRun(id).events }))
|
|
387
|
+
.filter((r) => r.events.length)
|
|
388
|
+
.map((r) => ({ agent: runSummary(r.id).agent, events: r.events }));
|
|
389
|
+
if (runs.length === 0) {
|
|
390
|
+
process.stdout.write(`sickr: no runs in ${sel.label} to share.\n`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const turns = runs.reduce((n, r) => n + r.events.filter((e) => e.kind === 'prompt').length, 0);
|
|
394
|
+
const agents = Array.from(new Set(runs.map((r) => r.agent))).join(', ');
|
|
395
|
+
const payload = buildCombinedPayload(runs, sel.label);
|
|
396
|
+
process.stdout.write(`sickr: about to publish a combined replay (${sel.label}) — ${runs.length} runs, ~${turns} turns across ${agents}, secrets already redacted, to ${REPLAY_ENDPOINT}\n` +
|
|
397
|
+
`sickr: tip — run the matching \`open\` window to review locally before sharing.\n`);
|
|
398
|
+
if (!(await confirmPublish(yes, 'this combined replay')))
|
|
399
|
+
return;
|
|
400
|
+
const url = await publishWithRetry(payload);
|
|
401
|
+
process.stdout.write(`sickr: published → ${url}\nsickr: this link expires in 24h.\n`);
|
|
312
402
|
if (open)
|
|
313
403
|
openInBrowser(url);
|
|
314
404
|
}
|
|
@@ -344,6 +434,11 @@ async function main() {
|
|
|
344
434
|
handleInit(provider, rest.includes('--no-name'));
|
|
345
435
|
return;
|
|
346
436
|
case 'open': {
|
|
437
|
+
const sel = selectWindow(rest);
|
|
438
|
+
if (sel) {
|
|
439
|
+
handleOpenCombined(sel);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
347
442
|
const openProvider = rest.includes('--codex') ? 'codex' : rest.includes('--claude') ? 'claude' : undefined;
|
|
348
443
|
handleOpen(rest.find((a) => !a.startsWith('-')), openProvider);
|
|
349
444
|
return;
|
|
@@ -359,9 +454,17 @@ async function main() {
|
|
|
359
454
|
case 'clear':
|
|
360
455
|
await handleClear(rest.includes('--yes') || rest.includes('-y'));
|
|
361
456
|
return;
|
|
362
|
-
case 'share':
|
|
363
|
-
|
|
457
|
+
case 'share': {
|
|
458
|
+
const yes = rest.includes('--yes') || rest.includes('-y');
|
|
459
|
+
const openAfter = rest.includes('--open');
|
|
460
|
+
const sel = selectWindow(rest);
|
|
461
|
+
if (sel) {
|
|
462
|
+
await handleShareCombined(sel, yes, openAfter);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
await handleShare(rest.find((a) => !a.startsWith('-')), yes, openAfter);
|
|
364
466
|
return;
|
|
467
|
+
}
|
|
365
468
|
default:
|
|
366
469
|
process.stderr.write('sickr: unknown command. Run `npx @sickr/replay help`.\n');
|
|
367
470
|
process.exit(1);
|
package/dist/render.js
CHANGED
|
@@ -126,6 +126,16 @@ const STYLES = `
|
|
|
126
126
|
details.work .peek{color:#5f6b80;font-size:11px}
|
|
127
127
|
details.work ol.tl{padding:6px 16px 14px}
|
|
128
128
|
details.work ol.tl::before{top:14px;bottom:14px}
|
|
129
|
+
.controls{position:sticky;top:0;z-index:3;display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 0 14px;margin-bottom:4px;background:linear-gradient(var(--ink) 72%,transparent)}
|
|
130
|
+
.chip{font-family:"JetBrains Mono",monospace;font-size:11px;padding:4px 11px;border-radius:999px;border:1px solid #1b2435;background:#04060b;color:#9aa6b6;cursor:pointer}
|
|
131
|
+
.chip.on{border-color:rgba(52,224,255,.5);color:var(--plasma)}
|
|
132
|
+
.sortbtn{margin-left:auto;font-family:"JetBrains Mono",monospace;font-size:11px;padding:4px 11px;border-radius:6px;border:1px solid #1b2435;background:#04060b;color:#9aa6b6;cursor:pointer}
|
|
133
|
+
.sortbtn:hover{border-color:rgba(52,224,255,.5);color:var(--plasma)}
|
|
134
|
+
.turnhead{margin:0 0 4px}
|
|
135
|
+
.agentchip{font-family:"JetBrains Mono",monospace;font-size:10px;text-transform:uppercase;letter-spacing:.1em;padding:2px 8px;border-radius:5px;border:1px solid #2a3850;color:#9aa6b6}
|
|
136
|
+
.agentchip.claude{color:var(--plasma);border-color:rgba(52,224,255,.4);background:rgba(52,224,255,.08)}
|
|
137
|
+
.agentchip.codex{color:#9b8cff;border-color:rgba(155,140,255,.4);background:rgba(155,140,255,.08)}
|
|
138
|
+
.turn.hide{display:none}
|
|
129
139
|
`;
|
|
130
140
|
const FIND_SCRIPT = `<script>
|
|
131
141
|
(function(){
|
|
@@ -417,3 +427,102 @@ export function renderRunHtml(run) {
|
|
|
417
427
|
${FIND_SCRIPT}
|
|
418
428
|
</div></body></html>`;
|
|
419
429
|
}
|
|
430
|
+
// Filter (by agent) + sort (prompt vs response time) + copy. Kept in sync with /r.
|
|
431
|
+
const COMBINED_SCRIPT = `<script>
|
|
432
|
+
(function(){
|
|
433
|
+
var tl=document.getElementById('tl'); if(!tl) return;
|
|
434
|
+
var turns=[].slice.call(tl.getElementsByClassName('turn'));
|
|
435
|
+
var chips=[].slice.call(document.getElementsByClassName('chip'));
|
|
436
|
+
chips.forEach(function(c){c.addEventListener('click',function(){
|
|
437
|
+
chips.forEach(function(x){x.classList.remove('on')}); c.classList.add('on');
|
|
438
|
+
var f=c.getAttribute('data-f');
|
|
439
|
+
turns.forEach(function(t){ t.className='turn'+((f==='All'||t.getAttribute('data-agent')===f)?'':' hide'); });
|
|
440
|
+
});});
|
|
441
|
+
var sb=document.getElementById('sortbtn'), byResp=false;
|
|
442
|
+
if(sb) sb.addEventListener('click',function(){
|
|
443
|
+
byResp=!byResp; sb.textContent=byResp?'sort: response time':'sort: prompt time';
|
|
444
|
+
var key=byResp?'data-rt':'data-pt';
|
|
445
|
+
turns.slice().sort(function(a,b){var x=a.getAttribute(key)||'',y=b.getAttribute(key)||'';return x<y?-1:x>y?1:0;}).forEach(function(t){tl.appendChild(t)});
|
|
446
|
+
});
|
|
447
|
+
document.addEventListener('click',function(e){
|
|
448
|
+
var b=e.target.closest&&e.target.closest('.copy'); if(!b)return;
|
|
449
|
+
var m=b.closest('.msg'),bb=m&&m.querySelector('.bubble');
|
|
450
|
+
if(bb&&navigator.clipboard)navigator.clipboard.writeText(bb.innerText);
|
|
451
|
+
b.classList.add('done'); setTimeout(function(){b.classList.remove('done');},1200);
|
|
452
|
+
});
|
|
453
|
+
})();
|
|
454
|
+
</script>`;
|
|
455
|
+
function combinedSection(agent, t) {
|
|
456
|
+
const pt = t.prompt?.at || t.work[0]?.at || t.response?.at || '';
|
|
457
|
+
const rt = t.response?.at || t.work[t.work.length - 1]?.at || pt;
|
|
458
|
+
const cls = agent === 'Codex' ? 'codex' : agent === 'Claude' ? 'claude' : '';
|
|
459
|
+
return `<section class="turn" data-agent="${esc(agent)}" data-pt="${esc(pt)}" data-rt="${esc(rt)}">
|
|
460
|
+
<div class="turnhead"><span class="agentchip ${cls}">${esc(agent)}</span></div>
|
|
461
|
+
${t.prompt ? bubble('human', speaker(t.prompt.label, 'Prompt', 'Human'), t.prompt) : ''}
|
|
462
|
+
${workBlock(t.work)}
|
|
463
|
+
${t.response && t.response.detail ? bubble('agent', speaker(t.response.label, 'Response', agent), t.response) : ''}
|
|
464
|
+
</section>`;
|
|
465
|
+
}
|
|
466
|
+
/** A combined, interleaved timeline across multiple runs/agents, sorted by prompt time. */
|
|
467
|
+
export function renderCombinedHtml(runs, window) {
|
|
468
|
+
const items = [];
|
|
469
|
+
for (const r of runs) {
|
|
470
|
+
for (const t of groupTurns(r.events).turns) {
|
|
471
|
+
items.push({ agent: r.agent, t, pt: t.prompt?.at || t.work[0]?.at || t.response?.at || '' });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
items.sort((a, b) => (a.pt < b.pt ? -1 : a.pt > b.pt ? 1 : 0));
|
|
475
|
+
const agents = Array.from(new Set(runs.map((r) => r.agent)));
|
|
476
|
+
const chips = ['All', ...agents].map((a) => `<button class="chip${a === 'All' ? ' on' : ''}" type="button" data-f="${esc(a)}">${esc(a)}</button>`).join('');
|
|
477
|
+
const sections = items.length ? items.map((it) => combinedSection(it.agent, it.t)).join('\n') : '<p class="sub">No turns in this window yet.</p>';
|
|
478
|
+
return `<!doctype html><html lang="en"><head><meta charset="UTF-8"/>
|
|
479
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
480
|
+
<title>SICKR Replay — combined (${esc(window)})</title>
|
|
481
|
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
482
|
+
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=Sora:wght@300;400;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
|
483
|
+
<style>${STYLES}</style></head>
|
|
484
|
+
<body>
|
|
485
|
+
<div class="bg"><div class="bg-grid"></div><div class="glow glow-a"></div><div class="glow glow-b"></div></div>
|
|
486
|
+
<div class="wrap">
|
|
487
|
+
<span id="tl-top"></span>
|
|
488
|
+
<header class="bar">
|
|
489
|
+
<a href="https://sickr.ai">${wordmark('lg')}</a>
|
|
490
|
+
<a class="bar-cta" href="https://sickr.ai">sickr.ai →</a>
|
|
491
|
+
</header>
|
|
492
|
+
<div class="layout">
|
|
493
|
+
<aside class="rail">
|
|
494
|
+
<p class="label">Combined replay</p>
|
|
495
|
+
<h3 style="margin-top:10px">All your agents, one timeline</h3>
|
|
496
|
+
<p>Every turn from every agent in this window, interleaved by time.</p>
|
|
497
|
+
<div style="margin-top:14px">
|
|
498
|
+
<div class="meta-row"><span>window</span><b>${esc(window)}</b></div>
|
|
499
|
+
<div class="meta-row"><span>runs</span><b>${runs.length}</b></div>
|
|
500
|
+
<div class="meta-row"><span>turns</span><b>${items.length}</b></div>
|
|
501
|
+
<div class="meta-row"><span>agents</span><b>${esc(agents.join(', ') || '—')}</b></div>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="jumps"><a href="#tl-top">↑ top</a><a href="#tl-bottom">↓ end</a></div>
|
|
504
|
+
</aside>
|
|
505
|
+
<main>
|
|
506
|
+
<h1 class="title">What <span class="hl">all your agents</span> did.</h1>
|
|
507
|
+
<p class="sub">${items.length} turns · ${runs.length} runs · ${esc(window)} · captured locally</p>
|
|
508
|
+
<div class="controls">${chips}<button id="sortbtn" type="button" class="sortbtn">sort: prompt time</button></div>
|
|
509
|
+
<div id="tl" class="turns">${sections}</div>
|
|
510
|
+
</main>
|
|
511
|
+
<aside class="rail panel">
|
|
512
|
+
<p class="label" style="margin-bottom:12px">Beyond your machine</p>
|
|
513
|
+
<h3>Govern your whole team</h3>
|
|
514
|
+
<p>This is your agents on one machine. SICKR adds gates, approvals, multi-agent hand-offs and a full, signed-off audit trail across humans and agents.</p>
|
|
515
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
516
|
+
</aside>
|
|
517
|
+
</div>
|
|
518
|
+
<span id="tl-bottom"></span>
|
|
519
|
+
<div class="bottombar">
|
|
520
|
+
<a href="https://sickr.ai">${wordmark('lg2')}</a>
|
|
521
|
+
<p class="pitch">All your agents, audited in one place. <span style="color:#fff">Govern the whole team</span> — gates, approvals, full audit trail.</p>
|
|
522
|
+
<a class="btn" href="https://sickr.ai">Explore SICKR →</a>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="foot">Captured locally with npx @sickr/replay · <a href="#tl-top">back to top ↑</a> · sickr.ai</div>
|
|
525
|
+
<nav class="jump" aria-label="scroll"><a href="#tl-top" aria-label="Scroll to top" title="Top">↑</a><a href="#tl-bottom" aria-label="Scroll to bottom" title="Bottom">↓</a></nav>
|
|
526
|
+
${COMBINED_SCRIPT}
|
|
527
|
+
</div></body></html>`;
|
|
528
|
+
}
|
package/dist/share.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export function buildSharePayload(run) {
|
|
3
3
|
return { run: { cwd: run.cwd, startedAt: run.startedAt, events: run.events } };
|
|
4
4
|
}
|
|
5
|
+
/** Combined multi-agent payload: tagged runs + the window label. */
|
|
6
|
+
export function buildCombinedPayload(runs, window) {
|
|
7
|
+
return { window, runs: runs.map((r) => ({ agent: r.agent, events: r.events })) };
|
|
8
|
+
}
|
|
5
9
|
export class PublishError extends Error {
|
|
6
10
|
status;
|
|
7
11
|
constructor(status) {
|
package/package.json
CHANGED