apteva 0.4.17 → 0.4.19
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/dist/ActivityPage.9a1qg4bp.js +3 -0
- package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
- package/dist/App.1nmg2h01.js +4 -0
- package/dist/App.5qw2dtxs.js +4 -0
- package/dist/App.6nc5acvk.js +4 -0
- package/dist/App.7vzbaz56.js +4 -0
- package/dist/App.8rfz30p1.js +4 -0
- package/dist/App.amwp54wf.js +4 -0
- package/dist/App.e4202qb4.js +267 -0
- package/dist/App.errxz2q4.js +4 -0
- package/dist/App.f8qsyhpr.js +4 -0
- package/dist/App.g8vq68n0.js +20 -0
- package/dist/App.kfyrnznw.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/App.p93mmyqw.js +4 -0
- package/dist/App.qmg33p02.js +4 -0
- package/dist/App.sdsc0258.js +4 -0
- package/dist/ConnectionsPage.7zqba1r0.js +3 -0
- package/dist/McpPage.kf2g327t.js +3 -0
- package/dist/SettingsPage.472c15ep.js +3 -0
- package/dist/SkillsPage.xdxnh68a.js +3 -0
- package/dist/TasksPage.7g0b8xwc.js +3 -0
- package/dist/TelemetryPage.pr7rbz4r.js +3 -0
- package/dist/TestsPage.zhc6rqjm.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -4
- package/src/auth/middleware.ts +2 -0
- package/src/channels/index.ts +40 -0
- package/src/channels/telegram.ts +306 -0
- package/src/db.ts +342 -11
- package/src/integrations/agentdojo.ts +1 -1
- package/src/mcp-handler.ts +31 -24
- package/src/mcp-platform.ts +41 -1
- package/src/providers.ts +22 -9
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/channels.ts +182 -0
- package/src/routes/api/integrations.ts +13 -5
- package/src/routes/api/mcp.ts +27 -9
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/telemetry.ts +30 -0
- package/src/routes/api/triggers.ts +478 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +7 -1
- package/src/routes/static.ts +12 -3
- package/src/server.ts +43 -6
- package/src/triggers/agentdojo.ts +253 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/tui/AgentList.tsx +145 -0
- package/src/tui/App.tsx +102 -0
- package/src/tui/Login.tsx +104 -0
- package/src/tui/api.ts +72 -0
- package/src/tui/index.tsx +7 -0
- package/src/web/App.tsx +18 -11
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +94 -137
- package/src/web/components/common/Icons.tsx +16 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/connections/ConnectionsPage.tsx +54 -0
- package/src/web/components/connections/IntegrationsTab.tsx +144 -0
- package/src/web/components/connections/OverviewTab.tsx +137 -0
- package/src/web/components/connections/TriggersTab.tsx +1169 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +196 -4
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
- package/src/web/components/settings/SettingsPage.tsx +364 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.fq4xbpcz.js +0 -228
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Generic Trigger Provider Interface
|
|
2
|
+
// Allows multiple providers (Composio, AgentDojo, local, etc.) to offer trigger/webhook integrations
|
|
3
|
+
|
|
4
|
+
export interface TriggerType {
|
|
5
|
+
slug: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
type: "webhook" | "poll";
|
|
9
|
+
toolkit_slug: string;
|
|
10
|
+
toolkit_name: string;
|
|
11
|
+
logo: string | null;
|
|
12
|
+
config_schema: Record<string, unknown>;
|
|
13
|
+
payload_schema: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TriggerInstance {
|
|
17
|
+
id: string;
|
|
18
|
+
trigger_slug: string;
|
|
19
|
+
connected_account_id: string | null;
|
|
20
|
+
status: "active" | "disabled";
|
|
21
|
+
config: Record<string, unknown>;
|
|
22
|
+
created_at: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TriggerProvider {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
|
|
29
|
+
// Browse available trigger types
|
|
30
|
+
listTriggerTypes(apiKey: string, toolkitSlugs?: string[]): Promise<TriggerType[]>;
|
|
31
|
+
getTriggerType(apiKey: string, slug: string): Promise<TriggerType | null>;
|
|
32
|
+
|
|
33
|
+
// CRUD trigger instances (all remote)
|
|
34
|
+
createTrigger(
|
|
35
|
+
apiKey: string,
|
|
36
|
+
slug: string,
|
|
37
|
+
connectedAccountId: string,
|
|
38
|
+
config?: Record<string, unknown>,
|
|
39
|
+
): Promise<{ triggerId: string }>;
|
|
40
|
+
listTriggers(apiKey: string): Promise<TriggerInstance[]>;
|
|
41
|
+
enableTrigger(apiKey: string, triggerId: string): Promise<boolean>;
|
|
42
|
+
disableTrigger(apiKey: string, triggerId: string): Promise<boolean>;
|
|
43
|
+
deleteTrigger(apiKey: string, triggerId: string): Promise<boolean>;
|
|
44
|
+
|
|
45
|
+
// Webhook configuration
|
|
46
|
+
setupWebhook(apiKey: string, webhookUrl: string): Promise<{ secret?: string }>;
|
|
47
|
+
getWebhookConfig(apiKey: string): Promise<{ url: string | null; secret: string | null }>;
|
|
48
|
+
|
|
49
|
+
// Webhook verification and parsing (each provider signs differently)
|
|
50
|
+
verifyWebhook(req: Request, body: string, secret: string): boolean;
|
|
51
|
+
parseWebhookPayload(body: Record<string, unknown>): {
|
|
52
|
+
triggerSlug: string;
|
|
53
|
+
triggerInstanceId: string | null;
|
|
54
|
+
payload: Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Provider registry
|
|
59
|
+
const triggerProviders: Map<string, TriggerProvider> = new Map();
|
|
60
|
+
|
|
61
|
+
export function registerTriggerProvider(provider: TriggerProvider) {
|
|
62
|
+
triggerProviders.set(provider.id, provider);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getTriggerProvider(id: string): TriggerProvider | undefined {
|
|
66
|
+
return triggerProviders.get(id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getTriggerProviderIds(): string[] {
|
|
70
|
+
return Array.from(triggerProviders.keys());
|
|
71
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import type { AptevaAPI, Agent, User } from "./api.js";
|
|
5
|
+
|
|
6
|
+
interface AgentListProps {
|
|
7
|
+
api: AptevaAPI;
|
|
8
|
+
user: User;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function AgentList({ api, user }: AgentListProps) {
|
|
12
|
+
const { exit } = useApp();
|
|
13
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [selected, setSelected] = useState(0);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const load = async () => {
|
|
19
|
+
const result = await api.getAgents();
|
|
20
|
+
setAgents(result);
|
|
21
|
+
setLoading(false);
|
|
22
|
+
};
|
|
23
|
+
load();
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
useInput((input, key) => {
|
|
27
|
+
if (input === "q") exit();
|
|
28
|
+
if (input === "r") {
|
|
29
|
+
setLoading(true);
|
|
30
|
+
api.getAgents().then(result => {
|
|
31
|
+
setAgents(result);
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (key.upArrow) setSelected(s => Math.max(0, s - 1));
|
|
36
|
+
if (key.downArrow) setSelected(s => Math.min(agents.length - 1, s + 1));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const statusColor = (status: string) => {
|
|
40
|
+
if (status === "running") return "green";
|
|
41
|
+
if (status === "error") return "red";
|
|
42
|
+
return "gray";
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const statusIcon = (status: string) => {
|
|
46
|
+
if (status === "running") return "●";
|
|
47
|
+
if (status === "error") return "✕";
|
|
48
|
+
return "○";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Column widths
|
|
52
|
+
const nameW = 24;
|
|
53
|
+
const statusW = 12;
|
|
54
|
+
const modelW = 28;
|
|
55
|
+
const providerW = 14;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Box flexDirection="column" padding={1}>
|
|
59
|
+
{/* Header */}
|
|
60
|
+
<Box marginBottom={1} justifyContent="space-between">
|
|
61
|
+
<Box>
|
|
62
|
+
<Text color="hex('#f97316')" bold>
|
|
63
|
+
{">"}_
|
|
64
|
+
</Text>
|
|
65
|
+
<Text bold> apteva</Text>
|
|
66
|
+
<Text dimColor> — agents</Text>
|
|
67
|
+
</Box>
|
|
68
|
+
<Text dimColor>
|
|
69
|
+
{user.username} ({user.role})
|
|
70
|
+
</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
|
|
73
|
+
{loading ? (
|
|
74
|
+
<Box>
|
|
75
|
+
<Text color="hex('#f97316')">
|
|
76
|
+
<Spinner type="dots" />
|
|
77
|
+
</Text>
|
|
78
|
+
<Text> Loading agents...</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
) : agents.length === 0 ? (
|
|
81
|
+
<Text dimColor>No agents found.</Text>
|
|
82
|
+
) : (
|
|
83
|
+
<Box flexDirection="column">
|
|
84
|
+
{/* Table header */}
|
|
85
|
+
<Box>
|
|
86
|
+
<Text bold color="hex('#888')">
|
|
87
|
+
{" "}
|
|
88
|
+
{pad("NAME", nameW)}
|
|
89
|
+
{pad("STATUS", statusW)}
|
|
90
|
+
{pad("MODEL", modelW)}
|
|
91
|
+
{pad("PROVIDER", providerW)}
|
|
92
|
+
</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
<Box marginBottom={0}>
|
|
95
|
+
<Text dimColor>
|
|
96
|
+
{" "}
|
|
97
|
+
{"─".repeat(nameW + statusW + modelW + providerW)}
|
|
98
|
+
</Text>
|
|
99
|
+
</Box>
|
|
100
|
+
|
|
101
|
+
{/* Rows */}
|
|
102
|
+
{agents.map((agent, i) => (
|
|
103
|
+
<Box key={agent.id}>
|
|
104
|
+
<Text color={i === selected ? "hex('#f97316')" : undefined}>
|
|
105
|
+
{i === selected ? "▸ " : " "}
|
|
106
|
+
</Text>
|
|
107
|
+
<Text color={i === selected ? "white" : undefined}>
|
|
108
|
+
{pad(agent.name, nameW)}
|
|
109
|
+
</Text>
|
|
110
|
+
<Text color={statusColor(agent.status)}>
|
|
111
|
+
{statusIcon(agent.status)}{" "}
|
|
112
|
+
{pad(agent.status, statusW - 2)}
|
|
113
|
+
</Text>
|
|
114
|
+
<Text dimColor>
|
|
115
|
+
{pad(agent.model, modelW)}
|
|
116
|
+
</Text>
|
|
117
|
+
<Text dimColor>
|
|
118
|
+
{pad(agent.provider, providerW)}
|
|
119
|
+
</Text>
|
|
120
|
+
</Box>
|
|
121
|
+
))}
|
|
122
|
+
</Box>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{/* Footer */}
|
|
126
|
+
<Box marginTop={1}>
|
|
127
|
+
<Text dimColor>
|
|
128
|
+
↑↓ navigate · r refresh · q quit
|
|
129
|
+
</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
<Box>
|
|
132
|
+
<Text dimColor>
|
|
133
|
+
{agents.length} agent{agents.length !== 1 ? "s" : ""}
|
|
134
|
+
{" · "}
|
|
135
|
+
{agents.filter(a => a.status === "running").length} running
|
|
136
|
+
</Text>
|
|
137
|
+
</Box>
|
|
138
|
+
</Box>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pad(str: string, width: number): string {
|
|
143
|
+
if (str.length >= width) return str.slice(0, width - 1) + "…";
|
|
144
|
+
return str + " ".repeat(width - str.length);
|
|
145
|
+
}
|
package/src/tui/App.tsx
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import { AptevaAPI, type User } from "./api.js";
|
|
5
|
+
import { Login } from "./Login.js";
|
|
6
|
+
import { AgentList } from "./AgentList.js";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { resolve, dirname } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
interface AppProps {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function App({ baseUrl }: AppProps) {
|
|
16
|
+
const [api] = useState(() => new AptevaAPI(baseUrl));
|
|
17
|
+
const [screen, setScreen] = useState<"connecting" | "login" | "agents">("connecting");
|
|
18
|
+
const [user, setUser] = useState<User | null>(null);
|
|
19
|
+
const [connectError, setConnectError] = useState("");
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
|
|
24
|
+
const tryConnect = async () => {
|
|
25
|
+
// First check if server is already running
|
|
26
|
+
const connected = await api.checkConnection();
|
|
27
|
+
if (cancelled) return;
|
|
28
|
+
|
|
29
|
+
if (connected) {
|
|
30
|
+
setScreen("login");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Server not running — try to start it
|
|
35
|
+
setConnectError("Server not running. Starting...");
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// Find the server entry point relative to this file
|
|
39
|
+
const serverPath = resolve(dirname(fileURLToPath(import.meta.url)), "../server.ts");
|
|
40
|
+
const child = spawn("bun", ["run", serverPath], {
|
|
41
|
+
stdio: "ignore",
|
|
42
|
+
detached: true,
|
|
43
|
+
env: { ...process.env, PORT: new URL(baseUrl).port || "4280" },
|
|
44
|
+
});
|
|
45
|
+
child.unref();
|
|
46
|
+
|
|
47
|
+
// Wait for server to come up (poll for up to 10 seconds)
|
|
48
|
+
for (let i = 0; i < 20; i++) {
|
|
49
|
+
if (cancelled) return;
|
|
50
|
+
await new Promise(r => setTimeout(r, 500));
|
|
51
|
+
const up = await api.checkConnection();
|
|
52
|
+
if (up) {
|
|
53
|
+
if (!cancelled) setScreen("login");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Spawn failed
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!cancelled) {
|
|
62
|
+
setConnectError(`Cannot connect to ${baseUrl}. Start the server with: bun run dev`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
tryConnect();
|
|
67
|
+
return () => { cancelled = true; };
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
if (screen === "connecting") {
|
|
71
|
+
return (
|
|
72
|
+
<Box flexDirection="column" padding={1}>
|
|
73
|
+
<Box marginBottom={1}>
|
|
74
|
+
<Text color="hex('#f97316')" bold>{">"}_</Text>
|
|
75
|
+
<Text bold> apteva</Text>
|
|
76
|
+
</Box>
|
|
77
|
+
<Box>
|
|
78
|
+
<Text color="hex('#f97316')"><Spinner type="dots" /></Text>
|
|
79
|
+
<Text> {connectError || `Connecting to ${baseUrl}...`}</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (screen === "login") {
|
|
86
|
+
return (
|
|
87
|
+
<Login
|
|
88
|
+
api={api}
|
|
89
|
+
onSuccess={(u) => {
|
|
90
|
+
setUser(u);
|
|
91
|
+
setScreen("agents");
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (screen === "agents" && user) {
|
|
98
|
+
return <AgentList api={api} user={user} />;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
4
|
+
import Spinner from "ink-spinner";
|
|
5
|
+
import type { AptevaAPI, User } from "./api.js";
|
|
6
|
+
|
|
7
|
+
interface LoginProps {
|
|
8
|
+
api: AptevaAPI;
|
|
9
|
+
onSuccess: (user: User) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Login({ api, onSuccess }: LoginProps) {
|
|
13
|
+
const [field, setField] = useState<"username" | "password">("username");
|
|
14
|
+
const [username, setUsername] = useState("");
|
|
15
|
+
const [password, setPassword] = useState("");
|
|
16
|
+
const [error, setError] = useState("");
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
useInput((input, key) => {
|
|
20
|
+
if (key.tab || (key.return && field === "username" && username)) {
|
|
21
|
+
setField(field === "username" ? "password" : "username");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const handleSubmit = async () => {
|
|
26
|
+
if (!username || !password) return;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError("");
|
|
29
|
+
const result = await api.login(username, password);
|
|
30
|
+
setLoading(false);
|
|
31
|
+
if (result.success && result.user) {
|
|
32
|
+
onSuccess(result.user);
|
|
33
|
+
} else {
|
|
34
|
+
setError(result.error || "Login failed");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Box flexDirection="column" padding={1}>
|
|
40
|
+
<Box marginBottom={1}>
|
|
41
|
+
<Text color="hex('#f97316')" bold>
|
|
42
|
+
{">"}_
|
|
43
|
+
</Text>
|
|
44
|
+
<Text bold> apteva</Text>
|
|
45
|
+
</Box>
|
|
46
|
+
|
|
47
|
+
<Box marginBottom={1}>
|
|
48
|
+
<Text dimColor>Sign in to continue</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
|
|
51
|
+
<Box>
|
|
52
|
+
<Text color={field === "username" ? "hex('#f97316')" : "white"}>
|
|
53
|
+
Username:{" "}
|
|
54
|
+
</Text>
|
|
55
|
+
{field === "username" ? (
|
|
56
|
+
<TextInput
|
|
57
|
+
value={username}
|
|
58
|
+
onChange={setUsername}
|
|
59
|
+
onSubmit={() => {
|
|
60
|
+
if (username) setField("password");
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<Text>{username}</Text>
|
|
65
|
+
)}
|
|
66
|
+
</Box>
|
|
67
|
+
|
|
68
|
+
<Box>
|
|
69
|
+
<Text color={field === "password" ? "hex('#f97316')" : "white"}>
|
|
70
|
+
Password:{" "}
|
|
71
|
+
</Text>
|
|
72
|
+
{field === "password" ? (
|
|
73
|
+
<TextInput
|
|
74
|
+
value={password}
|
|
75
|
+
onChange={setPassword}
|
|
76
|
+
onSubmit={handleSubmit}
|
|
77
|
+
mask="*"
|
|
78
|
+
/>
|
|
79
|
+
) : (
|
|
80
|
+
<Text dimColor>{"*".repeat(password.length) || "..."}</Text>
|
|
81
|
+
)}
|
|
82
|
+
</Box>
|
|
83
|
+
|
|
84
|
+
{loading && (
|
|
85
|
+
<Box marginTop={1}>
|
|
86
|
+
<Text color="hex('#f97316')">
|
|
87
|
+
<Spinner type="dots" />
|
|
88
|
+
</Text>
|
|
89
|
+
<Text> Signing in...</Text>
|
|
90
|
+
</Box>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{error && (
|
|
94
|
+
<Box marginTop={1}>
|
|
95
|
+
<Text color="red">{error}</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<Box marginTop={1}>
|
|
100
|
+
<Text dimColor>Tab to switch fields · Enter to submit</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
}
|
package/src/tui/api.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface Agent {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
model: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
status: "running" | "stopped" | "error";
|
|
7
|
+
port: number | null;
|
|
8
|
+
projectId: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface User {
|
|
12
|
+
id: string;
|
|
13
|
+
username: string;
|
|
14
|
+
role: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AptevaAPI {
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
private token: string | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(baseUrl: string) {
|
|
22
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async checkConnection(): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${this.baseUrl}/api/auth/check`, { signal: AbortSignal.timeout(3000) });
|
|
28
|
+
return res.ok;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async fetch(path: string, opts: RequestInit = {}): Promise<Response> {
|
|
35
|
+
const headers: Record<string, string> = {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...(opts.headers as Record<string, string> || {}),
|
|
38
|
+
};
|
|
39
|
+
if (this.token) {
|
|
40
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
41
|
+
}
|
|
42
|
+
return fetch(`${this.baseUrl}${path}`, { ...opts, headers });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async login(username: string, password: string): Promise<{ success: boolean; user?: User; error?: string }> {
|
|
46
|
+
try {
|
|
47
|
+
const res = await this.fetch("/api/auth/login", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify({ username, password }),
|
|
50
|
+
});
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
return { success: false, error: data.error || "Login failed" };
|
|
54
|
+
}
|
|
55
|
+
this.token = data.accessToken;
|
|
56
|
+
return { success: true, user: data.user };
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
return { success: false, error: err.message || "Connection failed" };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getAgents(): Promise<Agent[]> {
|
|
63
|
+
try {
|
|
64
|
+
const res = await this.fetch("/api/agents");
|
|
65
|
+
if (!res.ok) return [];
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
return data.agents || [];
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/web/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo } from "react";
|
|
1
|
+
import React, { useState, useEffect, useMemo, lazy, Suspense } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import "@apteva/apteva-kit/styles.css";
|
|
4
4
|
|
|
@@ -12,28 +12,31 @@ import { TelemetryProvider, AuthProvider, ProjectProvider, useAuth, useProjects,
|
|
|
12
12
|
// Hooks
|
|
13
13
|
import { useAgents, useProviders, useOnboarding } from "./hooks";
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Core components (always needed)
|
|
16
16
|
import {
|
|
17
17
|
LoadingSpinner,
|
|
18
18
|
Header,
|
|
19
19
|
Sidebar,
|
|
20
20
|
ErrorBanner,
|
|
21
21
|
OnboardingWizard,
|
|
22
|
-
SettingsPage,
|
|
23
22
|
CreateAgentModal,
|
|
24
23
|
AgentsView,
|
|
25
24
|
Dashboard,
|
|
26
|
-
ActivityPage,
|
|
27
|
-
TasksPage,
|
|
28
|
-
McpPage,
|
|
29
|
-
SkillsPage,
|
|
30
|
-
TestsPage,
|
|
31
|
-
TelemetryPage,
|
|
32
25
|
LoginPage,
|
|
33
26
|
} from "./components";
|
|
34
|
-
import { ApiDocsPage } from "./components/api/ApiDocsPage";
|
|
35
27
|
import { MetaAgentProvider, MetaAgentPanel } from "./components/meta-agent/MetaAgent";
|
|
36
28
|
|
|
29
|
+
// Lazy-loaded page components (only loaded when navigated to)
|
|
30
|
+
const SettingsPage = lazy(() => import("./components/settings/SettingsPage").then(m => ({ default: m.SettingsPage })));
|
|
31
|
+
const ActivityPage = lazy(() => import("./components/activity/ActivityPage").then(m => ({ default: m.ActivityPage })));
|
|
32
|
+
const TasksPage = lazy(() => import("./components/tasks/TasksPage").then(m => ({ default: m.TasksPage })));
|
|
33
|
+
const McpPage = lazy(() => import("./components/mcp/McpPage").then(m => ({ default: m.McpPage })));
|
|
34
|
+
const SkillsPage = lazy(() => import("./components/skills/SkillsPage").then(m => ({ default: m.SkillsPage })));
|
|
35
|
+
const TestsPage = lazy(() => import("./components/tests/TestsPage").then(m => ({ default: m.TestsPage })));
|
|
36
|
+
const TelemetryPage = lazy(() => import("./components/telemetry/TelemetryPage").then(m => ({ default: m.TelemetryPage })));
|
|
37
|
+
const ConnectionsPage = lazy(() => import("./components/connections/ConnectionsPage").then(m => ({ default: m.ConnectionsPage })));
|
|
38
|
+
const ApiDocsPage = lazy(() => import("./components/api/ApiDocsPage").then(m => ({ default: m.ApiDocsPage })));
|
|
39
|
+
|
|
37
40
|
function AppContent() {
|
|
38
41
|
// Auth state
|
|
39
42
|
const { isAuthenticated, isLoading: authLoading, hasUsers, accessToken, checkAuth } = useAuth();
|
|
@@ -237,7 +240,7 @@ function AppContent() {
|
|
|
237
240
|
|
|
238
241
|
return (
|
|
239
242
|
<div className="h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex flex-col overflow-hidden">
|
|
240
|
-
<Header onMenuClick={() => setMobileMenuOpen(true)} />
|
|
243
|
+
<Header onMenuClick={() => setMobileMenuOpen(true)} agents={agents} />
|
|
241
244
|
|
|
242
245
|
{startError && (
|
|
243
246
|
<ErrorBanner message={startError} onDismiss={() => setStartError(null)} />
|
|
@@ -254,6 +257,7 @@ function AppContent() {
|
|
|
254
257
|
/>
|
|
255
258
|
|
|
256
259
|
<main className="flex-1 overflow-hidden flex">
|
|
260
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
257
261
|
{route === "settings" && <SettingsPage />}
|
|
258
262
|
|
|
259
263
|
{route === "activity" && (
|
|
@@ -292,6 +296,8 @@ function AppContent() {
|
|
|
292
296
|
|
|
293
297
|
{route === "tasks" && <TasksPage />}
|
|
294
298
|
|
|
299
|
+
{route === "connections" && <ConnectionsPage />}
|
|
300
|
+
|
|
295
301
|
{route === "mcp" && <McpPage />}
|
|
296
302
|
|
|
297
303
|
{route === "skills" && <SkillsPage />}
|
|
@@ -301,6 +307,7 @@ function AppContent() {
|
|
|
301
307
|
{route === "telemetry" && <TelemetryPage />}
|
|
302
308
|
|
|
303
309
|
{route === "api" && <ApiDocsPage />}
|
|
310
|
+
</Suspense>
|
|
304
311
|
</main>
|
|
305
312
|
</div>
|
|
306
313
|
|
|
@@ -121,13 +121,16 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
121
121
|
|
|
122
122
|
<button
|
|
123
123
|
onClick={onToggle}
|
|
124
|
+
disabled={agent.status === "starting" || agent.status === "stopping"}
|
|
124
125
|
className={`w-full px-3 py-1.5 rounded text-sm font-medium transition mt-auto ${
|
|
125
|
-
agent.status === "
|
|
126
|
-
? "bg-[#
|
|
127
|
-
:
|
|
126
|
+
agent.status === "starting" || agent.status === "stopping"
|
|
127
|
+
? "bg-[#333] text-[#666] cursor-wait"
|
|
128
|
+
: agent.status === "running"
|
|
129
|
+
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
130
|
+
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
128
131
|
}`}
|
|
129
132
|
>
|
|
130
|
-
{agent.status === "running" ? "Stop" : "Start"}
|
|
133
|
+
{agent.status === "starting" ? "Starting..." : agent.status === "stopping" ? "Stopping..." : agent.status === "running" ? "Stop" : "Start"}
|
|
131
134
|
</button>
|
|
132
135
|
</div>
|
|
133
136
|
);
|
|
@@ -142,12 +145,16 @@ function StatusBadge({ status, isActive, activityType }: { status: Agent["status
|
|
|
142
145
|
);
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
const isTransitioning = status === "starting" || status === "stopping";
|
|
149
|
+
|
|
145
150
|
return (
|
|
146
151
|
<span
|
|
147
152
|
className={`px-2 py-1 rounded text-xs font-medium ${
|
|
148
|
-
|
|
149
|
-
? "bg-
|
|
150
|
-
:
|
|
153
|
+
isTransitioning
|
|
154
|
+
? "bg-yellow-500/20 text-yellow-400 animate-pulse"
|
|
155
|
+
: status === "running"
|
|
156
|
+
? "bg-[#3b82f6]/20 text-[#3b82f6]"
|
|
157
|
+
: "bg-[#333] text-[#666]"
|
|
151
158
|
}`}
|
|
152
159
|
>
|
|
153
160
|
{status}
|