@ggui-ai/mcp-server 0.1.0-rc.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 (141) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +48 -0
  3. package/dist/admin-blueprints-transport.d.ts +114 -0
  4. package/dist/admin-blueprints-transport.d.ts.map +1 -0
  5. package/dist/admin-blueprints-transport.js +118 -0
  6. package/dist/admin-oauth-providers-transport.d.ts +40 -0
  7. package/dist/admin-oauth-providers-transport.d.ts.map +1 -0
  8. package/dist/admin-oauth-providers-transport.js +263 -0
  9. package/dist/auth.d.ts +39 -0
  10. package/dist/auth.d.ts.map +1 -0
  11. package/dist/auth.js +75 -0
  12. package/dist/build-mcp.d.ts +128 -0
  13. package/dist/build-mcp.d.ts.map +1 -0
  14. package/dist/build-mcp.js +113 -0
  15. package/dist/code-store-fs.d.ts +19 -0
  16. package/dist/code-store-fs.d.ts.map +1 -0
  17. package/dist/code-store-fs.js +98 -0
  18. package/dist/console-auth.d.ts +139 -0
  19. package/dist/console-auth.d.ts.map +1 -0
  20. package/dist/console-auth.js +102 -0
  21. package/dist/console-cache.d.ts +78 -0
  22. package/dist/console-cache.d.ts.map +1 -0
  23. package/dist/console-cache.js +105 -0
  24. package/dist/console-headers.d.ts +124 -0
  25. package/dist/console-headers.d.ts.map +1 -0
  26. package/dist/console-headers.js +49 -0
  27. package/dist/console-llm-trace.d.ts +66 -0
  28. package/dist/console-llm-trace.d.ts.map +1 -0
  29. package/dist/console-llm-trace.js +105 -0
  30. package/dist/console-payloads.d.ts +67 -0
  31. package/dist/console-payloads.d.ts.map +1 -0
  32. package/dist/console-payloads.js +105 -0
  33. package/dist/console-theme-routes.d.ts +111 -0
  34. package/dist/console-theme-routes.d.ts.map +1 -0
  35. package/dist/console-theme-routes.js +202 -0
  36. package/dist/console-timeline.d.ts +45 -0
  37. package/dist/console-timeline.d.ts.map +1 -0
  38. package/dist/console-timeline.js +169 -0
  39. package/dist/console-validator.d.ts +67 -0
  40. package/dist/console-validator.d.ts.map +1 -0
  41. package/dist/console-validator.js +105 -0
  42. package/dist/console-welcome.d.ts +7 -0
  43. package/dist/console-welcome.d.ts.map +1 -0
  44. package/dist/console-welcome.js +221 -0
  45. package/dist/csrf-middleware.d.ts +55 -0
  46. package/dist/csrf-middleware.d.ts.map +1 -0
  47. package/dist/csrf-middleware.js +138 -0
  48. package/dist/email-login.d.ts +174 -0
  49. package/dist/email-login.d.ts.map +1 -0
  50. package/dist/email-login.js +254 -0
  51. package/dist/email-resend.d.ts +29 -0
  52. package/dist/email-resend.d.ts.map +1 -0
  53. package/dist/email-resend.js +71 -0
  54. package/dist/email-sender-from-env.d.ts +34 -0
  55. package/dist/email-sender-from-env.d.ts.map +1 -0
  56. package/dist/email-sender-from-env.js +112 -0
  57. package/dist/email-smtp.d.ts +42 -0
  58. package/dist/email-smtp.d.ts.map +1 -0
  59. package/dist/email-smtp.js +81 -0
  60. package/dist/index.d.ts +102 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +122 -0
  63. package/dist/instructions-presets.d.ts +112 -0
  64. package/dist/instructions-presets.d.ts.map +1 -0
  65. package/dist/instructions-presets.js +195 -0
  66. package/dist/llm-backed-negotiator.d.ts +178 -0
  67. package/dist/llm-backed-negotiator.d.ts.map +1 -0
  68. package/dist/llm-backed-negotiator.js +579 -0
  69. package/dist/logger.d.ts +23 -0
  70. package/dist/logger.d.ts.map +1 -0
  71. package/dist/logger.js +41 -0
  72. package/dist/mcp-apps-inbound.d.ts +86 -0
  73. package/dist/mcp-apps-inbound.d.ts.map +1 -0
  74. package/dist/mcp-apps-inbound.js +278 -0
  75. package/dist/mcp-apps-outbound.d.ts +448 -0
  76. package/dist/mcp-apps-outbound.d.ts.map +1 -0
  77. package/dist/mcp-apps-outbound.js +1163 -0
  78. package/dist/mcp-mounts.d.ts +239 -0
  79. package/dist/mcp-mounts.d.ts.map +1 -0
  80. package/dist/mcp-mounts.js +222 -0
  81. package/dist/oauth-login-types.d.ts +160 -0
  82. package/dist/oauth-login-types.d.ts.map +1 -0
  83. package/dist/oauth-login-types.js +9 -0
  84. package/dist/oauth-login.d.ts +77 -0
  85. package/dist/oauth-login.d.ts.map +1 -0
  86. package/dist/oauth-login.js +455 -0
  87. package/dist/oauth-providers/github.d.ts +17 -0
  88. package/dist/oauth-providers/github.d.ts.map +1 -0
  89. package/dist/oauth-providers/github.js +89 -0
  90. package/dist/oauth-providers/google.d.ts +18 -0
  91. package/dist/oauth-providers/google.d.ts.map +1 -0
  92. package/dist/oauth-providers/google.js +59 -0
  93. package/dist/oauth-providers-store.d.ts +32 -0
  94. package/dist/oauth-providers-store.d.ts.map +1 -0
  95. package/dist/oauth-providers-store.js +291 -0
  96. package/dist/oauth.d.ts +347 -0
  97. package/dist/oauth.d.ts.map +1 -0
  98. package/dist/oauth.js +686 -0
  99. package/dist/pairing-transport.d.ts +99 -0
  100. package/dist/pairing-transport.d.ts.map +1 -0
  101. package/dist/pairing-transport.js +223 -0
  102. package/dist/rate-limit-middleware.d.ts +36 -0
  103. package/dist/rate-limit-middleware.d.ts.map +1 -0
  104. package/dist/rate-limit-middleware.js +57 -0
  105. package/dist/render-gate.d.ts +87 -0
  106. package/dist/render-gate.d.ts.map +1 -0
  107. package/dist/render-gate.js +77 -0
  108. package/dist/render-rate-limit.d.ts +59 -0
  109. package/dist/render-rate-limit.d.ts.map +1 -0
  110. package/dist/render-rate-limit.js +73 -0
  111. package/dist/render-signing.d.ts +98 -0
  112. package/dist/render-signing.d.ts.map +1 -0
  113. package/dist/render-signing.js +113 -0
  114. package/dist/request-context.d.ts +113 -0
  115. package/dist/request-context.d.ts.map +1 -0
  116. package/dist/request-context.js +154 -0
  117. package/dist/reserved-validators.d.ts +22 -0
  118. package/dist/reserved-validators.d.ts.map +1 -0
  119. package/dist/reserved-validators.js +101 -0
  120. package/dist/schema-compat.d.ts +167 -0
  121. package/dist/schema-compat.d.ts.map +1 -0
  122. package/dist/schema-compat.js +187 -0
  123. package/dist/security-headers-middleware.d.ts +38 -0
  124. package/dist/security-headers-middleware.d.ts.map +1 -0
  125. package/dist/security-headers-middleware.js +30 -0
  126. package/dist/server.d.ts +2060 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +6338 -0
  129. package/dist/session-channel.d.ts +651 -0
  130. package/dist/session-channel.d.ts.map +1 -0
  131. package/dist/session-channel.js +1756 -0
  132. package/dist/storage.d.ts +89 -0
  133. package/dist/storage.d.ts.map +1 -0
  134. package/dist/storage.js +171 -0
  135. package/dist/thread-transport.d.ts +118 -0
  136. package/dist/thread-transport.d.ts.map +1 -0
  137. package/dist/thread-transport.js +478 -0
  138. package/dist/user-session-auth.d.ts +167 -0
  139. package/dist/user-session-auth.d.ts.map +1 -0
  140. package/dist/user-session-auth.js +148 -0
  141. package/package.json +76 -0
@@ -0,0 +1,49 @@
1
+ import { GGUI_SESSION_SHELL_SCRIPT_HASH } from './mcp-apps-outbound.js';
2
+ /**
3
+ * The CSP directive string. Exported so tests can assert against the
4
+ * exact shape without hard-coding the directive order inside the test
5
+ * file. Change this string and a focused header test catches the
6
+ * regression.
7
+ */
8
+ export const DEVTOOL_CSP = [
9
+ "default-src 'none'",
10
+ `script-src 'self' blob: data: ${GGUI_SESSION_SHELL_SCRIPT_HASH}`,
11
+ // Google Fonts CDN allowlisted for the brand-kit Inter + Geist Mono
12
+ // pair used by the public welcome page (`/`). The same allowlist
13
+ // applies to every console-served HTML — every page may opt to load
14
+ // these fonts; pages that don't reference them pay zero cost.
15
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
16
+ "connect-src 'self'",
17
+ "img-src 'self' data:",
18
+ "font-src 'self' https://fonts.gstatic.com",
19
+ "frame-ancestors 'none'",
20
+ "base-uri 'none'",
21
+ "form-action 'self'",
22
+ ].join('; ');
23
+ /**
24
+ * Other security headers applied alongside CSP. Split from `DEVTOOL_CSP`
25
+ * so tests can enumerate them separately. Keep this list small +
26
+ * well-justified — every header is a compat risk on the operator's
27
+ * browser matrix.
28
+ */
29
+ export const DEVTOOL_SECURITY_HEADERS = [
30
+ ['Content-Security-Policy', DEVTOOL_CSP],
31
+ ['X-Content-Type-Options', 'nosniff'],
32
+ ['X-Frame-Options', 'DENY'],
33
+ ['Referrer-Policy', 'strict-origin-when-cross-origin'],
34
+ ['Cross-Origin-Opener-Policy', 'same-origin'],
35
+ ];
36
+ /**
37
+ * Apply the console security header set to a response. Pure
38
+ * side-effect on `res`; returns `void`. Safe to call multiple times on
39
+ * the same response — `setHeader` overwrites.
40
+ *
41
+ * Accepts both Express `Response` and raw Node `ServerResponse` so the
42
+ * `express.static` `setHeaders(res, path, stat)` callback (which passes
43
+ * `ServerResponse`) can reuse the same helper.
44
+ */
45
+ export function applyDevtoolSecurityHeaders(res) {
46
+ for (const [name, value] of DEVTOOL_SECURITY_HEADERS) {
47
+ res.setHeader(name, value);
48
+ }
49
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Console-facing LLM trace sink + REST/SSE endpoints powering
3
+ * `/devtools/llm-trace` in the @ggui-ai/console SPA.
4
+ *
5
+ * This is the OSS-default sink the `ggui serve` process registers via
6
+ * {@link setLlmTraceSink}. A hosted closed runtime may swap in a
7
+ * durable sink (e.g. Redis-backed) — the harness only knows about the
8
+ * {@link LlmTraceSink} contract.
9
+ *
10
+ * **Two surfaces, both admin-gated:**
11
+ * - `GET /ggui/console/llm-trace/recent?limit=<n>` — JSON snapshot
12
+ * of the ring buffer's most recent N events, oldest-first within
13
+ * the page. Used for initial page load.
14
+ * - `GET /ggui/console/llm-trace/stream` — SSE stream of new events
15
+ * as they fire. Heartbeat every 15s to keep proxies awake.
16
+ *
17
+ * **Memory bound.** Default capacity = 200 events. Each event carries
18
+ * the full system + user prompt — at ~25KB/event that's ~5MB peak.
19
+ * Operator can override via `createGguiServer({ llmTrace: { capacity }})`
20
+ * if running on a small box.
21
+ */
22
+ import type { Express } from 'express';
23
+ import type { LlmTraceEvent, LlmTraceSink } from '@ggui-ai/ui-gen/harness/llm-trace-sink';
24
+ /** SSE listener — receives one event per LLM call. */
25
+ type SseListener = (event: LlmTraceEvent) => void;
26
+ /**
27
+ * In-memory ring buffer + listener fanout. Implements
28
+ * {@link LlmTraceSink} so it can be passed to
29
+ * {@link setLlmTraceSink}.
30
+ *
31
+ * **Why a class, not a closure.** Tests + operators read state
32
+ * (`recent()`, listener count) — instance methods on a class beat a
33
+ * pile of getter functions captured in scope. The shape is also the
34
+ * extension point if a hosted closed runtime wants to subclass and
35
+ * pipe events to Redis / DDB / S3 in addition to the ring buffer.
36
+ */
37
+ export declare class BoundedLlmTraceSink implements LlmTraceSink {
38
+ private readonly capacity;
39
+ private readonly buffer;
40
+ private readonly listeners;
41
+ constructor(opts?: {
42
+ readonly capacity?: number;
43
+ });
44
+ emit(event: LlmTraceEvent): void;
45
+ /**
46
+ * Snapshot of the most-recent `limit` events, oldest-first within
47
+ * the returned slice (so the operator UI can append in chronological
48
+ * order without re-sorting).
49
+ */
50
+ recent(limit: number): readonly LlmTraceEvent[];
51
+ /** Subscribe to live events. Returns an unsubscribe function. */
52
+ subscribe(listener: SseListener): () => void;
53
+ /** Listener count — for tests + the eventual `/devtools/info` view. */
54
+ listenerCount(): number;
55
+ /** Buffer size — for tests + future bound enforcement assertions. */
56
+ size(): number;
57
+ }
58
+ /**
59
+ * Mount the `/ggui/console/llm-trace/recent` + `/.../stream` routes on
60
+ * `app`. Caller is responsible for installing the admin gate
61
+ * middleware on these paths beforehand — this function does not
62
+ * re-implement auth.
63
+ */
64
+ export declare function mountConsoleLlmTraceRoutes(app: Express, sink: BoundedLlmTraceSink): void;
65
+ export {};
66
+ //# sourceMappingURL=console-llm-trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-llm-trace.d.ts","sourceRoot":"","sources":["../src/console-llm-trace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACb,MAAM,wCAAwC,CAAC;AAGhD,sDAAsD;AACtD,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAElD;;;;;;;;;;GAUG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;gBAExC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAUjD,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAchC;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE;IAK/C,iEAAiE;IACjE,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,IAAI;IAO5C,uEAAuE;IACvE,aAAa,IAAI,MAAM;IAIvB,qEAAqE;IACrE,IAAI,IAAI,MAAM;CAGf;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,mBAAmB,GACxB,IAAI,CA8CN"}
@@ -0,0 +1,105 @@
1
+ import { applyDevtoolSecurityHeaders } from './console-headers.js';
2
+ /**
3
+ * In-memory ring buffer + listener fanout. Implements
4
+ * {@link LlmTraceSink} so it can be passed to
5
+ * {@link setLlmTraceSink}.
6
+ *
7
+ * **Why a class, not a closure.** Tests + operators read state
8
+ * (`recent()`, listener count) — instance methods on a class beat a
9
+ * pile of getter functions captured in scope. The shape is also the
10
+ * extension point if a hosted closed runtime wants to subclass and
11
+ * pipe events to Redis / DDB / S3 in addition to the ring buffer.
12
+ */
13
+ export class BoundedLlmTraceSink {
14
+ capacity;
15
+ buffer = [];
16
+ listeners = new Set();
17
+ constructor(opts) {
18
+ const cap = opts?.capacity ?? 200;
19
+ if (!Number.isFinite(cap) || cap <= 0) {
20
+ throw new Error(`BoundedLlmTraceSink: capacity must be a positive integer, got ${cap}`);
21
+ }
22
+ this.capacity = Math.floor(cap);
23
+ }
24
+ emit(event) {
25
+ this.buffer.push(event);
26
+ if (this.buffer.length > this.capacity) {
27
+ this.buffer.shift();
28
+ }
29
+ for (const listener of this.listeners) {
30
+ try {
31
+ listener(event);
32
+ }
33
+ catch {
34
+ // One bad listener must not block fan-out to others.
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Snapshot of the most-recent `limit` events, oldest-first within
40
+ * the returned slice (so the operator UI can append in chronological
41
+ * order without re-sorting).
42
+ */
43
+ recent(limit) {
44
+ const n = Math.max(0, Math.min(limit, this.buffer.length));
45
+ return this.buffer.slice(-n);
46
+ }
47
+ /** Subscribe to live events. Returns an unsubscribe function. */
48
+ subscribe(listener) {
49
+ this.listeners.add(listener);
50
+ return () => {
51
+ this.listeners.delete(listener);
52
+ };
53
+ }
54
+ /** Listener count — for tests + the eventual `/devtools/info` view. */
55
+ listenerCount() {
56
+ return this.listeners.size;
57
+ }
58
+ /** Buffer size — for tests + future bound enforcement assertions. */
59
+ size() {
60
+ return this.buffer.length;
61
+ }
62
+ }
63
+ /**
64
+ * Mount the `/ggui/console/llm-trace/recent` + `/.../stream` routes on
65
+ * `app`. Caller is responsible for installing the admin gate
66
+ * middleware on these paths beforehand — this function does not
67
+ * re-implement auth.
68
+ */
69
+ export function mountConsoleLlmTraceRoutes(app, sink) {
70
+ // GET /ggui/console/llm-trace/recent?limit=<n> — JSON snapshot.
71
+ app.get('/ggui/console/llm-trace/recent', (req, res) => {
72
+ applyDevtoolSecurityHeaders(res);
73
+ const limitRaw = req.query['limit'];
74
+ let limit = 100;
75
+ if (typeof limitRaw === 'string') {
76
+ const parsed = Number.parseInt(limitRaw, 10);
77
+ if (Number.isFinite(parsed) && parsed > 0) {
78
+ limit = Math.min(500, parsed);
79
+ }
80
+ }
81
+ res.json({ events: sink.recent(limit) });
82
+ });
83
+ // GET /ggui/console/llm-trace/stream — SSE live stream.
84
+ // Heartbeat comment every 15s so reverse proxies don't kill the
85
+ // connection on idle. Client cleanup unregisters the listener.
86
+ app.get('/ggui/console/llm-trace/stream', (req, res) => {
87
+ applyDevtoolSecurityHeaders(res);
88
+ res.setHeader('content-type', 'text/event-stream');
89
+ res.setHeader('cache-control', 'no-store');
90
+ res.setHeader('connection', 'keep-alive');
91
+ // Flush headers immediately so the EventSource starts receiving.
92
+ res.flushHeaders?.();
93
+ const heartbeat = setInterval(() => {
94
+ // SSE comment frame — clients ignore but proxies see traffic.
95
+ res.write(': ping\n\n');
96
+ }, 15000);
97
+ const off = sink.subscribe((event) => {
98
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
99
+ });
100
+ req.on('close', () => {
101
+ clearInterval(heartbeat);
102
+ off();
103
+ });
104
+ });
105
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Console-facing payload trace sink + REST/SSE endpoints powering
3
+ * `/devtools/payloads` in the @ggui-ai/console SPA.
4
+ *
5
+ * This is the OSS-default sink the `ggui serve` process registers via
6
+ * {@link setPayloadTraceSink}. A hosted closed runtime may swap in a
7
+ * durable sink (e.g. Redis-backed) — the handlers only know about the
8
+ * {@link PayloadTraceSink} contract.
9
+ *
10
+ * **Two surfaces, both admin-gated:**
11
+ * - `GET /ggui/console/payloads/recent?limit=<n>` — JSON snapshot
12
+ * of the ring buffer's most recent N events, oldest-first within
13
+ * the page. Used for initial page load.
14
+ * - `GET /ggui/console/payloads/stream` — SSE stream of new events
15
+ * as they fire. Heartbeat every 15s to keep proxies awake.
16
+ *
17
+ * **Memory bound.** Default capacity = 100 events (vs LlmTraceSink's
18
+ * 200). Each `ggui_push` payload may carry full componentCode, base64
19
+ * blobs, or a fat `story.context`, so cap is tighter to keep peak
20
+ * memory bounded. At ~50 KB / event the worst-case ring is ~5 MB.
21
+ * Operator can override at construction.
22
+ */
23
+ import type { Express } from 'express';
24
+ import type { PayloadTraceEvent, PayloadTraceSink } from '@ggui-ai/mcp-server-handlers/session-mutations';
25
+ /** SSE listener — receives one event per accepted payload. */
26
+ type SseListener = (event: PayloadTraceEvent) => void;
27
+ /**
28
+ * In-memory ring buffer + listener fanout. Implements
29
+ * {@link PayloadTraceSink} so it can be passed to
30
+ * {@link setPayloadTraceSink}.
31
+ *
32
+ * **Why a class, not a closure.** Tests + operators read state
33
+ * (`recent()`, listener count) — instance methods on a class beat a
34
+ * pile of getter functions captured in scope. The shape is also the
35
+ * extension point if a hosted closed runtime wants to subclass and
36
+ * pipe events to Redis / DDB / S3 in addition to the ring buffer.
37
+ */
38
+ export declare class BoundedPayloadTraceSink implements PayloadTraceSink {
39
+ private readonly capacity;
40
+ private readonly buffer;
41
+ private readonly listeners;
42
+ constructor(opts?: {
43
+ readonly capacity?: number;
44
+ });
45
+ emit(event: PayloadTraceEvent): void;
46
+ /**
47
+ * Snapshot of the most-recent `limit` events, oldest-first within
48
+ * the returned slice (so the operator UI can append in chronological
49
+ * order without re-sorting).
50
+ */
51
+ recent(limit: number): readonly PayloadTraceEvent[];
52
+ /** Subscribe to live events. Returns an unsubscribe function. */
53
+ subscribe(listener: SseListener): () => void;
54
+ /** Listener count — for tests + the eventual `/devtools/info` view. */
55
+ listenerCount(): number;
56
+ /** Buffer size — for tests + future bound enforcement assertions. */
57
+ size(): number;
58
+ }
59
+ /**
60
+ * Mount the `/ggui/console/payloads/recent` + `/.../stream` routes on
61
+ * `app`. Caller is responsible for installing the admin gate
62
+ * middleware on these paths beforehand — this function does not
63
+ * re-implement auth.
64
+ */
65
+ export declare function mountConsolePayloadsRoutes(app: Express, sink: BoundedPayloadTraceSink): void;
66
+ export {};
67
+ //# sourceMappingURL=console-payloads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-payloads.d.ts","sourceRoot":"","sources":["../src/console-payloads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,gDAAgD,CAAC;AAGxD,8DAA8D;AAC9D,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;gBAExC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAUjD,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAcpC;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,iBAAiB,EAAE;IAKnD,iEAAiE;IACjE,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,IAAI;IAO5C,uEAAuE;IACvE,aAAa,IAAI,MAAM;IAIvB,qEAAqE;IACrE,IAAI,IAAI,MAAM;CAGf;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CA8CN"}
@@ -0,0 +1,105 @@
1
+ import { applyDevtoolSecurityHeaders } from './console-headers.js';
2
+ /**
3
+ * In-memory ring buffer + listener fanout. Implements
4
+ * {@link PayloadTraceSink} so it can be passed to
5
+ * {@link setPayloadTraceSink}.
6
+ *
7
+ * **Why a class, not a closure.** Tests + operators read state
8
+ * (`recent()`, listener count) — instance methods on a class beat a
9
+ * pile of getter functions captured in scope. The shape is also the
10
+ * extension point if a hosted closed runtime wants to subclass and
11
+ * pipe events to Redis / DDB / S3 in addition to the ring buffer.
12
+ */
13
+ export class BoundedPayloadTraceSink {
14
+ capacity;
15
+ buffer = [];
16
+ listeners = new Set();
17
+ constructor(opts) {
18
+ const cap = opts?.capacity ?? 100;
19
+ if (!Number.isFinite(cap) || cap <= 0) {
20
+ throw new Error(`BoundedPayloadTraceSink: capacity must be a positive integer, got ${cap}`);
21
+ }
22
+ this.capacity = Math.floor(cap);
23
+ }
24
+ emit(event) {
25
+ this.buffer.push(event);
26
+ if (this.buffer.length > this.capacity) {
27
+ this.buffer.shift();
28
+ }
29
+ for (const listener of this.listeners) {
30
+ try {
31
+ listener(event);
32
+ }
33
+ catch {
34
+ // One bad listener must not block fan-out to others.
35
+ }
36
+ }
37
+ }
38
+ /**
39
+ * Snapshot of the most-recent `limit` events, oldest-first within
40
+ * the returned slice (so the operator UI can append in chronological
41
+ * order without re-sorting).
42
+ */
43
+ recent(limit) {
44
+ const n = Math.max(0, Math.min(limit, this.buffer.length));
45
+ return this.buffer.slice(-n);
46
+ }
47
+ /** Subscribe to live events. Returns an unsubscribe function. */
48
+ subscribe(listener) {
49
+ this.listeners.add(listener);
50
+ return () => {
51
+ this.listeners.delete(listener);
52
+ };
53
+ }
54
+ /** Listener count — for tests + the eventual `/devtools/info` view. */
55
+ listenerCount() {
56
+ return this.listeners.size;
57
+ }
58
+ /** Buffer size — for tests + future bound enforcement assertions. */
59
+ size() {
60
+ return this.buffer.length;
61
+ }
62
+ }
63
+ /**
64
+ * Mount the `/ggui/console/payloads/recent` + `/.../stream` routes on
65
+ * `app`. Caller is responsible for installing the admin gate
66
+ * middleware on these paths beforehand — this function does not
67
+ * re-implement auth.
68
+ */
69
+ export function mountConsolePayloadsRoutes(app, sink) {
70
+ // GET /ggui/console/payloads/recent?limit=<n> — JSON snapshot.
71
+ app.get('/ggui/console/payloads/recent', (req, res) => {
72
+ applyDevtoolSecurityHeaders(res);
73
+ const limitRaw = req.query['limit'];
74
+ let limit = 100;
75
+ if (typeof limitRaw === 'string') {
76
+ const parsed = Number.parseInt(limitRaw, 10);
77
+ if (Number.isFinite(parsed) && parsed > 0) {
78
+ limit = Math.min(500, parsed);
79
+ }
80
+ }
81
+ res.json({ events: sink.recent(limit) });
82
+ });
83
+ // GET /ggui/console/payloads/stream — SSE live stream.
84
+ // Heartbeat comment every 15s so reverse proxies don't kill the
85
+ // connection on idle. Client cleanup unregisters the listener.
86
+ app.get('/ggui/console/payloads/stream', (req, res) => {
87
+ applyDevtoolSecurityHeaders(res);
88
+ res.setHeader('content-type', 'text/event-stream');
89
+ res.setHeader('cache-control', 'no-store');
90
+ res.setHeader('connection', 'keep-alive');
91
+ // Flush headers immediately so the EventSource starts receiving.
92
+ res.flushHeaders?.();
93
+ const heartbeat = setInterval(() => {
94
+ // SSE comment frame — clients ignore but proxies see traffic.
95
+ res.write(': ping\n\n');
96
+ }, 15000);
97
+ const off = sink.subscribe((event) => {
98
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
99
+ });
100
+ req.on('close', () => {
101
+ clearInterval(heartbeat);
102
+ off();
103
+ });
104
+ });
105
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Console theme picker routes — `GET /ggui/console/theme` +
3
+ * `POST /ggui/console/theme`.
4
+ *
5
+ * Shape:
6
+ *
7
+ * GET /ggui/console/theme
8
+ * → 200 {
9
+ * presets: ThemeEntry[], // from @ggui-ai/design#listThemes()
10
+ * current: ThemeConfig | null, // ggui.json#theme as parsed
11
+ * writerEnabled: boolean, // POST availability
12
+ * }
13
+ *
14
+ * POST /ggui/console/theme
15
+ * body: ThemeConfig | null // null = clear field, fall back to default
16
+ * → 200 { ok: true }
17
+ * → 400 { error: 'invalid_config', issue } — schema rejected
18
+ * → 501 { error: 'writer_not_configured' } — opts.themeWriter omitted
19
+ * → 500 { error: 'write_failed', message } — writer threw
20
+ *
21
+ * Authentication: piggy-backs on the same admin gate the LLM-keys
22
+ * routes use — operator-only since the value persists to ggui.json,
23
+ * which the OSS server treats as trusted manifest input. End-users
24
+ * who paired into a session don't get to mutate the project's
25
+ * theme. Multi-tenant deployments may relax this in a follow-up by
26
+ * scoping overrides per user (overrides become server state, not
27
+ * file state).
28
+ */
29
+ import type { Express, Request } from 'express';
30
+ import { type ThemeConfig } from '@ggui-ai/project-config';
31
+ /**
32
+ * Persists a theme selection back to disk. The mcp-server never
33
+ * touches the filesystem itself — `@ggui-ai/cli` provides the
34
+ * implementation that knows where `ggui.json` lives.
35
+ *
36
+ * `null` clears the field and falls the manifest back to the
37
+ * default-theme branch on next boot.
38
+ */
39
+ export type ThemeWriter = (config: ThemeConfig | null) => Promise<void>;
40
+ /**
41
+ * Writes an uploaded DTCG theme document next to `ggui.json` under the
42
+ * caller-supplied filename. Atomic + scoped to the project directory —
43
+ * the implementation rejects any filename containing path separators.
44
+ *
45
+ * Pairs with {@link ThemeWriter}: after a successful upload, the route
46
+ * calls `themeWriter({ file: './<filename>', mode })` so the manifest
47
+ * points at the freshly-saved file.
48
+ */
49
+ export type ThemeFileUploader = (filename: string, content: unknown) => Promise<void>;
50
+ interface MountOptions {
51
+ /**
52
+ * Express app to mount onto. The same app the rest of the console
53
+ * routes mount against.
54
+ */
55
+ app: Express;
56
+ /**
57
+ * Current resolved theme selection — what's parsed from ggui.json
58
+ * at boot. `null` when the manifest had no `theme` field. Read-only
59
+ * snapshot; subsequent picker GETs reflect what the operator saved
60
+ * via POST since the snapshot was taken (handled internally).
61
+ */
62
+ initialConfig: ThemeConfig | null;
63
+ /**
64
+ * Optional persister — mounted only when present. When omitted,
65
+ * POST returns 501 so the picker UI can surface a "read-only"
66
+ * banner instead of silently failing.
67
+ */
68
+ themeWriter?: ThemeWriter;
69
+ /**
70
+ * Optional file uploader — mounted only when present alongside
71
+ * `themeWriter`. When omitted, `POST /ggui/console/theme/upload`
72
+ * returns 501 and the picker hides its "Upload theme.json" affordance.
73
+ */
74
+ themeFileUploader?: ThemeFileUploader;
75
+ /**
76
+ * Auth gate — same callable the LLM-keys routes use. Returns
77
+ * `true` when the request carries a valid admin bearer/cookie.
78
+ */
79
+ requestHasAdminAuth: (req: Request) => boolean;
80
+ /**
81
+ * Optional change notifier — fires every time the operator's
82
+ * theme selection changes through `POST /ggui/console/theme` (or
83
+ * the `/upload` variant). The CLI uses this to mirror writes into
84
+ * a shared mutable state cell that the push handler's
85
+ * `themeProvider` reads, so a save reaches the next push without
86
+ * a server restart.
87
+ *
88
+ * Fires AFTER the on-disk write succeeds and AFTER the route's
89
+ * own internal cache updates. Errors thrown by the handler are
90
+ * caught and ignored — the route still returns 200 so the picker
91
+ * UI doesn't see a phantom failure for a downstream subscription
92
+ * issue. Operators with strict-observability needs should wire
93
+ * their own logger inside the callback.
94
+ *
95
+ * Optional: omitting this preserves the legacy per-restart
96
+ * behaviour (POST writes ggui.json; running server stays on the
97
+ * boot-baked theme until restart).
98
+ */
99
+ onConfigChange?: (next: ThemeConfig | null) => void;
100
+ }
101
+ /**
102
+ * Mount `GET /ggui/console/theme` + `POST /ggui/console/theme` onto
103
+ * the express app. Returns nothing — the routes self-register.
104
+ *
105
+ * The current-config state is held in-process (mutable closure) so
106
+ * subsequent GETs reflect the latest POST without re-reading
107
+ * ggui.json. Writes are durable via the supplied `themeWriter`.
108
+ */
109
+ export declare function mountDevtoolThemeRoutes(opts: MountOptions): void;
110
+ export {};
111
+ //# sourceMappingURL=console-theme-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-theme-routes.d.ts","sourceRoot":"","sources":["../src/console-theme-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAY,MAAM,SAAS,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,IAAI,CAAC,CAAC;AAUnB,UAAU,YAAY;IACpB;;;OAGG;IACH,GAAG,EAAE,OAAO,CAAC;IACb;;;;;OAKG;IACH,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;OAGG;IACH,mBAAmB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC/C;;;;;;;;;;;;;;;;;;OAkBG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA4KhE"}