This repository was archived by the owner on Jul 24, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtui-watch.ts
More file actions
106 lines (96 loc) · 3.04 KB
/
Copy pathtui-watch.ts
File metadata and controls
106 lines (96 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// examples/tui-watch.ts — minimal interactive consumer of the createSt API.
//
// Run with: npm run example:tui-watch
//
// Picks ST_ROOT and ST_IDENTITY out of the environment, watches the
// caller's own inbox, and renders newly-arriving filenames as they come
// in. Press `a` to archive the most recently received message; press `q`
// (or Ctrl+C) to quit.
//
// To watch every peer's inbox instead (suppressing the caller's own
// folder — the brief-005 cross-tree mode), pass `undefined` to
// `smalltalk.watch()` below. Note: archive on a peer's tree is unusual but
// technically valid (every machine has every tree via sync).
//
// Designed to surface DX issues with the embeddable API. If anything
// here feels clunky, that's a real signal.
import {
asIdentity,
type StError,
createSt,
type Filename,
type Identity,
IdentityNotHostedError,
} from '../src/index.ts';
const root = process.env.ST_ROOT;
const identityRaw = process.env.ST_IDENTITY;
if (!root || !identityRaw) {
process.stderr.write(
'usage: ST_ROOT=<path> ST_IDENTITY=<id> npm run example:tui-watch\n'
);
process.exit(2);
}
const me: Identity = asIdentity(identityRaw);
const smalltalk = createSt({ root, identity: me });
const ac = new AbortController();
let mostRecent:
| { filename: Filename; identity: Identity }
| undefined;
function render(line: string): void {
process.stdout.write(`${line}\n`);
}
function shutdown(code = 0): void {
ac.abort();
if (process.stdin.isTTY) process.stdin.setRawMode(false);
process.exit(code);
}
process.on('SIGINT', () => shutdown(130));
process.on('SIGTERM', () => shutdown(143));
// Raw-mode keypresses: archive on `a`, quit on `q` / Ctrl+C.
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', async (buf: Buffer) => {
const ch = buf.toString('utf8');
if (ch === 'q' || ch === '\x03') {
shutdown(0);
} else if (ch === 'a') {
if (!mostRecent) {
render(' (no message to archive)');
return;
}
try {
await smalltalk.archive(mostRecent.identity, mostRecent.filename);
render(
` archived: ${mostRecent.identity}/${mostRecent.filename}`
);
mostRecent = undefined;
} catch (err) {
const e = err as StError;
render(
` archive failed (${e.code ?? 'UNKNOWN'}): ${e.message}`
);
}
}
});
}
render(`watching ${me}/inbox — press 'a' to archive most recent, 'q' to quit`);
try {
// Per-identity watch on the caller's own inbox: the messages here are
// ones bob/myobie/etc. sent to ME, so "archive most recent" applies.
for await (const ev of smalltalk.watch(me, {
withSubject: true,
intervalMs: 250,
signal: ac.signal,
})) {
mostRecent = { filename: ev.filename, identity: ev.identity };
const subj = ev.subject ?? '';
render(`new ${ev.filename}\t${subj}`);
}
} catch (err) {
if (err instanceof IdentityNotHostedError) {
process.stderr.write(`smalltalk: ${err.message}\n`);
shutdown(1);
}
throw err;
}