expxagents 0.6.0 → 0.7.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 (156) hide show
  1. package/dist/__tests__/cli.test.d.ts +1 -0
  2. package/dist/__tests__/cli.test.js +23 -0
  3. package/dist/cli/src/commands/init.js +12 -2
  4. package/dist/cli/src/commands/virtual-office.js +42 -5
  5. package/dist/cli/src/utils/config.d.ts +5 -0
  6. package/dist/cli/src/utils/config.js +1 -1
  7. package/dist/commands/create.d.ts +1 -0
  8. package/dist/commands/create.js +34 -0
  9. package/dist/commands/init.d.ts +1 -0
  10. package/dist/commands/init.js +58 -0
  11. package/dist/commands/install.d.ts +1 -0
  12. package/dist/commands/install.js +16 -0
  13. package/dist/commands/list.d.ts +1 -0
  14. package/dist/commands/list.js +58 -0
  15. package/dist/commands/run.d.ts +1 -0
  16. package/dist/commands/run.js +173 -0
  17. package/dist/commands/server.d.ts +1 -0
  18. package/dist/commands/server.js +22 -0
  19. package/dist/commands/stop.d.ts +1 -0
  20. package/dist/commands/stop.js +23 -0
  21. package/dist/commands/uninstall.d.ts +1 -0
  22. package/dist/commands/uninstall.js +12 -0
  23. package/dist/dashboard/assets/BufferResource-Cf2Uo4_f.js +185 -0
  24. package/dist/dashboard/assets/CanvasRenderer-DFdMBORe.js +1 -0
  25. package/dist/dashboard/assets/RenderTargetSystem-CMh8XRf_.js +172 -0
  26. package/dist/dashboard/assets/WebGLRenderer-B5huw0RY.js +156 -0
  27. package/dist/dashboard/assets/WebGPURenderer-BdIKurkV.js +41 -0
  28. package/dist/dashboard/assets/browserAll-BjVJrv1L.js +14 -0
  29. package/dist/dashboard/assets/index-DwTFo09S.js +344 -0
  30. package/dist/dashboard/assets/webworkerAll-DMtK63GZ.js +83 -0
  31. package/dist/dashboard/index.html +16 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +47 -0
  34. package/dist/server/api/health-routes.d.ts +3 -0
  35. package/dist/server/api/health-routes.d.ts.map +1 -0
  36. package/dist/server/api/health-routes.js +6 -0
  37. package/dist/server/api/health-routes.js.map +1 -0
  38. package/dist/server/api/squads-routes.d.ts +7 -0
  39. package/dist/server/api/squads-routes.d.ts.map +1 -0
  40. package/dist/server/api/squads-routes.js +91 -0
  41. package/dist/server/api/squads-routes.js.map +1 -0
  42. package/dist/server/api/users-routes.d.ts +8 -0
  43. package/dist/server/api/users-routes.d.ts.map +1 -0
  44. package/dist/server/api/users-routes.js +45 -0
  45. package/dist/server/api/users-routes.js.map +1 -0
  46. package/dist/server/app.d.ts +7 -0
  47. package/dist/server/app.d.ts.map +1 -0
  48. package/dist/server/app.js +109 -0
  49. package/dist/server/app.js.map +1 -0
  50. package/dist/server/auth/auth-middleware.d.ts +13 -0
  51. package/dist/server/auth/auth-middleware.d.ts.map +1 -0
  52. package/dist/server/auth/auth-middleware.js +25 -0
  53. package/dist/server/auth/auth-middleware.js.map +1 -0
  54. package/dist/server/auth/auth-routes.d.ts +9 -0
  55. package/dist/server/auth/auth-routes.d.ts.map +1 -0
  56. package/dist/server/auth/auth-routes.js +149 -0
  57. package/dist/server/auth/auth-routes.js.map +1 -0
  58. package/dist/server/auth/jwt.d.ts +14 -0
  59. package/dist/server/auth/jwt.d.ts.map +1 -0
  60. package/dist/server/auth/jwt.js +16 -0
  61. package/dist/server/auth/jwt.js.map +1 -0
  62. package/dist/server/auth/password.d.ts +3 -0
  63. package/dist/server/auth/password.d.ts.map +1 -0
  64. package/dist/server/auth/password.js +9 -0
  65. package/dist/server/auth/password.js.map +1 -0
  66. package/dist/server/bridge/__tests__/chat-handler.test.d.ts +2 -0
  67. package/dist/server/bridge/__tests__/chat-handler.test.d.ts.map +1 -0
  68. package/dist/server/bridge/__tests__/chat-handler.test.js +132 -0
  69. package/dist/server/bridge/__tests__/chat-handler.test.js.map +1 -0
  70. package/dist/server/bridge/__tests__/chat-integration.test.d.ts +2 -0
  71. package/dist/server/bridge/__tests__/chat-integration.test.d.ts.map +1 -0
  72. package/dist/server/bridge/__tests__/chat-integration.test.js +141 -0
  73. package/dist/server/bridge/__tests__/chat-integration.test.js.map +1 -0
  74. package/dist/server/bridge/__tests__/claude-bridge.test.d.ts +2 -0
  75. package/dist/server/bridge/__tests__/claude-bridge.test.d.ts.map +1 -0
  76. package/dist/server/bridge/__tests__/claude-bridge.test.js +223 -0
  77. package/dist/server/bridge/__tests__/claude-bridge.test.js.map +1 -0
  78. package/dist/server/bridge/__tests__/conversation.test.d.ts +2 -0
  79. package/dist/server/bridge/__tests__/conversation.test.d.ts.map +1 -0
  80. package/dist/server/bridge/__tests__/conversation.test.js +168 -0
  81. package/dist/server/bridge/__tests__/conversation.test.js.map +1 -0
  82. package/dist/server/bridge/__tests__/stream-parser.test.d.ts +2 -0
  83. package/dist/server/bridge/__tests__/stream-parser.test.d.ts.map +1 -0
  84. package/dist/server/bridge/__tests__/stream-parser.test.js +66 -0
  85. package/dist/server/bridge/__tests__/stream-parser.test.js.map +1 -0
  86. package/dist/server/bridge/chat-handler.d.ts +19 -0
  87. package/dist/server/bridge/chat-handler.d.ts.map +1 -0
  88. package/dist/server/bridge/chat-handler.js +104 -0
  89. package/dist/server/bridge/chat-handler.js.map +1 -0
  90. package/dist/server/bridge/claude-bridge.d.ts +27 -0
  91. package/dist/server/bridge/claude-bridge.d.ts.map +1 -0
  92. package/dist/server/bridge/claude-bridge.js +91 -0
  93. package/dist/server/bridge/claude-bridge.js.map +1 -0
  94. package/dist/server/bridge/conversation.d.ts +44 -0
  95. package/dist/server/bridge/conversation.d.ts.map +1 -0
  96. package/dist/server/bridge/conversation.js +73 -0
  97. package/dist/server/bridge/conversation.js.map +1 -0
  98. package/dist/server/bridge/stream-parser.d.ts +14 -0
  99. package/dist/server/bridge/stream-parser.d.ts.map +1 -0
  100. package/dist/server/bridge/stream-parser.js +26 -0
  101. package/dist/server/bridge/stream-parser.js.map +1 -0
  102. package/dist/server/config.d.ts +10 -0
  103. package/dist/server/config.d.ts.map +1 -0
  104. package/dist/server/config.js +21 -0
  105. package/dist/server/config.js.map +1 -0
  106. package/dist/server/db/__tests__/chat-tables.test.d.ts +2 -0
  107. package/dist/server/db/__tests__/chat-tables.test.d.ts.map +1 -0
  108. package/dist/server/db/__tests__/chat-tables.test.js +82 -0
  109. package/dist/server/db/__tests__/chat-tables.test.js.map +1 -0
  110. package/dist/server/db/connection.d.ts +4 -0
  111. package/dist/server/db/connection.d.ts.map +1 -0
  112. package/dist/server/db/connection.js +21 -0
  113. package/dist/server/db/connection.js.map +1 -0
  114. package/dist/server/db/migrations.d.ts +4 -0
  115. package/dist/server/db/migrations.d.ts.map +1 -0
  116. package/dist/server/db/migrations.js +17 -0
  117. package/dist/server/db/migrations.js.map +1 -0
  118. package/dist/server/db/schema.d.ts +2 -0
  119. package/dist/server/db/schema.d.ts.map +1 -0
  120. package/dist/server/db/schema.js +44 -0
  121. package/dist/server/db/schema.js.map +1 -0
  122. package/dist/server/index.d.ts +2 -0
  123. package/dist/server/index.d.ts.map +1 -0
  124. package/dist/server/index.js +18 -0
  125. package/dist/server/index.js.map +1 -0
  126. package/dist/server/routes/__tests__/conversations.test.d.ts +2 -0
  127. package/dist/server/routes/__tests__/conversations.test.d.ts.map +1 -0
  128. package/dist/server/routes/__tests__/conversations.test.js +94 -0
  129. package/dist/server/routes/__tests__/conversations.test.js.map +1 -0
  130. package/dist/server/routes/conversations.d.ts +8 -0
  131. package/dist/server/routes/conversations.d.ts.map +1 -0
  132. package/dist/server/routes/conversations.js +25 -0
  133. package/dist/server/routes/conversations.js.map +1 -0
  134. package/dist/server/watcher/file-watcher.d.ts +12 -0
  135. package/dist/server/watcher/file-watcher.d.ts.map +1 -0
  136. package/dist/server/watcher/file-watcher.js +68 -0
  137. package/dist/server/watcher/file-watcher.js.map +1 -0
  138. package/dist/server/watcher/state-parser.d.ts +77 -0
  139. package/dist/server/watcher/state-parser.d.ts.map +1 -0
  140. package/dist/server/watcher/state-parser.js +74 -0
  141. package/dist/server/watcher/state-parser.js.map +1 -0
  142. package/dist/server/ws/ws-auth.d.ts +4 -0
  143. package/dist/server/ws/ws-auth.d.ts.map +1 -0
  144. package/dist/server/ws/ws-auth.js +42 -0
  145. package/dist/server/ws/ws-auth.js.map +1 -0
  146. package/dist/server/ws/ws-handler.d.ts +11 -0
  147. package/dist/server/ws/ws-handler.d.ts.map +1 -0
  148. package/dist/server/ws/ws-handler.js +107 -0
  149. package/dist/server/ws/ws-handler.js.map +1 -0
  150. package/dist/server/ws/ws-rooms.d.ts +12 -0
  151. package/dist/server/ws/ws-rooms.d.ts.map +1 -0
  152. package/dist/server/ws/ws-rooms.js +52 -0
  153. package/dist/server/ws/ws-rooms.js.map +1 -0
  154. package/dist/utils/config.d.ts +15 -0
  155. package/dist/utils/config.js +23 -0
  156. package/package.json +16 -5
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import path from 'path';
4
+ describe('CLI', () => {
5
+ const cliPath = path.resolve(__dirname, '../../bin/expxagents.js');
6
+ it('shows help output with all commands', () => {
7
+ execSync('npm run build', { cwd: path.resolve(__dirname, '../..') });
8
+ const output = execSync(`node ${cliPath} --help`, { encoding: 'utf-8' });
9
+ expect(output).toContain('expxagents');
10
+ expect(output).toContain('init');
11
+ expect(output).toContain('create');
12
+ expect(output).toContain('run');
13
+ expect(output).toContain('stop');
14
+ expect(output).toContain('list');
15
+ expect(output).toContain('install');
16
+ expect(output).toContain('uninstall');
17
+ expect(output).toContain('server');
18
+ });
19
+ it('shows version', () => {
20
+ const output = execSync(`node ${cliPath} --version`, { encoding: 'utf-8' });
21
+ expect(output.trim()).toMatch(/^\d+\.\d+\.\d+$/);
22
+ });
23
+ });
@@ -139,10 +139,20 @@ Present using AskUserQuestion with 2-4 options:
139
139
  | \`/expxagents install <name>\` | Install skill from catalog |
140
140
  | \`/expxagents uninstall <name>\` | Remove installed skill |
141
141
  | \`/expxagents delete <name>\` | Confirm and delete squad |
142
- | \`/expxagents dashboard\` | Start server + open dashboard |
143
- | \`/expxagents virtual-office\` | Start server + open virtual office in browser |
142
+ | \`/expxagents dashboard\` | Run CLI to start server + open dashboard |
143
+ | \`/expxagents virtual-office\` | Run CLI to start server + open virtual office in browser |
144
144
  | \`/expxagents help\` | Show help text |
145
145
 
146
+ ## Virtual Office / Dashboard
147
+
148
+ When the user requests \`/expxagents virtual-office\` or \`/expxagents dashboard\`:
149
+
150
+ 1. **ALWAYS run the CLI command** — do NOT generate HTML files or static pages
151
+ 2. Execute: \`npx expxagents virtual-office\` via the Bash tool
152
+ 3. This starts the Fastify server with the Pixi.js animated office dashboard and opens the browser
153
+ 4. The server runs on port 3001 (or PORT env var) and serves the React SPA with WebSocket support
154
+ 5. NEVER create static HTML files for the virtual office — the office is a full server-side application
155
+
146
156
  ## Loading Agents
147
157
 
148
158
  When a core agent needs to be activated:
@@ -1,5 +1,33 @@
1
1
  import { spawn, execSync } from 'child_process';
2
+ import fs from 'fs';
2
3
  import path from 'path';
4
+ import { findPackageRoot } from '../utils/config.js';
5
+ /**
6
+ * Resolve the server entry point and dashboard dist directory.
7
+ * Prefers bundled server from the npm package; falls back to monorepo paths.
8
+ */
9
+ function resolveServerPaths() {
10
+ // 1. Bundled package paths (npm / npx usage)
11
+ const pkgRoot = findPackageRoot();
12
+ const bundledServer = path.join(pkgRoot, 'dist', 'server', 'index.js');
13
+ const bundledDashboard = path.join(pkgRoot, 'dist', 'dashboard');
14
+ if (fs.existsSync(bundledServer) && fs.existsSync(bundledDashboard)) {
15
+ return {
16
+ serverEntry: bundledServer,
17
+ dashboardDist: bundledDashboard,
18
+ cwd: pkgRoot,
19
+ };
20
+ }
21
+ // 2. Monorepo development fallback
22
+ const projectRoot = process.cwd();
23
+ const monoServer = path.join(projectRoot, 'server', 'dist', 'index.js');
24
+ const monoDashboard = path.join(projectRoot, 'dashboard', 'dist');
25
+ return {
26
+ serverEntry: monoServer,
27
+ dashboardDist: monoDashboard,
28
+ cwd: path.join(projectRoot, 'server'),
29
+ };
30
+ }
3
31
  export async function virtualOfficeCommand() {
4
32
  const port = process.env.PORT ?? '3001';
5
33
  const url = `http://localhost:${port}/office`;
@@ -13,13 +41,22 @@ export async function virtualOfficeCommand() {
13
41
  serverRunning = false;
14
42
  }
15
43
  if (!serverRunning) {
44
+ const { serverEntry, dashboardDist, cwd } = resolveServerPaths();
45
+ if (!fs.existsSync(serverEntry)) {
46
+ console.error(`Server not found at ${serverEntry}`);
47
+ console.error('Run "npm run build" first, or install the latest expxagents package.');
48
+ process.exit(1);
49
+ }
16
50
  console.log('Starting ExpxAgents server...');
17
- const cwd = process.cwd();
18
- const serverDir = path.join(cwd, 'server');
19
- const child = spawn('node', ['dist/index.js'], {
20
- cwd: serverDir,
51
+ const child = spawn('node', [serverEntry], {
52
+ cwd,
21
53
  stdio: 'inherit',
22
- env: { ...process.env },
54
+ env: {
55
+ ...process.env,
56
+ NODE_ENV: 'production',
57
+ DASHBOARD_DIST: dashboardDist,
58
+ PORT: port,
59
+ },
23
60
  detached: true,
24
61
  });
25
62
  child.on('error', (err) => {
@@ -13,6 +13,11 @@ export declare function resolveConfig(): CliConfig;
13
13
  * Check if current directory is an initialized ExpxAgents project.
14
14
  */
15
15
  export declare function isInitialized(): boolean;
16
+ /**
17
+ * Find the package root by walking up from the compiled file's location
18
+ * until we find a package.json. Works regardless of dist/ nesting depth.
19
+ */
20
+ export declare function findPackageRoot(): string;
16
21
  /**
17
22
  * Get the directory where package assets are stored.
18
23
  * Resolves to <package-root>/assets/ regardless of compiled file nesting.
@@ -26,7 +26,7 @@ export function isInitialized() {
26
26
  * Find the package root by walking up from the compiled file's location
27
27
  * until we find a package.json. Works regardless of dist/ nesting depth.
28
28
  */
29
- function findPackageRoot() {
29
+ export function findPackageRoot() {
30
30
  let dir = path.dirname(fileURLToPath(import.meta.url));
31
31
  while (dir !== path.dirname(dir)) {
32
32
  if (fs.existsSync(path.join(dir, 'package.json')))
@@ -0,0 +1 @@
1
+ export declare function createCommand(): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ export async function createCommand() {
5
+ const cwd = process.cwd();
6
+ const architectPrompt = path.join(cwd, 'core', 'architect.md');
7
+ if (!fs.existsSync(architectPrompt)) {
8
+ console.error('Architect prompt not found. Run `expxagents init` first.');
9
+ process.exit(1);
10
+ }
11
+ const prompt = fs.readFileSync(architectPrompt, 'utf-8');
12
+ const squadsDir = path.join(cwd, 'squads');
13
+ if (!fs.existsSync(squadsDir)) {
14
+ fs.mkdirSync(squadsDir, { recursive: true });
15
+ }
16
+ console.log('Starting Architect agent to design your squad...\n');
17
+ const child = spawn('claude', ['-p', prompt], {
18
+ cwd: squadsDir,
19
+ stdio: 'inherit',
20
+ });
21
+ child.on('error', (err) => {
22
+ console.error(`Failed to start Claude Code: ${err.message}`);
23
+ console.error('Make sure Claude Code CLI is installed.');
24
+ process.exit(1);
25
+ });
26
+ child.on('exit', (code) => {
27
+ if (code === 0) {
28
+ console.log('\nSquad created successfully! Run `expxagents list` to see it.');
29
+ }
30
+ else {
31
+ console.error(`\nArchitect exited with code ${code}`);
32
+ }
33
+ });
34
+ }
@@ -0,0 +1 @@
1
+ export declare function initCommand(): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ export async function initCommand() {
5
+ const cwd = process.cwd();
6
+ console.log('Initializing ExpxAgents project...\n');
7
+ // Create directories
8
+ const dirs = ['squads', 'skills', 'templates'];
9
+ for (const dir of dirs) {
10
+ const dirPath = path.join(cwd, dir);
11
+ if (!fs.existsSync(dirPath)) {
12
+ fs.mkdirSync(dirPath, { recursive: true });
13
+ console.log(` Created ${dir}/`);
14
+ }
15
+ else {
16
+ console.log(` ${dir}/ already exists`);
17
+ }
18
+ }
19
+ // Create .env if it doesn't exist
20
+ const envPath = path.join(cwd, '.env');
21
+ if (!fs.existsSync(envPath)) {
22
+ const jwtSecret = crypto.randomBytes(32).toString('hex');
23
+ const envContent = `JWT_SECRET=${jwtSecret}
24
+ PORT=3001
25
+ CORS_ORIGIN=http://localhost:3001
26
+ NODE_ENV=development
27
+ BRIDGE_TIMEOUT_MS=300000
28
+ `;
29
+ fs.writeFileSync(envPath, envContent, 'utf-8');
30
+ console.log(' Created .env with generated JWT_SECRET');
31
+ }
32
+ else {
33
+ console.log(' .env already exists');
34
+ }
35
+ // Create .gitignore additions
36
+ const gitignorePath = path.join(cwd, '.gitignore');
37
+ const gitignoreEntries = [
38
+ 'node_modules/',
39
+ '.env',
40
+ '*.tmp',
41
+ 'squads/*/state.json',
42
+ 'squads/*/state.json.tmp',
43
+ 'squads/*/output/',
44
+ ];
45
+ if (fs.existsSync(gitignorePath)) {
46
+ const existing = fs.readFileSync(gitignorePath, 'utf-8');
47
+ const toAdd = gitignoreEntries.filter(e => !existing.includes(e));
48
+ if (toAdd.length > 0) {
49
+ fs.appendFileSync(gitignorePath, '\n# ExpxAgents\n' + toAdd.join('\n') + '\n');
50
+ console.log(' Updated .gitignore');
51
+ }
52
+ }
53
+ else {
54
+ fs.writeFileSync(gitignorePath, gitignoreEntries.join('\n') + '\n', 'utf-8');
55
+ console.log(' Created .gitignore');
56
+ }
57
+ console.log('\nProject initialized. Run `expxagents server` to start.');
58
+ }
@@ -0,0 +1 @@
1
+ export declare function installCommand(skill: string): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export async function installCommand(skill) {
4
+ const cwd = process.cwd();
5
+ const skillsDir = path.join(cwd, 'skills');
6
+ if (!fs.existsSync(skillsDir)) {
7
+ fs.mkdirSync(skillsDir, { recursive: true });
8
+ }
9
+ const skillDir = path.join(skillsDir, skill);
10
+ if (fs.existsSync(skillDir)) {
11
+ console.log(`Skill "${skill}" is already installed.`);
12
+ return;
13
+ }
14
+ console.log(`Installing skill "${skill}"...`);
15
+ console.log('Skill registry not yet implemented. Place skill files manually in skills/ directory.');
16
+ }
@@ -0,0 +1 @@
1
+ export declare function listCommand(): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { loadSquad } from '../../../core/squad-loader.js';
4
+ import { loadSkills } from '../../../core/skills-loader.js';
5
+ import { readState } from '../../../core/state-manager.js';
6
+ export async function listCommand() {
7
+ const cwd = process.cwd();
8
+ const squadsDir = path.join(cwd, 'squads');
9
+ const skillsDir = path.join(cwd, 'skills');
10
+ // List squads
11
+ console.log('Squads:');
12
+ console.log('-------');
13
+ if (fs.existsSync(squadsDir)) {
14
+ const entries = fs.readdirSync(squadsDir, { withFileTypes: true });
15
+ let found = false;
16
+ for (const entry of entries) {
17
+ if (!entry.isDirectory())
18
+ continue;
19
+ const squadDir = path.join(squadsDir, entry.name);
20
+ const yamlPath = path.join(squadDir, 'squad.yaml');
21
+ if (!fs.existsSync(yamlPath))
22
+ continue;
23
+ found = true;
24
+ try {
25
+ const config = loadSquad(squadDir);
26
+ const state = readState(squadDir);
27
+ const status = state?.status ?? 'idle';
28
+ const agentCount = config.squad.agents.length;
29
+ const stepCount = config.squad.pipeline.steps.length;
30
+ console.log(` ${config.squad.name} (${config.squad.code})`);
31
+ console.log(` Status: ${status} | Agents: ${agentCount} | Steps: ${stepCount}`);
32
+ console.log(` ${config.squad.description}`);
33
+ console.log('');
34
+ }
35
+ catch (err) {
36
+ console.log(` ${entry.name} — error loading: ${err.message}`);
37
+ }
38
+ }
39
+ if (!found) {
40
+ console.log(' No squads found. Run `expxagents create` to create one.\n');
41
+ }
42
+ }
43
+ else {
44
+ console.log(' No squads directory. Run `expxagents init` first.\n');
45
+ }
46
+ // List skills
47
+ console.log('Skills:');
48
+ console.log('-------');
49
+ const skills = loadSkills(skillsDir);
50
+ if (skills.length === 0) {
51
+ console.log(' No skills installed. Run `expxagents install <skill>` to add one.');
52
+ }
53
+ else {
54
+ for (const skill of skills) {
55
+ console.log(` ${skill.name} (${skill.type}) — ${skill.description}`);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1 @@
1
+ export declare function runCommand(name: string): Promise<void>;
@@ -0,0 +1,173 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { loadSquad } from '../../../core/squad-loader.js';
5
+ import { createInitialState, readState, writeState, updateAgentStatus, updateStep, setHandoff, setSquadStatus, HANDOFF_DELAY_MS, } from '../../../core/state-manager.js';
6
+ import { loadSkills } from '../../../core/skills-loader.js';
7
+ function delay(ms) {
8
+ return new Promise(resolve => setTimeout(resolve, ms));
9
+ }
10
+ function buildAgentPrompt(config, step, stepIndex, squadDir, skills, runnerPrompt) {
11
+ const agent = config.squad.agents.find(a => a.id === step.agent);
12
+ let agentInstructions = '';
13
+ const agentPromptPath = path.join(squadDir, agent.prompt);
14
+ if (fs.existsSync(agentPromptPath)) {
15
+ agentInstructions = fs.readFileSync(agentPromptPath, 'utf-8');
16
+ }
17
+ let previousOutput = '';
18
+ if (step.deliverFrom) {
19
+ const prevStepIdx = config.squad.pipeline.steps.findIndex(s => s.agent === step.deliverFrom);
20
+ if (prevStepIdx !== -1) {
21
+ const prevOutputPath = path.join(squadDir, 'output', `step-${String(prevStepIdx + 1).padStart(2, '0')}.md`);
22
+ if (fs.existsSync(prevOutputPath)) {
23
+ previousOutput = fs.readFileSync(prevOutputPath, 'utf-8');
24
+ }
25
+ }
26
+ }
27
+ const squadSkills = config.squad.skills || [];
28
+ const skillInstructions = skills
29
+ .filter(s => squadSkills.includes(s.name))
30
+ .map(s => `### Skill: ${s.name}\n${s.instructions}`)
31
+ .join('\n\n');
32
+ const parts = [
33
+ runnerPrompt,
34
+ '',
35
+ `## Current Task`,
36
+ `- **Squad:** ${config.squad.name} (${config.squad.code})`,
37
+ `- **Agent:** ${agent.name} (${agent.id})`,
38
+ `- **Step ${stepIndex}/${config.squad.pipeline.steps.length}:** ${step.label}`,
39
+ '',
40
+ ];
41
+ if (agentInstructions) {
42
+ parts.push('## Agent Instructions', '', agentInstructions, '');
43
+ }
44
+ if (previousOutput) {
45
+ parts.push(`## Input from ${step.deliverFrom}`, '', previousOutput, '');
46
+ }
47
+ if (skillInstructions) {
48
+ parts.push('## Available Skills', '', skillInstructions, '');
49
+ }
50
+ parts.push('## Output', '', `Write your complete output below. It will be saved to output/step-${String(stepIndex).padStart(2, '0')}.md`);
51
+ return parts.join('\n');
52
+ }
53
+ function spawnClaudeCode(prompt, cwd) {
54
+ return new Promise((resolve, reject) => {
55
+ const child = spawn('claude', ['-p', '--verbose', prompt], {
56
+ cwd,
57
+ stdio: ['pipe', 'pipe', 'pipe'],
58
+ });
59
+ let stdout = '';
60
+ let stderr = '';
61
+ child.stdout?.on('data', (data) => {
62
+ const chunk = data.toString();
63
+ stdout += chunk;
64
+ process.stdout.write(chunk);
65
+ });
66
+ child.stderr?.on('data', (data) => {
67
+ stderr += data.toString();
68
+ });
69
+ child.on('error', (err) => {
70
+ reject(new Error(`Failed to spawn Claude Code: ${err.message}`));
71
+ });
72
+ child.on('exit', (code) => {
73
+ if (code === 0) {
74
+ resolve(stdout);
75
+ }
76
+ else {
77
+ reject(new Error(`Claude Code exited with code ${code}: ${stderr}`));
78
+ }
79
+ });
80
+ });
81
+ }
82
+ export async function runCommand(name) {
83
+ const cwd = process.cwd();
84
+ const squadDir = path.join(cwd, 'squads', name);
85
+ const skillsDir = path.join(cwd, 'skills');
86
+ const coreDir = path.join(cwd, 'core');
87
+ if (!fs.existsSync(squadDir)) {
88
+ console.error(`Squad "${name}" not found in squads/ directory.`);
89
+ process.exit(1);
90
+ }
91
+ let config;
92
+ try {
93
+ config = loadSquad(squadDir);
94
+ }
95
+ catch (err) {
96
+ console.error(`Failed to load squad config: ${err.message}`);
97
+ process.exit(1);
98
+ }
99
+ const skills = loadSkills(skillsDir);
100
+ const runnerPromptPath = path.join(coreDir, 'runner.md');
101
+ let runnerPrompt = '';
102
+ if (fs.existsSync(runnerPromptPath)) {
103
+ runnerPrompt = fs.readFileSync(runnerPromptPath, 'utf-8');
104
+ }
105
+ const existingState = readState(squadDir);
106
+ if (existingState && existingState.status === 'running') {
107
+ console.error(`Squad "${name}" is already running. Use \`expxagents stop ${name}\` first.`);
108
+ process.exit(1);
109
+ }
110
+ const outputDir = path.join(squadDir, 'output');
111
+ if (!fs.existsSync(outputDir)) {
112
+ fs.mkdirSync(outputDir, { recursive: true });
113
+ }
114
+ let state = createInitialState(config);
115
+ state = setSquadStatus(state, 'running');
116
+ state = { ...state, startedAt: new Date().toISOString() };
117
+ writeState(squadDir, state);
118
+ console.log(`\nRunning squad "${config.squad.name}" (${config.squad.code})`);
119
+ console.log(`Pipeline: ${config.squad.pipeline.steps.length} steps\n`);
120
+ const steps = config.squad.pipeline.steps;
121
+ for (let i = 0; i < steps.length; i++) {
122
+ const step = steps[i];
123
+ const stepNumber = i + 1;
124
+ const agent = config.squad.agents.find(a => a.id === step.agent);
125
+ console.log(`--- Step ${stepNumber}/${steps.length}: ${step.label} (${agent.name}) ---\n`);
126
+ state = updateAgentStatus(state, step.agent, 'working');
127
+ state = updateStep(state, stepNumber, step.label);
128
+ writeState(squadDir, state);
129
+ const prompt = buildAgentPrompt(config, step, stepNumber, squadDir, skills, runnerPrompt);
130
+ let output;
131
+ try {
132
+ output = await spawnClaudeCode(prompt, squadDir);
133
+ }
134
+ catch (err) {
135
+ console.error(`\nStep ${stepNumber} failed: ${err.message}`);
136
+ state = updateAgentStatus(state, step.agent, 'idle');
137
+ state = setSquadStatus(state, 'idle');
138
+ writeState(squadDir, state);
139
+ process.exit(1);
140
+ }
141
+ const outputPath = path.join(outputDir, `step-${String(stepNumber).padStart(2, '0')}.md`);
142
+ fs.writeFileSync(outputPath, output, 'utf-8');
143
+ const outputLines = output.trim().split('\n').filter(l => l.trim().length > 0);
144
+ const agentMessage = outputLines[outputLines.length - 1]?.trim() || `Completed ${step.label}`;
145
+ const agentIdx = state.agents.findIndex(a => a.id === step.agent);
146
+ state = {
147
+ ...state,
148
+ agents: state.agents.map((a, idx) => idx === agentIdx ? { ...a, message: agentMessage } : a),
149
+ };
150
+ const nextStep = steps[i + 1];
151
+ const hasHandoff = nextStep && nextStep.deliverFrom === step.agent;
152
+ if (hasHandoff) {
153
+ state = updateAgentStatus(state, step.agent, 'delivering');
154
+ state = setHandoff(state, step.agent, nextStep.agent, agentMessage);
155
+ writeState(squadDir, state);
156
+ console.log(`\nHandoff: ${agent.name} -> ${config.squad.agents.find(a => a.id === nextStep.agent).name}`);
157
+ console.log(`Message: ${agentMessage}\n`);
158
+ await delay(HANDOFF_DELAY_MS);
159
+ state = updateAgentStatus(state, step.agent, 'done');
160
+ writeState(squadDir, state);
161
+ }
162
+ else {
163
+ state = updateAgentStatus(state, step.agent, 'done');
164
+ writeState(squadDir, state);
165
+ }
166
+ console.log(`\nStep ${stepNumber} completed.\n`);
167
+ }
168
+ state = setSquadStatus(state, 'completed');
169
+ state = updateStep(state, steps.length, 'Completed');
170
+ writeState(squadDir, state);
171
+ console.log(`\nSquad "${config.squad.name}" pipeline completed!`);
172
+ console.log(`Output files saved to squads/${name}/output/`);
173
+ }
@@ -0,0 +1 @@
1
+ export declare function serverCommand(): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ export async function serverCommand() {
4
+ const cwd = process.cwd();
5
+ const serverDir = path.join(cwd, 'server');
6
+ console.log('Starting ExpxAgents server...');
7
+ const child = spawn('node', ['dist/index.js'], {
8
+ cwd: serverDir,
9
+ stdio: 'inherit',
10
+ env: { ...process.env },
11
+ });
12
+ child.on('error', (err) => {
13
+ console.error(`Failed to start server: ${err.message}`);
14
+ console.error('Make sure you have built the server: cd server && npm run build');
15
+ process.exit(1);
16
+ });
17
+ child.on('exit', (code) => {
18
+ process.exit(code ?? 0);
19
+ });
20
+ process.on('SIGINT', () => child.kill('SIGINT'));
21
+ process.on('SIGTERM', () => child.kill('SIGTERM'));
22
+ }
@@ -0,0 +1 @@
1
+ export declare function stopCommand(name: string): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { readState, writeState, setSquadStatus } from '../../../core/state-manager.js';
4
+ export async function stopCommand(name) {
5
+ const cwd = process.cwd();
6
+ const squadDir = path.join(cwd, 'squads', name);
7
+ if (!fs.existsSync(squadDir)) {
8
+ console.error(`Squad "${name}" not found.`);
9
+ process.exit(1);
10
+ }
11
+ const state = readState(squadDir);
12
+ if (!state) {
13
+ console.log(`Squad "${name}" is not running (no state.json).`);
14
+ return;
15
+ }
16
+ if (state.status === 'idle' || state.status === 'completed') {
17
+ console.log(`Squad "${name}" is already ${state.status}.`);
18
+ return;
19
+ }
20
+ const updated = setSquadStatus(state, 'idle');
21
+ writeState(squadDir, updated);
22
+ console.log(`Squad "${name}" stopped.`);
23
+ }
@@ -0,0 +1 @@
1
+ export declare function uninstallCommand(skill: string): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ export async function uninstallCommand(skill) {
4
+ const cwd = process.cwd();
5
+ const skillDir = path.join(cwd, 'skills', skill);
6
+ if (!fs.existsSync(skillDir)) {
7
+ console.error(`Skill "${skill}" is not installed.`);
8
+ process.exit(1);
9
+ }
10
+ fs.rmSync(skillDir, { recursive: true, force: true });
11
+ console.log(`Skill "${skill}" uninstalled.`);
12
+ }