auramaxx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +77 -0
  3. package/apps/desktop-electron/main.js +428 -0
  4. package/bin/auramaxx.js +1063 -0
  5. package/docs/ADAPTERS.md +466 -0
  6. package/docs/AGENT_SETUP.md +159 -0
  7. package/docs/API.md +127 -0
  8. package/docs/APPS.md +199 -0
  9. package/docs/ARCHITECTURE.md +235 -0
  10. package/docs/AUTH.md +318 -0
  11. package/docs/BEST-PRACTICES.md +82 -0
  12. package/docs/CLI.md +141 -0
  13. package/docs/DESKTOP_ELECTRON.md +26 -0
  14. package/docs/DEVELOPING-APPS.md +453 -0
  15. package/docs/MCP.md +122 -0
  16. package/docs/PACKAGING_POLICY.md +19 -0
  17. package/docs/PERMISSION.md +137 -0
  18. package/docs/PROTOCOL.md +142 -0
  19. package/docs/README.md +50 -0
  20. package/docs/SKILLS.md +132 -0
  21. package/docs/TROUBLESHOOTING.md +376 -0
  22. package/docs/WORKSPACE.md +673 -0
  23. package/docs/agent-auth.md +14 -0
  24. package/docs/api/authentication.md +79 -0
  25. package/docs/api/secrets/api-keys.md +28 -0
  26. package/docs/api/secrets/credentials.md +80 -0
  27. package/docs/api/secrets/sharing.md +48 -0
  28. package/docs/api/system.md +41 -0
  29. package/docs/api/wallets/apps-strategies.md +66 -0
  30. package/docs/api/wallets/core.md +46 -0
  31. package/docs/api/wallets/data-portfolio.md +42 -0
  32. package/docs/aura-file.md +48 -0
  33. package/docs/core-concepts/FEATURES.md +114 -0
  34. package/docs/credentials.md +120 -0
  35. package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
  36. package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
  37. package/docs/external/getting-started.md +10 -0
  38. package/docs/external/overview.md +19 -0
  39. package/docs/external/persona-paths.md +7 -0
  40. package/docs/external/share-secret.md +76 -0
  41. package/docs/external/why-aura.md +7 -0
  42. package/docs/security.md +227 -0
  43. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  44. package/docs/wallet/AI.md +508 -0
  45. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  46. package/docs/wallet/README.md +47 -0
  47. package/docs/wallet/STRATEGY.md +89 -0
  48. package/next.config.ts +28 -0
  49. package/package.json +167 -0
  50. package/postcss.config.mjs +8 -0
  51. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  52. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  53. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  54. package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
  55. package/prisma/migrations/migration_lock.toml +3 -0
  56. package/prisma/schema.prisma +447 -0
  57. package/public/logo.webp +0 -0
  58. package/scripts/add-app.js +245 -0
  59. package/server/abi/SwapHelper.json +438 -0
  60. package/server/cli/approval.ts +447 -0
  61. package/server/cli/commands/actions.ts +474 -0
  62. package/server/cli/commands/api.ts +220 -0
  63. package/server/cli/commands/apikey.ts +277 -0
  64. package/server/cli/commands/app.ts +204 -0
  65. package/server/cli/commands/auth.ts +464 -0
  66. package/server/cli/commands/cron.ts +24 -0
  67. package/server/cli/commands/diary.ts +274 -0
  68. package/server/cli/commands/doctor.ts +1247 -0
  69. package/server/cli/commands/env.ts +476 -0
  70. package/server/cli/commands/experimental.ts +69 -0
  71. package/server/cli/commands/init.ts +798 -0
  72. package/server/cli/commands/lock.ts +157 -0
  73. package/server/cli/commands/mcp.ts +285 -0
  74. package/server/cli/commands/quickhack.ts +86 -0
  75. package/server/cli/commands/release-check.ts +231 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/service.ts +320 -0
  78. package/server/cli/commands/shell-hook.ts +512 -0
  79. package/server/cli/commands/skill.ts +216 -0
  80. package/server/cli/commands/start.ts +139 -0
  81. package/server/cli/commands/status.ts +59 -0
  82. package/server/cli/commands/stop.ts +36 -0
  83. package/server/cli/commands/token.ts +180 -0
  84. package/server/cli/commands/unlock.ts +50 -0
  85. package/server/cli/commands/vault.ts +1323 -0
  86. package/server/cli/commands/wallet.ts +209 -0
  87. package/server/cli/index.ts +280 -0
  88. package/server/cli/lib/approval-poll.ts +94 -0
  89. package/server/cli/lib/aura-parser.ts +64 -0
  90. package/server/cli/lib/credential-create.ts +74 -0
  91. package/server/cli/lib/credential-resolve.ts +280 -0
  92. package/server/cli/lib/dotenv-migrate.ts +116 -0
  93. package/server/cli/lib/dotenv-parser.ts +146 -0
  94. package/server/cli/lib/escalation.ts +57 -0
  95. package/server/cli/lib/http.ts +91 -0
  96. package/server/cli/lib/init-steps.ts +76 -0
  97. package/server/cli/lib/local-agent-trust.ts +45 -0
  98. package/server/cli/lib/lock-unlock-helper.ts +71 -0
  99. package/server/cli/lib/process.ts +162 -0
  100. package/server/cli/lib/prompt.ts +294 -0
  101. package/server/cli/lib/theme.ts +240 -0
  102. package/server/cli/socket.ts +579 -0
  103. package/server/cli/transport-client.ts +50 -0
  104. package/server/cron/index.ts +137 -0
  105. package/server/cron/job.ts +31 -0
  106. package/server/cron/jobs/balance-sync.ts +436 -0
  107. package/server/cron/jobs/incoming-scan.ts +506 -0
  108. package/server/cron/jobs/native-price.ts +70 -0
  109. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  110. package/server/cron/jobs/strategy-runner.ts +175 -0
  111. package/server/cron/scheduler.ts +125 -0
  112. package/server/index.ts +420 -0
  113. package/server/lib/adapters/factory.ts +119 -0
  114. package/server/lib/adapters/index.ts +19 -0
  115. package/server/lib/adapters/router.ts +297 -0
  116. package/server/lib/adapters/telegram.ts +645 -0
  117. package/server/lib/adapters/types.ts +89 -0
  118. package/server/lib/adapters/webhook.ts +95 -0
  119. package/server/lib/address.ts +49 -0
  120. package/server/lib/agent-auth/contracts.ts +1194 -0
  121. package/server/lib/agent-profiles.ts +419 -0
  122. package/server/lib/ai.ts +285 -0
  123. package/server/lib/api-registry/contracts.ts +86 -0
  124. package/server/lib/api-registry/validation.ts +172 -0
  125. package/server/lib/apikey-migration.ts +258 -0
  126. package/server/lib/app-installer.ts +505 -0
  127. package/server/lib/app-tokens.ts +247 -0
  128. package/server/lib/approval-link.ts +27 -0
  129. package/server/lib/auth.ts +314 -0
  130. package/server/lib/auto-execute.ts +160 -0
  131. package/server/lib/batch.ts +242 -0
  132. package/server/lib/cold.ts +1048 -0
  133. package/server/lib/config.ts +408 -0
  134. package/server/lib/credential-access-audit.ts +85 -0
  135. package/server/lib/credential-access-policy.ts +111 -0
  136. package/server/lib/credential-health.ts +343 -0
  137. package/server/lib/credential-import.ts +608 -0
  138. package/server/lib/credential-scope.ts +102 -0
  139. package/server/lib/credential-shares.ts +190 -0
  140. package/server/lib/credential-transport.ts +533 -0
  141. package/server/lib/credential-vault.ts +77 -0
  142. package/server/lib/credentials.ts +422 -0
  143. package/server/lib/crypto.ts +8 -0
  144. package/server/lib/db.ts +58 -0
  145. package/server/lib/defaults.ts +386 -0
  146. package/server/lib/dex/index.ts +80 -0
  147. package/server/lib/dex/relay.ts +235 -0
  148. package/server/lib/dex/types.ts +59 -0
  149. package/server/lib/dex/uniswap.ts +370 -0
  150. package/server/lib/diary.ts +34 -0
  151. package/server/lib/dont-ask-again-policy.ts +41 -0
  152. package/server/lib/e2e-agent/artifacts.ts +36 -0
  153. package/server/lib/e2e-agent/contracts.ts +112 -0
  154. package/server/lib/e2e-agent/validation.ts +135 -0
  155. package/server/lib/encrypt.ts +114 -0
  156. package/server/lib/error.ts +20 -0
  157. package/server/lib/events.ts +217 -0
  158. package/server/lib/feature-flags.ts +93 -0
  159. package/server/lib/hot.ts +357 -0
  160. package/server/lib/human-action-summary.ts +80 -0
  161. package/server/lib/key-fingerprint.ts +28 -0
  162. package/server/lib/logger.ts +340 -0
  163. package/server/lib/network.ts +137 -0
  164. package/server/lib/notifications.ts +230 -0
  165. package/server/lib/oauth2-refresh.ts +241 -0
  166. package/server/lib/oursecret.ts +71 -0
  167. package/server/lib/passkey-credential.ts +360 -0
  168. package/server/lib/passkey.ts +68 -0
  169. package/server/lib/permissions.ts +299 -0
  170. package/server/lib/pino.ts +24 -0
  171. package/server/lib/policy-preview.ts +138 -0
  172. package/server/lib/price.ts +338 -0
  173. package/server/lib/prices.ts +34 -0
  174. package/server/lib/project-scope.ts +297 -0
  175. package/server/lib/resolve-action.ts +328 -0
  176. package/server/lib/resolve.ts +36 -0
  177. package/server/lib/secret-gist-share.ts +296 -0
  178. package/server/lib/sessions.ts +634 -0
  179. package/server/lib/socket-path.ts +56 -0
  180. package/server/lib/solana/connection.ts +26 -0
  181. package/server/lib/solana/jupiter.ts +128 -0
  182. package/server/lib/solana/transfer.ts +108 -0
  183. package/server/lib/solana/wallet.ts +136 -0
  184. package/server/lib/strategy/emits.ts +21 -0
  185. package/server/lib/strategy/engine.ts +1305 -0
  186. package/server/lib/strategy/executor.ts +115 -0
  187. package/server/lib/strategy/hook-context.ts +159 -0
  188. package/server/lib/strategy/hooks.ts +990 -0
  189. package/server/lib/strategy/index.ts +28 -0
  190. package/server/lib/strategy/installer.ts +305 -0
  191. package/server/lib/strategy/loader.ts +256 -0
  192. package/server/lib/strategy/message.ts +237 -0
  193. package/server/lib/strategy/repository.ts +218 -0
  194. package/server/lib/strategy/session-logger.ts +693 -0
  195. package/server/lib/strategy/sources.ts +288 -0
  196. package/server/lib/strategy/state.ts +189 -0
  197. package/server/lib/strategy/templates.ts +403 -0
  198. package/server/lib/strategy/tick.ts +404 -0
  199. package/server/lib/strategy/types.ts +230 -0
  200. package/server/lib/swap.ts +3 -0
  201. package/server/lib/temp.ts +86 -0
  202. package/server/lib/token-metadata.ts +86 -0
  203. package/server/lib/token-safety.ts +200 -0
  204. package/server/lib/token-search.ts +444 -0
  205. package/server/lib/totp.ts +194 -0
  206. package/server/lib/transactions.ts +123 -0
  207. package/server/lib/transport.ts +84 -0
  208. package/server/lib/txhistory/decoder.ts +262 -0
  209. package/server/lib/txhistory/enricher.ts +652 -0
  210. package/server/lib/txhistory/index.ts +391 -0
  211. package/server/lib/txhistory/signatures.ts +59 -0
  212. package/server/lib/update-check.ts +35 -0
  213. package/server/lib/verified-summary.ts +414 -0
  214. package/server/lib/view-registry.ts +80 -0
  215. package/server/mcp/profile-policy.ts +30 -0
  216. package/server/mcp/server.ts +1589 -0
  217. package/server/mcp/tools.ts +276 -0
  218. package/server/middleware/auth.ts +119 -0
  219. package/server/middleware/requestLogger.ts +84 -0
  220. package/server/routes/actions.ts +539 -0
  221. package/server/routes/adapters.ts +711 -0
  222. package/server/routes/addressbook.ts +113 -0
  223. package/server/routes/ai.ts +34 -0
  224. package/server/routes/apikeys.ts +343 -0
  225. package/server/routes/apps.ts +601 -0
  226. package/server/routes/auth.ts +406 -0
  227. package/server/routes/backup.ts +404 -0
  228. package/server/routes/batch.ts +270 -0
  229. package/server/routes/bookmarks.ts +162 -0
  230. package/server/routes/credential-shares.ts +380 -0
  231. package/server/routes/credential-vaults.ts +159 -0
  232. package/server/routes/credentials.ts +1782 -0
  233. package/server/routes/dashboard.ts +97 -0
  234. package/server/routes/defaults.ts +124 -0
  235. package/server/routes/flags.ts +11 -0
  236. package/server/routes/fund.ts +225 -0
  237. package/server/routes/heartbeat.ts +375 -0
  238. package/server/routes/import.ts +364 -0
  239. package/server/routes/launch.ts +665 -0
  240. package/server/routes/lock.ts +54 -0
  241. package/server/routes/logs.ts +68 -0
  242. package/server/routes/nuke.ts +111 -0
  243. package/server/routes/passkey-credentials.ts +99 -0
  244. package/server/routes/passkey.ts +366 -0
  245. package/server/routes/portfolio.ts +217 -0
  246. package/server/routes/price.ts +63 -0
  247. package/server/routes/resolve.ts +31 -0
  248. package/server/routes/security.ts +45 -0
  249. package/server/routes/send-evm.ts +241 -0
  250. package/server/routes/send-solana.ts +281 -0
  251. package/server/routes/send.ts +178 -0
  252. package/server/routes/setup.ts +210 -0
  253. package/server/routes/strategy.ts +894 -0
  254. package/server/routes/swap-evm.ts +352 -0
  255. package/server/routes/swap-solana.ts +176 -0
  256. package/server/routes/swap.ts +356 -0
  257. package/server/routes/token.ts +247 -0
  258. package/server/routes/unlock.ts +467 -0
  259. package/server/routes/views.ts +41 -0
  260. package/server/routes/wallet-assets.ts +361 -0
  261. package/server/routes/wallet-transactions.ts +515 -0
  262. package/server/routes/wallet.ts +709 -0
  263. package/server/types.ts +146 -0
  264. package/shared/credential-field-schema.ts +248 -0
  265. package/skills/auramaxx/HEARTBEAT.md +78 -0
  266. package/skills/auramaxx/SKILL.md +745 -0
  267. package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
  268. package/skills/auramaxx/docs/API.md +127 -0
  269. package/skills/auramaxx/docs/AUTH.md +318 -0
  270. package/skills/auramaxx/docs/CLI.md +130 -0
  271. package/skills/auramaxx/docs/MCP.md +122 -0
  272. package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
  273. package/skills/auramaxx/docs/WORKSPACE.md +673 -0
  274. package/skills/auramaxx/docs/security.md +227 -0
  275. package/skills/task-lifecycle/SKILL.md +378 -0
  276. package/src/app/api/[...doc]/page.tsx +36 -0
  277. package/src/app/api/agent-requests/route.ts +30 -0
  278. package/src/app/api/apps/install/route.ts +132 -0
  279. package/src/app/api/apps/manifests/route.ts +16 -0
  280. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  281. package/src/app/api/docs/plain/route.ts +74 -0
  282. package/src/app/api/events/route.ts +92 -0
  283. package/src/app/api/page.tsx +290 -0
  284. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  285. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  286. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  287. package/src/app/api/workspace/[id]/route.ts +168 -0
  288. package/src/app/api/workspace/auth.ts +40 -0
  289. package/src/app/api/workspace/config/route.ts +121 -0
  290. package/src/app/api/workspace/import/route.ts +127 -0
  291. package/src/app/api/workspace/route.ts +116 -0
  292. package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
  293. package/src/app/apple-icon.png +0 -0
  294. package/src/app/approve/[actionId]/page.tsx +409 -0
  295. package/src/app/docs/DocsPageContent.tsx +269 -0
  296. package/src/app/docs/[...doc]/page.tsx +41 -0
  297. package/src/app/docs/page.tsx +38 -0
  298. package/src/app/favicon.ico +0 -0
  299. package/src/app/globals.css +819 -0
  300. package/src/app/health/page.tsx +5 -0
  301. package/src/app/hello/page.tsx +102 -0
  302. package/src/app/icon.png +0 -0
  303. package/src/app/layout.tsx +39 -0
  304. package/src/app/page.tsx +1964 -0
  305. package/src/app/privacy/page.tsx +63 -0
  306. package/src/app/providers.tsx +87 -0
  307. package/src/app/share/[token]/page.tsx +295 -0
  308. package/src/app/terms/page.tsx +80 -0
  309. package/src/components/ChainSelector.tsx +44 -0
  310. package/src/components/HumanActionBar.tsx +697 -0
  311. package/src/components/NotificationDrawer.tsx +387 -0
  312. package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
  313. package/src/components/apps/AgentKeysApp.tsx +490 -0
  314. package/src/components/apps/App.tsx +153 -0
  315. package/src/components/apps/AppGrid.tsx +15 -0
  316. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  317. package/src/components/apps/DraggableApp.tsx +562 -0
  318. package/src/components/apps/IFrameApp.tsx +73 -0
  319. package/src/components/apps/LogsApp.tsx +360 -0
  320. package/src/components/apps/SendApp.tsx +394 -0
  321. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  322. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  323. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  324. package/src/components/apps/TokenApp.tsx +319 -0
  325. package/src/components/apps/TransactionsApp.tsx +438 -0
  326. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  327. package/src/components/apps/index.ts +13 -0
  328. package/src/components/design-system/Button.tsx +88 -0
  329. package/src/components/design-system/ChainIndicator.tsx +65 -0
  330. package/src/components/design-system/ChainSelector.tsx +147 -0
  331. package/src/components/design-system/ConfirmationModal.tsx +107 -0
  332. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  333. package/src/components/design-system/DownloadButton.tsx +149 -0
  334. package/src/components/design-system/Drawer.tsx +133 -0
  335. package/src/components/design-system/FilterDropdown.tsx +183 -0
  336. package/src/components/design-system/ItemPicker.tsx +157 -0
  337. package/src/components/design-system/Modal.tsx +296 -0
  338. package/src/components/design-system/Popover.tsx +142 -0
  339. package/src/components/design-system/TextInput.tsx +85 -0
  340. package/src/components/design-system/Toggle.tsx +65 -0
  341. package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
  342. package/src/components/design-system/index.ts +14 -0
  343. package/src/components/docs/ClientSideMarkdown.tsx +51 -0
  344. package/src/components/docs/DocsSearchBar.tsx +118 -0
  345. package/src/components/docs/DocsThemeToggle.tsx +38 -0
  346. package/src/components/docs/PersistentDocGroup.tsx +91 -0
  347. package/src/components/docs/ShareUrlButton.tsx +33 -0
  348. package/src/components/docs/SidebarScrollMemory.tsx +56 -0
  349. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  350. package/src/components/icons/ChainIcons.tsx +72 -0
  351. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  352. package/src/components/layout/ContentArea.tsx +21 -0
  353. package/src/components/layout/CreateViewModal.tsx +88 -0
  354. package/src/components/layout/LeftRail.tsx +114 -0
  355. package/src/components/layout/TabBar.tsx +284 -0
  356. package/src/components/layout/WalletSidebar.tsx +1030 -0
  357. package/src/components/layout/index.ts +6 -0
  358. package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
  359. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  360. package/src/components/vault/ApiKeysConsole.tsx +1272 -0
  361. package/src/components/vault/AuditConsole.tsx +600 -0
  362. package/src/components/vault/CredentialDetail.tsx +625 -0
  363. package/src/components/vault/CredentialEmpty.tsx +55 -0
  364. package/src/components/vault/CredentialField.tsx +583 -0
  365. package/src/components/vault/CredentialForm.tsx +1484 -0
  366. package/src/components/vault/CredentialList.tsx +265 -0
  367. package/src/components/vault/CredentialRow.tsx +130 -0
  368. package/src/components/vault/CredentialShareModal.tsx +273 -0
  369. package/src/components/vault/CredentialVault.tsx +1662 -0
  370. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  371. package/src/components/vault/DocsConsole.tsx +113 -0
  372. package/src/components/vault/ImportCredentialsModal.tsx +578 -0
  373. package/src/components/vault/LargeTypeModal.tsx +88 -0
  374. package/src/components/vault/PasswordGenerator.tsx +232 -0
  375. package/src/components/vault/TOTPDisplay.tsx +108 -0
  376. package/src/components/vault/TotpSetupPanel.tsx +198 -0
  377. package/src/components/vault/VaultSidebar.tsx +881 -0
  378. package/src/components/vault/credentialFormName.ts +91 -0
  379. package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
  380. package/src/components/vault/types.ts +56 -0
  381. package/src/context/AuthContext.tsx +365 -0
  382. package/src/context/PriceContext.tsx +113 -0
  383. package/src/context/ThemeContext.tsx +164 -0
  384. package/src/context/WebSocketContext.tsx +269 -0
  385. package/src/context/WorkspaceContext.tsx +668 -0
  386. package/src/hooks/index.ts +4 -0
  387. package/src/hooks/useAgentActions.ts +552 -0
  388. package/src/hooks/useBalance.ts +103 -0
  389. package/src/hooks/useBalances.ts +129 -0
  390. package/src/hooks/useTheme.ts +156 -0
  391. package/src/instrumentation.ts +12 -0
  392. package/src/lib/api-docs.ts +154 -0
  393. package/src/lib/api.ts +474 -0
  394. package/src/lib/app-loader.ts +148 -0
  395. package/src/lib/app-registry.ts +178 -0
  396. package/src/lib/app-sdk.ts +157 -0
  397. package/src/lib/audit-console-adapter.ts +151 -0
  398. package/src/lib/auth-client.ts +75 -0
  399. package/src/lib/config.ts +74 -0
  400. package/src/lib/credential-field-schema.ts +11 -0
  401. package/src/lib/crypto.ts +112 -0
  402. package/src/lib/db.ts +21 -0
  403. package/src/lib/docs.ts +544 -0
  404. package/src/lib/events.ts +363 -0
  405. package/src/lib/pino.ts +24 -0
  406. package/src/lib/theme-handlers.ts +168 -0
  407. package/src/lib/theme.ts +351 -0
  408. package/src/lib/tokenData.ts +378 -0
  409. package/src/lib/totp-import.ts +57 -0
  410. package/src/lib/vault-crypto.ts +129 -0
  411. package/src/lib/view-registry.ts +57 -0
  412. package/src/lib/websocket-server.ts +302 -0
  413. package/src/lib/websocket-setup.ts +79 -0
  414. package/src/lib/wordlist.ts +2050 -0
  415. package/src/lib/workspace-handlers.ts +285 -0
  416. package/start.sh +170 -0
  417. package/tailwind.config.ts +99 -0
  418. package/tsconfig.json +42 -0
@@ -0,0 +1,711 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import { createHmac, randomBytes, timingSafeEqual } from 'crypto';
3
+ import { prisma } from '../lib/db';
4
+ import { requireWalletAuth } from '../middleware/auth';
5
+ import { requirePermission } from '../lib/permissions';
6
+ import { ApprovalRouter, loadAdaptersFromDb } from '../lib/adapters';
7
+ import { SERVER_PORT } from '../lib/config';
8
+ import { validateExternalUrl } from '../lib/network';
9
+ import {
10
+ ensureApiKeysMigrated,
11
+ listApiKeyCredentials,
12
+ readApiKeyValueByServiceName,
13
+ } from '../lib/apikey-migration';
14
+ import { logger } from '../lib/logger';
15
+ import { getErrorMessage } from '../lib/error';
16
+
17
+ /** Reference to the live approval router (set via setApprovalRouter) */
18
+ let approvalRouter: ApprovalRouter | null = null;
19
+
20
+ /** In-memory nonce store for Telegram chat ID auto-detection */
21
+ interface SetupNonce {
22
+ botToken: string;
23
+ botUsername: string;
24
+ expiresAt: number;
25
+ /** getUpdates offset — skip all updates before this ID */
26
+ offset?: number;
27
+ }
28
+ const telegramSetupNonces = new Map<string, SetupNonce>();
29
+
30
+ /** Exported for testing */
31
+ export { telegramSetupNonces };
32
+
33
+ /** Called from server/index.ts to share the approval router reference */
34
+ export function setApprovalRouter(router: ApprovalRouter | null): void {
35
+ approvalRouter = router;
36
+ }
37
+
38
+ /** Called from server/index.ts to read current router */
39
+ export function getApprovalRouter(): ApprovalRouter | null {
40
+ return approvalRouter;
41
+ }
42
+
43
+ const router = Router();
44
+
45
+ function listAdapterSecretNames(): Record<string, string[]> {
46
+ const records = listApiKeyCredentials().filter((credential) =>
47
+ credential.service.startsWith('adapter:'),
48
+ );
49
+
50
+ const byType: Record<string, string[]> = {};
51
+ for (const record of records) {
52
+ const adapterType = record.service.replace('adapter:', '');
53
+ if (!byType[adapterType]) byType[adapterType] = [];
54
+ byType[adapterType].push(record.name);
55
+ }
56
+ return byType;
57
+ }
58
+
59
+ async function readAdapterSecret(type: string, name: string): Promise<string | null> {
60
+ await ensureApiKeysMigrated();
61
+ return readApiKeyValueByServiceName(`adapter:${type}`, name);
62
+ }
63
+
64
+ // GET /adapters — List configured adapters
65
+ router.get('/', requireWalletAuth, requirePermission('adapter:manage'), async (_req: Request, res: Response) => {
66
+ try {
67
+ const appConfig = await prisma.appConfig.findUnique({
68
+ where: { id: 'global' },
69
+ });
70
+
71
+ let config: { enabled: boolean; chat?: { defaultApp?: string }; adapters: Array<{ type: string; enabled: boolean; config: Record<string, unknown>; chat?: { enabled?: boolean } }> } = {
72
+ enabled: false,
73
+ adapters: [],
74
+ };
75
+
76
+ if (appConfig?.adapterConfig) {
77
+ try {
78
+ config = JSON.parse(appConfig.adapterConfig);
79
+ } catch {
80
+ // Invalid JSON, return default
81
+ }
82
+ }
83
+
84
+ await ensureApiKeysMigrated();
85
+ const secretsByType = listAdapterSecretNames();
86
+
87
+ // Annotate adapters with secret status
88
+ const adapters = (config.adapters || []).map((a) => ({
89
+ type: a.type,
90
+ enabled: a.enabled,
91
+ config: a.config,
92
+ chat: a.chat,
93
+ hasSecrets: (secretsByType[a.type] || []).length > 0,
94
+ secretKeys: secretsByType[a.type] || [],
95
+ }));
96
+
97
+ res.json({
98
+ success: true,
99
+ enabled: config.enabled,
100
+ chat: config.chat,
101
+ adapters,
102
+ running: approvalRouter !== null,
103
+ });
104
+ } catch (error) {
105
+ const message = getErrorMessage(error);
106
+ res.status(500).json({ error: message });
107
+ }
108
+ });
109
+
110
+ // POST /adapters — Save adapter config
111
+ router.post('/', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
112
+ try {
113
+ const { type, enabled, config, chat: chatConfig } = req.body;
114
+
115
+ if (!type || typeof type !== 'string') {
116
+ res.status(400).json({ error: 'type is required' });
117
+ return;
118
+ }
119
+
120
+ if (typeof enabled !== 'boolean') {
121
+ res.status(400).json({ error: 'enabled must be a boolean' });
122
+ return;
123
+ }
124
+
125
+ // Read current config
126
+ const appConfig = await prisma.appConfig.findUnique({
127
+ where: { id: 'global' },
128
+ });
129
+
130
+ let current: { enabled: boolean; chat?: { defaultApp?: string }; adapters: Array<{ type: string; enabled: boolean; config: Record<string, unknown>; chat?: { enabled?: boolean } }> } = {
131
+ enabled: true,
132
+ adapters: [],
133
+ };
134
+
135
+ if (appConfig?.adapterConfig) {
136
+ try {
137
+ current = JSON.parse(appConfig.adapterConfig);
138
+ } catch {
139
+ // Reset on invalid JSON
140
+ }
141
+ }
142
+
143
+ // Upsert the adapter entry
144
+ const idx = current.adapters.findIndex((a) => a.type === type);
145
+ const entry: { type: string; enabled: boolean; config: Record<string, unknown>; chat?: { enabled?: boolean } } = { type, enabled, config: config || {} };
146
+ if (chatConfig && typeof chatConfig === 'object') {
147
+ entry.chat = chatConfig;
148
+ }
149
+
150
+ if (idx >= 0) {
151
+ current.adapters[idx] = entry;
152
+ } else {
153
+ current.adapters.push(entry);
154
+ }
155
+
156
+ // If any adapter is being enabled, enable the system
157
+ if (enabled) {
158
+ current.enabled = true;
159
+ }
160
+
161
+ await prisma.appConfig.upsert({
162
+ where: { id: 'global' },
163
+ update: { adapterConfig: JSON.stringify(current) },
164
+ create: { id: 'global', adapterConfig: JSON.stringify(current) },
165
+ });
166
+
167
+ const action = idx >= 0 ? 'updated' : 'created';
168
+ logger.adapterChanged(action, type);
169
+
170
+ res.json({ success: true, adapter: entry });
171
+ } catch (error) {
172
+ const message = getErrorMessage(error);
173
+ res.status(500).json({ error: message });
174
+ }
175
+ });
176
+
177
+ // DELETE /adapters/:type — Remove adapter config
178
+ router.delete('/:type', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
179
+ try {
180
+ const { type } = req.params;
181
+
182
+ const appConfig = await prisma.appConfig.findUnique({
183
+ where: { id: 'global' },
184
+ });
185
+
186
+ if (!appConfig?.adapterConfig) {
187
+ res.status(404).json({ error: 'No adapter config found' });
188
+ return;
189
+ }
190
+
191
+ let current: { enabled: boolean; adapters: Array<{ type: string; enabled: boolean; config: Record<string, unknown> }> };
192
+ try {
193
+ current = JSON.parse(appConfig.adapterConfig);
194
+ } catch {
195
+ res.status(500).json({ error: 'Invalid adapter config in database' });
196
+ return;
197
+ }
198
+
199
+ const idx = current.adapters.findIndex((a) => a.type === type);
200
+ if (idx < 0) {
201
+ res.status(404).json({ error: `Adapter type '${type}' not found` });
202
+ return;
203
+ }
204
+
205
+ current.adapters.splice(idx, 1);
206
+
207
+ // If no adapters remain, disable the system
208
+ if (current.adapters.length === 0) {
209
+ current.enabled = false;
210
+ }
211
+
212
+ await prisma.appConfig.update({
213
+ where: { id: 'global' },
214
+ data: { adapterConfig: JSON.stringify(current) },
215
+ });
216
+
217
+ logger.adapterChanged('deleted', type);
218
+
219
+ res.json({ success: true, message: `Adapter '${type}' removed` });
220
+ } catch (error) {
221
+ const message = getErrorMessage(error);
222
+ res.status(500).json({ error: message });
223
+ }
224
+ });
225
+
226
+ // POST /adapters/test — Send a test message through a configured adapter
227
+ router.post('/test', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
228
+ try {
229
+ const { type } = req.body;
230
+
231
+ if (!type || typeof type !== 'string') {
232
+ res.status(400).json({ error: 'type is required' });
233
+ return;
234
+ }
235
+
236
+ // 5s timeout for external calls
237
+ const controller = new AbortController();
238
+ const timeout = setTimeout(() => controller.abort(), 5000);
239
+
240
+ try {
241
+ switch (type) {
242
+ case 'telegram': {
243
+ // Read bot token from vault-backed API key credentials.
244
+ const botToken = await readAdapterSecret('telegram', 'botToken');
245
+ if (!botToken) {
246
+ console.warn('[adapters] telegram test: bot token not found in credential store');
247
+ res.status(400).json({ error: 'Telegram bot token not configured' });
248
+ return;
249
+ }
250
+
251
+ // Read chat ID from adapter config
252
+ const appConfig = await prisma.appConfig.findUnique({ where: { id: 'global' } });
253
+ let chatId: string | undefined;
254
+ if (appConfig?.adapterConfig) {
255
+ try {
256
+ const config = JSON.parse(appConfig.adapterConfig);
257
+ const telegramAdapter = config.adapters?.find((a: { type: string }) => a.type === 'telegram');
258
+ chatId = telegramAdapter?.config?.chatId ? String(telegramAdapter.config.chatId) : undefined;
259
+ } catch { /* ignore parse errors */ }
260
+ }
261
+
262
+ if (!chatId) {
263
+ console.warn('[adapters] telegram test: chatId not found in adapter config');
264
+ res.status(400).json({ error: 'Telegram chat ID not configured. Reconfigure the adapter with a chat ID.' });
265
+ return;
266
+ }
267
+
268
+ const resp = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
269
+ method: 'POST',
270
+ headers: { 'Content-Type': 'application/json' },
271
+ body: JSON.stringify({
272
+ chat_id: chatId,
273
+ text: '<b>AuraMaxx</b>\n\nAdapter chat is now active. You can send messages here and the AI will respond.',
274
+ parse_mode: 'HTML',
275
+ }),
276
+ signal: controller.signal,
277
+ });
278
+
279
+ const data = await resp.json() as { ok?: boolean; description?: string };
280
+ if (data.ok) {
281
+ res.json({ success: true });
282
+ } else {
283
+ console.warn('[adapters] telegram test: API call failed:', data.description);
284
+ res.json({ success: false, error: data.description || 'Failed to send test message' });
285
+ }
286
+ break;
287
+ }
288
+
289
+ case 'webhook': {
290
+ // Read webhook URL from adapter config
291
+ const appConfig2 = await prisma.appConfig.findUnique({ where: { id: 'global' } });
292
+ let webhookUrl: string | undefined;
293
+ if (appConfig2?.adapterConfig) {
294
+ try {
295
+ const config = JSON.parse(appConfig2.adapterConfig);
296
+ const webhookAdapter = config.adapters?.find((a: { type: string }) => a.type === 'webhook');
297
+ webhookUrl = webhookAdapter?.config?.url;
298
+ } catch { /* ignore parse errors */ }
299
+ }
300
+
301
+ if (!webhookUrl) {
302
+ res.status(404).json({ error: 'Webhook URL not configured' });
303
+ return;
304
+ }
305
+
306
+ // SSRF protection: validate webhook URL before sending test
307
+ try {
308
+ await validateExternalUrl(webhookUrl);
309
+ } catch (err) {
310
+ const msg = getErrorMessage(err);
311
+ res.status(403).json({ error: msg });
312
+ return;
313
+ }
314
+
315
+ const resp = await fetch(webhookUrl, {
316
+ method: 'POST',
317
+ headers: { 'Content-Type': 'application/json' },
318
+ body: JSON.stringify({
319
+ type: 'test',
320
+ data: {},
321
+ timestamp: Date.now(),
322
+ }),
323
+ signal: controller.signal,
324
+ });
325
+
326
+ if (resp.ok) {
327
+ res.json({ success: true });
328
+ } else {
329
+ res.json({ success: false, error: `Webhook returned ${resp.status}` });
330
+ }
331
+ break;
332
+ }
333
+
334
+ default:
335
+ res.status(400).json({ error: `Unknown adapter type: ${type}` });
336
+ return;
337
+ }
338
+ } catch (err) {
339
+ if (err instanceof Error && err.name === 'AbortError') {
340
+ res.json({ success: false, error: 'Test timed out' });
341
+ } else {
342
+ throw err;
343
+ }
344
+ } finally {
345
+ clearTimeout(timeout);
346
+ }
347
+ } catch (error) {
348
+ const message = getErrorMessage(error);
349
+ res.status(500).json({ error: message });
350
+ }
351
+ });
352
+
353
+ // POST /adapters/chat — Update top-level chat config (defaultApp)
354
+ router.post('/chat', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
355
+ try {
356
+ const { defaultApp } = req.body;
357
+
358
+ const appConfig = await prisma.appConfig.findUnique({
359
+ where: { id: 'global' },
360
+ });
361
+
362
+ let current: { enabled: boolean; chat?: { defaultApp?: string }; adapters: unknown[] } = {
363
+ enabled: true,
364
+ adapters: [],
365
+ };
366
+
367
+ if (appConfig?.adapterConfig) {
368
+ try {
369
+ current = JSON.parse(appConfig.adapterConfig);
370
+ } catch {
371
+ // Reset on invalid JSON
372
+ }
373
+ }
374
+
375
+ current.chat = {
376
+ ...current.chat,
377
+ defaultApp: defaultApp || undefined,
378
+ };
379
+
380
+ await prisma.appConfig.upsert({
381
+ where: { id: 'global' },
382
+ update: { adapterConfig: JSON.stringify(current) },
383
+ create: { id: 'global', adapterConfig: JSON.stringify(current) },
384
+ });
385
+
386
+ // Clear the app cache in the router
387
+ if (approvalRouter) {
388
+ approvalRouter.clearAppCache();
389
+ }
390
+
391
+ res.json({ success: true, chat: current.chat });
392
+ } catch (error) {
393
+ const message = getErrorMessage(error);
394
+ res.status(500).json({ error: message });
395
+ }
396
+ });
397
+
398
+ // POST /adapters/:type/message — Inbound chat from external adapter (HMAC-authenticated)
399
+ router.post('/:type/message', async (req: Request, res: Response) => {
400
+ try {
401
+ const { type } = req.params;
402
+ const { text, senderId } = req.body;
403
+
404
+ if (!text || typeof text !== 'string') {
405
+ res.status(400).json({ error: 'text is required' });
406
+ return;
407
+ }
408
+
409
+ if (!senderId || typeof senderId !== 'string') {
410
+ res.status(400).json({ error: 'senderId is required' });
411
+ return;
412
+ }
413
+
414
+ // HMAC authentication: look up the adapter's secret
415
+ const secretKey = await readAdapterSecret(type, 'secret');
416
+
417
+ if (secretKey) {
418
+ // Validate HMAC signature
419
+ const signature = req.headers['x-signature-256'] as string | undefined;
420
+ if (!signature) {
421
+ res.status(401).json({ error: 'Missing X-Signature-256 header' });
422
+ return;
423
+ }
424
+
425
+ const rawBody = JSON.stringify(req.body);
426
+ const expected = `sha256=${createHmac('sha256', secretKey).update(rawBody).digest('hex')}`;
427
+
428
+ try {
429
+ const sigBuf = Buffer.from(signature);
430
+ const expectedBuf = Buffer.from(expected);
431
+ if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
432
+ res.status(401).json({ error: 'Invalid signature' });
433
+ return;
434
+ }
435
+ } catch {
436
+ res.status(401).json({ error: 'Invalid signature' });
437
+ return;
438
+ }
439
+ }
440
+
441
+ if (!approvalRouter) {
442
+ res.status(503).json({ error: 'Adapter router not running' });
443
+ return;
444
+ }
445
+
446
+ const appId = await approvalRouter.resolveApp(req.body.targetApp);
447
+ if (!appId) {
448
+ res.status(400).json({ error: 'No target app configured. Set chat.defaultApp in adapter config.' });
449
+ return;
450
+ }
451
+
452
+ const result = await approvalRouter.sendMessage(appId, text, undefined, type);
453
+ res.json({
454
+ success: !result.error,
455
+ reply: result.reply,
456
+ error: result.error,
457
+ });
458
+ } catch (error) {
459
+ const message = getErrorMessage(error);
460
+ res.status(500).json({ error: message });
461
+ }
462
+ });
463
+
464
+ // POST /adapters/telegram/setup-link — Generate deep link for auto-detecting chat ID
465
+ router.post('/telegram/setup-link', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
466
+ try {
467
+ // Accept bot token from body (pre-save flow) or read from credential store
468
+ let botToken = req.body.botToken as string | undefined;
469
+ if (!botToken) {
470
+ const storedBotToken = await readAdapterSecret('telegram', 'botToken');
471
+ if (!storedBotToken) {
472
+ res.status(400).json({ error: 'Bot token not provided and not saved. Pass botToken in body or save it first.' });
473
+ return;
474
+ }
475
+ botToken = storedBotToken;
476
+ }
477
+
478
+ // Validate bot token via getMe
479
+ const controller = new AbortController();
480
+ const timeout = setTimeout(() => controller.abort(), 5000);
481
+ try {
482
+ const meResp = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, {
483
+ signal: controller.signal,
484
+ });
485
+ const meData = await meResp.json() as { ok?: boolean; result?: { username?: string }; description?: string };
486
+ if (!meData.ok) {
487
+ res.status(400).json({ error: meData.description || 'Invalid bot token' });
488
+ return;
489
+ }
490
+
491
+ const botUsername = meData.result?.username || '';
492
+
493
+ // Generate nonce
494
+ const nonce = randomBytes(12).toString('base64url');
495
+
496
+ // Lazy cleanup of expired nonces
497
+ const now = Date.now();
498
+ for (const [key, val] of telegramSetupNonces) {
499
+ if (val.expiresAt < now) telegramSetupNonces.delete(key);
500
+ }
501
+
502
+ // Store nonce with 120s TTL
503
+ telegramSetupNonces.set(nonce, {
504
+ botToken,
505
+ botUsername,
506
+ expiresAt: now + 120_000,
507
+ });
508
+
509
+ // Stop the running approval router so its Telegram polling doesn't
510
+ // consume the /start message before detect-chat can see it.
511
+ // It will be restarted after detection completes (via /adapters/restart).
512
+ if (approvalRouter) {
513
+ await approvalRouter.stop();
514
+ approvalRouter = null;
515
+ }
516
+
517
+ // Delete webhook to ensure getUpdates polling works
518
+ await fetch(`https://api.telegram.org/bot${botToken}/deleteWebhook`, {
519
+ signal: controller.signal,
520
+ }).catch(() => { /* non-fatal */ });
521
+
522
+ // Flush stale updates so detect-chat only sees new messages.
523
+ // Call getUpdates with offset=-1 to get the last update, then
524
+ // confirm it so subsequent polls start fresh.
525
+ let nextOffset = 0;
526
+ try {
527
+ const flushResp = await fetch(
528
+ `https://api.telegram.org/bot${botToken}/getUpdates?offset=-1&timeout=0`,
529
+ { signal: controller.signal },
530
+ );
531
+ const flushData = await flushResp.json() as { ok?: boolean; result?: Array<{ update_id: number }> };
532
+ if (flushData.ok && flushData.result?.length) {
533
+ const lastId = flushData.result[flushData.result.length - 1].update_id;
534
+ // Confirm the last update so it's removed from the queue
535
+ await fetch(
536
+ `https://api.telegram.org/bot${botToken}/getUpdates?offset=${lastId + 1}&timeout=0`,
537
+ { signal: controller.signal },
538
+ );
539
+ nextOffset = lastId + 1;
540
+ }
541
+ } catch { /* non-fatal — detect-chat will still work, just may see stale updates */ }
542
+
543
+ // Store offset in nonce so detect-chat can skip old updates
544
+ const nonceEntry = telegramSetupNonces.get(nonce);
545
+ if (nonceEntry) nonceEntry.offset = nextOffset;
546
+
547
+ res.json({
548
+ success: true,
549
+ link: `https://t.me/${botUsername}?start=${nonce}`,
550
+ setupToken: nonce,
551
+ botUsername,
552
+ });
553
+ } finally {
554
+ clearTimeout(timeout);
555
+ }
556
+ } catch (error) {
557
+ const message = getErrorMessage(error);
558
+ res.status(500).json({ error: message });
559
+ }
560
+ });
561
+
562
+ // POST /adapters/telegram/detect-chat — Poll for /start message to auto-detect chat ID
563
+ router.post('/telegram/detect-chat', requireWalletAuth, requirePermission('adapter:manage'), async (req: Request, res: Response) => {
564
+ try {
565
+ const { setupToken } = req.body;
566
+ if (!setupToken || typeof setupToken !== 'string') {
567
+ res.status(400).json({ error: 'setupToken is required' });
568
+ return;
569
+ }
570
+
571
+ const nonceEntry = telegramSetupNonces.get(setupToken);
572
+ if (!nonceEntry) {
573
+ res.status(400).json({ error: 'Invalid or expired setup token' });
574
+ return;
575
+ }
576
+
577
+ if (nonceEntry.expiresAt < Date.now()) {
578
+ telegramSetupNonces.delete(setupToken);
579
+ res.status(400).json({ error: 'Setup token expired' });
580
+ return;
581
+ }
582
+
583
+ const { botToken } = nonceEntry;
584
+
585
+ // Long-poll getUpdates (25s timeout)
586
+ const controller = new AbortController();
587
+ const timeout = setTimeout(() => controller.abort(), 30_000);
588
+ try {
589
+ const offsetParam = nonceEntry.offset ? `&offset=${nonceEntry.offset}` : '';
590
+ const updatesResp = await fetch(
591
+ `https://api.telegram.org/bot${botToken}/getUpdates?timeout=25&allowed_updates=${encodeURIComponent(JSON.stringify(['message']))}${offsetParam}`,
592
+ { signal: controller.signal },
593
+ );
594
+ const updatesData = await updatesResp.json() as {
595
+ ok?: boolean;
596
+ result?: Array<{
597
+ update_id: number;
598
+ message?: {
599
+ text?: string;
600
+ chat?: { id: number; first_name?: string; username?: string };
601
+ };
602
+ }>;
603
+ };
604
+
605
+ if (!updatesData.ok || !updatesData.result) {
606
+ res.json({ chatId: null, timeout: true });
607
+ return;
608
+ }
609
+
610
+ // Look for /start message
611
+ let detectedUpdate: (typeof updatesData.result)[number] | null = null;
612
+ let verified = false;
613
+
614
+ for (const update of updatesData.result) {
615
+ const text = update.message?.text || '';
616
+ if (text === `/start ${setupToken}`) {
617
+ detectedUpdate = update;
618
+ verified = true;
619
+ break;
620
+ }
621
+ if (text === '/start' || text.startsWith('/start ')) {
622
+ detectedUpdate = update;
623
+ verified = false;
624
+ // Keep looking for exact nonce match
625
+ }
626
+ }
627
+
628
+ if (!detectedUpdate || !detectedUpdate.message?.chat) {
629
+ // Advance offset past any updates we just saw so the next
630
+ // poll attempt doesn't re-process them
631
+ if (updatesData.result.length > 0) {
632
+ const maxId = updatesData.result[updatesData.result.length - 1].update_id;
633
+ nonceEntry.offset = maxId + 1;
634
+ await fetch(
635
+ `https://api.telegram.org/bot${botToken}/getUpdates?offset=${maxId + 1}&timeout=0`,
636
+ { signal: controller.signal },
637
+ ).catch(() => { /* non-fatal */ });
638
+ }
639
+ res.json({ chatId: null, timeout: true });
640
+ return;
641
+ }
642
+
643
+ const chat = detectedUpdate.message.chat;
644
+ const chatId = String(chat.id);
645
+
646
+ // Confirm the update by calling getUpdates with offset past it
647
+ await fetch(
648
+ `https://api.telegram.org/bot${botToken}/getUpdates?offset=${detectedUpdate.update_id + 1}&timeout=0`,
649
+ { signal: controller.signal },
650
+ ).catch(() => { /* non-fatal */ });
651
+
652
+ // Clean up nonce
653
+ telegramSetupNonces.delete(setupToken);
654
+
655
+ res.json({
656
+ chatId,
657
+ firstName: chat.first_name || null,
658
+ username: chat.username || null,
659
+ verified,
660
+ });
661
+ } finally {
662
+ clearTimeout(timeout);
663
+ }
664
+ } catch (error) {
665
+ if (error instanceof Error && error.name === 'AbortError') {
666
+ res.json({ chatId: null, timeout: true });
667
+ } else {
668
+ const message = getErrorMessage(error);
669
+ res.status(500).json({ error: message });
670
+ }
671
+ }
672
+ });
673
+
674
+ // POST /adapters/restart — Restart approval router with current DB config
675
+ router.post('/restart', requireWalletAuth, requirePermission('adapter:manage'), async (_req: Request, res: Response) => {
676
+ try {
677
+ // Stop existing router
678
+ if (approvalRouter) {
679
+ await approvalRouter.stop();
680
+ approvalRouter = null;
681
+ }
682
+
683
+ // Load adapters from DB
684
+ const adapters = await loadAdaptersFromDb();
685
+
686
+ if (adapters.length === 0) {
687
+ res.json({ success: true, message: 'No adapters configured, router stopped', running: false });
688
+ return;
689
+ }
690
+
691
+ const newRouter = new ApprovalRouter(`http://127.0.0.1:${SERVER_PORT}`);
692
+ for (const adapter of adapters) {
693
+ newRouter.registerAdapter(adapter);
694
+ }
695
+
696
+ await newRouter.start();
697
+ approvalRouter = newRouter;
698
+
699
+ res.json({
700
+ success: true,
701
+ message: `Approval router started with ${adapters.length} adapter(s)`,
702
+ running: true,
703
+ adapterCount: adapters.length,
704
+ });
705
+ } catch (error) {
706
+ const message = getErrorMessage(error);
707
+ res.status(500).json({ error: message });
708
+ }
709
+ });
710
+
711
+ export default router;