@agentxjs/portagent 1.3.0 → 1.5.0
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 +0 -0
- package/README.md +23 -9
- package/dist/cli/index.js +659 -16
- package/dist/cli/index.js.map +15 -1
- package/dist/public/assets/main-fa01qzjx.js +77 -0
- package/dist/public/assets/main-pnn8q2ty.js +54596 -0
- package/dist/public/assets/main-pnn8q2ty.js.map +312 -0
- package/dist/public/assets/styles.css +2186 -0
- package/dist/public/index.html +3 -3
- package/dist/server/index.js +90 -133
- package/dist/server/index.js.map +13 -1
- package/package.json +15 -37
- package/dist/public/assets/browser-C0DG1J1h.js +0 -2
- package/dist/public/assets/browser-C0DG1J1h.js.map +0 -1
- package/dist/public/assets/index-BbPBfuHo.js +0 -1012
- package/dist/public/assets/index-BbPBfuHo.js.map +0 -1
- package/dist/public/assets/index-C4JWk6jH.css +0 -1
- package/dist/public/assets/index-XtJbS8_7.js +0 -2
- package/dist/public/assets/index-XtJbS8_7.js.map +0 -1
- package/dist/public/assets/reconnecting-websocket-mjs-Dd04wD44.js +0 -20
- package/dist/public/assets/reconnecting-websocket-mjs-Dd04wD44.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,30 +1,672 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// src/server/auth.ts
|
|
32
|
+
import { Hono } from "hono";
|
|
33
|
+
import { createMiddleware } from "hono/factory";
|
|
34
|
+
import * as jose from "jose";
|
|
35
|
+
function isValidInviteCode(code) {
|
|
36
|
+
const timestamp = parseInt(code, 10);
|
|
37
|
+
if (isNaN(timestamp))
|
|
38
|
+
return false;
|
|
39
|
+
const now = new Date;
|
|
40
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 1);
|
|
41
|
+
const expectedTimestamp = Math.floor(todayStart.getTime() / 1000);
|
|
42
|
+
return timestamp === expectedTimestamp;
|
|
43
|
+
}
|
|
44
|
+
async function createToken(secret, userId) {
|
|
45
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
46
|
+
const token = await new jose.SignJWT({ sub: userId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(TOKEN_EXPIRY).sign(secretKey);
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
async function verifyToken(secret, token) {
|
|
50
|
+
try {
|
|
51
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
52
|
+
const { payload } = await jose.jwtVerify(token, secretKey);
|
|
53
|
+
return { userId: payload.sub };
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function toUserInfo(user) {
|
|
59
|
+
return {
|
|
60
|
+
userId: user.userId,
|
|
61
|
+
username: user.username,
|
|
62
|
+
email: user.email,
|
|
63
|
+
containerId: user.containerId,
|
|
64
|
+
displayName: user.displayName,
|
|
65
|
+
avatar: user.avatar,
|
|
66
|
+
createdAt: user.createdAt
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function authRoutes(userRepository, jwtSecret, agentx, config = {}) {
|
|
70
|
+
const app = new Hono;
|
|
71
|
+
const { inviteCodeRequired = true } = config;
|
|
72
|
+
app.get("/config", (c) => {
|
|
73
|
+
return c.json({ inviteCodeRequired });
|
|
74
|
+
});
|
|
75
|
+
app.post("/register", async (c) => {
|
|
76
|
+
try {
|
|
77
|
+
const body = await c.req.json();
|
|
78
|
+
if (!body.username || !body.password) {
|
|
79
|
+
return c.json({ error: "Username and password are required" }, 400);
|
|
80
|
+
}
|
|
81
|
+
if (inviteCodeRequired && (!body.inviteCode || !isValidInviteCode(body.inviteCode))) {
|
|
82
|
+
return c.json({ error: "Invalid invite code" }, 400);
|
|
83
|
+
}
|
|
84
|
+
if (body.username.length < 3) {
|
|
85
|
+
return c.json({ error: "Username must be at least 3 characters" }, 400);
|
|
86
|
+
}
|
|
87
|
+
if (body.password.length < 6) {
|
|
88
|
+
return c.json({ error: "Password must be at least 6 characters" }, 400);
|
|
89
|
+
}
|
|
90
|
+
if (body.email && !body.email.includes("@")) {
|
|
91
|
+
return c.json({ error: "Invalid email format" }, 400);
|
|
92
|
+
}
|
|
93
|
+
const containerId = `user-${crypto.randomUUID()}`;
|
|
94
|
+
const containerRes = await agentx.request("container_create_request", { containerId });
|
|
95
|
+
if (containerRes.data.error) {
|
|
96
|
+
return c.json({ error: "Failed to create user container" }, 500);
|
|
97
|
+
}
|
|
98
|
+
const user = await userRepository.createUser({
|
|
99
|
+
username: body.username,
|
|
100
|
+
email: body.email,
|
|
101
|
+
password: body.password,
|
|
102
|
+
containerId,
|
|
103
|
+
displayName: body.displayName,
|
|
104
|
+
avatar: body.avatar
|
|
105
|
+
});
|
|
106
|
+
const token = await createToken(jwtSecret, user.userId);
|
|
107
|
+
return c.json({
|
|
108
|
+
token,
|
|
109
|
+
user: toUserInfo(user),
|
|
110
|
+
expiresIn: TOKEN_EXPIRY
|
|
111
|
+
}, 201);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : "Registration failed";
|
|
114
|
+
return c.json({ error: message }, 400);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
app.post("/login", async (c) => {
|
|
118
|
+
try {
|
|
119
|
+
const body = await c.req.json();
|
|
120
|
+
if (!body.usernameOrEmail || !body.password) {
|
|
121
|
+
return c.json({ error: "Username/email and password are required" }, 400);
|
|
122
|
+
}
|
|
123
|
+
const user = await userRepository.verifyPassword(body.usernameOrEmail, body.password);
|
|
124
|
+
if (!user) {
|
|
125
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
126
|
+
}
|
|
127
|
+
const token = await createToken(jwtSecret, user.userId);
|
|
128
|
+
return c.json({
|
|
129
|
+
token,
|
|
130
|
+
user: toUserInfo(user),
|
|
131
|
+
expiresIn: TOKEN_EXPIRY
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : "Login failed";
|
|
135
|
+
return c.json({ error: message }, 500);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
app.get("/verify", async (c) => {
|
|
139
|
+
const authHeader = c.req.header("Authorization");
|
|
140
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
141
|
+
return c.json({ valid: false }, 401);
|
|
142
|
+
}
|
|
143
|
+
const token = authHeader.slice(7);
|
|
144
|
+
const result = await verifyToken(jwtSecret, token);
|
|
145
|
+
if (!result) {
|
|
146
|
+
return c.json({ valid: false }, 401);
|
|
147
|
+
}
|
|
148
|
+
const user = await userRepository.findUserById(result.userId);
|
|
149
|
+
if (!user || !user.isActive) {
|
|
150
|
+
return c.json({ valid: false }, 401);
|
|
151
|
+
}
|
|
152
|
+
return c.json({ valid: true, user: toUserInfo(user) });
|
|
153
|
+
});
|
|
154
|
+
app.post("/logout", (c) => {
|
|
155
|
+
return c.json({ success: true });
|
|
156
|
+
});
|
|
157
|
+
return app;
|
|
158
|
+
}
|
|
159
|
+
function createAuthMiddleware(jwtSecret) {
|
|
160
|
+
return createMiddleware(async (c, next) => {
|
|
161
|
+
const url = new URL(c.req.url);
|
|
162
|
+
const queryToken = url.searchParams.get("token");
|
|
163
|
+
const authHeader = c.req.header("Authorization");
|
|
164
|
+
let token = null;
|
|
165
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
166
|
+
token = authHeader.slice(7);
|
|
167
|
+
} else if (queryToken) {
|
|
168
|
+
token = queryToken;
|
|
169
|
+
}
|
|
170
|
+
if (!token) {
|
|
171
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
172
|
+
}
|
|
173
|
+
const result = await verifyToken(jwtSecret, token);
|
|
174
|
+
if (!result) {
|
|
175
|
+
return c.json({ error: "Invalid token" }, 401);
|
|
176
|
+
}
|
|
177
|
+
c.set("userId", result.userId);
|
|
178
|
+
c.header("X-User-Id", result.userId);
|
|
179
|
+
await next();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
var TOKEN_EXPIRY = "7d";
|
|
183
|
+
var init_auth = () => {};
|
|
184
|
+
|
|
185
|
+
// src/server/database/SQLiteUserRepository.ts
|
|
186
|
+
import { Database } from "bun:sqlite";
|
|
187
|
+
import { randomUUID } from "crypto";
|
|
188
|
+
|
|
189
|
+
class SQLiteUserRepository {
|
|
190
|
+
db;
|
|
191
|
+
constructor(dbPath) {
|
|
192
|
+
this.db = new Database(dbPath);
|
|
193
|
+
this.initDatabase();
|
|
194
|
+
}
|
|
195
|
+
initDatabase() {
|
|
196
|
+
this.db.run("PRAGMA foreign_keys = ON");
|
|
197
|
+
this.db.exec(`
|
|
198
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
199
|
+
userId TEXT PRIMARY KEY,
|
|
200
|
+
username TEXT UNIQUE NOT NULL,
|
|
201
|
+
email TEXT UNIQUE NOT NULL,
|
|
202
|
+
passwordHash TEXT NOT NULL,
|
|
203
|
+
containerId TEXT NOT NULL,
|
|
204
|
+
displayName TEXT,
|
|
205
|
+
avatar TEXT,
|
|
206
|
+
isActive INTEGER NOT NULL DEFAULT 1,
|
|
207
|
+
createdAt INTEGER NOT NULL,
|
|
208
|
+
updatedAt INTEGER NOT NULL
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_users_containerId ON users(containerId);
|
|
214
|
+
`);
|
|
215
|
+
}
|
|
216
|
+
async createUser(input) {
|
|
217
|
+
if (await this.usernameExists(input.username)) {
|
|
218
|
+
throw new Error(`Username '${input.username}' already exists`);
|
|
219
|
+
}
|
|
220
|
+
if (input.email && await this.emailExists(input.email)) {
|
|
221
|
+
throw new Error(`Email '${input.email}' already exists`);
|
|
222
|
+
}
|
|
223
|
+
const passwordHash = await Bun.password.hash(input.password, {
|
|
224
|
+
algorithm: "bcrypt",
|
|
225
|
+
cost: 10
|
|
226
|
+
});
|
|
227
|
+
const userId = randomUUID();
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
const email = input.email || `${userId}@noemail.portagent`;
|
|
230
|
+
const user = {
|
|
231
|
+
userId,
|
|
232
|
+
username: input.username,
|
|
233
|
+
email,
|
|
234
|
+
passwordHash,
|
|
235
|
+
containerId: input.containerId,
|
|
236
|
+
displayName: input.displayName,
|
|
237
|
+
avatar: input.avatar,
|
|
238
|
+
isActive: true,
|
|
239
|
+
createdAt: now,
|
|
240
|
+
updatedAt: now
|
|
241
|
+
};
|
|
242
|
+
const stmt = this.db.prepare(`
|
|
243
|
+
INSERT INTO users (
|
|
244
|
+
userId, username, email, passwordHash, containerId, displayName, avatar, isActive, createdAt, updatedAt
|
|
245
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
246
|
+
`);
|
|
247
|
+
stmt.run(user.userId, user.username, user.email, user.passwordHash, user.containerId, user.displayName ?? null, user.avatar ?? null, user.isActive ? 1 : 0, user.createdAt, user.updatedAt);
|
|
248
|
+
return user;
|
|
249
|
+
}
|
|
250
|
+
async findUserById(userId) {
|
|
251
|
+
const stmt = this.db.prepare(`
|
|
252
|
+
SELECT * FROM users WHERE userId = ?
|
|
253
|
+
`);
|
|
254
|
+
const row = stmt.get(userId);
|
|
255
|
+
return row ? this.rowToUser(row) : null;
|
|
256
|
+
}
|
|
257
|
+
async findUserByUsername(username) {
|
|
258
|
+
const stmt = this.db.prepare(`
|
|
259
|
+
SELECT * FROM users WHERE username = ?
|
|
260
|
+
`);
|
|
261
|
+
const row = stmt.get(username);
|
|
262
|
+
return row ? this.rowToUser(row) : null;
|
|
263
|
+
}
|
|
264
|
+
async findUserByEmail(email) {
|
|
265
|
+
const stmt = this.db.prepare(`
|
|
266
|
+
SELECT * FROM users WHERE email = ?
|
|
267
|
+
`);
|
|
268
|
+
const row = stmt.get(email);
|
|
269
|
+
return row ? this.rowToUser(row) : null;
|
|
270
|
+
}
|
|
271
|
+
async findUserByUsernameOrEmail(usernameOrEmail) {
|
|
272
|
+
const stmt = this.db.prepare(`
|
|
273
|
+
SELECT * FROM users WHERE username = ? OR email = ?
|
|
274
|
+
`);
|
|
275
|
+
const row = stmt.get(usernameOrEmail, usernameOrEmail);
|
|
276
|
+
return row ? this.rowToUser(row) : null;
|
|
277
|
+
}
|
|
278
|
+
async updateUser(userId, updates) {
|
|
279
|
+
const user = await this.findUserById(userId);
|
|
280
|
+
if (!user) {
|
|
281
|
+
throw new Error(`User ${userId} not found`);
|
|
282
|
+
}
|
|
283
|
+
const fields = [];
|
|
284
|
+
const values = [];
|
|
285
|
+
if (updates.username !== undefined) {
|
|
286
|
+
fields.push("username = ?");
|
|
287
|
+
values.push(updates.username);
|
|
288
|
+
}
|
|
289
|
+
if (updates.email !== undefined) {
|
|
290
|
+
fields.push("email = ?");
|
|
291
|
+
values.push(updates.email);
|
|
292
|
+
}
|
|
293
|
+
if (updates.passwordHash !== undefined) {
|
|
294
|
+
fields.push("passwordHash = ?");
|
|
295
|
+
values.push(updates.passwordHash);
|
|
296
|
+
}
|
|
297
|
+
if (updates.displayName !== undefined) {
|
|
298
|
+
fields.push("displayName = ?");
|
|
299
|
+
values.push(updates.displayName);
|
|
300
|
+
}
|
|
301
|
+
if (updates.avatar !== undefined) {
|
|
302
|
+
fields.push("avatar = ?");
|
|
303
|
+
values.push(updates.avatar);
|
|
304
|
+
}
|
|
305
|
+
if (updates.isActive !== undefined) {
|
|
306
|
+
fields.push("isActive = ?");
|
|
307
|
+
values.push(updates.isActive ? 1 : 0);
|
|
308
|
+
}
|
|
309
|
+
fields.push("updatedAt = ?");
|
|
310
|
+
values.push(Date.now());
|
|
311
|
+
if (fields.length === 0) {
|
|
312
|
+
return user;
|
|
313
|
+
}
|
|
314
|
+
values.push(userId);
|
|
315
|
+
const stmt = this.db.prepare(`
|
|
316
|
+
UPDATE users SET ${fields.join(", ")} WHERE userId = ?
|
|
317
|
+
`);
|
|
318
|
+
stmt.run(...values);
|
|
319
|
+
return await this.findUserById(userId);
|
|
320
|
+
}
|
|
321
|
+
async deleteUser(userId) {
|
|
322
|
+
const stmt = this.db.prepare(`
|
|
323
|
+
DELETE FROM users WHERE userId = ?
|
|
324
|
+
`);
|
|
325
|
+
const result = stmt.run(userId);
|
|
326
|
+
return result.changes > 0;
|
|
327
|
+
}
|
|
328
|
+
async listUsers() {
|
|
329
|
+
const stmt = this.db.prepare(`
|
|
330
|
+
SELECT * FROM users ORDER BY createdAt DESC
|
|
331
|
+
`);
|
|
332
|
+
const rows = stmt.all();
|
|
333
|
+
return rows.map((row) => this.rowToUser(row));
|
|
334
|
+
}
|
|
335
|
+
async usernameExists(username) {
|
|
336
|
+
const stmt = this.db.prepare(`
|
|
337
|
+
SELECT 1 FROM users WHERE username = ?
|
|
338
|
+
`);
|
|
339
|
+
return stmt.get(username) !== null;
|
|
340
|
+
}
|
|
341
|
+
async emailExists(email) {
|
|
342
|
+
const stmt = this.db.prepare(`
|
|
343
|
+
SELECT 1 FROM users WHERE email = ?
|
|
344
|
+
`);
|
|
345
|
+
return stmt.get(email) !== null;
|
|
346
|
+
}
|
|
347
|
+
async verifyPassword(usernameOrEmail, password) {
|
|
348
|
+
const user = await this.findUserByUsernameOrEmail(usernameOrEmail);
|
|
349
|
+
if (!user) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
if (!user.isActive) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const isValid = await Bun.password.verify(password, user.passwordHash);
|
|
356
|
+
return isValid ? user : null;
|
|
357
|
+
}
|
|
358
|
+
close() {
|
|
359
|
+
this.db.close();
|
|
360
|
+
}
|
|
361
|
+
rowToUser(row) {
|
|
362
|
+
return {
|
|
363
|
+
userId: row.userId,
|
|
364
|
+
username: row.username,
|
|
365
|
+
email: row.email,
|
|
366
|
+
passwordHash: row.passwordHash,
|
|
367
|
+
containerId: row.containerId,
|
|
368
|
+
displayName: row.displayName,
|
|
369
|
+
avatar: row.avatar,
|
|
370
|
+
isActive: row.isActive === 1,
|
|
371
|
+
createdAt: row.createdAt,
|
|
372
|
+
updatedAt: row.updatedAt
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
var init_SQLiteUserRepository = () => {};
|
|
377
|
+
|
|
378
|
+
// src/server/database/index.ts
|
|
379
|
+
var init_database = __esm(() => {
|
|
380
|
+
init_SQLiteUserRepository();
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// src/server/logger.ts
|
|
384
|
+
import pino from "pino";
|
|
385
|
+
import fs from "node:fs";
|
|
386
|
+
import path from "node:path";
|
|
387
|
+
|
|
388
|
+
class PinoLoggerAdapter {
|
|
389
|
+
name;
|
|
390
|
+
level;
|
|
391
|
+
pino;
|
|
392
|
+
constructor(name, level, pino2) {
|
|
393
|
+
this.name = name;
|
|
394
|
+
this.level = level;
|
|
395
|
+
this.pino = pino2;
|
|
396
|
+
}
|
|
397
|
+
debug(message, context) {
|
|
398
|
+
this.pino.debug(context || {}, message);
|
|
399
|
+
}
|
|
400
|
+
info(message, context) {
|
|
401
|
+
this.pino.info(context || {}, message);
|
|
402
|
+
}
|
|
403
|
+
warn(message, context) {
|
|
404
|
+
this.pino.warn(context || {}, message);
|
|
405
|
+
}
|
|
406
|
+
error(message, context) {
|
|
407
|
+
if (message instanceof Error) {
|
|
408
|
+
this.pino.error({ ...context, err: message }, message.message);
|
|
409
|
+
} else {
|
|
410
|
+
this.pino.error(context || {}, message);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
isDebugEnabled() {
|
|
414
|
+
return this.pino.isLevelEnabled("debug");
|
|
415
|
+
}
|
|
416
|
+
isInfoEnabled() {
|
|
417
|
+
return this.pino.isLevelEnabled("info");
|
|
418
|
+
}
|
|
419
|
+
isWarnEnabled() {
|
|
420
|
+
return this.pino.isLevelEnabled("warn");
|
|
421
|
+
}
|
|
422
|
+
isErrorEnabled() {
|
|
423
|
+
return this.pino.isLevelEnabled("error");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
class PinoLoggerFactory {
|
|
428
|
+
rootLogger;
|
|
429
|
+
constructor(options) {
|
|
430
|
+
const { level, logDir, pretty = true } = options;
|
|
431
|
+
if (!fs.existsSync(logDir)) {
|
|
432
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
433
|
+
}
|
|
434
|
+
const getLogFilePath = () => {
|
|
435
|
+
const now = new Date;
|
|
436
|
+
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
437
|
+
return path.join(logDir, `portagent-${dateStr}.log`);
|
|
438
|
+
};
|
|
439
|
+
const targets = [];
|
|
440
|
+
targets.push({
|
|
441
|
+
target: "pino/file",
|
|
442
|
+
level: LEVEL_MAP[level],
|
|
443
|
+
options: {
|
|
444
|
+
destination: getLogFilePath(),
|
|
445
|
+
mkdir: true
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
if (pretty) {
|
|
449
|
+
targets.push({
|
|
450
|
+
target: "pino-pretty",
|
|
451
|
+
level: LEVEL_MAP[level],
|
|
452
|
+
options: {
|
|
453
|
+
colorize: true,
|
|
454
|
+
translateTime: "SYS:standard",
|
|
455
|
+
ignore: "pid,hostname"
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
} else {
|
|
459
|
+
targets.push({
|
|
460
|
+
target: "pino/file",
|
|
461
|
+
level: LEVEL_MAP[level],
|
|
462
|
+
options: { destination: 1 }
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
this.rootLogger = pino({
|
|
466
|
+
level: LEVEL_MAP[level],
|
|
467
|
+
transport: {
|
|
468
|
+
targets
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
getLogger(name) {
|
|
473
|
+
const childLogger = this.rootLogger.child({ name });
|
|
474
|
+
return new PinoLoggerAdapter(name, this.rootLogger.level, childLogger);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
var LEVEL_MAP;
|
|
478
|
+
var init_logger = __esm(() => {
|
|
479
|
+
LEVEL_MAP = {
|
|
480
|
+
debug: "debug",
|
|
481
|
+
info: "info",
|
|
482
|
+
warn: "warn",
|
|
483
|
+
error: "error",
|
|
484
|
+
silent: "silent"
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// src/server/index.ts
|
|
489
|
+
var exports_server = {};
|
|
490
|
+
__export(exports_server, {
|
|
491
|
+
startServer: () => startServer,
|
|
492
|
+
createApp: () => createApp
|
|
493
|
+
});
|
|
494
|
+
import { resolve } from "path";
|
|
495
|
+
import { createServer } from "http";
|
|
496
|
+
import { Hono as Hono2 } from "hono";
|
|
497
|
+
import { cors } from "hono/cors";
|
|
498
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
499
|
+
import { existsSync, readFileSync } from "fs";
|
|
500
|
+
import { createAgentX } from "agentxjs";
|
|
501
|
+
import { homedir } from "node:os";
|
|
502
|
+
import { join } from "node:path";
|
|
503
|
+
import { mkdirSync } from "node:fs";
|
|
504
|
+
function getDataPaths() {
|
|
505
|
+
const dataDir = process.env.AGENTX_DIR || join(homedir(), ".agentx");
|
|
506
|
+
const dataDirPath = join(dataDir, "data");
|
|
507
|
+
const logsDirPath = join(dataDir, "logs");
|
|
508
|
+
mkdirSync(dataDirPath, { recursive: true });
|
|
509
|
+
mkdirSync(logsDirPath, { recursive: true });
|
|
510
|
+
return {
|
|
511
|
+
dataDir,
|
|
512
|
+
dataDirPath,
|
|
513
|
+
logsDirPath,
|
|
514
|
+
userDbPath: join(dataDirPath, "portagent.db"),
|
|
515
|
+
agentxDbPath: join(dataDirPath, "agentx.db"),
|
|
516
|
+
logFilePath: join(logsDirPath, "portagent.log")
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
async function createApp() {
|
|
520
|
+
const paths = getDataPaths();
|
|
521
|
+
const app = new Hono2;
|
|
522
|
+
app.use("*", cors({
|
|
523
|
+
origin: "*",
|
|
524
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"],
|
|
525
|
+
allowHeaders: ["Content-Type", "Authorization"]
|
|
526
|
+
}));
|
|
527
|
+
const apiKey = process.env.LLM_PROVIDER_KEY;
|
|
528
|
+
if (!apiKey) {
|
|
529
|
+
console.error("Error: LLM_PROVIDER_KEY is required");
|
|
530
|
+
console.log(`
|
|
531
|
+
Set it via environment variable:`);
|
|
532
|
+
console.log(" export LLM_PROVIDER_KEY=sk-ant-xxx");
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
const { getRequestListener } = await import("@hono/node-server");
|
|
536
|
+
const listener = getRequestListener(app.fetch);
|
|
537
|
+
const server = createServer(listener);
|
|
538
|
+
const logLevel = process.env.LOG_LEVEL || "info";
|
|
539
|
+
const loggerFactory = new PinoLoggerFactory({
|
|
540
|
+
level: logLevel,
|
|
541
|
+
logDir: paths.logsDirPath,
|
|
542
|
+
pretty: true
|
|
543
|
+
});
|
|
544
|
+
const agentx = await createAgentX({
|
|
545
|
+
llm: {
|
|
546
|
+
apiKey,
|
|
547
|
+
baseUrl: process.env.LLM_PROVIDER_URL,
|
|
548
|
+
model: process.env.LLM_PROVIDER_MODEL
|
|
549
|
+
},
|
|
550
|
+
logger: {
|
|
551
|
+
level: logLevel,
|
|
552
|
+
factory: loggerFactory
|
|
553
|
+
},
|
|
554
|
+
agentxDir: paths.dataDir,
|
|
555
|
+
server
|
|
556
|
+
});
|
|
557
|
+
const userRepository = new SQLiteUserRepository(paths.userDbPath);
|
|
558
|
+
const authMiddleware = createAuthMiddleware(JWT_SECRET);
|
|
559
|
+
app.get("/health", (c) => c.json({ status: "ok", timestamp: Date.now() }));
|
|
560
|
+
app.route("/api/auth", authRoutes(userRepository, JWT_SECRET, agentx, { inviteCodeRequired: INVITE_CODE_REQUIRED }));
|
|
561
|
+
app.use("/agentx/*", authMiddleware);
|
|
562
|
+
app.get("/agentx/info", (c) => {
|
|
563
|
+
const isDev2 = true;
|
|
564
|
+
const wsUrl = isDev2 ? `ws://localhost:${PORT}/ws` : undefined;
|
|
565
|
+
return c.json({
|
|
566
|
+
version: "0.1.0",
|
|
567
|
+
wsPath: "/ws",
|
|
568
|
+
wsUrl
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
const isDev = import.meta.dir.includes("/src/");
|
|
572
|
+
const publicDir = isDev ? resolve(import.meta.dir, "../../dist/public") : resolve(import.meta.dir, "../public");
|
|
573
|
+
if (existsSync(publicDir)) {
|
|
574
|
+
app.use("/*", serveStatic({ root: publicDir }));
|
|
575
|
+
app.get("*", (c) => {
|
|
576
|
+
const indexPath = resolve(publicDir, "index.html");
|
|
577
|
+
if (existsSync(indexPath)) {
|
|
578
|
+
const html = readFileSync(indexPath, "utf-8");
|
|
579
|
+
return c.html(html);
|
|
580
|
+
}
|
|
581
|
+
return c.text("Not Found", 404);
|
|
582
|
+
});
|
|
583
|
+
} else if (isDev) {
|
|
584
|
+
app.get("*", (c) => {
|
|
585
|
+
return c.text("Static files not found. Run 'pnpm build:client' first, or use 'pnpm dev' for development.", 404);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return { app, server, agentx, userRepository, paths };
|
|
589
|
+
}
|
|
590
|
+
async function startServer() {
|
|
591
|
+
const { server, agentx, userRepository, paths } = await createApp();
|
|
592
|
+
console.log(`
|
|
593
|
+
____ _ _
|
|
594
|
+
| _ \\ ___ _ __| |_ __ _ __ _ ___ _ __ | |_
|
|
595
|
+
| |_) / _ \\| '__| __/ _\` |/ _\` |/ _ \\ '_ \\| __|
|
|
596
|
+
| __/ (_) | | | || (_| | (_| | __/ | | | |_
|
|
597
|
+
|_| \\___/|_| \\__\\__,_|\\__, |\\___|_| |_|\\__|
|
|
598
|
+
|___/
|
|
599
|
+
|
|
600
|
+
AgentX Portal - Your AI Agent Gateway (Multi-User Mode)
|
|
601
|
+
`);
|
|
602
|
+
console.log("Configuration:");
|
|
603
|
+
console.log(` Port: ${PORT}`);
|
|
604
|
+
console.log(` Data Dir: ${paths.dataDir}`);
|
|
605
|
+
console.log(` API Key: ${process.env.LLM_PROVIDER_KEY.substring(0, 15)}...`);
|
|
606
|
+
console.log(` User DB: ${paths.userDbPath}`);
|
|
607
|
+
console.log(` AgentX DB: ${paths.agentxDbPath}`);
|
|
608
|
+
console.log(` Logs: ${paths.logsDirPath}`);
|
|
609
|
+
console.log(` Invite Code: ${INVITE_CODE_REQUIRED ? "required" : "disabled"}`);
|
|
610
|
+
console.log(`
|
|
611
|
+
Endpoints:`);
|
|
612
|
+
console.log(` GET /health - Health check`);
|
|
613
|
+
console.log(` POST /api/auth/register - Register new user`);
|
|
614
|
+
console.log(` POST /api/auth/login - Login`);
|
|
615
|
+
console.log(` GET /api/auth/verify - Verify token`);
|
|
616
|
+
console.log(` GET /agentx/info - Platform info`);
|
|
617
|
+
console.log(` WS /ws - WebSocket connection`);
|
|
618
|
+
server.listen(PORT, "0.0.0.0", () => {
|
|
619
|
+
console.log(`
|
|
620
|
+
\uD83D\uDE80 Server running at http://localhost:${PORT}`);
|
|
621
|
+
console.log(`\uD83D\uDD0C WebSocket available at ws://localhost:${PORT}/ws`);
|
|
622
|
+
});
|
|
623
|
+
const shutdown = async () => {
|
|
624
|
+
console.log(`
|
|
625
|
+
Shutting down...`);
|
|
626
|
+
await agentx.dispose();
|
|
627
|
+
userRepository.close();
|
|
628
|
+
server.close();
|
|
629
|
+
console.log("Server stopped");
|
|
630
|
+
process.exit(0);
|
|
631
|
+
};
|
|
632
|
+
process.on("SIGINT", shutdown);
|
|
633
|
+
process.on("SIGTERM", shutdown);
|
|
634
|
+
}
|
|
635
|
+
var PORT, JWT_SECRET, INVITE_CODE_REQUIRED;
|
|
636
|
+
var init_server = __esm(() => {
|
|
637
|
+
init_auth();
|
|
638
|
+
init_database();
|
|
639
|
+
init_logger();
|
|
640
|
+
PORT = parseInt(process.env.PORT || "5200", 10);
|
|
641
|
+
JWT_SECRET = process.env.JWT_SECRET || crypto.randomUUID();
|
|
642
|
+
INVITE_CODE_REQUIRED = process.env.INVITE_CODE_REQUIRED === "true";
|
|
643
|
+
});
|
|
2
644
|
|
|
3
645
|
// src/cli/index.ts
|
|
4
646
|
import { Command } from "commander";
|
|
5
647
|
import { config } from "dotenv";
|
|
6
|
-
import { existsSync, mkdirSync } from "fs";
|
|
7
|
-
import { resolve } from "path";
|
|
8
|
-
import { homedir } from "os";
|
|
9
|
-
var program = new Command
|
|
648
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
649
|
+
import { resolve as resolve2 } from "path";
|
|
650
|
+
import { homedir as homedir2 } from "os";
|
|
651
|
+
var program = new Command;
|
|
10
652
|
program.name("portagent").description("Portagent - AgentX Portal Application").version("0.0.1").option("-p, --port <port>", "Port to listen on", "5200").option("-d, --data-dir <path>", "Data directory (default: ~/.agentx)").option("-e, --env-file <path>", "Path to environment file").option("--jwt-secret <secret>", "JWT secret for token signing (or use JWT_SECRET env var)").option("--api-key <key>", "LLM provider API key (or use LLM_PROVIDER_KEY env var)").option("--api-url <url>", "LLM provider base URL (or use LLM_PROVIDER_URL env var)").option("--model <model>", "LLM model name (or use LLM_PROVIDER_MODEL env var)").action(async (options) => {
|
|
11
653
|
if (options.envFile) {
|
|
12
|
-
const envPath =
|
|
13
|
-
if (!
|
|
654
|
+
const envPath = resolve2(process.cwd(), options.envFile);
|
|
655
|
+
if (!existsSync2(envPath)) {
|
|
14
656
|
console.error(`Error: Environment file not found: ${envPath}`);
|
|
15
657
|
process.exit(1);
|
|
16
658
|
}
|
|
17
659
|
config({ path: envPath });
|
|
18
660
|
} else {
|
|
19
|
-
config({ path:
|
|
20
|
-
config({ path:
|
|
661
|
+
config({ path: resolve2(process.cwd(), ".env") });
|
|
662
|
+
config({ path: resolve2(process.cwd(), ".env.local") });
|
|
21
663
|
}
|
|
22
|
-
const dataDir = options.dataDir || process.env.AGENTX_DIR ||
|
|
664
|
+
const dataDir = options.dataDir || process.env.AGENTX_DIR || resolve2(homedir2(), ".agentx");
|
|
23
665
|
process.env.AGENTX_DIR = dataDir;
|
|
24
|
-
const dataDirPath =
|
|
25
|
-
const logsDirPath =
|
|
26
|
-
|
|
27
|
-
|
|
666
|
+
const dataDirPath = resolve2(dataDir, "data");
|
|
667
|
+
const logsDirPath = resolve2(dataDir, "logs");
|
|
668
|
+
mkdirSync2(dataDirPath, { recursive: true });
|
|
669
|
+
mkdirSync2(logsDirPath, { recursive: true });
|
|
28
670
|
if (options.port) {
|
|
29
671
|
process.env.PORT = options.port;
|
|
30
672
|
}
|
|
@@ -40,8 +682,9 @@ program.name("portagent").description("Portagent - AgentX Portal Application").v
|
|
|
40
682
|
if (options.model) {
|
|
41
683
|
process.env.LLM_PROVIDER_MODEL = options.model;
|
|
42
684
|
}
|
|
43
|
-
const { startServer } = await
|
|
44
|
-
await
|
|
685
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
686
|
+
await startServer2();
|
|
45
687
|
});
|
|
46
688
|
program.parse();
|
|
47
|
-
|
|
689
|
+
|
|
690
|
+
//# debugId=9AD642D37A34021364756E2164756E21
|