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.
- package/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified API client for calling both Express and Next.js backends
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { api, Api, unlockWallet, setupWallet } from '@/lib/api';
|
|
6
|
+
*
|
|
7
|
+
* // Wallet operations (Express :4242)
|
|
8
|
+
* const wallets = await api.get(Api.Wallet, '/wallets');
|
|
9
|
+
* await api.post(Api.Wallet, '/wallet/rename', { address, name });
|
|
10
|
+
*
|
|
11
|
+
* // Encrypted unlock/setup (password encrypted with server's RSA key)
|
|
12
|
+
* const result = await unlockWallet(password);
|
|
13
|
+
* const result = await setupWallet(password);
|
|
14
|
+
*
|
|
15
|
+
* // Workspace operations (Next.js :4747)
|
|
16
|
+
* const workspaces = await api.get(Api.Workspace, '/workspace');
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { encryptPassword, getTokenMintPubkey } from './crypto';
|
|
21
|
+
|
|
22
|
+
// Derive ports from dashboard port at runtime (no env var coordination needed)
|
|
23
|
+
// Convention: wallet = dashboard - 505, WS = dashboard + 1
|
|
24
|
+
// Default: 4747 → wallet 4242, WS 4748
|
|
25
|
+
// Sandbox: 5747 → wallet 5242, WS 5748
|
|
26
|
+
const IS_LOCAL = typeof window !== 'undefined'
|
|
27
|
+
&& (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
|
|
28
|
+
|
|
29
|
+
const DASHBOARD_PORT_NUM = typeof window !== 'undefined'
|
|
30
|
+
? parseInt(window.location.port || '4747', 10)
|
|
31
|
+
: parseInt(process.env.DASHBOARD_PORT || '4747', 10);
|
|
32
|
+
|
|
33
|
+
// When accessed via tunnel (non-localhost), use sibling subdomains over HTTPS
|
|
34
|
+
// e.g. wallet.auramaxx.xyz → wallet-api.auramaxx.xyz for Express
|
|
35
|
+
const EXPRESS_URL = typeof window !== 'undefined'
|
|
36
|
+
? IS_LOCAL
|
|
37
|
+
? `http://${window.location.hostname}:${DASHBOARD_PORT_NUM - 505}`
|
|
38
|
+
: `https://wallet-api.${window.location.hostname.split('.').slice(1).join('.')}`
|
|
39
|
+
: `http://localhost:${DASHBOARD_PORT_NUM - 505}`;
|
|
40
|
+
|
|
41
|
+
const NEXTJS_URL = typeof window !== 'undefined'
|
|
42
|
+
? IS_LOCAL
|
|
43
|
+
? `http://${window.location.hostname}:${DASHBOARD_PORT_NUM}`
|
|
44
|
+
: `${window.location.protocol}//${window.location.host}`
|
|
45
|
+
: `http://localhost:${DASHBOARD_PORT_NUM}`;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* API target enum - determines which backend to call
|
|
49
|
+
*/
|
|
50
|
+
export enum Api {
|
|
51
|
+
/** Express :4242 - wallet operations, auth, agents, transactions */
|
|
52
|
+
Wallet = 'wallet',
|
|
53
|
+
/** Next.js :4747/api - workspace CRUD */
|
|
54
|
+
Workspace = 'workspace',
|
|
55
|
+
/** Next.js :4747/api - event logs from database */
|
|
56
|
+
Events = 'events',
|
|
57
|
+
/** Next.js :4747/api - agent dashboard (requests + tokens) */
|
|
58
|
+
AgentDashboard = 'agentDashboard',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const API_CONFIG: Record<Api, { baseUrl: string; pathPrefix: string }> = {
|
|
62
|
+
[Api.Wallet]: { baseUrl: EXPRESS_URL, pathPrefix: '' },
|
|
63
|
+
[Api.Workspace]: { baseUrl: NEXTJS_URL, pathPrefix: '/api' },
|
|
64
|
+
[Api.Events]: { baseUrl: NEXTJS_URL, pathPrefix: '/api' },
|
|
65
|
+
[Api.AgentDashboard]: { baseUrl: NEXTJS_URL, pathPrefix: '/api' },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const TOKEN_STORAGE_KEY = 'aurawallet_admin_token';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get auth token from sessionStorage
|
|
72
|
+
*/
|
|
73
|
+
function getToken(): string | null {
|
|
74
|
+
if (typeof window === 'undefined') return null;
|
|
75
|
+
return sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Make an authenticated request to the specified backend
|
|
80
|
+
*/
|
|
81
|
+
async function request<T>(
|
|
82
|
+
target: Api,
|
|
83
|
+
path: string,
|
|
84
|
+
options: RequestInit = {}
|
|
85
|
+
): Promise<T> {
|
|
86
|
+
const token = getToken();
|
|
87
|
+
const config = API_CONFIG[target];
|
|
88
|
+
|
|
89
|
+
const headers: HeadersInit = {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
...options.headers,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (token) {
|
|
95
|
+
(headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const url = `${config.baseUrl}${config.pathPrefix}${path}`;
|
|
99
|
+
|
|
100
|
+
const res = await fetch(url, {
|
|
101
|
+
...options,
|
|
102
|
+
headers,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const data = await res.json().catch(() => ({ error: res.statusText }));
|
|
106
|
+
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const error = new Error(data.error || `Request failed: ${res.status}`);
|
|
109
|
+
(error as Error & { status: number }).status = res.status;
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return data as T;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* API client with typed methods
|
|
118
|
+
*/
|
|
119
|
+
/** Get the base URL for the wallet (Express) API */
|
|
120
|
+
export function getWalletBaseUrl(): string {
|
|
121
|
+
return EXPRESS_URL;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const api = {
|
|
125
|
+
/**
|
|
126
|
+
* GET request
|
|
127
|
+
*/
|
|
128
|
+
get: <T>(target: Api, path: string, params?: Record<string, string | number | boolean>, options?: RequestInit) => {
|
|
129
|
+
let url = path;
|
|
130
|
+
if (params) {
|
|
131
|
+
const searchParams = new URLSearchParams();
|
|
132
|
+
for (const [key, value] of Object.entries(params)) {
|
|
133
|
+
if (value !== undefined && value !== null) {
|
|
134
|
+
searchParams.set(key, String(value));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const queryString = searchParams.toString();
|
|
138
|
+
if (queryString) {
|
|
139
|
+
url = `${path}?${queryString}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return request<T>(target, url, { method: 'GET', ...options });
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* POST request
|
|
147
|
+
*/
|
|
148
|
+
post: <T>(target: Api, path: string, body?: unknown) =>
|
|
149
|
+
request<T>(target, path, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* PUT request
|
|
156
|
+
*/
|
|
157
|
+
put: <T>(target: Api, path: string, body?: unknown) =>
|
|
158
|
+
request<T>(target, path, {
|
|
159
|
+
method: 'PUT',
|
|
160
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* PATCH request
|
|
165
|
+
*/
|
|
166
|
+
patch: <T>(target: Api, path: string, body?: unknown) =>
|
|
167
|
+
request<T>(target, path, {
|
|
168
|
+
method: 'PATCH',
|
|
169
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* DELETE request
|
|
174
|
+
*/
|
|
175
|
+
delete: <T>(target: Api, path: string) =>
|
|
176
|
+
request<T>(target, path, { method: 'DELETE' }),
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the base URL for a target (useful for debugging)
|
|
180
|
+
*/
|
|
181
|
+
getBaseUrl: (target: Api = Api.Wallet) => {
|
|
182
|
+
const config = API_CONFIG[target];
|
|
183
|
+
return `${config.baseUrl}${config.pathPrefix}`;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Type definitions for common API responses
|
|
188
|
+
export interface WalletData {
|
|
189
|
+
address: string;
|
|
190
|
+
tier: 'cold' | 'hot' | 'temp';
|
|
191
|
+
chain: string;
|
|
192
|
+
balance?: string;
|
|
193
|
+
name?: string;
|
|
194
|
+
color?: string;
|
|
195
|
+
emoji?: string;
|
|
196
|
+
description?: string;
|
|
197
|
+
hidden?: boolean;
|
|
198
|
+
tokenHash?: string;
|
|
199
|
+
createdAt?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface WalletsResponse {
|
|
203
|
+
wallets: WalletData[];
|
|
204
|
+
unlocked: boolean;
|
|
205
|
+
agent?: { id: string; remaining: number };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface TrackedAsset {
|
|
209
|
+
id: string;
|
|
210
|
+
walletAddress: string;
|
|
211
|
+
tokenAddress: string;
|
|
212
|
+
symbol: string | null;
|
|
213
|
+
name: string | null;
|
|
214
|
+
decimals: number;
|
|
215
|
+
lastBalance: string | null;
|
|
216
|
+
lastBalanceAt: string | null;
|
|
217
|
+
isHidden: boolean;
|
|
218
|
+
chain: string;
|
|
219
|
+
poolAddress: string | null;
|
|
220
|
+
poolVersion: string | null;
|
|
221
|
+
icon: string | null;
|
|
222
|
+
createdAt: string;
|
|
223
|
+
updatedAt: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface AssetsResponse {
|
|
227
|
+
success: boolean;
|
|
228
|
+
assets: TrackedAsset[];
|
|
229
|
+
pagination: {
|
|
230
|
+
total: number;
|
|
231
|
+
limit: number;
|
|
232
|
+
offset: number;
|
|
233
|
+
hasMore: boolean;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface Transaction {
|
|
238
|
+
id: string;
|
|
239
|
+
walletAddress: string;
|
|
240
|
+
txHash: string | null;
|
|
241
|
+
type: string;
|
|
242
|
+
status: string;
|
|
243
|
+
amount: string | null;
|
|
244
|
+
tokenAddress: string | null;
|
|
245
|
+
tokenAmount: string | null;
|
|
246
|
+
from: string | null;
|
|
247
|
+
to: string | null;
|
|
248
|
+
description: string | null;
|
|
249
|
+
blockNumber: number | null;
|
|
250
|
+
chain: string;
|
|
251
|
+
createdAt: string;
|
|
252
|
+
updatedAt: string;
|
|
253
|
+
executedAt: string | null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface TransactionsResponse {
|
|
257
|
+
success: boolean;
|
|
258
|
+
transactions: Transaction[];
|
|
259
|
+
pagination: {
|
|
260
|
+
total: number;
|
|
261
|
+
limit: number;
|
|
262
|
+
offset: number;
|
|
263
|
+
hasMore: boolean;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface DashboardResponse {
|
|
268
|
+
success: boolean;
|
|
269
|
+
requests: Array<{
|
|
270
|
+
id: string;
|
|
271
|
+
type: string;
|
|
272
|
+
status: string;
|
|
273
|
+
createdAt: string;
|
|
274
|
+
metadata?: string;
|
|
275
|
+
chain: string;
|
|
276
|
+
amount?: string;
|
|
277
|
+
}>;
|
|
278
|
+
tokens: {
|
|
279
|
+
active: Array<{
|
|
280
|
+
tokenHash: string;
|
|
281
|
+
agentId: string;
|
|
282
|
+
limit: number;
|
|
283
|
+
spent: number;
|
|
284
|
+
remaining: number;
|
|
285
|
+
permissions: string[];
|
|
286
|
+
expiresAt: number;
|
|
287
|
+
isActive: boolean;
|
|
288
|
+
isRevoked: boolean;
|
|
289
|
+
isExpired: boolean;
|
|
290
|
+
}>;
|
|
291
|
+
inactive: Array<{
|
|
292
|
+
tokenHash: string;
|
|
293
|
+
agentId: string;
|
|
294
|
+
limit: number;
|
|
295
|
+
spent: number;
|
|
296
|
+
remaining: number;
|
|
297
|
+
permissions: string[];
|
|
298
|
+
expiresAt: number;
|
|
299
|
+
isActive: boolean;
|
|
300
|
+
isRevoked: boolean;
|
|
301
|
+
isExpired: boolean;
|
|
302
|
+
}>;
|
|
303
|
+
};
|
|
304
|
+
counts: {
|
|
305
|
+
pendingActions: number;
|
|
306
|
+
activeTokens: number;
|
|
307
|
+
inactiveTokens: number;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Encrypted Password Transport
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
// Cached server public key (cleared on failed decryption to force refetch)
|
|
316
|
+
let serverPublicKey: string | null = null;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Fetch the server's public key for encrypting passwords
|
|
320
|
+
* Key is cached until server restart (which invalidates it)
|
|
321
|
+
*/
|
|
322
|
+
async function getServerPublicKey(): Promise<string> {
|
|
323
|
+
if (!serverPublicKey) {
|
|
324
|
+
const res = await api.get<{ publicKey: string }>(Api.Wallet, '/auth/connect');
|
|
325
|
+
serverPublicKey = res.publicKey;
|
|
326
|
+
}
|
|
327
|
+
return serverPublicKey;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Clear cached public key (call on decryption failure to force refetch)
|
|
332
|
+
*/
|
|
333
|
+
export function clearServerPublicKey(): void {
|
|
334
|
+
serverPublicKey = null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface UnlockResponse {
|
|
338
|
+
success: boolean;
|
|
339
|
+
message?: string;
|
|
340
|
+
address?: string;
|
|
341
|
+
token?: string;
|
|
342
|
+
error?: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface SetupResponse {
|
|
346
|
+
success: boolean;
|
|
347
|
+
address?: string;
|
|
348
|
+
mnemonic?: string;
|
|
349
|
+
token?: string;
|
|
350
|
+
message?: string;
|
|
351
|
+
error?: string;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export interface ChangePrimaryPasswordResponse {
|
|
355
|
+
success: boolean;
|
|
356
|
+
message?: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Unlock the wallet with encrypted password transport
|
|
361
|
+
* @param password - Plaintext password (will be encrypted before sending)
|
|
362
|
+
* @param vaultId - Optional vault ID (defaults to primary vault)
|
|
363
|
+
* @returns Unlock response with optional admin token
|
|
364
|
+
*/
|
|
365
|
+
export async function unlockWallet(password: string, vaultId?: string, pubkey?: string): Promise<UnlockResponse> {
|
|
366
|
+
const publicKey = await getServerPublicKey();
|
|
367
|
+
const encrypted = await encryptPassword(password, publicKey);
|
|
368
|
+
const tokenPubkey = pubkey ?? await getTokenMintPubkey();
|
|
369
|
+
const path = vaultId ? `/unlock/${vaultId}` : '/unlock';
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
return await api.post<UnlockResponse>(Api.Wallet, path, { encrypted, pubkey: tokenPubkey });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
// If decryption failed on server, clear cached key and retry once
|
|
375
|
+
const error = err as Error & { status?: number };
|
|
376
|
+
if (error.message?.includes('decrypt') || error.message?.includes('refetch')) {
|
|
377
|
+
clearServerPublicKey();
|
|
378
|
+
const newKey = await getServerPublicKey();
|
|
379
|
+
const newEncrypted = await encryptPassword(password, newKey);
|
|
380
|
+
return await api.post<UnlockResponse>(Api.Wallet, path, { encrypted: newEncrypted, pubkey: tokenPubkey });
|
|
381
|
+
}
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Set up a new cold wallet with encrypted password transport
|
|
388
|
+
* @param password - Plaintext password (will be encrypted before sending)
|
|
389
|
+
* @returns Setup response with mnemonic
|
|
390
|
+
*/
|
|
391
|
+
export async function setupWallet(password: string, pubkey?: string): Promise<SetupResponse> {
|
|
392
|
+
const publicKey = await getServerPublicKey();
|
|
393
|
+
const encrypted = await encryptPassword(password, publicKey);
|
|
394
|
+
const tokenPubkey = pubkey ?? await getTokenMintPubkey();
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
return await api.post<SetupResponse>(Api.Wallet, '/setup', { encrypted, pubkey: tokenPubkey });
|
|
398
|
+
} catch (err) {
|
|
399
|
+
// If decryption failed on server, clear cached key and retry once
|
|
400
|
+
const error = err as Error & { status?: number };
|
|
401
|
+
if (error.message?.includes('decrypt') || error.message?.includes('refetch')) {
|
|
402
|
+
clearServerPublicKey();
|
|
403
|
+
const newKey = await getServerPublicKey();
|
|
404
|
+
const newEncrypted = await encryptPassword(password, newKey);
|
|
405
|
+
return await api.post<SetupResponse>(Api.Wallet, '/setup', { encrypted: newEncrypted, pubkey: tokenPubkey });
|
|
406
|
+
}
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Change the primary vault password with encrypted password transport.
|
|
413
|
+
*/
|
|
414
|
+
export async function changePrimaryVaultPassword(
|
|
415
|
+
currentPassword: string,
|
|
416
|
+
newPassword: string,
|
|
417
|
+
): Promise<ChangePrimaryPasswordResponse> {
|
|
418
|
+
const publicKey = await getServerPublicKey();
|
|
419
|
+
const currentEncrypted = await encryptPassword(currentPassword, publicKey);
|
|
420
|
+
const newEncrypted = await encryptPassword(newPassword, publicKey);
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
return await api.post<ChangePrimaryPasswordResponse>(Api.Wallet, '/setup/password', {
|
|
424
|
+
currentEncrypted,
|
|
425
|
+
newEncrypted,
|
|
426
|
+
});
|
|
427
|
+
} catch (err) {
|
|
428
|
+
const error = err as Error & { status?: number };
|
|
429
|
+
if (error.message?.includes('decrypt') || error.message?.includes('refetch')) {
|
|
430
|
+
clearServerPublicKey();
|
|
431
|
+
const newKey = await getServerPublicKey();
|
|
432
|
+
const retryCurrentEncrypted = await encryptPassword(currentPassword, newKey);
|
|
433
|
+
const retryNewEncrypted = await encryptPassword(newPassword, newKey);
|
|
434
|
+
return await api.post<ChangePrimaryPasswordResponse>(Api.Wallet, '/setup/password', {
|
|
435
|
+
currentEncrypted: retryCurrentEncrypted,
|
|
436
|
+
newEncrypted: retryNewEncrypted,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
throw err;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Re-key session with a new RSA public key (no password required).
|
|
445
|
+
* Used after page refresh when token survives but keypair is lost.
|
|
446
|
+
*/
|
|
447
|
+
export async function rekeySession(pubkey: string): Promise<{ success: boolean; token: string }> {
|
|
448
|
+
return api.post<{ success: boolean; token: string }>(Api.Wallet, '/unlock/rekey', { pubkey });
|
|
449
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface AppManifest {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
category: string;
|
|
9
|
+
size: { width: number; height: number };
|
|
10
|
+
permissions: string[];
|
|
11
|
+
data: string[];
|
|
12
|
+
description: string;
|
|
13
|
+
path: string;
|
|
14
|
+
hasUi: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse simple YAML frontmatter from app.md
|
|
19
|
+
* Handles key: value lines and arrays (- item)
|
|
20
|
+
*/
|
|
21
|
+
function parseFrontmatter(content: string): Record<string, unknown> {
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
const result: Record<string, unknown> = {};
|
|
24
|
+
|
|
25
|
+
let inFrontmatter = false;
|
|
26
|
+
let currentKey: string | null = null;
|
|
27
|
+
let currentArray: string[] | null = null;
|
|
28
|
+
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
|
|
32
|
+
if (trimmed === '---') {
|
|
33
|
+
if (inFrontmatter) break; // End of frontmatter
|
|
34
|
+
inFrontmatter = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!inFrontmatter) continue;
|
|
39
|
+
|
|
40
|
+
// Array item
|
|
41
|
+
if (trimmed.startsWith('- ') && currentKey) {
|
|
42
|
+
if (!currentArray) currentArray = [];
|
|
43
|
+
currentArray.push(trimmed.slice(2).trim());
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Save previous array
|
|
48
|
+
if (currentKey && currentArray) {
|
|
49
|
+
result[currentKey] = currentArray;
|
|
50
|
+
currentArray = null;
|
|
51
|
+
currentKey = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Key: value line
|
|
55
|
+
const colonIdx = trimmed.indexOf(':');
|
|
56
|
+
if (colonIdx > 0) {
|
|
57
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
58
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
59
|
+
|
|
60
|
+
if (value === '' || value === '[]') {
|
|
61
|
+
// Start of array or empty value or inline empty array
|
|
62
|
+
currentKey = key;
|
|
63
|
+
currentArray = [];
|
|
64
|
+
} else {
|
|
65
|
+
result[key] = value;
|
|
66
|
+
currentKey = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Save trailing array
|
|
72
|
+
if (currentKey && currentArray) {
|
|
73
|
+
result[currentKey] = currentArray;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract description from markdown body (after frontmatter)
|
|
81
|
+
*/
|
|
82
|
+
function extractDescription(content: string): string {
|
|
83
|
+
const parts = content.split('---');
|
|
84
|
+
if (parts.length < 3) return '';
|
|
85
|
+
// Everything after the second --- is the body
|
|
86
|
+
const body = parts.slice(2).join('---').trim();
|
|
87
|
+
// Take first paragraph
|
|
88
|
+
const firstParagraph = body.split('\n\n')[0];
|
|
89
|
+
return firstParagraph.replace(/^#+\s*/, '').trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse size string like "2x2" into { width, height }
|
|
94
|
+
* Grid units: 1 = 320px width, 280px height
|
|
95
|
+
*/
|
|
96
|
+
function parseSize(size: string): { width: number; height: number } {
|
|
97
|
+
const match = size.match(/^(\d+)x(\d+)$/);
|
|
98
|
+
if (!match) return { width: 320, height: 280 };
|
|
99
|
+
return {
|
|
100
|
+
width: parseInt(match[1]) * 320,
|
|
101
|
+
height: parseInt(match[2]) * 280,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Load all app manifests from the apps/ directory
|
|
107
|
+
*/
|
|
108
|
+
export function loadAppManifests(): AppManifest[] {
|
|
109
|
+
const appsDir = path.join(process.cwd(), 'apps');
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(appsDir)) return [];
|
|
112
|
+
|
|
113
|
+
const manifests: AppManifest[] = [];
|
|
114
|
+
|
|
115
|
+
const entries = fs.readdirSync(appsDir, { withFileTypes: true });
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (!entry.isDirectory()) continue;
|
|
118
|
+
|
|
119
|
+
const appMdPath = path.join(appsDir, entry.name, 'app.md');
|
|
120
|
+
const indexHtmlPath = path.join(appsDir, entry.name, 'index.html');
|
|
121
|
+
|
|
122
|
+
if (!fs.existsSync(appMdPath)) continue;
|
|
123
|
+
const hasUi = fs.existsSync(indexHtmlPath);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const content = fs.readFileSync(appMdPath, 'utf-8');
|
|
127
|
+
const fm = parseFrontmatter(content);
|
|
128
|
+
const description = extractDescription(content);
|
|
129
|
+
|
|
130
|
+
manifests.push({
|
|
131
|
+
id: entry.name,
|
|
132
|
+
name: (fm.name as string) || entry.name,
|
|
133
|
+
icon: (fm.icon as string) || 'Box',
|
|
134
|
+
category: (fm.category as string) || 'general',
|
|
135
|
+
size: parseSize((fm.size as string) || '1x1'),
|
|
136
|
+
permissions: (fm.permissions as string[]) || [],
|
|
137
|
+
data: (fm.data as string[]) || [],
|
|
138
|
+
description,
|
|
139
|
+
path: entry.name,
|
|
140
|
+
hasUi,
|
|
141
|
+
});
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(`[app-loader] Failed to load app ${entry.name}:`, err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return manifests;
|
|
148
|
+
}
|