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
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Installer
|
|
3
|
+
* ================
|
|
4
|
+
* Core logic for installing, removing, listing, and updating apps
|
|
5
|
+
* from external sources (git repos, tarballs, zips, local paths).
|
|
6
|
+
*
|
|
7
|
+
* Used by both CLI (server/cli/commands/app.ts) and
|
|
8
|
+
* dashboard API (src/app/api/apps/install/route.ts).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import { execFileSync } from 'child_process';
|
|
15
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
|
+
import { getErrorMessage } from './error';
|
|
17
|
+
import { getDefaultSync } from './defaults';
|
|
18
|
+
|
|
19
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface SourceInfo {
|
|
22
|
+
type: 'git' | 'tarball' | 'zip' | 'local';
|
|
23
|
+
url: string;
|
|
24
|
+
ref: string | null;
|
|
25
|
+
subdir: string | null;
|
|
26
|
+
installedAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface InstalledApp {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
permissions: string[];
|
|
34
|
+
source: SourceInfo | null;
|
|
35
|
+
path: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface InstallOptions {
|
|
39
|
+
name?: string;
|
|
40
|
+
force?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface InstallResult {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
path: string;
|
|
47
|
+
source: SourceInfo;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function getMaxFileSize(): number {
|
|
53
|
+
return getDefaultSync<number>('app.max_file_size_mb', 5) * 1024 * 1024;
|
|
54
|
+
}
|
|
55
|
+
function getMaxTotalSize(): number {
|
|
56
|
+
return getDefaultSync<number>('app.max_total_size_mb', 20) * 1024 * 1024;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function appsDir(): string {
|
|
60
|
+
return path.join(process.cwd(), 'apps');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Source Detection ───────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
interface ParsedSource {
|
|
66
|
+
type: 'git' | 'tarball' | 'zip' | 'local';
|
|
67
|
+
url: string;
|
|
68
|
+
subdir: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseSource(source: string): ParsedSource {
|
|
72
|
+
let subdir: string | null = null;
|
|
73
|
+
|
|
74
|
+
// Extract #path=subdir fragment
|
|
75
|
+
const hashIdx = source.indexOf('#path=');
|
|
76
|
+
let cleanSource = source;
|
|
77
|
+
if (hashIdx !== -1) {
|
|
78
|
+
subdir = source.slice(hashIdx + 6);
|
|
79
|
+
cleanSource = source.slice(0, hashIdx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Local path
|
|
83
|
+
if (cleanSource.startsWith('.') || cleanSource.startsWith('/')) {
|
|
84
|
+
return { type: 'local', url: cleanSource, subdir };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Tarball
|
|
88
|
+
if (cleanSource.endsWith('.tar.gz') || cleanSource.endsWith('.tgz')) {
|
|
89
|
+
return { type: 'tarball', url: cleanSource, subdir };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Zip
|
|
93
|
+
if (cleanSource.endsWith('.zip')) {
|
|
94
|
+
return { type: 'zip', url: cleanSource, subdir };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Git (github shorthand or URL)
|
|
98
|
+
let gitUrl = cleanSource;
|
|
99
|
+
if (gitUrl.startsWith('git@') || gitUrl.startsWith('ext::')) {
|
|
100
|
+
throw new Error('Only HTTPS git URLs are allowed. git@ and ext:: transports are rejected for security.');
|
|
101
|
+
}
|
|
102
|
+
if (!gitUrl.startsWith('http://') && !gitUrl.startsWith('https://')) {
|
|
103
|
+
gitUrl = `https://${gitUrl}`;
|
|
104
|
+
}
|
|
105
|
+
// Ensure .git suffix for HTTPS URLs
|
|
106
|
+
if (gitUrl.startsWith('https://') && !gitUrl.endsWith('.git')) {
|
|
107
|
+
gitUrl = `${gitUrl}.git`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { type: 'git', url: gitUrl, subdir };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Validation ─────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
function validateApp(dir: string): { name: string; description: string; permissions: string[] } {
|
|
116
|
+
const appMdPath = path.join(dir, 'app.md');
|
|
117
|
+
|
|
118
|
+
if (!fs.existsSync(appMdPath)) {
|
|
119
|
+
throw new Error('Missing app.md manifest');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Parse YAML frontmatter
|
|
123
|
+
const raw = fs.readFileSync(appMdPath, 'utf-8');
|
|
124
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
125
|
+
if (!match) {
|
|
126
|
+
throw new Error('app.md missing YAML frontmatter (--- delimiters)');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let manifest: Record<string, unknown>;
|
|
130
|
+
try {
|
|
131
|
+
manifest = parseYaml(match[1]);
|
|
132
|
+
} catch {
|
|
133
|
+
throw new Error('app.md has invalid YAML frontmatter');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!manifest) {
|
|
137
|
+
throw new Error('app.md has empty YAML frontmatter');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Extract description from body
|
|
141
|
+
const parts = raw.split('---');
|
|
142
|
+
const body = parts.length >= 3 ? parts.slice(2).join('---').trim() : '';
|
|
143
|
+
const description = body.split('\n\n')[0].replace(/^#+\s*/, '').trim();
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
name: (manifest.name as string) || '',
|
|
147
|
+
description,
|
|
148
|
+
permissions: (manifest.permissions as string[]) || [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function checkSizeAndSymlinks(dir: string): void {
|
|
153
|
+
const maxFileSize = getMaxFileSize();
|
|
154
|
+
const maxTotalSize = getMaxTotalSize();
|
|
155
|
+
let totalSize = 0;
|
|
156
|
+
|
|
157
|
+
function walk(current: string): void {
|
|
158
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
const fullPath = path.join(current, entry.name);
|
|
161
|
+
|
|
162
|
+
// Check for symlinks escaping the directory
|
|
163
|
+
if (entry.isSymbolicLink()) {
|
|
164
|
+
const resolved = fs.realpathSync(fullPath);
|
|
165
|
+
if (!resolved.startsWith(dir)) {
|
|
166
|
+
throw new Error(`Symlink escapes app directory: ${entry.name}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
walk(fullPath);
|
|
172
|
+
} else if (entry.isFile()) {
|
|
173
|
+
const stat = fs.statSync(fullPath);
|
|
174
|
+
if (stat.size > maxFileSize) {
|
|
175
|
+
throw new Error(`File too large (>${maxFileSize / (1024 * 1024)}MB): ${path.relative(dir, fullPath)}`);
|
|
176
|
+
}
|
|
177
|
+
totalSize += stat.size;
|
|
178
|
+
if (totalSize > maxTotalSize) {
|
|
179
|
+
throw new Error(`Total app size exceeds ${maxTotalSize / (1024 * 1024)}MB limit`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
walk(dir);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Fetch to Temp Dir ──────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
function fetchSource(parsed: ParsedSource, tmpDir: string): string {
|
|
191
|
+
switch (parsed.type) {
|
|
192
|
+
case 'local': {
|
|
193
|
+
const absPath = path.resolve(parsed.url);
|
|
194
|
+
if (!fs.existsSync(absPath)) {
|
|
195
|
+
throw new Error(`Local path not found: ${absPath}`);
|
|
196
|
+
}
|
|
197
|
+
// Copy to temp dir
|
|
198
|
+
copyDirSync(absPath, tmpDir);
|
|
199
|
+
return tmpDir;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case 'git': {
|
|
203
|
+
try {
|
|
204
|
+
execFileSync('git', ['clone', '--depth', '1', parsed.url, tmpDir], {
|
|
205
|
+
stdio: 'pipe',
|
|
206
|
+
timeout: 60000,
|
|
207
|
+
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const msg = getErrorMessage(err);
|
|
210
|
+
throw new Error(`git clone failed: ${msg}`);
|
|
211
|
+
}
|
|
212
|
+
// Remove .git directory
|
|
213
|
+
const gitDir = path.join(tmpDir, '.git');
|
|
214
|
+
if (fs.existsSync(gitDir)) {
|
|
215
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
return tmpDir;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case 'tarball': {
|
|
221
|
+
const archivePath = path.join(tmpDir, 'archive.tar.gz');
|
|
222
|
+
downloadFile(parsed.url, archivePath);
|
|
223
|
+
const extractDir = path.join(tmpDir, 'extracted');
|
|
224
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
225
|
+
try {
|
|
226
|
+
execFileSync('tar', ['xzf', archivePath, '-C', extractDir], {
|
|
227
|
+
stdio: 'pipe',
|
|
228
|
+
timeout: 30000,
|
|
229
|
+
});
|
|
230
|
+
} catch {
|
|
231
|
+
throw new Error('Failed to extract tarball');
|
|
232
|
+
}
|
|
233
|
+
// If tarball contained a single directory, use that
|
|
234
|
+
return findAppRoot(extractDir);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
case 'zip': {
|
|
238
|
+
const archivePath = path.join(tmpDir, 'archive.zip');
|
|
239
|
+
downloadFile(parsed.url, archivePath);
|
|
240
|
+
const extractDir = path.join(tmpDir, 'extracted');
|
|
241
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
242
|
+
try {
|
|
243
|
+
execFileSync('unzip', ['-o', archivePath, '-d', extractDir], {
|
|
244
|
+
stdio: 'pipe',
|
|
245
|
+
timeout: 30000,
|
|
246
|
+
});
|
|
247
|
+
} catch {
|
|
248
|
+
throw new Error('Failed to extract zip');
|
|
249
|
+
}
|
|
250
|
+
return findAppRoot(extractDir);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** If extracted archive has a single subdirectory, use that as root */
|
|
256
|
+
function findAppRoot(dir: string): string {
|
|
257
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
258
|
+
const dirs = entries.filter(e => e.isDirectory());
|
|
259
|
+
|
|
260
|
+
// If there's a single directory and no app.md at root level, descend
|
|
261
|
+
if (dirs.length === 1 && !fs.existsSync(path.join(dir, 'app.md'))) {
|
|
262
|
+
return path.join(dir, dirs[0].name);
|
|
263
|
+
}
|
|
264
|
+
return dir;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function downloadFile(url: string, dest: string): void {
|
|
268
|
+
// Use curl for downloading — universally available, restricted to HTTPS
|
|
269
|
+
try {
|
|
270
|
+
execFileSync('curl', ['-fsSL', '--proto', '=https', '-o', dest, url], {
|
|
271
|
+
stdio: 'pipe',
|
|
272
|
+
timeout: 60000,
|
|
273
|
+
});
|
|
274
|
+
} catch {
|
|
275
|
+
throw new Error(`Failed to download: ${url}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function copyDirSync(src: string, dest: string): void {
|
|
280
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
281
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
282
|
+
const srcPath = path.join(src, entry.name);
|
|
283
|
+
const destPath = path.join(dest, entry.name);
|
|
284
|
+
if (entry.isSymbolicLink()) {
|
|
285
|
+
// Preserve symlinks (checkSizeAndSymlinks validates them later)
|
|
286
|
+
const target = fs.readlinkSync(srcPath);
|
|
287
|
+
fs.symlinkSync(target, destPath);
|
|
288
|
+
} else if (entry.isDirectory()) {
|
|
289
|
+
copyDirSync(srcPath, destPath);
|
|
290
|
+
} else {
|
|
291
|
+
fs.copyFileSync(srcPath, destPath);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Derive a app ID from the source URL/path */
|
|
297
|
+
function deriveAppId(parsed: ParsedSource): string {
|
|
298
|
+
if (parsed.subdir) {
|
|
299
|
+
return path.basename(parsed.subdir);
|
|
300
|
+
}
|
|
301
|
+
switch (parsed.type) {
|
|
302
|
+
case 'local':
|
|
303
|
+
return path.basename(path.resolve(parsed.url));
|
|
304
|
+
case 'git': {
|
|
305
|
+
// https://github.com/user/my-app.git → my-app
|
|
306
|
+
const name = path.basename(parsed.url).replace(/\.git$/, '');
|
|
307
|
+
return name || 'app';
|
|
308
|
+
}
|
|
309
|
+
case 'tarball': {
|
|
310
|
+
// https://example.com/my-app.tar.gz → my-app
|
|
311
|
+
return path.basename(parsed.url).replace(/\.(tar\.gz|tgz)$/, '') || 'app';
|
|
312
|
+
}
|
|
313
|
+
case 'zip': {
|
|
314
|
+
return path.basename(parsed.url).replace(/\.zip$/, '') || 'app';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─── Public API ─────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Install a app from a source (git URL, tarball, zip, or local path).
|
|
323
|
+
*/
|
|
324
|
+
export function installApp(source: string, opts: InstallOptions = {}): InstallResult {
|
|
325
|
+
const parsed = parseSource(source);
|
|
326
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aura-app-'));
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
// Fetch source to temp directory
|
|
330
|
+
let appRoot = fetchSource(parsed, tmpDir);
|
|
331
|
+
|
|
332
|
+
// Handle #path=subdir
|
|
333
|
+
if (parsed.subdir) {
|
|
334
|
+
const subPath = path.join(appRoot, parsed.subdir);
|
|
335
|
+
if (!fs.existsSync(subPath)) {
|
|
336
|
+
throw new Error(`Subdirectory not found: ${parsed.subdir}`);
|
|
337
|
+
}
|
|
338
|
+
appRoot = subPath;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Validate the app
|
|
342
|
+
const manifest = validateApp(appRoot);
|
|
343
|
+
checkSizeAndSymlinks(appRoot);
|
|
344
|
+
|
|
345
|
+
// Determine app ID
|
|
346
|
+
const appId = opts.name || deriveAppId(parsed);
|
|
347
|
+
if (!appId || appId === '.' || appId === '..') {
|
|
348
|
+
throw new Error('Could not determine app ID. Use --name to specify one.');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Check for conflicts
|
|
352
|
+
const targetDir = path.join(appsDir(), appId);
|
|
353
|
+
if (fs.existsSync(targetDir)) {
|
|
354
|
+
if (!opts.force) {
|
|
355
|
+
throw new Error(`App "${appId}" already exists. Use --force to overwrite.`);
|
|
356
|
+
}
|
|
357
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Ensure apps/ directory exists
|
|
361
|
+
fs.mkdirSync(appsDir(), { recursive: true });
|
|
362
|
+
|
|
363
|
+
// Copy to final location
|
|
364
|
+
copyDirSync(appRoot, targetDir);
|
|
365
|
+
|
|
366
|
+
// Write .source.json for provenance
|
|
367
|
+
const sourceInfo: SourceInfo = {
|
|
368
|
+
type: parsed.type,
|
|
369
|
+
url: parsed.type === 'local' ? path.resolve(parsed.url) : parsed.url,
|
|
370
|
+
ref: null,
|
|
371
|
+
subdir: parsed.subdir,
|
|
372
|
+
installedAt: new Date().toISOString(),
|
|
373
|
+
};
|
|
374
|
+
fs.writeFileSync(
|
|
375
|
+
path.join(targetDir, '.source.json'),
|
|
376
|
+
JSON.stringify(sourceInfo, null, 2) + '\n',
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
id: appId,
|
|
381
|
+
name: manifest.name || appId,
|
|
382
|
+
path: targetDir,
|
|
383
|
+
source: sourceInfo,
|
|
384
|
+
};
|
|
385
|
+
} finally {
|
|
386
|
+
// Cleanup temp dir
|
|
387
|
+
try {
|
|
388
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
389
|
+
} catch {
|
|
390
|
+
// Best-effort cleanup
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Remove an installed app by ID.
|
|
397
|
+
*/
|
|
398
|
+
export function removeApp(id: string): void {
|
|
399
|
+
const targetDir = path.join(appsDir(), id);
|
|
400
|
+
|
|
401
|
+
if (!fs.existsSync(targetDir)) {
|
|
402
|
+
throw new Error(`App "${id}" not found`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Safety check: must have app.md
|
|
406
|
+
if (!fs.existsSync(path.join(targetDir, 'app.md'))) {
|
|
407
|
+
throw new Error(`"${id}" does not appear to be a app (no app.md)`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* List all installed apps with source info.
|
|
415
|
+
*/
|
|
416
|
+
export function listApps(): InstalledApp[] {
|
|
417
|
+
const dir = appsDir();
|
|
418
|
+
if (!fs.existsSync(dir)) return [];
|
|
419
|
+
|
|
420
|
+
const apps: InstalledApp[] = [];
|
|
421
|
+
|
|
422
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
423
|
+
if (!entry.isDirectory()) continue;
|
|
424
|
+
|
|
425
|
+
const appDir = path.join(dir, entry.name);
|
|
426
|
+
const mdPath = path.join(appDir, 'app.md');
|
|
427
|
+
if (!fs.existsSync(mdPath)) continue;
|
|
428
|
+
|
|
429
|
+
// Read manifest
|
|
430
|
+
let name = entry.name;
|
|
431
|
+
let description = '';
|
|
432
|
+
let permissions: string[] = [];
|
|
433
|
+
try {
|
|
434
|
+
const raw = fs.readFileSync(mdPath, 'utf-8');
|
|
435
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
436
|
+
if (match) {
|
|
437
|
+
const manifest = parseYaml(match[1]);
|
|
438
|
+
if (manifest) {
|
|
439
|
+
name = (manifest.name as string) || entry.name;
|
|
440
|
+
permissions = (manifest.permissions as string[]) || [];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const parts = raw.split('---');
|
|
444
|
+
if (parts.length >= 3) {
|
|
445
|
+
const body = parts.slice(2).join('---').trim();
|
|
446
|
+
description = body.split('\n\n')[0].replace(/^#+\s*/, '').trim();
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
// Use defaults
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Read .source.json if it exists
|
|
453
|
+
let source: SourceInfo | null = null;
|
|
454
|
+
const sourcePath = path.join(appDir, '.source.json');
|
|
455
|
+
if (fs.existsSync(sourcePath)) {
|
|
456
|
+
try {
|
|
457
|
+
source = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'));
|
|
458
|
+
} catch {
|
|
459
|
+
// Ignore parse errors
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
apps.push({
|
|
464
|
+
id: entry.name,
|
|
465
|
+
name,
|
|
466
|
+
description,
|
|
467
|
+
permissions,
|
|
468
|
+
source,
|
|
469
|
+
path: appDir,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return apps;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Update a app by re-installing from its original source.
|
|
478
|
+
*/
|
|
479
|
+
export function updateApp(id: string): InstallResult {
|
|
480
|
+
const targetDir = path.join(appsDir(), id);
|
|
481
|
+
const sourcePath = path.join(targetDir, '.source.json');
|
|
482
|
+
|
|
483
|
+
if (!fs.existsSync(targetDir)) {
|
|
484
|
+
throw new Error(`App "${id}" not found`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!fs.existsSync(sourcePath)) {
|
|
488
|
+
throw new Error(`App "${id}" has no .source.json — cannot determine original source`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let sourceInfo: SourceInfo;
|
|
492
|
+
try {
|
|
493
|
+
sourceInfo = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'));
|
|
494
|
+
} catch {
|
|
495
|
+
throw new Error(`App "${id}" has invalid .source.json`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Re-construct the source string
|
|
499
|
+
let source = sourceInfo.url;
|
|
500
|
+
if (sourceInfo.subdir) {
|
|
501
|
+
source += `#path=${sourceInfo.subdir}`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return installApp(source, { name: id, force: true });
|
|
505
|
+
}
|