nastechai-desktop 18.1.0
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/.prettierrc +11 -0
- package/DESIGN.md +167 -0
- package/README.md +141 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/components.json +21 -0
- package/electron/backend-env.cjs +112 -0
- package/electron/backend-env.test.cjs +111 -0
- package/electron/backend-probes.cjs +106 -0
- package/electron/backend-probes.test.cjs +82 -0
- package/electron/backend-ready.cjs +66 -0
- package/electron/bootstrap-platform.cjs +91 -0
- package/electron/bootstrap-platform.test.cjs +111 -0
- package/electron/bootstrap-runner.cjs +720 -0
- package/electron/bootstrap-runner.test.cjs +138 -0
- package/electron/connection-config.cjs +254 -0
- package/electron/connection-config.test.cjs +329 -0
- package/electron/dashboard-token.cjs +99 -0
- package/electron/dashboard-token.test.cjs +142 -0
- package/electron/desktop-uninstall.cjs +232 -0
- package/electron/desktop-uninstall.test.cjs +246 -0
- package/electron/entitlements.mac.inherit.plist +14 -0
- package/electron/entitlements.mac.plist +14 -0
- package/electron/fs-read-dir.cjs +109 -0
- package/electron/fs-read-dir.test.cjs +364 -0
- package/electron/gateway-ws-probe.cjs +188 -0
- package/electron/gateway-ws-probe.test.cjs +122 -0
- package/electron/git-root.cjs +54 -0
- package/electron/git-root.test.cjs +40 -0
- package/electron/git-worktrees.cjs +174 -0
- package/electron/hardening.cjs +184 -0
- package/electron/hardening.test.cjs +116 -0
- package/electron/main.cjs +5762 -0
- package/electron/oauth-net-request.cjs +20 -0
- package/electron/oauth-net-request.test.cjs +34 -0
- package/electron/preload.cjs +135 -0
- package/electron/session-windows.cjs +99 -0
- package/electron/session-windows.test.cjs +177 -0
- package/electron/update-remote.cjs +56 -0
- package/electron/update-remote.test.cjs +78 -0
- package/electron/vscode-marketplace.cjs +331 -0
- package/electron/vscode-marketplace.test.cjs +113 -0
- package/electron/windows-child-process.test.cjs +57 -0
- package/electron/windows-user-env.cjs +76 -0
- package/electron/windows-user-env.test.cjs +90 -0
- package/electron/workspace-cwd.cjs +38 -0
- package/electron/workspace-cwd.test.cjs +45 -0
- package/eslint.config.mjs +122 -0
- package/index.html +17 -0
- package/package.json +254 -0
- package/pr-assets/session-source-folders.png +0 -0
- package/preview-demo.html +65 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/ds-assets/filler-bg0.jpg +0 -0
- package/public/nastech-frames/nastech-frame-0.png +0 -0
- package/public/nastech-frames/nastech-frame-1.png +0 -0
- package/public/nastech-frames/nastech-frame-2.png +0 -0
- package/public/nastech-frames/nastech-frame-3.png +0 -0
- package/public/nastech-frames/nastech-frame-4.png +0 -0
- package/public/nastech-frames/nastech-frame-5.png +0 -0
- package/public/nastech-frames/nastech-frame-6.png +0 -0
- package/public/nastech-frames/nastech-frame-7.png +0 -0
- package/public/nastech-girl.jpg +0 -0
- package/public/nastech-sprite.png +0 -0
- package/public/nastech.png +0 -0
- package/scripts/after-pack.cjs +41 -0
- package/scripts/assert-dist-built.cjs +70 -0
- package/scripts/assert-dist-built.test.cjs +84 -0
- package/scripts/assert-root-install.cjs +13 -0
- package/scripts/before-build.cjs +11 -0
- package/scripts/before-pack.cjs +78 -0
- package/scripts/before-pack.test.cjs +53 -0
- package/scripts/click-session.mjs +51 -0
- package/scripts/dev-no-hmr.mjs +22 -0
- package/scripts/diag-jump.mjs +115 -0
- package/scripts/diag-scroll-reset.mjs +229 -0
- package/scripts/eval.mjs +21 -0
- package/scripts/leak-typing.mjs +222 -0
- package/scripts/measure-jump.mjs +108 -0
- package/scripts/measure-latency.mjs +184 -0
- package/scripts/measure-real-stream.mjs +252 -0
- package/scripts/measure-submit.mjs +179 -0
- package/scripts/measure-synthetic-stream.mjs +322 -0
- package/scripts/notarize-artifact.cjs +77 -0
- package/scripts/notarize.cjs +100 -0
- package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
- package/scripts/probe-renderer.mjs +38 -0
- package/scripts/probe-thread.mjs +40 -0
- package/scripts/profile-long-stream.mjs +191 -0
- package/scripts/profile-real-stream.mjs +137 -0
- package/scripts/profile-synth-stream.mjs +103 -0
- package/scripts/profile-typing-lag.md +381 -0
- package/scripts/profile-typing.mjs +260 -0
- package/scripts/reload-renderer.mjs +25 -0
- package/scripts/reload.mjs +36 -0
- package/scripts/set-exe-identity.cjs +94 -0
- package/scripts/stage-native-deps.cjs +159 -0
- package/scripts/test-desktop.mjs +425 -0
- package/scripts/write-build-stamp.cjs +126 -0
- package/src/app/agents/index.tsx +398 -0
- package/src/app/artifacts/index.test.ts +62 -0
- package/src/app/artifacts/index.tsx +906 -0
- package/src/app/chat/chat-drop-overlay.tsx +48 -0
- package/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/src/app/chat/composer/attachments.tsx +114 -0
- package/src/app/chat/composer/completion-drawer.tsx +63 -0
- package/src/app/chat/composer/context-menu.tsx +172 -0
- package/src/app/chat/composer/controls.tsx +289 -0
- package/src/app/chat/composer/drop-affordance.ts +2 -0
- package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/src/app/chat/composer/focus.ts +134 -0
- package/src/app/chat/composer/help-hint.tsx +59 -0
- package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
- package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
- package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
- package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
- package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
- package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
- package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/src/app/chat/composer/index.tsx +1611 -0
- package/src/app/chat/composer/inline-refs.ts +138 -0
- package/src/app/chat/composer/model-pill.tsx +86 -0
- package/src/app/chat/composer/queue-panel.tsx +130 -0
- package/src/app/chat/composer/rich-editor.test.ts +18 -0
- package/src/app/chat/composer/rich-editor.ts +165 -0
- package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
- package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/src/app/chat/composer/text-utils.test.ts +77 -0
- package/src/app/chat/composer/text-utils.ts +107 -0
- package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
- package/src/app/chat/composer/trigger-popover.tsx +116 -0
- package/src/app/chat/composer/types.ts +64 -0
- package/src/app/chat/composer/url-dialog.tsx +82 -0
- package/src/app/chat/composer/voice-activity.tsx +252 -0
- package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/src/app/chat/hooks/use-composer-actions.ts +525 -0
- package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
- package/src/app/chat/index.tsx +390 -0
- package/src/app/chat/perf-probe.tsx +269 -0
- package/src/app/chat/right-rail/index.ts +1 -0
- package/src/app/chat/right-rail/preview-console-state.ts +82 -0
- package/src/app/chat/right-rail/preview-console.tsx +290 -0
- package/src/app/chat/right-rail/preview-file.tsx +559 -0
- package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
- package/src/app/chat/right-rail/preview-pane.tsx +657 -0
- package/src/app/chat/right-rail/preview.tsx +171 -0
- package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
- package/src/app/chat/sidebar/index.tsx +1219 -0
- package/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/src/app/chat/sidebar/order.test.ts +21 -0
- package/src/app/chat/sidebar/order.ts +17 -0
- package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
- package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
- package/src/app/chat/sidebar/session-row.tsx +257 -0
- package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
- package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/src/app/chat/thread-loading.test.ts +34 -0
- package/src/app/chat/thread-loading.ts +26 -0
- package/src/app/command-center/index.tsx +654 -0
- package/src/app/command-palette/index.tsx +513 -0
- package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/src/app/cron/index.tsx +942 -0
- package/src/app/cron/job-state.ts +29 -0
- package/src/app/desktop-controller.tsx +938 -0
- package/src/app/floating-hud.ts +22 -0
- package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
- package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
- package/src/app/hooks/use-keybinds.ts +186 -0
- package/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/src/app/hooks/use-route-enum-param.ts +38 -0
- package/src/app/index.tsx +1 -0
- package/src/app/layout-constants.ts +13 -0
- package/src/app/messaging/index.tsx +648 -0
- package/src/app/messaging/platform-icon.tsx +93 -0
- package/src/app/model-picker-overlay.tsx +42 -0
- package/src/app/model-visibility-overlay.tsx +31 -0
- package/src/app/overlays/overlay-chrome.tsx +66 -0
- package/src/app/overlays/overlay-search-input.tsx +33 -0
- package/src/app/overlays/overlay-split-layout.tsx +130 -0
- package/src/app/overlays/overlay-view.tsx +91 -0
- package/src/app/page-search-shell.tsx +75 -0
- package/src/app/profiles/create-profile-dialog.tsx +154 -0
- package/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/src/app/profiles/index.tsx +671 -0
- package/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/src/app/right-sidebar/files/ipc.ts +161 -0
- package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/src/app/right-sidebar/files/tree.tsx +224 -0
- package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
- package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
- package/src/app/right-sidebar/index.test.tsx +75 -0
- package/src/app/right-sidebar/index.tsx +395 -0
- package/src/app/right-sidebar/store.ts +15 -0
- package/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/src/app/right-sidebar/terminal/index.tsx +98 -0
- package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
- package/src/app/right-sidebar/terminal/selection.ts +75 -0
- package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
- package/src/app/routes.ts +88 -0
- package/src/app/session/hooks/use-context-suggestions.ts +58 -0
- package/src/app/session/hooks/use-cwd-actions.ts +109 -0
- package/src/app/session/hooks/use-message-stream.ts +957 -0
- package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/src/app/session/hooks/use-model-controls.ts +106 -0
- package/src/app/session/hooks/use-nastech-config.ts +74 -0
- package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
- package/src/app/session/hooks/use-preview-routing.ts +223 -0
- package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
- package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
- package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
- package/src/app/session/hooks/use-route-resume.ts +115 -0
- package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
- package/src/app/session/hooks/use-session-actions.ts +885 -0
- package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
- package/src/app/session/hooks/use-session-state-cache.ts +191 -0
- package/src/app/session-picker-overlay.tsx +32 -0
- package/src/app/session-switcher.tsx +107 -0
- package/src/app/settings/about-settings.tsx +173 -0
- package/src/app/settings/appearance-settings.tsx +162 -0
- package/src/app/settings/config-settings.tsx +384 -0
- package/src/app/settings/constants.ts +545 -0
- package/src/app/settings/credential-key-ui.tsx +373 -0
- package/src/app/settings/env-credentials.tsx +198 -0
- package/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/src/app/settings/field-copy.ts +56 -0
- package/src/app/settings/gateway-settings.tsx +620 -0
- package/src/app/settings/helpers.test.ts +138 -0
- package/src/app/settings/helpers.ts +151 -0
- package/src/app/settings/index.tsx +237 -0
- package/src/app/settings/keys-settings.tsx +96 -0
- package/src/app/settings/mcp-settings.tsx +271 -0
- package/src/app/settings/model-settings.test.tsx +157 -0
- package/src/app/settings/model-settings.tsx +559 -0
- package/src/app/settings/notifications-settings.tsx +150 -0
- package/src/app/settings/primitives.tsx +115 -0
- package/src/app/settings/providers-settings.test.tsx +100 -0
- package/src/app/settings/providers-settings.tsx +258 -0
- package/src/app/settings/sessions-settings.tsx +276 -0
- package/src/app/settings/toolset-config-panel.test.tsx +289 -0
- package/src/app/settings/toolset-config-panel.tsx +449 -0
- package/src/app/settings/types.ts +42 -0
- package/src/app/settings/uninstall-section.tsx +185 -0
- package/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/src/app/shell/app-shell.tsx +167 -0
- package/src/app/shell/gateway-menu-panel.tsx +150 -0
- package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
- package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
- package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
- package/src/app/shell/keybind-panel.tsx +220 -0
- package/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/src/app/shell/model-edit-submenu.tsx +245 -0
- package/src/app/shell/model-menu-panel.tsx +295 -0
- package/src/app/shell/sidebar-label.tsx +22 -0
- package/src/app/shell/statusbar-controls.tsx +185 -0
- package/src/app/shell/titlebar-controls.tsx +244 -0
- package/src/app/shell/titlebar.test.ts +26 -0
- package/src/app/shell/titlebar.ts +45 -0
- package/src/app/shell/use-group-registry.ts +39 -0
- package/src/app/skills/index.test.tsx +103 -0
- package/src/app/skills/index.tsx +371 -0
- package/src/app/types.ts +99 -0
- package/src/app/updates-overlay.tsx +369 -0
- package/src/components/Backdrop.tsx +114 -0
- package/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/src/components/assistant-ui/clarify-tool.tsx +281 -0
- package/src/components/assistant-ui/directive-text.test.ts +39 -0
- package/src/components/assistant-ui/directive-text.tsx +389 -0
- package/src/components/assistant-ui/markdown-text.test.ts +204 -0
- package/src/components/assistant-ui/markdown-text.tsx +497 -0
- package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/src/components/assistant-ui/streaming.test.tsx +739 -0
- package/src/components/assistant-ui/thread-list.tsx +307 -0
- package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
- package/src/components/assistant-ui/thread.tsx +1474 -0
- package/src/components/assistant-ui/todo-tool.tsx +109 -0
- package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
- package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
- package/src/components/assistant-ui/tool-approval.tsx +209 -0
- package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
- package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
- package/src/components/assistant-ui/tool-fallback.tsx +466 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
- package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/src/components/assistant-ui/user-message-text.tsx +150 -0
- package/src/components/boot-failure-overlay.tsx +246 -0
- package/src/components/boot-failure-reauth.test.ts +100 -0
- package/src/components/boot-failure-reauth.ts +81 -0
- package/src/components/brand-mark.tsx +19 -0
- package/src/components/chat/activity-timer-text.tsx +24 -0
- package/src/components/chat/activity-timer.test.tsx +43 -0
- package/src/components/chat/activity-timer.ts +64 -0
- package/src/components/chat/code-card.tsx +78 -0
- package/src/components/chat/compact-markdown.tsx +113 -0
- package/src/components/chat/composer-dock.ts +31 -0
- package/src/components/chat/diff-lines.tsx +54 -0
- package/src/components/chat/disclosure-row.tsx +63 -0
- package/src/components/chat/generated-image-context.tsx +19 -0
- package/src/components/chat/generated-image-result.tsx +174 -0
- package/src/components/chat/image-generation-placeholder.tsx +279 -0
- package/src/components/chat/intro-copy.jsonl +75 -0
- package/src/components/chat/intro.tsx +182 -0
- package/src/components/chat/preview-attachment.tsx +125 -0
- package/src/components/chat/shiki-highlighter.tsx +107 -0
- package/src/components/chat/status-row.tsx +70 -0
- package/src/components/chat/status-section.tsx +42 -0
- package/src/components/chat/terminal-output.tsx +50 -0
- package/src/components/chat/zoomable-image.tsx +177 -0
- package/src/components/desktop-install-overlay.tsx +595 -0
- package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
- package/src/components/desktop-onboarding-overlay.tsx +1286 -0
- package/src/components/error-boundary.tsx +77 -0
- package/src/components/gateway-connecting-overlay.test.tsx +143 -0
- package/src/components/gateway-connecting-overlay.tsx +183 -0
- package/src/components/haptics-provider.tsx +19 -0
- package/src/components/language-switcher.test.tsx +53 -0
- package/src/components/language-switcher.tsx +175 -0
- package/src/components/model-picker.tsx +340 -0
- package/src/components/model-visibility-dialog.tsx +155 -0
- package/src/components/notifications.tsx +196 -0
- package/src/components/page-loader.tsx +34 -0
- package/src/components/pane-shell/context.ts +14 -0
- package/src/components/pane-shell/index.ts +4 -0
- package/src/components/pane-shell/pane-shell.test.tsx +333 -0
- package/src/components/pane-shell/pane-shell.tsx +330 -0
- package/src/components/prompt-overlays.tsx +234 -0
- package/src/components/session-picker.tsx +108 -0
- package/src/components/status-dot.tsx +26 -0
- package/src/components/ui/action-status.tsx +25 -0
- package/src/components/ui/alert.tsx +53 -0
- package/src/components/ui/badge.tsx +35 -0
- package/src/components/ui/braille-spinner.tsx +61 -0
- package/src/components/ui/button.tsx +81 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/codicon.tsx +20 -0
- package/src/components/ui/command.tsx +111 -0
- package/src/components/ui/confirm-dialog.tsx +109 -0
- package/src/components/ui/context-menu.tsx +141 -0
- package/src/components/ui/control.ts +25 -0
- package/src/components/ui/copy-button.test.tsx +36 -0
- package/src/components/ui/copy-button.tsx +229 -0
- package/src/components/ui/dialog.tsx +152 -0
- package/src/components/ui/disclosure-caret.tsx +20 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/error-state.tsx +50 -0
- package/src/components/ui/fade-text.tsx +110 -0
- package/src/components/ui/glyph-spinner.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +37 -0
- package/src/components/ui/loader.tsx +558 -0
- package/src/components/ui/log-view.tsx +17 -0
- package/src/components/ui/pagination.tsx +114 -0
- package/src/components/ui/popover.tsx +44 -0
- package/src/components/ui/scroll-area.tsx +43 -0
- package/src/components/ui/search-field.tsx +80 -0
- package/src/components/ui/segmented-control.tsx +51 -0
- package/src/components/ui/select.tsx +92 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +116 -0
- package/src/components/ui/sidebar.tsx +674 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/switch.tsx +49 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/components/ui/text-tab.tsx +43 -0
- package/src/components/ui/textarea.tsx +11 -0
- package/src/components/ui/tool-icon.tsx +65 -0
- package/src/components/ui/tooltip.tsx +69 -0
- package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/global.d.ts +457 -0
- package/src/hooks/use-image-download.ts +85 -0
- package/src/hooks/use-media-query.ts +24 -0
- package/src/hooks/use-mobile.ts +3 -0
- package/src/hooks/use-resize-observer.ts +38 -0
- package/src/hooks/use-worktree-info.ts +68 -0
- package/src/i18n/catalog.ts +12 -0
- package/src/i18n/context.test.tsx +232 -0
- package/src/i18n/context.tsx +183 -0
- package/src/i18n/define-locale.ts +41 -0
- package/src/i18n/en.ts +1779 -0
- package/src/i18n/index.ts +20 -0
- package/src/i18n/ja.ts +1890 -0
- package/src/i18n/languages.test.ts +43 -0
- package/src/i18n/languages.ts +86 -0
- package/src/i18n/runtime.test.ts +75 -0
- package/src/i18n/runtime.ts +53 -0
- package/src/i18n/types.ts +1452 -0
- package/src/i18n/zh-hant.ts +1849 -0
- package/src/i18n/zh.ts +1923 -0
- package/src/lib/ansi.test.ts +123 -0
- package/src/lib/ansi.ts +175 -0
- package/src/lib/chat-messages.test.ts +708 -0
- package/src/lib/chat-messages.ts +885 -0
- package/src/lib/chat-runtime.test.ts +18 -0
- package/src/lib/chat-runtime.ts +335 -0
- package/src/lib/clipboard.ts +28 -0
- package/src/lib/commit-changelog.test.ts +114 -0
- package/src/lib/commit-changelog.ts +177 -0
- package/src/lib/completion-sound.ts +519 -0
- package/src/lib/desktop-fs.test.ts +116 -0
- package/src/lib/desktop-fs.ts +113 -0
- package/src/lib/desktop-slash-commands.test.ts +126 -0
- package/src/lib/desktop-slash-commands.ts +286 -0
- package/src/lib/embedded-images.test.ts +35 -0
- package/src/lib/embedded-images.ts +60 -0
- package/src/lib/external-link.test.tsx +168 -0
- package/src/lib/external-link.tsx +303 -0
- package/src/lib/gateway-events.test.ts +27 -0
- package/src/lib/gateway-events.ts +49 -0
- package/src/lib/gateway-ws-url.test.ts +78 -0
- package/src/lib/gateway-ws-url.ts +91 -0
- package/src/lib/generated-images.test.ts +97 -0
- package/src/lib/generated-images.ts +116 -0
- package/src/lib/haptics.ts +129 -0
- package/src/lib/icons.ts +203 -0
- package/src/lib/incremental-external-store-runtime.ts +188 -0
- package/src/lib/katex-memo.ts +260 -0
- package/src/lib/keybinds/actions.ts +125 -0
- package/src/lib/keybinds/combo.test.ts +86 -0
- package/src/lib/keybinds/combo.ts +169 -0
- package/src/lib/local-preview.ts +126 -0
- package/src/lib/markdown-code.test.ts +23 -0
- package/src/lib/markdown-code.ts +195 -0
- package/src/lib/markdown-preprocess.ts +386 -0
- package/src/lib/media.remote.test.ts +58 -0
- package/src/lib/media.ts +111 -0
- package/src/lib/model-status-label.test.ts +31 -0
- package/src/lib/model-status-label.ts +103 -0
- package/src/lib/mutable-ref.ts +6 -0
- package/src/lib/preview-targets.test.ts +27 -0
- package/src/lib/preview-targets.ts +63 -0
- package/src/lib/profile-color.ts +58 -0
- package/src/lib/provider-setup-errors.test.ts +26 -0
- package/src/lib/provider-setup-errors.ts +12 -0
- package/src/lib/query-client.ts +13 -0
- package/src/lib/remend-tail.test.ts +105 -0
- package/src/lib/remend-tail.ts +108 -0
- package/src/lib/runtime-readiness.test.ts +65 -0
- package/src/lib/runtime-readiness.ts +147 -0
- package/src/lib/session-export.ts +57 -0
- package/src/lib/session-search.test.ts +58 -0
- package/src/lib/session-search.ts +19 -0
- package/src/lib/session-source.ts +62 -0
- package/src/lib/speech-text.ts +35 -0
- package/src/lib/statusbar.ts +91 -0
- package/src/lib/storage.test.ts +25 -0
- package/src/lib/storage.ts +107 -0
- package/src/lib/todos.test.ts +35 -0
- package/src/lib/todos.ts +51 -0
- package/src/lib/tool-result-summary.test.ts +106 -0
- package/src/lib/tool-result-summary.ts +467 -0
- package/src/lib/update-copy.test.ts +38 -0
- package/src/lib/update-copy.ts +44 -0
- package/src/lib/use-enter-animation.ts +100 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/voice-playback.ts +128 -0
- package/src/lib/yolo-session.ts +26 -0
- package/src/main.tsx +43 -0
- package/src/nastech.test.ts +49 -0
- package/src/nastech.ts +718 -0
- package/src/store/activity.ts +100 -0
- package/src/store/boot.ts +91 -0
- package/src/store/clarify.test.ts +81 -0
- package/src/store/clarify.ts +69 -0
- package/src/store/command-palette.ts +20 -0
- package/src/store/compaction.test.ts +53 -0
- package/src/store/compaction.ts +38 -0
- package/src/store/completion-sound.ts +32 -0
- package/src/store/composer-input-history.test.ts +147 -0
- package/src/store/composer-input-history.ts +158 -0
- package/src/store/composer-queue.test.ts +148 -0
- package/src/store/composer-queue.ts +239 -0
- package/src/store/composer-status.test.ts +99 -0
- package/src/store/composer-status.ts +277 -0
- package/src/store/composer.test.ts +106 -0
- package/src/store/composer.ts +184 -0
- package/src/store/cron.ts +19 -0
- package/src/store/gateway.ts +290 -0
- package/src/store/haptics.ts +17 -0
- package/src/store/keybinds.ts +139 -0
- package/src/store/layout.ts +176 -0
- package/src/store/model-presets.test.ts +51 -0
- package/src/store/model-presets.ts +86 -0
- package/src/store/model-visibility.test.ts +37 -0
- package/src/store/model-visibility.ts +108 -0
- package/src/store/native-notifications.test.ts +192 -0
- package/src/store/native-notifications.ts +203 -0
- package/src/store/notifications.ts +165 -0
- package/src/store/onboarding.test.ts +372 -0
- package/src/store/onboarding.ts +866 -0
- package/src/store/panes.test.ts +146 -0
- package/src/store/panes.ts +145 -0
- package/src/store/preview.test.ts +135 -0
- package/src/store/preview.ts +466 -0
- package/src/store/profile.test.ts +89 -0
- package/src/store/profile.ts +365 -0
- package/src/store/prompts.test.ts +121 -0
- package/src/store/prompts.ts +115 -0
- package/src/store/session-switcher.test.ts +115 -0
- package/src/store/session-switcher.ts +128 -0
- package/src/store/session-sync.ts +25 -0
- package/src/store/session.test.ts +131 -0
- package/src/store/session.ts +255 -0
- package/src/store/subagents.test.ts +111 -0
- package/src/store/subagents.ts +260 -0
- package/src/store/thread-scroll.ts +46 -0
- package/src/store/todos.test.ts +47 -0
- package/src/store/todos.ts +64 -0
- package/src/store/tool-diffs.ts +23 -0
- package/src/store/tool-dismiss.ts +45 -0
- package/src/store/tool-view.ts +91 -0
- package/src/store/translucency.ts +38 -0
- package/src/store/updates.test.ts +77 -0
- package/src/store/updates.ts +315 -0
- package/src/store/voice-playback.ts +24 -0
- package/src/store/windows.test.ts +143 -0
- package/src/store/windows.ts +77 -0
- package/src/styles.css +1235 -0
- package/src/themes/color.ts +142 -0
- package/src/themes/context.tsx +339 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/install.test.ts +119 -0
- package/src/themes/install.ts +95 -0
- package/src/themes/presets.test.ts +33 -0
- package/src/themes/presets.ts +293 -0
- package/src/themes/profile-theme.test.ts +41 -0
- package/src/themes/types.ts +66 -0
- package/src/themes/use-skin-command.ts +60 -0
- package/src/themes/user-themes.test.ts +63 -0
- package/src/themes/user-themes.ts +122 -0
- package/src/themes/vscode.test.ts +171 -0
- package/src/themes/vscode.ts +343 -0
- package/src/types/nastech.ts +646 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +56 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NasTechConnection,
|
|
3
|
+
NasTechReadDirResult,
|
|
4
|
+
NasTechReadFileTextResult,
|
|
5
|
+
NasTechSelectPathsOptions,
|
|
6
|
+
NasTechWorktreeInfo
|
|
7
|
+
} from '@/global'
|
|
8
|
+
import { $connection } from '@/store/session'
|
|
9
|
+
|
|
10
|
+
export interface DesktopFsRemotePicker {
|
|
11
|
+
selectPaths: (options?: NasTechSelectPathsOptions) => Promise<string[]>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let remotePicker: DesktopFsRemotePicker | null = null
|
|
15
|
+
|
|
16
|
+
export function setDesktopFsRemotePicker(next: DesktopFsRemotePicker | null) {
|
|
17
|
+
remotePicker = next
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function connectionCacheKey(connection: NasTechConnection | null) {
|
|
21
|
+
if (!connection) {
|
|
22
|
+
return 'local:'
|
|
23
|
+
}
|
|
24
|
+
return `${connection.mode || 'local'}:${connection.profile || ''}:${connection.baseUrl || ''}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function desktopFsCacheKey() {
|
|
28
|
+
return connectionCacheKey($connection.get())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isDesktopFsRemoteMode() {
|
|
32
|
+
return $connection.get()?.mode === 'remote'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fsPath(endpoint: string, filePath: string) {
|
|
36
|
+
return `/api/fs/${endpoint}?path=${encodeURIComponent(filePath)}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bridge() {
|
|
40
|
+
const desktop = window.NASTECHDesktop
|
|
41
|
+
if (!desktop) {
|
|
42
|
+
throw new Error('NasTech Desktop bridge is unavailable')
|
|
43
|
+
}
|
|
44
|
+
return desktop
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function readDesktopDir(path: string): Promise<NasTechReadDirResult> {
|
|
48
|
+
const desktop = bridge()
|
|
49
|
+
if (!isDesktopFsRemoteMode()) {
|
|
50
|
+
return desktop.readDir(path)
|
|
51
|
+
}
|
|
52
|
+
return desktop.api<NasTechReadDirResult>({ path: fsPath('list', path) })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function readDesktopFileText(path: string): Promise<NasTechReadFileTextResult> {
|
|
56
|
+
const desktop = bridge()
|
|
57
|
+
if (!isDesktopFsRemoteMode()) {
|
|
58
|
+
return desktop.readFileText(path)
|
|
59
|
+
}
|
|
60
|
+
return desktop.api<NasTechReadFileTextResult>({ path: fsPath('read-text', path) })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function readDesktopFileDataUrl(path: string): Promise<string> {
|
|
64
|
+
const desktop = bridge()
|
|
65
|
+
if (!isDesktopFsRemoteMode()) {
|
|
66
|
+
return desktop.readFileDataUrl(path)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await desktop.api<string | { dataUrl?: string }>({ path: fsPath('read-data-url', path) })
|
|
70
|
+
return typeof result === 'string' ? result : result.dataUrl || ''
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function desktopGitRoot(path: string): Promise<string | null> {
|
|
74
|
+
const desktop = bridge()
|
|
75
|
+
if (!isDesktopFsRemoteMode()) {
|
|
76
|
+
return desktop.gitRoot ? desktop.gitRoot(path) : null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = await desktop.api<{ root: string | null }>({ path: fsPath('git-root', path) })
|
|
80
|
+
return result.root
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Worktree detection runs against the LOCAL filesystem (the electron main
|
|
84
|
+
// process). For a remote backend the session cwds live on another machine, so
|
|
85
|
+
// we can't resolve them here — callers fall back to the path-name heuristic.
|
|
86
|
+
export async function desktopWorktrees(cwds: string[]): Promise<Record<string, NasTechWorktreeInfo | null>> {
|
|
87
|
+
if (isDesktopFsRemoteMode()) {
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const desktop = bridge()
|
|
92
|
+
|
|
93
|
+
return desktop.worktrees ? desktop.worktrees(cwds) : {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function desktopDefaultCwd(): Promise<{ branch: string; cwd: string } | null> {
|
|
97
|
+
if (!isDesktopFsRemoteMode()) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return bridge().api<{ branch: string; cwd: string }>({ path: '/api/fs/default-cwd' })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function selectDesktopPaths(options?: NasTechSelectPathsOptions): Promise<string[]> {
|
|
105
|
+
const desktop = bridge()
|
|
106
|
+
if (!isDesktopFsRemoteMode()) {
|
|
107
|
+
return desktop.selectPaths(options)
|
|
108
|
+
}
|
|
109
|
+
if (!options?.directories || options.multiple !== false) {
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
return remotePicker ? remotePicker.selectPaths(options) : []
|
|
113
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
desktopSkinSlashCompletions,
|
|
5
|
+
desktopSlashDescription,
|
|
6
|
+
desktopSlashUnavailableMessage,
|
|
7
|
+
filterDesktopCommandsCatalog,
|
|
8
|
+
isDesktopSlashCommand,
|
|
9
|
+
isDesktopSlashSuggestion,
|
|
10
|
+
isModelPickerCommand
|
|
11
|
+
} from './desktop-slash-commands'
|
|
12
|
+
|
|
13
|
+
describe('desktop slash command curation', () => {
|
|
14
|
+
it('keeps core desktop chat commands in suggestions', () => {
|
|
15
|
+
expect(isDesktopSlashSuggestion('/new')).toBe(true)
|
|
16
|
+
expect(isDesktopSlashSuggestion('/branch')).toBe(true)
|
|
17
|
+
expect(isDesktopSlashSuggestion('/skin')).toBe(true)
|
|
18
|
+
expect(isDesktopSlashSuggestion('/usage')).toBe(true)
|
|
19
|
+
expect(isDesktopSlashSuggestion('/version')).toBe(true)
|
|
20
|
+
expect(isDesktopSlashSuggestion('/yolo')).toBe(true)
|
|
21
|
+
expect(isDesktopSlashCommand('/yolo')).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('surfaces skill and quick commands (extensions) in suggestions and lets them run', () => {
|
|
25
|
+
expect(isDesktopSlashSuggestion('/my-skill')).toBe(true)
|
|
26
|
+
expect(isDesktopSlashSuggestion('/gif-search')).toBe(true)
|
|
27
|
+
expect(isDesktopSlashCommand('/my-skill')).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('hides terminal, messaging, and dedicated-UI commands from suggestions', () => {
|
|
31
|
+
expect(isDesktopSlashSuggestion('/clear')).toBe(false)
|
|
32
|
+
expect(isDesktopSlashSuggestion('/compact')).toBe(false)
|
|
33
|
+
expect(isDesktopSlashSuggestion('/redraw')).toBe(false)
|
|
34
|
+
expect(isDesktopSlashSuggestion('/approve')).toBe(false)
|
|
35
|
+
expect(isDesktopSlashSuggestion('/model')).toBe(false)
|
|
36
|
+
expect(isDesktopSlashSuggestion('/skills')).toBe(false)
|
|
37
|
+
expect(isDesktopSlashSuggestion('/voice')).toBe(false)
|
|
38
|
+
expect(isDesktopSlashSuggestion('/curator')).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('allows aliases to execute without cluttering the popover', () => {
|
|
42
|
+
expect(isDesktopSlashSuggestion('/reset')).toBe(false)
|
|
43
|
+
expect(isDesktopSlashCommand('/reset')).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('filters built-in catalog noise but keeps skill / quick-command extensions', () => {
|
|
47
|
+
const filtered = filterDesktopCommandsCatalog({
|
|
48
|
+
categories: [
|
|
49
|
+
{
|
|
50
|
+
name: 'Session',
|
|
51
|
+
pairs: [
|
|
52
|
+
['/new', 'Start a new session'],
|
|
53
|
+
['/clear', 'Clear terminal screen']
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'User commands',
|
|
58
|
+
pairs: [['/ship-it', 'Run release checklist']]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
pairs: [
|
|
62
|
+
['/new', 'Start a new session'],
|
|
63
|
+
['/model', 'Switch model'],
|
|
64
|
+
['/ship-it', 'Run release checklist']
|
|
65
|
+
],
|
|
66
|
+
skill_count: 2
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
expect(filtered.categories).toEqual([
|
|
70
|
+
{ name: 'Session', pairs: [['/new', 'Start a new desktop chat']] },
|
|
71
|
+
{ name: 'User commands', pairs: [['/ship-it', 'Run release checklist']] }
|
|
72
|
+
])
|
|
73
|
+
expect(filtered.pairs).toEqual([
|
|
74
|
+
['/new', 'Start a new desktop chat'],
|
|
75
|
+
['/ship-it', 'Run release checklist']
|
|
76
|
+
])
|
|
77
|
+
expect(filtered.skill_count).toBe(2)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('uses desktop-specific labels for commands with different UI behavior', () => {
|
|
81
|
+
expect(desktopSlashDescription('/branch', 'Branch the current session')).toBe(
|
|
82
|
+
'Branch the latest message into a new chat'
|
|
83
|
+
)
|
|
84
|
+
expect(desktopSlashDescription('/skin', 'Show or change the display skin/theme')).toBe(
|
|
85
|
+
'Switch desktop theme or cycle to the next one'
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('builds /skin completions from desktop themes', () => {
|
|
90
|
+
const completions = desktopSkinSlashCompletions(
|
|
91
|
+
[
|
|
92
|
+
{ name: 'mono', label: 'Mono', description: 'Clean grayscale' },
|
|
93
|
+
{ name: 'midnight', label: 'Midnight', description: 'Deep blue' },
|
|
94
|
+
{ name: 'slate', label: 'Slate', description: 'Cool slate blue' }
|
|
95
|
+
],
|
|
96
|
+
'mono',
|
|
97
|
+
'm'
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
expect(completions).toEqual([
|
|
101
|
+
{
|
|
102
|
+
text: '/skin mono',
|
|
103
|
+
display: '/skin mono',
|
|
104
|
+
meta: 'Mono (current) - Clean grayscale'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
text: '/skin midnight',
|
|
108
|
+
display: '/skin midnight',
|
|
109
|
+
meta: 'Midnight - Deep blue'
|
|
110
|
+
}
|
|
111
|
+
])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('explains known commands that desktop owns elsewhere', () => {
|
|
115
|
+
expect(desktopSlashUnavailableMessage('/model sonnet')).toContain('model picker')
|
|
116
|
+
expect(desktopSlashUnavailableMessage('/skills')).toContain('desktop sidebar')
|
|
117
|
+
expect(desktopSlashUnavailableMessage('/clear')).toContain('terminal interface')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('flags /model as a picker-owned command so the desktop opens the overlay', () => {
|
|
121
|
+
expect(isModelPickerCommand('/model')).toBe(true)
|
|
122
|
+
expect(isModelPickerCommand('/model sonnet')).toBe(true)
|
|
123
|
+
expect(isModelPickerCommand('/new')).toBe(false)
|
|
124
|
+
expect(isModelPickerCommand('/skills')).toBe(false)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
export interface CommandsCatalogSection {
|
|
2
|
+
name: string
|
|
3
|
+
pairs: [string, string][]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CommandsCatalogLike {
|
|
7
|
+
categories?: CommandsCatalogSection[]
|
|
8
|
+
pairs?: [string, string][]
|
|
9
|
+
skill_count?: number
|
|
10
|
+
warning?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DesktopSlashCompletion {
|
|
14
|
+
display: string
|
|
15
|
+
meta: string
|
|
16
|
+
text: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DesktopThemeCommandOption {
|
|
20
|
+
description: string
|
|
21
|
+
label: string
|
|
22
|
+
name: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DESKTOP_COMMAND_META = [
|
|
26
|
+
['/agents', 'Show active desktop sessions and running tasks'],
|
|
27
|
+
['/background', 'Run a prompt in the background'],
|
|
28
|
+
['/branch', 'Branch the latest message into a new chat'],
|
|
29
|
+
['/compress', 'Compress this conversation context'],
|
|
30
|
+
['/debug', 'Create a debug report'],
|
|
31
|
+
['/goal', 'Manage the standing goal for this session'],
|
|
32
|
+
['/help', 'Show desktop slash commands'],
|
|
33
|
+
['/new', 'Start a new desktop chat'],
|
|
34
|
+
['/profile', 'Switch the active NasTech profile'],
|
|
35
|
+
['/queue', 'Queue a prompt for the next turn'],
|
|
36
|
+
['/resume', 'Resume a saved session'],
|
|
37
|
+
['/retry', 'Retry the last user message'],
|
|
38
|
+
['/rollback', 'List or restore filesystem checkpoints'],
|
|
39
|
+
['/skin', 'Switch desktop theme or cycle to the next one'],
|
|
40
|
+
['/status', 'Show current session status'],
|
|
41
|
+
['/steer', 'Steer the current run after the next tool call'],
|
|
42
|
+
['/stop', 'Stop running background processes'],
|
|
43
|
+
['/title', 'Rename the current session'],
|
|
44
|
+
['/undo', 'Remove the last user/assistant exchange'],
|
|
45
|
+
['/usage', 'Show token usage for this session'],
|
|
46
|
+
['/version', 'Show NasTech Agent version'],
|
|
47
|
+
['/yolo', 'Toggle YOLO — auto-approve dangerous commands']
|
|
48
|
+
] as const
|
|
49
|
+
|
|
50
|
+
const DESKTOP_COMMANDS: ReadonlySet<string> = new Set(DESKTOP_COMMAND_META.map(([command]) => command))
|
|
51
|
+
|
|
52
|
+
const DESKTOP_ALIASES = new Map([
|
|
53
|
+
['/bg', '/background'],
|
|
54
|
+
['/btw', '/background'],
|
|
55
|
+
['/fork', '/branch'],
|
|
56
|
+
['/q', '/queue'],
|
|
57
|
+
['/reload_mcp', '/reload-mcp'],
|
|
58
|
+
['/reload_skills', '/reload-skills'],
|
|
59
|
+
['/reset', '/new'],
|
|
60
|
+
['/tasks', '/agents']
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const DESKTOP_COMMAND_DESCRIPTIONS: ReadonlyMap<string, string> = new Map(DESKTOP_COMMAND_META)
|
|
64
|
+
|
|
65
|
+
const PICKER_OWNED_COMMANDS = new Set(['/model'])
|
|
66
|
+
|
|
67
|
+
const TERMINAL_ONLY_COMMANDS = new Set([
|
|
68
|
+
'/browser',
|
|
69
|
+
'/busy',
|
|
70
|
+
'/clear',
|
|
71
|
+
'/commands',
|
|
72
|
+
'/compact',
|
|
73
|
+
'/config',
|
|
74
|
+
'/copy',
|
|
75
|
+
'/cron',
|
|
76
|
+
'/details',
|
|
77
|
+
'/exit',
|
|
78
|
+
'/footer',
|
|
79
|
+
'/gateway',
|
|
80
|
+
'/gquota',
|
|
81
|
+
'/history',
|
|
82
|
+
'/image',
|
|
83
|
+
'/indicator',
|
|
84
|
+
'/logs',
|
|
85
|
+
'/mouse',
|
|
86
|
+
'/paste',
|
|
87
|
+
'/platforms',
|
|
88
|
+
'/plugins',
|
|
89
|
+
'/quit',
|
|
90
|
+
'/redraw',
|
|
91
|
+
'/reload',
|
|
92
|
+
'/restart',
|
|
93
|
+
'/save',
|
|
94
|
+
'/sb',
|
|
95
|
+
'/set-home',
|
|
96
|
+
'/sethome',
|
|
97
|
+
'/snap',
|
|
98
|
+
'/snapshot',
|
|
99
|
+
'/statusbar',
|
|
100
|
+
'/toolsets',
|
|
101
|
+
'/tools',
|
|
102
|
+
'/update',
|
|
103
|
+
'/verbose'
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
const MESSAGING_ONLY_COMMANDS = new Set(['/approve', '/deny'])
|
|
107
|
+
|
|
108
|
+
const SETTINGS_OWNED_COMMANDS = new Set(['/skills'])
|
|
109
|
+
|
|
110
|
+
const ADVANCED_COMMANDS = new Set([
|
|
111
|
+
'/curator',
|
|
112
|
+
'/fast',
|
|
113
|
+
'/insights',
|
|
114
|
+
'/kanban',
|
|
115
|
+
'/personality',
|
|
116
|
+
'/reasoning',
|
|
117
|
+
'/reload-mcp',
|
|
118
|
+
'/reload-skills',
|
|
119
|
+
'/voice'
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
const BLOCKED_COMMANDS = new Set([
|
|
123
|
+
...PICKER_OWNED_COMMANDS,
|
|
124
|
+
...TERMINAL_ONLY_COMMANDS,
|
|
125
|
+
...MESSAGING_ONLY_COMMANDS,
|
|
126
|
+
...SETTINGS_OWNED_COMMANDS,
|
|
127
|
+
...ADVANCED_COMMANDS
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
function normalizeCommand(command: string): string {
|
|
131
|
+
const trimmed = command.trim()
|
|
132
|
+
const base = (trimmed.startsWith('/') ? trimmed : `/${trimmed}`).split(/\s+/, 1)[0]?.toLowerCase() || ''
|
|
133
|
+
|
|
134
|
+
return base
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function canonicalDesktopSlashCommand(command: string): string {
|
|
138
|
+
const normalized = normalizeCommand(command)
|
|
139
|
+
|
|
140
|
+
return DESKTOP_ALIASES.get(normalized) || normalized
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function isDesktopSlashCommand(command: string): boolean {
|
|
144
|
+
const normalized = normalizeCommand(command)
|
|
145
|
+
const canonical = canonicalDesktopSlashCommand(normalized)
|
|
146
|
+
|
|
147
|
+
if (BLOCKED_COMMANDS.has(normalized) || BLOCKED_COMMANDS.has(canonical)) {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return DESKTOP_COMMANDS.has(canonical) || !isKnownNasTechSlashCommand(normalized)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* An "extension" command is anything the backend surfaces that is NOT one of
|
|
156
|
+
* NasTech' built-in slash commands — i.e. skill commands (`/gif-search`,
|
|
157
|
+
* `/codex`, …) and user-defined quick commands. These are user-activated, so
|
|
158
|
+
* they should appear in the desktop slash palette even though they aren't in
|
|
159
|
+
* the curated `DESKTOP_COMMANDS` allow-list. This mirrors the predicate in
|
|
160
|
+
* `isDesktopSlashCommand` that already lets them EXECUTE when typed.
|
|
161
|
+
*/
|
|
162
|
+
export function isDesktopSlashExtensionCommand(command: string): boolean {
|
|
163
|
+
const normalized = normalizeCommand(command)
|
|
164
|
+
|
|
165
|
+
if (!normalized || normalized === '/') {
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return !isKnownNasTechSlashCommand(normalized)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function isDesktopSlashSuggestion(command: string): boolean {
|
|
173
|
+
const normalized = normalizeCommand(command)
|
|
174
|
+
const canonical = canonicalDesktopSlashCommand(normalized)
|
|
175
|
+
|
|
176
|
+
// Surface skill / quick commands (extensions the backend provides) alongside
|
|
177
|
+
// the curated built-ins. Built-in aliases stay hidden so the popover isn't
|
|
178
|
+
// cluttered with duplicates.
|
|
179
|
+
if (isDesktopSlashExtensionCommand(normalized)) {
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return DESKTOP_COMMANDS.has(canonical) && !DESKTOP_ALIASES.has(normalized)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* True for commands the desktop fulfils by opening the model picker overlay
|
|
188
|
+
* (e.g. `/model`) rather than executing a slash command. The caller opens the
|
|
189
|
+
* picker UI instead of printing the "uses the desktop model picker" notice.
|
|
190
|
+
*/
|
|
191
|
+
export function isModelPickerCommand(command: string): boolean {
|
|
192
|
+
const normalized = normalizeCommand(command)
|
|
193
|
+
const canonical = canonicalDesktopSlashCommand(normalized)
|
|
194
|
+
|
|
195
|
+
return PICKER_OWNED_COMMANDS.has(canonical)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function desktopSlashUnavailableMessage(command: string): string | null {
|
|
199
|
+
const normalized = normalizeCommand(command)
|
|
200
|
+
const canonical = canonicalDesktopSlashCommand(normalized)
|
|
201
|
+
|
|
202
|
+
if (PICKER_OWNED_COMMANDS.has(canonical)) {
|
|
203
|
+
return `/${canonical.slice(1)} uses the desktop model picker instead of a slash command.`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (SETTINGS_OWNED_COMMANDS.has(canonical)) {
|
|
207
|
+
return `/${canonical.slice(1)} is managed from the desktop sidebar.`
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (MESSAGING_ONLY_COMMANDS.has(canonical)) {
|
|
211
|
+
return `/${canonical.slice(1)} is only used from messaging platforms.`
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (ADVANCED_COMMANDS.has(canonical)) {
|
|
215
|
+
return `/${canonical.slice(1)} is not shown in the desktop slash palette. Use the relevant desktop control or terminal interface instead.`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (TERMINAL_ONLY_COMMANDS.has(normalized) || TERMINAL_ONLY_COMMANDS.has(canonical)) {
|
|
219
|
+
return `/${canonical.slice(1)} is only available in the terminal interface.`
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return null
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function desktopSlashDescription(command: string, fallback = ''): string {
|
|
226
|
+
const canonical = canonicalDesktopSlashCommand(command)
|
|
227
|
+
|
|
228
|
+
return DESKTOP_COMMAND_DESCRIPTIONS.get(canonical) || fallback
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function desktopSkinSlashCompletions(
|
|
232
|
+
themes: DesktopThemeCommandOption[],
|
|
233
|
+
activeThemeName: string,
|
|
234
|
+
argPrefix: string
|
|
235
|
+
): DesktopSlashCompletion[] {
|
|
236
|
+
const prefix = argPrefix.trim().toLowerCase()
|
|
237
|
+
|
|
238
|
+
const commands: DesktopSlashCompletion[] = [
|
|
239
|
+
{
|
|
240
|
+
text: '/skin list',
|
|
241
|
+
display: '/skin list',
|
|
242
|
+
meta: 'Show available desktop themes'
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
text: '/skin next',
|
|
246
|
+
display: '/skin next',
|
|
247
|
+
meta: 'Cycle to the next desktop theme'
|
|
248
|
+
},
|
|
249
|
+
...themes.map(theme => ({
|
|
250
|
+
text: `/skin ${theme.name}`,
|
|
251
|
+
display: `/skin ${theme.name}`,
|
|
252
|
+
meta: `${theme.label}${theme.name === activeThemeName ? ' (current)' : ''} - ${theme.description}`
|
|
253
|
+
}))
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
if (!prefix) {
|
|
257
|
+
return commands
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return commands.filter(item => item.text.slice('/skin '.length).toLowerCase().startsWith(prefix))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function filterDesktopCommandsCatalog(catalog: CommandsCatalogLike): CommandsCatalogLike {
|
|
264
|
+
const categories = catalog.categories
|
|
265
|
+
?.map(section => ({
|
|
266
|
+
...section,
|
|
267
|
+
pairs: section.pairs
|
|
268
|
+
.filter(([command]) => isDesktopSlashSuggestion(command))
|
|
269
|
+
.map(([command, description]) => [command, desktopSlashDescription(command, description)] as [string, string])
|
|
270
|
+
}))
|
|
271
|
+
.filter(section => section.pairs.length > 0)
|
|
272
|
+
|
|
273
|
+
const pairs = catalog.pairs
|
|
274
|
+
?.filter(([command]) => isDesktopSlashSuggestion(command))
|
|
275
|
+
.map(([command, description]) => [command, desktopSlashDescription(command, description)] as [string, string])
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
...catalog,
|
|
279
|
+
...(categories ? { categories } : {}),
|
|
280
|
+
...(pairs ? { pairs } : {})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function isKnownNasTechSlashCommand(command: string): boolean {
|
|
285
|
+
return DESKTOP_COMMANDS.has(command) || DESKTOP_ALIASES.has(command) || BLOCKED_COMMANDS.has(command)
|
|
286
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { extractEmbeddedImages } from './embedded-images'
|
|
4
|
+
|
|
5
|
+
const SAMPLE_PNG_DATA_URL = 'data:image/png;base64,' + 'A'.repeat(120)
|
|
6
|
+
|
|
7
|
+
describe('extractEmbeddedImages', () => {
|
|
8
|
+
it('returns text untouched when no data URL is present', () => {
|
|
9
|
+
expect(extractEmbeddedImages('describe this')).toEqual({ cleanedText: 'describe this', images: [] })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('lifts a bare data:image URL out of prose', () => {
|
|
13
|
+
const result = extractEmbeddedImages(`describe this ${SAMPLE_PNG_DATA_URL}`)
|
|
14
|
+
|
|
15
|
+
expect(result.cleanedText).toBe('describe this')
|
|
16
|
+
expect(result.images).toEqual([SAMPLE_PNG_DATA_URL])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('lifts a JSON-wrapped image_url envelope out of prose', () => {
|
|
20
|
+
const result = extractEmbeddedImages(
|
|
21
|
+
`describe this{"type":"image_url","image_url":{"url":"${SAMPLE_PNG_DATA_URL}"}}`
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
expect(result.cleanedText).toBe('describe this')
|
|
25
|
+
expect(result.images).toEqual([SAMPLE_PNG_DATA_URL])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('extracts multiple embedded images', () => {
|
|
29
|
+
const second = 'data:image/jpeg;base64,' + 'B'.repeat(96)
|
|
30
|
+
const result = extractEmbeddedImages(`first ${SAMPLE_PNG_DATA_URL} mid ${second} tail`)
|
|
31
|
+
|
|
32
|
+
expect(result.cleanedText).toBe('first mid tail')
|
|
33
|
+
expect(result.images).toEqual([SAMPLE_PNG_DATA_URL, second])
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const EMBEDDED_IMAGE_RE =
|
|
2
|
+
/(\{\s*"type"\s*:\s*"image_url"\s*,\s*"image_url"\s*:\s*\{\s*"url"\s*:\s*")?(data:image\/[\w.+-]+;base64,[A-Za-z0-9+/=]{64,})("\s*\}\s*\})?/g
|
|
3
|
+
|
|
4
|
+
const DATA_URL_RE = /^data:([\w./+-]+);base64,(.*)$/i
|
|
5
|
+
|
|
6
|
+
export const DATA_IMAGE_URL_RE = /^data:image\/[\w.+-]+;base64,/i
|
|
7
|
+
|
|
8
|
+
export interface EmbeddedImageExtraction {
|
|
9
|
+
cleanedText: string
|
|
10
|
+
images: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dataUrlToBlob(dataUrl: string): Blob | null {
|
|
14
|
+
const match = DATA_URL_RE.exec(dataUrl.trim())
|
|
15
|
+
|
|
16
|
+
if (!match) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const bytes = atob(match[2])
|
|
22
|
+
const buffer = new Uint8Array(bytes.length)
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
25
|
+
buffer[i] = bytes.charCodeAt(i)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new Blob([buffer], { type: match[1] })
|
|
29
|
+
} catch {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function extractEmbeddedImages(text: string): EmbeddedImageExtraction {
|
|
35
|
+
if (!text || !text.includes('data:image/')) {
|
|
36
|
+
return { cleanedText: text, images: [] }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const images: string[] = []
|
|
40
|
+
|
|
41
|
+
const cleanedText = text
|
|
42
|
+
.replace(EMBEDDED_IMAGE_RE, (_match, _open, dataUrl: string) => {
|
|
43
|
+
images.push(dataUrl)
|
|
44
|
+
|
|
45
|
+
return ''
|
|
46
|
+
})
|
|
47
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
48
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
49
|
+
.trim()
|
|
50
|
+
|
|
51
|
+
return { cleanedText, images }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function embeddedImageUrls(text: string): string[] {
|
|
55
|
+
return extractEmbeddedImages(text).images
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function textWithoutEmbeddedImages(text: string): string {
|
|
59
|
+
return extractEmbeddedImages(text).cleanedText
|
|
60
|
+
}
|