apteva 0.4.57 → 0.7.1
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/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +15 -76
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { json } from "./helpers";
|
|
2
|
-
import { AgentDB, SubscriptionDB, SettingsDB } from "../../db";
|
|
3
|
-
import { getTriggerProvider } from "../../triggers";
|
|
4
|
-
import { agentFetch } from "./agent-utils";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Central webhook receiver for trigger providers.
|
|
8
|
-
* POST /api/webhooks/:provider — receives trigger events from any registered provider,
|
|
9
|
-
* verifies HMAC, looks up local subscriptions, and dispatches to the appropriate agent(s).
|
|
10
|
-
*
|
|
11
|
-
* This endpoint is public (HMAC-verified, no JWT/API key auth).
|
|
12
|
-
*/
|
|
13
|
-
export async function handleWebhookRoutes(
|
|
14
|
-
req: Request,
|
|
15
|
-
path: string,
|
|
16
|
-
method: string,
|
|
17
|
-
): Promise<Response | null> {
|
|
18
|
-
|
|
19
|
-
// POST /api/webhooks/composio
|
|
20
|
-
if (path === "/api/webhooks/composio" && method === "POST") {
|
|
21
|
-
return handleProviderWebhook(req, "composio");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// POST /api/webhooks/agentdojo
|
|
25
|
-
if (path === "/api/webhooks/agentdojo" && method === "POST") {
|
|
26
|
-
return handleProviderWebhook(req, "agentdojo");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function handleProviderWebhook(req: Request, providerId: string): Promise<Response> {
|
|
33
|
-
const provider = getTriggerProvider(providerId);
|
|
34
|
-
if (!provider) {
|
|
35
|
-
return json({ error: `${providerId} provider not registered` }, 500);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Read raw body for HMAC verification
|
|
39
|
-
let rawBody: string;
|
|
40
|
-
try {
|
|
41
|
-
rawBody = await req.text();
|
|
42
|
-
} catch {
|
|
43
|
-
return json({ error: "Failed to read request body" }, 400);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Verify HMAC signature using stored webhook secret
|
|
47
|
-
const webhookSecret = SettingsDB.get(`${providerId}_webhook_secret`);
|
|
48
|
-
if (webhookSecret) {
|
|
49
|
-
const valid = provider.verifyWebhook(req, rawBody, webhookSecret);
|
|
50
|
-
if (!valid) {
|
|
51
|
-
console.warn(`[webhook] Invalid HMAC signature for ${providerId} webhook`);
|
|
52
|
-
return json({ error: "Invalid signature" }, 401);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
console.warn(`[webhook] No ${providerId} webhook secret configured — skipping HMAC verification`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Parse the payload
|
|
59
|
-
let body: Record<string, unknown>;
|
|
60
|
-
try {
|
|
61
|
-
body = JSON.parse(rawBody);
|
|
62
|
-
} catch {
|
|
63
|
-
return json({ error: "Invalid JSON" }, 400);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Log raw webhook for debugging
|
|
67
|
-
console.log(`[webhook:${providerId}] Raw payload:`, JSON.stringify(body, null, 2));
|
|
68
|
-
|
|
69
|
-
const { triggerSlug, triggerInstanceId, payload } = provider.parseWebhookPayload(body);
|
|
70
|
-
console.log(`[webhook:${providerId}] Parsed:`, { triggerSlug, triggerInstanceId });
|
|
71
|
-
|
|
72
|
-
// Respond 200 immediately — dispatch async
|
|
73
|
-
const dispatchPromise = dispatchToSubscribers(providerId, triggerSlug, triggerInstanceId, payload);
|
|
74
|
-
|
|
75
|
-
// Fire and forget — but log errors
|
|
76
|
-
dispatchPromise.catch(err => {
|
|
77
|
-
console.error(`[webhook:${providerId}] Dispatch error:`, err);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return json({ received: true, provider: providerId, trigger: triggerSlug });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function dispatchToSubscribers(
|
|
84
|
-
providerId: string,
|
|
85
|
-
triggerSlug: string,
|
|
86
|
-
triggerInstanceId: string | null,
|
|
87
|
-
payload: Record<string, unknown>,
|
|
88
|
-
): Promise<void> {
|
|
89
|
-
// Find matching subscriptions:
|
|
90
|
-
// 1. Exact match by trigger_instance_id (most specific)
|
|
91
|
-
// 2. Match by trigger_slug (broader)
|
|
92
|
-
let subscriptions = triggerInstanceId
|
|
93
|
-
? SubscriptionDB.findByTriggerInstanceId(triggerInstanceId)
|
|
94
|
-
: [];
|
|
95
|
-
|
|
96
|
-
// If no instance-level matches, fall back to slug-level
|
|
97
|
-
if (subscriptions.length === 0) {
|
|
98
|
-
subscriptions = SubscriptionDB.findByTriggerSlug(triggerSlug);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Filter to enabled only
|
|
102
|
-
subscriptions = subscriptions.filter(s => s.enabled);
|
|
103
|
-
|
|
104
|
-
if (subscriptions.length === 0) {
|
|
105
|
-
console.log(`[webhook:${providerId}] No subscriptions for trigger ${triggerSlug} (instance: ${triggerInstanceId || "none"})`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Dispatch to each subscribed agent
|
|
110
|
-
const results = await Promise.allSettled(
|
|
111
|
-
subscriptions.map(sub => dispatchToAgent(sub.agent_id, triggerSlug, payload)),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < results.length; i++) {
|
|
115
|
-
const result = results[i];
|
|
116
|
-
const sub = subscriptions[i];
|
|
117
|
-
if (result.status === "rejected") {
|
|
118
|
-
console.error(`[webhook:${providerId}] Failed to dispatch to agent ${sub.agent_id}:`, result.reason);
|
|
119
|
-
} else {
|
|
120
|
-
console.log(`[webhook:${providerId}] Dispatched ${triggerSlug} to agent ${sub.agent_id}: ${result.value}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function dispatchToAgent(
|
|
126
|
-
agentId: string,
|
|
127
|
-
triggerSlug: string,
|
|
128
|
-
payload: Record<string, unknown>,
|
|
129
|
-
): Promise<string> {
|
|
130
|
-
const agent = AgentDB.findById(agentId);
|
|
131
|
-
if (!agent) {
|
|
132
|
-
return "agent_not_found";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (agent.status !== "running" || !agent.port) {
|
|
136
|
-
return "agent_not_running";
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Format the trigger event as a chat message
|
|
140
|
-
const triggerName = triggerSlug.replace(/_/g, " ").replace(/:/g, " → ");
|
|
141
|
-
const message = [
|
|
142
|
-
`[Trigger: ${triggerName}]`,
|
|
143
|
-
"",
|
|
144
|
-
"```json",
|
|
145
|
-
JSON.stringify(payload, null, 2),
|
|
146
|
-
"```",
|
|
147
|
-
"",
|
|
148
|
-
"Process this event and take appropriate action.",
|
|
149
|
-
].join("\n");
|
|
150
|
-
|
|
151
|
-
const response = await agentFetch(agent.id, agent.port, "/chat", {
|
|
152
|
-
method: "POST",
|
|
153
|
-
headers: { "Content-Type": "application/json" },
|
|
154
|
-
body: JSON.stringify({ message }),
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Consume the streaming response
|
|
158
|
-
if (response.body) {
|
|
159
|
-
try {
|
|
160
|
-
const reader = response.body.getReader();
|
|
161
|
-
while (true) {
|
|
162
|
-
const { done } = await reader.read();
|
|
163
|
-
if (done) break;
|
|
164
|
-
}
|
|
165
|
-
} catch {
|
|
166
|
-
// Ignore read errors
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return response.ok ? "sent" : "agent_error";
|
|
171
|
-
}
|
package/src/routes/api.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { AuthContext } from "../auth/middleware";
|
|
2
|
-
import { json } from "./api/helpers";
|
|
3
|
-
import { handleSystemRoutes } from "./api/system";
|
|
4
|
-
import { handleProviderRoutes } from "./api/providers";
|
|
5
|
-
import { handleUserRoutes } from "./api/users";
|
|
6
|
-
import { handleProjectRoutes } from "./api/projects";
|
|
7
|
-
import { handleAgentRoutes } from "./api/agents";
|
|
8
|
-
import { handleMcpRoutes } from "./api/mcp";
|
|
9
|
-
import { handleSkillRoutes } from "./api/skills";
|
|
10
|
-
import { handleIntegrationRoutes } from "./api/integrations";
|
|
11
|
-
import { handleTriggerRoutes } from "./api/triggers";
|
|
12
|
-
import { handleWebhookRoutes } from "./api/webhooks";
|
|
13
|
-
import { handleMetaAgentRoutes } from "./api/meta-agent";
|
|
14
|
-
import { handleTelemetryRoutes } from "./api/telemetry";
|
|
15
|
-
import { handleTestRoutes } from "./api/tests";
|
|
16
|
-
import { handleApiKeyRoutes } from "./api/api-keys";
|
|
17
|
-
import { handleChannelRoutes } from "./api/channels";
|
|
18
|
-
import { handlePlatformMcpRequest } from "../mcp-platform";
|
|
19
|
-
|
|
20
|
-
// Re-export for backward compatibility (server.ts dynamic import)
|
|
21
|
-
export { startAgentProcess } from "./api/agent-utils";
|
|
22
|
-
|
|
23
|
-
export async function handleApiRequest(
|
|
24
|
-
req: Request,
|
|
25
|
-
path: string,
|
|
26
|
-
authContext?: AuthContext,
|
|
27
|
-
): Promise<Response> {
|
|
28
|
-
const method = req.method;
|
|
29
|
-
|
|
30
|
-
// Built-in platform MCP server (for meta agent)
|
|
31
|
-
if (path === "/api/mcp/platform" && method === "POST") {
|
|
32
|
-
return handlePlatformMcpRequest(req);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
(await handleWebhookRoutes(req, path, method)) ?? // Public, HMAC-verified — before auth
|
|
37
|
-
(await handleSystemRoutes(req, path, method, authContext)) ??
|
|
38
|
-
(await handleApiKeyRoutes(req, path, method, authContext)) ?? // Must be before provider routes to handle /api/keys/personal
|
|
39
|
-
(await handleProviderRoutes(req, path, method, authContext)) ??
|
|
40
|
-
(await handleUserRoutes(req, path, method, authContext)) ??
|
|
41
|
-
(await handleProjectRoutes(req, path, method, authContext)) ??
|
|
42
|
-
(await handleAgentRoutes(req, path, method, authContext)) ??
|
|
43
|
-
(await handleMcpRoutes(req, path, method)) ??
|
|
44
|
-
(await handleSkillRoutes(req, path, method)) ??
|
|
45
|
-
(await handleIntegrationRoutes(req, path, method, authContext)) ??
|
|
46
|
-
(await handleTriggerRoutes(req, path, method, authContext)) ??
|
|
47
|
-
(await handleChannelRoutes(req, path, method)) ??
|
|
48
|
-
(await handleMetaAgentRoutes(req, path, method)) ??
|
|
49
|
-
(await handleTelemetryRoutes(req, path, method)) ??
|
|
50
|
-
(await handleTestRoutes(req, path, method)) ??
|
|
51
|
-
json({ error: "Not found" }, 404)
|
|
52
|
-
);
|
|
53
|
-
}
|
package/src/routes/auth.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { UserDB } from "../db";
|
|
2
|
-
import {
|
|
3
|
-
login,
|
|
4
|
-
refreshSession,
|
|
5
|
-
invalidateSession,
|
|
6
|
-
getAuthStatus,
|
|
7
|
-
verifyAccessToken,
|
|
8
|
-
hashPassword,
|
|
9
|
-
validatePassword,
|
|
10
|
-
REFRESH_TOKEN_EXPIRY,
|
|
11
|
-
} from "../auth";
|
|
12
|
-
import { Onboarding } from "../providers";
|
|
13
|
-
import {
|
|
14
|
-
getTokenFromRequest,
|
|
15
|
-
getRefreshTokenFromCookie,
|
|
16
|
-
createRefreshTokenCookie,
|
|
17
|
-
clearRefreshTokenCookie,
|
|
18
|
-
} from "../auth/middleware";
|
|
19
|
-
|
|
20
|
-
function json(data: unknown, status = 200, headers: Record<string, string> = {}): Response {
|
|
21
|
-
return new Response(JSON.stringify(data), {
|
|
22
|
-
status,
|
|
23
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function handleAuthRequest(req: Request, path: string): Promise<Response> {
|
|
28
|
-
const method = req.method;
|
|
29
|
-
|
|
30
|
-
// GET /api/auth/check - Check authentication status (includes onboarding to avoid extra round trip)
|
|
31
|
-
if (path === "/api/auth/check" && method === "GET") {
|
|
32
|
-
const token = getTokenFromRequest(req);
|
|
33
|
-
const status = getAuthStatus(token || undefined);
|
|
34
|
-
const onboarding = Onboarding.getStatus();
|
|
35
|
-
return json({ ...status, onboarding });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// POST /api/auth/login - Login with username and password
|
|
39
|
-
if (path === "/api/auth/login" && method === "POST") {
|
|
40
|
-
try {
|
|
41
|
-
const body = await req.json();
|
|
42
|
-
const { username, password } = body;
|
|
43
|
-
|
|
44
|
-
if (!username || !password) {
|
|
45
|
-
return json({ error: "Username and password are required" }, 400);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const result = await login(username, password);
|
|
49
|
-
|
|
50
|
-
if (!result.success) {
|
|
51
|
-
return json({ error: result.error }, 401);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Set refresh token as httpOnly cookie
|
|
55
|
-
const cookieHeader = createRefreshTokenCookie(result.tokens!.refreshToken, REFRESH_TOKEN_EXPIRY);
|
|
56
|
-
|
|
57
|
-
return json(
|
|
58
|
-
{
|
|
59
|
-
user: result.user,
|
|
60
|
-
accessToken: result.tokens!.accessToken,
|
|
61
|
-
expiresIn: result.tokens!.expiresIn,
|
|
62
|
-
},
|
|
63
|
-
200,
|
|
64
|
-
{ "Set-Cookie": cookieHeader }
|
|
65
|
-
);
|
|
66
|
-
} catch (e) {
|
|
67
|
-
return json({ error: "Invalid request body" }, 400);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// POST /api/auth/logout - Logout (invalidate refresh token)
|
|
72
|
-
if (path === "/api/auth/logout" && method === "POST") {
|
|
73
|
-
const refreshToken = getRefreshTokenFromCookie(req);
|
|
74
|
-
|
|
75
|
-
if (refreshToken) {
|
|
76
|
-
invalidateSession(refreshToken);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Clear the cookie
|
|
80
|
-
return json(
|
|
81
|
-
{ success: true },
|
|
82
|
-
200,
|
|
83
|
-
{ "Set-Cookie": clearRefreshTokenCookie() }
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// POST /api/auth/refresh - Refresh access token
|
|
88
|
-
if (path === "/api/auth/refresh" && method === "POST") {
|
|
89
|
-
// No users = no valid sessions possible
|
|
90
|
-
if (!UserDB.hasUsers()) {
|
|
91
|
-
return json({ error: "No users exist" }, 401, { "Set-Cookie": clearRefreshTokenCookie() });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const refreshToken = getRefreshTokenFromCookie(req);
|
|
95
|
-
|
|
96
|
-
if (!refreshToken) {
|
|
97
|
-
return json({ error: "No refresh token" }, 401);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const result = await refreshSession(refreshToken);
|
|
101
|
-
|
|
102
|
-
if (!result) {
|
|
103
|
-
return json(
|
|
104
|
-
{ error: "Invalid or expired refresh token" },
|
|
105
|
-
401,
|
|
106
|
-
{ "Set-Cookie": clearRefreshTokenCookie() }
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Set new refresh token cookie
|
|
111
|
-
const cookieHeader = createRefreshTokenCookie(result.refreshToken, REFRESH_TOKEN_EXPIRY);
|
|
112
|
-
|
|
113
|
-
// Include user info + onboarding to avoid extra /api/auth/me round trip
|
|
114
|
-
const payload = verifyAccessToken(result.accessToken);
|
|
115
|
-
const user = payload ? UserDB.findById(payload.userId) : null;
|
|
116
|
-
const onboarding = Onboarding.getStatus();
|
|
117
|
-
|
|
118
|
-
return json(
|
|
119
|
-
{
|
|
120
|
-
accessToken: result.accessToken,
|
|
121
|
-
expiresIn: result.expiresIn,
|
|
122
|
-
user: user ? { id: user.id, username: user.username, role: user.role } : undefined,
|
|
123
|
-
onboarding,
|
|
124
|
-
},
|
|
125
|
-
200,
|
|
126
|
-
{ "Set-Cookie": cookieHeader }
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// GET /api/auth/me - Get current user
|
|
131
|
-
if (path === "/api/auth/me" && method === "GET") {
|
|
132
|
-
const token = getTokenFromRequest(req);
|
|
133
|
-
|
|
134
|
-
if (!token) {
|
|
135
|
-
return json({ error: "Unauthorized" }, 401);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const payload = verifyAccessToken(token);
|
|
139
|
-
if (!payload) {
|
|
140
|
-
return json({ error: "Invalid or expired token" }, 401);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const user = UserDB.findById(payload.userId);
|
|
144
|
-
if (!user) {
|
|
145
|
-
return json({ error: "User not found" }, 404);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return json({
|
|
149
|
-
user: {
|
|
150
|
-
id: user.id,
|
|
151
|
-
username: user.username,
|
|
152
|
-
email: user.email,
|
|
153
|
-
role: user.role,
|
|
154
|
-
createdAt: user.created_at,
|
|
155
|
-
lastLoginAt: user.last_login_at,
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// PUT /api/auth/me - Update current user profile
|
|
161
|
-
if (path === "/api/auth/me" && method === "PUT") {
|
|
162
|
-
const token = getTokenFromRequest(req);
|
|
163
|
-
|
|
164
|
-
if (!token) {
|
|
165
|
-
return json({ error: "Unauthorized" }, 401);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const payload = verifyAccessToken(token);
|
|
169
|
-
if (!payload) {
|
|
170
|
-
return json({ error: "Invalid or expired token" }, 401);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const user = UserDB.findById(payload.userId);
|
|
174
|
-
if (!user) {
|
|
175
|
-
return json({ error: "User not found" }, 404);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const body = await req.json();
|
|
180
|
-
const updates: Parameters<typeof UserDB.update>[1] = {};
|
|
181
|
-
|
|
182
|
-
if (body.email !== undefined) updates.email = body.email;
|
|
183
|
-
|
|
184
|
-
const updated = UserDB.update(user.id, updates);
|
|
185
|
-
|
|
186
|
-
return json({
|
|
187
|
-
user: updated ? {
|
|
188
|
-
id: updated.id,
|
|
189
|
-
username: updated.username,
|
|
190
|
-
email: updated.email,
|
|
191
|
-
role: updated.role,
|
|
192
|
-
createdAt: updated.created_at,
|
|
193
|
-
lastLoginAt: updated.last_login_at,
|
|
194
|
-
} : null,
|
|
195
|
-
});
|
|
196
|
-
} catch (e) {
|
|
197
|
-
return json({ error: "Invalid request body" }, 400);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// PUT /api/auth/password - Change password
|
|
202
|
-
if (path === "/api/auth/password" && method === "PUT") {
|
|
203
|
-
const token = getTokenFromRequest(req);
|
|
204
|
-
|
|
205
|
-
if (!token) {
|
|
206
|
-
return json({ error: "Unauthorized" }, 401);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const payload = verifyAccessToken(token);
|
|
210
|
-
if (!payload) {
|
|
211
|
-
return json({ error: "Invalid or expired token" }, 401);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const user = UserDB.findById(payload.userId);
|
|
215
|
-
if (!user) {
|
|
216
|
-
return json({ error: "User not found" }, 404);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
const body = await req.json();
|
|
221
|
-
const { currentPassword, newPassword } = body;
|
|
222
|
-
|
|
223
|
-
if (!currentPassword || !newPassword) {
|
|
224
|
-
return json({ error: "Current and new password are required" }, 400);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Verify current password
|
|
228
|
-
const { verifyPassword } = await import("../auth");
|
|
229
|
-
const isValid = await verifyPassword(currentPassword, user.password_hash);
|
|
230
|
-
if (!isValid) {
|
|
231
|
-
return json({ error: "Current password is incorrect" }, 401);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Validate new password
|
|
235
|
-
const validation = validatePassword(newPassword);
|
|
236
|
-
if (!validation.valid) {
|
|
237
|
-
return json({ error: validation.errors.join(". ") }, 400);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Update password
|
|
241
|
-
const newHash = await hashPassword(newPassword);
|
|
242
|
-
UserDB.update(user.id, { password_hash: newHash });
|
|
243
|
-
|
|
244
|
-
return json({ success: true, message: "Password updated successfully" });
|
|
245
|
-
} catch (e) {
|
|
246
|
-
return json({ error: "Invalid request body" }, 400);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return json({ error: "Not found" }, 404);
|
|
251
|
-
}
|
package/src/routes/share.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import { AgentDB, isRealtimeEnabled, type Agent } from "../db";
|
|
3
|
-
import { agentFetch } from "./api/agent-utils";
|
|
4
|
-
|
|
5
|
-
function deriveShareToken(apiKey: string, agentId: string): string {
|
|
6
|
-
return createHash("sha256")
|
|
7
|
-
.update(apiKey + ":" + agentId + ":share")
|
|
8
|
-
.digest("hex")
|
|
9
|
-
.substring(0, 32);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function findAgentByShareToken(token: string): Agent | null {
|
|
13
|
-
const agents = AgentDB.findAll();
|
|
14
|
-
for (const agent of agents) {
|
|
15
|
-
const apiKey = AgentDB.getApiKey(agent.id);
|
|
16
|
-
if (!apiKey) continue;
|
|
17
|
-
if (deriveShareToken(apiKey, agent.id) === token) return agent;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Get the share token for an agent (used by API route for the UI) */
|
|
23
|
-
export function getShareToken(agentId: string): string | null {
|
|
24
|
-
const apiKey = AgentDB.getApiKey(agentId);
|
|
25
|
-
if (!apiKey) return null;
|
|
26
|
-
return deriveShareToken(apiKey, agentId);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function handleShareRequest(req: Request, path: string): Promise<Response | null> {
|
|
30
|
-
// Match /share/<32 hex chars> sub-paths for API calls
|
|
31
|
-
const infoMatch = path.match(/^\/share\/([a-f0-9]{32})\/info$/);
|
|
32
|
-
const chatMatch = path.match(/^\/share\/([a-f0-9]{32})\/chat$/);
|
|
33
|
-
|
|
34
|
-
if (!infoMatch && !chatMatch) return null;
|
|
35
|
-
|
|
36
|
-
const token = (infoMatch || chatMatch)![1];
|
|
37
|
-
const agent = findAgentByShareToken(token);
|
|
38
|
-
|
|
39
|
-
// Intentionally vague 404 — don't reveal whether token exists
|
|
40
|
-
if (!agent) {
|
|
41
|
-
return new Response("Not found", { status: 404 });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// GET /share/:token/info — agent info (no secrets)
|
|
45
|
-
if (infoMatch && req.method === "GET") {
|
|
46
|
-
return Response.json({
|
|
47
|
-
name: agent.name,
|
|
48
|
-
status: agent.status,
|
|
49
|
-
voiceEnabled: isRealtimeEnabled(agent.features),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// POST /share/:token/chat — proxy to agent
|
|
54
|
-
if (chatMatch && req.method === "POST") {
|
|
55
|
-
if (agent.status !== "running" || !agent.port) {
|
|
56
|
-
return Response.json({ error: "Agent is currently offline" }, { status: 503 });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const body = await req.json();
|
|
61
|
-
const response = await agentFetch(agent.id, agent.port, "/chat", {
|
|
62
|
-
method: "POST",
|
|
63
|
-
headers: { "Content-Type": "application/json" },
|
|
64
|
-
body: JSON.stringify(body),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
const errorText = await response.text();
|
|
69
|
-
return Response.json({ error: `Agent error: ${errorText}` }, { status: response.status });
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return new Response(response.body, {
|
|
73
|
-
status: 200,
|
|
74
|
-
headers: {
|
|
75
|
-
"Content-Type": response.headers.get("Content-Type") || "text/event-stream",
|
|
76
|
-
"Cache-Control": "no-cache",
|
|
77
|
-
"Connection": "keep-alive",
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
} catch (err) {
|
|
81
|
-
return Response.json({ error: "Failed to connect to agent" }, { status: 500 });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return null;
|
|
86
|
-
}
|
package/src/routes/static.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { existsSync, statSync } from "fs";
|
|
3
|
-
|
|
4
|
-
// Find dist directory - handle development, npx, and compiled binary contexts
|
|
5
|
-
function findDistDir(): string {
|
|
6
|
-
const candidates = [
|
|
7
|
-
join(import.meta.dir, "../../dist"),
|
|
8
|
-
join(import.meta.dir, "../dist"),
|
|
9
|
-
join(import.meta.dir, "dist"), // compiled binary: dist/ alongside the executable
|
|
10
|
-
join(process.cwd(), "dist"),
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
for (const dir of candidates) {
|
|
14
|
-
try {
|
|
15
|
-
if (existsSync(dir) && statSync(dir).isDirectory()) {
|
|
16
|
-
const indexPath = join(dir, "index.html");
|
|
17
|
-
if (existsSync(indexPath)) {
|
|
18
|
-
return dir;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
} catch {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return candidates[0]; // Default to first candidate
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const DIST_DIR = findDistDir();
|
|
30
|
-
|
|
31
|
-
// MIME types for common file extensions
|
|
32
|
-
const MIME_TYPES: Record<string, string> = {
|
|
33
|
-
".html": "text/html; charset=utf-8",
|
|
34
|
-
".js": "application/javascript; charset=utf-8",
|
|
35
|
-
".css": "text/css; charset=utf-8",
|
|
36
|
-
".json": "application/json; charset=utf-8",
|
|
37
|
-
".png": "image/png",
|
|
38
|
-
".jpg": "image/jpeg",
|
|
39
|
-
".jpeg": "image/jpeg",
|
|
40
|
-
".gif": "image/gif",
|
|
41
|
-
".svg": "image/svg+xml",
|
|
42
|
-
".ico": "image/x-icon",
|
|
43
|
-
".woff": "font/woff",
|
|
44
|
-
".woff2": "font/woff2",
|
|
45
|
-
".ttf": "font/ttf",
|
|
46
|
-
".eot": "application/vnd.ms-fontobject",
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
function getMimeType(path: string): string {
|
|
50
|
-
const ext = path.substring(path.lastIndexOf(".")).toLowerCase();
|
|
51
|
-
return MIME_TYPES[ext] || "application/octet-stream";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function serveStatic(req: Request, path: string): Promise<Response> {
|
|
55
|
-
try {
|
|
56
|
-
// Default to index.html for root
|
|
57
|
-
let filePath = path === "/" ? "/index.html" : path;
|
|
58
|
-
|
|
59
|
-
// Prevent directory traversal attacks
|
|
60
|
-
if (filePath.includes("..")) {
|
|
61
|
-
return new Response("Forbidden", { status: 403 });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const fullPath = join(DIST_DIR, filePath);
|
|
65
|
-
|
|
66
|
-
// Check if file exists using sync API (more reliable)
|
|
67
|
-
if (existsSync(fullPath)) {
|
|
68
|
-
try {
|
|
69
|
-
const stat = statSync(fullPath);
|
|
70
|
-
if (stat.isFile()) {
|
|
71
|
-
const file = Bun.file(fullPath);
|
|
72
|
-
const mimeType = getMimeType(filePath);
|
|
73
|
-
const headers: Record<string, string> = { "Content-Type": mimeType };
|
|
74
|
-
|
|
75
|
-
// Hashed assets (e.g. App.fq4xbpcz.js) are immutable — cache aggressively
|
|
76
|
-
// index.html must never be cached (it references the hashed assets)
|
|
77
|
-
const hasHash = /\.[a-z0-9]{6,}\.(js|css|map)$/.test(filePath);
|
|
78
|
-
if (hasHash) {
|
|
79
|
-
headers["Cache-Control"] = "public, max-age=31536000, immutable";
|
|
80
|
-
} else if (filePath !== "/index.html") {
|
|
81
|
-
headers["Cache-Control"] = "public, max-age=3600";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return new Response(file, { headers });
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
// Fall through to SPA handling
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// For SPA: if file doesn't exist and it's not a static asset, serve index.html
|
|
92
|
-
if (!path.includes(".")) {
|
|
93
|
-
const indexPath = join(DIST_DIR, "index.html");
|
|
94
|
-
if (existsSync(indexPath)) {
|
|
95
|
-
const indexFile = Bun.file(indexPath);
|
|
96
|
-
return new Response(indexFile, {
|
|
97
|
-
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// If dist doesn't exist, serve a development message
|
|
103
|
-
return new Response(
|
|
104
|
-
`<!DOCTYPE html>
|
|
105
|
-
<html>
|
|
106
|
-
<head>
|
|
107
|
-
<title>apteva</title>
|
|
108
|
-
<style>
|
|
109
|
-
body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0f172a; color: #e2e8f0; }
|
|
110
|
-
.container { text-align: center; }
|
|
111
|
-
code { background: #1e293b; padding: 4px 8px; border-radius: 4px; }
|
|
112
|
-
</style>
|
|
113
|
-
</head>
|
|
114
|
-
<body>
|
|
115
|
-
<div class="container">
|
|
116
|
-
<h1>apteva</h1>
|
|
117
|
-
<p>Run <code>bun run build</code> to build the frontend</p>
|
|
118
|
-
<p>API available at <a href="/api/health" style="color: #60a5fa">/api/health</a></p>
|
|
119
|
-
</div>
|
|
120
|
-
</body>
|
|
121
|
-
</html>`,
|
|
122
|
-
{
|
|
123
|
-
status: 200,
|
|
124
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error("Static file error:", error);
|
|
129
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
130
|
-
}
|
|
131
|
-
}
|