@webship.co/workspace 1.0.0-alpha1 → 1.0.0-beta2
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} +1 -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/{plugin-loader.js → core/workspace-app/plugin-loader.js} +19 -1
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{row-layout.js → core/workspace-app/row-layout.js} +0 -1
- package/{server.js → core/workspace-app/server.js} +26 -225
- 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 +39 -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 +40 -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 +152 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -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 +177 -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 +153 -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/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 +74 -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 +198 -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 +417 -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/{workspace-types.js → core/workspace-app/workspace-types.js} +2 -2
- package/{workspaces.js → core/workspace-app/workspaces.js} +1 -5
- package/package.json +38 -36
- package/actions/graphs.js +0 -107
- package/actions/obsidian.js +0 -59
- package/graphs.js +0 -104
- package/obsidian.js +0 -405
- /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-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/{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
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the assistant is shown: sidebar, wide, floating, full page — plus the switch that closes it.
|
|
3
|
+
*
|
|
4
|
+
* Modelled on Drupal AI's own chatbot (a toggle in the admin toolbar, a menu in the panel header
|
|
5
|
+
* carrying Clear history and Close, an expand control, and the state on the document element), so
|
|
6
|
+
* what is checked here is the part that would break quietly: the state must be applied BEFORE first
|
|
7
|
+
* paint, the view must be a default rather than an override, and the full-page view must not leave
|
|
8
|
+
* two copies of one conversation on the screen.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const { test } = require('node:test');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const app = path.join(__dirname, '..', '..');
|
|
17
|
+
const server = fs.readFileSync(path.join(app, 'server.js'), 'utf8');
|
|
18
|
+
const ui = fs.readFileSync(path.join(app, 'public', 'ui.js'), 'utf8');
|
|
19
|
+
const css = fs.readFileSync(path.join(app, 'public', 'css', 'components', 'assistant-views.css'), 'utf8');
|
|
20
|
+
const { styleSettings } = require(path.join(app, 'workspaces'));
|
|
21
|
+
|
|
22
|
+
test('the view and the open state are settings, with a fallback rather than a blank panel', () => {
|
|
23
|
+
const s = styleSettings();
|
|
24
|
+
assert.ok(['sidebar', 'wide', 'float', 'full'].includes(s.assistantView), s.assistantView);
|
|
25
|
+
assert.ok(['yes', 'no'].includes(s.assistantOpen), s.assistantOpen);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('the state is applied before first paint, and a stored choice wins over the setting', () => {
|
|
29
|
+
const head = server.slice(server.indexOf("var d=document.documentElement"), server.indexOf('</script>'));
|
|
30
|
+
// Read through wsPref: the stored choice is a cookie on the hub domain, so it is the same choice on
|
|
31
|
+
// every workspace subdomain rather than one per origin.
|
|
32
|
+
assert.match(head, /wsPref\('ws-assistant-view',null\)\|\|'\$\{style\.assistantView\}'/,
|
|
33
|
+
'the setting is the default, the stored choice wins — as with style.mode');
|
|
34
|
+
assert.match(head, /indexOf\(v\)<0\)v='sidebar'/, 'an unknown view must fall back, not render nothing');
|
|
35
|
+
assert.match(head, /d\.dataset\.assistantView=v/);
|
|
36
|
+
assert.match(head, /classList\.add\('assistant-closed'\)/);
|
|
37
|
+
// The panel is 25rem of the layout: deciding this in the deferred ui.js would draw the page one
|
|
38
|
+
// way and reflow it a frame later, which is the bug the rail's own pre-paint line exists for. So
|
|
39
|
+
// ui.js may only CHANGE the class, from the one function a click reaches.
|
|
40
|
+
const setsClosed = ui.match(/classList\.toggle\('assistant-closed'/g) || [];
|
|
41
|
+
assert.strictEqual(setsClosed.length, 1, 'only setAssistantOpen may write that class');
|
|
42
|
+
const fn = ui.slice(ui.indexOf('function setAssistantOpen'), ui.indexOf('function setAssistantOpen') + 400);
|
|
43
|
+
assert.match(fn, /classList\.toggle\('assistant-closed', !open\)/, 'and that is where it lives');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('the panel header carries the menu, wider and close — and the menu carries the rest', () => {
|
|
47
|
+
// Up to the close button: the avatar's own menu (when it is on) comes first and is long.
|
|
48
|
+
const from = server.indexOf('class="assistant-controls"');
|
|
49
|
+
const head = server.slice(from, server.indexOf('assistant-hide', from) + 200);
|
|
50
|
+
assert.match(head, /assistant-ctl[^>]*Assistant options/, 'the kebab');
|
|
51
|
+
assert.match(head, /assistant-wider/, 'wider');
|
|
52
|
+
assert.match(head, /assistant-hide/, 'close');
|
|
53
|
+
// The items themselves are one function, since the toolbar hangs the same menu.
|
|
54
|
+
const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
|
|
55
|
+
assert.match(menu, /hx-post="\/actions\/chat-reset"[\s\S]*?hx-trigger="confirmed-remove"/,
|
|
56
|
+
'New chat must ask first, like every other action that throws something away');
|
|
57
|
+
assert.match(menu, /class="assistant-view-pick" data-view="\$\{v\}"/, 'the views are rendered from a list');
|
|
58
|
+
assert.match(menu, /assistant-close-pick/, 'and closing it is offered where the views are');
|
|
59
|
+
for (const [v, label] of [['sidebar', 'Sidebar'], ['wide', 'Wide'], ['float', 'Floating'], ['full', 'Full page']]) {
|
|
60
|
+
assert.match(menu, new RegExp(`\\['${v}', '${label}'`), `no way to choose ${v}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('the control is in the rail, not in the toolbar', () => {
|
|
65
|
+
// The toolbar is the corner the brand and the page identity own. The rail is where the things that
|
|
66
|
+
// apply to whatever page you are on live, which is exactly what this is.
|
|
67
|
+
const left = server.slice(server.indexOf('<div class="uk-navbar-left">'), server.indexOf('<div class="uk-navbar-right">'));
|
|
68
|
+
assert.ok(!/assistant-toggle|assistant-switch/.test(left), 'the toolbar must not carry the switch');
|
|
69
|
+
const rail = server.slice(server.indexOf("label: 'Go'"), server.indexOf("] });", server.indexOf("label: 'Go'")));
|
|
70
|
+
assert.match(rail, /<button type="button" class="action-btn actions-assistant/,
|
|
71
|
+
'the rail row is the control, and a real button — UIKit puts aria-haspopup on whatever precedes the '
|
|
72
|
+
+ 'dropdown, and that attribute is not allowed on a span');
|
|
73
|
+
assert.match(rail, /\$\{assistantMenuItemsHtml\(\)\}/, 'and it carries the whole menu');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('show and hide are two items the stylesheet picks between', () => {
|
|
77
|
+
const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
|
|
78
|
+
assert.match(menu, /assistant-when-closed[\s\S]*?assistant-open-pick/, 'Show, for when it is hidden');
|
|
79
|
+
assert.match(menu, /assistant-when-open[\s\S]*?assistant-close-pick/, 'Hide, for when it is not');
|
|
80
|
+
// Neither needs a script to rename itself: which applies is a state the stylesheet already knows.
|
|
81
|
+
assert.match(css, /html\.assistant-closed \.assistant-when-open,\s*\n\s*html:not\(\.assistant-closed\) \.assistant-when-closed \{ display: none; \}/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('the full-page view is a place, not a layout', () => {
|
|
85
|
+
assert.match(ui, /if \(view === 'full'\)[\s\S]*?window\.location\.href = '\/assistant'/,
|
|
86
|
+
'choosing it must go there');
|
|
87
|
+
assert.match(ui, /window\.location\.pathname === '\/assistant'[\s\S]*?referrer/,
|
|
88
|
+
'leaving it must leave the page too — /assistant has no sidebar to become');
|
|
89
|
+
assert.match(server, /pathname === '\/assistant'/, 'the page itself');
|
|
90
|
+
// Two panels on one screen would be two views of one conversation, both live.
|
|
91
|
+
assert.match(server, /context === 'assistant' \? '' : `<aside class="assistant-side">/,
|
|
92
|
+
'the page must not also carry the docked panel');
|
|
93
|
+
assert.match(css, /html\[data-assistant-view="full"\] \.assistant-side \{ display: none; \}/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('the full-page view is escapable from an ordinary page', () => {
|
|
97
|
+
// In that view no page but /assistant draws a panel, so the panel's own menu is not there to use.
|
|
98
|
+
// The toolbar carries the same menu, or picking Full page and then navigating anywhere would leave
|
|
99
|
+
// no way back to a sidebar — the tile alone only leads back to /assistant.
|
|
100
|
+
assert.match(server, /function assistantMenuItemsHtml\(\)/, 'one menu, hung from two places');
|
|
101
|
+
// To the end of the control, not a fixed window: the comments around it grow, and a slice that is
|
|
102
|
+
// one character short reads as the menu having been taken away.
|
|
103
|
+
const railStart = server.indexOf('class="uk-inline actions-assistant-wrap"');
|
|
104
|
+
const rail = server.slice(railStart, server.indexOf('</div>`', railStart));
|
|
105
|
+
assert.match(rail, /\$\{assistantMenuItemsHtml\(\)\}/, 'the rail carries the menu, so any page can change the view');
|
|
106
|
+
const header = server.slice(server.indexOf('class="uk-inline assistant-menu"'), server.indexOf('class="uk-inline assistant-menu"') + 600);
|
|
107
|
+
assert.match(header, /\$\{assistantMenuItemsHtml\(\)\}/, 'and it must be the same menu, not a second copy');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('showing it does not pretend to open a page that is already open', () => {
|
|
111
|
+
// On /assistant the assistant IS the page: writing a closed/open state there set something nobody
|
|
112
|
+
// could see, which then decided the panel on the next ordinary page for no reason the user gave.
|
|
113
|
+
const handler = ui.slice(ui.indexOf("e.target.closest('.assistant-open-pick')"), ui.indexOf("e.target.closest('.assistant-open-pick')") + 500);
|
|
114
|
+
assert.match(handler, /if \(window\.location\.pathname === '\/assistant'\) return;/);
|
|
115
|
+
assert.ok(handler.indexOf("pathname === '/assistant'") < handler.indexOf('setAssistantOpen'),
|
|
116
|
+
'it must return before writing the open state');
|
|
117
|
+
assert.match(handler, /assistantView\(\) === 'full'[\s\S]*?window\.location\.href = '\/assistant'/,
|
|
118
|
+
'and in the full-page view showing it means going there');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('the floating panel can be moved, and only that one', () => {
|
|
122
|
+
// Where a floating panel should sit depends on what is under it, and that changes page by page.
|
|
123
|
+
assert.match(ui, /function draggableAssistant/);
|
|
124
|
+
assert.match(ui, /dataset\.assistantView !== 'float'\) return;/, 'only the floating view is draggable');
|
|
125
|
+
// A drag that began on the close button would close the panel on release.
|
|
126
|
+
assert.match(ui, /e\.target\.closest\('button, a, input, select, \.assistant-dropdown'\)\) return;/,
|
|
127
|
+
'the header is the handle, minus its controls');
|
|
128
|
+
assert.match(ui, /const maxX = Math\.max\(0, window\.innerWidth - r\.width\)/, 'it is clamped into the viewport');
|
|
129
|
+
assert.match(ui, /window\.addEventListener\('resize', applyStored\)/,
|
|
130
|
+
'a window that got smaller must not leave it off-screen');
|
|
131
|
+
// Leftover inline positions would fight the stylesheet in every other view.
|
|
132
|
+
assert.match(ui, /el\.style\.left = el\.style\.top = el\.style\.bottom = el\.style\.right = '';/);
|
|
133
|
+
assert.match(css, /html\[data-assistant-view="float"\] \.assistant-side \.assistant-head \{\s*\n\s*cursor: grab;/,
|
|
134
|
+
'and it has to say it can be dragged');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('the launcher is the way back in, in every view', () => {
|
|
138
|
+
// The control lives in the rail now, and a collapsed rail is an icon strip a newcomer has no reason
|
|
139
|
+
// to search — so a closed assistant must leave something on the page that opens it.
|
|
140
|
+
assert.match(css, /\.assistant-launcher \{ display: none; \}/, 'not while the panel is open');
|
|
141
|
+
assert.match(css, /html\.assistant-closed \.assistant-launcher \{/, 'and in every view when it is closed');
|
|
142
|
+
assert.ok(!/html\[data-assistant-view="float"\]\.assistant-closed \.assistant-launcher \{/.test(css),
|
|
143
|
+
'not the floating view alone any more');
|
|
144
|
+
assert.match(ui, /closest\('\.assistant-launcher'\)[\s\S]*?assistantView\(\) === 'full'[\s\S]*?'\/assistant'/,
|
|
145
|
+
'and in the full-page view it leads to the page, since there is no panel to show');
|
|
146
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
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 ui = app('public/ui.js');
|
|
8
|
+
const css = app('public/css/components/working-indicators.css');
|
|
9
|
+
|
|
10
|
+
test('the Send button shows three animated dots while the assistant works, in place of the arrow', () => {
|
|
11
|
+
assert.ok(ui.includes(':host([data-assistant-working]) .input-button.inside-end::after'));
|
|
12
|
+
assert.ok(ui.includes('animation: ws-dots 1.2s ease-in-out infinite;'));
|
|
13
|
+
assert.ok(ui.includes(':host([data-assistant-working]) .input-button.inside-end svg { opacity: 0; }'));
|
|
14
|
+
assert.equal((ui.match(/radial-gradient\(circle, var\(--text-inverse\) 2\.4px/g) || []).length, 3, 'three dots, in the theme token and not a colour');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('deep-chat\'s own dots on the button are hidden so only one animation shows', () => {
|
|
18
|
+
assert.ok(ui.includes(':host([data-assistant-working]) .loading-submit-button { display: none !important; }'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('the dots bounce one after another', () => {
|
|
22
|
+
const frames = ui.slice(ui.indexOf('@keyframes ws-dots'), ui.indexOf('@keyframes ws-dots') + 500);
|
|
23
|
+
const raised = [...frames.matchAll(/(\d+)% \{ background-position: ([^;]+);/g)].filter((m) => m[1] !== '0').map((m) => m[2].split(',').map((p) => p.trim()).findIndex((p) => p.endsWith('30%')));
|
|
24
|
+
assert.deepEqual(raised.filter((i) => i >= 0), [0, 1, 2]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('the panel header pulses the avatar and sweeps a light while it works', () => {
|
|
28
|
+
assert.ok(css.includes('.assistant-panel[data-working] .assistant-head::after'));
|
|
29
|
+
assert.ok(css.includes('.assistant-panel[data-working] .assistant-avatar { animation: assistant-pulse'));
|
|
30
|
+
assert.ok(css.includes('@keyframes assistant-sweep') && css.includes('@keyframes assistant-pulse'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('motion is switched off for people who prefer reduced motion', () => {
|
|
34
|
+
assert.ok(ui.includes('@media (prefers-reduced-motion: reduce) { :host([data-assistant-working]) .input-button.inside-end::after { animation: none; } }'));
|
|
35
|
+
assert.match(css, /prefers-reduced-motion: reduce\) \{\s*\.assistant-panel\[data-working\] \.assistant-head::after,\s*\.assistant-panel\[data-working\] \.assistant-avatar \{ animation: none; \}/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('working starts when a message is sent and ends on the answer, a reply of any kind, an error, or after a time limit', () => {
|
|
39
|
+
assert.ok(ui.includes("if (message.role === 'user') { conversationEmpty = false; narrator.cancel(); playSendAnimation(); setWorking(true); }"));
|
|
40
|
+
assert.ok(ui.includes('else setWorking(false);'), 'an assistant message ends it');
|
|
41
|
+
assert.ok(ui.includes('el.onError = () => setWorking(false, true);'), 'an error ends it, and says it failed');
|
|
42
|
+
assert.ok(ui.includes(' setWorking(false);\n if (response && response.html)'), 'the response interceptor ends it');
|
|
43
|
+
assert.ok(ui.includes('setTimeout(() => setWorking(false), 16 * 60 * 1000)'), 'a request that never answers cannot leave it running for ever');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('the state is on the panel and the chat, so both the page and the shadow styles can read it', () => {
|
|
47
|
+
assert.ok(ui.includes("el.toggleAttribute('data-assistant-working', on)"));
|
|
48
|
+
assert.ok(ui.includes("document.querySelectorAll('.assistant-panel').forEach((p) => p.toggleAttribute('data-working', on))"));
|
|
49
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's page context: the part that reaches into the rest of the app.
|
|
3
|
+
*
|
|
4
|
+
* Every branch of it names something from another module, and a name that is not imported throws
|
|
5
|
+
* only when somebody asks a question from that page. That is not theoretical — after the module
|
|
6
|
+
* split the settings and workspace branches referenced `listSettingsFiles`, `CONFIG_DIR`,
|
|
7
|
+
* `findBuilderScripts` and `listBackups` without importing any of them, so the assistant worked on
|
|
8
|
+
* the home page and returned HTTP 500 on every workspace page and on settings.
|
|
9
|
+
*
|
|
10
|
+
* These tests call it for every context the dashboard can send. They cost nothing (no agent is
|
|
11
|
+
* spawned) and they fail on a missing import rather than leaving it for a person to find.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { test } = require('node:test');
|
|
15
|
+
const assert = require('node:assert');
|
|
16
|
+
|
|
17
|
+
const { pageContextFor } = require('../../assistant');
|
|
18
|
+
const { loadWorkspaces, findBuilderScripts, commandFlags } = require('../../workspaces');
|
|
19
|
+
|
|
20
|
+
test('every context the dashboard sends produces a context line', () => {
|
|
21
|
+
const keys = Object.keys(loadWorkspaces());
|
|
22
|
+
assert.ok(keys.length, 'there is at least one workspace to ask about');
|
|
23
|
+
const contexts = ['home', 'settings', 'commands',
|
|
24
|
+
...keys.map((k) => `workspace:${k}`), ...keys.map((k) => `backups:${k}`)];
|
|
25
|
+
for (const ctx of contexts) {
|
|
26
|
+
const out = pageContextFor(ctx);
|
|
27
|
+
assert.equal(typeof out, 'string', `${ctx} should produce a string`);
|
|
28
|
+
assert.ok(out.length > 20, `${ctx} produced nothing useful: ${out}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('the settings context names the files but never a secret', () => {
|
|
33
|
+
const out = pageContextFor('settings');
|
|
34
|
+
assert.match(out, /Settings page/);
|
|
35
|
+
assert.match(out, /settings\.yml/);
|
|
36
|
+
// It has to TELL the agent not to reveal a secret — that instruction is the safeguard.
|
|
37
|
+
assert.match(out, /NEVER print, echo, cat, grep/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('a workspace context names that workspace and its builders', () => {
|
|
41
|
+
// NOT `Object.keys(...)[0]`: the first workspace is whichever one the cards are ordered to put
|
|
42
|
+
// first, which is a setting somebody can drag. When `designs` was moved to the top this test began
|
|
43
|
+
// failing on a Penpot builder that has no --install — a true statement about the wrong workspace.
|
|
44
|
+
// Pick one that actually has a builder taking --install, which is what the assertion is about.
|
|
45
|
+
const all = loadWorkspaces();
|
|
46
|
+
const key = Object.keys(all).find((k) => {
|
|
47
|
+
const dir = all[k].dir;
|
|
48
|
+
return dir && findBuilderScripts(dir).some((b) => commandFlags(dir, b).some((f) => f.long === '--install'));
|
|
49
|
+
});
|
|
50
|
+
assert.ok(key, 'some workspace builds something with --install');
|
|
51
|
+
const out = pageContextFor(`workspace:${key}`);
|
|
52
|
+
assert.ok(out.includes(all[key].label), `should name ${all[key].label}`);
|
|
53
|
+
// The AI Context carries each command's real flag contract, not just names.
|
|
54
|
+
assert.match(out, /Command contract/);
|
|
55
|
+
assert.match(out, /--install \(-i\)/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('an unknown workspace falls back to the home context rather than throwing', () => {
|
|
59
|
+
assert.match(pageContextFor('workspace:no-such-workspace'), /home page/);
|
|
60
|
+
assert.match(pageContextFor('nonsense'), /home page/);
|
|
61
|
+
});
|