aimeat 2.5.0 → 2.6.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 (256) hide show
  1. package/dist/.env.example +33 -0
  2. package/dist/build-stamp.json +13 -0
  3. package/dist/locales/en.json +166 -3
  4. package/dist/locales/fi.json +166 -3
  5. package/dist/public/components/VoiceRecorder.js +116 -0
  6. package/dist/public/connection-done.html +53 -0
  7. package/dist/public/css/components/voice-recorder.css +66 -0
  8. package/dist/public/css/views/inbox.css +41 -0
  9. package/dist/public/css/views/profile.css +171 -95
  10. package/dist/public/js/services/audio-recorder.js +171 -0
  11. package/dist/public/js/services/messages.js +21 -2
  12. package/dist/public/spa.html +3 -0
  13. package/dist/public/views/profile/access-tab/connections.js +264 -0
  14. package/dist/public/views/profile/access-tab.js +2 -0
  15. package/dist/public/views/profile/inbox-tab/components.js +60 -10
  16. package/dist/public/views/profile/inbox-tab/helpers.js +33 -0
  17. package/dist/public/views/profile/inbox-tab/panels.js +6 -1
  18. package/dist/public/views/profile/inbox-tab/use-voice.js +50 -0
  19. package/dist/public/views/profile/inbox-tab/voice-parts.js +111 -0
  20. package/dist/public/views/profile/inbox-tab.js +18 -24
  21. package/dist/public/views/profile/openrouter/budget-panel.js +224 -0
  22. package/dist/public/views/profile/openrouter/model-picker.js +150 -0
  23. package/dist/public/views/profile/openrouter/pricing.js +139 -0
  24. package/dist/public/views/profile/openrouter-settings.js +425 -488
  25. package/dist/scripts/build-sdk-libs.d.ts.map +1 -1
  26. package/dist/scripts/build-sdk-libs.js +1 -0
  27. package/dist/scripts/build-sdk-libs.js.map +1 -1
  28. package/dist/scripts/check-ai-disclosure.d.ts.map +1 -1
  29. package/dist/scripts/check-ai-disclosure.js +65 -0
  30. package/dist/scripts/check-ai-disclosure.js.map +1 -1
  31. package/dist/src/cli/connect/ai-provenance-carry.d.ts +54 -3
  32. package/dist/src/cli/connect/ai-provenance-carry.d.ts.map +1 -1
  33. package/dist/src/cli/connect/ai-provenance-carry.js +84 -12
  34. package/dist/src/cli/connect/ai-provenance-carry.js.map +1 -1
  35. package/dist/src/cli/connect/api-client.d.ts +11 -0
  36. package/dist/src/cli/connect/api-client.d.ts.map +1 -1
  37. package/dist/src/cli/connect/api-client.js +2 -0
  38. package/dist/src/cli/connect/api-client.js.map +1 -1
  39. package/dist/src/cli/connect/mcp/local-server.d.ts.map +1 -1
  40. package/dist/src/cli/connect/mcp/local-server.js +11 -0
  41. package/dist/src/cli/connect/mcp/local-server.js.map +1 -1
  42. package/dist/src/cli/connect/mcp/tools/apps.d.ts +1 -0
  43. package/dist/src/cli/connect/mcp/tools/apps.d.ts.map +1 -1
  44. package/dist/src/cli/connect/mcp/tools/apps.js +3 -2
  45. package/dist/src/cli/connect/mcp/tools/apps.js.map +1 -1
  46. package/dist/src/cli/connect/mcp/tools/core.d.ts +2 -0
  47. package/dist/src/cli/connect/mcp/tools/core.d.ts.map +1 -1
  48. package/dist/src/cli/connect/mcp/tools/core.js +5 -2
  49. package/dist/src/cli/connect/mcp/tools/core.js.map +1 -1
  50. package/dist/src/cli/connect/mcp/tools/knowledge.d.ts +1 -0
  51. package/dist/src/cli/connect/mcp/tools/knowledge.d.ts.map +1 -1
  52. package/dist/src/cli/connect/mcp/tools/knowledge.js +3 -2
  53. package/dist/src/cli/connect/mcp/tools/knowledge.js.map +1 -1
  54. package/dist/src/config-types.d.ts +14 -0
  55. package/dist/src/config-types.d.ts.map +1 -1
  56. package/dist/src/config.d.ts.map +1 -1
  57. package/dist/src/config.js +10 -0
  58. package/dist/src/config.js.map +1 -1
  59. package/dist/src/generated/api-types.d.ts +969 -4
  60. package/dist/src/generated/api-types.d.ts.map +1 -1
  61. package/dist/src/mcp/ai-provenance-input.d.ts +33 -0
  62. package/dist/src/mcp/ai-provenance-input.d.ts.map +1 -1
  63. package/dist/src/mcp/ai-provenance-input.js +36 -0
  64. package/dist/src/mcp/ai-provenance-input.js.map +1 -1
  65. package/dist/src/mcp/apps.d.ts +7 -0
  66. package/dist/src/mcp/apps.d.ts.map +1 -1
  67. package/dist/src/mcp/apps.js +20 -2
  68. package/dist/src/mcp/apps.js.map +1 -1
  69. package/dist/src/models/ai-provenance-schemas.d.ts +7 -1
  70. package/dist/src/models/ai-provenance-schemas.d.ts.map +1 -1
  71. package/dist/src/models/ai-provenance-schemas.js +11 -1
  72. package/dist/src/models/ai-provenance-schemas.js.map +1 -1
  73. package/dist/src/models/connection-schemas.d.ts +203 -0
  74. package/dist/src/models/connection-schemas.d.ts.map +1 -0
  75. package/dist/src/models/connection-schemas.js +33 -0
  76. package/dist/src/models/connection-schemas.js.map +1 -0
  77. package/dist/src/models/message-schemas.d.ts +40 -0
  78. package/dist/src/models/message-schemas.d.ts.map +1 -1
  79. package/dist/src/models/message-schemas.js +23 -0
  80. package/dist/src/models/message-schemas.js.map +1 -1
  81. package/dist/src/routes/ai-provenance.d.ts +6 -0
  82. package/dist/src/routes/ai-provenance.d.ts.map +1 -1
  83. package/dist/src/routes/ai-provenance.js +34 -1
  84. package/dist/src/routes/ai-provenance.js.map +1 -1
  85. package/dist/src/routes/ai.d.ts +5 -0
  86. package/dist/src/routes/ai.d.ts.map +1 -1
  87. package/dist/src/routes/ai.js +83 -0
  88. package/dist/src/routes/ai.js.map +1 -1
  89. package/dist/src/routes/app-grants.d.ts.map +1 -1
  90. package/dist/src/routes/app-grants.js +4 -0
  91. package/dist/src/routes/app-grants.js.map +1 -1
  92. package/dist/src/routes/apps/drafts.d.ts +3 -0
  93. package/dist/src/routes/apps/drafts.d.ts.map +1 -1
  94. package/dist/src/routes/apps/drafts.js +13 -0
  95. package/dist/src/routes/apps/drafts.js.map +1 -1
  96. package/dist/src/routes/apps/publish.d.ts +5 -0
  97. package/dist/src/routes/apps/publish.d.ts.map +1 -1
  98. package/dist/src/routes/apps/publish.js +21 -5
  99. package/dist/src/routes/apps/publish.js.map +1 -1
  100. package/dist/src/routes/connections.d.ts +35 -0
  101. package/dist/src/routes/connections.d.ts.map +1 -0
  102. package/dist/src/routes/connections.js +406 -0
  103. package/dist/src/routes/connections.js.map +1 -0
  104. package/dist/src/routes/libs.js +1 -1
  105. package/dist/src/routes/libs.js.map +1 -1
  106. package/dist/src/routes/messages.d.ts +5 -0
  107. package/dist/src/routes/messages.d.ts.map +1 -1
  108. package/dist/src/routes/messages.js +92 -0
  109. package/dist/src/routes/messages.js.map +1 -1
  110. package/dist/src/routes/openrouter.d.ts +6 -1
  111. package/dist/src/routes/openrouter.d.ts.map +1 -1
  112. package/dist/src/routes/openrouter.js +37 -7
  113. package/dist/src/routes/openrouter.js.map +1 -1
  114. package/dist/src/routes/upload.d.ts +5 -0
  115. package/dist/src/routes/upload.d.ts.map +1 -1
  116. package/dist/src/routes/upload.js +32 -0
  117. package/dist/src/routes/upload.js.map +1 -1
  118. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  119. package/dist/src/server-bootstrap/routes-loader.js +2 -0
  120. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  121. package/dist/src/services/ai-completion.d.ts +48 -0
  122. package/dist/src/services/ai-completion.d.ts.map +1 -1
  123. package/dist/src/services/ai-completion.js +112 -72
  124. package/dist/src/services/ai-completion.js.map +1 -1
  125. package/dist/src/services/ai-disclosure.d.ts +6 -0
  126. package/dist/src/services/ai-disclosure.d.ts.map +1 -1
  127. package/dist/src/services/ai-disclosure.js +20 -2
  128. package/dist/src/services/ai-disclosure.js.map +1 -1
  129. package/dist/src/services/ai-provenance-page.d.ts +59 -0
  130. package/dist/src/services/ai-provenance-page.d.ts.map +1 -0
  131. package/dist/src/services/ai-provenance-page.js +185 -0
  132. package/dist/src/services/ai-provenance-page.js.map +1 -0
  133. package/dist/src/services/ai-provenance.d.ts +6 -0
  134. package/dist/src/services/ai-provenance.d.ts.map +1 -1
  135. package/dist/src/services/ai-provenance.js +5 -0
  136. package/dist/src/services/ai-provenance.js.map +1 -1
  137. package/dist/src/services/ai-transcription.d.ts +66 -0
  138. package/dist/src/services/ai-transcription.d.ts.map +1 -0
  139. package/dist/src/services/ai-transcription.js +90 -0
  140. package/dist/src/services/ai-transcription.js.map +1 -0
  141. package/dist/src/services/app-publish.d.ts.map +1 -1
  142. package/dist/src/services/app-publish.js +16 -1
  143. package/dist/src/services/app-publish.js.map +1 -1
  144. package/dist/src/services/build-app-prompt.d.ts.map +1 -1
  145. package/dist/src/services/build-app-prompt.js +2 -1
  146. package/dist/src/services/build-app-prompt.js.map +1 -1
  147. package/dist/src/services/connections/attach.d.ts +62 -0
  148. package/dist/src/services/connections/attach.d.ts.map +1 -0
  149. package/dist/src/services/connections/attach.js +168 -0
  150. package/dist/src/services/connections/attach.js.map +1 -0
  151. package/dist/src/services/connections/credential.d.ts +39 -0
  152. package/dist/src/services/connections/credential.d.ts.map +1 -0
  153. package/dist/src/services/connections/credential.js +66 -0
  154. package/dist/src/services/connections/credential.js.map +1 -0
  155. package/dist/src/services/connections/instance.d.ts +65 -0
  156. package/dist/src/services/connections/instance.d.ts.map +1 -0
  157. package/dist/src/services/connections/instance.js +144 -0
  158. package/dist/src/services/connections/instance.js.map +1 -0
  159. package/dist/src/services/connections/oauth.d.ts +77 -0
  160. package/dist/src/services/connections/oauth.d.ts.map +1 -0
  161. package/dist/src/services/connections/oauth.js +290 -0
  162. package/dist/src/services/connections/oauth.js.map +1 -0
  163. package/dist/src/services/connections/providers.d.ts +118 -0
  164. package/dist/src/services/connections/providers.d.ts.map +1 -0
  165. package/dist/src/services/connections/providers.js +260 -0
  166. package/dist/src/services/connections/providers.js.map +1 -0
  167. package/dist/src/services/connections/publish-gate.d.ts +103 -0
  168. package/dist/src/services/connections/publish-gate.d.ts.map +1 -0
  169. package/dist/src/services/connections/publish-gate.js +0 -0
  170. package/dist/src/services/connections/publish-gate.js.map +1 -0
  171. package/dist/src/services/connections/publish.d.ts +60 -0
  172. package/dist/src/services/connections/publish.d.ts.map +1 -0
  173. package/dist/src/services/connections/publish.js +340 -0
  174. package/dist/src/services/connections/publish.js.map +1 -0
  175. package/dist/src/services/connections/refresh.d.ts +55 -0
  176. package/dist/src/services/connections/refresh.d.ts.map +1 -0
  177. package/dist/src/services/connections/refresh.js +307 -0
  178. package/dist/src/services/connections/refresh.js.map +1 -0
  179. package/dist/src/services/message-send.d.ts.map +1 -1
  180. package/dist/src/services/message-send.js +16 -2
  181. package/dist/src/services/message-send.js.map +1 -1
  182. package/dist/src/services/openrouter.d.ts +67 -1
  183. package/dist/src/services/openrouter.d.ts.map +1 -1
  184. package/dist/src/services/openrouter.js +102 -11
  185. package/dist/src/services/openrouter.js.map +1 -1
  186. package/dist/src/services/upload-token.d.ts +5 -1
  187. package/dist/src/services/upload-token.d.ts.map +1 -1
  188. package/dist/src/services/upload-token.js +13 -1
  189. package/dist/src/services/upload-token.js.map +1 -1
  190. package/dist/src/services/workflow/engine-util.d.ts +18 -0
  191. package/dist/src/services/workflow/engine-util.d.ts.map +1 -1
  192. package/dist/src/services/workflow/engine-util.js +27 -0
  193. package/dist/src/services/workflow/engine-util.js.map +1 -1
  194. package/dist/src/services/workflow/engine.d.ts.map +1 -1
  195. package/dist/src/services/workflow/engine.js +4 -2
  196. package/dist/src/services/workflow/engine.js.map +1 -1
  197. package/dist/src/storage/interface.d.ts +2 -1
  198. package/dist/src/storage/interface.d.ts.map +1 -1
  199. package/dist/src/storage/providers/postgres-kysely/db-types.d.ts +65 -0
  200. package/dist/src/storage/providers/postgres-kysely/db-types.d.ts.map +1 -1
  201. package/dist/src/storage/providers/postgres-kysely/index.d.ts.map +1 -1
  202. package/dist/src/storage/providers/postgres-kysely/index.js +2 -1
  203. package/dist/src/storage/providers/postgres-kysely/index.js.map +1 -1
  204. package/dist/src/storage/providers/postgres-kysely/methods/connections.d.ts +44 -0
  205. package/dist/src/storage/providers/postgres-kysely/methods/connections.d.ts.map +1 -0
  206. package/dist/src/storage/providers/postgres-kysely/methods/connections.js +308 -0
  207. package/dist/src/storage/providers/postgres-kysely/methods/connections.js.map +1 -0
  208. package/dist/src/storage/providers/postgres-kysely/methods/node-infra.d.ts.map +1 -1
  209. package/dist/src/storage/providers/postgres-kysely/methods/node-infra.js +4 -2
  210. package/dist/src/storage/providers/postgres-kysely/methods/node-infra.js.map +1 -1
  211. package/dist/src/storage/providers/postgres-kysely/migrations/0021_connections.sql +116 -0
  212. package/dist/src/storage/providers/postgres-kysely/migrations/0022_provider_clients.sql +36 -0
  213. package/dist/src/storage/providers/postgres-kysely/migrations/0023_nonce_payload.sql +13 -0
  214. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  215. package/dist/src/storage/providers/sqlite/index.js +2 -1
  216. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  217. package/dist/src/storage/providers/sqlite/methods/community.d.ts.map +1 -1
  218. package/dist/src/storage/providers/sqlite/methods/community.js +2 -1
  219. package/dist/src/storage/providers/sqlite/methods/community.js.map +1 -1
  220. package/dist/src/storage/providers/sqlite/methods/connections.d.ts +60 -0
  221. package/dist/src/storage/providers/sqlite/methods/connections.d.ts.map +1 -0
  222. package/dist/src/storage/providers/sqlite/methods/connections.js +287 -0
  223. package/dist/src/storage/providers/sqlite/methods/connections.js.map +1 -0
  224. package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts.map +1 -1
  225. package/dist/src/storage/providers/sqlite/schema-tables-1.js +4 -0
  226. package/dist/src/storage/providers/sqlite/schema-tables-1.js.map +1 -1
  227. package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts.map +1 -1
  228. package/dist/src/storage/providers/sqlite/schema-tables-3.js +106 -0
  229. package/dist/src/storage/providers/sqlite/schema-tables-3.js.map +1 -1
  230. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  231. package/dist/src/storage/providers/sqlite/schema.js +2 -0
  232. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  233. package/dist/src/storage/repositories/connection.repository.d.ts +124 -0
  234. package/dist/src/storage/repositories/connection.repository.d.ts.map +1 -0
  235. package/dist/src/storage/repositories/connection.repository.js +2 -0
  236. package/dist/src/storage/repositories/connection.repository.js.map +1 -0
  237. package/dist/src/storage/types/agents-messaging.d.ts +19 -0
  238. package/dist/src/storage/types/agents-messaging.d.ts.map +1 -1
  239. package/dist/src/storage/types/agents-messaging.js.map +1 -1
  240. package/dist/src/storage/types/auth.d.ts +8 -1
  241. package/dist/src/storage/types/auth.d.ts.map +1 -1
  242. package/dist/src/utils/build-stamp.d.ts +56 -0
  243. package/dist/src/utils/build-stamp.d.ts.map +1 -0
  244. package/dist/src/utils/build-stamp.js +142 -0
  245. package/dist/src/utils/build-stamp.js.map +1 -0
  246. package/dist/src/utils/messaging.d.ts +14 -0
  247. package/dist/src/utils/messaging.d.ts.map +1 -1
  248. package/dist/src/utils/messaging.js +0 -0
  249. package/dist/src/utils/messaging.js.map +1 -1
  250. package/dist/static/sdk-libs/connect/index.js +199 -0
  251. package/dist/static/sdk-libs/connect/notes.js +47 -0
  252. package/dist/static/sdk-libs/connect/panel.js +238 -0
  253. package/dist/static/sdk-libs/data/index.js +63 -11
  254. package/dist/static/sdk-libs/dist/aimeat-connect.js +357 -0
  255. package/dist/static/sdk-libs/dist/aimeat-data.js +40 -9
  256. package/package.json +2 -2
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @file instance.ts
3
+ * @description Instance-scoped provider support (TARGET-057): normalising a user-supplied instance
4
+ * address, and registering this node as an application at that instance on first contact.
5
+ *
6
+ * THE ADDRESS COMES FROM A USER. That single fact drives this whole file. `mastodon.social` is
7
+ * typed into a box by whoever is connecting, and the node then makes an HTTP request to it. That
8
+ * is an SSRF vector, and the answer is two layers rather than one: normalise and reject
9
+ * syntactically here, then let safeFetch apply the network-level rules (private ranges, redirect
10
+ * re-validation) on every request that follows.
11
+ *
12
+ * LAZY REGISTRATION. There is no way to know which instance the next user arrives from, and there
13
+ * are thousands, so the client credentials cannot be configuration. The node registers itself the
14
+ * first time someone connects from an instance and remembers the result, so the second user
15
+ * reuses the first user's registration rather than minting a parallel one.
16
+ * @structure normalizeInstance · registerAtInstance
17
+ * @usage import { normalizeInstance, registerAtInstance } from './instance.js';
18
+ * @version-history
19
+ * v1.0.0 — 2026-08-02 — TARGET-057 Phase 1c.
20
+ */
21
+ import { randomUUID } from 'node:crypto';
22
+ import { safeFetch } from '../../utils/url-validator.js';
23
+ import { logger } from '../../utils/logger.js';
24
+ import { sealCredential, openCredential } from './credential.js';
25
+ /**
26
+ * Normalise `mastodon.social`, `https://mastodon.social/`, `HTTPS://Mastodon.Social` and friends to
27
+ * one canonical origin, or refuse.
28
+ *
29
+ * Canonicalising matters beyond tidiness: the origin is the storage key for the node's registration
30
+ * at that instance, so two spellings of one instance would register twice and hold two client
31
+ * secrets for the same place.
32
+ *
33
+ * HTTPS is forced rather than accepted-if-offered. An instance reached over http would carry the
34
+ * user's authorization code in the clear, and accepting `http://` from a text box is also the
35
+ * easiest way to reach something that is not on the internet at all.
36
+ */
37
+ export function normalizeInstance(raw) {
38
+ const trimmed = (raw ?? '').trim();
39
+ if (!trimmed)
40
+ return { ok: false, reason: 'instance address is empty' };
41
+ if (trimmed.length > 253)
42
+ return { ok: false, reason: 'instance address is too long' };
43
+ let url;
44
+ try {
45
+ url = new URL(trimmed.includes('://') ? trimmed : `https://${trimmed}`);
46
+ }
47
+ catch {
48
+ return { ok: false, reason: 'instance address is not a valid host' };
49
+ }
50
+ if (url.protocol !== 'https:')
51
+ return { ok: false, reason: 'instance must be reachable over https' };
52
+ // Credentials in the URL would be silently carried into every later request to that instance.
53
+ if (url.username || url.password)
54
+ return { ok: false, reason: 'instance address must not carry credentials' };
55
+ // A path would make two spellings of one instance look like two instances.
56
+ if (url.pathname !== '/' || url.search || url.hash) {
57
+ return { ok: false, reason: 'instance address must be a bare host, with no path or query' };
58
+ }
59
+ // Hostnames only. An IP literal here is either a mistake or an attempt to reach something
60
+ // internal; safeFetch would refuse the private ranges anyway, and refusing earlier is clearer.
61
+ if (/^\[|^\d+\.\d+\.\d+\.\d+$/.test(url.hostname)) {
62
+ return { ok: false, reason: 'instance address must be a hostname, not an IP address' };
63
+ }
64
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(url.hostname)) {
65
+ return { ok: false, reason: 'instance address is not a valid hostname' };
66
+ }
67
+ return { ok: true, origin: `https://${url.hostname.toLowerCase()}${url.port ? `:${url.port}` : ''}` };
68
+ }
69
+ /**
70
+ * The node's application registration at `origin`, registering it if this is the first contact.
71
+ *
72
+ * Concurrency is handled by the store rather than here: `upsertProviderClient` is insert-if-new and
73
+ * returns whatever is now there, so two users arriving from one instance at the same moment
74
+ * converge on a single registration. The loser of that race has registered an application at the
75
+ * instance that nothing will ever point at, which is untidy at the instance and harmless to us —
76
+ * the alternative, a lock held across a network call to a stranger's server, is worse.
77
+ */
78
+ export async function registerAtInstance(storage, key, provider, origin, app) {
79
+ const existing = await storage.getProviderClient(provider, origin);
80
+ if (existing) {
81
+ const opened = openCredential(existing.clientSecret, key);
82
+ // A registration whose secret cannot be read is worse than none: every authorization against it
83
+ // would fail at the token exchange with a message from the instance, far from the cause.
84
+ if (!opened)
85
+ return { error: 'stored registration for this instance cannot be read' };
86
+ return { clientId: existing.clientId, clientSecret: opened.accessToken };
87
+ }
88
+ const body = new URLSearchParams({
89
+ client_name: app.name,
90
+ redirect_uris: app.redirectUri,
91
+ scopes: app.scopes.join(' '),
92
+ website: app.website,
93
+ });
94
+ let res;
95
+ try {
96
+ res = await safeFetch(`${origin}/api/v1/apps`, {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
99
+ body: body.toString(),
100
+ signal: AbortSignal.timeout(15_000),
101
+ });
102
+ }
103
+ catch (err) {
104
+ // safeFetch throws "Fetch blocked: <reason>" for a refused address. That is the SSRF gate
105
+ // reporting, and it is worth surfacing verbatim: it tells the user their instance is unreachable
106
+ // rather than leaving them at a spinner.
107
+ return { error: `could not reach the instance: ${err.message}` };
108
+ }
109
+ if (!res.ok)
110
+ return { error: `instance refused the registration (HTTP ${res.status})` };
111
+ const json = await res.json().catch((err) => {
112
+ // Logged rather than swallowed: an instance answering 200 with something that is not JSON is a
113
+ // different problem from one answering with JSON that lacks the fields, and the message below
114
+ // is the same for both. Without this line the two are indistinguishable in production.
115
+ logger.warn('connections: instance registration returned unparseable JSON', {
116
+ provider, origin, error: String(err),
117
+ });
118
+ return null;
119
+ });
120
+ const clientId = typeof json?.client_id === 'string' ? json.client_id : '';
121
+ const clientSecret = typeof json?.client_secret === 'string' ? json.client_secret : '';
122
+ if (!clientId || !clientSecret)
123
+ return { error: 'instance returned no usable client credentials' };
124
+ // Sealed through the same path as a user token. It is not personal data, but a table that holds
125
+ // one secret in the clear teaches the next one to do the same.
126
+ const stored = await storage.upsertProviderClient({
127
+ id: randomUUID(),
128
+ provider,
129
+ instance: origin,
130
+ clientId,
131
+ clientSecret: sealCredential({ shape: 'static', accessToken: clientSecret }, key),
132
+ registeredAt: new Date().toISOString(),
133
+ });
134
+ // Read back what the store actually kept: on a race that is the OTHER registration, and using our
135
+ // own local values would authorize against credentials the store does not have.
136
+ if (stored.clientId !== clientId) {
137
+ const opened = openCredential(stored.clientSecret, key);
138
+ if (!opened)
139
+ return { error: 'stored registration for this instance cannot be read' };
140
+ return { clientId: stored.clientId, clientSecret: opened.accessToken };
141
+ }
142
+ return { clientId, clientSecret };
143
+ }
144
+ //# sourceMappingURL=instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../../src/services/connections/instance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @file oauth.ts
3
+ * @description The authorization round for an outbound connection (TARGET-057): start it, and
4
+ * complete it when the provider redirects back.
5
+ *
6
+ * WHAT THE URL MUST NOT CARRY. Everything the callback needs to make a decision — which provider,
7
+ * which instance, which mode, who asked — is stored server-side against a single-use `state` and
8
+ * never put in the redirect URL. Carrying it in the URL would let whoever reaches the callback
9
+ * choose which provider their code is redeemed against and whose connection it becomes.
10
+ *
11
+ * WHY THE STATE IS CONSUMED FIRST. `completeAuthorization` deletes the nonce BEFORE it exchanges
12
+ * the code. A replayed callback then finds nothing and stops, rather than racing the original and
13
+ * producing two connections from one authorization.
14
+ *
15
+ * THE GOOGLE PARAMETERS ARE NOT OPTIONAL. `access_type=offline` and `prompt=consent` are why a
16
+ * refresh token arrives at all. Without them a repeat authorization returns an access token and no
17
+ * refresh token, and the connection silently becomes a one-hour connection that dies and cannot
18
+ * renew — with nothing in the response to say so.
19
+ * @structure startAuthorization · completeAuthorization · resolveClient · fetchAccountIdentity
20
+ * @usage import { startAuthorization, completeAuthorization } from './oauth.js';
21
+ * @version-history
22
+ * v1.0.0 — 2026-08-02 — TARGET-057 Phase 1c.
23
+ */
24
+ import { type OutboundProvider } from './providers.js';
25
+ import type { AimeatConfig } from '../../config.js';
26
+ import type { Storage } from '../../storage/interface.js';
27
+ import type { ConnectionMode, ConnectionRecord } from '../../models/connection-schemas.js';
28
+ export interface ConnectContext {
29
+ config: AimeatConfig;
30
+ storage: Storage;
31
+ providers: OutboundProvider[];
32
+ key: Buffer;
33
+ }
34
+ export type StartResult = {
35
+ ok: true;
36
+ authorizeUrl: string;
37
+ state: string;
38
+ } | {
39
+ ok: false;
40
+ code: string;
41
+ reason: string;
42
+ };
43
+ export type CompleteResult = {
44
+ ok: true;
45
+ connection: ConnectionRecord;
46
+ returnUrl: string;
47
+ created: boolean;
48
+ } | {
49
+ ok: false;
50
+ code: string;
51
+ reason: string;
52
+ };
53
+ /** The node's own callback address. One per node, and it must match the provider registration byte for byte. */
54
+ export declare function callbackUrl(config: AimeatConfig): string;
55
+ /**
56
+ * Begin an authorization. Returns the URL to send the user's browser to; the user grants consent at
57
+ * the PROVIDER, never here.
58
+ */
59
+ export declare function startAuthorization(ctx: ConnectContext, input: {
60
+ principal: string;
61
+ provider: string;
62
+ instance?: string;
63
+ mode: ConnectionMode;
64
+ returnUrl: string;
65
+ }): Promise<StartResult>;
66
+ /**
67
+ * Complete an authorization: consume the state, exchange the code, learn who the account is, and
68
+ * store the connection.
69
+ *
70
+ * Re-authorising an account already connected UPDATES that row rather than adding a second one —
71
+ * which is what makes "reconnect" a repair rather than a way to accumulate duplicates.
72
+ */
73
+ export declare function completeAuthorization(ctx: ConnectContext, input: {
74
+ state: string;
75
+ code: string;
76
+ }): Promise<CompleteResult>;
77
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../../src/services/connections/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAK3F,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb;AASD,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC/E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAOhD,gHAAgH;AAChH,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAExD;AAsBD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzG,OAAO,CAAC,WAAW,CAAC,CA8DtB;AAiFD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,cAAc,CAAC,CAqGzB"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * @file oauth.ts
3
+ * @description The authorization round for an outbound connection (TARGET-057): start it, and
4
+ * complete it when the provider redirects back.
5
+ *
6
+ * WHAT THE URL MUST NOT CARRY. Everything the callback needs to make a decision — which provider,
7
+ * which instance, which mode, who asked — is stored server-side against a single-use `state` and
8
+ * never put in the redirect URL. Carrying it in the URL would let whoever reaches the callback
9
+ * choose which provider their code is redeemed against and whose connection it becomes.
10
+ *
11
+ * WHY THE STATE IS CONSUMED FIRST. `completeAuthorization` deletes the nonce BEFORE it exchanges
12
+ * the code. A replayed callback then finds nothing and stops, rather than racing the original and
13
+ * producing two connections from one authorization.
14
+ *
15
+ * THE GOOGLE PARAMETERS ARE NOT OPTIONAL. `access_type=offline` and `prompt=consent` are why a
16
+ * refresh token arrives at all. Without them a repeat authorization returns an access token and no
17
+ * refresh token, and the connection silently becomes a one-hour connection that dies and cannot
18
+ * renew — with nothing in the response to say so.
19
+ * @structure startAuthorization · completeAuthorization · resolveClient · fetchAccountIdentity
20
+ * @usage import { startAuthorization, completeAuthorization } from './oauth.js';
21
+ * @version-history
22
+ * v1.0.0 — 2026-08-02 — TARGET-057 Phase 1c.
23
+ */
24
+ import { randomUUID, randomBytes, createHash } from 'node:crypto';
25
+ import { safeFetch } from '../../utils/url-validator.js';
26
+ import { sealCredential } from './credential.js';
27
+ import { normalizeInstance, registerAtInstance } from './instance.js';
28
+ import { findProvider } from './providers.js';
29
+ /** How long an unfinished authorization stays valid. Long enough to read a consent screen. */
30
+ const STATE_TTL_MS = 10 * 60 * 1000;
31
+ /** base64url without padding — what PKCE and OAuth2 expect. */
32
+ function b64url(buf) {
33
+ return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
34
+ }
35
+ /** The node's own callback address. One per node, and it must match the provider registration byte for byte. */
36
+ export function callbackUrl(config) {
37
+ return config.connectRedirectUri || `${config.baseUrl.replace(/\/$/, '')}/v1/connections/callback`;
38
+ }
39
+ /**
40
+ * Client credentials for this provider: from config for a fixed endpoint, from the node's
41
+ * registration at the instance for a federated one (registering on first contact).
42
+ */
43
+ async function resolveClient(ctx, provider, instance) {
44
+ if (!provider.instanceScoped) {
45
+ if (!provider.client)
46
+ return { error: provider.disabledReason ?? 'provider is not configured' };
47
+ return { clientId: provider.client.id, clientSecret: provider.client.secret };
48
+ }
49
+ if (!instance)
50
+ return { error: 'this provider needs an instance address' };
51
+ return registerAtInstance(ctx.storage, ctx.key, provider.id, instance, {
52
+ name: 'AIMEAT',
53
+ redirectUri: callbackUrl(ctx.config),
54
+ scopes: provider.scopes,
55
+ website: ctx.config.baseUrl,
56
+ });
57
+ }
58
+ /**
59
+ * Begin an authorization. Returns the URL to send the user's browser to; the user grants consent at
60
+ * the PROVIDER, never here.
61
+ */
62
+ export async function startAuthorization(ctx, input) {
63
+ const provider = findProvider(ctx.providers, input.provider);
64
+ if (!provider)
65
+ return { ok: false, code: 'UNKNOWN_PROVIDER', reason: `no provider '${input.provider}'` };
66
+ if (!provider.enabled) {
67
+ // The reason travels with the refusal: an operator staring at "disabled" has nothing to act on.
68
+ return { ok: false, code: 'PROVIDER_DISABLED', reason: provider.disabledReason ?? 'provider is disabled' };
69
+ }
70
+ if (provider.credentialShape !== 'oauth2') {
71
+ return { ok: false, code: 'NOT_AN_OAUTH_PROVIDER', reason: `${provider.id} does not use an authorization round` };
72
+ }
73
+ let instance = null;
74
+ if (provider.instanceScoped) {
75
+ const norm = normalizeInstance(input.instance ?? '');
76
+ if (!norm.ok)
77
+ return { ok: false, code: 'BAD_INSTANCE', reason: norm.reason };
78
+ instance = norm.origin;
79
+ }
80
+ const client = await resolveClient(ctx, provider, instance);
81
+ if ('error' in client)
82
+ return { ok: false, code: 'CLIENT_UNAVAILABLE', reason: client.error };
83
+ const endpoints = provider.endpoints(instance);
84
+ if (!endpoints)
85
+ return { ok: false, code: 'NO_ENDPOINTS', reason: 'provider has no authorization endpoint' };
86
+ const state = b64url(randomBytes(24));
87
+ const verifier = b64url(randomBytes(32));
88
+ const payload = { provider: provider.id, instance, mode: input.mode };
89
+ await ctx.storage.createVerificationNonce({
90
+ id: randomUUID(),
91
+ // Binding the state to the principal is the CSRF gate: a code redeemed by anyone else lands on
92
+ // a nonce whose owner is not them, and the callback refuses.
93
+ owner: input.principal,
94
+ type: 'connect',
95
+ state,
96
+ // The PKCE verifier stays HERE. It is the half of the exchange that never travels.
97
+ nonce: verifier,
98
+ redirectUri: input.returnUrl,
99
+ payload: JSON.stringify(payload),
100
+ createdAt: new Date().toISOString(),
101
+ expiresAt: new Date(Date.now() + STATE_TTL_MS).toISOString(),
102
+ });
103
+ const q = new URLSearchParams({
104
+ response_type: 'code',
105
+ client_id: client.clientId,
106
+ redirect_uri: callbackUrl(ctx.config),
107
+ scope: provider.scopes.join(' '),
108
+ state,
109
+ });
110
+ if (provider.pkce) {
111
+ q.set('code_challenge', b64url(createHash('sha256').update(verifier).digest()));
112
+ q.set('code_challenge_method', 'S256');
113
+ }
114
+ if (provider.id === 'youtube') {
115
+ // Not optional, and the failure is silent: without these Google returns no refresh token on a
116
+ // repeat authorization and the connection quietly becomes a one-hour connection.
117
+ q.set('access_type', 'offline');
118
+ q.set('prompt', 'consent');
119
+ }
120
+ return { ok: true, authorizeUrl: `${endpoints.authorize}?${q.toString()}`, state };
121
+ }
122
+ /**
123
+ * Ask the provider who the token belongs to.
124
+ *
125
+ * This lives in the node rather than in a publishing recipe because it is part of the CONNECTION's
126
+ * lifecycle, not of publishing: `externalId` is the dedupe key that stops one account becoming two
127
+ * rows, and `accountLabel` is what the owner reads in the panel. A label the app supplied would be
128
+ * a label the app chose.
129
+ */
130
+ async function fetchAccountIdentity(provider, instance, accessToken) {
131
+ const auth = { Authorization: `Bearer ${accessToken}` };
132
+ try {
133
+ if (provider.id === 'mastodon') {
134
+ const r = await safeFetch(`${instance}/api/v1/accounts/verify_credentials`, {
135
+ headers: auth, signal: AbortSignal.timeout(15_000),
136
+ });
137
+ if (!r.ok)
138
+ return { error: `instance rejected the token (HTTP ${r.status})` };
139
+ const j = await r.json();
140
+ const id = typeof j.id === 'string' ? j.id : '';
141
+ const acct = typeof j.acct === 'string' ? j.acct : '';
142
+ if (!id)
143
+ return { error: 'instance returned no account id' };
144
+ const host = instance ? new URL(instance).hostname : '';
145
+ return { externalId: id, accountLabel: acct ? `@${acct}@${host}` : host };
146
+ }
147
+ if (provider.id === 'youtube') {
148
+ const r = await safeFetch('https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true', { headers: auth, signal: AbortSignal.timeout(15_000) });
149
+ if (!r.ok) {
150
+ // 403 here means the token is valid but not scoped for the read, which is a DIFFERENT
151
+ // problem from a bad token and has a different fix. Naming it saves the next person the
152
+ // hour it cost to find that youtube.upload alone cannot list your own channel.
153
+ return { error: r.status === 403
154
+ ? 'Google accepted the sign-in but would not say which channel it is for. The connection was authorised before youtube.readonly was requested; disconnect and connect again.'
155
+ : `Google rejected the token (HTTP ${r.status})` };
156
+ }
157
+ const j = await r.json();
158
+ const item = Array.isArray(j.items) ? j.items[0] : undefined;
159
+ const id = typeof item?.id === 'string' ? item.id : '';
160
+ // A Google account with no YouTube channel authorizes fine and then has nothing to publish to.
161
+ // Saying so here is far kinder than an upload failing later for a reason nobody can read.
162
+ if (!id)
163
+ return { error: 'this Google account has no YouTube channel' };
164
+ const title = typeof item?.snippet?.title === 'string' ? item.snippet.title : id;
165
+ return { externalId: id, accountLabel: title };
166
+ }
167
+ if (provider.id === 'fake') {
168
+ // Test-only, and reached only when a base URL is configured. It goes through the same
169
+ // safeFetch + shape-checking path as the real ones so the tests exercise that code rather
170
+ // than a shortcut around it.
171
+ const base = provider.endpoints(null)?.token.replace(/\/token$/, '') ?? '';
172
+ const r = await safeFetch(`${base}/me`, { headers: auth, signal: AbortSignal.timeout(15_000) });
173
+ if (!r.ok)
174
+ return { error: `test provider rejected the token (HTTP ${r.status})` };
175
+ const j = await r.json();
176
+ const id = typeof j.id === 'string' ? j.id : '';
177
+ if (!id)
178
+ return { error: 'test provider returned no account id' };
179
+ return { externalId: id, accountLabel: typeof j.label === 'string' ? j.label : id };
180
+ }
181
+ return { error: `no identity lookup for ${provider.id}` };
182
+ }
183
+ catch (err) {
184
+ return { error: `could not reach the provider: ${err.message}` };
185
+ }
186
+ }
187
+ /**
188
+ * Complete an authorization: consume the state, exchange the code, learn who the account is, and
189
+ * store the connection.
190
+ *
191
+ * Re-authorising an account already connected UPDATES that row rather than adding a second one —
192
+ * which is what makes "reconnect" a repair rather than a way to accumulate duplicates.
193
+ */
194
+ export async function completeAuthorization(ctx, input) {
195
+ const nonce = await ctx.storage.getVerificationNonce(input.state);
196
+ if (!nonce || nonce.type !== 'connect') {
197
+ return { ok: false, code: 'UNKNOWN_STATE', reason: 'this authorization is not one we started' };
198
+ }
199
+ // Consumed BEFORE the exchange: a replayed callback then finds nothing, rather than racing the
200
+ // original and producing two connections from one authorization.
201
+ await ctx.storage.deleteVerificationNonce(input.state);
202
+ if (new Date(nonce.expiresAt).getTime() < Date.now()) {
203
+ return { ok: false, code: 'STATE_EXPIRED', reason: 'the authorization took too long; start again' };
204
+ }
205
+ let payload;
206
+ try {
207
+ payload = JSON.parse(nonce.payload ?? '');
208
+ }
209
+ catch {
210
+ return { ok: false, code: 'BAD_STATE', reason: 'the stored authorization is unreadable' };
211
+ }
212
+ const provider = findProvider(ctx.providers, payload.provider);
213
+ if (!provider?.enabled) {
214
+ return { ok: false, code: 'PROVIDER_DISABLED', reason: 'that provider is no longer available' };
215
+ }
216
+ const client = await resolveClient(ctx, provider, payload.instance);
217
+ if ('error' in client)
218
+ return { ok: false, code: 'CLIENT_UNAVAILABLE', reason: client.error };
219
+ const endpoints = provider.endpoints(payload.instance);
220
+ if (!endpoints)
221
+ return { ok: false, code: 'NO_ENDPOINTS', reason: 'provider has no token endpoint' };
222
+ const body = new URLSearchParams({
223
+ grant_type: 'authorization_code',
224
+ code: input.code,
225
+ redirect_uri: callbackUrl(ctx.config),
226
+ client_id: client.clientId,
227
+ client_secret: client.clientSecret,
228
+ });
229
+ if (provider.pkce)
230
+ body.set('code_verifier', nonce.nonce);
231
+ let token;
232
+ try {
233
+ const r = await safeFetch(endpoints.token, {
234
+ method: 'POST',
235
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
236
+ body: body.toString(),
237
+ signal: AbortSignal.timeout(20_000),
238
+ });
239
+ if (!r.ok)
240
+ return { ok: false, code: 'EXCHANGE_FAILED', reason: `the provider refused the code (HTTP ${r.status})` };
241
+ token = await r.json();
242
+ }
243
+ catch (err) {
244
+ return { ok: false, code: 'EXCHANGE_FAILED', reason: `could not reach the provider: ${err.message}` };
245
+ }
246
+ const accessToken = typeof token.access_token === 'string' ? token.access_token : '';
247
+ if (!accessToken)
248
+ return { ok: false, code: 'EXCHANGE_FAILED', reason: 'the provider returned no access token' };
249
+ const refreshToken = typeof token.refresh_token === 'string' ? token.refresh_token : undefined;
250
+ const expiresAt = typeof token.expires_in === 'number'
251
+ ? new Date(Date.now() + token.expires_in * 1000).toISOString()
252
+ // NOT an error and NOT zero: some providers issue tokens that never expire, and treating the
253
+ // absence as "expired" would park a working connection on its first refresh check.
254
+ : null;
255
+ const scopes = typeof token.scope === 'string' ? token.scope.split(/[\s,]+/).filter(Boolean) : provider.scopes;
256
+ const identity = await fetchAccountIdentity(provider, payload.instance, accessToken);
257
+ if ('error' in identity)
258
+ return { ok: false, code: 'IDENTITY_FAILED', reason: identity.error };
259
+ const credential = sealCredential({ shape: provider.credentialShape, accessToken, ...(refreshToken ? { refreshToken } : {}) }, ctx.key);
260
+ const now = new Date().toISOString();
261
+ const existing = await ctx.storage.findConnection(nonce.owner, provider.id, identity.externalId, payload.instance);
262
+ if (existing) {
263
+ // Re-authorising repairs the row it already has. updateConnectionCredential also clears the
264
+ // error state, because a successful exchange IS the evidence that whatever was wrong is not.
265
+ await ctx.storage.updateConnectionCredential(existing.id, credential, expiresAt, scopes);
266
+ const refreshed = await ctx.storage.getConnection(existing.id);
267
+ return { ok: true, connection: refreshed ?? existing, returnUrl: nonce.redirectUri, created: false };
268
+ }
269
+ const connection = {
270
+ id: randomUUID(),
271
+ principal: nonce.owner,
272
+ mode: payload.mode,
273
+ provider: provider.id,
274
+ instance: payload.instance,
275
+ accountLabel: identity.accountLabel,
276
+ externalId: identity.externalId,
277
+ credential,
278
+ credentialShape: provider.credentialShape,
279
+ scopes,
280
+ expiresAt,
281
+ status: 'active',
282
+ lastOkAt: now,
283
+ lastError: null,
284
+ createdAt: now,
285
+ updatedAt: now,
286
+ };
287
+ await ctx.storage.createConnection(connection);
288
+ return { ok: true, connection, returnUrl: nonce.redirectUri, created: true };
289
+ }
290
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../../src/services/connections/oauth.ts"],"names":[],"mappings":"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"}