@jaimevalasek/aioson 1.4.0 → 1.5.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 (199) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/LICENSE +661 -21
  3. package/README.md +3 -1
  4. package/docs/en/squad-dashboard.md +372 -0
  5. package/docs/openclaw-bridge.md +308 -0
  6. package/docs/pt/agentes.md +124 -10
  7. package/docs/pt/cenarios.md +46 -2
  8. package/docs/pt/comandos-cli.md +60 -1
  9. package/docs/pt/inicio-rapido.md +18 -2
  10. package/docs/pt/squad-dashboard.md +373 -0
  11. package/docs/testing/genome-2.0-matrix.md +5 -5
  12. package/docs/testing/genome-2.0-rollout.md +9 -9
  13. package/package.json +2 -2
  14. package/src/backup-local.js +74 -0
  15. package/src/cli.js +98 -0
  16. package/src/commands/backup-local-cmd.js +25 -0
  17. package/src/commands/runtime.js +242 -0
  18. package/src/commands/setup-context.js +7 -2
  19. package/src/commands/squad-daemon.js +209 -0
  20. package/src/commands/squad-dashboard.js +39 -0
  21. package/src/commands/squad-deploy.js +64 -0
  22. package/src/commands/squad-doctor.js +52 -0
  23. package/src/commands/squad-mcp.js +270 -0
  24. package/src/commands/squad-processes.js +56 -0
  25. package/src/commands/squad-recovery.js +42 -0
  26. package/src/commands/squad-roi.js +291 -0
  27. package/src/commands/squad-score.js +250 -0
  28. package/src/commands/squad-status.js +37 -1
  29. package/src/commands/squad-validate.js +62 -1
  30. package/src/commands/squad-webhook.js +160 -0
  31. package/src/commands/squad-worker.js +191 -0
  32. package/src/commands/squad-worktrees.js +75 -0
  33. package/src/commands/web-map.js +70 -0
  34. package/src/commands/web-scrape.js +71 -0
  35. package/src/constants.js +8 -0
  36. package/src/context-writer.js +45 -1
  37. package/src/i18n/messages/en.js +127 -1
  38. package/src/i18n/messages/es.js +117 -0
  39. package/src/i18n/messages/fr.js +117 -0
  40. package/src/i18n/messages/pt-BR.js +126 -1
  41. package/src/lib/webhook-server.js +328 -0
  42. package/src/mcp-connectors/registry.js +602 -0
  43. package/src/runtime-store.js +259 -2
  44. package/src/squad/external-session.js +180 -0
  45. package/src/squad/inter-squad.js +74 -0
  46. package/src/squad/recovery-context.js +201 -0
  47. package/src/squad/worktree-manager.js +114 -0
  48. package/src/squad-daemon.js +490 -0
  49. package/src/squad-dashboard/api.js +223 -0
  50. package/src/squad-dashboard/attachment-handler.js +93 -0
  51. package/src/squad-dashboard/context-monitor.js +157 -0
  52. package/src/squad-dashboard/execution-logs.js +115 -0
  53. package/src/squad-dashboard/hunk-review.js +209 -0
  54. package/src/squad-dashboard/metrics.js +133 -0
  55. package/src/squad-dashboard/process-monitor.js +125 -0
  56. package/src/squad-dashboard/renderer.js +858 -0
  57. package/src/squad-dashboard/server.js +232 -0
  58. package/src/squad-dashboard/styles.js +525 -0
  59. package/src/squad-dashboard/token-tracker.js +99 -0
  60. package/src/web.js +284 -0
  61. package/src/worker-runner.js +339 -0
  62. package/template/.aioson/agents/analyst.md +4 -0
  63. package/template/.aioson/agents/architect.md +4 -0
  64. package/template/.aioson/agents/dev.md +120 -11
  65. package/template/.aioson/agents/deyvin.md +8 -0
  66. package/template/.aioson/agents/neo.md +152 -0
  67. package/template/.aioson/agents/orache.md +17 -0
  68. package/template/.aioson/agents/orchestrator.md +26 -0
  69. package/template/.aioson/agents/product.md +60 -12
  70. package/template/.aioson/agents/qa.md +1 -0
  71. package/template/.aioson/agents/setup.md +63 -19
  72. package/template/.aioson/agents/sheldon.md +603 -0
  73. package/template/.aioson/agents/squad.md +191 -0
  74. package/template/.aioson/agents/tester.md +254 -0
  75. package/template/.aioson/agents/ux-ui.md +12 -0
  76. package/template/.aioson/config.md +6 -0
  77. package/template/.aioson/locales/en/agents/analyst.md +8 -0
  78. package/template/.aioson/locales/en/agents/architect.md +8 -0
  79. package/template/.aioson/locales/en/agents/dev.md +66 -7
  80. package/template/.aioson/locales/en/agents/deyvin.md +8 -0
  81. package/template/.aioson/locales/en/agents/neo.md +8 -0
  82. package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
  83. package/template/.aioson/locales/en/agents/qa.md +49 -0
  84. package/template/.aioson/locales/en/agents/setup.md +2 -1
  85. package/template/.aioson/locales/en/agents/sheldon.md +340 -0
  86. package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
  87. package/template/.aioson/locales/es/agents/analyst.md +8 -0
  88. package/template/.aioson/locales/es/agents/architect.md +8 -0
  89. package/template/.aioson/locales/es/agents/dev.md +66 -7
  90. package/template/.aioson/locales/es/agents/deyvin.md +8 -0
  91. package/template/.aioson/locales/es/agents/neo.md +48 -0
  92. package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
  93. package/template/.aioson/locales/es/agents/qa.md +26 -0
  94. package/template/.aioson/locales/es/agents/setup.md +2 -1
  95. package/template/.aioson/locales/es/agents/sheldon.md +192 -0
  96. package/template/.aioson/locales/es/agents/squad.md +63 -0
  97. package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
  98. package/template/.aioson/locales/fr/agents/analyst.md +8 -0
  99. package/template/.aioson/locales/fr/agents/architect.md +8 -0
  100. package/template/.aioson/locales/fr/agents/dev.md +66 -7
  101. package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
  102. package/template/.aioson/locales/fr/agents/neo.md +48 -0
  103. package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
  104. package/template/.aioson/locales/fr/agents/qa.md +26 -0
  105. package/template/.aioson/locales/fr/agents/setup.md +2 -1
  106. package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
  107. package/template/.aioson/locales/fr/agents/squad.md +63 -0
  108. package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
  109. package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
  110. package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
  111. package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
  112. package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
  113. package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
  114. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
  115. package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
  116. package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
  117. package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
  118. package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
  119. package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
  120. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
  121. package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
  122. package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
  123. package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
  124. package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
  125. package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
  126. package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
  127. package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
  128. package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
  129. package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
  130. package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
  131. package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
  132. package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
  133. package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
  134. package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
  135. package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
  136. package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
  137. package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
  138. package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
  139. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
  140. package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
  141. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
  142. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
  143. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
  144. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
  145. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
  146. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
  147. package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
  148. package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
  149. package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
  150. package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
  151. package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
  152. package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
  153. package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
  154. package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
  155. package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
  156. package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
  157. package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
  158. package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
  159. package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
  160. package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
  161. package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
  162. package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
  163. package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
  164. package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
  165. package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
  166. package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
  167. package/template/.aioson/skills/squad/formats/catalog.json +15 -0
  168. package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
  169. package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
  170. package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
  171. package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
  172. package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
  173. package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
  174. package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
  175. package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
  176. package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
  177. package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
  178. package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
  179. package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
  180. package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
  181. package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
  182. package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
  183. package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
  184. package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
  185. package/template/.aioson/skills/static/debugging-protocol.md +42 -0
  186. package/template/.aioson/skills/static/git-worktrees.md +36 -0
  187. package/template/.aioson/tasks/implementation-plan.md +19 -0
  188. package/template/.aioson/tasks/squad-design.md +28 -0
  189. package/template/.aioson/tasks/squad-profile.md +48 -0
  190. package/template/.aioson/tasks/squad-review.md +61 -0
  191. package/template/.aioson/tasks/squad-task-decompose.md +66 -0
  192. package/template/.claude/commands/aioson/agent/neo.md +5 -0
  193. package/template/.claude/commands/aioson/agent/tester.md +5 -0
  194. package/template/.gemini/GEMINI.md +1 -0
  195. package/template/.gemini/commands/aios-neo.toml +4 -0
  196. package/template/.gemini/commands/aios-tester.toml +6 -0
  197. package/template/AGENTS.md +3 -0
  198. package/template/CLAUDE.md +5 -2
  199. package/template/OPENCODE.md +2 -0
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { SquadDaemon } = require('../squad-daemon');
6
+ const { openRuntimeDb } = require('../runtime-store');
7
+
8
+ async function handleStart(projectDir, squadSlug, options, { logger, t }) {
9
+ if (!squadSlug) {
10
+ logger.error(t('squad_daemon.squad_required'));
11
+ return { ok: false };
12
+ }
13
+
14
+ let squadConfig = {};
15
+ try {
16
+ const squadJsonPath = path.join(projectDir, '.aioson', 'squads', squadSlug, 'squad.json');
17
+ squadConfig = JSON.parse(await fs.readFile(squadJsonPath, 'utf8'));
18
+ } catch { /* squad.json is optional */ }
19
+
20
+ const daemon = new SquadDaemon(projectDir, squadSlug, {
21
+ port: options.port ? Number(options.port) : 0,
22
+ poll: options.poll ? Number(options.poll) : 10000,
23
+ config: squadConfig
24
+ });
25
+
26
+ try {
27
+ const info = await daemon.start();
28
+ logger.log(t('squad_daemon.started', {
29
+ squad: squadSlug,
30
+ port: info.port,
31
+ workers: info.workers,
32
+ cron: info.cronJobs
33
+ }));
34
+ logger.log(t('squad_daemon.webhook_hint', { port: info.port }));
35
+ logger.log(t('squad_daemon.stop_hint'));
36
+
37
+ // Keep alive until signal
38
+ await new Promise((resolve) => {
39
+ process.on('SIGINT', resolve);
40
+ process.on('SIGTERM', resolve);
41
+ });
42
+
43
+ logger.log(t('squad_daemon.stopping'));
44
+ await daemon.stop();
45
+ return { ok: true, ...info };
46
+ } catch (err) {
47
+ logger.error(t('squad_daemon.start_failed', { error: err.message }));
48
+ return { ok: false, error: err.message };
49
+ }
50
+ }
51
+
52
+ async function handleStatus(projectDir, squadSlug, { logger, t }) {
53
+ if (!squadSlug) {
54
+ // List all daemons
55
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
56
+ if (!handle) {
57
+ logger.error(t('squad_daemon.no_runtime'));
58
+ return { ok: false };
59
+ }
60
+ const { db } = handle;
61
+ try {
62
+ const daemons = db.prepare('SELECT * FROM squad_daemons ORDER BY squad_slug').all();
63
+ if (daemons.length === 0) {
64
+ logger.log(t('squad_daemon.no_daemons'));
65
+ return { ok: true, daemons: [] };
66
+ }
67
+ logger.log(`Daemons (${daemons.length}):`);
68
+ for (const d of daemons) {
69
+ const icon = d.status === 'running' ? '[*]' : '[ ]';
70
+ logger.log(` ${icon} ${d.squad_slug} (${d.status}) port:${d.port || '-'} pid:${d.pid || '-'} heartbeat:${d.last_heartbeat || '-'}`);
71
+ }
72
+ return { ok: true, daemons };
73
+ } finally {
74
+ db.close();
75
+ }
76
+ }
77
+
78
+ // Specific squad daemon status
79
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
80
+ if (!handle) {
81
+ logger.error(t('squad_daemon.no_runtime'));
82
+ return { ok: false };
83
+ }
84
+ const { db } = handle;
85
+ try {
86
+ const record = db.prepare('SELECT * FROM squad_daemons WHERE squad_slug = ?').get(squadSlug);
87
+ if (!record) {
88
+ logger.log(t('squad_daemon.not_found', { squad: squadSlug }));
89
+ return { ok: true, daemon: null };
90
+ }
91
+ logger.log(`Daemon: ${record.squad_slug}`);
92
+ logger.log(` Status: ${record.status}`);
93
+ logger.log(` PID: ${record.pid || '-'}`);
94
+ logger.log(` Port: ${record.port || '-'}`);
95
+ logger.log(` Started: ${record.started_at || '-'}`);
96
+ logger.log(` Heartbeat: ${record.last_heartbeat || '-'}`);
97
+ if (record.error_message) logger.log(` Error: ${record.error_message}`);
98
+ if (record.config_json) {
99
+ try {
100
+ const config = JSON.parse(record.config_json);
101
+ if (config.cronJobs && config.cronJobs.length > 0) {
102
+ logger.log(` Cron Jobs:`);
103
+ for (const job of config.cronJobs) {
104
+ logger.log(` - ${job.worker} (${job.cron})`);
105
+ }
106
+ }
107
+ } catch { /* ignore */ }
108
+ }
109
+ return { ok: true, daemon: record };
110
+ } finally {
111
+ db.close();
112
+ }
113
+ }
114
+
115
+ async function handleStop(projectDir, squadSlug, { logger, t }) {
116
+ if (!squadSlug) {
117
+ logger.error(t('squad_daemon.squad_required'));
118
+ return { ok: false };
119
+ }
120
+
121
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
122
+ if (!handle) {
123
+ logger.error(t('squad_daemon.no_runtime'));
124
+ return { ok: false };
125
+ }
126
+ const { db } = handle;
127
+ try {
128
+ const record = db.prepare('SELECT * FROM squad_daemons WHERE squad_slug = ?').get(squadSlug);
129
+ if (!record || record.status !== 'running') {
130
+ logger.log(t('squad_daemon.not_running', { squad: squadSlug }));
131
+ return { ok: true };
132
+ }
133
+
134
+ // Try to kill the process
135
+ if (record.pid) {
136
+ try {
137
+ process.kill(record.pid, 'SIGTERM');
138
+ logger.log(t('squad_daemon.signal_sent', { squad: squadSlug, pid: record.pid }));
139
+ } catch {
140
+ logger.log(t('squad_daemon.process_gone', { squad: squadSlug }));
141
+ }
142
+ }
143
+
144
+ // Update record
145
+ db.prepare(
146
+ "UPDATE squad_daemons SET status = 'stopped' WHERE squad_slug = ?"
147
+ ).run(squadSlug);
148
+
149
+ return { ok: true };
150
+ } finally {
151
+ db.close();
152
+ }
153
+ }
154
+
155
+ async function handleLogs(projectDir, squadSlug, { logger, t }) {
156
+ if (!squadSlug) {
157
+ logger.error(t('squad_daemon.squad_required'));
158
+ return { ok: false };
159
+ }
160
+
161
+ // Show recent worker runs as daemon logs
162
+ const handle = await openRuntimeDb(projectDir, { mustExist: true });
163
+ if (!handle) {
164
+ logger.error(t('squad_daemon.no_runtime'));
165
+ return { ok: false };
166
+ }
167
+ const { db } = handle;
168
+ try {
169
+ const runs = db.prepare(
170
+ 'SELECT * FROM worker_runs WHERE squad_slug = ? ORDER BY created_at DESC LIMIT 30'
171
+ ).all(squadSlug);
172
+ if (runs.length === 0) {
173
+ logger.log(t('squad_daemon.no_logs'));
174
+ return { ok: true, runs: [] };
175
+ }
176
+ logger.log(`Recent daemon activity for "${squadSlug}" (${runs.length}):`);
177
+ for (const r of runs) {
178
+ const icon = r.status === 'completed' ? '[ok]' : r.status === 'failed' ? '[!!]' : '[..]';
179
+ const ms = r.duration_ms ? `${r.duration_ms}ms` : '-';
180
+ logger.log(` ${icon} ${r.created_at} ${r.worker_slug} (${r.trigger_type}) ${ms}`);
181
+ if (r.error_message) logger.log(` ${r.error_message}`);
182
+ }
183
+ return { ok: true, runs };
184
+ } finally {
185
+ db.close();
186
+ }
187
+ }
188
+
189
+ async function runSquadDaemon({ args, options, logger, t }) {
190
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
191
+ const sub = options.sub || 'status';
192
+ const squadSlug = options.squad;
193
+
194
+ switch (sub) {
195
+ case 'start':
196
+ return handleStart(targetDir, squadSlug, options, { logger, t });
197
+ case 'status':
198
+ return handleStatus(targetDir, squadSlug, { logger, t });
199
+ case 'stop':
200
+ return handleStop(targetDir, squadSlug, { logger, t });
201
+ case 'logs':
202
+ return handleLogs(targetDir, squadSlug, { logger, t });
203
+ default:
204
+ logger.error(t('squad_daemon.unknown_sub', { sub }));
205
+ return { ok: false };
206
+ }
207
+ }
208
+
209
+ module.exports = { runSquadDaemon };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { createDashboardServer } = require('../squad-dashboard/server');
5
+
6
+ async function runSquadDashboard({ args, options, logger, t }) {
7
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
8
+ const port = Number(options.port) || 4180;
9
+ const filterSquad = options.squad || null;
10
+
11
+ const dashboard = createDashboardServer(targetDir, { port, squad: filterSquad });
12
+
13
+ try {
14
+ const info = await dashboard.start();
15
+ logger.log(t('squad_dashboard.started', { url: info.url, port: info.port }));
16
+ if (filterSquad) {
17
+ logger.log(t('squad_dashboard.filtered', { squad: filterSquad }));
18
+ }
19
+ logger.log(t('squad_dashboard.stop_hint'));
20
+
21
+ // Keep the process alive until SIGINT/SIGTERM
22
+ await new Promise((resolve) => {
23
+ process.on('SIGINT', resolve);
24
+ process.on('SIGTERM', resolve);
25
+ });
26
+
27
+ logger.log(t('squad_dashboard.stopping'));
28
+ await dashboard.stop();
29
+ return { ok: true, port: info.port, url: info.url };
30
+ } catch (err) {
31
+ if (err.code === 'EADDRINUSE') {
32
+ logger.error(t('squad_dashboard.port_in_use', { port }));
33
+ return { ok: false, error: `Port ${port} already in use` };
34
+ }
35
+ throw err;
36
+ }
37
+ }
38
+
39
+ module.exports = { runSquadDashboard };
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ async function runSquadDeploy({ args, options, logger }) {
7
+ const squadSlug = args[0] || options.squad;
8
+ const projectDir = path.resolve(args[1] || options.path || '.');
9
+ const provider = options.provider || 'cloudpanel';
10
+
11
+ if (!squadSlug) {
12
+ logger.log('Uso: aioson squad:deploy <squad> [dir] --provider=cloudpanel');
13
+ return { ok: false };
14
+ }
15
+
16
+ const squadJsonPath = path.join(projectDir, '.aioson', 'squads', squadSlug, 'squad.json');
17
+ let squadConfig;
18
+ try {
19
+ squadConfig = JSON.parse(await fs.readFile(squadJsonPath, 'utf8'));
20
+ } catch {
21
+ logger.log(`Squad "${squadSlug}" não encontrado em ${squadJsonPath}`);
22
+ return { ok: false, error: 'squad_not_found' };
23
+ }
24
+
25
+ const port = squadConfig.port || 3001;
26
+ const deployDir = path.join(projectDir, '.aioson', 'squads', squadSlug, 'deploy');
27
+ await fs.mkdir(deployDir, { recursive: true });
28
+
29
+ const nginxConf = generateNginxConf(squadSlug, port, provider);
30
+ const outPath = path.join(deployDir, 'nginx.conf');
31
+ await fs.writeFile(outPath, nginxConf);
32
+
33
+ logger.log(`✓ Configuração nginx gerada: ${outPath}`);
34
+ logger.log('');
35
+ logger.log('Instruções CloudPanel:');
36
+ logger.log(' 1. Acesse CloudPanel → Websites → [seu site] → Nginx');
37
+ logger.log(' 2. Cole o conteúdo do arquivo nginx.conf em "Custom Nginx Directives"');
38
+ logger.log(' 3. Salve e aguarde o reload automático do nginx');
39
+ logger.log(` 4. Certifique-se que o daemon está rodando: aioson squad:daemon ${squadSlug} start`);
40
+
41
+ return { ok: true, path: outPath };
42
+ }
43
+
44
+ function generateNginxConf(slug, port, provider) {
45
+ return [
46
+ `# Squad: ${slug} — gerado por aioson squad:deploy`,
47
+ `# Provider: ${provider}`,
48
+ `# Cole em: CloudPanel → Website → Nginx → Custom Nginx Directives`,
49
+ `#`,
50
+ `# Pré-requisito: aioson squad:daemon ${slug} start`,
51
+ ``,
52
+ `location /${slug}/webhook/ {`,
53
+ ` proxy_pass http://127.0.0.1:${port}/webhook/;`,
54
+ ` proxy_http_version 1.1;`,
55
+ ` proxy_set_header Host $host;`,
56
+ ` proxy_set_header X-Real-IP $remote_addr;`,
57
+ ` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`,
58
+ ` proxy_read_timeout 30s;`,
59
+ ` proxy_connect_timeout 5s;`,
60
+ `}`,
61
+ ].join('\n');
62
+ }
63
+
64
+ module.exports = { runSquadDeploy, generateNginxConf };
@@ -5,6 +5,7 @@ const path = require('node:path');
5
5
  const { openRuntimeDb } = require('../runtime-store');
6
6
  const { exists } = require('../utils');
7
7
  const { runSquadValidate } = require('./squad-validate');
8
+ const { scoreCompletude, scoreProfundidade, scoreQualidadeEstrutural, scorePotencial, gradeFromScore } = require('./squad-score');
8
9
 
9
10
  function normalizeRel(value) {
10
11
  return String(value || '')
@@ -259,6 +260,36 @@ async function runSquadDoctor({ args, options = {}, logger, t }) {
259
260
  ));
260
261
  }
261
262
 
263
+ // Webhook production checks (from squad.json, optional file)
264
+ const squadJsonPath = path.join(paths.packageDir, 'squad.json');
265
+ let squadConfig = null;
266
+ try {
267
+ const raw = await fs.readFile(squadJsonPath, 'utf8');
268
+ squadConfig = JSON.parse(raw);
269
+ } catch { /* squad.json is optional */ }
270
+
271
+ if (squadConfig && squadConfig.webhook?.validate_signature) {
272
+ const envKey = squadConfig.webhook.signature_env || 'WEBHOOK_SECRET';
273
+ const defined = Boolean(process.env[envKey]);
274
+ checks.push(makeCheck(
275
+ `webhook_env_${envKey}`,
276
+ defined,
277
+ defined ? 'info' : 'error',
278
+ defined
279
+ ? `Env ${envKey} definida (HMAC validation enabled)`
280
+ : `Env ${envKey} não definida — webhook HMAC validation está ativo mas a variável está ausente`
281
+ ));
282
+ }
283
+
284
+ if (squadConfig && squadConfig.webhook?.public) {
285
+ checks.push(makeCheck(
286
+ 'webhook_public_deploy',
287
+ null,
288
+ 'warn',
289
+ 'Config nginx: execute aioson squad:deploy para gerar nginx.conf'
290
+ ));
291
+ }
292
+
262
293
  const runtimeHandle = await openRuntimeDb(targetDir, { mustExist: true });
263
294
  if (!runtimeHandle) {
264
295
  checks.push(makeCheck('runtime_store', false, 'warn', t('squad_doctor.check_runtime_missing')));
@@ -347,6 +378,27 @@ async function runSquadDoctor({ args, options = {}, logger, t }) {
347
378
  checks.push(makeCheck('formal_validation', true, 'info', 'Manifest formally valid'));
348
379
  }
349
380
 
381
+ // Quality score
382
+ try {
383
+ const d1 = scoreCompletude(manifest);
384
+ const d2 = scoreProfundidade(manifest);
385
+ const d3 = scoreQualidadeEstrutural(manifest);
386
+ const d4 = scorePotencial(manifest);
387
+ const total = d1.score + d2.score + d3.score + d4.score;
388
+ const maxTotal = d1.max + d2.max + d3.max + d4.max;
389
+ const grade = gradeFromScore(total);
390
+ const isLow = total < 50;
391
+ checks.push(
392
+ makeCheck(
393
+ 'quality_score',
394
+ !isLow,
395
+ 'info',
396
+ `Quality score: ${total}/${maxTotal} — ${grade}`,
397
+ { qualityScore: { total, max: maxTotal, grade } }
398
+ )
399
+ );
400
+ } catch { /* scoring not critical */ }
401
+
350
402
  const summary = {
351
403
  failed: checks.filter((check) => check.severity === 'error' && !check.ok).length,
352
404
  warned: checks.filter((check) => check.severity === 'warn' && !check.ok).length,
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { openRuntimeDb, upsertMcpStatus, listMcpStatus, getMcpStatus } = require('../runtime-store');
5
+ const {
6
+ listBuiltInConnectors,
7
+ getBuiltInConnector,
8
+ loadIntegrationConfig,
9
+ saveIntegrationConfig,
10
+ listIntegrations,
11
+ resolveConnectorEnv,
12
+ runHealthCheck,
13
+ runAction
14
+ } = require('../mcp-connectors/registry');
15
+
16
+ async function runSquadMcp({ args, options, logger, t }) {
17
+ const projectDir = path.resolve(args[0] || options.path || '.');
18
+ const squadSlug = options.squad;
19
+ const sub = options.sub || 'status';
20
+
21
+ if (!squadSlug) {
22
+ logger.log(t('squad_mcp.squad_required'));
23
+ return { ok: false, error: 'squad_required' };
24
+ }
25
+
26
+ if (sub === 'connectors') {
27
+ return handleConnectors({ logger, t });
28
+ }
29
+ if (sub === 'status') {
30
+ return handleStatus({ projectDir, squadSlug, logger, t });
31
+ }
32
+ if (sub === 'configure') {
33
+ return handleConfigure({ projectDir, squadSlug, options, logger, t });
34
+ }
35
+ if (sub === 'test') {
36
+ return handleTest({ projectDir, squadSlug, options, logger, t });
37
+ }
38
+ if (sub === 'call') {
39
+ return handleCall({ projectDir, squadSlug, options, logger, t });
40
+ }
41
+
42
+ logger.log(t('squad_mcp.unknown_sub', { sub }));
43
+ return { ok: false, error: 'unknown_sub' };
44
+ }
45
+
46
+ function handleConnectors({ logger, t }) {
47
+ const connectors = listBuiltInConnectors();
48
+ logger.log(t('squad_mcp.connectors_title'));
49
+ for (const c of connectors) {
50
+ logger.log(` ${c.id} — ${c.name}`);
51
+ logger.log(` ${t('squad_mcp.actions')}: ${c.actions.join(', ')}`);
52
+ logger.log(` ${t('squad_mcp.required_config')}: ${c.requiredConfig.join(', ') || 'none'}`);
53
+ logger.log('');
54
+ }
55
+ return { ok: true, connectors };
56
+ }
57
+
58
+ async function handleStatus({ projectDir, squadSlug, logger, t }) {
59
+ const integrations = await listIntegrations(projectDir, squadSlug);
60
+
61
+ if (integrations.length === 0) {
62
+ logger.log(t('squad_mcp.no_integrations', { squad: squadSlug }));
63
+ return { ok: true, integrations: [] };
64
+ }
65
+
66
+ let handle;
67
+ try {
68
+ handle = await openRuntimeDb(projectDir);
69
+ } catch {
70
+ handle = null;
71
+ }
72
+
73
+ const results = [];
74
+ for (const integ of integrations) {
75
+ const connectorDef = getBuiltInConnector(integ.connector);
76
+ let status = 'unknown';
77
+ let missing = [];
78
+ if (connectorDef) {
79
+ const env = resolveConnectorEnv(connectorDef, integ.config);
80
+ status = env.status;
81
+ missing = env.missing;
82
+ }
83
+
84
+ let dbStatus = null;
85
+ if (handle) {
86
+ dbStatus = getMcpStatus(handle.db, squadSlug, integ.slug);
87
+ }
88
+
89
+ const entry = {
90
+ slug: integ.slug,
91
+ connector: integ.connector,
92
+ status: dbStatus ? dbStatus.status : status,
93
+ missing,
94
+ calls_total: dbStatus ? dbStatus.calls_total : 0,
95
+ calls_failed: dbStatus ? dbStatus.calls_failed : 0
96
+ };
97
+ results.push(entry);
98
+
99
+ const statusIcon = entry.status === 'connected' ? '+' : entry.status === 'configured' ? '~' : '-';
100
+ logger.log(` [${statusIcon}] ${integ.slug} (${integ.connector}) — ${entry.status}`);
101
+ if (missing.length > 0) {
102
+ logger.log(` ${t('squad_mcp.missing_config')}: ${missing.join(', ')}`);
103
+ }
104
+ if (entry.calls_total > 0) {
105
+ logger.log(` ${t('squad_mcp.calls')}: ${entry.calls_total} (${entry.calls_failed} failed)`);
106
+ }
107
+ }
108
+
109
+ if (handle) handle.db.close();
110
+ return { ok: true, integrations: results };
111
+ }
112
+
113
+ async function handleConfigure({ projectDir, squadSlug, options, logger, t }) {
114
+ const mcpSlug = options.mcp;
115
+ const connector = options.connector;
116
+
117
+ if (!mcpSlug) {
118
+ logger.log(t('squad_mcp.mcp_required'));
119
+ return { ok: false, error: 'mcp_required' };
120
+ }
121
+ if (!connector) {
122
+ logger.log(t('squad_mcp.connector_required'));
123
+ return { ok: false, error: 'connector_required' };
124
+ }
125
+
126
+ const connectorDef = getBuiltInConnector(connector);
127
+ if (!connectorDef) {
128
+ logger.log(t('squad_mcp.unknown_connector', { connector }));
129
+ return { ok: false, error: 'unknown_connector' };
130
+ }
131
+
132
+ // Build config from --key=value options
133
+ const config = {};
134
+ for (const [key] of Object.entries(connectorDef.configSchema)) {
135
+ if (options[key] !== undefined) {
136
+ config[key] = options[key];
137
+ }
138
+ }
139
+
140
+ const integrationConfig = { connector, config };
141
+ await saveIntegrationConfig(projectDir, squadSlug, mcpSlug, integrationConfig);
142
+
143
+ // Check resolution
144
+ const { status, missing } = resolveConnectorEnv(connectorDef, config);
145
+
146
+ // Update SQLite status
147
+ try {
148
+ const handle = await openRuntimeDb(projectDir);
149
+ upsertMcpStatus(handle.db, { squadSlug, mcpSlug, connector, status, lastError: null });
150
+ handle.db.close();
151
+ } catch { /* SQLite optional */ }
152
+
153
+ logger.log(t('squad_mcp.configured', { mcp: mcpSlug, connector, status }));
154
+ if (missing.length > 0) {
155
+ logger.log(t('squad_mcp.still_missing', { keys: missing.join(', ') }));
156
+ }
157
+ return { ok: true, slug: mcpSlug, connector, status, missing };
158
+ }
159
+
160
+ async function handleTest({ projectDir, squadSlug, options, logger, t }) {
161
+ const mcpSlug = options.mcp;
162
+ if (!mcpSlug) {
163
+ logger.log(t('squad_mcp.mcp_required'));
164
+ return { ok: false, error: 'mcp_required' };
165
+ }
166
+
167
+ const config = await loadIntegrationConfig(projectDir, squadSlug, mcpSlug);
168
+ if (!config) {
169
+ logger.log(t('squad_mcp.not_configured', { mcp: mcpSlug }));
170
+ return { ok: false, error: 'not_configured' };
171
+ }
172
+
173
+ const connectorDef = getBuiltInConnector(config.connector);
174
+ if (!connectorDef) {
175
+ logger.log(t('squad_mcp.unknown_connector', { connector: config.connector }));
176
+ return { ok: false, error: 'unknown_connector' };
177
+ }
178
+
179
+ const { resolved, missing, status } = resolveConnectorEnv(connectorDef, config.config);
180
+
181
+ if (missing.length > 0) {
182
+ logger.log(t('squad_mcp.test_missing', { mcp: mcpSlug, keys: missing.join(', ') }));
183
+
184
+ try {
185
+ const handle = await openRuntimeDb(projectDir);
186
+ upsertMcpStatus(handle.db, { squadSlug, mcpSlug, connector: config.connector, status: 'unconfigured', lastError: `Missing: ${missing.join(', ')}` });
187
+ handle.db.close();
188
+ } catch { /* optional */ }
189
+
190
+ return { ok: false, error: 'missing_config', missing };
191
+ }
192
+
193
+ logger.log(t('squad_mcp.testing_connection'));
194
+ const health = await runHealthCheck(config.connector, resolved);
195
+
196
+ const healthStatus = health.ok ? 'connected' : 'error';
197
+ try {
198
+ const handle = await openRuntimeDb(projectDir);
199
+ upsertMcpStatus(handle.db, {
200
+ squadSlug, mcpSlug,
201
+ connector: config.connector,
202
+ status: healthStatus,
203
+ lastCheck: new Date().toISOString(),
204
+ lastError: health.ok ? null : (health.error || String(health.statusCode))
205
+ });
206
+ handle.db.close();
207
+ } catch { /* optional */ }
208
+
209
+ if (health.skipped) {
210
+ logger.log(t('squad_mcp.health_skipped'));
211
+ return { ok: true, slug: mcpSlug, status: 'skipped' };
212
+ }
213
+ if (health.ok) {
214
+ logger.log(t('squad_mcp.health_ok', { statusCode: health.statusCode }));
215
+ return { ok: true, slug: mcpSlug, status: 'connected', statusCode: health.statusCode };
216
+ }
217
+ logger.log(t('squad_mcp.health_error', { error: health.error || health.statusCode }));
218
+ return { ok: false, slug: mcpSlug, status: 'error', error: health.error };
219
+ }
220
+
221
+ async function handleCall({ projectDir, squadSlug, options, logger, t }) {
222
+ const mcpSlug = options.mcp;
223
+ const actionSlug = options.action;
224
+
225
+ if (!mcpSlug) {
226
+ logger.log(t('squad_mcp.mcp_required'));
227
+ return { ok: false, error: 'mcp_required' };
228
+ }
229
+ if (!actionSlug) {
230
+ logger.log(t('squad_mcp.action_required'));
231
+ return { ok: false, error: 'action_required' };
232
+ }
233
+
234
+ let input;
235
+ try {
236
+ input = JSON.parse(options.input || '{}');
237
+ } catch {
238
+ logger.log(t('squad_mcp.invalid_input'));
239
+ return { ok: false, error: 'invalid_input' };
240
+ }
241
+
242
+ const config = await loadIntegrationConfig(projectDir, squadSlug, mcpSlug);
243
+ if (!config) {
244
+ logger.log(t('squad_mcp.not_configured', { mcp: mcpSlug }));
245
+ return { ok: false, error: 'not_configured' };
246
+ }
247
+
248
+ const connectorDef = getBuiltInConnector(config.connector);
249
+ if (!connectorDef) {
250
+ logger.log(t('squad_mcp.unknown_connector', { connector: config.connector }));
251
+ return { ok: false, error: 'unknown_connector' };
252
+ }
253
+
254
+ const { resolved, missing } = resolveConnectorEnv(connectorDef, config.config);
255
+ if (missing.length > 0) {
256
+ logger.log(t('squad_mcp.test_missing', { mcp: mcpSlug, keys: missing.join(', ') }));
257
+ return { ok: false, error: 'missing_config', missing };
258
+ }
259
+
260
+ const result = await runAction(config.connector, actionSlug, input, resolved);
261
+
262
+ if (result.ok) {
263
+ logger.log(JSON.stringify(result.result, null, 2));
264
+ } else {
265
+ logger.log(`Error: ${result.error}`);
266
+ }
267
+ return result;
268
+ }
269
+
270
+ module.exports = { runSquadMcp };