expxagents 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.
- package/assets/agents/_catalog.yaml +11 -0
- package/assets/agents/commercial/account-executive.agent.md +41 -0
- package/assets/agents/commercial/crm-manager.agent.md +41 -0
- package/assets/agents/commercial/pricing-strategist.agent.md +41 -0
- package/assets/agents/commercial/proposal-writer.agent.md +41 -0
- package/assets/agents/commercial/sdr.agent.md +41 -0
- package/assets/agents/development/backend-developer.agent.md +42 -0
- package/assets/agents/development/code-reviewer.agent.md +41 -0
- package/assets/agents/development/devops-engineer.agent.md +41 -0
- package/assets/agents/development/frontend-developer.agent.md +92 -0
- package/assets/agents/development/product-manager.agent.md +41 -0
- package/assets/agents/development/qa-engineer.agent.md +41 -0
- package/assets/agents/development/tech-lead.agent.md +42 -0
- package/assets/agents/development/ux-design-expert.agent.md +108 -0
- package/assets/agents/marketing/brand-guardian.agent.md +40 -0
- package/assets/agents/marketing/content-creator.agent.md +41 -0
- package/assets/agents/marketing/email-marketing.agent.md +41 -0
- package/assets/agents/marketing/landing-page-builder.agent.md +73 -0
- package/assets/agents/marketing/marketing-analyst.agent.md +41 -0
- package/assets/agents/marketing/paid-ads-manager.agent.md +41 -0
- package/assets/agents/marketing/seo-specialist.agent.md +41 -0
- package/assets/agents/marketing/social-media-manager.agent.md +41 -0
- package/assets/core/best-practices/_catalog.yaml +85 -0
- package/assets/core/best-practices/api-documentation.md +137 -0
- package/assets/core/best-practices/blog-post.md +86 -0
- package/assets/core/best-practices/blog-seo.md +91 -0
- package/assets/core/best-practices/code-review.md +97 -0
- package/assets/core/best-practices/copywriting.md +75 -0
- package/assets/core/best-practices/data-analysis.md +93 -0
- package/assets/core/best-practices/deploy-checklist.md +99 -0
- package/assets/core/best-practices/email-newsletter.md +84 -0
- package/assets/core/best-practices/email-sales.md +91 -0
- package/assets/core/best-practices/image-design.md +78 -0
- package/assets/core/best-practices/instagram-feed.md +70 -0
- package/assets/core/best-practices/instagram-reels.md +75 -0
- package/assets/core/best-practices/instagram-stories.md +68 -0
- package/assets/core/best-practices/landing-page.md +279 -0
- package/assets/core/best-practices/linkedin-article.md +83 -0
- package/assets/core/best-practices/linkedin-post.md +84 -0
- package/assets/core/best-practices/researching.md +89 -0
- package/assets/core/best-practices/review.md +95 -0
- package/assets/core/best-practices/sprint-planning.md +91 -0
- package/assets/core/best-practices/strategist.md +95 -0
- package/assets/core/best-practices/technical-writing.md +104 -0
- package/assets/core/best-practices/twitter-post.md +75 -0
- package/assets/core/best-practices/twitter-thread.md +92 -0
- package/assets/core/best-practices/whatsapp-broadcast.md +95 -0
- package/assets/core/best-practices/youtube-script.md +80 -0
- package/assets/core/best-practices/youtube-shorts.md +76 -0
- package/assets/core/prompts/insight-hunter.prompt.md +62 -0
- package/assets/core/runner.pipeline.md +117 -0
- package/assets/core/skills.engine.md +65 -0
- package/assets/core/solution-architect.agent.md +181 -0
- package/assets/templates/_expxagents/_memory/company.md +25 -0
- package/assets/templates/_expxagents/_memory/preferences.md +6 -0
- package/assets/templates/squads/_memory/memories.md +16 -0
- package/bin/expxagents.js +3 -0
- package/dist/cli/src/__tests__/cli.test.d.ts +1 -0
- package/dist/cli/src/__tests__/cli.test.js +23 -0
- package/dist/cli/src/commands/create.d.ts +1 -0
- package/dist/cli/src/commands/create.js +53 -0
- package/dist/cli/src/commands/doctor.d.ts +1 -0
- package/dist/cli/src/commands/doctor.js +86 -0
- package/dist/cli/src/commands/init.d.ts +1 -0
- package/dist/cli/src/commands/init.js +164 -0
- package/dist/cli/src/commands/install.d.ts +1 -0
- package/dist/cli/src/commands/install.js +65 -0
- package/dist/cli/src/commands/list.d.ts +1 -0
- package/dist/cli/src/commands/list.js +58 -0
- package/dist/cli/src/commands/onboarding.d.ts +1 -0
- package/dist/cli/src/commands/onboarding.js +39 -0
- package/dist/cli/src/commands/run.d.ts +1 -0
- package/dist/cli/src/commands/run.js +226 -0
- package/dist/cli/src/commands/server.d.ts +1 -0
- package/dist/cli/src/commands/server.js +22 -0
- package/dist/cli/src/commands/stop.d.ts +1 -0
- package/dist/cli/src/commands/stop.js +23 -0
- package/dist/cli/src/commands/uninstall.d.ts +1 -0
- package/dist/cli/src/commands/uninstall.js +12 -0
- package/dist/cli/src/index.d.ts +1 -0
- package/dist/cli/src/index.js +57 -0
- package/dist/cli/src/utils/config.d.ts +34 -0
- package/dist/cli/src/utils/config.js +79 -0
- package/dist/core/skills-loader.d.ts +16 -0
- package/dist/core/skills-loader.js +61 -0
- package/dist/core/squad-loader.d.ts +26 -0
- package/dist/core/squad-loader.js +51 -0
- package/dist/core/state-manager.d.ts +45 -0
- package/dist/core/state-manager.js +90 -0
- package/dist/core/vitest.config.d.ts +2 -0
- package/dist/core/vitest.config.js +7 -0
- package/package.json +49 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { initCommand } from './commands/init.js';
|
|
3
|
+
import { createCommand } from './commands/create.js';
|
|
4
|
+
import { runCommand } from './commands/run.js';
|
|
5
|
+
import { stopCommand } from './commands/stop.js';
|
|
6
|
+
import { listCommand } from './commands/list.js';
|
|
7
|
+
import { installCommand } from './commands/install.js';
|
|
8
|
+
import { uninstallCommand } from './commands/uninstall.js';
|
|
9
|
+
import { serverCommand } from './commands/server.js';
|
|
10
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
11
|
+
import { onboardingCommand } from './commands/onboarding.js';
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program
|
|
14
|
+
.name('expxagents')
|
|
15
|
+
.description('ExpxAgents — Multi-agent orchestration platform')
|
|
16
|
+
.version('0.1.0');
|
|
17
|
+
program
|
|
18
|
+
.command('init')
|
|
19
|
+
.description('Initialize project (dirs, .env, admin user)')
|
|
20
|
+
.action(initCommand);
|
|
21
|
+
program
|
|
22
|
+
.command('create [description]')
|
|
23
|
+
.description('Create a new squad with Solution Architect')
|
|
24
|
+
.action(createCommand);
|
|
25
|
+
program
|
|
26
|
+
.command('run <name>')
|
|
27
|
+
.description('Execute squad pipeline')
|
|
28
|
+
.action(runCommand);
|
|
29
|
+
program
|
|
30
|
+
.command('stop <name>')
|
|
31
|
+
.description('Stop running squad')
|
|
32
|
+
.action(stopCommand);
|
|
33
|
+
program
|
|
34
|
+
.command('list')
|
|
35
|
+
.description('List squads and skills')
|
|
36
|
+
.action(listCommand);
|
|
37
|
+
program
|
|
38
|
+
.command('install <skill>')
|
|
39
|
+
.description('Install skill from registry')
|
|
40
|
+
.action(installCommand);
|
|
41
|
+
program
|
|
42
|
+
.command('uninstall <skill>')
|
|
43
|
+
.description('Remove skill')
|
|
44
|
+
.action(uninstallCommand);
|
|
45
|
+
program
|
|
46
|
+
.command('server')
|
|
47
|
+
.description('Start the web server')
|
|
48
|
+
.action(serverCommand);
|
|
49
|
+
program
|
|
50
|
+
.command('doctor')
|
|
51
|
+
.description('Run system diagnostics and health checks')
|
|
52
|
+
.action(doctorCommand);
|
|
53
|
+
program
|
|
54
|
+
.command('onboarding')
|
|
55
|
+
.description('Configure company profile and preferences')
|
|
56
|
+
.action(onboardingCommand);
|
|
57
|
+
program.parse();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface CliConfig {
|
|
2
|
+
projectRoot: string;
|
|
3
|
+
squadsDir: string;
|
|
4
|
+
skillsDir: string;
|
|
5
|
+
coreDir: string;
|
|
6
|
+
serverDir: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve project directories relative to CWD.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveConfig(): CliConfig;
|
|
12
|
+
/**
|
|
13
|
+
* Check if current directory is an initialized ExpxAgents project.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isInitialized(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Get the directory where package assets are stored.
|
|
18
|
+
* Resolves to <package-root>/assets/ regardless of compiled file nesting.
|
|
19
|
+
* Works both in development (monorepo) and production (npx/global install).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAssetsDir(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a core asset path (solution-architect, runner, best-practices, etc).
|
|
24
|
+
* Falls back to monorepo path for development.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getCoreAsset(relativePath: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the agent catalog directory.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAgentCatalogDir(): string;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the templates directory.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getTemplateDir(): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve project directories relative to CWD.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveConfig() {
|
|
8
|
+
const projectRoot = process.cwd();
|
|
9
|
+
return {
|
|
10
|
+
projectRoot,
|
|
11
|
+
squadsDir: path.join(projectRoot, 'squads'),
|
|
12
|
+
skillsDir: path.join(projectRoot, 'skills'),
|
|
13
|
+
coreDir: path.join(projectRoot, 'core'),
|
|
14
|
+
serverDir: path.join(projectRoot, 'server'),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if current directory is an initialized ExpxAgents project.
|
|
19
|
+
*/
|
|
20
|
+
export function isInitialized() {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
return fs.existsSync(path.join(cwd, 'squads'))
|
|
23
|
+
&& fs.existsSync(path.join(cwd, '_expxagents', '_memory'));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find the package root by walking up from the compiled file's location
|
|
27
|
+
* until we find a package.json. Works regardless of dist/ nesting depth.
|
|
28
|
+
*/
|
|
29
|
+
function findPackageRoot() {
|
|
30
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
while (dir !== path.dirname(dir)) {
|
|
32
|
+
if (fs.existsSync(path.join(dir, 'package.json')))
|
|
33
|
+
return dir;
|
|
34
|
+
dir = path.dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
throw new Error('Could not find expxagents package root');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the directory where package assets are stored.
|
|
40
|
+
* Resolves to <package-root>/assets/ regardless of compiled file nesting.
|
|
41
|
+
* Works both in development (monorepo) and production (npx/global install).
|
|
42
|
+
*/
|
|
43
|
+
export function getAssetsDir() {
|
|
44
|
+
return path.join(findPackageRoot(), 'assets');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a core asset path (solution-architect, runner, best-practices, etc).
|
|
48
|
+
* Falls back to monorepo path for development.
|
|
49
|
+
*/
|
|
50
|
+
export function getCoreAsset(relativePath) {
|
|
51
|
+
const assetsPath = path.join(getAssetsDir(), 'core', relativePath);
|
|
52
|
+
if (fs.existsSync(assetsPath))
|
|
53
|
+
return assetsPath;
|
|
54
|
+
// Fallback: monorepo development path
|
|
55
|
+
const monoPath = path.join(process.cwd(), '_expxagents', 'core', relativePath);
|
|
56
|
+
if (fs.existsSync(monoPath))
|
|
57
|
+
return monoPath;
|
|
58
|
+
return assetsPath;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the agent catalog directory.
|
|
62
|
+
*/
|
|
63
|
+
export function getAgentCatalogDir() {
|
|
64
|
+
const assetsPath = path.join(getAssetsDir(), 'agents');
|
|
65
|
+
if (fs.existsSync(assetsPath))
|
|
66
|
+
return assetsPath;
|
|
67
|
+
const monoPath = path.join(process.cwd(), 'agents');
|
|
68
|
+
return fs.existsSync(monoPath) ? monoPath : assetsPath;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the templates directory.
|
|
72
|
+
*/
|
|
73
|
+
export function getTemplateDir() {
|
|
74
|
+
const assetsPath = path.join(getAssetsDir(), 'templates');
|
|
75
|
+
if (fs.existsSync(assetsPath))
|
|
76
|
+
return assetsPath;
|
|
77
|
+
const monoPath = path.join(process.cwd(), 'templates');
|
|
78
|
+
return fs.existsSync(monoPath) ? monoPath : assetsPath;
|
|
79
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Skill {
|
|
2
|
+
name: string;
|
|
3
|
+
type: 'mcp' | 'script' | 'prompt';
|
|
4
|
+
description: string;
|
|
5
|
+
server?: string;
|
|
6
|
+
instructions: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Load a single skill from its directory (expects SKILL.md inside).
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadSkill(skillDir: string): Skill;
|
|
12
|
+
/**
|
|
13
|
+
* Load all skills from the skills/ directory.
|
|
14
|
+
* Returns empty array if directory doesn't exist.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadSkills(skillsDir: string): Skill[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Parse SKILL.md frontmatter (YAML between --- delimiters) and body content.
|
|
5
|
+
*/
|
|
6
|
+
function parseFrontmatter(content) {
|
|
7
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
8
|
+
if (!match) {
|
|
9
|
+
throw new Error('SKILL.md must have YAML frontmatter between --- delimiters');
|
|
10
|
+
}
|
|
11
|
+
const rawMeta = match[1];
|
|
12
|
+
const body = match[2].trim();
|
|
13
|
+
const meta = {};
|
|
14
|
+
for (const line of rawMeta.split('\n')) {
|
|
15
|
+
const colonIdx = line.indexOf(':');
|
|
16
|
+
if (colonIdx === -1)
|
|
17
|
+
continue;
|
|
18
|
+
const key = line.slice(0, colonIdx).trim();
|
|
19
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
20
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
21
|
+
value = value.slice(1, -1);
|
|
22
|
+
}
|
|
23
|
+
meta[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return { meta, body };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load a single skill from its directory (expects SKILL.md inside).
|
|
29
|
+
*/
|
|
30
|
+
export function loadSkill(skillDir) {
|
|
31
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
32
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
33
|
+
const { meta, body } = parseFrontmatter(content);
|
|
34
|
+
return {
|
|
35
|
+
name: meta.name,
|
|
36
|
+
type: meta.type,
|
|
37
|
+
description: meta.description,
|
|
38
|
+
server: meta.server || undefined,
|
|
39
|
+
instructions: body,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load all skills from the skills/ directory.
|
|
44
|
+
* Returns empty array if directory doesn't exist.
|
|
45
|
+
*/
|
|
46
|
+
export function loadSkills(skillsDir) {
|
|
47
|
+
if (!fs.existsSync(skillsDir)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
51
|
+
const skills = [];
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (!entry.isDirectory())
|
|
54
|
+
continue;
|
|
55
|
+
const skillMdPath = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
56
|
+
if (!fs.existsSync(skillMdPath))
|
|
57
|
+
continue;
|
|
58
|
+
skills.push(loadSkill(path.join(skillsDir, entry.name)));
|
|
59
|
+
}
|
|
60
|
+
return skills;
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
icon: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PipelineStep {
|
|
8
|
+
id: string;
|
|
9
|
+
agent: string;
|
|
10
|
+
label: string;
|
|
11
|
+
deliverFrom?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SquadConfig {
|
|
14
|
+
squad: {
|
|
15
|
+
code: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
agents: AgentConfig[];
|
|
20
|
+
skills: string[];
|
|
21
|
+
pipeline: {
|
|
22
|
+
steps: PipelineStep[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function loadSquad(squadDir: string): SquadConfig;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
export function loadSquad(squadDir) {
|
|
5
|
+
const yamlPath = path.join(squadDir, 'squad.yaml');
|
|
6
|
+
if (!fs.existsSync(yamlPath)) {
|
|
7
|
+
throw new Error(`squad.yaml not found in ${squadDir}`);
|
|
8
|
+
}
|
|
9
|
+
const raw = fs.readFileSync(yamlPath, 'utf-8');
|
|
10
|
+
const parsed = yaml.load(raw);
|
|
11
|
+
if (!parsed || typeof parsed !== 'object' || !parsed.squad) {
|
|
12
|
+
throw new Error('Missing required field: squad');
|
|
13
|
+
}
|
|
14
|
+
const squad = parsed.squad;
|
|
15
|
+
const requiredFields = ['code', 'name', 'description', 'icon', 'agents', 'pipeline'];
|
|
16
|
+
for (const field of requiredFields) {
|
|
17
|
+
if (squad[field] === undefined || squad[field] === null) {
|
|
18
|
+
throw new Error(`Missing required field: squad.${field}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const agents = squad.agents;
|
|
22
|
+
if (!Array.isArray(agents) || agents.length === 0) {
|
|
23
|
+
throw new Error('Missing required field: squad.agents must be a non-empty array');
|
|
24
|
+
}
|
|
25
|
+
const pipeline = squad.pipeline;
|
|
26
|
+
if (!pipeline || !pipeline.steps) {
|
|
27
|
+
throw new Error('Missing required field: squad.pipeline.steps');
|
|
28
|
+
}
|
|
29
|
+
const steps = pipeline.steps;
|
|
30
|
+
const agentIds = new Set(agents.map(a => a.id));
|
|
31
|
+
for (const step of steps) {
|
|
32
|
+
if (!agentIds.has(step.agent)) {
|
|
33
|
+
throw new Error(`Agent "${step.agent}" not found in agents list (referenced by step "${step.id}")`);
|
|
34
|
+
}
|
|
35
|
+
if (step.deliverFrom && !agentIds.has(step.deliverFrom)) {
|
|
36
|
+
throw new Error(`deliverFrom agent "${step.deliverFrom}" not found in agents list (referenced by step "${step.id}")`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const skills = squad.skills ?? [];
|
|
40
|
+
return {
|
|
41
|
+
squad: {
|
|
42
|
+
code: squad.code,
|
|
43
|
+
name: squad.name,
|
|
44
|
+
description: squad.description,
|
|
45
|
+
icon: squad.icon,
|
|
46
|
+
agents,
|
|
47
|
+
skills,
|
|
48
|
+
pipeline: { steps },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SquadConfig } from './squad-loader.js';
|
|
2
|
+
export type AgentStatus = 'idle' | 'working' | 'delivering' | 'done' | 'checkpoint';
|
|
3
|
+
export type SquadStatus = 'idle' | 'running' | 'completed' | 'checkpoint';
|
|
4
|
+
export interface Agent {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
status: AgentStatus;
|
|
9
|
+
stepIndex: number;
|
|
10
|
+
stepLabel: string;
|
|
11
|
+
deliverTo: string | null;
|
|
12
|
+
desk: {
|
|
13
|
+
col: number;
|
|
14
|
+
row: number;
|
|
15
|
+
};
|
|
16
|
+
message: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface Handoff {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
message: string;
|
|
22
|
+
completedAt: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SquadState {
|
|
25
|
+
squad: string;
|
|
26
|
+
status: SquadStatus;
|
|
27
|
+
step: {
|
|
28
|
+
current: number;
|
|
29
|
+
total: number;
|
|
30
|
+
label: string;
|
|
31
|
+
};
|
|
32
|
+
agents: Agent[];
|
|
33
|
+
handoff: Handoff | null;
|
|
34
|
+
startedAt: string | null;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
/** Delay in ms between 'delivering' and 'done' to allow handoff animation */
|
|
38
|
+
export declare const HANDOFF_DELAY_MS = 2000;
|
|
39
|
+
export declare function createInitialState(config: SquadConfig): SquadState;
|
|
40
|
+
export declare function writeState(squadDir: string, state: SquadState): void;
|
|
41
|
+
export declare function readState(squadDir: string): SquadState | null;
|
|
42
|
+
export declare function updateAgentStatus(state: SquadState, agentId: string, status: AgentStatus): SquadState;
|
|
43
|
+
export declare function updateStep(state: SquadState, current: number, label: string): SquadState;
|
|
44
|
+
export declare function setHandoff(state: SquadState, fromId: string, toId: string, message: string): SquadState;
|
|
45
|
+
export declare function setSquadStatus(state: SquadState, status: SquadStatus): SquadState;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/** Delay in ms between 'delivering' and 'done' to allow handoff animation */
|
|
4
|
+
export const HANDOFF_DELAY_MS = 2000;
|
|
5
|
+
export function createInitialState(config) {
|
|
6
|
+
const { squad } = config;
|
|
7
|
+
const agentCount = squad.agents.length;
|
|
8
|
+
const cols = Math.ceil(Math.sqrt(agentCount));
|
|
9
|
+
const agents = squad.agents.map((agentConfig, index) => {
|
|
10
|
+
const col = (index % cols) + 1;
|
|
11
|
+
const row = Math.floor(index / cols) + 1;
|
|
12
|
+
const stepIdx = squad.pipeline.steps.findIndex(s => s.agent === agentConfig.id);
|
|
13
|
+
const step = stepIdx !== -1 ? squad.pipeline.steps[stepIdx] : null;
|
|
14
|
+
return {
|
|
15
|
+
id: agentConfig.id,
|
|
16
|
+
name: agentConfig.name,
|
|
17
|
+
icon: agentConfig.icon,
|
|
18
|
+
status: 'idle',
|
|
19
|
+
stepIndex: stepIdx + 1,
|
|
20
|
+
stepLabel: step?.label ?? '',
|
|
21
|
+
deliverTo: null,
|
|
22
|
+
desk: { col, row },
|
|
23
|
+
message: null,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
squad: squad.code,
|
|
28
|
+
status: 'idle',
|
|
29
|
+
step: { current: 0, total: squad.pipeline.steps.length, label: '' },
|
|
30
|
+
agents,
|
|
31
|
+
handoff: null,
|
|
32
|
+
startedAt: null,
|
|
33
|
+
updatedAt: new Date().toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function writeState(squadDir, state) {
|
|
37
|
+
const statePath = path.join(squadDir, 'state.json');
|
|
38
|
+
const tmpPath = path.join(squadDir, 'state.json.tmp');
|
|
39
|
+
const data = JSON.stringify(state, null, 2);
|
|
40
|
+
fs.writeFileSync(tmpPath, data, 'utf-8');
|
|
41
|
+
fs.renameSync(tmpPath, statePath);
|
|
42
|
+
}
|
|
43
|
+
export function readState(squadDir) {
|
|
44
|
+
const statePath = path.join(squadDir, 'state.json');
|
|
45
|
+
if (!fs.existsSync(statePath)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const raw = fs.readFileSync(statePath, 'utf-8');
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
export function updateAgentStatus(state, agentId, status) {
|
|
52
|
+
const agentIndex = state.agents.findIndex(a => a.id === agentId);
|
|
53
|
+
if (agentIndex === -1) {
|
|
54
|
+
throw new Error(`Agent "${agentId}" not found in state`);
|
|
55
|
+
}
|
|
56
|
+
const agents = state.agents.map((a, i) => i === agentIndex ? { ...a, status } : a);
|
|
57
|
+
return {
|
|
58
|
+
...state,
|
|
59
|
+
agents,
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function updateStep(state, current, label) {
|
|
64
|
+
return {
|
|
65
|
+
...state,
|
|
66
|
+
step: { ...state.step, current, label },
|
|
67
|
+
updatedAt: new Date().toISOString(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function setHandoff(state, fromId, toId, message) {
|
|
71
|
+
const agents = state.agents.map(a => a.id === fromId ? { ...a, deliverTo: toId } : a);
|
|
72
|
+
return {
|
|
73
|
+
...state,
|
|
74
|
+
agents,
|
|
75
|
+
handoff: {
|
|
76
|
+
from: fromId,
|
|
77
|
+
to: toId,
|
|
78
|
+
message,
|
|
79
|
+
completedAt: new Date().toISOString(),
|
|
80
|
+
},
|
|
81
|
+
updatedAt: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function setSquadStatus(state, status) {
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
status,
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expxagents",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-agent orchestration platform for AI squads",
|
|
5
|
+
"author": "ExpxAgents",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/bittencourtthulio/expxagents"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agents",
|
|
15
|
+
"orchestration",
|
|
16
|
+
"squad",
|
|
17
|
+
"multi-agent",
|
|
18
|
+
"claude"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"expxagents": "./bin/expxagents.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"dist/cli/",
|
|
26
|
+
"dist/core/",
|
|
27
|
+
"assets/"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc && node scripts/copy-assets.js",
|
|
34
|
+
"prepublishOnly": "npm run build",
|
|
35
|
+
"dev": "tsc --watch",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^13.0.0",
|
|
41
|
+
"js-yaml": "^4.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/js-yaml": "^4.0.9",
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"typescript": "^5.7.0",
|
|
47
|
+
"vitest": "^3.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|