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,1004 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
+
import { Check, Loader2, ChevronDown, ChevronRight, Bot, Globe, MessageSquare, ExternalLink, Trash2, X, KeyRound, FlaskConical } from 'lucide-react';
|
|
5
|
+
import { Button, TextInput } from '@/components/design-system';
|
|
6
|
+
import { api, Api } from '@/lib/api';
|
|
7
|
+
|
|
8
|
+
interface ProviderInfo {
|
|
9
|
+
mode: string;
|
|
10
|
+
label: string;
|
|
11
|
+
available: boolean;
|
|
12
|
+
reason: string;
|
|
13
|
+
models: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AiStatusResponse {
|
|
17
|
+
activeProvider: string;
|
|
18
|
+
defaultModel: string;
|
|
19
|
+
providers: ProviderInfo[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Map provider mode to the API key service name it requires (if any) */
|
|
23
|
+
const PROVIDER_KEY_SERVICE: Record<string, string | null> = {
|
|
24
|
+
'claude-cli': null,
|
|
25
|
+
'claude-api': 'anthropic',
|
|
26
|
+
'codex-cli': null,
|
|
27
|
+
'openai-api': 'openai',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Human-readable label for API key services */
|
|
31
|
+
const KEY_SERVICE_LABEL: Record<string, string> = {
|
|
32
|
+
anthropic: 'Anthropic',
|
|
33
|
+
openai: 'OpenAI',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Placeholder hints for API key inputs */
|
|
37
|
+
const KEY_PLACEHOLDER: Record<string, string> = {
|
|
38
|
+
anthropic: 'sk-ant-...',
|
|
39
|
+
openai: 'sk-...',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** CLI provider instructions */
|
|
43
|
+
const CLI_INSTRUCTIONS: Record<string, string> = {
|
|
44
|
+
'claude-cli': 'Make sure the `claude` CLI is installed and you\'re logged in (`claude login`).',
|
|
45
|
+
'codex-cli': 'Make sure the `codex` CLI is installed and you\'re authenticated.',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
interface SetupStatus {
|
|
49
|
+
hasWallet: boolean;
|
|
50
|
+
unlocked: boolean;
|
|
51
|
+
address: string | null;
|
|
52
|
+
apiKeys: { alchemy: boolean; anthropic: boolean };
|
|
53
|
+
adapters: { telegram: boolean; webhook: boolean };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface SetupWizardAppProps {
|
|
57
|
+
config?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function StepItem({ number, title, subtitle, icon, done, expanded, onToggle, children, doneContent }: {
|
|
61
|
+
number: number;
|
|
62
|
+
title: string;
|
|
63
|
+
subtitle: string;
|
|
64
|
+
icon: React.ReactNode;
|
|
65
|
+
done: boolean;
|
|
66
|
+
expanded: boolean;
|
|
67
|
+
onToggle: () => void;
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
doneContent?: React.ReactNode;
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<div style={{ border: '1px solid var(--color-border)', background: 'var(--color-surface)' }}>
|
|
73
|
+
<button
|
|
74
|
+
onClick={onToggle}
|
|
75
|
+
className="w-full flex items-center gap-3 p-3 text-left"
|
|
76
|
+
>
|
|
77
|
+
<div className="w-6 h-6 flex items-center justify-center flex-shrink-0" style={{
|
|
78
|
+
background: done ? 'var(--color-success)' : 'var(--color-background-alt)',
|
|
79
|
+
border: done ? 'none' : '1px solid var(--color-border)',
|
|
80
|
+
}}>
|
|
81
|
+
{done ? (
|
|
82
|
+
<Check size={12} style={{ color: 'white' }} />
|
|
83
|
+
) : (
|
|
84
|
+
<span className="font-mono text-[10px] font-bold" style={{ color: 'var(--color-text-muted)' }}>{number}</span>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex-1 min-w-0">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<span style={{ color: done ? 'var(--color-success)' : 'var(--color-text)' }}>{icon}</span>
|
|
90
|
+
<span className="font-mono text-[11px] font-bold" style={{ color: done ? 'var(--color-success)' : 'var(--color-text)' }}>
|
|
91
|
+
{title}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="font-mono text-[9px]" style={{ color: 'var(--color-text-muted)' }}>{subtitle}</div>
|
|
95
|
+
</div>
|
|
96
|
+
{expanded ? <ChevronDown size={14} style={{ color: 'var(--color-text-muted)' }} /> : <ChevronRight size={14} style={{ color: 'var(--color-text-muted)' }} />}
|
|
97
|
+
</button>
|
|
98
|
+
{expanded && (
|
|
99
|
+
<div className="px-3 pb-3 border-t" style={{ borderColor: 'var(--color-border)' }}>
|
|
100
|
+
{done ? doneContent : children}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const SetupWizardApp: React.FC<SetupWizardAppProps> = () => {
|
|
108
|
+
const [status, setStatus] = useState<SetupStatus | null>(null);
|
|
109
|
+
const [loading, setLoading] = useState(true);
|
|
110
|
+
const [expandedStep, setExpandedStep] = useState<number | null>(null);
|
|
111
|
+
const [dismissed, setDismissed] = useState(false);
|
|
112
|
+
|
|
113
|
+
// Step 1: AI Provider
|
|
114
|
+
const [aiStatus, setAiStatus] = useState<AiStatusResponse | null>(null);
|
|
115
|
+
const [aiLoading, setAiLoading] = useState(true);
|
|
116
|
+
const [selectedProvider, setSelectedProvider] = useState<string>('claude-cli');
|
|
117
|
+
const [providerKeyInput, setProviderKeyInput] = useState('');
|
|
118
|
+
const [providerKeyValidating, setProviderKeyValidating] = useState(false);
|
|
119
|
+
const [providerError, setProviderError] = useState('');
|
|
120
|
+
const [providerTesting, setProviderTesting] = useState(false);
|
|
121
|
+
const [providerTestResult, setProviderTestResult] = useState<'success' | 'fail' | null>(null);
|
|
122
|
+
const [providerSaving, setProviderSaving] = useState(false);
|
|
123
|
+
|
|
124
|
+
// Step 1 (continued): Permission Tier
|
|
125
|
+
const [agentTier, setAgentTier] = useState<string>('admin');
|
|
126
|
+
const [agentTierSaving, setAgentTierSaving] = useState(false);
|
|
127
|
+
|
|
128
|
+
// Step 2: Alchemy key
|
|
129
|
+
const [alchemyKey, setAlchemyKey] = useState('');
|
|
130
|
+
const [alchemyValidating, setAlchemyValidating] = useState(false);
|
|
131
|
+
const [alchemyError, setAlchemyError] = useState('');
|
|
132
|
+
|
|
133
|
+
// Step 3: Telegram
|
|
134
|
+
const [botToken, setBotToken] = useState('');
|
|
135
|
+
const [botUsername, setBotUsername] = useState('');
|
|
136
|
+
const [chatId, setChatId] = useState('');
|
|
137
|
+
const [chatEnabled, setChatEnabled] = useState(true);
|
|
138
|
+
const [telegramStep, setTelegramStep] = useState<'token' | 'detecting' | 'chatId' | 'testing' | 'done'>('token');
|
|
139
|
+
const [telegramValidating, setTelegramValidating] = useState(false);
|
|
140
|
+
const [telegramError, setTelegramError] = useState('');
|
|
141
|
+
const [deepLink, setDeepLink] = useState('');
|
|
142
|
+
const [, setSetupToken] = useState('');
|
|
143
|
+
const [detectedName, setDetectedName] = useState('');
|
|
144
|
+
const detectAbortRef = useRef<AbortController | null>(null);
|
|
145
|
+
|
|
146
|
+
// Editing state — when set, forces the form to show instead of doneContent
|
|
147
|
+
const [editingStep, setEditingStep] = useState<number | null>(null);
|
|
148
|
+
|
|
149
|
+
// Done-state test for AI provider
|
|
150
|
+
const [doneTestLoading, setDoneTestLoading] = useState(false);
|
|
151
|
+
const [doneTestResult, setDoneTestResult] = useState<'success' | 'fail' | null>(null);
|
|
152
|
+
|
|
153
|
+
// Removal state
|
|
154
|
+
const [removingAlchemy, setRemovingAlchemy] = useState(false);
|
|
155
|
+
const [removingTelegram, setRemovingTelegram] = useState(false);
|
|
156
|
+
|
|
157
|
+
// Step 1 two-path state
|
|
158
|
+
const [step1Tab, setStep1Tab] = useState<'provider' | 'agent'>('provider');
|
|
159
|
+
const [agentPairAcknowledged, setAgentPairAcknowledged] = useState(false);
|
|
160
|
+
|
|
161
|
+
/** Fetch AI status from /ai/status */
|
|
162
|
+
const fetchAiStatus = useCallback(async () => {
|
|
163
|
+
try {
|
|
164
|
+
const res = await api.get<AiStatusResponse>(Api.Wallet, '/ai/status');
|
|
165
|
+
setAiStatus(res);
|
|
166
|
+
setSelectedProvider(res.activeProvider);
|
|
167
|
+
return res;
|
|
168
|
+
} catch {
|
|
169
|
+
// AI status fetch failed
|
|
170
|
+
return null;
|
|
171
|
+
} finally {
|
|
172
|
+
setAiLoading(false);
|
|
173
|
+
}
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
/** Check if AI provider step is complete: activeProvider is set AND available */
|
|
177
|
+
const isAiStepDone = useCallback((ai: AiStatusResponse | null): boolean => {
|
|
178
|
+
if (!ai) return false;
|
|
179
|
+
const active = ai.providers.find(p => p.mode === ai.activeProvider);
|
|
180
|
+
return !!active?.available;
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
/** Fetch only setup status (wallet, apiKeys, adapters) — no /ai/status call */
|
|
184
|
+
const fetchSetupStatus = useCallback(async () => {
|
|
185
|
+
try {
|
|
186
|
+
const data = await api.get<SetupStatus>(Api.Wallet, '/setup');
|
|
187
|
+
setStatus(data);
|
|
188
|
+
return data;
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}, []);
|
|
193
|
+
|
|
194
|
+
/** Auto-expand the first incomplete step */
|
|
195
|
+
const autoExpand = useCallback((ai: AiStatusResponse | null, setup: SetupStatus | null, agentPaired?: boolean) => {
|
|
196
|
+
const aiDone = isAiStepDone(ai);
|
|
197
|
+
const step1Done = aiDone || (agentPaired ?? agentPairAcknowledged);
|
|
198
|
+
if (!step1Done) setExpandedStep(1);
|
|
199
|
+
else if (!setup?.apiKeys.alchemy) setExpandedStep(2);
|
|
200
|
+
else if (!setup?.adapters.telegram) setExpandedStep(3);
|
|
201
|
+
else setExpandedStep(null);
|
|
202
|
+
}, [isAiStepDone, agentPairAcknowledged]);
|
|
203
|
+
|
|
204
|
+
/** Initial load — fetch both endpoints + agent tier + localStorage */
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
(async () => {
|
|
207
|
+
try {
|
|
208
|
+
const [setup, ai] = await Promise.all([
|
|
209
|
+
fetchSetupStatus(),
|
|
210
|
+
fetchAiStatus(),
|
|
211
|
+
// Fetch agent tier
|
|
212
|
+
api.get<Record<string, Array<{ key: string; value: unknown }>>>(Api.Wallet, '/defaults').then(grouped => {
|
|
213
|
+
const permsGroup = grouped.permissions || [];
|
|
214
|
+
const tierRow = permsGroup.find((r: { key: string }) => r.key === 'permissions.agent_tier');
|
|
215
|
+
if (tierRow) setAgentTier(tierRow.value as string);
|
|
216
|
+
}).catch(() => { /* use default */ }),
|
|
217
|
+
]);
|
|
218
|
+
// Read agentPairAcknowledged from localStorage
|
|
219
|
+
const addr = setup?.address || 'unknown';
|
|
220
|
+
const paired = localStorage.getItem(`agentPaired:${addr}`) === 'true';
|
|
221
|
+
setAgentPairAcknowledged(paired);
|
|
222
|
+
if (paired) setStep1Tab('agent');
|
|
223
|
+
autoExpand(ai, setup, paired);
|
|
224
|
+
} finally {
|
|
225
|
+
setLoading(false);
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
}, [fetchSetupStatus, fetchAiStatus, autoExpand]);
|
|
229
|
+
|
|
230
|
+
// Handle provider radio change
|
|
231
|
+
const handleProviderChange = (mode: string) => {
|
|
232
|
+
setSelectedProvider(mode);
|
|
233
|
+
setProviderKeyInput('');
|
|
234
|
+
setProviderError('');
|
|
235
|
+
setProviderTestResult(null);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Handle permission tier change
|
|
239
|
+
const handleTierChange = async (tier: string) => {
|
|
240
|
+
setAgentTier(tier);
|
|
241
|
+
setAgentTierSaving(true);
|
|
242
|
+
try {
|
|
243
|
+
await api.patch(Api.Wallet, `/defaults/${encodeURIComponent('permissions.agent_tier')}`, { value: tier });
|
|
244
|
+
} catch { setAgentTier(tier === 'admin' ? 'restricted' : 'admin'); }
|
|
245
|
+
finally { setAgentTierSaving(false); }
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Validate + save API key for current provider, then save provider selection
|
|
249
|
+
const handleProviderKeySave = async () => {
|
|
250
|
+
const keyService = PROVIDER_KEY_SERVICE[selectedProvider];
|
|
251
|
+
if (!keyService) return;
|
|
252
|
+
setProviderError('');
|
|
253
|
+
setProviderKeyValidating(true);
|
|
254
|
+
try {
|
|
255
|
+
const validation = await api.post<{ valid: boolean; error?: string }>(Api.Wallet, '/apikeys/validate', { service: keyService, key: providerKeyInput.trim() });
|
|
256
|
+
if (!validation.valid) {
|
|
257
|
+
setProviderError(validation.error || 'Invalid key');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await api.post(Api.Wallet, '/apikeys', { service: keyService, name: 'default', key: providerKeyInput.trim() });
|
|
261
|
+
setProviderKeyInput('');
|
|
262
|
+
// Save the provider selection
|
|
263
|
+
await api.patch(Api.Wallet, `/defaults/${encodeURIComponent('ai.provider')}`, { value: selectedProvider });
|
|
264
|
+
setEditingStep(null);
|
|
265
|
+
const ai = await fetchAiStatus();
|
|
266
|
+
autoExpand(ai, status);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
setProviderError(err instanceof Error ? err.message : 'Failed to validate');
|
|
269
|
+
} finally {
|
|
270
|
+
setProviderKeyValidating(false);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Test a CLI provider by re-fetching /ai/status
|
|
275
|
+
const handleProviderTest = async () => {
|
|
276
|
+
setProviderError('');
|
|
277
|
+
setProviderTesting(true);
|
|
278
|
+
setProviderTestResult(null);
|
|
279
|
+
try {
|
|
280
|
+
const res = await api.get<AiStatusResponse>(Api.Wallet, '/ai/status');
|
|
281
|
+
setAiStatus(res);
|
|
282
|
+
const provider = res.providers.find(p => p.mode === selectedProvider);
|
|
283
|
+
if (provider?.available) {
|
|
284
|
+
setProviderTestResult('success');
|
|
285
|
+
// Save the provider selection
|
|
286
|
+
setProviderSaving(true);
|
|
287
|
+
await api.patch(Api.Wallet, `/defaults/${encodeURIComponent('ai.provider')}`, { value: selectedProvider });
|
|
288
|
+
setEditingStep(null);
|
|
289
|
+
autoExpand(res, status);
|
|
290
|
+
setProviderSaving(false);
|
|
291
|
+
} else {
|
|
292
|
+
setProviderTestResult('fail');
|
|
293
|
+
setProviderError(provider?.reason || 'Provider not available');
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
setProviderTestResult('fail');
|
|
297
|
+
setProviderError(err instanceof Error ? err.message : 'Test failed');
|
|
298
|
+
} finally {
|
|
299
|
+
setProviderTesting(false);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Validate + save Alchemy key
|
|
304
|
+
const handleAlchemySave = async () => {
|
|
305
|
+
setAlchemyError('');
|
|
306
|
+
setAlchemyValidating(true);
|
|
307
|
+
try {
|
|
308
|
+
const validation = await api.post<{ valid: boolean; error?: string }>(Api.Wallet, '/apikeys/validate', { service: 'alchemy', key: alchemyKey });
|
|
309
|
+
if (!validation.valid) {
|
|
310
|
+
setAlchemyError(validation.error || 'Invalid key');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
await api.post(Api.Wallet, '/apikeys', { service: 'alchemy', name: 'default', key: alchemyKey });
|
|
314
|
+
setAlchemyKey('');
|
|
315
|
+
setEditingStep(null);
|
|
316
|
+
const setup = await fetchSetupStatus();
|
|
317
|
+
autoExpand(aiStatus, setup);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
setAlchemyError(err instanceof Error ? err.message : 'Failed to validate');
|
|
320
|
+
} finally {
|
|
321
|
+
setAlchemyValidating(false);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Telegram flow: validate bot token, then start auto-detection
|
|
326
|
+
const handleTelegramValidate = async () => {
|
|
327
|
+
setTelegramError('');
|
|
328
|
+
setTelegramValidating(true);
|
|
329
|
+
try {
|
|
330
|
+
// Validate the token
|
|
331
|
+
const validation = await api.post<{ valid: boolean; error?: string; info?: { botUsername: string } }>(Api.Wallet, '/apikeys/validate', { service: 'adapter:telegram', key: botToken });
|
|
332
|
+
if (!validation.valid) {
|
|
333
|
+
setTelegramError(validation.error || 'Invalid bot token');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
setBotUsername(validation.info?.botUsername || '');
|
|
337
|
+
|
|
338
|
+
// Get deep link for auto-detection
|
|
339
|
+
const linkResult = await api.post<{ success: boolean; link: string; setupToken: string; botUsername: string; error?: string }>(Api.Wallet, '/adapters/telegram/setup-link', { botToken });
|
|
340
|
+
if (!linkResult.success) {
|
|
341
|
+
setTelegramError(linkResult.error || 'Failed to generate setup link');
|
|
342
|
+
setTelegramStep('chatId');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
setDeepLink(linkResult.link);
|
|
347
|
+
setSetupToken(linkResult.setupToken);
|
|
348
|
+
setBotUsername(linkResult.botUsername);
|
|
349
|
+
setTelegramStep('detecting');
|
|
350
|
+
|
|
351
|
+
// Start polling for chat ID detection (up to 2 attempts ~ 50s)
|
|
352
|
+
startDetection(linkResult.setupToken);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
setTelegramError(err instanceof Error ? err.message : 'Failed to validate');
|
|
355
|
+
} finally {
|
|
356
|
+
setTelegramValidating(false);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Poll detect-chat endpoint
|
|
361
|
+
const startDetection = async (token: string) => {
|
|
362
|
+
const abort = new AbortController();
|
|
363
|
+
detectAbortRef.current = abort;
|
|
364
|
+
|
|
365
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
366
|
+
if (abort.signal.aborted) return;
|
|
367
|
+
try {
|
|
368
|
+
const result = await api.post<{ chatId: string | null; firstName?: string; username?: string; verified?: boolean; timeout?: boolean }>(Api.Wallet, '/adapters/telegram/detect-chat', { setupToken: token });
|
|
369
|
+
if (abort.signal.aborted) return;
|
|
370
|
+
|
|
371
|
+
if (result.chatId) {
|
|
372
|
+
setChatId(result.chatId);
|
|
373
|
+
const name = result.username ? `@${result.username}` : result.firstName || '';
|
|
374
|
+
setDetectedName(name);
|
|
375
|
+
// Auto-proceed: save bot token, adapter config, restart, test, and finish
|
|
376
|
+
setTelegramStep('testing');
|
|
377
|
+
try {
|
|
378
|
+
await api.post(Api.Wallet, '/apikeys', { service: 'adapter:telegram', name: 'botToken', key: botToken });
|
|
379
|
+
await api.post(Api.Wallet, '/adapters', {
|
|
380
|
+
type: 'telegram',
|
|
381
|
+
enabled: true,
|
|
382
|
+
config: { chatId: result.chatId },
|
|
383
|
+
chat: { enabled: true },
|
|
384
|
+
});
|
|
385
|
+
await api.post(Api.Wallet, '/adapters/restart');
|
|
386
|
+
try {
|
|
387
|
+
await api.post(Api.Wallet, '/adapters/test', { type: 'telegram' });
|
|
388
|
+
} catch { /* non-critical */ }
|
|
389
|
+
setTelegramStep('done');
|
|
390
|
+
setBotToken('');
|
|
391
|
+
setEditingStep(null);
|
|
392
|
+
const setup = await fetchSetupStatus();
|
|
393
|
+
autoExpand(aiStatus, setup);
|
|
394
|
+
} catch {
|
|
395
|
+
// Fall back to manual chatId step on error
|
|
396
|
+
setTelegramStep('chatId');
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// timeout — try again
|
|
401
|
+
} catch {
|
|
402
|
+
if (abort.signal.aborted) return;
|
|
403
|
+
// Fall through to manual
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Timed out — fall back to manual entry
|
|
409
|
+
if (!abort.signal.aborted) {
|
|
410
|
+
setTelegramStep('chatId');
|
|
411
|
+
setTelegramError('Auto-detection timed out. Enter your chat ID manually.');
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Clean up detection polling on unmount
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
return () => {
|
|
418
|
+
detectAbortRef.current?.abort();
|
|
419
|
+
};
|
|
420
|
+
}, []);
|
|
421
|
+
|
|
422
|
+
// Telegram flow: save config + restart + test
|
|
423
|
+
const handleTelegramActivate = async () => {
|
|
424
|
+
setTelegramError('');
|
|
425
|
+
setTelegramValidating(true);
|
|
426
|
+
setTelegramStep('testing');
|
|
427
|
+
try {
|
|
428
|
+
// Save bot token
|
|
429
|
+
await api.post(Api.Wallet, '/apikeys', { service: 'adapter:telegram', name: 'botToken', key: botToken });
|
|
430
|
+
// Save adapter config (include chat opt-in)
|
|
431
|
+
await api.post(Api.Wallet, '/adapters', {
|
|
432
|
+
type: 'telegram',
|
|
433
|
+
enabled: true,
|
|
434
|
+
config: { chatId },
|
|
435
|
+
...(chatEnabled ? { chat: { enabled: true } } : {}),
|
|
436
|
+
});
|
|
437
|
+
// Restart adapters
|
|
438
|
+
await api.post(Api.Wallet, '/adapters/restart');
|
|
439
|
+
// Test
|
|
440
|
+
const testResult = await api.post<{ success: boolean; error?: string }>(Api.Wallet, '/adapters/test', { type: 'telegram' });
|
|
441
|
+
if (!testResult.success) {
|
|
442
|
+
setTelegramError(testResult.error || 'Test message failed');
|
|
443
|
+
setTelegramStep('chatId');
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
setTelegramStep('done');
|
|
447
|
+
setBotToken('');
|
|
448
|
+
setChatId('');
|
|
449
|
+
setEditingStep(null);
|
|
450
|
+
const setup = await fetchSetupStatus();
|
|
451
|
+
autoExpand(aiStatus, setup);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
setTelegramError(err instanceof Error ? err.message : 'Activation failed');
|
|
454
|
+
setTelegramStep('chatId');
|
|
455
|
+
} finally {
|
|
456
|
+
setTelegramValidating(false);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// Remove an API key by service name
|
|
461
|
+
const removeApiKey = async (service: string) => {
|
|
462
|
+
const { apiKeys } = await api.get<{ apiKeys: { id: string; service: string }[] }>(Api.Wallet, '/apikeys');
|
|
463
|
+
const key = apiKeys.find(k => k.service === service);
|
|
464
|
+
if (key) {
|
|
465
|
+
await api.delete(Api.Wallet, `/apikeys/${key.id}`);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const handleRemoveProvider = () => {
|
|
470
|
+
// Just clear local state and show the form — no API calls needed.
|
|
471
|
+
// The old provider is only replaced when the user saves a new one.
|
|
472
|
+
setProviderKeyInput('');
|
|
473
|
+
setProviderError('');
|
|
474
|
+
setProviderTestResult(null);
|
|
475
|
+
setDoneTestResult(null);
|
|
476
|
+
setEditingStep(1);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const handleRemoveAlchemy = async () => {
|
|
480
|
+
setRemovingAlchemy(true);
|
|
481
|
+
try {
|
|
482
|
+
await removeApiKey('alchemy');
|
|
483
|
+
setEditingStep(2);
|
|
484
|
+
const setup = await fetchSetupStatus();
|
|
485
|
+
autoExpand(aiStatus, setup);
|
|
486
|
+
} finally {
|
|
487
|
+
setRemovingAlchemy(false);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const handleRemoveTelegram = async () => {
|
|
492
|
+
setRemovingTelegram(true);
|
|
493
|
+
try {
|
|
494
|
+
// Abort any in-flight detection
|
|
495
|
+
detectAbortRef.current?.abort();
|
|
496
|
+
// Delete the adapter
|
|
497
|
+
await api.delete(Api.Wallet, '/adapters/telegram');
|
|
498
|
+
// Delete the bot token API key
|
|
499
|
+
await removeApiKey('adapter:telegram');
|
|
500
|
+
// Restart adapters
|
|
501
|
+
await api.post(Api.Wallet, '/adapters/restart');
|
|
502
|
+
// Clear all telegram state
|
|
503
|
+
setBotToken('');
|
|
504
|
+
setBotUsername('');
|
|
505
|
+
setChatId('');
|
|
506
|
+
setChatEnabled(false);
|
|
507
|
+
setDeepLink('');
|
|
508
|
+
setSetupToken('');
|
|
509
|
+
setDetectedName('');
|
|
510
|
+
setTelegramStep('token');
|
|
511
|
+
setTelegramError('');
|
|
512
|
+
setEditingStep(3);
|
|
513
|
+
const setup = await fetchSetupStatus();
|
|
514
|
+
autoExpand(aiStatus, setup);
|
|
515
|
+
} finally {
|
|
516
|
+
setRemovingTelegram(false);
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const handleDismiss = () => {
|
|
521
|
+
const addr = status?.address || 'unknown';
|
|
522
|
+
localStorage.setItem(`setupWizardDismissed:${addr}`, 'true');
|
|
523
|
+
setDismissed(true);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
if (dismissed) {
|
|
527
|
+
return (
|
|
528
|
+
<div className="flex items-center justify-center py-8">
|
|
529
|
+
<div className="font-mono text-[10px]" style={{ color: 'var(--color-text-muted)' }}>
|
|
530
|
+
Dismissed. Close this app with the X button above.
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (loading) {
|
|
537
|
+
return (
|
|
538
|
+
<div className="flex items-center justify-center py-8">
|
|
539
|
+
<Loader2 size={20} className="animate-spin" style={{ color: 'var(--color-text-muted)' }} />
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const aiStepDone = isAiStepDone(aiStatus);
|
|
545
|
+
const step1Done = aiStepDone || agentPairAcknowledged;
|
|
546
|
+
const allDone = step1Done && status?.apiKeys.alchemy && status?.adapters.telegram;
|
|
547
|
+
|
|
548
|
+
// Derive selected provider info for rendering
|
|
549
|
+
const selectedProviderInfo = aiStatus?.providers.find(p => p.mode === selectedProvider);
|
|
550
|
+
const activeProviderInfo = aiStatus?.providers.find(p => p.mode === aiStatus.activeProvider);
|
|
551
|
+
const keyService = PROVIDER_KEY_SERVICE[selectedProvider];
|
|
552
|
+
const isCliProvider = keyService === null;
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<div className="space-y-1 p-1">
|
|
556
|
+
{/* Header */}
|
|
557
|
+
<div className="text-center mb-3">
|
|
558
|
+
<div className="font-mono text-[10px] tracking-widest" style={{ color: 'var(--color-text-muted)' }}>
|
|
559
|
+
{allDone ? 'SETUP COMPLETE' : 'FINISH YOUR SETUP'}
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
|
|
563
|
+
{allDone && (
|
|
564
|
+
<div className="text-center py-4 space-y-3">
|
|
565
|
+
<div className="w-10 h-10 mx-auto flex items-center justify-center" style={{ background: 'var(--color-success)', color: 'white' }}>
|
|
566
|
+
<Check size={20} />
|
|
567
|
+
</div>
|
|
568
|
+
<div className="font-mono text-xs" style={{ color: 'var(--color-text)' }}>All set! Your wallet is fully configured.</div>
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
|
|
572
|
+
{/* Step 1: AI Agent */}
|
|
573
|
+
<StepItem
|
|
574
|
+
number={1}
|
|
575
|
+
title="AI Agent"
|
|
576
|
+
subtitle="Connect an AI provider or pair with an agent"
|
|
577
|
+
icon={<Bot size={14} />}
|
|
578
|
+
done={(aiStepDone || agentPairAcknowledged) && editingStep !== 1}
|
|
579
|
+
expanded={expandedStep === 1}
|
|
580
|
+
onToggle={() => setExpandedStep(expandedStep === 1 ? null : 1)}
|
|
581
|
+
doneContent={
|
|
582
|
+
<div className="space-y-2 pt-2">
|
|
583
|
+
{aiStepDone && !agentPairAcknowledged && (
|
|
584
|
+
<>
|
|
585
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
586
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
587
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>
|
|
588
|
+
{activeProviderInfo?.label || aiStatus?.activeProvider || 'AI provider'} configured
|
|
589
|
+
</span>
|
|
590
|
+
</div>
|
|
591
|
+
{doneTestResult === 'success' && (
|
|
592
|
+
<div className="flex items-center gap-2 p-1.5" style={{ border: '1px solid var(--color-success)', color: 'var(--color-success)' }}>
|
|
593
|
+
<Check size={10} />
|
|
594
|
+
<span className="font-mono text-[9px]">Provider available</span>
|
|
595
|
+
</div>
|
|
596
|
+
)}
|
|
597
|
+
{doneTestResult === 'fail' && (
|
|
598
|
+
<div className="font-mono text-[9px]" style={{ color: 'var(--color-warning)' }}>Provider not available</div>
|
|
599
|
+
)}
|
|
600
|
+
<div className="flex gap-2">
|
|
601
|
+
<Button variant="ghost" size="sm" onClick={async () => {
|
|
602
|
+
setDoneTestLoading(true);
|
|
603
|
+
setDoneTestResult(null);
|
|
604
|
+
try {
|
|
605
|
+
const res = await api.get<AiStatusResponse>(Api.Wallet, '/ai/status');
|
|
606
|
+
setAiStatus(res);
|
|
607
|
+
const provider = res.providers.find(p => p.mode === res.activeProvider);
|
|
608
|
+
setDoneTestResult(provider?.available ? 'success' : 'fail');
|
|
609
|
+
} catch {
|
|
610
|
+
setDoneTestResult('fail');
|
|
611
|
+
} finally {
|
|
612
|
+
setDoneTestLoading(false);
|
|
613
|
+
}
|
|
614
|
+
}} disabled={doneTestLoading} loading={doneTestLoading} icon={<FlaskConical size={10} />}>
|
|
615
|
+
TEST
|
|
616
|
+
</Button>
|
|
617
|
+
<Button variant="ghost" size="sm" onClick={handleRemoveProvider} icon={<Trash2 size={10} />}>
|
|
618
|
+
CHANGE
|
|
619
|
+
</Button>
|
|
620
|
+
</div>
|
|
621
|
+
</>
|
|
622
|
+
)}
|
|
623
|
+
{agentPairAcknowledged && (
|
|
624
|
+
<>
|
|
625
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
626
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
627
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>
|
|
628
|
+
Agent paired externally
|
|
629
|
+
</span>
|
|
630
|
+
</div>
|
|
631
|
+
<Button variant="ghost" size="sm" onClick={() => {
|
|
632
|
+
const addr = status?.address || 'unknown';
|
|
633
|
+
localStorage.removeItem(`agentPaired:${addr}`);
|
|
634
|
+
setAgentPairAcknowledged(false);
|
|
635
|
+
setEditingStep(1);
|
|
636
|
+
}} icon={<Trash2 size={10} />}>
|
|
637
|
+
CHANGE
|
|
638
|
+
</Button>
|
|
639
|
+
</>
|
|
640
|
+
)}
|
|
641
|
+
</div>
|
|
642
|
+
}
|
|
643
|
+
>
|
|
644
|
+
<div className="space-y-3 pt-2">
|
|
645
|
+
{/* Tab selector */}
|
|
646
|
+
<div className="flex gap-2">
|
|
647
|
+
<button
|
|
648
|
+
onClick={() => setStep1Tab('provider')}
|
|
649
|
+
className="flex-1 p-1.5 font-mono text-[9px] text-left"
|
|
650
|
+
style={{
|
|
651
|
+
border: step1Tab === 'provider' ? '1px solid var(--color-accent, #ccff00)' : '1px solid var(--color-border)',
|
|
652
|
+
background: step1Tab === 'provider' ? 'var(--color-background-alt)' : 'transparent',
|
|
653
|
+
color: step1Tab === 'provider' ? 'var(--color-text)' : 'var(--color-text-muted)',
|
|
654
|
+
}}
|
|
655
|
+
>
|
|
656
|
+
<div className="font-bold">AI Provider</div>
|
|
657
|
+
<div className="text-[8px]" style={{ color: 'var(--color-text-muted)' }}>Claude Max, API keys</div>
|
|
658
|
+
</button>
|
|
659
|
+
<button
|
|
660
|
+
onClick={() => setStep1Tab('agent')}
|
|
661
|
+
className="flex-1 p-1.5 font-mono text-[9px] text-left"
|
|
662
|
+
style={{
|
|
663
|
+
border: step1Tab === 'agent' ? '1px solid var(--color-accent, #ccff00)' : '1px solid var(--color-border)',
|
|
664
|
+
background: step1Tab === 'agent' ? 'var(--color-background-alt)' : 'transparent',
|
|
665
|
+
color: step1Tab === 'agent' ? 'var(--color-text)' : 'var(--color-text-muted)',
|
|
666
|
+
}}
|
|
667
|
+
>
|
|
668
|
+
<div className="font-bold">Pair with Agent</div>
|
|
669
|
+
<div className="text-[8px]" style={{ color: 'var(--color-text-muted)' }}>MCP, skill, CLI, API</div>
|
|
670
|
+
</button>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
{/* Provider tab */}
|
|
674
|
+
{step1Tab === 'provider' && (
|
|
675
|
+
<>
|
|
676
|
+
{aiLoading ? (
|
|
677
|
+
<div className="py-3 flex items-center justify-center">
|
|
678
|
+
<Loader2 size={14} className="animate-spin" style={{ color: 'var(--color-text-muted)' }} />
|
|
679
|
+
</div>
|
|
680
|
+
) : (
|
|
681
|
+
<>
|
|
682
|
+
{/* Provider radio buttons */}
|
|
683
|
+
<div className="space-y-1">
|
|
684
|
+
<div className="font-mono text-[9px] font-bold" style={{ color: 'var(--color-text)' }}>Provider</div>
|
|
685
|
+
{aiStatus?.providers.map((p) => (
|
|
686
|
+
<label
|
|
687
|
+
key={p.mode}
|
|
688
|
+
className="flex items-center gap-2 cursor-pointer py-1 px-1.5"
|
|
689
|
+
style={{
|
|
690
|
+
border: selectedProvider === p.mode ? '1px solid var(--color-accent, #ccff00)' : '1px solid transparent',
|
|
691
|
+
background: selectedProvider === p.mode ? 'var(--color-background-alt, #f4f4f5)' : 'transparent',
|
|
692
|
+
}}
|
|
693
|
+
>
|
|
694
|
+
<input
|
|
695
|
+
type="radio"
|
|
696
|
+
name="setup-ai-provider"
|
|
697
|
+
value={p.mode}
|
|
698
|
+
checked={selectedProvider === p.mode}
|
|
699
|
+
onChange={() => handleProviderChange(p.mode)}
|
|
700
|
+
className="accent-[var(--color-accent,#ccff00)]"
|
|
701
|
+
/>
|
|
702
|
+
<span className="font-mono text-[9px] flex-1" style={{ color: 'var(--color-text)' }}>
|
|
703
|
+
{p.label}
|
|
704
|
+
</span>
|
|
705
|
+
{p.available ? (
|
|
706
|
+
<Check size={10} style={{ color: 'var(--color-success, #22c55e)' }} />
|
|
707
|
+
) : (
|
|
708
|
+
<X size={10} style={{ color: 'var(--color-text-muted)' }} />
|
|
709
|
+
)}
|
|
710
|
+
{!p.available && (
|
|
711
|
+
<span className="font-mono text-[8px]" style={{ color: 'var(--color-text-muted)' }}>
|
|
712
|
+
{p.reason}
|
|
713
|
+
</span>
|
|
714
|
+
)}
|
|
715
|
+
</label>
|
|
716
|
+
))}
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
{/* CLI provider: instructions + test button */}
|
|
720
|
+
{isCliProvider && selectedProviderInfo && (
|
|
721
|
+
<div className="space-y-2">
|
|
722
|
+
<div className="p-1.5 font-mono text-[8px]" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)', color: 'var(--color-text-muted)' }}>
|
|
723
|
+
{CLI_INSTRUCTIONS[selectedProvider] || `Make sure the CLI is installed and authenticated.`}
|
|
724
|
+
</div>
|
|
725
|
+
{providerTestResult === 'success' && (
|
|
726
|
+
<div className="flex items-center gap-2 p-1.5" style={{ border: '1px solid var(--color-success)', color: 'var(--color-success)' }}>
|
|
727
|
+
<Check size={10} />
|
|
728
|
+
<span className="font-mono text-[9px]">Available! Provider saved.</span>
|
|
729
|
+
</div>
|
|
730
|
+
)}
|
|
731
|
+
<Button size="sm" onClick={handleProviderTest} disabled={providerTesting || providerSaving} loading={providerTesting || providerSaving} icon={<FlaskConical size={10} />}>
|
|
732
|
+
TEST
|
|
733
|
+
</Button>
|
|
734
|
+
</div>
|
|
735
|
+
)}
|
|
736
|
+
|
|
737
|
+
{/* API provider: key input form */}
|
|
738
|
+
{!isCliProvider && keyService && (
|
|
739
|
+
<div className="space-y-2">
|
|
740
|
+
<TextInput
|
|
741
|
+
label={`${KEY_SERVICE_LABEL[keyService] || keyService} API Key`}
|
|
742
|
+
type="password"
|
|
743
|
+
value={providerKeyInput}
|
|
744
|
+
onChange={e => { setProviderKeyInput(e.target.value); setProviderError(''); }}
|
|
745
|
+
placeholder={KEY_PLACEHOLDER[keyService] || 'Paste your API key...'}
|
|
746
|
+
compact
|
|
747
|
+
rightElement={<KeyRound size={10} style={{ color: 'var(--color-text-muted)' }} />}
|
|
748
|
+
/>
|
|
749
|
+
<Button size="sm" onClick={handleProviderKeySave} disabled={providerKeyValidating || !providerKeyInput.trim()} loading={providerKeyValidating}>
|
|
750
|
+
VALIDATE & SAVE
|
|
751
|
+
</Button>
|
|
752
|
+
</div>
|
|
753
|
+
)}
|
|
754
|
+
|
|
755
|
+
{/* Error display */}
|
|
756
|
+
{providerError && (
|
|
757
|
+
<div className="font-mono text-[9px]" style={{ color: 'var(--color-warning)' }}>{providerError}</div>
|
|
758
|
+
)}
|
|
759
|
+
|
|
760
|
+
{/* Permission Tier Toggle */}
|
|
761
|
+
<div className="space-y-1 pt-2 mt-2" style={{ borderTop: '1px solid var(--color-border)' }}>
|
|
762
|
+
<div className="font-mono text-[9px] font-bold" style={{ color: 'var(--color-text)' }}>Permission Level</div>
|
|
763
|
+
<div className="flex gap-2">
|
|
764
|
+
<button
|
|
765
|
+
onClick={() => handleTierChange('admin')}
|
|
766
|
+
disabled={agentTierSaving}
|
|
767
|
+
className="flex-1 p-1.5 font-mono text-[9px] text-left"
|
|
768
|
+
style={{
|
|
769
|
+
border: agentTier === 'admin' ? '1px solid var(--color-accent, #ccff00)' : '1px solid var(--color-border)',
|
|
770
|
+
background: agentTier === 'admin' ? 'var(--color-background-alt)' : 'transparent',
|
|
771
|
+
color: agentTier === 'admin' ? 'var(--color-text)' : 'var(--color-text-muted)',
|
|
772
|
+
opacity: agentTierSaving ? 0.5 : 1,
|
|
773
|
+
}}
|
|
774
|
+
>
|
|
775
|
+
<div className="font-bold">Full Admin</div>
|
|
776
|
+
<div className="text-[8px]" style={{ color: 'var(--color-text-muted)' }}>Recommended</div>
|
|
777
|
+
</button>
|
|
778
|
+
<button
|
|
779
|
+
onClick={() => handleTierChange('restricted')}
|
|
780
|
+
disabled={agentTierSaving}
|
|
781
|
+
className="flex-1 p-1.5 font-mono text-[9px] text-left"
|
|
782
|
+
style={{
|
|
783
|
+
border: agentTier === 'restricted' ? '1px solid var(--color-accent, #ccff00)' : '1px solid var(--color-border)',
|
|
784
|
+
background: agentTier === 'restricted' ? 'var(--color-background-alt)' : 'transparent',
|
|
785
|
+
color: agentTier === 'restricted' ? 'var(--color-text)' : 'var(--color-text-muted)',
|
|
786
|
+
opacity: agentTierSaving ? 0.5 : 1,
|
|
787
|
+
}}
|
|
788
|
+
>
|
|
789
|
+
<div className="font-bold">Restricted</div>
|
|
790
|
+
<div className="text-[8px]" style={{ color: 'var(--color-text-muted)' }}>Approval required</div>
|
|
791
|
+
</button>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
</>
|
|
795
|
+
)}
|
|
796
|
+
</>
|
|
797
|
+
)}
|
|
798
|
+
|
|
799
|
+
{/* Agent tab */}
|
|
800
|
+
{step1Tab === 'agent' && (
|
|
801
|
+
<div className="space-y-2">
|
|
802
|
+
<div className="font-mono text-[9px]" style={{ color: 'var(--color-text-muted)' }}>
|
|
803
|
+
Choose the path that matches your setup:
|
|
804
|
+
</div>
|
|
805
|
+
{[
|
|
806
|
+
{ label: 'Agent Skill', desc: 'Claude Code, Cursor, VS Code, Windsurf', cmd: 'npx skills add Aura-Industry/aurawallet', note: 'Then ask: "Set up my wallet"' },
|
|
807
|
+
{ label: 'MCP Server', desc: 'Claude Desktop or any MCP client', cmd: 'npx aurawallet mcp --install', note: 'Auto-configures your IDE' },
|
|
808
|
+
{ label: 'Headless CLI', desc: 'Local bots, CI/CD, containers', cmd: 'npm run cli', note: 'Approve agent requests in terminal' },
|
|
809
|
+
{ label: 'Direct API', desc: 'Any language or platform', cmd: 'curl http://localhost:4242/health', note: 'POST /auth to bootstrap a token' },
|
|
810
|
+
].map((path) => (
|
|
811
|
+
<div key={path.label} className="p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
812
|
+
<div className="flex items-center justify-between">
|
|
813
|
+
<span className="font-mono text-[10px] font-bold" style={{ color: 'var(--color-text)' }}>{path.label}</span>
|
|
814
|
+
<span className="font-mono text-[8px]" style={{ color: 'var(--color-text-muted)' }}>{path.desc}</span>
|
|
815
|
+
</div>
|
|
816
|
+
<div className="font-mono text-[9px] mt-1 px-1.5 py-1" style={{ background: 'var(--color-surface)', border: '1px solid var(--color-border)', color: 'var(--color-text-muted)' }}>
|
|
817
|
+
{path.cmd}
|
|
818
|
+
</div>
|
|
819
|
+
<div className="font-mono text-[8px] mt-1" style={{ color: 'var(--color-text-faint, #9ca3af)' }}>{path.note}</div>
|
|
820
|
+
</div>
|
|
821
|
+
))}
|
|
822
|
+
<Button size="sm" onClick={() => {
|
|
823
|
+
const addr = status?.address || 'unknown';
|
|
824
|
+
localStorage.setItem(`agentPaired:${addr}`, 'true');
|
|
825
|
+
setAgentPairAcknowledged(true);
|
|
826
|
+
setEditingStep(null);
|
|
827
|
+
autoExpand(aiStatus, status);
|
|
828
|
+
}} className="w-full">
|
|
829
|
+
I'VE CONNECTED
|
|
830
|
+
</Button>
|
|
831
|
+
</div>
|
|
832
|
+
)}
|
|
833
|
+
</div>
|
|
834
|
+
</StepItem>
|
|
835
|
+
|
|
836
|
+
{/* Step 2: RPC Provider */}
|
|
837
|
+
<StepItem
|
|
838
|
+
number={2}
|
|
839
|
+
title="RPC Provider"
|
|
840
|
+
subtitle="Add Alchemy for reliable RPC"
|
|
841
|
+
icon={<Globe size={14} />}
|
|
842
|
+
done={!!status?.apiKeys.alchemy && editingStep !== 2}
|
|
843
|
+
expanded={expandedStep === 2}
|
|
844
|
+
onToggle={() => setExpandedStep(expandedStep === 2 ? null : 2)}
|
|
845
|
+
doneContent={
|
|
846
|
+
<div className="space-y-2 pt-2">
|
|
847
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
848
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
849
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>Alchemy key configured</span>
|
|
850
|
+
</div>
|
|
851
|
+
<Button variant="ghost" size="sm" onClick={handleRemoveAlchemy} disabled={removingAlchemy} loading={removingAlchemy} icon={<Trash2 size={10} />}>
|
|
852
|
+
REMOVE
|
|
853
|
+
</Button>
|
|
854
|
+
</div>
|
|
855
|
+
}
|
|
856
|
+
>
|
|
857
|
+
<div className="space-y-2 pt-2">
|
|
858
|
+
<TextInput label="ALCHEMY_KEY" type="password" value={alchemyKey} onChange={e => setAlchemyKey(e.target.value)} placeholder="Paste your Alchemy API key..." compact />
|
|
859
|
+
{alchemyError && <div className="font-mono text-[9px]" style={{ color: 'var(--color-warning)' }}>{alchemyError}</div>}
|
|
860
|
+
<Button size="sm" onClick={handleAlchemySave} disabled={alchemyValidating || !alchemyKey} loading={alchemyValidating}>
|
|
861
|
+
VALIDATE & SAVE
|
|
862
|
+
</Button>
|
|
863
|
+
</div>
|
|
864
|
+
</StepItem>
|
|
865
|
+
|
|
866
|
+
{/* Step 3: Mobile Approvals */}
|
|
867
|
+
<StepItem
|
|
868
|
+
number={3}
|
|
869
|
+
title="Mobile Approvals"
|
|
870
|
+
subtitle="Approve agent actions via Telegram"
|
|
871
|
+
icon={<MessageSquare size={14} />}
|
|
872
|
+
done={!!status?.adapters.telegram && editingStep !== 3}
|
|
873
|
+
expanded={expandedStep === 3}
|
|
874
|
+
onToggle={() => setExpandedStep(expandedStep === 3 ? null : 3)}
|
|
875
|
+
doneContent={
|
|
876
|
+
<div className="space-y-2 pt-2">
|
|
877
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
878
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
879
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>Telegram connected</span>
|
|
880
|
+
</div>
|
|
881
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-muted)' }}>
|
|
882
|
+
Agent chat is enabled by default. You can toggle it in System Defaults → Adapter Chat.
|
|
883
|
+
</div>
|
|
884
|
+
<Button variant="ghost" size="sm" onClick={handleRemoveTelegram} disabled={removingTelegram} loading={removingTelegram} icon={<Trash2 size={10} />}>
|
|
885
|
+
REMOVE
|
|
886
|
+
</Button>
|
|
887
|
+
</div>
|
|
888
|
+
}
|
|
889
|
+
>
|
|
890
|
+
<div className="space-y-2 pt-2">
|
|
891
|
+
{telegramStep === 'token' && (
|
|
892
|
+
<>
|
|
893
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-muted)' }}>
|
|
894
|
+
Approve agent actions from your phone via Telegram. Optionally, chat with your AI agent directly.
|
|
895
|
+
</div>
|
|
896
|
+
<TextInput label="BOT_TOKEN" type="password" value={botToken} onChange={e => setBotToken(e.target.value)} placeholder="123456:ABC-DEF..." compact />
|
|
897
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-faint)' }}>
|
|
898
|
+
Create a bot via @BotFather on Telegram
|
|
899
|
+
</div>
|
|
900
|
+
<Button size="sm" onClick={handleTelegramValidate} disabled={telegramValidating || !botToken} loading={telegramValidating}>
|
|
901
|
+
VALIDATE BOT
|
|
902
|
+
</Button>
|
|
903
|
+
</>
|
|
904
|
+
)}
|
|
905
|
+
{telegramStep === 'detecting' && (
|
|
906
|
+
<>
|
|
907
|
+
{botUsername && (
|
|
908
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
909
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
910
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-text)' }}>@{botUsername}</span>
|
|
911
|
+
</div>
|
|
912
|
+
)}
|
|
913
|
+
<Button variant="secondary" size="sm" icon={<ExternalLink size={10} />}
|
|
914
|
+
onClick={() => window.open(deepLink, '_blank')}>
|
|
915
|
+
Open @{botUsername} in Telegram
|
|
916
|
+
</Button>
|
|
917
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-faint)' }}>
|
|
918
|
+
Click the link above, then press Start in Telegram
|
|
919
|
+
</div>
|
|
920
|
+
<div className="flex items-center gap-2 py-2">
|
|
921
|
+
<Loader2 size={14} className="animate-spin" style={{ color: 'var(--color-info)' }} />
|
|
922
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-text-muted)' }}>Waiting for you to press Start...</span>
|
|
923
|
+
</div>
|
|
924
|
+
<Button variant="ghost" size="sm" onClick={() => { detectAbortRef.current?.abort(); setTelegramStep('chatId'); }}>
|
|
925
|
+
ENTER MANUALLY
|
|
926
|
+
</Button>
|
|
927
|
+
</>
|
|
928
|
+
)}
|
|
929
|
+
{telegramStep === 'chatId' && (
|
|
930
|
+
<>
|
|
931
|
+
{botUsername && (
|
|
932
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-border)' }}>
|
|
933
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
934
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-text)' }}>@{botUsername}</span>
|
|
935
|
+
</div>
|
|
936
|
+
)}
|
|
937
|
+
{detectedName && chatId && (
|
|
938
|
+
<div className="flex items-center gap-2 p-2" style={{ background: 'var(--color-background-alt)', border: '1px solid var(--color-success)' }}>
|
|
939
|
+
<Check size={12} style={{ color: 'var(--color-success)' }} />
|
|
940
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>Detected: {detectedName} ({chatId})</span>
|
|
941
|
+
</div>
|
|
942
|
+
)}
|
|
943
|
+
{!detectedName && (
|
|
944
|
+
<>
|
|
945
|
+
<TextInput label="CHAT_ID" type="text" value={chatId} onChange={e => setChatId(e.target.value)} placeholder="Your Telegram chat ID..." compact />
|
|
946
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-faint)' }}>
|
|
947
|
+
Send /start to your bot, then use @userinfobot to get your chat ID
|
|
948
|
+
</div>
|
|
949
|
+
</>
|
|
950
|
+
)}
|
|
951
|
+
<label className="flex items-center gap-2 py-1 cursor-pointer">
|
|
952
|
+
<input
|
|
953
|
+
type="checkbox"
|
|
954
|
+
checked={chatEnabled}
|
|
955
|
+
onChange={e => setChatEnabled(e.target.checked)}
|
|
956
|
+
className="accent-[var(--color-accent)]"
|
|
957
|
+
/>
|
|
958
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-text)' }}>Enable agent chat</span>
|
|
959
|
+
</label>
|
|
960
|
+
<div className="font-mono text-[8px]" style={{ color: 'var(--color-text-faint)' }}>
|
|
961
|
+
Enable this to chat with your agent via Telegram. Your AI agent will respond to messages you send in this chat.
|
|
962
|
+
</div>
|
|
963
|
+
<Button size="sm" onClick={handleTelegramActivate} disabled={telegramValidating || !chatId} loading={telegramValidating}>
|
|
964
|
+
ACTIVATE & TEST
|
|
965
|
+
</Button>
|
|
966
|
+
</>
|
|
967
|
+
)}
|
|
968
|
+
{telegramStep === 'testing' && (
|
|
969
|
+
<div className="flex items-center gap-2 py-2">
|
|
970
|
+
<Loader2 size={14} className="animate-spin" style={{ color: 'var(--color-info)' }} />
|
|
971
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-text-muted)' }}>Testing connection...</span>
|
|
972
|
+
</div>
|
|
973
|
+
)}
|
|
974
|
+
{telegramStep === 'done' && (
|
|
975
|
+
<div className="flex items-center gap-2 py-2">
|
|
976
|
+
<Check size={14} style={{ color: 'var(--color-success)' }} />
|
|
977
|
+
<span className="font-mono text-[10px]" style={{ color: 'var(--color-success)' }}>Telegram connected!</span>
|
|
978
|
+
</div>
|
|
979
|
+
)}
|
|
980
|
+
{telegramError && <div className="font-mono text-[9px]" style={{ color: 'var(--color-warning)' }}>{telegramError}</div>}
|
|
981
|
+
</div>
|
|
982
|
+
</StepItem>
|
|
983
|
+
|
|
984
|
+
{!allDone && (
|
|
985
|
+
<div className="pt-3 flex justify-end">
|
|
986
|
+
<Button variant="ghost" size="sm" onClick={handleDismiss}>
|
|
987
|
+
SKIP FOR NOW
|
|
988
|
+
</Button>
|
|
989
|
+
</div>
|
|
990
|
+
)}
|
|
991
|
+
|
|
992
|
+
{allDone && (
|
|
993
|
+
<div className="pt-3 flex justify-end">
|
|
994
|
+
<Button variant="ghost" size="sm" onClick={handleDismiss}>
|
|
995
|
+
DISMISS
|
|
996
|
+
</Button>
|
|
997
|
+
</div>
|
|
998
|
+
)}
|
|
999
|
+
</div>
|
|
1000
|
+
);
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
export { SetupWizardApp };
|
|
1004
|
+
export default SetupWizardApp;
|