auramaxx 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,212 @@
1
+ import Link from 'next/link';
2
+ import DocsThemeToggle from '@/components/docs/DocsThemeToggle';
3
+
4
+ import { readDocFile, parseApiEndpoints, renderMarkdown, slugify, type ApiEndpoint } from '@/lib/docs';
5
+
6
+ /** Group endpoints by section, then sub-group by base resource path. */
7
+ const buildIndex = (endpoints: ApiEndpoint[]) => {
8
+ const sections = new Map<string, ApiEndpoint[]>();
9
+ for (const ep of endpoints) {
10
+ const group = sections.get(ep.section) ?? [];
11
+ group.push(ep);
12
+ sections.set(ep.section, group);
13
+ }
14
+
15
+ const result: { section: string; sectionSlug: string; resources: { base: string; endpoints: ApiEndpoint[] }[] }[] = [];
16
+
17
+ for (const [section, eps] of sections) {
18
+ // Derive the slug from the most specific heading (H3 if present)
19
+ const parts = section.split(' / ');
20
+ const sectionSlug = slugify(parts[parts.length - 1]);
21
+
22
+ // Sub-group by base resource: first two path segments (e.g. /wallet/create → /wallet, /actions/:id/resolve → /actions)
23
+ const byBase = new Map<string, ApiEndpoint[]>();
24
+ for (const ep of eps) {
25
+ const segments = ep.path.split('/').filter(Boolean);
26
+ const base = '/' + (segments[0] ?? '');
27
+ const group = byBase.get(base) ?? [];
28
+ group.push(ep);
29
+ byBase.set(base, group);
30
+ }
31
+
32
+ result.push({
33
+ section,
34
+ sectionSlug,
35
+ resources: [...byBase.entries()].map(([base, resEps]) => ({ base, endpoints: resEps })),
36
+ });
37
+ }
38
+
39
+ return result;
40
+ };
41
+
42
+ const METHOD_COLOR: Record<string, string> = {
43
+ GET: 'bg-emerald-600',
44
+ POST: 'bg-blue-600',
45
+ PUT: 'bg-amber-600',
46
+ PATCH: 'bg-orange-600',
47
+ DELETE: 'bg-red-600',
48
+ OPTIONS: 'bg-gray-500',
49
+ HEAD: 'bg-gray-500',
50
+ };
51
+
52
+ export default async function ApiReferencePage() {
53
+ const content = await readDocFile('API.md');
54
+ const endpoints = parseApiEndpoints(content);
55
+ const html = renderMarkdown(content);
56
+ const index = buildIndex(endpoints);
57
+
58
+ return (
59
+ <div className="min-h-screen bg-[var(--color-background,#f4f4f5)] relative p-4 py-8">
60
+ {/* Background matching Sterile Field */}
61
+ <div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
62
+ {/* Sterile Grid */}
63
+ <div className="absolute inset-0 bg-grid-adaptive bg-[size:4rem_4rem] opacity-30" />
64
+
65
+ {/* Tyvek Texture Overlay */}
66
+ <div className="absolute inset-0 tyvek-texture opacity-40 mix-blend-multiply" />
67
+
68
+ {/* Giant Background Typography */}
69
+ <div className="absolute top-[5%] left-[5%] opacity-5 select-none">
70
+ <h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter">
71
+ AURA
72
+ </h1>
73
+ </div>
74
+ <div className="absolute bottom-[5%] right-[5%] opacity-5 select-none">
75
+ <h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter text-right">
76
+ MAXXING
77
+ </h1>
78
+ </div>
79
+
80
+ {/* Lab Markings - Corner Finder Patterns */}
81
+ <div className="absolute top-10 left-10 w-32 h-32 border-l-4 border-t-4 border-[var(--color-text,#0a0a0a)] opacity-10">
82
+ <div className="absolute top-2 left-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
83
+ </div>
84
+ <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">
85
+ <div className="absolute bottom-2 right-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
86
+ </div>
87
+ </div>
88
+
89
+ {/* Logo header */}
90
+ <Link
91
+ href="/"
92
+ className="fixed top-6 left-6 z-50 flex items-center gap-3 hover:opacity-80 transition-opacity"
93
+ >
94
+ <div className="w-10 h-10">
95
+ <img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
96
+ </div>
97
+ <div className="font-black text-xl tracking-tighter text-[var(--color-text,#0a0a0a)]">AURAWALLET</div>
98
+ </Link>
99
+
100
+ {/* Nav */}
101
+ <div className="fixed top-7 right-6 z-50 flex items-center gap-3 font-mono text-[10px] tracking-widest">
102
+ <Link href="/docs" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">DOCS</Link>
103
+ <Link href="/app" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">APP</Link>
104
+ <DocsThemeToggle />
105
+ </div>
106
+
107
+ <div className="relative z-[5] max-w-[1400px] mx-auto pt-16">
108
+ <div className="grid gap-4 md:grid-cols-[240px_minmax(0,1fr)]">
109
+ {/* Left rail — endpoint index (sticky) */}
110
+ <aside className="font-mono hidden md:block">
111
+ <div className="sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto">
112
+ <div className="bg-[var(--color-surface,#f4f4f2)] border border-[var(--color-border,#d4d4d8)] shadow-lg overflow-hidden font-mono">
113
+ <div className="px-4 py-3 border-b border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface-alt,#fafafa)] flex items-center justify-between">
114
+ <span className="font-sans font-bold text-sm text-[var(--color-text,#0a0a0a)] uppercase tracking-tight">Endpoints</span>
115
+ <span className="text-[9px] text-[var(--color-text-faint,#9ca3af)] font-bold">QTY: {endpoints.length.toString().padStart(3, '0')}</span>
116
+ </div>
117
+ <div className="p-3 space-y-2.5">
118
+ {index.map(({ section, sectionSlug, resources }) => (
119
+ <div key={section}>
120
+ <a
121
+ href={`#${sectionSlug}`}
122
+ className="block text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase mb-1 hover:text-[var(--color-text,#0a0a0a)] transition-colors"
123
+ >
124
+ {section}
125
+ </a>
126
+ {resources.map(({ base, endpoints: resEps }) => (
127
+ <div key={base} className="mb-1 border-l border-[var(--color-border,#d4d4d8)] ml-1">
128
+ <div className="pl-2 py-0.5 text-[9px] text-[var(--color-text-faint,#9ca3af)] tracking-wide font-bold">{base}</div>
129
+ <div>
130
+ {resEps.map((ep) => (
131
+ <a
132
+ key={`${ep.method} ${ep.path}`}
133
+ href={`#${sectionSlug}`}
134
+ className="flex items-center gap-1.5 pl-2 pr-1 py-0.5 text-[10px] hover:bg-[var(--color-surface-alt,#fafafa)] transition-colors"
135
+ >
136
+ <span
137
+ className={`inline-block w-[34px] text-center py-px text-[7px] font-bold text-white tracking-wide shrink-0 ${METHOD_COLOR[ep.method] ?? 'bg-gray-500'}`}
138
+ >
139
+ {ep.method}
140
+ </span>
141
+ <span className="text-[var(--color-text-muted,#6b7280)] truncate">{ep.path}</span>
142
+ </a>
143
+ ))}
144
+ </div>
145
+ </div>
146
+ ))}
147
+ </div>
148
+ ))}
149
+
150
+ {/* Docs link */}
151
+ <div className="pt-2 border-t border-[var(--color-border,#d4d4d8)]">
152
+ <Link
153
+ href="/docs"
154
+ className="flex items-center gap-2 px-2 py-1.5 text-[11px] border border-transparent hover:border-[var(--color-border-muted,#e5e5e5)] hover:bg-[var(--color-surface-alt,#fafafa)] text-[var(--color-text-muted,#6b7280)] transition-colors"
155
+ >
156
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" /></svg>
157
+ DOCS
158
+ </Link>
159
+ </div>
160
+ </div>
161
+ {/* Barcode + Stripe */}
162
+ <div className="flex items-center gap-3 px-4 py-2 border-t border-[var(--color-border,#d4d4d8)]">
163
+ <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" />
164
+ </div>
165
+ <div className="h-2 w-full" style={{
166
+ backgroundImage: 'repeating-linear-gradient(45deg, var(--color-text, #000), var(--color-text, #000) 5px, transparent 5px, transparent 10px)',
167
+ opacity: 0.1,
168
+ }} />
169
+ </div>
170
+ </div>
171
+ </aside>
172
+
173
+ {/* Content — rendered API markdown */}
174
+ <div className="bg-[var(--color-surface,#f4f4f2)] border border-[var(--color-border,#d4d4d8)] shadow-lg overflow-hidden font-mono">
175
+ <div className="px-4 py-3 border-b border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface-alt,#fafafa)] flex items-center justify-between">
176
+ <span className="font-sans font-bold text-sm text-[var(--color-text,#0a0a0a)] uppercase tracking-tight">API Reference</span>
177
+ <a
178
+ href="http://localhost:4242"
179
+ target="_blank"
180
+ rel="noopener noreferrer"
181
+ className="text-[10px] text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors tracking-widest"
182
+ >
183
+ OPEN API →
184
+ </a>
185
+ </div>
186
+
187
+ <div
188
+ className="p-5 prose-mono"
189
+ dangerouslySetInnerHTML={{ __html: html }}
190
+ />
191
+
192
+ {/* Footer */}
193
+ <div className="px-4 py-2 border-t border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface-alt,#fafafa)]">
194
+ <div className="text-[8px] text-[var(--color-text-faint,#9ca3af)] uppercase tracking-wider mb-1">DOCUMENT REFERENCE</div>
195
+ <div className="text-[9px] text-[var(--color-text,#0a0a0a)] font-bold">API.md</div>
196
+ </div>
197
+
198
+ {/* Barcode + Stripe */}
199
+ <div className="flex items-center gap-3 px-4 py-2 border-t border-[var(--color-border,#d4d4d8)]">
200
+ <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" />
201
+ <span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-wider">AURAWALLET</span>
202
+ </div>
203
+ <div className="h-2 w-full" style={{
204
+ backgroundImage: 'repeating-linear-gradient(45deg, var(--color-text, #000), var(--color-text, #000) 5px, transparent 5px, transparent 10px)',
205
+ opacity: 0.1,
206
+ }} />
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ );
212
+ }
@@ -0,0 +1,119 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { PrismaClient } from '@prisma/client';
3
+ import { requireWorkspaceAdmin } from '../../../auth';
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ // PATCH /api/workspace/[id]/apps/[wid] - Update app
8
+ export async function PATCH(
9
+ request: NextRequest,
10
+ { params }: { params: Promise<{ id: string; wid: string }> }
11
+ ) {
12
+ try {
13
+ const unauthorized = await requireWorkspaceAdmin(request);
14
+ if (unauthorized) return unauthorized;
15
+
16
+ const { id: workspaceId, wid: appId } = await params;
17
+ const body = await request.json();
18
+ const { x, y, width, height, zIndex, isVisible, isLocked, config } = body;
19
+
20
+ // Verify app exists and belongs to workspace
21
+ const existing = await prisma.workspaceApp.findUnique({
22
+ where: { id: appId },
23
+ });
24
+
25
+ if (!existing) {
26
+ return NextResponse.json(
27
+ { success: false, error: 'App not found' },
28
+ { status: 404 }
29
+ );
30
+ }
31
+
32
+ if (existing.workspaceId !== workspaceId) {
33
+ return NextResponse.json(
34
+ { success: false, error: 'App does not belong to this workspace' },
35
+ { status: 400 }
36
+ );
37
+ }
38
+
39
+ const updateData: Record<string, unknown> = {};
40
+ if (x !== undefined) updateData.x = x;
41
+ if (y !== undefined) updateData.y = y;
42
+ if (width !== undefined) updateData.width = width;
43
+ if (height !== undefined) updateData.height = height;
44
+ if (zIndex !== undefined) updateData.zIndex = zIndex;
45
+ if (isVisible !== undefined) updateData.isVisible = isVisible;
46
+ if (isLocked !== undefined) updateData.isLocked = isLocked;
47
+ if (config !== undefined) updateData.config = JSON.stringify(config);
48
+
49
+ const app = await prisma.workspaceApp.update({
50
+ where: { id: appId },
51
+ data: updateData,
52
+ });
53
+
54
+ return NextResponse.json({
55
+ success: true,
56
+ app: {
57
+ id: app.id,
58
+ workspaceId: app.workspaceId,
59
+ appType: app.appType,
60
+ x: app.x,
61
+ y: app.y,
62
+ width: app.width,
63
+ height: app.height,
64
+ zIndex: app.zIndex,
65
+ isVisible: app.isVisible,
66
+ isLocked: app.isLocked,
67
+ config: app.config ? JSON.parse(app.config) : null,
68
+ },
69
+ });
70
+ } catch (error) {
71
+ console.error('Failed to update app:', error);
72
+ return NextResponse.json(
73
+ { success: false, error: 'Failed to update app' },
74
+ { status: 500 }
75
+ );
76
+ }
77
+ }
78
+
79
+ // DELETE /api/workspace/[id]/apps/[wid] - Remove app
80
+ export async function DELETE(
81
+ request: NextRequest,
82
+ { params }: { params: Promise<{ id: string; wid: string }> }
83
+ ) {
84
+ try {
85
+ const unauthorized = await requireWorkspaceAdmin(request);
86
+ if (unauthorized) return unauthorized;
87
+
88
+ const { id: workspaceId, wid: appId } = await params;
89
+
90
+ // Verify app exists and belongs to workspace
91
+ const existing = await prisma.workspaceApp.findUnique({
92
+ where: { id: appId },
93
+ });
94
+
95
+ if (!existing) {
96
+ return NextResponse.json(
97
+ { success: false, error: 'App not found' },
98
+ { status: 404 }
99
+ );
100
+ }
101
+
102
+ if (existing.workspaceId !== workspaceId) {
103
+ return NextResponse.json(
104
+ { success: false, error: 'App does not belong to this workspace' },
105
+ { status: 400 }
106
+ );
107
+ }
108
+
109
+ await prisma.workspaceApp.delete({ where: { id: appId } });
110
+
111
+ return NextResponse.json({ success: true });
112
+ } catch (error) {
113
+ console.error('Failed to delete app:', error);
114
+ return NextResponse.json(
115
+ { success: false, error: 'Failed to delete app' },
116
+ { status: 500 }
117
+ );
118
+ }
119
+ }
@@ -0,0 +1,81 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { PrismaClient } from '@prisma/client';
3
+ import { requireWorkspaceAdmin } from '../../auth';
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ // POST /api/workspace/[id]/apps - Add app to workspace
8
+ export async function POST(
9
+ request: NextRequest,
10
+ { params }: { params: Promise<{ id: string }> }
11
+ ) {
12
+ try {
13
+ const unauthorized = await requireWorkspaceAdmin(request);
14
+ if (unauthorized) return unauthorized;
15
+
16
+ const { id: workspaceId } = await params;
17
+ const body = await request.json();
18
+ const { appType, x, y, width, height, zIndex, isVisible, isLocked, config } = body;
19
+
20
+ if (!appType) {
21
+ return NextResponse.json(
22
+ { success: false, error: 'appType is required' },
23
+ { status: 400 }
24
+ );
25
+ }
26
+
27
+ // Verify workspace exists
28
+ const workspace = await prisma.workspace.findUnique({ where: { id: workspaceId } });
29
+ if (!workspace) {
30
+ return NextResponse.json(
31
+ { success: false, error: 'Workspace not found' },
32
+ { status: 404 }
33
+ );
34
+ }
35
+
36
+ // Get max zIndex for this workspace
37
+ const maxZ = await prisma.workspaceApp.aggregate({
38
+ where: { workspaceId },
39
+ _max: { zIndex: true },
40
+ });
41
+ const newZIndex = zIndex ?? (maxZ._max.zIndex ?? 9) + 1;
42
+
43
+ const app = await prisma.workspaceApp.create({
44
+ data: {
45
+ workspaceId,
46
+ appType,
47
+ x: x ?? 20,
48
+ y: y ?? 20,
49
+ width: width ?? 320,
50
+ height: height ?? 280,
51
+ zIndex: newZIndex,
52
+ isVisible: isVisible ?? true,
53
+ isLocked: isLocked ?? false,
54
+ config: config ? JSON.stringify(config) : null,
55
+ },
56
+ });
57
+
58
+ return NextResponse.json({
59
+ success: true,
60
+ app: {
61
+ id: app.id,
62
+ workspaceId: app.workspaceId,
63
+ appType: app.appType,
64
+ x: app.x,
65
+ y: app.y,
66
+ width: app.width,
67
+ height: app.height,
68
+ zIndex: app.zIndex,
69
+ isVisible: app.isVisible,
70
+ isLocked: app.isLocked,
71
+ config: app.config ? JSON.parse(app.config) : null,
72
+ },
73
+ });
74
+ } catch (error) {
75
+ console.error('Failed to add app:', error);
76
+ return NextResponse.json(
77
+ { success: false, error: 'Failed to add app' },
78
+ { status: 500 }
79
+ );
80
+ }
81
+ }
@@ -0,0 +1,67 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { PrismaClient } from '@prisma/client';
3
+ import { requireWorkspaceAdmin } from '../../auth';
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ // GET /api/workspace/[id]/export - Export workspace as JSON
8
+ export async function GET(
9
+ request: NextRequest,
10
+ { params }: { params: Promise<{ id: string }> }
11
+ ) {
12
+ try {
13
+ const unauthorized = await requireWorkspaceAdmin(request);
14
+ if (unauthorized) return unauthorized;
15
+
16
+ const { id } = await params;
17
+
18
+ const workspace = await prisma.workspace.findUnique({
19
+ where: { id },
20
+ include: {
21
+ apps: true,
22
+ },
23
+ });
24
+
25
+ if (!workspace) {
26
+ return NextResponse.json(
27
+ { success: false, error: 'Workspace not found' },
28
+ { status: 404 }
29
+ );
30
+ }
31
+
32
+ const exportData = {
33
+ version: 1,
34
+ exportedAt: new Date().toISOString(),
35
+ workspace: {
36
+ name: workspace.name,
37
+ slug: workspace.slug,
38
+ icon: workspace.icon,
39
+ order: workspace.order,
40
+ isDefault: workspace.isDefault,
41
+ isCloseable: workspace.isCloseable,
42
+ },
43
+ apps: workspace.apps.map((w: any) => ({
44
+ appType: w.appType,
45
+ x: w.x,
46
+ y: w.y,
47
+ width: w.width,
48
+ height: w.height,
49
+ zIndex: w.zIndex,
50
+ isVisible: w.isVisible,
51
+ isLocked: w.isLocked,
52
+ config: w.config ? JSON.parse(w.config) : null,
53
+ })),
54
+ };
55
+
56
+ return NextResponse.json({
57
+ success: true,
58
+ data: exportData,
59
+ });
60
+ } catch (error) {
61
+ console.error('Failed to export workspace:', error);
62
+ return NextResponse.json(
63
+ { success: false, error: 'Failed to export workspace' },
64
+ { status: 500 }
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,168 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { PrismaClient } from '@prisma/client';
3
+ import { requireWorkspaceAdmin } from '../auth';
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ // GET /api/workspace/[id] - Get workspace with apps
8
+ export async function GET(
9
+ request: NextRequest,
10
+ { params }: { params: Promise<{ id: string }> }
11
+ ) {
12
+ try {
13
+ const unauthorized = await requireWorkspaceAdmin(request);
14
+ if (unauthorized) return unauthorized;
15
+
16
+ const { id } = await params;
17
+
18
+ const workspace = await prisma.workspace.findUnique({
19
+ where: { id },
20
+ include: {
21
+ apps: {
22
+ orderBy: { zIndex: 'asc' },
23
+ },
24
+ },
25
+ });
26
+
27
+ if (!workspace) {
28
+ return NextResponse.json(
29
+ { success: false, error: 'Workspace not found' },
30
+ { status: 404 }
31
+ );
32
+ }
33
+
34
+ return NextResponse.json({
35
+ success: true,
36
+ workspace: {
37
+ id: workspace.id,
38
+ name: workspace.name,
39
+ slug: workspace.slug,
40
+ icon: workspace.icon,
41
+ order: workspace.order,
42
+ isDefault: workspace.isDefault,
43
+ isCloseable: workspace.isCloseable,
44
+ },
45
+ apps: workspace.apps.map((w: any) => ({
46
+ id: w.id,
47
+ workspaceId: w.workspaceId,
48
+ appType: w.appType,
49
+ x: w.x,
50
+ y: w.y,
51
+ width: w.width,
52
+ height: w.height,
53
+ zIndex: w.zIndex,
54
+ isVisible: w.isVisible,
55
+ isLocked: w.isLocked,
56
+ config: w.config ? JSON.parse(w.config) : null,
57
+ })),
58
+ });
59
+ } catch (error) {
60
+ console.error('Failed to get workspace:', error);
61
+ return NextResponse.json(
62
+ { success: false, error: 'Failed to get workspace' },
63
+ { status: 500 }
64
+ );
65
+ }
66
+ }
67
+
68
+ // PATCH /api/workspace/[id] - Update workspace
69
+ export async function PATCH(
70
+ request: NextRequest,
71
+ { params }: { params: Promise<{ id: string }> }
72
+ ) {
73
+ try {
74
+ const unauthorized = await requireWorkspaceAdmin(request);
75
+ if (unauthorized) return unauthorized;
76
+
77
+ const { id } = await params;
78
+ const body = await request.json();
79
+ const { name, icon, order, isDefault, isCloseable } = body;
80
+
81
+ // Check if workspace exists
82
+ const existing = await prisma.workspace.findUnique({ where: { id } });
83
+ if (!existing) {
84
+ return NextResponse.json(
85
+ { success: false, error: 'Workspace not found' },
86
+ { status: 404 }
87
+ );
88
+ }
89
+
90
+ // If setting as default, unset existing default
91
+ if (isDefault) {
92
+ await prisma.workspace.updateMany({
93
+ where: { isDefault: true, id: { not: id } },
94
+ data: { isDefault: false },
95
+ });
96
+ }
97
+
98
+ const workspace = await prisma.workspace.update({
99
+ where: { id },
100
+ data: {
101
+ name: name ?? existing.name,
102
+ icon: icon ?? existing.icon,
103
+ order: order ?? existing.order,
104
+ isDefault: isDefault ?? existing.isDefault,
105
+ isCloseable: isCloseable ?? existing.isCloseable,
106
+ },
107
+ });
108
+
109
+ return NextResponse.json({
110
+ success: true,
111
+ workspace: {
112
+ id: workspace.id,
113
+ name: workspace.name,
114
+ slug: workspace.slug,
115
+ icon: workspace.icon,
116
+ order: workspace.order,
117
+ isDefault: workspace.isDefault,
118
+ isCloseable: workspace.isCloseable,
119
+ },
120
+ });
121
+ } catch (error) {
122
+ console.error('Failed to update workspace:', error);
123
+ return NextResponse.json(
124
+ { success: false, error: 'Failed to update workspace' },
125
+ { status: 500 }
126
+ );
127
+ }
128
+ }
129
+
130
+ // DELETE /api/workspace/[id] - Delete workspace
131
+ export async function DELETE(
132
+ request: NextRequest,
133
+ { params }: { params: Promise<{ id: string }> }
134
+ ) {
135
+ try {
136
+ const unauthorized = await requireWorkspaceAdmin(request);
137
+ if (unauthorized) return unauthorized;
138
+
139
+ const { id } = await params;
140
+
141
+ // Check if workspace exists and is closeable
142
+ const workspace = await prisma.workspace.findUnique({ where: { id } });
143
+ if (!workspace) {
144
+ return NextResponse.json(
145
+ { success: false, error: 'Workspace not found' },
146
+ { status: 404 }
147
+ );
148
+ }
149
+
150
+ if (!workspace.isCloseable) {
151
+ return NextResponse.json(
152
+ { success: false, error: 'Cannot delete non-closeable workspace' },
153
+ { status: 400 }
154
+ );
155
+ }
156
+
157
+ // Cascade delete will remove apps
158
+ await prisma.workspace.delete({ where: { id } });
159
+
160
+ return NextResponse.json({ success: true });
161
+ } catch (error) {
162
+ console.error('Failed to delete workspace:', error);
163
+ return NextResponse.json(
164
+ { success: false, error: 'Failed to delete workspace' },
165
+ { status: 500 }
166
+ );
167
+ }
168
+ }
@@ -0,0 +1,34 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ const WALLET_API = process.env.WALLET_SERVER_URL || 'http://localhost:4242';
4
+
5
+ /**
6
+ * Validate admin access by forwarding the Authorization header to Express.
7
+ */
8
+ async function validateAdmin(authHeader: string | null): Promise<boolean> {
9
+ if (!authHeader) return false;
10
+
11
+ try {
12
+ // Use an authenticated admin-only endpoint as a lightweight verifier.
13
+ const resp = await fetch(`${WALLET_API}/setup`, {
14
+ headers: { Authorization: authHeader },
15
+ cache: 'no-store',
16
+ });
17
+ return resp.ok;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ export async function requireWorkspaceAdmin(
24
+ request: NextRequest
25
+ ): Promise<NextResponse | null> {
26
+ const authHeader = request.headers.get('authorization');
27
+ const isAdmin = await validateAdmin(authHeader);
28
+ if (isAdmin) return null;
29
+
30
+ return NextResponse.json(
31
+ { success: false, error: 'Admin access required' },
32
+ { status: 403 }
33
+ );
34
+ }