@sickr/replay 0.5.0 → 0.5.2
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 +81 -37
- package/dist/share.js +4 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ 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
9
|
import { renderRunHtml, renderCombinedHtml } from './render.js';
|
|
10
|
-
import { buildSharePayload, publish, PublishError } from './share.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) {
|
|
@@ -30,6 +30,9 @@ Commands:
|
|
|
30
30
|
runs to ~/.sickr/runs (secrets redacted).
|
|
31
31
|
--codex install for Codex (.codex/hooks.json) instead of
|
|
32
32
|
Claude Code (.claude/settings.json)
|
|
33
|
+
--both install for BOTH Claude Code and Codex (so the
|
|
34
|
+
combined `, open;
|
|
35
|
+
--today ` view sees everything)
|
|
33
36
|
--no-name label your prompts "Human" instead of your
|
|
34
37
|
login/git name (default is your login name)
|
|
35
38
|
open [run] Render a run to a local HTML timeline and open it. 100% local.
|
|
@@ -39,8 +42,10 @@ Commands:
|
|
|
39
42
|
filterable by agent, sortable by prompt/response time).
|
|
40
43
|
share [run] Redact and publish ONE run to a public sickr.ai/r/<id> link
|
|
41
44
|
(shows a preview and asks first). Links expire after 24h.
|
|
42
|
-
--open
|
|
43
|
-
--yes
|
|
45
|
+
--open also open the published link in your browser
|
|
46
|
+
--yes skip the confirmation prompt
|
|
47
|
+
Or publish a COMBINED multi-agent view with a window:
|
|
48
|
+
--today / --since <2h|30m|1d> / --all (+ --claude/--codex).
|
|
44
49
|
list List recorded runs, newest first.
|
|
45
50
|
stop Stop recording — removes SICKR's hooks from this project.
|
|
46
51
|
Your recorded runs are kept; run \`init\` to start again.
|
|
@@ -323,56 +328,80 @@ function handleList(provider) {
|
|
|
323
328
|
process.stdout.write(`${id} ${s.agent.padEnd(7)} ${String(s.events).padStart(4)} ev ${when}${snippet}\n`);
|
|
324
329
|
});
|
|
325
330
|
}
|
|
326
|
-
async function
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
331
|
+
async function confirmPublish(yes, what) {
|
|
332
|
+
if (yes)
|
|
333
|
+
return true;
|
|
334
|
+
if (!process.stdin.isTTY) {
|
|
335
|
+
process.stderr.write('sickr: re-run with --yes to publish non-interactively.\n');
|
|
330
336
|
process.exit(1);
|
|
331
|
-
return;
|
|
337
|
+
return false;
|
|
332
338
|
}
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (!process.stdin.isTTY) {
|
|
339
|
-
process.stderr.write('sickr: re-run with --yes to publish non-interactively.\n');
|
|
340
|
-
process.exit(1);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
process.stdout.write('Publish this run publicly? [y/N] ');
|
|
344
|
-
const answer = await promptLine();
|
|
345
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
346
|
-
process.stdout.write('sickr: cancelled.\n');
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
339
|
+
process.stdout.write(`Publish ${what} publicly? [y/N] `);
|
|
340
|
+
const a = await promptLine();
|
|
341
|
+
if (a !== 'y' && a !== 'yes') {
|
|
342
|
+
process.stdout.write('sickr: cancelled.\n');
|
|
343
|
+
return false;
|
|
349
344
|
}
|
|
350
|
-
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
/** Publish with a single friendly retry on 429 (the WAF allows ~1/10s). Returns the URL. */
|
|
348
|
+
async function publishWithRetry(payload) {
|
|
351
349
|
try {
|
|
352
|
-
(
|
|
350
|
+
return (await publish(payload, REPLAY_ENDPOINT)).url;
|
|
353
351
|
}
|
|
354
352
|
catch (err) {
|
|
355
353
|
if (err instanceof PublishError && err.status === 429) {
|
|
356
354
|
process.stdout.write('sickr: rate-limited — you can publish about once every 10s. Waiting to retry once...\n');
|
|
357
355
|
await new Promise((r) => setTimeout(r, 11_000));
|
|
358
356
|
try {
|
|
359
|
-
(
|
|
357
|
+
return (await publish(payload, REPLAY_ENDPOINT)).url;
|
|
360
358
|
}
|
|
361
359
|
catch (retryErr) {
|
|
362
360
|
if (retryErr instanceof PublishError && retryErr.status === 429) {
|
|
363
361
|
process.stderr.write('sickr: still rate-limited. Give it a minute and run `share` again.\n');
|
|
364
362
|
process.exit(1);
|
|
365
|
-
return;
|
|
366
363
|
}
|
|
367
364
|
throw retryErr;
|
|
368
365
|
}
|
|
369
366
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function handleShare(runId, yes, open) {
|
|
371
|
+
const id = runId ?? latestRunId();
|
|
372
|
+
if (!id) {
|
|
373
|
+
process.stderr.write('sickr: no runs to share. Use Claude Code or Codex first.\n');
|
|
374
|
+
process.exit(1);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const payload = buildSharePayload(loadRun(id));
|
|
378
|
+
process.stdout.write(`sickr: about to publish run "${id}" (${payload.run.events.length} events, secrets already redacted) to ${REPLAY_ENDPOINT}\n` +
|
|
379
|
+
`sickr: tip — run \`npx @sickr/replay open ${id}\` to review the full timeline locally before sharing.\n`);
|
|
380
|
+
if (!(await confirmPublish(yes, 'this run')))
|
|
381
|
+
return;
|
|
382
|
+
const url = await publishWithRetry(payload);
|
|
383
|
+
process.stdout.write(`sickr: published → ${url}\nsickr: this link expires in 24h.\n`);
|
|
384
|
+
if (open)
|
|
385
|
+
openInBrowser(url);
|
|
386
|
+
}
|
|
387
|
+
async function handleShareCombined(sel, yes, open) {
|
|
388
|
+
const runs = sel.ids
|
|
389
|
+
.map((id) => ({ id, events: loadRun(id).events }))
|
|
390
|
+
.filter((r) => r.events.length)
|
|
391
|
+
.map((r) => ({ agent: runSummary(r.id).agent, events: r.events }));
|
|
392
|
+
if (runs.length === 0) {
|
|
393
|
+
process.stdout.write(`sickr: no runs in ${sel.label} to share.\n`);
|
|
394
|
+
return;
|
|
373
395
|
}
|
|
374
|
-
|
|
375
|
-
|
|
396
|
+
const turns = runs.reduce((n, r) => n + r.events.filter((e) => e.kind === 'prompt').length, 0);
|
|
397
|
+
const agents = Array.from(new Set(runs.map((r) => r.agent))).join(', ');
|
|
398
|
+
const payload = buildCombinedPayload(runs, sel.label);
|
|
399
|
+
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` +
|
|
400
|
+
`sickr: tip — run the matching \`open\` window to review locally before sharing.\n`);
|
|
401
|
+
if (!(await confirmPublish(yes, 'this combined replay')))
|
|
402
|
+
return;
|
|
403
|
+
const url = await publishWithRetry(payload);
|
|
404
|
+
process.stdout.write(`sickr: published → ${url}\nsickr: this link expires in 24h.\n`);
|
|
376
405
|
if (open)
|
|
377
406
|
openInBrowser(url);
|
|
378
407
|
}
|
|
@@ -404,9 +433,16 @@ async function main() {
|
|
|
404
433
|
case 'record':
|
|
405
434
|
handleRecord(await readStdin(), provider);
|
|
406
435
|
return;
|
|
407
|
-
case 'init':
|
|
408
|
-
|
|
436
|
+
case 'init': {
|
|
437
|
+
const noName = rest.includes('--no-name');
|
|
438
|
+
if (rest.includes('--both')) {
|
|
439
|
+
handleInit('claude', noName);
|
|
440
|
+
handleInit('codex', noName);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
handleInit(provider, noName);
|
|
409
444
|
return;
|
|
445
|
+
}
|
|
410
446
|
case 'open': {
|
|
411
447
|
const sel = selectWindow(rest);
|
|
412
448
|
if (sel) {
|
|
@@ -428,9 +464,17 @@ async function main() {
|
|
|
428
464
|
case 'clear':
|
|
429
465
|
await handleClear(rest.includes('--yes') || rest.includes('-y'));
|
|
430
466
|
return;
|
|
431
|
-
case 'share':
|
|
432
|
-
|
|
467
|
+
case 'share': {
|
|
468
|
+
const yes = rest.includes('--yes') || rest.includes('-y');
|
|
469
|
+
const openAfter = rest.includes('--open');
|
|
470
|
+
const sel = selectWindow(rest);
|
|
471
|
+
if (sel) {
|
|
472
|
+
await handleShareCombined(sel, yes, openAfter);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
await handleShare(rest.find((a) => !a.startsWith('-')), yes, openAfter);
|
|
433
476
|
return;
|
|
477
|
+
}
|
|
434
478
|
default:
|
|
435
479
|
process.stderr.write('sickr: unknown command. Run `npx @sickr/replay help`.\n');
|
|
436
480
|
process.exit(1);
|
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