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

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 (632) 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/{PLUGINS.md → core/workspace-app/PLUGINS.md} +9 -1
  160. package/core/workspace-app/README.md +380 -0
  161. package/{actions → core/workspace-app/actions}/ddev.js +0 -1
  162. package/{actions → core/workspace-app/actions}/rag.js +0 -1
  163. package/{actions → core/workspace-app/actions}/testing.js +0 -1
  164. package/{assistant.js → core/workspace-app/assistant.js} +1 -10
  165. package/{git-info.js → core/workspace-app/git-info.js} +17 -1
  166. package/core/workspace-app/package.json +29 -0
  167. package/core/workspace-app/plugin-catalog.js +50 -0
  168. package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  169. package/{server.js → core/workspace-app/server.js} +18 -0
  170. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  171. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  172. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  173. package/core/workspace-app/tests/features/checks.feature +40 -0
  174. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  175. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  176. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  177. package/core/workspace-app/tests/features/menus.feature +29 -0
  178. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  179. package/core/workspace-app/tests/features/plugins.feature +20 -0
  180. package/core/workspace-app/tests/features/project-actions.feature +41 -0
  181. package/core/workspace-app/tests/features/screens.feature +29 -0
  182. package/core/workspace-app/tests/features/settings.feature +46 -0
  183. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  184. package/core/workspace-app/tests/features/theme.feature +40 -0
  185. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  186. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +157 -0
  187. package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
  188. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -0
  189. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  190. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  191. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  192. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  193. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  194. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  195. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  196. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  197. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  198. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  199. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  200. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  201. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  202. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  203. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  204. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  205. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  206. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  207. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  208. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  209. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  210. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  211. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  212. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  213. package/core/workspace-app/tests/unit/changelog.test.js +223 -0
  214. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  215. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  216. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  217. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  218. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  219. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  220. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  221. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  222. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  223. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  224. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  225. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  226. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  227. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  228. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  229. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  230. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  231. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  232. package/core/workspace-app/tests/unit/ddev-domains.test.js +179 -0
  233. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  234. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  235. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  236. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  237. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  238. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  239. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  240. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  241. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  242. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  243. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  244. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  245. package/core/workspace-app/tests/unit/density.test.js +52 -0
  246. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  247. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  248. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  249. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  250. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  251. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  252. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  253. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  254. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  255. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  256. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  257. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  258. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  259. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  260. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  261. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  262. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  263. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  264. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  265. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  266. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  267. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  268. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  269. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  270. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  271. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  272. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  273. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  274. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  275. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  276. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  277. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  278. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  279. package/core/workspace-app/tests/unit/preconditions.test.js +21 -0
  280. package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
  281. package/core/workspace-app/tests/unit/project-make.test.js +143 -0
  282. package/core/workspace-app/tests/unit/project-name.test.js +202 -0
  283. package/core/workspace-app/tests/unit/reader.test.js +62 -0
  284. package/core/workspace-app/tests/unit/release.test.js +92 -0
  285. package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
  286. package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
  287. package/core/workspace-app/tests/unit/round-11.test.js +31 -0
  288. package/core/workspace-app/tests/unit/round-12.test.js +37 -0
  289. package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
  290. package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
  291. package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
  292. package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
  293. package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
  294. package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
  295. package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
  296. package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
  297. package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
  298. package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
  299. package/core/workspace-app/tests/unit/screens.test.js +93 -0
  300. package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
  301. package/core/workspace-app/tests/unit/session-name.test.js +64 -0
  302. package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
  303. package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
  304. package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
  305. package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
  306. package/core/workspace-app/tests/unit/speech.test.js +290 -0
  307. package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
  308. package/core/workspace-app/tests/unit/status-language.test.js +47 -0
  309. package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
  310. package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
  311. package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
  312. package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
  313. package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
  314. package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
  315. package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
  316. package/core/workspace-app/tests/unit/themes.test.js +453 -0
  317. package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
  318. package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
  319. package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
  320. package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
  321. package/core/workspace-app/tests/unit/version-check.test.js +98 -0
  322. package/core/workspace-app/tests/unit/version-link.test.js +50 -0
  323. package/core/workspace-app/tests/unit/video-borrow.test.js +220 -0
  324. package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
  325. package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
  326. package/core/workspace-app/tests/unit/video-edit.test.js +436 -0
  327. package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
  328. package/core/workspace-app/tests/unit/waiting.test.js +48 -0
  329. package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
  330. package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
  331. package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
  332. package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
  333. package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
  334. package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
  335. package/package.json +38 -36
  336. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  337. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  338. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  339. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  340. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  341. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  342. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  343. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  355. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  356. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  357. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  358. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  359. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  360. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  361. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  362. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  363. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  364. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  365. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  366. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  367. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  368. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  369. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  370. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  371. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  372. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  373. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  374. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  375. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  376. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  377. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  378. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  379. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  380. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  381. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  382. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  383. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  384. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  385. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  386. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  387. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  388. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  389. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  390. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  391. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  392. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  393. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  394. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  395. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  396. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  397. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  453. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  454. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  455. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  456. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  457. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  458. /package/{format.js → core/workspace-app/format.js} +0 -0
  459. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  460. /package/{html.js → core/workspace-app/html.js} +0 -0
  461. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  462. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  463. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  464. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  465. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  466. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  467. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  468. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  469. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  470. /package/{images.js → core/workspace-app/images.js} +0 -0
  471. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  472. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  473. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  474. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  475. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  476. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  477. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  478. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  479. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  480. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  481. /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
  482. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  483. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  484. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  485. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  486. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  487. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  488. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  489. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  490. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  491. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  492. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  493. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  494. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  495. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  496. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  532. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  533. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  534. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  535. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  536. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  537. /package/{public → core/workspace-app/public}/logo.png +0 -0
  538. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  539. /package/{public → core/workspace-app/public}/mic.js +0 -0
  540. /package/{public → core/workspace-app/public}/narration.js +0 -0
  541. /package/{public → core/workspace-app/public}/palette.js +0 -0
  542. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  543. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  544. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  545. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  546. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  547. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  548. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  549. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  550. /package/{public → core/workspace-app/public}/ui.js +0 -0
  551. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  552. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  553. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  554. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  555. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  603. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  604. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  605. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  606. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  607. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  608. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  609. /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
  610. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  611. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  612. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  613. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  614. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  615. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  616. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  617. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  618. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  619. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  620. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  621. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  622. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  623. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  624. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  625. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  626. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  627. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  628. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  629. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  630. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
  631. /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
  632. /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ################################################################################
4
+ ## Sharing the specs that are worth sharing.
5
+ ################################################################################
6
+ ## SPDD's own claim is that "successful patterns naturally accumulate into a reusable prompt
7
+ ## library" and that each iteration "starts from accumulated assets". Accumulation does not happen
8
+ ## by itself: a template improved on one machine, in one workspace, improves nothing for anybody
9
+ ## else. Agents, skills and prompts already solve this — one shared repository, fetched with
10
+ ## `git archive` from the fetched remote branch, pulled into the workspace folder to read and edit.
11
+ ## This is the same mechanism for the same reason.
12
+ ##
13
+ ## **Only two kinds of spec are shared, and the exclusion is the design.**
14
+ ##
15
+ ## _defaults/ the templates every spec starts from, and the review checklist.
16
+ ## */00-constitution.md the general spec of a product: what it is, who for, what it must
17
+ ## never become. Written once, revised rarely, useful to everyone who
18
+ ## touches that product.
19
+ ##
20
+ ## A NUMBERED spec — `001-some-change/` — is about one change, in one tree, at one moment. It is
21
+ ## the most valuable artifact locally and pure noise shared: nobody else's repository has that
22
+ ## change, and the analysis it contains was true of a commit they are not on. Sharing those would
23
+ ## turn the shared repository into a landfill of other people's finished work, and the templates
24
+ ## nobody could find in it would stop being pulled. So they stay local.
25
+ ##
26
+ ## Nothing here pushes. Promotion to the shared repository goes through the issue and MR/PR path
27
+ ## like every other change, because a template everybody inherits deserves a review.
28
+ ################################################################################
29
+
30
+ # What is shared, as a predicate. One place, so the listing, the diff and the pull cannot disagree
31
+ # about what "a shared spec" means.
32
+ function specs_is_shared_path() {
33
+ local rel="$1";
34
+ case "${rel}" in
35
+ _defaults/*) return 0 ;;
36
+ */00-constitution.md) return 0 ;;
37
+ *) return 1 ;;
38
+ esac
39
+ }
40
+
41
+ function specs_root() { echo "${WORKSPACE_ROOT}/specs"; }
42
+
43
+ # Where the shared specs live in the shared repository. A top level `specs/`, beside `prompts/` and
44
+ # `.claude/`, because a spec is not a thing Claude Code loads — it is a thing people read.
45
+ function specs_repo_path() { echo "specs"; }
46
+
47
+ # The checkout the repository is read from, kept between runs. Gitignored and hidden from the
48
+ # workspace listing, the way the AI-items checkout and the commands mirror are.
49
+ function specs_checkout() { echo "$(specs_root)/dev-ai-agents"; }
50
+
51
+ # Every shared spec in this workspace folder, as paths relative to specs/.
52
+ function specs_local_shared() {
53
+ local root; root="$(specs_root)";
54
+ [ -d "${root}" ] || return 0 ;
55
+ ( cd "${root}" && find . -name '*.md' -o -name '*.yml' ) 2>/dev/null \
56
+ | sed 's|^\./||' | sort | while read -r rel ; do
57
+ specs_is_shared_path "${rel}" && echo "${rel}" ;
58
+ done
59
+ }
60
+
61
+ # Refresh the checkout and answer with the remote branch to read from. Prints nothing on success
62
+ # beyond the one line saying what it did, because this runs inside every other verb.
63
+ function specs_fetch_repo() {
64
+ local checkout; checkout="$(specs_checkout)";
65
+ ensure_gh_cli || return 1 ;
66
+ ensure_git_github_auth ;
67
+ if [ -d "${checkout}/.git" ] ; then
68
+ git -C "${checkout}" fetch --quiet origin || return 1 ;
69
+ else
70
+ echo "Cloning ${AI_ITEMS_REPO}…" >&2 ;
71
+ git clone --quiet "https://github.com/${AI_ITEMS_REPO}.git" "${checkout}" || {
72
+ echo "Could not clone ${AI_ITEMS_REPO} — check gh auth and repository access." >&2 ;
73
+ return 1 ;
74
+ }
75
+ fi
76
+ local remote_branch;
77
+ remote_branch=$(git -C "${checkout}" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null) ;
78
+ echo "${remote_branch:-origin/${AI_ITEMS_REF:-main}}" ;
79
+ }
80
+
81
+ # The shared specs as the repository has them, extracted to a temporary directory. Read from the
82
+ # FETCHED REMOTE BRANCH with git archive, never from the checkout's working tree: that clone may
83
+ # sit on any branch and hold anybody's work in progress.
84
+ function specs_remote_tree() {
85
+ local branch="$1" tmp;
86
+ tmp="$(mktemp -d)" || return 1 ;
87
+ # Both halves quiet: with no specs/ in the branch, git archive fails and tar then complains that
88
+ # an empty stream is not an archive. That pair of errors is the NORMAL answer the first time
89
+ # anybody runs this — the shared repository does not carry specs until somebody proposes one —
90
+ # and printing it makes a working command look broken.
91
+ if ! git -C "$(specs_checkout)" archive "${branch}" "$(specs_repo_path)" 2>/dev/null | tar -x -C "${tmp}" 2>/dev/null ; then
92
+ rm -rf "${tmp}" ;
93
+ return 1 ;
94
+ fi
95
+ echo "${tmp}" ;
96
+ }
97
+
98
+ # What the shared repository has.
99
+ function specs_list_remote() {
100
+ local branch tmp base;
101
+ branch="$(specs_fetch_repo)" || return 1 ;
102
+ tmp="$(specs_remote_tree "${branch}")" || {
103
+ echo "Nothing at $(specs_repo_path)/ in ${branch} — the shared repository carries no specs yet." ;
104
+ return 0 ;
105
+ }
106
+ base="${tmp}/$(specs_repo_path)";
107
+ echo "";
108
+ echo "Shared specs in ${AI_ITEMS_REPO} (${branch#origin/})";
109
+ echo "";
110
+ ( cd "${base}" && find . -type f \( -name '*.md' -o -name '*.yml' \) | sed 's|^\./||' | sort ) \
111
+ | while read -r rel ; do printf ' %s\n' "${rel}" ; done
112
+ echo "";
113
+ rm -rf "${tmp}" ;
114
+ }
115
+
116
+ # What is here, what is shared, and where the two differ. The answer people actually want, and the
117
+ # one that decides whether to pull.
118
+ function specs_status() {
119
+ local branch tmp base root rel local_file state;
120
+ root="$(specs_root)";
121
+ echo "";
122
+ echo "Shared specs — ${root}";
123
+ echo "";
124
+ branch="$(specs_fetch_repo)" || { echo " (the shared repository could not be read; showing local only)" ; branch='' ; }
125
+ if [ -n "${branch}" ] ; then
126
+ tmp="$(specs_remote_tree "${branch}")" || tmp='' ;
127
+ base="${tmp}/$(specs_repo_path)";
128
+ fi
129
+
130
+ printf ' %-52s %s\n' "SPEC" "STATE" ;
131
+ specs_local_shared | while read -r rel ; do
132
+ local_file="${root}/${rel}";
133
+ if [ -z "${branch}" ] || [ -z "${tmp}" ] || [ ! -f "${base}/${rel}" ] ; then
134
+ state="local only" ;
135
+ elif cmp -s "${local_file}" "${base}/${rel}" ; then
136
+ state="same as shared" ;
137
+ else
138
+ state="DIFFERS from shared" ;
139
+ fi
140
+ printf ' %-52s %s\n' "${rel}" "${state}" ;
141
+ done
142
+
143
+ # And what the repository has that this machine does not — the reason to run --pull.
144
+ if [ -n "${tmp}" ] && [ -d "${base}" ] ; then
145
+ ( cd "${base}" && find . -type f \( -name '*.md' -o -name '*.yml' \) | sed 's|^\./||' | sort ) \
146
+ | while read -r rel ; do
147
+ [ -f "${root}/${rel}" ] || printf ' %-52s %s\n' "${rel}" "in the repository only" ;
148
+ done
149
+ fi
150
+ echo "";
151
+ echo " --pull to take the shared copy, --diff to see what changed first." ;
152
+ echo "";
153
+ [ -n "${tmp}" ] && rm -rf "${tmp}" ;
154
+ return 0 ;
155
+ }
156
+
157
+ # What would change if this were pulled. Shown before it is taken, never after: a template is read
158
+ # by every future spec, and overwriting one unseen is how a local improvement disappears.
159
+ function specs_diff() {
160
+ local want="$1" branch tmp base root rel shown=0;
161
+ root="$(specs_root)";
162
+ branch="$(specs_fetch_repo)" || return 1 ;
163
+ tmp="$(specs_remote_tree "${branch}")" || { echo "The shared repository carries no specs yet." ; return 0 ; }
164
+ base="${tmp}/$(specs_repo_path)";
165
+
166
+ ( cd "${base}" && find . -type f \( -name '*.md' -o -name '*.yml' \) | sed 's|^\./||' | sort ) \
167
+ | while read -r rel ; do
168
+ [ "${want}" == '_all_' ] || [ -z "${want}" ] || case "${rel}" in *"${want}"*) : ;; *) continue ;; esac
169
+ if [ ! -f "${root}/${rel}" ] ; then
170
+ echo "=== ${rel} — not here yet" ;
171
+ elif ! cmp -s "${root}/${rel}" "${base}/${rel}" ; then
172
+ echo "=== ${rel}" ;
173
+ diff -u "${root}/${rel}" "${base}/${rel}" | sed '1,2d' ;
174
+ fi
175
+ done
176
+ shown=$? ;
177
+ rm -rf "${tmp}" ;
178
+ return 0 ;
179
+ }
180
+
181
+ # Take the shared copy. A local file that differs is BACKED UP first, with the path printed: a pull
182
+ # can move you backwards — the shared branch does not carry work still in review — and a template
183
+ # somebody improved this morning must not vanish because they ran a sync this afternoon.
184
+ function specs_pull() {
185
+ local want="$1" branch tmp base root rel dest backup stamp pulled=0;
186
+ root="$(specs_root)";
187
+ branch="$(specs_fetch_repo)" || return 1 ;
188
+ tmp="$(specs_remote_tree "${branch}")" || { echo "The shared repository carries no specs yet — nothing to pull." ; return 0 ; }
189
+ base="${tmp}/$(specs_repo_path)";
190
+ stamp="$(date +%Y-%m-%d-%H%M)";
191
+
192
+ while read -r rel ; do
193
+ [ -n "${rel}" ] || continue ;
194
+ [ "${want}" == '_all_' ] || [ -z "${want}" ] || case "${rel}" in *"${want}"*) : ;; *) continue ;; esac
195
+ specs_is_shared_path "${rel}" || continue ;
196
+ dest="${root}/${rel}";
197
+ if [ -f "${dest}" ] && cmp -s "${dest}" "${base}/${rel}" ; then continue ; fi
198
+ if [ -f "${dest}" ] ; then
199
+ backup="${WORKSPACE_ROOT}/backups/specs/${stamp}/${rel}";
200
+ mkdir -p "$(dirname "${backup}")" ;
201
+ cp "${dest}" "${backup}" ;
202
+ echo " kept your copy at ${backup}" ;
203
+ fi
204
+ mkdir -p "$(dirname "${dest}")" ;
205
+ cp "${base}/${rel}" "${dest}" ;
206
+ echo " pulled ${rel}" ;
207
+ pulled=$((pulled + 1)) ;
208
+ done < <( cd "${base}" && find . -type f \( -name '*.md' -o -name '*.yml' \) | sed 's|^\./||' | sort )
209
+
210
+ rm -rf "${tmp}" ;
211
+ echo "" ;
212
+ if [ "${pulled}" -eq 0 ] ; then
213
+ echo "Nothing to pull — everything shared is already what you have." ;
214
+ else
215
+ echo "${pulled} file(s) pulled from ${AI_ITEMS_REPO}." ;
216
+ fi
217
+ }
218
+
219
+ # Promoting a local improvement back to everybody. Deliberately NOT automated: a template is
220
+ # inherited by every future spec in every workspace, so it goes through the same issue-and-PR path
221
+ # as any other change. This prints exactly what to do, and creates nothing.
222
+ function specs_propose() {
223
+ local root; root="$(specs_root)";
224
+ echo "";
225
+ echo "Sharing a spec back to ${AI_ITEMS_REPO}";
226
+ echo "";
227
+ echo " Nothing here pushes. A template or a constitution is inherited by everyone who pulls it,"
228
+ echo " so it is proposed and reviewed like any other change:";
229
+ echo "";
230
+ echo " 1. git -C $(specs_checkout) checkout -b <branch>";
231
+ echo " 2. copy your file into $(specs_checkout)/$(specs_repo_path)/<same relative path>";
232
+ echo " 3. commit, push, and open the PR on ${AI_ITEMS_REPO}";
233
+ echo "";
234
+ echo " What is shared: _defaults/ and every 00-constitution.md. A numbered spec is about one";
235
+ echo " change in one tree and is not shared — see fun-specs-share.sh for why.";
236
+ echo "";
237
+ }
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Second storage: whole workspace folders kept on another disk, each one a symlink from the tree.
4
+ #
5
+ # `storage.second` in settings.yml names the folder on that disk that holds them. A moved workspace is
6
+ # `<root>/<name> -> <storage>/<name>`, so every script and the dashboard keep using `<root>/<name>`.
7
+ #
8
+ # Three rules hold throughout:
9
+ # * nothing is deleted without a flag that says so. A move keeps `<name>.pre-move`, a move back keeps
10
+ # the copy on the drive;
11
+ # * a copy is trusted only after a checksum compare finds no difference;
12
+ # * a workspace that holds DDEV projects is refused. DDEV bind-mounts a project's folder into its
13
+ # containers, and that has not been verified across a symlink onto another disk.
14
+
15
+ STORAGE_RSYNC_FLAGS="${STORAGE_RSYNC_FLAGS:--aHAX}";
16
+ STORAGE_COMPOSE_NAME="docker-compose.second-storage.yaml";
17
+
18
+ # The configured path, one layer of quotes stripped (parse_yaml keeps them), no trailing slash.
19
+ function storage_path() {
20
+ local p="${storage_second:-}" ;
21
+ p="${p#\"}" ; p="${p%\"}" ; p="${p#\'}" ; p="${p%\'}" ;
22
+ case "${p}" in ''|CHANGE_ME|none|off) return 0 ;; esac
23
+ printf '%s' "${p%/}" ;
24
+ }
25
+
26
+ function storage_say() { [ "${QUIET:-}" == 'yes' ] || echo "$@" ; }
27
+
28
+ function storage_is_workspace() {
29
+ local w ;
30
+ for w in "${workspaces[@]}" ; do [ "${w}" == "$1" ] && return 0 ; done
31
+ return 1 ;
32
+ }
33
+
34
+ # local | second | elsewhere | broken | missing
35
+ # second a symlink into the configured storage path that resolves
36
+ # elsewhere a symlink that resolves, but not into the configured path
37
+ # broken a symlink whose target is not there: the drive is not mounted, or it moved
38
+ function storage_state() {
39
+ local link="${WORKSPACE_ROOT}/$1" storage target ;
40
+ storage="$(storage_path)" ;
41
+ if [ -L "${link}" ] ; then
42
+ target="$(readlink "${link}")" ;
43
+ [ -d "${link}" ] || { echo 'broken' ; return 0 ; } ;
44
+ if [ -n "${storage}" ] && [ "${target%/}" == "${storage}/$1" ] ; then echo 'second' ; else echo 'elsewhere' ; fi
45
+ elif [ -d "${link}" ] ; then
46
+ echo 'local' ;
47
+ else
48
+ echo 'missing' ;
49
+ fi
50
+ }
51
+
52
+ # Any DDEV project inside, at the depths projects live at (a project, or a project inside a clone).
53
+ function storage_has_ddev() {
54
+ [ -n "$(find "$1" -mindepth 2 -maxdepth 4 -type f -path '*/.ddev/config.yaml' -print -quit 2>/dev/null)" ] ;
55
+ }
56
+
57
+ function storage_avail_bytes() { df -B1 --output=avail "$1" 2>/dev/null | tail -1 | tr -d ' ' ; }
58
+ function storage_size_bytes() { du -sb "$1" 2>/dev/null | cut -f1 ; }
59
+ function storage_human() { numfmt --to=iec --suffix=B "${1:-0}" 2>/dev/null || echo "${1:-0} bytes" ; }
60
+
61
+ # True when both paths are on one filesystem, where a move frees nothing.
62
+ function storage_same_fs() {
63
+ [ "$(stat -c %d "$1" 2>/dev/null)" == "$(stat -c %d "$2" 2>/dev/null)" ] ;
64
+ }
65
+
66
+ # The storage path exists and is on its own filesystem.
67
+ function storage_mounted() {
68
+ local storage ; storage="$(storage_path)" ;
69
+ [ -n "${storage}" ] && [ -d "${storage}" ] && ! storage_same_fs "${storage}" "${WORKSPACE_ROOT}" ;
70
+ }
71
+
72
+ # Room for size plus a margin: 5%, at least 1 GiB.
73
+ function storage_has_room() {
74
+ local size="$1" dest="$2" avail margin ;
75
+ avail="$(storage_avail_bytes "${dest}")" ;
76
+ margin=$(( size / 20 )) ; [ "${margin}" -lt 1073741824 ] && margin=1073741824 ;
77
+ [ -n "${avail}" ] && [ "${avail}" -ge $(( size + margin )) ] ;
78
+ }
79
+
80
+ # Lines that differ between two trees, by checksum. Empty means identical.
81
+ function storage_diff() {
82
+ rsync ${STORAGE_RSYNC_FLAGS} -nc --delete --itemize-changes "$1/" "$2/" 2>&1 ;
83
+ }
84
+
85
+ function storage_copy() {
86
+ rsync ${STORAGE_RSYNC_FLAGS} --delete "$1/" "$2/" ;
87
+ }
88
+
89
+ # Copy, then compare. Prints why and returns 1 on any difference.
90
+ function storage_copy_verified() {
91
+ local from="$1" to="$2" diff ;
92
+ storage_say " copying ${from} → ${to}" ;
93
+ if ! storage_copy "${from}" "${to}" ; then
94
+ echo " ✗ rsync failed. Nothing was swapped; ${from} is untouched." ; return 1 ;
95
+ fi
96
+ storage_say " verifying by checksum" ;
97
+ diff="$(storage_diff "${from}" "${to}")" ;
98
+ if [ -n "${diff}" ] ; then
99
+ echo " ✗ The copy differs from the source ($(printf '%s\n' "${diff}" | wc -l) lines). Nothing was swapped:" ;
100
+ printf '%s\n' "${diff}" | head -10 | sed 's/^/ /' ;
101
+ return 1 ;
102
+ fi
103
+ storage_say " ✓ identical" ;
104
+ }
105
+
106
+ # The mount the drive needs in the dashboard container, one host path per line, at the same path.
107
+ # Under /run/media/<user> the PARENT is mounted, because udisks picks the folder name at mount time
108
+ # (`workspace`, then `workspace1` when a stale `workspace` is in the way), and a bind of the parent with
109
+ # rslave also sees a drive mounted after the dashboard started.
110
+ function storage_mount_sources() {
111
+ local storage link target ;
112
+ storage="$(storage_path)" ;
113
+ {
114
+ [ -n "${storage}" ] && printf '%s\n' "${storage}" ;
115
+ # Links that still point somewhere else (an old path) need their target too, or they break inside.
116
+ for link in "${WORKSPACE_ROOT}"/* ; do
117
+ [ -L "${link}" ] || continue ;
118
+ target="$(readlink "${link}")" ;
119
+ case "${target}" in /*) printf '%s\n' "$(dirname "${target}")" ;; esac
120
+ done
121
+ } | while read -r p ; do
122
+ case "${p}" in
123
+ "${WORKSPACE_ROOT}"|"${WORKSPACE_ROOT}"/*) ;;
124
+ /run/media/*/*) printf '%s\n' "$(printf '%s' "${p}" | cut -d/ -f1-4)" ;;
125
+ *) printf '%s\n' "${p}" ;;
126
+ esac
127
+ done | sort -u ;
128
+ }
129
+
130
+ function storage_compose() {
131
+ local sources src ;
132
+ sources="$(storage_mount_sources)" ;
133
+ [ -n "${sources}" ] || return 0 ;
134
+ echo "# Generated by core/scripts/storage.sh --mounts from storage.second in settings.yml." ;
135
+ echo "# This machine only: never committed, and rewritten by every run. Edit the setting, not this file." ;
136
+ echo "# The second storage, at the same path as on the host, so the workspace symlinks resolve in here." ;
137
+ echo "services:" ;
138
+ echo " web:" ;
139
+ echo " volumes:" ;
140
+ while read -r src ; do
141
+ case "${src}" in
142
+ /run/media/*) echo " - ${src}:${src}:rslave" ;;
143
+ *) echo " - ${src}:${src}" ;;
144
+ esac
145
+ done <<< "${sources}" ;
146
+ }
147
+
148
+ function storage_compose_file() { echo "${WORKSPACE_ROOT}/core/workspace-app/.ddev/${STORAGE_COMPOSE_NAME}" ; }
149
+
150
+ # Write (or remove) the dashboard's override. Says a restart is needed only when it changed.
151
+ function storage_write_mounts() {
152
+ local file new old ;
153
+ file="$(storage_compose_file)" ;
154
+ [ -d "$(dirname "${file}")" ] || { echo " ✗ No dashboard at $(dirname "$(dirname "${file}")")." ; return 1 ; } ;
155
+ new="$(storage_compose)" ;
156
+ old="$(cat "${file}" 2>/dev/null)" ;
157
+ if [ "${new}" == "${old}" ] ; then
158
+ storage_say " ✓ dashboard mounts up to date${new:+ (${file##*/})}" ;
159
+ return 0 ;
160
+ fi
161
+ if [ -z "${new}" ] ; then
162
+ rm -f "${file}" ;
163
+ echo " ✓ removed ${file##*/}: no second storage is set or linked" ;
164
+ else
165
+ printf '%s\n' "${new}" > "${file}" ;
166
+ echo " ✓ wrote ${file#${WORKSPACE_ROOT}/}:" ;
167
+ grep '^ - ' "${file}" | sed 's/^ - / /' ;
168
+ fi
169
+ echo " The dashboard picks it up on its next restart:" ;
170
+ echo " cd ${WORKSPACE_ROOT}/core/workspace-app && ddev restart" ;
171
+ echo " Check no build job is running first; a restart orphans it." ;
172
+ }
173
+
174
+ # The stable fix for a udisks mount whose path can change: an fstab line by UUID to a fixed point.
175
+ function storage_fstab_hint() {
176
+ local storage mnt uuid fstype label point stale ;
177
+ storage="$(storage_path)" ;
178
+ case "${storage}" in /run/media/*) ;; *) return 0 ;; esac
179
+ [ -d "${storage}" ] || return 0 ;
180
+ mnt="$(findmnt -no TARGET -T "${storage}" 2>/dev/null)" ;
181
+ uuid="$(findmnt -no UUID -T "${storage}" 2>/dev/null)" ;
182
+ fstype="$(findmnt -no FSTYPE -T "${storage}" 2>/dev/null)" ;
183
+ label="$(findmnt -no LABEL -T "${storage}" 2>/dev/null)" ;
184
+ [ -n "${uuid}" ] || return 0 ;
185
+ point="${STORAGE_MOUNT_POINT:-/mnt/workspace-storage}" ;
186
+ # udisks names the folder after the label and adds a digit when that name is taken.
187
+ stale="$(dirname "${mnt}")/${label}" ;
188
+ echo "" ;
189
+ echo " ! ${mnt} is a udisks automount. Its path is picked at mount time (a leftover" ;
190
+ echo " folder named after the label makes udisks add a digit), so after a reboot every" ;
191
+ echo " workspace link can break. The stable fix, run by you (this tool never uses sudo):" ;
192
+ echo " (cd ${WORKSPACE_ROOT}/core/workspace-app && ddev stop) # it holds the mount open" ;
193
+ echo " sudo mkdir -p ${point}" ;
194
+ echo " echo 'UUID=${uuid} ${point} ${fstype:-auto} defaults,nofail,x-systemd.device-timeout=10s 0 2' | sudo tee -a /etc/fstab" ;
195
+ echo " sudo systemctl daemon-reload && sudo umount ${mnt} && sudo mount ${point}" ;
196
+ if [ "${stale}" != "${mnt}" ] && [ -d "${stale}" ] && [ -z "$(ls -A "${stale}" 2>/dev/null)" ] ; then
197
+ echo " sudo rmdir ${stale} # the empty leftover that pushed it to ${mnt##*/}" ;
198
+ fi
199
+ echo " then set storage.second to ${point}${storage#${mnt}} and run:" ;
200
+ echo " bash ${WORKSPACE_SCRIPTS}/storage.sh --relink --mounts" ;
201
+ }
202
+
203
+ function storage_status() {
204
+ local storage w state target size free_local free_second ;
205
+ storage="$(storage_path)" ;
206
+ echo "Second storage: ${storage:-not set (storage.second in settings.yml)}" ;
207
+ if [ -n "${storage}" ] ; then
208
+ if storage_mounted ; then
209
+ free_second="$(storage_avail_bytes "${storage}")" ;
210
+ echo " mounted: $(findmnt -no TARGET,SOURCE,FSTYPE -T "${storage}" 2>/dev/null | tr -s ' ')" ;
211
+ echo " free: $(storage_human "${free_second}")" ;
212
+ elif [ -d "${storage}" ] ; then
213
+ echo " ✗ ${storage} is on the same filesystem as the workspace: the drive is not mounted there." ;
214
+ else
215
+ echo " ✗ ${storage} does not exist: the drive is not mounted." ;
216
+ fi
217
+ fi
218
+ free_local="$(storage_avail_bytes "${WORKSPACE_ROOT}")" ;
219
+ echo "Workspace: ${WORKSPACE_ROOT}" ;
220
+ echo " free: $(storage_human "${free_local}")" ;
221
+ echo "" ;
222
+ for w in "${workspaces[@]}" ; do
223
+ state="$(storage_state "${w}")" ;
224
+ target="" ;
225
+ [ -L "${WORKSPACE_ROOT}/${w}" ] && target=" → $(readlink "${WORKSPACE_ROOT}/${w}")" ;
226
+ case "${state}" in
227
+ local) printf ' %-16s local\n' "${w}" ;;
228
+ second) printf ' %-16s second storage%s\n' "${w}" "${target}" ;;
229
+ elsewhere) printf ' %-16s ! linked elsewhere%s\n' "${w}" "${target}" ;;
230
+ broken) printf ' %-16s ✗ broken link%s\n' "${w}" "${target}" ;;
231
+ missing) printf ' %-16s (no folder)\n' "${w}" ;;
232
+ esac
233
+ if [ -d "${WORKSPACE_ROOT}/${w}.pre-move" ] ; then
234
+ size="$(storage_size_bytes "${WORKSPACE_ROOT}/${w}.pre-move")" ;
235
+ printf ' %-16s %s.pre-move kept locally, %s (--remove-pre-move %s frees it)\n' "" "${w}" "$(storage_human "${size}")" "${w}" ;
236
+ fi
237
+ done
238
+ if [ -n "$(storage_mount_sources)" ] && [ ! -f "$(storage_compose_file)" ] ; then
239
+ echo "" ;
240
+ echo " ! The dashboard does not mount the second storage yet: run --mounts." ;
241
+ fi
242
+ storage_fstab_hint ;
243
+ }
244
+
245
+ function storage_move() {
246
+ local w="$1" storage src dest size ;
247
+ storage="$(storage_path)" ; src="${WORKSPACE_ROOT}/${w}" ; dest="${storage}/${w}" ;
248
+ storage_is_workspace "${w}" || { echo " ✗ ${w} is not a workspace in settings.yml." ; return 1 ; } ;
249
+ [ -n "${storage}" ] || { echo " ✗ Set storage.second in settings.yml first." ; return 1 ; } ;
250
+ case "${storage}/" in "${WORKSPACE_ROOT}/"*) echo " ✗ ${storage} is inside the workspace." ; return 1 ;; esac
251
+ storage_mounted || { echo " ✗ ${storage} is not mounted on its own filesystem." ; return 1 ; } ;
252
+ [ -w "${storage}" ] || { echo " ✗ ${storage} is not writable by $(id -un)." ; return 1 ; } ;
253
+ [ "$(storage_state "${w}")" == 'local' ] || { echo " ✗ ${w} is not a local folder ($(storage_state "${w}"))." ; return 1 ; } ;
254
+ if storage_has_ddev "${src}" ; then
255
+ echo " ✗ ${w} holds DDEV projects. DDEV bind-mounts each project into its containers, and that" ;
256
+ echo " is not verified across a symlink onto another disk. Back them up or remove them first." ;
257
+ return 1 ;
258
+ fi
259
+ [ -e "${src}.pre-move" ] && { echo " ✗ ${src}.pre-move already exists. Remove it or move it aside first." ; return 1 ; } ;
260
+ if [ -e "${dest}" ] && [ -n "$(ls -A "${dest}" 2>/dev/null)" ] ; then
261
+ echo " ✗ ${dest} already holds files. Nothing on the second storage is overwritten." ; return 1 ;
262
+ fi
263
+ size="$(storage_size_bytes "${src}")" ;
264
+ if ! storage_has_room "${size}" "${storage}" ; then
265
+ echo " ✗ Not enough room: ${w} is $(storage_human "${size}"), ${storage} has $(storage_human "$(storage_avail_bytes "${storage}")") free (5%, at least 1 GiB, is kept spare)." ;
266
+ return 1 ;
267
+ fi
268
+ echo "Moving ${w} ($(storage_human "${size}")) to ${dest}" ;
269
+ if [ "${DRY_RUN:-}" == 'yes' ] ; then echo " dry run: nothing copied." ; return 0 ; fi
270
+ echo " Nothing should write into ${w} until this finishes." ;
271
+ mkdir -p "${dest}" || return 1 ;
272
+ storage_copy_verified "${src}" "${dest}" || return 1 ;
273
+ mv "${src}" "${src}.pre-move" || return 1 ;
274
+ if ! ln -s "${dest}" "${src}" ; then
275
+ mv "${src}.pre-move" "${src}" ; echo " ✗ Could not make the link; ${w} is back where it was." ; return 1 ;
276
+ fi
277
+ echo " ✓ ${src} → ${dest}" ;
278
+ echo " The original is kept as ${src}.pre-move. When you are happy, free it with:" ;
279
+ echo " bash ${WORKSPACE_SCRIPTS}/storage.sh --remove-pre-move ${w}" ;
280
+ }
281
+
282
+ function storage_back() {
283
+ local w="$1" src target stage size ;
284
+ src="${WORKSPACE_ROOT}/${w}" ;
285
+ storage_is_workspace "${w}" || { echo " ✗ ${w} is not a workspace in settings.yml." ; return 1 ; } ;
286
+ case "$(storage_state "${w}")" in
287
+ second|elsewhere) ;;
288
+ broken) echo " ✗ ${w} links to $(readlink "${src}"), which is not there. Mount the drive first." ; return 1 ;;
289
+ *) echo " ✗ ${w} is not on second storage." ; return 1 ;;
290
+ esac
291
+ target="$(readlink -f "${src}")" ;
292
+ # A kept pre-move copy is the cheapest place to start: rsync only sends what changed since.
293
+ stage="${src}.pre-move" ;
294
+ [ -d "${stage}" ] || stage="${WORKSPACE_ROOT}/.${w}.moving-back" ;
295
+ size="$(storage_size_bytes "${target}")" ;
296
+ if ! storage_has_room "${size}" "${WORKSPACE_ROOT}" ; then
297
+ echo " ✗ Not enough room: ${w} is $(storage_human "${size}"), the workspace disk has $(storage_human "$(storage_avail_bytes "${WORKSPACE_ROOT}")") free." ;
298
+ return 1 ;
299
+ fi
300
+ echo "Moving ${w} ($(storage_human "${size}")) back from ${target}" ;
301
+ if [ "${DRY_RUN:-}" == 'yes' ] ; then echo " dry run: nothing copied." ; return 0 ; fi
302
+ mkdir -p "${stage}" || return 1 ;
303
+ storage_copy_verified "${target}" "${stage}" || return 1 ;
304
+ unlink "${src}" || return 1 ;
305
+ if ! mv "${stage}" "${src}" ; then
306
+ ln -s "${target}" "${src}" ; echo " ✗ Could not put the folder in place; the link is restored." ; return 1 ;
307
+ fi
308
+ echo " ✓ ${src} is a local folder again." ;
309
+ echo " The copy on the second storage is kept: ${target}" ;
310
+ echo " Delete it by hand when you no longer want it; this tool never deletes on the drive." ;
311
+ }
312
+
313
+ function storage_remove_pre_move() {
314
+ local w="$1" kept="${WORKSPACE_ROOT}/$1.pre-move" ;
315
+ [ -d "${kept}" ] || { echo " ✗ There is no ${kept}." ; return 1 ; } ;
316
+ case "$(storage_state "${w}")" in
317
+ second|elsewhere) ;;
318
+ *) echo " ✗ ${w} is not a working link to second storage, so ${kept} may be the only copy. Kept." ; return 1 ;;
319
+ esac
320
+ [ -n "$(ls -A "${WORKSPACE_ROOT}/${w}/" 2>/dev/null)" ] || { echo " ✗ ${w} on second storage is empty. Kept ${kept}." ; return 1 ; } ;
321
+ if [ "${DRY_RUN:-}" == 'yes' ] ; then echo " dry run: would remove ${kept}." ; return 0 ; fi
322
+ rm -rf "${kept}" && echo " ✓ removed ${kept}" ;
323
+ }
324
+
325
+ # Point links at the configured path when the same folder is there (the drive got a new mount point).
326
+ function storage_relink() {
327
+ local storage w link target changed=0 ;
328
+ storage="$(storage_path)" ;
329
+ [ -n "${storage}" ] || { echo " ✗ Set storage.second in settings.yml first." ; return 1 ; } ;
330
+ for w in "${workspaces[@]}" ; do
331
+ link="${WORKSPACE_ROOT}/${w}" ;
332
+ [ -L "${link}" ] || continue ;
333
+ target="$(readlink "${link}")" ;
334
+ [ "${target%/}" == "${storage}/${w}" ] && continue ;
335
+ if [ -d "${storage}/${w}" ] && [ -n "$(ls -A "${storage}/${w}" 2>/dev/null)" ] ; then
336
+ if [ "${DRY_RUN:-}" == 'yes' ] ; then
337
+ echo " would relink ${w}: ${target} → ${storage}/${w}" ;
338
+ else
339
+ ln -sfn "${storage}/${w}" "${link}" && echo " ✓ ${w}: ${target} → ${storage}/${w}" ;
340
+ fi
341
+ changed=$((changed+1)) ;
342
+ else
343
+ echo " ! ${w} links to ${target}; ${storage}/${w} is not there, so it is left alone." ;
344
+ fi
345
+ done
346
+ [ "${changed}" -gt 0 ] || storage_say " ✓ every link already points at ${storage}" ;
347
+ }