@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,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editing a settings file as raw YAML, beside the field-by-field form.
|
|
3
|
+
*
|
|
4
|
+
* The form is the safer way and stays the default: it edits one value on one known line and leaves the
|
|
5
|
+
* comments, the quoting and the list blocks exactly as they were. But some changes are not a value — a
|
|
6
|
+
* key that does not exist yet, a block moved, a comment written for the next person — and for those the
|
|
7
|
+
* form has no answer at all.
|
|
8
|
+
*
|
|
9
|
+
* The rule it must not break is the one the whole settings editor is built around: **a secret's value
|
|
10
|
+
* never leaves the server.** A raw editor sends the whole file out and takes the whole file back, so
|
|
11
|
+
* every secret goes out wearing a mask and any value still wearing it comes back as what is on disk.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const assert = require('node:assert');
|
|
15
|
+
const { test } = require('node:test');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const yaml = require('js-yaml');
|
|
20
|
+
|
|
21
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'settings-raw-'));
|
|
22
|
+
process.env.WORKSPACE_CONFIG = tmp;
|
|
23
|
+
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
24
|
+
|
|
25
|
+
const sf = require('../../settings-file');
|
|
26
|
+
const { CONFIG_DIR } = require('../../workspaces');
|
|
27
|
+
|
|
28
|
+
// A fixture, not the machine's own settings: these tests must never read a real token, and a test that
|
|
29
|
+
// writes over somebody's configuration to prove it can write is not a test anyone should run twice.
|
|
30
|
+
const FIXTURE = `# The hub settings.
|
|
31
|
+
account:
|
|
32
|
+
# The admin username a fresh install is created with.
|
|
33
|
+
user: webmaster
|
|
34
|
+
pass: not-a-secret-by-design
|
|
35
|
+
figma:
|
|
36
|
+
token: fig-abc
|
|
37
|
+
git:
|
|
38
|
+
github:
|
|
39
|
+
token: gh-one
|
|
40
|
+
drupal:
|
|
41
|
+
token: dr-two
|
|
42
|
+
bitbucket:
|
|
43
|
+
token: bb-three
|
|
44
|
+
style:
|
|
45
|
+
mode: light # light | dark | system
|
|
46
|
+
workspaces:
|
|
47
|
+
- dev
|
|
48
|
+
- test
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const FILE = 'settings.yml';
|
|
52
|
+
const full = path.join(CONFIG_DIR, FILE);
|
|
53
|
+
const restore = () => fs.writeFileSync(full, FIXTURE);
|
|
54
|
+
restore();
|
|
55
|
+
|
|
56
|
+
test('every secret leaves wearing a mask, and its value does not', () => {
|
|
57
|
+
restore();
|
|
58
|
+
const read = sf.readSettingsText(FILE);
|
|
59
|
+
assert.ok(read, 'the file is readable');
|
|
60
|
+
const rows = sf.readSettingsRows(FILE).filter((r) => r.secret && r.value.trim());
|
|
61
|
+
assert.strictEqual(rows.length, 4, 'the fixture holds four secrets — and account.pass is not one of them');
|
|
62
|
+
const out = read.text.split('\n');
|
|
63
|
+
for (const r of rows) {
|
|
64
|
+
assert.ok(read.masked.includes(r.dotted), `${r.dotted} is masked`);
|
|
65
|
+
assert.ok(out[r.line].includes(sf.SECRET_MASK), `${r.dotted}'s line carries the mask`);
|
|
66
|
+
assert.ok(!out[r.line].includes(r.value), `${r.dotted}'s value is not on its line`);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('the mask is visible and unlikely to be typed by accident', () => {
|
|
71
|
+
// A row of dots would look like a value somebody had set.
|
|
72
|
+
assert.match(sf.SECRET_MASK, /kept/);
|
|
73
|
+
assert.ok(sf.SECRET_MASK.length > 8);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('a file saved back unchanged is byte for byte what it was', () => {
|
|
77
|
+
restore();
|
|
78
|
+
const before = fs.readFileSync(full, 'utf8');
|
|
79
|
+
const { text } = sf.readSettingsText(FILE);
|
|
80
|
+
const res = sf.writeSettingsText(FILE, text, { yaml });
|
|
81
|
+
assert.ok(res.kept.length, 'it says which secrets it put back');
|
|
82
|
+
assert.strictEqual(fs.readFileSync(full, 'utf8').trimEnd(), before.trimEnd(),
|
|
83
|
+
'comments, order, quoting and every secret survive a round trip');
|
|
84
|
+
restore();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('a masked secret is matched by its whole path, not by its key', () => {
|
|
88
|
+
// Three services each have a key called `token`. Matching by key gave all three the first one's
|
|
89
|
+
// value — one service's token copied onto another.
|
|
90
|
+
fs.writeFileSync(full, [
|
|
91
|
+
'git:', ' github:', ' token: gh-one', ' drupal:', ' token: dr-two', ' bitbucket:', ' token: bb-three', '',
|
|
92
|
+
].join('\n'));
|
|
93
|
+
const { text, masked } = sf.readSettingsText(FILE);
|
|
94
|
+
assert.deepStrictEqual(masked, ['git.github.token', 'git.drupal.token', 'git.bitbucket.token']);
|
|
95
|
+
assert.strictEqual((text.match(new RegExp(sf.SECRET_MASK.replace(/[.*+?^${}()|[\]\\<>—]/g, '\\$&'), 'g')) || []).length, 3);
|
|
96
|
+
sf.writeSettingsText(FILE, text, { yaml });
|
|
97
|
+
const back = fs.readFileSync(full, 'utf8');
|
|
98
|
+
assert.match(back, /github:\n\s+token: gh-one/);
|
|
99
|
+
assert.match(back, /drupal:\n\s+token: dr-two/);
|
|
100
|
+
assert.match(back, /bitbucket:\n\s+token: bb-three/);
|
|
101
|
+
restore();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('typing over the mask is how a new secret is set', () => {
|
|
105
|
+
fs.writeFileSync(full, 'git:\n github:\n token: old-one\n');
|
|
106
|
+
const { text } = sf.readSettingsText(FILE);
|
|
107
|
+
sf.writeSettingsText(FILE, text.replace(sf.SECRET_MASK, 'a-new-one'), { yaml });
|
|
108
|
+
assert.match(fs.readFileSync(full, 'utf8'), /token: a-new-one/);
|
|
109
|
+
restore();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a masked value with nothing behind it is written empty, never as the mask', () => {
|
|
113
|
+
fs.writeFileSync(full, 'git:\n github:\n token: old-one\n');
|
|
114
|
+
const { text } = sf.readSettingsText(FILE);
|
|
115
|
+
// The key was renamed while the mask was still on it: there is no value to put back.
|
|
116
|
+
sf.writeSettingsText(FILE, text.replace('token:', 'other_token:'), { yaml });
|
|
117
|
+
const back = fs.readFileSync(full, 'utf8');
|
|
118
|
+
assert.ok(!back.includes(sf.SECRET_MASK), 'the mask never reaches the file');
|
|
119
|
+
assert.match(back, /other_token:\s*$/m);
|
|
120
|
+
restore();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('a file that does not parse is refused, and nothing is written', () => {
|
|
124
|
+
restore();
|
|
125
|
+
const before = fs.readFileSync(full, 'utf8');
|
|
126
|
+
const res = sf.writeSettingsText(FILE, 'a: 1\n : : no\n', { yaml });
|
|
127
|
+
assert.match(res.error, /not YAML/);
|
|
128
|
+
assert.strictEqual(fs.readFileSync(full, 'utf8'), before,
|
|
129
|
+
'a settings file that does not parse takes the whole configuration with it');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('an empty file, and a file that is not a mapping, are refused too', () => {
|
|
133
|
+
restore();
|
|
134
|
+
const before = fs.readFileSync(full, 'utf8');
|
|
135
|
+
assert.match(sf.writeSettingsText(FILE, ' ', { yaml }).error, /empty file/);
|
|
136
|
+
assert.match(sf.writeSettingsText(FILE, '- one\n- two\n', { yaml }).error, /mapping/);
|
|
137
|
+
assert.strictEqual(fs.readFileSync(full, 'utf8'), before);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('a file that cannot be read is null, not an empty editor', () => {
|
|
141
|
+
assert.strictEqual(sf.readSettingsText('workspace.nosuchthing.settings.yml'), null);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/* ------------------------------------------------------------------ *
|
|
145
|
+
* How it is wired
|
|
146
|
+
* ------------------------------------------------------------------ */
|
|
147
|
+
|
|
148
|
+
const server = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
149
|
+
|
|
150
|
+
test('both ways are offered on every settings file, and fields is the default', () => {
|
|
151
|
+
assert.match(server, /function settingsModesHtml\(file, raw\)/);
|
|
152
|
+
assert.match(server, /'Fields', !raw/);
|
|
153
|
+
assert.match(server, /'Raw YAML', raw/);
|
|
154
|
+
assert.match(server, /\?raw=1/);
|
|
155
|
+
assert.match(server, /const raw = new URL\(req\.url, 'http:\/\/localhost'\)\.searchParams\.get\('raw'\) === '1';/);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('the raw save carries the mtime, and is refused when the file changed underneath', () => {
|
|
159
|
+
assert.match(server, /if \(String\(form\.mtime \|\| ''\) !== String\(mtime\)\) \{/);
|
|
160
|
+
assert.match(server, /That file changed since this editor was opened — nothing was written/);
|
|
161
|
+
assert.match(server, /, 409\)/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('a refusal keeps what was typed', () => {
|
|
165
|
+
assert.match(server, /a refusal that threw away what was typed would be worse/);
|
|
166
|
+
assert.match(server, /settingsRawHtml\(file, `<div class="msg error">\$\{esc\(result\.error\)\}/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('only a settings file can be written this way', () => {
|
|
170
|
+
const block = server.slice(server.indexOf("pathname === '/actions/save-settings-raw'"), server.indexOf("pathname === '/actions/save-settings'"));
|
|
171
|
+
assert.match(block, /if \(!SETTINGS_FILE_RE\.test\(file\)\)/);
|
|
172
|
+
assert.match(block, /Ambiguous request/, 'a duplicated file field is refused rather than guessed at');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('the action is logged once, by the central audit, so no secret reaches the log twice', () => {
|
|
176
|
+
assert.match(server, /No audit\(\) call: every action is already logged centrally/);
|
|
177
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One dashboard, one set of preferences.
|
|
3
|
+
*
|
|
4
|
+
* The theme, the rail, the assistant's view and whether it is open are per PERSON, not per workspace —
|
|
5
|
+
* but they lived in localStorage, which is per-origin, and this dashboard spans <workspace>.<hub>
|
|
6
|
+
* subdomains. So the theme was a different theme on /dev than on modules.workspace…, with no way to
|
|
7
|
+
* set it once. The lists' page-size cookie already had to be scoped to the hub for the same reason.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
const { test } = require('node:test');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const app = path.join(__dirname, '..', '..');
|
|
16
|
+
const server = fs.readFileSync(path.join(app, 'server.js'), 'utf8');
|
|
17
|
+
const ui = fs.readFileSync(path.join(app, 'public', 'ui.js'), 'utf8');
|
|
18
|
+
const head = server.slice(server.indexOf('window.wsPref='), server.indexOf('</script>'));
|
|
19
|
+
|
|
20
|
+
test('the preference helpers are in the head, so the first paint can read them', () => {
|
|
21
|
+
assert.match(head, /window\.wsPref\s*=\s*function\(name,fallback\)/);
|
|
22
|
+
assert.match(head, /window\.wsPrefSet\s*=\s*function\(name,value\)/);
|
|
23
|
+
// The pre-paint script is what applies the theme and the rail; reading a preference there is the
|
|
24
|
+
// whole point, since doing it later shows the wrong one for a frame.
|
|
25
|
+
assert.ok(server.indexOf('window.wsPref=') < server.indexOf("wsPref('ws-theme'"), 'defined before it is used');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('a preference is written to a cookie on the HUB domain', () => {
|
|
29
|
+
assert.match(head, /document\.cookie=name\+'='\+encodeURIComponent\(value\)\+'; Path=\/; Domain=\.\$\{hubDomain\(\)\}/,
|
|
30
|
+
'not the page host: a cookie on dev.workspace… would be the same bug in another form');
|
|
31
|
+
assert.match(head, /Max-Age=31536000/, 'a preference outlives the tab');
|
|
32
|
+
assert.match(head, /SameSite=Lax/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('localStorage stays as the fallback, so a browser that set one before is not reset', () => {
|
|
36
|
+
assert.match(head, /var ls=localStorage\.getItem\(name\);/);
|
|
37
|
+
assert.match(head, /if\(ls!==null\)return ls;/);
|
|
38
|
+
// The cookie is read FIRST, or a stale per-origin value would keep winning on that subdomain.
|
|
39
|
+
assert.ok(head.indexOf('document.cookie.match') < head.indexOf('localStorage.getItem'), 'cookie first');
|
|
40
|
+
assert.match(head, /try\{localStorage\.setItem\(name,value\);\}catch\(e\)\{\}/, 'and both are written');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('nothing writes these preferences straight to localStorage any more', () => {
|
|
44
|
+
for (const key of ['ws-theme', 'actions-collapsed', 'ws-assistant-view', 'ws-assistant-open', 'ws-page-width']) {
|
|
45
|
+
assert.ok(!new RegExp(`localStorage\\.setItem\\('${key}'`).test(ui), `${key} must go through wsPrefSet`);
|
|
46
|
+
assert.ok(!new RegExp(`localStorage\\.getItem\\('${key}'`).test(ui), `${key} must be read with wsPref`);
|
|
47
|
+
assert.ok(!new RegExp(`localStorage\\.getItem\\('${key}'`).test(server), `${key} must be read with wsPref`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The voice config the assistant panel is handed.
|
|
3
|
+
*
|
|
4
|
+
* Every case here guards a failure that is SILENT at runtime: deep-chat ignores a key it does not
|
|
5
|
+
* recognise and accepts one it does, so a wrong shape produces no error, no warning and no visible
|
|
6
|
+
* difference until someone dictates a sentence and the message sends itself.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const test = require('node:test');
|
|
12
|
+
const assert = require('node:assert');
|
|
13
|
+
|
|
14
|
+
const { assistantSettings, assistantIdentity, speechToTextConfig, textToSpeechConfig, panelConfig } = require('../../speech');
|
|
15
|
+
|
|
16
|
+
const of = (assistant) => panelConfig({ assistant });
|
|
17
|
+
|
|
18
|
+
test('with nothing configured, the recogniser is pinned to en-US', () => {
|
|
19
|
+
// en-US ships as the default rather than the browser's language: most dictation here is English,
|
|
20
|
+
// and a browser set to something else was silently recognising the wrong one.
|
|
21
|
+
const { speechToText } = of({});
|
|
22
|
+
assert.deepStrictEqual(speechToText.webSpeech, { language: 'en-US' });
|
|
23
|
+
assert.strictEqual(speechToText.displayInterimResults, true);
|
|
24
|
+
assert.strictEqual(speechToText.stopAfterSubmit, true);
|
|
25
|
+
assert.strictEqual(speechToText.submitAfterSilence, 2500);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('a language pins the recogniser — the setting that decides whether dictation works at all', () => {
|
|
29
|
+
// Left on the wrong language, web speech does not fail: it returns confident nonsense.
|
|
30
|
+
const { speechToText, textToSpeech } = of({ speech_language: 'ar-JO' });
|
|
31
|
+
assert.deepStrictEqual(speechToText.webSpeech, { language: 'ar-JO' });
|
|
32
|
+
// And the reply is spoken in the language it was asked in, without setting it twice.
|
|
33
|
+
assert.strictEqual(textToSpeech.lang, 'ar-JO');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('no commands are sent when none are configured', () => {
|
|
37
|
+
// The key must be ABSENT. An empty `commands` object is not "no commands" to deep-chat.
|
|
38
|
+
assert.ok(!('commands' in of({}).speechToText));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('only the commands actually given a phrase are sent', () => {
|
|
42
|
+
const { speechToText } = of({ command_submit: 'send it', command_clear: ' ', command_stop: 'halt' });
|
|
43
|
+
assert.deepStrictEqual(speechToText.commands, {
|
|
44
|
+
submit: 'send it',
|
|
45
|
+
stop: 'halt',
|
|
46
|
+
settings: { substrings: false, caseSensitive: false },
|
|
47
|
+
});
|
|
48
|
+
// A whitespace-only phrase is not a command; it would arm on almost anything.
|
|
49
|
+
assert.ok(!('removeAllText' in speechToText.commands));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('substring matching is off, so a command word inside a sentence does not fire', () => {
|
|
53
|
+
// deep-chat defaults this to true: "submit the form" would send the message mid-sentence.
|
|
54
|
+
const { speechToText } = of({ command_submit: 'submit' });
|
|
55
|
+
assert.strictEqual(speechToText.commands.settings.substrings, false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('submit-after-silence of 0 removes the key rather than sending zero', () => {
|
|
59
|
+
// 0 means "never send for me". deep-chat wants the key absent for that; zero would be read as
|
|
60
|
+
// "submit immediately", which sends a half-dictated sentence.
|
|
61
|
+
assert.ok(!('submitAfterSilence' in of({ submit_after_silence: 0 }).speechToText));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('voice off removes the microphone rather than leaving a dead button', () => {
|
|
65
|
+
assert.strictEqual(of({ voice_input: false }).speechToText, false);
|
|
66
|
+
assert.strictEqual(of({ voice_output: false }).textToSpeech, false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('a voice name is passed only when set, so an empty one cannot silence the reply', () => {
|
|
70
|
+
assert.ok(!('voiceName' in of({}).textToSpeech));
|
|
71
|
+
assert.strictEqual(of({ voice_name: 'Google US English' }).textToSpeech.voiceName, 'Google US English');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('rate, pitch and volume carry through', () => {
|
|
75
|
+
const { textToSpeech } = of({ speech_rate: 1.25, speech_pitch: 0.9, speech_volume: 0.5 });
|
|
76
|
+
assert.strictEqual(textToSpeech.rate, 1.25);
|
|
77
|
+
assert.strictEqual(textToSpeech.pitch, 0.9);
|
|
78
|
+
assert.strictEqual(textToSpeech.volume, 0.5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('speech colours are tokens, so both themes follow', () => {
|
|
82
|
+
const { textColor } = of({}).speechToText;
|
|
83
|
+
assert.match(textColor.interim, /^var\(--/);
|
|
84
|
+
assert.match(textColor.final, /^var\(--/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('the microphone button is styled in all three states', () => {
|
|
88
|
+
const { button } = of({}).speechToText;
|
|
89
|
+
for (const state of ['default', 'active', 'commandMode']) {
|
|
90
|
+
assert.ok(button[state], `the ${state} microphone state has no styling`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('Azure is never configured — its audio leaves the machine', () => {
|
|
95
|
+
const all = JSON.stringify(of({ speech_language: 'ar-JO', command_submit: 'go' }));
|
|
96
|
+
assert.ok(!/azure/i.test(all));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('strings survive a YAML round trip as "false" and as numbers', () => {
|
|
100
|
+
// parse_yaml hands values back as strings, so `voice_input: false` arrives as the STRING "false".
|
|
101
|
+
assert.strictEqual(assistantSettings({ assistant: { voice_input: 'false' } }).voiceInput, false);
|
|
102
|
+
assert.strictEqual(assistantSettings({ assistant: { speech_rate: '1.5' } }).speechRate, 1.5);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('a nonsense number falls back rather than reaching the browser', () => {
|
|
106
|
+
assert.strictEqual(assistantSettings({ assistant: { speech_rate: 'quickly' } }).speechRate, 1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('the two builders can be used on their own', () => {
|
|
110
|
+
const cfg = assistantSettings({ assistant: { voice_name: 'X' } });
|
|
111
|
+
assert.ok(speechToTextConfig(cfg).webSpeech);
|
|
112
|
+
assert.strictEqual(textToSpeechConfig(cfg).voiceName, 'X');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('the words for "not set" all resolve to nothing, so no row in settings.yml is empty', () => {
|
|
116
|
+
// `speech_language: ""` reads as a setting somebody forgot to finish. The file says a word.
|
|
117
|
+
for (const word of ['auto', 'default', 'off', 'none', 'browser', 'AUTO', 'Off']) {
|
|
118
|
+
const { speechToText, textToSpeech } = of({
|
|
119
|
+
speech_language: word, voice_name: word, command_submit: word,
|
|
120
|
+
});
|
|
121
|
+
assert.strictEqual(speechToText.webSpeech, true, `${word} pinned a language`); // auto = browser
|
|
122
|
+
assert.ok(!('commands' in speechToText), `${word} armed a voice command`);
|
|
123
|
+
assert.ok(!('voiceName' in textToSpeech), `${word} was used as a voice name`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('the field microphones get the same language as the assistant', () => {
|
|
128
|
+
// Before this they were pinned to document.documentElement.lang and ignored the setting, so
|
|
129
|
+
// 35 microphones stayed on English while the assistant moved.
|
|
130
|
+
const { fieldMic, speechToText } = of({ speech_language: 'ar-JO' });
|
|
131
|
+
assert.strictEqual(fieldMic.lang, 'ar-JO');
|
|
132
|
+
assert.strictEqual(speechToText.webSpeech.language, 'ar-JO');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('turning the microphone off removes the field mics too', () => {
|
|
136
|
+
assert.strictEqual(of({ voice_input: false }).fieldMic, false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('every offered language is a usable BCP-47 tag, and "follow the browser" stays reachable', () => {
|
|
140
|
+
const { LANGUAGES } = require('../../speech');
|
|
141
|
+
assert.strictEqual(LANGUAGES[0][0], 'auto', 'the browser default must be the first option');
|
|
142
|
+
for (const [code, label] of LANGUAGES.slice(1)) {
|
|
143
|
+
assert.match(code, /^[a-z]{2}-[A-Z]{2}$/, `${code} is not a BCP-47 tag`);
|
|
144
|
+
assert.ok(label.length > 3, `${code} has no readable label`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('a voice command is a checkbox: true listens for the command\'s own word', () => {
|
|
149
|
+
// A text box holding `off` asked people to type a phrase when what they wanted was to switch
|
|
150
|
+
// something on. `true` means "listen for the obvious word".
|
|
151
|
+
const { speechToText } = of({ command_stop: true, command_submit: true });
|
|
152
|
+
assert.strictEqual(speechToText.commands.stop, 'stop');
|
|
153
|
+
assert.strictEqual(speechToText.commands.submit, 'submit');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('a phrase of your own still works — the only way to have one in another language', () => {
|
|
157
|
+
const { speechToText } = of({ command_stop: 'توقف' });
|
|
158
|
+
assert.strictEqual(speechToText.commands.stop, 'توقف');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('false and off both mean no command, so the old spelling keeps working', () => {
|
|
162
|
+
for (const v of [false, 'false', 'off', 'no']) {
|
|
163
|
+
assert.ok(!('commands' in of({ command_stop: v }).speechToText), `${v} armed a command`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('the language dropdown is fed from the module, so the form and the resolver cannot disagree', () => {
|
|
168
|
+
// settings.labels.yml says `options: from:speech-languages` rather than repeating the list. A copy
|
|
169
|
+
// there would drift from the one the resolver validates against, and the drift would be invisible.
|
|
170
|
+
const fs = require('fs');
|
|
171
|
+
const path = require('path');
|
|
172
|
+
// CONFIG_DIR, not a relative walk: the app runs from /var/www/html inside its container, so
|
|
173
|
+
// ../../.. lands outside the tree and the file is simply not found.
|
|
174
|
+
const { CONFIG_DIR } = require('../../workspaces');
|
|
175
|
+
const labels = fs.readFileSync(path.join(CONFIG_DIR, 'settings.labels.yml'), 'utf8');
|
|
176
|
+
assert.match(labels, /options:\s*["']?from:speech-languages/,
|
|
177
|
+
'the language options are spelled out in the labels file instead of coming from speech.js');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('a command describes itself, and shell variables in that description are resolved', () => {
|
|
181
|
+
// Read from the script, not from a list beside it: 218 of the 239 commands already carry an
|
|
182
|
+
// ARGPARSE_DESCRIPTION, and a second copy would be wrong the first time somebody edited a command
|
|
183
|
+
// without knowing the copy existed.
|
|
184
|
+
const { commandDescription } = require('../../workspaces');
|
|
185
|
+
const path = require('path');
|
|
186
|
+
const { ROOT } = require('../../workspaces');
|
|
187
|
+
|
|
188
|
+
const built = commandDescription(path.join(ROOT, 'dev'), 'cmd-webship11-0-x-project.sh');
|
|
189
|
+
assert.ok(built, 'a builder with ARGPARSE_DESCRIPTION described itself as nothing');
|
|
190
|
+
// `Build a ${distribution_title} ${site_version} project` must not reach a person raw.
|
|
191
|
+
assert.ok(!/\$\{|\$[a-zA-Z_]/.test(built), `an unresolved variable is shown: "${built}"`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('a command with no description at all returns empty rather than throwing', () => {
|
|
195
|
+
const { commandDescription } = require('../../workspaces');
|
|
196
|
+
assert.strictEqual(commandDescription('/nonexistent', 'cmd-nope.sh'), '');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('a command title is short, and a builder keeps its product name however long', () => {
|
|
200
|
+
const { commandTitle, ROOT } = require('../../workspaces');
|
|
201
|
+
const path = require('path');
|
|
202
|
+
|
|
203
|
+
// Derived from the file name when nothing brief is declared: cmd-tools-reclaim.sh -> "Reclaim".
|
|
204
|
+
assert.strictEqual(commandTitle(path.join(ROOT, 'products'), 'cmd-tools-reclaim.sh'), 'Reclaim');
|
|
205
|
+
|
|
206
|
+
// A builder's `# workspace-name:` is a PRODUCT name, not a sentence, so it survives at any length.
|
|
207
|
+
const builder = commandTitle(path.join(ROOT, 'site-templates'), 'cmd-website_starter-1-0-x-project.sh');
|
|
208
|
+
assert.match(builder, /Website Starter/, `a builder lost its product name: "${builder}"`);
|
|
209
|
+
|
|
210
|
+
// A tools command using that header as free text gets a short derived title instead, because the
|
|
211
|
+
// declared one repeats the description underneath it.
|
|
212
|
+
const tools = commandTitle(path.join(ROOT, 'products'), 'cmd-tools-graphify.sh');
|
|
213
|
+
assert.ok(tools.split(/\s+/).length <= 4, `title is not short: "${tools}"`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('every command in every workspace folder has a description', () => {
|
|
217
|
+
const { commandDescription, ROOT } = require('../../workspaces');
|
|
218
|
+
const fs = require('fs');
|
|
219
|
+
const path = require('path');
|
|
220
|
+
const missing = [];
|
|
221
|
+
for (const d of fs.readdirSync(ROOT)) {
|
|
222
|
+
let files = [];
|
|
223
|
+
try { files = fs.readdirSync(path.join(ROOT, d)).filter((f) => /^cmd-.*\.sh$/.test(f)); } catch (_) { continue; }
|
|
224
|
+
for (const f of files) if (!commandDescription(path.join(ROOT, d), f)) missing.push(`${d}/${f}`);
|
|
225
|
+
}
|
|
226
|
+
assert.deepStrictEqual(missing, [], 'these commands describe themselves as nothing');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
/* ---------- what the assistant is CALLED, and what its composer says ---------- */
|
|
231
|
+
|
|
232
|
+
// One dashboard, twenty-four workspaces, one assistant whose conversation follows you between them.
|
|
233
|
+
// The name and the placeholder are configuration at two levels: settings.yml for the home dashboard
|
|
234
|
+
// and everything unscoped, and a workspace's own file for its page. The rule is the same at each
|
|
235
|
+
// level — say nothing and you inherit — so shipping the keys in every workspace file costs nothing.
|
|
236
|
+
|
|
237
|
+
const id = (global, own) => assistantIdentity({ assistant: global }, own ? { assistant: own } : null);
|
|
238
|
+
|
|
239
|
+
test('with nothing configured it is the AI Assistant, and the composer says nothing', () => {
|
|
240
|
+
assert.deepStrictEqual(assistantIdentity(), { name: 'AI Assistant', placeholder: '' });
|
|
241
|
+
// An empty field with a cursor in it already says "type here", and a prompt that suggests what to
|
|
242
|
+
// ask narrows what gets asked. The empty string still has to REACH deep-chat: left unset, the
|
|
243
|
+
// component renders its own library placeholder about `connect` and the demo property.
|
|
244
|
+
assert.strictEqual(panelConfig().placeholder, '');
|
|
245
|
+
assert.strictEqual(panelConfig().name, 'AI Assistant');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('a workspace renames its assistant for its own page and nowhere else', () => {
|
|
249
|
+
assert.deepStrictEqual(id({ name: 'AI Assistant' }, { name: 'Design Assistant' }),
|
|
250
|
+
{ name: 'Design Assistant', placeholder: '' });
|
|
251
|
+
// …and the global one is untouched by it, which is the half a per-page override gets wrong.
|
|
252
|
+
assert.strictEqual(id({ name: 'AI Assistant' }).name, 'AI Assistant');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('saying nothing means inherit, at either level', () => {
|
|
256
|
+
// The whole reason every workspace file can ship `name: default`: twenty-four pinned copies of
|
|
257
|
+
// the name would make a rename in settings.yml reach nothing.
|
|
258
|
+
for (const unset of ['default', 'auto', 'none', 'off', '', undefined]) {
|
|
259
|
+
assert.strictEqual(id({ name: 'Studio' }, { name: unset }).name, 'Studio', `own: ${unset}`);
|
|
260
|
+
assert.strictEqual(id({ placeholder: 'Ask me…' }, { placeholder: unset }).placeholder, 'Ask me…');
|
|
261
|
+
}
|
|
262
|
+
// And an unset GLOBAL falls through to what ships, rather than to an empty heading.
|
|
263
|
+
assert.strictEqual(id({ name: 'none' }).name, 'AI Assistant');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('a placeholder is a real string, whichever level set it', () => {
|
|
267
|
+
assert.strictEqual(id({ placeholder: 'Ask about the workspace…' }).placeholder, 'Ask about the workspace…');
|
|
268
|
+
assert.strictEqual(id({}, { placeholder: 'Ask about the Penpot instances…' }).placeholder,
|
|
269
|
+
'Ask about the Penpot instances…');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('the identity travels with the panel config, so the page is handed one object', () => {
|
|
273
|
+
const cfg = panelConfig({ assistant: { name: 'Studio', placeholder: 'Ask…' } });
|
|
274
|
+
assert.strictEqual(cfg.name, 'Studio');
|
|
275
|
+
assert.strictEqual(cfg.placeholder, 'Ask…');
|
|
276
|
+
// Still the voice config it always was: the identity is added to it, not instead of it.
|
|
277
|
+
assert.ok(cfg.speechToText, 'the microphone survives');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('the avatar is Druplicon unless it is none, and the memory folder never leaves the workspace tree', () => {
|
|
281
|
+
assert.strictEqual(assistantSettings({}).avatar, 'druplicon');
|
|
282
|
+
assert.strictEqual(assistantSettings({ assistant: { avatar: 'none' } }).avatar, 'none');
|
|
283
|
+
assert.strictEqual(assistantSettings({ assistant: { avatar: 'clippy' } }).avatar, 'druplicon');
|
|
284
|
+
assert.strictEqual(panelConfig({ assistant: { avatar: 'none' } }).avatar, 'none');
|
|
285
|
+
assert.strictEqual(assistantSettings({}).memory, 'core/workspace-app/.memory');
|
|
286
|
+
assert.strictEqual(assistantSettings({ assistant: { memory: 'docs/team-memory' } }).memory, 'docs/team-memory');
|
|
287
|
+
for (const bad of ['../outside', '/etc', 'docs/../../x', 'a b', '']) {
|
|
288
|
+
assert.strictEqual(assistantSettings({ assistant: { memory: bad } }).memory, 'core/workspace-app/.memory', bad);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const APP = path.join(__dirname, '..', '..');
|
|
7
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
8
|
+
const serveStatic = src.slice(src.indexOf('function serveStatic'), src.indexOf('function serveStatic') + 2600);
|
|
9
|
+
|
|
10
|
+
// Static assets were served with a Content-Type and nothing else: no Cache-Control, no ETag, no
|
|
11
|
+
// Last-Modified. With no validator a browser has nothing to revalidate against, so it refetched
|
|
12
|
+
// them — 300 KB gzipped per visit, the 1.4 MB Tabler sprite included. A repeat visit now costs
|
|
13
|
+
// zero bytes. None of that is visible in the UI, which is why it needs a test.
|
|
14
|
+
test('static assets carry a validator and a cache policy', () => {
|
|
15
|
+
for (const h of ['ETag', 'Last-Modified', 'Cache-Control']) {
|
|
16
|
+
assert.ok(serveStatic.includes(h), `serveStatic no longer sets ${h}`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('a versioned URL is immutable, an unversioned one revalidates', () => {
|
|
21
|
+
// assetUrl appends ?v=<mtime>, so when the file changes the URL changes and a stale copy is
|
|
22
|
+
// unreachable rather than merely old. Anything without that must not be frozen for a year.
|
|
23
|
+
assert.ok(serveStatic.includes('immutable'), 'versioned assets are no longer immutable');
|
|
24
|
+
assert.ok(serveStatic.includes('must-revalidate'), 'unversioned assets no longer revalidate');
|
|
25
|
+
assert.match(serveStatic, /\[?\?&\]v=|v=/, 'no longer distinguishes versioned URLs');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('a conditional request is answered 304 with no body', () => {
|
|
29
|
+
assert.ok(serveStatic.includes('304'), 'conditional requests are no longer answered 304');
|
|
30
|
+
assert.ok(/if-none-match/i.test(serveStatic), 'If-None-Match is no longer honoured');
|
|
31
|
+
assert.ok(/if-modified-since/i.test(serveStatic), 'If-Modified-Since is no longer honoured');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('HEAD is routed with GET, and sends no body', () => {
|
|
35
|
+
// HEAD used to fall through to 405, which broke curl -I and any uptime check.
|
|
36
|
+
assert.match(src, /req\.method === 'GET' \|\| req\.method === 'HEAD'/,
|
|
37
|
+
'HEAD is no longer routed with GET');
|
|
38
|
+
assert.ok(serveStatic.includes("req.method === 'HEAD'"), 'serveStatic no longer skips the body for HEAD');
|
|
39
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status chips say what they mean.
|
|
3
|
+
*
|
|
4
|
+
* Found by the local webship-ai-ux-designer agent reviewing the live pages: an agent that had stopped to ask a
|
|
5
|
+
* question was labelled "agent running", the same words as one that was working (the difference was an amber
|
|
6
|
+
* colour, an emoji and a tooltip); a Stopped site took the same brand blue as the agent chip and was the loudest
|
|
7
|
+
* chip on its card; an attached session took the same amber as "waiting for you".
|
|
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 screens = read('screens.js');
|
|
18
|
+
const css = read('public/css/components/project-rows.css');
|
|
19
|
+
|
|
20
|
+
test('the agent chip says working, or waiting for you, in words', () => {
|
|
21
|
+
assert.match(server, /\$\{agentWaiting \? 'waiting for you' : 'agent working'\}/);
|
|
22
|
+
assert.ok(!/agent running\$\{/.test(server), 'the chip no longer says running');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('the toolbar says how many are waiting, not only a hand', () => {
|
|
26
|
+
assert.match(server, /">✋ \$\{waiting\.length\} waiting<\/span>`/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('a stopped site is a quiet outline, a running one is green, and the dot is drawn by CSS', () => {
|
|
30
|
+
assert.match(server, /running: 'Running', paused: 'Paused', stopped: 'Stopped'/);
|
|
31
|
+
assert.match(server, /st === 'running' \? 'uk-label-success' : `state-chip-off state-chip-\$\{st\}`/);
|
|
32
|
+
assert.match(server, /<span class="state-dot" aria-hidden="true"><\/span><span class="state-word">/);
|
|
33
|
+
assert.ok(!/🟢 Running|⚪ \$\{/.test(server), 'no emoji: each machine draws them differently');
|
|
34
|
+
assert.match(css, /\.uk-label\.state-chip-off \{\s*background: transparent;\s*color: var\(--text-subtle\);\s*border: 1px solid var\(--line\);/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('on Screens, waiting names what it waits for, and an attached session is not amber', () => {
|
|
38
|
+
assert.match(screens, /✋ Waiting for you · \$\{esc\(ask\)\}/);
|
|
39
|
+
assert.match(screens, /replace\(\/\^waiting \(on\|for\) \/, ''\)/);
|
|
40
|
+
assert.match(screens, /attached \? 'screen-state-attached' : 'uk-label-success'/);
|
|
41
|
+
assert.match(css, /\.uk-label\.screen-state-attached \{\s*background: transparent;/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('the button that removes ended sessions says so', () => {
|
|
45
|
+
assert.match(screens, /Remove ended sessions<\/button>/);
|
|
46
|
+
assert.ok(!/Wipe dead<\/button>/.test(screens));
|
|
47
|
+
});
|