storebound watch: store events for every app on the machine (rejected, approved, live, halted, crash threshold, reviews) to ntfy / Telegram / webhook / OS notification, loop or --once, baseline first, events.jsonl; storebound events and MCP events_recent; docs/watch.md; tests

This commit is contained in:
CRTkafa 2026-10-22 22:38:15 +08:00
parent 4fe26326cc
commit 78c541f2b8
16 changed files with 984 additions and 4 deletions

20
cli/events.ts Normal file
View file

@ -0,0 +1,20 @@
// `storebound events`: the events `storebound watch` recorded in ~/.storebound/events.jsonl (and events.jsonl.1), read-only.
// storebound events [--since 24h] [--app <name>] [--kind rejected,live] [--json]
// Kinds: rejected, approved, live, rollout_halted, crash_threshold, review_low, review_needs_reply. Newest last.
import type { Args } from './lib/args';
import { done, printJson } from './lib/log';
import { textTable } from './lib/table';
import { durationMs, EVENT_KINDS, eventsFile, filterEvents, readEvents } from './lib/watch';
export function run(args: Args): Promise<void> {
const kinds = args.list('kind');
for (const k of kinds) if (!(EVENT_KINDS as readonly string[]).includes(k)) throw new Error(`--kind: ${k} is not an event kind (${EVENT_KINDS.join(', ')})`);
const since = args.get('since');
const evs = filterEvents(readEvents(), { ...(since ? { sinceMs: durationMs(since) } : {}), ...(args.get('app') ? { app: args.get('app')! } : {}), kind: kinds });
if (args.has('json')) printJson(evs);
else {
console.log(textTable(['at', 'app', 'store', 'kind', 'version', 'detail'], evs.map(e => [e.at.slice(0, 16).replace('T', ' '), e.app, e.store, e.kind, e.version ?? '', e.detail.slice(0, 90)])));
done('events', `${evs.length} event(s) in ${eventsFile()}${since ? ` since ${since}` : ''}`);
}
return Promise.resolve();
}