@jack200714/mafw 4.5.1 → 4.8.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 (330) hide show
  1. package/README.md +5 -2
  2. package/bin/mafw.js +16 -14
  3. package/gateway/dist/index.js +52 -38
  4. package/gateway/dist/media/media-plugin-loader.js +0 -12
  5. package/gateway/dist/plugins/hub.js +153 -19
  6. package/gateway/dist/routes/plugins.js +15 -5
  7. package/gateway/dist/runtime/event-broadcast.js +33 -0
  8. package/gateway/dist/runtime/loader.js +23 -13
  9. package/gateway/dist/usage/builtin-plugins/gateway.js +25 -3
  10. package/gateway/dist/usage/plugin-loader.js +0 -11
  11. package/gateway/package.json +2 -2
  12. package/package.json +1 -7
  13. package/packages/tui/dist/cli.js +8 -3
  14. package/.opencode/mafw/.backup-v61/parametric/base-skill-manifest.yaml +0 -2
  15. package/.opencode/mafw/STATUS.md +0 -6
  16. package/.opencode/mafw/ledger.md +0 -20
  17. package/.opencode/mafw/memory/.harmonic_index.json +0 -28
  18. package/.opencode/mafw/memory/.review_queue.json +0 -1
  19. package/.opencode/mafw/memory/tier3/parametric.json +0 -13
  20. package/.opencode/mafw/memory/tier3/test-goal.json +0 -19
  21. package/.opencode/mafw/memory-index.json +0 -6
  22. package/.opencode/mafw/parametric/base-skill-manifest.yaml +0 -2
  23. package/.opencode/mafw/waves.json +0 -4
  24. package/.opencode/opencode.json +0 -3
  25. package/.opencode/plugins/mafw-plugin.ts +0 -8
  26. package/.opencode/skills/banner-design/SKILL.md +0 -196
  27. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +0 -118
  28. package/.opencode/skills/brand/SKILL.md +0 -97
  29. package/.opencode/skills/brand/references/approval-checklist.md +0 -169
  30. package/.opencode/skills/brand/references/asset-organization.md +0 -157
  31. package/.opencode/skills/brand/references/brand-guideline-template.md +0 -140
  32. package/.opencode/skills/brand/references/color-palette-management.md +0 -186
  33. package/.opencode/skills/brand/references/consistency-checklist.md +0 -94
  34. package/.opencode/skills/brand/references/logo-usage-rules.md +0 -185
  35. package/.opencode/skills/brand/references/messaging-framework.md +0 -85
  36. package/.opencode/skills/brand/references/typography-specifications.md +0 -214
  37. package/.opencode/skills/brand/references/update.md +0 -118
  38. package/.opencode/skills/brand/references/visual-identity.md +0 -96
  39. package/.opencode/skills/brand/references/voice-framework.md +0 -88
  40. package/.opencode/skills/brand/scripts/extract-colors.cjs +0 -341
  41. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +0 -349
  42. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +0 -248
  43. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +0 -52
  44. package/.opencode/skills/brand/scripts/validate-asset.cjs +0 -387
  45. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +0 -275
  46. package/.opencode/skills/design/SKILL.md +0 -313
  47. package/.opencode/skills/design/data/cip/deliverables.csv +0 -51
  48. package/.opencode/skills/design/data/cip/industries.csv +0 -21
  49. package/.opencode/skills/design/data/cip/mockup-contexts.csv +0 -21
  50. package/.opencode/skills/design/data/cip/styles.csv +0 -21
  51. package/.opencode/skills/design/data/icon/styles.csv +0 -16
  52. package/.opencode/skills/design/data/logo/colors.csv +0 -56
  53. package/.opencode/skills/design/data/logo/industries.csv +0 -56
  54. package/.opencode/skills/design/data/logo/styles.csv +0 -56
  55. package/.opencode/skills/design/references/banner-sizes-and-styles.md +0 -118
  56. package/.opencode/skills/design/references/cip-deliverable-guide.md +0 -95
  57. package/.opencode/skills/design/references/cip-design.md +0 -121
  58. package/.opencode/skills/design/references/cip-prompt-engineering.md +0 -84
  59. package/.opencode/skills/design/references/cip-style-guide.md +0 -68
  60. package/.opencode/skills/design/references/design-routing.md +0 -207
  61. package/.opencode/skills/design/references/icon-design.md +0 -122
  62. package/.opencode/skills/design/references/logo-color-psychology.md +0 -101
  63. package/.opencode/skills/design/references/logo-design.md +0 -92
  64. package/.opencode/skills/design/references/logo-prompt-engineering.md +0 -158
  65. package/.opencode/skills/design/references/logo-style-guide.md +0 -109
  66. package/.opencode/skills/design/references/slides-copywriting-formulas.md +0 -84
  67. package/.opencode/skills/design/references/slides-create.md +0 -4
  68. package/.opencode/skills/design/references/slides-html-template.md +0 -295
  69. package/.opencode/skills/design/references/slides-layout-patterns.md +0 -137
  70. package/.opencode/skills/design/references/slides-strategies.md +0 -94
  71. package/.opencode/skills/design/references/slides.md +0 -42
  72. package/.opencode/skills/design/references/social-photos-design.md +0 -329
  73. package/.opencode/skills/design/scripts/cip/core.py +0 -215
  74. package/.opencode/skills/design/scripts/cip/generate.py +0 -484
  75. package/.opencode/skills/design/scripts/cip/render-html.py +0 -424
  76. package/.opencode/skills/design/scripts/cip/search.py +0 -127
  77. package/.opencode/skills/design/scripts/icon/generate.py +0 -487
  78. package/.opencode/skills/design/scripts/logo/core.py +0 -175
  79. package/.opencode/skills/design/scripts/logo/generate.py +0 -362
  80. package/.opencode/skills/design/scripts/logo/search.py +0 -114
  81. package/.opencode/skills/design-system/SKILL.md +0 -244
  82. package/.opencode/skills/design-system/data/slide-backgrounds.csv +0 -11
  83. package/.opencode/skills/design-system/data/slide-charts.csv +0 -26
  84. package/.opencode/skills/design-system/data/slide-color-logic.csv +0 -14
  85. package/.opencode/skills/design-system/data/slide-copy.csv +0 -26
  86. package/.opencode/skills/design-system/data/slide-layout-logic.csv +0 -16
  87. package/.opencode/skills/design-system/data/slide-layouts.csv +0 -26
  88. package/.opencode/skills/design-system/data/slide-strategies.csv +0 -16
  89. package/.opencode/skills/design-system/data/slide-typography.csv +0 -15
  90. package/.opencode/skills/design-system/references/component-specs.md +0 -236
  91. package/.opencode/skills/design-system/references/component-tokens.md +0 -214
  92. package/.opencode/skills/design-system/references/primitive-tokens.md +0 -203
  93. package/.opencode/skills/design-system/references/semantic-tokens.md +0 -215
  94. package/.opencode/skills/design-system/references/states-and-variants.md +0 -241
  95. package/.opencode/skills/design-system/references/tailwind-integration.md +0 -251
  96. package/.opencode/skills/design-system/references/token-architecture.md +0 -224
  97. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +0 -99
  98. package/.opencode/skills/design-system/scripts/fetch-background.py +0 -317
  99. package/.opencode/skills/design-system/scripts/generate-slide.py +0 -770
  100. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +0 -205
  101. package/.opencode/skills/design-system/scripts/html-token-validator.py +0 -327
  102. package/.opencode/skills/design-system/scripts/search-slides.py +0 -218
  103. package/.opencode/skills/design-system/scripts/slide-token-validator.py +0 -35
  104. package/.opencode/skills/design-system/scripts/slide_search_core.py +0 -453
  105. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +0 -48
  106. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +0 -246
  107. package/.opencode/skills/design-system/templates/design-tokens-starter.json +0 -143
  108. package/.opencode/skills/frontend-design/LICENSE.txt +0 -21
  109. package/.opencode/skills/frontend-design/SKILL.md +0 -115
  110. package/.opencode/skills/mafw-automation-interview/SKILL.md +0 -43
  111. package/.opencode/skills/mafw-cli-scanner/SKILL.md +0 -37
  112. package/.opencode/skills/mafw-compression-verifier/SKILL.md +0 -52
  113. package/.opencode/skills/mafw-desktop-inspector/SKILL.md +0 -170
  114. package/.opencode/skills/mafw-execute/SKILL.md +0 -62
  115. package/.opencode/skills/mafw-gateway-restart/SKILL.md +0 -87
  116. package/.opencode/skills/mafw-github-scanner/SKILL.md +0 -38
  117. package/.opencode/skills/mafw-goal/SKILL.md +0 -76
  118. package/.opencode/skills/mafw-interview/SKILL.md +0 -68
  119. package/.opencode/skills/mafw-memory-extractor/SKILL.md +0 -65
  120. package/.opencode/skills/mafw-plan/SKILL.md +0 -58
  121. package/.opencode/skills/mafw-review/SKILL.md +0 -85
  122. package/.opencode/skills/runtime-plugin-authoring/SKILL.md +0 -791
  123. package/.opencode/skills/slides/SKILL.md +0 -40
  124. package/.opencode/skills/slides/references/copywriting-formulas.md +0 -84
  125. package/.opencode/skills/slides/references/create.md +0 -4
  126. package/.opencode/skills/slides/references/html-template.md +0 -295
  127. package/.opencode/skills/slides/references/layout-patterns.md +0 -137
  128. package/.opencode/skills/slides/references/slide-strategies.md +0 -94
  129. package/.opencode/skills/ui-styling/LICENSE.txt +0 -202
  130. package/.opencode/skills/ui-styling/SKILL.md +0 -324
  131. package/.opencode/skills/ui-styling/references/canvas-design-system.md +0 -320
  132. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +0 -471
  133. package/.opencode/skills/ui-styling/references/shadcn-components.md +0 -424
  134. package/.opencode/skills/ui-styling/references/shadcn-theming.md +0 -373
  135. package/.opencode/skills/ui-styling/references/tailwind-customization.md +0 -483
  136. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +0 -382
  137. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +0 -455
  138. package/.opencode/skills/ui-styling/scripts/requirements.txt +0 -17
  139. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +0 -308
  140. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +0 -473
  141. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +0 -1
  142. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +0 -3
  143. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +0 -266
  144. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +0 -394
  145. package/.opencode/skills/ui-ux-pro-max/SKILL.md +0 -388
  146. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +0 -31
  147. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +0 -26
  148. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +0 -193
  149. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +0 -1924
  150. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +0 -106
  151. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +0 -35
  152. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +0 -17
  153. package/.opencode/skills/ui-ux-pro-max/data/products.csv +0 -193
  154. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +0 -45
  155. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +0 -51
  156. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +0 -54
  157. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +0 -57
  158. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  159. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  160. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +0 -76
  161. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  162. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +0 -51
  163. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  164. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -71
  165. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  166. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  167. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
  168. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  169. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  170. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  171. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +0 -54
  172. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +0 -60
  173. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +0 -56
  174. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  175. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +0 -60
  176. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +0 -57
  177. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +0 -85
  178. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +0 -75
  179. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +0 -162
  180. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  181. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +0 -464
  182. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +0 -1479
  183. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +0 -162
  184. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_core.py +0 -134
  185. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_design_system_mode.py +0 -159
  186. package/.opencode/skills/ui-ux-pro-max/scripts/validate_data.py +0 -114
  187. package/dist/hooks/bash-python-guide.d.ts +0 -4
  188. package/dist/hooks/bash-python-guide.d.ts.map +0 -1
  189. package/dist/hooks/bash-python-guide.js +0 -67
  190. package/dist/hooks/bash-python-guide.js.map +0 -1
  191. package/dist/hooks/handoff.d.ts +0 -9
  192. package/dist/hooks/handoff.d.ts.map +0 -1
  193. package/dist/hooks/handoff.js +0 -8
  194. package/dist/hooks/handoff.js.map +0 -1
  195. package/dist/hooks/hook-manager.d.ts +0 -32
  196. package/dist/hooks/hook-manager.d.ts.map +0 -1
  197. package/dist/hooks/hook-manager.js +0 -91
  198. package/dist/hooks/hook-manager.js.map +0 -1
  199. package/dist/hooks/llm-after.d.ts +0 -8
  200. package/dist/hooks/llm-after.d.ts.map +0 -1
  201. package/dist/hooks/llm-after.js +0 -8
  202. package/dist/hooks/llm-after.js.map +0 -1
  203. package/dist/hooks/media-ingest.d.ts +0 -43
  204. package/dist/hooks/media-ingest.d.ts.map +0 -1
  205. package/dist/hooks/media-ingest.js +0 -269
  206. package/dist/hooks/media-ingest.js.map +0 -1
  207. package/dist/hooks/memory-guide.d.ts +0 -3
  208. package/dist/hooks/memory-guide.d.ts.map +0 -1
  209. package/dist/hooks/memory-guide.js +0 -47
  210. package/dist/hooks/memory-guide.js.map +0 -1
  211. package/dist/hooks/observation-capture.d.ts +0 -17
  212. package/dist/hooks/observation-capture.d.ts.map +0 -1
  213. package/dist/hooks/observation-capture.js +0 -20
  214. package/dist/hooks/observation-capture.js.map +0 -1
  215. package/dist/hooks/session-compacting.d.ts +0 -6
  216. package/dist/hooks/session-compacting.d.ts.map +0 -1
  217. package/dist/hooks/session-compacting.js +0 -6
  218. package/dist/hooks/session-compacting.js.map +0 -1
  219. package/dist/hooks/session-ending.d.ts +0 -20
  220. package/dist/hooks/session-ending.d.ts.map +0 -1
  221. package/dist/hooks/session-ending.js +0 -113
  222. package/dist/hooks/session-ending.js.map +0 -1
  223. package/dist/hooks/session-recall.d.ts +0 -2
  224. package/dist/hooks/session-recall.d.ts.map +0 -1
  225. package/dist/hooks/session-recall.js +0 -103
  226. package/dist/hooks/session-recall.js.map +0 -1
  227. package/dist/hooks/session-start.d.ts +0 -9
  228. package/dist/hooks/session-start.d.ts.map +0 -1
  229. package/dist/hooks/session-start.js +0 -42
  230. package/dist/hooks/session-start.js.map +0 -1
  231. package/dist/hooks/tool-before.d.ts +0 -10
  232. package/dist/hooks/tool-before.d.ts.map +0 -1
  233. package/dist/hooks/tool-before.js +0 -6
  234. package/dist/hooks/tool-before.js.map +0 -1
  235. package/dist/hooks/tool-executed.d.ts +0 -19
  236. package/dist/hooks/tool-executed.d.ts.map +0 -1
  237. package/dist/hooks/tool-executed.js +0 -12
  238. package/dist/hooks/tool-executed.js.map +0 -1
  239. package/dist/hooks/user-profile.d.ts +0 -2
  240. package/dist/hooks/user-profile.d.ts.map +0 -1
  241. package/dist/hooks/user-profile.js +0 -30
  242. package/dist/hooks/user-profile.js.map +0 -1
  243. package/dist/hooks/user-prompt.d.ts +0 -9
  244. package/dist/hooks/user-prompt.d.ts.map +0 -1
  245. package/dist/hooks/user-prompt.js +0 -8
  246. package/dist/hooks/user-prompt.js.map +0 -1
  247. package/dist/hooks/voice-guide.d.ts +0 -8
  248. package/dist/hooks/voice-guide.d.ts.map +0 -1
  249. package/dist/hooks/voice-guide.js +0 -101
  250. package/dist/hooks/voice-guide.js.map +0 -1
  251. package/dist/plugin.d.ts +0 -55
  252. package/dist/plugin.d.ts.map +0 -1
  253. package/dist/plugin.js +0 -342
  254. package/dist/plugin.js.map +0 -1
  255. package/dist/tools/add-memory.d.ts +0 -17
  256. package/dist/tools/add-memory.d.ts.map +0 -1
  257. package/dist/tools/add-memory.js +0 -60
  258. package/dist/tools/add-memory.js.map +0 -1
  259. package/dist/tools/media-ask.d.ts +0 -14
  260. package/dist/tools/media-ask.d.ts.map +0 -1
  261. package/dist/tools/media-ask.js +0 -224
  262. package/dist/tools/media-ask.js.map +0 -1
  263. package/dist/tools/media-speak.d.ts +0 -16
  264. package/dist/tools/media-speak.d.ts.map +0 -1
  265. package/dist/tools/media-speak.js +0 -78
  266. package/dist/tools/media-speak.js.map +0 -1
  267. package/dist/tools/media-upload.d.ts +0 -14
  268. package/dist/tools/media-upload.d.ts.map +0 -1
  269. package/dist/tools/media-upload.js +0 -155
  270. package/dist/tools/media-upload.js.map +0 -1
  271. package/dist/tools/python-exec.d.ts +0 -13
  272. package/dist/tools/python-exec.d.ts.map +0 -1
  273. package/dist/tools/python-exec.js +0 -99
  274. package/dist/tools/python-exec.js.map +0 -1
  275. package/dist/tools/python-restart.d.ts +0 -11
  276. package/dist/tools/python-restart.d.ts.map +0 -1
  277. package/dist/tools/python-restart.js +0 -33
  278. package/dist/tools/python-restart.js.map +0 -1
  279. package/dist/utils/circuit-breaker.d.ts +0 -23
  280. package/dist/utils/circuit-breaker.d.ts.map +0 -1
  281. package/dist/utils/circuit-breaker.js +0 -75
  282. package/dist/utils/circuit-breaker.js.map +0 -1
  283. package/dist/utils/config-loader.d.ts +0 -20
  284. package/dist/utils/config-loader.d.ts.map +0 -1
  285. package/dist/utils/config-loader.js +0 -194
  286. package/dist/utils/config-loader.js.map +0 -1
  287. package/dist/utils/fallback.d.ts +0 -7
  288. package/dist/utils/fallback.d.ts.map +0 -1
  289. package/dist/utils/fallback.js +0 -20
  290. package/dist/utils/fallback.js.map +0 -1
  291. package/dist/utils/git.d.ts +0 -24
  292. package/dist/utils/git.d.ts.map +0 -1
  293. package/dist/utils/git.js +0 -71
  294. package/dist/utils/git.js.map +0 -1
  295. package/dist/utils/github.d.ts +0 -37
  296. package/dist/utils/github.d.ts.map +0 -1
  297. package/dist/utils/github.js +0 -53
  298. package/dist/utils/github.js.map +0 -1
  299. package/dist/utils/global-path.d.ts +0 -2
  300. package/dist/utils/global-path.d.ts.map +0 -1
  301. package/dist/utils/global-path.js +0 -32
  302. package/dist/utils/global-path.js.map +0 -1
  303. package/dist/utils/logger.d.ts +0 -6
  304. package/dist/utils/logger.d.ts.map +0 -1
  305. package/dist/utils/logger.js +0 -69
  306. package/dist/utils/logger.js.map +0 -1
  307. package/dist/utils/obs-capture.d.ts +0 -7
  308. package/dist/utils/obs-capture.d.ts.map +0 -1
  309. package/dist/utils/obs-capture.js +0 -49
  310. package/dist/utils/obs-capture.js.map +0 -1
  311. package/dist/utils/retry.d.ts +0 -11
  312. package/dist/utils/retry.d.ts.map +0 -1
  313. package/dist/utils/retry.js +0 -45
  314. package/dist/utils/retry.js.map +0 -1
  315. package/dist/utils/self-wiring.d.ts +0 -13
  316. package/dist/utils/self-wiring.d.ts.map +0 -1
  317. package/dist/utils/self-wiring.js +0 -86
  318. package/dist/utils/self-wiring.js.map +0 -1
  319. package/dist/utils/state.d.ts +0 -106
  320. package/dist/utils/state.d.ts.map +0 -1
  321. package/dist/utils/state.js +0 -166
  322. package/dist/utils/state.js.map +0 -1
  323. package/dist/utils/status.d.ts +0 -47
  324. package/dist/utils/status.d.ts.map +0 -1
  325. package/dist/utils/status.js +0 -147
  326. package/dist/utils/status.js.map +0 -1
  327. package/dist/utils/ttl-map.d.ts +0 -17
  328. package/dist/utils/ttl-map.d.ts.map +0 -1
  329. package/dist/utils/ttl-map.js +0 -67
  330. package/dist/utils/ttl-map.js.map +0 -1
@@ -1,248 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * sync-brand-to-tokens.cjs
4
- *
5
- * Syncs brand-guidelines.md colors → design-tokens.json → design-tokens.css
6
- *
7
- * Usage:
8
- * node sync-brand-to-tokens.cjs
9
- * node sync-brand-to-tokens.cjs --dry-run
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const { execFileSync } = require('child_process');
15
-
16
- // Paths
17
- const BRAND_GUIDELINES = 'docs/brand-guidelines.md';
18
- const DESIGN_TOKENS_JSON = 'assets/design-tokens.json';
19
- const DESIGN_TOKENS_CSS = 'assets/design-tokens.css';
20
- const GENERATE_TOKENS_SCRIPT = '.claude/skills/design-system/scripts/generate-tokens.cjs';
21
-
22
- /**
23
- * Extract color info from brand guidelines markdown
24
- */
25
- function extractColorsFromMarkdown(content) {
26
- const colors = {
27
- primary: { name: 'primary', shades: {} },
28
- secondary: { name: 'secondary', shades: {} },
29
- accent: { name: 'accent', shades: {} }
30
- };
31
-
32
- // Match a "| Label | #hex |" markdown table row. Bold around the label
33
- // (**Label**) is optional, so this handles both the bundled starter template
34
- // ("| Primary Blue | #2563EB |") and bolded variants.
35
- const rowRe = /\|\s*\*{0,2}([^*|]+?)\*{0,2}\s*\|\s*#([A-Fa-f0-9]{6})\b/g;
36
-
37
- // 1) Quick Reference table — hex only, no parenthesized name required.
38
- const quickRef = {
39
- primary: /Primary Color\s*\|\s*#([A-Fa-f0-9]{6})/i,
40
- secondary: /Secondary Color\s*\|\s*#([A-Fa-f0-9]{6})/i,
41
- accent: /Accent Color\s*\|\s*#([A-Fa-f0-9]{6})/i
42
- };
43
- for (const key of Object.keys(quickRef)) {
44
- const m = content.match(quickRef[key]);
45
- if (m) colors[key].base = `#${m[1]}`;
46
- }
47
-
48
- // 2) Dedicated "### <Role> Colors" tables — assign base/dark/light by the
49
- // row label keyword.
50
- const assignFromSection = (heading, target) => {
51
- const section = content.match(new RegExp(`### ${heading}[\\s\\S]*?(?=\\n###|$)`, 'i'));
52
- if (!section) return;
53
- for (const m of section[0].matchAll(rowRe)) {
54
- const label = m[1].trim().toLowerCase();
55
- const hex = `#${m[2]}`;
56
- if (label.includes('dark')) target.dark = hex;
57
- else if (label.includes('light')) target.light = hex;
58
- else if (!target.base) target.base = hex;
59
- }
60
- };
61
- assignFromSection('Primary Colors', colors.primary);
62
- assignFromSection('Secondary Colors', colors.secondary);
63
- assignFromSection('Accent Colors', colors.accent);
64
-
65
- // 3) Fallback: an accent swatch may live in another table (the starter
66
- // lists "Accent Green" under Secondary Colors).
67
- if (!colors.accent.base) {
68
- for (const m of content.matchAll(rowRe)) {
69
- if (m[1].trim().toLowerCase().includes('accent')) {
70
- colors.accent.base = `#${m[2]}`;
71
- break;
72
- }
73
- }
74
- }
75
-
76
- return colors;
77
- }
78
-
79
- /**
80
- * Generate color scale from base color (simple approach)
81
- */
82
- function generateColorScale(baseHex, darkHex, lightHex) {
83
- // Use provided shades or generate approximations
84
- return {
85
- "50": { "$value": lightHex || adjustBrightness(baseHex, 0.9), "$type": "color" },
86
- "100": { "$value": lightHex || adjustBrightness(baseHex, 0.8), "$type": "color" },
87
- "200": { "$value": adjustBrightness(baseHex, 0.6), "$type": "color" },
88
- "300": { "$value": adjustBrightness(baseHex, 0.4), "$type": "color" },
89
- "400": { "$value": adjustBrightness(baseHex, 0.2), "$type": "color" },
90
- "500": { "$value": baseHex, "$type": "color" },
91
- "600": { "$value": darkHex || adjustBrightness(baseHex, -0.15), "$type": "color" },
92
- "700": { "$value": adjustBrightness(baseHex, -0.3), "$type": "color" },
93
- "800": { "$value": adjustBrightness(baseHex, -0.45), "$type": "color" },
94
- "900": { "$value": adjustBrightness(baseHex, -0.6), "$type": "color" }
95
- };
96
- }
97
-
98
- /**
99
- * Adjust hex color brightness
100
- */
101
- function adjustBrightness(hex, percent) {
102
- if (typeof hex !== 'string') return '#000000';
103
- const num = parseInt(hex.replace('#', ''), 16);
104
- const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));
105
- const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent)));
106
- const b = Math.min(255, Math.max(0, (num & 0x0000FF) + Math.round(255 * percent)));
107
- return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase()}`;
108
- }
109
-
110
- /**
111
- * Update design tokens JSON
112
- */
113
- function updateDesignTokens(tokens, colors) {
114
- // Update brand name
115
- const brandName = `ClaudeKit Marketing - ${colors.primary.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}`;
116
- tokens.brand = brandName;
117
-
118
- // Update primitive colors with new names
119
- tokens.primitive = tokens.primitive || {};
120
- const primitiveColors = tokens.primitive.color || {};
121
-
122
- // Remove old color keys, add new ones
123
- delete primitiveColors.coral;
124
- delete primitiveColors.purple;
125
- delete primitiveColors.mint;
126
-
127
- // Add new named colors. Skip any role with no base hex rather than crashing
128
- // on an unexpected guidelines format.
129
- for (const role of ['primary', 'secondary', 'accent']) {
130
- const c = colors[role];
131
- if (!c.base) {
132
- console.warn(`⚠️ No base hex found for ${role} color — skipping its token scale.`);
133
- continue;
134
- }
135
- primitiveColors[c.name] = generateColorScale(c.base, c.dark, c.light);
136
- }
137
-
138
- tokens.primitive.color = primitiveColors;
139
-
140
- // Update ALL semantic color references
141
- if (tokens.semantic?.color) {
142
- const sem = tokens.semantic.color;
143
- const p = colors.primary.name;
144
- const s = colors.secondary.name;
145
- const a = colors.accent.name;
146
-
147
- // Primary variants
148
- sem.primary = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };
149
- sem['primary-hover'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };
150
- sem['primary-active'] = { "$value": `{primitive.color.${p}.700}`, "$type": "color" };
151
- sem['primary-light'] = { "$value": `{primitive.color.${p}.400}`, "$type": "color" };
152
- sem['primary-lighter'] = { "$value": `{primitive.color.${p}.100}`, "$type": "color" };
153
- sem['primary-dark'] = { "$value": `{primitive.color.${p}.600}`, "$type": "color" };
154
-
155
- // Secondary variants
156
- sem.secondary = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };
157
- sem['secondary-hover'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };
158
- sem['secondary-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };
159
- sem['secondary-dark'] = { "$value": `{primitive.color.${s}.600}`, "$type": "color" };
160
-
161
- // Accent variants
162
- sem.accent = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };
163
- sem['accent-hover'] = { "$value": `{primitive.color.${a}.600}`, "$type": "color" };
164
- sem['accent-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };
165
-
166
- // Status colors (use accent for success, primary for error/info)
167
- sem.success = { "$value": `{primitive.color.${a}.500}`, "$type": "color" };
168
- sem['success-light'] = { "$value": `{primitive.color.${a}.300}`, "$type": "color" };
169
- sem.error = { "$value": `{primitive.color.${p}.500}`, "$type": "color" };
170
- sem['error-light'] = { "$value": `{primitive.color.${p}.300}`, "$type": "color" };
171
- sem.info = { "$value": `{primitive.color.${s}.500}`, "$type": "color" };
172
- sem['info-light'] = { "$value": `{primitive.color.${s}.300}`, "$type": "color" };
173
- }
174
-
175
- // Update component references (button uses primary color with opacity)
176
- if (tokens.component?.button?.secondary && colors.primary.base) {
177
- const primaryBase = colors.primary.base;
178
- tokens.component.button.secondary['bg-hover'] = {
179
- "$value": `${primaryBase}1A`,
180
- "$type": "color"
181
- };
182
- }
183
-
184
- return tokens;
185
- }
186
-
187
- /**
188
- * Main
189
- */
190
- function main() {
191
- const dryRun = process.argv.includes('--dry-run');
192
-
193
- console.log('🔄 Syncing brand guidelines → design tokens\n');
194
-
195
- // Read brand guidelines
196
- const guidelinesPath = path.resolve(process.cwd(), BRAND_GUIDELINES);
197
- if (!fs.existsSync(guidelinesPath)) {
198
- console.error(`❌ Brand guidelines not found: ${guidelinesPath}`);
199
- process.exit(1);
200
- }
201
- const guidelinesContent = fs.readFileSync(guidelinesPath, 'utf-8');
202
-
203
- // Extract colors
204
- const colors = extractColorsFromMarkdown(guidelinesContent);
205
- console.log('📊 Extracted colors:');
206
- console.log(` Primary: ${colors.primary.name} (${colors.primary.base})`);
207
- console.log(` Secondary: ${colors.secondary.name} (${colors.secondary.base})`);
208
- console.log(` Accent: ${colors.accent.name} (${colors.accent.base})\n`);
209
-
210
- // Read existing tokens
211
- const tokensPath = path.resolve(process.cwd(), DESIGN_TOKENS_JSON);
212
- let tokens = {};
213
- if (fs.existsSync(tokensPath)) {
214
- tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
215
- }
216
-
217
- // Update tokens
218
- tokens = updateDesignTokens(tokens, colors);
219
-
220
- if (dryRun) {
221
- console.log('📋 Would update design-tokens.json:');
222
- console.log(JSON.stringify(tokens.primitive.color, null, 2).slice(0, 500) + '...');
223
- console.log('\n⏭️ Dry run - no files changed');
224
- return;
225
- }
226
-
227
- // Write updated tokens
228
- fs.writeFileSync(tokensPath, JSON.stringify(tokens, null, 2));
229
- console.log(`✅ Updated: ${DESIGN_TOKENS_JSON}`);
230
-
231
- // Regenerate CSS
232
- const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT);
233
- if (fs.existsSync(generateScript)) {
234
- try {
235
- execFileSync('node', [generateScript, '--config', DESIGN_TOKENS_JSON, '-o', DESIGN_TOKENS_CSS], {
236
- cwd: process.cwd(),
237
- stdio: 'inherit'
238
- });
239
- console.log(`✅ Regenerated: ${DESIGN_TOKENS_CSS}`);
240
- } catch (e) {
241
- console.error('⚠️ Failed to regenerate CSS:', e.message);
242
- }
243
- }
244
-
245
- console.log('\n✨ Brand sync complete!');
246
- }
247
-
248
- main();
@@ -1,52 +0,0 @@
1
- """Regression test for sync-brand-to-tokens.cjs.
2
-
3
- The color parser required a parenthesized name in the Quick Reference row
4
- (`#2563EB (name)`) and a bolded label in the color tables (`**Primary Blue**`),
5
- neither of which the bundled starter template uses. As a result the base hex
6
- came back `undefined` and `adjustBrightness(undefined)` threw a TypeError —
7
- i.e. the script crashed on its own documented happy path. This test runs the
8
- sync against the bundled starter template and asserts it completes and writes
9
- the expected base colors. It is pytest-based so the existing pytest CI runs it.
10
- """
11
-
12
- import json
13
- import shutil
14
- import subprocess
15
- from pathlib import Path
16
-
17
- import pytest
18
-
19
- SCRIPTS = Path(__file__).resolve().parent.parent
20
- SCRIPT = SCRIPTS / "sync-brand-to-tokens.cjs"
21
- BRAND_STARTER = SCRIPTS.parent / "templates" / "brand-guidelines-starter.md"
22
- TOKENS_STARTER = (
23
- SCRIPTS.parent.parent / "design-system" / "templates" / "design-tokens-starter.json"
24
- )
25
-
26
-
27
- def test_sync_parses_bundled_starter_template(tmp_path):
28
- node = shutil.which("node")
29
- if not node:
30
- pytest.skip("node not available")
31
-
32
- (tmp_path / "docs").mkdir()
33
- (tmp_path / "assets").mkdir()
34
- shutil.copy(BRAND_STARTER, tmp_path / "docs" / "brand-guidelines.md")
35
- shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
36
-
37
- result = subprocess.run(
38
- [node, str(SCRIPT)],
39
- cwd=tmp_path,
40
- capture_output=True,
41
- text=True,
42
- )
43
-
44
- # Must not crash (the bug raised an unhandled TypeError).
45
- assert "TypeError" not in result.stderr, result.stderr
46
- assert result.returncode == 0, result.stderr + result.stdout
47
-
48
- tokens = json.loads((tmp_path / "assets" / "design-tokens.json").read_text())
49
- primitive = tokens["primitive"]["color"]
50
- assert primitive["primary"]["500"]["$value"] == "#2563EB"
51
- assert primitive["secondary"]["500"]["$value"] == "#8B5CF6"
52
- assert primitive["accent"]["500"]["$value"] == "#10B981"
@@ -1,387 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * validate-asset.cjs
4
- *
5
- * Validates marketing assets against brand guidelines.
6
- * Checks: file naming, dimensions, file size, metadata.
7
- *
8
- * Usage:
9
- * node validate-asset.cjs <asset-path>
10
- * node validate-asset.cjs <asset-path> --json
11
- * node validate-asset.cjs <asset-path> --fix
12
- *
13
- * For color validation of images, use with extract-colors.cjs
14
- */
15
-
16
- const fs = require("fs");
17
- const path = require("path");
18
-
19
- // Validation rules
20
- const RULES = {
21
- naming: {
22
- pattern: /^[a-z]+_[a-z0-9-]+_[a-z0-9-]+_\d{8}(_[a-z0-9-]+)?\.[a-z]+$/,
23
- description:
24
- "{type}_{campaign}_{description}_{timestamp}_{variant}.{ext}",
25
- examples: [
26
- "banner_claude-launch_hero-image_20251209.png",
27
- "logo_brand-refresh_horizontal_20251209_dark.svg",
28
- ],
29
- },
30
- dimensions: {
31
- banner: { minWidth: 600, minHeight: 300 },
32
- logo: { minWidth: 100, minHeight: 100 },
33
- design: { minWidth: 800, minHeight: 600 },
34
- video: { minWidth: 640, minHeight: 480 },
35
- default: { minWidth: 100, minHeight: 100 },
36
- },
37
- fileSize: {
38
- image: { max: 5 * 1024 * 1024, recommended: 1 * 1024 * 1024 },
39
- video: { max: 100 * 1024 * 1024, recommended: 50 * 1024 * 1024 },
40
- svg: { max: 500 * 1024, recommended: 100 * 1024 },
41
- },
42
- formats: {
43
- image: ["png", "jpg", "jpeg", "webp", "gif"],
44
- vector: ["svg"],
45
- video: ["mp4", "mov", "webm"],
46
- document: ["pdf", "psd", "ai", "fig"],
47
- },
48
- };
49
-
50
- /**
51
- * Parse asset filename
52
- */
53
- function parseFilename(filename) {
54
- const parts = filename.replace(/\.[^.]+$/, "").split("_");
55
-
56
- if (parts.length < 4) {
57
- return null;
58
- }
59
-
60
- return {
61
- type: parts[0],
62
- campaign: parts[1],
63
- description: parts[2],
64
- timestamp: parts[3],
65
- variant: parts.length > 4 ? parts[4] : null,
66
- extension: path.extname(filename).slice(1).toLowerCase(),
67
- };
68
- }
69
-
70
- /**
71
- * Validate filename convention
72
- */
73
- function validateFilename(filename) {
74
- const issues = [];
75
- const suggestions = [];
76
-
77
- // Check pattern match
78
- if (!RULES.naming.pattern.test(filename)) {
79
- issues.push("Filename does not match naming convention");
80
- suggestions.push(`Expected format: ${RULES.naming.description}`);
81
- suggestions.push(`Examples: ${RULES.naming.examples.join(", ")}`);
82
- }
83
-
84
- // Parse and check components
85
- const parsed = parseFilename(filename);
86
- if (parsed) {
87
- // Check timestamp format
88
- if (!/^\d{8}$/.test(parsed.timestamp)) {
89
- issues.push("Timestamp should be YYYYMMDD format");
90
- }
91
-
92
- // Check kebab-case for campaign and description
93
- if (parsed.campaign && !/^[a-z0-9-]+$/.test(parsed.campaign)) {
94
- issues.push("Campaign name should be kebab-case");
95
- }
96
-
97
- if (parsed.description && !/^[a-z0-9-]+$/.test(parsed.description)) {
98
- issues.push("Description should be kebab-case");
99
- }
100
-
101
- // Check valid type
102
- const validTypes = [
103
- "banner",
104
- "logo",
105
- "design",
106
- "video",
107
- "infographic",
108
- "icon",
109
- "photo",
110
- ];
111
- if (!validTypes.includes(parsed.type)) {
112
- suggestions.push(`Consider using type: ${validTypes.join(", ")}`);
113
- }
114
- }
115
-
116
- return { valid: issues.length === 0, issues, suggestions, parsed };
117
- }
118
-
119
- /**
120
- * Validate file size
121
- */
122
- function validateFileSize(filepath, extension) {
123
- const issues = [];
124
- const warnings = [];
125
-
126
- const stats = fs.statSync(filepath);
127
- const size = stats.size;
128
-
129
- let limits;
130
- if (RULES.formats.video.includes(extension)) {
131
- limits = RULES.fileSize.video;
132
- } else if (extension === "svg") {
133
- limits = RULES.fileSize.svg;
134
- } else {
135
- limits = RULES.fileSize.image;
136
- }
137
-
138
- if (size > limits.max) {
139
- issues.push(
140
- `File size (${formatBytes(size)}) exceeds maximum (${formatBytes(
141
- limits.max
142
- )})`
143
- );
144
- } else if (size > limits.recommended) {
145
- warnings.push(
146
- `File size (${formatBytes(size)}) exceeds recommended (${formatBytes(
147
- limits.recommended
148
- )})`
149
- );
150
- }
151
-
152
- return { valid: issues.length === 0, issues, warnings, size };
153
- }
154
-
155
- /**
156
- * Validate file format
157
- */
158
- function validateFormat(extension) {
159
- const issues = [];
160
- const info = { category: null };
161
-
162
- const allFormats = [
163
- ...RULES.formats.image,
164
- ...RULES.formats.vector,
165
- ...RULES.formats.video,
166
- ...RULES.formats.document,
167
- ];
168
-
169
- if (!allFormats.includes(extension)) {
170
- issues.push(`Unsupported file format: .${extension}`);
171
- return { valid: false, issues, info };
172
- }
173
-
174
- // Determine category
175
- if (RULES.formats.image.includes(extension)) info.category = "image";
176
- else if (RULES.formats.vector.includes(extension)) info.category = "vector";
177
- else if (RULES.formats.video.includes(extension)) info.category = "video";
178
- else if (RULES.formats.document.includes(extension))
179
- info.category = "document";
180
-
181
- return { valid: true, issues, info };
182
- }
183
-
184
- /**
185
- * Check if asset exists in manifest
186
- */
187
- function checkManifest(filepath) {
188
- const manifestPath = path.join(process.cwd(), ".assets", "manifest.json");
189
-
190
- if (!fs.existsSync(manifestPath)) {
191
- return { registered: false, message: "Manifest not found" };
192
- }
193
-
194
- try {
195
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
196
- const relativePath = path.relative(process.cwd(), filepath);
197
- const found = manifest.assets?.find(
198
- (a) => a.path === relativePath || a.path === filepath
199
- );
200
-
201
- return {
202
- registered: !!found,
203
- message: found ? "Asset registered in manifest" : "Asset not in manifest",
204
- asset: found,
205
- };
206
- } catch {
207
- return { registered: false, message: "Error reading manifest" };
208
- }
209
- }
210
-
211
- /**
212
- * Generate suggested filename
213
- */
214
- function suggestFilename(original, parsed) {
215
- if (!parsed) return null;
216
-
217
- const today = new Date().toISOString().slice(0, 10).replace(/-/g, "");
218
- const type = parsed.type || "asset";
219
- const campaign = parsed.campaign || "general";
220
- const description = parsed.description || "untitled";
221
- const ext = parsed.extension || "png";
222
-
223
- return `${type}_${campaign}_${description}_${today}.${ext}`;
224
- }
225
-
226
- /**
227
- * Format bytes to human readable
228
- */
229
- function formatBytes(bytes) {
230
- if (bytes === 0) return "0 Bytes";
231
- const k = 1024;
232
- const sizes = ["Bytes", "KB", "MB", "GB"];
233
- const i = Math.floor(Math.log(bytes) / Math.log(k));
234
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
235
- }
236
-
237
- /**
238
- * Main validation function
239
- */
240
- function validateAsset(assetPath) {
241
- const results = {
242
- path: assetPath,
243
- filename: path.basename(assetPath),
244
- valid: true,
245
- issues: [],
246
- warnings: [],
247
- suggestions: [],
248
- checks: {},
249
- };
250
-
251
- // Check file exists
252
- if (!fs.existsSync(assetPath)) {
253
- results.valid = false;
254
- results.issues.push(`File not found: ${assetPath}`);
255
- return results;
256
- }
257
-
258
- const filename = path.basename(assetPath);
259
- const extension = path.extname(filename).slice(1).toLowerCase();
260
-
261
- // 1. Validate filename
262
- const filenameResult = validateFilename(filename);
263
- results.checks.filename = filenameResult;
264
- if (!filenameResult.valid) {
265
- results.issues.push(...filenameResult.issues);
266
- results.suggestions.push(...filenameResult.suggestions);
267
- }
268
-
269
- // 2. Validate format
270
- const formatResult = validateFormat(extension);
271
- results.checks.format = formatResult;
272
- if (!formatResult.valid) {
273
- results.issues.push(...formatResult.issues);
274
- }
275
-
276
- // 3. Validate file size
277
- const sizeResult = validateFileSize(assetPath, extension);
278
- results.checks.fileSize = sizeResult;
279
- if (!sizeResult.valid) {
280
- results.issues.push(...sizeResult.issues);
281
- }
282
- results.warnings.push(...sizeResult.warnings);
283
-
284
- // 4. Check manifest registration
285
- const manifestResult = checkManifest(assetPath);
286
- results.checks.manifest = manifestResult;
287
- if (!manifestResult.registered) {
288
- results.warnings.push("Asset not registered in manifest.json");
289
- results.suggestions.push(
290
- "Register asset in .assets/manifest.json for tracking"
291
- );
292
- }
293
-
294
- // 5. Suggest corrected filename if needed
295
- if (!filenameResult.valid && filenameResult.parsed) {
296
- const suggested = suggestFilename(filename, filenameResult.parsed);
297
- if (suggested) {
298
- results.suggestions.push(`Suggested filename: ${suggested}`);
299
- }
300
- }
301
-
302
- // Overall validity
303
- results.valid = results.issues.length === 0;
304
-
305
- return results;
306
- }
307
-
308
- /**
309
- * Format output for console
310
- */
311
- function formatOutput(results) {
312
- const lines = [];
313
-
314
- lines.push("\n" + "=".repeat(60));
315
- lines.push(`ASSET VALIDATION: ${results.filename}`);
316
- lines.push("=".repeat(60));
317
-
318
- lines.push(`\nStatus: ${results.valid ? "PASS" : "FAIL"}`);
319
- lines.push(`Path: ${results.path}`);
320
-
321
- if (results.issues.length > 0) {
322
- lines.push("\nISSUES:");
323
- results.issues.forEach((issue) => lines.push(` - ${issue}`));
324
- }
325
-
326
- if (results.warnings.length > 0) {
327
- lines.push("\nWARNINGS:");
328
- results.warnings.forEach((warning) => lines.push(` - ${warning}`));
329
- }
330
-
331
- if (results.suggestions.length > 0) {
332
- lines.push("\nSUGGESTIONS:");
333
- results.suggestions.forEach((suggestion) =>
334
- lines.push(` - ${suggestion}`)
335
- );
336
- }
337
-
338
- // File size info
339
- if (results.checks.fileSize?.size) {
340
- lines.push(`\nFile Size: ${formatBytes(results.checks.fileSize.size)}`);
341
- }
342
-
343
- lines.push("\n" + "=".repeat(60));
344
-
345
- return lines.join("\n");
346
- }
347
-
348
- /**
349
- * Main
350
- */
351
- function main() {
352
- const args = process.argv.slice(2);
353
- const jsonOutput = args.includes("--json");
354
- const assetPath = args.find((a) => !a.startsWith("--"));
355
-
356
- if (!assetPath) {
357
- console.error("Usage: node validate-asset.cjs <asset-path> [--json]");
358
- console.error("\nExamples:");
359
- console.error(
360
- " node validate-asset.cjs assets/banners/social-media/banner_launch_hero_20251209.png"
361
- );
362
- console.error(
363
- " node validate-asset.cjs assets/logos/icon-only/logo-icon.svg --json"
364
- );
365
- process.exit(1);
366
- }
367
-
368
- // Resolve path
369
- const resolvedPath = path.isAbsolute(assetPath)
370
- ? assetPath
371
- : path.join(process.cwd(), assetPath);
372
-
373
- // Validate
374
- const results = validateAsset(resolvedPath);
375
-
376
- // Output
377
- if (jsonOutput) {
378
- console.log(JSON.stringify(results, null, 2));
379
- } else {
380
- console.log(formatOutput(results));
381
- }
382
-
383
- // Exit with appropriate code
384
- process.exit(results.valid ? 0 : 1);
385
- }
386
-
387
- main();