@agentxjs/portagent 0.0.2
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 +21 -0
- package/bin/portagent.js +2 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/cli/index.js +43 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/public/assets/index-BEpPZXDY.js +245 -0
- package/dist/public/assets/index-BEpPZXDY.js.map +1 -0
- package/dist/public/assets/index-CwJV4aRD.css +1 -0
- package/dist/public/favicon.svg +4 -0
- package/dist/public/index.html +14 -0
- package/dist/server/index.js +642 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
import { dirname, resolve } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { serve } from "@hono/node-server";
|
|
5
|
+
import { Hono as Hono2 } from "hono";
|
|
6
|
+
import { cors } from "hono/cors";
|
|
7
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { createAgentX, defineAgent } from "agentxjs";
|
|
10
|
+
import { createAgentXHandler } from "agentxjs/server";
|
|
11
|
+
import { toHonoHandler } from "agentxjs/server/adapters/hono";
|
|
12
|
+
import { nodeRuntime } from "@agentxjs/node-runtime";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
|
|
16
|
+
// src/server/auth.ts
|
|
17
|
+
import { Hono } from "hono";
|
|
18
|
+
import { createMiddleware } from "hono/factory";
|
|
19
|
+
import * as jose from "jose";
|
|
20
|
+
var TOKEN_EXPIRY = "7d";
|
|
21
|
+
async function createToken(secret, userId) {
|
|
22
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
23
|
+
const token = await new jose.SignJWT({ sub: userId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(TOKEN_EXPIRY).sign(secretKey);
|
|
24
|
+
return token;
|
|
25
|
+
}
|
|
26
|
+
async function verifyToken(secret, token) {
|
|
27
|
+
try {
|
|
28
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
29
|
+
const { payload } = await jose.jwtVerify(token, secretKey);
|
|
30
|
+
return { userId: payload.sub };
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function toUserInfo(user) {
|
|
36
|
+
return {
|
|
37
|
+
userId: user.userId,
|
|
38
|
+
username: user.username,
|
|
39
|
+
email: user.email,
|
|
40
|
+
containerId: user.containerId,
|
|
41
|
+
displayName: user.displayName,
|
|
42
|
+
avatar: user.avatar,
|
|
43
|
+
createdAt: user.createdAt
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function authRoutes(userRepository, jwtSecret, agentx) {
|
|
47
|
+
const app = new Hono();
|
|
48
|
+
app.post("/register", async (c) => {
|
|
49
|
+
try {
|
|
50
|
+
const body = await c.req.json();
|
|
51
|
+
if (!body.username || !body.email || !body.password) {
|
|
52
|
+
return c.json({ error: "Username, email, and password are required" }, 400);
|
|
53
|
+
}
|
|
54
|
+
if (body.username.length < 3) {
|
|
55
|
+
return c.json({ error: "Username must be at least 3 characters" }, 400);
|
|
56
|
+
}
|
|
57
|
+
if (body.password.length < 6) {
|
|
58
|
+
return c.json({ error: "Password must be at least 6 characters" }, 400);
|
|
59
|
+
}
|
|
60
|
+
if (!body.email.includes("@")) {
|
|
61
|
+
return c.json({ error: "Invalid email format" }, 400);
|
|
62
|
+
}
|
|
63
|
+
const container = await agentx.containers.create();
|
|
64
|
+
const user = await userRepository.createUser({
|
|
65
|
+
username: body.username,
|
|
66
|
+
email: body.email,
|
|
67
|
+
password: body.password,
|
|
68
|
+
containerId: container.containerId,
|
|
69
|
+
displayName: body.displayName,
|
|
70
|
+
avatar: body.avatar
|
|
71
|
+
});
|
|
72
|
+
const token = await createToken(jwtSecret, user.userId);
|
|
73
|
+
return c.json(
|
|
74
|
+
{
|
|
75
|
+
token,
|
|
76
|
+
user: toUserInfo(user),
|
|
77
|
+
expiresIn: TOKEN_EXPIRY
|
|
78
|
+
},
|
|
79
|
+
201
|
|
80
|
+
);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : "Registration failed";
|
|
83
|
+
return c.json({ error: message }, 400);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
app.post("/login", async (c) => {
|
|
87
|
+
try {
|
|
88
|
+
const body = await c.req.json();
|
|
89
|
+
if (!body.usernameOrEmail || !body.password) {
|
|
90
|
+
return c.json({ error: "Username/email and password are required" }, 400);
|
|
91
|
+
}
|
|
92
|
+
const user = await userRepository.verifyPassword(body.usernameOrEmail, body.password);
|
|
93
|
+
if (!user) {
|
|
94
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
95
|
+
}
|
|
96
|
+
const token = await createToken(jwtSecret, user.userId);
|
|
97
|
+
return c.json({
|
|
98
|
+
token,
|
|
99
|
+
user: toUserInfo(user),
|
|
100
|
+
expiresIn: TOKEN_EXPIRY
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const message = error instanceof Error ? error.message : "Login failed";
|
|
104
|
+
return c.json({ error: message }, 500);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
app.get("/verify", async (c) => {
|
|
108
|
+
const authHeader = c.req.header("Authorization");
|
|
109
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
110
|
+
return c.json({ valid: false }, 401);
|
|
111
|
+
}
|
|
112
|
+
const token = authHeader.slice(7);
|
|
113
|
+
const result = await verifyToken(jwtSecret, token);
|
|
114
|
+
if (!result) {
|
|
115
|
+
return c.json({ valid: false }, 401);
|
|
116
|
+
}
|
|
117
|
+
const user = await userRepository.findUserById(result.userId);
|
|
118
|
+
if (!user || !user.isActive) {
|
|
119
|
+
return c.json({ valid: false }, 401);
|
|
120
|
+
}
|
|
121
|
+
return c.json({ valid: true, user: toUserInfo(user) });
|
|
122
|
+
});
|
|
123
|
+
app.post("/logout", (c) => {
|
|
124
|
+
return c.json({ success: true });
|
|
125
|
+
});
|
|
126
|
+
return app;
|
|
127
|
+
}
|
|
128
|
+
function createAuthMiddleware(jwtSecret) {
|
|
129
|
+
return createMiddleware(async (c, next) => {
|
|
130
|
+
const url = new URL(c.req.url);
|
|
131
|
+
const queryToken = url.searchParams.get("token");
|
|
132
|
+
const authHeader = c.req.header("Authorization");
|
|
133
|
+
let token = null;
|
|
134
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
135
|
+
token = authHeader.slice(7);
|
|
136
|
+
} else if (queryToken) {
|
|
137
|
+
token = queryToken;
|
|
138
|
+
}
|
|
139
|
+
if (!token) {
|
|
140
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
141
|
+
}
|
|
142
|
+
const result = await verifyToken(jwtSecret, token);
|
|
143
|
+
if (!result) {
|
|
144
|
+
return c.json({ error: "Invalid token" }, 401);
|
|
145
|
+
}
|
|
146
|
+
c.set("userId", result.userId);
|
|
147
|
+
c.header("X-User-Id", result.userId);
|
|
148
|
+
await next();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/server/database/SQLiteUserRepository.ts
|
|
153
|
+
import Database from "better-sqlite3";
|
|
154
|
+
import { randomUUID } from "crypto";
|
|
155
|
+
import { hash, compare } from "bcrypt";
|
|
156
|
+
var SALT_ROUNDS = 10;
|
|
157
|
+
var SQLiteUserRepository = class {
|
|
158
|
+
db;
|
|
159
|
+
constructor(dbPath) {
|
|
160
|
+
this.db = new Database(dbPath);
|
|
161
|
+
this.initDatabase();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Initialize database schema
|
|
165
|
+
*/
|
|
166
|
+
initDatabase() {
|
|
167
|
+
this.db.pragma("foreign_keys = ON");
|
|
168
|
+
this.db.exec(`
|
|
169
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
170
|
+
userId TEXT PRIMARY KEY,
|
|
171
|
+
username TEXT UNIQUE NOT NULL,
|
|
172
|
+
email TEXT UNIQUE NOT NULL,
|
|
173
|
+
passwordHash TEXT NOT NULL,
|
|
174
|
+
containerId TEXT NOT NULL,
|
|
175
|
+
displayName TEXT,
|
|
176
|
+
avatar TEXT,
|
|
177
|
+
isActive INTEGER NOT NULL DEFAULT 1,
|
|
178
|
+
createdAt INTEGER NOT NULL,
|
|
179
|
+
updatedAt INTEGER NOT NULL
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_users_containerId ON users(containerId);
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a new user
|
|
189
|
+
*/
|
|
190
|
+
async createUser(input) {
|
|
191
|
+
if (await this.usernameExists(input.username)) {
|
|
192
|
+
throw new Error(`Username '${input.username}' already exists`);
|
|
193
|
+
}
|
|
194
|
+
if (await this.emailExists(input.email)) {
|
|
195
|
+
throw new Error(`Email '${input.email}' already exists`);
|
|
196
|
+
}
|
|
197
|
+
const passwordHash = await hash(input.password, SALT_ROUNDS);
|
|
198
|
+
const userId = randomUUID();
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
const user = {
|
|
201
|
+
userId,
|
|
202
|
+
username: input.username,
|
|
203
|
+
email: input.email,
|
|
204
|
+
passwordHash,
|
|
205
|
+
containerId: input.containerId,
|
|
206
|
+
displayName: input.displayName,
|
|
207
|
+
avatar: input.avatar,
|
|
208
|
+
isActive: true,
|
|
209
|
+
createdAt: now,
|
|
210
|
+
updatedAt: now
|
|
211
|
+
};
|
|
212
|
+
const stmt = this.db.prepare(`
|
|
213
|
+
INSERT INTO users (
|
|
214
|
+
userId, username, email, passwordHash, containerId, displayName, avatar, isActive, createdAt, updatedAt
|
|
215
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
216
|
+
`);
|
|
217
|
+
stmt.run(
|
|
218
|
+
user.userId,
|
|
219
|
+
user.username,
|
|
220
|
+
user.email,
|
|
221
|
+
user.passwordHash,
|
|
222
|
+
user.containerId,
|
|
223
|
+
user.displayName,
|
|
224
|
+
user.avatar,
|
|
225
|
+
user.isActive ? 1 : 0,
|
|
226
|
+
user.createdAt,
|
|
227
|
+
user.updatedAt
|
|
228
|
+
);
|
|
229
|
+
return user;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Find user by ID
|
|
233
|
+
*/
|
|
234
|
+
async findUserById(userId) {
|
|
235
|
+
const stmt = this.db.prepare(`
|
|
236
|
+
SELECT * FROM users WHERE userId = ?
|
|
237
|
+
`);
|
|
238
|
+
const row = stmt.get(userId);
|
|
239
|
+
return row ? this.rowToUser(row) : null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Find user by username
|
|
243
|
+
*/
|
|
244
|
+
async findUserByUsername(username) {
|
|
245
|
+
const stmt = this.db.prepare(`
|
|
246
|
+
SELECT * FROM users WHERE username = ?
|
|
247
|
+
`);
|
|
248
|
+
const row = stmt.get(username);
|
|
249
|
+
return row ? this.rowToUser(row) : null;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Find user by email
|
|
253
|
+
*/
|
|
254
|
+
async findUserByEmail(email) {
|
|
255
|
+
const stmt = this.db.prepare(`
|
|
256
|
+
SELECT * FROM users WHERE email = ?
|
|
257
|
+
`);
|
|
258
|
+
const row = stmt.get(email);
|
|
259
|
+
return row ? this.rowToUser(row) : null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find user by username or email
|
|
263
|
+
*/
|
|
264
|
+
async findUserByUsernameOrEmail(usernameOrEmail) {
|
|
265
|
+
const stmt = this.db.prepare(`
|
|
266
|
+
SELECT * FROM users WHERE username = ? OR email = ?
|
|
267
|
+
`);
|
|
268
|
+
const row = stmt.get(usernameOrEmail, usernameOrEmail);
|
|
269
|
+
return row ? this.rowToUser(row) : null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Update user
|
|
273
|
+
*/
|
|
274
|
+
async updateUser(userId, updates) {
|
|
275
|
+
const user = await this.findUserById(userId);
|
|
276
|
+
if (!user) {
|
|
277
|
+
throw new Error(`User ${userId} not found`);
|
|
278
|
+
}
|
|
279
|
+
const fields = [];
|
|
280
|
+
const values = [];
|
|
281
|
+
if (updates.username !== void 0) {
|
|
282
|
+
fields.push("username = ?");
|
|
283
|
+
values.push(updates.username);
|
|
284
|
+
}
|
|
285
|
+
if (updates.email !== void 0) {
|
|
286
|
+
fields.push("email = ?");
|
|
287
|
+
values.push(updates.email);
|
|
288
|
+
}
|
|
289
|
+
if (updates.passwordHash !== void 0) {
|
|
290
|
+
fields.push("passwordHash = ?");
|
|
291
|
+
values.push(updates.passwordHash);
|
|
292
|
+
}
|
|
293
|
+
if (updates.displayName !== void 0) {
|
|
294
|
+
fields.push("displayName = ?");
|
|
295
|
+
values.push(updates.displayName);
|
|
296
|
+
}
|
|
297
|
+
if (updates.avatar !== void 0) {
|
|
298
|
+
fields.push("avatar = ?");
|
|
299
|
+
values.push(updates.avatar);
|
|
300
|
+
}
|
|
301
|
+
if (updates.isActive !== void 0) {
|
|
302
|
+
fields.push("isActive = ?");
|
|
303
|
+
values.push(updates.isActive ? 1 : 0);
|
|
304
|
+
}
|
|
305
|
+
fields.push("updatedAt = ?");
|
|
306
|
+
values.push(Date.now());
|
|
307
|
+
if (fields.length === 0) {
|
|
308
|
+
return user;
|
|
309
|
+
}
|
|
310
|
+
values.push(userId);
|
|
311
|
+
const stmt = this.db.prepare(`
|
|
312
|
+
UPDATE users SET ${fields.join(", ")} WHERE userId = ?
|
|
313
|
+
`);
|
|
314
|
+
stmt.run(...values);
|
|
315
|
+
return await this.findUserById(userId);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Delete user
|
|
319
|
+
*/
|
|
320
|
+
async deleteUser(userId) {
|
|
321
|
+
const stmt = this.db.prepare(`
|
|
322
|
+
DELETE FROM users WHERE userId = ?
|
|
323
|
+
`);
|
|
324
|
+
const result = stmt.run(userId);
|
|
325
|
+
return result.changes > 0;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* List all users
|
|
329
|
+
*/
|
|
330
|
+
async listUsers() {
|
|
331
|
+
const stmt = this.db.prepare(`
|
|
332
|
+
SELECT * FROM users ORDER BY createdAt DESC
|
|
333
|
+
`);
|
|
334
|
+
const rows = stmt.all();
|
|
335
|
+
return rows.map((row) => this.rowToUser(row));
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if username exists
|
|
339
|
+
*/
|
|
340
|
+
async usernameExists(username) {
|
|
341
|
+
const stmt = this.db.prepare(`
|
|
342
|
+
SELECT 1 FROM users WHERE username = ?
|
|
343
|
+
`);
|
|
344
|
+
return stmt.get(username) !== void 0;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Check if email exists
|
|
348
|
+
*/
|
|
349
|
+
async emailExists(email) {
|
|
350
|
+
const stmt = this.db.prepare(`
|
|
351
|
+
SELECT 1 FROM users WHERE email = ?
|
|
352
|
+
`);
|
|
353
|
+
return stmt.get(email) !== void 0;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Verify password for login
|
|
357
|
+
*
|
|
358
|
+
* @param usernameOrEmail - Username or email
|
|
359
|
+
* @param password - Plain text password
|
|
360
|
+
* @returns User record if valid, null if invalid
|
|
361
|
+
*/
|
|
362
|
+
async verifyPassword(usernameOrEmail, password) {
|
|
363
|
+
const user = await this.findUserByUsernameOrEmail(usernameOrEmail);
|
|
364
|
+
if (!user) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
if (!user.isActive) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const isValid = await compare(password, user.passwordHash);
|
|
371
|
+
return isValid ? user : null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Close database connection
|
|
375
|
+
*/
|
|
376
|
+
close() {
|
|
377
|
+
this.db.close();
|
|
378
|
+
}
|
|
379
|
+
// ============================================================================
|
|
380
|
+
// Private Helpers
|
|
381
|
+
// ============================================================================
|
|
382
|
+
rowToUser(row) {
|
|
383
|
+
return {
|
|
384
|
+
userId: row.userId,
|
|
385
|
+
username: row.username,
|
|
386
|
+
email: row.email,
|
|
387
|
+
passwordHash: row.passwordHash,
|
|
388
|
+
containerId: row.containerId,
|
|
389
|
+
displayName: row.displayName,
|
|
390
|
+
avatar: row.avatar,
|
|
391
|
+
isActive: row.isActive === 1,
|
|
392
|
+
createdAt: row.createdAt,
|
|
393
|
+
updatedAt: row.updatedAt
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/server/ownership.ts
|
|
399
|
+
function getUserId(request) {
|
|
400
|
+
const authHeader = request.headers.get("Authorization");
|
|
401
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
402
|
+
const url = new URL(request.url);
|
|
403
|
+
const queryToken = url.searchParams.get("token");
|
|
404
|
+
if (!queryToken) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return request.headers.get("X-User-Id");
|
|
409
|
+
}
|
|
410
|
+
function createOwnershipHandler(agentx, baseHandler) {
|
|
411
|
+
return async (request) => {
|
|
412
|
+
const url = new URL(request.url);
|
|
413
|
+
const method = request.method;
|
|
414
|
+
const path = url.pathname;
|
|
415
|
+
const userId = getUserId(request);
|
|
416
|
+
if (!userId) {
|
|
417
|
+
return baseHandler(request);
|
|
418
|
+
}
|
|
419
|
+
if (method === "POST" && path.endsWith("/containers")) {
|
|
420
|
+
try {
|
|
421
|
+
const body = await request.json();
|
|
422
|
+
const config = body.config || {};
|
|
423
|
+
config.ownerId = userId;
|
|
424
|
+
const modifiedBody = { ...body, config };
|
|
425
|
+
const modifiedRequest = new Request(request.url, {
|
|
426
|
+
method: request.method,
|
|
427
|
+
headers: request.headers,
|
|
428
|
+
body: JSON.stringify(modifiedBody)
|
|
429
|
+
});
|
|
430
|
+
return baseHandler(modifiedRequest);
|
|
431
|
+
} catch {
|
|
432
|
+
return baseHandler(request);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const putContainerMatch = path.match(/\/containers\/([^/]+)$/);
|
|
436
|
+
if (method === "PUT" && putContainerMatch) {
|
|
437
|
+
const containerId = putContainerMatch[1];
|
|
438
|
+
const existingContainer = await agentx.containers.get(containerId);
|
|
439
|
+
if (existingContainer) {
|
|
440
|
+
const ownerId = existingContainer.config?.ownerId;
|
|
441
|
+
if (ownerId && ownerId !== userId) {
|
|
442
|
+
return new Response(JSON.stringify({ error: "Access denied" }), {
|
|
443
|
+
status: 403,
|
|
444
|
+
headers: { "Content-Type": "application/json" }
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return baseHandler(request);
|
|
448
|
+
} else {
|
|
449
|
+
try {
|
|
450
|
+
const body = await request.json();
|
|
451
|
+
const config = body.config || {};
|
|
452
|
+
config.ownerId = userId;
|
|
453
|
+
const modifiedBody = { ...body, config };
|
|
454
|
+
const modifiedRequest = new Request(request.url, {
|
|
455
|
+
method: request.method,
|
|
456
|
+
headers: request.headers,
|
|
457
|
+
body: JSON.stringify(modifiedBody)
|
|
458
|
+
});
|
|
459
|
+
return baseHandler(modifiedRequest);
|
|
460
|
+
} catch {
|
|
461
|
+
return baseHandler(request);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const getContainerMatch = path.match(/\/containers\/([^/]+)$/);
|
|
466
|
+
if (method === "GET" && getContainerMatch) {
|
|
467
|
+
const containerId = getContainerMatch[1];
|
|
468
|
+
const container = await agentx.containers.get(containerId);
|
|
469
|
+
if (!container) {
|
|
470
|
+
return new Response(JSON.stringify({ error: "Container not found" }), {
|
|
471
|
+
status: 404,
|
|
472
|
+
headers: { "Content-Type": "application/json" }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
const ownerId = container.config?.ownerId;
|
|
476
|
+
if (ownerId && ownerId !== userId) {
|
|
477
|
+
return new Response(JSON.stringify({ error: "Access denied" }), {
|
|
478
|
+
status: 403,
|
|
479
|
+
headers: { "Content-Type": "application/json" }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return baseHandler(request);
|
|
483
|
+
}
|
|
484
|
+
const deleteContainerMatch = path.match(/\/containers\/([^/]+)$/);
|
|
485
|
+
if (method === "DELETE" && deleteContainerMatch) {
|
|
486
|
+
const containerId = deleteContainerMatch[1];
|
|
487
|
+
const container = await agentx.containers.get(containerId);
|
|
488
|
+
if (!container) {
|
|
489
|
+
return new Response(JSON.stringify({ error: "Container not found" }), {
|
|
490
|
+
status: 404,
|
|
491
|
+
headers: { "Content-Type": "application/json" }
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const ownerId = container.config?.ownerId;
|
|
495
|
+
if (ownerId && ownerId !== userId) {
|
|
496
|
+
return new Response(JSON.stringify({ error: "Access denied" }), {
|
|
497
|
+
status: 403,
|
|
498
|
+
headers: { "Content-Type": "application/json" }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
return baseHandler(request);
|
|
502
|
+
}
|
|
503
|
+
if (method === "GET" && path.endsWith("/containers")) {
|
|
504
|
+
const response = await baseHandler(request);
|
|
505
|
+
if (response.ok) {
|
|
506
|
+
const body = await response.json();
|
|
507
|
+
if (Array.isArray(body)) {
|
|
508
|
+
const filtered = body.filter((container) => {
|
|
509
|
+
const ownerId = container.config?.ownerId;
|
|
510
|
+
return !ownerId || ownerId === userId;
|
|
511
|
+
});
|
|
512
|
+
return new Response(JSON.stringify(filtered), {
|
|
513
|
+
status: 200,
|
|
514
|
+
headers: { "Content-Type": "application/json" }
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return response;
|
|
519
|
+
}
|
|
520
|
+
return baseHandler(request);
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/server/index.ts
|
|
525
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
526
|
+
var __dirname = dirname(__filename);
|
|
527
|
+
var PORT = parseInt(process.env.PORT || "5200", 10);
|
|
528
|
+
var JWT_SECRET = process.env.JWT_SECRET || crypto.randomUUID();
|
|
529
|
+
var USER_DB_PATH = process.env.USER_DB_PATH || join(homedir(), ".agentx/data/portagent.db");
|
|
530
|
+
var DefaultAgent = defineAgent({
|
|
531
|
+
name: "Assistant",
|
|
532
|
+
systemPrompt: "You are a helpful AI assistant."
|
|
533
|
+
});
|
|
534
|
+
function createApp() {
|
|
535
|
+
const app = new Hono2();
|
|
536
|
+
app.use(
|
|
537
|
+
"*",
|
|
538
|
+
cors({
|
|
539
|
+
origin: "*",
|
|
540
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"],
|
|
541
|
+
allowHeaders: ["Content-Type", "Authorization"]
|
|
542
|
+
})
|
|
543
|
+
);
|
|
544
|
+
const runtime = nodeRuntime();
|
|
545
|
+
const agentx = createAgentX(runtime);
|
|
546
|
+
agentx.definitions.register(DefaultAgent);
|
|
547
|
+
const agentxHandler = createAgentXHandler(agentx, {
|
|
548
|
+
basePath: "/agentx",
|
|
549
|
+
allowDynamicCreation: true,
|
|
550
|
+
allowedDefinitions: ["Assistant"],
|
|
551
|
+
repository: runtime.repository
|
|
552
|
+
});
|
|
553
|
+
agentxHandler.registerDefinition("Assistant", DefaultAgent);
|
|
554
|
+
const userRepository = new SQLiteUserRepository(USER_DB_PATH);
|
|
555
|
+
const authMiddleware = createAuthMiddleware(JWT_SECRET);
|
|
556
|
+
app.get("/health", (c) => c.json({ status: "ok", timestamp: Date.now() }));
|
|
557
|
+
app.route("/api/auth", authRoutes(userRepository, JWT_SECRET, agentx));
|
|
558
|
+
const ownershipHandler = createOwnershipHandler(agentx, agentxHandler);
|
|
559
|
+
app.use("/agentx/*", authMiddleware);
|
|
560
|
+
app.all("/agentx/*", toHonoHandler(ownershipHandler));
|
|
561
|
+
const publicDir = resolve(__dirname, "../public");
|
|
562
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
563
|
+
if (existsSync(publicDir)) {
|
|
564
|
+
app.use("/*", serveStatic({ root: publicDir }));
|
|
565
|
+
app.get("*", (c) => {
|
|
566
|
+
const indexPath = resolve(publicDir, "index.html");
|
|
567
|
+
if (existsSync(indexPath)) {
|
|
568
|
+
const html = readFileSync(indexPath, "utf-8");
|
|
569
|
+
return c.html(html);
|
|
570
|
+
}
|
|
571
|
+
return c.text("Not Found", 404);
|
|
572
|
+
});
|
|
573
|
+
} else if (isDev) {
|
|
574
|
+
app.get("*", (c) => {
|
|
575
|
+
return c.text(
|
|
576
|
+
"Static files not found. Run 'pnpm build:client' first, or use 'pnpm dev' for development.",
|
|
577
|
+
404
|
|
578
|
+
);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
return { app, agentx, userRepository };
|
|
582
|
+
}
|
|
583
|
+
async function startServer() {
|
|
584
|
+
if (!process.env.LLM_PROVIDER_KEY) {
|
|
585
|
+
console.error("Error: LLM_PROVIDER_KEY is required");
|
|
586
|
+
console.log("\nSet it via environment variable:");
|
|
587
|
+
console.log(" export LLM_PROVIDER_KEY=sk-ant-xxx");
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
const { app, agentx, userRepository } = createApp();
|
|
591
|
+
console.log(`
|
|
592
|
+
____ _ _
|
|
593
|
+
| _ \\ ___ _ __| |_ __ _ __ _ ___ _ __ | |_
|
|
594
|
+
| |_) / _ \\| '__| __/ _\` |/ _\` |/ _ \\ '_ \\| __|
|
|
595
|
+
| __/ (_) | | | || (_| | (_| | __/ | | | |_
|
|
596
|
+
|_| \\___/|_| \\__\\__,_|\\__, |\\___|_| |_|\\__|
|
|
597
|
+
|___/
|
|
598
|
+
|
|
599
|
+
AgentX Portal - Your AI Agent Gateway (Multi-User Mode)
|
|
600
|
+
`);
|
|
601
|
+
console.log("Configuration:");
|
|
602
|
+
console.log(` Port: ${PORT}`);
|
|
603
|
+
console.log(` API Key: ${process.env.LLM_PROVIDER_KEY.substring(0, 15)}...`);
|
|
604
|
+
console.log(` User DB: ${USER_DB_PATH}`);
|
|
605
|
+
console.log(` AgentX DB: ${join(homedir(), ".agentx/data/agentx.db")}`);
|
|
606
|
+
console.log(`
|
|
607
|
+
Endpoints:`);
|
|
608
|
+
console.log(` GET /health - Health check`);
|
|
609
|
+
console.log(` POST /api/auth/register - Register new user`);
|
|
610
|
+
console.log(` POST /api/auth/login - Login`);
|
|
611
|
+
console.log(` GET /api/auth/verify - Verify token`);
|
|
612
|
+
console.log(` --- AgentX API (protected) ---`);
|
|
613
|
+
console.log(` GET /agentx/info - Platform info`);
|
|
614
|
+
console.log(` GET /agentx/containers - List containers`);
|
|
615
|
+
console.log(` POST /agentx/containers - Create container`);
|
|
616
|
+
console.log(` GET /agentx/containers/:id - Get container`);
|
|
617
|
+
console.log(` DELETE /agentx/containers/:id - Delete container`);
|
|
618
|
+
console.log(` POST /agentx/agents - Create agent`);
|
|
619
|
+
console.log(` GET /agentx/agents/:id/sse - SSE stream`);
|
|
620
|
+
console.log(` POST /agentx/agents/:id/messages- Send message`);
|
|
621
|
+
serve({
|
|
622
|
+
fetch: app.fetch,
|
|
623
|
+
port: PORT,
|
|
624
|
+
hostname: "0.0.0.0"
|
|
625
|
+
});
|
|
626
|
+
console.log(`
|
|
627
|
+
\u{1F680} Server running at http://localhost:${PORT}`);
|
|
628
|
+
const shutdown = async () => {
|
|
629
|
+
console.log("\nShutting down...");
|
|
630
|
+
await agentx.agents.destroyAll();
|
|
631
|
+
userRepository.close();
|
|
632
|
+
console.log("Server stopped");
|
|
633
|
+
process.exit(0);
|
|
634
|
+
};
|
|
635
|
+
process.on("SIGINT", shutdown);
|
|
636
|
+
process.on("SIGTERM", shutdown);
|
|
637
|
+
}
|
|
638
|
+
export {
|
|
639
|
+
createApp,
|
|
640
|
+
startServer
|
|
641
|
+
};
|
|
642
|
+
//# sourceMappingURL=index.js.map
|