agentopia 1.0.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.
Files changed (140) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/dist/app.d.ts +10 -0
  3. package/dist/app.d.ts.map +1 -0
  4. package/dist/app.js +121 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/config.d.ts +9 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +19 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/db/database.d.ts +5 -0
  11. package/dist/db/database.d.ts.map +1 -0
  12. package/dist/db/database.js +39 -0
  13. package/dist/db/database.js.map +1 -0
  14. package/dist/db/schema.d.ts +3 -0
  15. package/dist/db/schema.d.ts.map +1 -0
  16. package/dist/db/schema.js +621 -0
  17. package/dist/db/schema.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +49 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +4 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +9 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/middleware/auth.d.ts +13 -0
  27. package/dist/middleware/auth.d.ts.map +1 -0
  28. package/dist/middleware/auth.js +733 -0
  29. package/dist/middleware/auth.js.map +1 -0
  30. package/dist/routes/agents.d.ts +3 -0
  31. package/dist/routes/agents.d.ts.map +1 -0
  32. package/dist/routes/agents.js +1058 -0
  33. package/dist/routes/agents.js.map +1 -0
  34. package/dist/routes/issues.d.ts +4 -0
  35. package/dist/routes/issues.d.ts.map +1 -0
  36. package/dist/routes/issues.js +946 -0
  37. package/dist/routes/issues.js.map +1 -0
  38. package/dist/routes/knowledge.d.ts +3 -0
  39. package/dist/routes/knowledge.d.ts.map +1 -0
  40. package/dist/routes/knowledge.js +117 -0
  41. package/dist/routes/knowledge.js.map +1 -0
  42. package/dist/routes/memories.d.ts +3 -0
  43. package/dist/routes/memories.d.ts.map +1 -0
  44. package/dist/routes/memories.js +115 -0
  45. package/dist/routes/memories.js.map +1 -0
  46. package/dist/routes/messages.d.ts +3 -0
  47. package/dist/routes/messages.d.ts.map +1 -0
  48. package/dist/routes/messages.js +130 -0
  49. package/dist/routes/messages.js.map +1 -0
  50. package/dist/routes/projects.d.ts +3 -0
  51. package/dist/routes/projects.d.ts.map +1 -0
  52. package/dist/routes/projects.js +754 -0
  53. package/dist/routes/projects.js.map +1 -0
  54. package/dist/routes/templates.d.ts +3 -0
  55. package/dist/routes/templates.d.ts.map +1 -0
  56. package/dist/routes/templates.js +117 -0
  57. package/dist/routes/templates.js.map +1 -0
  58. package/dist/routes/ui.d.ts +3 -0
  59. package/dist/routes/ui.d.ts.map +1 -0
  60. package/dist/routes/ui.js +38 -0
  61. package/dist/routes/ui.js.map +1 -0
  62. package/dist/services/agent-hierarchy.d.ts +14 -0
  63. package/dist/services/agent-hierarchy.d.ts.map +1 -0
  64. package/dist/services/agent-hierarchy.js +58 -0
  65. package/dist/services/agent-hierarchy.js.map +1 -0
  66. package/dist/services/agent-issue-batch.d.ts +17 -0
  67. package/dist/services/agent-issue-batch.d.ts.map +1 -0
  68. package/dist/services/agent-issue-batch.js +57 -0
  69. package/dist/services/agent-issue-batch.js.map +1 -0
  70. package/dist/services/controller.d.ts +4 -0
  71. package/dist/services/controller.d.ts.map +1 -0
  72. package/dist/services/controller.js +237 -0
  73. package/dist/services/controller.js.map +1 -0
  74. package/dist/services/langgraph-runner.d.ts +33 -0
  75. package/dist/services/langgraph-runner.d.ts.map +1 -0
  76. package/dist/services/langgraph-runner.js +478 -0
  77. package/dist/services/langgraph-runner.js.map +1 -0
  78. package/dist/services/orchestrator.d.ts +9 -0
  79. package/dist/services/orchestrator.d.ts.map +1 -0
  80. package/dist/services/orchestrator.js +116 -0
  81. package/dist/services/orchestrator.js.map +1 -0
  82. package/dist/services/pre-controller.d.ts +7 -0
  83. package/dist/services/pre-controller.d.ts.map +1 -0
  84. package/dist/services/pre-controller.js +101 -0
  85. package/dist/services/pre-controller.js.map +1 -0
  86. package/dist/services/process-manager.d.ts +67 -0
  87. package/dist/services/process-manager.d.ts.map +1 -0
  88. package/dist/services/process-manager.js +938 -0
  89. package/dist/services/process-manager.js.map +1 -0
  90. package/dist/services/project-permissions.d.ts +84 -0
  91. package/dist/services/project-permissions.d.ts.map +1 -0
  92. package/dist/services/project-permissions.js +129 -0
  93. package/dist/services/project-permissions.js.map +1 -0
  94. package/dist/services/scheduler.d.ts +6 -0
  95. package/dist/services/scheduler.d.ts.map +1 -0
  96. package/dist/services/scheduler.js +300 -0
  97. package/dist/services/scheduler.js.map +1 -0
  98. package/dist/services/system-prompt.d.ts +3 -0
  99. package/dist/services/system-prompt.d.ts.map +1 -0
  100. package/dist/services/system-prompt.js +285 -0
  101. package/dist/services/system-prompt.js.map +1 -0
  102. package/dist/services/terminal.d.ts +18 -0
  103. package/dist/services/terminal.d.ts.map +1 -0
  104. package/dist/services/terminal.js +222 -0
  105. package/dist/services/terminal.js.map +1 -0
  106. package/dist/services/websocket.d.ts +15 -0
  107. package/dist/services/websocket.d.ts.map +1 -0
  108. package/dist/services/websocket.js +204 -0
  109. package/dist/services/websocket.js.map +1 -0
  110. package/dist/types.d.ts +108 -0
  111. package/dist/types.d.ts.map +1 -0
  112. package/dist/types.js +3 -0
  113. package/dist/types.js.map +1 -0
  114. package/env.ini +18 -0
  115. package/package.json +38 -0
  116. package/project_id +0 -0
  117. package/public/admin-users.html +188 -0
  118. package/public/agent.html +199 -0
  119. package/public/css/issues.css +275 -0
  120. package/public/css/style.css +1299 -0
  121. package/public/index.html +166 -0
  122. package/public/issue.html +76 -0
  123. package/public/js/agent.js +19 -0
  124. package/public/js/common.js +735 -0
  125. package/public/js/dashboard.js +772 -0
  126. package/public/js/files-panel.js +703 -0
  127. package/public/js/interactive-terminal.js +201 -0
  128. package/public/js/issue-renderer.js +559 -0
  129. package/public/js/issue.js +57 -0
  130. package/public/js/project.js +2425 -0
  131. package/public/js/terminal.js +564 -0
  132. package/public/project.html +430 -0
  133. package/public/terminal.html +67 -0
  134. package/public/vendor/marked.js +74 -0
  135. package/public/vendor/xterm-addon-fit.js +2 -0
  136. package/public/vendor/xterm.css +209 -0
  137. package/public/vendor/xterm.js +2 -0
  138. package/send_message_and_update_issue.js +65 -0
  139. package/tsconfig.json +19 -0
  140. package/update_round2_and_create_round3.js +284 -0
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.setupWebSocket = setupWebSocket;
7
+ exports.broadcastToAgent = broadcastToAgent;
8
+ exports.broadcastToProject = broadcastToProject;
9
+ exports.clearAllPtyCleanupTimers = clearAllPtyCleanupTimers;
10
+ const ws_1 = require("ws");
11
+ const terminal_1 = require("./terminal");
12
+ const logger_1 = __importDefault(require("../logger"));
13
+ // Map of agentId -> Set of connected WebSocket clients
14
+ const agentClients = new Map();
15
+ // Map of projectId -> Set of connected WebSocket clients (project-level events)
16
+ const projectClients = new Map();
17
+ // Map of agentId -> Set of connected terminal WebSocket clients
18
+ const terminalClients = new Map();
19
+ // Map of agentId -> cleanup timer (auto-kill PTY after disconnect)
20
+ const ptyCleanupTimers = new Map();
21
+ // Auto-kill PTY session 5 minutes after last client disconnects
22
+ const PTY_CLEANUP_DELAY_MS = 5 * 60 * 1000;
23
+ function setupWebSocket(fastify) {
24
+ // Per-agent terminal output stream
25
+ fastify.get('/ws/agents/:id/terminal', { websocket: true }, (socket, request) => {
26
+ const { id } = request.params;
27
+ if (!agentClients.has(id)) {
28
+ agentClients.set(id, new Set());
29
+ }
30
+ agentClients.get(id).add(socket);
31
+ socket.on('close', () => {
32
+ const clients = agentClients.get(id);
33
+ if (clients) {
34
+ clients.delete(socket);
35
+ if (clients.size === 0) {
36
+ agentClients.delete(id);
37
+ }
38
+ }
39
+ });
40
+ socket.on('error', () => {
41
+ const clients = agentClients.get(id);
42
+ if (clients) {
43
+ clients.delete(socket);
44
+ }
45
+ });
46
+ socket.send(JSON.stringify({ type: 'connected', agentId: id }));
47
+ });
48
+ // Project-level event stream (agent status, issues, comments)
49
+ fastify.get('/ws/projects/:id/events', { websocket: true }, (socket, request) => {
50
+ const { id } = request.params;
51
+ if (!projectClients.has(id)) {
52
+ projectClients.set(id, new Set());
53
+ }
54
+ projectClients.get(id).add(socket);
55
+ socket.on('close', () => {
56
+ const clients = projectClients.get(id);
57
+ if (clients) {
58
+ clients.delete(socket);
59
+ if (clients.size === 0) {
60
+ projectClients.delete(id);
61
+ }
62
+ }
63
+ });
64
+ socket.on('error', () => {
65
+ const clients = projectClients.get(id);
66
+ if (clients) {
67
+ clients.delete(socket);
68
+ }
69
+ });
70
+ socket.send(JSON.stringify({ type: 'connected', projectId: id }));
71
+ });
72
+ // Interactive PTY terminal for agent chat
73
+ fastify.get('/ws/terminal/:agentId', { websocket: true }, (socket, request) => {
74
+ const { agentId } = request.params;
75
+ const query = (request.query || {});
76
+ const newSession = query.newSession === 'true';
77
+ const cols = parseInt(query.cols || '120') || 120;
78
+ const rows = parseInt(query.rows || '30') || 30;
79
+ // Track terminal client connections
80
+ if (!terminalClients.has(agentId)) {
81
+ terminalClients.set(agentId, new Set());
82
+ }
83
+ terminalClients.get(agentId).add(socket);
84
+ // Cancel any pending cleanup timer — a client reconnected
85
+ const existingTimer = ptyCleanupTimers.get(agentId);
86
+ if (existingTimer) {
87
+ clearTimeout(existingTimer);
88
+ ptyCleanupTimers.delete(agentId);
89
+ logger_1.default.info(`PTY cleanup timer cancelled for agent ${agentId} — client reconnected`);
90
+ }
91
+ // Check if session already exists
92
+ const hasExisting = (0, terminal_1.hasPtySession)(agentId);
93
+ socket.send(JSON.stringify({
94
+ type: 'connected',
95
+ agentId,
96
+ hasExistingSession: hasExisting,
97
+ }));
98
+ // Create or resume PTY session
99
+ const session = (0, terminal_1.getOrCreatePtySession)(agentId, newSession, cols, rows);
100
+ // Replay buffered terminal content so the client immediately sees the
101
+ // current screen even if it connected after PTY startup output.
102
+ const bufferedOutput = (0, terminal_1.getPtyOutputBuffer)(agentId);
103
+ if (bufferedOutput && socket.readyState === ws_1.WebSocket.OPEN) {
104
+ socket.send(JSON.stringify({
105
+ type: 'output',
106
+ data: Buffer.from(bufferedOutput).toString('base64'),
107
+ }));
108
+ }
109
+ // Forward PTY output -> WebSocket (base64 encoded)
110
+ const onData = session.pty.onData((data) => {
111
+ if (socket.readyState === ws_1.WebSocket.OPEN) {
112
+ socket.send(JSON.stringify({
113
+ type: 'output',
114
+ data: Buffer.from(data).toString('base64'),
115
+ }));
116
+ }
117
+ });
118
+ const onExit = session.pty.onExit(({ exitCode }) => {
119
+ if (socket.readyState === ws_1.WebSocket.OPEN) {
120
+ socket.send(JSON.stringify({ type: 'exit', exitCode }));
121
+ }
122
+ });
123
+ // Handle messages from browser
124
+ socket.on('message', (raw) => {
125
+ try {
126
+ const msg = JSON.parse(raw.toString());
127
+ switch (msg.type) {
128
+ case 'input':
129
+ session.pty.write(msg.data);
130
+ break;
131
+ case 'resize':
132
+ if (msg.cols && msg.rows) {
133
+ session.pty.resize(msg.cols, msg.rows);
134
+ }
135
+ break;
136
+ case 'kill':
137
+ (0, terminal_1.killPtySession)(agentId);
138
+ break;
139
+ }
140
+ }
141
+ catch {
142
+ // Ignore malformed messages
143
+ }
144
+ });
145
+ const removeClient = () => {
146
+ onData.dispose();
147
+ onExit.dispose();
148
+ const clients = terminalClients.get(agentId);
149
+ if (clients) {
150
+ clients.delete(socket);
151
+ // If no more clients connected, start cleanup timer
152
+ if (clients.size === 0) {
153
+ terminalClients.delete(agentId);
154
+ if ((0, terminal_1.hasPtySession)(agentId)) {
155
+ logger_1.default.info(`All terminal clients disconnected for agent ${agentId}, PTY will be killed in ${PTY_CLEANUP_DELAY_MS / 1000}s`);
156
+ const timer = setTimeout(() => {
157
+ ptyCleanupTimers.delete(agentId);
158
+ if ((0, terminal_1.hasPtySession)(agentId)) {
159
+ logger_1.default.info(`Auto-killing PTY session for agent ${agentId} — no client reconnected`);
160
+ (0, terminal_1.killPtySession)(agentId);
161
+ }
162
+ }, PTY_CLEANUP_DELAY_MS);
163
+ ptyCleanupTimers.set(agentId, timer);
164
+ }
165
+ }
166
+ }
167
+ };
168
+ socket.on('close', removeClient);
169
+ socket.on('error', removeClient);
170
+ });
171
+ }
172
+ function broadcastToAgent(agentId, data) {
173
+ const clients = agentClients.get(agentId);
174
+ if (!clients)
175
+ return;
176
+ const message = JSON.stringify(data);
177
+ for (const client of clients) {
178
+ if (client.readyState === ws_1.WebSocket.OPEN) {
179
+ client.send(message);
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * Broadcast a project-level event to all connected clients.
185
+ * Event types: agent_status, issue_created, issue_updated, comment_added
186
+ */
187
+ function broadcastToProject(projectId, event) {
188
+ const clients = projectClients.get(projectId);
189
+ if (!clients)
190
+ return;
191
+ const message = JSON.stringify(event);
192
+ for (const client of clients) {
193
+ if (client.readyState === ws_1.WebSocket.OPEN) {
194
+ client.send(message);
195
+ }
196
+ }
197
+ }
198
+ function clearAllPtyCleanupTimers() {
199
+ for (const [, timer] of ptyCleanupTimers) {
200
+ clearTimeout(timer);
201
+ }
202
+ ptyCleanupTimers.clear();
203
+ }
204
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/services/websocket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ export type OrchestratorEngine = 'native' | 'langgraph';
2
+ export interface Project {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ task_description: string;
7
+ command_template: string;
8
+ orchestrator_engine: OrchestratorEngine;
9
+ status: 'active' | 'paused' | 'completed';
10
+ owner_id: string | null;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+ export interface ProjectMember {
15
+ id: string;
16
+ project_id: string;
17
+ user_id: string;
18
+ role: 'owner' | 'member';
19
+ created_at: string;
20
+ }
21
+ export interface Agent {
22
+ id: string;
23
+ project_id: string;
24
+ name: string;
25
+ role: string;
26
+ is_controller: boolean;
27
+ parent_agent_id: string | null;
28
+ session_id: string | null;
29
+ working_directory: string | null;
30
+ custom_instructions: string;
31
+ session_run_count: number;
32
+ session_max_runs: number;
33
+ session_token_count: number;
34
+ session_max_tokens: number;
35
+ session_resume_timeout: number;
36
+ command_template: string | null;
37
+ status: 'idle' | 'running' | 'waiting' | 'error' | 'stopped';
38
+ paused: boolean;
39
+ pid: number | null;
40
+ last_prompt: string | null;
41
+ started_at: string | null;
42
+ finished_at: string | null;
43
+ created_at: string;
44
+ }
45
+ export interface Issue {
46
+ id: string;
47
+ project_id: string;
48
+ number: number;
49
+ title: string;
50
+ body: string;
51
+ created_by: string;
52
+ assigned_to: string | null;
53
+ priority: number;
54
+ status: 'open' | 'in_progress' | 'pending' | 'done' | 'closed';
55
+ labels: string;
56
+ milestone_id: string | null;
57
+ parent_id: string | null;
58
+ created_at: string;
59
+ updated_at: string;
60
+ }
61
+ export interface IssueComment {
62
+ id: string;
63
+ issue_id: string;
64
+ author_id: string;
65
+ body: string;
66
+ event_type: 'comment' | 'status_change' | 'assignment' | 'label_change';
67
+ meta: string;
68
+ created_at: string;
69
+ }
70
+ export interface ConversationLog {
71
+ id: number;
72
+ agent_id: string;
73
+ run_id: string;
74
+ content: string;
75
+ stream: 'stdin' | 'stdout' | 'stderr' | 'cost';
76
+ created_at: string;
77
+ }
78
+ export interface CreateProjectInput {
79
+ name: string;
80
+ description?: string;
81
+ task_description: string;
82
+ command_template?: string;
83
+ orchestrator_engine?: OrchestratorEngine;
84
+ }
85
+ export interface CreateAgentInput {
86
+ name: string;
87
+ role: string;
88
+ is_controller?: boolean;
89
+ parent_agent_id?: string | null;
90
+ session_id?: string;
91
+ working_directory?: string;
92
+ command_template?: string;
93
+ }
94
+ export interface StartAgentInput {
95
+ prompt: string;
96
+ }
97
+ export interface User {
98
+ id: string;
99
+ username: string;
100
+ email: string;
101
+ password_hash: string;
102
+ password_salt: string;
103
+ display_name: string;
104
+ role: 'admin' | 'member';
105
+ created_at: string;
106
+ last_login_at: string | null;
107
+ }
108
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/env.ini ADDED
@@ -0,0 +1,18 @@
1
+ [DEFAULT]
2
+ ui_host = localhost
3
+ ui_port = 3000
4
+ ui_open_browser = False
5
+ ui_report_root_dirs = output/exps,output/alpha,output
6
+ ui_report_index_cache_ttl = 0
7
+ stock_db_path = data/stock_data.db
8
+ factor_cache_dir = data/factor_cache
9
+ statistics_cache_path = data/cache/statistics_cache.json
10
+ experiment_output_dir = output/exps
11
+ alpha_db_path = output/alpha/alphas.db
12
+ batch_output_dir = output/exps
13
+ stock_comb_path = stock_comb.json
14
+ log_level = INFO
15
+ log_format = %(asctime)s - %(name)s - %(levelname)s - %(message)s
16
+
17
+ [development]
18
+
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "agentopia",
3
+ "version": "1.0.0",
4
+ "description": "Multi-Agent Collaboration Platform",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "start": "node dist/index.js",
9
+ "dev": "tsx watch --exclude '*.db' --exclude '*.db-*' --exclude 'test/**' src/index.ts",
10
+ "test": "npm run build && npx tsx test/api.test.ts"
11
+ },
12
+ "dependencies": {
13
+ "@fastify/multipart": "^9.4.0",
14
+ "@fastify/static": "^8.1.0",
15
+ "@fastify/websocket": "^11.0.1",
16
+ "@langchain/core": "^1.1.35",
17
+ "@langchain/langgraph": "^1.2.5",
18
+ "better-sqlite3": "^11.7.0",
19
+ "fastify": "^5.2.0",
20
+ "marked": "^17.0.5",
21
+ "node-cron": "^3.0.3",
22
+ "node-pty": "^1.1.0",
23
+ "uuid": "^11.1.0",
24
+ "xterm": "^5.3.0",
25
+ "xterm-addon-fit": "^0.8.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/better-sqlite3": "^7.6.12",
29
+ "@types/node": "^22.10.0",
30
+ "@types/node-cron": "^3.0.11",
31
+ "@types/uuid": "^10.0.0",
32
+ "@types/ws": "^8.18.1",
33
+ "concurrently": "^9.2.1",
34
+ "nodemon": "^3.1.14",
35
+ "tsx": "^4.19.0",
36
+ "typescript": "^5.7.0"
37
+ }
38
+ }
package/project_id ADDED
File without changes
@@ -0,0 +1,188 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Argus - User Management</title>
7
+ <link rel="stylesheet" href="/public/css/style.css">
8
+ </head>
9
+ <body>
10
+ <div class="overlay" id="overlay" onclick="closeDrawer()"></div>
11
+ <div class="drawer" id="drawer">
12
+ <div class="drawer-header">
13
+ <h3><span style="color:var(--accent)">Argus</span></h3>
14
+ <button class="drawer-close" onclick="closeDrawer()">&times;</button>
15
+ </div>
16
+ <div class="drawer-content">
17
+ <div class="drawer-section">
18
+ <div class="drawer-section-header">Navigation</div>
19
+ <a href="/" class="drawer-link">Dashboard</a>
20
+ </div>
21
+ <div class="drawer-section">
22
+ <div class="drawer-section-header">Settings</div>
23
+ <div class="settings-section">
24
+ <div class="setting-group">
25
+ <label>Theme</label>
26
+ <select id="theme-select" onchange="changeTheme(this.value)">
27
+ <option value="github-dark">GitHub Dark</option>
28
+ <option value="dracula">Dracula</option>
29
+ <option value="nord-dark">Nord Dark</option>
30
+ <option value="nord-light">Nord Light</option>
31
+ <option value="monokai">Monokai</option>
32
+ <option value="solarized-dark">Solarized Dark</option>
33
+ <option value="solarized-light">Solarized Light</option>
34
+ </select>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <header>
42
+ <button class="menu-btn" onclick="toggleDrawer()">
43
+ <span></span><span></span><span></span>
44
+ </button>
45
+ <h1><a href="/" style="color:var(--accent);text-decoration:none">Argus</a></h1>
46
+ <div class="breadcrumb" style="margin-left:12px">
47
+ <a href="/">Home</a> / <span>User Management</span>
48
+ </div>
49
+ <div class="header-right"></div>
50
+ </header>
51
+
52
+ <div class="container" style="max-width:800px;margin:0 auto;padding:24px 16px">
53
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
54
+ <h2 style="font-size:18px;font-weight:600">Users</h2>
55
+ <button class="btn btn-primary btn-sm" onclick="showAddUser()">+ Add User</button>
56
+ </div>
57
+ <div id="users-list"></div>
58
+ <div id="error-msg" style="color:var(--error);margin-top:8px;font-size:13px;display:none"></div>
59
+ </div>
60
+
61
+ <!-- Add User Modal -->
62
+ <div id="add-user-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:1000;display:none;align-items:center;justify-content:center">
63
+ <div style="background:var(--header-bg);border:1px solid var(--border);border-radius:12px;padding:24px;width:100%;max-width:360px;box-shadow:0 8px 32px rgba(0,0,0,0.3)">
64
+ <h3 style="margin-bottom:16px;font-size:16px">Add User</h3>
65
+ <form id="add-user-form">
66
+ <label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary)">Username</label>
67
+ <input type="text" id="new-username" required style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--fg);font-size:14px;margin-bottom:12px;font-family:inherit;box-sizing:border-box">
68
+ <label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary)">Display Name</label>
69
+ <input type="text" id="new-display-name" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--fg);font-size:14px;margin-bottom:12px;font-family:inherit;box-sizing:border-box">
70
+ <label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary)">Password</label>
71
+ <input type="password" id="new-password" required minlength="4" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--fg);font-size:14px;margin-bottom:12px;font-family:inherit;box-sizing:border-box">
72
+ <label style="display:block;margin-bottom:4px;font-size:13px;color:var(--text-secondary)">Role</label>
73
+ <select id="new-role" style="width:100%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--fg);font-size:14px;margin-bottom:16px;font-family:inherit">
74
+ <option value="member">Member</option>
75
+ <option value="admin">Admin</option>
76
+ </select>
77
+ <div id="add-user-error" style="color:var(--error);font-size:13px;margin-bottom:8px;display:none"></div>
78
+ <div style="display:flex;gap:8px;justify-content:flex-end">
79
+ <button type="button" class="btn btn-sm" onclick="hideAddUser()" style="background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:6px 14px;border-radius:6px;cursor:pointer;font-family:inherit">Cancel</button>
80
+ <button type="submit" class="btn btn-primary btn-sm" style="padding:6px 14px">Create</button>
81
+ </div>
82
+ </form>
83
+ </div>
84
+ </div>
85
+
86
+ <script src="/public/js/common.js?v=2"></script>
87
+ <script>
88
+ let users = [];
89
+
90
+ async function loadUsers() {
91
+ try {
92
+ const res = await fetch('/api/auth/users');
93
+ if (res.status === 403) {
94
+ document.getElementById('users-list').innerHTML = '<p style="color:var(--error)">Admin access required.</p>';
95
+ return;
96
+ }
97
+ const data = await res.json();
98
+ users = data.users || [];
99
+ renderUsers();
100
+ } catch (e) {
101
+ showError('Failed to load users');
102
+ }
103
+ }
104
+
105
+ function renderUsers() {
106
+ const el = document.getElementById('users-list');
107
+ if (!users.length) { el.innerHTML = '<p style="color:var(--text-secondary)">No users yet.</p>'; return; }
108
+ el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:13px">
109
+ <thead><tr style="border-bottom:2px solid var(--border);text-align:left">
110
+ <th style="padding:8px 12px">Username</th>
111
+ <th style="padding:8px 12px">Display Name</th>
112
+ <th style="padding:8px 12px">Role</th>
113
+ <th style="padding:8px 12px">Created</th>
114
+ <th style="padding:8px 12px">Last Login</th>
115
+ <th style="padding:8px 12px">Actions</th>
116
+ </tr></thead>
117
+ <tbody>${users.map(u => `<tr style="border-bottom:1px solid var(--border)">
118
+ <td style="padding:8px 12px">${esc(u.username)}</td>
119
+ <td style="padding:8px 12px">${esc(u.display_name || '-')}</td>
120
+ <td style="padding:8px 12px">
121
+ <select onchange="changeRole('${u.id}', this.value)" style="background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:12px;font-family:inherit"${_currentUser && u.id === _currentUser.id ? ' disabled' : ''}>
122
+ <option value="member"${u.role==='member'?' selected':''}>member</option>
123
+ <option value="admin"${u.role==='admin'?' selected':''}>admin</option>
124
+ </select>
125
+ </td>
126
+ <td style="padding:8px 12px;color:var(--text-secondary)">${u.created_at ? timeAgo(u.created_at) : '-'}</td>
127
+ <td style="padding:8px 12px;color:var(--text-secondary)">${u.last_login_at ? timeAgo(u.last_login_at) : 'Never'}</td>
128
+ <td style="padding:8px 12px">${_currentUser && u.id === _currentUser.id ? '<span style="color:var(--text-secondary)">you</span>' : `<button onclick="deleteUser('${u.id}','${esc(u.username)}')" style="background:none;border:none;color:var(--error);cursor:pointer;font-size:12px;font-family:inherit">Delete</button>`}</td>
129
+ </tr>`).join('')}</tbody>
130
+ </table>`;
131
+ }
132
+
133
+ async function changeRole(id, role) {
134
+ const res = await fetch('/api/auth/users/' + id, { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify({role}) });
135
+ if (!res.ok) { const d = await res.json(); showError(d.error); loadUsers(); return; }
136
+ loadUsers();
137
+ }
138
+
139
+ async function deleteUser(id, name) {
140
+ if (!confirm('Delete user "' + name + '"? This cannot be undone.')) return;
141
+ const res = await fetch('/api/auth/users/' + id, { method: 'DELETE' });
142
+ if (!res.ok) { const d = await res.json(); showError(d.error); return; }
143
+ loadUsers();
144
+ }
145
+
146
+ function showAddUser() {
147
+ document.getElementById('add-user-modal').style.display = 'flex';
148
+ }
149
+ function hideAddUser() {
150
+ document.getElementById('add-user-modal').style.display = 'none';
151
+ document.getElementById('add-user-form').reset();
152
+ document.getElementById('add-user-error').style.display = 'none';
153
+ }
154
+
155
+ document.getElementById('add-user-form').addEventListener('submit', async (e) => {
156
+ e.preventDefault();
157
+ const errEl = document.getElementById('add-user-error');
158
+ errEl.style.display = 'none';
159
+ const username = document.getElementById('new-username').value;
160
+ const display_name = document.getElementById('new-display-name').value;
161
+ const password = document.getElementById('new-password').value;
162
+ const role = document.getElementById('new-role').value;
163
+
164
+ const res = await fetch('/api/auth/register', {
165
+ method: 'POST', headers: {'Content-Type':'application/json'},
166
+ body: JSON.stringify({ username, password, display_name: display_name || undefined })
167
+ });
168
+ if (!res.ok) { const d = await res.json(); errEl.textContent = d.error || 'Failed'; errEl.style.display = 'block'; return; }
169
+ const data = await res.json();
170
+ // If role is admin, update it
171
+ if (role === 'admin' && data.user) {
172
+ await fetch('/api/auth/users/' + data.user.id, { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify({role:'admin'}) });
173
+ }
174
+ hideAddUser();
175
+ loadUsers();
176
+ });
177
+
178
+ function showError(msg) {
179
+ const el = document.getElementById('error-msg');
180
+ el.textContent = msg;
181
+ el.style.display = 'block';
182
+ setTimeout(() => el.style.display = 'none', 5000);
183
+ }
184
+
185
+ loadUsers();
186
+ </script>
187
+ </body>
188
+ </html>