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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +240 -223
- package/.next/app-path-routes-manifest.json +8 -7
- package/.next/build-manifest.json +14 -14
- package/.next/prerender-manifest.json +53 -29
- package/.next/react-loadable-manifest.json +41 -41
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/api/[...doc]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent-requests/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/manifests/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/static/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/docs/plain/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/events/route.js +1 -19
- package/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/[channel]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/validate/[channel]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/restart/route.js +1 -1
- package/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route.js +63 -1
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/apps/[wid]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/export/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/import/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/route_client-reference-manifest.js +1 -1
- package/.next/server/app/app-legacy-do-not-use/page.js +1 -1
- package/.next/server/app/app-legacy-do-not-use/page.js.nft.json +1 -1
- package/.next/server/app/app-legacy-do-not-use/page_client-reference-manifest.js +1 -1
- package/.next/server/app/app-legacy-do-not-use.html +1 -1
- package/.next/server/app/app-legacy-do-not-use.rsc +14 -14
- package/.next/server/app/approve/[actionId]/page.js +1 -1
- package/.next/server/app/approve/[actionId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/docs/[...doc]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/health/page_client-reference-manifest.js +1 -1
- package/.next/server/app/health.html +1 -1
- package/.next/server/app/health.rsc +13 -13
- package/.next/server/app/hello/page_client-reference-manifest.js +1 -1
- package/.next/server/app/hello.html +1 -1
- package/.next/server/app/hello.rsc +14 -14
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +18 -21
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
- package/.next/server/app/privacy.html +1 -1
- package/.next/server/app/privacy.rsc +13 -13
- package/.next/server/app/share/[token]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/terms/page_client-reference-manifest.js +1 -1
- package/.next/server/app/terms.html +1 -1
- package/.next/server/app/terms.rsc +13 -13
- package/.next/server/app/yo/page.js +2 -0
- package/.next/server/app/yo/page.js.nft.json +1 -0
- package/.next/server/app/yo/page_client-reference-manifest.js +1 -0
- package/.next/server/app/yo.html +1 -0
- package/.next/server/app/yo.meta +7 -0
- package/.next/server/app/yo.rsc +23 -0
- package/.next/server/app-paths-manifest.json +8 -7
- package/.next/server/chunks/2145.js +1 -1
- package/.next/server/chunks/2460.js +1 -1
- package/.next/server/chunks/5246.js +1 -1
- package/.next/server/chunks/5678.js +1 -1
- package/.next/server/chunks/5784.js +1 -1
- package/.next/server/chunks/6086.js +2 -20
- package/.next/server/chunks/{5553.js → 6415.js} +2 -2
- package/.next/server/chunks/7935.js +2 -2
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/WshFGr6RxGYP6AbWuT9OG/_buildManifest.js +1 -0
- package/.next/static/chunks/1168.aaac1edbb597fe5a.js +1 -0
- package/.next/static/chunks/1255-7999eac54f80a49f.js +1 -0
- package/.next/static/chunks/142-fa9752f53a551f63.js +1 -0
- package/.next/static/chunks/2505.d54ccadc42f4e3d1.js +1 -0
- package/.next/static/chunks/2619-04bc32f026a0d946.js +1 -0
- package/.next/static/chunks/2927.7e00cc878d9a3f52.js +1 -0
- package/.next/static/chunks/3573-1b41d7b8a000d015.js +1 -0
- package/.next/static/chunks/3609.ded5a5306e18af9c.js +1 -0
- package/.next/static/chunks/3667-1db7bd03948e60df.js +1 -0
- package/.next/static/chunks/3826.a73d4a88d8c09030.js +1 -0
- package/.next/static/chunks/{3a91511d-648a2ba3dad7df0c.js → 3a91511d-ba215c0b5dc21ba9.js} +1 -1
- package/.next/static/chunks/4256.48407d9abad5ea33.js +1 -0
- package/.next/static/chunks/4685-7f53bbfc4a9845eb.js +1 -0
- package/.next/static/chunks/4901-ba6a32818662e70a.js +1 -0
- package/.next/static/chunks/4919-7e7cdd5efc9f2110.js +1 -0
- package/.next/static/chunks/4bd1b696-100b9d70ed4e49c1.js +1 -0
- package/.next/static/chunks/5336-233ec7ab3807267c.js +1 -0
- package/.next/static/chunks/5442-b5bb869e832e8967.js +1 -0
- package/.next/static/chunks/6233-97a810aa272af547.js +1 -0
- package/.next/static/chunks/626.a5109d16f9eca1f6.js +1 -0
- package/.next/static/chunks/6872-faea0f088ab2d450.js +1 -0
- package/.next/static/chunks/7338-3ce17a93614f1d77.js +59 -0
- package/.next/static/chunks/7394-4bdb1feefad1a74a.js +1 -0
- package/.next/static/chunks/7616-1129bcb3eee8d315.js +1 -0
- package/.next/static/chunks/786-26deffb41572cbb3.js +1 -0
- package/.next/static/chunks/8273-8e92d34180669ca9.js +1 -0
- package/.next/static/chunks/8357.5dee1e0c4e5bb091.js +1 -0
- package/.next/static/chunks/9062-2bc2d089f9c9c6ba.js +1 -0
- package/.next/static/chunks/9380.f198afbf0c6b5369.js +1 -0
- package/.next/static/chunks/app/_not-found/page-5a8c6a29f762fa58.js +1 -0
- package/.next/static/chunks/app/api/[...doc]/page-f0852f35f0fd1d44.js +1 -0
- package/.next/static/chunks/app/api/agent-requests/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/apps/install/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/apps/manifests/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/apps/static/[...path]/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/docs/plain/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/events/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/import-from-openclaw/[channel]/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/import-from-openclaw/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/import-from-openclaw/validate/[channel]/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/page-cc59bebcc0d2c01d.js +1 -0
- package/.next/static/chunks/app/api/restart/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/update/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/version/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/[id]/apps/[wid]/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/[id]/apps/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/[id]/export/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/[id]/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/config/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/import/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/api/workspace/route-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/app-legacy-do-not-use/page-e5dc864e92d90ca7.js +1 -0
- package/.next/static/chunks/app/approve/[actionId]/page-2acca1f490424f21.js +1 -0
- package/.next/static/chunks/app/docs/[...doc]/page-8e2a2d036caab242.js +1 -0
- package/.next/static/chunks/app/docs/page-acf872a03ff79893.js +1 -0
- package/.next/static/chunks/app/error-66f983b7769dabfa.js +1 -0
- package/.next/static/chunks/app/health/page-c9185854ed9c86d0.js +1 -0
- package/.next/static/chunks/app/hello/page-74c9f4deaa4b03dd.js +1 -0
- package/.next/static/chunks/app/layout-af8d9969c7aeb758.js +1 -0
- package/.next/static/chunks/app/page-16dfcd1c7cc88bcc.js +1 -0
- package/.next/static/chunks/app/privacy/page-8e2d17079355c2cc.js +1 -0
- package/.next/static/chunks/app/share/[token]/page-5dd9b0418eee411f.js +1 -0
- package/.next/static/chunks/app/terms/page-8e2d17079355c2cc.js +1 -0
- package/.next/static/chunks/app/yo/layout-cf84f975aad4c719.js +1 -0
- package/.next/static/chunks/app/yo/page-719dc5f213fdfb30.js +1 -0
- package/.next/static/chunks/framework-a32a2a465584c0bc.js +1 -0
- package/.next/static/chunks/main-0f0f9142f74e7215.js +1 -0
- package/.next/static/chunks/main-app-24f0c92ba10af457.js +1 -0
- package/.next/static/chunks/pages/_app-4b3fb5e477a0267f.js +1 -0
- package/.next/static/chunks/pages/_error-c970d8b55ace1b48.js +1 -0
- package/.next/static/chunks/{webpack-768de8b7d6a7a27a.js → webpack-79ad58260e9b10b4.js} +1 -1
- package/.next/static/css/83cd401584ab787f.css +3 -0
- package/.next/trace +28 -28
- package/.next/types/app/yo/layout.ts +84 -0
- package/.next/types/app/yo/page.ts +84 -0
- package/.next/types/routes.d.ts +4 -2
- package/.next/types/validator.ts +18 -0
- package/bin/auramaxx.js +11 -26
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/AUTH.md +6 -3
- package/docs/CLI.md +2 -0
- package/docs/MCP.md +2 -0
- package/docs/TROUBLESHOOTING.md +24 -0
- package/docs/credentials.md +2 -0
- package/package.json +2 -1
- package/prisma/migrations/20260227214000_update_agent_action_ttl_defaults/migration.sql +19 -0
- package/public/0a167e5e-4f52-4715-ae23-bf63d259a6b1.png +0 -0
- package/public/141ec92c-6780-4b23-838f-9a7bf1e91bb8.png +0 -0
- package/public/3afc4935-92cb-42af-9624-0b1341c12a5e.png +0 -0
- package/public/43947df5-dbcf-4e49-ab8b-41b9162c0410.png +0 -0
- package/public/5aeae9ce-0d38-49ea-8fd1-167892a04a85.png +0 -0
- package/public/660e4ea3-a3a6-4be4-a8ca-2cb74c51dfb5.png +0 -0
- package/public/733f02d7-6b58-4ba6-a5c8-d062cd205e1d.png +0 -0
- package/public/a32d65cb-95b0-4977-be6b-cf69f515afbe.png +0 -0
- package/public/agent1.png +0 -0
- package/public/agent10.png +0 -0
- package/public/agent2.png +0 -0
- package/public/agent3.png +0 -0
- package/public/agent4.png +0 -0
- package/public/agent5.png +0 -0
- package/public/agent6.png +0 -0
- package/public/agent7.png +0 -0
- package/public/agent8.png +0 -0
- package/public/agent9.png +0 -0
- package/public/c4938305-b811-4ccc-91db-94d309734827.png +0 -0
- package/public/f2ca6825-a4f3-4107-815c-51ee740dfc09.png +0 -0
- package/public/llm.txt +2 -0
- package/public/llms.txt +39 -0
- package/public/ss-dark1.png +0 -0
- package/public/ss-dark1.webp +0 -0
- package/public/ss-dark2.png +0 -0
- package/public/ss-dark2.webp +0 -0
- package/public/ss-dark3.png +0 -0
- package/public/ss-dark3.webp +0 -0
- package/public/ss-light1.png +0 -0
- package/public/ss-light1.webp +0 -0
- package/public/ss-light2.png +0 -0
- package/public/ss-light2.webp +0 -0
- package/public/ss-light3.png +0 -0
- package/public/ss-light3.webp +0 -0
- package/shared/agent-profile-schema.ts +81 -0
- package/shared/credential-field-schema.ts +12 -0
- package/skills/auramaxx/SKILL.md +71 -691
- package/src/app/UnlockPageClient.tsx +1939 -0
- package/src/app/api/page.tsx +8 -9
- package/src/app/api/restart/route.ts +2 -18
- package/src/app/api/update/route.ts +104 -51
- package/src/app/approve/[actionId]/page.tsx +4 -1
- package/src/app/docs/DocsPageContent.tsx +3 -3
- package/src/app/globals.css +94 -0
- package/src/app/layout.tsx +1 -0
- package/src/app/page.tsx +25 -1935
- package/src/app/yo/layout.tsx +29 -0
- package/src/app/yo/page.tsx +528 -0
- package/src/components/HumanActionBar.tsx +34 -8
- package/src/components/agent/AgentSidebar.tsx +3 -1
- package/src/components/agent/CredentialAgent.tsx +5 -1
- package/src/components/agent/CredentialDetail.tsx +32 -1
- package/src/components/agent/CredentialForm.tsx +94 -7
- package/src/components/agent/CredentialRow.tsx +8 -1
- package/src/components/agent/credentialFormName.ts +22 -1
- package/src/components/agent/types.ts +2 -2
- package/src/components/design-system/Modal.tsx +14 -1
- package/src/hooks/useUpdateChecker.ts +17 -1
- package/src/lib/pino.ts +77 -8
- package/src/server/cli/commands/actions.ts +1 -1
- package/src/server/cli/commands/agent.ts +110 -65
- package/src/server/cli/commands/approve.ts +1 -1
- package/src/server/cli/commands/auth.ts +81 -20
- package/src/server/cli/commands/start.ts +42 -3
- package/src/server/cli/commands/token.ts +2 -2
- package/src/server/cli/lib/escalation.ts +109 -24
- package/src/server/cli/lib/process.ts +54 -1
- package/src/server/cli/socket.ts +1 -1
- package/src/server/index.ts +2 -0
- package/src/server/lib/agent-profile-records.ts +72 -0
- package/src/server/lib/credential-transport.ts +27 -11
- package/src/server/lib/defaults.ts +3 -3
- package/src/server/lib/escalation-responder.ts +1 -1
- package/src/server/lib/resolve-action.ts +2 -2
- package/src/server/lib/update-check.ts +1 -1
- package/src/server/mcp/server.ts +6 -1
- package/src/server/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/server/routes/actions.ts +2 -2
- package/src/server/routes/agent-profiles.ts +82 -0
- package/src/server/routes/auth.ts +39 -4
- package/src/server/routes/credentials.ts +18 -0
- package/src/server/tests/cli/agent-auth.test.ts +20 -39
- package/src/server/tests/cli/agent.test.ts +18 -0
- package/src/server/tests/cli/auth-action-flag.test.ts +3 -2
- package/src/server/tests/cli/bin-entrypoint.test.ts +35 -11
- package/src/server/tests/cli/escalation.test.ts +7 -3
- package/src/server/tests/cli/process.test.ts +3 -3
- package/src/server/tests/cli/socket.test.ts +2 -2
- package/src/server/tests/cli/start-run.test.ts +24 -1
- package/src/server/tests/endpoints/actions.test.ts +2 -2
- package/src/server/tests/endpoints/agent-profiles.test.ts +117 -0
- package/src/server/tests/endpoints/auth.test.ts +34 -0
- package/src/server/tests/lib/credential-transport.test.ts +68 -2
- package/src/server/tests/lib/defaults.test.ts +2 -2
- package/src/server/tests/lib/escalation-responder.test.ts +2 -2
- package/src/server/tests/lib/update-check.test.ts +1 -1
- package/src/server/tests/setup.ts +7 -0
- package/src/server/tsconfig.tsbuildinfo +1 -1
- package/src/server/types.ts +1 -1
- package/.next/static/AcaCjQ4akovHBUnVGPpfN/_buildManifest.js +0 -1
- package/.next/static/chunks/1168.63dbb444a33b1867.js +0 -1
- package/.next/static/chunks/1255-e8718b02724690dd.js +0 -1
- package/.next/static/chunks/142-aeaf7ffa9c53516d.js +0 -1
- package/.next/static/chunks/2505.22aaa333fd65908f.js +0 -1
- package/.next/static/chunks/2619-3c9e02e22d10480a.js +0 -1
- package/.next/static/chunks/2927.e7e9e2a1b8d2dc61.js +0 -1
- package/.next/static/chunks/3573-27e17f4ff2dd86ed.js +0 -1
- package/.next/static/chunks/3609.6f8e0ecd6de9566c.js +0 -1
- package/.next/static/chunks/3667-d6770121629db38b.js +0 -1
- package/.next/static/chunks/3826.7dfe96467cd74e45.js +0 -1
- package/.next/static/chunks/4256.50cb375c979ffd5a.js +0 -1
- package/.next/static/chunks/4685-3f8d92f574366fec.js +0 -1
- package/.next/static/chunks/4901-54c1ac380b7b43bb.js +0 -1
- package/.next/static/chunks/4919-fe6f1553abfc9420.js +0 -1
- package/.next/static/chunks/4bd1b696-f785427dddbba9fb.js +0 -1
- package/.next/static/chunks/5336-bd251f91235f7c11.js +0 -1
- package/.next/static/chunks/5442-be197c885bf12079.js +0 -1
- package/.next/static/chunks/5553-c8b86fe3513fce04.js +0 -59
- package/.next/static/chunks/6233-44e6fe57a552a816.js +0 -1
- package/.next/static/chunks/626.2583673a0386a81b.js +0 -1
- package/.next/static/chunks/6872-6442f2f5cce36ce5.js +0 -1
- package/.next/static/chunks/7411-3ca797c21b722ccd.js +0 -1
- package/.next/static/chunks/7616-b8bd37ce1f735d6f.js +0 -1
- package/.next/static/chunks/786-9ed39f96091b2be4.js +0 -1
- package/.next/static/chunks/8273-922091226ba84a94.js +0 -1
- package/.next/static/chunks/8357.6159472717ff7d11.js +0 -1
- package/.next/static/chunks/9062-3eb1607c96486f88.js +0 -1
- package/.next/static/chunks/9380.93f361baab2eefdf.js +0 -1
- package/.next/static/chunks/app/_not-found/page-c3b87025baf0a9c2.js +0 -1
- package/.next/static/chunks/app/api/[...doc]/page-790c4b33ba1fde4a.js +0 -1
- package/.next/static/chunks/app/api/agent-requests/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/apps/install/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/apps/manifests/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/apps/static/[...path]/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/docs/plain/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/events/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/import-from-openclaw/[channel]/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/import-from-openclaw/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/import-from-openclaw/validate/[channel]/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/page-b53f9aa17a4c5201.js +0 -1
- package/.next/static/chunks/app/api/restart/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/update/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/version/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/[id]/apps/[wid]/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/[id]/apps/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/[id]/export/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/[id]/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/config/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/import/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/api/workspace/route-e83b12cbab2e8707.js +0 -1
- package/.next/static/chunks/app/app-legacy-do-not-use/page-0052191daef60036.js +0 -1
- package/.next/static/chunks/app/approve/[actionId]/page-45cd3b8fa062d5e5.js +0 -1
- package/.next/static/chunks/app/docs/[...doc]/page-632ac406200b66fe.js +0 -1
- package/.next/static/chunks/app/docs/page-b7556394709b43df.js +0 -1
- package/.next/static/chunks/app/error-3d6057da512253d8.js +0 -1
- package/.next/static/chunks/app/health/page-80c985cd72328b74.js +0 -1
- package/.next/static/chunks/app/hello/page-fd71babcd192729b.js +0 -1
- package/.next/static/chunks/app/layout-285c6ef3f16bae63.js +0 -1
- package/.next/static/chunks/app/page-85017185df14c37b.js +0 -1
- package/.next/static/chunks/app/privacy/page-faf36cd0dde6dfa3.js +0 -1
- package/.next/static/chunks/app/share/[token]/page-22d51d6c5a47bb75.js +0 -1
- package/.next/static/chunks/app/terms/page-faf36cd0dde6dfa3.js +0 -1
- package/.next/static/chunks/framework-e60c938074ff7136.js +0 -1
- package/.next/static/chunks/main-447abf206d7ebd2f.js +0 -1
- package/.next/static/chunks/main-app-f63b86bdbf5b7b88.js +0 -1
- package/.next/static/chunks/pages/_app-6c8c2371b16a04b8.js +0 -1
- package/.next/static/chunks/pages/_error-94812ad32cad7365.js +0 -1
- package/.next/static/css/eb25c6452113486f.css +0 -3
- /package/.next/static/{AcaCjQ4akovHBUnVGPpfN → WshFGr6RxGYP6AbWuT9OG}/_ssgManifest.js +0 -0
package/src/app/page.tsx
CHANGED
|
@@ -1,1939 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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 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
|
}
|