@toon-protocol/views 0.2.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 (206) hide show
  1. package/dist/a2ui/A2UIRenderer.d.ts +47 -0
  2. package/dist/a2ui/A2UIRenderer.d.ts.map +1 -0
  3. package/dist/a2ui/A2UIRenderer.js +106 -0
  4. package/dist/a2ui/A2UIRenderer.js.map +1 -0
  5. package/dist/a2ui/binding.d.ts +46 -0
  6. package/dist/a2ui/binding.d.ts.map +1 -0
  7. package/dist/a2ui/binding.js +111 -0
  8. package/dist/a2ui/binding.js.map +1 -0
  9. package/dist/a2ui/index.d.ts +17 -0
  10. package/dist/a2ui/index.d.ts.map +1 -0
  11. package/dist/a2ui/index.js +17 -0
  12. package/dist/a2ui/index.js.map +1 -0
  13. package/dist/a2ui/types.d.ts +101 -0
  14. package/dist/a2ui/types.d.ts.map +1 -0
  15. package/dist/a2ui/types.js +64 -0
  16. package/dist/a2ui/types.js.map +1 -0
  17. package/dist/a2ui/validate.d.ts +55 -0
  18. package/dist/a2ui/validate.d.ts.map +1 -0
  19. package/dist/a2ui/validate.js +117 -0
  20. package/dist/a2ui/validate.js.map +1 -0
  21. package/dist/app/index.html +246 -0
  22. package/dist/app-bridge/ext-apps-bridge.d.ts +16 -0
  23. package/dist/app-bridge/ext-apps-bridge.d.ts.map +1 -0
  24. package/dist/app-bridge/ext-apps-bridge.js +66 -0
  25. package/dist/app-bridge/ext-apps-bridge.js.map +1 -0
  26. package/dist/app-bridge/types.d.ts +41 -0
  27. package/dist/app-bridge/types.d.ts.map +1 -0
  28. package/dist/app-bridge/types.js +10 -0
  29. package/dist/app-bridge/types.js.map +1 -0
  30. package/dist/app-entry.d.ts +13 -0
  31. package/dist/app-entry.d.ts.map +1 -0
  32. package/dist/app-entry.js +45 -0
  33. package/dist/app-entry.js.map +1 -0
  34. package/dist/atoms/defi.d.ts +3 -0
  35. package/dist/atoms/defi.d.ts.map +1 -0
  36. package/dist/atoms/defi.js +80 -0
  37. package/dist/atoms/defi.js.map +1 -0
  38. package/dist/atoms/fallback.d.ts +4 -0
  39. package/dist/atoms/fallback.d.ts.map +1 -0
  40. package/dist/atoms/fallback.js +15 -0
  41. package/dist/atoms/fallback.js.map +1 -0
  42. package/dist/atoms/forge.d.ts +3 -0
  43. package/dist/atoms/forge.d.ts.map +1 -0
  44. package/dist/atoms/forge.js +50 -0
  45. package/dist/atoms/forge.js.map +1 -0
  46. package/dist/atoms/interactive.d.ts +3 -0
  47. package/dist/atoms/interactive.d.ts.map +1 -0
  48. package/dist/atoms/interactive.js +99 -0
  49. package/dist/atoms/interactive.js.map +1 -0
  50. package/dist/atoms/layout.d.ts +3 -0
  51. package/dist/atoms/layout.d.ts.map +1 -0
  52. package/dist/atoms/layout.js +22 -0
  53. package/dist/atoms/layout.js.map +1 -0
  54. package/dist/atoms/media.d.ts +3 -0
  55. package/dist/atoms/media.d.ts.map +1 -0
  56. package/dist/atoms/media.js +92 -0
  57. package/dist/atoms/media.js.map +1 -0
  58. package/dist/atoms/onboard.d.ts +3 -0
  59. package/dist/atoms/onboard.d.ts.map +1 -0
  60. package/dist/atoms/onboard.js +26 -0
  61. package/dist/atoms/onboard.js.map +1 -0
  62. package/dist/atoms/registry.d.ts +35 -0
  63. package/dist/atoms/registry.d.ts.map +1 -0
  64. package/dist/atoms/registry.js +77 -0
  65. package/dist/atoms/registry.js.map +1 -0
  66. package/dist/atoms/social.d.ts +3 -0
  67. package/dist/atoms/social.d.ts.map +1 -0
  68. package/dist/atoms/social.js +52 -0
  69. package/dist/atoms/social.js.map +1 -0
  70. package/dist/atoms/types.d.ts +72 -0
  71. package/dist/atoms/types.d.ts.map +1 -0
  72. package/dist/atoms/types.js +11 -0
  73. package/dist/atoms/types.js.map +1 -0
  74. package/dist/catalog.d.ts +27 -0
  75. package/dist/catalog.d.ts.map +1 -0
  76. package/dist/catalog.js +121 -0
  77. package/dist/catalog.js.map +1 -0
  78. package/dist/components/mono-id.d.ts +16 -0
  79. package/dist/components/mono-id.d.ts.map +1 -0
  80. package/dist/components/mono-id.js +18 -0
  81. package/dist/components/mono-id.js.map +1 -0
  82. package/dist/components/ui/avatar.d.ts +12 -0
  83. package/dist/components/ui/avatar.d.ts.map +1 -0
  84. package/dist/components/ui/avatar.js +25 -0
  85. package/dist/components/ui/avatar.js.map +1 -0
  86. package/dist/components/ui/badge.d.ts +10 -0
  87. package/dist/components/ui/badge.d.ts.map +1 -0
  88. package/dist/components/ui/badge.js +26 -0
  89. package/dist/components/ui/badge.js.map +1 -0
  90. package/dist/components/ui/button.d.ts +11 -0
  91. package/dist/components/ui/button.d.ts.map +1 -0
  92. package/dist/components/ui/button.js +37 -0
  93. package/dist/components/ui/button.js.map +1 -0
  94. package/dist/components/ui/input.d.ts +4 -0
  95. package/dist/components/ui/input.d.ts.map +1 -0
  96. package/dist/components/ui/input.js +8 -0
  97. package/dist/components/ui/input.js.map +1 -0
  98. package/dist/components/ui/separator.d.ts +5 -0
  99. package/dist/components/ui/separator.d.ts.map +1 -0
  100. package/dist/components/ui/separator.js +9 -0
  101. package/dist/components/ui/separator.js.map +1 -0
  102. package/dist/components/ui/textarea.d.ts +4 -0
  103. package/dist/components/ui/textarea.d.ts.map +1 -0
  104. package/dist/components/ui/textarea.js +8 -0
  105. package/dist/components/ui/textarea.js.map +1 -0
  106. package/dist/examples.d.ts +39 -0
  107. package/dist/examples.d.ts.map +1 -0
  108. package/dist/examples.js +168 -0
  109. package/dist/examples.js.map +1 -0
  110. package/dist/filters.d.ts +40 -0
  111. package/dist/filters.d.ts.map +1 -0
  112. package/dist/filters.js +84 -0
  113. package/dist/filters.js.map +1 -0
  114. package/dist/index.d.ts +18 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +18 -0
  117. package/dist/index.js.map +1 -0
  118. package/dist/lib/cn.d.ts +4 -0
  119. package/dist/lib/cn.d.ts.map +1 -0
  120. package/dist/lib/cn.js +7 -0
  121. package/dist/lib/cn.js.map +1 -0
  122. package/dist/lib/utils.d.ts +3 -0
  123. package/dist/lib/utils.d.ts.map +1 -0
  124. package/dist/lib/utils.js +6 -0
  125. package/dist/lib/utils.js.map +1 -0
  126. package/dist/main.d.ts +3 -0
  127. package/dist/main.d.ts.map +1 -0
  128. package/dist/main.js +7 -0
  129. package/dist/main.js.map +1 -0
  130. package/dist/mcp-ui/ConsentPrompt.d.ts +41 -0
  131. package/dist/mcp-ui/ConsentPrompt.d.ts.map +1 -0
  132. package/dist/mcp-ui/ConsentPrompt.js +45 -0
  133. package/dist/mcp-ui/ConsentPrompt.js.map +1 -0
  134. package/dist/mcp-ui/SandboxedAppRenderer.d.ts +54 -0
  135. package/dist/mcp-ui/SandboxedAppRenderer.d.ts.map +1 -0
  136. package/dist/mcp-ui/SandboxedAppRenderer.js +74 -0
  137. package/dist/mcp-ui/SandboxedAppRenderer.js.map +1 -0
  138. package/dist/mcp-ui/index.d.ts +18 -0
  139. package/dist/mcp-ui/index.d.ts.map +1 -0
  140. package/dist/mcp-ui/index.js +18 -0
  141. package/dist/mcp-ui/index.js.map +1 -0
  142. package/dist/mcp-ui/sandbox.d.ts +62 -0
  143. package/dist/mcp-ui/sandbox.d.ts.map +1 -0
  144. package/dist/mcp-ui/sandbox.js +78 -0
  145. package/dist/mcp-ui/sandbox.js.map +1 -0
  146. package/dist/parsers/media.d.ts +68 -0
  147. package/dist/parsers/media.d.ts.map +1 -0
  148. package/dist/parsers/media.js +124 -0
  149. package/dist/parsers/media.js.map +1 -0
  150. package/dist/parsers/nip34.d.ts +75 -0
  151. package/dist/parsers/nip34.d.ts.map +1 -0
  152. package/dist/parsers/nip34.js +142 -0
  153. package/dist/parsers/nip34.js.map +1 -0
  154. package/dist/parsers/social.d.ts +89 -0
  155. package/dist/parsers/social.d.ts.map +1 -0
  156. package/dist/parsers/social.js +136 -0
  157. package/dist/parsers/social.js.map +1 -0
  158. package/dist/render/node-shims/node-builtins.d.ts +30 -0
  159. package/dist/render/node-shims/node-builtins.d.ts.map +1 -0
  160. package/dist/render/node-shims/node-builtins.js +38 -0
  161. package/dist/render/node-shims/node-builtins.js.map +1 -0
  162. package/dist/render/resolve.d.ts +72 -0
  163. package/dist/render/resolve.d.ts.map +1 -0
  164. package/dist/render/resolve.js +115 -0
  165. package/dist/render/resolve.js.map +1 -0
  166. package/dist/runtime.d.ts +17 -0
  167. package/dist/runtime.d.ts.map +1 -0
  168. package/dist/runtime.js +218 -0
  169. package/dist/runtime.js.map +1 -0
  170. package/dist/server/apps-server.d.ts +32 -0
  171. package/dist/server/apps-server.d.ts.map +1 -0
  172. package/dist/server/apps-server.js +163 -0
  173. package/dist/server/apps-server.js.map +1 -0
  174. package/dist/server/backend.d.ts +148 -0
  175. package/dist/server/backend.d.ts.map +1 -0
  176. package/dist/server/backend.js +11 -0
  177. package/dist/server/backend.js.map +1 -0
  178. package/dist/server/daemon-backend.d.ts +113 -0
  179. package/dist/server/daemon-backend.d.ts.map +1 -0
  180. package/dist/server/daemon-backend.js +79 -0
  181. package/dist/server/daemon-backend.js.map +1 -0
  182. package/dist/server/daemon-main.d.ts +25 -0
  183. package/dist/server/daemon-main.d.ts.map +1 -0
  184. package/dist/server/daemon-main.js +165 -0
  185. package/dist/server/daemon-main.js.map +1 -0
  186. package/dist/server/fake-backend.d.ts +53 -0
  187. package/dist/server/fake-backend.d.ts.map +1 -0
  188. package/dist/server/fake-backend.js +157 -0
  189. package/dist/server/fake-backend.js.map +1 -0
  190. package/dist/server/fake-main.d.ts +15 -0
  191. package/dist/server/fake-main.d.ts.map +1 -0
  192. package/dist/server/fake-main.js +39 -0
  193. package/dist/server/fake-main.js.map +1 -0
  194. package/dist/spec.d.ts +67 -0
  195. package/dist/spec.d.ts.map +1 -0
  196. package/dist/spec.js +221 -0
  197. package/dist/spec.js.map +1 -0
  198. package/dist/tool-names.d.ts +27 -0
  199. package/dist/tool-names.d.ts.map +1 -0
  200. package/dist/tool-names.js +32 -0
  201. package/dist/tool-names.js.map +1 -0
  202. package/dist/types.d.ts +39 -0
  203. package/dist/types.d.ts.map +1 -0
  204. package/dist/types.js +20 -0
  205. package/dist/types.js.map +1 -0
  206. package/package.json +71 -0
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Real TOON apps MCP server (stdio) — the daemon-backed counterpart to
4
+ * {@link ./fake-main fake-main.ts}.
5
+ *
6
+ * Reads resolve over the free Nostr relay side and writes go through the
7
+ * always-on `toon-clientd` control plane: this entrypoint wires a
8
+ * {@link DaemonAppBackend} (over a tiny fetch-based {@link DaemonControl}) into
9
+ * {@link ./apps-server.registerToonApps registerToonApps}. No chain keys ever
10
+ * live in this process or the iframe — the daemon holds the key and signs+pays.
11
+ *
12
+ * pnpm --filter @toon-protocol/views build
13
+ * node packages/views/dist/server/daemon-main.js # toon-clientd must be up
14
+ *
15
+ * Then connect it as an MCP server (e.g. `claude mcp add toon -- node …/daemon-main.js`).
16
+ *
17
+ * The daemon URL is resolved the same way `client-mcp/src/mcp.ts` does — from
18
+ * `TOON_CLIENT_HTTP_PORT` or the config file's `httpPort`, default 8787. To keep
19
+ * `@toon-protocol/views` free of a dependency cycle on `@toon-protocol/client-mcp`
20
+ * (which imports views), this entrypoint speaks the control plane over `fetch`
21
+ * directly rather than importing the client-mcp `ControlClient` — the structural
22
+ * `DaemonControl` port is all `DaemonAppBackend` needs.
23
+ */
24
+ import { readFileSync } from 'node:fs';
25
+ import { homedir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
28
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
29
+ import { registerToonApps } from './apps-server.js';
30
+ import { DaemonAppBackend, } from './daemon-backend.js';
31
+ import {} from './backend.js';
32
+ function loadAppHtml() {
33
+ // Compiled to dist/server/daemon-main.js; the bundle is at dist/app/index.html.
34
+ try {
35
+ return readFileSync(new URL('../app/index.html', import.meta.url), 'utf8');
36
+ }
37
+ catch {
38
+ return '<!doctype html><html><body><div id="root">toon app bundle not built — run `pnpm --filter @toon-protocol/views build`</div></body></html>';
39
+ }
40
+ }
41
+ /** Default `toon-clientd` config path (mirrors client-mcp's `defaultConfigPath`). */
42
+ function defaultConfigPath() {
43
+ return join(homedir(), '.config', 'toon', 'client.json');
44
+ }
45
+ /**
46
+ * Resolve the daemon control-plane URL without needing the mnemonic — the same
47
+ * precedence `client-mcp/src/mcp.ts` uses: `TOON_CLIENT_HTTP_PORT`, then the
48
+ * config file's `httpPort`, default 8787.
49
+ */
50
+ function controlPlaneUrl() {
51
+ let filePort;
52
+ const configPath = process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath();
53
+ try {
54
+ const file = JSON.parse(readFileSync(configPath, 'utf8'));
55
+ filePort = file.httpPort;
56
+ }
57
+ catch {
58
+ /* no/unreadable config — fall back to env / default */
59
+ }
60
+ const port = Number(process.env['TOON_CLIENT_HTTP_PORT'] ?? filePort ?? 8787);
61
+ return `http://127.0.0.1:${port}`;
62
+ }
63
+ /** Thrown when the daemon control plane is unreachable. */
64
+ class DaemonUnreachableError extends Error {
65
+ baseUrl;
66
+ causedBy;
67
+ constructor(baseUrl, causedBy) {
68
+ super(`toon-clientd not reachable at ${baseUrl}`);
69
+ this.baseUrl = baseUrl;
70
+ this.causedBy = causedBy;
71
+ this.name = 'DaemonUnreachableError';
72
+ }
73
+ }
74
+ /**
75
+ * A minimal fetch-based {@link DaemonControl} over the daemon HTTP control plane
76
+ * (`POST /query`, `/publish-unsigned`, `/upload-media`). This is a deliberately
77
+ * tiny subset of client-mcp's `ControlClient`; the production `toon-mcp` server
78
+ * uses the full client (see the §3 reconciliation note in the PR).
79
+ */
80
+ function makeControl(baseUrl) {
81
+ const root = baseUrl.replace(/\/+$/, '');
82
+ const timeoutMs = 35_000; // publishes can wait on FULFILL
83
+ async function post(path, body) {
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
86
+ let res;
87
+ try {
88
+ res = await fetch(`${root}${path}`, {
89
+ method: 'POST',
90
+ headers: { 'content-type': 'application/json' },
91
+ body: JSON.stringify(body),
92
+ signal: controller.signal,
93
+ });
94
+ }
95
+ catch (err) {
96
+ throw new DaemonUnreachableError(root, err);
97
+ }
98
+ finally {
99
+ clearTimeout(timer);
100
+ }
101
+ const text = await res.text();
102
+ const json = text ? JSON.parse(text) : undefined;
103
+ if (!res.ok) {
104
+ const e = (json ?? {});
105
+ throw new Error(`${path} failed (HTTP ${res.status}): ${e.error ?? 'unknown'}${e.detail ? ` — ${e.detail}` : ''}`);
106
+ }
107
+ return json;
108
+ }
109
+ async function get(path) {
110
+ const controller = new AbortController();
111
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
112
+ let res;
113
+ try {
114
+ res = await fetch(`${root}${path}`, { signal: controller.signal });
115
+ }
116
+ catch (err) {
117
+ throw new DaemonUnreachableError(root, err);
118
+ }
119
+ finally {
120
+ clearTimeout(timer);
121
+ }
122
+ const text = await res.text();
123
+ const json = text ? JSON.parse(text) : undefined;
124
+ if (!res.ok) {
125
+ const e = (json ?? {});
126
+ throw new Error(`${path} failed (HTTP ${res.status}): ${e.error ?? 'unknown'}${e.detail ? ` — ${e.detail}` : ''}`);
127
+ }
128
+ return json;
129
+ }
130
+ return {
131
+ // GET /status returns the daemon `StatusResponse`; map the fields the
132
+ // confirm UX needs. `settlementChain` is reported directly; `feePerEvent`
133
+ // is the daemon's configured per-event fee (default '1' if not surfaced).
134
+ status: async () => {
135
+ const s = await get('/status');
136
+ return {
137
+ feePerEvent: s.feePerEvent ?? '1',
138
+ settlementChain: s.settlementChain ?? 'unknown',
139
+ ...(s.asset ? { asset: s.asset } : {}),
140
+ };
141
+ },
142
+ query: (b) => post('/query', b),
143
+ publishUnsigned: (b) => post('/publish-unsigned', b),
144
+ uploadMedia: (b) => post('/upload-media', b),
145
+ openChannel: (b) => post('/channels', b),
146
+ swap: (b) => post('/swap', b),
147
+ };
148
+ }
149
+ async function main() {
150
+ const url = controlPlaneUrl();
151
+ const control = makeControl(url);
152
+ const server = new McpServer({ name: 'toon-client', version: '0.1.0' });
153
+ registerToonApps(server, {
154
+ backend: new DaemonAppBackend(control),
155
+ appHtml: loadAppHtml(),
156
+ });
157
+ await server.connect(new StdioServerTransport());
158
+ // stdout is the MCP channel; log to stderr.
159
+ console.error(`[toon-apps] ready; proxying to ${url}`);
160
+ }
161
+ main().catch((err) => {
162
+ console.error('[toon-apps]', err instanceof Error ? err.stack : String(err));
163
+ process.exitCode = 1;
164
+ });
165
+ //# sourceMappingURL=daemon-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-main.js","sourceRoot":"","sources":["../../src/server/daemon-main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,gBAAgB,GASjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAuC,MAAM,cAAc,CAAC;AAEnE,SAAS,WAAW;IAClB,gFAAgF;IAChF,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,0IAA0I,CAAC;IACpJ,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe;IACtB,IAAI,QAA4B,CAAC;IACjC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,iBAAiB,EAAE,CAAC;IAC5E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAEvD,CAAC;QACF,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;IACzD,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,QAAQ,IAAI,IAAI,CAAC,CAAC;IAC9E,OAAO,oBAAoB,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,2DAA2D;AAC3D,MAAM,sBAAuB,SAAQ,KAAK;IAE7B;IACA;IAFX,YACW,OAAe,EACf,QAAkB;QAE3B,KAAK,CAAC,iCAAiC,OAAO,EAAE,CAAC,CAAC;QAHzC,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAU;QAG3B,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,gCAAgC;IAE1D,KAAK,UAAU,IAAI,CAAI,IAAY,EAAE,IAAa;QAChD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE;gBAClC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,iBAAiB,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI,SAAS,GAC1D,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAChC,EAAE,CACH,CAAC;QACJ,CAAC;QACD,OAAO,IAAS,CAAC;IACnB,CAAC;IAED,KAAK,UAAU,GAAG,CAAI,IAAY;QAChC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwC,CAAC;YAC9D,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,iBAAiB,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI,SAAS,GAC1D,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAChC,EAAE,CACH,CAAC;QACJ,CAAC;QACD,OAAO,IAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,sEAAsE;QACtE,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAM,EAAE,KAAK,IAAmC,EAAE;YAChD,MAAM,CAAC,GAAG,MAAM,GAAG,CAIhB,SAAS,CAAC,CAAC;YACd,OAAO;gBACL,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,GAAG;gBACjC,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,SAAS;gBAC/C,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvC,CAAC;QACJ,CAAC;QACD,KAAK,EAAE,CAAC,CAAqB,EAAE,EAAE,CAC/B,IAAI,CAAsB,QAAQ,EAAE,CAAC,CAAC;QACxC,eAAe,EAAE,CAAC,CAA+B,EAAE,EAAE,CACnD,IAAI,CAAwB,mBAAmB,EAAE,CAAC,CAAC;QACrD,WAAW,EAAE,CAAC,CAA2B,EAAE,EAAE,CAC3C,IAAI,CAA4B,eAAe,EAAE,CAAC,CAAC;QACrD,WAAW,EAAE,CAAC,CAA2B,EAAE,EAAE,CAC3C,IAAI,CAAwB,WAAW,EAAE,CAAC,CAAC;QAC7C,IAAI,EAAE,CAAC,CAAc,EAAE,EAAE,CAAC,IAAI,CAAe,OAAO,EAAE,CAAC,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACxE,gBAAgB,CAAC,MAAM,EAAE;QACvB,OAAO,EAAE,IAAI,gBAAgB,CAAC,OAAO,CAAC;QACtC,OAAO,EAAE,WAAW,EAAE;KACvB,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,4CAA4C;IAC5C,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * In-memory fake backend: a seeded "relay" for reads + writes that append to the
3
+ * same store (so a published note / follow / reaction shows up on the next
4
+ * `toon_query`, making the agent-driven journey feel real). No network, no keys,
5
+ * no payments — good enough to exercise the generative-UI loop while core/sdk
6
+ * are in flux.
7
+ */
8
+ import { type NostrEvent, type NostrFilter } from '../types.js';
9
+ import { type AppBackend, type AppStatus, type PublishResult, type SwapRequest, type SwapResponse, type UploadResult } from './backend.js';
10
+ export declare class FakeBackend implements AppBackend {
11
+ private events;
12
+ private seq;
13
+ /** Fixed base so ids/timestamps are deterministic across a session. */
14
+ private clock;
15
+ /**
16
+ * Deterministic pay-to-write status stub. The confirm UX renders this fee +
17
+ * chain so its snapshot is stable; the real daemon (#16) returns live values
18
+ * with no UI change. No payment, no network.
19
+ */
20
+ status(): Promise<AppStatus>;
21
+ query(filter: NostrFilter): Promise<NostrEvent[]>;
22
+ publish(req: {
23
+ kind: number;
24
+ content?: string;
25
+ tags?: string[][];
26
+ }): Promise<PublishResult>;
27
+ uploadMedia(req: {
28
+ dataBase64: string;
29
+ mime?: string;
30
+ kind?: number;
31
+ caption?: string;
32
+ tags?: string[][];
33
+ }): Promise<UploadResult>;
34
+ /**
35
+ * Pre-open a payment channel. Deterministic id so the example renders the
36
+ * same receipt every time. No keys, no settlement — the real daemon (#16)
37
+ * delegates to `control-client.openChannel`.
38
+ */
39
+ openChannel(req: {
40
+ destination?: string;
41
+ }): Promise<{
42
+ channelId: string;
43
+ }>;
44
+ /**
45
+ * Run a swap. Returns a canned `SwapResponse` with one `SwapClaim` derived
46
+ * deterministically from the request (rate applied to the source amount) so
47
+ * the settlement receipt is reproducible. No signing happens here.
48
+ */
49
+ swap(req: SwapRequest): Promise<SwapResponse>;
50
+ /** Test helper: total events currently in the store. */
51
+ size(): number;
52
+ }
53
+ //# sourceMappingURL=fake-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-backend.d.ts","sourceRoot":"","sources":["../../src/server/fake-backend.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EACL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAqDtB,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,GAAG,CAAK;IAChB,uEAAuE;IACvE,OAAO,CAAC,KAAK,CAAiB;IAE9B;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAI5B,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAQjD,OAAO,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAkB3F,WAAW,CAAC,GAAG,EAAE;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,CAAC;IAgBzB;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAK1E;;;;OAIG;IACH,IAAI,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA0B7C,wDAAwD;IACxD,IAAI,IAAI,MAAM;CAGf"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * In-memory fake backend: a seeded "relay" for reads + writes that append to the
3
+ * same store (so a published note / follow / reaction shows up on the next
4
+ * `toon_query`, making the agent-driven journey feel real). No network, no keys,
5
+ * no payments — good enough to exercise the generative-UI loop while core/sdk
6
+ * are in flux.
7
+ */
8
+ import {} from '../types.js';
9
+ import {} from './backend.js';
10
+ const SELF = 'fakeself00000000000000000000000000000000000000000000000000000000';
11
+ const ALICE = 'a11ce0000000000000000000000000000000000000000000000000000000aaaa';
12
+ const BOB = 'b0b0000000000000000000000000000000000000000000000000000000000bbb';
13
+ function ev(e) {
14
+ return {
15
+ pubkey: e.pubkey ?? ALICE,
16
+ created_at: e.created_at ?? 1_700_000_000,
17
+ tags: e.tags ?? [],
18
+ content: e.content ?? '',
19
+ sig: e.sig ?? 'fakesig',
20
+ ...e,
21
+ };
22
+ }
23
+ /** A small but representative dataset spanning the supported NIPs. */
24
+ function seed() {
25
+ return [
26
+ // profiles (kind:0)
27
+ ev({ id: 'p_alice', kind: 0, pubkey: ALICE, content: JSON.stringify({ name: 'alice', about: 'building toon', nip05: 'alice@toon.dev' }) }),
28
+ ev({ id: 'p_bob', kind: 0, pubkey: BOB, content: JSON.stringify({ name: 'bob', about: 'just here for the pics' }) }),
29
+ // notes (kind:1) — a tiny thread
30
+ ev({ id: 'n_root', kind: 1, pubkey: ALICE, content: 'gm — first post over TOON', created_at: 1_700_000_100 }),
31
+ ev({ id: 'n_reply', kind: 1, pubkey: BOB, content: 'gm! looks great', created_at: 1_700_000_200, tags: [['e', 'n_root', '', 'root'], ['p', ALICE]] }),
32
+ // follows (kind:3)
33
+ ev({ id: 'f_self', kind: 3, pubkey: SELF, created_at: 1_700_000_050, tags: [['p', ALICE]] }),
34
+ // reactions (kind:7)
35
+ ev({ id: 'r1', kind: 7, pubkey: BOB, content: '+', tags: [['e', 'n_root'], ['p', ALICE]] }),
36
+ // forge: repo (30617) + issue (1621)
37
+ ev({ id: 'repo1', kind: 30617, pubkey: ALICE, tags: [['d', 'toon'], ['name', 'toon'], ['description', 'pay-to-write nostr'], ['r', 'HEAD', 'main']] }),
38
+ ev({ id: 'issue1', kind: 1621, pubkey: BOB, content: 'reads should be free', tags: [['subject', 'Free reads'], ['a', `30617:${ALICE}:toon`], ['t', 'design']] }),
39
+ // media: a picture post (kind:20, NIP-68 + NIP-92 imeta)
40
+ ev({ id: 'pic1', kind: 20, pubkey: ALICE, content: 'sunset over the mempool', tags: [['title', 'Sunset'], ['imeta', 'url https://arweave.net/seed-pic', 'm image/png'], ['t', 'photo']] }),
41
+ ];
42
+ }
43
+ function matches(event, filter) {
44
+ if (filter.ids && !filter.ids.includes(event.id))
45
+ return false;
46
+ if (filter.kinds && !filter.kinds.includes(event.kind))
47
+ return false;
48
+ if (filter.authors && !filter.authors.includes(event.pubkey))
49
+ return false;
50
+ if (filter.since !== undefined && event.created_at < filter.since)
51
+ return false;
52
+ if (filter.until !== undefined && event.created_at > filter.until)
53
+ return false;
54
+ for (const [key, values] of Object.entries(filter)) {
55
+ if (!key.startsWith('#') || !Array.isArray(values))
56
+ continue;
57
+ const letter = key.slice(1);
58
+ const hit = event.tags.some((t) => t[0] === letter && t[1] !== undefined && values.includes(t[1]));
59
+ if (!hit)
60
+ return false;
61
+ }
62
+ return true;
63
+ }
64
+ export class FakeBackend {
65
+ events = seed();
66
+ seq = 0;
67
+ /** Fixed base so ids/timestamps are deterministic across a session. */
68
+ clock = 1_700_001_000;
69
+ /**
70
+ * Deterministic pay-to-write status stub. The confirm UX renders this fee +
71
+ * chain so its snapshot is stable; the real daemon (#16) returns live values
72
+ * with no UI change. No payment, no network.
73
+ */
74
+ status() {
75
+ return Promise.resolve({ feePerEvent: '1', settlementChain: 'base', asset: 'USDC' });
76
+ }
77
+ query(filter) {
78
+ const out = this.events
79
+ .filter((e) => matches(e, filter))
80
+ .sort((a, b) => b.created_at - a.created_at);
81
+ const limited = filter.limit !== undefined ? out.slice(0, filter.limit) : out;
82
+ return Promise.resolve(limited);
83
+ }
84
+ publish(req) {
85
+ const id = `w_${++this.seq}`;
86
+ const event = ev({
87
+ id,
88
+ kind: req.kind,
89
+ pubkey: SELF,
90
+ content: req.content ?? '',
91
+ tags: req.tags ?? [],
92
+ created_at: ++this.clock,
93
+ });
94
+ // Replaceable kinds (0/3): drop the prior self event so reads reflect the latest.
95
+ if (req.kind === 0 || req.kind === 3) {
96
+ this.events = this.events.filter((e) => !(e.kind === req.kind && e.pubkey === SELF));
97
+ }
98
+ this.events.push(event);
99
+ return Promise.resolve({ eventId: id, channelId: 'fake-channel', nonce: this.seq });
100
+ }
101
+ uploadMedia(req) {
102
+ const txId = `fake-${++this.seq}`;
103
+ const url = `https://arweave.net/${txId}`;
104
+ const kind = req.kind ?? 1063;
105
+ const mime = req.mime ?? 'application/octet-stream';
106
+ const mediaTags = kind === 1063
107
+ ? [['url', url], ['m', mime], ...(req.tags ?? [])]
108
+ : [['imeta', `url ${url}`, `m ${mime}`], ...(req.tags ?? [])];
109
+ const id = `w_${this.seq}`;
110
+ this.events.push(ev({ id, kind, pubkey: SELF, content: req.caption ?? '', tags: mediaTags, created_at: ++this.clock }));
111
+ return Promise.resolve({ eventId: id, url, txId, channelId: 'fake-channel', nonce: this.seq });
112
+ }
113
+ /**
114
+ * Pre-open a payment channel. Deterministic id so the example renders the
115
+ * same receipt every time. No keys, no settlement — the real daemon (#16)
116
+ * delegates to `control-client.openChannel`.
117
+ */
118
+ openChannel(req) {
119
+ const channelId = `fake-channel-${++this.seq}${req.destination ? `-${req.destination}` : ''}`;
120
+ return Promise.resolve({ channelId });
121
+ }
122
+ /**
123
+ * Run a swap. Returns a canned `SwapResponse` with one `SwapClaim` derived
124
+ * deterministically from the request (rate applied to the source amount) so
125
+ * the settlement receipt is reproducible. No signing happens here.
126
+ */
127
+ swap(req) {
128
+ const seq = ++this.seq;
129
+ const rate = Number.parseFloat(req.pair.rate) || 1;
130
+ const source = req.amount;
131
+ const target = String(Math.floor((Number.parseFloat(source) || 0) * rate));
132
+ const claim = {
133
+ sourceAmount: source,
134
+ targetAmount: target,
135
+ claim: Buffer.from(`fake-claim-${seq}`).toString('base64'),
136
+ channelId: `fake-target-channel-${seq}`,
137
+ recipient: req.chainRecipient,
138
+ millSignerAddress: '0x000000000000000000000000000000000000dEaD',
139
+ claimId: `fake-claim-id-${seq}`,
140
+ nonce: String(seq),
141
+ cumulativeAmount: target,
142
+ };
143
+ return Promise.resolve({
144
+ accepted: true,
145
+ packetsAccepted: req.packetCount ?? 1,
146
+ claims: [claim],
147
+ cumulativeSource: source,
148
+ cumulativeTarget: target,
149
+ state: 'completed',
150
+ });
151
+ }
152
+ /** Test helper: total events currently in the store. */
153
+ size() {
154
+ return this.events.length;
155
+ }
156
+ }
157
+ //# sourceMappingURL=fake-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-backend.js","sourceRoot":"","sources":["../../src/server/fake-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Runnable fake TOON apps MCP server (stdio).
4
+ *
5
+ * Demonstrates the agent-driven generative-UI loop end-to-end with NO core/sdk:
6
+ * reads come from a seeded in-memory relay, writes are faked (and reflected on
7
+ * the next read). Point an MCP host at it:
8
+ *
9
+ * pnpm --filter @toon-protocol/views build && pnpm --filter @toon-protocol/views build:app
10
+ * node packages/views/dist/server/fake-main.js
11
+ *
12
+ * Then connect it as an MCP server (e.g. `claude mcp add toon-fake -- node …/fake-main.js`).
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=fake-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-main.d.ts","sourceRoot":"","sources":["../../src/server/fake-main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Runnable fake TOON apps MCP server (stdio).
4
+ *
5
+ * Demonstrates the agent-driven generative-UI loop end-to-end with NO core/sdk:
6
+ * reads come from a seeded in-memory relay, writes are faked (and reflected on
7
+ * the next read). Point an MCP host at it:
8
+ *
9
+ * pnpm --filter @toon-protocol/views build && pnpm --filter @toon-protocol/views build:app
10
+ * node packages/views/dist/server/fake-main.js
11
+ *
12
+ * Then connect it as an MCP server (e.g. `claude mcp add toon-fake -- node …/fake-main.js`).
13
+ */
14
+ import { readFileSync } from 'node:fs';
15
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
16
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
17
+ import { registerToonApps } from './apps-server.js';
18
+ import { FakeBackend } from './fake-backend.js';
19
+ function loadAppHtml() {
20
+ // Compiled to dist/server/fake-main.js; the bundle is at dist/app/index.html.
21
+ try {
22
+ return readFileSync(new URL('../app/index.html', import.meta.url), 'utf8');
23
+ }
24
+ catch {
25
+ return '<!doctype html><html><body><div id="root">toon app bundle not built — run `pnpm --filter @toon-protocol/views build:app`</div></body></html>';
26
+ }
27
+ }
28
+ async function main() {
29
+ const server = new McpServer({ name: 'toon-fake', version: '0.1.0' });
30
+ registerToonApps(server, { backend: new FakeBackend(), appHtml: loadAppHtml() });
31
+ await server.connect(new StdioServerTransport());
32
+ // stdout is the MCP channel; log to stderr.
33
+ console.error('[toon-fake] ready (fake reads + fake writes)');
34
+ }
35
+ main().catch((err) => {
36
+ console.error('[toon-fake]', err instanceof Error ? err.stack : String(err));
37
+ process.exitCode = 1;
38
+ });
39
+ //# sourceMappingURL=fake-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-main.js","sourceRoot":"","sources":["../../src/server/fake-main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,SAAS,WAAW;IAClB,8EAA8E;IAC9E,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,8IAA8I,CAAC;IACxJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACtE,gBAAgB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IACjF,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,4CAA4C;IAC5C,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAChE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
package/dist/spec.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ViewSpec — the declarative composition language the client-side agent authors
3
+ * to assemble atoms into a user journey. A ViewSpec rides as the *result* of the
4
+ * `toon_render` tool (MCP Apps delivers dynamic data via tool results, not a
5
+ * side-channel); the iframe runtime interprets it.
6
+ *
7
+ * ViewSpecs are MODEL-AUTHORED → UNTRUSTED. `validateViewSpec` is the security
8
+ * boundary: it allowlists atom ids, caps depth/breadth, and rejects anything
9
+ * non-serializable. Invalid specs must degrade to a fallback, never `eval`.
10
+ */
11
+ import { type NostrFilter } from './types.js';
12
+ /** Data binding for a node; resolved client-side via free reads (`toon_read`). */
13
+ export interface ViewBind {
14
+ /** A NIP-01 filter to query and feed into the atom. */
15
+ query?: NostrFilter;
16
+ /** Fetch a single event by id. */
17
+ eventId?: string;
18
+ /** Render the bound event(s) via their kind's default atom. */
19
+ kindAuto?: boolean;
20
+ }
21
+ /** Binds a UI affordance to a write tool call (always `toon_publish_unsigned` / `toon_upload_media`). */
22
+ export interface WriteActionRef {
23
+ /** Tool name to invoke. */
24
+ tool: string;
25
+ /** Static argument template merged with runtime values supplied by the atom. */
26
+ args?: Record<string, unknown>;
27
+ /** Spendy actions require host confirmation (elicitation) before firing. */
28
+ spendy?: boolean;
29
+ /** Human-readable confirmation label for spendy actions. */
30
+ confirmLabel?: string;
31
+ }
32
+ /** One node in the composition tree. */
33
+ export interface ViewNode {
34
+ atom: string;
35
+ props?: Record<string, unknown>;
36
+ bind?: ViewBind;
37
+ actions?: Record<string, WriteActionRef>;
38
+ children?: ViewNode[];
39
+ }
40
+ /** A complete view the agent asked the host to render. */
41
+ export interface ViewSpec {
42
+ title?: string;
43
+ root: ViewNode;
44
+ }
45
+ /** Limits applied during validation (defense against malicious/huge specs). */
46
+ export interface ValidateOptions {
47
+ /** Atom ids the runtime knows how to render. */
48
+ allowedAtoms: ReadonlySet<string> | readonly string[];
49
+ /** Write tool names the runtime is allowed to invoke. */
50
+ allowedTools?: ReadonlySet<string> | readonly string[];
51
+ maxDepth?: number;
52
+ maxNodes?: number;
53
+ }
54
+ export type ValidationResult = {
55
+ ok: true;
56
+ spec: ViewSpec;
57
+ } | {
58
+ ok: false;
59
+ errors: string[];
60
+ };
61
+ /**
62
+ * Validate a model-authored ViewSpec against the atom/tool allowlists and the
63
+ * depth/breadth caps. Returns the (structurally identical) spec on success or a
64
+ * list of human-readable errors on failure.
65
+ */
66
+ export declare function validateViewSpec(input: unknown, opts: ValidateOptions): ValidationResult;
67
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,kFAAkF;AAClF,MAAM,WAAW,QAAQ;IACvB,uDAAuD;IACvD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yGAAyG;AACzG,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IACtD,yDAAyD;IACzD,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC5B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA+FpC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,eAAe,GACpB,gBAAgB,CAmHlB"}