@webship.co/workspace 0.0.0-stage → 1.0.0-alpha1

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 (311) hide show
  1. package/PLUGINS.md +249 -0
  2. package/README.md +379 -2
  3. package/WORKSPACES.md +69 -0
  4. package/actions/ai-items.js +293 -0
  5. package/actions/audio.js +48 -0
  6. package/actions/build-order.js +79 -0
  7. package/actions/checks.js +95 -0
  8. package/actions/claude-session.js +104 -0
  9. package/actions/commands-center.js +258 -0
  10. package/actions/composer.js +267 -0
  11. package/actions/ddev-addons.js +90 -0
  12. package/actions/ddev-mail.js +80 -0
  13. package/actions/ddev.js +237 -0
  14. package/actions/folders.js +165 -0
  15. package/actions/graphs.js +107 -0
  16. package/actions/images-svg.js +33 -0
  17. package/actions/images.js +145 -0
  18. package/actions/navigation.js +190 -0
  19. package/actions/node.js +222 -0
  20. package/actions/notes.js +55 -0
  21. package/actions/obsidian.js +59 -0
  22. package/actions/plugins.js +36 -0
  23. package/actions/project-make.js +28 -0
  24. package/actions/rag.js +202 -0
  25. package/actions/row-actions.js +262 -0
  26. package/actions/screens.js +270 -0
  27. package/actions/testing.js +121 -0
  28. package/actions/video-edit.js +54 -0
  29. package/ai-inventory.js +182 -0
  30. package/ai-items.js +202 -0
  31. package/assistant-cast.js +176 -0
  32. package/assistant-suggestions.js +91 -0
  33. package/assistant.js +484 -0
  34. package/audio-voice.js +528 -0
  35. package/auth-google.js +260 -0
  36. package/build-ai.js +52 -0
  37. package/build-order-page.js +191 -0
  38. package/build-order.js +313 -0
  39. package/builder-args.js +117 -0
  40. package/changelog.js +302 -0
  41. package/chat-threads.js +134 -0
  42. package/checks.js +105 -0
  43. package/claude-args.js +282 -0
  44. package/claude-sessions.js +129 -0
  45. package/command-author.js +117 -0
  46. package/command-block.js +20 -0
  47. package/commands/defaults.yml +159 -0
  48. package/commands-page.js +460 -0
  49. package/commands.js +568 -0
  50. package/cucumber.js +43 -0
  51. package/ddev-addons-page.js +124 -0
  52. package/ddev-addons.js +154 -0
  53. package/ddev-custom-commands.js +96 -0
  54. package/ddev-domains.js +84 -0
  55. package/ddev-import.js +93 -0
  56. package/ddev-mail-page.js +167 -0
  57. package/ddev-mail.js +110 -0
  58. package/ddev-options.js +145 -0
  59. package/ddev-snapshots.js +126 -0
  60. package/ddev.js +145 -0
  61. package/docs/DEVELOPER-GUIDE.md +224 -0
  62. package/docs/README.md +252 -0
  63. package/docs/REFERENCE.md +684 -0
  64. package/docs/USER-GUIDE.md +697 -0
  65. package/docs/screenshots/assistant/avatar-board.png +0 -0
  66. package/docs/screenshots/assistant/avatar-coffee.png +0 -0
  67. package/docs/screenshots/assistant/avatar-colours.png +0 -0
  68. package/docs/screenshots/assistant/avatar-icons.png +0 -0
  69. package/docs/screenshots/assistant/avatar-moves.png +0 -0
  70. package/docs/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  71. package/docs/screenshots/assistant/avatar-settings-voices.png +0 -0
  72. package/docs/screenshots/assistant/avatar-status.png +0 -0
  73. package/docs/screenshots/create-card/create-card.png +0 -0
  74. package/docs/screenshots/create-card/start-ai.png +0 -0
  75. package/docs/screenshots/guide-clone-address.png +0 -0
  76. package/docs/screenshots/guide-clone-worktree-off.png +0 -0
  77. package/docs/screenshots/guide-clone-worktree-on.png +0 -0
  78. package/docs/screenshots/guide-ddev-menu.png +0 -0
  79. package/docs/screenshots/guide-domains.png +0 -0
  80. package/docs/screenshots/guide-home.png +0 -0
  81. package/docs/screenshots/guide-ide-menu.png +0 -0
  82. package/docs/screenshots/guide-remove-refusal.png +0 -0
  83. package/docs/screenshots/guide-rows-worktree.png +0 -0
  84. package/docs/screenshots/guide-screens.png +0 -0
  85. package/docs/screenshots/guide-settings.png +0 -0
  86. package/docs/screenshots/guide-workspace-page.png +0 -0
  87. package/docs/screenshots/home/card-settings.png +0 -0
  88. package/docs/screenshots/home/drag-reorder.png +0 -0
  89. package/docs/screenshots/home/edit-card.png +0 -0
  90. package/docs/screenshots/images/claude-code-image.png +0 -0
  91. package/docs/screenshots/images/edit-with-ai.png +0 -0
  92. package/docs/screenshots/images/gallery.png +0 -0
  93. package/docs/screenshots/images/generate-description.png +0 -0
  94. package/docs/screenshots/images/generate-plan.png +0 -0
  95. package/docs/screenshots/images/providers.png +0 -0
  96. package/docs/screenshots/images/svg-form.png +0 -0
  97. package/docs/screenshots/images/svg-result.png +0 -0
  98. package/docs/screenshots/images/upload.png +0 -0
  99. package/docs/screenshots/new-folder/2-form.png +0 -0
  100. package/docs/screenshots/new-folder/8-ddev-options.png +0 -0
  101. package/docs/screenshots/new-folder/files-folder.png +0 -0
  102. package/docs/screenshots/projects/ddev-custom-commands.png +0 -0
  103. package/docs/screenshots/projects/state-chip-stop.png +0 -0
  104. package/docs/screenshots/readme/dashboard-dark.png +0 -0
  105. package/docs/screenshots/readme/dashboard-light.png +0 -0
  106. package/docs/screenshots/readme/dev-dark.png +0 -0
  107. package/docs/screenshots/readme/dev-light.png +0 -0
  108. package/docs/screenshots/readme/docs-dark.png +0 -0
  109. package/docs/screenshots/readme/docs-light.png +0 -0
  110. package/docs/screenshots/readme/icons/ai.svg +4 -0
  111. package/docs/screenshots/readme/icons/code.svg +4 -0
  112. package/docs/screenshots/readme/icons/docs.svg +4 -0
  113. package/docs/screenshots/readme/icons/services.svg +4 -0
  114. package/docs/screenshots/readme/icons/sites.svg +4 -0
  115. package/docs/screenshots/readme/test-report-dark.png +0 -0
  116. package/docs/screenshots/readme/test-report-light.png +0 -0
  117. package/docs/screenshots/readme/test-scenarios.png +0 -0
  118. package/docs/screenshots/readme/test-steps.png +0 -0
  119. package/docs/screenshots/readme/videos-dark.png +0 -0
  120. package/docs/screenshots/readme/videos-light.png +0 -0
  121. package/docs/screenshots/screens-menu.png +0 -0
  122. package/docs/screenshots/screens-new.png +0 -0
  123. package/docs/screenshots/settings/auth-google-lists.png +0 -0
  124. package/docs/screenshots/settings/delete-1-row.png +0 -0
  125. package/docs/screenshots/settings/delete-2-confirm.png +0 -0
  126. package/docs/screenshots/settings/delete-3-type-name.png +0 -0
  127. package/docs/screenshots/settings/delete-4-type-yes.png +0 -0
  128. package/event-log.js +59 -0
  129. package/files-browser.js +247 -0
  130. package/folders.js +121 -0
  131. package/format.js +73 -0
  132. package/git-info.js +81 -0
  133. package/github-auth.js +62 -0
  134. package/graphs.js +104 -0
  135. package/html.js +10 -0
  136. package/icons.js +83 -0
  137. package/images-ai.js +447 -0
  138. package/images-create.js +226 -0
  139. package/images-page.js +417 -0
  140. package/images-records.js +39 -0
  141. package/images-render.js +36 -0
  142. package/images-svg-core.js +214 -0
  143. package/images-svg-sanitize.js +188 -0
  144. package/images-svg.js +241 -0
  145. package/images.js +270 -0
  146. package/jobs.js +430 -0
  147. package/linger.js +21 -0
  148. package/lists.js +292 -0
  149. package/media-info.js +136 -0
  150. package/navigation-page.js +264 -0
  151. package/navigation.js +394 -0
  152. package/notes.js +0 -0
  153. package/obsidian.js +405 -0
  154. package/package.json +47 -4
  155. package/palette-index.js +46 -0
  156. package/playwright.config.js +23 -0
  157. package/plugin-install.js +239 -0
  158. package/plugin-loader.js +448 -0
  159. package/plugin-manifest.js +83 -0
  160. package/plugin-settings.js +44 -0
  161. package/plugins/workspace-notes/index.js +59 -0
  162. package/plugins-page.js +103 -0
  163. package/project-make.js +241 -0
  164. package/project-name.js +210 -0
  165. package/public/assistant-avatar-moves.js +646 -0
  166. package/public/assistant-avatar-ui.js +1223 -0
  167. package/public/assistant-avatar.js +530 -0
  168. package/public/avatar/druplicon.vrm +0 -0
  169. package/public/css/base.css +260 -0
  170. package/public/css/components/ai-assistant.css +263 -0
  171. package/public/css/components/assistant-avatar.css +279 -0
  172. package/public/css/components/assistant-views.css +198 -0
  173. package/public/css/components/build-order.css +74 -0
  174. package/public/css/components/clone-as-a-git-worktree.css +30 -0
  175. package/public/css/components/command-palette.css +61 -0
  176. package/public/css/components/commands-center.css +139 -0
  177. package/public/css/components/confirm-dialog.css +40 -0
  178. package/public/css/components/create-card.css +8 -0
  179. package/public/css/components/ddev-menu.css +23 -0
  180. package/public/css/components/deep-chat.css +17 -0
  181. package/public/css/components/density.css +11 -0
  182. package/public/css/components/floating-assistant-workspace-pages.css +55 -0
  183. package/public/css/components/folders.css +5 -0
  184. package/public/css/components/full-height-left-assistant-sidebar-all-pages.css +39 -0
  185. package/public/css/components/full-height-right-actions-rail-pages-that-have-actions.css +267 -0
  186. package/public/css/components/grid-and-list.css +203 -0
  187. package/public/css/components/help-page.css +36 -0
  188. package/public/css/components/hero.css +16 -0
  189. package/public/css/components/images.css +70 -0
  190. package/public/css/components/layout-rhythm-and-spacing-polish.css +139 -0
  191. package/public/css/components/mic-on-any-field-that-wants-typing.css +416 -0
  192. package/public/css/components/modal-fit.css +113 -0
  193. package/public/css/components/page-width.css +23 -0
  194. package/public/css/components/plugins-page.css +9 -0
  195. package/public/css/components/project-cards.css +256 -0
  196. package/public/css/components/project-rows.css +390 -0
  197. package/public/css/components/reordering-the-workspace-cards.css +85 -0
  198. package/public/css/components/responsive-home-and-the-workspace-pages.css +457 -0
  199. package/public/css/components/running-commands-as-toasts.css +327 -0
  200. package/public/css/components/settings-editor-core-config-yml.css +828 -0
  201. package/public/css/components/short-results-as-a-toast-at-the-top.css +203 -0
  202. package/public/css/components/sound-player.css +84 -0
  203. package/public/css/components/suggest-agents-skills-commands.css +112 -0
  204. package/public/css/components/tab-modal.css +142 -0
  205. package/public/css/components/terminals.css +244 -0
  206. package/public/css/components/test-report-media-one-file-per-row-paged.css +144 -0
  207. package/public/css/components/top-toolbar-logo-breadcrumb.css +107 -0
  208. package/public/css/components/video-edit.css +6 -0
  209. package/public/css/components/web-ide-open-with.css +23 -0
  210. package/public/css/components/working-indicators.css +158 -0
  211. package/public/css/components/workspace-cards.css +79 -0
  212. package/public/favicon.ico +0 -0
  213. package/public/favicon.png +0 -0
  214. package/public/htmx.min.js +1 -0
  215. package/public/logo.png +0 -0
  216. package/public/media-player.js +215 -0
  217. package/public/mic.js +255 -0
  218. package/public/narration.js +110 -0
  219. package/public/palette.js +278 -0
  220. package/public/sortable.js +251 -0
  221. package/public/suggest.js +337 -0
  222. package/public/terminal.js +903 -0
  223. package/public/themes/default/theme.css +267 -0
  224. package/public/themes/ember/theme.css +180 -0
  225. package/public/themes/press/theme.css +290 -0
  226. package/public/themes/slate/theme.css +182 -0
  227. package/public/touch-icon.png +0 -0
  228. package/public/ui.js +2721 -0
  229. package/public/vendor/ace/LICENSE.txt +24 -0
  230. package/public/vendor/ace/ace.js +23 -0
  231. package/public/vendor/ace/ext-language_tools.js +8 -0
  232. package/public/vendor/ace/ext-searchbox.js +8 -0
  233. package/public/vendor/ace/mode-css.js +8 -0
  234. package/public/vendor/ace/mode-html.js +8 -0
  235. package/public/vendor/ace/mode-javascript.js +8 -0
  236. package/public/vendor/ace/mode-markdown.js +8 -0
  237. package/public/vendor/ace/mode-sh.js +8 -0
  238. package/public/vendor/ace/mode-yaml.js +8 -0
  239. package/public/vendor/ace/theme-textmate.js +8 -0
  240. package/public/vendor/ace/theme-tomorrow_night.js +8 -0
  241. package/public/vendor/ace/worker-yaml.js +1 -0
  242. package/public/vendor/deep-chat.LICENSE +21 -0
  243. package/public/vendor/deep-chat.bundle.js +1 -0
  244. package/public/vendor/fonts/dm-serif-text-LICENSE.txt +93 -0
  245. package/public/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  246. package/public/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  247. package/public/vendor/fonts/figtree-LICENSE.txt +93 -0
  248. package/public/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  249. package/public/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  250. package/public/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  251. package/public/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  252. package/public/vendor/fonts/jetbrains-mono-LICENSE.txt +93 -0
  253. package/public/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  254. package/public/vendor/fonts/ubuntu-mono-LICENSE.txt +98 -0
  255. package/public/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  256. package/public/vendor/tabler-LICENSE.txt +21 -0
  257. package/public/vendor/tabler-sprite.svg +1 -0
  258. package/public/vendor/three/LICENSE +21 -0
  259. package/public/vendor/three/addons/loaders/GLTFLoader.js +4860 -0
  260. package/public/vendor/three/addons/utils/BufferGeometryUtils.js +1501 -0
  261. package/public/vendor/three/addons/utils/SkeletonUtils.js +496 -0
  262. package/public/vendor/three/three.core.min.js +6 -0
  263. package/public/vendor/three/three.module.min.js +6 -0
  264. package/public/vendor/three-vrm/LICENSE +22 -0
  265. package/public/vendor/three-vrm/three-vrm.module.min.js +983 -0
  266. package/public/vendor/uikit-icons.min.js +1 -0
  267. package/public/vendor/uikit.min.css +1 -0
  268. package/public/vendor/uikit.min.js +1 -0
  269. package/public/vendor/webship-icons/ui/bookmark-outlined.svg +1 -0
  270. package/public/vendor/webship-icons/ui/headset.svg +1 -0
  271. package/public/vendor/webship-icons/ui/menu.svg +1 -0
  272. package/public/vendor/webship-icons/ui/pause.svg +1 -0
  273. package/public/vendor/webship-icons/ui/play.svg +1 -0
  274. package/public/vendor/webship-icons/ui/share.svg +1 -0
  275. package/public/vendor/webship-icons/ui/sound-off.svg +1 -0
  276. package/public/vendor/webship-icons/ui/sound-on.svg +1 -0
  277. package/public/vendor/webship-icons/ui/stop.svg +1 -0
  278. package/public/vendor/xterm/addon-fit.js +2 -0
  279. package/public/vendor/xterm/addon-unicode11.js +2 -0
  280. package/public/vendor/xterm/addon-web-links.js +2 -0
  281. package/public/vendor/xterm/xterm.css +218 -0
  282. package/public/vendor/xterm/xterm.js +2 -0
  283. package/rag.js +169 -0
  284. package/reader.js +132 -0
  285. package/repo-blobs.js +66 -0
  286. package/report-pdf.js +337 -0
  287. package/row-layout.js +1440 -0
  288. package/screenrc +37 -0
  289. package/screens.js +1063 -0
  290. package/scripts/code-server-start.sh +190 -0
  291. package/scripts/images-claude-finish.js +107 -0
  292. package/scripts/screen-host.sh +114 -0
  293. package/scripts/screen-runner.js +362 -0
  294. package/scripts/screen-then.js +47 -0
  295. package/scripts/update.sh +350 -0
  296. package/server.js +8140 -0
  297. package/settings-file.js +423 -0
  298. package/speech.js +297 -0
  299. package/tab-modal.js +75 -0
  300. package/test-reports.js +260 -0
  301. package/themes.js +381 -0
  302. package/transcripts.js +168 -0
  303. package/urls.js +13 -0
  304. package/validate.js +18 -0
  305. package/version-check.js +94 -0
  306. package/video-edit.js +588 -0
  307. package/waiting.js +85 -0
  308. package/workspace-card-tools.js +41 -0
  309. package/workspace-delete.js +139 -0
  310. package/workspace-types.js +85 -0
  311. package/workspaces.js +557 -0
@@ -0,0 +1,684 @@
1
+ # Webship Workspace: reference
2
+
3
+ > **The full reference.** Every command, every flag and how the tooling is built. The short introduction is the
4
+ > [README](../../../README.md); the day-one walkthrough is the [user guide](USER-GUIDE.md). Paths in this file
5
+ > are relative to the repository root (`~/workspace`).
6
+
7
+
8
+ The Webship team's Drupal development workspace: manage the base-code development work cycle for Webship's distributions, recipes, profiles, and starter-kit templates on top of [DDEV](https://ddev.com) — no host-level Apache, PHP, or MySQL required.
9
+
10
+ Ships builders for Webship plus plain Drupal:
11
+
12
+ | Distribution | Versions | Project template |
13
+ | --- | --- | --- |
14
+ | [Webship](https://www.drupal.org/project/webship) | 9.1, 9.2, 10.0, 10.1, 11.0 (`x` + pinned) | `drupal/webship_project` |
15
+ | [Website Starter](https://www.drupal.org/project/website_starter) · [Haven](https://www.drupal.org/project/haven) site templates | 1.0.x on Webship 11 | `drupal/webship_project` + the recipe |
16
+ | [Drupal](https://www.drupal.org/project/drupal) | latest 11 | `drupal/recommended-project` |
17
+ | [Drupal CMS](https://www.drupal.org/project/cms) | latest 2 | `drupal/cms` |
18
+
19
+ ## Layout
20
+
21
+ Every folder in `~/workspace` is a **workspace**: its own `cmd-*.sh` commands and its own page in the
22
+ dashboard. A builder run from one creates a project in a subdirectory of it, with its own DDEV
23
+ containers and its own database — nothing is shared on the host.
24
+
25
+ | | |
26
+ | --- | --- |
27
+ | **Sites you build** | `dev` `test` `sandboxes` `demos` `projects` |
28
+ | **Code we maintain**, as git clones | `products` `modules` `themes` `profiles` `recipes` `site-templates` |
29
+ | **AI items** — drafts that install into the Claude Code CLI | `ai-agents` `ai-skills` `ai-commands` `ai-prompts` |
30
+ | **Documents** | `docs` `worklogs` `specs` `videos` `audio` |
31
+ | **Services** | `designs` (Penpot) `rag` (Milvus) `graphs` (graphify) |
32
+
33
+ `core/` holds the whole build chain — `core/scripts` (the libraries every `cmd-*.sh` sources) and
34
+ `core/config` (`settings.yml` plus one file per workspace). The `workspaces:` list in `settings.yml`
35
+ is what registers a folder; adding one is two entries and a directory, and the dashboard picks it up
36
+ with no restart. Settings → `workspaces:` adds one from the browser, and deletes one that nothing uses
37
+ after asking three times.
38
+
39
+ ## Get started
40
+
41
+ Four steps, in this order. Everything after them can be done from the dashboard.
42
+
43
+ ### 1. Get the files
44
+
45
+ Open **[github.com/webship/workspace](https://github.com/webship/workspace)** (sign in — it is a
46
+ private repository), switch the branch selector to **`1.0.x`**, then **Code ▾ → Download ZIP**.
47
+
48
+ Unzip it and rename the extracted `workspace-1.0.x` folder to **`workspace`** in your home
49
+ directory, so you end up with `~/workspace`.
50
+
51
+ ### 2. Install the tools
52
+
53
+ ```
54
+ bash ~/workspace/core/scripts/install.sh
55
+ ```
56
+
57
+ It reports what is missing and installs it — everything the workspace needs, and nothing else:
58
+
59
+ | | |
60
+ |---|---|
61
+ | Runs the sites | Docker, [DDEV](https://ddev.readthedocs.io/en/stable/users/install/) |
62
+ | Runs the tooling | git, gh, Node.js (npm, npx — also the [skills.sh](https://skills.sh) CLI), jq, rsync, python3, gawk, sed, screen |
63
+ | Runs the agents | [Claude Code](https://code.claude.com/docs/en/quickstart) |
64
+ | Trusted https | mkcert, libnss3-tools / nss-tools |
65
+ | Shell and build basics | curl, wget, unzip, zip, p7zip, diffutils, vim, links, lynx, htop, gcc, g++, make |
66
+ | Pipelines locally | gitlab-ci-local |
67
+
68
+ It also **trusts the local https certificates** — every workspace URL is https, and without that
69
+ trust the browser refuses each one with `ERR_CERT_AUTHORITY_INVALID`. Snap browsers each get their
70
+ own store; see `core/scripts/trust-https.sh`.
71
+
72
+ This script is the only installer there is: it lives here, in this repository, so what a machine
73
+ gets is whatever this checkout says. Nothing is fetched from another repository to bootstrap it.
74
+
75
+ Idempotent, so it is also the update path. `--check` reports and changes nothing; `-y` skips the
76
+ per-group prompts. It installs nothing at the OS level beyond those tools and writes nothing to
77
+ your `~/.bashrc`.
78
+
79
+ **On a fresh Ubuntu, one thing needs you afterwards.** Installing Docker adds you to the
80
+ `docker` group, and a group is applied at **login** — so the shell you ran the installer in still
81
+ cannot reach Docker, and step 3 would fail with:
82
+
83
+ ```
84
+ Docker error: permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
85
+ ```
86
+
87
+ That is a session that predates a group, not a broken tool. **Log out and back in.** The installer
88
+ tries Docker for real at the end and tells you which of these you are in:
89
+
90
+ ```
91
+ Next steps:
92
+ 1. LOG OUT AND BACK IN. You are in the docker group, but a group only
93
+ applies at login, so this shell still cannot reach Docker …
94
+ 2. gh auth login
95
+ 3. cd core/workspace-app && ddev start
96
+ 4. Open https://workspace.ddev.site
97
+ ```
98
+
99
+ Then sign in to GitHub, which the release and PR tooling needs:
100
+
101
+ ```
102
+ gh auth login
103
+ ```
104
+
105
+ ### 3. Start the dashboard
106
+
107
+ ```
108
+ cd ~/workspace/core/workspace-app
109
+ ddev start
110
+ ```
111
+
112
+ The first start does three things for you, so a fresh machine needs no per-machine edits:
113
+
114
+ - **Builds the container** with the docker group this host actually uses, so the dashboard can see
115
+ what Docker is doing. Get this wrong and every project reads as Stopped forever while running
116
+ perfectly well — `bash core/scripts/docker-gid.sh --check` reports it, and step 2 already set it.
117
+ - **Trusts the https certificate** through the project's own `post-start` hook, and names any
118
+ browser that was open before the CA arrived (a trust store is read once, at startup).
119
+ - **Publishes every workspace hostname**, so `<project>.<workspace>.workspace.ddev.site` resolves
120
+ without touching `/etc/hosts`.
121
+
122
+ ### 4. Open it
123
+
124
+ **https://workspace.ddev.site** — and build your first site from the **Build** form.
125
+
126
+ That is the whole setup. Building, starting, stopping, backing up, restoring and removing projects
127
+ are all done from there.
128
+
129
+ ### Make it come back after a reboot
130
+
131
+ Worth doing once, because nothing else will:
132
+
133
+ ```
134
+ bash ~/workspace/core/scripts/autostart.sh --enable
135
+ sudo loginctl enable-linger $USER
136
+ ```
137
+
138
+ A DDEV project's containers carry `restart: no`, so after a reboot the dashboard is simply not
139
+ running and **every workspace URL answers 404** from a router with nothing behind it. That is not
140
+ a fault to debug; it is the default. `--enable` installs a systemd *user* service that runs
141
+ `ddev start` once Docker is up. The `enable-linger` line is the difference between starting at
142
+ **boot** and starting when you next log in, and it is the one step that needs your password.
143
+
144
+ It also starts the **screen runner** each time, which is what the Screens page and every
145
+ item-sync button use. Sessions run on your machine rather than in the dashboard's container — so
146
+ `screen -r <session>` works in your own terminal, and a browser an agent drives opens on your
147
+ desktop — and a container cannot start a host process. Without the boot service, start it once with
148
+ `bash core/workspace-app/scripts/screen-host.sh --start`.
149
+
150
+ Check it any time with `bash core/scripts/autostart.sh` — it prints the service state, whether
151
+ lingering is on, and exactly which projects will come back.
152
+
153
+ **Which projects come back** is `dashboard.autostart` in `core/config/settings.yml`. It takes a
154
+ word, not just a list, because a hand-kept list is wrong within a week — you build a project and
155
+ never think about this file again:
156
+
157
+ | Value | What starts |
158
+ |---|---|
159
+ | `workspace` | Just the dashboard. The default: boots in seconds, and everything else is one click away from it. |
160
+ | `running` | Whatever was running when the machine last shut down cleanly. What most people mean by "bring my work back". |
161
+ | `all` | Every project DDEV has registered. Honest about the cost — each one is a web + db container competing for RAM at boot. |
162
+ | `dev1,test2` | Exactly those, in that order. |
163
+
164
+ The dashboard is always started first and is always included, whatever the value says: a boot that
165
+ brings back four sites and no dashboard is worse than one that brings back only the dashboard.
166
+
167
+ ### Notes on the four steps
168
+
169
+ - **Platforms.** Linux (Debian/Ubuntu apt, Fedora dnf) and macOS ([Homebrew](https://brew.sh);
170
+ Docker via Docker Desktop or OrbStack) install directly. On Windows, do all of this inside
171
+ [WSL2](https://learn.microsoft.com/windows/wsl/install) — `wsl --install -d Ubuntu`, reboot, then
172
+ step 2 in Ubuntu; the script detects a native Windows shell and says exactly this.
173
+ - **The location is yours.** Every script finds the tooling from its own path, so `/srv/workspace`,
174
+ `~/Sites/webship` or a second copy beside the first all work. The folder name only decides whether
175
+ the URLs in this README match what you see.
176
+ - **Jira (optional).** The JIRA button on a project row, the tasks modal and the Jira agents all
177
+ drive the **Teamwork Graph CLI**, `twg` — [teamworkgraph.com/cli](https://teamworkgraph.com/cli) ·
178
+ [developer.atlassian.com/cloud/twg-cli](https://developer.atlassian.com/cloud/twg-cli/). Step 2
179
+ offers to install it; by hand it is:
180
+ ```bash
181
+ curl -fsSL --retry 2 https://teamwork-graph.atlassian.com/cli/install | bash
182
+ ~/.local/bin/twg doctor # signs you in through the browser, then checks the setup
183
+ ```
184
+ It installs into `~/.local/bin` and holds one OAuth session per person, which is why it is
185
+ offered rather than assumed. Then fill in `jira:` in `core/config/settings.yml` (`site`,
186
+ `account`, `default_project`) and `ddev restart` so the dashboard can see the binary.
187
+
188
+ Configured Jira but no button? The button is there, disabled, and its tooltip says which half is
189
+ missing — the settings and the CLI are separate things.
190
+ - **https, if a browser still complains.** A browser reads its trust store **once, at startup**,
191
+ so one that was already open when the CA arrived keeps refusing every workspace URL — a reload
192
+ or a cache clear will not do it. Step 2 and every `ddev start` name the browsers in that state
193
+ and how to restart each without losing tabs (`chrome://restart`, `brave://restart`).
194
+ `bash core/scripts/trust-https.sh` reports and fixes the stores themselves, and
195
+ `--restart-browsers` closes the stale ones for you. A snap browser (Brave, Chromium) is confined
196
+ to its own store, so it needs the CA separately from a packaged Chrome.
197
+ - **Changing the tooling?** Keep a git clone for that, separate from the live tree above, and open
198
+ pull requests from it:
199
+ ```
200
+ git clone --branch '1.0.x' https://github.com/webship/workspace.git ~/workspace/products/workspace
201
+ ```
202
+ After a PR merges, `bash core/scripts/sync-tooling.sh --pull` copies the scripts, the
203
+ dashboard and the per-workspace settings into the live tree — and deliberately leaves
204
+ `core/config/settings.yml` alone, since that file holds your real account details and API keys.
205
+
206
+ ### Update — same place as the install
207
+
208
+ Installing brings the machine up to date; updating keeps it there. One command per thing that
209
+ moves, and neither touches `core/config/settings.yml`, which holds your real account details and
210
+ API keys:
211
+
212
+ | What | Command | What it does |
213
+ | --- | --- | --- |
214
+ | The tools | `bash core/scripts/install.sh --check` | Reports what is missing, then installs it without `--check`. Idempotent, so it is also the update path for git, gh, Node.js, Docker, DDEV and Claude Code. |
215
+ | The dashboard app, and the prompt templates | `bash core/workspace-app/scripts/update.sh --check` | Compares the installed version with the newest release; without `--check` it updates to it. |
216
+ | The `cmd-*.sh` tooling (clone only) | `bash core/scripts/sync-tooling.sh --pull` | Copies the scripts, the dashboard and the per-workspace settings out of the clone into the live tree. |
217
+
218
+ The dashboard updater is self-contained — plain bash, `curl`/`gh`, `tar` and `rsync`, no workspace
219
+ bootstrap — because it has to keep working while replacing the tooling around it:
220
+
221
+ ```
222
+ bash core/workspace-app/scripts/update.sh --check # installed vs the newest release
223
+ bash core/workspace-app/scripts/update.sh # update to it
224
+ bash core/workspace-app/scripts/update.sh --tag 1.0.0 # pin a release
225
+ bash core/workspace-app/scripts/update.sh --dry-run # itemize, change nothing
226
+ ```
227
+
228
+ Runtime state survives an update: `node_modules` is kept unless `package.json` changed (then
229
+ `npm install` runs in DDEV), and `actions.log`, screenshots and test reports stay where they are.
230
+
231
+ It also brings the release's **prompt templates** into `prompts/`, because the New screen form and
232
+ the Jira verbs offer them — a release that improves a prompt is no use if the prompt never reaches
233
+ the machine. That folder is where prompts are written as well as installed, so a prompt *you* have
234
+ edited is never overwritten:
235
+
236
+ ```
237
+ new prompt: webship-canvas-component.md
238
+ updated: review.md
239
+ yours, kept: explain.md
240
+ Prompts: 1 new, 1 updated, 1 left as yours.
241
+ ```
242
+
243
+ Edited means edited, not "differs from the new release": each update records the checksum of every
244
+ prompt as it shipped, so one still matching the installed version is refreshed and only one you
245
+ changed is kept. Add `--force-prompts` to take the release's copy of those too, or `--no-prompts`
246
+ to skip the step.
247
+
248
+ ### First build
249
+
250
+ A build is one command — or the **Build** form on the dashboard:
251
+
252
+ ```
253
+ cd ~/workspace/dev
254
+ bash cmd-webship11-0-x-project.sh webship11c1 --install
255
+ ```
256
+
257
+ ## Settings
258
+
259
+ Set your own webmaster account and any extras in the settings file — either from the dashboard's
260
+ **Settings** page (https://workspace.ddev.site/settings), or by editing it directly:
261
+
262
+ ```
263
+ vim ~/workspace/core/config/settings.yml
264
+ ```
265
+
266
+ ```yaml
267
+ host: 127.0.0.1
268
+ web: http://127.0.0.1/core
269
+ protocol: http
270
+ account:
271
+ name: webmaster
272
+ pass: CHANGE_ME
273
+ mail: info@webship.co
274
+ contributor:
275
+ name: CHANGE_ME # who commits/files issues from this machine (#32)
276
+ email: CHANGE_ME
277
+ ai_providers: # keys for the AI stack of the sites you build
278
+ openai:
279
+ api_key: sk-change-this
280
+ anthropic:
281
+ api_key: sk-change-this
282
+ gemini:
283
+ api_key: sk-change-this
284
+ workspaces:
285
+ - products
286
+ - dev
287
+ - test
288
+ - demos
289
+ - sandboxes
290
+ - profiles
291
+ - modules
292
+ - themes
293
+ - libraries
294
+ - recipes
295
+ - components
296
+ - agents
297
+ - skills
298
+ - prompts
299
+ - docs
300
+ ```
301
+
302
+ Keep real passwords and API keys in your own copy — never commit them back.
303
+
304
+ `ai_providers` feeds the AI stack of the sites you build: after an install, `set_ai_provider_keys` stores each real key on the site through the Key module and points the matching `ai_provider_<name>` module at it (Drupal CMS AI, Webship AI). Placeholders are skipped, values travel over STDIN (never a command line), and everything runs inside the project's own DDEV containers.
305
+
306
+ No paths are listed: `root`, `path`, `scripts`, `config` and `backups` are derived from the checkout (`backups/` is created on first run). Add them to `settings.yml`, or export `WORKSPACE_ROOT` / `_PATH` / `_SCRIPTS` / `_CONFIG`, only to point the tooling somewhere else.
307
+
308
+ There is no `database:` section either: every DDEV project manages its own isolated database, and DDEV's `settings.ddev.php` owns the connection.
309
+
310
+ ## Web dashboard (workspace.ddev.site)
311
+
312
+ A visual dashboard + AI assistant for the whole workspace lives in
313
+ `core/workspace-app` (basic Node.js + HTMX, served through DDEV):
314
+
315
+ ```
316
+ cd ~/workspace/core/workspace-app
317
+ ddev start
318
+ ```
319
+
320
+ Then open **https://workspace.ddev.site** — workspace cards with live project
321
+ counts, per-workspace pages (`/dev`, `/test`, …) with build/backup/remove
322
+ actions, and an AI assistant on every page. See
323
+ [`core/workspace-app/README.md`](../../../core/workspace-app/README.md) for details.
324
+
325
+ ### Domains
326
+
327
+ The dashboard manages a hierarchical domain scheme by default:
328
+
329
+ - `https://workspace.ddev.site` — the dashboard home
330
+ - `https://<workspace>.workspace.ddev.site` — that workspace's page
331
+ (e.g. `dev.workspace.ddev.site`, `test.workspace.ddev.site`)
332
+ - `https://<project>.<workspace>.workspace.ddev.site` — the project's real
333
+ site (e.g. `webship11demo.dev.workspace.ddev.site`); the **Launch**
334
+ button uses these, and the canonical `https://<project>.ddev.site`
335
+ always keeps working
336
+
337
+ No per-project configuration is needed — wildcard hostnames + an nginx
338
+ tier in `core/workspace-app/.ddev/` route everything with valid TLS.
339
+
340
+ All navigation links and the Launch buttons follow this scheme by
341
+ default, and they are all built from **one field** — `hub_domain:` in
342
+ `core/config/settings.yml`. Its first label is the DDEV project name and
343
+ the rest is the project TLD.
344
+
345
+ Change it through the command, not by hand, so the dashboard's links and
346
+ DDEV's routing cannot drift apart:
347
+
348
+ ```
349
+ cd ~/workspace/products
350
+ bash core/scripts/autostart.sh --domain workspace.ddev.site
351
+ ```
352
+
353
+ The TLD is set **globally**, with `ddev config global --project-tld`, so
354
+ one field moves everything together: the dashboard, every workspace page
355
+ under it, and each project's own canonical `<project>.<tld>`. No project
356
+ in this tree carries its own `project_tld`, so they all inherit it — and
357
+ any that does is named in the output as one that will not follow.
358
+
359
+ Projects already running keep answering on the old name until they
360
+ restart, since their containers were started against the previous TLD.
361
+ They are listed rather than restarted for you (restarting a site
362
+ mid-build is how a half-written database happens); `--restart-all` does
363
+ it when nothing is in flight.
364
+
365
+ It refuses two changes rather than half-applying them:
366
+
367
+ - **A TLD with no wildcard DNS.** `*.ddev.site` resolves to `127.0.0.1`
368
+ *at any depth*, which is the only reason the nested scheme above works
369
+ with an empty `/etc/hosts`. `/etc/hosts` cannot hold wildcards, so a
370
+ custom TLD needs one hand-added line per hostname, with sudo, forever —
371
+ and every new project 404s until someone adds it.
372
+ - **A first label that differs from the project name.** That is a rename,
373
+ not a domain change: DDEV keys its volumes on the project name, so it
374
+ strands the database and builds an empty one.
375
+
376
+ Both are allowed with `--force` once you mean them.
377
+
378
+ **On a Docker host or a dedicated server** the same system runs as one
379
+ shared **development workspace hub on a real domain** —
380
+ `workspace.example.com` → `dev.workspace.example.com` →
381
+ `myproject.dev.workspace.example.com`. There the custom TLD is the normal
382
+ case, not the exception, and the DNS check above passes on its own
383
+ because the record is real:
384
+
385
+ ```
386
+ # 1. Point *.workspace.example.com at the host in your DNS. One wildcard
387
+ # record covers every workspace and every project, forever.
388
+ # 2. Then:
389
+ cd ~/workspace/products
390
+ bash core/scripts/autostart.sh --domain workspace.example.com
391
+ ```
392
+
393
+ One more thing is needed there, and the command says so rather than
394
+ leaving you to find it: DDEV binds its router to `127.0.0.1`, so on a
395
+ server the ports are open, the DNS resolves, and every request from
396
+ outside times out with nothing in any log. Open it deliberately, with a
397
+ firewall in front:
398
+
399
+ ```
400
+ ddev config global --router-bind-all-interfaces=true
401
+ ```
402
+
403
+ That exposes **every** DDEV project on the machine, and they are dev
404
+ sites. Put it behind a firewall or a reverse proxy. `core/scripts/autostart.sh`
405
+ reports which of the two situations a machine is in, so you are never
406
+ guessing:
407
+
408
+ ```
409
+ reachable : this machine only (ddev.site, router on 127.0.0.1)
410
+ reachable : from the network — shared hub, router on all interfaces
411
+ ```
412
+
413
+ ### Examples
414
+
415
+ Things you can do from the dashboard UI:
416
+
417
+ - `/dev` → pick **"Webship 11.0.x"** from the Build dropdown, name it
418
+ `blog1`, press **Build** — and watch the real terminal output stream
419
+ until the site is installed at `https://blog1.ddev.site`.
420
+ - Press **Stop** / **Start** / **Launch** on any project row (buttons follow
421
+ the live DDEV status), **Backup** to archive it, or open
422
+ `/dev/backups` to **Restore** an archive (files + automatic DB import).
423
+ - `/agents` → **Generate with AI** → describe the agent you want; then
424
+ **Install** it into `~/.claude/agents/` for Claude Code.
425
+ - `/docs` → write a doc, press **PDF** or **HTML** to render it, or
426
+ **Screenshot a site** to capture any URL into `docs/`.
427
+
428
+ Things you can ask the Webship Workspace AI Assistant (it really does them):
429
+
430
+ - "Build a Webship 11 site named webship11c1"
431
+ - "Create a Drupal 11.4 site called d114test"
432
+ - "can you back up my webship11c1 site please, then show me the backups page"
433
+ - "what's running right now?"
434
+ - "Generate an agent that reviews cmd- scripts and save it as cmd-linter"
435
+ - "Write a doc about demo1 and make a PDF"
436
+
437
+
438
+ ## Building a project
439
+
440
+ ```
441
+ cd ~/workspace/dev/
442
+ bash cmd-webship11-0-x-project.sh webship11c1 --install --add-users
443
+ ```
444
+
445
+ ### Drupal and Drupal CMS, for example:
446
+ ```
447
+ cd ~/workspace/dev/
448
+ bash cmd-drupal11-recommended-project.sh d11c1 --install
449
+ bash cmd-drupalcms2-x-project.sh cms1 --install
450
+ ```
451
+
452
+ ### Drupal CMS site templates:
453
+ ```
454
+ cd ~/workspace/site-templates/
455
+ bash cmd-healthcare-project.sh healthcare1 --install
456
+ ```
457
+
458
+ Builds Drupal CMS, requires the template package, and with `--install` installs it (answering the installer's site-template step non-interactively), then prints both URLs:
459
+
460
+ ```
461
+ https://healthcare1.site-templates.workspace.ddev.site
462
+ https://healthcare1.ddev.site
463
+ ```
464
+
465
+ One command per free template from [new.drupal.org/browse/site-templates](https://new.drupal.org/browse/site-templates): `healthcare`, `byte`, `haven`, `archimedes`, `everbright`, `provus_edu`, `convene`, `caresphere`, `convivial_gov`, `mercury_demo`, `local`, plus the four Webship ones below (`haven`, `website_starter`, `pulse`, `website_starter`). Without `--install` the build stops at the codebase and you pick the template in the browser installer. See [`site-templates/README.md`](../../../site-templates/README.md).
466
+
467
+ ### Webship site templates (Website Starter, Website Starter, Haven, Pulse)
468
+
469
+ The Drupal CMS installer lists all four (Website Starter's card is named "Webship") and downloads the package when one is picked, so a stock Drupal CMS is enough:
470
+
471
+ ```
472
+ ddev config --project-type=drupal11 --docroot=web
473
+ ddev composer create-project drupal/cms
474
+ ddev launch # then pick the template
475
+ ```
476
+
477
+ ```
478
+ cd ~/workspace/dev/
479
+ bash cmd-drupalcms2-x-website_starter1-0-0-project.sh website_starter1 # on Drupal CMS, released line, pick it in the browser
480
+ bash cmd-drupalcms2-x-website_starter1-0-0-project.sh website_starter1 --install # on Drupal CMS, released line, installed by drush
481
+ bash cmd-website_starter1-0-0-project.sh website_starter2 # on Webship ~11, released line
482
+ ```
483
+
484
+ Every folder that builds sites carries each template on both bases its project page publishes, one builder per line: `1-0-0` is the released template (`~1`) and `1-0-x` the development branch (`1.0.x-dev`). Pulse has `1-0-0` on Drupal CMS only; its Webship builders are `1-0-x`.
485
+
486
+ | | Drupal CMS — `cmd-drupalcms2-x-<template>1-0-?-project.sh` | Webship — `cmd-<template>1-0-?-project.sh` |
487
+ | --- | --- | --- |
488
+ | Base | `drupal/cms`, PHP 8.4 | `drupal/webship_project:~11` for the released lines, `11.0.x-dev` for the development lines, PHP 8.4 |
489
+ | Released line | nothing required, the installer fetches it; `--install` requires `drupal/<template>:~1`, the root's stability left as shipped | `drupal/<template>:~1` |
490
+ | Development line | `minimum-stability dev`, then `drupal/<template>:1.0.x-dev` | `drupal/<template>:1.0.x-dev` |
491
+
492
+ Without `--install` a builder stops at a codebase and prints the installer URL; with it the site template is installed non-interactively. A `composer` step that fails stops the build with the resolver output, rather than reporting the site as ready.
493
+
494
+ `test/` also carries `cmd-automated-testing-<template>1-0-?-project.sh` for Website Starter, Haven and Pulse, which add the Playwright suite on top of the same build.
495
+
496
+ Run any builder with `--help` for its full argument list.
497
+
498
+ Each of these scripts creates the project folder, runs `ddev config` + `ddev start`, then `ddev composer create-project` and `ddev drush site:install` inside that project's own containers.
499
+
500
+ ## Agents, skills and prompts: three sources
501
+
502
+ `agents/`, `skills/` and `prompts/` show what you have here, what is installed in `~/.claude` (what a Claude Code session actually loads), and what is in [webship/ai-agents](https://github.com/webship/ai-agents) — merged into one list, so it is obvious what is only a draft, only installed, or only shared.
503
+
504
+ ```
505
+ cd ~/workspace/ai-skills/
506
+ bash cmd-tools-ai-items.sh # the three counts
507
+ bash cmd-tools-ai-items.sh --list-remote # what the shared repo has
508
+ bash cmd-tools-ai-items.sh webship-install --fetch # repo -> ~/.claude (use it now)
509
+ bash cmd-tools-ai-items.sh webship-install --pull # repo -> here (read or edit it)
510
+ bash cmd-tools-ai-items.sh --pull --source claude # this machine -> here (webship-* only)
511
+ bash cmd-tools-ai-items.sh my-draft --install # here -> ~/.claude
512
+ ```
513
+
514
+ Omit the name to act on all of them. On the dashboard the same rows carry **installed** / **workspace only** / **shared** labels, and anything that exists only in the shared repo gets **Fetch to ~/.claude** and **Copy here to edit** buttons. Restart your Claude Code session to pick up a fetch.
515
+
516
+ Sharing the other way — a draft here becoming a PR on dev-ai-agents — stays a deliberate act through the issue/PR agents, never automatic.
517
+
518
+ ## GitLab CI locally (the green-gate)
519
+
520
+ Before pushing anything to git.drupalcode.org, the pipeline runs locally:
521
+
522
+ ```
523
+ cd ~/workspace/modules/
524
+ bash cmd-tools-gitlab-ci-local.sh <project> --doctor # setup check (installs the drupal-ci-local wrapper)
525
+ bash cmd-tools-gitlab-ci-local.sh <project> --list # what jobs exist
526
+ bash cmd-tools-gitlab-ci-local.sh <project> phpcs # one job
527
+ bash cmd-tools-gitlab-ci-local.sh <project> --slim # the project's .gitlab-ci-local.yml subset
528
+ ```
529
+
530
+ Green = push; red = fix first, never mask with `allow_failure` or `|| true`. Note: the **full** drupal/gitlab_templates pipeline currently cannot run under gitlab-ci-local at all ([firecow/gitlab-ci-local#1893](https://github.com/firecow/gitlab-ci-local/issues/1893) — the templates' `$variable`-in-regex rules crash its evaluator on every ref). The command detects that crash and explains it; until it's fixed upstream, `--slim` is the runnable path and template-only jobs gate on the remote pipeline.
531
+
532
+ ## Upsun: the cloud side of a project
533
+
534
+ Workspace projects run locally on DDEV and, when they graduate, on [Upsun](https://upsun.com) — client demos, sandboxes, the public Webship demo. `cmd-tools-upsun.sh` in the `projects`, `demos` and `sandboxes` workspaces is the bridge:
535
+
536
+ ```
537
+ cd ~/workspace/demos/
538
+ bash cmd-tools-upsun.sh dcms2b1 # status: linked ID, environments
539
+ bash cmd-tools-upsun.sh mydemo --link abc123xyz # upsun project:set-remote
540
+ bash cmd-tools-upsun.sh mydemo --init # scaffold .upsun/config.yaml
541
+ bash cmd-tools-upsun.sh mydemo --push # deploy the current branch
542
+ bash cmd-tools-upsun.sh mydemo --activities # last 10 Upsun activities
543
+ ```
544
+
545
+ The default is read-only status. `--link` refuses to silently relink a project that already points at a different Upsun ID (use `--force`), `--push` goes through upsun's own confirmation, and nothing here ever creates or deletes an Upsun project or environment. The `upsun` CLI must be installed and authenticated (`upsun auth:browser-login`); the tool tells you how if it is missing.
546
+
547
+ Upsun's official AI toolkit ([upsun/ai](https://github.com/upsun/ai), MIT) complements this: an `upsun` agent skill with per-framework `.upsun/config.yaml` starters (including Drupal), a Claude Code plugin (`/plugin marketplace add upsun/ai`), and an MCP server (`claude mcp add --transport http upsun https://mcp.upsun.com/mcp` — token via the `upsun-api-token` header, writes disabled by default). The LLM-readable doc index is [developer.upsun.com/llms.txt](https://developer.upsun.com/llms.txt). For CI, use an API token with `UPSUN_CLI_NO_INTERACTION=1`.
548
+
549
+ ## Syncing the tooling after a merged PR
550
+
551
+ Two checkouts exist: `~/workspace/products/workspace` is the git clone where commits and PRs happen, `~/workspace` is the live tree where projects are built. After a PR merges, pull the clone and copy the tooling across:
552
+
553
+ ```
554
+ cd ~/workspace/products/
555
+ bash core/scripts/sync-tooling.sh --pull
556
+ ```
557
+
558
+ That syncs `core/scripts`, the per-workspace settings files, `core/workspace-app`, and every workspace folder's `cmd-*.sh` / `README.md` / `*.code-workspace`; prunes commands the clone no longer ships; and runs the smoke test. Built project content is never touched. `--dry-run` shows what would change.
559
+
560
+ `core/config/settings.yml` is deliberately **not** synced — your copy holds the real account password and API keys, while the committed one ships `CHANGE_ME` placeholders. The command reports any workspace missing from your `workspaces:` list instead of editing the file.
561
+
562
+ If the dashboard's `.ddev/` config or Dockerfile changed, finish with `ddev restart` in `~/workspace/core/workspace-app`; for a `server.js` change alone, `docker exec ddev-workspace-web supervisorctl restart webextradaemons:workspace-app` is enough.
563
+
564
+ ## Knowledge graph of a project (graphify)
565
+
566
+ [graphify](https://github.com/Graphify-Labs/graphify) parses a project with tree-sitter into a knowledge graph you query instead of grepping — local, deterministic, no vector store. Every project workspace has a `cmd-tools-graphify.sh`:
567
+
568
+ ```
569
+ cd ~/workspace/dev/
570
+ bash cmd-tools-graphify.sh webship11c1
571
+ bash cmd-tools-graphify.sh webship11c1 --query "what does this controller depend on?"
572
+ ```
573
+
574
+ It writes `webship11c1/graphify-out/` — `graph.html` (clickable graph), `GRAPH_REPORT.md` (key concepts, suggested questions) and `graph.json` (queried by `graphify query|path|explain|god-nodes`). The default pass is code-only: local AST, no API key, nothing leaves the machine.
575
+
576
+ **What it covers on a Drupal project, honestly.** graphify maps PHP classes under `src/` — methods, `use` imports, `extends`, `implements`, trait `use`, and constructor-injected services — plus JS, SQL and Markdown. It does **not** cover hooks in `*.module` / `*.theme` / `*.install` (those extensions are not in its dispatch table), Twig templates, or the Drupal wiring YAML (`*.info.yml`, `*.services.yml`, `*.routing.yml`, `*.component.yml`, `recipe.yml`), and it has no PHP-attribute support, so plugin-id → class never appears. Useful for "what does this service depend on"; no use for "which route hits this controller". `--deep` adds a semantic pass over docs and PDFs but does not close that gap — it builds a parallel document graph.
577
+
578
+ Third-party trees (`vendor/`, `core/`, `contrib`, `libraries`, `node_modules`) are skipped by default. On a Webship 11 build that is the difference between 1,236 nodes in 11s and 20,618 nodes in 139s, 84% of them vendored JS. `--all` disables the skipping, `--exclude` adds more paths.
579
+
580
+ The CLI is installed on first use (`uv tool install graphifyy==0.9.28`), and it ships in the dashboard image. On the dashboard, every project row has a **Graph** button and, once a graph exists, **Open graph** and **Report** links. `graph.html` loads vis-network from unpkg.com, so it needs internet to render.
581
+
582
+ ## Second storage: workspaces on another disk
583
+
584
+ A workspace without DDEV projects can live on another disk as a symlink. Set the folder on that disk in `core/config/settings.yml` (per machine, never synced):
585
+
586
+ ```yaml
587
+ storage:
588
+ second: /mnt/workspace-storage/workspace
589
+ ```
590
+
591
+ ```
592
+ bash core/scripts/storage.sh # status: local or second storage, links, free space
593
+ bash core/scripts/storage.sh --move videos # copy, verify by checksum, swap to a symlink
594
+ bash core/scripts/storage.sh --remove-pre-move videos # free the kept local copy
595
+ bash core/scripts/storage.sh --back videos # the reverse; the drive copy is kept
596
+ bash core/scripts/storage.sh --mounts # let the dashboard container follow the links
597
+ bash core/scripts/storage.sh --relink --mounts # after the drive got a new mount point
598
+ ```
599
+
600
+ `--mounts` writes `core/workspace-app/.ddev/docker-compose.second-storage.yaml`; restart the dashboard (`ddev restart` in `core/workspace-app`) when it says so, once no build is running. `--dry-run` says what would happen. On a `/run/media` automount, the status prints an fstab line by UUID to a fixed mount point: run it with sudo yourself.
601
+
602
+ ## Removing / backing up a project
603
+
604
+ ```
605
+ cd ~/workspace/dev/
606
+ bash cmd-tools-remove.sh myproject
607
+ bash cmd-tools-backup-dev.sh myproject
608
+ ```
609
+
610
+ `cmd-tools-remove.sh` runs `ddev delete -y -O` before removing the folder. `cmd-tools-backup-*.sh` tars the project folder and runs `ddev export-db` for the database dump — no host MySQL client involved.
611
+
612
+ ## Add your own commands
613
+
614
+ The dashboard's **Commands** page (`/commands`, in the rail) is where commands are added, and most need no script at all.
615
+
616
+ ### A command
617
+
618
+ A command is one line and what it runs on: a workspace folder, a project, a folder or a file. Add one from **Commands → New**, or in `core/config/commands.yml`. The built-in commands ship in `core/workspace-app/commands/defaults.yml`, and one of yours with a built-in id replaces it.
619
+
620
+ ```yaml
621
+ commands:
622
+ - id: rebuild-caches
623
+ label: Rebuild caches
624
+ group: Drupal
625
+ on: project # workspace | project | folder | file
626
+ when: [ddev, composer] # which folders it is for; ! negates, workspace:<name> names one
627
+ run: ddev drush cr
628
+ - id: php-lint
629
+ label: Check PHP syntax
630
+ on: file
631
+ when: [ddev]
632
+ run: ddev exec --dir /var/www/html php -l {{file}}
633
+ - id: switch-to-branch
634
+ label: Switch to a branch
635
+ on: project
636
+ when: [git]
637
+ confirm: true # ask in a dialog before it runs
638
+ options:
639
+ - { name: branch, value: main, regex: '^[A-Za-z0-9._/-]+$', required: true }
640
+ run: git fetch --prune && git switch {{opt.branch}}
641
+ ```
642
+
643
+ A line gets values only as `{{workspace}}`, `{{project}}`, `{{folder}}`, `{{file}}`, `{{path}}` and `{{opt.<name>}}`, each single-quoted for you, and it cannot contain `$` or a backtick. **Run** asks for the workspace, the project and the folder or file, and runs the command in a screen session. **Definition** shows your commands as YAML, and **Check** says what loading a pasted list would change.
644
+
645
+ ### A script, when one line is not enough
646
+
647
+ **Commands → New → New script** (or **Generate a script with AI**) scaffolds a `cmd-*.sh` in the workspace folder it belongs to, and so does the CLI:
648
+
649
+ ```bash
650
+ bash ~/workspace/commands/cmd-tools-commands.sh --new cmd-tools-list-sites.sh --workspace dev --label "List Sites"
651
+ ```
652
+
653
+ The scaffold already has what every command needs:
654
+ - the `# workspace-name:` header that the smoke test and the Build dropdown read;
655
+ - a bootstrap that finds `core/scripts` from the script's own path, so a fresh clone needs no setup;
656
+ - the workspace's settings;
657
+ - an `argparse` block.
658
+
659
+ Write what it does (DDEV only: `ddev composer`, `ddev drush`, `ddev export-db`), then check it:
660
+
661
+ ```bash
662
+ bash ~/workspace/core/scripts/tests/cmd-smoke-test.sh
663
+ ```
664
+
665
+ Settings of your own go in YAML and are read as shell variables: `eval $(parse_yaml path/to/file.yml)` turns `distribution.webroot` into `$distribution_webroot`. The scripts under `dev/`, `themes/` and `profiles/` are the examples to read. Every script is listed on the Commands page with a page of its own, where **Use in a command** makes a command that calls it (`script: dev/cmd-tools-list-sites.sh`, `args: '{{project}}'`).
666
+
667
+ ### On a project row
668
+
669
+ A command can go on any button of the project rows: **Add to a button** on its page, or **Use a command** under **Commands → Buttons → Add an action**. The steps of an action, kept in `core/config/row-actions.yml`, can mix command lines with `use: <command id>`:
670
+
671
+ ```yaml
672
+ custom:
673
+ - id: refresh-the-site
674
+ label: Refresh the site
675
+ button: ddev
676
+ group: Drupal
677
+ when: [ddev, composer]
678
+ steps:
679
+ - use: git-switch-branch
680
+ - ddev composer install
681
+ - use: drush-update-database
682
+ ```
683
+
684
+ A used command runs on the row's project, asks for its own options, and keeps its own `when`.