diragent 0.1.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.
@@ -0,0 +1,1621 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/server/config.ts
14
+ var config_exports = {};
15
+ __export(config_exports, {
16
+ loadConfig: () => loadConfig,
17
+ validateAuth: () => validateAuth
18
+ });
19
+ import { readFileSync, existsSync as existsSync2 } from "fs";
20
+ import { join as join2 } from "path";
21
+ import { z } from "zod";
22
+ function loadConfig(configPath) {
23
+ if (cachedConfig) return cachedConfig;
24
+ const path = configPath || findConfigPath();
25
+ if (!path || !existsSync2(path)) {
26
+ cachedConfig = ConfigSchema.parse({});
27
+ return cachedConfig;
28
+ }
29
+ const raw = readFileSync(path, "utf-8");
30
+ const parsed = JSON.parse(raw);
31
+ cachedConfig = ConfigSchema.parse(parsed);
32
+ return cachedConfig;
33
+ }
34
+ function findConfigPath() {
35
+ const candidates = [
36
+ join2(process.cwd(), ".dirigent", "config.json"),
37
+ join2(process.cwd(), "dirigent.json"),
38
+ join2(process.env.HOME || "", ".dirigent", "config.json")
39
+ ];
40
+ for (const candidate of candidates) {
41
+ if (existsSync2(candidate)) return candidate;
42
+ }
43
+ return null;
44
+ }
45
+ function validateAuth(token, config) {
46
+ if (!config.auth.enabled) return true;
47
+ if (config.auth.adminToken && token === config.auth.adminToken) return true;
48
+ if (config.auth.apiKeys?.includes(token)) return true;
49
+ return false;
50
+ }
51
+ var AgentTemplateSchema, ConfigSchema, cachedConfig;
52
+ var init_config = __esm({
53
+ "src/server/config.ts"() {
54
+ "use strict";
55
+ AgentTemplateSchema = z.object({
56
+ driver: z.string(),
57
+ model: z.string().optional(),
58
+ maxTokens: z.number().optional(),
59
+ command: z.array(z.string()).optional(),
60
+ env: z.record(z.string()).optional()
61
+ });
62
+ ConfigSchema = z.object({
63
+ version: z.string().default("1"),
64
+ server: z.object({
65
+ port: z.number().default(3e3),
66
+ host: z.string().default("0.0.0.0")
67
+ }).default({}),
68
+ auth: z.object({
69
+ enabled: z.boolean().default(true),
70
+ adminToken: z.string().optional(),
71
+ apiKeys: z.array(z.string()).optional()
72
+ }).default({}),
73
+ agents: z.object({
74
+ maxConcurrent: z.number().default(10),
75
+ defaultTimeout: z.number().default(3600),
76
+ templates: z.record(AgentTemplateSchema).default({})
77
+ }).default({}),
78
+ logging: z.object({
79
+ level: z.enum(["debug", "info", "warn", "error"]).default("info"),
80
+ format: z.enum(["pretty", "json"]).default("pretty"),
81
+ file: z.string().optional()
82
+ }).default({}),
83
+ database: z.object({
84
+ path: z.string().default("data/dirigent.db")
85
+ }).default({})
86
+ });
87
+ cachedConfig = null;
88
+ }
89
+ });
90
+
91
+ // src/server/db/index.ts
92
+ import Database from "better-sqlite3";
93
+ function initDatabase(path) {
94
+ db = new Database(path);
95
+ db.pragma("journal_mode = WAL");
96
+ db.exec(`
97
+ -- Agents table
98
+ CREATE TABLE IF NOT EXISTS agents (
99
+ id TEXT PRIMARY KEY,
100
+ name TEXT NOT NULL,
101
+ template TEXT NOT NULL,
102
+ status TEXT NOT NULL DEFAULT 'created',
103
+ workspace TEXT,
104
+ model TEXT,
105
+ config TEXT,
106
+ pid INTEGER,
107
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
108
+ started_at INTEGER,
109
+ stopped_at INTEGER,
110
+ error TEXT
111
+ );
112
+
113
+ -- Agent logs table
114
+ CREATE TABLE IF NOT EXISTS agent_logs (
115
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
116
+ agent_id TEXT NOT NULL,
117
+ level TEXT NOT NULL,
118
+ message TEXT NOT NULL,
119
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
120
+ metadata TEXT,
121
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
122
+ );
123
+
124
+ -- Tasks table
125
+ CREATE TABLE IF NOT EXISTS tasks (
126
+ id TEXT PRIMARY KEY,
127
+ agent_id TEXT,
128
+ content TEXT NOT NULL,
129
+ status TEXT NOT NULL DEFAULT 'pending',
130
+ result TEXT,
131
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
132
+ started_at INTEGER,
133
+ completed_at INTEGER,
134
+ error TEXT,
135
+ FOREIGN KEY (agent_id) REFERENCES agents(id)
136
+ );
137
+
138
+ -- Audit log table
139
+ CREATE TABLE IF NOT EXISTS audit_log (
140
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
141
+ action TEXT NOT NULL,
142
+ actor TEXT,
143
+ target_type TEXT,
144
+ target_id TEXT,
145
+ details TEXT,
146
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
147
+ );
148
+
149
+ -- Sessions table (for WebSocket connections)
150
+ CREATE TABLE IF NOT EXISTS sessions (
151
+ id TEXT PRIMARY KEY,
152
+ user_id TEXT,
153
+ connected_at INTEGER NOT NULL DEFAULT (unixepoch()),
154
+ last_activity INTEGER NOT NULL DEFAULT (unixepoch()),
155
+ metadata TEXT
156
+ );
157
+
158
+ -- Indexes
159
+ CREATE INDEX IF NOT EXISTS idx_agent_logs_agent_id ON agent_logs(agent_id);
160
+ CREATE INDEX IF NOT EXISTS idx_agent_logs_timestamp ON agent_logs(timestamp);
161
+ CREATE INDEX IF NOT EXISTS idx_tasks_agent_id ON tasks(agent_id);
162
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
163
+ CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON audit_log(timestamp);
164
+ `);
165
+ return db;
166
+ }
167
+ function getDatabase() {
168
+ return db;
169
+ }
170
+ function insertAgent(agent2) {
171
+ const stmt = db.prepare(`
172
+ INSERT INTO agents (id, name, template, workspace, model, config)
173
+ VALUES (?, ?, ?, ?, ?, ?)
174
+ `);
175
+ stmt.run(
176
+ agent2.id,
177
+ agent2.name,
178
+ agent2.template,
179
+ agent2.workspace || null,
180
+ agent2.model || null,
181
+ agent2.config ? JSON.stringify(agent2.config) : null
182
+ );
183
+ }
184
+ function updateAgent(id, updates) {
185
+ const keys = Object.keys(updates);
186
+ const values = keys.map((k) => typeof updates[k] === "object" ? JSON.stringify(updates[k]) : updates[k]);
187
+ const stmt = db.prepare(`
188
+ UPDATE agents SET ${keys.map((k) => `${k} = ?`).join(", ")}
189
+ WHERE id = ?
190
+ `);
191
+ stmt.run(...values, id);
192
+ }
193
+ function getAgents(includesStopped = false) {
194
+ const stmt = db.prepare(
195
+ includesStopped ? "SELECT * FROM agents ORDER BY created_at DESC" : "SELECT * FROM agents WHERE status != 'stopped' ORDER BY created_at DESC"
196
+ );
197
+ return stmt.all().map((row) => {
198
+ if (row.config) row.config = JSON.parse(row.config);
199
+ return row;
200
+ });
201
+ }
202
+ function insertLog(log) {
203
+ const stmt = db.prepare(`
204
+ INSERT INTO agent_logs (agent_id, level, message, metadata)
205
+ VALUES (?, ?, ?, ?)
206
+ `);
207
+ stmt.run(log.agentId, log.level, log.message, log.metadata ? JSON.stringify(log.metadata) : null);
208
+ }
209
+ function getLogs(agentId, limit = 100) {
210
+ const stmt = db.prepare(`
211
+ SELECT * FROM agent_logs
212
+ WHERE agent_id = ?
213
+ ORDER BY timestamp DESC
214
+ LIMIT ?
215
+ `);
216
+ return stmt.all(agentId, limit).reverse().map((row) => {
217
+ if (row.metadata) row.metadata = JSON.parse(row.metadata);
218
+ return row;
219
+ });
220
+ }
221
+ function audit(action, actor, targetType, targetId, details) {
222
+ const stmt = db.prepare(`
223
+ INSERT INTO audit_log (action, actor, target_type, target_id, details)
224
+ VALUES (?, ?, ?, ?, ?)
225
+ `);
226
+ stmt.run(action, actor, targetType, targetId, details ? JSON.stringify(details) : null);
227
+ }
228
+ var db;
229
+ var init_db = __esm({
230
+ "src/server/db/index.ts"() {
231
+ "use strict";
232
+ db = null;
233
+ }
234
+ });
235
+
236
+ // src/server/agents/manager.ts
237
+ import { EventEmitter } from "events";
238
+ import { nanoid as nanoid2 } from "nanoid";
239
+ import { spawn } from "child_process";
240
+ import treeKill from "tree-kill";
241
+ import { join as join3 } from "path";
242
+ import { mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
243
+ var AgentManager;
244
+ var init_manager = __esm({
245
+ "src/server/agents/manager.ts"() {
246
+ "use strict";
247
+ init_db();
248
+ AgentManager = class extends EventEmitter {
249
+ agents = /* @__PURE__ */ new Map();
250
+ processes = /* @__PURE__ */ new Map();
251
+ dataDir;
252
+ config;
253
+ logger;
254
+ constructor(options) {
255
+ super();
256
+ this.dataDir = options.dataDir;
257
+ this.config = options.config;
258
+ this.logger = options.logger;
259
+ this.loadAgents();
260
+ }
261
+ loadAgents() {
262
+ const dbAgents = getAgents(false);
263
+ for (const dbAgent of dbAgents) {
264
+ if (dbAgent.status !== "stopped") {
265
+ this.agents.set(dbAgent.id, {
266
+ id: dbAgent.id,
267
+ name: dbAgent.name,
268
+ template: dbAgent.template,
269
+ status: "stopped",
270
+ // Reset to stopped since we restarted
271
+ workspace: dbAgent.workspace,
272
+ model: dbAgent.model,
273
+ createdAt: dbAgent.created_at * 1e3
274
+ });
275
+ updateAgent(dbAgent.id, { status: "stopped" });
276
+ }
277
+ }
278
+ }
279
+ async spawn(options) {
280
+ const { template, name, workspace, task, model } = options;
281
+ const templateConfig = this.config.agents.templates[template];
282
+ if (!templateConfig) {
283
+ throw new Error(`Unknown template: ${template}`);
284
+ }
285
+ const runningCount = Array.from(this.agents.values()).filter(
286
+ (a) => a.status === "running" || a.status === "starting"
287
+ ).length;
288
+ if (runningCount >= this.config.agents.maxConcurrent) {
289
+ throw new Error(`Max concurrent agents (${this.config.agents.maxConcurrent}) reached`);
290
+ }
291
+ const id = nanoid2(12);
292
+ const agentName = name || `${template}-${id.slice(0, 6)}`;
293
+ const agentWorkspace = workspace || join3(this.dataDir, "workspaces", id);
294
+ if (!existsSync3(agentWorkspace)) {
295
+ mkdirSync2(agentWorkspace, { recursive: true });
296
+ }
297
+ const agent2 = {
298
+ id,
299
+ name: agentName,
300
+ template,
301
+ status: "created",
302
+ workspace: agentWorkspace,
303
+ model: model || templateConfig.model,
304
+ currentTask: task,
305
+ createdAt: Date.now()
306
+ };
307
+ insertAgent({
308
+ id: agent2.id,
309
+ name: agent2.name,
310
+ template: agent2.template,
311
+ workspace: agent2.workspace,
312
+ model: agent2.model
313
+ });
314
+ audit("agent.created", null, "agent", id, { name: agentName, template });
315
+ this.agents.set(id, agent2);
316
+ this.emit("agent:created", agent2);
317
+ await this.startAgent(agent2, templateConfig, task);
318
+ return agent2;
319
+ }
320
+ async startAgent(agent2, template, initialTask) {
321
+ agent2.status = "starting";
322
+ updateAgent(agent2.id, { status: "starting" });
323
+ this.emit("agent:starting", agent2);
324
+ try {
325
+ const { command, env } = this.buildCommand(agent2, template);
326
+ this.logger.info({ agentId: agent2.id, command }, "Starting agent");
327
+ const proc = spawn(command[0], command.slice(1), {
328
+ cwd: agent2.workspace,
329
+ env: {
330
+ ...process.env,
331
+ ...env,
332
+ DIRIGENT_AGENT_ID: agent2.id,
333
+ DIRIGENT_AGENT_NAME: agent2.name
334
+ },
335
+ stdio: ["pipe", "pipe", "pipe"]
336
+ });
337
+ agent2.pid = proc.pid;
338
+ agent2.process = proc;
339
+ agent2.status = "running";
340
+ agent2.startedAt = Date.now();
341
+ updateAgent(agent2.id, {
342
+ status: "running",
343
+ pid: proc.pid,
344
+ started_at: Math.floor(Date.now() / 1e3)
345
+ });
346
+ this.processes.set(agent2.id, proc);
347
+ proc.stdout?.on("data", (data) => {
348
+ const message = data.toString().trim();
349
+ if (message) {
350
+ this.log(agent2.id, "info", message);
351
+ }
352
+ });
353
+ proc.stderr?.on("data", (data) => {
354
+ const message = data.toString().trim();
355
+ if (message) {
356
+ this.log(agent2.id, "error", message);
357
+ }
358
+ });
359
+ proc.on("exit", (code, signal) => {
360
+ this.logger.info({ agentId: agent2.id, code, signal }, "Agent exited");
361
+ agent2.status = code === 0 ? "stopped" : "error";
362
+ agent2.stoppedAt = Date.now();
363
+ if (code !== 0) {
364
+ agent2.error = `Exited with code ${code}`;
365
+ }
366
+ updateAgent(agent2.id, {
367
+ status: agent2.status,
368
+ stopped_at: Math.floor(Date.now() / 1e3),
369
+ error: agent2.error || null
370
+ });
371
+ this.processes.delete(agent2.id);
372
+ this.emit("agent:stopped", agent2);
373
+ audit("agent.stopped", null, "agent", agent2.id, { code, signal });
374
+ });
375
+ proc.on("error", (err) => {
376
+ this.logger.error({ agentId: agent2.id, err }, "Agent process error");
377
+ agent2.status = "error";
378
+ agent2.error = err.message;
379
+ updateAgent(agent2.id, { status: "error", error: err.message });
380
+ this.emit("agent:error", agent2, err);
381
+ });
382
+ this.emit("agent:running", agent2);
383
+ audit("agent.started", null, "agent", agent2.id, { pid: proc.pid });
384
+ if (initialTask && proc.stdin) {
385
+ proc.stdin.write(initialTask + "\n");
386
+ }
387
+ } catch (err) {
388
+ agent2.status = "error";
389
+ agent2.error = err.message;
390
+ updateAgent(agent2.id, { status: "error", error: err.message });
391
+ this.emit("agent:error", agent2, err);
392
+ throw err;
393
+ }
394
+ }
395
+ buildCommand(agent2, template) {
396
+ const env = { ...template.env };
397
+ switch (template.driver) {
398
+ case "claude-code":
399
+ return {
400
+ command: ["claude", "--dangerously-skip-permissions"],
401
+ env: {
402
+ ...env,
403
+ ANTHROPIC_MODEL: agent2.model || template.model || "claude-sonnet-4-5"
404
+ }
405
+ };
406
+ case "codex":
407
+ return {
408
+ command: ["codex", "--model", agent2.model || template.model || "codex-1"],
409
+ env
410
+ };
411
+ case "clawdbot":
412
+ return {
413
+ command: ["clawdbot", "agent", "--headless"],
414
+ env: {
415
+ ...env,
416
+ CLAWDBOT_MODEL: agent2.model || template.model || "claude-sonnet-4-5"
417
+ }
418
+ };
419
+ case "subprocess":
420
+ if (!template.command || template.command.length === 0) {
421
+ throw new Error("subprocess driver requires command");
422
+ }
423
+ return { command: template.command, env };
424
+ default:
425
+ throw new Error(`Unknown driver: ${template.driver}`);
426
+ }
427
+ }
428
+ async stop(id, force = false) {
429
+ const agent2 = this.agents.get(id);
430
+ if (!agent2) {
431
+ throw new Error(`Agent not found: ${id}`);
432
+ }
433
+ if (agent2.status !== "running" && agent2.status !== "starting") {
434
+ return;
435
+ }
436
+ agent2.status = "stopping";
437
+ this.emit("agent:stopping", agent2);
438
+ const proc = this.processes.get(id);
439
+ if (proc && proc.pid) {
440
+ await new Promise((resolve, reject) => {
441
+ treeKill(proc.pid, force ? "SIGKILL" : "SIGTERM", (err) => {
442
+ if (err) reject(err);
443
+ else resolve();
444
+ });
445
+ });
446
+ }
447
+ audit("agent.stop_requested", null, "agent", id, { force });
448
+ }
449
+ async stopAll() {
450
+ const promises = Array.from(this.agents.values()).filter((a) => a.status === "running" || a.status === "starting").map((a) => this.stop(a.id, true));
451
+ await Promise.allSettled(promises);
452
+ }
453
+ async send(id, message) {
454
+ const agent2 = this.agents.get(id);
455
+ if (!agent2) {
456
+ throw new Error(`Agent not found: ${id}`);
457
+ }
458
+ if (agent2.status !== "running") {
459
+ throw new Error(`Agent is not running: ${agent2.status}`);
460
+ }
461
+ const proc = this.processes.get(id);
462
+ if (!proc || !proc.stdin) {
463
+ throw new Error("Agent process not available");
464
+ }
465
+ proc.stdin.write(message + "\n");
466
+ this.log(id, "info", `[USER] ${message}`);
467
+ audit("agent.message_sent", null, "agent", id, { length: message.length });
468
+ }
469
+ get(id) {
470
+ return this.agents.get(id);
471
+ }
472
+ list(includeStopped = false) {
473
+ const agents = Array.from(this.agents.values());
474
+ if (includeStopped) return agents;
475
+ return agents.filter((a) => a.status !== "stopped");
476
+ }
477
+ log(agentId, level, message) {
478
+ insertLog({ agentId, level, message });
479
+ this.emit("agent:log", { agentId, level, message, timestamp: Date.now() });
480
+ }
481
+ getLogs(agentId, limit = 100) {
482
+ return getLogs(agentId, limit);
483
+ }
484
+ getStats() {
485
+ const agents = Array.from(this.agents.values());
486
+ return {
487
+ total: agents.length,
488
+ running: agents.filter((a) => a.status === "running").length,
489
+ idle: agents.filter((a) => a.status === "idle").length,
490
+ stopped: agents.filter((a) => a.status === "stopped").length,
491
+ error: agents.filter((a) => a.status === "error").length
492
+ };
493
+ }
494
+ };
495
+ }
496
+ });
497
+
498
+ // src/server/api/routes.ts
499
+ function registerApiRoutes(server2, options) {
500
+ const { config, agentManager: agentManager2, startTime: startTime2 } = options;
501
+ server2.addHook("onRequest", async (request, reply) => {
502
+ if (request.url === "/health") return;
503
+ if (!request.url.startsWith("/api")) return;
504
+ if (config.auth.enabled) {
505
+ const authHeader = request.headers.authorization;
506
+ const token = authHeader?.replace("Bearer ", "");
507
+ if (!token || !validateAuth(token, config)) {
508
+ reply.code(401).send({ error: "Unauthorized" });
509
+ return;
510
+ }
511
+ }
512
+ });
513
+ server2.get("/api/status", async () => {
514
+ const stats = agentManager2.getStats();
515
+ const agents = agentManager2.list(false);
516
+ return {
517
+ status: "ok",
518
+ uptime: Math.floor((Date.now() - startTime2) / 1e3),
519
+ agents: {
520
+ ...stats,
521
+ list: agents.map((a) => ({
522
+ id: a.id,
523
+ name: a.name,
524
+ template: a.template,
525
+ status: a.status,
526
+ workspace: a.workspace,
527
+ currentTask: a.currentTask
528
+ }))
529
+ }
530
+ };
531
+ });
532
+ server2.get("/api/agents", async (request) => {
533
+ const includeStopped = request.query.all === "true";
534
+ const agents = agentManager2.list(includeStopped);
535
+ return {
536
+ agents: agents.map((a) => ({
537
+ id: a.id,
538
+ name: a.name,
539
+ template: a.template,
540
+ status: a.status,
541
+ workspace: a.workspace,
542
+ model: a.model,
543
+ pid: a.pid,
544
+ currentTask: a.currentTask,
545
+ createdAt: a.createdAt,
546
+ startedAt: a.startedAt,
547
+ stoppedAt: a.stoppedAt,
548
+ error: a.error
549
+ }))
550
+ };
551
+ });
552
+ server2.post("/api/agents", async (request, reply) => {
553
+ const { template, name, workspace, task, model } = request.body;
554
+ if (!template) {
555
+ reply.code(400).send({ error: "template is required" });
556
+ return;
557
+ }
558
+ try {
559
+ const agent2 = await agentManager2.spawn({ template, name, workspace, task, model });
560
+ return {
561
+ agent: {
562
+ id: agent2.id,
563
+ name: agent2.name,
564
+ template: agent2.template,
565
+ status: agent2.status,
566
+ workspace: agent2.workspace,
567
+ model: agent2.model
568
+ }
569
+ };
570
+ } catch (err) {
571
+ reply.code(400).send({ error: err.message });
572
+ }
573
+ });
574
+ server2.get("/api/agents/:id", async (request, reply) => {
575
+ const agent2 = agentManager2.get(request.params.id);
576
+ if (!agent2) {
577
+ reply.code(404).send({ error: "Agent not found" });
578
+ return;
579
+ }
580
+ return {
581
+ agent: {
582
+ id: agent2.id,
583
+ name: agent2.name,
584
+ template: agent2.template,
585
+ status: agent2.status,
586
+ workspace: agent2.workspace,
587
+ model: agent2.model,
588
+ pid: agent2.pid,
589
+ currentTask: agent2.currentTask,
590
+ createdAt: agent2.createdAt,
591
+ startedAt: agent2.startedAt,
592
+ stoppedAt: agent2.stoppedAt,
593
+ error: agent2.error
594
+ }
595
+ };
596
+ });
597
+ server2.delete(
598
+ "/api/agents/:id",
599
+ async (request, reply) => {
600
+ const agent2 = agentManager2.get(request.params.id);
601
+ if (!agent2) {
602
+ reply.code(404).send({ error: "Agent not found" });
603
+ return;
604
+ }
605
+ const force = request.query.force === "true";
606
+ try {
607
+ await agentManager2.stop(request.params.id, force);
608
+ return { ok: true };
609
+ } catch (err) {
610
+ reply.code(500).send({ error: err.message });
611
+ }
612
+ }
613
+ );
614
+ server2.post(
615
+ "/api/agents/:id/send",
616
+ async (request, reply) => {
617
+ const agent2 = agentManager2.get(request.params.id);
618
+ if (!agent2) {
619
+ reply.code(404).send({ error: "Agent not found" });
620
+ return;
621
+ }
622
+ const { message } = request.body;
623
+ if (!message) {
624
+ reply.code(400).send({ error: "message is required" });
625
+ return;
626
+ }
627
+ try {
628
+ await agentManager2.send(request.params.id, message);
629
+ return { ok: true };
630
+ } catch (err) {
631
+ reply.code(400).send({ error: err.message });
632
+ }
633
+ }
634
+ );
635
+ server2.get(
636
+ "/api/agents/:id/logs",
637
+ async (request, reply) => {
638
+ const agent2 = agentManager2.get(request.params.id);
639
+ if (!agent2) {
640
+ reply.code(404).send({ error: "Agent not found" });
641
+ return;
642
+ }
643
+ const limit = parseInt(request.query.lines || "100");
644
+ const logs = agentManager2.getLogs(request.params.id, limit);
645
+ return { logs };
646
+ }
647
+ );
648
+ server2.get("/api/templates", async () => {
649
+ return {
650
+ templates: Object.entries(config.agents.templates).map(([name, template]) => ({
651
+ name,
652
+ driver: template.driver,
653
+ model: template.model
654
+ }))
655
+ };
656
+ });
657
+ server2.get("/api/audit", async (request) => {
658
+ return { logs: [] };
659
+ });
660
+ }
661
+ var init_routes = __esm({
662
+ "src/server/api/routes.ts"() {
663
+ "use strict";
664
+ init_config();
665
+ }
666
+ });
667
+
668
+ // src/server/ws/index.ts
669
+ function registerWebSocket(server2, options) {
670
+ const { config, agentManager: agentManager2 } = options;
671
+ const clients = /* @__PURE__ */ new Map();
672
+ server2.register(async (fastify) => {
673
+ fastify.get("/ws", { websocket: true }, (connection, req) => {
674
+ const ws = connection;
675
+ const clientId = Math.random().toString(36).slice(2);
676
+ const client = {
677
+ ws,
678
+ subscriptions: /* @__PURE__ */ new Set(),
679
+ authenticated: !config.auth.enabled
680
+ };
681
+ clients.set(clientId, client);
682
+ ws.on("message", (data) => {
683
+ try {
684
+ const msg = JSON.parse(data.toString());
685
+ handleMessage(clientId, client, msg);
686
+ } catch (err) {
687
+ send(ws, { type: "error", error: "Invalid JSON" });
688
+ }
689
+ });
690
+ ws.on("close", () => {
691
+ clients.delete(clientId);
692
+ });
693
+ ws.on("error", (err) => {
694
+ console.error("WebSocket error:", err);
695
+ clients.delete(clientId);
696
+ });
697
+ send(ws, {
698
+ type: "welcome",
699
+ clientId,
700
+ authenticated: client.authenticated
701
+ });
702
+ });
703
+ });
704
+ function handleMessage(clientId, client, msg) {
705
+ if (msg.type === "auth") {
706
+ if (validateAuth(msg.token, config)) {
707
+ client.authenticated = true;
708
+ send(client.ws, { type: "auth", success: true });
709
+ } else {
710
+ send(client.ws, { type: "auth", success: false, error: "Invalid token" });
711
+ }
712
+ return;
713
+ }
714
+ if (!client.authenticated) {
715
+ send(client.ws, { type: "error", error: "Not authenticated" });
716
+ return;
717
+ }
718
+ switch (msg.type) {
719
+ case "subscribe:logs":
720
+ if (msg.agentId) {
721
+ client.subscriptions.add(`logs:${msg.agentId}`);
722
+ send(client.ws, { type: "subscribed", channel: `logs:${msg.agentId}` });
723
+ }
724
+ break;
725
+ case "unsubscribe:logs":
726
+ if (msg.agentId) {
727
+ client.subscriptions.delete(`logs:${msg.agentId}`);
728
+ send(client.ws, { type: "unsubscribed", channel: `logs:${msg.agentId}` });
729
+ }
730
+ break;
731
+ case "subscribe:agents":
732
+ client.subscriptions.add("agents");
733
+ send(client.ws, { type: "subscribed", channel: "agents" });
734
+ break;
735
+ case "unsubscribe:agents":
736
+ client.subscriptions.delete("agents");
737
+ send(client.ws, { type: "unsubscribed", channel: "agents" });
738
+ break;
739
+ case "ping":
740
+ send(client.ws, { type: "pong", ts: Date.now() });
741
+ break;
742
+ default:
743
+ send(client.ws, { type: "error", error: `Unknown message type: ${msg.type}` });
744
+ }
745
+ }
746
+ agentManager2.on("agent:created", (agent2) => {
747
+ broadcast("agents", { type: "agent:created", agent: sanitizeAgent(agent2) });
748
+ });
749
+ agentManager2.on("agent:running", (agent2) => {
750
+ broadcast("agents", { type: "agent:running", agent: sanitizeAgent(agent2) });
751
+ });
752
+ agentManager2.on("agent:stopped", (agent2) => {
753
+ broadcast("agents", { type: "agent:stopped", agent: sanitizeAgent(agent2) });
754
+ });
755
+ agentManager2.on("agent:error", (agent2, error) => {
756
+ broadcast("agents", { type: "agent:error", agent: sanitizeAgent(agent2), error: error.message });
757
+ });
758
+ agentManager2.on("agent:log", (data) => {
759
+ broadcast(`logs:${data.agentId}`, { type: "agent:log", ...data });
760
+ });
761
+ function broadcast(channel, message) {
762
+ for (const client of clients.values()) {
763
+ if (client.authenticated && client.subscriptions.has(channel)) {
764
+ send(client.ws, message);
765
+ }
766
+ }
767
+ }
768
+ function send(ws, message) {
769
+ if (ws.readyState === ws.OPEN) {
770
+ ws.send(JSON.stringify(message));
771
+ }
772
+ }
773
+ function sanitizeAgent(agent2) {
774
+ return {
775
+ id: agent2.id,
776
+ name: agent2.name,
777
+ template: agent2.template,
778
+ status: agent2.status,
779
+ workspace: agent2.workspace,
780
+ model: agent2.model,
781
+ pid: agent2.pid,
782
+ currentTask: agent2.currentTask,
783
+ createdAt: agent2.createdAt,
784
+ startedAt: agent2.startedAt,
785
+ stoppedAt: agent2.stoppedAt,
786
+ error: agent2.error
787
+ };
788
+ }
789
+ }
790
+ var init_ws = __esm({
791
+ "src/server/ws/index.ts"() {
792
+ "use strict";
793
+ init_config();
794
+ }
795
+ });
796
+
797
+ // src/server/index.ts
798
+ var server_exports = {};
799
+ __export(server_exports, {
800
+ startServer: () => startServer
801
+ });
802
+ import Fastify from "fastify";
803
+ import fastifyWebsocket from "@fastify/websocket";
804
+ import fastifyCors from "@fastify/cors";
805
+ import fastifyStatic from "@fastify/static";
806
+ import pino from "pino";
807
+ import { join as join4, dirname } from "path";
808
+ import { fileURLToPath } from "url";
809
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
810
+ async function startServer(options) {
811
+ const config = loadConfig(options.configPath);
812
+ const logPath = join4(options.dataDir, "logs", "dirigent.log");
813
+ mkdirSync3(dirname(logPath), { recursive: true });
814
+ const logger = pino({
815
+ level: config.logging?.level || "info",
816
+ transport: {
817
+ targets: [
818
+ {
819
+ target: "pino-pretty",
820
+ options: { colorize: true },
821
+ level: "info"
822
+ },
823
+ {
824
+ target: "pino/file",
825
+ options: { destination: logPath },
826
+ level: "debug"
827
+ }
828
+ ]
829
+ }
830
+ });
831
+ const dbPath = join4(options.dataDir, "data", "dirigent.db");
832
+ mkdirSync3(dirname(dbPath), { recursive: true });
833
+ initDatabase(dbPath);
834
+ server = Fastify({ logger });
835
+ await server.register(fastifyCors, {
836
+ origin: true,
837
+ credentials: true
838
+ });
839
+ await server.register(fastifyWebsocket);
840
+ const dashboardPath = join4(__dirname, "..", "dashboard", "dist");
841
+ if (existsSync4(dashboardPath)) {
842
+ await server.register(fastifyStatic, {
843
+ root: dashboardPath,
844
+ prefix: "/"
845
+ });
846
+ }
847
+ agentManager = new AgentManager({
848
+ dataDir: options.dataDir,
849
+ config,
850
+ logger
851
+ });
852
+ registerApiRoutes(server, {
853
+ config,
854
+ agentManager,
855
+ startTime
856
+ });
857
+ registerWebSocket(server, {
858
+ config,
859
+ agentManager
860
+ });
861
+ server.get("/health", async () => ({ status: "ok", uptime: Math.floor((Date.now() - startTime) / 1e3) }));
862
+ const shutdown = async (signal) => {
863
+ logger.info(`Received ${signal}, shutting down...`);
864
+ if (agentManager) {
865
+ await agentManager.stopAll();
866
+ }
867
+ if (server) {
868
+ await server.close();
869
+ }
870
+ const db2 = getDatabase();
871
+ if (db2) db2.close();
872
+ process.exit(0);
873
+ };
874
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
875
+ process.on("SIGINT", () => shutdown("SIGINT"));
876
+ try {
877
+ await server.listen({ port: options.port, host: config.server?.host || "0.0.0.0" });
878
+ console.log(`
879
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
880
+ \u2551 \u2551
881
+ \u2551 \u{1F3AD} DIRIGENT SERVER RUNNING \u2551
882
+ \u2551 \u2551
883
+ \u2551 Dashboard: http://localhost:${options.port.toString().padEnd(25)}\u2551
884
+ \u2551 API: http://localhost:${options.port}/api${" ".repeat(21)}\u2551
885
+ \u2551 WebSocket: ws://localhost:${options.port}/ws${" ".repeat(22)}\u2551
886
+ \u2551 \u2551
887
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
888
+ `);
889
+ } catch (err) {
890
+ logger.error(err);
891
+ process.exit(1);
892
+ }
893
+ }
894
+ var __filename, __dirname, server, agentManager, startTime;
895
+ var init_server = __esm({
896
+ "src/server/index.ts"() {
897
+ "use strict";
898
+ init_config();
899
+ init_db();
900
+ init_manager();
901
+ init_routes();
902
+ init_ws();
903
+ __filename = fileURLToPath(import.meta.url);
904
+ __dirname = dirname(__filename);
905
+ server = null;
906
+ agentManager = null;
907
+ startTime = Date.now();
908
+ if (process.env.DIRIGENT_CONFIG) {
909
+ startServer({
910
+ port: parseInt(process.env.DIRIGENT_PORT || "3000"),
911
+ configPath: process.env.DIRIGENT_CONFIG,
912
+ dataDir: process.env.DIRIGENT_DATA_DIR || ".dirigent"
913
+ });
914
+ }
915
+ }
916
+ });
917
+
918
+ // src/cli/index.ts
919
+ import { Command } from "commander";
920
+ import chalk8 from "chalk";
921
+
922
+ // src/cli/commands/init.ts
923
+ import chalk from "chalk";
924
+ import ora from "ora";
925
+ import * as readline from "readline";
926
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
927
+ import { join } from "path";
928
+ import { nanoid } from "nanoid";
929
+ async function prompt(question, defaultValue) {
930
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
931
+ return new Promise((resolve) => {
932
+ const q = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
933
+ rl.question(q, (answer) => {
934
+ rl.close();
935
+ resolve(answer || defaultValue || "");
936
+ });
937
+ });
938
+ }
939
+ async function confirm(question, defaultValue = true) {
940
+ const answer = await prompt(`${question} (${defaultValue ? "Y/n" : "y/N"})`, defaultValue ? "y" : "n");
941
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
942
+ }
943
+ var DEFAULT_CONFIG = {
944
+ version: "1",
945
+ server: {
946
+ port: 3e3,
947
+ host: "0.0.0.0"
948
+ },
949
+ auth: {
950
+ enabled: true,
951
+ adminToken: ""
952
+ // Generated during init
953
+ },
954
+ agents: {
955
+ maxConcurrent: 10,
956
+ defaultTimeout: 3600,
957
+ templates: {
958
+ claude: {
959
+ driver: "claude-code",
960
+ model: "claude-sonnet-4-5",
961
+ maxTokens: 16e3
962
+ },
963
+ codex: {
964
+ driver: "codex",
965
+ model: "codex-1"
966
+ },
967
+ custom: {
968
+ driver: "subprocess",
969
+ command: []
970
+ }
971
+ }
972
+ },
973
+ logging: {
974
+ level: "info",
975
+ format: "pretty",
976
+ file: "logs/dirigent.log"
977
+ },
978
+ database: {
979
+ path: "data/dirigent.db"
980
+ }
981
+ };
982
+ async function initCommand(options) {
983
+ console.log(chalk.cyan("\n\u{1F3AD} Initializing Dirigent workspace\n"));
984
+ const dataDir = options.dataDir || ".dirigent";
985
+ const configPath = join(process.cwd(), dataDir, "config.json");
986
+ if (existsSync(configPath)) {
987
+ const overwrite = options.yes ? true : await confirm("Dirigent is already initialized. Overwrite config?", false);
988
+ if (!overwrite) {
989
+ console.log(chalk.yellow("Aborted."));
990
+ return;
991
+ }
992
+ }
993
+ let config = { ...DEFAULT_CONFIG };
994
+ if (!options.yes) {
995
+ const portStr = await prompt("Server port", options.port || "3000");
996
+ const authEnabled = await confirm("Enable authentication?", true);
997
+ const maxAgentsStr = await prompt("Max concurrent agents", "10");
998
+ config.server.port = parseInt(portStr) || 3e3;
999
+ config.auth.enabled = authEnabled;
1000
+ config.agents.maxConcurrent = parseInt(maxAgentsStr) || 10;
1001
+ } else {
1002
+ config.server.port = parseInt(options.port || "3000");
1003
+ }
1004
+ config.auth.adminToken = nanoid(32);
1005
+ const spinner = ora("Creating workspace...").start();
1006
+ try {
1007
+ const dirs = [
1008
+ dataDir,
1009
+ join(dataDir, "data"),
1010
+ join(dataDir, "logs"),
1011
+ join(dataDir, "agents"),
1012
+ join(dataDir, "workspaces")
1013
+ ];
1014
+ for (const dir of dirs) {
1015
+ mkdirSync(dir, { recursive: true });
1016
+ }
1017
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1018
+ writeFileSync(
1019
+ join(dataDir, ".gitignore"),
1020
+ `# Dirigent data
1021
+ data/
1022
+ logs/
1023
+ agents/
1024
+ workspaces/
1025
+ *.db
1026
+ *.db-journal
1027
+ `
1028
+ );
1029
+ spinner.succeed("Workspace created");
1030
+ console.log(chalk.green("\n\u2705 Dirigent initialized successfully!\n"));
1031
+ console.log(chalk.dim("Configuration:"));
1032
+ console.log(` ${chalk.cyan("Config:")} ${configPath}`);
1033
+ console.log(` ${chalk.cyan("Port:")} ${config.server.port}`);
1034
+ console.log(` ${chalk.cyan("Auth:")} ${config.auth.enabled ? "Enabled" : "Disabled"}`);
1035
+ if (config.auth.enabled) {
1036
+ console.log(chalk.yellow("\n\u26A0\uFE0F Save your admin token (shown only once):"));
1037
+ console.log(chalk.bold.white(` ${config.auth.adminToken}
1038
+ `));
1039
+ }
1040
+ console.log(chalk.dim("Next steps:"));
1041
+ console.log(` ${chalk.cyan("1.")} Start the server: ${chalk.bold("dirigent up")}`);
1042
+ console.log(` ${chalk.cyan("2.")} Open dashboard: ${chalk.bold(`http://localhost:${config.server.port}`)}`);
1043
+ console.log(` ${chalk.cyan("3.")} Spawn an agent: ${chalk.bold("dirigent agent spawn claude")}
1044
+ `);
1045
+ } catch (err) {
1046
+ spinner.fail("Failed to create workspace");
1047
+ console.error(chalk.red(err));
1048
+ process.exit(1);
1049
+ }
1050
+ }
1051
+
1052
+ // src/cli/commands/up.ts
1053
+ import chalk2 from "chalk";
1054
+ import ora2 from "ora";
1055
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1056
+ import { join as join5 } from "path";
1057
+ import { spawn as spawn2 } from "child_process";
1058
+ import { fileURLToPath as fileURLToPath2 } from "url";
1059
+ import { dirname as dirname2 } from "path";
1060
+ async function upCommand(options) {
1061
+ const dataDir = ".dirigent";
1062
+ const configPath = join5(process.cwd(), dataDir, "config.json");
1063
+ const pidPath = join5(process.cwd(), dataDir, "dirigent.pid");
1064
+ if (!existsSync5(configPath)) {
1065
+ console.log(chalk2.red("\n\u274C Dirigent not initialized. Run `dirigent init` first.\n"));
1066
+ process.exit(1);
1067
+ }
1068
+ if (existsSync5(pidPath)) {
1069
+ const pid = parseInt(readFileSync3(pidPath, "utf-8").trim());
1070
+ try {
1071
+ process.kill(pid, 0);
1072
+ console.log(chalk2.yellow(`
1073
+ \u26A0\uFE0F Dirigent is already running (PID: ${pid})`));
1074
+ console.log(chalk2.dim(" Use `dirigent down` to stop it first.\n"));
1075
+ return;
1076
+ } catch {
1077
+ }
1078
+ }
1079
+ const config = JSON.parse(readFileSync3(configPath, "utf-8"));
1080
+ const port = options.port || config.server.port;
1081
+ console.log(chalk2.cyan("\n\u{1F3AD} Starting Dirigent server...\n"));
1082
+ const spinner = ora2("Starting server...").start();
1083
+ try {
1084
+ const __filename2 = fileURLToPath2(import.meta.url);
1085
+ const __dirname2 = dirname2(__filename2);
1086
+ const serverPath = join5(__dirname2, "..", "..", "server", "index.js");
1087
+ const env = {
1088
+ ...process.env,
1089
+ DIRIGENT_PORT: port.toString(),
1090
+ DIRIGENT_CONFIG: configPath,
1091
+ DIRIGENT_DATA_DIR: join5(process.cwd(), dataDir)
1092
+ };
1093
+ if (options.detach) {
1094
+ const child = spawn2(process.execPath, [serverPath], {
1095
+ detached: true,
1096
+ stdio: "ignore",
1097
+ env
1098
+ });
1099
+ child.unref();
1100
+ writeFileSync3(pidPath, child.pid.toString());
1101
+ spinner.succeed(`Server started (PID: ${child.pid})`);
1102
+ console.log(chalk2.green(`
1103
+ \u2705 Dirigent is running
1104
+ `));
1105
+ console.log(` ${chalk2.cyan("Dashboard:")} http://localhost:${port}`);
1106
+ console.log(` ${chalk2.cyan("API:")} http://localhost:${port}/api`);
1107
+ console.log(` ${chalk2.cyan("WebSocket:")} ws://localhost:${port}/ws
1108
+ `);
1109
+ console.log(chalk2.dim(` Stop with: dirigent down
1110
+ `));
1111
+ } else {
1112
+ spinner.succeed("Server starting...");
1113
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1114
+ await startServer2({
1115
+ port: parseInt(port),
1116
+ configPath,
1117
+ dataDir: join5(process.cwd(), dataDir)
1118
+ });
1119
+ }
1120
+ } catch (err) {
1121
+ spinner.fail("Failed to start server");
1122
+ console.error(chalk2.red(err));
1123
+ process.exit(1);
1124
+ }
1125
+ }
1126
+
1127
+ // src/cli/commands/down.ts
1128
+ import chalk3 from "chalk";
1129
+ import ora3 from "ora";
1130
+ import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync } from "fs";
1131
+ import { join as join6 } from "path";
1132
+ import treeKill2 from "tree-kill";
1133
+ async function downCommand(options) {
1134
+ const dataDir = ".dirigent";
1135
+ const pidPath = join6(process.cwd(), dataDir, "dirigent.pid");
1136
+ if (!existsSync6(pidPath)) {
1137
+ console.log(chalk3.yellow("\n\u26A0\uFE0F Dirigent is not running.\n"));
1138
+ return;
1139
+ }
1140
+ const pid = parseInt(readFileSync4(pidPath, "utf-8").trim());
1141
+ const spinner = ora3("Stopping Dirigent...").start();
1142
+ try {
1143
+ try {
1144
+ process.kill(pid, 0);
1145
+ } catch {
1146
+ spinner.warn("Dirigent was not running (stale PID file)");
1147
+ unlinkSync(pidPath);
1148
+ return;
1149
+ }
1150
+ await new Promise((resolve, reject) => {
1151
+ const signal = options.force ? "SIGKILL" : "SIGTERM";
1152
+ treeKill2(pid, signal, (err) => {
1153
+ if (err) reject(err);
1154
+ else resolve();
1155
+ });
1156
+ });
1157
+ unlinkSync(pidPath);
1158
+ spinner.succeed("Dirigent stopped");
1159
+ console.log(chalk3.green("\n\u2705 Dirigent has been stopped.\n"));
1160
+ } catch (err) {
1161
+ spinner.fail("Failed to stop Dirigent");
1162
+ console.error(chalk3.red(err));
1163
+ if (options.force) {
1164
+ try {
1165
+ unlinkSync(pidPath);
1166
+ } catch {
1167
+ }
1168
+ }
1169
+ process.exit(1);
1170
+ }
1171
+ }
1172
+
1173
+ // src/cli/commands/status.ts
1174
+ import chalk4 from "chalk";
1175
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
1176
+ import { join as join7 } from "path";
1177
+ import boxen from "boxen";
1178
+ async function statusCommand(options) {
1179
+ const dataDir = ".dirigent";
1180
+ const configPath = join7(process.cwd(), dataDir, "config.json");
1181
+ const pidPath = join7(process.cwd(), dataDir, "dirigent.pid");
1182
+ if (!existsSync7(configPath)) {
1183
+ if (options.json) {
1184
+ console.log(JSON.stringify({ initialized: false, running: false }));
1185
+ } else {
1186
+ console.log(chalk4.yellow("\n\u26A0\uFE0F Dirigent not initialized. Run `dirigent init` first.\n"));
1187
+ }
1188
+ return;
1189
+ }
1190
+ const config = JSON.parse(readFileSync5(configPath, "utf-8"));
1191
+ let running = false;
1192
+ let pid = null;
1193
+ let serverStatus = null;
1194
+ if (existsSync7(pidPath)) {
1195
+ pid = parseInt(readFileSync5(pidPath, "utf-8").trim());
1196
+ try {
1197
+ process.kill(pid, 0);
1198
+ running = true;
1199
+ try {
1200
+ const res = await fetch(`http://localhost:${config.server.port}/api/status`);
1201
+ if (res.ok) {
1202
+ serverStatus = await res.json();
1203
+ }
1204
+ } catch {
1205
+ }
1206
+ } catch {
1207
+ running = false;
1208
+ }
1209
+ }
1210
+ if (options.json) {
1211
+ console.log(
1212
+ JSON.stringify({
1213
+ initialized: true,
1214
+ running,
1215
+ pid,
1216
+ port: config.server.port,
1217
+ agents: serverStatus?.agents || [],
1218
+ uptime: serverStatus?.uptime || null
1219
+ })
1220
+ );
1221
+ return;
1222
+ }
1223
+ const statusIcon = running ? chalk4.green("\u25CF") : chalk4.red("\u25CF");
1224
+ const statusText = running ? chalk4.green("Running") : chalk4.red("Stopped");
1225
+ console.log(
1226
+ boxen(
1227
+ `${chalk4.bold.white("\u{1F3AD} DIRIGENT STATUS")}
1228
+
1229
+ ${chalk4.dim("Status:")} ${statusIcon} ${statusText}${pid ? chalk4.dim(` (PID: ${pid})`) : ""}
1230
+ ${chalk4.dim("Port:")} ${config.server.port}
1231
+ ${chalk4.dim("Auth:")} ${config.auth.enabled ? "Enabled" : "Disabled"}
1232
+ ` + (serverStatus ? `${chalk4.dim("Uptime:")} ${formatUptime(serverStatus.uptime)}
1233
+ ${chalk4.dim("Agents:")} ${serverStatus.agents.running}/${serverStatus.agents.total} running` : ""),
1234
+ {
1235
+ padding: 1,
1236
+ margin: 1,
1237
+ borderStyle: "round",
1238
+ borderColor: running ? "green" : "red"
1239
+ }
1240
+ )
1241
+ );
1242
+ if (running && serverStatus?.agents?.list?.length > 0) {
1243
+ console.log(chalk4.dim("\nActive Agents:\n"));
1244
+ for (const agent2 of serverStatus.agents.list) {
1245
+ const agentIcon = agent2.status === "running" ? chalk4.green("\u25CF") : chalk4.yellow("\u25CF");
1246
+ console.log(` ${agentIcon} ${chalk4.bold(agent2.name)} ${chalk4.dim(`(${agent2.id})`)}`);
1247
+ console.log(` ${chalk4.dim("Template:")} ${agent2.template}`);
1248
+ console.log(` ${chalk4.dim("Workspace:")} ${agent2.workspace}`);
1249
+ console.log("");
1250
+ }
1251
+ }
1252
+ if (!running) {
1253
+ console.log(chalk4.dim(`
1254
+ Start with: ${chalk4.cyan("dirigent up")}
1255
+ `));
1256
+ }
1257
+ }
1258
+ function formatUptime(seconds) {
1259
+ if (seconds < 60) return `${seconds}s`;
1260
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
1261
+ const h = Math.floor(seconds / 3600);
1262
+ const m = Math.floor(seconds % 3600 / 60);
1263
+ return `${h}h ${m}m`;
1264
+ }
1265
+
1266
+ // src/cli/commands/agent.ts
1267
+ import chalk5 from "chalk";
1268
+ import ora4 from "ora";
1269
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
1270
+ import { join as join8 } from "path";
1271
+ async function getConfig() {
1272
+ const dataDir = ".dirigent";
1273
+ const configPath = join8(process.cwd(), dataDir, "config.json");
1274
+ if (!existsSync8(configPath)) {
1275
+ console.log(chalk5.red("\n\u274C Dirigent not initialized. Run `dirigent init` first.\n"));
1276
+ process.exit(1);
1277
+ }
1278
+ return JSON.parse(readFileSync6(configPath, "utf-8"));
1279
+ }
1280
+ async function apiCall(method, path, body) {
1281
+ const config = await getConfig();
1282
+ const url = `http://localhost:${config.server.port}/api${path}`;
1283
+ const res = await fetch(url, {
1284
+ method,
1285
+ headers: {
1286
+ "Content-Type": "application/json",
1287
+ Authorization: `Bearer ${config.auth.adminToken}`
1288
+ },
1289
+ body: body ? JSON.stringify(body) : void 0
1290
+ });
1291
+ if (!res.ok) {
1292
+ const error = await res.json().catch(() => ({ error: res.statusText }));
1293
+ throw new Error(error.error || error.message || "API call failed");
1294
+ }
1295
+ return res.json();
1296
+ }
1297
+ async function agentCommand(action, options) {
1298
+ switch (action) {
1299
+ case "list":
1300
+ await listAgents(options);
1301
+ break;
1302
+ case "spawn":
1303
+ await spawnAgent(options);
1304
+ break;
1305
+ case "stop":
1306
+ await stopAgent(options);
1307
+ break;
1308
+ case "send":
1309
+ await sendToAgent(options);
1310
+ break;
1311
+ case "logs":
1312
+ await agentLogs(options);
1313
+ break;
1314
+ default:
1315
+ console.log(chalk5.red(`Unknown action: ${action}`));
1316
+ }
1317
+ }
1318
+ async function listAgents(options) {
1319
+ try {
1320
+ const result = await apiCall("GET", `/agents${options.all ? "?all=true" : ""}`);
1321
+ if (options.json) {
1322
+ console.log(JSON.stringify(result, null, 2));
1323
+ return;
1324
+ }
1325
+ if (result.agents.length === 0) {
1326
+ console.log(chalk5.yellow("\nNo agents found.\n"));
1327
+ console.log(chalk5.dim(" Spawn one with: dirigent agent spawn claude\n"));
1328
+ return;
1329
+ }
1330
+ console.log(chalk5.cyan("\n\u{1F916} Agents\n"));
1331
+ for (const agent2 of result.agents) {
1332
+ const icon = agent2.status === "running" ? chalk5.green("\u25CF") : agent2.status === "idle" ? chalk5.yellow("\u25CF") : chalk5.red("\u25CF");
1333
+ console.log(`${icon} ${chalk5.bold(agent2.name)} ${chalk5.dim(`[${agent2.id}]`)}`);
1334
+ console.log(` ${chalk5.dim("Template:")} ${agent2.template}`);
1335
+ console.log(` ${chalk5.dim("Status:")} ${agent2.status}`);
1336
+ console.log(` ${chalk5.dim("Workspace:")} ${agent2.workspace || "N/A"}`);
1337
+ if (agent2.currentTask) {
1338
+ console.log(` ${chalk5.dim("Task:")} ${agent2.currentTask.substring(0, 60)}...`);
1339
+ }
1340
+ console.log("");
1341
+ }
1342
+ } catch (err) {
1343
+ console.log(chalk5.red(`
1344
+ \u274C ${err.message}
1345
+ `));
1346
+ console.log(chalk5.dim(" Make sure Dirigent is running: dirigent up\n"));
1347
+ }
1348
+ }
1349
+ async function spawnAgent(options) {
1350
+ const spinner = ora4("Spawning agent...").start();
1351
+ try {
1352
+ const result = await apiCall("POST", "/agents", {
1353
+ template: options.template,
1354
+ name: options.name,
1355
+ workspace: options.workspace,
1356
+ task: options.task,
1357
+ model: options.model
1358
+ });
1359
+ spinner.succeed(`Agent spawned: ${chalk5.bold(result.agent.name)}`);
1360
+ console.log(`
1361
+ ${chalk5.dim("ID:")} ${result.agent.id}`);
1362
+ console.log(` ${chalk5.dim("Template:")} ${result.agent.template}`);
1363
+ console.log(` ${chalk5.dim("Workspace:")} ${result.agent.workspace}`);
1364
+ if (options.task) {
1365
+ console.log(` ${chalk5.dim("Task:")} ${options.task.substring(0, 60)}...`);
1366
+ }
1367
+ console.log(chalk5.dim(`
1368
+ View logs: dirigent agent logs ${result.agent.id}
1369
+ `));
1370
+ } catch (err) {
1371
+ spinner.fail("Failed to spawn agent");
1372
+ console.log(chalk5.red(`
1373
+ \u274C ${err.message}
1374
+ `));
1375
+ }
1376
+ }
1377
+ async function stopAgent(options) {
1378
+ const spinner = ora4("Stopping agent...").start();
1379
+ try {
1380
+ await apiCall("DELETE", `/agents/${options.id}${options.force ? "?force=true" : ""}`);
1381
+ spinner.succeed(`Agent stopped: ${options.id}`);
1382
+ } catch (err) {
1383
+ spinner.fail("Failed to stop agent");
1384
+ console.log(chalk5.red(`
1385
+ \u274C ${err.message}
1386
+ `));
1387
+ }
1388
+ }
1389
+ async function sendToAgent(options) {
1390
+ try {
1391
+ const result = await apiCall("POST", `/agents/${options.id}/send`, {
1392
+ message: options.message
1393
+ });
1394
+ console.log(chalk5.green(`
1395
+ \u2709\uFE0F Message sent to agent ${options.id}
1396
+ `));
1397
+ if (result.response) {
1398
+ console.log(chalk5.dim("Response:"));
1399
+ console.log(result.response);
1400
+ }
1401
+ } catch (err) {
1402
+ console.log(chalk5.red(`
1403
+ \u274C ${err.message}
1404
+ `));
1405
+ }
1406
+ }
1407
+ async function agentLogs(options) {
1408
+ const config = await getConfig();
1409
+ if (options.follow) {
1410
+ const { io } = await import("socket.io-client");
1411
+ const socket = io(`http://localhost:${config.server.port}`, {
1412
+ auth: { token: config.auth.adminToken }
1413
+ });
1414
+ socket.on("connect", () => {
1415
+ socket.emit("subscribe:logs", { agentId: options.id });
1416
+ console.log(chalk5.dim(`
1417
+ Streaming logs for agent ${options.id}...
1418
+ `));
1419
+ });
1420
+ socket.on("agent:log", (data) => {
1421
+ const timestamp = chalk5.dim(new Date(data.timestamp).toISOString().split("T")[1].slice(0, 8));
1422
+ const level = data.level === "error" ? chalk5.red("ERR") : data.level === "warn" ? chalk5.yellow("WRN") : chalk5.blue("INF");
1423
+ console.log(`${timestamp} ${level} ${data.message}`);
1424
+ });
1425
+ socket.on("disconnect", () => {
1426
+ console.log(chalk5.dim("\nDisconnected.\n"));
1427
+ process.exit(0);
1428
+ });
1429
+ process.on("SIGINT", () => {
1430
+ socket.disconnect();
1431
+ });
1432
+ } else {
1433
+ try {
1434
+ const result = await apiCall(
1435
+ "GET",
1436
+ `/agents/${options.id}/logs?lines=${options.lines || 50}`
1437
+ );
1438
+ console.log(chalk5.dim(`
1439
+ Logs for agent ${options.id}:
1440
+ `));
1441
+ for (const log of result.logs) {
1442
+ const timestamp = chalk5.dim(new Date(log.timestamp).toISOString().split("T")[1].slice(0, 8));
1443
+ const level = log.level === "error" ? chalk5.red("ERR") : log.level === "warn" ? chalk5.yellow("WRN") : chalk5.blue("INF");
1444
+ console.log(`${timestamp} ${level} ${log.message}`);
1445
+ }
1446
+ console.log(chalk5.dim(`
1447
+ Stream with: dirigent agent logs ${options.id} -f
1448
+ `));
1449
+ } catch (err) {
1450
+ console.log(chalk5.red(`
1451
+ \u274C ${err.message}
1452
+ `));
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ // src/cli/commands/logs.ts
1458
+ import chalk6 from "chalk";
1459
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1460
+ import { join as join9 } from "path";
1461
+ async function logsCommand(options) {
1462
+ const dataDir = ".dirigent";
1463
+ const logPath = join9(process.cwd(), dataDir, "logs", "dirigent.log");
1464
+ if (!existsSync9(logPath)) {
1465
+ console.log(chalk6.yellow("\n\u26A0\uFE0F No logs found.\n"));
1466
+ return;
1467
+ }
1468
+ const lines = parseInt(options.lines || "100");
1469
+ if (options.follow) {
1470
+ const { spawn: spawn4 } = await import("child_process");
1471
+ const tail = spawn4("tail", ["-f", "-n", lines.toString(), logPath], {
1472
+ stdio: ["ignore", "pipe", "pipe"]
1473
+ });
1474
+ tail.stdout.on("data", (data) => {
1475
+ process.stdout.write(formatLog(data.toString()));
1476
+ });
1477
+ tail.stderr.on("data", (data) => {
1478
+ process.stderr.write(data);
1479
+ });
1480
+ process.on("SIGINT", () => {
1481
+ tail.kill();
1482
+ process.exit(0);
1483
+ });
1484
+ } else {
1485
+ const content = readFileSync7(logPath, "utf-8");
1486
+ const allLines = content.trim().split("\n");
1487
+ const lastLines = allLines.slice(-lines);
1488
+ for (const line of lastLines) {
1489
+ console.log(formatLog(line));
1490
+ }
1491
+ console.log(chalk6.dim(`
1492
+ Stream with: dirigent logs -f
1493
+ `));
1494
+ }
1495
+ }
1496
+ function formatLog(line) {
1497
+ try {
1498
+ const parsed = JSON.parse(line);
1499
+ const time = chalk6.dim(
1500
+ new Date(parsed.time).toISOString().split("T")[1].slice(0, 8)
1501
+ );
1502
+ const level = parsed.level <= 20 ? chalk6.dim("DBG") : parsed.level <= 30 ? chalk6.blue("INF") : parsed.level <= 40 ? chalk6.yellow("WRN") : chalk6.red("ERR");
1503
+ const msg = parsed.msg || "";
1504
+ return `${time} ${level} ${msg}`;
1505
+ } catch {
1506
+ return line;
1507
+ }
1508
+ }
1509
+
1510
+ // src/cli/commands/config.ts
1511
+ import chalk7 from "chalk";
1512
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1513
+ import { join as join10 } from "path";
1514
+ import { spawn as spawn3 } from "child_process";
1515
+ async function configCommand(options) {
1516
+ const dataDir = ".dirigent";
1517
+ const configPath = join10(process.cwd(), dataDir, "config.json");
1518
+ if (!existsSync10(configPath)) {
1519
+ console.log(chalk7.red("\n\u274C Dirigent not initialized. Run `dirigent init` first.\n"));
1520
+ process.exit(1);
1521
+ }
1522
+ const config = JSON.parse(readFileSync8(configPath, "utf-8"));
1523
+ if (options.get) {
1524
+ const value = getNestedValue(config, options.get);
1525
+ if (value === void 0) {
1526
+ console.log(chalk7.yellow(`
1527
+ \u26A0\uFE0F Key not found: ${options.get}
1528
+ `));
1529
+ } else {
1530
+ console.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
1531
+ }
1532
+ return;
1533
+ }
1534
+ if (options.set) {
1535
+ const [key, ...valueParts] = options.set.split("=");
1536
+ const value = valueParts.join("=");
1537
+ if (!key || value === void 0) {
1538
+ console.log(chalk7.red("\n\u274C Invalid format. Use: --set key=value\n"));
1539
+ process.exit(1);
1540
+ }
1541
+ let parsedValue = value;
1542
+ if (value === "true") parsedValue = true;
1543
+ else if (value === "false") parsedValue = false;
1544
+ else if (!isNaN(Number(value))) parsedValue = Number(value);
1545
+ else {
1546
+ try {
1547
+ parsedValue = JSON.parse(value);
1548
+ } catch {
1549
+ }
1550
+ }
1551
+ setNestedValue(config, key, parsedValue);
1552
+ writeFileSync4(configPath, JSON.stringify(config, null, 2));
1553
+ console.log(chalk7.green(`
1554
+ \u2705 Set ${key} = ${JSON.stringify(parsedValue)}
1555
+ `));
1556
+ console.log(chalk7.dim(" Restart Dirigent for changes to take effect: dirigent down && dirigent up\n"));
1557
+ return;
1558
+ }
1559
+ if (options.edit) {
1560
+ const editor = process.env.EDITOR || process.env.VISUAL || "vim";
1561
+ const child = spawn3(editor, [configPath], {
1562
+ stdio: "inherit"
1563
+ });
1564
+ child.on("exit", () => {
1565
+ console.log(chalk7.dim("\n Restart Dirigent for changes to take effect: dirigent down && dirigent up\n"));
1566
+ });
1567
+ return;
1568
+ }
1569
+ console.log(chalk7.cyan("\n\u{1F527} Configuration\n"));
1570
+ console.log(JSON.stringify(config, null, 2));
1571
+ console.log(chalk7.dim(`
1572
+ File: ${configPath}
1573
+ `));
1574
+ }
1575
+ function getNestedValue(obj, path) {
1576
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
1577
+ }
1578
+ function setNestedValue(obj, path, value) {
1579
+ const keys = path.split(".");
1580
+ const lastKey = keys.pop();
1581
+ const target = keys.reduce((acc, key) => {
1582
+ if (!(key in acc)) acc[key] = {};
1583
+ return acc[key];
1584
+ }, obj);
1585
+ target[lastKey] = value;
1586
+ }
1587
+
1588
+ // src/cli/index.ts
1589
+ var version = "0.1.0";
1590
+ var program = new Command();
1591
+ var banner = `
1592
+ ${chalk8.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
1593
+ ${chalk8.bold.cyan("\u2551")} ${chalk8.bold.white("\u{1F3AD} DIRIGENT")} ${chalk8.bold.cyan("\u2551")}
1594
+ ${chalk8.bold.cyan("\u2551")} ${chalk8.dim("AI Agent Orchestration Platform")} ${chalk8.bold.cyan("\u2551")}
1595
+ ${chalk8.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
1596
+ `;
1597
+ program.name("dirigent").description("AI Agent Orchestration Platform - Enterprise-grade orchestration for AI coding agents").version(version).addHelpText("beforeAll", banner);
1598
+ program.command("init").description("Initialize a new Dirigent workspace").option("-y, --yes", "Accept all defaults").option("--port <port>", "Server port", "3000").option("--data-dir <dir>", "Data directory", ".dirigent").action(initCommand);
1599
+ program.command("up").description("Start the Dirigent server").option("-d, --detach", "Run in background").option("--port <port>", "Override server port").action(upCommand);
1600
+ program.command("down").description("Stop the Dirigent server").option("-f, --force", "Force stop all agents").action(downCommand);
1601
+ program.command("status").description("Show server and agent status").option("-j, --json", "Output as JSON").action(statusCommand);
1602
+ var agent = program.command("agent").description("Manage agents");
1603
+ agent.command("list").description("List all agents").option("-a, --all", "Include stopped agents").option("-j, --json", "Output as JSON").action((opts) => agentCommand("list", opts));
1604
+ agent.command("spawn <template>").description("Spawn a new agent from template").option("-n, --name <name>", "Agent name").option("-w, --workspace <path>", "Workspace directory").option("-t, --task <task>", "Initial task").option("--model <model>", "Model override").action((template, opts) => agentCommand("spawn", { template, ...opts }));
1605
+ agent.command("stop <id>").description("Stop an agent").option("-f, --force", "Force stop").action((id, opts) => agentCommand("stop", { id, ...opts }));
1606
+ agent.command("send <id> <message>").description("Send a message to an agent").action((id, message) => agentCommand("send", { id, message }));
1607
+ agent.command("logs <id>").description("Stream agent logs").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Number of lines", "50").action((id, opts) => agentCommand("logs", { id, ...opts }));
1608
+ program.command("logs").description("View server logs").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Number of lines", "100").action(logsCommand);
1609
+ program.command("config").description("View or edit configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value").option("--list", "List all config").option("--edit", "Open config in editor").action(configCommand);
1610
+ program.command("dashboard").alias("ui").description("Open the web dashboard").option("-p, --port <port>", "Dashboard port").action(async () => {
1611
+ const config = await Promise.resolve().then(() => (init_config(), config_exports)).then((m) => m.loadConfig());
1612
+ console.log(chalk8.cyan(`
1613
+ \u{1F310} Dashboard: ${chalk8.bold(`http://localhost:${config.server.port}`)}
1614
+ `));
1615
+ });
1616
+ program.parse();
1617
+ if (!process.argv.slice(2).length) {
1618
+ console.log(banner);
1619
+ program.outputHelp();
1620
+ }
1621
+ //# sourceMappingURL=index.js.map