runcastle 0.2.0 → 1.0.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.
Files changed (80) hide show
  1. package/README.md +47 -0
  2. package/bin/runcastle.js +5609 -0
  3. package/drizzle/0000_amused_bruce_banner.sql +74 -0
  4. package/drizzle/0001_mapped_feature_flag.sql +1 -0
  5. package/drizzle/0002_waypoints.sql +14 -0
  6. package/drizzle/0003_project_closed_at.sql +1 -0
  7. package/drizzle/0004_events_project_id.sql +28 -0
  8. package/drizzle/0005_project_setting_overrides.sql +2 -0
  9. package/drizzle/meta/0000_snapshot.json +466 -0
  10. package/drizzle/meta/0001_snapshot.json +474 -0
  11. package/drizzle/meta/0002_snapshot.json +568 -0
  12. package/drizzle/meta/0003_snapshot.json +575 -0
  13. package/drizzle/meta/0004_snapshot.json +582 -0
  14. package/drizzle/meta/0005_snapshot.json +596 -0
  15. package/drizzle/meta/_journal.json +48 -0
  16. package/hook-client.ts +67 -0
  17. package/index.js +5453 -0
  18. package/package.json +24 -30
  19. package/pty-host.cjs +186 -0
  20. package/sandcastle-template/Containerfile +33 -0
  21. package/sandcastle-template/Dockerfile +33 -0
  22. package/skills/burner/implement-ticket.md +42 -0
  23. package/skills/burner/research-waypoint.md +45 -0
  24. package/skills/packs/README.md +50 -0
  25. package/skills/packs/runcastle/.claude-plugin/plugin.json +6 -0
  26. package/skills/packs/runcastle/skills/converge/SKILL.md +51 -0
  27. package/skills/packs/runcastle/skills/ideate/SKILL.md +81 -0
  28. package/skills/packs/runcastle/skills/qa/SKILL.md +30 -0
  29. package/skills/packs/runcastle/skills/spec/SKILL.md +43 -0
  30. package/skills/packs/runcastle/skills/tickets/SKILL.md +52 -0
  31. package/skills/packs/runcastle/skills/waypoint/SKILL.md +54 -0
  32. package/web/assets/index-CKZ5Z7vc.css +1 -0
  33. package/web/assets/index-YBuT7tTz.js +108 -0
  34. package/web/index.html +13 -0
  35. package/dist/add-DD7G73HH.js +0 -315
  36. package/dist/add-DD7G73HH.js.map +0 -1
  37. package/dist/chunk-44KDPUYJ.js +0 -49
  38. package/dist/chunk-44KDPUYJ.js.map +0 -1
  39. package/dist/chunk-5VPKMXSV.js +0 -190
  40. package/dist/chunk-5VPKMXSV.js.map +0 -1
  41. package/dist/chunk-6CWCR5SO.js +0 -24
  42. package/dist/chunk-6CWCR5SO.js.map +0 -1
  43. package/dist/chunk-7CHGGMP7.js +0 -73
  44. package/dist/chunk-7CHGGMP7.js.map +0 -1
  45. package/dist/chunk-AHRTPV2C.js +0 -1082
  46. package/dist/chunk-AHRTPV2C.js.map +0 -1
  47. package/dist/chunk-GU4LRYYH.js +0 -41
  48. package/dist/chunk-GU4LRYYH.js.map +0 -1
  49. package/dist/chunk-LP5MV76P.js +0 -33
  50. package/dist/chunk-LP5MV76P.js.map +0 -1
  51. package/dist/chunk-OJ356IMJ.js +0 -136
  52. package/dist/chunk-OJ356IMJ.js.map +0 -1
  53. package/dist/chunk-PRNBOQHW.js +0 -32
  54. package/dist/chunk-PRNBOQHW.js.map +0 -1
  55. package/dist/chunk-QTC5Y3LU.js +0 -146
  56. package/dist/chunk-QTC5Y3LU.js.map +0 -1
  57. package/dist/chunk-SNUH2UT6.js +0 -95
  58. package/dist/chunk-SNUH2UT6.js.map +0 -1
  59. package/dist/chunk-UG25BZUN.js +0 -59
  60. package/dist/chunk-UG25BZUN.js.map +0 -1
  61. package/dist/chunk-VWAIXSB6.js +0 -78
  62. package/dist/chunk-VWAIXSB6.js.map +0 -1
  63. package/dist/index.js +0 -99
  64. package/dist/index.js.map +0 -1
  65. package/dist/info-3PW6A6JA.js +0 -80
  66. package/dist/info-3PW6A6JA.js.map +0 -1
  67. package/dist/init-MOJNLWXK.js +0 -212
  68. package/dist/init-MOJNLWXK.js.map +0 -1
  69. package/dist/logout-YPGZ6BB6.js +0 -27
  70. package/dist/logout-YPGZ6BB6.js.map +0 -1
  71. package/dist/publish-W37Y6TO6.js +0 -211
  72. package/dist/publish-W37Y6TO6.js.map +0 -1
  73. package/dist/run-M4XXNPGD.js +0 -291
  74. package/dist/run-M4XXNPGD.js.map +0 -1
  75. package/dist/search-2KNAXPM5.js +0 -55
  76. package/dist/search-2KNAXPM5.js.map +0 -1
  77. package/dist/update-UCT4K6IQ.js +0 -125
  78. package/dist/update-UCT4K6IQ.js.map +0 -1
  79. package/dist/validate-GFDSEVPV.js +0 -12
  80. package/dist/validate-GFDSEVPV.js.map +0 -1
@@ -0,0 +1,596 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "a1b2c3d4-0005-4005-8005-000000000005",
5
+ "prevId": "a1b2c3d4-0004-4004-8004-000000000004",
6
+ "tables": {
7
+ "events": {
8
+ "name": "events",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "integer",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "project_id": {
18
+ "name": "project_id",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "feature_id": {
25
+ "name": "feature_id",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": false,
29
+ "autoincrement": false
30
+ },
31
+ "run_id": {
32
+ "name": "run_id",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "ticket_id": {
39
+ "name": "ticket_id",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ },
45
+ "ts": {
46
+ "name": "ts",
47
+ "type": "integer",
48
+ "primaryKey": false,
49
+ "notNull": true,
50
+ "autoincrement": false
51
+ },
52
+ "type": {
53
+ "name": "type",
54
+ "type": "text",
55
+ "primaryKey": false,
56
+ "notNull": true,
57
+ "autoincrement": false
58
+ },
59
+ "message": {
60
+ "name": "message",
61
+ "type": "text",
62
+ "primaryKey": false,
63
+ "notNull": true,
64
+ "autoincrement": false
65
+ },
66
+ "data": {
67
+ "name": "data",
68
+ "type": "text",
69
+ "primaryKey": false,
70
+ "notNull": false,
71
+ "autoincrement": false
72
+ }
73
+ },
74
+ "indexes": {},
75
+ "foreignKeys": {},
76
+ "compositePrimaryKeys": {},
77
+ "uniqueConstraints": {},
78
+ "checkConstraints": {}
79
+ },
80
+ "features": {
81
+ "name": "features",
82
+ "columns": {
83
+ "id": {
84
+ "name": "id",
85
+ "type": "text",
86
+ "primaryKey": true,
87
+ "notNull": true,
88
+ "autoincrement": false
89
+ },
90
+ "project_id": {
91
+ "name": "project_id",
92
+ "type": "text",
93
+ "primaryKey": false,
94
+ "notNull": true,
95
+ "autoincrement": false
96
+ },
97
+ "slug": {
98
+ "name": "slug",
99
+ "type": "text",
100
+ "primaryKey": false,
101
+ "notNull": true,
102
+ "autoincrement": false
103
+ },
104
+ "title": {
105
+ "name": "title",
106
+ "type": "text",
107
+ "primaryKey": false,
108
+ "notNull": true,
109
+ "autoincrement": false
110
+ },
111
+ "one_liner": {
112
+ "name": "one_liner",
113
+ "type": "text",
114
+ "primaryKey": false,
115
+ "notNull": true,
116
+ "autoincrement": false
117
+ },
118
+ "size": {
119
+ "name": "size",
120
+ "type": "text",
121
+ "primaryKey": false,
122
+ "notNull": true,
123
+ "autoincrement": false
124
+ },
125
+ "phase": {
126
+ "name": "phase",
127
+ "type": "text",
128
+ "primaryKey": false,
129
+ "notNull": true,
130
+ "autoincrement": false
131
+ },
132
+ "branch": {
133
+ "name": "branch",
134
+ "type": "text",
135
+ "primaryKey": false,
136
+ "notNull": true,
137
+ "autoincrement": false
138
+ },
139
+ "status": {
140
+ "name": "status",
141
+ "type": "text",
142
+ "primaryKey": false,
143
+ "notNull": true,
144
+ "autoincrement": false
145
+ },
146
+ "created_at": {
147
+ "name": "created_at",
148
+ "type": "integer",
149
+ "primaryKey": false,
150
+ "notNull": true,
151
+ "autoincrement": false
152
+ },
153
+ "mapped": {
154
+ "name": "mapped",
155
+ "type": "integer",
156
+ "primaryKey": false,
157
+ "notNull": true,
158
+ "autoincrement": false,
159
+ "default": "false"
160
+ }
161
+ },
162
+ "indexes": {},
163
+ "foreignKeys": {},
164
+ "compositePrimaryKeys": {},
165
+ "uniqueConstraints": {},
166
+ "checkConstraints": {}
167
+ },
168
+ "gate_overrides": {
169
+ "name": "gate_overrides",
170
+ "columns": {
171
+ "id": {
172
+ "name": "id",
173
+ "type": "integer",
174
+ "primaryKey": true,
175
+ "notNull": true,
176
+ "autoincrement": true
177
+ },
178
+ "feature_id": {
179
+ "name": "feature_id",
180
+ "type": "text",
181
+ "primaryKey": false,
182
+ "notNull": true,
183
+ "autoincrement": false
184
+ },
185
+ "gate": {
186
+ "name": "gate",
187
+ "type": "text",
188
+ "primaryKey": false,
189
+ "notNull": true,
190
+ "autoincrement": false
191
+ },
192
+ "reason": {
193
+ "name": "reason",
194
+ "type": "text",
195
+ "primaryKey": false,
196
+ "notNull": true,
197
+ "autoincrement": false
198
+ },
199
+ "ts": {
200
+ "name": "ts",
201
+ "type": "integer",
202
+ "primaryKey": false,
203
+ "notNull": true,
204
+ "autoincrement": false
205
+ }
206
+ },
207
+ "indexes": {},
208
+ "foreignKeys": {},
209
+ "compositePrimaryKeys": {},
210
+ "uniqueConstraints": {},
211
+ "checkConstraints": {}
212
+ },
213
+ "projects": {
214
+ "name": "projects",
215
+ "columns": {
216
+ "id": {
217
+ "name": "id",
218
+ "type": "text",
219
+ "primaryKey": true,
220
+ "notNull": true,
221
+ "autoincrement": false
222
+ },
223
+ "name": {
224
+ "name": "name",
225
+ "type": "text",
226
+ "primaryKey": false,
227
+ "notNull": true,
228
+ "autoincrement": false
229
+ },
230
+ "repo_path": {
231
+ "name": "repo_path",
232
+ "type": "text",
233
+ "primaryKey": false,
234
+ "notNull": true,
235
+ "autoincrement": false
236
+ },
237
+ "main_branch": {
238
+ "name": "main_branch",
239
+ "type": "text",
240
+ "primaryKey": false,
241
+ "notNull": true,
242
+ "autoincrement": false
243
+ },
244
+ "dev_command": {
245
+ "name": "dev_command",
246
+ "type": "text",
247
+ "primaryKey": false,
248
+ "notNull": false,
249
+ "autoincrement": false
250
+ },
251
+ "model": {
252
+ "name": "model",
253
+ "type": "text",
254
+ "primaryKey": false,
255
+ "notNull": false,
256
+ "autoincrement": false
257
+ },
258
+ "sandbox": {
259
+ "name": "sandbox",
260
+ "type": "text",
261
+ "primaryKey": false,
262
+ "notNull": false,
263
+ "autoincrement": false
264
+ },
265
+ "closed_at": {
266
+ "name": "closed_at",
267
+ "type": "integer",
268
+ "primaryKey": false,
269
+ "notNull": false,
270
+ "autoincrement": false
271
+ }
272
+ },
273
+ "indexes": {},
274
+ "foreignKeys": {},
275
+ "compositePrimaryKeys": {},
276
+ "uniqueConstraints": {},
277
+ "checkConstraints": {}
278
+ },
279
+ "runs": {
280
+ "name": "runs",
281
+ "columns": {
282
+ "id": {
283
+ "name": "id",
284
+ "type": "text",
285
+ "primaryKey": true,
286
+ "notNull": true,
287
+ "autoincrement": false
288
+ },
289
+ "feature_id": {
290
+ "name": "feature_id",
291
+ "type": "text",
292
+ "primaryKey": false,
293
+ "notNull": true,
294
+ "autoincrement": false
295
+ },
296
+ "workflow": {
297
+ "name": "workflow",
298
+ "type": "text",
299
+ "primaryKey": false,
300
+ "notNull": true,
301
+ "autoincrement": false
302
+ },
303
+ "status": {
304
+ "name": "status",
305
+ "type": "text",
306
+ "primaryKey": false,
307
+ "notNull": true,
308
+ "autoincrement": false
309
+ },
310
+ "started_at": {
311
+ "name": "started_at",
312
+ "type": "integer",
313
+ "primaryKey": false,
314
+ "notNull": true,
315
+ "autoincrement": false
316
+ },
317
+ "ended_at": {
318
+ "name": "ended_at",
319
+ "type": "integer",
320
+ "primaryKey": false,
321
+ "notNull": false,
322
+ "autoincrement": false
323
+ },
324
+ "summary": {
325
+ "name": "summary",
326
+ "type": "text",
327
+ "primaryKey": false,
328
+ "notNull": false,
329
+ "autoincrement": false
330
+ }
331
+ },
332
+ "indexes": {},
333
+ "foreignKeys": {},
334
+ "compositePrimaryKeys": {},
335
+ "uniqueConstraints": {},
336
+ "checkConstraints": {}
337
+ },
338
+ "sessions": {
339
+ "name": "sessions",
340
+ "columns": {
341
+ "id": {
342
+ "name": "id",
343
+ "type": "text",
344
+ "primaryKey": true,
345
+ "notNull": true,
346
+ "autoincrement": false
347
+ },
348
+ "feature_id": {
349
+ "name": "feature_id",
350
+ "type": "text",
351
+ "primaryKey": false,
352
+ "notNull": true,
353
+ "autoincrement": false
354
+ },
355
+ "kind": {
356
+ "name": "kind",
357
+ "type": "text",
358
+ "primaryKey": false,
359
+ "notNull": true,
360
+ "autoincrement": false
361
+ },
362
+ "cc_session_id": {
363
+ "name": "cc_session_id",
364
+ "type": "text",
365
+ "primaryKey": false,
366
+ "notNull": false,
367
+ "autoincrement": false
368
+ },
369
+ "transcript_path": {
370
+ "name": "transcript_path",
371
+ "type": "text",
372
+ "primaryKey": false,
373
+ "notNull": false,
374
+ "autoincrement": false
375
+ },
376
+ "status": {
377
+ "name": "status",
378
+ "type": "text",
379
+ "primaryKey": false,
380
+ "notNull": true,
381
+ "autoincrement": false
382
+ },
383
+ "worktree_path": {
384
+ "name": "worktree_path",
385
+ "type": "text",
386
+ "primaryKey": false,
387
+ "notNull": true,
388
+ "autoincrement": false
389
+ }
390
+ },
391
+ "indexes": {},
392
+ "foreignKeys": {},
393
+ "compositePrimaryKeys": {},
394
+ "uniqueConstraints": {},
395
+ "checkConstraints": {}
396
+ },
397
+ "tickets": {
398
+ "name": "tickets",
399
+ "columns": {
400
+ "id": {
401
+ "name": "id",
402
+ "type": "text",
403
+ "primaryKey": true,
404
+ "notNull": true,
405
+ "autoincrement": false
406
+ },
407
+ "feature_id": {
408
+ "name": "feature_id",
409
+ "type": "text",
410
+ "primaryKey": false,
411
+ "notNull": true,
412
+ "autoincrement": false
413
+ },
414
+ "seq": {
415
+ "name": "seq",
416
+ "type": "integer",
417
+ "primaryKey": false,
418
+ "notNull": true,
419
+ "autoincrement": false
420
+ },
421
+ "title": {
422
+ "name": "title",
423
+ "type": "text",
424
+ "primaryKey": false,
425
+ "notNull": true,
426
+ "autoincrement": false
427
+ },
428
+ "goal": {
429
+ "name": "goal",
430
+ "type": "text",
431
+ "primaryKey": false,
432
+ "notNull": true,
433
+ "autoincrement": false
434
+ },
435
+ "context": {
436
+ "name": "context",
437
+ "type": "text",
438
+ "primaryKey": false,
439
+ "notNull": true,
440
+ "autoincrement": false
441
+ },
442
+ "acceptance_criteria": {
443
+ "name": "acceptance_criteria",
444
+ "type": "text",
445
+ "primaryKey": false,
446
+ "notNull": true,
447
+ "autoincrement": false
448
+ },
449
+ "seams": {
450
+ "name": "seams",
451
+ "type": "text",
452
+ "primaryKey": false,
453
+ "notNull": true,
454
+ "autoincrement": false
455
+ },
456
+ "blocked_by": {
457
+ "name": "blocked_by",
458
+ "type": "text",
459
+ "primaryKey": false,
460
+ "notNull": true,
461
+ "autoincrement": false
462
+ },
463
+ "status": {
464
+ "name": "status",
465
+ "type": "text",
466
+ "primaryKey": false,
467
+ "notNull": true,
468
+ "autoincrement": false
469
+ },
470
+ "commits": {
471
+ "name": "commits",
472
+ "type": "text",
473
+ "primaryKey": false,
474
+ "notNull": true,
475
+ "autoincrement": false
476
+ },
477
+ "error": {
478
+ "name": "error",
479
+ "type": "text",
480
+ "primaryKey": false,
481
+ "notNull": false,
482
+ "autoincrement": false
483
+ }
484
+ },
485
+ "indexes": {},
486
+ "foreignKeys": {},
487
+ "compositePrimaryKeys": {},
488
+ "uniqueConstraints": {},
489
+ "checkConstraints": {}
490
+ },
491
+ "waypoints": {
492
+ "name": "waypoints",
493
+ "columns": {
494
+ "id": {
495
+ "name": "id",
496
+ "type": "text",
497
+ "primaryKey": true,
498
+ "notNull": true,
499
+ "autoincrement": false
500
+ },
501
+ "feature_id": {
502
+ "name": "feature_id",
503
+ "type": "text",
504
+ "primaryKey": false,
505
+ "notNull": true,
506
+ "autoincrement": false
507
+ },
508
+ "seq": {
509
+ "name": "seq",
510
+ "type": "integer",
511
+ "primaryKey": false,
512
+ "notNull": true,
513
+ "autoincrement": false
514
+ },
515
+ "title": {
516
+ "name": "title",
517
+ "type": "text",
518
+ "primaryKey": false,
519
+ "notNull": true,
520
+ "autoincrement": false
521
+ },
522
+ "type": {
523
+ "name": "type",
524
+ "type": "text",
525
+ "primaryKey": false,
526
+ "notNull": true,
527
+ "autoincrement": false
528
+ },
529
+ "question": {
530
+ "name": "question",
531
+ "type": "text",
532
+ "primaryKey": false,
533
+ "notNull": true,
534
+ "autoincrement": false
535
+ },
536
+ "blocked_by": {
537
+ "name": "blocked_by",
538
+ "type": "text",
539
+ "primaryKey": false,
540
+ "notNull": true,
541
+ "autoincrement": false
542
+ },
543
+ "origin_waypoint_id": {
544
+ "name": "origin_waypoint_id",
545
+ "type": "text",
546
+ "primaryKey": false,
547
+ "notNull": false,
548
+ "autoincrement": false
549
+ },
550
+ "status": {
551
+ "name": "status",
552
+ "type": "text",
553
+ "primaryKey": false,
554
+ "notNull": true,
555
+ "autoincrement": false
556
+ },
557
+ "claimed_by": {
558
+ "name": "claimed_by",
559
+ "type": "text",
560
+ "primaryKey": false,
561
+ "notNull": false,
562
+ "autoincrement": false
563
+ },
564
+ "last_session_id": {
565
+ "name": "last_session_id",
566
+ "type": "text",
567
+ "primaryKey": false,
568
+ "notNull": false,
569
+ "autoincrement": false
570
+ },
571
+ "summary": {
572
+ "name": "summary",
573
+ "type": "text",
574
+ "primaryKey": false,
575
+ "notNull": false,
576
+ "autoincrement": false
577
+ }
578
+ },
579
+ "indexes": {},
580
+ "foreignKeys": {},
581
+ "compositePrimaryKeys": {},
582
+ "uniqueConstraints": {},
583
+ "checkConstraints": {}
584
+ }
585
+ },
586
+ "views": {},
587
+ "enums": {},
588
+ "_meta": {
589
+ "schemas": {},
590
+ "tables": {},
591
+ "columns": {}
592
+ },
593
+ "internal": {
594
+ "indexes": {}
595
+ }
596
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1784012829567,
9
+ "tag": "0000_amused_bruce_banner",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1784012829568,
16
+ "tag": "0001_mapped_feature_flag",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1784012829569,
23
+ "tag": "0002_waypoints",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1784012829570,
30
+ "tag": "0003_project_closed_at",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1784012829571,
37
+ "tag": "0004_events_project_id",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1784012829572,
44
+ "tag": "0005_project_setting_overrides",
45
+ "breakpoints": true
46
+ }
47
+ ]
48
+ }
package/hook-client.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Standalone hook client (SPEC §5.5). Runs INSIDE a Claude Code session, invoked
3
+ * by the settings.json hooks as `bun run "<abs path>" <route-event>`.
4
+ *
5
+ * It reads the hook JSON from stdin, POSTs `{ event, sessionId, payload }` to
6
+ * `RUNCASTLE_SERVER_URL/api/hooks/<event>`, and prints the server's response body
7
+ * VERBATIM to stdout (the server returns the final hook JSON that Claude Code
8
+ * consumes as context). Zero dependencies (plain `fetch`), 3s timeout. On ANY
9
+ * error it prints `{}` and exits 0 — a hook must never break the user's session.
10
+ */
11
+
12
+ const FETCH_TIMEOUT_MS = 3000
13
+ const STDIN_TIMEOUT_MS = 2500
14
+
15
+ async function readStdin(): Promise<string> {
16
+ const chunks: Buffer[] = []
17
+ return await new Promise<string>((resolvePromise) => {
18
+ let settled = false
19
+ const finish = (): void => {
20
+ if (settled) return
21
+ settled = true
22
+ resolvePromise(Buffer.concat(chunks).toString('utf8'))
23
+ }
24
+ const stdin = process.stdin
25
+ stdin.on('data', (c: Buffer | string) => {
26
+ chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c))
27
+ })
28
+ stdin.on('end', finish)
29
+ stdin.on('error', finish)
30
+ // Safety net: never hang if stdin is a TTY that never closes.
31
+ const t = setTimeout(finish, STDIN_TIMEOUT_MS)
32
+ if (typeof t.unref === 'function') t.unref()
33
+ })
34
+ }
35
+
36
+ export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
37
+ const event = argv[0] ?? ''
38
+ const serverUrl = process.env.RUNCASTLE_SERVER_URL ?? 'http://localhost:4512'
39
+ const sessionId = process.env.RUNCASTLE_SESSION_ID
40
+
41
+ const raw = await readStdin()
42
+ let payload: unknown
43
+ try {
44
+ payload = raw.trim().length > 0 ? JSON.parse(raw) : {}
45
+ } catch {
46
+ payload = { raw }
47
+ }
48
+
49
+ const res = await fetch(`${serverUrl}/api/hooks/${event}`, {
50
+ method: 'POST',
51
+ headers: { 'content-type': 'application/json' },
52
+ body: JSON.stringify({ event, sessionId, payload }),
53
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
54
+ })
55
+
56
+ const text = await res.text()
57
+ process.stdout.write(text.trim().length > 0 ? text : '{}')
58
+ }
59
+
60
+ if (import.meta.main) {
61
+ main().catch((err) => {
62
+ // Never break the user's session: log, print `{}`, exit 0 (SPEC §5.5).
63
+ console.error(err)
64
+ console.log('{}')
65
+ process.exit(0)
66
+ })
67
+ }