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.
Files changed (57) hide show
  1. package/assets/core/solution-architect.agent.md +16 -10
  2. package/dist/cli/src/commands/init.js +18 -0
  3. package/dist/cli/src/commands/list.js +100 -28
  4. package/dist/cli/src/commands/reorganize.d.ts +1 -0
  5. package/dist/cli/src/commands/reorganize.js +296 -0
  6. package/dist/cli/src/commands/run.js +7 -5
  7. package/dist/cli/src/commands/server.d.ts +3 -1
  8. package/dist/cli/src/commands/server.js +19 -1
  9. package/dist/cli/src/commands/stop.js +4 -3
  10. package/dist/cli/src/index.js +7 -1
  11. package/dist/cli/src/runners/__tests__/provider-runner.test.d.ts +1 -0
  12. package/dist/cli/src/runners/__tests__/provider-runner.test.js +86 -0
  13. package/dist/cli/src/runners/anthropic-runner.d.ts +1 -0
  14. package/dist/cli/src/runners/anthropic-runner.js +15 -0
  15. package/dist/cli/src/runners/openai-runner.d.ts +1 -0
  16. package/dist/cli/src/runners/openai-runner.js +13 -0
  17. package/dist/cli/src/runners/provider-runner.d.ts +2 -0
  18. package/dist/cli/src/runners/provider-runner.js +13 -0
  19. package/dist/core/squad-loader.d.ts +10 -0
  20. package/dist/core/squad-loader.js +62 -0
  21. package/dist/dashboard/assets/{BufferResource-DuDG7xRm.js → BufferResource-sdeVYlCP.js} +1 -1
  22. package/dist/dashboard/assets/{CanvasRenderer-DgtlELOg.js → CanvasRenderer-BHTY8nWw.js} +1 -1
  23. package/dist/dashboard/assets/{JarvisView-BvMMhjfv.js → JarvisView-lYnkwpPj.js} +1 -1
  24. package/dist/dashboard/assets/{RenderTargetSystem-BDF6yZwH.js → RenderTargetSystem-D8lfYGjb.js} +1 -1
  25. package/dist/dashboard/assets/{ThreeBackground-DVvmqB4o.js → ThreeBackground-DIpxzfWj.js} +1 -1
  26. package/dist/dashboard/assets/{WebGLRenderer-50NXaS3u.js → WebGLRenderer-D6I_TSkc.js} +1 -1
  27. package/dist/dashboard/assets/{WebGPURenderer-Dng8T71e.js → WebGPURenderer-BYclzvEl.js} +1 -1
  28. package/dist/dashboard/assets/{browserAll-CmiVZDVb.js → browserAll-C-f1RWmb.js} +1 -1
  29. package/dist/dashboard/assets/{index-DbfeP1Uv.js → index-CGqUxwDX.js} +72 -72
  30. package/dist/dashboard/assets/{webworkerAll-CDMUUiju.js → webworkerAll-BRIGXnpD.js} +1 -1
  31. package/dist/dashboard/index.html +1 -1
  32. package/dist/server/api/__tests__/webhook-routes.test.d.ts +2 -0
  33. package/dist/server/api/__tests__/webhook-routes.test.d.ts.map +1 -0
  34. package/dist/server/api/__tests__/webhook-routes.test.js +111 -0
  35. package/dist/server/api/__tests__/webhook-routes.test.js.map +1 -0
  36. package/dist/server/api/squads-routes.d.ts.map +1 -1
  37. package/dist/server/api/squads-routes.js +107 -46
  38. package/dist/server/api/squads-routes.js.map +1 -1
  39. package/dist/server/api/webhook-routes.d.ts +9 -0
  40. package/dist/server/api/webhook-routes.d.ts.map +1 -0
  41. package/dist/server/api/webhook-routes.js +44 -0
  42. package/dist/server/api/webhook-routes.js.map +1 -0
  43. package/dist/server/app.d.ts +2 -0
  44. package/dist/server/app.d.ts.map +1 -1
  45. package/dist/server/app.js +10 -1
  46. package/dist/server/app.js.map +1 -1
  47. package/dist/server/types/a2ui.d.ts +27 -0
  48. package/dist/server/types/a2ui.d.ts.map +1 -0
  49. package/dist/server/types/a2ui.js +2 -0
  50. package/dist/server/types/a2ui.js.map +1 -0
  51. package/dist/server/watcher/__tests__/file-watcher.test.js +49 -1
  52. package/dist/server/watcher/__tests__/file-watcher.test.js.map +1 -1
  53. package/dist/server/watcher/file-watcher.d.ts +4 -0
  54. package/dist/server/watcher/file-watcher.d.ts.map +1 -1
  55. package/dist/server/watcher/file-watcher.js +27 -1
  56. package/dist/server/watcher/file-watcher.js.map +1 -1
  57. 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 6 questions)
22
+ ## Discovery Phase (max 7 questions)
23
23
 
24
24
  Before designing, gather requirements through focused questions:
25
25
 
26
- 1. **Group:** Which group does this squad belong to? (e.g., marketing, commercial, development, support, finance, hr, operations). The group name will be used as a prefix for the squad code: `<group>-<squad-name>`.
27
- 2. **Objective:** What specific outcome do you want this squad to produce?
28
- 3. **Context:** Who is the target audience and what platform/format?
29
- 4. **Process:** Are there specific steps or stages you want in the workflow?
30
- 5. **Quality:** What are the quality criteria for the final output?
31
- 6. **References:** Any reference profiles, competitors, or examples to investigate?
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 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: <group>-<squad-name> # e.g., marketing-landing-page, commercial-outbound
52
- group: <group> # e.g., marketing, commercial, development
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 { loadSquad } from '../../../core/squad-loader.js';
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 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('');
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
- catch (err) {
36
- console.log(` ${entry.name} error loading: ${err.message}`);
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 squadDir = path.join(cwd, 'squads', name);
134
+ const squadsDir = path.join(cwd, 'squads');
135
+ const squadDir = findSquadDir(squadsDir, name);
134
136
  const skillsDir = path.join(cwd, 'skills');
135
- if (!fs.existsSync(squadDir)) {
136
- console.error(`Squad "${name}" not found in squads/ directory.`);
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 spawnClaudeCode(prompt, squadDir);
180
+ output = await runWithProvider(prompt, agent, squadDir, spawnClaudeCode);
179
181
  }
180
182
  catch (err) {
181
183
  console.error(`\nStep ${stepNumber} failed: ${err.message}`);
@@ -1 +1,3 @@
1
- export declare function serverCommand(): Promise<void>;
1
+ export declare function serverCommand(options?: {
2
+ tunnel?: boolean;
3
+ }): Promise<void>;
@@ -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 squadDir = path.join(cwd, 'squads', name);
7
- if (!fs.existsSync(squadDir)) {
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
  }
@@ -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
- .action(serverCommand);
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')