@tienne/gestalt 0.108.0 → 0.109.0

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.
Files changed (69) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/package.json +1 -1
  3. package/dist/src/cli/commands/peer.d.ts +19 -0
  4. package/dist/src/cli/commands/peer.d.ts.map +1 -0
  5. package/dist/src/cli/commands/peer.js +147 -0
  6. package/dist/src/cli/commands/peer.js.map +1 -0
  7. package/dist/src/cli/index.d.ts.map +1 -1
  8. package/dist/src/cli/index.js +35 -2
  9. package/dist/src/cli/index.js.map +1 -1
  10. package/dist/src/peer/client.d.ts +9 -0
  11. package/dist/src/peer/client.d.ts.map +1 -0
  12. package/dist/src/peer/client.js +51 -0
  13. package/dist/src/peer/client.js.map +1 -0
  14. package/dist/src/peer/daemon.d.ts +71 -0
  15. package/dist/src/peer/daemon.d.ts.map +1 -0
  16. package/dist/src/peer/daemon.js +407 -0
  17. package/dist/src/peer/daemon.js.map +1 -0
  18. package/dist/src/peer/errors.d.ts +2 -0
  19. package/dist/src/peer/errors.d.ts.map +1 -0
  20. package/dist/src/peer/errors.js +4 -0
  21. package/dist/src/peer/errors.js.map +1 -0
  22. package/dist/src/peer/index.d.ts +14 -0
  23. package/dist/src/peer/index.d.ts.map +1 -0
  24. package/dist/src/peer/index.js +9 -0
  25. package/dist/src/peer/index.js.map +1 -0
  26. package/dist/src/peer/locate.d.ts +44 -0
  27. package/dist/src/peer/locate.d.ts.map +1 -0
  28. package/dist/src/peer/locate.js +116 -0
  29. package/dist/src/peer/locate.js.map +1 -0
  30. package/dist/src/peer/paths.d.ts +23 -0
  31. package/dist/src/peer/paths.d.ts.map +1 -0
  32. package/dist/src/peer/paths.js +50 -0
  33. package/dist/src/peer/paths.js.map +1 -0
  34. package/dist/src/peer/protocol.d.ts +61 -0
  35. package/dist/src/peer/protocol.d.ts.map +1 -0
  36. package/dist/src/peer/protocol.js +7 -0
  37. package/dist/src/peer/protocol.js.map +1 -0
  38. package/dist/src/peer/redact.d.ts +30 -0
  39. package/dist/src/peer/redact.d.ts.map +1 -0
  40. package/dist/src/peer/redact.js +214 -0
  41. package/dist/src/peer/redact.js.map +1 -0
  42. package/dist/src/peer/share.d.ts +123 -0
  43. package/dist/src/peer/share.d.ts.map +1 -0
  44. package/dist/src/peer/share.js +204 -0
  45. package/dist/src/peer/share.js.map +1 -0
  46. package/dist/src/peer/state.d.ts +37 -0
  47. package/dist/src/peer/state.d.ts.map +1 -0
  48. package/dist/src/peer/state.js +99 -0
  49. package/dist/src/peer/state.js.map +1 -0
  50. package/dist/src/peer/tail.d.ts +55 -0
  51. package/dist/src/peer/tail.d.ts.map +1 -0
  52. package/dist/src/peer/tail.js +148 -0
  53. package/dist/src/peer/tail.js.map +1 -0
  54. package/dist/src/peer/transform.d.ts +79 -0
  55. package/dist/src/peer/transform.d.ts.map +1 -0
  56. package/dist/src/peer/transform.js +237 -0
  57. package/dist/src/peer/transform.js.map +1 -0
  58. package/dist/src/peer/viewer-html.d.ts +10 -0
  59. package/dist/src/peer/viewer-html.d.ts.map +1 -0
  60. package/dist/src/peer/viewer-html.js +157 -0
  61. package/dist/src/peer/viewer-html.js.map +1 -0
  62. package/dist/src/peer/viewer.d.ts +58 -0
  63. package/dist/src/peer/viewer.d.ts.map +1 -0
  64. package/dist/src/peer/viewer.js +249 -0
  65. package/dist/src/peer/viewer.js.map +1 -0
  66. package/package.json +1 -1
  67. package/plugin/.codex-plugin/plugin.json +1 -1
  68. package/plugin/.mcp.json +1 -1
  69. package/plugin/mcp.json +1 -1
@@ -0,0 +1,249 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { renderViewerHtml } from './viewer-html.js';
4
+ import { errMessage } from './errors.js';
5
+ const PAGE_PATH = /^\/s\/([A-Za-z0-9_-]{1,64})\/?$/;
6
+ const EVENTS_PATH = /^\/s\/([A-Za-z0-9_-]{1,64})\/events$/;
7
+ /** SSE 연결이 프록시나 OS에서 놀다 끊기지 않게 보내는 빈 주석 간격 */
8
+ const KEEPALIVE_MS = 15_000;
9
+ /**
10
+ * 공유 하나에 동시에 붙는 뷰어 상한. 넘으면 429. 뷰어 하나가 붙잡는 메모리는 소켓 버퍼로
11
+ * 묶여 있지만 연결마다 소켓과 타이머가 생기니 토큰을 아는 쪽이 끝없이 열지 못하게 한다.
12
+ * 사람이 보는 숫자로는 넉넉하다.
13
+ */
14
+ export const MAX_VIEWERS_PER_SHARE = 16;
15
+ /**
16
+ * 실시간 프레임을 쓰기 전에 소켓 버퍼에 이만큼 넘게 밀려 있으면 그 뷰어를 끊는다. 안 읽는
17
+ * 뷰어 하나가 데몬 메모리를 끝없이 붙잡지 못하게 한다. 이보다 큰 이벤트 하나는 들어가지만
18
+ * 소켓으로 다 빠지기 전에 다음 프레임이 오면 그때 끊는다. 앞부분은 소켓이 비는 만큼만 꺼내
19
+ * 보내서 여기에 안 쌓인다. 브라우저는 다시 붙어 Last-Event-ID부터 받는다.
20
+ */
21
+ export const MAX_LIVE_BACKLOG_BYTES = 8 * 1024 * 1024;
22
+ /** 앞부분을 공유에서 한 번에 꺼내는 양. 소켓이 차면 그 자리에서 멈추므로 대략이면 된다 */
23
+ export const REPLAY_CHUNK_BYTES = 256 * 1024;
24
+ /**
25
+ * 로컬 뷰어 HTTP 서버. `127.0.0.1`에만 붙는다.
26
+ *
27
+ * 같은 머신의 다른 프로세스나 브라우저에 열린 남의 웹페이지도 이 포트에 닿을 수 있다.
28
+ * 그래서 공유마다 토큰을 주소에 붙이고 없거나 틀리면 401이다. 모르는 공유 id도 401로
29
+ * 돌려준다 — 404와 401을 나누면 토큰 없이 공유 id가 살아 있는지 떠볼 수 있다.
30
+ *
31
+ * Routes:
32
+ * GET /s/:id?t=TOKEN → 뷰어 HTML
33
+ * GET /s/:id/events?t=TOKEN → SSE (line, trimmed, stats, state, closed). 뷰어 상한을 넘으면 429.
34
+ * closed의 missed는 따라잡는 중에 공유가 종료돼 못 보낸 이벤트 수
35
+ * * → 404
36
+ */
37
+ export class ViewerServer {
38
+ hooks;
39
+ server = null;
40
+ open = new Set();
41
+ viewers = new Map();
42
+ maxLiveBacklogBytes;
43
+ constructor(hooks, options = {}) {
44
+ this.hooks = hooks;
45
+ this.maxLiveBacklogBytes = options.maxLiveBacklogBytes ?? MAX_LIVE_BACKLOG_BYTES;
46
+ }
47
+ start(port = 0) {
48
+ return new Promise((resolve, reject) => {
49
+ this.server = createServer((req, res) => {
50
+ // 핸들러가 던지면 uncaughtException으로 데몬이 죽는다. 요청 대상이 `//[`처럼 URL로
51
+ // 안 읽히면 new URL이 던지는데 이 포트에 닿는 누구나 그런 요청을 보낼 수 있다
52
+ try {
53
+ this.handle(req, res);
54
+ }
55
+ catch (err) {
56
+ process.stderr.write(`[peer-viewer] request failed: ${errMessage(err)}\n`);
57
+ if (!res.headersSent)
58
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
59
+ res.end('Internal Server Error');
60
+ }
61
+ });
62
+ this.server.once('error', reject);
63
+ this.server.listen(port, '127.0.0.1', () => {
64
+ this.server.off('error', reject);
65
+ this.server.on('error', (err) => {
66
+ process.stderr.write(`[peer-viewer] server error: ${err.message}\n`);
67
+ });
68
+ resolve(this.port);
69
+ });
70
+ });
71
+ }
72
+ get port() {
73
+ const addr = this.server?.address();
74
+ return addr && typeof addr === 'object' ? addr.port : undefined;
75
+ }
76
+ stop() {
77
+ for (const res of this.open)
78
+ res.end();
79
+ this.open.clear();
80
+ return new Promise((resolve) => {
81
+ if (!this.server) {
82
+ resolve();
83
+ return;
84
+ }
85
+ this.server.closeAllConnections?.();
86
+ this.server.close(() => {
87
+ this.server = null;
88
+ resolve();
89
+ });
90
+ });
91
+ }
92
+ handle(req, res) {
93
+ if (req.method !== 'GET')
94
+ return plain(res, 405, 'Method Not Allowed');
95
+ // DNS 리바인딩으로 남의 도메인이 이 포트를 가리키게 해도 Host가 달라서 막힌다
96
+ if (!this.hostAllowed(req.headers.host))
97
+ return plain(res, 403, 'Forbidden');
98
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
99
+ const page = PAGE_PATH.exec(url.pathname);
100
+ const events = page ? null : EVENTS_PATH.exec(url.pathname);
101
+ const id = page?.[1] ?? events?.[1];
102
+ if (!id)
103
+ return plain(res, 404, 'Not Found');
104
+ const share = this.hooks.lookup(id);
105
+ if (!share || !tokenMatches(url.searchParams.get('t'), share.token)) {
106
+ return plain(res, 401, 'Unauthorized');
107
+ }
108
+ if (page)
109
+ return this.page(res);
110
+ if ((this.viewers.get(id) ?? 0) >= MAX_VIEWERS_PER_SHARE) {
111
+ return plain(res, 429, 'Too Many Viewers');
112
+ }
113
+ return this.stream(req, res, share.stream);
114
+ }
115
+ hostAllowed(host) {
116
+ if (!host)
117
+ return false;
118
+ const port = this.port;
119
+ return host === `127.0.0.1:${port}` || host === `localhost:${port}`;
120
+ }
121
+ page(res) {
122
+ const nonce = randomBytes(16).toString('base64');
123
+ res.writeHead(200, {
124
+ 'Content-Type': 'text/html; charset=utf-8',
125
+ 'Content-Security-Policy': [
126
+ "default-src 'none'",
127
+ `script-src 'nonce-${nonce}'`,
128
+ `style-src 'nonce-${nonce}'`,
129
+ "connect-src 'self'",
130
+ "base-uri 'none'",
131
+ "form-action 'none'",
132
+ "frame-ancestors 'none'",
133
+ ].join('; '),
134
+ ...PRIVATE_HEADERS,
135
+ });
136
+ res.end(renderViewerHtml(nonce, MAX_VIEWERS_PER_SHARE));
137
+ }
138
+ stream(req, res, stream) {
139
+ const lastId = Number.parseInt(String(req.headers['last-event-id'] ?? '0'), 10);
140
+ res.writeHead(200, {
141
+ 'Content-Type': 'text/event-stream; charset=utf-8',
142
+ Connection: 'keep-alive',
143
+ ...PRIVATE_HEADERS,
144
+ });
145
+ res.write(': connected\n\n');
146
+ this.open.add(res);
147
+ this.viewers.set(stream.id, (this.viewers.get(stream.id) ?? 0) + 1);
148
+ this.hooks.onConnect?.(stream.id);
149
+ const keepalive = setInterval(() => res.write(': ping\n\n'), KEEPALIVE_MS);
150
+ keepalive.unref();
151
+ let detached = false;
152
+ let unsubscribe = () => { };
153
+ const detach = () => {
154
+ if (detached)
155
+ return;
156
+ detached = true;
157
+ clearInterval(keepalive);
158
+ unsubscribe();
159
+ this.open.delete(res);
160
+ const left = (this.viewers.get(stream.id) ?? 1) - 1;
161
+ if (left > 0)
162
+ this.viewers.set(stream.id, left);
163
+ else
164
+ this.viewers.delete(stream.id);
165
+ this.hooks.onDisconnect?.(stream.id);
166
+ };
167
+ req.on('close', detach);
168
+ res.on('close', detach);
169
+ const onFrame = (frame) => {
170
+ if (detached)
171
+ return;
172
+ if (res.writableLength > this.maxLiveBacklogBytes) {
173
+ res.destroy();
174
+ detach();
175
+ return;
176
+ }
177
+ res.write(sseFrame(frame));
178
+ if (frame.type === 'closed') {
179
+ res.end();
180
+ detach();
181
+ }
182
+ };
183
+ // 앞부분은 소켓이 비는 만큼씩 꺼내 쓴다. 다 따라잡으면 이벤트 루프로 돌아가지 않고 바로
184
+ // subscribe로 붙어서 그 사이에 poll이 끼지 않는다. subscribe가 상태와 통계를 다시 보내
185
+ // 따라잡는 동안 바뀐 것도 맞춘다. 기다리는 사이 공유가 종료되면 subscribe가 못 보낸 개수를
186
+ // closed에 실어 보낸다
187
+ let cursor = Number.isFinite(lastId) && lastId > 0 ? lastId : 0;
188
+ const pump = () => {
189
+ if (detached)
190
+ return;
191
+ while (!stream.isClosed) {
192
+ const { dropped, items } = stream.backlogAfter(cursor, REPLAY_CHUNK_BYTES);
193
+ if (dropped > 0) {
194
+ res.write(sseFrame({ type: 'trimmed', dropped }));
195
+ cursor += dropped;
196
+ }
197
+ if (items.length === 0)
198
+ break;
199
+ for (const item of items) {
200
+ cursor = item.seq;
201
+ if (!res.write(sseFrame({ type: 'event', ...item }))) {
202
+ res.once('drain', pump);
203
+ return;
204
+ }
205
+ }
206
+ }
207
+ unsubscribe = stream.subscribe(onFrame, cursor);
208
+ if (detached)
209
+ unsubscribe();
210
+ };
211
+ res.write(sseFrame({ type: 'state', status: stream.currentStatus }));
212
+ res.write(sseFrame({ type: 'stats', unknownLines: stream.unknownLines }));
213
+ pump();
214
+ }
215
+ }
216
+ /**
217
+ * 뷰어는 디스크에 아무것도 남기지 않아야 한다. 브라우저 캐시도 디스크다.
218
+ */
219
+ const PRIVATE_HEADERS = {
220
+ 'Cache-Control': 'no-store',
221
+ 'Referrer-Policy': 'no-referrer',
222
+ 'X-Content-Type-Options': 'nosniff',
223
+ };
224
+ function plain(res, status, body) {
225
+ res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8', ...PRIVATE_HEADERS });
226
+ res.end(body);
227
+ }
228
+ function tokenMatches(given, expected) {
229
+ if (!given)
230
+ return false;
231
+ const a = Buffer.from(given);
232
+ const b = Buffer.from(expected);
233
+ return a.length === b.length && timingSafeEqual(a, b);
234
+ }
235
+ function sseFrame(frame) {
236
+ switch (frame.type) {
237
+ case 'event':
238
+ return `id: ${frame.seq}\nevent: line\ndata: ${frame.json}\n\n`;
239
+ case 'stats':
240
+ return `event: stats\ndata: ${JSON.stringify({ unknownLines: frame.unknownLines })}\n\n`;
241
+ case 'state':
242
+ return `event: state\ndata: ${JSON.stringify({ status: frame.status })}\n\n`;
243
+ case 'closed':
244
+ return `event: closed\ndata: ${JSON.stringify({ reason: frame.reason, missed: frame.missed })}\n\n`;
245
+ case 'trimmed':
246
+ return `event: trimmed\ndata: ${JSON.stringify({ dropped: frame.dropped })}\n\n`;
247
+ }
248
+ }
249
+ //# sourceMappingURL=viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../../../src/peer/viewer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.108.0",
3
+ "version": "0.109.0",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt",
3
- "version": "0.108.0",
3
+ "version": "0.109.0",
4
4
  "description": "Gestalt psychology-driven AI development harness. Transforms scattered requirements into structured, validated specifications through interactive interviews.",
5
5
  "author": {
6
6
  "name": "tienne"
package/plugin/.mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "gestalt": {
4
4
  "command": "npx",
5
- "args": ["-y", "@tienne/gestalt@0.108.0", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.109.0", "serve"],
6
6
  "startup_timeout_sec": 180,
7
7
  "tool_timeout_sec": 900
8
8
  }
package/plugin/mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "gestalt": {
4
4
  "command": "npx",
5
- "args": ["-y", "@tienne/gestalt@0.108.0", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.109.0", "serve"],
6
6
  "startup_timeout_sec": 180,
7
7
  "tool_timeout_sec": 900
8
8
  }