@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,64 @@
|
|
|
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
|
+
|
|
7
|
+
const { descriptionOf, suggestFor, aiInventory, TRIGGERS } = require('../../ai-inventory');
|
|
8
|
+
|
|
9
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-inv-'));
|
|
10
|
+
const write = (name, body) => { const f = path.join(tmp, name); fs.writeFileSync(f, body); return f; };
|
|
11
|
+
|
|
12
|
+
test('a plain frontmatter description is read', () => {
|
|
13
|
+
const f = write('a.md', '---\nname: a\ndescription: Build a Drupal project.\n---\n\n# body\n');
|
|
14
|
+
assert.equal(descriptionOf(f), 'Build a Drupal project.');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('a FOLDED description is joined back into one line', () => {
|
|
18
|
+
// YAML lets a long description continue on indented lines, and real agents use it —
|
|
19
|
+
// reading only the first line would cut the sentence that decides whether to use them.
|
|
20
|
+
const f = write('b.md', '---\nname: b\ndescription: Use this agent when\n the workspace needs a\n new builder script.\nmodel: opus\n---\n');
|
|
21
|
+
assert.equal(descriptionOf(f), 'Use this agent when the workspace needs a new builder script.');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('a following key does not get swallowed into the description', () => {
|
|
25
|
+
const f = write('c.md', '---\nname: c\ndescription: Short one.\nmodel: opus\ncolor: green\n---\n');
|
|
26
|
+
assert.equal(descriptionOf(f), 'Short one.');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('quotes are stripped, and a file with no frontmatter has no description', () => {
|
|
30
|
+
assert.equal(descriptionOf(write('d.md', '---\ndescription: "Quoted."\n---\n')), 'Quoted.');
|
|
31
|
+
assert.equal(descriptionOf(write('e.md', '# Just a heading\n')), '');
|
|
32
|
+
assert.equal(descriptionOf(path.join(tmp, 'missing.md')), '');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('each kind is offered by the right trigger', () => {
|
|
36
|
+
// The contract the popup and the Ace completer both rely on.
|
|
37
|
+
assert.deepEqual(TRIGGERS.agent, ['@']);
|
|
38
|
+
assert.deepEqual(TRIGGERS.command, ['/']);
|
|
39
|
+
assert.deepEqual(TRIGGERS.skill, ['@', '/'], 'a skill is both named and invoked');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('the inventory reads this machine and every item is well formed', () => {
|
|
43
|
+
const all = aiInventory();
|
|
44
|
+
assert.ok(all.length > 0, 'no items found at all');
|
|
45
|
+
for (const i of all.slice(0, 40)) {
|
|
46
|
+
assert.match(i.kind, /^(agent|skill|command)$/);
|
|
47
|
+
assert.ok(i.name && !i.name.endsWith('.md'), `bad name: ${i.name}`);
|
|
48
|
+
assert.ok(Array.isArray(i.triggers) && i.triggers.length, `no triggers: ${i.name}`);
|
|
49
|
+
assert.ok(i.where, `no provenance: ${i.name}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('a "/" query never returns an agent, and "@" never returns a command', () => {
|
|
54
|
+
assert.equal(suggestFor('/', '').filter((i) => i.kind === 'agent').length, 0);
|
|
55
|
+
assert.equal(suggestFor('@', '').filter((i) => i.kind === 'command').length, 0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('a name that starts with what was typed ranks above one that merely contains it', () => {
|
|
59
|
+
const hits = suggestFor('@', 'workspace');
|
|
60
|
+
const starts = hits.findIndex((i) => i.name.toLowerCase().startsWith('workspace'));
|
|
61
|
+
const contains = hits.findIndex((i) => !i.name.toLowerCase().startsWith('workspace')
|
|
62
|
+
&& i.name.toLowerCase().includes('workspace'));
|
|
63
|
+
if (starts !== -1 && contains !== -1) assert.ok(starts < contains, 'prefix match must come first');
|
|
64
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The illustrated guide, and its images.
|
|
3
|
+
*
|
|
4
|
+
* A screenshot that has been renamed or moved leaves a broken image, and a broken
|
|
5
|
+
* image in a guide is invisible to whoever wrote it and obvious to everybody else.
|
|
6
|
+
* Thirty-six screenshots sat in this folder referenced by nothing at all before the
|
|
7
|
+
* guide existed, which is the other half of the same problem.
|
|
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 DOCS = path.join(__dirname, '..', '..', 'docs');
|
|
16
|
+
const GUIDE = path.join(DOCS, 'README.md');
|
|
17
|
+
|
|
18
|
+
test('every image the guides link to is actually there', () => {
|
|
19
|
+
const md = [GUIDE, path.join(DOCS, 'USER-GUIDE.md')].map((f) => fs.readFileSync(f, 'utf8')).join('\n');
|
|
20
|
+
const links = [...md.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)].map((m) => m[1]);
|
|
21
|
+
assert.ok(links.length >= 10, `expected the guide to be illustrated, found ${links.length} images`);
|
|
22
|
+
for (const rel of links) {
|
|
23
|
+
assert.ok(fs.existsSync(path.join(DOCS, rel)), `missing image: ${rel}`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('every image has alt text', () => {
|
|
28
|
+
// The guide is read on GitHub, in the repository, and by people who cannot see
|
|
29
|
+
// the picture. An empty alt is a picture that says nothing to them.
|
|
30
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
31
|
+
for (const m of md.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/g)) {
|
|
32
|
+
assert.ok(m[1].trim().length > 3, `no alt text for ${m[2]}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('the guide links to the other docs, and they exist', () => {
|
|
37
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
38
|
+
const root = path.join(__dirname, '..', '..', '..', '..');
|
|
39
|
+
for (const rel of ['docs/workspace-tips-and-rules.md', 'README.md']) {
|
|
40
|
+
assert.match(md, new RegExp(rel.replace(/[/.]/g, '\\$&')), `the guide never points at ${rel}`);
|
|
41
|
+
assert.ok(fs.existsSync(path.join(root, rel)), `${rel} does not exist`);
|
|
42
|
+
}
|
|
43
|
+
// The three documents point at each other, or the set is three orphans.
|
|
44
|
+
for (const f of ['USER-GUIDE.md', 'DEVELOPER-GUIDE.md']) {
|
|
45
|
+
assert.ok(fs.existsSync(path.join(DOCS, f)), `${f} is missing`);
|
|
46
|
+
assert.match(md, new RegExp(f), `the index never points at ${f}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('/help reads the guide from the app docs, and can serve its images', () => {
|
|
51
|
+
const src = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
52
|
+
assert.match(src, /APP_DOCS, 'USER-GUIDE\.md'/);
|
|
53
|
+
// Relative image paths work in the repository; over HTTP they need the route.
|
|
54
|
+
assert.match(src, /\/help\/screenshots\//);
|
|
55
|
+
// And the route must be contained: this is a docs folder, not a second static root.
|
|
56
|
+
assert.match(src, /realpathSync\(path\.join\(APP_DOCS, 'screenshots'\)\)/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('the README points at the guide, and says what it is', () => {
|
|
60
|
+
const md = fs.readFileSync(path.join(__dirname, '..', '..', '..', '..', 'README.md'), 'utf8');
|
|
61
|
+
assert.match(md, /core\/workspace-app\/docs/);
|
|
62
|
+
// One "New here" callout, not two: the second was a duplicate of the first.
|
|
63
|
+
assert.strictEqual((md.match(/New here/g) || []).length, 1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's avatar. What must never drift: every move the assistant is told it may write exists in
|
|
3
|
+
* the avatar; the 3D part is fetched only when the avatar is on (an import map, a module loaded on
|
|
4
|
+
* demand); `assistant.avatar: none` takes the control and the stage away; and the avatar's lines are
|
|
5
|
+
* asked for only while it is shown.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
14
|
+
const { AVATAR_GESTURES } = require('../../assistant');
|
|
15
|
+
|
|
16
|
+
test('every move the assistant may write is one the avatar can do', () => {
|
|
17
|
+
const avatar = app('public/assistant-avatar.js');
|
|
18
|
+
const moves = [...avatar.matchAll(/^ {2}([a-z]+): \{ duration:/gm)].map((m) => m[1]);
|
|
19
|
+
for (const g of AVATAR_GESTURES) assert.ok(moves.includes(g), `[${g}] is offered but the avatar has no such move`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('the libraries and the model are vendored, and fetched only when the avatar is shown', () => {
|
|
23
|
+
for (const f of ['public/vendor/three/three.module.min.js', 'public/vendor/three/three.core.min.js', 'public/vendor/three/LICENSE',
|
|
24
|
+
'public/vendor/three/addons/loaders/GLTFLoader.js', 'public/vendor/three-vrm/three-vrm.module.min.js', 'public/vendor/three-vrm/LICENSE',
|
|
25
|
+
'public/avatar/druplicon.vrm']) {
|
|
26
|
+
assert.ok(fs.existsSync(path.join(__dirname, '../..', f)), f);
|
|
27
|
+
}
|
|
28
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
29
|
+
assert.ok(ui.includes("import('/assistant-avatar.js')"), 'the 3D module is imported on demand');
|
|
30
|
+
const server = app('server.js');
|
|
31
|
+
assert.ok(server.includes('<script type="importmap">'), 'the import map that resolves three and three-vrm');
|
|
32
|
+
assert.ok(/\$\{avatarOn \? `<script src="\$\{assetUrl\('\/assistant-avatar-ui\.js'\)\}" defer>/.test(server), 'the wiring loads only when the avatar is on');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('the avatar lines are asked for only while it is shown, and never left in the reply', () => {
|
|
36
|
+
const assistant = app('assistant.js');
|
|
37
|
+
assert.ok(assistant.includes('...(avatar ? ['), 'the avatar instructions are conditional');
|
|
38
|
+
for (const line of ['BOARD', 'DRAW', 'SHOW', 'VIDEO', 'BROWSE', 'SEARCH', 'MOVE', 'SIZE']) {
|
|
39
|
+
assert.ok(assistant.includes(`/^\\s*${line}:\\s*`), `${line}: is taken out of the reply text`);
|
|
40
|
+
}
|
|
41
|
+
assert.ok(app('public/ui.js').includes("avatar: Boolean(window.wsAssistantAvatarOn && window.wsAssistantAvatarOn()), avatarName: window.wsAvatarName ? window.wsAvatarName() : '', avatarId: window.wsAvatarId ? window.wsAvatarId() : '', page: window.wsAvatarContext ? window.wsAvatarContext() : null"));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('every control is on the assistant side, and nothing is drawn on the avatar itself', () => {
|
|
45
|
+
const server = app('server.js');
|
|
46
|
+
const stage = server.slice(server.indexOf('<div class="assistant-avatar-stage"'), server.indexOf('<div class="assistant-avatar-chips"'));
|
|
47
|
+
assert.ok(stage.includes('<canvas'), 'the stage is the character');
|
|
48
|
+
assert.ok(!/<button/.test(stage), 'and carries no button: they are all in the panel bar');
|
|
49
|
+
for (const c of ['assistant-dock', 'dock-voice', 'dock-talk', 'dock-mic', 'data-kit="board"', 'data-pop="windows"', 'data-pop="settings"', 'assistant-avatar-chips']) {
|
|
50
|
+
assert.ok(server.includes(c), c);
|
|
51
|
+
}
|
|
52
|
+
assert.ok(!server.includes('data-pop="moves"'), 'the moves are the reply\'s to choose, not a list in the menu');
|
|
53
|
+
assert.ok(!server.includes('data-pop="open"'), 'no box for typing links in the panel: Druplicon opens pages when asked');
|
|
54
|
+
const menu = server.slice(server.indexOf('<div class="uk-inline assistant-avatar-menu">'), server.indexOf('<div class="uk-inline assistant-menu">'));
|
|
55
|
+
for (const c of ['assistant-avatar-toggle', 'data-kit="board"', 'data-pop="settings"', 'assistant-avatar-pops']) assert.ok(menu.includes(c), `${c} is in the robot button's menu`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('an empty saved volume never silences the voice', () => {
|
|
59
|
+
const ui = app('public/ui.js');
|
|
60
|
+
assert.ok(ui.includes("if (raw !== '' && raw !== null && Number.isFinite(v) && v >= min && v <= max) narrationParams[key] = v;"),
|
|
61
|
+
'volume, speed and pitch: an empty cookie means not chosen, never 0');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('every big move the assistant may write is one the page can do, and spin, walk and leap exist', () => {
|
|
65
|
+
const { AVATAR_BIG_MOVES } = require('../../assistant');
|
|
66
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
67
|
+
const big = ui.slice(ui.indexOf('const BIG = {'), ui.indexOf('};', ui.indexOf('const BIG = {')));
|
|
68
|
+
for (const m of Object.keys(AVATAR_BIG_MOVES)) assert.match(big, new RegExp(`(async )?${m}(\\(\\)|:)`), `[${m}] is offered but the page has no such big move`);
|
|
69
|
+
const avatar = app('public/assistant-avatar.js');
|
|
70
|
+
for (const m of ['walk', 'spin', 'leap']) assert.match(avatar, new RegExp(`^ ${m}: \\{ duration:`, 'm'), m);
|
|
71
|
+
assert.ok(/if \(reduced\(\)\) return still/.test(big), 'reduced motion keeps it on the spot');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('the avatar scripts parse: a clash in one leaves the page with no avatar at all', () => {
|
|
75
|
+
for (const f of ['public/assistant-avatar.js', 'public/assistant-avatar-moves.js', 'public/assistant-avatar-ui.js']) {
|
|
76
|
+
// Modules: their imports and exports taken off, the rest must compile as a function body.
|
|
77
|
+
const body = app(f).replace(/^import .*$/gm, '').replace(/^export (const|function|async function|let|class) /gm, '$1 ');
|
|
78
|
+
assert.doesNotThrow(() => new Function(body), f);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('every avatar may speak in an ElevenLabs voice, and none does by default', () => {
|
|
83
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
84
|
+
assert.ok(ui.includes("voice().set('voiceName', String(getPref(`ws-voice-name-${a.id}`, '') || ''))"), 'a voice per avatar, free until one is picked');
|
|
85
|
+
assert.ok(app('public/ui.js').includes("if (/^elevenlabs:/i.test(String(narrationParams.voiceName || ''))) delete narrationParams.voiceName;"),
|
|
86
|
+
'settings.yml cannot make a paid voice the default');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('the interface glyphs are vendored and used, never a temporary Figma link', () => {
|
|
90
|
+
const dir = path.join(__dirname, '../../public/vendor/webship-icons');
|
|
91
|
+
for (const f of ['ui/sound-on.svg', 'ui/sound-off.svg', 'ui/play.svg', 'ui/pause.svg', 'ui/stop.svg', 'ui/headset.svg', 'ui/share.svg', 'ui/menu.svg',
|
|
92
|
+
'ui/bookmark-outlined.svg']) {
|
|
93
|
+
const svg = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
94
|
+
assert.match(svg, /^<svg[^>]*width="[\d.]+" height="[\d.]+"/, `${f} is an SVG with its own size`);
|
|
95
|
+
}
|
|
96
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
97
|
+
for (const name of ['sound-on', 'sound-off', 'play', 'pause', 'stop', 'headset', 'share', 'bookmark-outlined']) assert.ok(ui.includes(`'${name}'`), `${name} is used`);
|
|
98
|
+
for (const f of ['public/assistant-avatar-ui.js', 'server.js', 'public/css/components/assistant-avatar.css']) assert.ok(!/figma\.com\/api\/mcp\/asset/.test(app(f)), `${f} links no temporary Figma asset`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('while it works the avatar shows a status as the Claude Code CLI does, and acts each one out', () => {
|
|
102
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
103
|
+
const list = ui.slice(ui.indexOf('const STATUSES = ['), ui.indexOf('];', ui.indexOf('const STATUSES = [')));
|
|
104
|
+
const moves = [...list.matchAll(/\['([A-Z][a-z]+)', '([a-z]+)'\]/g)];
|
|
105
|
+
assert.ok(moves.length >= 6 && moves.some(([, v]) => v === 'Tempering'), 'Thinking, Tempering and the rest');
|
|
106
|
+
const avatar = app('public/assistant-avatar.js');
|
|
107
|
+
for (const [, , m] of moves) assert.match(avatar, new RegExp(`^ ${m}: \\{ duration:`, 'm'), `${m} is a shared move, so every avatar can do it`);
|
|
108
|
+
assert.ok(ui.includes("avatar.gesture(failed ? 'shrug' : 'thumbsup')"), 'Done or Stopped, with a move');
|
|
109
|
+
assert.ok(app('public/ui.js').includes("el.onError = () => setWorking(false, true);"), 'a failed request says so');
|
|
110
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who the avatar can be and which voices it speaks in (assistant-cast.js). What must never drift: only
|
|
3
|
+
* the avatars settings.yml names are offered, a model is only ever served from inside the avatars
|
|
4
|
+
* workspace, the ElevenLabs key stays a placeholder until a real one is set, and a sentence already
|
|
5
|
+
* spoken is never paid for twice.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { avatarCast, findAvatar, elevenKey, elevenSpeak, elevenVoices } = require('../../assistant-cast');
|
|
14
|
+
const { assistantSettings, workspaceLanguage } = require('../../speech');
|
|
15
|
+
|
|
16
|
+
function tree() {
|
|
17
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cast-'));
|
|
18
|
+
const p = path.join(root, 'avatars', 'scratch');
|
|
19
|
+
fs.mkdirSync(path.join(p, 'characters'), { recursive: true });
|
|
20
|
+
fs.mkdirSync(path.join(p, 'public', 'models'), { recursive: true });
|
|
21
|
+
fs.writeFileSync(path.join(p, 'public', 'models', 'osa-coffee.vrm'), 'glTF');
|
|
22
|
+
fs.writeFileSync(path.join(p, 'public', 'models', 'labiba-meme.vrm'), 'glTF');
|
|
23
|
+
fs.writeFileSync(path.join(p, 'characters', 'osa-coffee.json'), JSON.stringify({ name: 'Coffee', model: '/models/osa-coffee.vrm', credit: 'Coffee by Polygonal-Mind. CC0.' }));
|
|
24
|
+
fs.writeFileSync(path.join(p, 'characters', 'escape.json'), JSON.stringify({ name: 'Out', model: '/models/../../../../etc/passwd' }));
|
|
25
|
+
fs.writeFileSync(path.join(root, 'secret.vrm'), 'no');
|
|
26
|
+
fs.symlinkSync(path.join(root, 'secret.vrm'), path.join(p, 'public', 'models', 'linked.vrm'));
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('only the avatars settings.yml names are offered, each found in the avatars workspace', () => {
|
|
31
|
+
const root = tree();
|
|
32
|
+
const cast = avatarCast({ assistant: { avatars: ['druplicon', 'coffee', 'labiba-meme', 'missing', '../x', 'escape', 'linked'] } }, root);
|
|
33
|
+
assert.deepStrictEqual(cast.map((a) => a.id), ['druplicon', 'coffee', 'labiba-meme']);
|
|
34
|
+
assert.strictEqual(cast[1].name, 'Coffee');
|
|
35
|
+
assert.match(cast[1].credit, /CC0/);
|
|
36
|
+
assert.match(cast[1].model, /^\/avatar\/model\/coffee\.vrm\?v=[0-9a-z]+$/, 'the link carries the file time, so a rebuilt model is fetched');
|
|
37
|
+
assert.ok(cast[2].credit, 'a bare model still says where its licence is');
|
|
38
|
+
assert.strictEqual(findAvatar('linked', root), null, 'a link out of the avatars workspace is never served');
|
|
39
|
+
assert.deepStrictEqual(avatarCast({}, root).map((a) => a.id), ['druplicon'], 'the built-in one by default');
|
|
40
|
+
assert.ok(fs.existsSync(path.join(__dirname, '../../public/avatar/druplicon.vrm')), 'Druplicon ships with the dashboard');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the avatar it starts as is one of the list, or none', () => {
|
|
44
|
+
assert.strictEqual(assistantSettings({ assistant: { avatar: 'coffee', avatars: ['druplicon', 'coffee'] } }).avatar, 'coffee');
|
|
45
|
+
assert.strictEqual(assistantSettings({ assistant: { avatar: 'coffee' } }).avatar, 'druplicon', 'Druplicon when the one named is not listed');
|
|
46
|
+
assert.strictEqual(assistantSettings({ assistant: { avatar: 'none', avatars: ['coffee'] } }).avatar, 'none');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('the Workspace language sets the voice and the microphone unless they are pinned', () => {
|
|
50
|
+
assert.strictEqual(workspaceLanguage({ language: 'ar-JO' }), 'ar-JO');
|
|
51
|
+
assert.strictEqual(workspaceLanguage({ language: 'klingon' }), 'en-US');
|
|
52
|
+
assert.strictEqual(assistantSettings({ language: 'ar-JO' }).speechLanguage, 'ar-JO');
|
|
53
|
+
assert.strictEqual(assistantSettings({ language: 'ar-JO', assistant: { speech_language: 'en-GB' } }).speechLanguage, 'en-GB');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('ElevenLabs stays off until a real key is set, and the key never reaches the page', async () => {
|
|
57
|
+
assert.strictEqual(elevenKey({ elevenlabs: { api_key: 'sk-change-this' } }), '');
|
|
58
|
+
assert.strictEqual(elevenKey({ elevenlabs: { api_key: 'CHANGE_ME' } }), '');
|
|
59
|
+
assert.deepStrictEqual(await elevenVoices({}), { connected: false, voices: [] });
|
|
60
|
+
const ui = fs.readFileSync(path.join(__dirname, '../../public/assistant-avatar-ui.js'), 'utf8');
|
|
61
|
+
assert.ok(!/xi-api-key|api_key/.test(ui), 'the page only posts words to /actions/assistant/speak');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('a sentence already spoken is played from the cache, never paid for twice', async () => {
|
|
65
|
+
const settings = { elevenlabs: { api_key: 'a'.repeat(32) } };
|
|
66
|
+
const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'voice-'));
|
|
67
|
+
let calls = 0;
|
|
68
|
+
const fetchImpl = async (url, opts) => {
|
|
69
|
+
calls += 1;
|
|
70
|
+
assert.match(url, /\/v1\/text-to-speech\/EXAVITQu4vr4xnSDxMaL\?/);
|
|
71
|
+
assert.strictEqual(opts.headers['xi-api-key'], 'a'.repeat(32));
|
|
72
|
+
return { ok: true, status: 200, arrayBuffer: async () => Buffer.from('mp3') };
|
|
73
|
+
};
|
|
74
|
+
const ask = { voice: 'EXAVITQu4vr4xnSDxMaL', text: 'Hello team.' };
|
|
75
|
+
assert.strictEqual(String(await elevenSpeak(settings, ask, { fetchImpl, cacheDir })), 'mp3');
|
|
76
|
+
assert.strictEqual(String(await elevenSpeak(settings, ask, { fetchImpl, cacheDir })), 'mp3');
|
|
77
|
+
assert.strictEqual(calls, 1);
|
|
78
|
+
await assert.rejects(elevenSpeak(settings, { voice: '../v1/user', text: 'x' }, { fetchImpl, cacheDir }), /voice/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("an avatar's own moves exist, and the set is for work: no headscarf, nothing meant for children", async () => {
|
|
82
|
+
const { AVATAR_MOVE_SETS } = require('../../assistant');
|
|
83
|
+
const src = fs.readFileSync(path.join(__dirname, '../../public/assistant-avatar-moves.js'), 'utf8');
|
|
84
|
+
const defined = [...src.matchAll(/^ {4}([a-z]+): \{$/gm)].map((m) => m[1]);
|
|
85
|
+
for (const m of Object.keys(AVATAR_MOVE_SETS.labiba)) assert.ok(defined.includes(m), `[${m}] is offered but Labiba has no such move`);
|
|
86
|
+
for (const m of ['hijab', 'kiss', 'hug', 'dab', 'peekaboo', 'love']) assert.ok(!defined.includes(m), `${m} is not a move for work`);
|
|
87
|
+
assert.strictEqual(require('../../assistant-cast').moveSetOf('labiba-meme'), 'labiba');
|
|
88
|
+
assert.strictEqual(require('../../assistant-cast').moveSetOf('druplicon'), '', 'Druplicon has the shared moves and no set of its own');
|
|
89
|
+
for (const [set, moves] of Object.entries(AVATAR_MOVE_SETS)) {
|
|
90
|
+
for (const m of Object.keys(moves)) assert.ok(defined.includes(m), `[${m}] is offered but the ${set} set has no such move`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('hair colours and agents come from settings, checked', () => {
|
|
95
|
+
const { hairOf, agentOf } = require('../../assistant-cast');
|
|
96
|
+
assert.strictEqual(hairOf({ assistant: { avatar_hair: { 'labiba-meme': '"#0074E0"' } } }, 'labiba-meme'), '#0074e0');
|
|
97
|
+
assert.strictEqual(hairOf({ assistant: { avatar_hair: { x: 'url(evil)' } } }, 'x'), '');
|
|
98
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'home-'));
|
|
99
|
+
fs.mkdirSync(path.join(home, '.claude', 'agents'), { recursive: true });
|
|
100
|
+
fs.writeFileSync(path.join(home, '.claude', 'agents', 'avatar-druplicon.md'), '---\nname: avatar-druplicon\n---\n');
|
|
101
|
+
const s = { assistant: { avatar_agents: { druplicon: 'avatar-druplicon', coffee: 'not-installed', bot: '../../etc' } } };
|
|
102
|
+
assert.strictEqual(agentOf(s, 'druplicon', home), 'avatar-druplicon');
|
|
103
|
+
assert.strictEqual(agentOf(s, 'coffee', home), '', 'an agent that is not installed is not used');
|
|
104
|
+
assert.strictEqual(agentOf(s, 'bot', home), '');
|
|
105
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
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 ui = fs.readFileSync(path.join(__dirname, '../..', 'public/ui.js'), 'utf8');
|
|
7
|
+
|
|
8
|
+
test('the send button animates when a message is sent, by click or Enter, unless motion is reduced', () => {
|
|
9
|
+
assert.ok(ui.includes('.input-button.inside-end.ws-sent svg { animation: ws-send-fly'));
|
|
10
|
+
assert.ok(ui.includes('@keyframes ws-send-fly'));
|
|
11
|
+
const frames = ui.slice(ui.indexOf('@keyframes ws-send-fly'), ui.indexOf('@keyframes ws-send-fly') + 400);
|
|
12
|
+
assert.ok(frames.includes('translate:') && !frames.includes('transform:'), 'transform on the arrow is forced to none, so the individual properties are used');
|
|
13
|
+
assert.ok(ui.includes('@media (prefers-reduced-motion: reduce) { .input-button.inside-end.ws-sent svg { animation: none; } }'));
|
|
14
|
+
assert.ok(ui.includes('narrator.cancel(); playSendAnimation(); setWorking(true); }'), 'it is tied to the message being sent, not to a click');
|
|
15
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggested prompts in the assistant panel.
|
|
3
|
+
*
|
|
4
|
+
* What matters is what they are NOT: they never name a project, a branch, a command or a key, because
|
|
5
|
+
* a name in a suggestion goes stale the day the thing is renamed, and a list that points at things
|
|
6
|
+
* that are gone teaches the reader to skip the list. They are made from what a workspace states about
|
|
7
|
+
* itself, so a new workspace gets sensible ones with no code change.
|
|
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 { suggestionsFor, defaultSuggestions, cleanSuggestions, MAX_SUGGESTIONS, MAX_LENGTH } = require('../../assistant-suggestions');
|
|
16
|
+
const { loadWorkspaces } = require('../../workspaces');
|
|
17
|
+
|
|
18
|
+
const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
19
|
+
// The pronoun is the one capital a plain sentence may carry besides its first letter.
|
|
20
|
+
const plain = (s) => s.replace(/\bI\b/g, 'i');
|
|
21
|
+
const meta = (over) => ({ noun: 'project', nounPlural: 'projects', kind: 'projects', typeActions: new Set(['ddev', 'build', 'backup', 'tests', 'clone']), ...over });
|
|
22
|
+
|
|
23
|
+
test('every real workspace gets a short, general list', () => {
|
|
24
|
+
const all = loadWorkspaces();
|
|
25
|
+
assert.ok(Object.keys(all).length > 5, 'there are workspaces to check');
|
|
26
|
+
for (const [key, ws] of Object.entries(all)) {
|
|
27
|
+
const list = suggestionsFor({ context: `workspace:${key}`, meta: ws });
|
|
28
|
+
assert.ok(list.length >= 3 && list.length <= MAX_SUGGESTIONS, `${key} has ${list.length}`);
|
|
29
|
+
// Plain words only: a capital to start, lowercase after, and a question mark. That is what keeps a
|
|
30
|
+
// project name, a script name, a key or a number out of every one of them.
|
|
31
|
+
for (const s of list) assert.match(plain(s), /^[A-Z][a-z ,']*\?$/, `${key}: "${s}" names something or is not a question`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('the pages that belong to no workspace get general ones too', () => {
|
|
36
|
+
for (const context of ['home', 'assistant', 'settings', 'commands']) {
|
|
37
|
+
const list = suggestionsFor({ context, meta: null });
|
|
38
|
+
assert.ok(list.length >= 3, context);
|
|
39
|
+
for (const s of list) assert.match(plain(s), /^[A-Z][a-z ,']*\?$/, `${context}: "${s}"`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('a workspace is asked about its own kind of thing', () => {
|
|
44
|
+
const dev = defaultSuggestions('workspace:dev', meta());
|
|
45
|
+
assert.ok(dev.some((s) => /projects/.test(s)));
|
|
46
|
+
const agents = defaultSuggestions('workspace:ai-agents', meta({ noun: 'agent', nounPlural: 'agents', kind: 'files', typeActions: new Set() }));
|
|
47
|
+
assert.ok(agents.every((s) => /agent/.test(s)), 'every one is about agents');
|
|
48
|
+
assert.ok(agents.some((s) => /add an agent/.test(s)), 'an agent, not a agent');
|
|
49
|
+
const skill = defaultSuggestions('workspace:ai-skills', meta({ noun: 'skill', nounPlural: 'skills', kind: 'files', typeActions: new Set() }));
|
|
50
|
+
assert.ok(skill.some((s) => /add a skill/.test(s)));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('it never offers what the workspace cannot do', () => {
|
|
54
|
+
const clones = defaultSuggestions('workspace:modules', meta({ noun: 'module', nounPlural: 'modules', typeActions: new Set(['build', 'graphify']) }));
|
|
55
|
+
assert.ok(!clones.some((s) => /running\?$/.test(s) && /which are running/.test(s)), 'a folder of git clones has nothing "running"');
|
|
56
|
+
assert.ok(!clones.some((s) => /back up/.test(s)), 'and cannot back anything up');
|
|
57
|
+
assert.ok(clones.some((s) => /not committed/.test(s)));
|
|
58
|
+
const site = defaultSuggestions('workspace:dev', meta());
|
|
59
|
+
assert.ok(site.some((s) => /which are running/.test(s)));
|
|
60
|
+
assert.ok(site.some((s) => /back up/.test(s)));
|
|
61
|
+
const bare = defaultSuggestions('workspace:x', meta({ typeActions: new Set(['ddev']) }));
|
|
62
|
+
assert.ok(!bare.some((s) => /build a new/.test(s)) && !bare.some((s) => /back up/.test(s)));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('the backups page asks about backups', () => {
|
|
66
|
+
const list = defaultSuggestions('backups:dev', meta());
|
|
67
|
+
assert.ok(list.every((s) => /back ?up/.test(s)));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a workspace can say its own, and an empty list switches them off', () => {
|
|
71
|
+
const m = meta();
|
|
72
|
+
assert.deepStrictEqual(suggestionsFor({ context: 'workspace:dev', meta: m, own: ['What is in here?', 'How do I start?'] }), ['What is in here?', 'How do I start?']);
|
|
73
|
+
assert.deepStrictEqual(suggestionsFor({ context: 'workspace:dev', meta: m, own: [] }), [], 'none, not the defaults');
|
|
74
|
+
assert.strictEqual(suggestionsFor({ context: 'workspace:dev', meta: m, own: undefined }).length >= 3, true, 'absent means the defaults');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('settings.yml gives one list to every page that has none of its own', () => {
|
|
78
|
+
const m = meta();
|
|
79
|
+
assert.deepStrictEqual(suggestionsFor({ context: 'workspace:dev', meta: m, global: ['Where is it?'] }), ['Where is it?']);
|
|
80
|
+
assert.deepStrictEqual(suggestionsFor({ context: 'workspace:dev', meta: m, own: ['Mine?'], global: ['Where is it?'] }), ['Mine?'], 'the workspace wins');
|
|
81
|
+
assert.deepStrictEqual(suggestionsFor({ context: 'home', meta: null, global: [] }), [], 'an empty global list switches it off');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('what a person writes is cleaned the way the defaults are made', () => {
|
|
85
|
+
assert.strictEqual(cleanSuggestions('not a list'), null);
|
|
86
|
+
assert.strictEqual(cleanSuggestions(undefined), null);
|
|
87
|
+
assert.deepStrictEqual(cleanSuggestions([' spaced out ', '', ' ', 42, null, { a: 1 }, 'Spaced out']), ['spaced out'], 'trimmed, one line, deduplicated, strings only');
|
|
88
|
+
assert.strictEqual(cleanSuggestions(Array.from({ length: 12 }, (_, i) => `Question ${i}?`)).length, MAX_SUGGESTIONS);
|
|
89
|
+
assert.strictEqual(cleanSuggestions(['x'.repeat(500)])[0].length, MAX_LENGTH);
|
|
90
|
+
assert.deepStrictEqual(cleanSuggestions(['one\ntwo']), ['one two'], 'one line each');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('the page hands the list to the panel, and a workspace file can carry its own', () => {
|
|
94
|
+
const server = app('server.js');
|
|
95
|
+
assert.ok(server.includes("require('./assistant-suggestions')"));
|
|
96
|
+
assert.ok(server.includes('suggestions });') || server.includes('suggestions };'), 'it is part of the settings the panel is given');
|
|
97
|
+
assert.ok(server.includes('own: meta ? meta.assistantSuggestions : undefined'));
|
|
98
|
+
assert.ok(app('workspaces.js').includes('assistantSuggestions: (wsSettings.assistant || {}).suggestions'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('the panel shows them only on an empty conversation, and a click fills the composer without sending', () => {
|
|
102
|
+
const ui = app('public/ui.js');
|
|
103
|
+
assert.ok(ui.includes('else showSuggestions();'), 'a thread with turns in it never sees them');
|
|
104
|
+
assert.ok(ui.includes('deep-chat-temporary-message'), 'gone once anything is sent, never saved to the thread');
|
|
105
|
+
assert.ok(ui.includes("'ws-suggestion':"));
|
|
106
|
+
// The handler that runs on a click must not be able to send: the assistant has a shell.
|
|
107
|
+
const handler = ui.slice(ui.indexOf("'ws-suggestion':"), ui.indexOf("'ws-suggestion':") + 500);
|
|
108
|
+
assert.ok(handler.includes('fillComposer('));
|
|
109
|
+
assert.ok(!handler.includes('submitUserMessage'));
|
|
110
|
+
assert.ok(ui.includes('escapeHtml(s)'), 'text goes into the markup escaped');
|
|
111
|
+
assert.ok(ui.includes('suggestTries += 1) < 8'), 'the retry is bounded');
|
|
112
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
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 = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
7
|
+
const server = app('server.js');
|
|
8
|
+
const ui = app('public/ui.js');
|
|
9
|
+
const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
|
|
10
|
+
|
|
11
|
+
test('the assistant options offer a check button for each part of the prompt', () => {
|
|
12
|
+
for (const key of ['voice', 'mic', 'mode', 'suggest']) assert.ok(menu.includes(`['${key}', `), key);
|
|
13
|
+
assert.ok(menu.includes('class="uk-checkbox assistant-switch" type="checkbox" data-switch="${key}"'));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('every switch in the menu has a handler on the page', () => {
|
|
17
|
+
const keys = [...menu.matchAll(/\['(voice|mic|mode|suggest)', '/g)].map((m) => m[1]);
|
|
18
|
+
for (const key of keys) assert.match(ui, new RegExp(`\\n ${key}: \\{`), `no handler for ${key}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('the choice is kept per browser and the setting is only where it starts', () => {
|
|
22
|
+
for (const name of ['ws-mic', 'ws-mode', 'ws-suggest']) assert.ok(ui.includes(`'${name}'`), name);
|
|
23
|
+
assert.ok(ui.includes("prefSwitch('ws-mic', cfg.voiceInput !== false"));
|
|
24
|
+
assert.ok(ui.includes("prefSwitch('ws-mode', true"));
|
|
25
|
+
assert.ok(ui.includes("prefSwitch('ws-suggest', true"));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('the microphone is hidden by an attribute on the panel, and only when it exists at all', () => {
|
|
29
|
+
assert.ok(ui.includes(':host([data-assistant-mic="off"]) #microphone-button { display: none !important; }'));
|
|
30
|
+
assert.ok(ui.includes("el.setAttribute('data-assistant-mic', 'off')"));
|
|
31
|
+
assert.ok(ui.includes('show: cfg.speechToText !== false'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('prompt mode is hidden on the selector itself', () => {
|
|
35
|
+
assert.ok(ui.includes("picker.style.display = on ? '' : 'none'"));
|
|
36
|
+
assert.ok(ui.includes('show: !!picker'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('suggestions honour the switch when loaded, can be removed, and are not shown over a conversation', () => {
|
|
40
|
+
assert.ok(ui.includes("'ws-suggest', null) : null) !== '0'"));
|
|
41
|
+
assert.ok(ui.includes('!suggestOn()) return;'));
|
|
42
|
+
assert.ok(ui.includes('if (conversationEmpty) showSuggestions()'));
|
|
43
|
+
assert.ok(ui.includes("closest('[class*=\"outer-message-container\"]')"));
|
|
44
|
+
assert.ok(ui.includes('init: null'), 'the switch must not show them before the history has loaded');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('a switch with nothing to switch is hidden, not offered', () => {
|
|
48
|
+
assert.ok(ui.includes("if (!sw || !sw.show) { box.closest('li').hidden = true; return; }"));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('the copies of the menu for each way of showing the assistant stay in step', () => {
|
|
52
|
+
assert.ok(ui.includes('.assistant-switch[data-switch="${box.dataset.switch}"]'));
|
|
53
|
+
});
|