@omnizap-system/omnizap 2.6.1 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +54 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +625 -124
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +88 -9
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +10 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import OpenAI from 'openai';
|
|
9
|
+
import prettier from 'prettier';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
15
|
+
const modulesRoot = path.join(repoRoot, 'app', 'modules');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_MODEL = String(process.env.COMMAND_CONFIG_UX_ENRICH_MODEL || process.env.OPENAI_MODEL || 'gpt-4o-mini').trim() || 'gpt-4o-mini';
|
|
18
|
+
const DEFAULT_DELAY_MS = Math.max(0, Number.parseInt(String(process.env.COMMAND_CONFIG_UX_ENRICH_DELAY_MS || '120'), 10) || 120);
|
|
19
|
+
const MAX_ATTEMPTS = Math.max(1, Number.parseInt(String(process.env.COMMAND_CONFIG_UX_ENRICH_MAX_ATTEMPTS || '3'), 10) || 3);
|
|
20
|
+
|
|
21
|
+
const UX_FIELDS = ['resumo_usuario', 'quando_usar', 'exemplos_reais', 'resposta_esperada', 'erros_comuns_usuario', 'passos_se_der_erro'];
|
|
22
|
+
|
|
23
|
+
const OUTPUT_SCHEMA = z
|
|
24
|
+
.object({
|
|
25
|
+
resumo_usuario: z.string(),
|
|
26
|
+
quando_usar: z.array(z.string()),
|
|
27
|
+
exemplos_reais: z.array(
|
|
28
|
+
z.object({
|
|
29
|
+
situacao: z.string(),
|
|
30
|
+
comando: z.string(),
|
|
31
|
+
resposta_esperada: z.string(),
|
|
32
|
+
variacao: z.string().optional(),
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
resposta_esperada: z.array(z.string()),
|
|
36
|
+
erros_comuns_usuario: z.array(z.string()),
|
|
37
|
+
passos_se_der_erro: z.array(z.string()),
|
|
38
|
+
})
|
|
39
|
+
.strict();
|
|
40
|
+
|
|
41
|
+
const SYSTEM_PROMPT = ['Voce escreve textos de ajuda para usuario final de um bot WhatsApp.', 'Responda SOMENTE JSON valido com as chaves exatas:', '{"resumo_usuario":"","quando_usar":[],"exemplos_reais":[{"situacao":"","comando":"","resposta_esperada":"","variacao":""}],"resposta_esperada":[],"erros_comuns_usuario":[],"passos_se_der_erro":[]}.', 'Regras:', '- pt-BR simples, objetivo e pratico.', '- Nao use linguagem tecnica de desenvolvimento.', '- Mostre acao concreta do usuario (o que fazer agora).', '- Comandos em exemplos devem manter "<prefix>" quando aplicavel.', '- Inclua restricao Premium quando existir.', '- Evite promessas absolutas e evite texto repetido.'].join(' ');
|
|
42
|
+
|
|
43
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
|
|
45
|
+
const isObject = (value) => Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
46
|
+
|
|
47
|
+
const ensureArray = (value) => (Array.isArray(value) ? value : []);
|
|
48
|
+
|
|
49
|
+
const normalizeText = (value) =>
|
|
50
|
+
String(value || '')
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/\s+/g, ' ');
|
|
53
|
+
|
|
54
|
+
const ensureSentence = (value) => {
|
|
55
|
+
const text = normalizeText(value);
|
|
56
|
+
if (!text) return '';
|
|
57
|
+
if (/[.!?]$/.test(text)) return text;
|
|
58
|
+
return `${text}.`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const uniqueStrings = (values, { max = 8, minLength = 2, maxLength = 220 } = {}) => {
|
|
62
|
+
const out = [];
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (const value of ensureArray(values)) {
|
|
65
|
+
const normalized = normalizeText(value).slice(0, maxLength);
|
|
66
|
+
if (!normalized || normalized.length < minLength) continue;
|
|
67
|
+
const key = normalized.toLowerCase();
|
|
68
|
+
if (seen.has(key)) continue;
|
|
69
|
+
seen.add(key);
|
|
70
|
+
out.push(normalized);
|
|
71
|
+
if (out.length >= max) break;
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const parseArgs = (argv) => {
|
|
77
|
+
const options = {
|
|
78
|
+
moduleFilter: '',
|
|
79
|
+
commandFilter: '',
|
|
80
|
+
limit: Number.POSITIVE_INFINITY,
|
|
81
|
+
overwrite: false,
|
|
82
|
+
dryRun: false,
|
|
83
|
+
model: DEFAULT_MODEL,
|
|
84
|
+
delayMs: DEFAULT_DELAY_MS,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
for (const arg of argv) {
|
|
88
|
+
if (!arg) continue;
|
|
89
|
+
if (arg === '--overwrite') {
|
|
90
|
+
options.overwrite = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg === '--dry-run') {
|
|
94
|
+
options.dryRun = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg.startsWith('--module=')) {
|
|
98
|
+
options.moduleFilter = normalizeText(arg.slice('--module='.length)).toLowerCase();
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (arg.startsWith('--command=')) {
|
|
102
|
+
options.commandFilter = normalizeText(arg.slice('--command='.length)).toLowerCase();
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg.startsWith('--limit=')) {
|
|
106
|
+
const parsed = Number.parseInt(arg.slice('--limit='.length), 10);
|
|
107
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
108
|
+
options.limit = parsed;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (arg.startsWith('--model=')) {
|
|
113
|
+
const model = normalizeText(arg.slice('--model='.length));
|
|
114
|
+
if (model) options.model = model;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (arg.startsWith('--delay-ms=')) {
|
|
118
|
+
const parsed = Number.parseInt(arg.slice('--delay-ms='.length), 10);
|
|
119
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
120
|
+
options.delayMs = parsed;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return options;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const listModuleConfigPaths = async () => {
|
|
129
|
+
const dirs = await fs.readdir(modulesRoot, { withFileTypes: true });
|
|
130
|
+
const files = [];
|
|
131
|
+
for (const entry of dirs) {
|
|
132
|
+
if (!entry.isDirectory()) continue;
|
|
133
|
+
const configPath = path.join(modulesRoot, entry.name, 'commandConfig.json');
|
|
134
|
+
try {
|
|
135
|
+
await fs.access(configPath);
|
|
136
|
+
files.push(configPath);
|
|
137
|
+
} catch {
|
|
138
|
+
// ignore modules sem commandConfig
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return files.sort((a, b) => a.localeCompare(b, 'pt-BR'));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const commandMatchesFilter = (command, commandFilter) => {
|
|
145
|
+
if (!commandFilter) return true;
|
|
146
|
+
const name = normalizeText(command?.name).toLowerCase();
|
|
147
|
+
if (name.includes(commandFilter)) return true;
|
|
148
|
+
const aliases = ensureArray(command?.aliases).map((alias) => normalizeText(alias).toLowerCase());
|
|
149
|
+
return aliases.some((alias) => alias.includes(commandFilter));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const resolveRequirements = (command) => {
|
|
153
|
+
const req = isObject(command?.requirements) ? command.requirements : isObject(command?.pre_condicoes) ? command.pre_condicoes : {};
|
|
154
|
+
return {
|
|
155
|
+
group: Boolean(req.require_group ?? req.requer_grupo),
|
|
156
|
+
admin: Boolean(req.require_group_admin ?? req.requer_admin),
|
|
157
|
+
owner: Boolean(req.require_bot_owner ?? req.requer_admin_principal),
|
|
158
|
+
googleLogin: Boolean(req.require_google_login ?? req.requer_google_login),
|
|
159
|
+
nsfw: Boolean(req.require_nsfw_enabled ?? req.requer_nsfw),
|
|
160
|
+
media: Boolean(req.require_media ?? req.requer_midia),
|
|
161
|
+
reply: Boolean(req.require_reply_message ?? req.requer_mensagem_respondida),
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const resolvePremium = (command) => {
|
|
166
|
+
const access = isObject(command?.access) ? command.access : isObject(command?.acesso) ? command.acesso : {};
|
|
167
|
+
return {
|
|
168
|
+
premium: Boolean(access.premium_only ?? access.somente_premium),
|
|
169
|
+
plans: uniqueStrings(access.allowed_plans || access.planos_permitidos, { max: 8, maxLength: 40 }),
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const resolveUsageMethods = (command) =>
|
|
174
|
+
uniqueStrings(command?.metodos_de_uso || command?.usage, {
|
|
175
|
+
max: 6,
|
|
176
|
+
maxLength: 220,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const resolveResponses = (command) => {
|
|
180
|
+
const responses = isObject(command?.responses) ? command.responses : isObject(command?.respostas_padrao) ? command.respostas_padrao : {};
|
|
181
|
+
return {
|
|
182
|
+
success: normalizeText(responses.success || responses.sucesso),
|
|
183
|
+
usageError: normalizeText(responses.usage_error || responses.erro_uso),
|
|
184
|
+
permissionError: normalizeText(responses.permission_error || responses.erro_permissao),
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const resolveArgs = (command) =>
|
|
189
|
+
ensureArray(command?.arguments || command?.argumentos)
|
|
190
|
+
.map((arg) => ({
|
|
191
|
+
name: normalizeText(arg?.name || arg?.nome),
|
|
192
|
+
type: normalizeText(arg?.type || arg?.tipo || 'string'),
|
|
193
|
+
required: Boolean(arg?.required ?? arg?.obrigatorio),
|
|
194
|
+
description: normalizeText(arg?.description || arg?.descricao),
|
|
195
|
+
}))
|
|
196
|
+
.filter((arg) => arg.name)
|
|
197
|
+
.slice(0, 6);
|
|
198
|
+
|
|
199
|
+
const normalizeExampleCommand = (value, fallback) => {
|
|
200
|
+
const raw = normalizeText(value);
|
|
201
|
+
const candidate = raw || normalizeText(fallback);
|
|
202
|
+
if (!candidate) return '';
|
|
203
|
+
if (candidate.startsWith('/')) return `<prefix>${candidate.slice(1)}`;
|
|
204
|
+
return candidate;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const buildFallbackUx = (context) => {
|
|
208
|
+
const commandName = context?.name || 'comando';
|
|
209
|
+
const commandUsage = context?.usageMethods?.[0] || `<prefix>${commandName}`;
|
|
210
|
+
const description = normalizeText(context?.description) || `Use <prefix>${commandName} para executar esta acao`;
|
|
211
|
+
const successText = normalizeText(context?.responses?.success) || 'O bot confirma que executou com sucesso';
|
|
212
|
+
const usageErrorText = normalizeText(context?.responses?.usageError) || 'Se o formato estiver errado, o bot mostra como corrigir';
|
|
213
|
+
const permissionText = normalizeText(context?.responses?.permissionError) || 'Sem permissao, o bot informa o motivo';
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
resumo_usuario: ensureSentence(description),
|
|
217
|
+
quando_usar: uniqueStrings([`Quando voce precisa desta acao: ${ensureSentence(description)}`, context?.requirements?.group ? 'Funciona dentro de grupos.' : 'Pode ser usado no privado e em grupo.', context?.requirements?.admin ? 'Voce precisa ser admin para executar.' : '', context?.premium?.premium ? 'Disponivel para usuarios Premium.' : ''], { max: 5 }),
|
|
218
|
+
exemplos_reais: [
|
|
219
|
+
{
|
|
220
|
+
situacao: ensureSentence(`Cenario comum para usar ${commandUsage}`),
|
|
221
|
+
comando: normalizeExampleCommand(commandUsage, `<prefix>${commandName}`),
|
|
222
|
+
resposta_esperada: ensureSentence(successText),
|
|
223
|
+
variacao: ensureSentence(usageErrorText),
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
resposta_esperada: uniqueStrings([`Sucesso: ${ensureSentence(successText)}`, `Uso incorreto: ${ensureSentence(usageErrorText)}`, `Permissao: ${ensureSentence(permissionText)}`], { max: 5 }),
|
|
227
|
+
erros_comuns_usuario: uniqueStrings(['Digitar o comando fora do formato esperado.', context?.requirements?.group ? 'Tentar executar fora de um grupo.' : '', context?.requirements?.admin ? 'Tentar executar sem ser admin.' : '', context?.premium?.premium ? 'Tentar usar sem plano Premium ativo.' : ''], { max: 5 }),
|
|
228
|
+
passos_se_der_erro: uniqueStrings(['Copie e teste um exemplo desta pagina.', 'Confira se voce esta no local correto e com permissao.', 'Se continuar com erro, fale com o admin no privado.'], { max: 5 }),
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const sanitizeUxPayload = (payload, context) => {
|
|
233
|
+
const fallback = buildFallbackUx(context);
|
|
234
|
+
const usageFallback = context?.usageMethods?.[0] || `<prefix>${context?.name || 'comando'}`;
|
|
235
|
+
|
|
236
|
+
const examples = ensureArray(payload?.exemplos_reais)
|
|
237
|
+
.map((example) => {
|
|
238
|
+
if (!isObject(example)) return null;
|
|
239
|
+
const situacao = ensureSentence(example.situacao) || ensureSentence(fallback.exemplos_reais[0].situacao);
|
|
240
|
+
const comando = normalizeExampleCommand(example.comando, usageFallback);
|
|
241
|
+
const resposta = ensureSentence(example.resposta_esperada) || ensureSentence(fallback.exemplos_reais[0].resposta_esperada);
|
|
242
|
+
const variacao = ensureSentence(example.variacao || '') || ensureSentence(fallback.exemplos_reais[0].variacao);
|
|
243
|
+
if (!comando) return null;
|
|
244
|
+
return {
|
|
245
|
+
situacao,
|
|
246
|
+
comando,
|
|
247
|
+
resposta_esperada: resposta,
|
|
248
|
+
variacao,
|
|
249
|
+
};
|
|
250
|
+
})
|
|
251
|
+
.filter(Boolean)
|
|
252
|
+
.slice(0, 3);
|
|
253
|
+
|
|
254
|
+
const normalized = {
|
|
255
|
+
resumo_usuario: ensureSentence(payload?.resumo_usuario) || fallback.resumo_usuario,
|
|
256
|
+
quando_usar: uniqueStrings(payload?.quando_usar, { max: 5 }),
|
|
257
|
+
exemplos_reais: examples,
|
|
258
|
+
resposta_esperada: uniqueStrings(payload?.resposta_esperada, { max: 5 }),
|
|
259
|
+
erros_comuns_usuario: uniqueStrings(payload?.erros_comuns_usuario, { max: 5 }),
|
|
260
|
+
passos_se_der_erro: uniqueStrings(payload?.passos_se_der_erro, { max: 5 }),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (!normalized.quando_usar.length) normalized.quando_usar = fallback.quando_usar;
|
|
264
|
+
if (!normalized.exemplos_reais.length) normalized.exemplos_reais = fallback.exemplos_reais;
|
|
265
|
+
if (!normalized.resposta_esperada.length) normalized.resposta_esperada = fallback.resposta_esperada;
|
|
266
|
+
if (!normalized.erros_comuns_usuario.length) normalized.erros_comuns_usuario = fallback.erros_comuns_usuario;
|
|
267
|
+
if (!normalized.passos_se_der_erro.length) normalized.passos_se_der_erro = fallback.passos_se_der_erro;
|
|
268
|
+
|
|
269
|
+
return normalized;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const extractCurrentUx = (command) => {
|
|
273
|
+
const userExperience = isObject(command?.user_experience) ? command.user_experience : {};
|
|
274
|
+
return {
|
|
275
|
+
resumo_usuario: userExperience.resumo_usuario ?? command?.resumo_usuario ?? '',
|
|
276
|
+
quando_usar: userExperience.quando_usar ?? command?.quando_usar ?? [],
|
|
277
|
+
exemplos_reais: userExperience.exemplos_reais ?? command?.exemplos_reais ?? [],
|
|
278
|
+
resposta_esperada: userExperience.resposta_esperada ?? command?.resposta_esperada ?? [],
|
|
279
|
+
erros_comuns_usuario: userExperience.erros_comuns_usuario ?? command?.erros_comuns_usuario ?? [],
|
|
280
|
+
passos_se_der_erro: userExperience.passos_se_der_erro ?? command?.passos_se_der_erro ?? [],
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const hasCompleteUx = (command) => {
|
|
285
|
+
const ux = extractCurrentUx(command);
|
|
286
|
+
return normalizeText(ux.resumo_usuario).length >= 8 && uniqueStrings(ux.quando_usar, { max: 10 }).length > 0 && ensureArray(ux.exemplos_reais).length > 0 && uniqueStrings(ux.resposta_esperada, { max: 10 }).length > 0 && uniqueStrings(ux.erros_comuns_usuario, { max: 10 }).length > 0 && uniqueStrings(ux.passos_se_der_erro, { max: 10 }).length > 0;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const buildCommandContext = ({ moduleDirName, command }) => {
|
|
290
|
+
const name = normalizeText(command?.name);
|
|
291
|
+
const description = normalizeText(command?.description || command?.descricao);
|
|
292
|
+
const usageMethods = resolveUsageMethods(command);
|
|
293
|
+
const requirements = resolveRequirements(command);
|
|
294
|
+
const premium = resolvePremium(command);
|
|
295
|
+
const responses = resolveResponses(command);
|
|
296
|
+
const argumentsList = resolveArgs(command);
|
|
297
|
+
const aliases = uniqueStrings(command?.aliases, { max: 8, maxLength: 60 });
|
|
298
|
+
const category = normalizeText(command?.categoria || command?.category);
|
|
299
|
+
const currentUx = extractCurrentUx(command);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
module: moduleDirName,
|
|
303
|
+
name,
|
|
304
|
+
aliases,
|
|
305
|
+
category,
|
|
306
|
+
description,
|
|
307
|
+
usageMethods,
|
|
308
|
+
requirements,
|
|
309
|
+
premium,
|
|
310
|
+
responses,
|
|
311
|
+
arguments: argumentsList,
|
|
312
|
+
currentUx,
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const extractJsonContent = (completion) => {
|
|
317
|
+
const content = completion?.choices?.[0]?.message?.content;
|
|
318
|
+
if (typeof content === 'string') return content;
|
|
319
|
+
if (Array.isArray(content)) {
|
|
320
|
+
return content
|
|
321
|
+
.map((item) => {
|
|
322
|
+
if (!item) return '';
|
|
323
|
+
if (typeof item === 'string') return item;
|
|
324
|
+
if (typeof item?.text === 'string') return item.text;
|
|
325
|
+
if (typeof item?.text?.value === 'string') return item.text.value;
|
|
326
|
+
return '';
|
|
327
|
+
})
|
|
328
|
+
.filter(Boolean)
|
|
329
|
+
.join('\n')
|
|
330
|
+
.trim();
|
|
331
|
+
}
|
|
332
|
+
return '';
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const generateUxWithOpenAI = async ({ client, model, context }) => {
|
|
336
|
+
const userPayload = {
|
|
337
|
+
objective: 'Gerar conteudo de pagina de comando focado em usuario final',
|
|
338
|
+
output_keys: UX_FIELDS,
|
|
339
|
+
command_context: context,
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
let lastError = null;
|
|
343
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
|
|
344
|
+
try {
|
|
345
|
+
const requestPayload = {
|
|
346
|
+
model,
|
|
347
|
+
response_format: { type: 'json_object' },
|
|
348
|
+
messages: [
|
|
349
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
350
|
+
{ role: 'user', content: JSON.stringify(userPayload, null, 2) },
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
if (
|
|
354
|
+
!String(model || '')
|
|
355
|
+
.trim()
|
|
356
|
+
.toLowerCase()
|
|
357
|
+
.startsWith('gpt-5')
|
|
358
|
+
) {
|
|
359
|
+
requestPayload.temperature = 0.2;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const completion = await client.chat.completions.create(requestPayload);
|
|
363
|
+
|
|
364
|
+
const content = extractJsonContent(completion);
|
|
365
|
+
const parsed = JSON.parse(content);
|
|
366
|
+
const validated = OUTPUT_SCHEMA.parse(parsed);
|
|
367
|
+
return sanitizeUxPayload(validated, context);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
lastError = error;
|
|
370
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
371
|
+
await sleep(400 * attempt);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
throw lastError || new Error('Falha ao gerar UX com OpenAI');
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const writeFormattedJson = async (filePath, payload) => {
|
|
380
|
+
const serialized = `${JSON.stringify(payload, null, 2)}\n`;
|
|
381
|
+
const prettierConfig = (await prettier.resolveConfig(filePath)) || {};
|
|
382
|
+
const formatted = await prettier.format(serialized, {
|
|
383
|
+
...prettierConfig,
|
|
384
|
+
parser: 'json',
|
|
385
|
+
filepath: filePath,
|
|
386
|
+
});
|
|
387
|
+
await fs.writeFile(filePath, formatted, 'utf8');
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const main = async () => {
|
|
391
|
+
const options = parseArgs(process.argv.slice(2));
|
|
392
|
+
const apiKey = normalizeText(process.env.OPENAI_API_KEY);
|
|
393
|
+
if (!apiKey) {
|
|
394
|
+
throw new Error('OPENAI_API_KEY nao configurada. Defina no ambiente ou no arquivo .env');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const client = new OpenAI({
|
|
398
|
+
apiKey,
|
|
399
|
+
timeout: 30_000,
|
|
400
|
+
maxRetries: 1,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const configPaths = await listModuleConfigPaths();
|
|
404
|
+
const stats = {
|
|
405
|
+
scanned: 0,
|
|
406
|
+
target: 0,
|
|
407
|
+
updated: 0,
|
|
408
|
+
skippedExisting: 0,
|
|
409
|
+
failed: 0,
|
|
410
|
+
filesChanged: 0,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
console.log(`[ux-enrich] model=${options.model} dryRun=${options.dryRun} overwrite=${options.overwrite} limit=${Number.isFinite(options.limit) ? options.limit : 'all'} delayMs=${options.delayMs}`);
|
|
414
|
+
|
|
415
|
+
for (const configPath of configPaths) {
|
|
416
|
+
const moduleDirName = path.basename(path.dirname(configPath));
|
|
417
|
+
if (options.moduleFilter && moduleDirName.toLowerCase() !== options.moduleFilter) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const raw = await fs.readFile(configPath, 'utf8');
|
|
422
|
+
const parsed = JSON.parse(raw);
|
|
423
|
+
const commands = ensureArray(parsed?.commands);
|
|
424
|
+
|
|
425
|
+
let fileChanged = false;
|
|
426
|
+
|
|
427
|
+
for (const command of commands) {
|
|
428
|
+
if (stats.target >= options.limit) break;
|
|
429
|
+
if (!isObject(command)) continue;
|
|
430
|
+
if (command.enabled === false) continue;
|
|
431
|
+
if (!commandMatchesFilter(command, options.commandFilter)) continue;
|
|
432
|
+
|
|
433
|
+
stats.scanned += 1;
|
|
434
|
+
|
|
435
|
+
if (!options.overwrite && hasCompleteUx(command)) {
|
|
436
|
+
stats.skippedExisting += 1;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const commandName = normalizeText(command.name);
|
|
441
|
+
if (!commandName) continue;
|
|
442
|
+
|
|
443
|
+
stats.target += 1;
|
|
444
|
+
const context = buildCommandContext({ moduleDirName, command });
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const ux = await generateUxWithOpenAI({
|
|
448
|
+
client,
|
|
449
|
+
model: options.model,
|
|
450
|
+
context,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const previousUx = isObject(command.user_experience) ? command.user_experience : {};
|
|
454
|
+
command.user_experience = {
|
|
455
|
+
...previousUx,
|
|
456
|
+
...ux,
|
|
457
|
+
resumo_usuario_origem: 'auto_ia_assistida',
|
|
458
|
+
resumo_usuario_revisao_pendente: true,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
stats.updated += 1;
|
|
462
|
+
fileChanged = true;
|
|
463
|
+
console.log(`[ux-enrich] ok ${moduleDirName}/${commandName}`);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
stats.failed += 1;
|
|
466
|
+
console.warn(`[ux-enrich] fail ${moduleDirName}/${commandName}: ${error?.message || 'erro desconhecido'}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (options.delayMs > 0) {
|
|
470
|
+
await sleep(options.delayMs);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (fileChanged) {
|
|
475
|
+
stats.filesChanged += 1;
|
|
476
|
+
if (!options.dryRun) {
|
|
477
|
+
await writeFormattedJson(configPath, parsed);
|
|
478
|
+
}
|
|
479
|
+
console.log(`[ux-enrich] file ${options.dryRun ? 'would-update' : 'updated'} ${path.relative(repoRoot, configPath)}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (stats.target >= options.limit) break;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log('[ux-enrich] done');
|
|
486
|
+
console.log([`scanned=${stats.scanned}`, `target=${stats.target}`, `updated=${stats.updated}`, `skipped_existing=${stats.skippedExisting}`, `failed=${stats.failed}`, `files_changed=${stats.filesChanged}`].join(' '));
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
main().catch((error) => {
|
|
490
|
+
console.error(`[ux-enrich] fatal: ${error?.message || error}`);
|
|
491
|
+
process.exitCode = 1;
|
|
492
|
+
});
|
|
@@ -60,6 +60,143 @@ const resolveCategoryMeta = (key) => {
|
|
|
60
60
|
return { label, icon: '🧩' };
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
const pickFirstText = (...values) => {
|
|
64
|
+
for (const value of values) {
|
|
65
|
+
const text = String(value || '').trim();
|
|
66
|
+
if (text) return text;
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ensureSentence = (value) => {
|
|
72
|
+
const text = String(value || '')
|
|
73
|
+
.trim()
|
|
74
|
+
.replace(/\s+/g, ' ');
|
|
75
|
+
if (!text) return '';
|
|
76
|
+
if (/[.!?]$/.test(text)) return text;
|
|
77
|
+
return `${text}.`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const parseOptionalBoolean = (value) => {
|
|
81
|
+
if (value === true) return true;
|
|
82
|
+
if (value === false) return false;
|
|
83
|
+
const normalized = String(value || '')
|
|
84
|
+
.trim()
|
|
85
|
+
.toLowerCase();
|
|
86
|
+
if (!normalized) return null;
|
|
87
|
+
if (['1', 'true', 'yes', 'sim'].includes(normalized)) return true;
|
|
88
|
+
if (['0', 'false', 'no', 'nao', 'não'].includes(normalized)) return false;
|
|
89
|
+
return null;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const normalizeUserList = (value) => unique(ensureArray(value).map((item) => String(item || '').trim()));
|
|
93
|
+
|
|
94
|
+
const normalizeExampleCommand = (value, fallback = '') => {
|
|
95
|
+
const raw = String(value || '').trim();
|
|
96
|
+
if (!raw) return String(fallback || '').trim();
|
|
97
|
+
return raw.replaceAll('<prefix>', '/');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const normalizeUserExample = (value, { fallbackSituation = '', fallbackCommand = '', fallbackExpected = '', fallbackVariation = '' } = {}) => {
|
|
101
|
+
if (!value) return null;
|
|
102
|
+
|
|
103
|
+
if (typeof value === 'string') {
|
|
104
|
+
const commandText = normalizeExampleCommand(value, fallbackCommand);
|
|
105
|
+
if (!commandText) return null;
|
|
106
|
+
return {
|
|
107
|
+
situacao: ensureSentence(fallbackSituation || 'Exemplo real de uso do comando'),
|
|
108
|
+
comando: commandText,
|
|
109
|
+
resposta_esperada: ensureSentence(fallbackExpected || 'O bot confirma a execução'),
|
|
110
|
+
variacao: ensureSentence(fallbackVariation || ''),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
115
|
+
const situacao = pickFirstText(value.situacao, value.cenario, value.contexto, fallbackSituation);
|
|
116
|
+
const comando = normalizeExampleCommand(pickFirstText(value.comando, value.command, value.uso), fallbackCommand);
|
|
117
|
+
const respostaEsperada = pickFirstText(value.resposta_esperada, value.expected_response, value.resposta, fallbackExpected);
|
|
118
|
+
const variacao = pickFirstText(value.variacao, value.outcome_variation, fallbackVariation);
|
|
119
|
+
|
|
120
|
+
if (!comando) return null;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
situacao: ensureSentence(situacao || 'Exemplo real de uso do comando'),
|
|
124
|
+
comando,
|
|
125
|
+
resposta_esperada: ensureSentence(respostaEsperada || 'O bot confirma a execução'),
|
|
126
|
+
variacao: ensureSentence(variacao || ''),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const buildUserExperienceContract = ({ commandName = '', description = '', docsSummary = '', usageMethods = [], responses = {}, requirements = {}, premium = false, rawUserExperience = {} } = {}) => {
|
|
134
|
+
const explicitSummary = pickFirstText(rawUserExperience.resumo_usuario, rawUserExperience.summary, rawUserExperience.resumo_ia);
|
|
135
|
+
const explicitSummaryOrigin = pickFirstText(rawUserExperience.resumo_usuario_origem, rawUserExperience.summary_origin);
|
|
136
|
+
const normalizedSummaryOrigin = explicitSummaryOrigin === 'manual' || explicitSummaryOrigin === 'auto_ia_assistida' ? explicitSummaryOrigin : '';
|
|
137
|
+
const summaryBase = pickFirstText(explicitSummary, docsSummary, description, `Use /${commandName} para executar esta ação no bot`);
|
|
138
|
+
const resumoUsuario = ensureSentence(summaryBase);
|
|
139
|
+
const summaryOrigin = normalizedSummaryOrigin || (explicitSummary ? 'manual' : 'auto_ia_assistida');
|
|
140
|
+
const explicitReviewPending = parseOptionalBoolean(rawUserExperience.resumo_usuario_revisao_pendente);
|
|
141
|
+
|
|
142
|
+
const explicitWhenToUse = normalizeUserList(rawUserExperience.quando_usar || rawUserExperience.when_to_use);
|
|
143
|
+
const descriptionNoPunctuation = String(description || '')
|
|
144
|
+
.trim()
|
|
145
|
+
.replace(/[.!?]+$/, '');
|
|
146
|
+
const descriptionSentence = ensureSentence(descriptionNoPunctuation);
|
|
147
|
+
const derivedWhenToUse = unique([descriptionSentence ? `Use quando você precisa desta ação: ${descriptionSentence}` : '', requirements.group ? 'Funciona dentro de grupos.' : 'Pode ser usado no privado e em grupo.', requirements.admin ? 'Você precisa ser admin para executar.' : '', requirements.owner ? 'Esse comando é restrito ao admin principal do sistema.' : '', requirements.google_login ? 'É necessário estar logado no sistema para usar.' : '', premium ? 'Disponível para usuários Premium.' : ''].filter(Boolean));
|
|
148
|
+
const quandoUsar = explicitWhenToUse.length ? explicitWhenToUse : derivedWhenToUse.slice(0, 5);
|
|
149
|
+
|
|
150
|
+
const successResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_sucesso, responses.success, responses.sucesso, 'O bot confirma que executou o comando'));
|
|
151
|
+
const usageErrorResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_uso_incorreto, responses.usage_error, responses.erro_uso, 'Se o formato estiver incorreto, o bot mostra como usar corretamente'));
|
|
152
|
+
const permissionResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_sem_permissao, responses.permission_error, responses.erro_permissao, premium ? 'Sem plano Premium ativo, o bot informa a restrição de acesso' : 'Sem permissão suficiente, o bot informa o motivo'));
|
|
153
|
+
|
|
154
|
+
const explicitExpectedResponses = normalizeUserList(rawUserExperience.resposta_esperada || rawUserExperience.expected_response);
|
|
155
|
+
const respostaEsperada = explicitExpectedResponses.length ? explicitExpectedResponses : unique([`Sucesso: ${successResponse}`, `Uso incorreto: ${usageErrorResponse}`, `Permissão: ${permissionResponse}`]);
|
|
156
|
+
|
|
157
|
+
const explicitExamples = ensureArray(rawUserExperience.exemplos_reais || rawUserExperience.real_examples);
|
|
158
|
+
const defaultSituation = descriptionSentence ? `Cenário comum: ${descriptionSentence}` : `Cenário comum: você quer usar /${commandName} no dia a dia.`;
|
|
159
|
+
const fallbackUsageMethods = usageMethods.length ? usageMethods : [`/${commandName}`];
|
|
160
|
+
let exemplosReais = explicitExamples
|
|
161
|
+
.map((example) =>
|
|
162
|
+
normalizeUserExample(example, {
|
|
163
|
+
fallbackSituation: defaultSituation,
|
|
164
|
+
fallbackCommand: fallbackUsageMethods[0] || `/${commandName}`,
|
|
165
|
+
fallbackExpected: successResponse,
|
|
166
|
+
fallbackVariation: usageErrorResponse,
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
.filter(Boolean);
|
|
170
|
+
|
|
171
|
+
if (!exemplosReais.length) {
|
|
172
|
+
exemplosReais = fallbackUsageMethods.slice(0, 3).map((usageMethod, index) => ({
|
|
173
|
+
situacao: ensureSentence(index === 0 ? defaultSituation : `Variação ${index + 1} de uso do comando no mesmo contexto`),
|
|
174
|
+
comando: normalizeExampleCommand(usageMethod, `/${commandName}`),
|
|
175
|
+
resposta_esperada: successResponse,
|
|
176
|
+
variacao: usageErrorResponse,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const explicitCommonErrors = normalizeUserList(rawUserExperience.erros_comuns_usuario || rawUserExperience.common_user_errors);
|
|
181
|
+
const derivedCommonErrors = unique(['Digitar o comando fora do formato esperado.', requirements.group ? 'Tentar executar fora de um grupo.' : '', requirements.admin ? 'Tentar executar sem ser admin.' : '', requirements.owner ? 'Tentar executar sem ser admin principal do sistema.' : '', requirements.google_login ? 'Tentar usar sem estar logado no sistema.' : '', premium ? 'Tentar usar sem acesso Premium ativo.' : ''].filter(Boolean));
|
|
182
|
+
const errosComunsUsuario = explicitCommonErrors.length ? explicitCommonErrors : derivedCommonErrors.slice(0, 5);
|
|
183
|
+
|
|
184
|
+
const explicitErrorSteps = normalizeUserList(rawUserExperience.passos_se_der_erro || rawUserExperience.error_steps);
|
|
185
|
+
const fallbackErrorSteps = ['Copie e teste um exemplo pronto desta página.', 'Confira se você está no local certo (grupo/privado) e com a permissão necessária.', premium ? 'Verifique se seu acesso Premium está ativo.' : '', 'Se ainda falhar, fale com o admin do sistema no privado.'].filter(Boolean);
|
|
186
|
+
const passosSeDerErro = explicitErrorSteps.length ? explicitErrorSteps : fallbackErrorSteps;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
resumo_usuario: resumoUsuario,
|
|
190
|
+
quando_usar: quandoUsar,
|
|
191
|
+
exemplos_reais: exemplosReais,
|
|
192
|
+
resposta_esperada: respostaEsperada,
|
|
193
|
+
erros_comuns_usuario: errosComunsUsuario,
|
|
194
|
+
passos_se_der_erro: passosSeDerErro,
|
|
195
|
+
resumo_usuario_origem: summaryOrigin,
|
|
196
|
+
resumo_usuario_revisao_pendente: explicitReviewPending ?? summaryOrigin !== 'manual',
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
63
200
|
const deepMerge = (target, source) => {
|
|
64
201
|
if (!source) return target;
|
|
65
202
|
const output = { ...target };
|
|
@@ -162,6 +299,23 @@ const sanitizeCommand = ({ command: rawCommand, moduleDefaults, moduleDirName, m
|
|
|
162
299
|
const sideEffects = unique([...ensureArray(command?.efeitos_colaterais), ...ensureArray(command?.side_effects)]);
|
|
163
300
|
|
|
164
301
|
const responses = deepMerge(moduleDefaults?.responses || moduleDefaults?.respostas_padrao || {}, command?.responses || command?.respostas_padrao || {});
|
|
302
|
+
const userExperienceSeed = deepMerge(moduleDefaults?.user_experience || moduleDefaults?.experiencia_usuario || {}, command?.user_experience || command?.experiencia_usuario || {});
|
|
303
|
+
if (command?.resumo_usuario !== undefined) userExperienceSeed.resumo_usuario = command.resumo_usuario;
|
|
304
|
+
if (command?.quando_usar !== undefined) userExperienceSeed.quando_usar = command.quando_usar;
|
|
305
|
+
if (command?.exemplos_reais !== undefined) userExperienceSeed.exemplos_reais = command.exemplos_reais;
|
|
306
|
+
if (command?.resposta_esperada !== undefined) userExperienceSeed.resposta_esperada = command.resposta_esperada;
|
|
307
|
+
if (command?.erros_comuns_usuario !== undefined) userExperienceSeed.erros_comuns_usuario = command.erros_comuns_usuario;
|
|
308
|
+
if (command?.passos_se_der_erro !== undefined) userExperienceSeed.passos_se_der_erro = command.passos_se_der_erro;
|
|
309
|
+
const userExperience = buildUserExperienceContract({
|
|
310
|
+
commandName,
|
|
311
|
+
description: String(command?.description || command?.descricao || '').trim(),
|
|
312
|
+
docsSummary: String(command?.docs?.summary || '').trim(),
|
|
313
|
+
usageMethods,
|
|
314
|
+
responses,
|
|
315
|
+
requirements,
|
|
316
|
+
premium,
|
|
317
|
+
rawUserExperience: userExperienceSeed,
|
|
318
|
+
});
|
|
165
319
|
|
|
166
320
|
const observability = deepMerge(moduleDefaults?.observability || {}, command?.observability || {});
|
|
167
321
|
const privacy = deepMerge(moduleDefaults?.privacy || {}, command?.privacy || {});
|
|
@@ -185,6 +339,7 @@ const sanitizeCommand = ({ command: rawCommand, moduleDefaults, moduleDirName, m
|
|
|
185
339
|
subcomandos: unique(ensureArray(command?.subcomandos).map((item) => String(item).trim())),
|
|
186
340
|
metodos_de_uso: usageMethods,
|
|
187
341
|
mensagens_uso: normalizedUsageVariants,
|
|
342
|
+
...userExperience,
|
|
188
343
|
arguments: args,
|
|
189
344
|
responses,
|
|
190
345
|
technical: {
|