@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.
- package/LICENSE +339 -0
- package/README.md +28 -364
- package/core/bin/workspace.js +135 -0
- package/core/config/settings.labels.yml +514 -0
- package/core/config/settings.template.yml +292 -0
- package/core/config/style.yml +53 -0
- package/core/config/theme.default.yml +161 -0
- package/core/config/theme.dispatch.yml +166 -0
- package/core/config/theme.drupal.yml +175 -0
- package/core/config/theme.ember.yml +160 -0
- package/core/config/theme.github.yml +172 -0
- package/core/config/theme.gitlab.yml +168 -0
- package/core/config/theme.press.yml +161 -0
- package/core/config/theme.slate.yml +160 -0
- package/core/config/theme.upsun.yml +174 -0
- package/core/docs/architecture.svg +65 -0
- package/core/docs/getting-started.md +51 -0
- package/core/docs/plugins.md +27 -0
- package/core/docs/releases.md +23 -0
- package/core/docs/testing.md +15 -0
- package/core/docs/workspaces.md +25 -0
- package/core/scripts/args/arg-ai-items.sh +65 -0
- package/core/scripts/args/arg-autostart.sh +31 -0
- package/core/scripts/args/arg-avatar-agent.sh +28 -0
- package/core/scripts/args/arg-avatar-catalog.sh +12 -0
- package/core/scripts/args/arg-avatar-character.sh +68 -0
- package/core/scripts/args/arg-avatar-voice.sh +24 -0
- package/core/scripts/args/arg-avatar.sh +22 -0
- package/core/scripts/args/arg-checks.sh +36 -0
- package/core/scripts/args/arg-clone.sh +42 -0
- package/core/scripts/args/arg-commands.sh +51 -0
- package/core/scripts/args/arg-cucumber.sh +34 -0
- package/core/scripts/args/arg-distribution.sh +30 -0
- package/core/scripts/args/arg-drupal.sh +39 -0
- package/core/scripts/args/arg-drupal_cms.sh +18 -0
- package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
- package/core/scripts/args/arg-graphify.sh +36 -0
- package/core/scripts/args/arg-graphs.sh +12 -0
- package/core/scripts/args/arg-hostnames.sh +23 -0
- package/core/scripts/args/arg-milvus-backup.sh +8 -0
- package/core/scripts/args/arg-milvus-mcp.sh +12 -0
- package/core/scripts/args/arg-milvus.sh +32 -0
- package/core/scripts/args/arg-obsidian.sh +17 -0
- package/core/scripts/args/arg-odoo.sh +18 -0
- package/core/scripts/args/arg-penpot-backup.sh +8 -0
- package/core/scripts/args/arg-penpot-mcp.sh +15 -0
- package/core/scripts/args/arg-penpot-token.sh +17 -0
- package/core/scripts/args/arg-penpot.sh +44 -0
- package/core/scripts/args/arg-rag.sh +21 -0
- package/core/scripts/args/arg-ragify.sh +35 -0
- package/core/scripts/args/arg-reclaim.sh +18 -0
- package/core/scripts/args/arg-release.sh +21 -0
- package/core/scripts/args/arg-screen.sh +28 -0
- package/core/scripts/args/arg-site-template.sh +13 -0
- package/core/scripts/args/arg-spdd.sh +16 -0
- package/core/scripts/args/arg-specs.sh +22 -0
- package/core/scripts/args/arg-storage.sh +36 -0
- package/core/scripts/args/arg-testing.sh +36 -0
- package/core/scripts/args/arg-webship.sh +34 -0
- package/core/scripts/args/arg-webships-project.sh +22 -0
- package/core/scripts/args/arg-webships-site-template.sh +17 -0
- package/core/scripts/args/arg-webships.sh +34 -0
- package/core/scripts/args/arg-website-site-template.sh +17 -0
- package/core/scripts/args/arg-website.sh +25 -0
- package/core/scripts/args/arg-wordpress.sh +18 -0
- package/core/scripts/autostart-run.sh +162 -0
- package/core/scripts/autostart-stop.sh +30 -0
- package/core/scripts/autostart-watch.sh +43 -0
- package/core/scripts/autostart.sh +26 -0
- package/core/scripts/bootstrap-functions.sh +117 -0
- package/core/scripts/bootstrap-libraries.sh +10 -0
- package/core/scripts/bootstrap.sh +71 -0
- package/core/scripts/ddev-shim/ddev +122 -0
- package/core/scripts/docker-gid.sh +119 -0
- package/core/scripts/functions/arg-site-template.sh +28 -0
- package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
- package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
- package/core/scripts/functions/fun-ai-items.sh +621 -0
- package/core/scripts/functions/fun-ai-providers.sh +72 -0
- package/core/scripts/functions/fun-autostart.sh +429 -0
- package/core/scripts/functions/fun-avatar.sh +441 -0
- package/core/scripts/functions/fun-backup-item.sh +50 -0
- package/core/scripts/functions/fun-build-distribution.sh +226 -0
- package/core/scripts/functions/fun-build-site-template.sh +104 -0
- package/core/scripts/functions/fun-build-webships.sh +161 -0
- package/core/scripts/functions/fun-build-website.sh +199 -0
- package/core/scripts/functions/fun-checks.sh +243 -0
- package/core/scripts/functions/fun-clone-project.sh +289 -0
- package/core/scripts/functions/fun-commands.sh +521 -0
- package/core/scripts/functions/fun-database.sh +63 -0
- package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
- package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
- package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
- package/core/scripts/functions/fun-default-settings.sh +28 -0
- package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
- package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
- package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
- package/core/scripts/functions/fun-distribution-webship.sh +28 -0
- package/core/scripts/functions/fun-distribution-webships.sh +33 -0
- package/core/scripts/functions/fun-distribution-website.sh +19 -0
- package/core/scripts/functions/fun-drush.sh +48 -0
- package/core/scripts/functions/fun-elevenlabs.sh +179 -0
- package/core/scripts/functions/fun-files.sh +19 -0
- package/core/scripts/functions/fun-git-worktrees.sh +142 -0
- package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
- package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
- package/core/scripts/functions/fun-milvus.sh +584 -0
- package/core/scripts/functions/fun-odoo.sh +121 -0
- package/core/scripts/functions/fun-penpot.sh +724 -0
- package/core/scripts/functions/fun-project-name.sh +20 -0
- package/core/scripts/functions/fun-rag.sh +253 -0
- package/core/scripts/functions/fun-ragify.sh +165 -0
- package/core/scripts/functions/fun-reclaim.sh +297 -0
- package/core/scripts/functions/fun-release.sh +159 -0
- package/core/scripts/functions/fun-remove-project.sh +319 -0
- package/core/scripts/functions/fun-screen.sh +96 -0
- package/core/scripts/functions/fun-spdd.sh +676 -0
- package/core/scripts/functions/fun-specs-share.sh +237 -0
- package/core/scripts/functions/fun-storage.sh +347 -0
- package/core/scripts/functions/fun-testing.sh +329 -0
- package/core/scripts/functions/fun-users.sh +87 -0
- package/core/scripts/functions/fun-version.sh +72 -0
- package/core/scripts/functions/fun-video-edit.sh +48 -0
- package/core/scripts/functions/fun-voice.sh +45 -0
- package/core/scripts/functions/fun-wordpress.sh +59 -0
- package/core/scripts/host-mounts.sh +161 -0
- package/core/scripts/install.sh +602 -0
- package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
- package/core/scripts/libs/argparse.sh +93 -0
- package/core/scripts/libs/bash-yaml.sh +52 -0
- package/core/scripts/libs/checks_plan.js +312 -0
- package/core/scripts/libs/elevenlabs.py +627 -0
- package/core/scripts/libs/progress-bar.sh +19 -0
- package/core/scripts/libs/rag_index.py +446 -0
- package/core/scripts/libs/video_edit.py +636 -0
- package/core/scripts/libs/voice.py +430 -0
- package/core/scripts/plugins/jira/host-install.sh +119 -0
- package/core/scripts/plugins/jira/host-mounts.txt +2 -0
- package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
- package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
- package/core/scripts/plugins/upsun/host-install.sh +99 -0
- package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
- package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
- package/core/scripts/plugins.sh +152 -0
- package/core/scripts/release.sh +69 -0
- package/core/scripts/storage.sh +39 -0
- package/core/scripts/sync-tooling.sh +246 -0
- package/core/scripts/tests/cmd-smoke-test.sh +109 -0
- package/core/scripts/trust-https.sh +257 -0
- package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
- package/core/workspace-app/.ddev/config.yaml +400 -0
- package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
- package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
- package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
- package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
- package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
- package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +9 -1
- package/core/workspace-app/README.md +380 -0
- package/{actions → core/workspace-app/actions}/ddev.js +0 -1
- package/{actions → core/workspace-app/actions}/rag.js +0 -1
- package/{actions → core/workspace-app/actions}/testing.js +0 -1
- package/{assistant.js → core/workspace-app/assistant.js} +1 -10
- package/{git-info.js → core/workspace-app/git-info.js} +17 -1
- package/core/workspace-app/package.json +29 -0
- package/core/workspace-app/plugin-catalog.js +50 -0
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{server.js → core/workspace-app/server.js} +18 -0
- package/core/workspace-app/tests/features/actions-rail.feature +44 -0
- package/core/workspace-app/tests/features/ai-items.feature +23 -0
- package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
- package/core/workspace-app/tests/features/checks.feature +40 -0
- package/core/workspace-app/tests/features/clone-project.feature +30 -0
- package/core/workspace-app/tests/features/command-authoring.feature +58 -0
- package/core/workspace-app/tests/features/dashboard.feature +57 -0
- package/core/workspace-app/tests/features/menus.feature +29 -0
- package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
- package/core/workspace-app/tests/features/plugins.feature +20 -0
- package/core/workspace-app/tests/features/project-actions.feature +41 -0
- package/core/workspace-app/tests/features/screens.feature +29 -0
- package/core/workspace-app/tests/features/settings.feature +46 -0
- package/core/workspace-app/tests/features/test-reports.feature +32 -0
- package/core/workspace-app/tests/features/theme.feature +40 -0
- package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
- package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +157 -0
- package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -0
- package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
- package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
- package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
- package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
- package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
- package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
- package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
- package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
- package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
- package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
- package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
- package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
- package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
- package/core/workspace-app/tests/unit/assistant.test.js +61 -0
- package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
- package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
- package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
- package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
- package/core/workspace-app/tests/unit/browsers.test.js +76 -0
- package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
- package/core/workspace-app/tests/unit/build-order.test.js +153 -0
- package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
- package/core/workspace-app/tests/unit/builders.test.js +158 -0
- package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
- package/core/workspace-app/tests/unit/changelog.test.js +223 -0
- package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
- package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
- package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
- package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
- package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
- package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
- package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
- package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
- package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
- package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
- package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
- package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
- package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
- package/core/workspace-app/tests/unit/create-card.test.js +85 -0
- package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
- package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
- package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-domains.test.js +179 -0
- package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
- package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
- package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
- package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
- package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
- package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
- package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
- package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
- package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
- package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
- package/core/workspace-app/tests/unit/density.test.js +52 -0
- package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
- package/core/workspace-app/tests/unit/event-log.test.js +102 -0
- package/core/workspace-app/tests/unit/favicon.test.js +68 -0
- package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
- package/core/workspace-app/tests/unit/git-info.test.js +26 -0
- package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
- package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
- package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
- package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
- package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
- package/core/workspace-app/tests/unit/images-create.test.js +59 -0
- package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
- package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
- package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
- package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
- package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
- package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
- package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
- package/core/workspace-app/tests/unit/narration.test.js +186 -0
- package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
- package/core/workspace-app/tests/unit/navigation.test.js +254 -0
- package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
- package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
- package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
- package/core/workspace-app/tests/unit/notes.test.js +128 -0
- package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
- package/core/workspace-app/tests/unit/page-width.test.js +59 -0
- package/core/workspace-app/tests/unit/php-version.test.js +90 -0
- package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
- package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
- package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
- package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
- package/core/workspace-app/tests/unit/plugins.test.js +58 -0
- package/core/workspace-app/tests/unit/preconditions.test.js +21 -0
- package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
- package/core/workspace-app/tests/unit/project-make.test.js +143 -0
- package/core/workspace-app/tests/unit/project-name.test.js +202 -0
- package/core/workspace-app/tests/unit/reader.test.js +62 -0
- package/core/workspace-app/tests/unit/release.test.js +92 -0
- package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
- package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
- package/core/workspace-app/tests/unit/round-11.test.js +31 -0
- package/core/workspace-app/tests/unit/round-12.test.js +37 -0
- package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
- package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
- package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
- package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
- package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
- package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
- package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
- package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
- package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
- package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
- package/core/workspace-app/tests/unit/screens.test.js +93 -0
- package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
- package/core/workspace-app/tests/unit/session-name.test.js +64 -0
- package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
- package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
- package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
- package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
- package/core/workspace-app/tests/unit/speech.test.js +290 -0
- package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
- package/core/workspace-app/tests/unit/status-language.test.js +47 -0
- package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
- package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
- package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
- package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
- package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
- package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
- package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
- package/core/workspace-app/tests/unit/themes.test.js +453 -0
- package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
- package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
- package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
- package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
- package/core/workspace-app/tests/unit/version-check.test.js +98 -0
- package/core/workspace-app/tests/unit/version-link.test.js +50 -0
- package/core/workspace-app/tests/unit/video-borrow.test.js +220 -0
- package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
- package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
- package/core/workspace-app/tests/unit/video-edit.test.js +436 -0
- package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
- package/core/workspace-app/tests/unit/waiting.test.js +48 -0
- package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
- package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
- package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
- package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
- package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
- package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
- package/package.json +38 -36
- /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
- /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
- /package/{actions → core/workspace-app/actions}/audio.js +0 -0
- /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
- /package/{actions → core/workspace-app/actions}/checks.js +0 -0
- /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
- /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
- /package/{actions → core/workspace-app/actions}/composer.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
- /package/{actions → core/workspace-app/actions}/folders.js +0 -0
- /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
- /package/{actions → core/workspace-app/actions}/images.js +0 -0
- /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
- /package/{actions → core/workspace-app/actions}/node.js +0 -0
- /package/{actions → core/workspace-app/actions}/notes.js +0 -0
- /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
- /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
- /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
- /package/{actions → core/workspace-app/actions}/screens.js +0 -0
- /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
- /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
- /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
- /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
- /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
- /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
- /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
- /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
- /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
- /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
- /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
- /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
- /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
- /package/{checks.js → core/workspace-app/checks.js} +0 -0
- /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
- /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
- /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
- /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
- /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
- /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
- /package/{commands.js → core/workspace-app/commands.js} +0 -0
- /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
- /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
- /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
- /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
- /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
- /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
- /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
- /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
- /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
- /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
- /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
- /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/README.md +0 -0
- /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
- /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
- /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
- /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
- /package/{folders.js → core/workspace-app/folders.js} +0 -0
- /package/{format.js → core/workspace-app/format.js} +0 -0
- /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
- /package/{html.js → core/workspace-app/html.js} +0 -0
- /package/{icons.js → core/workspace-app/icons.js} +0 -0
- /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
- /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
- /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
- /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
- /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
- /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
- /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
- /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
- /package/{images.js → core/workspace-app/images.js} +0 -0
- /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
- /package/{linger.js → core/workspace-app/linger.js} +0 -0
- /package/{lists.js → core/workspace-app/lists.js} +0 -0
- /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
- /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
- /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
- /package/{notes.js → core/workspace-app/notes.js} +0 -0
- /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
- /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
- /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
- /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
- /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
- /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
- /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
- /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
- /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
- /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
- /package/{public → core/workspace-app/public}/css/base.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/favicon.ico +0 -0
- /package/{public → core/workspace-app/public}/favicon.png +0 -0
- /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
- /package/{public → core/workspace-app/public}/logo.png +0 -0
- /package/{public → core/workspace-app/public}/media-player.js +0 -0
- /package/{public → core/workspace-app/public}/mic.js +0 -0
- /package/{public → core/workspace-app/public}/narration.js +0 -0
- /package/{public → core/workspace-app/public}/palette.js +0 -0
- /package/{public → core/workspace-app/public}/sortable.js +0 -0
- /package/{public → core/workspace-app/public}/suggest.js +0 -0
- /package/{public → core/workspace-app/public}/terminal.js +0 -0
- /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
- /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
- /package/{public → core/workspace-app/public}/ui.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
- /package/{rag.js → core/workspace-app/rag.js} +0 -0
- /package/{reader.js → core/workspace-app/reader.js} +0 -0
- /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
- /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
- /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
- /package/{screenrc → core/workspace-app/screenrc} +0 -0
- /package/{screens.js → core/workspace-app/screens.js} +0 -0
- /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
- /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
- /package/{speech.js → core/workspace-app/speech.js} +0 -0
- /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
- /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
- /package/{themes.js → core/workspace-app/themes.js} +0 -0
- /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
- /package/{urls.js → core/workspace-app/urls.js} +0 -0
- /package/{validate.js → core/workspace-app/validate.js} +0 -0
- /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
- /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
- /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
- /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
- /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
- /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
- /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comfortable or compact project rows.
|
|
3
|
+
*
|
|
4
|
+
* A project card carries about a dozen buttons, each with a small caption under it ("36 commands", "linked",
|
|
5
|
+
* "locked"). The research on well-known dashboards (Railway, Render, GitHub Primer, GitLab Pajamas, Linear, the
|
|
6
|
+
* WordPress DataViews) agrees a row should show its actions and fold the notes. Compact does that without
|
|
7
|
+
* hiding an action, and without the choice being made for anybody.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8');
|
|
16
|
+
const server = read('server.js');
|
|
17
|
+
const ui = read('public/ui.js');
|
|
18
|
+
const css = read('public/css/components/density.css');
|
|
19
|
+
|
|
20
|
+
test('the choice is made before first paint, in the same place as the page width', () => {
|
|
21
|
+
assert.match(server, /d\.dataset\.density=wsPref\('ws-density',null\)==='compact'\?'compact':'comfortable';/);
|
|
22
|
+
assert.ok(!/`/.test(server.split("How dense a project row is")[1].split('</script>')[0]),
|
|
23
|
+
'no backtick in the pre-paint script: it lives inside a template literal');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('the switch sits beside the page width, on pages that list projects only', () => {
|
|
27
|
+
assert.match(server, /function densitySwitcherHtml\(\)/);
|
|
28
|
+
assert.match(server, /\$\{!files && !home \? densitySwitcherHtml\(\) : ''\}/);
|
|
29
|
+
assert.match(server, /class="density-pick" data-density="\$\{esc\(v\)\}"/);
|
|
30
|
+
assert.match(server, /aria-label="How dense the project rows are"/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('it is a pressed state, remembered like the width, and the caption becomes the tooltip', () => {
|
|
34
|
+
assert.match(ui, /e\.target\.closest\('\.density-pick'\)/);
|
|
35
|
+
assert.match(ui, /wsPrefSet\('ws-density', density\)/);
|
|
36
|
+
assert.match(ui, /for \(const btn of document\.querySelectorAll\('\.density-pick'\)\)/);
|
|
37
|
+
assert.match(ui, /btn\.title = btn\.title \? `\$\{btn\.title\} · \$\{text\}` : text;/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('compact folds the caption away from sight, not from a screen reader', () => {
|
|
41
|
+
assert.match(css, /html\[data-density="compact"\] \.act-with-status > \.act-status \{/);
|
|
42
|
+
assert.match(css, /clip: rect\(0 0 0 0\); clip-path: inset\(50%\)/);
|
|
43
|
+
assert.ok(!/display:\s*none/.test(css.split('.act-status')[1].split('}')[0]), 'display: none would hide it from assistive technology too');
|
|
44
|
+
assert.match(require('../../themes').assembleCss(''), /\/\* component: density \*\//);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('the palette offers both, through the same preference', () => {
|
|
48
|
+
const js = read('public/palette.js');
|
|
49
|
+
assert.match(js, /label: 'Rows: compact'/);
|
|
50
|
+
assert.match(js, /label: 'Rows: comfortable'/);
|
|
51
|
+
assert.match(js, /window\.wsPrefSet\('ws-density', value\)/);
|
|
52
|
+
});
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const { execFile, spawnSync } = require('child_process');
|
|
8
|
+
const yaml = require('js-yaml');
|
|
9
|
+
|
|
10
|
+
const REPO = path.join(__dirname, '../../../..');
|
|
11
|
+
const CORE = path.join(REPO, 'core');
|
|
12
|
+
const COMMAND = path.join(REPO, 'videos/cmd-tools-elevenlabs.sh');
|
|
13
|
+
const KEY = 'test-key-1234567890';
|
|
14
|
+
const VOICE = 'JBFqnCBsd6RMkjVDRZzb';
|
|
15
|
+
const have = (tool) => spawnSync('which', [tool]).status === 0;
|
|
16
|
+
const media = have('ffmpeg') && have('ffprobe') && have('python3');
|
|
17
|
+
const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), 'elevenlabs-'));
|
|
18
|
+
|
|
19
|
+
function workspace(block = '') {
|
|
20
|
+
const root = tmp();
|
|
21
|
+
fs.mkdirSync(path.join(root, 'config'));
|
|
22
|
+
fs.writeFileSync(path.join(root, 'config/settings.yml'), `workspaces:\n - videos\n${block}`);
|
|
23
|
+
return { root, config: path.join(root, 'config') };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function run(args, { ws, env = {}, bin } = {}) {
|
|
27
|
+
const base = { ...process.env };
|
|
28
|
+
for (const k of Object.keys(base)) if (k.startsWith('ELEVENLABS_')) delete base[k];
|
|
29
|
+
const w = ws || workspace();
|
|
30
|
+
const full = { ...base, WORKSPACE_ROOT: w.root, WORKSPACE_CONFIG: w.config, ...env };
|
|
31
|
+
if (bin) full.PATH = `${bin}:${full.PATH}`;
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
execFile('bash', [COMMAND, ...args], { env: full, cwd: w.root }, (error, stdout, stderr) => {
|
|
34
|
+
resolve({ code: error ? error.code : 0, stdout, stderr, ws: w });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function tone(seconds) {
|
|
40
|
+
return spawnSync('ffmpeg', ['-v', 'error', '-f', 'lavfi', '-i', `sine=frequency=300:duration=${seconds.toFixed(2)}`, '-f', 'mp3', '-'], { maxBuffer: 1 << 26 }).stdout;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stub() {
|
|
44
|
+
const seen = [];
|
|
45
|
+
const server = http.createServer((req, res) => {
|
|
46
|
+
const chunks = [];
|
|
47
|
+
req.on('data', (c) => chunks.push(c));
|
|
48
|
+
req.on('end', () => {
|
|
49
|
+
const body = Buffer.concat(chunks);
|
|
50
|
+
const url = new URL(req.url, 'http://x');
|
|
51
|
+
seen.push({ method: req.method, path: url.pathname, query: url.search, key: req.headers['xi-api-key'], body });
|
|
52
|
+
const json = (code, obj) => { res.writeHead(code, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(obj)); };
|
|
53
|
+
if (req.headers['xi-api-key'] !== KEY) return json(401, { detail: { status: 'invalid_api_key', message: 'Invalid API key' } });
|
|
54
|
+
if (url.pathname === '/v1/user/subscription') return json(200, { tier: 'creator', status: 'active', character_count: 1000, character_limit: 100000, next_character_count_reset_unix: 1796000000 });
|
|
55
|
+
if (url.pathname === '/v2/voices') return json(200, { voices: [{ voice_id: VOICE, name: 'George', category: 'premade', labels: { accent: 'british' } }] });
|
|
56
|
+
if (url.pathname.startsWith('/v1/text-to-speech/')) {
|
|
57
|
+
const text = JSON.parse(body.toString()).text;
|
|
58
|
+
const audio = tone((text.length / 15) * (text.includes('TOOLONG') ? 2 : 1));
|
|
59
|
+
res.writeHead(200, { 'Content-Type': 'audio/mpeg', 'request-id': 'req_test', 'x-character-count': String(text.length) });
|
|
60
|
+
return res.end(audio);
|
|
61
|
+
}
|
|
62
|
+
if (url.pathname === '/v1/audio-isolation') { res.writeHead(200, { 'Content-Type': 'audio/mpeg' }); return res.end(tone(3)); }
|
|
63
|
+
return json(404, {});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve({
|
|
67
|
+
base: `http://127.0.0.1:${server.address().port}`, seen, close: () => server.close(),
|
|
68
|
+
posts: () => seen.filter((r) => r.method === 'POST'),
|
|
69
|
+
})));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const SCRIPT = `# Demo (draft 1)
|
|
73
|
+
|
|
74
|
+
## How to use this script
|
|
75
|
+
|
|
76
|
+
Ignore this section.
|
|
77
|
+
|
|
78
|
+
## S01 Opening (00:00.0 to 00:04.0)
|
|
79
|
+
|
|
80
|
+
**On screen:** the home page.
|
|
81
|
+
|
|
82
|
+
> Hello from the **first** section.
|
|
83
|
+
|
|
84
|
+
## S02 Middle part (00:04.0 to 00:10.0)
|
|
85
|
+
|
|
86
|
+
> One sentence with \`code\` and a [link](https://example.com/x).
|
|
87
|
+
> *(optional)* A spare line to drop when time runs short.
|
|
88
|
+
|
|
89
|
+
## S03 Close (00:10.0 to 00:12.0)
|
|
90
|
+
|
|
91
|
+
> Goodbye.
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
function fixtures() {
|
|
95
|
+
const dir = tmp();
|
|
96
|
+
const script = path.join(dir, 'draft-1-voiceover-script.md');
|
|
97
|
+
const video = path.join(dir, 'draft-1.mp4');
|
|
98
|
+
fs.writeFileSync(script, SCRIPT);
|
|
99
|
+
if (media) {
|
|
100
|
+
spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=12:size=160x90:rate=5', '-f', 'lavfi', '-i', 'sine=frequency=100:duration=12',
|
|
101
|
+
'-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-shortest', video]);
|
|
102
|
+
spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', video, '-frames:v', '1', path.join(dir, 'draft-1-poster.jpg')]);
|
|
103
|
+
}
|
|
104
|
+
return { dir, script, video };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const seconds = (file) => Number(spawnSync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', file]).stdout.toString());
|
|
108
|
+
|
|
109
|
+
/* ---- settings and wiring ---- */
|
|
110
|
+
|
|
111
|
+
// A machine's settings.yml holds its real key, so the committed copy can only be checked in a git checkout.
|
|
112
|
+
const CHECKOUT = fs.existsSync(path.join(REPO, '.git'));
|
|
113
|
+
|
|
114
|
+
test('the committed settings carry the block with placeholders, and every key is labelled', { skip: CHECKOUT ? false : 'not a git checkout: settings.yml is this machine\'s own' }, () => {
|
|
115
|
+
const settings = yaml.load(fs.readFileSync(path.join(CORE, 'config/settings.yml'), 'utf8'));
|
|
116
|
+
assert.deepStrictEqual(settings.elevenlabs, { api_key: 'CHANGE_ME', voice_id: 'ask', model_id: 'eleven_v4', region: 'global', pronunciation: '' });
|
|
117
|
+
const labels = yaml.load(fs.readFileSync(path.join(CORE, 'config/settings.labels.yml'), 'utf8'));
|
|
118
|
+
for (const k of ['api_key', 'voice_id', 'model_id', 'region', 'pronunciation']) {
|
|
119
|
+
assert.ok(labels[`elevenlabs.${k}`] && labels[`elevenlabs.${k}`].label && labels[`elevenlabs.${k}`].info, `elevenlabs.${k}`);
|
|
120
|
+
}
|
|
121
|
+
assert.deepStrictEqual(Object.keys(labels['elevenlabs.region'].options), ['global', 'us', 'eu', 'in', 'sg']);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('the key row is masked by the settings editor like every other secret', () => {
|
|
125
|
+
const { isSecretKey } = require('../../settings-file');
|
|
126
|
+
assert.strictEqual(isSecretKey('api_key', 'elevenlabs.api_key'), true);
|
|
127
|
+
assert.strictEqual(isSecretKey('voice_id', 'elevenlabs.voice_id'), false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('the function file is loaded by bootstrap, and the scripts parse', () => {
|
|
131
|
+
assert.ok(fs.readFileSync(path.join(CORE, 'scripts/bootstrap-functions.sh'), 'utf8').includes('functions/fun-elevenlabs.sh'));
|
|
132
|
+
for (const f of [COMMAND, path.join(CORE, 'scripts/functions/fun-elevenlabs.sh')]) assert.strictEqual(spawnSync('bash', ['-n', f]).status, 0, f);
|
|
133
|
+
if (have('python3')) assert.strictEqual(spawnSync('python3', ['-c', 'import ast,sys; ast.parse(open(sys.argv[1]).read())', path.join(CORE, 'scripts/libs/elevenlabs.py')]).status, 0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('the catalog offers the two free commands on the videos workspace, and the script they run exists', () => {
|
|
137
|
+
const cc = require('../../commands');
|
|
138
|
+
cc.invalidateCatalog();
|
|
139
|
+
const cat = cc.loadCatalog();
|
|
140
|
+
const mine = cat.builtin.filter((e) => e.id.startsWith('elevenlabs-'));
|
|
141
|
+
assert.deepStrictEqual(mine.map((e) => e.id), ['elevenlabs-status', 'elevenlabs-check']);
|
|
142
|
+
for (const e of mine) {
|
|
143
|
+
assert.deepStrictEqual(e.when, ['workspace:videos']);
|
|
144
|
+
assert.ok(fs.existsSync(path.join(REPO, e.script)), e.script);
|
|
145
|
+
assert.doesNotMatch(e.args, /confirm|--out/, 'a catalog command must not spend credits');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/* ---- the command, without the network ---- */
|
|
150
|
+
|
|
151
|
+
test('help lists every action and exits 0', async () => {
|
|
152
|
+
const r = await run(['--help']);
|
|
153
|
+
assert.strictEqual(r.code, 0);
|
|
154
|
+
for (const a of ['status', 'init', 'sections', 'check', 'voices', 'speak', 'voiceover', 'isolate', 'polish', 'mcp-register']) assert.ok(r.stdout.includes(a), a);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('status says no key and exits 3 for a missing key and for the placeholder', async () => {
|
|
158
|
+
for (const block of ['', 'elevenlabs:\n api_key: CHANGE_ME\n voice_id: ask\n']) {
|
|
159
|
+
const r = await run(['-a', 'status'], { ws: workspace(block) });
|
|
160
|
+
assert.strictEqual(r.code, 3);
|
|
161
|
+
assert.match(r.stdout, /key:\s+not set/);
|
|
162
|
+
assert.doesNotMatch(r.stdout, /CHANGE_ME/);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('status finds a key in settings.yml or the environment and never prints it', async () => {
|
|
167
|
+
const fromFile = await run(['-a', 'status'], { ws: workspace(`elevenlabs:\n api_key: "${KEY}"\n voice_id: ${VOICE}\n region: eu\n`) });
|
|
168
|
+
assert.strictEqual(fromFile.code, 0);
|
|
169
|
+
assert.match(fromFile.stdout, /set \(settings\.yml\)/);
|
|
170
|
+
assert.ok(fromFile.stdout.includes(VOICE) && fromFile.stdout.includes('api.eu.residency.elevenlabs.io'));
|
|
171
|
+
const fromEnv = await run(['-a', 'status'], { env: { ELEVENLABS_API_KEY: KEY } });
|
|
172
|
+
assert.match(fromEnv.stdout, /set \(environment\)/);
|
|
173
|
+
for (const r of [fromFile, fromEnv]) assert.ok(!r.stdout.includes(KEY) && !r.stderr.includes(KEY));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('init adds the block once, with a placeholder, and leaves the rest of the file alone', async () => {
|
|
177
|
+
const ws = workspace();
|
|
178
|
+
const before = fs.readFileSync(path.join(ws.config, 'settings.yml'), 'utf8');
|
|
179
|
+
const first = await run(['-a', 'init'], { ws });
|
|
180
|
+
assert.strictEqual(first.code, 0);
|
|
181
|
+
const after = fs.readFileSync(path.join(ws.config, 'settings.yml'), 'utf8');
|
|
182
|
+
assert.ok(after.startsWith(before));
|
|
183
|
+
assert.deepStrictEqual(yaml.load(after).elevenlabs, { api_key: 'CHANGE_ME', voice_id: 'ask', model_id: 'eleven_v4', region: 'global', pronunciation: '' });
|
|
184
|
+
const second = await run(['-a', 'init'], { ws });
|
|
185
|
+
assert.match(second.stdout, /already has/);
|
|
186
|
+
assert.strictEqual(fs.readFileSync(path.join(ws.config, 'settings.yml'), 'utf8'), after);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('an unknown action exits 2', async () => {
|
|
190
|
+
const r = await run(['-a', 'nonsense']);
|
|
191
|
+
assert.strictEqual(r.code, 2);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('no key: an action that needs one exits 3 before anything is sent', async () => {
|
|
195
|
+
const r = await run(['-a', 'voices']);
|
|
196
|
+
assert.strictEqual(r.code, 3);
|
|
197
|
+
assert.match(r.stderr, /No ElevenLabs key/);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('an argument cannot carry a command: unsafe characters are refused before anything runs', async () => {
|
|
201
|
+
const dir = tmp();
|
|
202
|
+
const marker = path.join(dir, 'RAN');
|
|
203
|
+
for (const args of [['--voice', `$(touch ${marker})`, '--out', 'x.mp3'], ['--voice', VOICE, '--out', `x\`touch ${marker}\`.mp3`], ['--voice', 'a"b', '--out', 'x.mp3']]) {
|
|
204
|
+
const r = await run(['-a', 'speak', '--text-file', path.join(dir, 'none.txt'), ...args], { env: { ELEVENLABS_API_KEY: KEY } });
|
|
205
|
+
assert.strictEqual(r.code, 2, JSON.stringify(args));
|
|
206
|
+
assert.match(r.stderr, /may not contain/);
|
|
207
|
+
}
|
|
208
|
+
assert.ok(!fs.existsSync(marker));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/* ---- the script ---- */
|
|
212
|
+
|
|
213
|
+
test('sections: the timed sections, cleaned of markdown, with the optional line counted apart', { skip: !have('python3') }, async () => {
|
|
214
|
+
const { script } = fixtures();
|
|
215
|
+
const r = await run(['-a', 'sections', '--script', script, '--json']);
|
|
216
|
+
assert.strictEqual(r.code, 0);
|
|
217
|
+
const found = JSON.parse(r.stdout).sections;
|
|
218
|
+
assert.deepStrictEqual(found.map((s) => [s.id, s.start, s.end, s.window]), [['S01', 0, 4, 4], ['S02', 4, 10, 6], ['S03', 10, 12, 2]]);
|
|
219
|
+
assert.strictEqual(found[0].text, 'Hello from the first section.');
|
|
220
|
+
assert.strictEqual(found[1].text, 'One sentence with code and a link. A spare line to drop when time runs short.');
|
|
221
|
+
const dropped = JSON.parse((await run(['-a', 'sections', '--script', script, '--json', '--drop-optional'])).stdout).sections;
|
|
222
|
+
assert.strictEqual(dropped[1].text, 'One sentence with code and a link.');
|
|
223
|
+
assert.ok(found[1].optional_chars > 0 && dropped[1].chars < found[1].chars);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('sections: a script without timed sections is refused with the format, not guessed at', { skip: !have('python3') }, async () => {
|
|
227
|
+
const dir = tmp();
|
|
228
|
+
const plain = path.join(dir, 'plain.md');
|
|
229
|
+
fs.writeFileSync(plain, '# A script\n\nJust some lines.\n> A quote\n');
|
|
230
|
+
const r = await run(['-a', 'sections', '--script', plain]);
|
|
231
|
+
assert.strictEqual(r.code, 1);
|
|
232
|
+
assert.match(r.stderr, /No timed sections found/);
|
|
233
|
+
fs.writeFileSync(plain, '## S01 One (00:05.0 to 00:02.0)\n> Hi.\n');
|
|
234
|
+
assert.match((await run(['-a', 'sections', '--script', plain])).stderr, /ends before it starts/);
|
|
235
|
+
fs.writeFileSync(plain, '## S01 One (00:00.0 to 00:05.0)\n> Hi.\n## S02 Two (00:03.0 to 00:09.0)\n> Again.\n');
|
|
236
|
+
assert.match((await run(['-a', 'sections', '--script', plain])).stderr, /starts before the previous/);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/* ---- the network, against a stub ---- */
|
|
240
|
+
|
|
241
|
+
test('check reads the account and voices lists them; the key travels as a header and never on a command line', { skip: !have('python3') }, async () => {
|
|
242
|
+
const api = await stub();
|
|
243
|
+
const bin = tmp();
|
|
244
|
+
const log = path.join(bin, 'python3.log');
|
|
245
|
+
fs.writeFileSync(path.join(bin, 'python3'), `#!/bin/bash\nprintf '%s\\n' "$*" >> "${log}"\nprintf 'env=%s\\n' "\${ELEVENLABS_API_KEY:+present}" >> "${log}"\nexec "$(PATH="\${PATH#*:}" command -v python3)" "$@"\n`, { mode: 0o755 });
|
|
246
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
247
|
+
const check = await run(['-a', 'check'], { env, bin });
|
|
248
|
+
const voices = await run(['-a', 'voices'], { env, bin });
|
|
249
|
+
api.close();
|
|
250
|
+
assert.strictEqual(check.code, 0);
|
|
251
|
+
assert.match(check.stdout, /Plan: creator \(active\)/);
|
|
252
|
+
assert.match(check.stdout, /Remaining: 99000/);
|
|
253
|
+
assert.match(voices.stdout, new RegExp(`${VOICE}\\s+George`));
|
|
254
|
+
assert.ok(api.seen.length >= 2 && api.seen.every((r) => r.key === KEY));
|
|
255
|
+
const logged = fs.readFileSync(log, 'utf8');
|
|
256
|
+
assert.ok(!logged.includes(KEY), 'the key must not be on a command line');
|
|
257
|
+
assert.match(logged, /env=present/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('a rejected key is reported plainly, without the key', { skip: !have('python3') }, async () => {
|
|
261
|
+
const api = await stub();
|
|
262
|
+
const dir = tmp();
|
|
263
|
+
fs.writeFileSync(path.join(dir, 't.txt'), 'hello');
|
|
264
|
+
const r = await run(['-a', 'speak', '--text-file', path.join(dir, 't.txt'), '--out', path.join(dir, 'o.mp3'), '--voice', VOICE, '--confirm'],
|
|
265
|
+
{ env: { ELEVENLABS_API_KEY: 'wrong-key-0000', ELEVENLABS_API_BASE: api.base } });
|
|
266
|
+
api.close();
|
|
267
|
+
assert.strictEqual(r.code, 1);
|
|
268
|
+
assert.match(r.stderr, /HTTP 401\. ElevenLabs rejected the key\. Invalid API key/);
|
|
269
|
+
assert.ok(!r.stderr.includes('wrong-key-0000'));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('speak sends nothing without --confirm, then writes the audio once and refuses to overwrite it', { skip: !media }, async () => {
|
|
273
|
+
const api = await stub();
|
|
274
|
+
const dir = tmp();
|
|
275
|
+
fs.writeFileSync(path.join(dir, 't.txt'), 'Hello there, this is a test.');
|
|
276
|
+
const out = path.join(dir, 'o.mp3');
|
|
277
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
278
|
+
const args = ['-a', 'speak', '--text-file', path.join(dir, 't.txt'), '--out', out, '--voice', VOICE];
|
|
279
|
+
const plan = await run(args, { env });
|
|
280
|
+
assert.strictEqual(plan.code, 4);
|
|
281
|
+
assert.match(plan.stdout, /nothing was sent/);
|
|
282
|
+
assert.strictEqual(api.posts().length, 0);
|
|
283
|
+
assert.ok(!fs.existsSync(out));
|
|
284
|
+
const done = await run([...args, '--confirm'], { env });
|
|
285
|
+
assert.strictEqual(done.code, 0);
|
|
286
|
+
const post = api.posts()[0];
|
|
287
|
+
assert.strictEqual(post.path, `/v1/text-to-speech/${VOICE}`);
|
|
288
|
+
assert.match(post.query, /output_format=mp3_44100_128/);
|
|
289
|
+
assert.deepStrictEqual(JSON.parse(post.body.toString()), { text: 'Hello there, this is a test.', model_id: 'eleven_v4' });
|
|
290
|
+
assert.ok(seconds(out) > 1);
|
|
291
|
+
const again = await run([...args, '--confirm'], { env });
|
|
292
|
+
api.close();
|
|
293
|
+
assert.strictEqual(again.code, 1);
|
|
294
|
+
assert.match(again.stderr, /already exists.*Nothing was sent/);
|
|
295
|
+
assert.strictEqual(api.posts().length, 1, 'refusing to overwrite must happen before any request');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('brand names are respelled only in the text sent to the voice: Webship as Var-dot, Webship as Var-base, plus settings', { skip: !media }, async () => {
|
|
299
|
+
const api = await stub();
|
|
300
|
+
const dir = tmp();
|
|
301
|
+
fs.writeFileSync(path.join(dir, 't.txt'), "This is Webship 11, Webship's Drupal distribution, from Acme.");
|
|
302
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
303
|
+
const args = ['-a', 'speak', '--text-file', path.join(dir, 't.txt'), '--voice', VOICE, '--confirm'];
|
|
304
|
+
const one = await run([...args, '--out', path.join(dir, 'a.mp3')], { env });
|
|
305
|
+
assert.strictEqual(one.code, 0);
|
|
306
|
+
assert.strictEqual(JSON.parse(api.posts()[0].body.toString()).text, "This is Var-base 11, Var-dot's Drupal distribution, from Acme.");
|
|
307
|
+
const ws = workspace('elevenlabs:\n pronunciation: "Acme=Ack-me"\n');
|
|
308
|
+
const two = await run([...args, '--out', path.join(dir, 'b.mp3')], { env, ws });
|
|
309
|
+
assert.strictEqual(two.code, 0);
|
|
310
|
+
assert.strictEqual(JSON.parse(api.posts()[1].body.toString()).text, "This is Var-base 11, Var-dot's Drupal distribution, from Ack-me.");
|
|
311
|
+
assert.strictEqual(fs.readFileSync(path.join(dir, 't.txt'), 'utf8'), "This is Webship 11, Webship's Drupal distribution, from Acme.", 'the text file keeps the real spelling');
|
|
312
|
+
api.close();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('voiceover: a plan first, then speech per section placed on its window over the draft, with only changed sections re-sent', { skip: !media }, async () => {
|
|
316
|
+
const api = await stub();
|
|
317
|
+
const { dir, script, video } = fixtures();
|
|
318
|
+
const out = path.join(dir, 'final.mp4');
|
|
319
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
320
|
+
const args = ['-a', 'voiceover', '--script', script, '--video', video, '--out', out, '--voice', VOICE];
|
|
321
|
+
const plan = await run(args, { env });
|
|
322
|
+
assert.strictEqual(plan.code, 4);
|
|
323
|
+
assert.match(plan.stdout, /3 sections|S01/);
|
|
324
|
+
assert.strictEqual(api.posts().length, 0);
|
|
325
|
+
assert.ok(!fs.existsSync(out));
|
|
326
|
+
|
|
327
|
+
const done = await run([...args, '--confirm', '--json'], { env });
|
|
328
|
+
assert.strictEqual(done.code, 0, done.stderr);
|
|
329
|
+
assert.strictEqual(api.posts().length, 3);
|
|
330
|
+
assert.ok(Math.abs(seconds(out) - seconds(video)) < 0.2, 'the video keeps its length');
|
|
331
|
+
const streams = spawnSync('ffprobe', ['-v', 'error', '-show_entries', 'stream=codec_type,codec_name', '-of', 'csv=p=0', out]).stdout.toString();
|
|
332
|
+
assert.match(streams, /h264,video/);
|
|
333
|
+
assert.match(streams, /aac,audio/);
|
|
334
|
+
const base = path.join(dir, 'final');
|
|
335
|
+
assert.ok(fs.existsSync(`${base}-voiceover.mp3`) && fs.existsSync(`${base}-poster.jpg`));
|
|
336
|
+
const report = JSON.parse(fs.readFileSync(`${base}-voiceover.json`, 'utf8'));
|
|
337
|
+
assert.deepStrictEqual(report.sections.map((s) => [s.id, s.fit]), [['S01', 'ok'], ['S02', 'ok'], ['S03', 'ok']]);
|
|
338
|
+
assert.ok(!JSON.stringify(report).includes(KEY));
|
|
339
|
+
assert.ok(!fs.existsSync(path.join(`${base}-voiceover-sections`, '_mix.wav')), 'the large intermediate is removed');
|
|
340
|
+
|
|
341
|
+
const edited = path.join(dir, 'edited.md');
|
|
342
|
+
fs.writeFileSync(edited, SCRIPT.replace('Goodbye.', 'Goodbye for now.'));
|
|
343
|
+
const second = await run(['-a', 'voiceover', '--script', edited, '--video', video, '--out', path.join(dir, 'final2.mp4'), '--voice', VOICE,
|
|
344
|
+
'--work', `${base}-voiceover-sections`, '--confirm'], { env });
|
|
345
|
+
api.close();
|
|
346
|
+
assert.strictEqual(second.code, 0, second.stderr);
|
|
347
|
+
assert.strictEqual(api.posts().length, 4, 'only the edited section is sent again');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('voiceover: a section that cannot fit its window stops the run with exit 5 and makes no video', { skip: !media }, async () => {
|
|
351
|
+
const api = await stub();
|
|
352
|
+
const { dir, script, video } = fixtures();
|
|
353
|
+
const long = path.join(dir, 'long.md');
|
|
354
|
+
fs.writeFileSync(long, SCRIPT.replace('Goodbye.', 'TOOLONG Goodbye and thank you for watching.'));
|
|
355
|
+
const out = path.join(dir, 'final.mp4');
|
|
356
|
+
const r = await run(['-a', 'voiceover', '--script', long, '--video', video, '--out', out, '--voice', VOICE, '--confirm'],
|
|
357
|
+
{ env: { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base } });
|
|
358
|
+
api.close();
|
|
359
|
+
assert.strictEqual(r.code, 5);
|
|
360
|
+
assert.match(r.stdout, /S03 Close/);
|
|
361
|
+
assert.match(r.stdout, /Finished sections are kept/);
|
|
362
|
+
assert.ok(!fs.existsSync(out));
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('voiceover and isolate refuse to overwrite before spending anything', { skip: !media }, async () => {
|
|
366
|
+
const api = await stub();
|
|
367
|
+
const { dir, script, video } = fixtures();
|
|
368
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
369
|
+
const out = path.join(dir, 'final.mp4');
|
|
370
|
+
fs.writeFileSync(out, 'x');
|
|
371
|
+
const a = await run(['-a', 'voiceover', '--script', script, '--video', video, '--out', out, '--voice', VOICE, '--confirm'], { env });
|
|
372
|
+
fs.rmSync(out);
|
|
373
|
+
fs.writeFileSync(path.join(dir, 'final-voiceover.mp3'), 'x');
|
|
374
|
+
const b = await run(['-a', 'voiceover', '--script', script, '--video', video, '--out', out, '--voice', VOICE, '--confirm'], { env });
|
|
375
|
+
const take = path.join(dir, 'take.wav');
|
|
376
|
+
spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'sine=frequency=250:duration=3', take]);
|
|
377
|
+
fs.writeFileSync(path.join(dir, 'clean.mp3'), 'x');
|
|
378
|
+
const c = await run(['-a', 'isolate', '--audio', take, '--out', path.join(dir, 'clean.mp3'), '--confirm'], { env });
|
|
379
|
+
fs.writeFileSync(path.join(dir, 'p.mp4'), 'x');
|
|
380
|
+
const d = await run(['-a', 'polish', '--audio', take, '--video', video, '--out', path.join(dir, 'p.mp4'), '--isolate', '--confirm'], { env });
|
|
381
|
+
api.close();
|
|
382
|
+
for (const r of [a, b, c, d]) { assert.strictEqual(r.code, 1); assert.match(r.stderr, /already exists/); }
|
|
383
|
+
assert.strictEqual(api.posts().length, 0);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('voiceover: a script that runs past the video is refused as the wrong draft', { skip: !media }, async () => {
|
|
387
|
+
const api = await stub();
|
|
388
|
+
const { dir, script } = fixtures();
|
|
389
|
+
const short = path.join(dir, 'short.mp4');
|
|
390
|
+
spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=5:size=160x90:rate=5', '-pix_fmt', 'yuv420p', short]);
|
|
391
|
+
const r = await run(['-a', 'voiceover', '--script', script, '--video', short, '--out', path.join(dir, 'o.mp4'), '--voice', VOICE],
|
|
392
|
+
{ env: { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base } });
|
|
393
|
+
api.close();
|
|
394
|
+
assert.strictEqual(r.code, 1);
|
|
395
|
+
assert.match(r.stderr, /right draft/);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('polish levels a recorded take onto the draft, locally; cleaning it with ElevenLabs needs --confirm', { skip: !media }, async () => {
|
|
399
|
+
const api = await stub();
|
|
400
|
+
const { dir, video } = fixtures();
|
|
401
|
+
const take = path.join(dir, 'take.wav');
|
|
402
|
+
spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'sine=frequency=250:duration=20', take]);
|
|
403
|
+
const env = { ELEVENLABS_API_KEY: KEY, ELEVENLABS_API_BASE: api.base };
|
|
404
|
+
const local = await run(['-a', 'polish', '--audio', take, '--video', video, '--out', path.join(dir, 'p.mp4')], { env });
|
|
405
|
+
assert.strictEqual(local.code, 0, local.stderr);
|
|
406
|
+
assert.ok(Math.abs(seconds(path.join(dir, 'p.mp4')) - 12) < 0.2, 'a long take is trimmed to the video');
|
|
407
|
+
assert.match(local.stdout, /Trimmed/);
|
|
408
|
+
assert.strictEqual(api.seen.length, 0, 'a local polish sends nothing');
|
|
409
|
+
const out = path.join(dir, 'c.mp4');
|
|
410
|
+
const plan = await run(['-a', 'polish', '--audio', take, '--video', video, '--out', out, '--isolate'], { env });
|
|
411
|
+
assert.strictEqual(plan.code, 4);
|
|
412
|
+
assert.strictEqual(api.posts().length, 0);
|
|
413
|
+
const done = await run(['-a', 'polish', '--audio', take, '--video', video, '--out', out, '--isolate', '--confirm'], { env });
|
|
414
|
+
api.close();
|
|
415
|
+
assert.strictEqual(done.code, 0, done.stderr);
|
|
416
|
+
assert.strictEqual(api.posts().filter((r) => r.path === '/v1/audio-isolation').length, 1);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
/* ---- the hosted MCP server ---- */
|
|
420
|
+
|
|
421
|
+
function fakeClaude(existing) {
|
|
422
|
+
const bin = tmp();
|
|
423
|
+
const log = path.join(bin, 'claude.log');
|
|
424
|
+
fs.writeFileSync(path.join(bin, 'claude'), `#!/bin/bash\nprintf '%s\\n' "$*" >> "${log}"\n[ "$2" = get ] && ${existing ? 'exit 0' : 'exit 1'}\nexit 0\n`, { mode: 0o755 });
|
|
425
|
+
return { bin, calls: () => (fs.existsSync(log) ? fs.readFileSync(log, 'utf8').trim().split('\n') : []) };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
test('mcp-register adds the hosted server over HTTP with OAuth, stores no key, and warns that updates publish directly', async () => {
|
|
429
|
+
const fake = fakeClaude(false);
|
|
430
|
+
const r = await run(['-a', 'mcp-register'], { bin: fake.bin, env: { ELEVENLABS_API_KEY: KEY } });
|
|
431
|
+
assert.strictEqual(r.code, 0);
|
|
432
|
+
assert.ok(fake.calls().includes('mcp add --transport http --scope user elevenlabs https://api.elevenlabs.io/v1/mcp'));
|
|
433
|
+
assert.ok(!fake.calls().join('\n').includes(KEY));
|
|
434
|
+
assert.match(r.stdout, /OAuth/);
|
|
435
|
+
assert.match(r.stdout, /published directly/);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('mcp-register does nothing when it is already registered, and uses the region address', async () => {
|
|
439
|
+
const fake = fakeClaude(true);
|
|
440
|
+
const r = await run(['-a', 'mcp-register'], { bin: fake.bin, ws: workspace('elevenlabs:\n api_key: CHANGE_ME\n region: eu\n') });
|
|
441
|
+
assert.match(r.stdout, /already registered/);
|
|
442
|
+
assert.ok(!fake.calls().some((c) => c.startsWith('mcp add')));
|
|
443
|
+
const fresh = fakeClaude(false);
|
|
444
|
+
await run(['-a', 'mcp-register'], { bin: fresh.bin, ws: workspace('elevenlabs:\n api_key: CHANGE_ME\n region: eu\n') });
|
|
445
|
+
assert.ok(fresh.calls().includes('mcp add --transport http --scope user elevenlabs https://api.eu.residency.elevenlabs.io/v1/mcp'));
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test('mcp-unregister removes it from the user scope', async () => {
|
|
449
|
+
const fake = fakeClaude(true);
|
|
450
|
+
const r = await run(['-a', 'mcp-unregister'], { bin: fake.bin });
|
|
451
|
+
assert.strictEqual(r.code, 0);
|
|
452
|
+
assert.ok(fake.calls().includes('mcp remove --scope user elevenlabs'));
|
|
453
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard's own log, and the workspace a request belongs to.
|
|
3
|
+
*
|
|
4
|
+
* A refused New screen form left nothing behind, and the form defaulted to the workspace root, which the
|
|
5
|
+
* dispatcher then rejected as an unknown workspace, while the page it came from was the Dev workspace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
const { test } = require('node:test');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8');
|
|
15
|
+
const server = read('server.js');
|
|
16
|
+
|
|
17
|
+
function freshLog() {
|
|
18
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-log-'));
|
|
19
|
+
process.env.WORKSPACE_APP_LOG = path.join(dir, 'logs', 'dashboard.log');
|
|
20
|
+
delete require.cache[require.resolve('../../event-log')];
|
|
21
|
+
return { log: require('../../event-log'), file: process.env.WORKSPACE_APP_LOG };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('an event is one JSON line with a time, a level and the event', () => {
|
|
25
|
+
const { log, file } = freshLog();
|
|
26
|
+
log.logEvent('warn', 'action.rejected', { action: '/actions/screen-new', status: 400 });
|
|
27
|
+
const row = JSON.parse(fs.readFileSync(file, 'utf8').trim());
|
|
28
|
+
assert.equal(row.level, 'warn');
|
|
29
|
+
assert.equal(row.event, 'action.rejected');
|
|
30
|
+
assert.equal(row.status, 400);
|
|
31
|
+
assert.match(row.time, /^\d{4}-\d\d-\d\dT/);
|
|
32
|
+
assert.equal((fs.statSync(file).mode & 0o777), 0o600, 'readable by this user only');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('a prompt is clipped, and secrets in it never reach the file', () => {
|
|
36
|
+
const { log } = freshLog();
|
|
37
|
+
const text = log.clip('use sk-abcdefghijklmnop1234 and password: hunter2 and Bearer abcdefgh12345678 then ghp_abcdefghijklmnop1234');
|
|
38
|
+
assert.ok(!/sk-abcdef|hunter2|abcdefgh12345678|ghp_abcdef/.test(text), text);
|
|
39
|
+
assert.match(text, /\[redacted\]/);
|
|
40
|
+
assert.ok(log.clip('x'.repeat(900), 500).length <= 501, 'clipped to the limit');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('html in a message becomes text', () => {
|
|
44
|
+
const { log } = freshLog();
|
|
45
|
+
assert.equal(log.plain('<div class="msg error">Unknown workspace.</div>'), 'Unknown workspace.');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('the file rotates past its size and keeps a few', () => {
|
|
49
|
+
const { log, file } = freshLog();
|
|
50
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
51
|
+
fs.writeFileSync(file, 'x'.repeat(log.MAX_BYTES + 10));
|
|
52
|
+
log.logEvent('info', 'after.rotation');
|
|
53
|
+
assert.ok(fs.existsSync(`${file}.1`), 'the old one is kept');
|
|
54
|
+
assert.equal(JSON.parse(fs.readFileSync(file, 'utf8').trim()).event, 'after.rotation');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('a log that cannot be written never throws', () => {
|
|
58
|
+
const blocker = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ws-log-')), 'file');
|
|
59
|
+
fs.writeFileSync(blocker, 'x');
|
|
60
|
+
process.env.WORKSPACE_APP_LOG = path.join(blocker, 'logs', 'dashboard.log');
|
|
61
|
+
delete require.cache[require.resolve('../../event-log')];
|
|
62
|
+
assert.doesNotThrow(() => require('../../event-log').logEvent('error', 'x'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('a form that names no workspace takes the one its host names', () => {
|
|
66
|
+
assert.match(server, /const workspace = form\.workspace \|\| hostWorkspaceOf\(req\) \|\| 'dev';/);
|
|
67
|
+
assert.match(server, /String\(q\.get\('workspace'\) \|\| hostWorkspaceOf\(req\)\)/,
|
|
68
|
+
'and the New screen form opens on it');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('New screen in the workspace root is a place, not an unknown workspace', () => {
|
|
72
|
+
assert.match(server, /const isRootScreen = pathname === '\/actions\/screen-new' && workspace === 'root';/);
|
|
73
|
+
assert.match(server, /!isRootScreen && !isValidWorkspace\(workspace\)/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('every refusal, failure and crash is logged', () => {
|
|
77
|
+
assert.match(server, /if \(status >= 400\) logRejected\(pathname, form, workspace, status, html\);/);
|
|
78
|
+
assert.match(server, /eventLog\.logEvent\('error', 'action\.failed'/);
|
|
79
|
+
assert.match(server, /process\.on\('uncaughtExceptionMonitor'/);
|
|
80
|
+
assert.match(server, /eventLog\.logEvent\('info', 'app\.started'/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('a rejected screen keeps its prompt', () => {
|
|
84
|
+
assert.match(server, /prompt: eventLog\.clip\(form\.command, 500\)/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('on a workspace address the workspace is not asked', () => {
|
|
88
|
+
const screens = read('screens.js');
|
|
89
|
+
assert.match(screens, /const locked = !!p\.hostWorkspace && p\.hostWorkspace === ws;/);
|
|
90
|
+
assert.match(screens, /locked \? `<input type="hidden" name="workspace" value="\$\{esc\(ws\)\}">`/,
|
|
91
|
+
'a hidden field carries it, so the folder list and the action still know it');
|
|
92
|
+
assert.match(server, /newScreenFormHtml\(pre && \{ \.\.\.pre, hostWorkspace: hostWorkspaceOf\(req\) \}\)/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('a new screen starts knowing its workspace, project and folder', () => {
|
|
96
|
+
const act = read('actions/screens.js');
|
|
97
|
+
assert.match(act, /WEBSHIP_WORKSPACE: pm \? pm\[1\] : wm \? wm\[1\] : ''/);
|
|
98
|
+
assert.match(act, /WEBSHIP_PROJECT: pm \? pm\[2\] : ''/);
|
|
99
|
+
assert.match(act, /WEBSHIP_WORKSPACE_DIR: dir/);
|
|
100
|
+
assert.match(act, /inner = \/\^exec \/\.test\(inner\) \? `exec env \$\{vars\} \$\{inner\.slice\(5\)\}`/,
|
|
101
|
+
'inside the exec: a command that stops starting with exec is piped through tee, and claude then prints and quits');
|
|
102
|
+
});
|