auramaxx 0.0.11 → 0.0.13

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 (342) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +240 -223
  3. package/.next/app-path-routes-manifest.json +8 -7
  4. package/.next/build-manifest.json +14 -14
  5. package/.next/prerender-manifest.json +53 -29
  6. package/.next/react-loadable-manifest.json +41 -41
  7. package/.next/routes-manifest.json +6 -0
  8. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_not-found.html +1 -1
  10. package/.next/server/app/_not-found.rsc +12 -12
  11. package/.next/server/app/api/[...doc]/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/agent-requests/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/apps/install/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/apps/manifests/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/apps/static/[...path]/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/docs/plain/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/events/route.js +1 -19
  18. package/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/import-from-openclaw/[channel]/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/import-from-openclaw/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/import-from-openclaw/validate/[channel]/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/restart/route.js +1 -1
  24. package/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/update/route.js +63 -1
  26. package/.next/server/app/api/update/route.js.nft.json +1 -1
  27. package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/version/route.js +1 -1
  29. package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/workspace/[id]/apps/[wid]/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/workspace/[id]/apps/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/workspace/[id]/export/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/workspace/[id]/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/workspace/config/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/workspace/import/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/workspace/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/app-legacy-do-not-use/page.js +1 -1
  38. package/.next/server/app/app-legacy-do-not-use/page.js.nft.json +1 -1
  39. package/.next/server/app/app-legacy-do-not-use/page_client-reference-manifest.js +1 -1
  40. package/.next/server/app/app-legacy-do-not-use.html +1 -1
  41. package/.next/server/app/app-legacy-do-not-use.rsc +14 -14
  42. package/.next/server/app/approve/[actionId]/page.js +1 -1
  43. package/.next/server/app/approve/[actionId]/page_client-reference-manifest.js +1 -1
  44. package/.next/server/app/docs/[...doc]/page_client-reference-manifest.js +1 -1
  45. package/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  46. package/.next/server/app/health/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/health.html +1 -1
  48. package/.next/server/app/health.rsc +13 -13
  49. package/.next/server/app/hello/page_client-reference-manifest.js +1 -1
  50. package/.next/server/app/hello.html +1 -1
  51. package/.next/server/app/hello.rsc +14 -14
  52. package/.next/server/app/index.html +1 -1
  53. package/.next/server/app/index.rsc +18 -21
  54. package/.next/server/app/page.js +3 -3
  55. package/.next/server/app/page.js.nft.json +1 -1
  56. package/.next/server/app/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
  58. package/.next/server/app/privacy.html +1 -1
  59. package/.next/server/app/privacy.rsc +13 -13
  60. package/.next/server/app/share/[token]/page_client-reference-manifest.js +1 -1
  61. package/.next/server/app/terms/page_client-reference-manifest.js +1 -1
  62. package/.next/server/app/terms.html +1 -1
  63. package/.next/server/app/terms.rsc +13 -13
  64. package/.next/server/app/yo/page.js +2 -0
  65. package/.next/server/app/yo/page.js.nft.json +1 -0
  66. package/.next/server/app/yo/page_client-reference-manifest.js +1 -0
  67. package/.next/server/app/yo.html +1 -0
  68. package/.next/server/app/yo.meta +7 -0
  69. package/.next/server/app/yo.rsc +23 -0
  70. package/.next/server/app-paths-manifest.json +8 -7
  71. package/.next/server/chunks/2145.js +1 -1
  72. package/.next/server/chunks/2460.js +1 -1
  73. package/.next/server/chunks/5246.js +1 -1
  74. package/.next/server/chunks/5678.js +1 -1
  75. package/.next/server/chunks/5784.js +1 -1
  76. package/.next/server/chunks/6086.js +2 -20
  77. package/.next/server/chunks/{5553.js → 6415.js} +2 -2
  78. package/.next/server/chunks/7935.js +2 -2
  79. package/.next/server/functions-config-manifest.json +1 -1
  80. package/.next/server/instrumentation.js +1 -1
  81. package/.next/server/middleware-build-manifest.js +1 -1
  82. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  83. package/.next/server/pages/404.html +1 -1
  84. package/.next/server/pages/500.html +1 -1
  85. package/.next/server/server-reference-manifest.json +1 -1
  86. package/.next/server/webpack-runtime.js +1 -1
  87. package/.next/static/WshFGr6RxGYP6AbWuT9OG/_buildManifest.js +1 -0
  88. package/.next/static/chunks/1168.aaac1edbb597fe5a.js +1 -0
  89. package/.next/static/chunks/1255-7999eac54f80a49f.js +1 -0
  90. package/.next/static/chunks/142-fa9752f53a551f63.js +1 -0
  91. package/.next/static/chunks/2505.d54ccadc42f4e3d1.js +1 -0
  92. package/.next/static/chunks/2619-04bc32f026a0d946.js +1 -0
  93. package/.next/static/chunks/2927.7e00cc878d9a3f52.js +1 -0
  94. package/.next/static/chunks/3573-1b41d7b8a000d015.js +1 -0
  95. package/.next/static/chunks/3609.ded5a5306e18af9c.js +1 -0
  96. package/.next/static/chunks/3667-1db7bd03948e60df.js +1 -0
  97. package/.next/static/chunks/3826.a73d4a88d8c09030.js +1 -0
  98. package/.next/static/chunks/{3a91511d-648a2ba3dad7df0c.js → 3a91511d-ba215c0b5dc21ba9.js} +1 -1
  99. package/.next/static/chunks/4256.48407d9abad5ea33.js +1 -0
  100. package/.next/static/chunks/4685-7f53bbfc4a9845eb.js +1 -0
  101. package/.next/static/chunks/4901-ba6a32818662e70a.js +1 -0
  102. package/.next/static/chunks/4919-7e7cdd5efc9f2110.js +1 -0
  103. package/.next/static/chunks/4bd1b696-100b9d70ed4e49c1.js +1 -0
  104. package/.next/static/chunks/5336-233ec7ab3807267c.js +1 -0
  105. package/.next/static/chunks/5442-b5bb869e832e8967.js +1 -0
  106. package/.next/static/chunks/6233-97a810aa272af547.js +1 -0
  107. package/.next/static/chunks/626.a5109d16f9eca1f6.js +1 -0
  108. package/.next/static/chunks/6872-faea0f088ab2d450.js +1 -0
  109. package/.next/static/chunks/7338-3ce17a93614f1d77.js +59 -0
  110. package/.next/static/chunks/7394-4bdb1feefad1a74a.js +1 -0
  111. package/.next/static/chunks/7616-1129bcb3eee8d315.js +1 -0
  112. package/.next/static/chunks/786-26deffb41572cbb3.js +1 -0
  113. package/.next/static/chunks/8273-8e92d34180669ca9.js +1 -0
  114. package/.next/static/chunks/8357.5dee1e0c4e5bb091.js +1 -0
  115. package/.next/static/chunks/9062-2bc2d089f9c9c6ba.js +1 -0
  116. package/.next/static/chunks/9380.f198afbf0c6b5369.js +1 -0
  117. package/.next/static/chunks/app/_not-found/page-5a8c6a29f762fa58.js +1 -0
  118. package/.next/static/chunks/app/api/[...doc]/page-f0852f35f0fd1d44.js +1 -0
  119. package/.next/static/chunks/app/api/agent-requests/route-cf84f975aad4c719.js +1 -0
  120. package/.next/static/chunks/app/api/apps/install/route-cf84f975aad4c719.js +1 -0
  121. package/.next/static/chunks/app/api/apps/manifests/route-cf84f975aad4c719.js +1 -0
  122. package/.next/static/chunks/app/api/apps/static/[...path]/route-cf84f975aad4c719.js +1 -0
  123. package/.next/static/chunks/app/api/docs/plain/route-cf84f975aad4c719.js +1 -0
  124. package/.next/static/chunks/app/api/events/route-cf84f975aad4c719.js +1 -0
  125. package/.next/static/chunks/app/api/import-from-openclaw/[channel]/route-cf84f975aad4c719.js +1 -0
  126. package/.next/static/chunks/app/api/import-from-openclaw/route-cf84f975aad4c719.js +1 -0
  127. package/.next/static/chunks/app/api/import-from-openclaw/validate/[channel]/route-cf84f975aad4c719.js +1 -0
  128. package/.next/static/chunks/app/api/page-cc59bebcc0d2c01d.js +1 -0
  129. package/.next/static/chunks/app/api/restart/route-cf84f975aad4c719.js +1 -0
  130. package/.next/static/chunks/app/api/update/route-cf84f975aad4c719.js +1 -0
  131. package/.next/static/chunks/app/api/version/route-cf84f975aad4c719.js +1 -0
  132. package/.next/static/chunks/app/api/workspace/[id]/apps/[wid]/route-cf84f975aad4c719.js +1 -0
  133. package/.next/static/chunks/app/api/workspace/[id]/apps/route-cf84f975aad4c719.js +1 -0
  134. package/.next/static/chunks/app/api/workspace/[id]/export/route-cf84f975aad4c719.js +1 -0
  135. package/.next/static/chunks/app/api/workspace/[id]/route-cf84f975aad4c719.js +1 -0
  136. package/.next/static/chunks/app/api/workspace/config/route-cf84f975aad4c719.js +1 -0
  137. package/.next/static/chunks/app/api/workspace/import/route-cf84f975aad4c719.js +1 -0
  138. package/.next/static/chunks/app/api/workspace/route-cf84f975aad4c719.js +1 -0
  139. package/.next/static/chunks/app/app-legacy-do-not-use/page-e5dc864e92d90ca7.js +1 -0
  140. package/.next/static/chunks/app/approve/[actionId]/page-2acca1f490424f21.js +1 -0
  141. package/.next/static/chunks/app/docs/[...doc]/page-8e2a2d036caab242.js +1 -0
  142. package/.next/static/chunks/app/docs/page-acf872a03ff79893.js +1 -0
  143. package/.next/static/chunks/app/error-66f983b7769dabfa.js +1 -0
  144. package/.next/static/chunks/app/health/page-c9185854ed9c86d0.js +1 -0
  145. package/.next/static/chunks/app/hello/page-74c9f4deaa4b03dd.js +1 -0
  146. package/.next/static/chunks/app/layout-af8d9969c7aeb758.js +1 -0
  147. package/.next/static/chunks/app/page-16dfcd1c7cc88bcc.js +1 -0
  148. package/.next/static/chunks/app/privacy/page-8e2d17079355c2cc.js +1 -0
  149. package/.next/static/chunks/app/share/[token]/page-5dd9b0418eee411f.js +1 -0
  150. package/.next/static/chunks/app/terms/page-8e2d17079355c2cc.js +1 -0
  151. package/.next/static/chunks/app/yo/layout-cf84f975aad4c719.js +1 -0
  152. package/.next/static/chunks/app/yo/page-719dc5f213fdfb30.js +1 -0
  153. package/.next/static/chunks/framework-a32a2a465584c0bc.js +1 -0
  154. package/.next/static/chunks/main-0f0f9142f74e7215.js +1 -0
  155. package/.next/static/chunks/main-app-24f0c92ba10af457.js +1 -0
  156. package/.next/static/chunks/pages/_app-4b3fb5e477a0267f.js +1 -0
  157. package/.next/static/chunks/pages/_error-c970d8b55ace1b48.js +1 -0
  158. package/.next/static/chunks/{webpack-768de8b7d6a7a27a.js → webpack-79ad58260e9b10b4.js} +1 -1
  159. package/.next/static/css/83cd401584ab787f.css +3 -0
  160. package/.next/trace +28 -28
  161. package/.next/types/app/yo/layout.ts +84 -0
  162. package/.next/types/app/yo/page.ts +84 -0
  163. package/.next/types/routes.d.ts +4 -2
  164. package/.next/types/validator.ts +18 -0
  165. package/bin/auramaxx.js +11 -26
  166. package/docs/ARCHITECTURE.md +1 -1
  167. package/docs/AUTH.md +6 -3
  168. package/docs/CLI.md +2 -0
  169. package/docs/MCP.md +2 -0
  170. package/docs/TROUBLESHOOTING.md +24 -0
  171. package/docs/credentials.md +2 -0
  172. package/package.json +2 -1
  173. package/prisma/migrations/20260227214000_update_agent_action_ttl_defaults/migration.sql +19 -0
  174. package/public/0a167e5e-4f52-4715-ae23-bf63d259a6b1.png +0 -0
  175. package/public/141ec92c-6780-4b23-838f-9a7bf1e91bb8.png +0 -0
  176. package/public/3afc4935-92cb-42af-9624-0b1341c12a5e.png +0 -0
  177. package/public/43947df5-dbcf-4e49-ab8b-41b9162c0410.png +0 -0
  178. package/public/5aeae9ce-0d38-49ea-8fd1-167892a04a85.png +0 -0
  179. package/public/660e4ea3-a3a6-4be4-a8ca-2cb74c51dfb5.png +0 -0
  180. package/public/733f02d7-6b58-4ba6-a5c8-d062cd205e1d.png +0 -0
  181. package/public/a32d65cb-95b0-4977-be6b-cf69f515afbe.png +0 -0
  182. package/public/agent1.png +0 -0
  183. package/public/agent10.png +0 -0
  184. package/public/agent2.png +0 -0
  185. package/public/agent3.png +0 -0
  186. package/public/agent4.png +0 -0
  187. package/public/agent5.png +0 -0
  188. package/public/agent6.png +0 -0
  189. package/public/agent7.png +0 -0
  190. package/public/agent8.png +0 -0
  191. package/public/agent9.png +0 -0
  192. package/public/c4938305-b811-4ccc-91db-94d309734827.png +0 -0
  193. package/public/f2ca6825-a4f3-4107-815c-51ee740dfc09.png +0 -0
  194. package/public/llm.txt +2 -0
  195. package/public/llms.txt +39 -0
  196. package/public/ss-dark1.png +0 -0
  197. package/public/ss-dark1.webp +0 -0
  198. package/public/ss-dark2.png +0 -0
  199. package/public/ss-dark2.webp +0 -0
  200. package/public/ss-dark3.png +0 -0
  201. package/public/ss-dark3.webp +0 -0
  202. package/public/ss-light1.png +0 -0
  203. package/public/ss-light1.webp +0 -0
  204. package/public/ss-light2.png +0 -0
  205. package/public/ss-light2.webp +0 -0
  206. package/public/ss-light3.png +0 -0
  207. package/public/ss-light3.webp +0 -0
  208. package/shared/agent-profile-schema.ts +81 -0
  209. package/shared/credential-field-schema.ts +12 -0
  210. package/skills/auramaxx/SKILL.md +71 -691
  211. package/src/app/UnlockPageClient.tsx +1939 -0
  212. package/src/app/api/page.tsx +8 -9
  213. package/src/app/api/restart/route.ts +2 -18
  214. package/src/app/api/update/route.ts +104 -51
  215. package/src/app/approve/[actionId]/page.tsx +4 -1
  216. package/src/app/docs/DocsPageContent.tsx +3 -3
  217. package/src/app/globals.css +94 -0
  218. package/src/app/layout.tsx +1 -0
  219. package/src/app/page.tsx +25 -1935
  220. package/src/app/yo/layout.tsx +29 -0
  221. package/src/app/yo/page.tsx +528 -0
  222. package/src/components/HumanActionBar.tsx +34 -8
  223. package/src/components/agent/AgentSidebar.tsx +3 -1
  224. package/src/components/agent/CredentialAgent.tsx +5 -1
  225. package/src/components/agent/CredentialDetail.tsx +32 -1
  226. package/src/components/agent/CredentialForm.tsx +94 -7
  227. package/src/components/agent/CredentialRow.tsx +8 -1
  228. package/src/components/agent/credentialFormName.ts +22 -1
  229. package/src/components/agent/types.ts +2 -2
  230. package/src/components/design-system/Modal.tsx +14 -1
  231. package/src/hooks/useUpdateChecker.ts +17 -1
  232. package/src/lib/pino.ts +77 -8
  233. package/src/server/cli/commands/actions.ts +1 -1
  234. package/src/server/cli/commands/agent.ts +110 -65
  235. package/src/server/cli/commands/approve.ts +1 -1
  236. package/src/server/cli/commands/auth.ts +81 -20
  237. package/src/server/cli/commands/start.ts +42 -3
  238. package/src/server/cli/commands/token.ts +2 -2
  239. package/src/server/cli/lib/escalation.ts +109 -24
  240. package/src/server/cli/lib/process.ts +54 -1
  241. package/src/server/cli/socket.ts +1 -1
  242. package/src/server/index.ts +2 -0
  243. package/src/server/lib/agent-profile-records.ts +72 -0
  244. package/src/server/lib/credential-transport.ts +27 -11
  245. package/src/server/lib/defaults.ts +3 -3
  246. package/src/server/lib/escalation-responder.ts +1 -1
  247. package/src/server/lib/resolve-action.ts +2 -2
  248. package/src/server/lib/update-check.ts +1 -1
  249. package/src/server/mcp/server.ts +6 -1
  250. package/src/server/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  251. package/src/server/routes/actions.ts +2 -2
  252. package/src/server/routes/agent-profiles.ts +82 -0
  253. package/src/server/routes/auth.ts +39 -4
  254. package/src/server/routes/credentials.ts +18 -0
  255. package/src/server/tests/cli/agent-auth.test.ts +20 -39
  256. package/src/server/tests/cli/agent.test.ts +18 -0
  257. package/src/server/tests/cli/auth-action-flag.test.ts +3 -2
  258. package/src/server/tests/cli/bin-entrypoint.test.ts +35 -11
  259. package/src/server/tests/cli/escalation.test.ts +7 -3
  260. package/src/server/tests/cli/process.test.ts +3 -3
  261. package/src/server/tests/cli/socket.test.ts +2 -2
  262. package/src/server/tests/cli/start-run.test.ts +24 -1
  263. package/src/server/tests/endpoints/actions.test.ts +2 -2
  264. package/src/server/tests/endpoints/agent-profiles.test.ts +117 -0
  265. package/src/server/tests/endpoints/auth.test.ts +34 -0
  266. package/src/server/tests/lib/credential-transport.test.ts +68 -2
  267. package/src/server/tests/lib/defaults.test.ts +2 -2
  268. package/src/server/tests/lib/escalation-responder.test.ts +2 -2
  269. package/src/server/tests/lib/update-check.test.ts +1 -1
  270. package/src/server/tests/setup.ts +7 -0
  271. package/src/server/tsconfig.tsbuildinfo +1 -1
  272. package/src/server/types.ts +1 -1
  273. package/.next/static/AcaCjQ4akovHBUnVGPpfN/_buildManifest.js +0 -1
  274. package/.next/static/chunks/1168.63dbb444a33b1867.js +0 -1
  275. package/.next/static/chunks/1255-e8718b02724690dd.js +0 -1
  276. package/.next/static/chunks/142-aeaf7ffa9c53516d.js +0 -1
  277. package/.next/static/chunks/2505.22aaa333fd65908f.js +0 -1
  278. package/.next/static/chunks/2619-3c9e02e22d10480a.js +0 -1
  279. package/.next/static/chunks/2927.e7e9e2a1b8d2dc61.js +0 -1
  280. package/.next/static/chunks/3573-27e17f4ff2dd86ed.js +0 -1
  281. package/.next/static/chunks/3609.6f8e0ecd6de9566c.js +0 -1
  282. package/.next/static/chunks/3667-d6770121629db38b.js +0 -1
  283. package/.next/static/chunks/3826.7dfe96467cd74e45.js +0 -1
  284. package/.next/static/chunks/4256.50cb375c979ffd5a.js +0 -1
  285. package/.next/static/chunks/4685-3f8d92f574366fec.js +0 -1
  286. package/.next/static/chunks/4901-54c1ac380b7b43bb.js +0 -1
  287. package/.next/static/chunks/4919-fe6f1553abfc9420.js +0 -1
  288. package/.next/static/chunks/4bd1b696-f785427dddbba9fb.js +0 -1
  289. package/.next/static/chunks/5336-bd251f91235f7c11.js +0 -1
  290. package/.next/static/chunks/5442-be197c885bf12079.js +0 -1
  291. package/.next/static/chunks/5553-c8b86fe3513fce04.js +0 -59
  292. package/.next/static/chunks/6233-44e6fe57a552a816.js +0 -1
  293. package/.next/static/chunks/626.2583673a0386a81b.js +0 -1
  294. package/.next/static/chunks/6872-6442f2f5cce36ce5.js +0 -1
  295. package/.next/static/chunks/7411-3ca797c21b722ccd.js +0 -1
  296. package/.next/static/chunks/7616-b8bd37ce1f735d6f.js +0 -1
  297. package/.next/static/chunks/786-9ed39f96091b2be4.js +0 -1
  298. package/.next/static/chunks/8273-922091226ba84a94.js +0 -1
  299. package/.next/static/chunks/8357.6159472717ff7d11.js +0 -1
  300. package/.next/static/chunks/9062-3eb1607c96486f88.js +0 -1
  301. package/.next/static/chunks/9380.93f361baab2eefdf.js +0 -1
  302. package/.next/static/chunks/app/_not-found/page-c3b87025baf0a9c2.js +0 -1
  303. package/.next/static/chunks/app/api/[...doc]/page-790c4b33ba1fde4a.js +0 -1
  304. package/.next/static/chunks/app/api/agent-requests/route-e83b12cbab2e8707.js +0 -1
  305. package/.next/static/chunks/app/api/apps/install/route-e83b12cbab2e8707.js +0 -1
  306. package/.next/static/chunks/app/api/apps/manifests/route-e83b12cbab2e8707.js +0 -1
  307. package/.next/static/chunks/app/api/apps/static/[...path]/route-e83b12cbab2e8707.js +0 -1
  308. package/.next/static/chunks/app/api/docs/plain/route-e83b12cbab2e8707.js +0 -1
  309. package/.next/static/chunks/app/api/events/route-e83b12cbab2e8707.js +0 -1
  310. package/.next/static/chunks/app/api/import-from-openclaw/[channel]/route-e83b12cbab2e8707.js +0 -1
  311. package/.next/static/chunks/app/api/import-from-openclaw/route-e83b12cbab2e8707.js +0 -1
  312. package/.next/static/chunks/app/api/import-from-openclaw/validate/[channel]/route-e83b12cbab2e8707.js +0 -1
  313. package/.next/static/chunks/app/api/page-b53f9aa17a4c5201.js +0 -1
  314. package/.next/static/chunks/app/api/restart/route-e83b12cbab2e8707.js +0 -1
  315. package/.next/static/chunks/app/api/update/route-e83b12cbab2e8707.js +0 -1
  316. package/.next/static/chunks/app/api/version/route-e83b12cbab2e8707.js +0 -1
  317. package/.next/static/chunks/app/api/workspace/[id]/apps/[wid]/route-e83b12cbab2e8707.js +0 -1
  318. package/.next/static/chunks/app/api/workspace/[id]/apps/route-e83b12cbab2e8707.js +0 -1
  319. package/.next/static/chunks/app/api/workspace/[id]/export/route-e83b12cbab2e8707.js +0 -1
  320. package/.next/static/chunks/app/api/workspace/[id]/route-e83b12cbab2e8707.js +0 -1
  321. package/.next/static/chunks/app/api/workspace/config/route-e83b12cbab2e8707.js +0 -1
  322. package/.next/static/chunks/app/api/workspace/import/route-e83b12cbab2e8707.js +0 -1
  323. package/.next/static/chunks/app/api/workspace/route-e83b12cbab2e8707.js +0 -1
  324. package/.next/static/chunks/app/app-legacy-do-not-use/page-0052191daef60036.js +0 -1
  325. package/.next/static/chunks/app/approve/[actionId]/page-45cd3b8fa062d5e5.js +0 -1
  326. package/.next/static/chunks/app/docs/[...doc]/page-632ac406200b66fe.js +0 -1
  327. package/.next/static/chunks/app/docs/page-b7556394709b43df.js +0 -1
  328. package/.next/static/chunks/app/error-3d6057da512253d8.js +0 -1
  329. package/.next/static/chunks/app/health/page-80c985cd72328b74.js +0 -1
  330. package/.next/static/chunks/app/hello/page-fd71babcd192729b.js +0 -1
  331. package/.next/static/chunks/app/layout-285c6ef3f16bae63.js +0 -1
  332. package/.next/static/chunks/app/page-85017185df14c37b.js +0 -1
  333. package/.next/static/chunks/app/privacy/page-faf36cd0dde6dfa3.js +0 -1
  334. package/.next/static/chunks/app/share/[token]/page-22d51d6c5a47bb75.js +0 -1
  335. package/.next/static/chunks/app/terms/page-faf36cd0dde6dfa3.js +0 -1
  336. package/.next/static/chunks/framework-e60c938074ff7136.js +0 -1
  337. package/.next/static/chunks/main-447abf206d7ebd2f.js +0 -1
  338. package/.next/static/chunks/main-app-f63b86bdbf5b7b88.js +0 -1
  339. package/.next/static/chunks/pages/_app-6c8c2371b16a04b8.js +0 -1
  340. package/.next/static/chunks/pages/_error-94812ad32cad7365.js +0 -1
  341. package/.next/static/css/eb25c6452113486f.css +0 -3
  342. /package/.next/static/{AcaCjQ4akovHBUnVGPpfN → WshFGr6RxGYP6AbWuT9OG}/_ssgManifest.js +0 -0
package/src/app/page.tsx CHANGED
@@ -1,1939 +1,29 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
- import * as bip39 from 'bip39';
5
- import Link from 'next/link';
6
- import { Fingerprint, Settings, CircleHelp } from 'lucide-react';
7
- import { useAuth } from '@/context/AuthContext';
8
- import { api, Api, unlockWallet, setupWallet, rekeySession, changePrimaryAgentPassword, recoverWalletAccess } from '@/lib/api';
9
- import { generateAgentKeypair, getAgentPrivateKey } from '@/lib/agent-crypto';
10
- import { CredentialAgent } from '@/components/agent/CredentialAgent';
11
- import { NotificationDrawer } from '@/components/NotificationDrawer';
12
- import { SettingsDrawer } from '@/components/layout/SettingsDrawer';
13
- import { useAgentActions } from '@/hooks/useAgentActions';
14
- import DocsThemeToggle from '@/components/docs/DocsThemeToggle';
15
- import { Modal, Button, ItemPicker, TextInput, ConfirmationModal } from '@/components/design-system';
16
- import { PasskeyEnrollmentPrompt } from '@/components/PasskeyEnrollmentPrompt';
17
- import { UpdateBanner } from '@/components/UpdateBanner';
18
- import { START_BANNER_QUOTES, getNextStartBannerQuote } from '@/lib/startBannerQuotes';
19
- import { useTheme, type ColorMode, type UiScale } from '@/hooks/useTheme';
20
-
21
- interface AgentInfo {
22
- id: string;
23
- name?: string;
24
- address: string;
25
- solanaAddress?: string;
26
- isUnlocked: boolean;
27
- isPrimary: boolean;
28
- createdAt: string;
29
- }
30
-
31
- interface WalletData {
32
- address: string;
33
- tier: 'cold' | 'hot' | 'temp';
34
- chain: string;
35
- balance?: string;
36
- }
37
-
38
- type PageState = 'loading' | 'setup' | 'locked' | 'transition' | 'unlocked';
39
- type LocalAgentMode = 'strict' | 'dev' | 'admin';
40
- type ProjectScopeMode = 'auto' | 'strict' | 'off';
41
- type SetupOnboardingStep = 'seed' | 'trust';
42
- type SeedPhraseActionStatus = 'copied' | 'copy-failed' | 'downloaded' | 'download-failed';
43
- type LocalPolicySettings = {
44
- profile: LocalAgentMode;
45
- profileVersion: 'v1';
46
- autoApprove: boolean;
47
- projectScopeMode: ProjectScopeMode;
48
- };
49
-
50
- const LOCAL_POLICY_PROFILES: LocalAgentMode[] = ['strict', 'dev', 'admin'];
51
- const LOCAL_PROJECT_SCOPE_MODES: ProjectScopeMode[] = ['auto', 'strict', 'off'];
52
- const LOCAL_PROFILE_ITEM_OPTIONS = [
53
- {
54
- value: 'strict',
55
- label: 'strict',
56
- description: 'Minimal local token scope with explicit allowlists.',
57
- },
58
- {
59
- value: 'dev',
60
- label: 'dev',
61
- description: 'Balanced local automation with scoped defaults.',
62
- },
63
- {
64
- value: 'admin',
65
- label: 'admin (dangerous)',
66
- description: 'Broad token scope. Use only in tightly controlled local environments.',
1
+ import type { Metadata } from 'next';
2
+ import UnlockPageClient from './UnlockPageClient';
3
+
4
+ const SEO_TITLE = 'auramaxx.sh';
5
+ const SEO_DESCRIPTION = 'THE APPLE KEYCHAIN FOR AI AGENTS. share passwpords, api keys, and credit cards with OpenClaw, Claude, Codex, Gemini,etc';
6
+
7
+ export const metadata: Metadata = {
8
+ title: SEO_TITLE,
9
+ description: SEO_DESCRIPTION,
10
+ alternates: {
11
+ canonical: '/',
12
+ },
13
+ openGraph: {
14
+ type: 'website',
15
+ url: '/',
16
+ siteName: SEO_TITLE,
17
+ title: SEO_TITLE,
18
+ description: SEO_DESCRIPTION,
19
+ },
20
+ twitter: {
21
+ card: 'summary_large_image',
22
+ title: SEO_TITLE,
23
+ description: SEO_DESCRIPTION,
67
24
  },
68
- ] as const;
69
- const LOCAL_PROJECT_SCOPE_ITEM_OPTIONS = [
70
- {
71
- value: 'auto',
72
- label: 'auto (recommended)',
73
- description: 'Uses `.aura` when present and safely falls back to token scope when missing.',
74
- },
75
- {
76
- value: 'strict',
77
- label: 'strict (require .aura)',
78
- description: 'Requires explicit `.aura` mappings for secret access.',
79
- },
80
- {
81
- value: 'off',
82
- label: 'off (disable project allowlist)',
83
- description: 'Disables project allowlist checks for local token access.',
84
- },
85
- ] as const;
86
- const ONBOARDING_LOCAL_AGENT_MODE_OPTIONS = [
87
- {
88
- value: 'dev',
89
- label: 'mid (dev)',
90
- description: 'Access to most things. Human approval for stuff like CVV.',
91
- },
92
- {
93
- value: 'strict',
94
- label: 'sus (local)',
95
- description: 'Most locked down. Every request needs manual approval.',
96
- },
97
- {
98
- value: 'admin',
99
- label: 'maxx (admin)',
100
- description: 'Full access. Use only when you fully trust the agent.',
101
- },
102
- ] as const;
103
- const AGENT_COLOR_MODE_OPTIONS = [
104
- { value: 'light', label: 'Light' },
105
- { value: 'dark', label: 'Dark' },
106
- ] as const;
107
- const AGENT_UI_SCALE_OPTIONS = [
108
- { value: 'normal', label: 'Normal' },
109
- { value: 'big', label: 'Big' },
110
- ] as const;
111
-
112
- type OnboardingSeedDraft = {
113
- mnemonic: string;
114
- createdAt: number;
115
25
  };
116
26
 
117
- const ONBOARDING_SEED_STORAGE_KEY = 'aura:onboarding-seed-draft';
118
- const ONBOARDING_SEED_TTL_MS = 15 * 60 * 1000;
119
- const DASHBOARD_TRANSITION_TIMEOUT_MS = 1500;
120
- const LOADER_EXIT_DURATION_MS = 220;
121
- const LOCK_STATE_RECHECK_MS = 450;
122
- const TOKEN_HYDRATION_GRACE_MS = 120;
123
- const TOKEN_STORAGE_KEY = 'auramaxx_admin_token';
124
- const BIP39_WORD_SET = new Set((bip39.wordlists.english as string[]).map((word) => word.toLowerCase()));
125
-
126
- const normalizeRecoveryWords = (raw: string): string[] => raw
127
- .trim()
128
- .toLowerCase()
129
- .split(/\s+/)
130
- .filter(Boolean);
131
-
132
- export default function UnlockPage() {
133
- const { token, setToken, clearToken } = useAuth();
134
- const { colorMode, uiScale, setColorMode, setUiScale } = useTheme();
135
-
136
- const [pageState, setPageState] = useState<PageState>('loading');
137
- const [password, setPassword] = useState('');
138
- const [trustDevice, setTrustDevice] = useState(true);
139
- const [loading, setLoading] = useState(false);
140
- const [error, setError] = useState<string | null>(null);
141
- const [mnemonic, setMnemonic] = useState<string | null>(null);
142
- const [seedAcknowledged, setSeedAcknowledged] = useState(false);
143
- const [seedRecoveryNotice, setSeedRecoveryNotice] = useState<string | null>(null);
144
- const [seedPhraseActionStatus, setSeedPhraseActionStatus] = useState<SeedPhraseActionStatus | null>(null);
145
- const [loaderExiting, setLoaderExiting] = useState(false);
146
- const [localAgentMode, setLocalAgentMode] = useState<LocalAgentMode>('admin');
147
- const [setupOnboardingStep, setSetupOnboardingStep] = useState<SetupOnboardingStep>('seed');
148
- const [onboardingToken, setOnboardingToken] = useState<string | null>(null);
149
- const [dashboardTransitionTimedOut, setDashboardTransitionTimedOut] = useState(false);
150
- const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
151
- const { notifications: pageNotifications, dismissNotification: pageDismissNotification } = useAgentActions({ autoFetch: !!token });
152
- const [nukeConfirmOpen, setNukeConfirmOpen] = useState(false);
153
- const [nuking, setNuking] = useState(false);
154
- const [nukeError, setNukeError] = useState<string | null>(null);
155
- const [policySettings, setPolicySettings] = useState<LocalPolicySettings | null>(null);
156
- const [policyForm, setPolicyForm] = useState<LocalPolicySettings>({
157
- profile: 'admin',
158
- profileVersion: 'v1',
159
- autoApprove: true,
160
- projectScopeMode: 'auto',
161
- });
162
- const [policyLoadError, setPolicyLoadError] = useState<string | null>(null);
163
- const [policySaveError, setPolicySaveError] = useState<string | null>(null);
164
- const [policyFormErrors, setPolicyFormErrors] = useState<Record<string, string>>({});
165
- const [policySaveSuccess, setPolicySaveSuccess] = useState<string | null>(null);
166
- const [policyLoading, setPolicyLoading] = useState(false);
167
- const [policySaving, setPolicySaving] = useState(false);
168
- const [dangerConfirmOpen, setDangerConfirmOpen] = useState(false);
169
- const [discardConfirmOpen, setDiscardConfirmOpen] = useState(false);
170
- const [agentThemeOpen, setAgentThemeOpen] = useState(true);
171
- const [agentSettingsOpen, setAgentSettingsOpen] = useState(false);
172
- const [securitySettingsOpen, setSecuritySettingsOpen] = useState(false);
173
- const [dangerZoneOpen, setDangerZoneOpen] = useState(false);
174
- const [backupSectionOpen, setBackupSectionOpen] = useState(false);
175
- const [backups, setBackups] = useState<Array<{ filename: string; timestamp: string; size: number; date: string }>>([]);
176
- const [backupsLoading, setBackupsLoading] = useState(false);
177
- const [creatingBackup, setCreatingBackup] = useState(false);
178
- const [restoringBackup, setRestoringBackup] = useState<string | null>(null);
179
- const [exportingDb, setExportingDb] = useState(false);
180
- const [restoreConfirmOpen, setRestoreConfirmOpen] = useState<string | null>(null);
181
- const [restoreAnchorEl, setRestoreAnchorEl] = useState<HTMLElement | null>(null);
182
- const [showPasswordModal, setShowPasswordModal] = useState(false);
183
- const [currentPasswordValue, setCurrentPasswordValue] = useState('');
184
- const [newPasswordValue, setNewPasswordValue] = useState('');
185
- const [confirmPasswordValue, setConfirmPasswordValue] = useState('');
186
- const [passwordChangeError, setPasswordChangeError] = useState<string | null>(null);
187
- const [passwordChangeSuccess, setPasswordChangeSuccess] = useState<string | null>(null);
188
- const [passwordChanging, setPasswordChanging] = useState(false);
189
- const [showSeedRecovery, setShowSeedRecovery] = useState(false);
190
- const [recoveryWordCount, setRecoveryWordCount] = useState<12 | 24>(12);
191
- const [recoveryWords, setRecoveryWords] = useState<string[]>(Array(12).fill(''));
192
-
193
- // Passkey biometric unlock state
194
- const [passkeyAvailable, setPasskeyAvailable] = useState(false);
195
- const [passkeyLoading, setPasskeyLoading] = useState(false);
196
- const [startBannerQuote] = useState<string>(() => {
197
- if (typeof window === 'undefined') return START_BANNER_QUOTES[0];
198
- try {
199
- return getNextStartBannerQuote(window.localStorage);
200
- } catch {
201
- return START_BANNER_QUOTES[0];
202
- }
203
- });
204
- const [recoveryNewPassword, setRecoveryNewPassword] = useState('');
205
- const [recoveryError, setRecoveryError] = useState<string | null>(null);
206
- const [recoveryLoading, setRecoveryLoading] = useState(false);
207
- const pageStateRef = useRef<PageState>('loading');
208
- const loaderExitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
209
- const pendingPageStateRef = useRef<PageState | null>(null);
210
- const mountedRef = useRef(true);
211
- const fetchStateRunIdRef = useRef(0);
212
- const rekeyInFlightRef = useRef<Promise<{ success: boolean; token: string }> | null>(null);
213
-
214
- const hasPendingSeedConfirmation = useMemo(() => Boolean(mnemonic), [mnemonic]);
215
- const pendingHumanActionCount = useMemo(
216
- () => pageNotifications.filter((notification) => notification.status === 'pending' && notification.type !== 'notify').length,
217
- [pageNotifications],
218
- );
219
- const utilityLinksBottomOffset = useMemo(
220
- () => (pendingHumanActionCount > 0 ? 'calc(4rem + 3.5rem)' : '4rem'),
221
- [pendingHumanActionCount],
222
- );
223
- const recoveryWordsFilled = useMemo(() => recoveryWords.filter((word) => word.length > 0).length, [recoveryWords]);
224
- const normalizedRecoveryPhrase = useMemo(() => recoveryWords.map((word) => word.trim().toLowerCase()).join(' ').trim(), [recoveryWords]);
225
- const invalidRecoveryIndexes = useMemo(() => {
226
- const invalid = new Set<number>();
227
- recoveryWords.forEach((word, index) => {
228
- if (!word) return;
229
- if (!BIP39_WORD_SET.has(word.trim().toLowerCase())) {
230
- invalid.add(index);
231
- }
232
- });
233
- return invalid;
234
- }, [recoveryWords]);
235
- const isRecoveryPhraseStructurallyValid = useMemo(() => {
236
- if (recoveryWordsFilled !== recoveryWordCount) return false;
237
- if (invalidRecoveryIndexes.size > 0) return false;
238
- return bip39.validateMnemonic(normalizedRecoveryPhrase);
239
- }, [invalidRecoveryIndexes.size, normalizedRecoveryPhrase, recoveryWordCount, recoveryWordsFilled]);
240
-
241
- const isDangerousPolicySelection = useCallback((settings: LocalPolicySettings) => {
242
- return settings.profile === 'admin';
243
- }, []);
244
-
245
- const policyFormDirty = useMemo(() => {
246
- if (!policySettings) return false;
247
- return JSON.stringify(policySettings) !== JSON.stringify(policyForm);
248
- }, [policyForm, policySettings]);
249
-
250
- useEffect(() => {
251
- try {
252
- const rawDraft = sessionStorage.getItem(ONBOARDING_SEED_STORAGE_KEY);
253
- if (!rawDraft) return;
254
- const parsed = JSON.parse(rawDraft) as OnboardingSeedDraft;
255
- if (!parsed?.mnemonic || typeof parsed.createdAt !== 'number') {
256
- sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
257
- return;
258
- }
259
- if (Date.now() - parsed.createdAt > ONBOARDING_SEED_TTL_MS) {
260
- sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
261
- setSeedRecoveryNotice('Recovery phrase draft expired. Restart setup to generate a new phrase.');
262
- return;
263
- }
264
-
265
- setMnemonic(parsed.mnemonic);
266
- setSetupOnboardingStep('seed');
267
- setPageState('setup');
268
- setSeedRecoveryNotice('Recovered your in-progress recovery phrase for this tab. Confirm after you store it safely.');
269
- } catch {
270
- sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
271
- }
272
- }, []);
273
-
274
- useEffect(() => {
275
- if (!mnemonic) {
276
- sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
277
- setSetupOnboardingStep('seed');
278
- setSeedPhraseActionStatus(null);
279
- return;
280
- }
281
- const draft: OnboardingSeedDraft = {
282
- mnemonic,
283
- createdAt: Date.now(),
284
- };
285
- sessionStorage.setItem(ONBOARDING_SEED_STORAGE_KEY, JSON.stringify(draft));
286
- }, [mnemonic]);
287
-
288
- useEffect(() => {
289
- if (pageState !== 'locked') {
290
- setShowSeedRecovery(false);
291
- }
292
- }, [pageState]);
293
-
294
- useEffect(() => {
295
- pageStateRef.current = pageState;
296
- }, [pageState]);
297
-
298
- useEffect(() => {
299
- mountedRef.current = true;
300
- return () => {
301
- mountedRef.current = false;
302
- if (loaderExitTimerRef.current) {
303
- clearTimeout(loaderExitTimerRef.current);
304
- }
305
- };
306
- }, []);
307
-
308
- const queuePageStateAfterLoader = useCallback((nextState: PageState) => {
309
- const currentState = pageStateRef.current;
310
- if (currentState === 'loading' || currentState === 'transition') {
311
- if (pendingPageStateRef.current === nextState) return;
312
- pendingPageStateRef.current = nextState;
313
- setLoaderExiting(true);
314
- if (loaderExitTimerRef.current) {
315
- clearTimeout(loaderExitTimerRef.current);
316
- }
317
- loaderExitTimerRef.current = setTimeout(() => {
318
- setLoaderExiting(false);
319
- pendingPageStateRef.current = null;
320
- setPageState(nextState);
321
- }, LOADER_EXIT_DURATION_MS);
322
- return;
323
- }
324
-
325
- if (loaderExitTimerRef.current) {
326
- clearTimeout(loaderExitTimerRef.current);
327
- loaderExitTimerRef.current = null;
328
- }
329
- pendingPageStateRef.current = null;
330
- setLoaderExiting(false);
331
- setPageState(nextState);
332
- }, []);
333
-
334
- const beginTransitionState = useCallback(() => {
335
- if (loaderExitTimerRef.current) {
336
- clearTimeout(loaderExitTimerRef.current);
337
- loaderExitTimerRef.current = null;
338
- }
339
- pendingPageStateRef.current = null;
340
- setLoaderExiting(false);
341
- setPageState('transition');
342
- }, []);
343
-
344
- // Check if passkey biometric unlock is usable (registered + agent unlocked server-side)
345
- useEffect(() => {
346
- if (pageState !== 'locked') {
347
- setPasskeyAvailable(false);
348
- return;
349
- }
350
- if (typeof window === 'undefined' || !window.PublicKeyCredential) return;
351
- // Electron's Chromium reports WebAuthn as available but the sandbox
352
- // can't complete the ceremony — skip passkey in desktop app.
353
- if (typeof window !== 'undefined' && (window as unknown as Record<string, unknown>).auraDesktop) return;
354
-
355
- let cancelled = false;
356
- (async () => {
357
- try {
358
- const status = await api.get<{ registered: boolean }>(Api.Wallet, '/auth/passkey/status');
359
- if (cancelled || !status.registered) return;
360
- // Probe authenticate/options to confirm agent is unlocked server-side.
361
- // If the server returns agent_locked, biometric auth won't work.
362
- await api.post(Api.Wallet, '/auth/passkey/authenticate/options', {});
363
- if (!cancelled) setPasskeyAvailable(true);
364
- } catch { /* agent_locked or no passkeys — hide button */ }
365
- })();
366
- return () => { cancelled = true; };
367
- }, [pageState]);
368
-
369
- const fetchState = useCallback(async () => {
370
- const runId = ++fetchStateRunIdRef.current;
371
- const canMutate = () => mountedRef.current && runId === fetchStateRunIdRef.current;
372
- let activeToken = token;
373
- if (!activeToken && typeof window !== 'undefined') {
374
- const localToken = window.localStorage.getItem(TOKEN_STORAGE_KEY);
375
- const sessionToken = window.sessionStorage.getItem(TOKEN_STORAGE_KEY);
376
- const recoveredToken = localToken || sessionToken;
377
- if (recoveredToken) {
378
- const persist = localToken ? 'local' : 'session';
379
- setToken(recoveredToken, { persist });
380
- // Allow the auth context to re-render with the recovered token before
381
- // resolving page state; this avoids a brief locked-screen flash.
382
- return;
383
- }
384
- }
385
-
386
- // Page reload recovery: keypair is memory-only and lost on reload.
387
- // If token exists but keypair is gone, regenerate keypair and re-key the session.
388
- if (activeToken && !getAgentPrivateKey()) {
389
- try {
390
- if (!rekeyInFlightRef.current) {
391
- rekeyInFlightRef.current = (async () => {
392
- const { publicKeyBase64 } = await generateAgentKeypair();
393
- return rekeySession(publicKeyBase64);
394
- })()
395
- .finally(() => {
396
- rekeyInFlightRef.current = null;
397
- });
398
- }
399
-
400
- const result = await rekeyInFlightRef.current;
401
- if (!canMutate()) return;
402
- if (result.token) {
403
- setToken(result.token);
404
- activeToken = result.token;
405
- }
406
- // Keypair restored, continue to fetch state normally
407
- } catch {
408
- if (!canMutate()) return;
409
- // Re-key failed (token expired, server restarted, agent locked, or no agent yet).
410
- // Clear stale token and continue with setup status checks.
411
- clearToken();
412
- activeToken = null;
413
- }
414
- }
415
-
416
- try {
417
- // Use /setup (lightweight status check) instead of /wallets (which fetches RPC balances
418
- // and can hang on cold start). /setup only checks in-memory agent state — no RPC calls.
419
- const [status, agentData] = await Promise.all([
420
- api.get<{ hasWallet: boolean; unlocked: boolean }>(Api.Wallet, '/setup'),
421
- api.get<{ agents: AgentInfo[] }>(Api.Wallet, '/setup/agents'),
422
- ]);
423
- if (!canMutate()) return;
424
-
425
- const agents = Array.isArray(agentData.agents) ? agentData.agents : [];
426
- const configured = status.hasWallet || agents.some(v => v.isPrimary) || agents.length > 0;
427
-
428
- if (!configured) {
429
- queuePageStateAfterLoader('setup');
430
- return;
431
- }
432
-
433
- if (hasPendingSeedConfirmation) {
434
- queuePageStateAfterLoader('setup');
435
- return;
436
- }
437
-
438
- const isInitialResolution =
439
- pageStateRef.current === 'loading' || pageStateRef.current === 'transition';
440
- if (status.unlocked && !activeToken && isInitialResolution) {
441
- try {
442
- await new Promise((resolve) => setTimeout(resolve, TOKEN_HYDRATION_GRACE_MS));
443
- if (!canMutate()) return;
444
-
445
- const localToken = window.localStorage.getItem(TOKEN_STORAGE_KEY);
446
- const sessionToken = window.sessionStorage.getItem(TOKEN_STORAGE_KEY);
447
- const recoveredToken = localToken || sessionToken;
448
- if (recoveredToken) {
449
- setToken(recoveredToken, { persist: localToken ? 'local' : 'session' });
450
- return;
451
- }
452
- } catch {
453
- if (!canMutate()) return;
454
- }
455
- }
456
-
457
- if (!status.unlocked && activeToken && isInitialResolution) {
458
- try {
459
- await new Promise((resolve) => setTimeout(resolve, LOCK_STATE_RECHECK_MS));
460
- if (!canMutate()) return;
461
- const retryStatus = await api.get<{ hasWallet: boolean; unlocked: boolean }>(Api.Wallet, '/setup');
462
- if (!canMutate()) return;
463
- if (retryStatus.unlocked) {
464
- queuePageStateAfterLoader('unlocked');
465
- return;
466
- }
467
- } catch {
468
- if (!canMutate()) return;
469
- }
470
- }
471
-
472
- if (!status.unlocked || !activeToken) {
473
- queuePageStateAfterLoader('locked');
474
- return;
475
- }
476
-
477
- queuePageStateAfterLoader('unlocked');
478
- } catch {
479
- if (!canMutate()) return;
480
- queuePageStateAfterLoader('setup');
481
- }
482
- }, [token, clearToken, hasPendingSeedConfirmation, queuePageStateAfterLoader, setToken]);
483
-
484
- useEffect(() => {
485
- fetchState();
486
- }, [fetchState]);
487
-
488
- const loadLocalPolicySettings = useCallback(async () => {
489
- if (!token) {
490
- setPolicyLoadError('Unlock agent first to manage local socket policy.');
491
- return;
492
- }
493
-
494
- setPolicyLoading(true);
495
- setPolicyLoadError(null);
496
- setPolicySaveError(null);
497
- setPolicySaveSuccess(null);
498
- try {
499
- const headers = { Authorization: `Bearer ${token}` };
500
- const baseUrl = api.getBaseUrl(Api.Wallet);
501
- const defaultsRes = await fetch(`${baseUrl}/defaults`, { headers });
502
- if (!defaultsRes.ok) {
503
- throw new Error('Failed to load canonical trust policy defaults.');
504
- }
505
-
506
- const defaultsJson = await defaultsRes.json() as {
507
- success?: boolean;
508
- defaults?: Record<string, Array<{ key: string; value: unknown }>>;
509
- };
510
- if (defaultsJson.success === false) {
511
- throw new Error('Failed to load canonical trust policy defaults.');
512
- }
513
- const flatDefaults = Object.values(defaultsJson.defaults || {}).flat();
514
- const findDefault = (key: string): unknown => flatDefaults.find((item) => item.key === key)?.value;
515
-
516
- const loadedProfile = String(findDefault('trust.localProfile') ?? '').trim() as LocalAgentMode;
517
- if (!LOCAL_POLICY_PROFILES.includes(loadedProfile)) {
518
- throw new Error(`Unknown persisted local profile: ${loadedProfile || '(empty)'}`);
519
- }
520
-
521
- const profileVersion = String(findDefault('trust.localProfileVersion') ?? 'v1').trim();
522
- if (profileVersion !== 'v1') {
523
- throw new Error('Unknown local profile version; refusing to edit settings.');
524
- }
525
-
526
- const loadedProjectScopeMode = String(findDefault('trust.projectScopeMode') ?? 'auto').trim() as ProjectScopeMode;
527
- if (!LOCAL_PROJECT_SCOPE_MODES.includes(loadedProjectScopeMode)) {
528
- throw new Error(`Unknown persisted project scope mode: ${loadedProjectScopeMode || '(empty)'}`);
529
- }
530
-
531
- const loaded: LocalPolicySettings = {
532
- profile: loadedProfile,
533
- profileVersion: 'v1',
534
- autoApprove: Boolean(findDefault('trust.localAutoApprove')),
535
- projectScopeMode: loadedProjectScopeMode,
536
- };
537
-
538
- setPolicySettings(loaded);
539
- setPolicyForm(loaded);
540
- } catch (err) {
541
- setPolicyLoadError((err as Error).message || 'Failed to load policy settings');
542
- setPolicySettings(null);
543
- } finally {
544
- setPolicyLoading(false);
545
- }
546
- }, [token]);
547
-
548
- useEffect(() => {
549
- if (showSettingsDrawer) {
550
- void loadLocalPolicySettings();
551
- }
552
- }, [showSettingsDrawer, loadLocalPolicySettings]);
553
-
554
- const persistLocalPolicySettings = useCallback(async () => {
555
- if (!token) throw new Error('Missing auth token for save.');
556
- if (!LOCAL_POLICY_PROFILES.includes(policyForm.profile)) {
557
- throw new Error('Unknown profile selected; refusing to persist.');
558
- }
559
- if (!LOCAL_PROJECT_SCOPE_MODES.includes(policyForm.projectScopeMode)) {
560
- throw new Error('Unknown project scope mode selected; refusing to persist.');
561
- }
562
-
563
- const baseUrl = api.getBaseUrl(Api.Wallet);
564
- const headers = {
565
- 'Content-Type': 'application/json',
566
- Authorization: `Bearer ${token}`,
567
- };
568
-
569
- const updates: Array<[string, unknown]> = [
570
- ['trust.localProfile', policyForm.profile],
571
- ['trust.localProfileVersion', 'v1'],
572
- ['trust.localAutoApprove', policyForm.autoApprove],
573
- ['trust.projectScopeMode', policyForm.projectScopeMode],
574
- ];
575
-
576
- const results = await Promise.all(
577
- updates.map(async ([key, value]) => {
578
- const response = await fetch(`${baseUrl}/defaults/${key}`, {
579
- method: 'PATCH',
580
- headers,
581
- body: JSON.stringify({ value }),
582
- });
583
- return response.ok;
584
- })
585
- );
586
-
587
- if (!results.every(Boolean)) {
588
- throw new Error('Failed to save canonical trust policy defaults.');
589
- }
590
-
591
- return { ...policyForm, profileVersion: 'v1' } as LocalPolicySettings;
592
- }, [policyForm, token]);
593
-
594
- const handleSaveLocalPolicy = useCallback(async () => {
595
- if (!policySettings || policyLoading || policySaving) return;
596
- const enablingDangerous = !isDangerousPolicySelection(policySettings) && isDangerousPolicySelection(policyForm);
597
- if (enablingDangerous && !dangerConfirmOpen) {
598
- setDangerConfirmOpen(true);
599
- return;
600
- }
601
-
602
- setPolicySaving(true);
603
- setPolicySaveError(null);
604
- setPolicySaveSuccess(null);
605
- try {
606
- const saved = await persistLocalPolicySettings();
607
- setPolicySettings(saved);
608
- setPolicyForm(saved);
609
- setDangerConfirmOpen(false);
610
- setPolicySaveSuccess('Local trust policy saved. Changes apply to newly issued local tokens only.');
611
- } catch (err) {
612
- const message = (err as Error).message || 'Failed to save policy settings';
613
- setDangerConfirmOpen(false);
614
- if (
615
- message.includes('Unknown profile selected')
616
- || message.includes('Unknown project scope mode selected')
617
- ) {
618
- setPolicySaveError(message);
619
- } else {
620
- await loadLocalPolicySettings();
621
- setPolicySaveError(`${message} Server values were reloaded.`);
622
- }
623
- } finally {
624
- setPolicySaving(false);
625
- }
626
- }, [dangerConfirmOpen, isDangerousPolicySelection, loadLocalPolicySettings, persistLocalPolicySettings, policyForm, policyLoading, policySaving, policySettings]);
627
-
628
- const closePasswordModal = useCallback(() => {
629
- setShowPasswordModal(false);
630
- setCurrentPasswordValue('');
631
- setNewPasswordValue('');
632
- setConfirmPasswordValue('');
633
- setPasswordChangeError(null);
634
- setPasswordChanging(false);
635
- }, []);
636
-
637
- const handleChangePrimaryPassword = useCallback(async (e: React.FormEvent) => {
638
- e.preventDefault();
639
- setPasswordChangeError(null);
640
- setPasswordChangeSuccess(null);
641
-
642
- if (newPasswordValue.length < 8) {
643
- setPasswordChangeError('New password must be at least 8 characters.');
644
- return;
645
- }
646
- if (newPasswordValue !== confirmPasswordValue) {
647
- setPasswordChangeError('New password and confirmation do not match.');
648
- return;
649
- }
650
-
651
- setPasswordChanging(true);
652
- try {
653
- await changePrimaryAgentPassword(currentPasswordValue, newPasswordValue);
654
- setPasswordChangeSuccess('Primary agent password updated.');
655
- closePasswordModal();
656
- } catch (err) {
657
- setPasswordChangeError((err as Error).message || 'Failed to change primary password.');
658
- } finally {
659
- setPasswordChanging(false);
660
- }
661
- }, [closePasswordModal, confirmPasswordValue, currentPasswordValue, newPasswordValue]);
662
-
663
- const handleRecoveryWordChange = useCallback((index: number, value: string) => {
664
- const normalized = value.trim().toLowerCase();
665
-
666
- if (normalized.includes(' ')) {
667
- const parsed = normalizeRecoveryWords(normalized);
668
- if (parsed.length > 1) {
669
- const nextWords = [...recoveryWords];
670
- parsed.forEach((word, offset) => {
671
- const targetIndex = index + offset;
672
- if (targetIndex < nextWords.length) nextWords[targetIndex] = word;
673
- });
674
- setRecoveryWords(nextWords);
675
- setRecoveryError(null);
676
- return;
677
- }
678
- }
679
-
680
- const nextWords = [...recoveryWords];
681
- nextWords[index] = normalized;
682
- setRecoveryWords(nextWords);
683
- setRecoveryError(null);
684
- }, [recoveryWords]);
685
-
686
- const handleRecoveryPaste = useCallback((index: number, text: string) => {
687
- const parsed = normalizeRecoveryWords(text);
688
- if (parsed.length <= 1) return false;
689
-
690
- const nextWordCount: 12 | 24 = parsed.length > 12 ? 24 : recoveryWordCount;
691
- if (nextWordCount !== recoveryWordCount) {
692
- setRecoveryWordCount(nextWordCount);
693
- const nextWords = Array(nextWordCount).fill('');
694
- parsed.slice(0, nextWordCount).forEach((word, wordIndex) => {
695
- nextWords[wordIndex] = word;
696
- });
697
- setRecoveryWords(nextWords);
698
- setRecoveryError(null);
699
- return true;
700
- }
701
-
702
- const nextWords = [...recoveryWords];
703
- parsed.forEach((word, offset) => {
704
- const targetIndex = index + offset;
705
- if (targetIndex < nextWords.length) nextWords[targetIndex] = word;
706
- });
707
- setRecoveryWords(nextWords);
708
- setRecoveryError(null);
709
- return true;
710
- }, [recoveryWordCount, recoveryWords]);
711
-
712
- const handleRecoverAccess = useCallback(async (e: React.FormEvent) => {
713
- e.preventDefault();
714
- setRecoveryError(null);
715
-
716
- if (recoveryNewPassword.length < 8) {
717
- setRecoveryError('New password must be at least 8 characters.');
718
- return;
719
- }
720
- if (!isRecoveryPhraseStructurallyValid) {
721
- setRecoveryError('Enter a valid 12 or 24-word BIP-39 seed phrase.');
722
- return;
723
- }
724
-
725
- setRecoveryLoading(true);
726
- try {
727
- const { publicKeyBase64 } = await generateAgentKeypair();
728
- const result = await recoverWalletAccess(normalizedRecoveryPhrase, recoveryNewPassword, publicKeyBase64);
729
- if (result.token) {
730
- setToken(result.token, { persist: trustDevice ? 'local' : 'session' });
731
- }
732
- setPassword('');
733
- setRecoveryNewPassword('');
734
- setRecoveryWords(Array(recoveryWordCount).fill(''));
735
- // Route through transition state to avoid jarring layout shift
736
- beginTransitionState();
737
- void bootstrapDashboardTransition();
738
- } catch (err) {
739
- setRecoveryError((err as Error).message || 'Recovery failed.');
740
- } finally {
741
- setRecoveryLoading(false);
742
- }
743
- }, [beginTransitionState, isRecoveryPhraseStructurallyValid, normalizedRecoveryPhrase, recoveryNewPassword, recoveryWordCount, setToken, trustDevice]);
744
-
745
- const handleUnlock = async (e: React.FormEvent) => {
746
- e.preventDefault();
747
- if (!password) return;
748
-
749
- setLoading(true);
750
- setError(null);
751
- try {
752
- // Generate keypair before unlock so the token is minted with our pubkey
753
- const { publicKeyBase64 } = await generateAgentKeypair();
754
- const data = await unlockWallet(password, undefined, publicKeyBase64);
755
- if (data.token) {
756
- setToken(data.token, { persist: trustDevice ? 'local' : 'session' });
757
- }
758
- setPassword('');
759
- // Route through transition state to avoid jarring layout shift
760
- beginTransitionState();
761
- void bootstrapDashboardTransition();
762
- } catch (err) {
763
- setError((err as Error).message || 'Unlock failed');
764
- } finally {
765
- setLoading(false);
766
- }
767
- };
768
-
769
- const handleSetup = async (e: React.FormEvent) => {
770
- e.preventDefault();
771
- if (password.length < 8) return;
772
-
773
- setLoading(true);
774
- setError(null);
775
- try {
776
- // Generate keypair before setup so the initial token has our pubkey
777
- const { publicKeyBase64 } = await generateAgentKeypair();
778
- const result = await setupWallet(password, publicKeyBase64);
779
- if (result.token) {
780
- setToken(result.token, { persist: trustDevice ? 'local' : 'session' });
781
- setOnboardingToken(result.token);
782
- }
783
- if (result.mnemonic) {
784
- setMnemonic(result.mnemonic);
785
- setSeedAcknowledged(false);
786
- setSetupOnboardingStep('seed');
787
- setSeedRecoveryNotice(null);
788
- setSeedPhraseActionStatus(null);
789
- }
790
- setPassword('');
791
- if (!result.mnemonic) fetchState();
792
- } catch (err) {
793
- const message = (err as Error).message || 'Setup failed';
794
- if (/already exists/i.test(message)) {
795
- setError('Primary agent already exists. Enter your password to unlock it.');
796
- setPageState('locked');
797
- return;
798
- }
799
- setError(message);
800
- } finally {
801
- setLoading(false);
802
- }
803
- };
804
-
805
- const handleCopySeedPhrase = useCallback(async () => {
806
- if (!mnemonic) return;
807
-
808
- let copied = false;
809
- if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
810
- try {
811
- await navigator.clipboard.writeText(mnemonic);
812
- copied = true;
813
- } catch {
814
- copied = false;
815
- }
816
- }
817
-
818
- if (copied) {
819
- setSeedPhraseActionStatus('copied');
820
- return;
821
- }
822
-
823
- const fallbackTextarea = document.createElement('textarea');
824
- fallbackTextarea.value = mnemonic;
825
- fallbackTextarea.setAttribute('readonly', 'true');
826
- fallbackTextarea.style.position = 'fixed';
827
- fallbackTextarea.style.left = '-9999px';
828
- document.body.appendChild(fallbackTextarea);
829
- fallbackTextarea.focus();
830
- fallbackTextarea.select();
831
-
832
- let fallbackCopied = false;
833
- try {
834
- fallbackCopied = document.execCommand('copy');
835
- } catch {
836
- fallbackCopied = false;
837
- } finally {
838
- fallbackTextarea.remove();
839
- }
840
-
841
- setSeedPhraseActionStatus(fallbackCopied ? 'copied' : 'copy-failed');
842
- }, [mnemonic]);
843
-
844
- const handleDownloadSeedBackup = useCallback(() => {
845
- if (!mnemonic) return;
846
-
847
- const dateStamp = new Date().toISOString().slice(0, 10);
848
- const numberedWords = mnemonic
849
- .split(' ')
850
- .map((word, index) => `${index + 1}. ${word}`)
851
- .join('\n');
852
- const markdown = `# Aura Agent Seed Phrase Backup\n\n**Date:** ${dateStamp}\n**WARNING:** Keep this file safe and private. Anyone with this phrase can access your agent.\n\n${numberedWords}\n`;
853
- const filename = `aura-seed-backup-${dateStamp}.md`;
854
-
855
- let objectUrl = '';
856
- const downloadLink = document.createElement('a');
857
- try {
858
- const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' });
859
- objectUrl = URL.createObjectURL(blob);
860
- downloadLink.href = objectUrl;
861
- downloadLink.download = filename;
862
- downloadLink.style.display = 'none';
863
- document.body.appendChild(downloadLink);
864
- downloadLink.click();
865
- setSeedPhraseActionStatus('downloaded');
866
- } catch {
867
- setSeedPhraseActionStatus('download-failed');
868
- } finally {
869
- downloadLink.remove();
870
- if (objectUrl) {
871
- URL.revokeObjectURL(objectUrl);
872
- }
873
- }
874
- }, [mnemonic]);
875
-
876
- const persistLocalAgentMode = useCallback(async () => {
877
- const authToken = onboardingToken || token;
878
- if (!authToken) {
879
- throw new Error('Session token unavailable. Unlock again and retry setup.');
880
- }
881
-
882
- const profile = localAgentMode;
883
- const profileVersion = 'v1';
884
- const autoApprove = profile !== 'strict';
885
- const baseUrl = api.getBaseUrl(Api.Wallet);
886
- const headers = {
887
- 'Content-Type': 'application/json',
888
- 'Authorization': `Bearer ${authToken}`,
889
- };
890
-
891
- await fetch(`${baseUrl}/defaults/trust.localProfile`, {
892
- method: 'PATCH',
893
- headers,
894
- body: JSON.stringify({ value: profile }),
895
- });
896
- await fetch(`${baseUrl}/defaults/trust.localProfileVersion`, {
897
- method: 'PATCH',
898
- headers,
899
- body: JSON.stringify({ value: profileVersion }),
900
- });
901
- await fetch(`${baseUrl}/defaults/trust.localAutoApprove`, {
902
- method: 'PATCH',
903
- headers,
904
- body: JSON.stringify({ value: autoApprove }),
905
- });
906
- }, [localAgentMode, onboardingToken, token]);
907
-
908
- const bootstrapDashboardTransition = useCallback(async () => {
909
- setDashboardTransitionTimedOut(false);
910
- let finished = false;
911
- const timeout = window.setTimeout(() => {
912
- if (!finished) setDashboardTransitionTimedOut(true);
913
- }, DASHBOARD_TRANSITION_TIMEOUT_MS);
914
-
915
- try {
916
- await fetchState();
917
- finished = true;
918
- } finally {
919
- window.clearTimeout(timeout);
920
- }
921
- }, [fetchState]);
922
-
923
- const handlePasskeyUnlock = useCallback(async () => {
924
- setPasskeyLoading(true);
925
- setError(null);
926
- try {
927
- const { publicKeyBase64 } = await generateAgentKeypair();
928
-
929
- const options = await api.post<{
930
- challenge: string;
931
- rpId: string;
932
- allowCredentials: Array<{ id: string; transports?: string[] }>;
933
- timeout: number;
934
- userVerification: string;
935
- }>(Api.Wallet, '/auth/passkey/authenticate/options', {});
936
-
937
- const toBuffer = (b: string): ArrayBuffer => {
938
- let s = b.replace(/-/g, '+').replace(/_/g, '/');
939
- while (s.length % 4) s += '=';
940
- const bin = atob(s);
941
- const a = new Uint8Array(bin.length);
942
- for (let i = 0; i < bin.length; i++) a[i] = bin.charCodeAt(i);
943
- return a.buffer;
944
- };
945
- const toBase64url = (b: ArrayBuffer): string => {
946
- const bytes = new Uint8Array(b);
947
- let bin = '';
948
- for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
949
- return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
950
- };
951
-
952
- const publicKey: PublicKeyCredentialRequestOptions = {
953
- challenge: toBuffer(options.challenge),
954
- rpId: options.rpId,
955
- allowCredentials: (options.allowCredentials || []).map((c) => ({
956
- type: 'public-key' as const,
957
- id: toBuffer(c.id),
958
- transports: c.transports as AuthenticatorTransport[] | undefined,
959
- })),
960
- timeout: options.timeout,
961
- userVerification: (options.userVerification || 'required') as UserVerificationRequirement,
962
- };
963
-
964
- const credential = await navigator.credentials.get({ publicKey }) as PublicKeyCredential | null;
965
- if (!credential) {
966
- setPasskeyLoading(false);
967
- return;
968
- }
969
-
970
- const response = credential.response as AuthenticatorAssertionResponse;
971
-
972
- const result = await api.post<{ success: boolean; token?: string; error?: string }>(
973
- Api.Wallet,
974
- '/auth/passkey/authenticate/verify',
975
- {
976
- credential: {
977
- id: toBase64url(credential.rawId),
978
- rawId: toBase64url(credential.rawId),
979
- type: credential.type,
980
- response: {
981
- clientDataJSON: toBase64url(response.clientDataJSON),
982
- authenticatorData: toBase64url(response.authenticatorData),
983
- signature: toBase64url(response.signature),
984
- userHandle: response.userHandle ? toBase64url(response.userHandle) : undefined,
985
- },
986
- },
987
- pubkey: publicKeyBase64,
988
- },
989
- );
990
-
991
- if (result.success && result.token) {
992
- setToken(result.token, { persist: trustDevice ? 'local' : 'session' });
993
- beginTransitionState();
994
- void bootstrapDashboardTransition();
995
- } else {
996
- setError(result.error || 'Biometric authentication failed');
997
- }
998
- } catch (err) {
999
- if (err instanceof Error && err.name === 'NotAllowedError') {
1000
- setPasskeyLoading(false);
1001
- return;
1002
- }
1003
- const msg = err instanceof Error ? err.message : 'Biometric authentication failed';
1004
- if (msg.includes('agent_locked')) {
1005
- // Agent was locked between probe and attempt — just hide the button
1006
- setPasskeyAvailable(false);
1007
- } else {
1008
- setError(msg);
1009
- }
1010
- } finally {
1011
- setPasskeyLoading(false);
1012
- }
1013
- }, [beginTransitionState, trustDevice, setToken, bootstrapDashboardTransition]);
1014
-
1015
- const handleFinalizeOnboarding = useCallback(async () => {
1016
- setLoading(true);
1017
- setError(null);
1018
- try {
1019
- await persistLocalAgentMode();
1020
- setMnemonic(null);
1021
- setSeedAcknowledged(false);
1022
- setSeedRecoveryNotice(null);
1023
- setOnboardingToken(null);
1024
- setSetupOnboardingStep('seed');
1025
- beginTransitionState();
1026
- void bootstrapDashboardTransition();
1027
- } catch (err) {
1028
- setError((err as Error).message || 'Failed to save local agent mode');
1029
- } finally {
1030
- setLoading(false);
1031
- }
1032
- }, [beginTransitionState, bootstrapDashboardTransition, persistLocalAgentMode]);
1033
-
1034
- // CredentialAgent already calls POST /lock and clearToken() before invoking onLock.
1035
- // This handler only needs to transition the page state.
1036
- const handleLock = useCallback(() => {
1037
- if (loaderExitTimerRef.current) {
1038
- clearTimeout(loaderExitTimerRef.current);
1039
- loaderExitTimerRef.current = null;
1040
- }
1041
- setLoaderExiting(false);
1042
- setError(null);
1043
- setPageState('locked');
1044
- }, []);
1045
-
1046
- const handleNuke = useCallback(async () => {
1047
- setNuking(true);
1048
- setNukeError(null);
1049
- try {
1050
- await api.post(Api.Wallet, '/nuke', {});
1051
- setShowSettingsDrawer(false);
1052
- setDangerConfirmOpen(false);
1053
- setPolicySaveError(null);
1054
- setPolicyFormErrors({});
1055
- setPasswordChangeError(null);
1056
- setAgentThemeOpen(true);
1057
- setAgentSettingsOpen(false);
1058
- setSecuritySettingsOpen(false);
1059
- setDangerZoneOpen(false);
1060
- setShowPasswordModal(false);
1061
- setNukeConfirmOpen(false);
1062
- setNuking(false);
1063
- setNukeError(null);
1064
- if (policySettings) setPolicyForm(policySettings);
1065
- window.location.reload();
1066
- } catch (err) {
1067
- setNukeError((err as Error).message || 'Failed to nuke wallet');
1068
- console.error('[UnlockPage] Nuke failed:', err);
1069
- } finally {
1070
- setNuking(false);
1071
- }
1072
- }, [policySettings]);
1073
-
1074
- // --- Backup / Export handlers ---
1075
-
1076
- const fetchBackups = useCallback(async () => {
1077
- if (!token) { setBackups([]); return; }
1078
- setBackupsLoading(true);
1079
- try {
1080
- const data = await api.get<{ success: boolean; backups: Array<{ filename: string; timestamp: string; size: number; date: string }> }>(Api.Wallet, '/backup');
1081
- if (data.success) setBackups(data.backups);
1082
- } catch { setBackups([]); }
1083
- finally { setBackupsLoading(false); }
1084
- }, [token]);
1085
-
1086
- const handleCreateBackup = useCallback(async () => {
1087
- setCreatingBackup(true);
1088
- try {
1089
- const data = await api.post<{ success: boolean; error?: string }>(Api.Wallet, '/backup');
1090
- if (data.success) await fetchBackups();
1091
- } catch (err) { console.error('Backup create failed', err); }
1092
- finally { setCreatingBackup(false); }
1093
- }, [fetchBackups]);
1094
-
1095
- const handleExportDb = useCallback(async () => {
1096
- setExportingDb(true);
1097
- try {
1098
- const baseUrl = api.getBaseUrl(Api.Wallet);
1099
- const res = await fetch(`${baseUrl}/backup/export`, {
1100
- headers: { Authorization: `Bearer ${token || ''}` },
1101
- });
1102
- if (!res.ok) return;
1103
- const blob = await res.blob();
1104
- const disposition = res.headers.get('Content-Disposition') || '';
1105
- const match = disposition.match(/filename="?([^"]+)"?/);
1106
- const filename = match ? match[1] : 'auramaxx-export.db';
1107
- const url = URL.createObjectURL(blob);
1108
- const a = document.createElement('a');
1109
- a.href = url;
1110
- a.download = filename;
1111
- document.body.appendChild(a);
1112
- a.click();
1113
- document.body.removeChild(a);
1114
- URL.revokeObjectURL(url);
1115
- } catch (err) { console.error('Export failed', err); }
1116
- finally { setExportingDb(false); }
1117
- }, [token]);
1118
-
1119
- const handleRestoreBackup = useCallback(async (filename: string) => {
1120
- setRestoringBackup(filename);
1121
- try {
1122
- const data = await api.put<{ success: boolean; error?: string }>(Api.Wallet, '/backup', { filename });
1123
- if (data.success) window.location.reload();
1124
- } catch (err) { console.error('Restore failed', err); }
1125
- finally { setRestoringBackup(null); }
1126
- }, []);
1127
-
1128
- const formatBackupDate = (ts: string) => {
1129
- const y = ts.slice(0, 4), m = ts.slice(4, 6), d = ts.slice(6, 8), h = ts.slice(9, 11), min = ts.slice(11, 13);
1130
- return `${y}-${m}-${d} ${h}:${min}`;
1131
- };
1132
-
1133
- const formatSize = (bytes: number) => {
1134
- if (bytes < 1024) return `${bytes} B`;
1135
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1136
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1137
- };
1138
-
1139
- const closeSettingsDrawer = useCallback(() => {
1140
- if (policyFormDirty) {
1141
- setDiscardConfirmOpen(true);
1142
- return;
1143
- }
1144
- setShowSettingsDrawer(false);
1145
- setDangerConfirmOpen(false);
1146
- setPolicySaveError(null);
1147
- setPolicyFormErrors({});
1148
- setPasswordChangeError(null);
1149
- setAgentThemeOpen(true);
1150
- setAgentSettingsOpen(false);
1151
- setSecuritySettingsOpen(false);
1152
- setDangerZoneOpen(false);
1153
- setBackupSectionOpen(false);
1154
- setRestoreConfirmOpen(null);
1155
- setRestoreAnchorEl(null);
1156
- setShowPasswordModal(false);
1157
- setNukeConfirmOpen(false);
1158
- setNuking(false);
1159
- setNukeError(null);
1160
- if (policySettings) setPolicyForm(policySettings);
1161
- }, [policyFormDirty, policySettings]);
1162
-
1163
- // Full-screen loader for initial state + unlock transition.
1164
- // Keeps UI stable while agent status resolves.
1165
- if (pageState === 'loading' || pageState === 'transition') {
1166
- const statusLabel = pageState === 'transition' ? 'DECRYPTING AGENT' : 'CONNECTING';
1167
- return (
1168
- <div className="min-h-screen bg-[var(--color-background,#f4f4f5)] relative flex items-center justify-center p-4 overflow-hidden">
1169
- <UpdateBanner />
1170
- <div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
1171
- <div className="absolute inset-0 bg-grid-adaptive bg-[size:4rem_4rem] opacity-30" />
1172
- <div className="absolute inset-0 tyvek-texture opacity-40 mix-blend-multiply" />
1173
-
1174
- <div className="absolute bottom-[5%] right-[5%] opacity-5 select-none">
1175
- <h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter text-right">
1176
- AURAMAXX
1177
- </h1>
1178
- </div>
1179
- </div>
1180
-
1181
- <div className="fixed top-6 left-6 z-50 flex items-center gap-3">
1182
- <div className="w-10 h-10">
1183
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1184
- </div>
1185
- </div>
1186
-
1187
- <div className="fixed top-7 right-6 z-50 flex items-center gap-3 font-mono text-[10px] tracking-widest">
1188
- <Link href="/docs" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">DOCS</Link>
1189
- <Link href="/api" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">API</Link>
1190
- <a href="https://github.com/Aura-Industry/auramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">GITHUB</a>
1191
- <a href="https://x.com/npxauramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">X</a>
1192
- <a href="https://x.com/hi_im_nico" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">HELP</a>
1193
- <DocsThemeToggle />
1194
- </div>
1195
-
1196
- <div className={`relative z-10 w-full max-w-[320px] p-6 flex flex-col items-center text-center ${loaderExiting ? 'animate-fade-out-up' : 'animate-fade-in-up'}`}>
1197
- <div className="w-10 h-10 mb-2 opacity-60">
1198
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1199
- </div>
1200
- <div className="w-6 h-6 border-2 border-[var(--color-border,#d4d4d8)] border-t-[var(--color-text,#0a0a0a)] animate-spin" />
1201
- <div className="mt-4 label-specimen text-[var(--color-text-muted,#6b7280)] animate-pulse">
1202
- {statusLabel}
1203
- </div>
1204
- <div className="mt-3 w-32 h-[2px] skeleton-mech" />
1205
- {pageState === 'transition' && dashboardTransitionTimedOut && (
1206
- <div className="mt-6 w-full max-w-[280px] space-y-3 text-center">
1207
- <div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
1208
- Dashboard took too long. You can retry without re-running onboarding.
1209
- </div>
1210
- <button
1211
- onClick={() => { void bootstrapDashboardTransition(); }}
1212
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity clip-specimen-sm"
1213
- >
1214
- RETRY
1215
- </button>
1216
- </div>
1217
- )}
1218
- </div>
1219
- </div>
1220
- );
1221
- }
1222
-
1223
- // Unlocked: render full-screen agent + root settings drawer controls
1224
- if (pageState === 'unlocked') {
1225
- return (
1226
- <div className="relative h-screen agent-surface">
1227
- <UpdateBanner />
1228
- {/* Top-right icon bar: Help, Settings, Notifications, Dark mode */}
1229
- <div className="fixed top-3 right-6 z-50 flex items-center gap-1.5">
1230
- <a
1231
- href="https://x.com/hi_im_nico"
1232
- target="_blank"
1233
- rel="noopener noreferrer"
1234
- className="p-1.5 text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-surface,#ffffff)]/50 transition-colors rounded"
1235
- title="Help"
1236
- aria-label="Help"
1237
- >
1238
- <CircleHelp size={14} />
1239
- </a>
1240
- <button
1241
- onClick={() => setShowSettingsDrawer(true)}
1242
- className="p-1.5 text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-surface,#ffffff)]/50 transition-colors rounded"
1243
- title="Settings"
1244
- aria-label="Settings"
1245
- >
1246
- <Settings size={14} />
1247
- </button>
1248
- <NotificationDrawer
1249
- notifications={pageNotifications}
1250
- onDismiss={pageDismissNotification}
1251
- />
1252
- <DocsThemeToggle />
1253
- </div>
1254
-
1255
- {/* Bottom-right links: Docs, API, GitHub, X, Help */}
1256
- <div
1257
- className="fixed right-6 z-50 flex items-center gap-3 font-mono text-[10px] tracking-widest transition-[bottom]"
1258
- style={{ bottom: utilityLinksBottomOffset }}
1259
- >
1260
- <Link href="/docs" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">DOCS</Link>
1261
- <Link href="/api" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">API</Link>
1262
- <a href="https://github.com/Aura-Industry/auramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">GITHUB</a>
1263
- <a href="https://x.com/npxauramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">X</a>
1264
- <a href="https://x.com/hi_im_nico" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">HELP</a>
1265
- </div>
1266
-
1267
- <CredentialAgent
1268
- onLock={handleLock}
1269
- onSettings={() => setShowSettingsDrawer(true)}
1270
- />
1271
-
1272
- <SettingsDrawer
1273
- isOpen={showSettingsDrawer}
1274
- onClose={closeSettingsDrawer}
1275
- passwordChangeSuccess={passwordChangeSuccess}
1276
- agentThemeOpen={agentThemeOpen}
1277
- onToggleAgentTheme={() => setAgentThemeOpen((open) => !open)}
1278
- colorMode={colorMode}
1279
- uiScale={uiScale}
1280
- onColorModeChange={setColorMode}
1281
- onUiScaleChange={setUiScale}
1282
- agentColorModeOptions={AGENT_COLOR_MODE_OPTIONS}
1283
- agentUiScaleOptions={AGENT_UI_SCALE_OPTIONS}
1284
- agentSettingsOpen={agentSettingsOpen}
1285
- onToggleAgentSettings={() => setAgentSettingsOpen((open) => !open)}
1286
- policyLoadError={policyLoadError}
1287
- policyLoading={policyLoading}
1288
- onRetryLoadPolicy={() => { void loadLocalPolicySettings(); }}
1289
- policyForm={policyForm}
1290
- onPolicyAutoApproveChange={(checked) => setPolicyForm((prev) => ({ ...prev, autoApprove: checked }))}
1291
- localProfileOptions={LOCAL_PROFILE_ITEM_OPTIONS}
1292
- onPolicyProfileChange={(value) => setPolicyForm((prev) => ({ ...prev, profile: value }))}
1293
- localProjectScopeOptions={LOCAL_PROJECT_SCOPE_ITEM_OPTIONS}
1294
- onPolicyProjectScopeModeChange={(value) => setPolicyForm((prev) => ({ ...prev, projectScopeMode: value }))}
1295
- dangerConfirmOpen={dangerConfirmOpen}
1296
- onCancelDangerConfirm={() => {
1297
- setDangerConfirmOpen(false);
1298
- if (policySettings) setPolicyForm(policySettings);
1299
- }}
1300
- onConfirmDangerousSave={() => { void handleSaveLocalPolicy(); }}
1301
- policySaving={policySaving}
1302
- policySaveError={policySaveError}
1303
- policySaveSuccess={policySaveSuccess}
1304
- canSavePolicy={!Boolean(policyLoadError) && !policyLoading && !policySaving && Boolean(policySettings)}
1305
- onSavePolicy={() => { void handleSaveLocalPolicy(); }}
1306
- securitySettingsOpen={securitySettingsOpen}
1307
- onToggleSecuritySettings={() => setSecuritySettingsOpen((open) => !open)}
1308
- passwordChangeError={passwordChangeError}
1309
- onOpenPasswordModal={() => {
1310
- setPasswordChangeError(null);
1311
- setShowPasswordModal(true);
1312
- }}
1313
- backupSectionOpen={backupSectionOpen}
1314
- onToggleBackupSection={() => {
1315
- const next = !backupSectionOpen;
1316
- setBackupSectionOpen(next);
1317
- if (next) void fetchBackups();
1318
- }}
1319
- creatingBackup={creatingBackup}
1320
- onCreateBackup={() => { void handleCreateBackup(); }}
1321
- exportingDb={exportingDb}
1322
- onExportDb={() => { void handleExportDb(); }}
1323
- backupsLoading={backupsLoading}
1324
- backups={backups}
1325
- formatBackupDate={formatBackupDate}
1326
- formatSize={formatSize}
1327
- restoreConfirmOpen={restoreConfirmOpen}
1328
- onOpenRestoreConfirm={(filename, anchorEl) => {
1329
- setRestoreAnchorEl(anchorEl);
1330
- setRestoreConfirmOpen(filename);
1331
- }}
1332
- onCloseRestoreConfirm={() => {
1333
- setRestoreConfirmOpen(null);
1334
- setRestoreAnchorEl(null);
1335
- }}
1336
- onConfirmRestore={(filename) => {
1337
- setRestoreConfirmOpen(null);
1338
- setRestoreAnchorEl(null);
1339
- void handleRestoreBackup(filename);
1340
- }}
1341
- restoringBackup={restoringBackup}
1342
- dangerZoneOpen={dangerZoneOpen}
1343
- onToggleDangerZone={() => setDangerZoneOpen((open) => !open)}
1344
- nukeError={nukeError}
1345
- onOpenNukeConfirm={() => {
1346
- setNukeError(null);
1347
- setNukeConfirmOpen(true);
1348
- }}
1349
- nuking={nuking}
1350
- />
1351
-
1352
- <ConfirmationModal
1353
- isOpen={nukeConfirmOpen}
1354
- onClose={() => setNukeConfirmOpen(false)}
1355
- onConfirm={() => { void handleNuke(); }}
1356
- title="Nuke Agent"
1357
- message="Permanently delete your agent, wallets, credentials, and local configuration. This cannot be undone."
1358
- confirmText="NUKE"
1359
- cancelText="CANCEL"
1360
- variant="danger"
1361
- loading={nuking}
1362
- />
1363
-
1364
- <ConfirmationModal
1365
- isOpen={discardConfirmOpen}
1366
- onClose={() => setDiscardConfirmOpen(false)}
1367
- onConfirm={() => {
1368
- setDiscardConfirmOpen(false);
1369
- setShowSettingsDrawer(false);
1370
- setDangerConfirmOpen(false);
1371
- setPolicySaveError(null);
1372
- setPolicyFormErrors({});
1373
- setPasswordChangeError(null);
1374
- setAgentThemeOpen(true);
1375
- setAgentSettingsOpen(false);
1376
- setSecuritySettingsOpen(false);
1377
- setDangerZoneOpen(false);
1378
- setBackupSectionOpen(false);
1379
- setRestoreConfirmOpen(null);
1380
- setRestoreAnchorEl(null);
1381
- setShowPasswordModal(false);
1382
- setNukeConfirmOpen(false);
1383
- setNuking(false);
1384
- setNukeError(null);
1385
- if (policySettings) setPolicyForm(policySettings);
1386
- }}
1387
- variant="warning"
1388
- title="Discard Changes"
1389
- message="You have unsaved local policy changes. Discard them?"
1390
- confirmText="DISCARD"
1391
- cancelText="KEEP EDITING"
1392
- />
1393
-
1394
- <Modal
1395
- isOpen={showPasswordModal}
1396
- onClose={closePasswordModal}
1397
- title="Change Primary Password"
1398
- subtitle="Security"
1399
- size="sm"
1400
- >
1401
- <form onSubmit={handleChangePrimaryPassword} className="space-y-3">
1402
- <TextInput
1403
- type="password"
1404
- label="CURRENT PASSWORD"
1405
- aria-label="CURRENT PASSWORD"
1406
- value={currentPasswordValue}
1407
- onChange={(e) => setCurrentPasswordValue(e.target.value)}
1408
- autoFocus
1409
- compact
1410
- />
1411
- <TextInput
1412
- type="password"
1413
- label="NEW PASSWORD"
1414
- aria-label="NEW PASSWORD"
1415
- value={newPasswordValue}
1416
- onChange={(e) => setNewPasswordValue(e.target.value)}
1417
- compact
1418
- />
1419
- <TextInput
1420
- type="password"
1421
- label="CONFIRM NEW PASSWORD"
1422
- aria-label="CONFIRM NEW PASSWORD"
1423
- value={confirmPasswordValue}
1424
- onChange={(e) => setConfirmPasswordValue(e.target.value)}
1425
- compact
1426
- />
1427
- {passwordChangeError && <div className="text-[10px] text-[var(--color-danger)]">{passwordChangeError}</div>}
1428
- <div className="flex gap-2 pt-2">
1429
- <Button
1430
- type="button"
1431
- onClick={closePasswordModal}
1432
- disabled={passwordChanging}
1433
- variant="secondary"
1434
- size="lg"
1435
- className="flex-1"
1436
- >
1437
- CANCEL
1438
- </Button>
1439
- <Button
1440
- type="submit"
1441
- disabled={passwordChanging || !currentPasswordValue || !newPasswordValue || !confirmPasswordValue}
1442
- variant="primary"
1443
- size="lg"
1444
- className="flex-1"
1445
- >
1446
- {passwordChanging ? 'UPDATING...' : 'UPDATE PASSWORD'}
1447
- </Button>
1448
- </div>
1449
- </form>
1450
- </Modal>
1451
-
1452
- <PasskeyEnrollmentPrompt isUnlocked={pageState === 'unlocked'} />
1453
- </div>
1454
- );
1455
- }
1456
-
1457
- return (
1458
- <div className="min-h-screen bg-[var(--color-background,#f4f4f5)] relative flex items-center justify-center p-4">
1459
- <UpdateBanner />
1460
- {/* Background — sterile field (same as docs/api) */}
1461
- <div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
1462
- <div className="absolute inset-0 bg-grid-adaptive bg-[size:4rem_4rem] opacity-30" />
1463
- <div className="absolute inset-0 tyvek-texture opacity-40 mix-blend-multiply" />
1464
-
1465
- {/* Giant background typography */}
1466
- <div className="absolute bottom-[5%] right-[5%] opacity-5 select-none" data-testid="home-background-branding">
1467
- <h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter text-right">
1468
- AURAMAXX
1469
- </h1>
1470
- </div>
1471
-
1472
- {/* Corner finder patterns */}
1473
- <div className="absolute top-10 left-10 w-32 h-32 border-l-4 border-t-4 border-[var(--color-text,#0a0a0a)] opacity-10">
1474
- <div className="absolute top-2 left-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
1475
- </div>
1476
- <div className="absolute bottom-10 right-10 w-32 h-32 border-r-4 border-b-4 border-[var(--color-text,#0a0a0a)] opacity-10 flex items-end justify-end">
1477
- <div className="absolute bottom-2 right-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
1478
- </div>
1479
- </div>
1480
-
1481
- {/* Logo header */}
1482
- <div className="fixed top-6 left-6 z-50 flex items-center gap-3">
1483
- <div className="w-10 h-10">
1484
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1485
- </div>
1486
- </div>
1487
-
1488
- {/* Nav */}
1489
- <div className="fixed top-7 right-6 z-50 flex items-center gap-3 font-mono text-[10px] tracking-widest">
1490
- <Link href="/docs" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">DOCS</Link>
1491
- <Link href="/api" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">API</Link>
1492
- <a href="https://github.com/Aura-Industry/auramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">GITHUB</a>
1493
- <a href="https://x.com/npxauramaxx" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">X</a>
1494
- <a href="https://x.com/hi_im_nico" target="_blank" rel="noopener noreferrer" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">HELP</a>
1495
- <DocsThemeToggle />
1496
- </div>
1497
-
1498
- {/* Unlock card */}
1499
- <div className="relative z-10 w-full max-w-[380px]">
1500
- {/* Vertical specimen label */}
1501
- <div className="absolute -left-8 top-1/2 -translate-y-1/2 text-vertical label-specimen-sm text-[var(--color-text-faint,#9ca3af)] select-none hidden sm:block">
1502
- AGENT&nbsp;ACCESS
1503
- </div>
1504
- <div className="bg-[var(--color-surface,#f4f4f2)] clip-specimen border-mech shadow-mech overflow-hidden font-mono corner-marks">
1505
- {/* Card header bar */}
1506
- <div className="px-5 py-3 border-b border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface-alt,#fafafa)] flex items-center justify-between">
1507
- <span className="font-sans font-bold text-sm text-[var(--color-text,#0a0a0a)] uppercase tracking-tight">
1508
- {pageState === 'setup' ? 'Initialize' : 'Unlock'}
1509
- </span>
1510
- <span className="text-[9px] text-[var(--color-text-faint,#9ca3af)] font-bold tracking-widest">
1511
- {pageState === 'setup' ? 'NO_AGENT' : 'LOCKED'}
1512
- </span>
1513
- </div>
1514
-
1515
- <div className="p-6">
1516
- {pageState === 'setup' && mnemonic && setupOnboardingStep === 'seed' && (
1517
- <div className="flex flex-col items-center">
1518
- <div className="w-16 h-16 mb-4">
1519
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1520
- </div>
1521
- <div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center mb-4">
1522
- SAVE YOUR RECOVERY PHRASE
1523
- </div>
1524
- <div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20 mb-3">
1525
- Write this down and store it securely. You will stay on this screen until you explicitly confirm.
1526
- </div>
1527
- {seedRecoveryNotice && (
1528
- <div className="text-[9px] text-[var(--color-info,#0047ff)] bg-[var(--color-info,#0047ff)]/10 px-3 py-2 border border-[var(--color-info,#0047ff)]/20 mb-3">
1529
- {seedRecoveryNotice}
1530
- </div>
1531
- )}
1532
- <div className="grid grid-cols-3 gap-2 w-full mb-4">
1533
- {mnemonic.split(' ').map((word, i) => (
1534
- <div key={i} className="text-[10px] font-mono text-[var(--color-text,#0a0a0a)] bg-[var(--color-background,#f4f4f5)] px-2 py-1 border border-[var(--color-border,#d4d4d8)]">
1535
- <span className="text-[var(--color-text-faint,#9ca3af)] mr-1">{i + 1}.</span>{word}
1536
- </div>
1537
- ))}
1538
- </div>
1539
- <div className="grid grid-cols-2 gap-2 w-full mb-3">
1540
- <button
1541
- type="button"
1542
- onClick={() => { void handleCopySeedPhrase(); }}
1543
- className="h-9 px-2 border border-[var(--color-border,#d4d4d8)] font-mono text-[9px] tracking-widest text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-surface-alt,#fafafa)] transition-colors"
1544
- >
1545
- COPY SEED PHRASE
1546
- </button>
1547
- <button
1548
- type="button"
1549
- onClick={handleDownloadSeedBackup}
1550
- className="h-9 px-2 border border-[var(--color-border,#d4d4d8)] font-mono text-[9px] tracking-widest text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-surface-alt,#fafafa)] transition-colors"
1551
- >
1552
- DOWNLOAD BACKUP (.MD)
1553
- </button>
1554
- </div>
1555
- {seedPhraseActionStatus && (
1556
- <div
1557
- className="w-full mb-3 text-[9px] text-[var(--color-text-muted,#6b7280)]"
1558
- aria-live="polite"
1559
- data-testid="seed-phrase-action-status"
1560
- >
1561
- {seedPhraseActionStatus}
1562
- </div>
1563
- )}
1564
- <label className="flex items-start gap-2 w-full mb-3 cursor-pointer">
1565
- <input
1566
- type="checkbox"
1567
- checked={seedAcknowledged}
1568
- onChange={(e) => setSeedAcknowledged(e.target.checked)}
1569
- className="mt-0.5"
1570
- />
1571
- <span className="text-[9px] text-[var(--color-text-muted,#6b7280)]">
1572
- I have written and verified this recovery phrase in a secure location.
1573
- </span>
1574
- </label>
1575
- <button
1576
- onClick={() => {
1577
- if (!seedAcknowledged) return;
1578
- setSetupOnboardingStep('trust');
1579
- }}
1580
- disabled={!seedAcknowledged}
1581
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed"
1582
- >
1583
- CONTINUE TO AGENT MODE
1584
- </button>
1585
- <div className="w-full mt-3 text-[8px] text-[var(--color-text-faint,#9ca3af)] text-center">
1586
- If you leave before confirming, this phrase is recoverable only temporarily in this tab session. If recovery expires, restart onboarding to regenerate.
1587
- </div>
1588
- </div>
1589
- )}
1590
-
1591
- {pageState === 'setup' && mnemonic && setupOnboardingStep === 'trust' && (
1592
- <>
1593
- <div className="flex flex-col items-center mb-6">
1594
- <div className="w-16 h-16 mb-4">
1595
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1596
- </div>
1597
- <div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
1598
- LOCAL AGENT MODE
1599
- </div>
1600
- </div>
1601
-
1602
- <div className="space-y-4">
1603
- <div className="text-[9px] text-[var(--color-text-muted,#6b7280)] bg-[var(--color-background,#f4f4f5)] px-3 py-2 border border-[var(--color-border,#d4d4d8)]">
1604
- How much do you trust your agent?
1605
- </div>
1606
-
1607
- <fieldset className="border border-[var(--color-border,#d4d4d8)] p-2.5 bg-[var(--color-background,#f4f4f5)]">
1608
- <legend className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase px-1">
1609
- Pick a profile
1610
- </legend>
1611
- <ItemPicker
1612
- options={[...ONBOARDING_LOCAL_AGENT_MODE_OPTIONS]}
1613
- value={localAgentMode}
1614
- onChange={(value) => setLocalAgentMode(value as LocalAgentMode)}
1615
- ariaLabel="Onboarding local agent mode"
1616
- />
1617
- </fieldset>
1618
-
1619
- {error && (
1620
- <div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
1621
- {error}
1622
- </div>
1623
- )}
1624
-
1625
- <button
1626
- onClick={() => { void handleFinalizeOnboarding(); }}
1627
- disabled={loading}
1628
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
1629
- >
1630
- {loading ? (
1631
- <>
1632
- <div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
1633
- SAVING...
1634
- </>
1635
- ) : (
1636
- 'SAVE MODE AND CONTINUE'
1637
- )}
1638
- </button>
1639
- </div>
1640
- </>
1641
- )}
1642
-
1643
- {pageState === 'setup' && !mnemonic && (
1644
- <>
1645
- {/* Logo centered */}
1646
- <div className="flex flex-col items-center mb-6">
1647
- <div className="w-16 h-16 mb-4">
1648
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1649
- </div>
1650
- <div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
1651
- CREATE YOUR ENCRYPTED AGENT
1652
- </div>
1653
- <div className="mt-2 text-[9px] text-[var(--color-text-faint,#9ca3af)] text-center leading-relaxed" data-testid="start-banner-quote">
1654
- {startBannerQuote}
1655
- </div>
1656
- </div>
1657
-
1658
- <form onSubmit={handleSetup} className="space-y-4">
1659
- <div>
1660
- <label className="block text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-1.5 uppercase">
1661
- Encryption Password
1662
- </label>
1663
- <input
1664
- type="password"
1665
- value={password}
1666
- onChange={(e) => { setPassword(e.target.value); setError(null); }}
1667
- placeholder="Minimum 8 characters"
1668
- className="w-full px-3 py-2.5 border border-[var(--color-border,#d4d4d8)] font-mono text-sm text-[var(--color-text,#0a0a0a)] focus:outline-none focus:border-[var(--color-text,#0a0a0a)] bg-[var(--color-surface,#ffffff)] placeholder-[var(--color-text-faint,#9ca3af)] transition-colors"
1669
- autoFocus
1670
- />
1671
- </div>
1672
- <label className="flex items-center justify-between gap-3 text-[8px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
1673
- <span className="inline-flex items-center gap-2">
1674
- <input
1675
- type="checkbox"
1676
- checked={trustDevice}
1677
- onChange={(e) => setTrustDevice(e.target.checked)}
1678
- className="h-3.5 w-3.5 border border-[var(--color-border,#d4d4d8)] accent-[var(--color-text,#0a0a0a)]"
1679
- />
1680
- Trusted device
1681
- </span>
1682
- <span>{trustDevice ? 'PERSISTENT' : 'TAB ONLY'}</span>
1683
- </label>
1684
-
1685
- {error && (
1686
- <div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
1687
- {error}
1688
- </div>
1689
- )}
1690
-
1691
- <button
1692
- type="submit"
1693
- disabled={loading || password.length < 8}
1694
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
1695
- >
1696
- {loading ? (
1697
- <>
1698
- <div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
1699
- INITIALIZING...
1700
- </>
1701
- ) : (
1702
- 'INITIALIZE AGENT'
1703
- )}
1704
- </button>
1705
- </form>
1706
-
1707
- <div className="mt-4 pt-4 border-t border-[var(--color-border,#d4d4d8)]">
1708
- <div className="flex items-start gap-2">
1709
- <div className="w-1 h-1 bg-[var(--color-text-muted,#6b7280)] mt-1.5 flex-shrink-0" />
1710
- <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] leading-relaxed">
1711
- This password encrypts your seed phrase locally. It never leaves your machine.
1712
- </span>
1713
- </div>
1714
- </div>
1715
- </>
1716
- )}
1717
-
1718
- {pageState === 'locked' && (
1719
- <>
1720
- {/* Logo centered */}
1721
- <div className="flex flex-col items-center mb-6">
1722
- <div className="w-16 h-16 mb-4">
1723
- <img src="/logo.webp" alt="AuraMaxx" className="w-full h-full object-contain" />
1724
- </div>
1725
- <div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
1726
- {showSeedRecovery ? 'RECOVER WITH SEED PHRASE' : passkeyAvailable ? 'UNLOCK AGENT' : 'ENTER PASSWORD TO UNLOCK'}
1727
- </div>
1728
- <div className="mt-2 text-[9px] text-[var(--color-text,#0a0a0a)] text-center leading-relaxed" data-testid="start-banner-quote">
1729
- {startBannerQuote}
1730
- </div>
1731
- </div>
1732
-
1733
- {!showSeedRecovery && (
1734
- <>
1735
- {passkeyAvailable && (
1736
- <div className="mb-4">
1737
- <button
1738
- type="button"
1739
- onClick={() => { void handlePasskeyUnlock(); }}
1740
- disabled={passkeyLoading}
1741
- className="w-full py-3 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-50 flex items-center justify-center gap-2"
1742
- >
1743
- {passkeyLoading ? (
1744
- <>
1745
- <div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
1746
- AUTHENTICATING...
1747
- </>
1748
- ) : (
1749
- <>
1750
- <Fingerprint size={14} />
1751
- UNLOCK WITH PASSKEY
1752
- </>
1753
- )}
1754
- </button>
1755
- <div className="mt-3 flex items-center gap-3">
1756
- <div className="flex-1 h-px bg-[var(--color-border,#d4d4d8)]" />
1757
- <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest">OR USE PASSWORD</span>
1758
- <div className="flex-1 h-px bg-[var(--color-border,#d4d4d8)]" />
1759
- </div>
1760
- </div>
1761
- )}
1762
- <form onSubmit={handleUnlock} className="space-y-4">
1763
- <div>
1764
- <label className="block text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-1.5 uppercase">
1765
- Password
1766
- </label>
1767
- <input
1768
- type="password"
1769
- value={password}
1770
- onChange={(e) => { setPassword(e.target.value); setError(null); }}
1771
- placeholder="Enter agent password"
1772
- className="w-full px-3 py-2.5 border border-[var(--color-border,#d4d4d8)] font-mono text-sm text-[var(--color-text,#0a0a0a)] focus:outline-none focus:border-[var(--color-text,#0a0a0a)] bg-[var(--color-surface,#ffffff)] placeholder-[var(--color-text-faint,#9ca3af)] transition-colors"
1773
- autoFocus
1774
- />
1775
- </div>
1776
- <label className="flex items-center justify-between gap-3 text-[8px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">
1777
- <span className="inline-flex items-center gap-2">
1778
- <input
1779
- type="checkbox"
1780
- checked={trustDevice}
1781
- onChange={(e) => setTrustDevice(e.target.checked)}
1782
- className="h-3.5 w-3.5 border border-[var(--color-border,#d4d4d8)] accent-[var(--color-text,#0a0a0a)]"
1783
- />
1784
- Trusted device
1785
- </span>
1786
- <span>{trustDevice ? 'PERSISTENT' : 'TAB ONLY'}</span>
1787
- </label>
1788
-
1789
- {error && (
1790
- <div
1791
- data-testid="unlock-error-banner"
1792
- className="text-[9px] text-[var(--color-danger,#ef4444)] px-3 py-2 border"
1793
- style={{
1794
- borderColor: 'color-mix(in srgb, var(--color-danger,#ef4444) 35%, transparent)',
1795
- background: 'color-mix(in srgb, var(--color-danger,#ef4444) 12%, transparent)',
1796
- }}
1797
- >
1798
- {error}
1799
- </div>
1800
- )}
1801
-
1802
- <button
1803
- type="submit"
1804
- disabled={loading || !password}
1805
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
1806
- >
1807
- {loading ? (
1808
- <>
1809
- <div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
1810
- UNLOCKING...
1811
- </>
1812
- ) : (
1813
- 'UNLOCK'
1814
- )}
1815
- </button>
1816
- </form>
1817
-
1818
- <div className="mt-4 border-t border-[var(--color-border,#d4d4d8)] pt-3 text-center">
1819
- <button
1820
- type="button"
1821
- onClick={() => {
1822
- setShowSeedRecovery(true);
1823
- setRecoveryError(null);
1824
- }}
1825
- className="text-[10px] underline underline-offset-2 text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
1826
- >
1827
- Forgot password?
1828
- </button>
1829
- </div>
1830
- </>
1831
- )}
1832
-
1833
- {showSeedRecovery && (
1834
- <div className="mt-4 border-t border-[var(--color-border,#d4d4d8)] pt-3 text-center">
1835
- <button
1836
- type="button"
1837
- onClick={() => {
1838
- setShowSeedRecovery(false);
1839
- setRecoveryError(null);
1840
- }}
1841
- className="text-[10px] underline underline-offset-2 text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
1842
- >
1843
- Back to unlock
1844
- </button>
1845
- </div>
1846
- )}
1847
-
1848
- {showSeedRecovery && (
1849
- <form onSubmit={handleRecoverAccess} className="mt-4 space-y-3">
1850
- <div className="flex items-center justify-between">
1851
- <div className="text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)]">Seed Recovery</div>
1852
- <div className="flex items-center gap-1">
1853
- <button
1854
- type="button"
1855
- onClick={() => { setRecoveryWordCount(12); setRecoveryWords(Array(12).fill('')); }}
1856
- className={`px-2 py-1 text-[8px] border ${recoveryWordCount === 12 ? 'bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#fff)] border-[var(--color-text,#0a0a0a)]' : 'border-[var(--color-border,#d4d4d8)] text-[var(--color-text-muted,#6b7280)]'}`}
1857
- >12</button>
1858
- <button
1859
- type="button"
1860
- onClick={() => { setRecoveryWordCount(24); setRecoveryWords(Array(24).fill('')); }}
1861
- className={`px-2 py-1 text-[8px] border ${recoveryWordCount === 24 ? 'bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#fff)] border-[var(--color-text,#0a0a0a)]' : 'border-[var(--color-border,#d4d4d8)] text-[var(--color-text-muted,#6b7280)]'}`}
1862
- >24</button>
1863
- </div>
1864
- </div>
1865
-
1866
- <div className="grid grid-cols-3 gap-1.5">
1867
- {recoveryWords.map((word, index) => {
1868
- const invalid = invalidRecoveryIndexes.has(index);
1869
- return (
1870
- <TextInput
1871
- key={`recovery-word-${index}`}
1872
- compact
1873
- error={invalid}
1874
- aria-label={`Recovery word ${index + 1}`}
1875
- value={word}
1876
- onChange={(e) => handleRecoveryWordChange(index, e.target.value)}
1877
- onPaste={(e) => {
1878
- const didSplit = handleRecoveryPaste(index, e.clipboardData.getData('text'));
1879
- if (didSplit) e.preventDefault();
1880
- }}
1881
- placeholder={`${index + 1}`}
1882
- className="min-w-0"
1883
- />
1884
- );
1885
- })}
1886
- </div>
1887
-
1888
- <div className="text-[9px] text-[var(--color-text-faint,#9ca3af)]">
1889
- {recoveryWordsFilled}/{recoveryWordCount} words · {invalidRecoveryIndexes.size > 0 ? `${invalidRecoveryIndexes.size} invalid word(s)` : (isRecoveryPhraseStructurallyValid ? 'BIP-39 phrase valid' : 'Waiting for valid BIP-39 phrase')}
1890
- </div>
1891
-
1892
- <TextInput
1893
- type="password"
1894
- compact
1895
- value={recoveryNewPassword}
1896
- onChange={(e) => { setRecoveryNewPassword(e.target.value); setRecoveryError(null); }}
1897
- placeholder="New password"
1898
- aria-label="New password"
1899
- />
1900
- {recoveryError && (
1901
- <div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
1902
- {recoveryError}
1903
- </div>
1904
- )}
1905
-
1906
- <button
1907
- type="submit"
1908
- disabled={recoveryLoading}
1909
- className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30"
1910
- >
1911
- {recoveryLoading ? 'RECOVERING...' : 'RECOVER & UNLOCK'}
1912
- </button>
1913
- </form>
1914
- )}
1915
- </>
1916
- )}
1917
- </div>
1918
-
1919
- {/* Barcode + stripe */}
1920
- <div className="flex items-center gap-3 px-5 py-2 border-t border-[var(--color-border,#d4d4d8)]">
1921
- <div className="h-4 flex-1 bg-[repeating-linear-gradient(90deg,var(--color-text,#000),var(--color-text,#000)_1px,transparent_1px,transparent_3px)] opacity-30" />
1922
- <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-wider">AURAMAXX</span>
1923
- </div>
1924
- <div className="h-2 w-full" style={{
1925
- backgroundImage: 'repeating-linear-gradient(45deg, var(--color-text, #000), var(--color-text, #000) 5px, transparent 5px, transparent 10px)',
1926
- opacity: 0.1,
1927
- }} />
1928
- </div>
1929
-
1930
- {/* Specimen label below card */}
1931
- <div className="mt-4 text-center">
1932
- <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-[0.2em] font-mono">
1933
- SECURE LOCAL WALLETS FOR AI AGENTS
1934
- </span>
1935
- </div>
1936
- </div>
1937
- </div>
1938
- );
27
+ export default function Page() {
28
+ return <UnlockPageClient />;
1939
29
  }