@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,602 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+
6
+ const SQUADS_DIR = path.join('.aioson', 'squads');
7
+ const INTEGRATIONS_DIR = 'integrations';
8
+
9
+ /**
10
+ * Built-in connector definitions.
11
+ * Each connector declares its config schema, available actions, and optional health-check URL pattern.
12
+ */
13
+ const BUILT_IN_CONNECTORS = {
14
+ 'whatsapp-business': {
15
+ name: 'WhatsApp Business API',
16
+ configSchema: {
17
+ phone_id: { type: 'string', env: 'WHATSAPP_PHONE_ID', secret: false, required: true },
18
+ api_token: { type: 'string', env: 'WHATSAPP_API_TOKEN', secret: true, required: true }
19
+ },
20
+ actions: {
21
+ send_message: { input: ['to', 'body'], output: ['message_id'] },
22
+ send_template: { input: ['to', 'template_name', 'params'], output: ['message_id'] }
23
+ },
24
+ baseUrl: 'https://graph.facebook.com/v18.0',
25
+ healthPath: '/{phone_id}'
26
+ },
27
+
28
+ 'telegram-bot': {
29
+ name: 'Telegram Bot API',
30
+ configSchema: {
31
+ bot_token: { type: 'string', env: 'TELEGRAM_BOT_TOKEN', secret: true, required: true },
32
+ chat_id: { type: 'string', env: 'TELEGRAM_CHAT_ID', secret: false, required: false }
33
+ },
34
+ actions: {
35
+ send_message: { input: ['chat_id', 'text'], output: ['message_id'] },
36
+ get_updates: { input: [], output: ['updates'] }
37
+ },
38
+ baseUrl: 'https://api.telegram.org',
39
+ healthPath: '/bot{bot_token}/getMe'
40
+ },
41
+
42
+ 'smtp-email': {
43
+ name: 'SMTP Email',
44
+ configSchema: {
45
+ smtp_host: { type: 'string', env: 'SMTP_HOST', secret: false, required: true },
46
+ smtp_port: { type: 'number', env: 'SMTP_PORT', secret: false, required: false },
47
+ smtp_user: { type: 'string', env: 'SMTP_USER', secret: false, required: true },
48
+ smtp_pass: { type: 'string', env: 'SMTP_PASS', secret: true, required: true },
49
+ from_email: { type: 'string', env: 'SMTP_FROM', secret: false, required: true }
50
+ },
51
+ actions: {
52
+ send_email: { input: ['to', 'subject', 'body'], output: ['message_id'] }
53
+ },
54
+ baseUrl: null,
55
+ healthPath: null
56
+ },
57
+
58
+ 'webhook-generic': {
59
+ name: 'Generic Webhook',
60
+ configSchema: {
61
+ webhook_url: { type: 'string', env: 'WEBHOOK_URL', secret: false, required: true },
62
+ auth_header: { type: 'string', env: 'WEBHOOK_AUTH', secret: true, required: false },
63
+ method: { type: 'string', env: null, secret: false, required: false }
64
+ },
65
+ actions: {
66
+ send: { input: ['payload'], output: ['response'] }
67
+ },
68
+ baseUrl: null,
69
+ healthPath: null
70
+ },
71
+
72
+ 'google-calendar': {
73
+ name: 'Google Calendar API',
74
+ configSchema: {
75
+ client_id: { type: 'string', env: 'GCAL_CLIENT_ID', secret: false, required: true },
76
+ client_secret: { type: 'string', env: 'GCAL_CLIENT_SECRET', secret: true, required: true },
77
+ refresh_token: { type: 'string', env: 'GCAL_REFRESH_TOKEN', secret: true, required: true },
78
+ calendar_id: { type: 'string', env: 'GCAL_CALENDAR_ID', secret: false, required: false }
79
+ },
80
+ actions: {
81
+ list_events: { input: ['date_from', 'date_to'], output: ['events'] },
82
+ create_event: { input: ['summary', 'start', 'end'], output: ['event_id'] }
83
+ },
84
+ baseUrl: 'https://www.googleapis.com/calendar/v3',
85
+ healthPath: '/users/me/calendarList'
86
+ },
87
+
88
+ 'mysql': {
89
+ name: 'MySQL / MariaDB',
90
+ configSchema: {
91
+ host: { type: 'string', env: 'DB_HOST', secret: false, required: true },
92
+ port: { type: 'number', env: 'DB_PORT', secret: false, required: false },
93
+ database: { type: 'string', env: 'DB_NAME', secret: false, required: true },
94
+ user: { type: 'string', env: 'DB_USER', secret: false, required: true },
95
+ password: { type: 'string', env: 'DB_PASSWORD', secret: true, required: true },
96
+ ssl: { type: 'string', env: 'DB_SSL', secret: false, required: false }
97
+ },
98
+ actions: {
99
+ query: { input: ['sql', 'params'], output: ['rows', 'rowCount'] },
100
+ query_one: { input: ['sql', 'params'], output: ['row'] },
101
+ execute: { input: ['sql', 'params'], output: ['affectedRows'] },
102
+ table_schema: { input: ['table'], output: ['columns'] }
103
+ },
104
+ baseUrl: null,
105
+ healthPath: null
106
+ },
107
+
108
+ 'postgres': {
109
+ name: 'PostgreSQL',
110
+ configSchema: {
111
+ host: { type: 'string', env: 'DB_HOST', secret: false, required: true },
112
+ port: { type: 'number', env: 'DB_PORT', secret: false, required: false },
113
+ database: { type: 'string', env: 'DB_NAME', secret: false, required: true },
114
+ user: { type: 'string', env: 'DB_USER', secret: false, required: true },
115
+ password: { type: 'string', env: 'DB_PASSWORD', secret: true, required: true },
116
+ ssl: { type: 'string', env: 'DB_SSL', secret: false, required: false }
117
+ },
118
+ actions: {
119
+ query: { input: ['sql', 'params'], output: ['rows', 'rowCount'] },
120
+ query_one: { input: ['sql', 'params'], output: ['row'] },
121
+ execute: { input: ['sql', 'params'], output: ['affectedRows'] },
122
+ table_schema: { input: ['table'], output: ['columns'] }
123
+ },
124
+ baseUrl: null,
125
+ healthPath: null
126
+ },
127
+
128
+ 'sqlite-external': {
129
+ name: 'SQLite (arquivo externo)',
130
+ configSchema: {
131
+ path: { type: 'string', env: 'SQLITE_PATH', secret: false, required: true }
132
+ },
133
+ actions: {
134
+ query: { input: ['sql', 'params'], output: ['rows', 'rowCount'] },
135
+ query_one: { input: ['sql', 'params'], output: ['row'] },
136
+ execute: { input: ['sql', 'params'], output: ['affectedRows'] },
137
+ table_schema: { input: ['table'], output: ['columns'] }
138
+ },
139
+ baseUrl: null,
140
+ healthPath: null
141
+ }
142
+ };
143
+
144
+ function getBuiltInConnector(connectorId) {
145
+ return BUILT_IN_CONNECTORS[connectorId] || null;
146
+ }
147
+
148
+ function listBuiltInConnectors() {
149
+ return Object.entries(BUILT_IN_CONNECTORS).map(([id, def]) => ({
150
+ id,
151
+ name: def.name,
152
+ actions: Object.keys(def.actions),
153
+ requiredConfig: Object.entries(def.configSchema)
154
+ .filter(([, spec]) => spec.required)
155
+ .map(([key]) => key)
156
+ }));
157
+ }
158
+
159
+ function integrationsDir(projectDir, squadSlug) {
160
+ return path.join(projectDir, SQUADS_DIR, squadSlug, INTEGRATIONS_DIR);
161
+ }
162
+
163
+ async function loadIntegrationConfig(projectDir, squadSlug, mcpSlug) {
164
+ const filePath = path.join(integrationsDir(projectDir, squadSlug), `${mcpSlug}.json`);
165
+ try {
166
+ const raw = await fs.readFile(filePath, 'utf8');
167
+ return JSON.parse(raw);
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ async function saveIntegrationConfig(projectDir, squadSlug, mcpSlug, config) {
174
+ const dir = integrationsDir(projectDir, squadSlug);
175
+ await fs.mkdir(dir, { recursive: true });
176
+ const filePath = path.join(dir, `${mcpSlug}.json`);
177
+ await fs.writeFile(filePath, JSON.stringify(config, null, 2));
178
+ }
179
+
180
+ async function listIntegrations(projectDir, squadSlug) {
181
+ const dir = integrationsDir(projectDir, squadSlug);
182
+ let entries;
183
+ try {
184
+ entries = await fs.readdir(dir);
185
+ } catch {
186
+ return [];
187
+ }
188
+ const results = [];
189
+ for (const file of entries) {
190
+ if (!file.endsWith('.json')) continue;
191
+ const slug = file.replace(/\.json$/, '');
192
+ const config = await loadIntegrationConfig(projectDir, squadSlug, slug);
193
+ if (config) {
194
+ results.push({ slug, ...config });
195
+ }
196
+ }
197
+ return results;
198
+ }
199
+
200
+ function resolveConfigValues(configSchema, savedConfig) {
201
+ const resolved = {};
202
+ const missing = [];
203
+ for (const [key, spec] of Object.entries(configSchema)) {
204
+ // Priority: saved config → env var → undefined
205
+ if (savedConfig && savedConfig[key] !== undefined) {
206
+ resolved[key] = savedConfig[key];
207
+ } else if (spec.env && process.env[spec.env]) {
208
+ resolved[key] = process.env[spec.env];
209
+ } else if (spec.required) {
210
+ missing.push(key);
211
+ }
212
+ }
213
+ return { resolved, missing };
214
+ }
215
+
216
+ function resolveConnectorEnv(connectorDef, savedConfig) {
217
+ const { resolved, missing } = resolveConfigValues(connectorDef.configSchema, savedConfig);
218
+ const status = missing.length === 0 ? 'configured' : 'unconfigured';
219
+ return { resolved, missing, status };
220
+ }
221
+
222
+ function buildWorkerMcpEnv(projectDir, squadSlug, mcpSlugs, integrationConfigs) {
223
+ const env = {};
224
+ for (const mcpSlug of (mcpSlugs || [])) {
225
+ const integration = integrationConfigs.find(i => i.slug === mcpSlug);
226
+ if (!integration) continue;
227
+ const connectorDef = getBuiltInConnector(integration.connector);
228
+ if (!connectorDef) continue;
229
+ const { resolved } = resolveConnectorEnv(connectorDef, integration.config);
230
+ // Expose as MCP_<SLUG> env var for the worker process
231
+ const envKey = `MCP_${mcpSlug.toUpperCase().replace(/-/g, '_')}`;
232
+ env[envKey] = JSON.stringify({
233
+ ...resolved,
234
+ connector: integration.connector,
235
+ base_url: connectorDef.baseUrl || null,
236
+ actions: Object.keys(connectorDef.actions)
237
+ });
238
+ }
239
+ return env;
240
+ }
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // DB lazy-load helpers
244
+ // ---------------------------------------------------------------------------
245
+
246
+ function requireMysql() {
247
+ try { return require('mysql2/promise'); }
248
+ catch { throw new Error('mysql2 não instalado. Execute: npm install mysql2'); }
249
+ }
250
+
251
+ function requirePg() {
252
+ try { const { Client } = require('pg'); return Client; }
253
+ catch { throw new Error('pg não instalado. Execute: npm install pg'); }
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // HTTP execution helpers
258
+ // ---------------------------------------------------------------------------
259
+
260
+ function buildHeaders(connectorSlug, cfg) {
261
+ if (connectorSlug === 'whatsapp-business') {
262
+ return { 'Authorization': `Bearer ${cfg.api_token}` };
263
+ }
264
+ return {};
265
+ }
266
+
267
+ const EXECUTORS = {
268
+ 'whatsapp-business': {
269
+ async send_message({ to, body }, cfg) {
270
+ const res = await fetch(`https://graph.facebook.com/v18.0/${cfg.phone_id}/messages`, {
271
+ method: 'POST',
272
+ headers: { 'Authorization': `Bearer ${cfg.api_token}`, 'Content-Type': 'application/json' },
273
+ body: JSON.stringify({ messaging_product: 'whatsapp', to, type: 'text', text: { body } }),
274
+ signal: AbortSignal.timeout(10000)
275
+ });
276
+ const json = await res.json();
277
+ if (!res.ok) throw new Error(json.error?.message || `HTTP ${res.status}`);
278
+ return { message_id: json.messages?.[0]?.id };
279
+ },
280
+ async send_template({ to, template_name, params = [] }, cfg) {
281
+ const components = params.length
282
+ ? [{ type: 'body', parameters: params.map(p => ({ type: 'text', text: String(p) })) }]
283
+ : [];
284
+ const res = await fetch(`https://graph.facebook.com/v18.0/${cfg.phone_id}/messages`, {
285
+ method: 'POST',
286
+ headers: { 'Authorization': `Bearer ${cfg.api_token}`, 'Content-Type': 'application/json' },
287
+ body: JSON.stringify({
288
+ messaging_product: 'whatsapp', to, type: 'template',
289
+ template: { name: template_name, language: { code: 'pt_BR' }, components }
290
+ }),
291
+ signal: AbortSignal.timeout(10000)
292
+ });
293
+ const json = await res.json();
294
+ if (!res.ok) throw new Error(json.error?.message || `HTTP ${res.status}`);
295
+ return { message_id: json.messages?.[0]?.id };
296
+ }
297
+ },
298
+
299
+ 'telegram-bot': {
300
+ async send_message({ chat_id, text }, cfg) {
301
+ const id = chat_id || cfg.chat_id;
302
+ const res = await fetch(`https://api.telegram.org/bot${cfg.bot_token}/sendMessage`, {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/json' },
305
+ body: JSON.stringify({ chat_id: id, text }),
306
+ signal: AbortSignal.timeout(10000)
307
+ });
308
+ const json = await res.json();
309
+ if (!res.ok || !json.ok) throw new Error(json.description || `HTTP ${res.status}`);
310
+ return { message_id: json.result?.message_id };
311
+ },
312
+ async get_updates(_input, cfg) {
313
+ const res = await fetch(`https://api.telegram.org/bot${cfg.bot_token}/getUpdates`, {
314
+ signal: AbortSignal.timeout(10000)
315
+ });
316
+ const json = await res.json();
317
+ if (!res.ok || !json.ok) throw new Error(json.description || `HTTP ${res.status}`);
318
+ return { updates: json.result };
319
+ }
320
+ },
321
+
322
+ 'webhook-generic': {
323
+ async send({ url, payload, method = 'POST', headers: extraHeaders = {} }, cfg) {
324
+ const target = url || cfg.webhook_url;
325
+ const res = await fetch(target, {
326
+ method,
327
+ headers: { 'Content-Type': 'application/json', ...extraHeaders },
328
+ body: JSON.stringify(payload),
329
+ signal: AbortSignal.timeout(15000)
330
+ });
331
+ return { status: res.status, ok: res.ok };
332
+ }
333
+ },
334
+
335
+ 'mysql': {
336
+ async query({ sql, params }, cfg) {
337
+ const mysql = requireMysql();
338
+ const conn = await mysql.createConnection({
339
+ host: cfg.host, port: cfg.port || 3306,
340
+ database: cfg.database, user: cfg.user, password: cfg.password,
341
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
342
+ });
343
+ try {
344
+ const [rows] = await conn.execute(sql, params || []);
345
+ return { rows, rowCount: rows.length };
346
+ } finally {
347
+ await conn.end();
348
+ }
349
+ },
350
+ async query_one({ sql, params }, cfg) {
351
+ const mysql = requireMysql();
352
+ const conn = await mysql.createConnection({
353
+ host: cfg.host, port: cfg.port || 3306,
354
+ database: cfg.database, user: cfg.user, password: cfg.password,
355
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
356
+ });
357
+ try {
358
+ const [rows] = await conn.execute(sql, params || []);
359
+ return { row: rows[0] || null };
360
+ } finally {
361
+ await conn.end();
362
+ }
363
+ },
364
+ async execute({ sql, params }, cfg) {
365
+ const mysql = requireMysql();
366
+ const conn = await mysql.createConnection({
367
+ host: cfg.host, port: cfg.port || 3306,
368
+ database: cfg.database, user: cfg.user, password: cfg.password,
369
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
370
+ });
371
+ try {
372
+ const [result] = await conn.execute(sql, params || []);
373
+ return { affectedRows: result.affectedRows };
374
+ } finally {
375
+ await conn.end();
376
+ }
377
+ },
378
+ async table_schema({ table }, cfg) {
379
+ const mysql = requireMysql();
380
+ const conn = await mysql.createConnection({
381
+ host: cfg.host, port: cfg.port || 3306,
382
+ database: cfg.database, user: cfg.user, password: cfg.password,
383
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
384
+ });
385
+ try {
386
+ const [rows] = await conn.execute(`SHOW COLUMNS FROM \`${table}\``);
387
+ const columns = rows.map(r => ({
388
+ name: r.Field,
389
+ type: r.Type.split('(')[0].toLowerCase(),
390
+ nullable: r.Null === 'YES',
391
+ pk: r.Key === 'PRI'
392
+ }));
393
+ return { columns };
394
+ } finally {
395
+ await conn.end();
396
+ }
397
+ }
398
+ },
399
+
400
+ 'postgres': {
401
+ async query({ sql, params }, cfg) {
402
+ const Client = requirePg();
403
+ const client = new Client({
404
+ host: cfg.host, port: cfg.port || 5432,
405
+ database: cfg.database, user: cfg.user, password: cfg.password,
406
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
407
+ });
408
+ await client.connect();
409
+ try {
410
+ const result = await client.query(sql, params || []);
411
+ return { rows: result.rows, rowCount: result.rowCount };
412
+ } finally {
413
+ await client.end();
414
+ }
415
+ },
416
+ async query_one({ sql, params }, cfg) {
417
+ const Client = requirePg();
418
+ const client = new Client({
419
+ host: cfg.host, port: cfg.port || 5432,
420
+ database: cfg.database, user: cfg.user, password: cfg.password,
421
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
422
+ });
423
+ await client.connect();
424
+ try {
425
+ const result = await client.query(sql, params || []);
426
+ return { row: result.rows[0] || null };
427
+ } finally {
428
+ await client.end();
429
+ }
430
+ },
431
+ async execute({ sql, params }, cfg) {
432
+ const Client = requirePg();
433
+ const client = new Client({
434
+ host: cfg.host, port: cfg.port || 5432,
435
+ database: cfg.database, user: cfg.user, password: cfg.password,
436
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
437
+ });
438
+ await client.connect();
439
+ try {
440
+ const result = await client.query(sql, params || []);
441
+ return { affectedRows: result.rowCount };
442
+ } finally {
443
+ await client.end();
444
+ }
445
+ },
446
+ async table_schema({ table }, cfg) {
447
+ const Client = requirePg();
448
+ const client = new Client({
449
+ host: cfg.host, port: cfg.port || 5432,
450
+ database: cfg.database, user: cfg.user, password: cfg.password,
451
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined
452
+ });
453
+ await client.connect();
454
+ try {
455
+ const result = await client.query(
456
+ `SELECT column_name, data_type, is_nullable,
457
+ (SELECT COUNT(*) FROM information_schema.table_constraints tc
458
+ JOIN information_schema.key_column_usage kcu
459
+ ON tc.constraint_name = kcu.constraint_name
460
+ AND tc.table_name = kcu.table_name
461
+ WHERE tc.constraint_type = 'PRIMARY KEY'
462
+ AND kcu.table_name = $1
463
+ AND kcu.column_name = c.column_name) > 0 AS pk
464
+ FROM information_schema.columns c
465
+ WHERE table_name = $1
466
+ ORDER BY ordinal_position`,
467
+ [table]
468
+ );
469
+ const columns = result.rows.map(r => ({
470
+ name: r.column_name,
471
+ type: r.data_type,
472
+ nullable: r.is_nullable === 'YES',
473
+ pk: r.pk
474
+ }));
475
+ return { columns };
476
+ } finally {
477
+ await client.end();
478
+ }
479
+ }
480
+ },
481
+
482
+ 'sqlite-external': {
483
+ async query({ sql, params }, cfg) {
484
+ const Database = require('better-sqlite3');
485
+ const db = new Database(cfg.path, { readonly: true });
486
+ try {
487
+ const rows = db.prepare(sql).all(params || []);
488
+ return { rows, rowCount: rows.length };
489
+ } finally {
490
+ db.close();
491
+ }
492
+ },
493
+ async query_one({ sql, params }, cfg) {
494
+ const Database = require('better-sqlite3');
495
+ const db = new Database(cfg.path, { readonly: true });
496
+ try {
497
+ const row = db.prepare(sql).get(params || []) || null;
498
+ return { row };
499
+ } finally {
500
+ db.close();
501
+ }
502
+ },
503
+ async execute({ sql, params }, cfg) {
504
+ const Database = require('better-sqlite3');
505
+ const db = new Database(cfg.path);
506
+ try {
507
+ const result = db.prepare(sql).run(params || []);
508
+ return { affectedRows: result.changes };
509
+ } finally {
510
+ db.close();
511
+ }
512
+ },
513
+ async table_schema({ table }, cfg) {
514
+ const Database = require('better-sqlite3');
515
+ const db = new Database(cfg.path, { readonly: true });
516
+ try {
517
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
518
+ const columns = rows.map(r => ({
519
+ name: r.name,
520
+ type: r.type.split('(')[0].toLowerCase(),
521
+ nullable: r.notnull === 0,
522
+ pk: r.pk > 0
523
+ }));
524
+ return { columns };
525
+ } finally {
526
+ db.close();
527
+ }
528
+ }
529
+ }
530
+ };
531
+
532
+ /**
533
+ * Execute a health check for a connector.
534
+ * Returns { ok: boolean, statusCode?, error?, skipped?, url? }
535
+ */
536
+ async function runHealthCheck(connectorSlug, resolvedConfig) {
537
+ const def = BUILT_IN_CONNECTORS[connectorSlug];
538
+ if (!def || !def.baseUrl || !def.healthPath) {
539
+ return { ok: true, skipped: true, reason: 'no_health_path' };
540
+ }
541
+
542
+ let url = def.baseUrl + def.healthPath;
543
+ for (const [key, val] of Object.entries(resolvedConfig)) {
544
+ url = url.replace(`{${key}}`, encodeURIComponent(val));
545
+ }
546
+
547
+ try {
548
+ const res = await fetch(url, {
549
+ method: 'GET',
550
+ headers: buildHeaders(connectorSlug, resolvedConfig),
551
+ signal: AbortSignal.timeout(8000)
552
+ });
553
+ return { ok: res.ok, statusCode: res.status, url };
554
+ } catch (err) {
555
+ return { ok: false, error: err.message, url };
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Execute a connector action.
561
+ * Returns { ok: boolean, result?, error? }
562
+ */
563
+ async function runAction(connectorSlug, actionSlug, input, resolvedConfig) {
564
+ const def = BUILT_IN_CONNECTORS[connectorSlug];
565
+ if (!def) return { ok: false, error: `Unknown connector: ${connectorSlug}` };
566
+ if (!def.actions[actionSlug]) return { ok: false, error: `Unknown action: ${actionSlug}` };
567
+
568
+ try {
569
+ const executor = EXECUTORS[connectorSlug]?.[actionSlug];
570
+ if (!executor) return { ok: false, error: 'No executor registered for this connector/action' };
571
+ const result = await executor(input, resolvedConfig);
572
+ return { ok: true, result };
573
+ } catch (err) {
574
+ return { ok: false, error: err.message };
575
+ }
576
+ }
577
+
578
+ async function loadCustomConnector(projectDir, squadSlug, connectorSlug) {
579
+ const filePath = path.join(projectDir, SQUADS_DIR, squadSlug, 'connectors', `${connectorSlug}.js`);
580
+ try {
581
+ await fs.access(filePath);
582
+ return require(filePath);
583
+ } catch {
584
+ return null;
585
+ }
586
+ }
587
+
588
+ module.exports = {
589
+ BUILT_IN_CONNECTORS,
590
+ getBuiltInConnector,
591
+ listBuiltInConnectors,
592
+ loadIntegrationConfig,
593
+ saveIntegrationConfig,
594
+ listIntegrations,
595
+ resolveConfigValues,
596
+ resolveConnectorEnv,
597
+ buildWorkerMcpEnv,
598
+ loadCustomConnector,
599
+ integrationsDir,
600
+ runHealthCheck,
601
+ runAction
602
+ };