expxagents 0.5.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.
- package/dist/__tests__/cli.test.d.ts +1 -0
- package/dist/__tests__/cli.test.js +23 -0
- package/dist/cli/src/commands/init.js +12 -2
- package/dist/cli/src/commands/virtual-office.js +42 -5
- package/dist/cli/src/utils/config.d.ts +5 -0
- package/dist/cli/src/utils/config.js +1 -1
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +34 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +58 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +16 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +58 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +173 -0
- package/dist/commands/server.d.ts +1 -0
- package/dist/commands/server.js +22 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +23 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +12 -0
- package/dist/dashboard/assets/BufferResource-Cf2Uo4_f.js +185 -0
- package/dist/dashboard/assets/CanvasRenderer-DFdMBORe.js +1 -0
- package/dist/dashboard/assets/RenderTargetSystem-CMh8XRf_.js +172 -0
- package/dist/dashboard/assets/WebGLRenderer-B5huw0RY.js +156 -0
- package/dist/dashboard/assets/WebGPURenderer-BdIKurkV.js +41 -0
- package/dist/dashboard/assets/browserAll-BjVJrv1L.js +14 -0
- package/dist/dashboard/assets/index-DwTFo09S.js +344 -0
- package/dist/dashboard/assets/webworkerAll-DMtK63GZ.js +83 -0
- package/dist/dashboard/index.html +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +47 -0
- package/dist/server/api/health-routes.d.ts +3 -0
- package/dist/server/api/health-routes.d.ts.map +1 -0
- package/dist/server/api/health-routes.js +6 -0
- package/dist/server/api/health-routes.js.map +1 -0
- package/dist/server/api/squads-routes.d.ts +7 -0
- package/dist/server/api/squads-routes.d.ts.map +1 -0
- package/dist/server/api/squads-routes.js +91 -0
- package/dist/server/api/squads-routes.js.map +1 -0
- package/dist/server/api/users-routes.d.ts +8 -0
- package/dist/server/api/users-routes.d.ts.map +1 -0
- package/dist/server/api/users-routes.js +45 -0
- package/dist/server/api/users-routes.js.map +1 -0
- package/dist/server/app.d.ts +7 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +109 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/auth/auth-middleware.d.ts +13 -0
- package/dist/server/auth/auth-middleware.d.ts.map +1 -0
- package/dist/server/auth/auth-middleware.js +25 -0
- package/dist/server/auth/auth-middleware.js.map +1 -0
- package/dist/server/auth/auth-routes.d.ts +9 -0
- package/dist/server/auth/auth-routes.d.ts.map +1 -0
- package/dist/server/auth/auth-routes.js +149 -0
- package/dist/server/auth/auth-routes.js.map +1 -0
- package/dist/server/auth/jwt.d.ts +14 -0
- package/dist/server/auth/jwt.d.ts.map +1 -0
- package/dist/server/auth/jwt.js +16 -0
- package/dist/server/auth/jwt.js.map +1 -0
- package/dist/server/auth/password.d.ts +3 -0
- package/dist/server/auth/password.d.ts.map +1 -0
- package/dist/server/auth/password.js +9 -0
- package/dist/server/auth/password.js.map +1 -0
- package/dist/server/bridge/__tests__/chat-handler.test.d.ts +2 -0
- package/dist/server/bridge/__tests__/chat-handler.test.d.ts.map +1 -0
- package/dist/server/bridge/__tests__/chat-handler.test.js +132 -0
- package/dist/server/bridge/__tests__/chat-handler.test.js.map +1 -0
- package/dist/server/bridge/__tests__/chat-integration.test.d.ts +2 -0
- package/dist/server/bridge/__tests__/chat-integration.test.d.ts.map +1 -0
- package/dist/server/bridge/__tests__/chat-integration.test.js +141 -0
- package/dist/server/bridge/__tests__/chat-integration.test.js.map +1 -0
- package/dist/server/bridge/__tests__/claude-bridge.test.d.ts +2 -0
- package/dist/server/bridge/__tests__/claude-bridge.test.d.ts.map +1 -0
- package/dist/server/bridge/__tests__/claude-bridge.test.js +223 -0
- package/dist/server/bridge/__tests__/claude-bridge.test.js.map +1 -0
- package/dist/server/bridge/__tests__/conversation.test.d.ts +2 -0
- package/dist/server/bridge/__tests__/conversation.test.d.ts.map +1 -0
- package/dist/server/bridge/__tests__/conversation.test.js +168 -0
- package/dist/server/bridge/__tests__/conversation.test.js.map +1 -0
- package/dist/server/bridge/__tests__/stream-parser.test.d.ts +2 -0
- package/dist/server/bridge/__tests__/stream-parser.test.d.ts.map +1 -0
- package/dist/server/bridge/__tests__/stream-parser.test.js +66 -0
- package/dist/server/bridge/__tests__/stream-parser.test.js.map +1 -0
- package/dist/server/bridge/chat-handler.d.ts +19 -0
- package/dist/server/bridge/chat-handler.d.ts.map +1 -0
- package/dist/server/bridge/chat-handler.js +104 -0
- package/dist/server/bridge/chat-handler.js.map +1 -0
- package/dist/server/bridge/claude-bridge.d.ts +27 -0
- package/dist/server/bridge/claude-bridge.d.ts.map +1 -0
- package/dist/server/bridge/claude-bridge.js +91 -0
- package/dist/server/bridge/claude-bridge.js.map +1 -0
- package/dist/server/bridge/conversation.d.ts +44 -0
- package/dist/server/bridge/conversation.d.ts.map +1 -0
- package/dist/server/bridge/conversation.js +73 -0
- package/dist/server/bridge/conversation.js.map +1 -0
- package/dist/server/bridge/stream-parser.d.ts +14 -0
- package/dist/server/bridge/stream-parser.d.ts.map +1 -0
- package/dist/server/bridge/stream-parser.js +26 -0
- package/dist/server/bridge/stream-parser.js.map +1 -0
- package/dist/server/config.d.ts +10 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +21 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/db/__tests__/chat-tables.test.d.ts +2 -0
- package/dist/server/db/__tests__/chat-tables.test.d.ts.map +1 -0
- package/dist/server/db/__tests__/chat-tables.test.js +82 -0
- package/dist/server/db/__tests__/chat-tables.test.js.map +1 -0
- package/dist/server/db/connection.d.ts +4 -0
- package/dist/server/db/connection.d.ts.map +1 -0
- package/dist/server/db/connection.js +21 -0
- package/dist/server/db/connection.js.map +1 -0
- package/dist/server/db/migrations.d.ts +4 -0
- package/dist/server/db/migrations.d.ts.map +1 -0
- package/dist/server/db/migrations.js +17 -0
- package/dist/server/db/migrations.js.map +1 -0
- package/dist/server/db/schema.d.ts +2 -0
- package/dist/server/db/schema.d.ts.map +1 -0
- package/dist/server/db/schema.js +44 -0
- package/dist/server/db/schema.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +18 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/__tests__/conversations.test.d.ts +2 -0
- package/dist/server/routes/__tests__/conversations.test.d.ts.map +1 -0
- package/dist/server/routes/__tests__/conversations.test.js +94 -0
- package/dist/server/routes/__tests__/conversations.test.js.map +1 -0
- package/dist/server/routes/conversations.d.ts +8 -0
- package/dist/server/routes/conversations.d.ts.map +1 -0
- package/dist/server/routes/conversations.js +25 -0
- package/dist/server/routes/conversations.js.map +1 -0
- package/dist/server/watcher/file-watcher.d.ts +12 -0
- package/dist/server/watcher/file-watcher.d.ts.map +1 -0
- package/dist/server/watcher/file-watcher.js +68 -0
- package/dist/server/watcher/file-watcher.js.map +1 -0
- package/dist/server/watcher/state-parser.d.ts +77 -0
- package/dist/server/watcher/state-parser.d.ts.map +1 -0
- package/dist/server/watcher/state-parser.js +74 -0
- package/dist/server/watcher/state-parser.js.map +1 -0
- package/dist/server/ws/ws-auth.d.ts +4 -0
- package/dist/server/ws/ws-auth.d.ts.map +1 -0
- package/dist/server/ws/ws-auth.js +42 -0
- package/dist/server/ws/ws-auth.js.map +1 -0
- package/dist/server/ws/ws-handler.d.ts +11 -0
- package/dist/server/ws/ws-handler.d.ts.map +1 -0
- package/dist/server/ws/ws-handler.js +107 -0
- package/dist/server/ws/ws-handler.js.map +1 -0
- package/dist/server/ws/ws-rooms.d.ts +12 -0
- package/dist/server/ws/ws-rooms.d.ts.map +1 -0
- package/dist/server/ws/ws-rooms.js +52 -0
- package/dist/server/ws/ws-rooms.js.map +1 -0
- package/dist/utils/config.d.ts +15 -0
- package/dist/utils/config.js +23 -0
- 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\` |
|
|
143
|
-
| \`/expxagents virtual-office\` |
|
|
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
|
|
18
|
-
|
|
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: {
|
|
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
|
+
}
|