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

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 (307) 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/images-svg.js +33 -0
  16. package/actions/images.js +145 -0
  17. package/actions/navigation.js +190 -0
  18. package/actions/node.js +222 -0
  19. package/actions/notes.js +55 -0
  20. package/actions/plugins.js +36 -0
  21. package/actions/project-make.js +28 -0
  22. package/actions/rag.js +202 -0
  23. package/actions/row-actions.js +262 -0
  24. package/actions/screens.js +270 -0
  25. package/actions/testing.js +121 -0
  26. package/actions/video-edit.js +54 -0
  27. package/ai-inventory.js +182 -0
  28. package/ai-items.js +202 -0
  29. package/assistant-cast.js +176 -0
  30. package/assistant-suggestions.js +91 -0
  31. package/assistant.js +484 -0
  32. package/audio-voice.js +528 -0
  33. package/auth-google.js +260 -0
  34. package/build-ai.js +52 -0
  35. package/build-order-page.js +191 -0
  36. package/build-order.js +313 -0
  37. package/builder-args.js +117 -0
  38. package/changelog.js +302 -0
  39. package/chat-threads.js +134 -0
  40. package/checks.js +105 -0
  41. package/claude-args.js +282 -0
  42. package/claude-sessions.js +129 -0
  43. package/command-author.js +117 -0
  44. package/command-block.js +20 -0
  45. package/commands/defaults.yml +159 -0
  46. package/commands-page.js +460 -0
  47. package/commands.js +568 -0
  48. package/cucumber.js +43 -0
  49. package/ddev-addons-page.js +124 -0
  50. package/ddev-addons.js +154 -0
  51. package/ddev-custom-commands.js +96 -0
  52. package/ddev-domains.js +84 -0
  53. package/ddev-import.js +93 -0
  54. package/ddev-mail-page.js +167 -0
  55. package/ddev-mail.js +110 -0
  56. package/ddev-options.js +145 -0
  57. package/ddev-snapshots.js +126 -0
  58. package/ddev.js +145 -0
  59. package/docs/DEVELOPER-GUIDE.md +224 -0
  60. package/docs/README.md +252 -0
  61. package/docs/REFERENCE.md +684 -0
  62. package/docs/USER-GUIDE.md +697 -0
  63. package/docs/screenshots/assistant/avatar-board.png +0 -0
  64. package/docs/screenshots/assistant/avatar-coffee.png +0 -0
  65. package/docs/screenshots/assistant/avatar-colours.png +0 -0
  66. package/docs/screenshots/assistant/avatar-icons.png +0 -0
  67. package/docs/screenshots/assistant/avatar-moves.png +0 -0
  68. package/docs/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  69. package/docs/screenshots/assistant/avatar-settings-voices.png +0 -0
  70. package/docs/screenshots/assistant/avatar-status.png +0 -0
  71. package/docs/screenshots/create-card/create-card.png +0 -0
  72. package/docs/screenshots/create-card/start-ai.png +0 -0
  73. package/docs/screenshots/guide-clone-address.png +0 -0
  74. package/docs/screenshots/guide-clone-worktree-off.png +0 -0
  75. package/docs/screenshots/guide-clone-worktree-on.png +0 -0
  76. package/docs/screenshots/guide-ddev-menu.png +0 -0
  77. package/docs/screenshots/guide-domains.png +0 -0
  78. package/docs/screenshots/guide-home.png +0 -0
  79. package/docs/screenshots/guide-ide-menu.png +0 -0
  80. package/docs/screenshots/guide-remove-refusal.png +0 -0
  81. package/docs/screenshots/guide-rows-worktree.png +0 -0
  82. package/docs/screenshots/guide-screens.png +0 -0
  83. package/docs/screenshots/guide-settings.png +0 -0
  84. package/docs/screenshots/guide-workspace-page.png +0 -0
  85. package/docs/screenshots/home/card-settings.png +0 -0
  86. package/docs/screenshots/home/drag-reorder.png +0 -0
  87. package/docs/screenshots/home/edit-card.png +0 -0
  88. package/docs/screenshots/images/claude-code-image.png +0 -0
  89. package/docs/screenshots/images/edit-with-ai.png +0 -0
  90. package/docs/screenshots/images/gallery.png +0 -0
  91. package/docs/screenshots/images/generate-description.png +0 -0
  92. package/docs/screenshots/images/generate-plan.png +0 -0
  93. package/docs/screenshots/images/providers.png +0 -0
  94. package/docs/screenshots/images/svg-form.png +0 -0
  95. package/docs/screenshots/images/svg-result.png +0 -0
  96. package/docs/screenshots/images/upload.png +0 -0
  97. package/docs/screenshots/new-folder/2-form.png +0 -0
  98. package/docs/screenshots/new-folder/8-ddev-options.png +0 -0
  99. package/docs/screenshots/new-folder/files-folder.png +0 -0
  100. package/docs/screenshots/projects/ddev-custom-commands.png +0 -0
  101. package/docs/screenshots/projects/state-chip-stop.png +0 -0
  102. package/docs/screenshots/readme/dashboard-dark.png +0 -0
  103. package/docs/screenshots/readme/dashboard-light.png +0 -0
  104. package/docs/screenshots/readme/dev-dark.png +0 -0
  105. package/docs/screenshots/readme/dev-light.png +0 -0
  106. package/docs/screenshots/readme/docs-dark.png +0 -0
  107. package/docs/screenshots/readme/docs-light.png +0 -0
  108. package/docs/screenshots/readme/icons/ai.svg +4 -0
  109. package/docs/screenshots/readme/icons/code.svg +4 -0
  110. package/docs/screenshots/readme/icons/docs.svg +4 -0
  111. package/docs/screenshots/readme/icons/services.svg +4 -0
  112. package/docs/screenshots/readme/icons/sites.svg +4 -0
  113. package/docs/screenshots/readme/test-report-dark.png +0 -0
  114. package/docs/screenshots/readme/test-report-light.png +0 -0
  115. package/docs/screenshots/readme/test-scenarios.png +0 -0
  116. package/docs/screenshots/readme/test-steps.png +0 -0
  117. package/docs/screenshots/readme/videos-dark.png +0 -0
  118. package/docs/screenshots/readme/videos-light.png +0 -0
  119. package/docs/screenshots/screens-menu.png +0 -0
  120. package/docs/screenshots/screens-new.png +0 -0
  121. package/docs/screenshots/settings/auth-google-lists.png +0 -0
  122. package/docs/screenshots/settings/delete-1-row.png +0 -0
  123. package/docs/screenshots/settings/delete-2-confirm.png +0 -0
  124. package/docs/screenshots/settings/delete-3-type-name.png +0 -0
  125. package/docs/screenshots/settings/delete-4-type-yes.png +0 -0
  126. package/event-log.js +59 -0
  127. package/files-browser.js +247 -0
  128. package/folders.js +121 -0
  129. package/format.js +73 -0
  130. package/git-info.js +81 -0
  131. package/github-auth.js +62 -0
  132. package/html.js +10 -0
  133. package/icons.js +83 -0
  134. package/images-ai.js +447 -0
  135. package/images-create.js +226 -0
  136. package/images-page.js +417 -0
  137. package/images-records.js +39 -0
  138. package/images-render.js +36 -0
  139. package/images-svg-core.js +214 -0
  140. package/images-svg-sanitize.js +188 -0
  141. package/images-svg.js +241 -0
  142. package/images.js +270 -0
  143. package/jobs.js +430 -0
  144. package/linger.js +21 -0
  145. package/lists.js +292 -0
  146. package/media-info.js +136 -0
  147. package/navigation-page.js +264 -0
  148. package/navigation.js +394 -0
  149. package/notes.js +0 -0
  150. package/package.json +47 -4
  151. package/palette-index.js +46 -0
  152. package/playwright.config.js +23 -0
  153. package/plugin-install.js +239 -0
  154. package/plugin-loader.js +466 -0
  155. package/plugin-manifest.js +83 -0
  156. package/plugin-settings.js +44 -0
  157. package/plugins/workspace-notes/index.js +59 -0
  158. package/plugins-page.js +103 -0
  159. package/project-make.js +241 -0
  160. package/project-name.js +210 -0
  161. package/public/assistant-avatar-moves.js +646 -0
  162. package/public/assistant-avatar-ui.js +1223 -0
  163. package/public/assistant-avatar.js +530 -0
  164. package/public/avatar/druplicon.vrm +0 -0
  165. package/public/css/base.css +260 -0
  166. package/public/css/components/ai-assistant.css +263 -0
  167. package/public/css/components/assistant-avatar.css +279 -0
  168. package/public/css/components/assistant-views.css +198 -0
  169. package/public/css/components/build-order.css +74 -0
  170. package/public/css/components/clone-as-a-git-worktree.css +30 -0
  171. package/public/css/components/command-palette.css +61 -0
  172. package/public/css/components/commands-center.css +139 -0
  173. package/public/css/components/confirm-dialog.css +40 -0
  174. package/public/css/components/create-card.css +8 -0
  175. package/public/css/components/ddev-menu.css +23 -0
  176. package/public/css/components/deep-chat.css +17 -0
  177. package/public/css/components/density.css +11 -0
  178. package/public/css/components/floating-assistant-workspace-pages.css +55 -0
  179. package/public/css/components/folders.css +5 -0
  180. package/public/css/components/full-height-left-assistant-sidebar-all-pages.css +39 -0
  181. package/public/css/components/full-height-right-actions-rail-pages-that-have-actions.css +267 -0
  182. package/public/css/components/grid-and-list.css +203 -0
  183. package/public/css/components/help-page.css +36 -0
  184. package/public/css/components/hero.css +16 -0
  185. package/public/css/components/images.css +70 -0
  186. package/public/css/components/layout-rhythm-and-spacing-polish.css +139 -0
  187. package/public/css/components/mic-on-any-field-that-wants-typing.css +416 -0
  188. package/public/css/components/modal-fit.css +113 -0
  189. package/public/css/components/page-width.css +23 -0
  190. package/public/css/components/plugins-page.css +9 -0
  191. package/public/css/components/project-cards.css +256 -0
  192. package/public/css/components/project-rows.css +390 -0
  193. package/public/css/components/reordering-the-workspace-cards.css +85 -0
  194. package/public/css/components/responsive-home-and-the-workspace-pages.css +457 -0
  195. package/public/css/components/running-commands-as-toasts.css +327 -0
  196. package/public/css/components/settings-editor-core-config-yml.css +828 -0
  197. package/public/css/components/short-results-as-a-toast-at-the-top.css +203 -0
  198. package/public/css/components/sound-player.css +84 -0
  199. package/public/css/components/suggest-agents-skills-commands.css +112 -0
  200. package/public/css/components/tab-modal.css +142 -0
  201. package/public/css/components/terminals.css +244 -0
  202. package/public/css/components/test-report-media-one-file-per-row-paged.css +144 -0
  203. package/public/css/components/top-toolbar-logo-breadcrumb.css +107 -0
  204. package/public/css/components/video-edit.css +6 -0
  205. package/public/css/components/web-ide-open-with.css +23 -0
  206. package/public/css/components/working-indicators.css +158 -0
  207. package/public/css/components/workspace-cards.css +79 -0
  208. package/public/favicon.ico +0 -0
  209. package/public/favicon.png +0 -0
  210. package/public/htmx.min.js +1 -0
  211. package/public/logo.png +0 -0
  212. package/public/media-player.js +215 -0
  213. package/public/mic.js +255 -0
  214. package/public/narration.js +110 -0
  215. package/public/palette.js +278 -0
  216. package/public/sortable.js +251 -0
  217. package/public/suggest.js +337 -0
  218. package/public/terminal.js +903 -0
  219. package/public/themes/default/theme.css +267 -0
  220. package/public/themes/ember/theme.css +180 -0
  221. package/public/themes/press/theme.css +290 -0
  222. package/public/themes/slate/theme.css +182 -0
  223. package/public/touch-icon.png +0 -0
  224. package/public/ui.js +2721 -0
  225. package/public/vendor/ace/LICENSE.txt +24 -0
  226. package/public/vendor/ace/ace.js +23 -0
  227. package/public/vendor/ace/ext-language_tools.js +8 -0
  228. package/public/vendor/ace/ext-searchbox.js +8 -0
  229. package/public/vendor/ace/mode-css.js +8 -0
  230. package/public/vendor/ace/mode-html.js +8 -0
  231. package/public/vendor/ace/mode-javascript.js +8 -0
  232. package/public/vendor/ace/mode-markdown.js +8 -0
  233. package/public/vendor/ace/mode-sh.js +8 -0
  234. package/public/vendor/ace/mode-yaml.js +8 -0
  235. package/public/vendor/ace/theme-textmate.js +8 -0
  236. package/public/vendor/ace/theme-tomorrow_night.js +8 -0
  237. package/public/vendor/ace/worker-yaml.js +1 -0
  238. package/public/vendor/deep-chat.LICENSE +21 -0
  239. package/public/vendor/deep-chat.bundle.js +1 -0
  240. package/public/vendor/fonts/dm-serif-text-LICENSE.txt +93 -0
  241. package/public/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  242. package/public/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  243. package/public/vendor/fonts/figtree-LICENSE.txt +93 -0
  244. package/public/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  245. package/public/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  246. package/public/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  247. package/public/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  248. package/public/vendor/fonts/jetbrains-mono-LICENSE.txt +93 -0
  249. package/public/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  250. package/public/vendor/fonts/ubuntu-mono-LICENSE.txt +98 -0
  251. package/public/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  252. package/public/vendor/tabler-LICENSE.txt +21 -0
  253. package/public/vendor/tabler-sprite.svg +1 -0
  254. package/public/vendor/three/LICENSE +21 -0
  255. package/public/vendor/three/addons/loaders/GLTFLoader.js +4860 -0
  256. package/public/vendor/three/addons/utils/BufferGeometryUtils.js +1501 -0
  257. package/public/vendor/three/addons/utils/SkeletonUtils.js +496 -0
  258. package/public/vendor/three/three.core.min.js +6 -0
  259. package/public/vendor/three/three.module.min.js +6 -0
  260. package/public/vendor/three-vrm/LICENSE +22 -0
  261. package/public/vendor/three-vrm/three-vrm.module.min.js +983 -0
  262. package/public/vendor/uikit-icons.min.js +1 -0
  263. package/public/vendor/uikit.min.css +1 -0
  264. package/public/vendor/uikit.min.js +1 -0
  265. package/public/vendor/webship-icons/ui/bookmark-outlined.svg +1 -0
  266. package/public/vendor/webship-icons/ui/headset.svg +1 -0
  267. package/public/vendor/webship-icons/ui/menu.svg +1 -0
  268. package/public/vendor/webship-icons/ui/pause.svg +1 -0
  269. package/public/vendor/webship-icons/ui/play.svg +1 -0
  270. package/public/vendor/webship-icons/ui/share.svg +1 -0
  271. package/public/vendor/webship-icons/ui/sound-off.svg +1 -0
  272. package/public/vendor/webship-icons/ui/sound-on.svg +1 -0
  273. package/public/vendor/webship-icons/ui/stop.svg +1 -0
  274. package/public/vendor/xterm/addon-fit.js +2 -0
  275. package/public/vendor/xterm/addon-unicode11.js +2 -0
  276. package/public/vendor/xterm/addon-web-links.js +2 -0
  277. package/public/vendor/xterm/xterm.css +218 -0
  278. package/public/vendor/xterm/xterm.js +2 -0
  279. package/rag.js +169 -0
  280. package/reader.js +132 -0
  281. package/repo-blobs.js +66 -0
  282. package/report-pdf.js +337 -0
  283. package/row-layout.js +1439 -0
  284. package/screenrc +37 -0
  285. package/screens.js +1063 -0
  286. package/scripts/code-server-start.sh +190 -0
  287. package/scripts/images-claude-finish.js +107 -0
  288. package/scripts/screen-host.sh +114 -0
  289. package/scripts/screen-runner.js +362 -0
  290. package/scripts/screen-then.js +47 -0
  291. package/scripts/update.sh +350 -0
  292. package/server.js +7923 -0
  293. package/settings-file.js +423 -0
  294. package/speech.js +297 -0
  295. package/tab-modal.js +75 -0
  296. package/test-reports.js +260 -0
  297. package/themes.js +381 -0
  298. package/transcripts.js +168 -0
  299. package/urls.js +13 -0
  300. package/validate.js +18 -0
  301. package/version-check.js +94 -0
  302. package/video-edit.js +588 -0
  303. package/waiting.js +85 -0
  304. package/workspace-card-tools.js +41 -0
  305. package/workspace-delete.js +139 -0
  306. package/workspace-types.js +85 -0
  307. package/workspaces.js +553 -0
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Update the Webship Workspace dashboard app to a released tag.
4
+ #
5
+ # bash core/workspace-app/scripts/update.sh --check installed vs the latest tag
6
+ # bash core/workspace-app/scripts/update.sh update to the latest tag
7
+ # bash core/workspace-app/scripts/update.sh --tag 1.0.3 pin a tag (any tarball-servable ref)
8
+ # bash core/workspace-app/scripts/update.sh --dry-run itemize, change nothing
9
+ # bash core/workspace-app/scripts/update.sh --no-prompts leave prompts/ alone
10
+ # bash core/workspace-app/scripts/update.sh --prompts-only sync only the prompts
11
+ # bash core/workspace-app/scripts/update.sh --baseline 1.0.0 the version your prompts
12
+ # came from, when it cannot be told
13
+ # bash core/workspace-app/scripts/update.sh --force-prompts take the release's copy of
14
+ # every prompt, local edits included
15
+ #
16
+ # Deliberately SELF-CONTAINED: no workspace bootstrap, no argparse, no function
17
+ # library — the updater must never depend on the tooling it might be replacing.
18
+ # Plain bash + curl/gh + tar + rsync, and it locates the app from its own path.
19
+ #
20
+ # It updates core/workspace-app: runtime state survives (node_modules is kept
21
+ # unless package.json changed — then npm install runs in DDEV; actions.log,
22
+ # screenshots, test reports and the .app-version marker stay).
23
+ #
24
+ # It does NOT change your configuration. core/config and settings.yml are not
25
+ # its business at all, and inside the app the DDEV project's own identity is
26
+ # preserved across the update: its name, the hostnames derived from that name,
27
+ # any additional_fqdns and any router ports. A workspace called something else —
28
+ # a second checkout at ~/test1-workspace serving test1-workspace.ddev.site — must
29
+ # still be called that afterwards, or the update would point it at the first
30
+ # one's domains. Per-machine .ddev/config.*.yaml overrides are left alone too.
31
+ #
32
+ # It also brings the released PROMPT TEMPLATES into prompts/, because the New
33
+ # screen form and the Jira verbs offer them: a release that improves a prompt is
34
+ # useless if the prompt never reaches the machine. That folder is a staging area
35
+ # where prompts get worked on, so a prompt YOU have edited is never overwritten.
36
+ #
37
+ # "Edited" means edited, not "differs from the new release": every install and
38
+ # update records the checksum of each prompt as it shipped (.prompts-shipped),
39
+ # so a prompt still matching what came with the installed version is refreshed,
40
+ # and only one you actually changed is kept and reported. Before that file
41
+ # exists, the installed version's own prompts are fetched once to work it out —
42
+ # and if even that is not resolvable, differing prompts are kept, which is the
43
+ # safe way to be wrong. --force-prompts takes the release's copy regardless,
44
+ # --no-prompts skips the whole step.
45
+ #
46
+ # core/scripts, core/config, settings.yml and the other workspace folders belong
47
+ # to core/scripts/sync-tooling.sh, never to this script.
48
+
49
+ set -u
50
+
51
+ APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
52
+ VERSION_FILE="${APP_DIR}/.app-version"
53
+ SHIPPED_FILE="${APP_DIR}/.prompts-shipped"
54
+ REPO="webship/workspace"
55
+ TAG=""
56
+ CHECK="no"
57
+ DRY_RUN="no"
58
+ PROMPTS="yes"
59
+ PROMPTS_ONLY="no"
60
+ BASELINE=""
61
+ FORCE_PROMPTS="no"
62
+ ROOT="$(cd "${APP_DIR}/../.." && pwd)"
63
+
64
+ while [ $# -gt 0 ]; do
65
+ case "$1" in
66
+ -t|--tag) TAG="${2:-}"; shift 2 ;;
67
+ -r|--repo) REPO="${2:-}"; shift 2 ;;
68
+ -c|--check) CHECK="yes"; shift ;;
69
+ -n|--dry-run) DRY_RUN="yes"; shift ;;
70
+ --no-prompts) PROMPTS="no"; shift ;;
71
+ --prompts-only) PROMPTS_ONLY="yes"; shift ;;
72
+ --baseline) BASELINE="${2:-}"; shift 2 ;;
73
+ --force-prompts) FORCE_PROMPTS="yes"; shift ;;
74
+ -h|--help)
75
+ sed -n '3,12p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
76
+ exit 0 ;;
77
+ *) echo "Unknown option: $1 (see --help)"; exit 1 ;;
78
+ esac
79
+ done
80
+
81
+ installed="(untracked)"
82
+ [ -f "${VERSION_FILE}" ] && installed="$(cat "${VERSION_FILE}")"
83
+ # What the prompts on disk came with. It is the installed version UNLESS this run
84
+ # is the second half of an update that already bumped it — hence --baseline, which
85
+ # the re-exec passes through with the value from before the bump.
86
+ [ -z "${BASELINE}" ] && BASELINE="${installed}"
87
+
88
+ have_gh="no"
89
+ command -v gh > /dev/null 2>&1 && gh auth status > /dev/null 2>&1 && have_gh="yes"
90
+
91
+ # The newest tag, by version order — resolved only when needed, so an explicit
92
+ # --tag works before the first release exists.
93
+ latest="(no tags yet)"
94
+ if [ -z "${TAG}" ] || [ "${CHECK}" = "yes" ]; then
95
+ if [ "${have_gh}" = "yes" ]; then
96
+ found=$(gh api "repos/${REPO}/tags" --jq '.[].name' 2>/dev/null | sort -V | tail -1)
97
+ else
98
+ found=$(curl -fsSL "https://api.github.com/repos/${REPO}/tags" 2>/dev/null \
99
+ | grep -o '"name": *"[^"]*"' | cut -d'"' -f4 | sort -V | tail -1)
100
+ fi
101
+ [ -n "${found}" ] && latest="${found}"
102
+ fi
103
+
104
+ wanted="${TAG:-${found:-}}"
105
+
106
+ echo "Installed: ${installed}"
107
+ echo "Latest: ${latest}"
108
+ if [ "${CHECK}" = "yes" ]; then
109
+ if [ "${latest}" = "(no tags yet)" ]; then
110
+ echo "Releases have not started (or no access) — nothing to compare."
111
+ elif [ "${installed}" = "${latest}" ]; then
112
+ echo "Up to date."
113
+ else
114
+ echo "An update is available: bash ${BASH_SOURCE[0]} --tag ${latest}"
115
+ fi
116
+ exit 0
117
+ fi
118
+ if [ -z "${wanted}" ]; then
119
+ echo "No tags found on ${REPO} — nothing to update from (releases have not started, or no access)."
120
+ exit 1
121
+ fi
122
+ if [ "${installed}" = "${wanted}" ] && [ "${DRY_RUN}" = "no" ] && [ "${PROMPTS}" = "no" ]; then
123
+ echo "Already on ${wanted} — nothing to do."
124
+ exit 0
125
+ fi
126
+ # Being on the version is not the same as having its prompts — which is exactly
127
+ # what happens the first time a release adds a step to this script.
128
+ if [ "${installed}" = "${wanted}" ] && [ "${PROMPTS_ONLY}" = "no" ]; then
129
+ echo "The app is already on ${wanted} — checking the prompts."
130
+ PROMPTS_ONLY="yes"
131
+ fi
132
+
133
+ workdir=$(mktemp -d)
134
+ trap 'rm -rf "${workdir}"' EXIT
135
+ echo "Downloading ${REPO}@${wanted}…"
136
+ if [ "${have_gh}" = "yes" ]; then
137
+ gh api "repos/${REPO}/tarball/${wanted}" > "${workdir}/app.tar.gz" || exit 1
138
+ else
139
+ curl -fsSL "https://api.github.com/repos/${REPO}/tarball/${wanted}" -o "${workdir}/app.tar.gz" || exit 1
140
+ fi
141
+ tar -xzf "${workdir}/app.tar.gz" -C "${workdir}"
142
+ extracted=$(find "${workdir}" -maxdepth 1 -type d ! -path "${workdir}" | head -1)
143
+ if [ ! -d "${extracted}/core/workspace-app" ]; then
144
+ echo "The tag's tarball carries no core/workspace-app — refusing."
145
+ exit 1
146
+ fi
147
+
148
+ deps_changed="no"
149
+ cmp -s "${extracted}/core/workspace-app/package.json" "${APP_DIR}/package.json" || deps_changed="yes"
150
+ ddev_changed="no"
151
+ diff -rq "${extracted}/core/workspace-app/.ddev" "${APP_DIR}/.ddev" > /dev/null 2>&1 || ddev_changed="yes"
152
+
153
+ updater_changed="no"
154
+ cmp -s "${extracted}/core/workspace-app/scripts/update.sh" "${BASH_SOURCE[0]}" || updater_changed="yes"
155
+
156
+ rsync_flags="-a --itemize-changes"
157
+ [ "${DRY_RUN}" = "yes" ] && rsync_flags="${rsync_flags} --dry-run"
158
+ if [ "${PROMPTS_ONLY}" = "yes" ]; then
159
+ echo "Only the prompts this time — ${APP_DIR} is already on ${wanted}."
160
+ else
161
+ # The DDEV project's identity, read before the release's config lands on top.
162
+ ddev_config="${APP_DIR}/.ddev/config.yaml"
163
+ local_name="" ; local_fqdns="" ; local_http="" ; local_https=""
164
+ if [ -f "${ddev_config}" ]; then
165
+ local_name=$(grep -m1 '^name:' "${ddev_config}" | sed 's/^name: *//')
166
+ local_fqdns=$(grep -m1 '^additional_fqdns:' "${ddev_config}")
167
+ local_http=$(grep -m1 '^router_http_port:' "${ddev_config}")
168
+ local_https=$(grep -m1 '^router_https_port:' "${ddev_config}")
169
+ fi
170
+
171
+ echo "Updating ${APP_DIR} (runtime state survives)…"
172
+ rsync ${rsync_flags} --delete \
173
+ --filter='protect .ddev/config.*.yaml' \
174
+ --exclude=node_modules --exclude=actions.log --exclude=logs --exclude=.app-version \
175
+ --exclude=.prompts-shipped --exclude=.chats --exclude=.memory --exclude=.voice-cache \
176
+ --exclude=screenshots --exclude='tests/reports' --exclude='.playwright-mcp' \
177
+ "${extracted}/core/workspace-app/" "${APP_DIR}/"
178
+
179
+ # Put the project's identity back. The hostnames are patterns built from the
180
+ # name (*.dev.<name>), so they are renamed with it — otherwise this checkout
181
+ # would answer on another one's domains.
182
+ if [ "${DRY_RUN}" = "no" ] && [ -n "${local_name}" ] && [ -f "${ddev_config}" ]; then
183
+ shipped_name=$(grep -m1 '^name:' "${ddev_config}" | sed 's/^name: *//')
184
+ if [ "${shipped_name}" != "${local_name}" ]; then
185
+ sed -i "1,/^name:/ s/^name: .*/name: ${local_name}/" "${ddev_config}"
186
+ sed -i "s/^\(- \"\*\..*\)${shipped_name}\"$/\1${local_name}\"/" "${ddev_config}"
187
+ echo "Kept this workspace's DDEV project name: ${local_name}"
188
+ fi
189
+ [ -n "${local_fqdns}" ] && sed -i "s/^additional_fqdns:.*/${local_fqdns}/" "${ddev_config}"
190
+ [ -n "${local_http}" ] && sed -i "s/^router_http_port:.*/${local_http}/" "${ddev_config}"
191
+ [ -n "${local_https}" ] && sed -i "s/^router_https_port:.*/${local_https}/" "${ddev_config}"
192
+ fi
193
+ fi
194
+
195
+ # The release brought a newer updater and the one running is the old one, so the
196
+ # steps the new version adds would be skipped in the very release that adds
197
+ # them. Hand the rest of the job to the new script, once.
198
+ if [ "${PROMPTS_ONLY}" = "no" ] && [ "${DRY_RUN}" = "no" ] && [ "${updater_changed}" = "yes" ] \
199
+ && [ -z "${WORKSPACE_UPDATE_REEXEC:-}" ] && [ -f "${APP_DIR}/scripts/update.sh" ] \
200
+ && grep -q -- '--prompts-only' "${APP_DIR}/scripts/update.sh"; then
201
+ echo "${wanted}" > "${VERSION_FILE}"
202
+ echo "The updater changed in ${wanted} — finishing with the new one."
203
+ extra=""
204
+ [ "${FORCE_PROMPTS}" = "yes" ] && extra="--force-prompts"
205
+ WORKSPACE_UPDATE_REEXEC=1 exec bash "${APP_DIR}/scripts/update.sh" \
206
+ --repo "${REPO}" --tag "${wanted}" --prompts-only --baseline "${installed}" ${extra}
207
+ fi
208
+
209
+ # This machine's Docker access, put back.
210
+ #
211
+ # The release's .ddev/ lands on top of the local one, and TWO files in it are
212
+ # per-machine, written by core/scripts/docker-gid.sh rather than by hand:
213
+ # docker-compose.dood.yaml (group_add — the GID the running process is granted)
214
+ # and web-build/Dockerfile.workspace-app (the GID the group is created with).
215
+ # The shipped values are placeholders.
216
+ #
217
+ # Nothing here noticed. The project NAME, ports and fqdns were carefully restored
218
+ # and the GID was not, so every supported update left the dashboard with a docker
219
+ # GID that is not this host's — and the failure is silent until the next
220
+ # `ddev restart`, when the container loses access to docker.sock and the dashboard
221
+ # reports EVERY project as Stopped while they all run perfectly well. That is an
222
+ # hour of debugging a fault that is not one, once per machine, per update.
223
+ #
224
+ # sync-tooling.sh has always re-run docker-gid.sh after mirroring, for exactly this
225
+ # reason. The updater has to as well: it is the path a machine on releases uses,
226
+ # and it is the only one those machines have.
227
+ if [ "${DRY_RUN}" = "no" ]; then
228
+ if [ -f "${ROOT}/core/scripts/docker-gid.sh" ]; then
229
+ bash "${ROOT}/core/scripts/docker-gid.sh" > /dev/null 2>&1 \
230
+ && echo "Docker GID re-applied for this host." \
231
+ || echo "⚠️ Could not re-apply the docker GID: bash core/scripts/docker-gid.sh"
232
+ else
233
+ # A tree with the app but not the tooling. Say it plainly rather than leave a
234
+ # dashboard that will go blind on its next restart with no clue why.
235
+ echo "⚠️ core/scripts/docker-gid.sh is not here, so this release's placeholder"
236
+ echo " docker GID is in place. On the next 'ddev restart' the dashboard will"
237
+ echo " show every project as Stopped. Fix it with:"
238
+ echo " bash core/scripts/sync-tooling.sh --pull"
239
+ fi
240
+ fi
241
+
242
+ # The prompt templates the dashboard offers. New ones arrive, ones still as they
243
+ # shipped are refreshed, and anything YOU edited stays exactly as it is: this
244
+ # folder is where prompts are written, not only where they are installed.
245
+ if [ "${PROMPTS}" = "yes" ] && [ -d "${extracted}/prompts" ]; then
246
+ mkdir -p "${ROOT}/prompts"
247
+
248
+ # What the installed version shipped, so "edited" can mean edited rather than
249
+ # "differs from the new release" — without it, every improved prompt would be
250
+ # kept out forever on an install nobody has touched.
251
+ if [ ! -f "${SHIPPED_FILE}" ] && [ "${BASELINE}" != "(untracked)" ] && [ "${BASELINE}" != "${wanted}" ]; then
252
+ echo "Working out which prompts you have edited (fetching ${BASELINE})…"
253
+ if [ "${have_gh}" = "yes" ]; then
254
+ gh api "repos/${REPO}/tarball/${BASELINE}" > "${workdir}/installed.tar.gz" 2>/dev/null
255
+ else
256
+ curl -fsSL "https://api.github.com/repos/${REPO}/tarball/${BASELINE}" -o "${workdir}/installed.tar.gz" 2>/dev/null
257
+ fi
258
+ if [ -s "${workdir}/installed.tar.gz" ]; then
259
+ mkdir -p "${workdir}/installed" && tar -xzf "${workdir}/installed.tar.gz" -C "${workdir}/installed" 2>/dev/null
260
+ base=$(find "${workdir}/installed" -maxdepth 2 -type d -name prompts | head -1)
261
+ if [ -n "${base}" ]; then
262
+ : > "${workdir}/shipped"
263
+ for old_prompt in "${base}"/*.md ; do
264
+ [ -e "${old_prompt}" ] || continue
265
+ printf '%s %s\n' "$(sha256sum "${old_prompt}" | cut -d' ' -f1)" "$(basename "${old_prompt}")" >> "${workdir}/shipped"
266
+ done
267
+ [ "${DRY_RUN}" = "no" ] && cp "${workdir}/shipped" "${SHIPPED_FILE}"
268
+ SHIPPED_LIST="${workdir}/shipped"
269
+ fi
270
+ fi
271
+ fi
272
+ [ -z "${SHIPPED_LIST:-}" ] && [ -f "${SHIPPED_FILE}" ] && SHIPPED_LIST="${SHIPPED_FILE}"
273
+
274
+ # Did this prompt arrive with the installed version and stay untouched?
275
+ as_shipped() {
276
+ [ -n "${SHIPPED_LIST:-}" ] || return 1
277
+ local want ; want=$(grep -F " $1" "${SHIPPED_LIST}" 2>/dev/null | head -1 | cut -d' ' -f1)
278
+ [ -n "${want}" ] || return 1
279
+ [ "${want}" = "$(sha256sum "$2" | cut -d' ' -f1)" ]
280
+ }
281
+
282
+ added=0 ; updated=0 ; kept=0
283
+ for src in "${extracted}"/prompts/*.md ; do
284
+ [ -e "${src}" ] || continue
285
+ name="$(basename "${src}")"
286
+ dest="${ROOT}/prompts/${name}"
287
+ if [ ! -e "${dest}" ]; then
288
+ [ "${DRY_RUN}" = "no" ] && cp "${src}" "${dest}"
289
+ added=$((added + 1)) ; echo " new prompt: ${name}"
290
+ elif cmp -s "${src}" "${dest}" ; then
291
+ : # already the released one
292
+ elif [ "${FORCE_PROMPTS}" = "yes" ] || as_shipped "${name}" "${dest}" ; then
293
+ [ "${DRY_RUN}" = "no" ] && cp "${src}" "${dest}"
294
+ updated=$((updated + 1)) ; echo " updated: ${name}"
295
+ else
296
+ kept=$((kept + 1)) ; echo " yours, kept: ${name}"
297
+ fi
298
+ done
299
+
300
+ # Record what this version ships, so the next update knows the same thing
301
+ # without fetching anything.
302
+ if [ "${DRY_RUN}" = "no" ]; then
303
+ : > "${SHIPPED_FILE}"
304
+ for src in "${extracted}"/prompts/*.md ; do
305
+ [ -e "${src}" ] || continue
306
+ printf '%s %s\n' "$(sha256sum "${src}" | cut -d' ' -f1)" "$(basename "${src}")" >> "${SHIPPED_FILE}"
307
+ done
308
+ fi
309
+
310
+ echo "Prompts: ${added} new, ${updated} updated, ${kept} left as yours."
311
+ if [ "${kept}" -gt 0 ]; then
312
+ if [ -n "${SHIPPED_LIST:-}" ]; then
313
+ echo " (take the release's copy of those too with --force-prompts)"
314
+ else
315
+ echo " Nothing to compare them against, so they were all kept. If you have not edited them,"
316
+ echo " re-run with --baseline <the version they came from>, or --force-prompts."
317
+ fi
318
+ fi
319
+ fi
320
+
321
+ if [ "${DRY_RUN}" = "yes" ]; then
322
+ echo "(dry run — nothing was changed)"
323
+ exit 0
324
+ fi
325
+ echo "${wanted}" > "${VERSION_FILE}"
326
+ if [ "${PROMPTS_ONLY}" = "yes" ]; then
327
+ echo ""
328
+ echo "*---------------------------------------------------------------------*"
329
+ echo " The dashboard app is on ${wanted}, prompts included."
330
+ echo "*---------------------------------------------------------------------*"
331
+ exit 0
332
+ fi
333
+
334
+ if [ "${deps_changed}" = "yes" ]; then
335
+ echo "package.json changed — installing dependencies…"
336
+ (cd "${APP_DIR}" && ddev exec npm install --no-audit --no-fund) \
337
+ || echo "npm install failed — run it in ${APP_DIR} yourself."
338
+ fi
339
+ if [ "${ddev_changed}" = "yes" ]; then
340
+ echo ""
341
+ echo "⚠️ .ddev/ changed in this release — a full restart is needed:"
342
+ echo " cd ${APP_DIR} && ddev restart"
343
+ else
344
+ echo "Restarting the app…"
345
+ (cd "${APP_DIR}" && ddev exec supervisorctl restart webextradaemons:workspace-app) > /dev/null 2>&1 || true
346
+ fi
347
+ echo ""
348
+ echo "*---------------------------------------------------------------------*"
349
+ echo " The dashboard app is on ${wanted}."
350
+ echo "*---------------------------------------------------------------------*"