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,331 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VS Code Marketplace color-theme fetcher (main process).
|
|
5
|
+
*
|
|
6
|
+
* Resolves an extension's latest version via the (undocumented but stable)
|
|
7
|
+
* gallery ExtensionQuery API, downloads the `.vsix` (a zip), and extracts the
|
|
8
|
+
* color-theme JSON files it contributes. No theme code is ever executed — we
|
|
9
|
+
* only read `package.json` + the referenced `*.json` theme files out of the
|
|
10
|
+
* archive and hand their text back to the renderer to convert.
|
|
11
|
+
*
|
|
12
|
+
* Dependency-free on purpose: a `.vsix` is a plain zip, so we parse the central
|
|
13
|
+
* directory and inflate just the entries we need with `zlib`. Avoids pulling a
|
|
14
|
+
* zip library into the desktop bundle for a feature this small.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const https = require('node:https')
|
|
18
|
+
const zlib = require('node:zlib')
|
|
19
|
+
|
|
20
|
+
const GALLERY_QUERY_URL = 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery'
|
|
21
|
+
const VSIX_ASSET_TYPE = 'Microsoft.VisualStudio.Services.VSIXPackage'
|
|
22
|
+
const MAX_VSIX_BYTES = 40 * 1024 * 1024 // 40 MB — themes are tiny; this is paranoia.
|
|
23
|
+
const MAX_REDIRECTS = 5
|
|
24
|
+
const REQUEST_TIMEOUT_MS = 20_000
|
|
25
|
+
|
|
26
|
+
const ID_RE = /^[\w-]+\.[\w-]+$/
|
|
27
|
+
|
|
28
|
+
/** Minimal HTTPS helper with redirect-following, timeout, and a size cap. */
|
|
29
|
+
function request(url, { method = 'GET', headers = {}, body = null, maxBytes = MAX_VSIX_BYTES } = {}, redirectsLeft = MAX_REDIRECTS) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const req = https.request(url, { method, headers }, res => {
|
|
32
|
+
const status = res.statusCode ?? 0
|
|
33
|
+
|
|
34
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
35
|
+
if (redirectsLeft <= 0) {
|
|
36
|
+
res.resume()
|
|
37
|
+
reject(new Error('Too many redirects.'))
|
|
38
|
+
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const next = new URL(res.headers.location, url).toString()
|
|
43
|
+
res.resume()
|
|
44
|
+
// Redirects to the CDN are plain GETs (drop the POST body).
|
|
45
|
+
resolve(request(next, { method: 'GET', headers: { 'User-Agent': headers['User-Agent'] }, maxBytes }, redirectsLeft - 1))
|
|
46
|
+
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (status < 200 || status >= 300) {
|
|
51
|
+
res.resume()
|
|
52
|
+
reject(new Error(`Request failed (${status}) for ${url}`))
|
|
53
|
+
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const chunks = []
|
|
58
|
+
let total = 0
|
|
59
|
+
|
|
60
|
+
res.on('data', chunk => {
|
|
61
|
+
total += chunk.length
|
|
62
|
+
|
|
63
|
+
if (total > maxBytes) {
|
|
64
|
+
req.destroy()
|
|
65
|
+
reject(new Error('Response exceeded the size limit.'))
|
|
66
|
+
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
chunks.push(chunk)
|
|
71
|
+
})
|
|
72
|
+
res.on('end', () => resolve(Buffer.concat(chunks)))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
req.on('error', reject)
|
|
76
|
+
req.setTimeout(REQUEST_TIMEOUT_MS, () => req.destroy(new Error('Request timed out.')))
|
|
77
|
+
|
|
78
|
+
if (body) {
|
|
79
|
+
req.write(body)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
req.end()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Resolve `{ displayName, vsixUrl }` for the latest version of `id`. */
|
|
87
|
+
async function resolveExtension(id) {
|
|
88
|
+
const json = await queryGallery({
|
|
89
|
+
// FilterType 7 = ExtensionName (the full publisher.extension id).
|
|
90
|
+
filters: [{ criteria: [{ filterType: 7, value: id }], pageNumber: 1, pageSize: 1 }],
|
|
91
|
+
// Flags: IncludeFiles | IncludeVersionProperties | IncludeAssetUri |
|
|
92
|
+
// IncludeCategoryAndTags | IncludeLatestVersionOnly = 914.
|
|
93
|
+
flags: 914
|
|
94
|
+
})
|
|
95
|
+
const extension = json?.results?.[0]?.extensions?.[0]
|
|
96
|
+
|
|
97
|
+
if (!extension) {
|
|
98
|
+
throw new Error(`Extension "${id}" was not found on the Marketplace.`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const version = extension.versions?.[0]
|
|
102
|
+
|
|
103
|
+
if (!version) {
|
|
104
|
+
throw new Error(`Extension "${id}" has no published versions.`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const asset = (version.files ?? []).find(file => file.assetType === VSIX_ASSET_TYPE)
|
|
108
|
+
const vsixUrl = asset?.source
|
|
109
|
+
|
|
110
|
+
if (!vsixUrl) {
|
|
111
|
+
throw new Error(`Could not find a downloadable package for "${id}".`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { displayName: extension.displayName || id, vsixUrl }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** POST an ExtensionQuery payload and return the parsed gallery response. */
|
|
118
|
+
async function queryGallery(payload, { maxBytes = 4 * 1024 * 1024 } = {}) {
|
|
119
|
+
const body = JSON.stringify(payload)
|
|
120
|
+
const raw = await request(GALLERY_QUERY_URL, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
Accept: 'application/json;api-version=3.0-preview.1',
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
'Content-Length': Buffer.byteLength(body),
|
|
126
|
+
'User-Agent': 'NasTech-Desktop'
|
|
127
|
+
},
|
|
128
|
+
body,
|
|
129
|
+
maxBytes
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return JSON.parse(raw.toString('utf8'))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Search the Marketplace for color-theme extensions. With an empty query this
|
|
137
|
+
* returns the most-installed themes; with a query it's a full-text search
|
|
138
|
+
* scoped to the Themes category. Returns lightweight cards (no download).
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* The "Themes" category also contains file-icon and product-icon themes (the
|
|
142
|
+
* gallery has no color-only category). We can't see an extension's actual
|
|
143
|
+
* contributions without downloading it, so filter the obvious icon packs out by
|
|
144
|
+
* tag + name/description. Color themes that also ship icons are rare; worst case
|
|
145
|
+
* a user installs them by exact id from settings.
|
|
146
|
+
*/
|
|
147
|
+
function looksLikeIconTheme(extension) {
|
|
148
|
+
const tags = (extension.tags ?? []).map(tag => String(tag).toLowerCase())
|
|
149
|
+
|
|
150
|
+
if (tags.includes('icon-theme') || tags.includes('product-icon-theme')) {
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const text = `${extension.displayName ?? ''} ${extension.shortDescription ?? ''}`.toLowerCase()
|
|
155
|
+
|
|
156
|
+
return /\b(icon theme|file icons?|product icons?|icon pack|fileicons)\b/.test(text)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function searchMarketplaceThemes(query, limit = 20) {
|
|
160
|
+
const text = String(query || '').trim()
|
|
161
|
+
const pageSize = Math.min(Math.max(Number(limit) || 20, 1), 50)
|
|
162
|
+
|
|
163
|
+
// FilterType: 8=Target, 5=Category, 10=SearchText, 12=ExcludeWithFlags.
|
|
164
|
+
const criteria = [
|
|
165
|
+
{ filterType: 8, value: 'Microsoft.VisualStudio.Code' },
|
|
166
|
+
{ filterType: 5, value: 'Themes' },
|
|
167
|
+
{ filterType: 12, value: '4096' } // Exclude unpublished (Unpublished = 0x1000).
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
if (text) {
|
|
171
|
+
criteria.push({ filterType: 10, value: text })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const json = await queryGallery({
|
|
175
|
+
// Over-fetch so the icon-theme filter below still leaves a full page.
|
|
176
|
+
filters: [{ criteria, pageNumber: 1, pageSize: Math.min(pageSize * 2, 50), sortBy: 4, sortOrder: 0 }],
|
|
177
|
+
// IncludeStatistics (0x100) | IncludeLatestVersionOnly (0x200) | IncludeCategoryAndTags (0x4).
|
|
178
|
+
flags: 772
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const extensions = json?.results?.[0]?.extensions ?? []
|
|
182
|
+
|
|
183
|
+
return extensions
|
|
184
|
+
.filter(extension => !looksLikeIconTheme(extension))
|
|
185
|
+
.slice(0, pageSize)
|
|
186
|
+
.map(extension => {
|
|
187
|
+
const publisherName = extension.publisher?.publisherName ?? ''
|
|
188
|
+
const installStat = (extension.statistics ?? []).find(stat => stat.statisticName === 'install')
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
extensionId: `${publisherName}.${extension.extensionName}`,
|
|
192
|
+
displayName: extension.displayName || extension.extensionName,
|
|
193
|
+
publisher: extension.publisher?.displayName || publisherName,
|
|
194
|
+
description: extension.shortDescription || '',
|
|
195
|
+
installs: Math.round(installStat?.value ?? 0)
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Minimal zip reader ─────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
function findEndOfCentralDirectory(buf) {
|
|
203
|
+
// EOCD signature 0x06054b50, scanning back from the end (comment is rare).
|
|
204
|
+
for (let i = buf.length - 22; i >= 0; i--) {
|
|
205
|
+
if (buf.readUInt32LE(i) === 0x06054b50) {
|
|
206
|
+
return i
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
throw new Error('Not a valid zip archive (no end-of-central-directory).')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Parse the central directory into a name → record map. */
|
|
214
|
+
function readCentralDirectory(buf) {
|
|
215
|
+
const eocd = findEndOfCentralDirectory(buf)
|
|
216
|
+
const count = buf.readUInt16LE(eocd + 10)
|
|
217
|
+
let offset = buf.readUInt32LE(eocd + 16)
|
|
218
|
+
const records = new Map()
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < count; i++) {
|
|
221
|
+
if (buf.readUInt32LE(offset) !== 0x02014b50) {
|
|
222
|
+
break
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const method = buf.readUInt16LE(offset + 10)
|
|
226
|
+
const compressedSize = buf.readUInt32LE(offset + 20)
|
|
227
|
+
const nameLen = buf.readUInt16LE(offset + 28)
|
|
228
|
+
const extraLen = buf.readUInt16LE(offset + 30)
|
|
229
|
+
const commentLen = buf.readUInt16LE(offset + 32)
|
|
230
|
+
const localOffset = buf.readUInt32LE(offset + 42)
|
|
231
|
+
const name = buf.toString('utf8', offset + 46, offset + 46 + nameLen)
|
|
232
|
+
|
|
233
|
+
records.set(name, { method, compressedSize, localOffset })
|
|
234
|
+
offset += 46 + nameLen + extraLen + commentLen
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return records
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Inflate a single entry to a string. */
|
|
241
|
+
function extractEntry(buf, record) {
|
|
242
|
+
// The local header's name/extra lengths can differ from the central record,
|
|
243
|
+
// so re-read them here to locate the compressed payload.
|
|
244
|
+
if (buf.readUInt32LE(record.localOffset) !== 0x04034b50) {
|
|
245
|
+
throw new Error('Corrupt zip: bad local file header.')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const nameLen = buf.readUInt16LE(record.localOffset + 26)
|
|
249
|
+
const extraLen = buf.readUInt16LE(record.localOffset + 28)
|
|
250
|
+
const dataStart = record.localOffset + 30 + nameLen + extraLen
|
|
251
|
+
const data = buf.subarray(dataStart, dataStart + record.compressedSize)
|
|
252
|
+
|
|
253
|
+
// 0 = stored, 8 = deflate. Theme files are one or the other.
|
|
254
|
+
return record.method === 0 ? data.toString('utf8') : zlib.inflateRawSync(data).toString('utf8')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Normalize a package.json theme path to its zip entry name. */
|
|
258
|
+
function themeEntryName(themePath) {
|
|
259
|
+
const clean = String(themePath).replace(/^\.\//, '').replace(/^\//, '')
|
|
260
|
+
|
|
261
|
+
return `extension/${clean}`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Extract every contributed color theme from a `.vsix` buffer. */
|
|
265
|
+
function extractThemes(vsixBuffer) {
|
|
266
|
+
const records = readCentralDirectory(vsixBuffer)
|
|
267
|
+
const pkgRecord = records.get('extension/package.json')
|
|
268
|
+
|
|
269
|
+
if (!pkgRecord) {
|
|
270
|
+
throw new Error('Package manifest missing from the extension.')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const pkg = JSON.parse(extractEntry(vsixBuffer, pkgRecord))
|
|
274
|
+
const contributed = pkg?.contributes?.themes
|
|
275
|
+
|
|
276
|
+
if (!Array.isArray(contributed) || contributed.length === 0) {
|
|
277
|
+
return []
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const themes = []
|
|
281
|
+
|
|
282
|
+
for (const entry of contributed) {
|
|
283
|
+
if (!entry?.path) {
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const record = records.get(themeEntryName(entry.path))
|
|
288
|
+
|
|
289
|
+
if (!record) {
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
themes.push({
|
|
295
|
+
label: entry.label || entry.id || pkg.displayName || pkg.name || 'VS Code Theme',
|
|
296
|
+
uiTheme: entry.uiTheme,
|
|
297
|
+
contents: extractEntry(vsixBuffer, record)
|
|
298
|
+
})
|
|
299
|
+
} catch {
|
|
300
|
+
// Skip an entry we can't inflate rather than failing the whole install.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return themes
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Public entry: resolve, download, and extract color themes for `id`
|
|
309
|
+
* (`publisher.extension`). Returns `{ extensionId, displayName, themes }`.
|
|
310
|
+
*/
|
|
311
|
+
async function fetchMarketplaceThemes(id) {
|
|
312
|
+
const trimmed = String(id || '').trim()
|
|
313
|
+
|
|
314
|
+
if (!ID_RE.test(trimmed)) {
|
|
315
|
+
throw new Error('Expected a Marketplace id like "publisher.extension".')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const { displayName, vsixUrl } = await resolveExtension(trimmed)
|
|
319
|
+
const vsix = await request(vsixUrl, { headers: { 'User-Agent': 'NasTech-Desktop' } })
|
|
320
|
+
const themes = extractThemes(vsix)
|
|
321
|
+
|
|
322
|
+
return { extensionId: trimmed, displayName, themes }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
fetchMarketplaceThemes,
|
|
327
|
+
searchMarketplaceThemes,
|
|
328
|
+
extractThemes,
|
|
329
|
+
readCentralDirectory,
|
|
330
|
+
__testing: { themeEntryName, looksLikeIconTheme }
|
|
331
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
|
|
6
|
+
const { __testing, extractThemes, readCentralDirectory } = require('./vscode-marketplace.cjs')
|
|
7
|
+
|
|
8
|
+
// Build a minimal zip with stored (uncompressed) entries so the test controls
|
|
9
|
+
// the bytes exactly — exercises the central-directory reader + theme extraction
|
|
10
|
+
// without a deflate dependency.
|
|
11
|
+
function makeZip(entries) {
|
|
12
|
+
const locals = []
|
|
13
|
+
const centrals = []
|
|
14
|
+
let offset = 0
|
|
15
|
+
|
|
16
|
+
for (const { name, data } of entries) {
|
|
17
|
+
const nameBuf = Buffer.from(name, 'utf8')
|
|
18
|
+
const body = Buffer.from(data, 'utf8')
|
|
19
|
+
|
|
20
|
+
const local = Buffer.alloc(30 + nameBuf.length)
|
|
21
|
+
local.writeUInt32LE(0x04034b50, 0)
|
|
22
|
+
local.writeUInt16LE(0, 8) // method: stored
|
|
23
|
+
local.writeUInt32LE(body.length, 18) // compressed size
|
|
24
|
+
local.writeUInt32LE(body.length, 22) // uncompressed size
|
|
25
|
+
local.writeUInt16LE(nameBuf.length, 26)
|
|
26
|
+
nameBuf.copy(local, 30)
|
|
27
|
+
|
|
28
|
+
locals.push(local, body)
|
|
29
|
+
|
|
30
|
+
const central = Buffer.alloc(46 + nameBuf.length)
|
|
31
|
+
central.writeUInt32LE(0x02014b50, 0)
|
|
32
|
+
central.writeUInt16LE(0, 10) // method: stored
|
|
33
|
+
central.writeUInt32LE(body.length, 20)
|
|
34
|
+
central.writeUInt32LE(body.length, 24)
|
|
35
|
+
central.writeUInt16LE(nameBuf.length, 28)
|
|
36
|
+
central.writeUInt32LE(offset, 42) // local header offset
|
|
37
|
+
nameBuf.copy(central, 46)
|
|
38
|
+
|
|
39
|
+
centrals.push(central)
|
|
40
|
+
offset += local.length + body.length
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const centralStart = offset
|
|
44
|
+
const centralBuf = Buffer.concat(centrals)
|
|
45
|
+
|
|
46
|
+
const eocd = Buffer.alloc(22)
|
|
47
|
+
eocd.writeUInt32LE(0x06054b50, 0)
|
|
48
|
+
eocd.writeUInt16LE(entries.length, 8)
|
|
49
|
+
eocd.writeUInt16LE(entries.length, 10)
|
|
50
|
+
eocd.writeUInt32LE(centralBuf.length, 12)
|
|
51
|
+
eocd.writeUInt32LE(centralStart, 16)
|
|
52
|
+
|
|
53
|
+
return Buffer.concat([...locals, centralBuf, eocd])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('readCentralDirectory finds every entry', () => {
|
|
57
|
+
const zip = makeZip([
|
|
58
|
+
{ name: 'extension/package.json', data: '{}' },
|
|
59
|
+
{ name: 'extension/themes/x.json', data: '{}' }
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
const records = readCentralDirectory(zip)
|
|
63
|
+
assert.ok(records.has('extension/package.json'))
|
|
64
|
+
assert.ok(records.has('extension/themes/x.json'))
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('extractThemes reads contributed color themes (resolving ./ paths)', () => {
|
|
68
|
+
const pkg = JSON.stringify({
|
|
69
|
+
name: 'theme-dracula',
|
|
70
|
+
displayName: 'Dracula',
|
|
71
|
+
contributes: {
|
|
72
|
+
themes: [{ label: 'Dracula', uiTheme: 'vs-dark', path: './themes/dracula.json' }]
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
const themeJson = JSON.stringify({ name: 'Dracula', type: 'dark', colors: { 'editor.background': '#282a36' } })
|
|
76
|
+
|
|
77
|
+
const zip = makeZip([
|
|
78
|
+
{ name: 'extension/package.json', data: pkg },
|
|
79
|
+
{ name: 'extension/themes/dracula.json', data: themeJson }
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
const themes = extractThemes(zip)
|
|
83
|
+
assert.strictEqual(themes.length, 1)
|
|
84
|
+
assert.strictEqual(themes[0].label, 'Dracula')
|
|
85
|
+
assert.strictEqual(themes[0].uiTheme, 'vs-dark')
|
|
86
|
+
assert.match(themes[0].contents, /editor\.background/)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('extractThemes returns empty when the extension contributes no themes', () => {
|
|
90
|
+
const zip = makeZip([{ name: 'extension/package.json', data: JSON.stringify({ name: 'x', contributes: {} }) }])
|
|
91
|
+
assert.deepStrictEqual(extractThemes(zip), [])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('extractThemes throws when the manifest is missing', () => {
|
|
95
|
+
const zip = makeZip([{ name: 'extension/other.txt', data: 'hi' }])
|
|
96
|
+
assert.throws(() => extractThemes(zip), /manifest missing/i)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('looksLikeIconTheme filters icon/product-icon packs out of theme search', () => {
|
|
100
|
+
const { looksLikeIconTheme } = __testing
|
|
101
|
+
|
|
102
|
+
// Tagged contribution points are the strongest signal.
|
|
103
|
+
assert.strictEqual(looksLikeIconTheme({ tags: ['theme', 'icon-theme'] }), true)
|
|
104
|
+
assert.strictEqual(looksLikeIconTheme({ tags: ['product-icon-theme'] }), true)
|
|
105
|
+
|
|
106
|
+
// Name/description fallback for packs that don't tag themselves.
|
|
107
|
+
assert.strictEqual(looksLikeIconTheme({ displayName: 'Material Icon Theme' }), true)
|
|
108
|
+
assert.strictEqual(looksLikeIconTheme({ shortDescription: 'A pack of file icons.' }), true)
|
|
109
|
+
|
|
110
|
+
// Real color themes survive.
|
|
111
|
+
assert.strictEqual(looksLikeIconTheme({ displayName: 'Dracula Official', tags: ['theme', 'color-theme'] }), false)
|
|
112
|
+
assert.strictEqual(looksLikeIconTheme({ displayName: 'One Dark Pro' }), false)
|
|
113
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const test = require('node:test')
|
|
4
|
+
const assert = require('node:assert/strict')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
|
|
8
|
+
const ELECTRON_DIR = __dirname
|
|
9
|
+
|
|
10
|
+
function readElectronFile(name) {
|
|
11
|
+
return fs.readFileSync(path.join(ELECTRON_DIR, name), 'utf8').replace(/\r\n/g, '\n')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requireHiddenChildOptions(source, needle) {
|
|
15
|
+
const index = source.indexOf(needle)
|
|
16
|
+
assert.notEqual(index, -1, `missing call site: ${needle}`)
|
|
17
|
+
const snippet = source.slice(index, index + 700)
|
|
18
|
+
assert.match(
|
|
19
|
+
snippet,
|
|
20
|
+
/hiddenWindowsChildOptions\(/,
|
|
21
|
+
`expected ${needle} to wrap child-process options with hiddenWindowsChildOptions`
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('desktop background child processes opt into hidden Windows consoles', () => {
|
|
26
|
+
const source = readElectronFile('main.cjs')
|
|
27
|
+
|
|
28
|
+
assert.match(source, /function hiddenWindowsChildOptions\(options = \{\}\)/)
|
|
29
|
+
|
|
30
|
+
requireHiddenChildOptions(source, "execFileSync(\n 'reg'")
|
|
31
|
+
requireHiddenChildOptions(source, 'execFileSync(pyExe')
|
|
32
|
+
requireHiddenChildOptions(source, 'spawn(resolveGitBinary()')
|
|
33
|
+
requireHiddenChildOptions(source, "execFileSync('taskkill'")
|
|
34
|
+
requireHiddenChildOptions(source, 'spawn(command, args')
|
|
35
|
+
requireHiddenChildOptions(source, "spawn('curl'")
|
|
36
|
+
requireHiddenChildOptions(source, 'spawn(backend.command, backend.args')
|
|
37
|
+
requireHiddenChildOptions(source, 'nastechProcess = spawn(backend.command, backend.args')
|
|
38
|
+
requireHiddenChildOptions(source, "spawn(py, ['-m', 'nastech_cli.main', 'uninstall', '--gui-summary']")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('intentional or interactive desktop child processes stay documented', () => {
|
|
42
|
+
const source = readElectronFile('main.cjs')
|
|
43
|
+
|
|
44
|
+
assert.match(source, /windowsHide: false/)
|
|
45
|
+
assert.match(source, /handOffWindowsBootstrapRecovery/)
|
|
46
|
+
assert.match(source, /'--repair', '--branch'/)
|
|
47
|
+
assert.match(source, /'--update', '--branch'/)
|
|
48
|
+
assert.match(source, /nodePty\.spawn\(command, args/)
|
|
49
|
+
assert.match(source, /spawn\('cmd\.exe', \['\/c', 'start'/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('bootstrap PowerShell runner hides Windows console children', () => {
|
|
53
|
+
const source = readElectronFile('bootstrap-runner.cjs')
|
|
54
|
+
|
|
55
|
+
assert.match(source, /function hiddenWindowsChildOptions\(options = \{\}\)/)
|
|
56
|
+
requireHiddenChildOptions(source, 'spawn(ps, fullArgs')
|
|
57
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// windows-user-env.cjs
|
|
2
|
+
//
|
|
3
|
+
// Read a User-scoped environment variable straight from the Windows registry
|
|
4
|
+
// (HKCU\Environment).
|
|
5
|
+
//
|
|
6
|
+
// A GUI app launched from Explorer inherits the environment block captured at
|
|
7
|
+
// login, so a variable set via `setx` AFTER login is invisible in process.env
|
|
8
|
+
// even though a fresh shell — and the NasTech CLI — sees it immediately. The
|
|
9
|
+
// desktop's NASTECH_HOME resolution relies on process.env, so that stale-snapshot
|
|
10
|
+
// gap silently sends the backend to the default %LOCALAPPDATA%\nastech. Reading
|
|
11
|
+
// the live registry value closes the gap. See #45471.
|
|
12
|
+
|
|
13
|
+
const { execFileSync } = require('node:child_process')
|
|
14
|
+
|
|
15
|
+
// Parse the output of `reg query HKCU\Environment /v <name>`, which looks like:
|
|
16
|
+
//
|
|
17
|
+
// HKEY_CURRENT_USER\Environment
|
|
18
|
+
// NASTECH_HOME REG_SZ F:\NasTech\data
|
|
19
|
+
//
|
|
20
|
+
// Returns the raw value string (spaces inside the value preserved), or null when
|
|
21
|
+
// the requested value line isn't present.
|
|
22
|
+
function parseRegQueryValue(stdout, name) {
|
|
23
|
+
if (!stdout || !name) return null
|
|
24
|
+
const typePattern =
|
|
25
|
+
/^(\S+)\s+(?:REG_SZ|REG_EXPAND_SZ|REG_MULTI_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_NONE)\s+(.*)$/
|
|
26
|
+
for (const rawLine of String(stdout).split(/\r?\n/)) {
|
|
27
|
+
const line = rawLine.trim()
|
|
28
|
+
const match = line.match(typePattern)
|
|
29
|
+
if (match && match[1].toLowerCase() === name.toLowerCase()) {
|
|
30
|
+
return match[2]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Expand %VAR% references against an env map. REG_EXPAND_SZ values store
|
|
37
|
+
// unexpanded references; plain REG_SZ paths have none, so this is a no-op for
|
|
38
|
+
// the common F:\... case. Unknown references are left verbatim.
|
|
39
|
+
function expandWindowsEnvRefs(value, env = process.env) {
|
|
40
|
+
if (!value) return value
|
|
41
|
+
return value.replace(/%([^%]+)%/g, (whole, name) => {
|
|
42
|
+
const key = Object.keys(env).find(k => k.toUpperCase() === String(name).toUpperCase())
|
|
43
|
+
return key != null && env[key] != null ? env[key] : whole
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Read a User-scoped env var from HKCU\Environment. Windows-only: returns null
|
|
48
|
+
// off-Windows (without spawning), on any spawn error, when `reg` exits non-zero
|
|
49
|
+
// (the value doesn't exist), or when the value is empty.
|
|
50
|
+
function readWindowsUserEnvVar(
|
|
51
|
+
name,
|
|
52
|
+
{ platform = process.platform, env = process.env, exec = execFileSync } = {}
|
|
53
|
+
) {
|
|
54
|
+
if (platform !== 'win32' || !name) return null
|
|
55
|
+
let stdout
|
|
56
|
+
try {
|
|
57
|
+
stdout = exec('reg', ['query', 'HKCU\\Environment', '/v', name], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
timeout: 5000
|
|
61
|
+
})
|
|
62
|
+
} catch {
|
|
63
|
+
// `reg` missing, or value absent (reg exits 1) — caller falls back.
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
const raw = parseRegQueryValue(stdout, name)
|
|
67
|
+
if (raw == null) return null
|
|
68
|
+
const expanded = expandWindowsEnvRefs(raw, env).trim()
|
|
69
|
+
return expanded || null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
expandWindowsEnvRefs,
|
|
74
|
+
parseRegQueryValue,
|
|
75
|
+
readWindowsUserEnvVar
|
|
76
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const { test } = require('node:test')
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
expandWindowsEnvRefs,
|
|
6
|
+
parseRegQueryValue,
|
|
7
|
+
readWindowsUserEnvVar
|
|
8
|
+
} = require('./windows-user-env.cjs')
|
|
9
|
+
|
|
10
|
+
// ── parseRegQueryValue ─────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
test('parseRegQueryValue extracts a REG_SZ value', () => {
|
|
13
|
+
const out = [
|
|
14
|
+
'',
|
|
15
|
+
'HKEY_CURRENT_USER\\Environment',
|
|
16
|
+
' NASTECH_HOME REG_SZ F:\\NasTech\\data',
|
|
17
|
+
''
|
|
18
|
+
].join('\r\n')
|
|
19
|
+
assert.equal(parseRegQueryValue(out, 'NASTECH_HOME'), 'F:\\NasTech\\data')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('parseRegQueryValue matches the name case-insensitively', () => {
|
|
23
|
+
const out = 'HKEY_CURRENT_USER\\Environment\r\n NasTech_Home REG_EXPAND_SZ %USERPROFILE%\\h\r\n'
|
|
24
|
+
assert.equal(parseRegQueryValue(out, 'NASTECH_HOME'), '%USERPROFILE%\\h')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('parseRegQueryValue preserves spaces inside the value', () => {
|
|
28
|
+
const out = ' NASTECH_HOME REG_SZ C:\\Program Files\\NasTech\r\n'
|
|
29
|
+
assert.equal(parseRegQueryValue(out, 'NASTECH_HOME'), 'C:\\Program Files\\NasTech')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('parseRegQueryValue returns null when the value line is absent', () => {
|
|
33
|
+
const out = 'HKEY_CURRENT_USER\\Environment\r\n Path REG_SZ C:\\x\r\n'
|
|
34
|
+
assert.equal(parseRegQueryValue(out, 'NASTECH_HOME'), null)
|
|
35
|
+
assert.equal(parseRegQueryValue('', 'NASTECH_HOME'), null)
|
|
36
|
+
assert.equal(parseRegQueryValue('garbage', 'NASTECH_HOME'), null)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ── expandWindowsEnvRefs ───────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
test('expandWindowsEnvRefs expands %VAR% case-insensitively', () => {
|
|
42
|
+
assert.equal(
|
|
43
|
+
expandWindowsEnvRefs('%UserProfile%\\h', { USERPROFILE: 'C:\\Users\\jeff' }),
|
|
44
|
+
'C:\\Users\\jeff\\h'
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('expandWindowsEnvRefs leaves literal paths and unknown refs intact', () => {
|
|
49
|
+
assert.equal(expandWindowsEnvRefs('F:\\NasTech\\data', {}), 'F:\\NasTech\\data')
|
|
50
|
+
assert.equal(expandWindowsEnvRefs('%NOPE%\\x', {}), '%NOPE%\\x')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// ── readWindowsUserEnvVar ──────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
test('readWindowsUserEnvVar returns null off Windows without spawning', () => {
|
|
56
|
+
let spawned = false
|
|
57
|
+
const exec = () => {
|
|
58
|
+
spawned = true
|
|
59
|
+
return ''
|
|
60
|
+
}
|
|
61
|
+
assert.equal(readWindowsUserEnvVar('NASTECH_HOME', { platform: 'linux', exec }), null)
|
|
62
|
+
assert.equal(spawned, false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('readWindowsUserEnvVar queries HKCU\\Environment and expands the value', () => {
|
|
66
|
+
const calls = []
|
|
67
|
+
const exec = (cmd, args) => {
|
|
68
|
+
calls.push([cmd, args])
|
|
69
|
+
return 'HKEY_CURRENT_USER\\Environment\r\n NASTECH_HOME REG_EXPAND_SZ %DRIVE%\\NasTech\r\n'
|
|
70
|
+
}
|
|
71
|
+
const value = readWindowsUserEnvVar('NASTECH_HOME', {
|
|
72
|
+
platform: 'win32',
|
|
73
|
+
env: { DRIVE: 'F:' },
|
|
74
|
+
exec
|
|
75
|
+
})
|
|
76
|
+
assert.equal(value, 'F:\\NasTech')
|
|
77
|
+
assert.deepEqual(calls, [['reg', ['query', 'HKCU\\Environment', '/v', 'NASTECH_HOME']]])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('readWindowsUserEnvVar returns null when reg exits non-zero (value missing)', () => {
|
|
81
|
+
const exec = () => {
|
|
82
|
+
throw new Error('reg exited 1')
|
|
83
|
+
}
|
|
84
|
+
assert.equal(readWindowsUserEnvVar('NASTECH_HOME', { platform: 'win32', exec }), null)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('readWindowsUserEnvVar returns null for an empty value', () => {
|
|
88
|
+
const exec = () => ' NASTECH_HOME REG_SZ \r\n'
|
|
89
|
+
assert.equal(readWindowsUserEnvVar('NASTECH_HOME', { platform: 'win32', exec }), null)
|
|
90
|
+
})
|