@webship.co/workspace 1.0.0-beta1 → 1.0.0-beta2

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 (630) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +28 -364
  3. package/core/bin/workspace.js +135 -0
  4. package/core/config/settings.labels.yml +514 -0
  5. package/core/config/settings.template.yml +292 -0
  6. package/core/config/style.yml +53 -0
  7. package/core/config/theme.default.yml +161 -0
  8. package/core/config/theme.dispatch.yml +166 -0
  9. package/core/config/theme.drupal.yml +175 -0
  10. package/core/config/theme.ember.yml +160 -0
  11. package/core/config/theme.github.yml +172 -0
  12. package/core/config/theme.gitlab.yml +168 -0
  13. package/core/config/theme.press.yml +161 -0
  14. package/core/config/theme.slate.yml +160 -0
  15. package/core/config/theme.upsun.yml +174 -0
  16. package/core/docs/architecture.svg +65 -0
  17. package/core/docs/getting-started.md +51 -0
  18. package/core/docs/plugins.md +27 -0
  19. package/core/docs/releases.md +23 -0
  20. package/core/docs/testing.md +15 -0
  21. package/core/docs/workspaces.md +25 -0
  22. package/core/scripts/args/arg-ai-items.sh +65 -0
  23. package/core/scripts/args/arg-autostart.sh +31 -0
  24. package/core/scripts/args/arg-avatar-agent.sh +28 -0
  25. package/core/scripts/args/arg-avatar-catalog.sh +12 -0
  26. package/core/scripts/args/arg-avatar-character.sh +68 -0
  27. package/core/scripts/args/arg-avatar-voice.sh +24 -0
  28. package/core/scripts/args/arg-avatar.sh +22 -0
  29. package/core/scripts/args/arg-checks.sh +36 -0
  30. package/core/scripts/args/arg-clone.sh +42 -0
  31. package/core/scripts/args/arg-commands.sh +51 -0
  32. package/core/scripts/args/arg-cucumber.sh +34 -0
  33. package/core/scripts/args/arg-distribution.sh +30 -0
  34. package/core/scripts/args/arg-drupal.sh +39 -0
  35. package/core/scripts/args/arg-drupal_cms.sh +18 -0
  36. package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
  37. package/core/scripts/args/arg-graphify.sh +36 -0
  38. package/core/scripts/args/arg-graphs.sh +12 -0
  39. package/core/scripts/args/arg-hostnames.sh +23 -0
  40. package/core/scripts/args/arg-milvus-backup.sh +8 -0
  41. package/core/scripts/args/arg-milvus-mcp.sh +12 -0
  42. package/core/scripts/args/arg-milvus.sh +32 -0
  43. package/core/scripts/args/arg-obsidian.sh +17 -0
  44. package/core/scripts/args/arg-odoo.sh +18 -0
  45. package/core/scripts/args/arg-penpot-backup.sh +8 -0
  46. package/core/scripts/args/arg-penpot-mcp.sh +15 -0
  47. package/core/scripts/args/arg-penpot-token.sh +17 -0
  48. package/core/scripts/args/arg-penpot.sh +44 -0
  49. package/core/scripts/args/arg-rag.sh +21 -0
  50. package/core/scripts/args/arg-ragify.sh +35 -0
  51. package/core/scripts/args/arg-reclaim.sh +18 -0
  52. package/core/scripts/args/arg-release.sh +21 -0
  53. package/core/scripts/args/arg-screen.sh +28 -0
  54. package/core/scripts/args/arg-site-template.sh +13 -0
  55. package/core/scripts/args/arg-spdd.sh +16 -0
  56. package/core/scripts/args/arg-specs.sh +22 -0
  57. package/core/scripts/args/arg-storage.sh +36 -0
  58. package/core/scripts/args/arg-testing.sh +36 -0
  59. package/core/scripts/args/arg-webship.sh +34 -0
  60. package/core/scripts/args/arg-webships-project.sh +22 -0
  61. package/core/scripts/args/arg-webships-site-template.sh +17 -0
  62. package/core/scripts/args/arg-webships.sh +34 -0
  63. package/core/scripts/args/arg-website-site-template.sh +17 -0
  64. package/core/scripts/args/arg-website.sh +25 -0
  65. package/core/scripts/args/arg-wordpress.sh +18 -0
  66. package/core/scripts/autostart-run.sh +162 -0
  67. package/core/scripts/autostart-stop.sh +30 -0
  68. package/core/scripts/autostart-watch.sh +43 -0
  69. package/core/scripts/autostart.sh +26 -0
  70. package/core/scripts/bootstrap-functions.sh +117 -0
  71. package/core/scripts/bootstrap-libraries.sh +10 -0
  72. package/core/scripts/bootstrap.sh +71 -0
  73. package/core/scripts/ddev-shim/ddev +122 -0
  74. package/core/scripts/docker-gid.sh +119 -0
  75. package/core/scripts/functions/arg-site-template.sh +28 -0
  76. package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
  77. package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
  78. package/core/scripts/functions/fun-ai-items.sh +621 -0
  79. package/core/scripts/functions/fun-ai-providers.sh +72 -0
  80. package/core/scripts/functions/fun-autostart.sh +429 -0
  81. package/core/scripts/functions/fun-avatar.sh +441 -0
  82. package/core/scripts/functions/fun-backup-item.sh +50 -0
  83. package/core/scripts/functions/fun-build-distribution.sh +226 -0
  84. package/core/scripts/functions/fun-build-site-template.sh +104 -0
  85. package/core/scripts/functions/fun-build-webships.sh +161 -0
  86. package/core/scripts/functions/fun-build-website.sh +199 -0
  87. package/core/scripts/functions/fun-checks.sh +243 -0
  88. package/core/scripts/functions/fun-clone-project.sh +289 -0
  89. package/core/scripts/functions/fun-commands.sh +521 -0
  90. package/core/scripts/functions/fun-database.sh +63 -0
  91. package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
  92. package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
  93. package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
  94. package/core/scripts/functions/fun-default-settings.sh +28 -0
  95. package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
  96. package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
  97. package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
  98. package/core/scripts/functions/fun-distribution-webship.sh +28 -0
  99. package/core/scripts/functions/fun-distribution-webships.sh +33 -0
  100. package/core/scripts/functions/fun-distribution-website.sh +19 -0
  101. package/core/scripts/functions/fun-drush.sh +48 -0
  102. package/core/scripts/functions/fun-elevenlabs.sh +179 -0
  103. package/core/scripts/functions/fun-files.sh +19 -0
  104. package/core/scripts/functions/fun-git-worktrees.sh +142 -0
  105. package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
  106. package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
  107. package/core/scripts/functions/fun-milvus.sh +584 -0
  108. package/core/scripts/functions/fun-odoo.sh +121 -0
  109. package/core/scripts/functions/fun-penpot.sh +724 -0
  110. package/core/scripts/functions/fun-project-name.sh +20 -0
  111. package/core/scripts/functions/fun-rag.sh +253 -0
  112. package/core/scripts/functions/fun-ragify.sh +165 -0
  113. package/core/scripts/functions/fun-reclaim.sh +297 -0
  114. package/core/scripts/functions/fun-release.sh +159 -0
  115. package/core/scripts/functions/fun-remove-project.sh +319 -0
  116. package/core/scripts/functions/fun-screen.sh +96 -0
  117. package/core/scripts/functions/fun-spdd.sh +676 -0
  118. package/core/scripts/functions/fun-specs-share.sh +237 -0
  119. package/core/scripts/functions/fun-storage.sh +347 -0
  120. package/core/scripts/functions/fun-testing.sh +329 -0
  121. package/core/scripts/functions/fun-users.sh +87 -0
  122. package/core/scripts/functions/fun-version.sh +72 -0
  123. package/core/scripts/functions/fun-video-edit.sh +48 -0
  124. package/core/scripts/functions/fun-voice.sh +45 -0
  125. package/core/scripts/functions/fun-wordpress.sh +59 -0
  126. package/core/scripts/host-mounts.sh +161 -0
  127. package/core/scripts/install.sh +602 -0
  128. package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
  129. package/core/scripts/libs/argparse.sh +93 -0
  130. package/core/scripts/libs/bash-yaml.sh +52 -0
  131. package/core/scripts/libs/checks_plan.js +312 -0
  132. package/core/scripts/libs/elevenlabs.py +627 -0
  133. package/core/scripts/libs/progress-bar.sh +19 -0
  134. package/core/scripts/libs/rag_index.py +446 -0
  135. package/core/scripts/libs/video_edit.py +636 -0
  136. package/core/scripts/libs/voice.py +430 -0
  137. package/core/scripts/plugins/jira/host-install.sh +119 -0
  138. package/core/scripts/plugins/jira/host-mounts.txt +2 -0
  139. package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
  140. package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
  141. package/core/scripts/plugins/upsun/host-install.sh +99 -0
  142. package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
  143. package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
  144. package/core/scripts/plugins.sh +152 -0
  145. package/core/scripts/release.sh +69 -0
  146. package/core/scripts/storage.sh +39 -0
  147. package/core/scripts/sync-tooling.sh +246 -0
  148. package/core/scripts/tests/cmd-smoke-test.sh +109 -0
  149. package/core/scripts/trust-https.sh +257 -0
  150. package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
  151. package/core/workspace-app/.ddev/config.yaml +400 -0
  152. package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
  153. package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
  154. package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
  155. package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
  156. package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
  157. package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
  158. package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
  159. package/core/workspace-app/README.md +380 -0
  160. package/{actions → core/workspace-app/actions}/ddev.js +0 -1
  161. package/{actions → core/workspace-app/actions}/rag.js +0 -1
  162. package/{actions → core/workspace-app/actions}/testing.js +0 -1
  163. package/{assistant.js → core/workspace-app/assistant.js} +1 -10
  164. package/{git-info.js → core/workspace-app/git-info.js} +17 -1
  165. package/core/workspace-app/package.json +29 -0
  166. package/core/workspace-app/plugin-catalog.js +50 -0
  167. package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  168. package/{server.js → core/workspace-app/server.js} +18 -0
  169. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  170. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  171. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  172. package/core/workspace-app/tests/features/checks.feature +39 -0
  173. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  174. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  175. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  176. package/core/workspace-app/tests/features/menus.feature +29 -0
  177. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  178. package/core/workspace-app/tests/features/plugins.feature +20 -0
  179. package/core/workspace-app/tests/features/project-actions.feature +40 -0
  180. package/core/workspace-app/tests/features/screens.feature +29 -0
  181. package/core/workspace-app/tests/features/settings.feature +46 -0
  182. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  183. package/core/workspace-app/tests/features/theme.feature +40 -0
  184. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  185. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
  186. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
  187. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  188. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  189. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  190. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  191. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  192. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  193. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  194. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  195. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  196. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  197. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  198. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  199. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  200. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  201. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  202. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  203. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  204. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  205. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  206. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  207. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  208. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  209. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  210. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  211. package/core/workspace-app/tests/unit/changelog.test.js +177 -0
  212. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  213. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  214. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  215. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  216. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  217. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  218. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  219. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  220. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  221. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  222. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  223. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  224. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  225. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  226. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  227. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  228. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  229. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  230. package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
  231. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  232. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  233. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  234. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  235. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  236. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  237. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  238. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  239. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  240. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  241. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  242. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  243. package/core/workspace-app/tests/unit/density.test.js +52 -0
  244. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  245. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  246. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  247. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  248. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  249. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  250. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  251. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  252. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  253. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  254. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  255. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  256. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  257. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  258. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  259. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  260. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  261. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  262. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  263. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  264. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  265. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  266. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  267. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  268. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  269. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  270. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  271. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  272. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  273. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  274. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  275. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  276. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  277. package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
  278. package/core/workspace-app/tests/unit/project-make.test.js +143 -0
  279. package/core/workspace-app/tests/unit/project-name.test.js +202 -0
  280. package/core/workspace-app/tests/unit/reader.test.js +62 -0
  281. package/core/workspace-app/tests/unit/release.test.js +92 -0
  282. package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
  283. package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
  284. package/core/workspace-app/tests/unit/round-11.test.js +31 -0
  285. package/core/workspace-app/tests/unit/round-12.test.js +37 -0
  286. package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
  287. package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
  288. package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
  289. package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
  290. package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
  291. package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
  292. package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
  293. package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
  294. package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
  295. package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
  296. package/core/workspace-app/tests/unit/screens.test.js +74 -0
  297. package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
  298. package/core/workspace-app/tests/unit/session-name.test.js +64 -0
  299. package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
  300. package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
  301. package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
  302. package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
  303. package/core/workspace-app/tests/unit/speech.test.js +290 -0
  304. package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
  305. package/core/workspace-app/tests/unit/status-language.test.js +47 -0
  306. package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
  307. package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
  308. package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
  309. package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
  310. package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
  311. package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
  312. package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
  313. package/core/workspace-app/tests/unit/themes.test.js +453 -0
  314. package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
  315. package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
  316. package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
  317. package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
  318. package/core/workspace-app/tests/unit/version-check.test.js +98 -0
  319. package/core/workspace-app/tests/unit/version-link.test.js +50 -0
  320. package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
  321. package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
  322. package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
  323. package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
  324. package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
  325. package/core/workspace-app/tests/unit/waiting.test.js +48 -0
  326. package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
  327. package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
  328. package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
  329. package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
  330. package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
  331. package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
  332. package/package.json +38 -36
  333. /package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +0 -0
  334. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  335. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  336. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  337. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  338. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  339. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  340. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  341. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  342. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  343. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  355. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  356. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  357. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  358. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  359. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  360. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  361. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  362. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  363. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  364. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  365. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  366. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  367. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  368. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  369. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  370. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  371. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  372. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  373. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  374. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  375. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  376. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  377. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  378. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  379. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  380. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  381. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  382. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  383. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  384. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  385. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  386. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  387. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  388. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  389. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  390. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  391. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  392. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  393. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  394. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  395. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  396. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  397. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  453. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  454. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  455. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  456. /package/{format.js → core/workspace-app/format.js} +0 -0
  457. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  458. /package/{html.js → core/workspace-app/html.js} +0 -0
  459. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  460. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  461. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  462. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  463. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  464. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  465. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  466. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  467. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  468. /package/{images.js → core/workspace-app/images.js} +0 -0
  469. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  470. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  471. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  472. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  473. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  474. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  475. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  476. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  477. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  478. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  479. /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
  480. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  481. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  482. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  483. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  484. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  485. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  486. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  487. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  488. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  489. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  490. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  491. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  492. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  493. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  494. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  495. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  496. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  532. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  533. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  534. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  535. /package/{public → core/workspace-app/public}/logo.png +0 -0
  536. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  537. /package/{public → core/workspace-app/public}/mic.js +0 -0
  538. /package/{public → core/workspace-app/public}/narration.js +0 -0
  539. /package/{public → core/workspace-app/public}/palette.js +0 -0
  540. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  541. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  542. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  543. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  544. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  545. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  546. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  547. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  548. /package/{public → core/workspace-app/public}/ui.js +0 -0
  549. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  550. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  551. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  552. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  553. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  554. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  555. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  603. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  604. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  605. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  606. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  607. /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
  608. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  609. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  610. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  611. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  612. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  613. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  614. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  615. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  616. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  617. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  618. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  619. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  620. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  621. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  622. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  623. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  624. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  625. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  626. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  627. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  628. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
  629. /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
  630. /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
@@ -0,0 +1,724 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Penpot, self-hosted inside DDEV.
4
+ #
5
+ # The whole Penpot stack runs as DDEV custom compose services on the project's
6
+ # `default` network, so they resolve each other by service name, and DDEV's own
7
+ # web container (nginx) reverse-proxies the site to `penpot-frontend:8080`. DDEV
8
+ # owns the router and the TLS certificate, which is what makes
9
+ # https://<project>.ddev.site work with no host ports and no standalone
10
+ # docker-compose stack to keep in sync.
11
+ #
12
+ # Browser ──https──▶ ddev-router ──▶ ddev-<project>-web (nginx reverse proxy)
13
+ # └─▶ penpot-frontend:8080
14
+ # penpot-frontend ─▶ penpot-backend ─▶ penpot-postgres (15)
15
+ # │ └──────────▶ penpot-valkey (redis)
16
+ # └─▶ penpot-exporter penpot-mailcatch (SMTP catcher)
17
+ #
18
+ # Reference: products/ai_penpot/docs/penpot-on-ddev.md, which this command
19
+ # automates end to end — it used to be a page of copy-paste.
20
+ #
21
+ # The project is NOT a Drupal site: it is `--project-type=php` with settings
22
+ # management disabled and no docroot, because nothing in it is served from disk.
23
+
24
+ # The last-resort flag set, used only when the workspace settings carry none.
25
+ # `enable-plugins` is what the Penpot MCP needs, and
26
+ # `enable-access-tokens` is what the AI Penpot Drupal module needs — without the
27
+ # latter a token is silently ignored (requests resolve to Anonymous, not a 401),
28
+ # which is a bad afternoon. Registration is open and email verification off, so
29
+ # registering logs you straight in on a local instance.
30
+ PENPOT_DEFAULT_FLAGS="disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-registration enable-plugins enable-access-tokens";
31
+
32
+ # parse_yaml keeps a value's own quotes, so `version: "2.16"` reaches the shell as the
33
+ # five-character string `"2.16"` — which lands in an image tag as `penpotapp/frontend:"2.16"`
34
+ # and, worse, makes `secret_key: ""` a NON-empty string, so every `${x:-fallback}` default
35
+ # silently stops working. Strip one layer of matching quotes from anything read from settings.
36
+ function penpot_setting() {
37
+ local value="$1";
38
+ case "${value}" in
39
+ \"*\") value="${value#\"}"; value="${value%\"}" ;;
40
+ \'*\') value="${value#\'}"; value="${value%\'}" ;;
41
+ esac
42
+ printf '%s' "${value}";
43
+ }
44
+
45
+ # Write .ddev/docker-compose.penpot.yaml for one project.
46
+ #
47
+ # $1 the project directory, $2 the public URI, $3 PENPOT_FLAGS, $4 the secret key,
48
+ # $5 the host port for the mail catcher
49
+ function penpot_write_compose() {
50
+ local project_dir="$1" public_uri="$2" flags="$3" secret_key="$4" mail_port="$5";
51
+
52
+ mkdir -p "${project_dir}/.ddev" ;
53
+
54
+ cat > "${project_dir}/.ddev/docker-compose.penpot.yaml" <<COMPOSEEOF
55
+ ## Penpot self-hosted, orchestrated by DDEV.
56
+ ## Written by cmd-penpot-project.sh — re-run that command rather than editing by hand.
57
+ ## The DDEV web container (nginx) reverse-proxies ${public_uri} -> penpot-frontend:8080.
58
+ ## Every service is on the DDEV project's 'default' network, so they resolve each other by name.
59
+
60
+ volumes:
61
+ penpot_postgres_v15:
62
+ penpot_assets:
63
+
64
+ services:
65
+ penpot-frontend:
66
+ container_name: ddev-\${DDEV_SITENAME}-penpot-frontend
67
+ image: "penpotapp/frontend:\${PENPOT_VERSION:-${PENPOT_VERSION}}"
68
+ restart: "no"
69
+ labels:
70
+ com.ddev.site-name: \${DDEV_SITENAME}
71
+ com.ddev.approot: \${DDEV_APPROOT}
72
+ volumes:
73
+ - penpot_assets:/opt/data/assets
74
+ depends_on:
75
+ - penpot-backend
76
+ - penpot-exporter
77
+ environment:
78
+ PENPOT_FLAGS: ${flags}
79
+ PENPOT_PUBLIC_URI: ${public_uri}
80
+ PENPOT_HTTP_SERVER_MAX_BODY_SIZE: "367001600"
81
+ PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE: "367001600"
82
+
83
+ penpot-backend:
84
+ container_name: ddev-\${DDEV_SITENAME}-penpot-backend
85
+ image: "penpotapp/backend:\${PENPOT_VERSION:-${PENPOT_VERSION}}"
86
+ restart: "no"
87
+ labels:
88
+ com.ddev.site-name: \${DDEV_SITENAME}
89
+ com.ddev.approot: \${DDEV_APPROOT}
90
+ volumes:
91
+ - penpot_assets:/opt/data/assets
92
+ depends_on:
93
+ penpot-postgres:
94
+ condition: service_healthy
95
+ penpot-valkey:
96
+ condition: service_healthy
97
+ environment:
98
+ PENPOT_FLAGS: ${flags}
99
+ PENPOT_PUBLIC_URI: ${public_uri}
100
+ PENPOT_SECRET_KEY: ${secret_key}
101
+ PENPOT_HTTP_SERVER_MAX_BODY_SIZE: "367001600"
102
+ PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE: "367001600"
103
+ PENPOT_DATABASE_URI: postgresql://penpot-postgres/penpot
104
+ PENPOT_DATABASE_USERNAME: penpot
105
+ PENPOT_DATABASE_PASSWORD: penpot
106
+ PENPOT_REDIS_URI: redis://penpot-valkey/0
107
+ PENPOT_OBJECTS_STORAGE_BACKEND: fs
108
+ PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
109
+ PENPOT_TELEMETRY_ENABLED: "false"
110
+ PENPOT_SMTP_DEFAULT_FROM: no-reply@example.com
111
+ PENPOT_SMTP_DEFAULT_REPLY_TO: no-reply@example.com
112
+ PENPOT_SMTP_HOST: penpot-mailcatch
113
+ PENPOT_SMTP_PORT: "1025"
114
+ PENPOT_SMTP_TLS: "false"
115
+ PENPOT_SMTP_SSL: "false"
116
+
117
+ penpot-exporter:
118
+ container_name: ddev-\${DDEV_SITENAME}-penpot-exporter
119
+ image: "penpotapp/exporter:\${PENPOT_VERSION:-${PENPOT_VERSION}}"
120
+ restart: "no"
121
+ labels:
122
+ com.ddev.site-name: \${DDEV_SITENAME}
123
+ com.ddev.approot: \${DDEV_APPROOT}
124
+ depends_on:
125
+ penpot-valkey:
126
+ condition: service_healthy
127
+ environment:
128
+ PENPOT_SECRET_KEY: ${secret_key}
129
+ ## The exporter talks to the frontend container directly, inside the network.
130
+ PENPOT_PUBLIC_URI: http://penpot-frontend:8080
131
+ PENPOT_REDIS_URI: redis://penpot-valkey/0
132
+
133
+ penpot-postgres:
134
+ container_name: ddev-\${DDEV_SITENAME}-penpot-postgres
135
+ image: "postgres:15"
136
+ restart: "no"
137
+ stop_signal: SIGINT
138
+ labels:
139
+ com.ddev.site-name: \${DDEV_SITENAME}
140
+ com.ddev.approot: \${DDEV_APPROOT}
141
+ healthcheck:
142
+ test: ["CMD-SHELL", "pg_isready -U penpot"]
143
+ interval: 2s
144
+ timeout: 10s
145
+ retries: 5
146
+ start_period: 2s
147
+ volumes:
148
+ - penpot_postgres_v15:/var/lib/postgresql/data
149
+ environment:
150
+ POSTGRES_INITDB_ARGS: "--data-checksums"
151
+ POSTGRES_DB: penpot
152
+ POSTGRES_USER: penpot
153
+ POSTGRES_PASSWORD: penpot
154
+
155
+ penpot-valkey:
156
+ container_name: ddev-\${DDEV_SITENAME}-penpot-valkey
157
+ image: "valkey/valkey:8.1"
158
+ restart: "no"
159
+ labels:
160
+ com.ddev.site-name: \${DDEV_SITENAME}
161
+ com.ddev.approot: \${DDEV_APPROOT}
162
+ healthcheck:
163
+ test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
164
+ interval: 1s
165
+ timeout: 3s
166
+ retries: 5
167
+ start_period: 3s
168
+
169
+ penpot-mailcatch:
170
+ container_name: ddev-\${DDEV_SITENAME}-penpot-mailcatch
171
+ image: "sj26/mailcatcher:latest"
172
+ restart: "no"
173
+ labels:
174
+ com.ddev.site-name: \${DDEV_SITENAME}
175
+ com.ddev.approot: \${DDEV_APPROOT}
176
+ environment:
177
+ - VIRTUAL_HOST=\$DDEV_HOSTNAME
178
+ - HTTP_EXPOSE=${mail_port}:1080
179
+ - HTTPS_EXPOSE=$((mail_port + 363)):1080
180
+ COMPOSEEOF
181
+ }
182
+
183
+ # Write the nginx site config that turns the DDEV web container into the reverse
184
+ # proxy. A full replacement (nginx_full), because none of the default PHP site
185
+ # config applies to a project that serves no files.
186
+ function penpot_write_nginx() {
187
+ local project_dir="$1";
188
+
189
+ mkdir -p "${project_dir}/.ddev/nginx_full" ;
190
+
191
+ cat > "${project_dir}/.ddev/nginx_full/nginx-site.conf" <<'NGINXEOF'
192
+ # DDEV web container nginx: reverse-proxy everything to the Penpot frontend container.
193
+ # Full replacement of the default site config (nginx_full).
194
+ # Written by cmd-penpot-project.sh — re-run that command rather than editing by hand.
195
+
196
+ server {
197
+ listen 80 default_server;
198
+ listen 443 ssl default_server;
199
+
200
+ root /var/www/html;
201
+
202
+ ssl_certificate /etc/ssl/certs/master.crt;
203
+ ssl_certificate_key /etc/ssl/certs/master.key;
204
+
205
+ include /etc/nginx/monitoring.conf;
206
+
207
+ # Penpot handles large asset/media uploads.
208
+ client_max_body_size 400M;
209
+
210
+ # Real IP from the DDEV router.
211
+ include /etc/nginx/common.d/*.conf;
212
+
213
+ # The Penpot MCP server runs INSIDE this project's web container network namespace
214
+ # (cmd-penpot-mcp.sh starts it with --network container:), so its three ports are on this
215
+ # container's loopback and are proxied onto the instance's own https origin. That is the only
216
+ # arrangement that works here: the servers bind 127.0.0.1 (so no container can reach them
217
+ # across the network), the editor page is https (so an http://localhost manifest is mixed
218
+ # content and Penpot refuses it: "The plugin doesn't exist or the URL is not correct"), and
219
+ # this machine's firewall drops container-to-host traffic (so a socat relay on the host is
220
+ # unreachable too). Same origin, https and wss, nothing published on the host.
221
+ #
222
+ # The upstreams are [::1], not 127.0.0.1: these servers bind IPv6 loopback ONLY, so an IPv4
223
+ # upstream is refused and nginx answers 502 while the servers are plainly running and logging.
224
+ location /mcp-plugin/ {
225
+ proxy_pass http://[::1]:4400/;
226
+ proxy_http_version 1.1;
227
+ # Vite's preview server refuses an unknown Host with 403 ("This host is not allowed"), so
228
+ # the upstream keeps its own name and the browser-facing origin stays this instance.
229
+ proxy_set_header Host localhost:4400;
230
+ proxy_set_header X-Forwarded-Proto $scheme;
231
+ proxy_set_header Upgrade $http_upgrade;
232
+ proxy_set_header Connection "upgrade";
233
+ }
234
+
235
+ location /mcp-bridge {
236
+ proxy_pass http://[::1]:4402;
237
+ proxy_http_version 1.1;
238
+ proxy_set_header Host $host;
239
+ proxy_set_header Upgrade $http_upgrade;
240
+ proxy_set_header Connection "upgrade";
241
+ proxy_read_timeout 3600s;
242
+ }
243
+
244
+ location /mcp {
245
+ proxy_pass http://[::1]:4401;
246
+ proxy_http_version 1.1;
247
+ proxy_set_header Host $host;
248
+ proxy_set_header Connection "";
249
+ proxy_buffering off;
250
+ proxy_read_timeout 3600s;
251
+ }
252
+
253
+ location / {
254
+ proxy_pass http://penpot-frontend:8080;
255
+ proxy_http_version 1.1;
256
+
257
+ proxy_set_header Host $host;
258
+ proxy_set_header X-Real-IP $remote_addr;
259
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
260
+ proxy_set_header X-Forwarded-Proto $scheme;
261
+ proxy_set_header X-Forwarded-Host $host;
262
+
263
+ # WebSocket support (Penpot's live notifications).
264
+ proxy_set_header Upgrade $http_upgrade;
265
+ proxy_set_header Connection "upgrade";
266
+
267
+ proxy_connect_timeout 75s;
268
+ proxy_read_timeout 3600s;
269
+ proxy_send_timeout 3600s;
270
+ proxy_buffering off;
271
+ }
272
+
273
+ include /etc/nginx/server.d/*.conf;
274
+ }
275
+ NGINXEOF
276
+ }
277
+
278
+ # Register the first account over Penpot's RPC API, so the instance is usable without a
279
+ # browser detour. Two calls: prepare-register-profile returns a signed token (it takes the
280
+ # full name too — leaving it out fails validation there, not at the second step), then
281
+ # register-profile creates the profile and its default project.
282
+ #
283
+ # An existing email is not a failure: `email-already-exists` is what a rebuild that kept its
284
+ # volumes reports, and the account it names is the one you already use.
285
+ #
286
+ # $1 base url, $2 fullname, $3 email, $4 password
287
+ function penpot_register_first_account() {
288
+ local base_url="$1" fullname="$2" email="$3" password="$4";
289
+ local prepared token registered;
290
+
291
+ # The backend answers before it is ready to register, so wait for the API itself.
292
+ local waited=0;
293
+ until curl -sk -o /dev/null --max-time 5 "${base_url}/api/rpc/command/get-profile" -X POST -H 'Accept: application/json' -d '{}' ; do
294
+ sleep 3 ; waited=$((waited + 3));
295
+ if [ "${waited}" -ge 120 ] ; then
296
+ echo "The Penpot API did not answer within 2 minutes — register in the browser instead.";
297
+ return 1 ;
298
+ fi
299
+ done
300
+
301
+ prepared=$(curl -sk -X POST "${base_url}/api/rpc/command/prepare-register-profile" \
302
+ -H 'Accept: application/json' -H 'Content-Type: application/json' -H "Origin: ${base_url}" \
303
+ --data-binary @- <<JSONEOF
304
+ {"email": "${email}", "password": "${password}", "fullname": "${fullname}"}
305
+ JSONEOF
306
+ );
307
+
308
+ if printf '%s' "${prepared}" | grep -q 'email-already-exists' ; then
309
+ echo "${email} is already registered on this instance — keeping it.";
310
+ return 0 ;
311
+ fi
312
+
313
+ token=$(printf '%s' "${prepared}" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p');
314
+ if [ -z "${token}" ] ; then
315
+ echo "Could not prepare the registration:";
316
+ printf ' %s\n' "${prepared}";
317
+ return 1 ;
318
+ fi
319
+
320
+ registered=$(curl -sk -X POST "${base_url}/api/rpc/command/register-profile" \
321
+ -H 'Accept: application/json' -H 'Content-Type: application/json' -H "Origin: ${base_url}" \
322
+ --data-binary @- <<JSONEOF
323
+ {"token": "${token}", "fullname": "${fullname}", "acceptTermsAndPrivacy": true, "acceptNewsletterSubscription": false}
324
+ JSONEOF
325
+ );
326
+
327
+ if printf '%s' "${registered}" | grep -q '"isActive":true' ; then
328
+ echo "Registered ${fullname} <${email}> and its default project.";
329
+ return 0 ;
330
+ fi
331
+
332
+ echo "Registration did not complete:";
333
+ printf ' %s\n' "${registered}";
334
+ return 1 ;
335
+ }
336
+
337
+ # The next free host port from $1 upwards, as seen by docker. Several Penpot instances is the
338
+ # normal case — one per design system — and each mail catcher is published on the host, so a
339
+ # fixed 1080 would make the second instance fail to start with "port is already in use".
340
+ # The pair 1080/1443 keeps its shape: the HTTPS port is always the HTTP one plus 363.
341
+ function penpot_free_mail_port() {
342
+ local port="$1" taken;
343
+ taken="$(docker ps --format '{{.Ports}}' 2>/dev/null)";
344
+ while printf '%s' "${taken}" | grep -q ":${port}->" || printf '%s' "${taken}" | grep -q ":$((port + 363))->" ; do
345
+ port=$((port + 1));
346
+ if [ "${port}" -gt 1179 ] ; then
347
+ echo "1080" ; return 0 ; # give up scanning and let ddev report the conflict
348
+ fi
349
+ done
350
+ printf '%s' "${port}";
351
+ }
352
+
353
+ # Build (or rebuild) a Penpot instance in the calling workspace folder.
354
+ #
355
+ # The calling cmd-*.sh sets nothing but the workspace: everything else comes from
356
+ # the arguments in args/arg-penpot.sh.
357
+ function build_penpot() {
358
+
359
+ # The workspace settings hold the defaults; an argument that was actually given wins. The
360
+ # sentinel is what tells the two apart — argparse cannot report "not given" on its own.
361
+ if [ "${PROJECT_NAME}" == '_settings_' ] ; then PROJECT_NAME="$(penpot_setting "${penpot_project}")"; PROJECT_NAME="${PROJECT_NAME:-penpot}"; fi
362
+ if [ "${PENPOT_VERSION}" == '_settings_' ] ; then PENPOT_VERSION="$(penpot_setting "${penpot_version}")"; PENPOT_VERSION="${PENPOT_VERSION:-2.16}"; fi
363
+ if [ "${FLAGS}" == '_settings_' ] ; then FLAGS="$(penpot_setting "${penpot_flags}")"; FLAGS="${FLAGS:-${PENPOT_DEFAULT_FLAGS}}"; fi
364
+ if [ "${SECRET_KEY}" == '_settings_' ] ; then SECRET_KEY="$(penpot_setting "${penpot_secret_key}")"; fi
365
+ # The account falls back to the same `account:` block the Drupal installs use, so a machine
366
+ # states its developer identity once.
367
+ if [ "${FULLNAME}" == '_settings_' ] ; then FULLNAME="$(penpot_setting "${penpot_account_fullname}")"; FULLNAME="${FULLNAME:-$(penpot_setting "${account_name}")}"; fi
368
+ if [ "${EMAIL}" == '_settings_' ] ; then EMAIL="$(penpot_setting "${penpot_account_email}")"; EMAIL="${EMAIL:-$(penpot_setting "${account_mail}")}"; fi
369
+ if [ "${PASSWORD}" == '_settings_' ] ; then PASSWORD="$(penpot_setting "${penpot_account_password}")"; PASSWORD="${PASSWORD:-$(penpot_setting "${account_pass}")}"; fi
370
+
371
+ local base_url="https://${PROJECT_NAME}.ddev.site";
372
+ local hub_url="https://${PROJECT_NAME}.${doc_name}.workspace.ddev.site";
373
+ local project_dir="${WORKSPACE_ROOT}/${doc_name}/${PROJECT_NAME}";
374
+ local flags secret_key mail_port public_uri other_url;
375
+
376
+ flags="${FLAGS}";
377
+
378
+ # A per-project key, not a literal shared by every instance the tooling builds:
379
+ # the session cookies of one instance must not be forgeable from another's
380
+ # committed config. Derived, so a rebuild of the same project keeps its
381
+ # sessions valid.
382
+ secret_key="${SECRET_KEY}";
383
+ if [ -z "${secret_key}" ] ; then
384
+ secret_key="$(printf 'penpot-%s-%s' "${PROJECT_NAME}" "$(hostname)" | sha256sum | cut -c1-48)";
385
+ fi
386
+
387
+ # Penpot's frontend fetches its API from PENPOT_PUBLIC_URI, so the instance works on exactly
388
+ # ONE origin: opened on any other host the API calls are cross-origin and CORS blocks every
389
+ # one of them, which shows up as a blank page rather than an error. The hub hostname is the
390
+ # default because that is the address the workspace hands out — one Penpot per design system,
391
+ # penpot-website_starter.designs… beside penpot-haven.designs… — and the canonical
392
+ # <project>.ddev.site stays useful for the RPC API, where there is no browser to enforce CORS.
393
+ public_uri="${PUBLIC_URI}";
394
+ if [ "${public_uri}" == '_settings_' ] ; then
395
+ public_uri="$(penpot_setting "${penpot_public_uri}")";
396
+ public_uri="${public_uri:-hub}";
397
+ fi
398
+ case "${public_uri}" in
399
+ hub) public_uri="${hub_url}"; other_url="${base_url}" ;;
400
+ canonical) public_uri="${base_url}"; other_url="${hub_url}" ;;
401
+ *) other_url="" ;;
402
+ esac
403
+
404
+ mail_port="${MAIL_PORT}";
405
+ if [ "${mail_port}" == '_settings_' ] ; then
406
+ mail_port="$(penpot_setting "${penpot_mail_port}")";
407
+ mail_port="${mail_port:-1080}";
408
+ fi
409
+ mail_port="$(penpot_free_mail_port "${mail_port}")";
410
+
411
+ cd ${WORKSPACE_ROOT}/${doc_name} || exit 1 ;
412
+
413
+ if [ -d "${PROJECT_NAME}" ] ; then
414
+ if [ "${FORCE}" != 'yes' ] ; then
415
+ echo "";
416
+ echo "${PROJECT_NAME} already exists in ${WORKSPACE_ROOT}/${doc_name}.";
417
+ echo " Start it with: cd ${project_dir} && ddev start";
418
+ echo " Rebuild it with: bash cmd-penpot-project.sh ${PROJECT_NAME} --force";
419
+ echo " (--force deletes the Penpot volumes too, so the designs in it go with them.)";
420
+ exit 1 ;
421
+ fi
422
+ echo "Removing the existing ${PROJECT_NAME} instance, its database and its assets.";
423
+ # -O removes the project's volumes, which for this project means the Penpot
424
+ # Postgres data and the uploaded assets: the designs are IN there.
425
+ (cd "${project_dir}" && ddev delete -y -O) ;
426
+ rm -rf "${project_dir}" ;
427
+ fi
428
+
429
+ mkdir -p "${project_dir}" ;
430
+ cd "${project_dir}" || exit 1 ;
431
+
432
+ # No docroot and no settings management: this project serves no files of its
433
+ # own, it only proxies.
434
+ ddev config --project-name=${PROJECT_NAME} --project-type=php --docroot="" --disable-settings-management ;
435
+
436
+ penpot_write_compose "${project_dir}" "${public_uri}" "${flags}" "${secret_key}" "${mail_port}" ;
437
+ penpot_write_nginx "${project_dir}" ;
438
+
439
+ echo "";
440
+ echo "Starting Penpot ${PENPOT_VERSION} — the first run pulls the images, which takes a few minutes.";
441
+ ddev start -y ;
442
+
443
+ if [ "${NO_REGISTER}" == 'yes' ] ; then
444
+ echo "";
445
+ echo "Skipping registration — register the first account at ${base_url}/#/auth/register.";
446
+ else
447
+ echo "";
448
+ echo "Registering the first account.";
449
+ penpot_register_first_account "${public_uri}" "${FULLNAME}" "${EMAIL}" "${PASSWORD}" ;
450
+ fi
451
+
452
+ echo "";
453
+ echo "*---------------------------------------------------------------------*";
454
+ echo " Penpot ${PENPOT_VERSION} is ready — open it here:";
455
+ echo " ${public_uri}";
456
+ if [ -n "${other_url}" ] ; then
457
+ echo "";
458
+ echo " ${other_url} reaches the same instance for the RPC API, but NOT in a browser:";
459
+ echo " Penpot serves one origin, and from the other one CORS blocks every API call.";
460
+ fi
461
+ echo "";
462
+ if [ "${NO_REGISTER}" == 'yes' ] ; then
463
+ echo " Register the first account (it logs you straight in, no email step):";
464
+ echo " ${public_uri}/#/auth/register";
465
+ else
466
+ echo " Log in at ${public_uri}/#/auth/login";
467
+ echo " ${EMAIL}";
468
+ fi
469
+ echo "";
470
+ echo " Mail sent by this instance is caught, not delivered:";
471
+ echo " https://${PROJECT_NAME}.ddev.site:$((mail_port + 363))";
472
+ echo "";
473
+ echo " Flags: ${flags}";
474
+ echo " Design agents: enable-plugins is on, so Ctrl+Alt+P loads the Penpot MCP plugin.";
475
+ echo " Drupal AI Penpot: enable-access-tokens is on, so an access token authenticates.";
476
+ echo " The full integration walkthrough is in products/ai_penpot/docs/penpot-on-ddev.md.";
477
+ echo "*---------------------------------------------------------------------*";
478
+
479
+ if [ "${LAUNCH}" == 'yes' ] ; then
480
+ ddev launch ;
481
+ fi
482
+ }
483
+
484
+ # ---------------------------------------------------------------------------
485
+ # Per-instance tools. Each resolves its defaults from the workspace settings
486
+ # (`penpot:` in workspace.designs.settings.yml), so the commands take a project
487
+ # name and nothing else in the normal case.
488
+ # ---------------------------------------------------------------------------
489
+
490
+ # The project a tool acts on: the argument, else penpot.project from the settings.
491
+ function penpot_resolve_project() {
492
+ local given="$1" name;
493
+ if [ -n "${given}" ] && [ "${given}" != '_settings_' ] ; then
494
+ printf '%s' "${given}" ; return 0 ;
495
+ fi
496
+ name="$(penpot_setting "${penpot_project}")";
497
+ printf '%s' "${name:-penpot}";
498
+ }
499
+
500
+ # The one origin an instance serves, resolved the same way build_penpot resolves it — a tool
501
+ # that talked to the other host would be CORS-blocked in a browser and inconsistent here.
502
+ function penpot_resolve_url() {
503
+ local project="$1" setting;
504
+ setting="$(penpot_setting "${penpot_public_uri}")";
505
+ case "${setting:-hub}" in
506
+ hub) printf 'https://%s.%s.workspace.ddev.site' "${project}" "${doc_name}" ;;
507
+ canonical) printf 'https://%s.ddev.site' "${project}" ;;
508
+ *) printf '%s' "${setting}" ;;
509
+ esac
510
+ }
511
+
512
+ # Refuse politely rather than erroring deep inside docker.
513
+ function penpot_require_running() {
514
+ local project="$1";
515
+ if ! docker ps --format '{{.Names}}' | grep -qx "ddev-${project}-penpot-backend" ; then
516
+ echo "Penpot ${project} is not running.";
517
+ echo " Start it: cd ${WORKSPACE_ROOT}/${doc_name}/${project} && ddev start";
518
+ return 1 ;
519
+ fi
520
+ return 0 ;
521
+ }
522
+
523
+ # Back up one instance: the Postgres database AND the asset volume. Either alone is useless —
524
+ # a design is rows in the database plus the files those rows point at.
525
+ function penpot_backup() {
526
+ local project stamp out db_file assets_file;
527
+ project="$(penpot_resolve_project "${PROJECT_NAME}")";
528
+ penpot_require_running "${project}" || return 1 ;
529
+
530
+ stamp="$(date +%Y-%m-%d-%H%M%S)";
531
+ out="${backups}/${doc_name}/${project}";
532
+ mkdir -p "${out}" ;
533
+ db_file="${out}/${project}-penpot-db-${stamp}.sql.gz";
534
+ assets_file="${out}/${project}-penpot-assets-${stamp}.tar.gz";
535
+
536
+ echo "Dumping the Penpot database of ${project}.";
537
+ if ! docker exec "ddev-${project}-penpot-postgres" pg_dump -U penpot penpot | gzip > "${db_file}" ; then
538
+ echo "The database dump failed — removing the partial file.";
539
+ rm -f "${db_file}" ;
540
+ return 1 ;
541
+ fi
542
+
543
+ echo "Archiving the Penpot assets of ${project}.";
544
+ # The assets live in a named volume, not in the project directory, so they are read through a
545
+ # throwaway container rather than from the host.
546
+ # --user, or the archive lands owned by root: a backup the user cannot delete is a bad backup.
547
+ if ! docker run --rm --user "$(id -u):$(id -g)" \
548
+ -v "ddev-${project}_penpot_assets:/assets:ro" -v "${out}:/out" \
549
+ busybox tar czf "/out/$(basename "${assets_file}")" -C /assets . ; then
550
+ echo "The asset archive failed — removing the partial file.";
551
+ rm -f "${assets_file}" ;
552
+ return 1 ;
553
+ fi
554
+
555
+ echo "";
556
+ echo "*---------------------------------------------------------------------*";
557
+ echo " Backed up ${project}:";
558
+ echo " ${db_file}";
559
+ echo " ${assets_file}";
560
+ echo " Restore the database with:";
561
+ echo " gunzip -c <dump> | docker exec -i ddev-${project}-penpot-postgres psql -U penpot penpot";
562
+ echo "*---------------------------------------------------------------------*";
563
+ }
564
+
565
+ # Create a Penpot access token — what the Drupal AI Penpot module authenticates with.
566
+ #
567
+ # The token is printed ONCE and nowhere else: it is a credential, so it is not written to a
568
+ # file, not echoed into a command line, and not left in the backups folder.
569
+ function penpot_token() {
570
+ local project url jar name fullname email password prepared token;
571
+ project="$(penpot_resolve_project "${PROJECT_NAME}")";
572
+ penpot_require_running "${project}" || return 1 ;
573
+ url="$(penpot_resolve_url "${project}")";
574
+
575
+ email="${EMAIL}";
576
+ if [ "${email}" == '_settings_' ] ; then
577
+ email="$(penpot_setting "${penpot_account_email}")";
578
+ email="${email:-$(penpot_setting "${account_mail}")}";
579
+ fi
580
+ password="${PASSWORD}";
581
+ if [ "${password}" == '_settings_' ] ; then
582
+ password="$(penpot_setting "${penpot_account_password}")";
583
+ password="${password:-$(penpot_setting "${account_pass}")}";
584
+ fi
585
+ name="${TOKEN_NAME}";
586
+
587
+ jar="$(mktemp)";
588
+ if ! curl -sk -c "${jar}" -X POST "${url}/api/rpc/command/login-with-password" \
589
+ -H 'Accept: application/json' -H 'Content-Type: application/json' -H "Origin: ${url}" \
590
+ --data-binary @- >/dev/null <<JSONEOF
591
+ {"email": "${email}", "password": "${password}"}
592
+ JSONEOF
593
+ then
594
+ rm -f "${jar}" ;
595
+ echo "Could not log in to ${url} as ${email}.";
596
+ return 1 ;
597
+ fi
598
+
599
+ prepared=$(curl -sk -b "${jar}" -X POST "${url}/api/rpc/command/create-access-token" \
600
+ -H 'Accept: application/json' -H 'Content-Type: application/json' -H "Origin: ${url}" \
601
+ --data-binary @- <<JSONEOF
602
+ {"name": "${name}"}
603
+ JSONEOF
604
+ );
605
+ rm -f "${jar}" ;
606
+
607
+ token=$(printf '%s' "${prepared}" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p');
608
+ if [ -z "${token}" ] ; then
609
+ echo "Could not create the access token:";
610
+ printf ' %s\n' "${prepared}";
611
+ return 1 ;
612
+ fi
613
+
614
+ echo "";
615
+ echo "*---------------------------------------------------------------------*";
616
+ echo " Access token \"${name}\" for ${email} on ${url}";
617
+ echo "";
618
+ echo " ${token}";
619
+ echo "";
620
+ echo " Shown once and stored nowhere. Paste it into the Drupal site's penpot Key at";
621
+ echo " /admin/config/system/keys, with the instance URL ${url}.";
622
+ echo " It needs enable-access-tokens in PENPOT_FLAGS, which this workspace sets by default —";
623
+ echo " without that flag a token is ignored silently rather than refused.";
624
+ echo "*---------------------------------------------------------------------*";
625
+ }
626
+
627
+ # The Penpot MCP server: a node:24 container on the host network, plus the one-line Claude Code
628
+ # registration. Two processes are needed for the MCP to reach a design and only one of them is
629
+ # here: the other is a browser tab holding the plugin open, which no script can do for you.
630
+ function penpot_mcp() {
631
+ local project url package image port plugin_port bridge_port work container public_host;
632
+ project="$(penpot_resolve_project "${PROJECT_NAME}")";
633
+ url="$(penpot_resolve_url "${project}")";
634
+ package="$(penpot_setting "${penpot_mcp_package}")"; package="${package:-@penpot/mcp@stable}";
635
+ image="$(penpot_setting "${penpot_mcp_image}")"; image="${image:-node:24}";
636
+ port="$(penpot_setting "${penpot_mcp_port}")"; port="${port:-4401}";
637
+ plugin_port="$(penpot_setting "${penpot_mcp_plugin_port}")"; plugin_port="${plugin_port:-4400}";
638
+ bridge_port="$(penpot_setting "${penpot_mcp_bridge_port}")"; bridge_port="${bridge_port:-4402}";
639
+ container="penpot-mcp-${project}";
640
+ public_host="$(printf '%s' "$(penpot_resolve_url "${project}")" | sed 's#^https\?://##')";
641
+ work="${WORKSPACE_ROOT}/${doc_name}/${project}/.penpot-mcp";
642
+
643
+ case "${MCP_ACTION}" in
644
+ status)
645
+ if docker ps --format '{{.Names}}' | grep -qx "${container}" ; then
646
+ echo "${container} is running.";
647
+ curl -sk -o /dev/null -w " MCP endpoint ${url}/mcp → HTTP %{http_code} (406 on a plain GET is correct)\n" "${url}/mcp" ;
648
+ curl -sk -o /dev/null -w " plugin manifest ${url}/mcp-plugin/manifest.json → HTTP %{http_code}\n" "${url}/mcp-plugin/manifest.json" ;
649
+ else
650
+ echo "${container} is not running. Start it with --start.";
651
+ fi
652
+ claude mcp list 2>/dev/null | grep -i penpot || echo " Not registered in Claude Code yet — run with --register.";
653
+ ;;
654
+ stop)
655
+ docker rm -f "${container}" >/dev/null 2>&1 && echo "Stopped ${container}." || echo "${container} was not running.";
656
+ ;;
657
+ register)
658
+ claude mcp add --scope user --transport http "penpot-${project}" "${url}/mcp" \
659
+ && echo "Registered penpot-${project} in Claude Code. Open a NEW session to see its tools." ;
660
+ ;;
661
+ start)
662
+ penpot_require_running "${project}" || return 1 ;
663
+ mkdir -p "${work}" ;
664
+ cd "${work}" || return 1 ;
665
+ if [ ! -d package ] ; then
666
+ echo "Fetching ${package}.";
667
+ npm pack "${package}" >/dev/null || return 1 ;
668
+ tar xzf penpot-mcp-*.tgz || return 1 ;
669
+ # pnpm's supply-chain gate blocks esbuild and sharp from running their install scripts,
670
+ # and @penpot/mcp cannot build without them. pnpm 11 has moved the answer from
671
+ # `onlyBuiltDependencies` to `allowBuilds` — given only the old key it writes an
672
+ # `allowBuilds:` stub reading "set this to true or false" and fails with
673
+ # ERR_PNPM_IGNORED_BUILDS anyway. Both keys go in, so either version is satisfied.
674
+ cat >> package/pnpm-workspace.yaml <<'YAMLEOF'
675
+
676
+ onlyBuiltDependencies:
677
+ - esbuild
678
+ - sharp
679
+
680
+ allowBuilds:
681
+ esbuild: true
682
+ sharp: true
683
+ YAMLEOF
684
+ fi
685
+ docker rm -f "${container}" >/dev/null 2>&1 ;
686
+ # pnpm comes from a /tmp prefix rather than corepack: `corepack enable` symlinks into
687
+ # /usr/local/bin, which a non-root container user cannot write (EACCES on pnpm.js).
688
+ # As the host user, or the install writes a node_modules tree the user cannot delete —
689
+ # `rm -rf` on the work directory then fails with Permission denied on every binary. pnpm and
690
+ # corepack need a writable HOME, which /tmp gives them inside the container.
691
+ # The web container's network namespace, NOT the host's: the MCP servers bind 127.0.0.1, and
692
+ # sharing the namespace is what lets this instance's nginx proxy them onto its https origin.
693
+ # It also means the container has to be started AFTER the project and restarted with it — a
694
+ # `ddev restart` destroys the namespace and leaves the routes answering 502.
695
+ docker run -d --name "${container}" --network "container:ddev-${project}-web" \
696
+ --user "$(id -u):$(id -g)" -e HOME=/tmp \
697
+ -e CI=false -e "PENPOT_MCP_WEBSOCKET_URL=wss://${public_host}/mcp-bridge" \
698
+ -v "${work}/package:/pkg" -w /pkg "${image}" \
699
+ sh -c 'set -e; export PATH=/tmp/npm-global/bin:$PATH; \
700
+ npm install -g --prefix /tmp/npm-global pnpm@11.13.0 >/dev/null; \
701
+ pnpm rebuild esbuild sharp; pnpm -r run build; pnpm -r --parallel run start' >/dev/null || return 1 ;
702
+ echo "";
703
+ echo "*---------------------------------------------------------------------*";
704
+ echo " ${container} is starting — the first build takes a couple of minutes.";
705
+ echo " docker logs -f ${container}";
706
+ echo "";
707
+ echo " MCP endpoint ${url}/mcp";
708
+ echo " plugin manifest ${url}/mcp-plugin/manifest.json (same origin — an http://localhost";
709
+ echo " manifest is mixed content on an https editor page and is refused)";
710
+ echo " WS bridge wss://${public_host}/mcp-bridge";
711
+ echo "";
712
+ echo " Then, and this part no script can do:";
713
+ echo " 1. bash cmd-penpot-mcp.sh ${project} -a register";
714
+ echo " 2. In a Penpot editor tab on ${url}: Ctrl+Alt+P, paste the manifest URL,";
715
+ echo " INSTALL, OPEN, CONNECT MCP SERVER — and leave that tab open. It holds the";
716
+ echo " bridge; close it and the tools stop reaching the design.";
717
+ echo "*---------------------------------------------------------------------*";
718
+ ;;
719
+ *)
720
+ echo "Pick one of --start, --stop, --status or --register.";
721
+ return 1 ;
722
+ ;;
723
+ esac
724
+ }