foliko 1.1.8 → 1.1.9

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 (306) hide show
  1. package/.agent/agents/code-assistant.json +17 -0
  2. package/.agent/agents/email-assistant.json +14 -0
  3. package/.agent/agents/file-assistant.json +18 -0
  4. package/.agent/agents/orchestrator-demo.md +53 -0
  5. package/.agent/agents/orchestrator.json +7 -0
  6. package/.agent/agents/poster-expert.md +228 -0
  7. package/.agent/agents/system-assistant.json +15 -0
  8. package/.agent/agents/web-assistant.json +12 -0
  9. package/.agent/data/default.json +5 -404
  10. package/.agent/data/email/processed-emails.json +1 -0
  11. package/.agent/data/plugins-state.json +173 -172
  12. package/.agent/data/scheduler/tasks.json +1 -0
  13. package/.agent/data/web/web-config.json +5 -0
  14. package/.agent/mcp_config.json +0 -14
  15. package/.agent/package.json +8 -0
  16. package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
  17. package/.agent/plugins/daytona/README.md +89 -0
  18. package/.agent/plugins/daytona/index.js +377 -0
  19. package/.agent/plugins/daytona/package.json +12 -0
  20. package/.agent/plugins/marknative/README.md +134 -0
  21. package/.agent/plugins/marknative/fonts.zip +0 -0
  22. package/.agent/plugins/marknative/index.js +256 -0
  23. package/.agent/plugins/marknative/package.json +12 -0
  24. package/.agent/plugins/system-info/index.js +387 -0
  25. package/.agent/plugins/system-info/package.json +4 -0
  26. package/.agent/plugins/system-info/test.js +40 -0
  27. package/.agent/plugins/test-plugin.py +123 -0
  28. package/.agent/plugins/test_nested_plugin.py +85 -0
  29. package/.agent/plugins.json +11 -5
  30. package/.agent/python-scripts/test_sample.py +24 -0
  31. package/.agent/sessions/cli_default.json +96 -249
  32. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
  33. package/.agent/skills/agent-browser/SKILL.md +311 -0
  34. package/.agent/skills/agent-browser/TEST_PLAN.md +200 -0
  35. package/.agent/skills/sysinfo/SKILL.md +38 -0
  36. package/.agent/skills/sysinfo/system-info.sh +130 -0
  37. package/.agent/skills/workflow/SKILL.md +324 -0
  38. package/.agent/test-agent.js +35 -0
  39. package/.agent/weixin.json +6 -0
  40. package/.agent/workflows/email-digest.json +50 -0
  41. package/.agent/workflows/file-backup.json +21 -0
  42. package/.agent/workflows/get-ip-notify.json +32 -0
  43. package/.agent/workflows/news-aggregator.json +93 -0
  44. package/.agent/workflows/news-dashboard-v2.json +94 -0
  45. package/.agent/workflows/notification-batch.json +32 -0
  46. package/.claude/settings.local.json +9 -1
  47. package/.env.example +56 -56
  48. package/README.md +441 -441
  49. package/cli/src/ui/chat-ui.js +32 -21
  50. package/foliko_poster.png +0 -0
  51. package/output/business_poster_final.png +0 -0
  52. package/output/emoji_test_v2.png +0 -0
  53. package/output/foliko-ai-launch-poster.png +0 -0
  54. package/output/foliko-poster.png +0 -0
  55. package/output/muji_style_poster.png +0 -0
  56. package/output/new_product_launch.png +0 -0
  57. package/output/news_poster_625yi_subsidy.png +0 -0
  58. package/output/tech_future_2026.png +0 -0
  59. package/output/tech_future_poster.png +0 -0
  60. package/package.json +1 -2
  61. package/plugins/default-plugins.js +58 -6
  62. package/plugins/extension-executor-plugin.js +8 -21
  63. package/plugins/python-plugin-loader.js +461 -40
  64. package/plugins/python-plugin-loader.js.bak +856 -0
  65. package/plugins/subagent-plugin.js +0 -121
  66. package/plugins/weixin-plugin.js +38 -29
  67. package/poster.png +0 -0
  68. package/skills/find-skills/AGENTS.md +162 -162
  69. package/skills/find-skills/SKILL.md +133 -133
  70. package/skills/foliko-dev/SKILL.md +6 -0
  71. package/skills/python-plugin-dev/SKILL.md +124 -2
  72. package/src/core/agent-chat.js +220 -281
  73. package/src/core/agent.js +10 -0
  74. package/src/utils/plugin-helpers.js +22 -4
  75. package/system.md +1678 -1574
  76. package/test-scan.js +18 -0
  77. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +0 -26
  78. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +0 -97
  79. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +0 -101
  80. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +0 -31
  81. package/.agent/.shared/ui-ux-pro-max/data/products.csv +0 -97
  82. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
  83. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
  84. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  85. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  86. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  87. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  88. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
  89. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  90. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  91. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
  92. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  93. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  94. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  95. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  96. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +0 -59
  97. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +0 -58
  98. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
  99. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  100. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
  101. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  102. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  103. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +0 -258
  104. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
  105. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +0 -106
  106. package/.agent/ARCHITECTURE.md +0 -288
  107. package/.agent/agents/ambient-agent.md +0 -57
  108. package/.agent/agents/debugger.md +0 -55
  109. package/.agent/agents/email-assistant.md +0 -49
  110. package/.agent/agents/file-manager.md +0 -42
  111. package/.agent/agents/python-developer.md +0 -60
  112. package/.agent/agents/scheduler.md +0 -59
  113. package/.agent/agents/web-developer.md +0 -45
  114. package/.agent/data/puppeteer-sessions/undefined.json +0 -6
  115. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  116. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  117. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  118. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  119. package/.agent/memory/feedback/mnrdvj5i-ca3dkd.md +0 -9
  120. package/.agent/memory/feedback/mnre365e-7s4zax.md +0 -9
  121. package/.agent/memory/feedback/mnre36jn-nkfgmp.md +0 -9
  122. package/.agent/memory/feedback/mnre3805-kjiq6h.md +0 -9
  123. package/.agent/memory/feedback/mnsf66kp-b10rcd.md +0 -9
  124. package/.agent/memory/feedback/mnsi3sz8-p5g2cw.md +0 -9
  125. package/.agent/memory/feedback/mnsibe47-sv2ni1.md +0 -9
  126. package/.agent/memory/feedback/mnsic89w-nn228o.md +0 -9
  127. package/.agent/memory/feedback/mnsj1xe9-x83ba0.md +0 -9
  128. package/.agent/memory/feedback/mnsj21iv-wnwelx.md +0 -9
  129. package/.agent/memory/feedback/mnsj2g4a-cog7a2.md +0 -9
  130. package/.agent/memory/feedback/mnsj4js7-lktjp6.md +0 -9
  131. package/.agent/memory/feedback/mnsj5d4y-uglwvp.md +0 -9
  132. package/.agent/memory/feedback/mnslkuo9-uous66.md +0 -24
  133. package/.agent/memory/feedback/mnsm3vq0-megoil.md +0 -9
  134. package/.agent/memory/feedback/mnsnn5x2-sxcihd.md +0 -9
  135. package/.agent/memory/feedback/mnsnq17s-nabrn9.md +0 -9
  136. package/.agent/memory/feedback/mnsnybet-wz7rn3.md +0 -9
  137. package/.agent/memory/feedback/mnsrw0s7-7s9e30.md +0 -9
  138. package/.agent/memory/feedback/mnu5hpnd-tlm16q.md +0 -9
  139. package/.agent/memory/feedback/mnu60uqe-xuoxp4.md +0 -9
  140. package/.agent/memory/project/mnqx54u5-loqtoe.md +0 -9
  141. package/.agent/memory/project/mnqx84cv-mx6dmd.md +0 -9
  142. package/.agent/memory/project/mnsacuyr-hgtk5n.md +0 -20
  143. package/.agent/memory/project/mnu5hy2x-bjsg7u.md +0 -9
  144. package/.agent/memory/reference/mnre3cww-penbo1.md +0 -9
  145. package/.agent/memory/reference/mns9wn48-luerua.md +0 -14
  146. package/.agent/memory/reference/mns9yz5c-thc2s0.md +0 -16
  147. package/.agent/memory/reference/mnsfy4um-910f1o.md +0 -23
  148. package/.agent/memory/reference/mnsg37dp-lmfj18.md +0 -32
  149. package/.agent/memory/reference/mnsll60q-0j911u.md +0 -36
  150. package/.agent/memory/reference/mnsmlb5y-nej31u.md +0 -16
  151. package/.agent/memory/reference/mnssle72-yrot96.md +0 -9
  152. package/.agent/memory/user/mnsfuon6-l416q1.md +0 -21
  153. package/.agent/memory/user/mnsg9kut-95m7rf.md +0 -20
  154. package/.agent/memory/user/mnu2eo1v-yy6fhe.md +0 -9
  155. package/.agent/memory/user/mnu2etuo-8u8jk8.md +0 -9
  156. package/.agent/plugins/poster-plugin/README.md +0 -304
  157. package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
  158. package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
  159. package/.agent/plugins/poster-plugin/fonts/Symbola_hint.ttf +0 -0
  160. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  161. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
  162. package/.agent/plugins/poster-plugin/index.js +0 -13
  163. package/.agent/plugins/poster-plugin/package.json +0 -29
  164. package/.agent/plugins/poster-plugin/src/canvas.js +0 -232
  165. package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
  166. package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
  167. package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
  168. package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -123
  169. package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -154
  170. package/.agent/plugins/poster-plugin/src/components/button.js +0 -168
  171. package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
  172. package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
  173. package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
  174. package/.agent/plugins/poster-plugin/src/components/columns.js +0 -121
  175. package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
  176. package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
  177. package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
  178. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
  179. package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
  180. package/.agent/plugins/poster-plugin/src/components/grid.js +0 -130
  181. package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -145
  182. package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
  183. package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -205
  184. package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
  185. package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
  186. package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
  187. package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
  188. package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
  189. package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
  190. package/.agent/plugins/poster-plugin/src/components/quote.js +0 -169
  191. package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
  192. package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -197
  193. package/.agent/plugins/poster-plugin/src/components/seal.js +0 -148
  194. package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
  195. package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
  196. package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
  197. package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
  198. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
  199. package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
  200. package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -54
  201. package/.agent/plugins/poster-plugin/src/composer.js +0 -2314
  202. package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -69
  203. package/.agent/plugins/poster-plugin/src/elements/background.js +0 -99
  204. package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
  205. package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
  206. package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
  207. package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
  208. package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
  209. package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
  210. package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -283
  211. package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
  212. package/.agent/plugins/poster-plugin/src/elements/text.js +0 -112
  213. package/.agent/plugins/poster-plugin/src/fonts.js +0 -674
  214. package/.agent/plugins/poster-plugin/src/index.js +0 -2126
  215. package/.agent/plugins/poster-plugin/src/presets.js +0 -36
  216. package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
  217. package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
  218. package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
  219. package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
  220. package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
  221. package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
  222. package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
  223. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
  224. package/.agent/plugins/poster-plugin/yarn.lock +0 -837
  225. package/.agent/plugins/puppeteer-plugin/README.md +0 -147
  226. package/.agent/plugins/puppeteer-plugin/index.js +0 -1422
  227. package/.agent/plugins/puppeteer-plugin/package.json +0 -9
  228. package/.agent/rules/GEMINI.md +0 -273
  229. package/.agent/rules/allow-rule.md +0 -77
  230. package/.agent/rules/log-rule.md +0 -83
  231. package/.agent/rules/security-rule.md +0 -93
  232. package/.agent/scripts/auto_preview.py +0 -148
  233. package/.agent/scripts/checklist.py +0 -217
  234. package/.agent/scripts/session_manager.py +0 -120
  235. package/.agent/scripts/verify_all.py +0 -327
  236. package/.agent/skills/api-patterns/SKILL.md +0 -81
  237. package/.agent/skills/api-patterns/api-style.md +0 -42
  238. package/.agent/skills/api-patterns/auth.md +0 -24
  239. package/.agent/skills/api-patterns/documentation.md +0 -26
  240. package/.agent/skills/api-patterns/graphql.md +0 -41
  241. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  242. package/.agent/skills/api-patterns/response.md +0 -37
  243. package/.agent/skills/api-patterns/rest.md +0 -40
  244. package/.agent/skills/api-patterns/scripts/api_validator.py +0 -211
  245. package/.agent/skills/api-patterns/security-testing.md +0 -122
  246. package/.agent/skills/api-patterns/trpc.md +0 -41
  247. package/.agent/skills/api-patterns/versioning.md +0 -22
  248. package/.agent/skills/app-builder/SKILL.md +0 -75
  249. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  250. package/.agent/skills/app-builder/feature-building.md +0 -53
  251. package/.agent/skills/app-builder/project-detection.md +0 -34
  252. package/.agent/skills/app-builder/scaffolding.md +0 -118
  253. package/.agent/skills/app-builder/tech-stack.md +0 -40
  254. package/.agent/skills/app-builder/templates/SKILL.md +0 -39
  255. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +0 -76
  256. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +0 -92
  257. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +0 -88
  258. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +0 -88
  259. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +0 -83
  260. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +0 -90
  261. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +0 -90
  262. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +0 -122
  263. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +0 -122
  264. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +0 -169
  265. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +0 -134
  266. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +0 -83
  267. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +0 -119
  268. package/.agent/skills/architecture/SKILL.md +0 -55
  269. package/.agent/skills/architecture/context-discovery.md +0 -43
  270. package/.agent/skills/architecture/examples.md +0 -94
  271. package/.agent/skills/architecture/pattern-selection.md +0 -68
  272. package/.agent/skills/architecture/patterns-reference.md +0 -50
  273. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  274. package/.agent/skills/clean-code/SKILL.md +0 -201
  275. package/.agent/skills/doc.md +0 -177
  276. package/.agent/skills/frontend-design/SKILL.md +0 -418
  277. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  278. package/.agent/skills/frontend-design/color-system.md +0 -311
  279. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  280. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  281. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +0 -183
  282. package/.agent/skills/frontend-design/scripts/ux_audit.py +0 -722
  283. package/.agent/skills/frontend-design/typography-system.md +0 -345
  284. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  285. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  286. package/.agent/skills/i18n-localization/SKILL.md +0 -154
  287. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +0 -241
  288. package/.agent/skills/mcp-builder/SKILL.md +0 -176
  289. package/.agent/skills/poster-design/SKILL.md +0 -385
  290. package/.agent/skills/web-design-guidelines/SKILL.md +0 -57
  291. package/.agent/workflows/brainstorm.md +0 -113
  292. package/.agent/workflows/create.md +0 -59
  293. package/.agent/workflows/debug.md +0 -103
  294. package/.agent/workflows/deploy.md +0 -176
  295. package/.agent/workflows/enhance.md +0 -63
  296. package/.agent/workflows/orchestrate.md +0 -237
  297. package/.agent/workflows/plan.md +0 -89
  298. package/.agent/workflows/preview.md +0 -81
  299. package/.agent/workflows/simple-test.md +0 -42
  300. package/.agent/workflows/status.md +0 -86
  301. package/.agent/workflows/structured-orchestrate.md +0 -180
  302. package/.agent/workflows/test.md +0 -144
  303. package/.agent/workflows/ui-ux-pro-max.md +0 -296
  304. package/outputs/emoji-font-showcase.png +0 -0
  305. package/outputs/foliko-muji-style.png +0 -0
  306. /package/.agent/plugins/{poster-plugin → marknative}/fonts/SegoeUI Emoji.ttf +0 -0
@@ -0,0 +1,256 @@
1
+ const { z } = require('zod');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ module.exports = function (Plugin) {
6
+ return class MarkNativePlugin extends Plugin {
7
+ constructor(config = {}) {
8
+ super();
9
+ this.name = 'marknative';
10
+ this.version = '1.0.1';
11
+ this.description = 'Markdown 转图片插件 - 将 Markdown 转换为 PNG/SVG';
12
+ this.priority = 10;
13
+ this.marknative = null;
14
+ this.checked = false;
15
+ }
16
+
17
+ async _ensureMarkNative() {
18
+ if (this.checked) return;
19
+ this.checked = true;
20
+
21
+ this.marknative = await import('marknative');
22
+ }
23
+
24
+ tools = {
25
+ marknative_render: {
26
+ description: '将 Markdown 渲染为图片并保存到文件(返回文件路径)',
27
+ inputSchema: z.object({
28
+ markdown: z.string().describe('Markdown 内容'),
29
+ outputPath: z.string().optional().describe('输出文件路径(如 output.png),不提供则自动生成'),
30
+ format: z.enum(['png', 'svg']).optional().describe('输出格式:png 或 svg,默认 png'),
31
+ theme: z.string().optional().describe('主题:default, github, solarized, sepia, rose, dark, nord, dracula, ocean, forest'),
32
+ singlePage: z.boolean().optional().describe('是否渲染为单张图片(默认 false)'),
33
+ codeTheme: z.string().optional().describe('代码高亮主题(如 github-dark, nord 等)'),
34
+ }),
35
+ execute: async (args) => {
36
+ try {
37
+ await this._ensureMarkNative();
38
+
39
+ if (!this.marknative) {
40
+ return {
41
+ success: false,
42
+ error: 'marknative 未安装。请运行: cd .agent/plugins/marknative && npm install'
43
+ };
44
+ }
45
+
46
+ const { renderMarkdown } = this.marknative;
47
+
48
+ const format = args.format || 'png';
49
+ const options = {
50
+ format,
51
+ singlePage: args.singlePage || false
52
+ };
53
+
54
+ if (args.theme) options.theme = args.theme;
55
+ if (args.codeTheme) options.codeHighlighting = { theme: args.codeTheme };
56
+
57
+ const pages = await renderMarkdown(args.markdown, options);
58
+
59
+ if (pages.length === 0) {
60
+ return { success: false, error: '渲染失败,无输出' };
61
+ }
62
+
63
+ // 自动生成输出路径
64
+ let basePath = args.outputPath;
65
+ if (!basePath) {
66
+ const timestamp = Date.now().toString(36);
67
+ basePath = `output/markdown-${timestamp}.${format}`;
68
+ }
69
+
70
+ const dir = path.dirname(basePath);
71
+ if (dir && dir !== '.' && !fs.existsSync(dir)) {
72
+ fs.mkdirSync(dir, { recursive: true });
73
+ }
74
+
75
+ const files = [];
76
+ for (let i = 0; i < pages.length; i++) {
77
+ const page = pages[i];
78
+ const filePath = pages.length === 1
79
+ ? basePath
80
+ : basePath.replace(/(\.[^.]+)$/, `-${String(i + 1).padStart(2, '0')}$1`);
81
+
82
+ if (page.format === 'png') {
83
+ fs.writeFileSync(filePath, page.data);
84
+ } else {
85
+ fs.writeFileSync(filePath, page.data, 'utf-8');
86
+ }
87
+ files.push(filePath);
88
+ }
89
+
90
+ return {
91
+ success: true,
92
+ files,
93
+ pageCount: pages.length,
94
+ message: pages.length === 1
95
+ ? `图片已生成: ${files[0]}`
96
+ : `已生成 ${pages.length} 页图片: ${files.join(', ')}`
97
+ };
98
+ } catch (error) {
99
+ return { success: false, error: error.message };
100
+ }
101
+ },
102
+ },
103
+
104
+ marknative_render_to_file: {
105
+ description: '将 Markdown 渲染为图片并保存到文件',
106
+ inputSchema: z.object({
107
+ markdown: z.string().describe('Markdown 内容'),
108
+ outputPath: z.string().describe('输出文件路径(如 output.png)'),
109
+ format: z.enum(['png', 'svg']).optional().describe('输出格式,默认根据文件扩展名'),
110
+ theme: z.string().optional().describe('主题'),
111
+ codeTheme: z.string().optional().describe('代码高亮主题'),
112
+ }),
113
+ execute: async (args) => {
114
+ try {
115
+ await this._ensureMarkNative();
116
+
117
+ if (!this.marknative) {
118
+ return {
119
+ success: false,
120
+ error: 'marknative 未安装。请运行: cd .agent/plugins/marknative && npm install'
121
+ };
122
+ }
123
+
124
+ const { renderMarkdown } = this.marknative;
125
+
126
+ const ext = path.extname(args.outputPath).toLowerCase();
127
+ const format = args.format || (ext === '.svg' ? 'svg' : 'png');
128
+
129
+ const options = { format };
130
+ if (args.theme) options.theme = args.theme;
131
+ if (args.codeTheme) options.codeHighlighting = { theme: args.codeTheme };
132
+
133
+ const pages = await renderMarkdown(args.markdown, options);
134
+
135
+ const dir = path.dirname(args.outputPath);
136
+ if (dir && dir !== '.' && !fs.existsSync(dir)) {
137
+ fs.mkdirSync(dir, { recursive: true });
138
+ }
139
+
140
+ const files = [];
141
+ for (let i = 0; i < pages.length; i++) {
142
+ const page = pages[i];
143
+ const filePath = pages.length === 1
144
+ ? args.outputPath
145
+ : args.outputPath.replace(/(\.[^.]+)$/, `-${String(i + 1).padStart(2, '0')}$1`);
146
+
147
+ if (page.format === 'png') {
148
+ fs.writeFileSync(filePath, page.data);
149
+ } else {
150
+ fs.writeFileSync(filePath, page.data, 'utf-8');
151
+ }
152
+ files.push(filePath);
153
+ }
154
+
155
+ return {
156
+ success: true,
157
+ files,
158
+ pageCount: pages.length,
159
+ message: `已生成 ${pages.length} 页图片: ${files.join(', ')}`
160
+ };
161
+ } catch (error) {
162
+ return { success: false, error: error.message };
163
+ }
164
+ },
165
+ },
166
+
167
+ marknative_preview: {
168
+ description: '预览 Markdown 渲染效果(返回第一页 Base64)',
169
+ inputSchema: z.object({
170
+ markdown: z.string().describe('Markdown 内容'),
171
+ theme: z.string().optional().describe('主题'),
172
+ codeTheme: z.string().optional().describe('代码高亮主题'),
173
+ }),
174
+ execute: async (args) => {
175
+ try {
176
+ await this._ensureMarkNative();
177
+
178
+ if (!this.marknative) {
179
+ return {
180
+ success: false,
181
+ error: 'marknative 未安装。请运行: cd .agent/plugins/marknative && npm install'
182
+ };
183
+ }
184
+
185
+ const { renderMarkdown } = this.marknative;
186
+
187
+ const options = {
188
+ format: 'png',
189
+ singlePage: true
190
+ };
191
+
192
+ if (args.theme) options.theme = args.theme;
193
+ if (args.codeTheme) options.codeHighlighting = { theme: args.codeTheme };
194
+
195
+ const pages = await renderMarkdown(args.markdown, options);
196
+
197
+ if (pages.length === 0) {
198
+ return { success: false, error: '预览生成失败' };
199
+ }
200
+
201
+ return {
202
+ success: true,
203
+ preview: pages[0].data.toString('base64'),
204
+ format: 'png'
205
+ };
206
+ } catch (error) {
207
+ return { success: false, error: error.message };
208
+ }
209
+ },
210
+ },
211
+
212
+ marknative_get_themes: {
213
+ description: '获取所有可用的主题列表',
214
+ inputSchema: z.object({}),
215
+ execute: async () => {
216
+ return {
217
+ success: true,
218
+ themes: [
219
+ { name: 'default', description: '默认主题(浅色)' },
220
+ { name: 'github', description: 'GitHub 风格' },
221
+ { name: 'solarized', description: 'Solarized 风格' },
222
+ { name: 'sepia', description: '复古纸张风格' },
223
+ { name: 'rose', description: '玫瑰粉风格' },
224
+ { name: 'dark', description: '深色主题' },
225
+ { name: 'nord', description: 'Nord 风格' },
226
+ { name: 'dracula', description: 'Dracula 风格' },
227
+ { name: 'ocean', description: '海洋风格' },
228
+ { name: 'forest', description: '森林风格' }
229
+ ],
230
+ codeThemes: ['github-light', 'github-dark', 'nord', 'dracula', 'solarized-light', 'solarized-dark', 'monokai']
231
+ };
232
+ },
233
+ },
234
+
235
+ marknative_status: {
236
+ description: '检查 marknative 插件状态',
237
+ inputSchema: z.object({}),
238
+ execute: async () => {
239
+ await this._ensureMarkNative();
240
+
241
+ return {
242
+ success: true,
243
+ available: this.marknative !== null,
244
+ message: this.marknative ? 'marknative 已就绪' : 'marknative 未安装,请运行: cd .agent/plugins/marknative && npm install'
245
+ };
246
+ },
247
+ },
248
+ };
249
+
250
+ install(framework) {
251
+ return this;
252
+ }
253
+
254
+ uninstall(framework) {}
255
+ };
256
+ };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "marknative-plugin",
3
+ "version": "1.0.0",
4
+ "description": "Markdown 转图片插件 - 将 Markdown 转换为 PNG/SVG",
5
+ "main": "index.js",
6
+ "keywords": ["markdown", "png", "svg", "image", "render", "foliko", "plugin"],
7
+ "author": "",
8
+ "license": "MIT",
9
+ "dependencies": {
10
+ "marknative": "^0.3.1"
11
+ }
12
+ }
@@ -0,0 +1,387 @@
1
+ /**
2
+ * System Info Plugin - 获取系统信息插件
3
+ * 提供 CPU、内存、操作系统、磁盘、网络等系统信息查询功能
4
+ */
5
+
6
+ const { Plugin } = require('../../../src/core/plugin-base')
7
+ const { z } = require('zod')
8
+ const os = require('os')
9
+ const { execSync } = require('child_process')
10
+
11
+ class SystemInfoPlugin extends Plugin {
12
+ constructor(config = {}) {
13
+ super()
14
+ this.name = 'system-info'
15
+ this.version = '1.0.0'
16
+ this.description = '获取系统信息插件 - 提供 CPU、内存、操作系统、磁盘、网络等系统信息查询功能'
17
+ this.priority = 10
18
+ }
19
+
20
+ install(framework) {
21
+ super.install(framework)
22
+ this._registerTools()
23
+ return this
24
+ }
25
+
26
+ _registerTools() {
27
+ const framework = this._framework
28
+
29
+ // 获取简略系统信息
30
+ framework.registerTool({
31
+ name: 'get_basic_info',
32
+ description: '获取简略系统信息,包括主机名、操作系统、平台、架构、运行时间等基本信息',
33
+ inputSchema: z.object({}),
34
+ execute: async (args, framework) => {
35
+ const uptime = os.uptime()
36
+ const uptimeStr = this.formatUptime(uptime)
37
+
38
+ return {
39
+ success: true,
40
+ data: {
41
+ hostname: os.hostname(),
42
+ platform: os.platform(),
43
+ osType: os.type(),
44
+ osRelease: os.release(),
45
+ arch: os.arch(),
46
+ nodeVersion: process.version,
47
+ processUptime: uptimeStr,
48
+ totalMemory: this.formatBytes(os.totalmem()),
49
+ cpuCount: os.cpus().length,
50
+ cpuModel: os.cpus()[0]?.model || 'Unknown'
51
+ }
52
+ }
53
+ }
54
+ })
55
+
56
+ // 获取 CPU 详细信息
57
+ framework.registerTool({
58
+ name: 'get_cpu_info',
59
+ description: '获取 CPU 详细信息,包括每个核心的频率、使用率等',
60
+ inputSchema: z.object({}),
61
+ execute: async (args, framework) => {
62
+ const cpus = os.cpus()
63
+ const cpuInfo = cpus.map((cpu, index) => ({
64
+ core: index,
65
+ model: cpu.model,
66
+ speed: `${cpu.speed} MHz`,
67
+ times: {
68
+ user: this.formatMs(cpu.times.user),
69
+ nice: this.formatMs(cpu.times.nice),
70
+ sys: this.formatMs(cpu.times.sys),
71
+ idle: this.formatMs(cpu.times.idle),
72
+ irq: this.formatMs(cpu.times.irq)
73
+ }
74
+ }))
75
+
76
+ // 计算平均 CPU 使用率
77
+ const avgUsage = this.calculateCpuUsage(cpus)
78
+
79
+ return {
80
+ success: true,
81
+ data: {
82
+ cpuCount: cpus.length,
83
+ totalCores: cpus.length,
84
+ averageUsage: avgUsage,
85
+ cores: cpuInfo
86
+ }
87
+ }
88
+ }
89
+ })
90
+
91
+ // 获取内存信息
92
+ framework.registerTool({
93
+ name: 'get_memory_info',
94
+ description: '获取内存使用信息,包括总内存、已用内存、可用内存、使用率等',
95
+ inputSchema: z.object({}),
96
+ execute: async (args, framework) => {
97
+ const totalMem = os.totalmem()
98
+ const freeMem = os.freemem()
99
+ const usedMem = totalMem - freeMem
100
+ const usagePercent = ((usedMem / totalMem) * 100).toFixed(2)
101
+
102
+ return {
103
+ success: true,
104
+ data: {
105
+ total: this.formatBytes(totalMem),
106
+ free: this.formatBytes(freeMem),
107
+ used: this.formatBytes(usedMem),
108
+ usagePercent: `${usagePercent}%`,
109
+ details: {
110
+ totalBytes: totalMem,
111
+ freeBytes: freeMem,
112
+ usedBytes: usedMem
113
+ }
114
+ }
115
+ }
116
+ }
117
+ })
118
+
119
+ // 获取网络接口信息
120
+ framework.registerTool({
121
+ name: 'get_network_info',
122
+ description: '获取网络接口信息,包括 IP 地址、MAC 地址、网络类型等',
123
+ inputSchema: z.object({}),
124
+ execute: async (args, framework) => {
125
+ const interfaces = os.networkInterfaces()
126
+ const networkInfo = {}
127
+
128
+ for (const [name, addrs] of Object.entries(interfaces)) {
129
+ const addrList = addrs.map(addr => ({
130
+ address: addr.address,
131
+ family: addr.family,
132
+ mac: addr.mac,
133
+ internal: addr.internal,
134
+ netmask: addr.netmask
135
+ }))
136
+ networkInfo[name] = addrList
137
+ }
138
+
139
+ return {
140
+ success: true,
141
+ data: {
142
+ interfaces: networkInfo,
143
+ hostname: os.hostname()
144
+ }
145
+ }
146
+ }
147
+ })
148
+
149
+ // 获取磁盘信息
150
+ framework.registerTool({
151
+ name: 'get_disk_info',
152
+ description: '获取磁盘使用信息,包括各磁盘的总容量、已用空间、可用空间、使用率',
153
+ inputSchema: z.object({
154
+ platform: z.enum(['windows', 'linux', 'darwin', 'auto']).optional().describe('操作系统平台,auto 为自动检测')
155
+ }),
156
+ execute: async (args, framework) => {
157
+ const diskInfo = []
158
+ const currentPlatform = args.platform === 'auto' ? os.platform() : (args.platform || os.platform())
159
+
160
+ try {
161
+ if (currentPlatform === 'win32') {
162
+ // Windows 系统
163
+ const output = execSync('wmic logicaldisk get size,freespace,caption', { encoding: 'utf8', timeout: 5000 })
164
+ const lines = output.trim().split('\n').slice(1)
165
+
166
+ for (const line of lines) {
167
+ const parts = line.trim().split(/\s+/)
168
+ if (parts.length >= 3) {
169
+ const caption = parts[0]
170
+ const freeSpace = parseInt(parts[1]) || 0
171
+ const size = parseInt(parts[2]) || 0
172
+ const used = size - freeSpace
173
+ const usagePercent = size > 0 ? ((used / size) * 100).toFixed(2) : 0
174
+
175
+ diskInfo.push({
176
+ filesystem: caption,
177
+ total: this.formatBytes(size),
178
+ used: this.formatBytes(used),
179
+ free: this.formatBytes(freeSpace),
180
+ usagePercent: `${usagePercent}%`,
181
+ details: {
182
+ totalBytes: size,
183
+ usedBytes: used,
184
+ freeBytes: freeSpace
185
+ }
186
+ })
187
+ }
188
+ }
189
+ } else if (currentPlatform === 'linux') {
190
+ // Linux 系统
191
+ const output = execSync('df -B1 --output=source,size,used,avail -x tmpfs -x devtmpfs -x squashfs 2>/dev/null || df -B1', { encoding: 'utf8', timeout: 5000 })
192
+ const lines = output.trim().split('\n').slice(1)
193
+
194
+ for (const line of lines) {
195
+ const parts = line.trim().split(/\s+/)
196
+ if (parts.length >= 4) {
197
+ const filesystem = parts[0]
198
+ const size = parseInt(parts[1]) || 0
199
+ const used = parseInt(parts[2]) || 0
200
+ const free = parseInt(parts[3]) || 0
201
+ const usagePercent = size > 0 ? ((used / size) * 100).toFixed(2) : 0
202
+
203
+ // 过滤掉虚拟文件系统
204
+ if (!filesystem.startsWith('/dev') && !filesystem.startsWith('/sys') && !filesystem.startsWith('/proc')) {
205
+ continue
206
+ }
207
+
208
+ diskInfo.push({
209
+ filesystem,
210
+ total: this.formatBytes(size),
211
+ used: this.formatBytes(used),
212
+ free: this.formatBytes(free),
213
+ usagePercent: `${usagePercent}%`,
214
+ details: {
215
+ totalBytes: size,
216
+ usedBytes: used,
217
+ freeBytes: free
218
+ }
219
+ })
220
+ }
221
+ }
222
+ } else if (currentPlatform === 'darwin') {
223
+ // macOS 系统
224
+ const output = execSync('df -B1', { encoding: 'utf8', timeout: 5000 })
225
+ const lines = output.trim().split('\n').slice(1)
226
+
227
+ for (const line of lines) {
228
+ const parts = line.trim().split(/\s+/)
229
+ if (parts.length >= 6) {
230
+ const filesystem = parts[0]
231
+ const size = parseInt(parts[1]) || 0
232
+ const used = parseInt(parts[2]) || 0
233
+ const free = parseInt(parts[3]) || 0
234
+ const usagePercent = size > 0 ? ((used / size) * 100).toFixed(2) : 0
235
+
236
+ diskInfo.push({
237
+ filesystem,
238
+ total: this.formatBytes(size),
239
+ used: this.formatBytes(used),
240
+ free: this.formatBytes(free),
241
+ usagePercent: `${usagePercent}%`,
242
+ details: {
243
+ totalBytes: size,
244
+ usedBytes: used,
245
+ freeBytes: free
246
+ }
247
+ })
248
+ }
249
+ }
250
+ }
251
+ } catch (error) {
252
+ return {
253
+ success: false,
254
+ error: `获取磁盘信息失败: ${error.message}`,
255
+ data: []
256
+ }
257
+ }
258
+
259
+ return {
260
+ success: true,
261
+ platform: currentPlatform,
262
+ data: diskInfo
263
+ }
264
+ }
265
+ })
266
+
267
+ // 获取完整系统信息(综合)
268
+ framework.registerTool({
269
+ name: 'get_full_system_info',
270
+ description: '获取完整的系统信息,包括 CPU、内存、磁盘、网络等所有信息',
271
+ inputSchema: z.object({}),
272
+ execute: async (args, framework) => {
273
+ const uptime = os.uptime()
274
+
275
+ // CPU 信息
276
+ const cpus = os.cpus()
277
+ const avgUsage = this.calculateCpuUsage(cpus)
278
+
279
+ // 内存信息
280
+ const totalMem = os.totalmem()
281
+ const freeMem = os.freemem()
282
+ const usedMem = totalMem - freeMem
283
+ const memUsagePercent = ((usedMem / totalMem) * 100).toFixed(2)
284
+
285
+ // 网络信息
286
+ const networkInterfaces = os.networkInterfaces()
287
+
288
+ // 磁盘信息
289
+ let diskInfo = []
290
+ try {
291
+ const diskResult = await framework.callTool('get_disk_info', {})
292
+ diskInfo = diskResult.data || []
293
+ } catch (e) {
294
+ diskInfo = []
295
+ }
296
+
297
+ return {
298
+ success: true,
299
+ data: {
300
+ system: {
301
+ hostname: os.hostname(),
302
+ platform: os.platform(),
303
+ osType: os.type(),
304
+ osRelease: os.release(),
305
+ arch: os.arch(),
306
+ nodeVersion: process.version,
307
+ uptime: this.formatUptime(uptime)
308
+ },
309
+ cpu: {
310
+ count: cpus.length,
311
+ model: cpus[0]?.model || 'Unknown',
312
+ averageUsage: avgUsage,
313
+ speed: `${cpus[0]?.speed || 'N/A'} MHz`
314
+ },
315
+ memory: {
316
+ total: this.formatBytes(totalMem),
317
+ free: this.formatBytes(freeMem),
318
+ used: this.formatBytes(usedMem),
319
+ usagePercent: `${memUsagePercent}%`
320
+ },
321
+ disk: diskInfo,
322
+ network: {
323
+ interfacesCount: Object.keys(networkInterfaces).length,
324
+ interfaces: Object.keys(networkInterfaces)
325
+ }
326
+ }
327
+ }
328
+ }
329
+ })
330
+ }
331
+
332
+ // 格式化字节数
333
+ formatBytes(bytes) {
334
+ if (bytes === 0) return '0 Bytes'
335
+ const k = 1024
336
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
337
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
338
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
339
+ }
340
+
341
+ // 格式化毫秒
342
+ formatMs(ms) {
343
+ return `${(ms / 1000).toFixed(2)}s`
344
+ }
345
+
346
+ // 格式化运行时间
347
+ formatUptime(seconds) {
348
+ const days = Math.floor(seconds / 86400)
349
+ const hours = Math.floor((seconds % 86400) / 3600)
350
+ const minutes = Math.floor((seconds % 3600) / 60)
351
+ const secs = Math.floor(seconds % 60)
352
+
353
+ const parts = []
354
+ if (days > 0) parts.push(`${days} 天`)
355
+ if (hours > 0) parts.push(`${hours} 小时`)
356
+ if (minutes > 0) parts.push(`${minutes} 分钟`)
357
+ if (secs > 0 || parts.length === 0) parts.push(`${secs} 秒`)
358
+
359
+ return parts.join(' ')
360
+ }
361
+
362
+ // 计算 CPU 使用率
363
+ calculateCpuUsage(cpus) {
364
+ let totalIdle = 0
365
+ let totalTick = 0
366
+
367
+ cpus.forEach(cpu => {
368
+ for (const type in cpu.times) {
369
+ totalTick += cpu.times[type]
370
+ }
371
+ totalIdle += cpu.times.idle
372
+ })
373
+
374
+ const idle = totalIdle / cpus.length
375
+ const total = totalTick / cpus.length
376
+ const usage = ((total - idle) / total * 100).toFixed(2)
377
+
378
+ return `${usage}%`
379
+ }
380
+
381
+ uninstall(framework) {
382
+ // 清理资源
383
+ this._framework = null
384
+ }
385
+ }
386
+
387
+ module.exports =SystemInfoPlugin
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "system-info",
3
+ "main": "index.js"
4
+ }
@@ -0,0 +1,40 @@
1
+ // 测试系统信息插件
2
+ const os = require('os');
3
+
4
+ // 模拟插件安装
5
+ const plugin = require('./index.js');
6
+ const mockPlugin = plugin({});
7
+ const registeredTools = [];
8
+
9
+ const mockFramework = {
10
+ registerTool: (tool) => {
11
+ registeredTools.push(tool.name);
12
+ console.log('✓ Registered tool:', tool.name);
13
+ },
14
+ callTool: async (name, args) => {
15
+ if (name === 'get_disk_info') {
16
+ // 模拟磁盘信息获取
17
+ return { success: true, data: [] };
18
+ }
19
+ }
20
+ };
21
+
22
+ console.log('\n=== Testing System Info Plugin ===\n');
23
+ mockPlugin.install(mockFramework);
24
+
25
+ console.log('\n✓ Plugin name:', mockPlugin.name);
26
+ console.log('✓ Plugin version:', mockPlugin.version);
27
+ console.log('✓ Plugin description:', mockPlugin.description);
28
+
29
+ console.log('\n✓ Total tools registered:', registeredTools.length);
30
+ console.log('✓ Tools:', registeredTools.join(', '));
31
+
32
+ console.log('\n=== System Information Preview ===\n');
33
+ console.log('Hostname:', os.hostname());
34
+ console.log('Platform:', os.platform());
35
+ console.log('OS Type:', os.type());
36
+ console.log('CPU Count:', os.cpus().length);
37
+ console.log('Total Memory:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
38
+ console.log('Node Version:', process.version);
39
+
40
+ console.log('\n✓ All tests passed!');