expxagents 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/dashboard/assets/{BufferResource-BcVsF5HP.js → BufferResource-DTKCcSRK.js} +1 -1
  2. package/dist/dashboard/assets/{CanvasRenderer-kA1Maw0x.js → CanvasRenderer-RC-R9HHB.js} +1 -1
  3. package/dist/dashboard/assets/{JarvisView-DBrCWArD.js → JarvisView-BxEaO5CE.js} +1 -1
  4. package/dist/dashboard/assets/{RenderTargetSystem-Bp9B4iP8.js → RenderTargetSystem-BNs6mZy_.js} +1 -1
  5. package/dist/dashboard/assets/{WebGLRenderer-CNJyeb_W.js → WebGLRenderer-DZqhYwUo.js} +1 -1
  6. package/dist/dashboard/assets/{WebGPURenderer-DCbozzdc.js → WebGPURenderer-DKvNGGnd.js} +1 -1
  7. package/dist/dashboard/assets/{browserAll-Bu4cXbCn.js → browserAll-DpQDqd5Z.js} +1 -1
  8. package/dist/dashboard/assets/{index-zfHiMrG2.js → index-C_HJOTiO.js} +58 -58
  9. package/dist/dashboard/assets/{webworkerAll-BFb9bpT-.js → webworkerAll-DKgN-y6e.js} +1 -1
  10. package/dist/dashboard/index.html +1 -1
  11. package/dist/server/api/__tests__/orgchart-routes.test.d.ts +2 -0
  12. package/dist/server/api/__tests__/orgchart-routes.test.d.ts.map +1 -0
  13. package/dist/server/api/__tests__/orgchart-routes.test.js +171 -0
  14. package/dist/server/api/__tests__/orgchart-routes.test.js.map +1 -0
  15. package/dist/server/api/__tests__/settings-routes.test.d.ts +2 -0
  16. package/dist/server/api/__tests__/settings-routes.test.d.ts.map +1 -0
  17. package/dist/server/api/__tests__/settings-routes.test.js +177 -0
  18. package/dist/server/api/__tests__/settings-routes.test.js.map +1 -0
  19. package/dist/server/api/orgchart-routes.d.ts +7 -0
  20. package/dist/server/api/orgchart-routes.d.ts.map +1 -0
  21. package/dist/server/api/orgchart-routes.js +102 -0
  22. package/dist/server/api/orgchart-routes.js.map +1 -0
  23. package/dist/server/api/settings-routes.d.ts +11 -0
  24. package/dist/server/api/settings-routes.d.ts.map +1 -0
  25. package/dist/server/api/settings-routes.js +105 -0
  26. package/dist/server/api/settings-routes.js.map +1 -0
  27. package/dist/server/app.d.ts.map +1 -1
  28. package/dist/server/app.js +4 -0
  29. package/dist/server/app.js.map +1 -1
  30. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import{a0 as G,a2 as I,a3 as B,k as _,M as k,V as O,L as A,a8 as m,T as v,ar as C,R as E,w as z,a7 as U,t as w}from"./index-zfHiMrG2.js";var M=`in vec2 aPosition;
1
+ import{a0 as G,a2 as I,a3 as B,k as _,M as k,V as O,L as A,a8 as m,T as v,ar as C,R as E,w as z,a7 as U,t as w}from"./index-C_HJOTiO.js";var M=`in vec2 aPosition;
2
2
  out vec2 vTextureCoord;
3
3
 
4
4
  uniform vec4 uInputSize;
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ExpxAgents — Mission Control</title>
7
- <script type="module" crossorigin src="/assets/index-zfHiMrG2.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-C_HJOTiO.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-B-8_BLE5.css">
9
9
  </head>
10
10
  <body>
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orgchart-routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgchart-routes.test.d.ts","sourceRoot":"","sources":["../../../src/api/__tests__/orgchart-routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,171 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import Fastify from 'fastify';
3
+ import cookie from '@fastify/cookie';
4
+ import Database from 'better-sqlite3';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { orgchartRoutes } from '../orgchart-routes.js';
9
+ import { registerAuthMiddleware } from '../../auth/auth-middleware.js';
10
+ import { authRoutes } from '../../auth/auth-routes.js';
11
+ import { hashPassword } from '../../auth/password.js';
12
+ import { runMigrations } from '../../db/migrations.js';
13
+ const SECRET = 'test-secret-that-is-at-least-32-chars-long!!';
14
+ async function buildApp(db, squadsDir) {
15
+ const app = Fastify();
16
+ await app.register(cookie);
17
+ registerAuthMiddleware(app, SECRET);
18
+ await app.register(authRoutes, { db, jwtSecret: SECRET });
19
+ await app.register(orgchartRoutes, { squadsDir });
20
+ await app.ready();
21
+ return app;
22
+ }
23
+ async function getAuthCookie(app) {
24
+ const res = await app.inject({
25
+ method: 'POST',
26
+ url: '/api/auth/login',
27
+ payload: { username: 'admin', password: 'admin123' },
28
+ });
29
+ const cookies = res.cookies;
30
+ const accessToken = cookies.find(c => c.name === 'access_token');
31
+ return `access_token=${accessToken.value}`;
32
+ }
33
+ describe('orgchart routes', () => {
34
+ let db;
35
+ let app;
36
+ let authCookie;
37
+ let tmpDir;
38
+ let squadsDir;
39
+ beforeEach(async () => {
40
+ db = new Database(':memory:');
41
+ runMigrations(db);
42
+ const hash = await hashPassword('admin123');
43
+ db.prepare("INSERT INTO users (id, username, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?)").run('admin-id', 'admin', hash, 'admin', new Date().toISOString());
44
+ // Create temp directory structure
45
+ tmpDir = path.join(os.tmpdir(), `orgchart-test-${Date.now()}`);
46
+ squadsDir = path.join(tmpDir, 'squads');
47
+ fs.mkdirSync(squadsDir, { recursive: true });
48
+ // Create agents catalog
49
+ const agentsDir = path.join(tmpDir, 'agents');
50
+ fs.mkdirSync(agentsDir, { recursive: true });
51
+ fs.writeFileSync(path.join(agentsDir, '_catalog.yaml'), `catalog:
52
+ sectors:
53
+ - name: Development
54
+ agents:
55
+ - tech-lead
56
+ - backend-dev
57
+ - frontend-dev
58
+ - name: Marketing
59
+ agents:
60
+ - content-writer
61
+ - seo-analyst
62
+ `);
63
+ // Create a squad
64
+ const squadDir = path.join(squadsDir, 'alpha-team');
65
+ fs.mkdirSync(squadDir, { recursive: true });
66
+ fs.writeFileSync(path.join(squadDir, 'squad.yaml'), `squad:
67
+ code: alpha-team
68
+ name: Alpha Team
69
+ description: Core development squad
70
+ icon: rocket
71
+ agents:
72
+ - id: tech-lead
73
+ name: Tech Lead
74
+ icon: star
75
+ - id: backend-dev
76
+ name: Backend Dev
77
+ icon: code
78
+ `);
79
+ // Create company memory
80
+ const memDir = path.join(tmpDir, '_expxagents', '_memory');
81
+ fs.mkdirSync(memDir, { recursive: true });
82
+ fs.writeFileSync(path.join(memDir, 'company.md'), '# TestCorp\nA test company');
83
+ app = await buildApp(db, squadsDir);
84
+ authCookie = await getAuthCookie(app);
85
+ });
86
+ afterEach(async () => {
87
+ await app?.close();
88
+ db?.close();
89
+ fs.rmSync(tmpDir, { recursive: true, force: true });
90
+ });
91
+ it('GET /api/orgchart — returns tree and squads', async () => {
92
+ const res = await app.inject({
93
+ method: 'GET',
94
+ url: '/api/orgchart',
95
+ headers: { cookie: authCookie },
96
+ });
97
+ expect(res.statusCode).toBe(200);
98
+ const body = JSON.parse(res.payload);
99
+ // Tree structure
100
+ expect(body.tree).toBeDefined();
101
+ expect(body.tree.name).toBe('TestCorp');
102
+ expect(body.tree.type).toBe('company');
103
+ expect(body.tree.children).toHaveLength(2);
104
+ // Sectors
105
+ const dev = body.tree.children.find((s) => s.name === 'Development');
106
+ expect(dev).toBeDefined();
107
+ expect(dev.agentCount).toBe(3);
108
+ expect(dev.children).toHaveLength(3);
109
+ // Agent names are title-cased from IDs
110
+ const techLead = dev.children.find((a) => a.id === 'tech-lead');
111
+ expect(techLead).toBeDefined();
112
+ expect(techLead.name).toBe('Tech Lead');
113
+ expect(techLead.type).toBe('agent');
114
+ });
115
+ it('returns agent → squad mapping', async () => {
116
+ const res = await app.inject({
117
+ method: 'GET',
118
+ url: '/api/orgchart',
119
+ headers: { cookie: authCookie },
120
+ });
121
+ const body = JSON.parse(res.payload);
122
+ const dev = body.tree.children.find((s) => s.name === 'Development');
123
+ const techLead = dev.children.find((a) => a.id === 'tech-lead');
124
+ // tech-lead is in Alpha Team
125
+ expect(techLead.squads).toContain('Alpha Team');
126
+ // frontend-dev is not in any squad
127
+ const frontendDev = dev.children.find((a) => a.id === 'frontend-dev');
128
+ expect(frontendDev.squads).toEqual([]);
129
+ });
130
+ it('returns squads list', async () => {
131
+ const res = await app.inject({
132
+ method: 'GET',
133
+ url: '/api/orgchart',
134
+ headers: { cookie: authCookie },
135
+ });
136
+ const body = JSON.parse(res.payload);
137
+ expect(body.squads).toHaveLength(1);
138
+ expect(body.squads[0].name).toBe('Alpha Team');
139
+ expect(body.squads[0].agents).toHaveLength(2);
140
+ });
141
+ it('works with no catalog file', async () => {
142
+ // Remove the catalog
143
+ fs.unlinkSync(path.join(tmpDir, 'agents', '_catalog.yaml'));
144
+ const res = await app.inject({
145
+ method: 'GET',
146
+ url: '/api/orgchart',
147
+ headers: { cookie: authCookie },
148
+ });
149
+ expect(res.statusCode).toBe(200);
150
+ const body = JSON.parse(res.payload);
151
+ expect(body.tree.children).toEqual([]);
152
+ });
153
+ it('defaults company name when no company.md', async () => {
154
+ fs.unlinkSync(path.join(tmpDir, '_expxagents', '_memory', 'company.md'));
155
+ const res = await app.inject({
156
+ method: 'GET',
157
+ url: '/api/orgchart',
158
+ headers: { cookie: authCookie },
159
+ });
160
+ const body = JSON.parse(res.payload);
161
+ expect(body.tree.name).toBe('Company');
162
+ });
163
+ it('rejects unauthenticated requests', async () => {
164
+ const res = await app.inject({
165
+ method: 'GET',
166
+ url: '/api/orgchart',
167
+ });
168
+ expect(res.statusCode).toBe(401);
169
+ });
170
+ });
171
+ //# sourceMappingURL=orgchart-routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgchart-routes.test.js","sourceRoot":"","sources":["../../../src/api/__tests__/orgchart-routes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=settings-routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-routes.test.d.ts","sourceRoot":"","sources":["../../../src/api/__tests__/settings-routes.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,177 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import Fastify from 'fastify';
3
+ import cookie from '@fastify/cookie';
4
+ import Database from 'better-sqlite3';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { settingsRoutes, parseEnvFile, maskValue } from '../settings-routes.js';
9
+ import { registerAuthMiddleware } from '../../auth/auth-middleware.js';
10
+ import { authRoutes } from '../../auth/auth-routes.js';
11
+ import { hashPassword } from '../../auth/password.js';
12
+ import { runMigrations } from '../../db/migrations.js';
13
+ const SECRET = 'test-secret-that-is-at-least-32-chars-long!!';
14
+ async function buildApp(db) {
15
+ const app = Fastify();
16
+ await app.register(cookie);
17
+ registerAuthMiddleware(app, SECRET);
18
+ await app.register(authRoutes, { db, jwtSecret: SECRET });
19
+ await app.register(settingsRoutes);
20
+ await app.ready();
21
+ return app;
22
+ }
23
+ async function getAuthCookie(app) {
24
+ const res = await app.inject({
25
+ method: 'POST',
26
+ url: '/api/auth/login',
27
+ payload: { username: 'admin', password: 'admin123' },
28
+ });
29
+ const cookies = res.cookies;
30
+ const accessToken = cookies.find(c => c.name === 'access_token');
31
+ return `access_token=${accessToken.value}`;
32
+ }
33
+ describe('settings routes', () => {
34
+ let db;
35
+ let app;
36
+ let authCookie;
37
+ let tmpEnvPath;
38
+ let origDotenvPath;
39
+ beforeEach(async () => {
40
+ db = new Database(':memory:');
41
+ runMigrations(db);
42
+ const hash = await hashPassword('admin123');
43
+ db.prepare("INSERT INTO users (id, username, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?)").run('admin-id', 'admin', hash, 'admin', new Date().toISOString());
44
+ // Create temp .env file
45
+ tmpEnvPath = path.join(os.tmpdir(), `.env-test-${Date.now()}`);
46
+ fs.writeFileSync(tmpEnvPath, 'AWS_REGION=us-east-1\nSES_FROM_EMAIL=test@example.com\nJWT_SECRET=should-not-be-exposed\n');
47
+ origDotenvPath = process.env.DOTENV_PATH;
48
+ process.env.DOTENV_PATH = tmpEnvPath;
49
+ app = await buildApp(db);
50
+ authCookie = await getAuthCookie(app);
51
+ });
52
+ afterEach(async () => {
53
+ await app?.close();
54
+ db?.close();
55
+ if (origDotenvPath !== undefined) {
56
+ process.env.DOTENV_PATH = origDotenvPath;
57
+ }
58
+ else {
59
+ delete process.env.DOTENV_PATH;
60
+ }
61
+ try {
62
+ fs.unlinkSync(tmpEnvPath);
63
+ }
64
+ catch { /* ignore */ }
65
+ });
66
+ it('GET /api/settings/env — returns allowed keys only', async () => {
67
+ const res = await app.inject({
68
+ method: 'GET',
69
+ url: '/api/settings/env',
70
+ headers: { cookie: authCookie },
71
+ });
72
+ expect(res.statusCode).toBe(200);
73
+ const body = JSON.parse(res.payload);
74
+ expect(body.settings.AWS_REGION).toBe('us-east-1');
75
+ expect(body.settings.SES_FROM_EMAIL).toBe('test@example.com');
76
+ // JWT_SECRET should NOT be in the response (not in ALLOWED_KEYS)
77
+ expect(body.settings.JWT_SECRET).toBeUndefined();
78
+ });
79
+ it('GET /api/settings/env — masks secret values', async () => {
80
+ fs.writeFileSync(tmpEnvPath, 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nAWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n');
81
+ const res = await app.inject({
82
+ method: 'GET',
83
+ url: '/api/settings/env',
84
+ headers: { cookie: authCookie },
85
+ });
86
+ expect(res.statusCode).toBe(200);
87
+ const body = JSON.parse(res.payload);
88
+ expect(body.settings.AWS_ACCESS_KEY_ID).toBe('****MPLE');
89
+ expect(body.settings.AWS_SECRET_ACCESS_KEY).toBe('****EKEY');
90
+ });
91
+ it('PATCH /api/settings/env — updates values', async () => {
92
+ const res = await app.inject({
93
+ method: 'PATCH',
94
+ url: '/api/settings/env',
95
+ headers: { cookie: authCookie },
96
+ payload: { SES_FROM_EMAIL: 'new@example.com', AWS_REGION: 'eu-west-1' },
97
+ });
98
+ expect(res.statusCode).toBe(200);
99
+ const body = JSON.parse(res.payload);
100
+ expect(body.settings.SES_FROM_EMAIL).toBe('new@example.com');
101
+ expect(body.settings.AWS_REGION).toBe('eu-west-1');
102
+ // Verify file was written
103
+ const content = fs.readFileSync(tmpEnvPath, 'utf-8');
104
+ expect(content).toContain('SES_FROM_EMAIL=new@example.com');
105
+ expect(content).toContain('AWS_REGION=eu-west-1');
106
+ // Verify process.env was updated
107
+ expect(process.env.SES_FROM_EMAIL).toBe('new@example.com');
108
+ });
109
+ it('PATCH /api/settings/env — rejects invalid keys', async () => {
110
+ const res = await app.inject({
111
+ method: 'PATCH',
112
+ url: '/api/settings/env',
113
+ headers: { cookie: authCookie },
114
+ payload: { JWT_SECRET: 'hacked', SOME_RANDOM: 'value' },
115
+ });
116
+ expect(res.statusCode).toBe(400);
117
+ expect(JSON.parse(res.payload).error).toContain('Invalid keys');
118
+ });
119
+ it('PATCH /api/settings/env — empty string removes key', async () => {
120
+ await app.inject({
121
+ method: 'PATCH',
122
+ url: '/api/settings/env',
123
+ headers: { cookie: authCookie },
124
+ payload: { SES_FROM_EMAIL: '' },
125
+ });
126
+ const content = fs.readFileSync(tmpEnvPath, 'utf-8');
127
+ expect(content).not.toContain('SES_FROM_EMAIL');
128
+ });
129
+ it('rejects non-admin users', async () => {
130
+ // Create operator user
131
+ const hash = await hashPassword('op123');
132
+ db.prepare("INSERT INTO users (id, username, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?)").run('op-id', 'operator', hash, 'operator', new Date().toISOString());
133
+ const loginRes = await app.inject({
134
+ method: 'POST',
135
+ url: '/api/auth/login',
136
+ payload: { username: 'operator', password: 'op123' },
137
+ });
138
+ const cookies = loginRes.cookies;
139
+ const opCookie = `access_token=${cookies.find(c => c.name === 'access_token').value}`;
140
+ const res = await app.inject({
141
+ method: 'GET',
142
+ url: '/api/settings/env',
143
+ headers: { cookie: opCookie },
144
+ });
145
+ expect(res.statusCode).toBe(403);
146
+ });
147
+ it('rejects unauthenticated requests', async () => {
148
+ const res = await app.inject({
149
+ method: 'GET',
150
+ url: '/api/settings/env',
151
+ });
152
+ expect(res.statusCode).toBe(401);
153
+ });
154
+ });
155
+ describe('parseEnvFile', () => {
156
+ it('parses key=value pairs, skips comments and blanks', () => {
157
+ const tmpFile = path.join(os.tmpdir(), `.env-parse-${Date.now()}`);
158
+ fs.writeFileSync(tmpFile, '# Comment\nKEY1=value1\n\nKEY2=value2\n');
159
+ const result = parseEnvFile(tmpFile);
160
+ expect(result.get('KEY1')).toBe('value1');
161
+ expect(result.get('KEY2')).toBe('value2');
162
+ expect(result.size).toBe(2);
163
+ fs.unlinkSync(tmpFile);
164
+ });
165
+ });
166
+ describe('maskValue', () => {
167
+ it('masks secret keys', () => {
168
+ expect(maskValue('AWS_ACCESS_KEY_ID', 'AKIAIOSFODNN7EXAMPLE')).toBe('****MPLE');
169
+ });
170
+ it('does not mask non-secret keys', () => {
171
+ expect(maskValue('AWS_REGION', 'us-east-1')).toBe('us-east-1');
172
+ });
173
+ it('returns **** for short secrets', () => {
174
+ expect(maskValue('AWS_SECRET_ACCESS_KEY', 'abc')).toBe('****');
175
+ });
176
+ });
177
+ //# sourceMappingURL=settings-routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-routes.test.js","sourceRoot":"","sources":["../../../src/api/__tests__/settings-routes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
2
+ interface OrgChartRoutesOptions extends FastifyPluginOptions {
3
+ squadsDir: string;
4
+ }
5
+ export declare function orgchartRoutes(app: FastifyInstance, opts: OrgChartRoutesOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=orgchart-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgchart-routes.d.ts","sourceRoot":"","sources":["../../src/api/orgchart-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAKrE,UAAU,qBAAsB,SAAQ,oBAAoB;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AA2HD,wBAAsB,cAAc,CAClC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ function loadCatalog(squadsDir) {
5
+ const catalogPath = path.resolve(squadsDir, '..', 'agents', '_catalog.yaml');
6
+ if (!fs.existsSync(catalogPath))
7
+ return [];
8
+ try {
9
+ const raw = fs.readFileSync(catalogPath, 'utf-8');
10
+ const parsed = parseYaml(raw);
11
+ return parsed?.catalog?.sectors || [];
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ }
17
+ function loadSquads(squadsDir) {
18
+ if (!fs.existsSync(squadsDir))
19
+ return [];
20
+ const entries = fs.readdirSync(squadsDir, { withFileTypes: true });
21
+ const squads = [];
22
+ for (const entry of entries) {
23
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_'))
24
+ continue;
25
+ const yamlPath = path.join(squadsDir, entry.name, 'squad.yaml');
26
+ if (!fs.existsSync(yamlPath))
27
+ continue;
28
+ try {
29
+ const raw = fs.readFileSync(yamlPath, 'utf-8');
30
+ const parsed = parseYaml(raw);
31
+ const s = parsed?.squad;
32
+ if (s) {
33
+ squads.push({
34
+ code: s.code || entry.name,
35
+ name: s.name || entry.name,
36
+ description: s.description || '',
37
+ icon: s.icon || 'folder',
38
+ agents: Array.isArray(s.agents)
39
+ ? s.agents.map((a) => ({ id: a.id || a, name: a.name || a.id || a, icon: a.icon || 'robot' }))
40
+ : [],
41
+ });
42
+ }
43
+ }
44
+ catch { /* skip */ }
45
+ }
46
+ return squads;
47
+ }
48
+ function loadCompanyName(squadsDir) {
49
+ const companyPath = path.resolve(squadsDir, '..', '_expxagents', '_memory', 'company.md');
50
+ if (!fs.existsSync(companyPath))
51
+ return 'Company';
52
+ try {
53
+ const content = fs.readFileSync(companyPath, 'utf-8');
54
+ const nameMatch = content.match(/(?:^|\n)#\s+(.+)/);
55
+ if (nameMatch)
56
+ return nameMatch[1].trim();
57
+ const fieldMatch = content.match(/company_name:\s*(.+)/i);
58
+ if (fieldMatch)
59
+ return fieldMatch[1].trim();
60
+ }
61
+ catch { /* ignore */ }
62
+ return 'Company';
63
+ }
64
+ function buildOrgChart(squadsDir) {
65
+ const sectors = loadCatalog(squadsDir);
66
+ const squads = loadSquads(squadsDir);
67
+ const companyName = loadCompanyName(squadsDir);
68
+ // Build agent → squads map
69
+ const agentSquadMap = new Map();
70
+ for (const squad of squads) {
71
+ for (const agent of squad.agents) {
72
+ const existing = agentSquadMap.get(agent.id) || [];
73
+ existing.push(squad.name);
74
+ agentSquadMap.set(agent.id, existing);
75
+ }
76
+ }
77
+ const sectorNodes = sectors.map((sector) => ({
78
+ id: sector.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
79
+ name: sector.name,
80
+ type: 'sector',
81
+ agentCount: sector.agents.length,
82
+ children: sector.agents.map((agentId) => ({
83
+ id: agentId,
84
+ name: agentId.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
85
+ type: 'agent',
86
+ squads: agentSquadMap.get(agentId) || [],
87
+ })),
88
+ }));
89
+ const tree = {
90
+ id: 'company',
91
+ name: companyName,
92
+ type: 'company',
93
+ children: sectorNodes,
94
+ };
95
+ return { tree, squads };
96
+ }
97
+ export async function orgchartRoutes(app, opts) {
98
+ app.get('/api/orgchart', { preHandler: [app.requireAuth] }, async () => {
99
+ return buildOrgChart(opts.squadsDir);
100
+ });
101
+ }
102
+ //# sourceMappingURL=orgchart-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orgchart-routes.js","sourceRoot":"","sources":["../../src/api/orgchart-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
2
+ declare const ALLOWED_KEYS: string[];
3
+ declare const SECRET_KEYS: Set<string>;
4
+ declare function parseEnvFile(filePath: string): Map<string, string>;
5
+ declare function writeEnvFile(filePath: string, entries: Map<string, string>): void;
6
+ declare function maskValue(key: string, value: string): string;
7
+ interface SettingsRoutesOptions extends FastifyPluginOptions {
8
+ }
9
+ export declare function settingsRoutes(app: FastifyInstance, _opts: SettingsRoutesOptions): Promise<void>;
10
+ export { ALLOWED_KEYS, SECRET_KEYS, parseEnvFile, writeEnvFile, maskValue };
11
+ //# sourceMappingURL=settings-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-routes.d.ts","sourceRoot":"","sources":["../../src/api/settings-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAQrE,QAAA,MAAM,YAAY,UAUjB,CAAC;AAGF,QAAA,MAAM,WAAW,aAGf,CAAC;AAMH,iBAAS,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAe3D;AAED,iBAAS,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM1E;AAED,iBAAS,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED,UAAU,qBAAsB,SAAQ,oBAAoB;CAAG;AAE/D,wBAAsB,cAAc,CAClC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAGD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC"}