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,328 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { expandPermissions } from './permissions';
|
|
3
|
+
import type { AgentTokenPayload } from '../types';
|
|
4
|
+
import { normalizeScope } from './credential-scope';
|
|
5
|
+
|
|
6
|
+
export type AgentProfileId =
|
|
7
|
+
| 'strict'
|
|
8
|
+
| 'dev'
|
|
9
|
+
| 'admin'
|
|
10
|
+
| 'observer'
|
|
11
|
+
| 'deploy-bot'
|
|
12
|
+
| 'rotation-bot'
|
|
13
|
+
| 'trader';
|
|
14
|
+
|
|
15
|
+
export const LOCAL_AGENT_PROFILE_MODES = ['strict', 'dev', 'admin'] as const;
|
|
16
|
+
export type LocalAgentProfileMode = typeof LOCAL_AGENT_PROFILE_MODES[number];
|
|
17
|
+
|
|
18
|
+
export interface AgentPolicyProfileV1 {
|
|
19
|
+
id: AgentProfileId;
|
|
20
|
+
version: 'v1';
|
|
21
|
+
displayName: string;
|
|
22
|
+
description: string;
|
|
23
|
+
rationale: string;
|
|
24
|
+
permissions: string[];
|
|
25
|
+
credentialAccess: {
|
|
26
|
+
readScopes: string[];
|
|
27
|
+
writeScopes: string[];
|
|
28
|
+
excludeFields: string[];
|
|
29
|
+
maxReads?: number;
|
|
30
|
+
};
|
|
31
|
+
tokenDefaults: {
|
|
32
|
+
ttlSeconds: number;
|
|
33
|
+
maxReads?: number;
|
|
34
|
+
};
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ResolveProfileInput {
|
|
39
|
+
profileId: string;
|
|
40
|
+
profileVersion?: string;
|
|
41
|
+
overrides?: {
|
|
42
|
+
ttlSeconds?: number;
|
|
43
|
+
maxReads?: number;
|
|
44
|
+
readScopes?: string[];
|
|
45
|
+
writeScopes?: string[];
|
|
46
|
+
excludeFields?: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ResolvedProfilePolicy {
|
|
51
|
+
profile: {
|
|
52
|
+
id: AgentProfileId;
|
|
53
|
+
version: 'v1';
|
|
54
|
+
displayName: string;
|
|
55
|
+
rationale: string;
|
|
56
|
+
};
|
|
57
|
+
permissions: string[];
|
|
58
|
+
ttlSeconds: number;
|
|
59
|
+
credentialAccess: NonNullable<AgentTokenPayload['credentialAccess']>;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
overrideDelta: string[];
|
|
62
|
+
effectivePolicyHash: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class AgentProfileError extends Error {
|
|
66
|
+
code: string;
|
|
67
|
+
constructor(code: string, message: string) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = 'AgentProfileError';
|
|
70
|
+
this.code = code;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const BUILTIN_PROFILES: AgentPolicyProfileV1[] = [
|
|
75
|
+
{
|
|
76
|
+
id: 'strict',
|
|
77
|
+
version: 'v1',
|
|
78
|
+
displayName: 'Strict',
|
|
79
|
+
description: 'Manual-approval-first baseline profile for local agents.',
|
|
80
|
+
rationale: 'Use when you want every agent token request reviewed by a human.',
|
|
81
|
+
permissions: ['secret:read'],
|
|
82
|
+
credentialAccess: {
|
|
83
|
+
readScopes: ['vault:*'],
|
|
84
|
+
writeScopes: [],
|
|
85
|
+
excludeFields: ['password', 'cvv', 'privateKey', 'seedPhrase', 'refresh_token'],
|
|
86
|
+
maxReads: 50,
|
|
87
|
+
},
|
|
88
|
+
tokenDefaults: { ttlSeconds: 900, maxReads: 50 },
|
|
89
|
+
warnings: ['Pair with trust.localAutoApprove=false for strict local approval flow.'],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'dev',
|
|
93
|
+
version: 'v1',
|
|
94
|
+
displayName: 'Dev',
|
|
95
|
+
description: 'Developer profile for local agent automation with non-financial scope.',
|
|
96
|
+
rationale: 'Use for day-to-day local dev workflows without granting financial operations.',
|
|
97
|
+
permissions: ['wallet:list', 'secret:read', 'secret:write', 'action:create', 'action:read', 'action:resolve'],
|
|
98
|
+
credentialAccess: {
|
|
99
|
+
readScopes: ['vault:*'],
|
|
100
|
+
writeScopes: ['vault:*'],
|
|
101
|
+
excludeFields: ['cvv', 'seedPhrase', 'privateKey', 'refresh_token'],
|
|
102
|
+
maxReads: 500,
|
|
103
|
+
},
|
|
104
|
+
tokenDefaults: { ttlSeconds: 3600, maxReads: 500 },
|
|
105
|
+
warnings: ['Includes secret:write. Prefer dedicated non-primary vaults for agent-managed credentials.'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'admin',
|
|
109
|
+
version: 'v1',
|
|
110
|
+
displayName: 'Admin',
|
|
111
|
+
description: 'Full-access local agent profile.',
|
|
112
|
+
rationale: 'Use only when you explicitly trust the local agent process with broad access.',
|
|
113
|
+
permissions: ['admin:*'],
|
|
114
|
+
credentialAccess: {
|
|
115
|
+
readScopes: ['*'],
|
|
116
|
+
writeScopes: ['*'],
|
|
117
|
+
excludeFields: [],
|
|
118
|
+
},
|
|
119
|
+
tokenDefaults: { ttlSeconds: 3600 },
|
|
120
|
+
warnings: ['Dangerous profile: grants admin:* (full access). Not recommended for primary vault workflows.'],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'observer',
|
|
124
|
+
version: 'v1',
|
|
125
|
+
displayName: 'Observer',
|
|
126
|
+
description: 'Read-only observer with strong field redaction defaults.',
|
|
127
|
+
rationale: 'Use for diagnostics and reporting agents that should not mutate secrets.',
|
|
128
|
+
permissions: ['secret:read'],
|
|
129
|
+
credentialAccess: {
|
|
130
|
+
readScopes: ['vault:*'],
|
|
131
|
+
writeScopes: [],
|
|
132
|
+
excludeFields: ['password', 'cvv', 'privateKey', 'seedPhrase', 'refresh_token'],
|
|
133
|
+
maxReads: 500,
|
|
134
|
+
},
|
|
135
|
+
tokenDefaults: { ttlSeconds: 7200, maxReads: 500 },
|
|
136
|
+
warnings: ['Read-only profile. Sensitive fields are redacted by default.'],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'deploy-bot',
|
|
140
|
+
version: 'v1',
|
|
141
|
+
displayName: 'Deploy Bot',
|
|
142
|
+
description: 'Project-scoped read profile for runtime/deploy credentials.',
|
|
143
|
+
rationale: 'Use for CI/CD agents that only need narrow secret reads.',
|
|
144
|
+
permissions: ['secret:read'],
|
|
145
|
+
credentialAccess: {
|
|
146
|
+
readScopes: ['tag:deploy', 'tag:runtime'],
|
|
147
|
+
writeScopes: [],
|
|
148
|
+
excludeFields: ['cvv', 'seedPhrase', 'privateKey'],
|
|
149
|
+
maxReads: 200,
|
|
150
|
+
},
|
|
151
|
+
tokenDefaults: { ttlSeconds: 3600, maxReads: 200 },
|
|
152
|
+
warnings: ['Ensure deployment credentials are tagged explicitly.'],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'rotation-bot',
|
|
156
|
+
version: 'v1',
|
|
157
|
+
displayName: 'Rotation Bot',
|
|
158
|
+
description: 'Secret rotation profile with constrained read/write selectors.',
|
|
159
|
+
rationale: 'Use for automated credential rotation where write access is required.',
|
|
160
|
+
permissions: ['secret:read', 'secret:write'],
|
|
161
|
+
credentialAccess: {
|
|
162
|
+
readScopes: ['tag:rotation'],
|
|
163
|
+
writeScopes: ['tag:rotation'],
|
|
164
|
+
excludeFields: ['seedPhrase'],
|
|
165
|
+
maxReads: 100,
|
|
166
|
+
},
|
|
167
|
+
tokenDefaults: { ttlSeconds: 1800, maxReads: 100 },
|
|
168
|
+
warnings: ['Includes secret:write. Restrict scope tags to explicit rotation inventory.'],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'trader',
|
|
172
|
+
version: 'v1',
|
|
173
|
+
displayName: 'Trader',
|
|
174
|
+
description: 'Trading profile with trade permissions plus scoped secret read.',
|
|
175
|
+
rationale: 'Use for trading automation with explicit risk review.',
|
|
176
|
+
permissions: ['trade:all', 'secret:read'],
|
|
177
|
+
credentialAccess: {
|
|
178
|
+
readScopes: ['tag:trading', 'tag:exchange'],
|
|
179
|
+
writeScopes: [],
|
|
180
|
+
excludeFields: ['seedPhrase', 'privateKey'],
|
|
181
|
+
maxReads: 300,
|
|
182
|
+
},
|
|
183
|
+
tokenDefaults: { ttlSeconds: 1200, maxReads: 300 },
|
|
184
|
+
warnings: ['High-risk profile: includes trading permissions. Verify wallet limits separately.'],
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
export function listProfiles(): AgentPolicyProfileV1[] {
|
|
189
|
+
return BUILTIN_PROFILES.map((p) => ({ ...p }));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getProfile(id: string, version: string = 'v1'): AgentPolicyProfileV1 {
|
|
193
|
+
const anyVersion = BUILTIN_PROFILES.find((p) => p.id === id);
|
|
194
|
+
if (!anyVersion) {
|
|
195
|
+
throw new AgentProfileError('AGENT_PROFILE_NOT_FOUND', `Unknown profile: ${id}@${version}`);
|
|
196
|
+
}
|
|
197
|
+
const profile = BUILTIN_PROFILES.find((p) => p.id === id && p.version === version);
|
|
198
|
+
if (!profile) {
|
|
199
|
+
throw new AgentProfileError('AGENT_PROFILE_VERSION_UNSUPPORTED', `Unsupported profile version: ${id}@${version}`);
|
|
200
|
+
}
|
|
201
|
+
return profile;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function canonicalScopes(scopes: string[] | undefined): string[] {
|
|
205
|
+
const normalized = (scopes || []).map(normalizeScope);
|
|
206
|
+
if (normalized.some((scope) => scope.includes('**') || scope.includes('(') || scope.includes(')'))) {
|
|
207
|
+
throw new AgentProfileError('AGENT_PROFILE_SCOPE_INVALID', 'Scope selectors contain unsupported wildcard/pattern syntax');
|
|
208
|
+
}
|
|
209
|
+
return Array.from(new Set(normalized)).sort();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function canonicalStringList(values: string[] | undefined): string[] {
|
|
213
|
+
return Array.from(new Set((values || []).map((v) => v.trim()).filter(Boolean))).sort();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function hashPolicy(policy: Omit<ResolvedProfilePolicy, 'effectivePolicyHash'>): string {
|
|
217
|
+
const stable = JSON.stringify({
|
|
218
|
+
permissions: Array.from(new Set(policy.permissions)).sort(),
|
|
219
|
+
credentialAccess: {
|
|
220
|
+
read: Array.from(new Set(policy.credentialAccess.read || [])).sort(),
|
|
221
|
+
write: Array.from(new Set(policy.credentialAccess.write || [])).sort(),
|
|
222
|
+
excludeFields: Array.from(new Set(policy.credentialAccess.excludeFields || [])).sort(),
|
|
223
|
+
maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
|
|
224
|
+
},
|
|
225
|
+
ttlSeconds: policy.ttlSeconds,
|
|
226
|
+
maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
|
|
227
|
+
rateBudget: {
|
|
228
|
+
state: 'none',
|
|
229
|
+
requests: null,
|
|
230
|
+
windowSeconds: null,
|
|
231
|
+
source: 'none',
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
return createHash('sha256').update(stable).digest('hex');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resolveProfileToEffectivePolicy(input: ResolveProfileInput): ResolvedProfilePolicy {
|
|
238
|
+
const profile = getProfile(input.profileId, input.profileVersion || 'v1');
|
|
239
|
+
const overrides = input.overrides || {};
|
|
240
|
+
|
|
241
|
+
const profileReadScopes = canonicalScopes(profile.credentialAccess.readScopes);
|
|
242
|
+
const profileWriteScopes = canonicalScopes(profile.credentialAccess.writeScopes);
|
|
243
|
+
const profileExclude = canonicalStringList(profile.credentialAccess.excludeFields);
|
|
244
|
+
|
|
245
|
+
const overrideDelta: string[] = [];
|
|
246
|
+
|
|
247
|
+
let ttlSeconds = profile.tokenDefaults.ttlSeconds;
|
|
248
|
+
if (overrides.ttlSeconds !== undefined) {
|
|
249
|
+
if (overrides.ttlSeconds <= 0 || overrides.ttlSeconds > ttlSeconds) {
|
|
250
|
+
throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'ttlSeconds override must be positive and tighten-only');
|
|
251
|
+
}
|
|
252
|
+
ttlSeconds = overrides.ttlSeconds;
|
|
253
|
+
overrideDelta.push('ttlSeconds');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let maxReads = profile.credentialAccess.maxReads ?? profile.tokenDefaults.maxReads;
|
|
257
|
+
if (overrides.maxReads !== undefined) {
|
|
258
|
+
if (overrides.maxReads <= 0 || (typeof maxReads === 'number' && overrides.maxReads > maxReads)) {
|
|
259
|
+
throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'maxReads override must be positive and tighten-only');
|
|
260
|
+
}
|
|
261
|
+
maxReads = overrides.maxReads;
|
|
262
|
+
overrideDelta.push('maxReads');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const readScopes = overrides.readScopes ? canonicalScopes(overrides.readScopes) : profileReadScopes;
|
|
266
|
+
const writeScopes = overrides.writeScopes ? canonicalScopes(overrides.writeScopes) : profileWriteScopes;
|
|
267
|
+
|
|
268
|
+
if (overrides.readScopes) {
|
|
269
|
+
if (!readScopes.every((scope) => profileReadScopes.includes(scope))) {
|
|
270
|
+
throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'readScopes override cannot broaden profile scope');
|
|
271
|
+
}
|
|
272
|
+
overrideDelta.push('readScopes');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (overrides.writeScopes) {
|
|
276
|
+
if (!writeScopes.every((scope) => profileWriteScopes.includes(scope))) {
|
|
277
|
+
throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'writeScopes override cannot broaden profile scope');
|
|
278
|
+
}
|
|
279
|
+
overrideDelta.push('writeScopes');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (readScopes.length === 0 && writeScopes.length === 0) {
|
|
283
|
+
throw new AgentProfileError('AGENT_PROFILE_SCOPE_EMPTY', 'Resolved profile selectors are empty');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const excludeFields = overrides.excludeFields ? canonicalStringList(overrides.excludeFields) : profileExclude;
|
|
287
|
+
if (overrides.excludeFields) {
|
|
288
|
+
const attemptedRemoval = profileExclude.some((field) => !excludeFields.includes(field));
|
|
289
|
+
if (attemptedRemoval) {
|
|
290
|
+
throw new AgentProfileError('AGENT_PROFILE_OVERRIDE_NOT_ALLOWED', 'excludeFields override cannot remove profile-required exclusions');
|
|
291
|
+
}
|
|
292
|
+
overrideDelta.push('excludeFields');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const resolvedBase: Omit<ResolvedProfilePolicy, 'effectivePolicyHash'> = {
|
|
296
|
+
profile: {
|
|
297
|
+
id: profile.id,
|
|
298
|
+
version: profile.version,
|
|
299
|
+
displayName: profile.displayName,
|
|
300
|
+
rationale: profile.rationale,
|
|
301
|
+
},
|
|
302
|
+
permissions: Array.from(new Set(expandPermissions(profile.permissions))).sort(),
|
|
303
|
+
ttlSeconds,
|
|
304
|
+
credentialAccess: {
|
|
305
|
+
read: readScopes,
|
|
306
|
+
write: writeScopes,
|
|
307
|
+
excludeFields,
|
|
308
|
+
...(typeof maxReads === 'number' ? { maxReads } : {}),
|
|
309
|
+
},
|
|
310
|
+
warnings: [...profile.warnings],
|
|
311
|
+
overrideDelta: Array.from(new Set(overrideDelta)).sort(),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
...resolvedBase,
|
|
316
|
+
effectivePolicyHash: hashPolicy(resolvedBase),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function summarizeEffectivePolicy(policy: ResolvedProfilePolicy): string {
|
|
321
|
+
return [
|
|
322
|
+
`${policy.profile.id}@${policy.profile.version}`,
|
|
323
|
+
`permissions=${policy.permissions.join(',')}`,
|
|
324
|
+
`ttl=${policy.ttlSeconds}s`,
|
|
325
|
+
`read=${(policy.credentialAccess.read || []).join(',') || 'none'}`,
|
|
326
|
+
`write=${(policy.credentialAccess.write || []).join(',') || 'none'}`,
|
|
327
|
+
].join(' | ');
|
|
328
|
+
}
|
package/server/lib/ai.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Client Module
|
|
3
|
+
* ================
|
|
4
|
+
* Multi-provider AI abstraction supporting:
|
|
5
|
+
* - Claude CLI (subscription/OAuth via `claude` binary)
|
|
6
|
+
* - Claude API (direct Anthropic SDK with API key)
|
|
7
|
+
* - Codex CLI (OpenAI's `codex` binary)
|
|
8
|
+
* - OpenAI API (direct OpenAI SDK with API key)
|
|
9
|
+
*
|
|
10
|
+
* Provider selection read from system defaults (ai.provider); model auto-derived per provider.
|
|
11
|
+
* Extracted from strategy/hooks.ts so it's reusable beyond strategies.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
15
|
+
import OpenAI from 'openai';
|
|
16
|
+
import { execFile } from 'child_process';
|
|
17
|
+
import { prisma } from './db';
|
|
18
|
+
import { getDefault, onDefaultChanged } from './defaults';
|
|
19
|
+
|
|
20
|
+
// ─── Types ─────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export type AiProviderMode = 'claude-cli' | 'claude-api' | 'codex-cli' | 'openai-api';
|
|
23
|
+
|
|
24
|
+
export type ModelTier = 'fast' | 'standard' | 'powerful';
|
|
25
|
+
|
|
26
|
+
export interface ProviderStatus {
|
|
27
|
+
mode: AiProviderMode;
|
|
28
|
+
label: string;
|
|
29
|
+
available: boolean;
|
|
30
|
+
reason: string;
|
|
31
|
+
models: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Model Maps ────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/** Claude model short names → full SDK model IDs */
|
|
37
|
+
const MODEL_MAP: Record<string, string> = {
|
|
38
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
39
|
+
sonnet: 'claude-sonnet-4-5-20250929',
|
|
40
|
+
opus: 'claude-opus-4-6',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Codex/OpenAI model short names → full SDK model IDs */
|
|
44
|
+
const CODEX_MODEL_MAP: Record<string, string> = {
|
|
45
|
+
'codex-mini': 'gpt-5.1-codex-mini',
|
|
46
|
+
'codex': 'gpt-5.3-codex',
|
|
47
|
+
'codex-max': 'gpt-5.1-codex-max',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Available models per provider */
|
|
51
|
+
export const PROVIDER_MODELS: Record<AiProviderMode, string[]> = {
|
|
52
|
+
'claude-cli': ['haiku', 'sonnet', 'opus'],
|
|
53
|
+
'claude-api': ['haiku', 'sonnet', 'opus'],
|
|
54
|
+
'codex-cli': ['codex-mini', 'codex', 'codex-max'],
|
|
55
|
+
'openai-api': ['codex-mini', 'codex', 'codex-max'],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Model tiers per provider — fast/standard/powerful mapped to model short names */
|
|
59
|
+
export const MODEL_TIERS: Record<AiProviderMode, Record<ModelTier, string>> = {
|
|
60
|
+
'claude-cli': { fast: 'haiku', standard: 'sonnet', powerful: 'opus' },
|
|
61
|
+
'claude-api': { fast: 'haiku', standard: 'sonnet', powerful: 'opus' },
|
|
62
|
+
'codex-cli': { fast: 'codex-mini', standard: 'codex', powerful: 'codex-max' },
|
|
63
|
+
'openai-api': { fast: 'codex-mini', standard: 'codex', powerful: 'codex-max' },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Permissions that trigger the 'powerful' tier (financial operations) */
|
|
67
|
+
const POWERFUL_PERMISSIONS = new Set(['swap', 'send:hot', 'send:temp', 'fund', 'launch', 'admin:*']);
|
|
68
|
+
|
|
69
|
+
/** Permissions that trigger the 'standard' tier (write operations) */
|
|
70
|
+
const STANDARD_PERMISSIONS = new Set(['wallet:create:hot', 'wallet:create:temp']);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Select a model tier based on hook name and token permissions.
|
|
74
|
+
* - init/shutdown → fast (lightweight lifecycle hooks)
|
|
75
|
+
* - Financial or admin permissions → powerful
|
|
76
|
+
* - Write permissions → standard
|
|
77
|
+
* - No token or read-only → fast
|
|
78
|
+
*/
|
|
79
|
+
export function selectModelTier(hookName: string, permissions: string[]): ModelTier {
|
|
80
|
+
// Lifecycle hooks always use fast tier
|
|
81
|
+
if (hookName === 'init' || hookName === 'shutdown') return 'fast';
|
|
82
|
+
|
|
83
|
+
// Check for powerful-tier permissions
|
|
84
|
+
for (const perm of permissions) {
|
|
85
|
+
if (POWERFUL_PERMISSIONS.has(perm)) return 'powerful';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check for standard-tier permissions
|
|
89
|
+
for (const perm of permissions) {
|
|
90
|
+
if (STANDARD_PERMISSIONS.has(perm)) return 'standard';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// No token or read-only permissions
|
|
94
|
+
return 'fast';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Cached Clients ────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
let cachedAnthropicClient: Anthropic | null = null;
|
|
100
|
+
let cachedOpenAiClient: OpenAI | null = null;
|
|
101
|
+
|
|
102
|
+
/** @internal Reset cached clients (for testing only) */
|
|
103
|
+
export function __resetCachedClient() {
|
|
104
|
+
cachedAnthropicClient = null;
|
|
105
|
+
cachedOpenAiClient = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Reset cached clients when provider changes
|
|
109
|
+
onDefaultChanged('ai.provider', () => {
|
|
110
|
+
cachedAnthropicClient = null;
|
|
111
|
+
cachedOpenAiClient = null;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ─── Provider & Model Selection ────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get the active AI provider mode from system defaults.
|
|
118
|
+
*/
|
|
119
|
+
export async function getProviderMode(): Promise<AiProviderMode> {
|
|
120
|
+
return getDefault<AiProviderMode>('ai.provider', 'claude-cli');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the system-wide default model (standard tier), derived from the active provider.
|
|
125
|
+
*/
|
|
126
|
+
export async function getDefaultModel(): Promise<string> {
|
|
127
|
+
const provider = await getProviderMode();
|
|
128
|
+
return MODEL_TIERS[provider].standard;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a model short name to the full SDK model ID based on provider.
|
|
133
|
+
* CLI modes return the short name directly (CLIs handle them natively).
|
|
134
|
+
* API modes map to full model IDs.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveModelId(name: string, provider: AiProviderMode): string {
|
|
137
|
+
switch (provider) {
|
|
138
|
+
case 'claude-cli':
|
|
139
|
+
return name; // Claude CLI handles short names natively
|
|
140
|
+
case 'claude-api':
|
|
141
|
+
return MODEL_MAP[name] || name;
|
|
142
|
+
case 'codex-cli':
|
|
143
|
+
return name; // Codex CLI handles short names natively
|
|
144
|
+
case 'openai-api':
|
|
145
|
+
return CODEX_MODEL_MAP[name] || name;
|
|
146
|
+
default:
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── SDK Clients ───────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get an Anthropic SDK client (for claude-api mode).
|
|
155
|
+
* Priority: ANTHROPIC_API_KEY env → DB ApiKey (service: 'anthropic').
|
|
156
|
+
*/
|
|
157
|
+
export async function getAnthropicClient(): Promise<Anthropic> {
|
|
158
|
+
if (cachedAnthropicClient) return cachedAnthropicClient;
|
|
159
|
+
|
|
160
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
161
|
+
cachedAnthropicClient = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
162
|
+
return cachedAnthropicClient;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const apiKeyRow = await prisma.apiKey.findFirst({
|
|
166
|
+
where: { service: 'anthropic', isActive: true },
|
|
167
|
+
});
|
|
168
|
+
if (apiKeyRow) {
|
|
169
|
+
cachedAnthropicClient = new Anthropic({ apiKey: apiKeyRow.key });
|
|
170
|
+
return cachedAnthropicClient;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(
|
|
174
|
+
'No Anthropic credentials found. Add an API key via Settings, set ANTHROPIC_API_KEY, or use Claude CLI mode (subscription).'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get an OpenAI SDK client (for openai-api mode).
|
|
180
|
+
* Priority: OPENAI_API_KEY env → DB ApiKey (service: 'openai').
|
|
181
|
+
*/
|
|
182
|
+
export async function getOpenAiClient(): Promise<OpenAI> {
|
|
183
|
+
if (cachedOpenAiClient) return cachedOpenAiClient;
|
|
184
|
+
|
|
185
|
+
if (process.env.OPENAI_API_KEY) {
|
|
186
|
+
cachedOpenAiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
187
|
+
return cachedOpenAiClient;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const apiKeyRow = await prisma.apiKey.findFirst({
|
|
191
|
+
where: { service: 'openai', isActive: true },
|
|
192
|
+
});
|
|
193
|
+
if (apiKeyRow) {
|
|
194
|
+
cachedOpenAiClient = new OpenAI({ apiKey: apiKeyRow.key });
|
|
195
|
+
return cachedOpenAiClient;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
throw new Error(
|
|
199
|
+
'No OpenAI credentials found. Add an API key via Settings or set OPENAI_API_KEY.'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Provider Status ───────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/** Check if a CLI binary is available in PATH */
|
|
206
|
+
function checkCliAvailable(binary: string): Promise<boolean> {
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
execFile('which', [binary], (err) => {
|
|
209
|
+
resolve(!err);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Check if an API key exists for a service (env or DB) */
|
|
215
|
+
async function hasApiKey(service: string, envVar: string): Promise<boolean> {
|
|
216
|
+
if (process.env[envVar]) return true;
|
|
217
|
+
const row = await prisma.apiKey.findFirst({
|
|
218
|
+
where: { service, isActive: true },
|
|
219
|
+
});
|
|
220
|
+
return !!row;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get availability status for each provider.
|
|
225
|
+
* Used by the dashboard UI to show status indicators.
|
|
226
|
+
*/
|
|
227
|
+
export async function getProviderStatus(): Promise<ProviderStatus[]> {
|
|
228
|
+
const [claudeCliOk, codexCliOk, anthropicKeyOk, openaiKeyOk] = await Promise.all([
|
|
229
|
+
checkCliAvailable('claude'),
|
|
230
|
+
checkCliAvailable('codex'),
|
|
231
|
+
hasApiKey('anthropic', 'ANTHROPIC_API_KEY'),
|
|
232
|
+
hasApiKey('openai', 'OPENAI_API_KEY'),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
mode: 'claude-cli',
|
|
238
|
+
label: 'Claude Max (CLI)',
|
|
239
|
+
available: claudeCliOk,
|
|
240
|
+
reason: claudeCliOk ? 'claude CLI found in PATH' : 'claude CLI not found in PATH',
|
|
241
|
+
models: PROVIDER_MODELS['claude-cli'],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
mode: 'claude-api',
|
|
245
|
+
label: 'Claude API Key',
|
|
246
|
+
available: anthropicKeyOk,
|
|
247
|
+
reason: anthropicKeyOk ? 'Anthropic API key configured' : 'No Anthropic API key configured',
|
|
248
|
+
models: PROVIDER_MODELS['claude-api'],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
mode: 'codex-cli',
|
|
252
|
+
label: 'Codex Max (CLI)',
|
|
253
|
+
available: codexCliOk,
|
|
254
|
+
reason: codexCliOk ? 'codex CLI found in PATH' : 'codex CLI not found in PATH',
|
|
255
|
+
models: PROVIDER_MODELS['codex-cli'],
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
mode: 'openai-api',
|
|
259
|
+
label: 'OpenAI API Key',
|
|
260
|
+
available: openaiKeyOk,
|
|
261
|
+
reason: openaiKeyOk ? 'OpenAI API key configured' : 'No OpenAI API key configured',
|
|
262
|
+
models: PROVIDER_MODELS['openai-api'],
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ─── Backward Compatibility ────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @deprecated Use getProviderMode() instead.
|
|
271
|
+
* Kept temporarily for backward compatibility.
|
|
272
|
+
*/
|
|
273
|
+
export async function shouldUseCli(): Promise<boolean> {
|
|
274
|
+
const mode = await getProviderMode();
|
|
275
|
+
return mode === 'claude-cli' || mode === 'codex-cli';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @deprecated Use getProviderMode() instead.
|
|
280
|
+
* Returns 'sdk' if provider uses direct API, 'cli' otherwise.
|
|
281
|
+
*/
|
|
282
|
+
export async function getAiProvider(): Promise<'sdk' | 'cli'> {
|
|
283
|
+
const mode = await getProviderMode();
|
|
284
|
+
return (mode === 'claude-api' || mode === 'openai-api') ? 'sdk' : 'cli';
|
|
285
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const API_REGISTRY_ERROR_CODES = {
|
|
4
|
+
nameInvalid: 'E_NAME_INVALID',
|
|
5
|
+
egressDenied: 'E_EGRESS_DENIED',
|
|
6
|
+
signatureAlgorithmUnsupported: 'E_SIG_ALG_UNSUPPORTED',
|
|
7
|
+
permissionUnresolved: 'E_PERMISSION_UNRESOLVED',
|
|
8
|
+
keyTrustInvalid: 'E_KEY_TRUST_INVALID',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const API_REGISTRY_RESERVED_NAMESPACES = new Set([
|
|
12
|
+
'aura',
|
|
13
|
+
'registry',
|
|
14
|
+
'security',
|
|
15
|
+
'system',
|
|
16
|
+
'admin',
|
|
17
|
+
'root',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export const API_REGISTRY_RESERVED_PACKAGE_NAMES = new Set([
|
|
21
|
+
'internal',
|
|
22
|
+
'private',
|
|
23
|
+
'null',
|
|
24
|
+
'undefined',
|
|
25
|
+
'latest',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export const API_REGISTRY_IDENTITY_REGEX =
|
|
29
|
+
/^@([a-z0-9][a-z0-9-]{1,38}[a-z0-9])\/([a-z0-9][a-z0-9-]{1,62}[a-z0-9])$/;
|
|
30
|
+
|
|
31
|
+
export const API_REGISTRY_ALLOWED_PERMISSIONS = [
|
|
32
|
+
'http.read',
|
|
33
|
+
'http.write',
|
|
34
|
+
'events.emit',
|
|
35
|
+
'filesystem.read:workspace',
|
|
36
|
+
'filesystem.write:workspace',
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
const scopedPermissionPattern = /^(secrets\.(read|write):[a-z0-9][a-z0-9:_-]{0,63})$/;
|
|
40
|
+
|
|
41
|
+
export const permissionSchema = z
|
|
42
|
+
.string()
|
|
43
|
+
.refine((value) => API_REGISTRY_ALLOWED_PERMISSIONS.includes(value as (typeof API_REGISTRY_ALLOWED_PERMISSIONS)[number]) || scopedPermissionPattern.test(value), {
|
|
44
|
+
message: 'Permission is outside of the MVP bounded permission catalog',
|
|
45
|
+
})
|
|
46
|
+
.refine((value) => !value.includes('*'), {
|
|
47
|
+
message: 'Wildcard permissions are not permitted in MVP',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export type KeyStatus = 'active' | 'retired' | 'compromised';
|
|
51
|
+
|
|
52
|
+
export interface PublisherKey {
|
|
53
|
+
keyId: string;
|
|
54
|
+
status: KeyStatus;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
compromiseDetectedAt?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SignatureEnvelope {
|
|
60
|
+
algorithm: string;
|
|
61
|
+
keyId: string;
|
|
62
|
+
sig: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
payloadHash: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface LocalPolicy {
|
|
68
|
+
allow?: string[];
|
|
69
|
+
deny?: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const API_AUDIT_EXIT_CODES = {
|
|
73
|
+
ok: 0,
|
|
74
|
+
warning: 20,
|
|
75
|
+
advisoryBlocked: 21,
|
|
76
|
+
yankedBlocked: 22,
|
|
77
|
+
integrityFailure: 23,
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
export type AdvisorySeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
81
|
+
export type AuditMode = 'ci' | 'local';
|
|
82
|
+
|
|
83
|
+
export interface AdvisoryFinding {
|
|
84
|
+
severity: AdvisorySeverity;
|
|
85
|
+
exploitKnown?: boolean;
|
|
86
|
+
}
|