@esquadrao/cli 0.0.0-stage → 1.6.0

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -2
  3. package/assets/fonts/Anton-Regular.ttf +0 -0
  4. package/assets/fonts/BebasNeue-Regular.ttf +0 -0
  5. package/assets/fonts/OFL.txt +93 -0
  6. package/assets/fonts/Poppins-Bold.ttf +0 -0
  7. package/assets/fonts/README.md +13 -0
  8. package/bin/cli.js +126 -0
  9. package/package.json +58 -4
  10. package/scripts/ast_slice.js +73 -0
  11. package/scripts/cutout.py +197 -0
  12. package/scripts/dag_validator.js +78 -0
  13. package/scripts/lint.js +43 -0
  14. package/scripts/merkle_verify.js +83 -0
  15. package/scripts/wcag_check.js +77 -0
  16. package/skill-audit/SKILL.md +42 -0
  17. package/skill-audit/references/merkle-ledger-spec.md +27 -0
  18. package/skill-audit/references/receipt-ed25519-verification.md +17 -0
  19. package/skill-audit/references/time-travel-replay.md +10 -0
  20. package/skill-code-reviewer/SKILL.md +40 -0
  21. package/skill-context/SKILL.md +44 -0
  22. package/skill-context/references/ast-virtualization.md +10 -0
  23. package/skill-context/references/openapi-contract-synthesis.md +10 -0
  24. package/skill-engineering-manager/SKILL.md +40 -0
  25. package/skill-media-producer/SKILL.md +328 -0
  26. package/skill-media-producer/references/caption-templates.md +194 -0
  27. package/skill-media-producer/references/composicoes-especiais.md +99 -0
  28. package/skill-media-producer/references/configuracao-inicial.md +239 -0
  29. package/skill-media-producer/references/corte-podcast-3-niveis.md +179 -0
  30. package/skill-media-producer/references/llm-agent-guide.md +231 -0
  31. package/skill-orchestrate/SKILL.md +49 -0
  32. package/skill-orchestrate/references/mission-lifecycle.md +36 -0
  33. package/skill-orchestrate/references/swarm-coordination.md +33 -0
  34. package/skill-pipeline/SKILL.md +42 -0
  35. package/skill-pipeline/references/auto-healing-patterns.md +17 -0
  36. package/skill-pipeline/references/dag-decomposition-rules.md +18 -0
  37. package/skill-pipeline/references/topological-waves.md +16 -0
  38. package/skill-product-manager/SKILL.md +40 -0
  39. package/skill-qa-engineer/SKILL.md +40 -0
  40. package/skill-review/SKILL.md +46 -0
  41. package/skill-review/references/anti-slop-patterns.md +17 -0
  42. package/skill-review/references/dual-blind-adjudication.md +15 -0
  43. package/skill-review/references/voice-calibration.md +10 -0
  44. package/skill-security-engineer/SKILL.md +40 -0
  45. package/skill-software-architect/SKILL.md +40 -0
  46. package/skill-software-engineer/SKILL.md +40 -0
  47. package/skill-sre-engineer/SKILL.md +40 -0
  48. package/skill-system-analyst/SKILL.md +40 -0
  49. package/skill-tech-lead/SKILL.md +40 -0
  50. package/skill-technical-writer/SKILL.md +40 -0
  51. package/skill-ui/SKILL.md +45 -0
  52. package/skill-ui/references/design-tokens-spec.md +19 -0
  53. package/skill-ui/references/visual-tdd-methodology.md +10 -0
  54. package/skill-ui/references/wcag-target-sizes.md +9 -0
  55. package/src/captionTemplates.js +285 -0
  56. package/src/commands/onboard.js +618 -0
  57. package/src/commands/role.js +119 -0
  58. package/src/commands/setup.js +174 -0
  59. package/src/commands/studio.js +1822 -0
  60. package/src/roles/authority.js +129 -0
  61. package/src/roles/codeReviewer.js +65 -0
  62. package/src/roles/engineeringManager.js +53 -0
  63. package/src/roles/index.js +80 -0
  64. package/src/roles/mediaProducer.js +223 -0
  65. package/src/roles/productManager.js +50 -0
  66. package/src/roles/qaEngineer.js +54 -0
  67. package/src/roles/securityEngineer.js +53 -0
  68. package/src/roles/softwareArchitect.js +58 -0
  69. package/src/roles/softwareEngineer.js +76 -0
  70. package/src/roles/sreEngineer.js +60 -0
  71. package/src/roles/systemAnalyst.js +45 -0
  72. package/src/roles/techLead.js +46 -0
  73. package/src/roles/technicalWriter.js +46 -0
@@ -0,0 +1,285 @@
1
+ // Canonical caption templates and deterministic FFmpeg filter builder.
2
+ // All geometry is authored on a 640x360 reference canvas and scaled by width.
3
+
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import crypto from 'node:crypto';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const PACKAGE_ROOT = fileURLToPath(new URL('../', import.meta.url));
11
+ const FONT_DIR = path.join(PACKAGE_ROOT, 'assets', 'fonts');
12
+
13
+ export const REFERENCE_CANVAS = Object.freeze({ width: 640, height: 360 });
14
+
15
+ const HOME_DIR = os.homedir();
16
+ const SYSTEM_FONT_PATHS = {
17
+ 'LiberationSans-Bold.ttf': [
18
+ '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
19
+ path.join(HOME_DIR, '.hermes/cache/libs/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf'),
20
+ ],
21
+ 'DejaVuSansMono-Bold.ttf': [
22
+ '/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf',
23
+ path.join(HOME_DIR, '.hermes/cache/libs/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf'),
24
+ ],
25
+ };
26
+
27
+ const BASE = {
28
+ fontFamily: 'Anton',
29
+ fontFile: 'Anton-Regular.ttf',
30
+ fontSize: 64,
31
+ fill: '#FFE600',
32
+ stroke: { color: '#000000', width: 7 },
33
+ shadow: { x: 4, y: 4, color: '#000000', opacity: 0.8 },
34
+ lines: 2,
35
+ y: [100, 170],
36
+ lineGap: 1.1,
37
+ uppercase: true,
38
+ animation: { type: 'fade', durationMs: 400 },
39
+ rule: null,
40
+ glitch: null,
41
+ };
42
+
43
+ export const CAPTION_TEMPLATES = Object.freeze([
44
+ { ...BASE, id: 'mrbeast-yellow', name: 'MrBeast Yellow' },
45
+ {
46
+ ...BASE,
47
+ id: 'tiktok-white',
48
+ lineGap: 1.2963,
49
+ name: 'TikTok White',
50
+ fontFamily: 'Poppins',
51
+ fontFile: 'Poppins-Bold.ttf',
52
+ fontSize: 54,
53
+ fill: '#FFFFFF',
54
+ stroke: { color: '#000000', width: 8 },
55
+ shadow: { x: 3, y: 3, color: '#000000', opacity: 0.7 },
56
+ y: [105, 175],
57
+ },
58
+ {
59
+ ...BASE,
60
+ id: 'gold-stroke',
61
+ name: 'Gold Stroke',
62
+ fontSize: 64,
63
+ fill: '#FFF6D6',
64
+ stroke: { color: '#FFC400', width: 5 },
65
+ shadow: { x: 3, y: 3, color: '#000000', opacity: 0.85 },
66
+ },
67
+ {
68
+ ...BASE,
69
+ id: 'green-stroke',
70
+ lineGap: 0.9714,
71
+ name: 'Green Stroke',
72
+ fontFamily: 'Bebas Neue',
73
+ fontFile: 'BebasNeue-Regular.ttf',
74
+ fontSize: 70,
75
+ fill: '#FFFFFF',
76
+ stroke: { color: '#00E676', width: 6 },
77
+ shadow: { x: 3, y: 3, color: '#000000', opacity: 0.85 },
78
+ y: [108, 176],
79
+ },
80
+ {
81
+ ...BASE,
82
+ id: 'neon-glitch',
83
+ lineGap: 1.129,
84
+ name: 'Neon Glitch',
85
+ fontSize: 62,
86
+ fill: '#FFFFFF',
87
+ stroke: { color: '#000000', width: 3 },
88
+ shadow: null,
89
+ glitch: {
90
+ offsets: [
91
+ { x: -4, y: 2, color: '#00E5FF' },
92
+ { x: 4, y: -2, color: '#FF00E5' },
93
+ ],
94
+ },
95
+ },
96
+ {
97
+ ...BASE,
98
+ id: 'minimal-lower-third',
99
+ name: 'Minimal Lower Third',
100
+ fontFamily: 'Liberation Sans',
101
+ fontFile: 'LiberationSans-Bold.ttf',
102
+ fontSize: 40,
103
+ fill: '#FFFFFF',
104
+ stroke: { color: '#000000', width: 3 },
105
+ shadow: { x: 2, y: 2, color: '#000000', opacity: 0.9 },
106
+ lines: 1,
107
+ y: [276],
108
+ rule: { y: 252, width: 500, height: 2, color: '#FFFFFF', opacity: 0.9 },
109
+ },
110
+ {
111
+ ...BASE,
112
+ id: 'red-stroke',
113
+ lineGap: 1.2963,
114
+ name: 'Red Stroke',
115
+ fontFamily: 'Poppins',
116
+ fontFile: 'Poppins-Bold.ttf',
117
+ fontSize: 54,
118
+ fill: '#FFFFFF',
119
+ stroke: { color: '#E11D2E', width: 6 },
120
+ shadow: { x: 3, y: 3, color: '#000000', opacity: 0.85 },
121
+ y: [105, 175],
122
+ },
123
+ {
124
+ ...BASE,
125
+ id: 'heavy-white',
126
+ lineGap: 1.2167,
127
+ name: 'Heavy White',
128
+ fontFamily: 'Poppins',
129
+ fontFile: 'Poppins-Bold.ttf',
130
+ fontSize: 60,
131
+ fill: '#FFFFFF',
132
+ stroke: { color: '#000000', width: 8 },
133
+ shadow: { x: 4, y: 4, color: '#000000', opacity: 0.9 },
134
+ y: [105, 178],
135
+ },
136
+ {
137
+ ...BASE,
138
+ id: 'mono-clean',
139
+ lineGap: 1.375,
140
+ name: 'Mono Clean',
141
+ fontFamily: 'DejaVu Sans Mono',
142
+ fontFile: 'DejaVuSansMono-Bold.ttf',
143
+ fontSize: 48,
144
+ fill: '#FFFFFF',
145
+ stroke: { color: '#000000', width: 9 },
146
+ shadow: { x: 4, y: 4, color: '#000000', opacity: 0.85 },
147
+ y: [112, 178],
148
+ },
149
+ ]);
150
+
151
+ export function getCaptionTemplate(id) {
152
+ const key = String(id || '').trim().toLowerCase();
153
+ const template = CAPTION_TEMPLATES.find((item) => item.id === key);
154
+ if (!template) {
155
+ throw new Error(`Unknown caption template '${id}'. Available: ${CAPTION_TEMPLATES.map((item) => item.id).join(', ')}`);
156
+ }
157
+ return template;
158
+ }
159
+
160
+ export function resolveTemplateFont(template) {
161
+ const bundled = path.join(FONT_DIR, template.fontFile);
162
+ if (fs.existsSync(bundled)) return bundled;
163
+
164
+ for (const candidate of SYSTEM_FONT_PATHS[template.fontFile] || []) {
165
+ if (fs.existsSync(candidate)) return candidate;
166
+ }
167
+
168
+ for (const fallback of ['Poppins-Bold.ttf', 'Anton-Regular.ttf', 'BebasNeue-Regular.ttf']) {
169
+ const candidate = path.join(FONT_DIR, fallback);
170
+ if (fs.existsSync(candidate)) return candidate;
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ export function listCaptionTemplates() {
177
+ return CAPTION_TEMPLATES.map((template) => ({
178
+ id: template.id,
179
+ name: template.name,
180
+ font: template.fontFamily,
181
+ font_file: template.fontFile,
182
+ font_available: Boolean(resolveTemplateFont(template)),
183
+ font_size: template.fontSize,
184
+ line_gap: template.lineGap,
185
+ fill: template.fill,
186
+ stroke: template.stroke,
187
+ shadow: template.shadow,
188
+ lines: template.lines,
189
+ y: template.y,
190
+ animation: template.animation,
191
+ rule: template.rule,
192
+ glitch: template.glitch,
193
+ }));
194
+ }
195
+
196
+ export function splitCaptionLines(text, lineCount = 2) {
197
+ const source = String(text ?? '').trim();
198
+ if (!source) return [''];
199
+ if (lineCount <= 1) return [source.replace(/\s+/g, ' ')];
200
+
201
+ const explicit = source.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
202
+ if (explicit.length >= 2) return explicit.slice(0, 2);
203
+
204
+ const words = source.replace(/\s+/g, ' ').split(' ');
205
+ if (words.length === 1) return [words[0], ''];
206
+
207
+ let bestIndex = 1;
208
+ let bestDelta = Infinity;
209
+ for (let index = 1; index < words.length; index += 1) {
210
+ const left = words.slice(0, index).join(' ').length;
211
+ const right = words.slice(index).join(' ').length;
212
+ const delta = Math.abs(left - right);
213
+ if (delta < bestDelta) {
214
+ bestDelta = delta;
215
+ bestIndex = index;
216
+ }
217
+ }
218
+ return [words.slice(0, bestIndex).join(' '), words.slice(bestIndex).join(' ')];
219
+ }
220
+
221
+ function escapeFilterPath(filePath) {
222
+ return filePath.replace(/\\/g, '/').replace(/:/g, '\\:').replace(/'/g, "\\'");
223
+ }
224
+
225
+ function writeTextFile(text, tempFiles) {
226
+ const file = path.join(os.tmpdir(), `usk-tpl-${crypto.randomBytes(6).toString('hex')}.txt`);
227
+ fs.writeFileSync(file, text, 'utf8');
228
+ tempFiles.push(file);
229
+ return file;
230
+ }
231
+
232
+ export function buildTemplateCaptionFilters(caption, template, { width = 1080, height = 1920, tempFiles = [] } = {}) {
233
+ const fontPath = resolveTemplateFont(template);
234
+ if (!fontPath) {
235
+ throw new Error(`Font for caption template '${template.id}' not found; expected ${template.fontFile}`);
236
+ }
237
+
238
+ const scale = width / REFERENCE_CANVAS.width;
239
+ const fontSize = Math.max(12, Math.round(template.fontSize * scale));
240
+ const strokeWidth = Math.max(2, Math.round(template.stroke.width * scale));
241
+ const start = ((caption.timeline_start_ms || 0) / 1000).toFixed(3);
242
+ const end = (((caption.timeline_start_ms || 0) + (caption.duration_ms || 1000)) / 1000).toFixed(3);
243
+ const text = template.uppercase ? String(caption.text || '').toUpperCase() : String(caption.text || '');
244
+ const lines = splitCaptionLines(text, template.lines);
245
+ const fontOpt = `:fontfile='${escapeFilterPath(fontPath)}'`;
246
+ const enable = `enable='between(t\\,${start}\\,${end})'`;
247
+ const fade = template.animation?.type === 'fade'
248
+ ? `:alpha='if(lt(t-${start}\\,0.4)\\,(t-${start})/0.4\\,1)'`
249
+ : '';
250
+ const filters = [];
251
+
252
+ if (template.rule) {
253
+ const ruleY = Math.round((template.rule.y / REFERENCE_CANVAS.height) * height);
254
+ const ruleW = Math.round(template.rule.width * scale);
255
+ const ruleH = Math.max(1, Math.round(template.rule.height * scale));
256
+ const ruleX = Math.round((width - ruleW) / 2);
257
+ filters.push(`drawbox=x=${ruleX}:y=${ruleY}:w=${ruleW}:h=${ruleH}:color=${template.rule.color}@${template.rule.opacity}:t=fill:${enable}`);
258
+ }
259
+
260
+ lines.forEach((line, index) => {
261
+ if (!line) return;
262
+ const y1 = Math.round((template.y[0] / REFERENCE_CANVAS.height) * height);
263
+ const lineGap = Math.round(fontSize * (template.lineGap || 1.1));
264
+ const y = y1 + (index * lineGap);
265
+ const textFile = escapeFilterPath(writeTextFile(line, tempFiles));
266
+ const base = `textfile='${textFile}':expansion=none${fontOpt}:fontsize=${fontSize}:x=(w-text_w)/2:y=${y}:${enable}${fade}`;
267
+
268
+ if (template.glitch) {
269
+ for (const offset of template.glitch.offsets) {
270
+ const x = offset.x === 0 ? '(w-text_w)/2' : `(w-text_w)/2${offset.x > 0 ? '+' : ''}${offset.x}`;
271
+ const oy = offset.y === 0 ? `${y}` : `${y}${offset.y > 0 ? '+' : ''}${offset.y}`;
272
+ filters.push(`drawtext=${base.replace('x=(w-text_w)/2', `x=${x}`).replace(`y=${y}`, `y=${oy}`)}:fontcolor=${offset.color}`);
273
+ }
274
+ filters.push(`drawtext=${base}:fontcolor=${template.fill}:borderw=${strokeWidth}:bordercolor=${template.stroke.color}`);
275
+ return;
276
+ }
277
+
278
+ const shadow = template.shadow
279
+ ? `:shadowx=${Math.max(1, Math.round(template.shadow.x * scale))}:shadowy=${Math.max(1, Math.round(template.shadow.y * scale))}:shadowcolor=${template.shadow.color}@${template.shadow.opacity}`
280
+ : '';
281
+ filters.push(`drawtext=${base}:fontcolor=${template.fill}:borderw=${strokeWidth}:bordercolor=${template.stroke.color}${shadow}`);
282
+ });
283
+
284
+ return filters.join(',');
285
+ }