expxagents 0.16.2 → 0.17.1
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/core/solution-architect.agent.md +16 -10
- package/dist/cli/src/commands/init.js +18 -0
- package/dist/cli/src/commands/list.js +100 -28
- package/dist/cli/src/commands/reorganize.d.ts +1 -0
- package/dist/cli/src/commands/reorganize.js +296 -0
- package/dist/cli/src/commands/run.js +7 -5
- package/dist/cli/src/commands/server.d.ts +3 -1
- package/dist/cli/src/commands/server.js +19 -1
- package/dist/cli/src/commands/stop.js +4 -3
- package/dist/cli/src/index.js +7 -1
- package/dist/cli/src/runners/__tests__/provider-runner.test.d.ts +1 -0
- package/dist/cli/src/runners/__tests__/provider-runner.test.js +86 -0
- package/dist/cli/src/runners/anthropic-runner.d.ts +1 -0
- package/dist/cli/src/runners/anthropic-runner.js +15 -0
- package/dist/cli/src/runners/openai-runner.d.ts +1 -0
- package/dist/cli/src/runners/openai-runner.js +13 -0
- package/dist/cli/src/runners/provider-runner.d.ts +2 -0
- package/dist/cli/src/runners/provider-runner.js +13 -0
- package/dist/core/squad-loader.d.ts +10 -0
- package/dist/core/squad-loader.js +62 -0
- package/dist/dashboard/assets/{BufferResource-DuDG7xRm.js → BufferResource-sdeVYlCP.js} +1 -1
- package/dist/dashboard/assets/{CanvasRenderer-DgtlELOg.js → CanvasRenderer-BHTY8nWw.js} +1 -1
- package/dist/dashboard/assets/{JarvisView-BvMMhjfv.js → JarvisView-lYnkwpPj.js} +1 -1
- package/dist/dashboard/assets/{RenderTargetSystem-BDF6yZwH.js → RenderTargetSystem-D8lfYGjb.js} +1 -1
- package/dist/dashboard/assets/{ThreeBackground-DVvmqB4o.js → ThreeBackground-DIpxzfWj.js} +1 -1
- package/dist/dashboard/assets/{WebGLRenderer-50NXaS3u.js → WebGLRenderer-D6I_TSkc.js} +1 -1
- package/dist/dashboard/assets/{WebGPURenderer-Dng8T71e.js → WebGPURenderer-BYclzvEl.js} +1 -1
- package/dist/dashboard/assets/{browserAll-CmiVZDVb.js → browserAll-C-f1RWmb.js} +1 -1
- package/dist/dashboard/assets/{index-DbfeP1Uv.js → index-CGqUxwDX.js} +72 -72
- package/dist/dashboard/assets/{webworkerAll-CDMUUiju.js → webworkerAll-BRIGXnpD.js} +1 -1
- package/dist/dashboard/index.html +1 -1
- package/dist/server/api/__tests__/webhook-routes.test.d.ts +2 -0
- package/dist/server/api/__tests__/webhook-routes.test.d.ts.map +1 -0
- package/dist/server/api/__tests__/webhook-routes.test.js +111 -0
- package/dist/server/api/__tests__/webhook-routes.test.js.map +1 -0
- package/dist/server/api/squads-routes.d.ts.map +1 -1
- package/dist/server/api/squads-routes.js +107 -46
- package/dist/server/api/squads-routes.js.map +1 -1
- package/dist/server/api/webhook-routes.d.ts +9 -0
- package/dist/server/api/webhook-routes.d.ts.map +1 -0
- package/dist/server/api/webhook-routes.js +44 -0
- package/dist/server/api/webhook-routes.js.map +1 -0
- package/dist/server/app.d.ts +2 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +10 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/types/a2ui.d.ts +27 -0
- package/dist/server/types/a2ui.d.ts.map +1 -0
- package/dist/server/types/a2ui.js +2 -0
- package/dist/server/types/a2ui.js.map +1 -0
- package/dist/server/watcher/__tests__/file-watcher.test.js +49 -1
- package/dist/server/watcher/__tests__/file-watcher.test.js.map +1 -1
- package/dist/server/watcher/file-watcher.d.ts +4 -0
- package/dist/server/watcher/file-watcher.d.ts.map +1 -1
- package/dist/server/watcher/file-watcher.js +27 -1
- package/dist/server/watcher/file-watcher.js.map +1 -1
- package/package.json +12 -8
|
@@ -19,16 +19,20 @@ You are the Solution Architect of ExpxAgents. You design AI agent squads that ex
|
|
|
19
19
|
- **Role:** Design squads by understanding user needs, researching context, and generating complete configurations
|
|
20
20
|
- **Communication:** Clear, structured, asks precise questions
|
|
21
21
|
|
|
22
|
-
## Discovery Phase (max
|
|
22
|
+
## Discovery Phase (max 7 questions)
|
|
23
23
|
|
|
24
24
|
Before designing, gather requirements through focused questions:
|
|
25
25
|
|
|
26
|
-
1. **
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
1. **Hierarquia:** Qual o Setor, Grupo e Sessão desta squad?
|
|
27
|
+
- Setores comuns: marketing, comercial, desenvolvimento, suporte, financeiro, rh, operações, estrategia, design, juridico, administrativo
|
|
28
|
+
- Se a descrição da squad já deixa claro, infira e apresente como sugestão para confirmação.
|
|
29
|
+
- Exemplo: "Esta squad parece ser de **marketing > redes-sociais > instagram**. Confirma?"
|
|
30
|
+
- A resposta definirá o diretório: `squads/<setor>/<grupo>/<sessao>/<code>/`
|
|
31
|
+
2. **Objetivo:** Qual resultado específico esta squad deve produzir?
|
|
32
|
+
3. **Contexto:** Quem é o público-alvo e qual plataforma/formato?
|
|
33
|
+
4. **Processo:** Há etapas ou fases específicas que você quer no fluxo?
|
|
34
|
+
5. **Qualidade:** Quais são os critérios de qualidade para o output final?
|
|
35
|
+
6. **Referências:** Há perfis de referência, concorrentes ou exemplos para investigar?
|
|
32
36
|
|
|
33
37
|
If the user provides reference URLs, delegate to the **Insight Hunter** to investigate before designing.
|
|
34
38
|
|
|
@@ -48,8 +52,10 @@ After discovery, generate the complete squad structure:
|
|
|
48
52
|
|
|
49
53
|
```yaml
|
|
50
54
|
squad:
|
|
51
|
-
code: <
|
|
52
|
-
|
|
55
|
+
code: <sessao>-<squad-name> # e.g., instagram-publicador
|
|
56
|
+
setor: <setor> # e.g., marketing
|
|
57
|
+
grupo: <grupo> # e.g., redes-sociais
|
|
58
|
+
sessao: <sessao> # e.g., instagram
|
|
53
59
|
name: <Human Readable Name>
|
|
54
60
|
description: <What this squad produces>
|
|
55
61
|
icon: <emoji-name>
|
|
@@ -197,4 +203,4 @@ Before presenting the design to the user:
|
|
|
197
203
|
|
|
198
204
|
Present the complete structure to the user and ask for confirmation before writing files.
|
|
199
205
|
|
|
200
|
-
After confirmation, create all files in the `squads/<code>/` directory.
|
|
206
|
+
After confirmation, create all files in the `squads/<setor>/<grupo>/<sessao>/<code>/` directory.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
4
5
|
import { getTemplateDir, getAssetsDir } from '../utils/config.js';
|
|
5
6
|
function getDefaultTemplate(file) {
|
|
6
7
|
if (file === 'company.md') {
|
|
@@ -396,6 +397,23 @@ BRIDGE_TIMEOUT_MS=300000
|
|
|
396
397
|
fs.writeFileSync(gitignorePath, gitignoreEntries.join('\n') + '\n', 'utf-8');
|
|
397
398
|
console.log(' Created .gitignore');
|
|
398
399
|
}
|
|
400
|
+
// Install Python dependencies
|
|
401
|
+
const pythonDeps = ['aiohttp', 'aiofiles', 'python-dotenv'];
|
|
402
|
+
console.log(`\n Installing Python dependencies (${pythonDeps.join(', ')})...`);
|
|
403
|
+
try {
|
|
404
|
+
execSync(`pip3 install ${pythonDeps.join(' ')}`, { stdio: 'inherit' });
|
|
405
|
+
console.log(' Python dependencies installed.');
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
try {
|
|
409
|
+
execSync(`pip install ${pythonDeps.join(' ')}`, { stdio: 'inherit' });
|
|
410
|
+
console.log(' Python dependencies installed.');
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
console.warn(' Warning: Could not install Python dependencies. Make sure pip3/pip is available and run:');
|
|
414
|
+
console.warn(` pip3 install ${pythonDeps.join(' ')}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
399
417
|
if (update) {
|
|
400
418
|
console.log('\nUpdate complete! Assets refreshed to latest version.');
|
|
401
419
|
}
|
|
@@ -1,49 +1,121 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
4
|
import { loadSkills } from '../../../core/skills-loader.js';
|
|
5
5
|
import { readState } from '../../../core/state-manager.js';
|
|
6
|
+
function walkSquads(squadsDir) {
|
|
7
|
+
if (!fs.existsSync(squadsDir))
|
|
8
|
+
return [];
|
|
9
|
+
const entries = [];
|
|
10
|
+
function walk(dir) {
|
|
11
|
+
let dirEntries;
|
|
12
|
+
try {
|
|
13
|
+
dirEntries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (const entry of dirEntries) {
|
|
19
|
+
if (!entry.isDirectory())
|
|
20
|
+
continue;
|
|
21
|
+
if (entry.name.startsWith('.') || entry.name.startsWith('_'))
|
|
22
|
+
continue;
|
|
23
|
+
const fullPath = path.join(dir, entry.name);
|
|
24
|
+
const yamlPath = path.join(fullPath, 'squad.yaml');
|
|
25
|
+
if (fs.existsSync(yamlPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = fs.readFileSync(yamlPath, 'utf-8');
|
|
28
|
+
const parsed = yaml.load(raw);
|
|
29
|
+
const s = parsed?.squad;
|
|
30
|
+
if (s) {
|
|
31
|
+
const state = readState(fullPath);
|
|
32
|
+
entries.push({
|
|
33
|
+
code: s.code ?? entry.name,
|
|
34
|
+
name: s.name ?? entry.name,
|
|
35
|
+
description: s.description ?? '',
|
|
36
|
+
setor: s.setor,
|
|
37
|
+
grupo: s.grupo,
|
|
38
|
+
sessao: s.sessao,
|
|
39
|
+
agentCount: Array.isArray(s.agents) ? s.agents.length : 0,
|
|
40
|
+
stepCount: Array.isArray(s.pipeline?.steps) ? s.pipeline.steps.length : 0,
|
|
41
|
+
status: state?.status ?? 'idle',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
entries.push({
|
|
47
|
+
code: entry.name,
|
|
48
|
+
name: entry.name,
|
|
49
|
+
description: `error loading: ${err.message}`,
|
|
50
|
+
agentCount: 0,
|
|
51
|
+
stepCount: 0,
|
|
52
|
+
status: 'error',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Do NOT recurse into squad directories
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
walk(fullPath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
walk(squadsDir);
|
|
63
|
+
return entries;
|
|
64
|
+
}
|
|
65
|
+
function capitalize(s) {
|
|
66
|
+
return s
|
|
67
|
+
.split('-')
|
|
68
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
69
|
+
.join(' ');
|
|
70
|
+
}
|
|
6
71
|
export async function listCommand() {
|
|
7
72
|
const cwd = process.cwd();
|
|
8
73
|
const squadsDir = path.join(cwd, 'squads');
|
|
9
74
|
const skillsDir = path.join(cwd, 'skills');
|
|
10
|
-
// List squads
|
|
11
75
|
console.log('Squads:');
|
|
12
76
|
console.log('-------');
|
|
13
77
|
if (fs.existsSync(squadsDir)) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
78
|
+
const allSquads = walkSquads(squadsDir);
|
|
79
|
+
if (allSquads.length === 0) {
|
|
80
|
+
console.log(' No squads found. Run `expxagents create` to create one.\n');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const hierarchical = allSquads
|
|
84
|
+
.filter((s) => s.setor)
|
|
85
|
+
.sort((a, b) => {
|
|
86
|
+
const ak = `${a.setor}/${a.grupo}/${a.sessao}/${a.code}`;
|
|
87
|
+
const bk = `${b.setor}/${b.grupo}/${b.sessao}/${b.code}`;
|
|
88
|
+
return ak.localeCompare(bk);
|
|
89
|
+
});
|
|
90
|
+
const flat = allSquads.filter((s) => !s.setor);
|
|
91
|
+
// Render hierarchical squads
|
|
92
|
+
let lastHeader = '';
|
|
93
|
+
for (const s of hierarchical) {
|
|
94
|
+
const header = `${capitalize(s.setor)} > ${capitalize(s.grupo ?? '')} > ${capitalize(s.sessao ?? '')}`;
|
|
95
|
+
if (header !== lastHeader) {
|
|
96
|
+
console.log(`\n ${header}`);
|
|
97
|
+
lastHeader = header;
|
|
98
|
+
}
|
|
99
|
+
console.log(` ${s.name} (${s.code})`);
|
|
100
|
+
console.log(` Status: ${s.status} | Agents: ${s.agentCount} | Steps: ${s.stepCount}`);
|
|
101
|
+
console.log(` ${s.description}`);
|
|
34
102
|
}
|
|
35
|
-
|
|
36
|
-
|
|
103
|
+
// Render flat (legacy) squads
|
|
104
|
+
if (flat.length > 0) {
|
|
105
|
+
console.log('\n [sem hierarquia]');
|
|
106
|
+
for (const s of flat) {
|
|
107
|
+
console.log(` ${s.name} (${s.code})`);
|
|
108
|
+
console.log(` Status: ${s.status} | Agents: ${s.agentCount} | Steps: ${s.stepCount}`);
|
|
109
|
+
console.log(` ${s.description}`);
|
|
110
|
+
}
|
|
37
111
|
}
|
|
38
|
-
|
|
39
|
-
if (!found) {
|
|
40
|
-
console.log(' No squads found. Run `expxagents create` to create one.\n');
|
|
112
|
+
console.log('');
|
|
41
113
|
}
|
|
42
114
|
}
|
|
43
115
|
else {
|
|
44
116
|
console.log(' No squads directory. Run `expxagents init` first.\n');
|
|
45
117
|
}
|
|
46
|
-
// List skills
|
|
118
|
+
// List skills (unchanged)
|
|
47
119
|
console.log('Skills:');
|
|
48
120
|
console.log('-------');
|
|
49
121
|
const skills = loadSkills(skillsDir);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function reorganizeCommand(): Promise<void>;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import * as readline from 'readline';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
// Walk squadsDir recursively, return all squad summaries
|
|
7
|
+
function discoverAllSquads(squadsDir) {
|
|
8
|
+
const result = [];
|
|
9
|
+
function walk(dir) {
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (!entry.isDirectory())
|
|
19
|
+
continue;
|
|
20
|
+
if (entry.name.startsWith('.') || entry.name.startsWith('_'))
|
|
21
|
+
continue;
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
const yamlPath = path.join(fullPath, 'squad.yaml');
|
|
24
|
+
if (fs.existsSync(yamlPath)) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = fs.readFileSync(yamlPath, 'utf-8');
|
|
27
|
+
const parsed = yaml.load(raw);
|
|
28
|
+
const s = parsed?.squad;
|
|
29
|
+
if (s) {
|
|
30
|
+
result.push({
|
|
31
|
+
code: s.code ?? entry.name,
|
|
32
|
+
name: s.name ?? entry.name,
|
|
33
|
+
description: s.description ?? '',
|
|
34
|
+
setor: s.setor,
|
|
35
|
+
grupo: s.grupo,
|
|
36
|
+
sessao: s.sessao,
|
|
37
|
+
dir: fullPath,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Skip malformed YAML
|
|
43
|
+
}
|
|
44
|
+
// Don't recurse into squad directories
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
walk(fullPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
walk(squadsDir);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
// Call Claude to infer hierarchy for a list of squads
|
|
55
|
+
async function inferHierarchyWithClaude(squads) {
|
|
56
|
+
const squadList = squads
|
|
57
|
+
.map((s) => `- code: ${s.code}, name: "${s.name}", description: "${s.description}"`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
const prompt = `Você é um classificador de squads de IA para uma software house.
|
|
60
|
+
Para cada squad abaixo, identifique o Setor, Grupo e Sessão mais adequados.
|
|
61
|
+
|
|
62
|
+
Setores comuns: marketing, comercial, desenvolvimento, suporte, financeiro, rh, operações, estrategia, design, juridico, administrativo
|
|
63
|
+
|
|
64
|
+
Retorne APENAS um JSON válido (sem markdown, sem explicações), no seguinte formato:
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
"code": "<squad-code>",
|
|
68
|
+
"setor": "<setor-em-lowercase-sem-acentos>",
|
|
69
|
+
"grupo": "<grupo-em-lowercase-sem-acentos>",
|
|
70
|
+
"sessao": "<sessao-em-lowercase-sem-acentos>",
|
|
71
|
+
"confidence": "high"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
Use "confidence": "low" quando não tiver certeza da classificação.
|
|
76
|
+
Use hifens em vez de espaços (ex: "redes-sociais", não "redes sociais").
|
|
77
|
+
|
|
78
|
+
Squads para classificar:
|
|
79
|
+
${squadList}`;
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
let output = '';
|
|
82
|
+
const child = spawn('claude', ['-p', prompt], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
83
|
+
child.stdout?.on('data', (d) => { output += d.toString(); });
|
|
84
|
+
child.on('error', reject);
|
|
85
|
+
child.on('exit', (code) => {
|
|
86
|
+
if (code !== 0) {
|
|
87
|
+
reject(new Error(`Claude exited with code ${code}`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
// Extract JSON from output (Claude may include reasoning text)
|
|
92
|
+
const jsonMatch = output.match(/\[[\s\S]*\]/);
|
|
93
|
+
if (!jsonMatch) {
|
|
94
|
+
reject(new Error('No JSON array found in Claude output'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
resolve(JSON.parse(jsonMatch[0]));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
reject(new Error(`Failed to parse Claude output: ${err.message}`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function ask(rl, question) {
|
|
106
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
107
|
+
}
|
|
108
|
+
// Update squad.yaml with new hierarchy fields, remove old 'group' field
|
|
109
|
+
function updateSquadYaml(squadDir, setor, grupo, sessao) {
|
|
110
|
+
const yamlPath = path.join(squadDir, 'squad.yaml');
|
|
111
|
+
const raw = fs.readFileSync(yamlPath, 'utf-8');
|
|
112
|
+
const parsed = yaml.load(raw);
|
|
113
|
+
parsed.squad.setor = setor;
|
|
114
|
+
parsed.squad.grupo = grupo;
|
|
115
|
+
parsed.squad.sessao = sessao;
|
|
116
|
+
delete parsed.squad.group; // remove deprecated field
|
|
117
|
+
fs.writeFileSync(yamlPath, yaml.dump(parsed), 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
// Update cross-squad path references in ALL squad.yaml files after a squad moved
|
|
120
|
+
function updateCrossReferences(squadsDir, oldPath, newPath) {
|
|
121
|
+
const crossFields = ['design_system', 'brand_guidelines', 'assets_path'];
|
|
122
|
+
// Get the old relative path from project root (e.g., "squads/design-system/...")
|
|
123
|
+
// oldPath is absolute; we need relative from cwd
|
|
124
|
+
const cwd = process.cwd();
|
|
125
|
+
const oldRel = path.relative(cwd, oldPath).replace(/\\/g, '/');
|
|
126
|
+
const newRel = path.relative(cwd, newPath).replace(/\\/g, '/');
|
|
127
|
+
function walkForYaml(dir) {
|
|
128
|
+
let entries;
|
|
129
|
+
try {
|
|
130
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (!entry.isDirectory())
|
|
137
|
+
continue;
|
|
138
|
+
if (entry.name.startsWith('.') || entry.name.startsWith('_'))
|
|
139
|
+
continue;
|
|
140
|
+
const fullPath = path.join(dir, entry.name);
|
|
141
|
+
const yamlPath = path.join(fullPath, 'squad.yaml');
|
|
142
|
+
if (fs.existsSync(yamlPath)) {
|
|
143
|
+
try {
|
|
144
|
+
const raw = fs.readFileSync(yamlPath, 'utf-8');
|
|
145
|
+
const parsed = yaml.load(raw);
|
|
146
|
+
let changed = false;
|
|
147
|
+
for (const field of crossFields) {
|
|
148
|
+
const val = parsed.squad?.[field];
|
|
149
|
+
if (typeof val === 'string' && val.startsWith(oldRel)) {
|
|
150
|
+
parsed.squad[field] = val.replace(oldRel, newRel);
|
|
151
|
+
changed = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (changed) {
|
|
155
|
+
fs.writeFileSync(yamlPath, yaml.dump(parsed), 'utf-8');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Skip
|
|
160
|
+
}
|
|
161
|
+
// Don't recurse into squad dirs
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
walkForYaml(fullPath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
walkForYaml(squadsDir);
|
|
169
|
+
}
|
|
170
|
+
export async function reorganizeCommand() {
|
|
171
|
+
const cwd = process.cwd();
|
|
172
|
+
const squadsDir = path.join(cwd, 'squads');
|
|
173
|
+
if (!fs.existsSync(squadsDir)) {
|
|
174
|
+
console.error('No squads directory found. Run `expxagents init` first.');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
console.log('Escaneando squads...\n');
|
|
178
|
+
const allSquads = discoverAllSquads(squadsDir);
|
|
179
|
+
if (allSquads.length === 0) {
|
|
180
|
+
console.log('Nenhuma squad encontrada.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Separate squads that already have hierarchy
|
|
184
|
+
const alreadyClassified = allSquads.filter((s) => s.setor && s.grupo && s.sessao);
|
|
185
|
+
const needClassification = allSquads.filter((s) => !s.setor || !s.grupo || !s.sessao);
|
|
186
|
+
if (alreadyClassified.length > 0) {
|
|
187
|
+
console.log(`${alreadyClassified.length} squad(s) já classificada(s) — sem alterações necessárias.`);
|
|
188
|
+
}
|
|
189
|
+
if (needClassification.length === 0) {
|
|
190
|
+
console.log('Todas as squads já estão classificadas!');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
console.log(`\n${needClassification.length} squad(s) precisam de classificação. Consultando IA...\n`);
|
|
194
|
+
let inferences = [];
|
|
195
|
+
try {
|
|
196
|
+
inferences = await inferHierarchyWithClaude(needClassification);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.error(`Falha ao consultar IA: ${err.message}`);
|
|
200
|
+
console.log('Você precisará classificar manualmente cada squad.');
|
|
201
|
+
inferences = [];
|
|
202
|
+
}
|
|
203
|
+
// Map inferences by code
|
|
204
|
+
const inferenceMap = new Map(inferences.map((i) => [i.code, i]));
|
|
205
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
206
|
+
const highConfidence = [];
|
|
207
|
+
const lowConfidence = [];
|
|
208
|
+
for (const squad of needClassification) {
|
|
209
|
+
const inference = inferenceMap.get(squad.code);
|
|
210
|
+
if (inference && inference.confidence === 'high') {
|
|
211
|
+
highConfidence.push({ squad, setor: inference.setor, grupo: inference.grupo, sessao: inference.sessao });
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
lowConfidence.push(squad);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Show high-confidence inferences
|
|
218
|
+
if (highConfidence.length > 0) {
|
|
219
|
+
console.log('Inferências com alta confiança:');
|
|
220
|
+
for (const c of highConfidence) {
|
|
221
|
+
const newDir = `${c.setor}/${c.grupo}/${c.sessao}/${c.squad.code}`;
|
|
222
|
+
console.log(` ${c.squad.code} → ${newDir}`);
|
|
223
|
+
}
|
|
224
|
+
const confirm = await ask(rl, '\nConfirmar movimentações acima? [s/n]: ');
|
|
225
|
+
if (confirm.trim().toLowerCase() !== 's') {
|
|
226
|
+
console.log('Cancelado.');
|
|
227
|
+
rl.close();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Collect manual classifications for low-confidence squads
|
|
232
|
+
const manualClassifications = [];
|
|
233
|
+
for (const squad of lowConfidence) {
|
|
234
|
+
const inference = inferenceMap.get(squad.code);
|
|
235
|
+
const suggestion = inference
|
|
236
|
+
? ` (sugestão: ${inference.setor}/${inference.grupo}/${inference.sessao})`
|
|
237
|
+
: '';
|
|
238
|
+
console.log(`\nClassificação necessária para: ${squad.code}${suggestion}`);
|
|
239
|
+
console.log(` Descrição: ${squad.description}`);
|
|
240
|
+
const setor = (await ask(rl, ' Setor (ex: marketing, desenvolvimento, rh...): ')).trim();
|
|
241
|
+
const grupo = (await ask(rl, ' Grupo: ')).trim();
|
|
242
|
+
const sessao = (await ask(rl, ' Sessão: ')).trim();
|
|
243
|
+
if (!setor || !grupo || !sessao) {
|
|
244
|
+
console.log(` Pulando ${squad.code} — classificação incompleta.`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
manualClassifications.push({ squad, setor, grupo, sessao });
|
|
248
|
+
}
|
|
249
|
+
rl.close();
|
|
250
|
+
const allClassified = [...highConfidence, ...manualClassifications];
|
|
251
|
+
if (allClassified.length === 0) {
|
|
252
|
+
console.log('\nNenhuma squad para mover.');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Show dry-run summary
|
|
256
|
+
console.log('\nPlano de reorganização:');
|
|
257
|
+
for (const c of allClassified) {
|
|
258
|
+
console.log(` ${path.relative(squadsDir, c.squad.dir)} → ${c.setor}/${c.grupo}/${c.sessao}/${c.squad.code}`);
|
|
259
|
+
}
|
|
260
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
261
|
+
const finalConfirm = await ask(rl2, '\nExecutar reorganização? [s/n]: ');
|
|
262
|
+
rl2.close();
|
|
263
|
+
if (finalConfirm.trim().toLowerCase() !== 's') {
|
|
264
|
+
console.log('Cancelado.');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Execute moves
|
|
268
|
+
console.log('\nMovendo squads...');
|
|
269
|
+
const moved = [];
|
|
270
|
+
for (const c of allClassified) {
|
|
271
|
+
const newDir = path.join(squadsDir, c.setor, c.grupo, c.sessao, c.squad.code);
|
|
272
|
+
if (c.squad.dir === newDir) {
|
|
273
|
+
console.log(` ${c.squad.code} — já está no local correto.`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
fs.mkdirSync(path.dirname(newDir), { recursive: true });
|
|
278
|
+
fs.renameSync(c.squad.dir, newDir);
|
|
279
|
+
updateSquadYaml(newDir, c.setor, c.grupo, c.sessao);
|
|
280
|
+
moved.push({ squad: c.squad, newDir });
|
|
281
|
+
console.log(` ✓ ${c.squad.code} → ${c.setor}/${c.grupo}/${c.sessao}/${c.squad.code}`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
console.error(` ✗ ${c.squad.code} — falha ao mover: ${err.message}`);
|
|
285
|
+
console.error(' Squads movidas até agora foram mantidas. Corrija o erro e reexecute.');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Update cross-references for moved squads
|
|
289
|
+
if (moved.length > 0) {
|
|
290
|
+
console.log('\nAtualizando referências cruzadas...');
|
|
291
|
+
for (const { squad, newDir } of moved) {
|
|
292
|
+
updateCrossReferences(squadsDir, squad.dir, newDir);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
console.log('\nReorganização concluída!');
|
|
296
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
-
import { loadSquad } from '../../../core/squad-loader.js';
|
|
4
|
+
import { loadSquad, findSquadDir } from '../../../core/squad-loader.js';
|
|
5
5
|
import { createInitialState, readState, writeState, updateAgentStatus, updateStep, setHandoff, setSquadStatus, HANDOFF_DELAY_MS, } from '../../../core/state-manager.js';
|
|
6
6
|
import { loadSkills } from '../../../core/skills-loader.js';
|
|
7
7
|
import { getCoreAsset } from '../utils/config.js';
|
|
8
|
+
import { runWithProvider } from '../runners/provider-runner.js';
|
|
8
9
|
function delay(ms) {
|
|
9
10
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
10
11
|
}
|
|
@@ -130,10 +131,11 @@ function spawnClaudeCode(prompt, cwd) {
|
|
|
130
131
|
}
|
|
131
132
|
export async function runCommand(name) {
|
|
132
133
|
const cwd = process.cwd();
|
|
133
|
-
const
|
|
134
|
+
const squadsDir = path.join(cwd, 'squads');
|
|
135
|
+
const squadDir = findSquadDir(squadsDir, name);
|
|
134
136
|
const skillsDir = path.join(cwd, 'skills');
|
|
135
|
-
if (!
|
|
136
|
-
console.error(`Squad "${name}" not found
|
|
137
|
+
if (!squadDir) {
|
|
138
|
+
console.error(`Squad "${name}" not found. Run \`expxagents list\` to see available squads.`);
|
|
137
139
|
process.exit(1);
|
|
138
140
|
}
|
|
139
141
|
let config;
|
|
@@ -175,7 +177,7 @@ export async function runCommand(name) {
|
|
|
175
177
|
const prompt = buildAgentPrompt(config, step, stepNumber, squadDir, skills, runnerPrompt);
|
|
176
178
|
let output;
|
|
177
179
|
try {
|
|
178
|
-
output = await
|
|
180
|
+
output = await runWithProvider(prompt, agent, squadDir, spawnClaudeCode);
|
|
179
181
|
}
|
|
180
182
|
catch (err) {
|
|
181
183
|
console.error(`\nStep ${stepNumber} failed: ${err.message}`);
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { resolveServerPaths } from '../utils/server-paths.js';
|
|
5
5
|
import { findPackageRoot } from '../utils/config.js';
|
|
6
|
-
export async function serverCommand() {
|
|
6
|
+
export async function serverCommand(options = {}) {
|
|
7
7
|
const userProjectDir = process.cwd();
|
|
8
8
|
const port = process.env.PORT ?? '3001';
|
|
9
9
|
let stopping = false;
|
|
@@ -36,6 +36,24 @@ export async function serverCommand() {
|
|
|
36
36
|
console.error('Make sure you have the latest expxagents package installed.');
|
|
37
37
|
process.exit(1);
|
|
38
38
|
});
|
|
39
|
+
if (options.tunnel) {
|
|
40
|
+
// Give server 2s to start before opening tunnel
|
|
41
|
+
setTimeout(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const { default: localtunnel } = await import('localtunnel');
|
|
44
|
+
const tunnel = await localtunnel({ port: Number(port) });
|
|
45
|
+
console.log(`\n Public URL: ${tunnel.url}`);
|
|
46
|
+
console.log(' Share this to access your dashboard remotely.\n');
|
|
47
|
+
tunnel.on('close', () => console.log('[tunnel] Connection closed.'));
|
|
48
|
+
tunnel.on('error', (err) => console.error('[tunnel] Error:', err.message));
|
|
49
|
+
process.on('SIGINT', () => tunnel.close());
|
|
50
|
+
process.on('SIGTERM', () => tunnel.close());
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error('[tunnel] Failed to create tunnel:', err.message);
|
|
54
|
+
}
|
|
55
|
+
}, 2000);
|
|
56
|
+
}
|
|
39
57
|
child.on('exit', (code) => {
|
|
40
58
|
if (stopping) {
|
|
41
59
|
process.exit(code ?? 0);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import path from 'path';
|
|
2
|
+
import { findSquadDir } from '../../../core/squad-loader.js';
|
|
3
3
|
import { readState, writeState, setSquadStatus } from '../../../core/state-manager.js';
|
|
4
4
|
export async function stopCommand(name) {
|
|
5
5
|
const cwd = process.cwd();
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const squadsDir = path.join(cwd, 'squads');
|
|
7
|
+
const squadDir = findSquadDir(squadsDir, name);
|
|
8
|
+
if (!squadDir) {
|
|
8
9
|
console.error(`Squad "${name}" not found.`);
|
|
9
10
|
process.exit(1);
|
|
10
11
|
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { onboardingCommand } from './commands/onboarding.js';
|
|
|
13
13
|
import { virtualOfficeCommand } from './commands/virtual-office.js';
|
|
14
14
|
import { jarvisCommand } from './commands/jarvis.js';
|
|
15
15
|
import { schedulerCommand } from './commands/scheduler.js';
|
|
16
|
+
import { reorganizeCommand } from './commands/reorganize.js';
|
|
16
17
|
import { findPackageRoot } from './utils/config.js';
|
|
17
18
|
function getVersion() {
|
|
18
19
|
try {
|
|
@@ -50,6 +51,10 @@ program
|
|
|
50
51
|
.command('list')
|
|
51
52
|
.description('List squads and skills')
|
|
52
53
|
.action(listCommand);
|
|
54
|
+
program
|
|
55
|
+
.command('reorganize')
|
|
56
|
+
.description('Reorganize squads into Setor > Grupo > Sessão > Squad hierarchy')
|
|
57
|
+
.action(reorganizeCommand);
|
|
53
58
|
program
|
|
54
59
|
.command('install <skill>')
|
|
55
60
|
.description('Install skill from registry')
|
|
@@ -61,7 +66,8 @@ program
|
|
|
61
66
|
program
|
|
62
67
|
.command('server')
|
|
63
68
|
.description('Start the web server')
|
|
64
|
-
.
|
|
69
|
+
.option('--tunnel', 'Create a public URL for remote access via localtunnel')
|
|
70
|
+
.action((options) => serverCommand(options));
|
|
65
71
|
program
|
|
66
72
|
.command('doctor')
|
|
67
73
|
.description('Run system diagnostics and health checks')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|