@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,430 @@
1
+ #!/usr/bin/env python3
2
+ """Voices for the audio workspace: which engines this machine can use, and one text spoken by one of them.
3
+
4
+ Piper is the local voice of the voice-narration skill: free, offline, no key. ElevenLabs is offered when
5
+ a key is set, and what it offers follows what that key is allowed to read. Standard library only.
6
+ """
7
+
8
+ import argparse
9
+ import datetime
10
+ import json
11
+ import os
12
+ import re
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+
18
+ sys.dont_write_bytecode = True # importing the engine must not leave a __pycache__ in the tooling tree
19
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
20
+ import elevenlabs as el # noqa: E402
21
+
22
+ OK, ERROR, USAGE = el.OK, el.ERROR, el.USAGE
23
+ MODELS = [('eleven_v4', 'Eleven v4: the default'),
24
+ ('eleven_v3', 'Eleven v3: most expressive, 70+ languages'),
25
+ ('eleven_multilingual_v2', 'Multilingual v2: steady and lifelike, 29 languages'),
26
+ ('eleven_flash_v2_5', 'Flash v2.5: fastest, 32 languages, half the credits'),
27
+ ('eleven_turbo_v2_5', 'Turbo v2.5: quick and balanced, 32 languages, half the credits'),
28
+ ('eleven_flash_v2', 'Flash v2: English only, fast, half the credits'),
29
+ ('eleven_turbo_v2', 'Turbo v2: English only, low latency, half the credits')]
30
+ CATALOG_LINE = re.compile(r'^ ([ !])(\d+)\) (\S+)\s+(.*)$')
31
+ NOT_INSTALLED = ('Piper is not installed where this runs. Install it on the host with the voice-narration skill '
32
+ '(scripts/install-voice.sh); the dashboard container has none.')
33
+
34
+
35
+ def home(*parts):
36
+ return os.path.join(os.path.expanduser('~'), *parts)
37
+
38
+
39
+ def piper_bin():
40
+ for candidate in (os.environ.get('PIPER_BIN'), home('.local', 'bin', 'piper'), shutil.which('piper')):
41
+ if candidate and os.access(candidate, os.X_OK):
42
+ return candidate
43
+ return None
44
+
45
+
46
+ def voice_dir():
47
+ return os.environ.get('PIPER_VOICE_DIR') or home('.local', 'share', 'piper-voices')
48
+
49
+
50
+ def say_script():
51
+ for candidate in (home('.claude', 'skills', 'voice-narration', 'scripts', 'claude-say'), home('.local', 'bin', 'claude-say')):
52
+ if os.path.isfile(candidate):
53
+ return candidate
54
+ return None
55
+
56
+
57
+ def piper_voices():
58
+ """The voice-narration catalog, in its numbered order, with what is installed marked."""
59
+ voices = []
60
+ script = say_script()
61
+ if script:
62
+ try:
63
+ listing = subprocess.run(['bash', script, 'list'], capture_output=True, text=True, timeout=20).stdout
64
+ except (OSError, subprocess.SubprocessError):
65
+ listing = ''
66
+ for line in listing.splitlines():
67
+ found = CATALOG_LINE.match(line)
68
+ if found:
69
+ voices.append({'id': found.group(3), 'number': int(found.group(2)), 'label': found.group(4).strip(),
70
+ 'installed': os.path.isfile(os.path.join(voice_dir(), found.group(3) + '.onnx'))})
71
+ known = {v['id'] for v in voices}
72
+ try:
73
+ extra = sorted(n[:-5] for n in os.listdir(voice_dir()) if n.endswith('.onnx') and n[:-5] not in known)
74
+ except OSError:
75
+ extra = []
76
+ voices += [{'id': name, 'number': None, 'label': 'Installed', 'installed': True} for name in extra]
77
+ return voices
78
+
79
+
80
+ def write_belongings(out, text, record):
81
+ """What belongs to a generated file, kept beside it: the text it was made from, and how it was made."""
82
+ stem = os.path.splitext(out)[0]
83
+ record = dict(record, file=os.path.basename(out), created_utc=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'))
84
+ try:
85
+ el.write_atomic(stem + '.txt', (text.strip() + '\n').encode('utf-8'), True)
86
+ el.write_atomic(stem + '.json', (json.dumps(record, indent=2, ensure_ascii=False) + '\n').encode('utf-8'), True)
87
+ except OSError as error:
88
+ print('The audio was made, but its text and record could not be saved: %s' % error, file=sys.stderr)
89
+
90
+
91
+ def piper_info():
92
+ info = {'id': 'piper', 'label': 'Piper (voice-narration, local)', 'free': True, 'needs_key': False,
93
+ 'available': False, 'reason': '', 'reason_code': '', 'voices': [], 'models': []}
94
+ binary = piper_bin()
95
+ voices = piper_voices()
96
+ info['voices'] = voices
97
+ if not binary:
98
+ info.update(reason=NOT_INSTALLED, reason_code='no-binary')
99
+ elif not any(v['installed'] for v in voices):
100
+ info.update(reason='Piper is installed but no voice model is in %s.' % voice_dir(), reason_code='no-voices')
101
+ else:
102
+ info['available'] = True
103
+ info['default_voice'] = next((v['id'] for v in voices if v['installed']), '')
104
+ info['formats'] = ['wav'] + (['mp3'] if shutil.which('ffmpeg') else [])
105
+ return info
106
+
107
+
108
+ def elevenlabs_info():
109
+ info = {'id': 'elevenlabs', 'label': 'ElevenLabs', 'free': False, 'needs_key': True, 'available': False, 'reason': '',
110
+ 'voices': [], 'models': [{'id': m, 'label': label} for m, label in MODELS],
111
+ 'default_model': os.environ.get('ELEVENLABS_MODEL') or el.DEFAULT_MODEL,
112
+ 'default_voice': os.environ.get('ELEVENLABS_VOICE', ''), 'formats': ['mp3'],
113
+ 'can_read_account': False, 'can_list_voices': False}
114
+ try:
115
+ el.key()
116
+ except el.Stop as stop:
117
+ info['reason'] = str(stop)
118
+ return info
119
+ try:
120
+ found, why = el.account()
121
+ status, _, body = el.call('GET', '/v2/voices', query={'page_size': '100'})
122
+ except el.Stop as stop:
123
+ info['reason'] = str(stop)
124
+ return info
125
+ if found:
126
+ info.update(can_read_account=True, plan=found['tier'], remaining=found['remaining'], limit=found['limit'])
127
+ else:
128
+ info['account_note'] = why
129
+ if status == 200:
130
+ info['can_list_voices'] = True
131
+ info['voices'] = [{'id': v.get('voice_id'), 'label': v.get('name') or v.get('voice_id'), 'category': v.get('category'),
132
+ 'labels': v.get('labels') or {}} for v in json.loads(body).get('voices', [])]
133
+ # A default given by name (Brian) is the voice whose name starts with it.
134
+ want = info['default_voice'].strip().lower()
135
+ if want and not any(v['id'] == info['default_voice'] for v in info['voices']):
136
+ hit = next((v for v in info['voices'] if (v['label'] or '').lower().startswith(want)), None)
137
+ info['default_voice'] = hit['id'] if hit else ''
138
+ else:
139
+ info['voices_note'] = el.explain(status, body)
140
+ # When the key may read models, any speech model it offers beyond the built-in list is added.
141
+ try:
142
+ listed, _, models = el.call('GET', '/v1/models')
143
+ except el.Stop:
144
+ listed, models = 0, '[]'
145
+ if listed == 200:
146
+ info['can_list_models'] = True
147
+ known = {m['id'] for m in info['models']}
148
+ for m in json.loads(models):
149
+ if m.get('can_do_text_to_speech') and m.get('model_id') and m['model_id'] not in known:
150
+ info['models'].append({'id': m['model_id'], 'label': m.get('name') or m['model_id']})
151
+ # A rejected key fails both reads with 401; a key that only lacks a scope fails with 403 and can still speak.
152
+ if not found and status == 401:
153
+ info['reason'] = 'ElevenLabs rejected the key.'
154
+ return info
155
+ info['available'] = True
156
+ return info
157
+
158
+
159
+ def cmd_engines(args):
160
+ engines = [piper_info(), elevenlabs_info()]
161
+ lines = []
162
+ for e in engines:
163
+ lines.append('%s: %s' % (e['label'], 'available' if e['available'] else 'not available. ' + e['reason']))
164
+ if e['available']:
165
+ lines.append(' %d voices%s' % (len(e['voices']), '' if e['id'] == 'piper' or e['can_list_voices'] else ' (the key cannot list them; pass --voice <id>)'))
166
+ if e.get('remaining') is not None:
167
+ lines.append(' %s characters left on the %s plan' % (e['remaining'], e['plan']))
168
+ el.emit(args, {'engines': engines}, lines)
169
+ return OK
170
+
171
+
172
+ def cmd_voices(args):
173
+ if args.engine == 'elevenlabs':
174
+ return el.main(['voices', '--limit', str(args.limit)] + (['--search', args.search] if args.search else []) + (['--json'] if args.json else []))
175
+ voices = piper_voices()
176
+ lines = ['%-3s %-24s %-9s %s' % ('#', 'voice', 'installed', 'about')] + [
177
+ '%-3s %-24s %-9s %s' % (v['number'] or '', v['id'], 'yes' if v['installed'] else 'no', v['label']) for v in voices]
178
+ el.emit(args, {'voices': voices}, lines)
179
+ return OK
180
+
181
+
182
+ def read_text(path):
183
+ try:
184
+ with open(path, encoding='utf-8') as handle:
185
+ return handle.read().strip()
186
+ except OSError:
187
+ return ''
188
+
189
+
190
+ def cmd_speak(args):
191
+ if not args.out:
192
+ print('Pass --out <file>.', file=sys.stderr)
193
+ return USAGE
194
+ if args.engine == 'elevenlabs':
195
+ argv = ['speak', '--text-file', args.text_file, '--out', args.out, '--voice', args.voice, '--model', args.model]
196
+ code = el.main(argv + (['--confirm'] if args.confirm else []) + (['--force'] if args.force else []) + (['--json'] if args.json else []))
197
+ if code == OK:
198
+ write_belongings(args.out, read_text(args.text_file), {'kind': 'voice', 'engine': 'elevenlabs', 'voice': args.voice, 'model': args.model})
199
+ return code
200
+ return speak_piper(args)
201
+
202
+
203
+ def speak_piper(args):
204
+ text = read_text(args.text_file)
205
+ if not text:
206
+ print('The text file is missing or empty: %s' % args.text_file, file=sys.stderr)
207
+ return USAGE
208
+ binary = piper_bin()
209
+ if not binary:
210
+ print(NOT_INSTALLED, file=sys.stderr)
211
+ return ERROR
212
+ installed = [v['id'] for v in piper_voices() if v['installed']]
213
+ voice = args.voice or (installed[0] if installed else '')
214
+ model = os.path.join(voice_dir(), voice + '.onnx')
215
+ if not voice or not os.path.isfile(model):
216
+ print('The Piper voice "%s" is not installed. Installed: %s.' % (voice, ', '.join(installed) or 'none'), file=sys.stderr)
217
+ return USAGE
218
+ ext = os.path.splitext(args.out)[1].lower()
219
+ if ext not in ('.wav', '.mp3'):
220
+ print('Piper writes .wav, or .mp3 when ffmpeg is installed. Use one of those for --out.', file=sys.stderr)
221
+ return USAGE
222
+ if ext == '.mp3' and not shutil.which('ffmpeg'):
223
+ print('ffmpeg is not installed here, so Piper cannot make an mp3. Use a .wav name.', file=sys.stderr)
224
+ return USAGE
225
+ try:
226
+ el.ensure_free([args.out], args.force)
227
+ except el.Stop as stop:
228
+ print(stop, file=sys.stderr)
229
+ return stop.code
230
+ wav = args.out if ext == '.wav' else args.out + '.tmp.wav'
231
+ try:
232
+ subprocess.run([binary, '--model', model, '--output_file', wav], input=text.encode('utf-8'), capture_output=True, check=True, timeout=300)
233
+ if ext == '.mp3':
234
+ subprocess.run(['ffmpeg', '-y', '-loglevel', 'error', '-i', wav, '-codec:a', 'libmp3lame', '-b:a', '128k', args.out], check=True, timeout=300)
235
+ except (OSError, subprocess.SubprocessError) as error:
236
+ print('Piper failed: %s' % getattr(error, 'stderr', error), file=sys.stderr)
237
+ return ERROR
238
+ finally:
239
+ if wav != args.out and os.path.exists(wav):
240
+ os.remove(wav)
241
+ write_belongings(args.out, text, {'kind': 'voice', 'engine': 'piper', 'voice': voice})
242
+ el.emit(args, {'engine': 'piper', 'voice': voice, 'characters': len(text), 'out': args.out},
243
+ ['Wrote %s (voice %s, %d characters).' % (args.out, voice, len(text))])
244
+ return OK
245
+
246
+
247
+ def piper_synthesize(text, voice, model, fmt):
248
+ """The engine's synthesize(), spoken by Piper: one section in, mp3 bytes out."""
249
+ binary = piper_bin()
250
+ if not binary:
251
+ raise el.Stop(NOT_INSTALLED)
252
+ path = os.path.join(voice_dir(), voice + '.onnx')
253
+ if not os.path.isfile(path):
254
+ raise el.Stop('The Piper voice "%s" is not installed.' % voice, USAGE)
255
+ with tempfile.TemporaryDirectory() as work:
256
+ wav, mp3 = os.path.join(work, 's.wav'), os.path.join(work, 's.mp3')
257
+ try:
258
+ subprocess.run([binary, '--model', path, '--output_file', wav], input=text.encode('utf-8'), capture_output=True, check=True, timeout=300)
259
+ subprocess.run(['ffmpeg', '-y', '-loglevel', 'error', '-i', wav, '-codec:a', 'libmp3lame', '-b:a', '128k', mp3], check=True, timeout=300)
260
+ except (OSError, subprocess.SubprocessError) as error:
261
+ raise el.Stop('Piper failed: %s' % getattr(error, 'stderr', error))
262
+ with open(mp3, 'rb') as handle:
263
+ return handle.read(), {'engine': 'piper', 'voice': voice}
264
+
265
+
266
+ def timed_script(path, video):
267
+ """The script the engine reads. A transcript with no timed sections is spread over the whole video."""
268
+ text = read_text(path)
269
+ if not text:
270
+ raise el.Stop('The script is missing or empty: %s' % path, USAGE)
271
+ if any(el.HEADING.match(line) for line in text.splitlines()):
272
+ return path, None
273
+ length = el.duration(video)
274
+ if not length:
275
+ raise el.Stop('Cannot read the video duration: %s' % video)
276
+ lines = [re.sub(r'^\s*(?:[-*+]\s+|\d+[.)]\s+|>\s*)', '', line).strip() for line in text.splitlines()]
277
+ spoken = [line for line in lines if line and not line.startswith('#')]
278
+ if not spoken:
279
+ raise el.Stop('The script has nothing to say: %s' % path, USAGE)
280
+ work = tempfile.mkdtemp(prefix='ws-voice-script-')
281
+ timed = os.path.join(work, 'script.md')
282
+ with open(timed, 'w', encoding='utf-8') as handle:
283
+ handle.write('## S01 Narration (00:00.0 to %s)\n\n%s\n' % (el.mmss(length), '\n'.join('> ' + line for line in spoken)))
284
+ return timed, work
285
+
286
+
287
+ def cmd_voiceover(args):
288
+ if args.engine == 'piper':
289
+ installed = [v['id'] for v in piper_voices() if v['installed']]
290
+ args.voice = args.voice or (installed[0] if installed else '')
291
+ if not piper_bin() or args.voice not in installed:
292
+ print(NOT_INSTALLED if not piper_bin() else 'The Piper voice "%s" is not installed. Installed: %s.' % (args.voice, ', '.join(installed) or 'none'), file=sys.stderr)
293
+ return USAGE if piper_bin() else ERROR
294
+ # Piper is local and free: no key, no credits, and the engine's section loop speaks through it.
295
+ el.key = lambda: ''
296
+ el.remaining_characters = lambda: None
297
+ el.synthesize = piper_synthesize
298
+ el.resolve_voice = lambda value: value # a Piper voice is never looked up on ElevenLabs
299
+ args.model = 'piper'
300
+ script, work = timed_script(args.script, args.video)
301
+ try:
302
+ argv = ['voiceover', '--script', script, '--video', args.video, '--out', args.out, '--voice', args.voice, '--model', args.model]
303
+ flags = [f for on, f in ((args.confirm, '--confirm'), (args.force, '--force'), (args.json, '--json'), (args.drop_optional, '--drop-optional')) if on]
304
+ return el.main(argv + flags)
305
+ finally:
306
+ if work:
307
+ shutil.rmtree(work, ignore_errors=True)
308
+
309
+
310
+ SOUND_FORMAT = 'mp3_44100_128'
311
+
312
+
313
+ def _generate(args, endpoint, body, label, plan, text):
314
+ """A sound or a piece of music from ElevenLabs: plan first, send only with --confirm."""
315
+ el.key()
316
+ plan = dict(plan, remaining=el.remaining_characters(), out=args.out)
317
+ if not args.confirm:
318
+ el.emit(args, dict(plan, confirmed=False), ['Plan only, nothing was sent.'] + ['%s: %s' % kv for kv in plan.items() if kv[0] != 'remaining']
319
+ + ['Credits left: %s.' % ('unknown' if plan['remaining'] is None else plan['remaining']), 'Run again with --confirm to create %s.' % args.out])
320
+ return el.PLAN_ONLY
321
+ el.ensure_free([args.out], args.force)
322
+ status, _, data = el.call('POST', endpoint, body=json.dumps(body).encode(), content_type='application/json', query={'output_format': SOUND_FORMAT})
323
+ if status != 200:
324
+ raise el.Stop('%s failed. %s' % (label, el.explain(status, data)))
325
+ el.write_atomic(args.out, data, args.force)
326
+ write_belongings(args.out, text, {'kind': plan['kind'], 'engine': 'elevenlabs', **{k: v for k, v in plan.items() if k not in ('kind', 'remaining', 'out', 'prompt')}})
327
+ el.emit(args, dict(plan, confirmed=True, bytes=len(data)), ['Wrote %s (%d bytes).' % (args.out, len(data))])
328
+ return OK
329
+
330
+
331
+ def cmd_sound(args):
332
+ text = read_text(args.text_file)
333
+ if not text:
334
+ print('The description is missing or empty: %s' % args.text_file, file=sys.stderr)
335
+ return USAGE
336
+ if args.duration is not None and not 0.5 <= args.duration <= 30:
337
+ print('A sound runs from 0.5 to 30 seconds.', file=sys.stderr)
338
+ return USAGE
339
+ if not 0 <= args.influence <= 1:
340
+ print('--influence is between 0 and 1.', file=sys.stderr)
341
+ return USAGE
342
+ body = {'text': text, 'prompt_influence': args.influence}
343
+ if args.duration is not None:
344
+ body['duration_seconds'] = args.duration
345
+ if args.loop:
346
+ body['loop'] = True
347
+ return _generate(args, '/v1/sound-generation', body, 'Sound generation',
348
+ {'kind': 'sound', 'prompt': text[:120], 'duration': args.duration if args.duration is not None else 'chosen by ElevenLabs', 'loop': args.loop}, text)
349
+
350
+
351
+ def cmd_music(args):
352
+ text = read_text(args.text_file)
353
+ if not text:
354
+ print('The description is missing or empty: %s' % args.text_file, file=sys.stderr)
355
+ return USAGE
356
+ if not 3 <= args.length <= 600:
357
+ print('Music runs from 3 to 600 seconds.', file=sys.stderr)
358
+ return USAGE
359
+ body = {'prompt': text, 'music_length_ms': int(round(args.length * 1000)), 'force_instrumental': bool(args.instrumental)}
360
+ return _generate(args, '/v1/music', body, 'Music generation',
361
+ {'kind': 'music', 'prompt': text[:120], 'seconds': args.length, 'instrumental': args.instrumental}, text)
362
+
363
+
364
+ def build_parser():
365
+ parser = argparse.ArgumentParser(prog='voice.py', description='Voices for the audio workspace.')
366
+ sub = parser.add_subparsers(dest='command', required=True)
367
+ engines = sub.add_parser('engines', help='the engines this machine can use')
368
+ engines.add_argument('--json', action='store_true')
369
+ voices = sub.add_parser('voices', help='the voices of one engine')
370
+ voices.add_argument('--engine', choices=('piper', 'elevenlabs'), required=True)
371
+ voices.add_argument('--search', type=el.text_arg, default='')
372
+ voices.add_argument('--limit', type=int, default=30)
373
+ voices.add_argument('--json', action='store_true')
374
+ speak = sub.add_parser('speak', help='one text, one file')
375
+ speak.add_argument('--engine', choices=('piper', 'elevenlabs'), required=True)
376
+ speak.add_argument('--text-file', type=el.text_arg, required=True)
377
+ speak.add_argument('--out', type=el.text_arg, required=True)
378
+ speak.add_argument('--voice', type=el.text_arg, default='')
379
+ speak.add_argument('--model', type=el.text_arg, default=os.environ.get('ELEVENLABS_MODEL') or el.DEFAULT_MODEL)
380
+ speak.add_argument('--confirm', action='store_true', help='ElevenLabs only: actually send; without it only the plan is printed')
381
+ speak.add_argument('--force', action='store_true')
382
+ speak.add_argument('--json', action='store_true')
383
+ sound = sub.add_parser('sound', help='a sound effect from a description (ElevenLabs)')
384
+ sound.add_argument('--text-file', type=el.text_arg, required=True)
385
+ sound.add_argument('--out', type=el.text_arg, required=True)
386
+ sound.add_argument('--duration', type=float, default=None, help='0.5 to 30 seconds; left out, ElevenLabs chooses')
387
+ sound.add_argument('--loop', action='store_true', help='a sound that repeats without a seam')
388
+ sound.add_argument('--influence', type=float, default=0.3, help='how closely to follow the description, 0 to 1')
389
+ sound.add_argument('--confirm', action='store_true')
390
+ sound.add_argument('--force', action='store_true')
391
+ sound.add_argument('--json', action='store_true')
392
+ music = sub.add_parser('music', help='music from a description (ElevenLabs)')
393
+ music.add_argument('--text-file', type=el.text_arg, required=True)
394
+ music.add_argument('--out', type=el.text_arg, required=True)
395
+ music.add_argument('--length', type=float, default=30, help='3 to 600 seconds')
396
+ music.add_argument('--instrumental', action='store_true', help='no vocals')
397
+ music.add_argument('--confirm', action='store_true')
398
+ music.add_argument('--force', action='store_true')
399
+ music.add_argument('--json', action='store_true')
400
+ over = sub.add_parser('voiceover', help='a script, spoken over a video')
401
+ over.add_argument('--engine', choices=('piper', 'elevenlabs'), required=True)
402
+ over.add_argument('--script', type=el.text_arg, required=True)
403
+ over.add_argument('--video', type=el.text_arg, required=True)
404
+ over.add_argument('--out', type=el.text_arg, required=True)
405
+ over.add_argument('--voice', type=el.text_arg, default='')
406
+ over.add_argument('--model', type=el.text_arg, default=os.environ.get('ELEVENLABS_MODEL') or el.DEFAULT_MODEL)
407
+ over.add_argument('--confirm', action='store_true')
408
+ over.add_argument('--force', action='store_true')
409
+ over.add_argument('--drop-optional', action='store_true')
410
+ over.add_argument('--json', action='store_true')
411
+ return parser
412
+
413
+
414
+ def main(argv):
415
+ args = build_parser().parse_args(argv)
416
+ if args.command in ('speak', 'voiceover') and args.engine == 'elevenlabs' and not args.voice:
417
+ args.voice = os.environ.get('ELEVENLABS_VOICE', '')
418
+ if not args.voice:
419
+ print('No ElevenLabs voice. Pass --voice <id>, or set voice_id in settings.yml (list them: -a voices --engine elevenlabs).', file=sys.stderr)
420
+ return USAGE
421
+ try:
422
+ return {'engines': cmd_engines, 'voices': cmd_voices, 'speak': cmd_speak, 'voiceover': cmd_voiceover,
423
+ 'sound': cmd_sound, 'music': cmd_music}[args.command](args)
424
+ except el.Stop as stop:
425
+ print(stop, file=sys.stderr)
426
+ return stop.code
427
+
428
+
429
+ if __name__ == '__main__':
430
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ ## The host side of the Jira plugin: twg, the Teamwork Graph CLI, on this machine and signed in.
3
+ ## Run by core/scripts/plugins.sh --install jira (--check: report only) and in full by install.sh.
4
+ ## Offered, never automatic: twg is one OAuth session per person, not a machine-wide tool.
5
+ set -u
6
+ confirm() { local a; read -r -p "$1 [y/N] " a < /dev/tty 2>/dev/null || return 1; [[ "${a}" =~ ^[Yy] ]]; }
7
+
8
+ # Docker CREATES a missing bind-mount source as a root-owned directory. The plugin mounts ~/.local/bin
9
+ # (the directory, so `twg update` on the host is live in the container) and ~/.config/twg; a twg that
10
+ # is a DIRECTORY at ~/.local/bin/twg is such a placeholder from an older mount, and blocks installing.
11
+ TWG_PLACEHOLDER="${HOME}/.local/bin/twg"
12
+ if [ -d "${TWG_PLACEHOLDER}" ] ; then
13
+ if [ -z "$(ls -A "${TWG_PLACEHOLDER}" 2>/dev/null)" ] && rmdir "${TWG_PLACEHOLDER}" 2>/dev/null ; then
14
+ echo " ✓ removed the empty ${TWG_PLACEHOLDER/#${HOME}/~} placeholder, so twg can be installed there" ;
15
+ else
16
+ echo " ✗ ${TWG_PLACEHOLDER/#${HOME}/~} is a directory — remove it so twg can be installed there (sudo rmdir ${TWG_PLACEHOLDER})" ;
17
+ fi
18
+ fi
19
+
20
+ # twg installs into ~/.local/bin, which is not always on PATH, so `command -v` alone
21
+ # would report it missing on a machine that has it. The dashboard looks for the binary
22
+ # at a path for the same reason.
23
+ TWG_BIN='';
24
+ for candidate in "$(command -v twg 2>/dev/null)" "${HOME}/.local/bin/twg" /usr/local/bin/twg ; do
25
+ [ -n "${candidate}" ] && [ -f "${candidate}" ] && [ -x "${candidate}" ] && { TWG_BIN="${candidate}"; break; }
26
+ done
27
+ if [ -n "${TWG_BIN}" ] ; then
28
+ # Installed is not the same as signed in, and the difference is the whole symptom:
29
+ # the dashboard's Jira gate is a BINARY check, so the button appears the moment the
30
+ # file exists and every Jira action then fails with `AUTH_REQUIRED`. Reporting only
31
+ # the binary sends you looking for a broken button instead of an unfinished login.
32
+ # Read from the CLI's own state file rather than by calling the API: a survey must
33
+ # not make a network round trip, and must never touch the token.
34
+ # Detect the ERROR shape, not a success marker. twg prints `{"projects": [...]}` on
35
+ # success with NO "ok" field at all — only failures carry `"ok": false` and a code — so
36
+ # the first version of this check (grep for `"ok": true`) reported EVERY signed-in CLI as
37
+ # signed out. Matching the failure is also robust to the success shape changing.
38
+ if ! "${TWG_BIN}" jira space list --limit 1 2>&1 | grep -qE '"ok": *false|AUTH_REQUIRED|authentication required' ; then
39
+ printf ' ✓ %-16s %s\n' "twg" "${TWG_BIN} — signed in" ;
40
+ else
41
+ printf ' ✗ %-16s %s\n' "twg" "${TWG_BIN} — NOT signed in: run ${TWG_BIN/#${HOME}/~} login --force" ;
42
+ printf ' %-18s %s\n' "" "The Jira button appears anyway (the gate is a binary check), and every" ;
43
+ printf ' %-18s %s\n' "" "Jira action answers 'authentication required' until you have signed in." ;
44
+ fi
45
+ else
46
+ printf ' ✗ %-16s %s\n' "twg" "missing — the Jira button stays hidden (teamworkgraph.com/cli)" ;
47
+ fi
48
+
49
+
50
+ [ "${1:-}" == '--check' ] && exit 0 ;
51
+
52
+ # twg — the Teamwork Graph CLI, which is how the dashboard and the Jira agents read and
53
+ # write Jira. https://teamworkgraph.com/cli · https://developer.atlassian.com/cloud/twg-cli/
54
+ # It installs into ~/.local/bin and authenticates in the browser, so it is offered rather
55
+ # than assumed: it is one OAuth session per person, not a machine-wide tool.
56
+ # Its last step is a consent prompt that needs a controlling terminal — so this works
57
+ # when a person runs the installer and fails under -y in a pipe, where it says to pass
58
+ # --agree. Recording that consent is a person's to give, so it is not passed here.
59
+ if [ -z "${TWG_BIN}" ] && confirm "Install twg, the Teamwork Graph CLI (Jira from the terminal)?" ; then
60
+ # NOT `curl … | bash`, which is what Atlassian documents and which installs NOTHING
61
+ # here. Its last step records consent, that prompt needs a controlling terminal, and
62
+ # on failure the installer DELETES the binary it just downloaded:
63
+ #
64
+ # Interactive auth prompting is unavailable … without a controlling terminal.
65
+ # Pass --agree or --yes to record consent without an interactive prompt.
66
+ # Command failed: ~/.local/bin/twg consent --source direct-public-installer
67
+ #
68
+ # `--yes` alone does not help: the script forwards it to consent only when --yes,
69
+ # --skip-login AND --skip-skills are all set. And passing --agree from here would
70
+ # record a person's agreement to Atlassian's terms on their behalf, which is not
71
+ # this script's to give — so the release binary is fetched directly and consent is
72
+ # left to the first `twg login`, where a person answers it.
73
+ #
74
+ # The download is CHECKSUM-VERIFIED against the SHA256SUMS file published beside it.
75
+ # Fetching a 100MB binary over https and running it unverified would be worse than
76
+ # the pipe this replaces.
77
+ twg_arch='x64'; [ "$(uname -m)" == 'aarch64' ] && twg_arch='arm64' ;
78
+ twg_ver="$(curl -fsSL --retry 2 https://teamwork-graph.atlassian.com/cli/install 2>/dev/null \
79
+ | sed -n 's/^DEFAULT_VERSION="\([^"]*\)".*/\1/p' | head -1)" ;
80
+ if [ -z "${twg_ver}" ] ; then
81
+ echo " ✗ could not read the current twg version from teamwork-graph.atlassian.com — skipped." ;
82
+ else
83
+ twg_asset="twg-linux-${twg_arch}-v${twg_ver}" ;
84
+ twg_tmp="$(mktemp -d)" ;
85
+ if curl -fsSL --retry 2 -o "${twg_tmp}/twg" "https://teamwork-graph.atlassian.com/cli/${twg_asset}" \
86
+ && curl -fsSL --retry 2 -o "${twg_tmp}/sums" "https://teamwork-graph.atlassian.com/cli/SHA256SUMS-v${twg_ver}" ; then
87
+ twg_want="$(awk -v a="${twg_asset}" '$2 == a { print $1 }' "${twg_tmp}/sums")" ;
88
+ twg_got="$(sha256sum "${twg_tmp}/twg" | awk '{print $1}')" ;
89
+ if [ -n "${twg_want}" ] && [ "${twg_want}" == "${twg_got}" ] ; then
90
+ mkdir -p "${HOME}/.local/bin" ;
91
+ install -m 0755 "${twg_tmp}/twg" "${HOME}/.local/bin/twg" ;
92
+ TWG_BIN="${HOME}/.local/bin/twg" ;
93
+ echo " ✓ twg ${twg_ver} installed to ~/.local/bin/twg (sha256 verified)" ;
94
+ # The mount is disabled while twg is absent, so it has to be turned back on now
95
+ # — Docker recreates the placeholder on every start otherwise. See host-mounts.sh.
96
+ true ;
97
+ echo "" ;
98
+ echo " One step is yours, because it needs a browser:" ;
99
+ echo " ~/.local/bin/twg login --force" ;
100
+ echo " It prints a URL and a code (OAuth DEVICE authorization), so the browser does" ;
101
+ echo " not have to be on this machine — open the URL anywhere, enter the code, and" ;
102
+ echo " the CLI finishes on its own. Check it with: ~/.local/bin/twg jira space list --limit 1" ;
103
+ echo " The dashboard shares ~/.config/twg, so it sees the session with no restart." ;
104
+ case ":${PATH}:" in
105
+ *":${HOME}/.local/bin:"*) ;;
106
+ *) echo " ~/.local/bin is not on your PATH — add it, or call twg by its full path." ;;
107
+ esac
108
+ else
109
+ echo " ✗ twg download did not match the published sha256 — NOT installed." ;
110
+ echo " expected ${twg_want:-<no entry for ${twg_asset}>}" ;
111
+ echo " got ${twg_got}" ;
112
+ fi
113
+ else
114
+ echo " ✗ could not download ${twg_asset} — check network access." ;
115
+ fi
116
+ rm -rf "${twg_tmp}" ;
117
+ fi
118
+ fi
119
+
@@ -0,0 +1,2 @@
1
+ ~/.config/twg
2
+ ~/.local/bin
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+
3
+ argparse "$@" <<ARGEOF || exit 1
4
+ parser.add_argument('PROJECT_NAME',
5
+ help='The project directory in this workspace.')
6
+ parser.add_argument('-l', '--link',
7
+ default="_none_",
8
+ help='Link the project to an Upsun project ID (upsun project:set-remote). Find IDs with: upsun projects')
9
+ parser.add_argument('-i', '--init',
10
+ action='store_true',
11
+ default=False,
12
+ help='Scaffold .upsun/config.yaml with upsun project:init (interactive).')
13
+ parser.add_argument('-p', '--push',
14
+ action='store_true',
15
+ default=False,
16
+ help='Deploy the current branch with upsun push. Outward-facing: upsun asks for confirmation.')
17
+ parser.add_argument('-e', '--environment',
18
+ default="_none_",
19
+ help='Environment for THIS run: the --push target, and the source --pull-db/--pull-files take data from. Default: the one remembered by --select.')
20
+ parser.add_argument('-E', '--environments',
21
+ action='store_true',
22
+ default=False,
23
+ help='List the environments (branches) of the linked Upsun project.')
24
+ parser.add_argument('-s', '--select',
25
+ default="_none_",
26
+ help='Remember which environment this project works against, in .upsun/local/ (git-excluded). The pulls then need no flag. Checked against the real environment list.')
27
+ parser.add_argument('-D', '--pull-db',
28
+ action='store_true',
29
+ default=False,
30
+ help='Dump the selected environment\'s database and import it into DDEV. REPLACES the local database — the current one is exported to backups/ first. Client data: it lands on this machine.')
31
+ parser.add_argument('-F', '--pull-files',
32
+ action='store_true',
33
+ default=False,
34
+ help='Download the selected environment\'s public files mount into the local docroot (adds and overwrites, never deletes). Client data.')
35
+ parser.add_argument('-g', '--logs',
36
+ action='store_true',
37
+ default=False,
38
+ help='Show the most recent deploy log.')
39
+ parser.add_argument('-a', '--activities',
40
+ action='store_true',
41
+ default=False,
42
+ help='List the last 10 Upsun activities.')
43
+ parser.add_argument('-B', '--no-safety-backup',
44
+ action='store_true',
45
+ default=False,
46
+ help='Skip exporting the local database before --pull-db replaces it. The export exists because a pull that ate an afternoon of work is a bad pull, so skipping it is a choice, not a default.')
47
+ parser.add_argument('-f', '--force',
48
+ action='store_true',
49
+ default=False,
50
+ help='Allow --link to relink a project that already points at a different Upsun ID.')
51
+ ARGEOF