@julioventura/opensquad 0.1.17

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 (247) hide show
  1. package/README.md +433 -0
  2. package/_opensquad/config/playwright.config.json +11 -0
  3. package/_opensquad/core/architect.agent.yaml +112 -0
  4. package/_opensquad/core/best-practices/_catalog.yaml +126 -0
  5. package/_opensquad/core/best-practices/blog-post.md +132 -0
  6. package/_opensquad/core/best-practices/blog-seo.md +127 -0
  7. package/_opensquad/core/best-practices/brand-resolution-checklist.md +172 -0
  8. package/_opensquad/core/best-practices/copywriting.md +441 -0
  9. package/_opensquad/core/best-practices/data-analysis.md +401 -0
  10. package/_opensquad/core/best-practices/email-newsletter.md +118 -0
  11. package/_opensquad/core/best-practices/email-sales.md +110 -0
  12. package/_opensquad/core/best-practices/image-design.md +348 -0
  13. package/_opensquad/core/best-practices/instagram-feed.md +235 -0
  14. package/_opensquad/core/best-practices/instagram-reels.md +112 -0
  15. package/_opensquad/core/best-practices/instagram-stories.md +107 -0
  16. package/_opensquad/core/best-practices/linkedin-article.md +116 -0
  17. package/_opensquad/core/best-practices/linkedin-post.md +121 -0
  18. package/_opensquad/core/best-practices/researching.md +349 -0
  19. package/_opensquad/core/best-practices/review.md +269 -0
  20. package/_opensquad/core/best-practices/run-recovery.md +61 -0
  21. package/_opensquad/core/best-practices/social-networks-publishing.md +327 -0
  22. package/_opensquad/core/best-practices/squad-creation-checklist.md +32 -0
  23. package/_opensquad/core/best-practices/strategist.md +344 -0
  24. package/_opensquad/core/best-practices/technical-writing.md +365 -0
  25. package/_opensquad/core/best-practices/twitter-post.md +105 -0
  26. package/_opensquad/core/best-practices/twitter-thread.md +122 -0
  27. package/_opensquad/core/best-practices/whatsapp-broadcast.md +107 -0
  28. package/_opensquad/core/best-practices/youtube-script.md +122 -0
  29. package/_opensquad/core/best-practices/youtube-shorts.md +112 -0
  30. package/_opensquad/core/defaults/youtube-video-assembly.json +84 -0
  31. package/_opensquad/core/prompts/build.prompt.md +613 -0
  32. package/_opensquad/core/prompts/design.prompt.md +606 -0
  33. package/_opensquad/core/prompts/discovery.prompt.md +377 -0
  34. package/_opensquad/core/prompts/sherlock-instagram.md +123 -0
  35. package/_opensquad/core/prompts/sherlock-linkedin.md +73 -0
  36. package/_opensquad/core/prompts/sherlock-shared.md +684 -0
  37. package/_opensquad/core/prompts/sherlock-twitter.md +78 -0
  38. package/_opensquad/core/prompts/sherlock-youtube.md +85 -0
  39. package/_opensquad/core/runner.pipeline.md +743 -0
  40. package/_opensquad/core/skills.engine.md +384 -0
  41. package/bin/opensquad.js +108 -0
  42. package/dashboard/index.html +15 -0
  43. package/dashboard/package-lock.json +1964 -0
  44. package/dashboard/package.json +28 -0
  45. package/dashboard/public/assets/avatars/Female1_1wave.png +0 -0
  46. package/dashboard/public/assets/avatars/Female1_2wave.png +0 -0
  47. package/dashboard/public/assets/avatars/Female1_blink.png +0 -0
  48. package/dashboard/public/assets/avatars/Female1_talk.png +0 -0
  49. package/dashboard/public/assets/avatars/Female2_1wave.png +0 -0
  50. package/dashboard/public/assets/avatars/Female2_2wave.png +0 -0
  51. package/dashboard/public/assets/avatars/Female2_blink.png +0 -0
  52. package/dashboard/public/assets/avatars/Female2_talk.png +0 -0
  53. package/dashboard/public/assets/avatars/Female3_blink.png +0 -0
  54. package/dashboard/public/assets/avatars/Female3_talk.png +0 -0
  55. package/dashboard/public/assets/avatars/Female3_wave.png +0 -0
  56. package/dashboard/public/assets/avatars/Female4_blink.png +0 -0
  57. package/dashboard/public/assets/avatars/Female4_talk.png +0 -0
  58. package/dashboard/public/assets/avatars/Female4_wave.png +0 -0
  59. package/dashboard/public/assets/avatars/Female5_blink.png +0 -0
  60. package/dashboard/public/assets/avatars/Female5_talk.png +0 -0
  61. package/dashboard/public/assets/avatars/Female5_wave.png +0 -0
  62. package/dashboard/public/assets/avatars/Female6_blink.png +0 -0
  63. package/dashboard/public/assets/avatars/Female6_talk.png +0 -0
  64. package/dashboard/public/assets/avatars/Female6_wave.png +0 -0
  65. package/dashboard/public/assets/avatars/Male1_1wave.png +0 -0
  66. package/dashboard/public/assets/avatars/Male1_2wave.png +0 -0
  67. package/dashboard/public/assets/avatars/Male1_blink.png +0 -0
  68. package/dashboard/public/assets/avatars/Male1_talk.png +0 -0
  69. package/dashboard/public/assets/avatars/Male2_1wave.png +0 -0
  70. package/dashboard/public/assets/avatars/Male2_2wave.png +0 -0
  71. package/dashboard/public/assets/avatars/Male2_blink.png +0 -0
  72. package/dashboard/public/assets/avatars/Male2_talk.png +0 -0
  73. package/dashboard/public/assets/avatars/Male3_blink.png +0 -0
  74. package/dashboard/public/assets/avatars/Male3_talk.png +0 -0
  75. package/dashboard/public/assets/avatars/Male3_wave.png +0 -0
  76. package/dashboard/public/assets/avatars/Male4_blink.png +0 -0
  77. package/dashboard/public/assets/avatars/Male4_talk.png +0 -0
  78. package/dashboard/public/assets/avatars/Male4_wave.png +0 -0
  79. package/dashboard/public/assets/desks/desktop_set_black_down.png +0 -0
  80. package/dashboard/public/assets/desks/desktop_set_black_down_coding-1.png +0 -0
  81. package/dashboard/public/assets/desks/desktop_set_black_down_coding.png +0 -0
  82. package/dashboard/public/assets/desks/desktop_set_black_up.png +0 -0
  83. package/dashboard/public/assets/desks/desktop_set_white_down.png +0 -0
  84. package/dashboard/public/assets/desks/desktop_set_white_down_coding-1.png +0 -0
  85. package/dashboard/public/assets/desks/desktop_set_white_down_coding.png +0 -0
  86. package/dashboard/public/assets/desks/desktop_set_white_up.png +0 -0
  87. package/dashboard/public/assets/furniture/armchair_tan.png +0 -0
  88. package/dashboard/public/assets/furniture/armchair_tan_down.png +0 -0
  89. package/dashboard/public/assets/furniture/backpack_blue.png +0 -0
  90. package/dashboard/public/assets/furniture/backpack_red.png +0 -0
  91. package/dashboard/public/assets/furniture/blinds.png +0 -0
  92. package/dashboard/public/assets/furniture/blinds_large_closed_white.png +0 -0
  93. package/dashboard/public/assets/furniture/bookshelf.png +0 -0
  94. package/dashboard/public/assets/furniture/bookshelf_purple_tall.png +0 -0
  95. package/dashboard/public/assets/furniture/bulletin_board.png +0 -0
  96. package/dashboard/public/assets/furniture/clock.png +0 -0
  97. package/dashboard/public/assets/furniture/coffee_mug.png +0 -0
  98. package/dashboard/public/assets/furniture/coffee_mug_blue.png +0 -0
  99. package/dashboard/public/assets/furniture/coffee_table.png +0 -0
  100. package/dashboard/public/assets/furniture/coffeepot_right.png +0 -0
  101. package/dashboard/public/assets/furniture/coffeetable_black_horizontal.png +0 -0
  102. package/dashboard/public/assets/furniture/couch.png +0 -0
  103. package/dashboard/public/assets/furniture/couch_tan_down.png +0 -0
  104. package/dashboard/public/assets/furniture/cushion_blue.png +0 -0
  105. package/dashboard/public/assets/furniture/cushion_tan.png +0 -0
  106. package/dashboard/public/assets/furniture/desk_wood.png +0 -0
  107. package/dashboard/public/assets/furniture/fancy_rug.png +0 -0
  108. package/dashboard/public/assets/furniture/fancy_rug_wide.png +0 -0
  109. package/dashboard/public/assets/furniture/flowers1.png +0 -0
  110. package/dashboard/public/assets/furniture/flowers2.png +0 -0
  111. package/dashboard/public/assets/furniture/lamp_tan.png +0 -0
  112. package/dashboard/public/assets/furniture/lantern.png +0 -0
  113. package/dashboard/public/assets/furniture/monstera.png +0 -0
  114. package/dashboard/public/assets/furniture/monstera_small.png +0 -0
  115. package/dashboard/public/assets/furniture/picture_frame.png +0 -0
  116. package/dashboard/public/assets/furniture/plant1.png +0 -0
  117. package/dashboard/public/assets/furniture/plant2.png +0 -0
  118. package/dashboard/public/assets/furniture/plant3.png +0 -0
  119. package/dashboard/public/assets/furniture/plant_poof.png +0 -0
  120. package/dashboard/public/assets/furniture/plant_spindly.png +0 -0
  121. package/dashboard/public/assets/furniture/poster_blue.png +0 -0
  122. package/dashboard/public/assets/furniture/rug.png +0 -0
  123. package/dashboard/public/assets/furniture/succulent_blue.png +0 -0
  124. package/dashboard/public/assets/furniture/succulent_green.png +0 -0
  125. package/dashboard/public/assets/furniture/treasurechest_closed_gold.png +0 -0
  126. package/dashboard/public/assets/furniture/water_cooler_better.png +0 -0
  127. package/dashboard/public/assets/furniture/whiteboard.png +0 -0
  128. package/dashboard/public/assets/furniture/whiteboard_stand_graph.png +0 -0
  129. package/dashboard/public/assets/furniture/window_blinds_open.png +0 -0
  130. package/dashboard/src/App.tsx +46 -0
  131. package/dashboard/src/components/RunDashboardButton.tsx +92 -0
  132. package/dashboard/src/components/SquadCard.tsx +49 -0
  133. package/dashboard/src/components/SquadSelector.tsx +67 -0
  134. package/dashboard/src/components/StatusBadge.tsx +32 -0
  135. package/dashboard/src/components/StatusBar.tsx +116 -0
  136. package/dashboard/src/hooks/useSquadSocket.ts +135 -0
  137. package/dashboard/src/lib/formatTime.ts +16 -0
  138. package/dashboard/src/lib/normalizeState.ts +25 -0
  139. package/dashboard/src/main.tsx +10 -0
  140. package/dashboard/src/office/AgentSprite.ts +241 -0
  141. package/dashboard/src/office/OfficeScene.ts +153 -0
  142. package/dashboard/src/office/PhaserGame.tsx +80 -0
  143. package/dashboard/src/office/RoomBuilder.ts +190 -0
  144. package/dashboard/src/office/assetKeys.ts +150 -0
  145. package/dashboard/src/office/palette.ts +32 -0
  146. package/dashboard/src/plugin/squadWatcher.ts +397 -0
  147. package/dashboard/src/store/useSquadStore.ts +56 -0
  148. package/dashboard/src/styles/globals.css +36 -0
  149. package/dashboard/src/types/state.ts +63 -0
  150. package/dashboard/src/vite-env.d.ts +1 -0
  151. package/dashboard/tsconfig.json +24 -0
  152. package/dashboard/vite.config.ts +13 -0
  153. package/package.json +59 -0
  154. package/public/sfx/slide-transition-sfx.mp3 +0 -0
  155. package/skills/README.md +84 -0
  156. package/skills/apify/SKILL.md +55 -0
  157. package/skills/blotato/SKILL.md +63 -0
  158. package/skills/canva/SKILL.md +60 -0
  159. package/skills/higgsfield/SKILL.md +147 -0
  160. package/skills/image-ai-generator/SKILL.md +124 -0
  161. package/skills/image-ai-generator/scripts/generate.py +175 -0
  162. package/skills/image-creator/SKILL.md +166 -0
  163. package/skills/image-creator/editorial-slide-template.js +645 -0
  164. package/skills/image-fetcher/SKILL.md +91 -0
  165. package/skills/imgbb-uploader/SKILL.md +73 -0
  166. package/skills/imgbb-uploader/scripts/upload.js +125 -0
  167. package/skills/instagram-publisher/README.md +36 -0
  168. package/skills/instagram-publisher/SKILL.md +231 -0
  169. package/skills/instagram-publisher/scripts/publish-playwright.js +418 -0
  170. package/skills/instagram-publisher/scripts/publish.js +521 -0
  171. package/skills/opensquad-agent-creator/SKILL.md +192 -0
  172. package/skills/opensquad-skill-creator/SKILL.md +420 -0
  173. package/skills/opensquad-skill-creator/agents/analyzer.md +274 -0
  174. package/skills/opensquad-skill-creator/agents/comparator.md +202 -0
  175. package/skills/opensquad-skill-creator/agents/grader.md +223 -0
  176. package/skills/opensquad-skill-creator/assets/eval_review.html +146 -0
  177. package/skills/opensquad-skill-creator/eval-viewer/generate_review.py +471 -0
  178. package/skills/opensquad-skill-creator/eval-viewer/viewer.html +1325 -0
  179. package/skills/opensquad-skill-creator/references/schemas.md +430 -0
  180. package/skills/opensquad-skill-creator/references/skill-format.md +235 -0
  181. package/skills/opensquad-skill-creator/scripts/__init__.py +0 -0
  182. package/skills/opensquad-skill-creator/scripts/aggregate_benchmark.py +401 -0
  183. package/skills/opensquad-skill-creator/scripts/quick_validate.py +103 -0
  184. package/skills/opensquad-skill-creator/scripts/run_eval.py +310 -0
  185. package/skills/opensquad-skill-creator/scripts/utils.py +47 -0
  186. package/skills/pdf-extractor/SKILL.md +57 -0
  187. package/skills/pdf-extractor/scripts/extract.py +82 -0
  188. package/skills/resend/SKILL.md +80 -0
  189. package/skills/run-dashboard/README.md +93 -0
  190. package/skills/run-dashboard/SKILL.md +173 -0
  191. package/skills/run-dashboard/scripts/finalize-state.js +273 -0
  192. package/skills/run-dashboard/scripts/generate.js +1296 -0
  193. package/skills/run-dashboard/scripts/serve.js +135 -0
  194. package/skills/run-dashboard/templates/run-dashboard-simple.template.html +191 -0
  195. package/skills/run-dashboard/templates/run-dashboard.template.html +1164 -0
  196. package/skills/smtp-sender/SKILL.md +88 -0
  197. package/skills/smtp-sender/scripts/send.js +478 -0
  198. package/skills/template-designer/SKILL.md +201 -0
  199. package/skills/template-designer/base-templates/model-a.html +27 -0
  200. package/skills/template-designer/base-templates/model-b.html +31 -0
  201. package/skills/template-designer/base-templates/model-c.html +42 -0
  202. package/skills/youtube-publisher/SKILL.md +232 -0
  203. package/skills/youtube-publisher/scripts/publish.js +2078 -0
  204. package/src/agents-cli.js +158 -0
  205. package/src/agents.js +134 -0
  206. package/src/i18n.js +48 -0
  207. package/src/init.js +442 -0
  208. package/src/locales/en.json +79 -0
  209. package/src/locales/es.json +78 -0
  210. package/src/locales/pt-BR.json +78 -0
  211. package/src/logger.js +38 -0
  212. package/src/prompt.js +46 -0
  213. package/src/readme/README.md +146 -0
  214. package/src/runs.js +318 -0
  215. package/src/skills-cli.js +157 -0
  216. package/src/skills.js +146 -0
  217. package/src/supabase-cli.js +584 -0
  218. package/src/update.js +169 -0
  219. package/templates/_opensquad/.opensquad-version +1 -0
  220. package/templates/_opensquad/_investigations/.gitkeep +0 -0
  221. package/templates/ide-templates/antigravity/.agent/rules/opensquad.md +68 -0
  222. package/templates/ide-templates/antigravity/.agent/workflows/opensquad.md +102 -0
  223. package/templates/ide-templates/claude-code/.claude/skills/opensquad/SKILL.md +182 -0
  224. package/templates/ide-templates/claude-code/.mcp.json +8 -0
  225. package/templates/ide-templates/claude-code/CLAUDE.md +57 -0
  226. package/templates/ide-templates/codex/.agents/skills/opensquad/SKILL.md +6 -0
  227. package/templates/ide-templates/codex/AGENTS.md +120 -0
  228. package/templates/ide-templates/cursor/.cursor/commands/opensquad.md +9 -0
  229. package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
  230. package/templates/ide-templates/cursor/.cursor/rules/opensquad.mdc +62 -0
  231. package/templates/ide-templates/cursor/.cursorignore +3 -0
  232. package/templates/ide-templates/gemini-cli/.gemini/settings.json +8 -0
  233. package/templates/ide-templates/gemini-cli/.gemini/skills/opensquad/SKILL.md +186 -0
  234. package/templates/ide-templates/gemini-cli/GEMINI.md +57 -0
  235. package/templates/ide-templates/opencode/.opencode/commands/opensquad.md +9 -0
  236. package/templates/ide-templates/opencode/AGENTS.md +120 -0
  237. package/templates/ide-templates/qwen-code/.qwen/settings.json +8 -0
  238. package/templates/ide-templates/qwen-code/.qwen/skills/opensquad/SKILL.md +182 -0
  239. package/templates/ide-templates/qwen-code/QWEN.md +57 -0
  240. package/templates/ide-templates/trae/.trae/mcp.json +8 -0
  241. package/templates/ide-templates/trae/.trae/rules/opensquad.md +64 -0
  242. package/templates/ide-templates/vscode-copilot/.github/copilot-instructions.md +59 -0
  243. package/templates/ide-templates/vscode-copilot/.github/prompts/opensquad.prompt.md +209 -0
  244. package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
  245. package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
  246. package/templates/package.json +8 -0
  247. package/templates/squads/.gitkeep +0 -0
@@ -0,0 +1,157 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { listInstalled, installSkill, removeSkill, getSkillMeta, getLocalizedDescription } from './skills.js';
5
+ import { loadLocale, t, getLocaleCode } from './i18n.js';
6
+ import { loadSavedLocale } from './init.js';
7
+ import { logEvent } from './logger.js';
8
+
9
+ async function confirm(question) {
10
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
11
+ return new Promise((resolve) => {
12
+ rl.question(question, (answer) => {
13
+ rl.close();
14
+ resolve(answer.trim().toLowerCase());
15
+ });
16
+ });
17
+ }
18
+
19
+ export async function skillsCli(subcommand, args, targetDir) {
20
+ // Require initialized project
21
+ try {
22
+ await stat(join(targetDir, '_opensquad'));
23
+ } catch {
24
+ await loadLocale('English');
25
+ console.log(`\n ${t('skillsNotInitialized')}\n`);
26
+ return { success: false };
27
+ }
28
+
29
+ await loadSavedLocale(targetDir);
30
+
31
+ try {
32
+ if (subcommand === 'list' || !subcommand) {
33
+ await runList(targetDir);
34
+ } else if (subcommand === 'install') {
35
+ const installed = await runInstall(args[0], targetDir);
36
+ if (installed === false) return { success: false };
37
+ } else if (subcommand === 'remove') {
38
+ const removed = await runRemove(args[0], targetDir);
39
+ if (removed === false) return { success: false };
40
+ } else if (subcommand === 'update') {
41
+ await runUpdate(targetDir);
42
+ } else if (subcommand === 'update-one') {
43
+ await runUpdateOne(args[0], targetDir);
44
+ } else {
45
+ console.log(`\n ${t('skillsUnknownCommand', { cmd: subcommand })}\n`);
46
+ return { success: false };
47
+ }
48
+ } catch (err) {
49
+ console.log(`\n ${t('skillsError', { message: err.message })}\n`);
50
+ return { success: false };
51
+ }
52
+
53
+ return { success: true };
54
+ }
55
+
56
+ async function runList(targetDir) {
57
+ console.log(`\n Opensquad Skills\n`);
58
+
59
+ const installed = await listInstalled(targetDir);
60
+
61
+ if (installed.length > 0) {
62
+ console.log(` ${t('skillsInstalledHeader')}`);
63
+ for (const id of installed) {
64
+ const meta = await getSkillMeta(id);
65
+ if (meta) {
66
+ const desc = getLocalizedDescription(meta, getLocaleCode());
67
+ const parts = [meta.name];
68
+ if (meta.type) parts.push(`(${meta.type})`);
69
+ parts.push(`- ${desc.split('.')[0]}`);
70
+ console.log(` ${parts.join(' ')}`);
71
+ } else {
72
+ console.log(` ${id}`);
73
+ }
74
+ }
75
+ } else {
76
+ console.log(` ${t('skillsNoneInstalled')}`);
77
+ }
78
+
79
+ console.log(`\n Browse available skills at: https://github.com/julioventura/opensquad/tree/main/skills\n`);
80
+ }
81
+
82
+ async function runInstall(id, targetDir) {
83
+ if (!id) {
84
+ console.log('\n Usage: opensquad install <id>\n');
85
+ return false;
86
+ }
87
+
88
+ const installed = await listInstalled(targetDir);
89
+ if (installed.includes(id)) {
90
+ const answer = await confirm(`\n ${t('skillsAlreadyInstalled', { id })}`);
91
+ // Accept 'y' (English) or 's' (Portuguese "sim") as affirmative answers
92
+ if (answer !== 'y' && answer !== 's') return false;
93
+ console.log(` ${t('skillsInstalling', { id })}`);
94
+ await installSkill(id, targetDir);
95
+ console.log(` ${t('skillsReinstalled', { id })}\n`);
96
+ await logEvent('skill:install', { name: id, reinstall: true }, targetDir);
97
+ return;
98
+ }
99
+
100
+ console.log(`\n ${t('skillsInstalling', { id })}`);
101
+ await installSkill(id, targetDir);
102
+ console.log(` ${t('skillsInstalled', { id })}\n`);
103
+ await logEvent('skill:install', { name: id }, targetDir);
104
+ }
105
+
106
+ async function runRemove(id, targetDir) {
107
+ if (!id) {
108
+ console.log('\n Usage: opensquad uninstall <id>\n');
109
+ return false;
110
+ }
111
+
112
+ const installed = await listInstalled(targetDir);
113
+ if (!installed.includes(id)) {
114
+ console.log(`\n ${t('skillsNotInstalled', { id })}\n`);
115
+ return;
116
+ }
117
+
118
+ console.log(`\n ${t('skillsRemoving', { id })}`);
119
+ await removeSkill(id, targetDir);
120
+ await logEvent('skill:remove', { name: id }, targetDir);
121
+ console.log(` ${t('skillsRemoved', { id })}\n`);
122
+ }
123
+
124
+ async function runUpdate(targetDir) {
125
+ const installed = await listInstalled(targetDir);
126
+ if (installed.length === 0) {
127
+ console.log(`\n ${t('skillsUpdateNone')}\n`);
128
+ return;
129
+ }
130
+
131
+ console.log(`\n ${t('skillsUpdating')}`);
132
+ for (const id of installed) {
133
+ console.log(` ${t('skillsInstalling', { id })}`);
134
+ await installSkill(id, targetDir);
135
+ console.log(` ${t('skillsInstalled', { id })}`);
136
+ }
137
+ await logEvent('skill:update', { count: installed.length }, targetDir);
138
+ console.log(`\n ${t('skillsUpdateDone', { count: installed.length })}\n`);
139
+ }
140
+
141
+ async function runUpdateOne(id, targetDir) {
142
+ if (!id) {
143
+ console.log('\n Usage: opensquad update <name>\n');
144
+ return;
145
+ }
146
+
147
+ const installed = await listInstalled(targetDir);
148
+ if (!installed.includes(id)) {
149
+ console.log(`\n ${t('skillsNotInstalled', { id })}\n`);
150
+ return;
151
+ }
152
+
153
+ console.log(`\n ${t('skillsInstalling', { id })}`);
154
+ await installSkill(id, targetDir);
155
+ await logEvent('skill:update', { name: id }, targetDir);
156
+ console.log(` ${t('skillsInstalled', { id })}\n`);
157
+ }
package/src/skills.js ADDED
@@ -0,0 +1,146 @@
1
+ import { cp, readdir, readFile, rm, stat } from 'node:fs/promises';
2
+ import { dirname, join, resolve, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const BUNDLED_SKILLS_DIR = join(__dirname, '..', 'skills');
7
+
8
+ const metaCache = new Map();
9
+
10
+ export async function listInstalled(targetDir) {
11
+ try {
12
+ const skillsDir = join(targetDir, 'skills');
13
+ const entries = await readdir(skillsDir, { withFileTypes: true });
14
+ return entries
15
+ .filter((e) => e.isDirectory() && e.name !== 'opensquad-skill-creator')
16
+ .map((e) => e.name);
17
+ } catch (err) {
18
+ if (err.code === 'ENOENT') return [];
19
+ throw err;
20
+ }
21
+ }
22
+
23
+ export async function listAvailable() {
24
+ try {
25
+ const entries = await readdir(BUNDLED_SKILLS_DIR, { withFileTypes: true });
26
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ export async function getSkillMeta(id) {
33
+ if (metaCache.has(id)) return metaCache.get(id);
34
+ try {
35
+ const raw = await readFile(join(BUNDLED_SKILLS_DIR, id, 'SKILL.md'), 'utf-8');
36
+ const content = raw.replace(/\r\n/g, '\n');
37
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
38
+ if (!fmMatch) return { name: id, description: '', descriptions: {}, type: '', env: [] };
39
+
40
+ const fm = fmMatch[1];
41
+ const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() || id;
42
+ const type = fm.match(/^type:\s*(.+)$/m)?.[1]?.trim() || '';
43
+
44
+ // description may use YAML folded scalar (>)
45
+ let description = '';
46
+ const descBlock = fm.match(/^description:\s*>\s*\n((?:\s{2,}.+\n?)+)/m);
47
+ if (descBlock) {
48
+ description = descBlock[1].replace(/\n\s*/g, ' ').trim();
49
+ } else {
50
+ const descInline = fm.match(/^description:\s*(.+)$/m);
51
+ if (descInline) description = descInline[1].trim();
52
+ }
53
+
54
+ // localized descriptions: description_pt-BR, description_es, etc.
55
+ const descriptions = {};
56
+ for (const code of ['pt-BR', 'es']) {
57
+ const key = `description_${code}`;
58
+ // folded scalar
59
+ const blockMatch = fm.match(new RegExp(`^${key}:\\s*>\\s*\\n((?:\\s{2,}.+\\n?)+)`, 'm'));
60
+ if (blockMatch) {
61
+ descriptions[code] = blockMatch[1].replace(/\n\s*/g, ' ').trim();
62
+ } else {
63
+ // inline
64
+ const inlineMatch = fm.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
65
+ if (inlineMatch) descriptions[code] = inlineMatch[1].trim();
66
+ }
67
+ }
68
+
69
+ // env is a YAML list: lines starting with " - "
70
+ const env = [];
71
+ const envSection = fm.match(/^env:\s*\n((?:\s+-\s+.+\n?)+)/m);
72
+ if (envSection) {
73
+ for (const line of envSection[1].split('\n')) {
74
+ const item = line.match(/^\s+-\s+(.+)/);
75
+ if (item) env.push(item[1].trim());
76
+ }
77
+ }
78
+
79
+ const result = { name, description, descriptions, type, env };
80
+ metaCache.set(id, result);
81
+ return result;
82
+ } catch (err) {
83
+ if (err.code === 'ENOENT') {
84
+ metaCache.set(id, null);
85
+ return null;
86
+ }
87
+ throw err;
88
+ }
89
+ }
90
+
91
+ function validateSkillId(id) {
92
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
93
+ throw new Error(`Invalid skill id: '${id}'`);
94
+ }
95
+ }
96
+
97
+ export async function installSkill(id, targetDir) {
98
+ validateSkillId(id);
99
+ const srcDir = join(BUNDLED_SKILLS_DIR, id);
100
+ try {
101
+ await stat(srcDir);
102
+ } catch (err) {
103
+ if (err.code === 'ENOENT') throw new Error(`Skill '${id}' not found in registry`, { cause: err });
104
+ throw err;
105
+ }
106
+ const destDir = join(targetDir, 'skills', id);
107
+ const resolvedSrc = resolve(srcDir);
108
+ const resolvedDest = resolve(destDir);
109
+ if (resolvedSrc === resolvedDest || resolvedDest.startsWith(resolvedSrc + sep)) {
110
+ return;
111
+ }
112
+ await cp(srcDir, destDir, { recursive: true });
113
+ metaCache.delete(id);
114
+ }
115
+
116
+ export async function removeSkill(id, targetDir) {
117
+ validateSkillId(id);
118
+ const skillDir = join(targetDir, 'skills', id);
119
+ await rm(skillDir, { recursive: true, force: true });
120
+ metaCache.delete(id);
121
+ }
122
+
123
+ export function clearMetaCache() {
124
+ metaCache.clear();
125
+ }
126
+
127
+ export async function getSkillVersion(id, targetDir) {
128
+ try {
129
+ const skillPath = join(targetDir, 'skills', id, 'SKILL.md');
130
+ const content = await readFile(skillPath, 'utf-8');
131
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
132
+ if (!fmMatch) return null;
133
+ const versionMatch = fmMatch[1].match(/^version:\s*(.+)$/m);
134
+ return versionMatch ? versionMatch[1].trim() : null;
135
+ } catch (err) {
136
+ if (err.code === 'ENOENT') return null;
137
+ throw err;
138
+ }
139
+ }
140
+
141
+ export function getLocalizedDescription(meta, localeCode) {
142
+ if (localeCode && localeCode !== 'en' && meta.descriptions?.[localeCode]) {
143
+ return meta.descriptions[localeCode];
144
+ }
145
+ return meta.description;
146
+ }