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.
Files changed (546) hide show
  1. package/.prettierrc +11 -0
  2. package/DESIGN.md +167 -0
  3. package/README.md +141 -0
  4. package/assets/icon.icns +0 -0
  5. package/assets/icon.ico +0 -0
  6. package/assets/icon.png +0 -0
  7. package/components.json +21 -0
  8. package/electron/backend-env.cjs +112 -0
  9. package/electron/backend-env.test.cjs +111 -0
  10. package/electron/backend-probes.cjs +106 -0
  11. package/electron/backend-probes.test.cjs +82 -0
  12. package/electron/backend-ready.cjs +66 -0
  13. package/electron/bootstrap-platform.cjs +91 -0
  14. package/electron/bootstrap-platform.test.cjs +111 -0
  15. package/electron/bootstrap-runner.cjs +720 -0
  16. package/electron/bootstrap-runner.test.cjs +138 -0
  17. package/electron/connection-config.cjs +254 -0
  18. package/electron/connection-config.test.cjs +329 -0
  19. package/electron/dashboard-token.cjs +99 -0
  20. package/electron/dashboard-token.test.cjs +142 -0
  21. package/electron/desktop-uninstall.cjs +232 -0
  22. package/electron/desktop-uninstall.test.cjs +246 -0
  23. package/electron/entitlements.mac.inherit.plist +14 -0
  24. package/electron/entitlements.mac.plist +14 -0
  25. package/electron/fs-read-dir.cjs +109 -0
  26. package/electron/fs-read-dir.test.cjs +364 -0
  27. package/electron/gateway-ws-probe.cjs +188 -0
  28. package/electron/gateway-ws-probe.test.cjs +122 -0
  29. package/electron/git-root.cjs +54 -0
  30. package/electron/git-root.test.cjs +40 -0
  31. package/electron/git-worktrees.cjs +174 -0
  32. package/electron/hardening.cjs +184 -0
  33. package/electron/hardening.test.cjs +116 -0
  34. package/electron/main.cjs +5762 -0
  35. package/electron/oauth-net-request.cjs +20 -0
  36. package/electron/oauth-net-request.test.cjs +34 -0
  37. package/electron/preload.cjs +135 -0
  38. package/electron/session-windows.cjs +99 -0
  39. package/electron/session-windows.test.cjs +177 -0
  40. package/electron/update-remote.cjs +56 -0
  41. package/electron/update-remote.test.cjs +78 -0
  42. package/electron/vscode-marketplace.cjs +331 -0
  43. package/electron/vscode-marketplace.test.cjs +113 -0
  44. package/electron/windows-child-process.test.cjs +57 -0
  45. package/electron/windows-user-env.cjs +76 -0
  46. package/electron/windows-user-env.test.cjs +90 -0
  47. package/electron/workspace-cwd.cjs +38 -0
  48. package/electron/workspace-cwd.test.cjs +45 -0
  49. package/eslint.config.mjs +122 -0
  50. package/index.html +17 -0
  51. package/package.json +254 -0
  52. package/pr-assets/session-source-folders.png +0 -0
  53. package/preview-demo.html +65 -0
  54. package/public/apple-touch-icon.png +0 -0
  55. package/public/ds-assets/filler-bg0.jpg +0 -0
  56. package/public/nastech-frames/nastech-frame-0.png +0 -0
  57. package/public/nastech-frames/nastech-frame-1.png +0 -0
  58. package/public/nastech-frames/nastech-frame-2.png +0 -0
  59. package/public/nastech-frames/nastech-frame-3.png +0 -0
  60. package/public/nastech-frames/nastech-frame-4.png +0 -0
  61. package/public/nastech-frames/nastech-frame-5.png +0 -0
  62. package/public/nastech-frames/nastech-frame-6.png +0 -0
  63. package/public/nastech-frames/nastech-frame-7.png +0 -0
  64. package/public/nastech-girl.jpg +0 -0
  65. package/public/nastech-sprite.png +0 -0
  66. package/public/nastech.png +0 -0
  67. package/scripts/after-pack.cjs +41 -0
  68. package/scripts/assert-dist-built.cjs +70 -0
  69. package/scripts/assert-dist-built.test.cjs +84 -0
  70. package/scripts/assert-root-install.cjs +13 -0
  71. package/scripts/before-build.cjs +11 -0
  72. package/scripts/before-pack.cjs +78 -0
  73. package/scripts/before-pack.test.cjs +53 -0
  74. package/scripts/click-session.mjs +51 -0
  75. package/scripts/dev-no-hmr.mjs +22 -0
  76. package/scripts/diag-jump.mjs +115 -0
  77. package/scripts/diag-scroll-reset.mjs +229 -0
  78. package/scripts/eval.mjs +21 -0
  79. package/scripts/leak-typing.mjs +222 -0
  80. package/scripts/measure-jump.mjs +108 -0
  81. package/scripts/measure-latency.mjs +184 -0
  82. package/scripts/measure-real-stream.mjs +252 -0
  83. package/scripts/measure-submit.mjs +179 -0
  84. package/scripts/measure-synthetic-stream.mjs +322 -0
  85. package/scripts/notarize-artifact.cjs +77 -0
  86. package/scripts/notarize.cjs +100 -0
  87. package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
  88. package/scripts/probe-renderer.mjs +38 -0
  89. package/scripts/probe-thread.mjs +40 -0
  90. package/scripts/profile-long-stream.mjs +191 -0
  91. package/scripts/profile-real-stream.mjs +137 -0
  92. package/scripts/profile-synth-stream.mjs +103 -0
  93. package/scripts/profile-typing-lag.md +381 -0
  94. package/scripts/profile-typing.mjs +260 -0
  95. package/scripts/reload-renderer.mjs +25 -0
  96. package/scripts/reload.mjs +36 -0
  97. package/scripts/set-exe-identity.cjs +94 -0
  98. package/scripts/stage-native-deps.cjs +159 -0
  99. package/scripts/test-desktop.mjs +425 -0
  100. package/scripts/write-build-stamp.cjs +126 -0
  101. package/src/app/agents/index.tsx +398 -0
  102. package/src/app/artifacts/index.test.ts +62 -0
  103. package/src/app/artifacts/index.tsx +906 -0
  104. package/src/app/chat/chat-drop-overlay.tsx +48 -0
  105. package/src/app/chat/chat-swap-overlay.tsx +47 -0
  106. package/src/app/chat/composer/attachments.tsx +114 -0
  107. package/src/app/chat/composer/completion-drawer.tsx +63 -0
  108. package/src/app/chat/composer/context-menu.tsx +172 -0
  109. package/src/app/chat/composer/controls.tsx +289 -0
  110. package/src/app/chat/composer/drop-affordance.ts +2 -0
  111. package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  112. package/src/app/chat/composer/focus.ts +134 -0
  113. package/src/app/chat/composer/help-hint.tsx +59 -0
  114. package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
  115. package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
  116. package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
  117. package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
  118. package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
  119. package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
  120. package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  121. package/src/app/chat/composer/index.tsx +1611 -0
  122. package/src/app/chat/composer/inline-refs.ts +138 -0
  123. package/src/app/chat/composer/model-pill.tsx +86 -0
  124. package/src/app/chat/composer/queue-panel.tsx +130 -0
  125. package/src/app/chat/composer/rich-editor.test.ts +18 -0
  126. package/src/app/chat/composer/rich-editor.ts +165 -0
  127. package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
  128. package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  129. package/src/app/chat/composer/status-stack/index.tsx +202 -0
  130. package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  131. package/src/app/chat/composer/text-utils.test.ts +77 -0
  132. package/src/app/chat/composer/text-utils.ts +107 -0
  133. package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
  134. package/src/app/chat/composer/trigger-popover.tsx +116 -0
  135. package/src/app/chat/composer/types.ts +64 -0
  136. package/src/app/chat/composer/url-dialog.tsx +82 -0
  137. package/src/app/chat/composer/voice-activity.tsx +252 -0
  138. package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  139. package/src/app/chat/hooks/use-composer-actions.ts +525 -0
  140. package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
  141. package/src/app/chat/index.tsx +390 -0
  142. package/src/app/chat/perf-probe.tsx +269 -0
  143. package/src/app/chat/right-rail/index.ts +1 -0
  144. package/src/app/chat/right-rail/preview-console-state.ts +82 -0
  145. package/src/app/chat/right-rail/preview-console.tsx +290 -0
  146. package/src/app/chat/right-rail/preview-file.tsx +559 -0
  147. package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
  148. package/src/app/chat/right-rail/preview-pane.tsx +657 -0
  149. package/src/app/chat/right-rail/preview.tsx +171 -0
  150. package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  151. package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  152. package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
  153. package/src/app/chat/sidebar/index.tsx +1219 -0
  154. package/src/app/chat/sidebar/load-more-row.tsx +30 -0
  155. package/src/app/chat/sidebar/order.test.ts +21 -0
  156. package/src/app/chat/sidebar/order.ts +17 -0
  157. package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
  158. package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
  159. package/src/app/chat/sidebar/session-row.tsx +257 -0
  160. package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
  161. package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  162. package/src/app/chat/sidebar/workspace-groups.ts +326 -0
  163. package/src/app/chat/thread-loading.test.ts +34 -0
  164. package/src/app/chat/thread-loading.ts +26 -0
  165. package/src/app/command-center/index.tsx +654 -0
  166. package/src/app/command-palette/index.tsx +513 -0
  167. package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  168. package/src/app/cron/index.tsx +942 -0
  169. package/src/app/cron/job-state.ts +29 -0
  170. package/src/app/desktop-controller.tsx +938 -0
  171. package/src/app/floating-hud.ts +22 -0
  172. package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  173. package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
  174. package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
  175. package/src/app/hooks/use-keybinds.ts +186 -0
  176. package/src/app/hooks/use-refresh-hotkey.ts +45 -0
  177. package/src/app/hooks/use-route-enum-param.ts +38 -0
  178. package/src/app/index.tsx +1 -0
  179. package/src/app/layout-constants.ts +13 -0
  180. package/src/app/messaging/index.tsx +648 -0
  181. package/src/app/messaging/platform-icon.tsx +93 -0
  182. package/src/app/model-picker-overlay.tsx +42 -0
  183. package/src/app/model-visibility-overlay.tsx +31 -0
  184. package/src/app/overlays/overlay-chrome.tsx +66 -0
  185. package/src/app/overlays/overlay-search-input.tsx +33 -0
  186. package/src/app/overlays/overlay-split-layout.tsx +130 -0
  187. package/src/app/overlays/overlay-view.tsx +91 -0
  188. package/src/app/page-search-shell.tsx +75 -0
  189. package/src/app/profiles/create-profile-dialog.tsx +154 -0
  190. package/src/app/profiles/delete-profile-dialog.tsx +65 -0
  191. package/src/app/profiles/index.tsx +671 -0
  192. package/src/app/profiles/rename-profile-dialog.tsx +125 -0
  193. package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  194. package/src/app/right-sidebar/files/ipc.test.ts +100 -0
  195. package/src/app/right-sidebar/files/ipc.ts +161 -0
  196. package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  197. package/src/app/right-sidebar/files/tree.tsx +224 -0
  198. package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
  199. package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
  200. package/src/app/right-sidebar/index.test.tsx +75 -0
  201. package/src/app/right-sidebar/index.tsx +395 -0
  202. package/src/app/right-sidebar/store.ts +15 -0
  203. package/src/app/right-sidebar/terminal/buffer.ts +65 -0
  204. package/src/app/right-sidebar/terminal/index.tsx +98 -0
  205. package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
  206. package/src/app/right-sidebar/terminal/selection.ts +75 -0
  207. package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
  208. package/src/app/routes.ts +88 -0
  209. package/src/app/session/hooks/use-context-suggestions.ts +58 -0
  210. package/src/app/session/hooks/use-cwd-actions.ts +109 -0
  211. package/src/app/session/hooks/use-message-stream.ts +957 -0
  212. package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  213. package/src/app/session/hooks/use-model-controls.ts +106 -0
  214. package/src/app/session/hooks/use-nastech-config.ts +74 -0
  215. package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
  216. package/src/app/session/hooks/use-preview-routing.ts +223 -0
  217. package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
  218. package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
  219. package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
  220. package/src/app/session/hooks/use-route-resume.ts +115 -0
  221. package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
  222. package/src/app/session/hooks/use-session-actions.ts +885 -0
  223. package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
  224. package/src/app/session/hooks/use-session-state-cache.ts +191 -0
  225. package/src/app/session-picker-overlay.tsx +32 -0
  226. package/src/app/session-switcher.tsx +107 -0
  227. package/src/app/settings/about-settings.tsx +173 -0
  228. package/src/app/settings/appearance-settings.tsx +162 -0
  229. package/src/app/settings/config-settings.tsx +384 -0
  230. package/src/app/settings/constants.ts +545 -0
  231. package/src/app/settings/credential-key-ui.tsx +373 -0
  232. package/src/app/settings/env-credentials.tsx +198 -0
  233. package/src/app/settings/env-var-actions-menu.tsx +136 -0
  234. package/src/app/settings/field-copy.ts +56 -0
  235. package/src/app/settings/gateway-settings.tsx +620 -0
  236. package/src/app/settings/helpers.test.ts +138 -0
  237. package/src/app/settings/helpers.ts +151 -0
  238. package/src/app/settings/index.tsx +237 -0
  239. package/src/app/settings/keys-settings.tsx +96 -0
  240. package/src/app/settings/mcp-settings.tsx +271 -0
  241. package/src/app/settings/model-settings.test.tsx +157 -0
  242. package/src/app/settings/model-settings.tsx +559 -0
  243. package/src/app/settings/notifications-settings.tsx +150 -0
  244. package/src/app/settings/primitives.tsx +115 -0
  245. package/src/app/settings/providers-settings.test.tsx +100 -0
  246. package/src/app/settings/providers-settings.tsx +258 -0
  247. package/src/app/settings/sessions-settings.tsx +276 -0
  248. package/src/app/settings/toolset-config-panel.test.tsx +289 -0
  249. package/src/app/settings/toolset-config-panel.tsx +449 -0
  250. package/src/app/settings/types.ts +42 -0
  251. package/src/app/settings/uninstall-section.tsx +185 -0
  252. package/src/app/settings/use-deep-link-highlight.ts +60 -0
  253. package/src/app/shell/app-shell.tsx +167 -0
  254. package/src/app/shell/gateway-menu-panel.tsx +150 -0
  255. package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
  256. package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
  257. package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
  258. package/src/app/shell/keybind-panel.tsx +220 -0
  259. package/src/app/shell/model-edit-submenu.test.tsx +84 -0
  260. package/src/app/shell/model-edit-submenu.tsx +245 -0
  261. package/src/app/shell/model-menu-panel.tsx +295 -0
  262. package/src/app/shell/sidebar-label.tsx +22 -0
  263. package/src/app/shell/statusbar-controls.tsx +185 -0
  264. package/src/app/shell/titlebar-controls.tsx +244 -0
  265. package/src/app/shell/titlebar.test.ts +26 -0
  266. package/src/app/shell/titlebar.ts +45 -0
  267. package/src/app/shell/use-group-registry.ts +39 -0
  268. package/src/app/skills/index.test.tsx +103 -0
  269. package/src/app/skills/index.tsx +371 -0
  270. package/src/app/types.ts +99 -0
  271. package/src/app/updates-overlay.tsx +369 -0
  272. package/src/components/Backdrop.tsx +114 -0
  273. package/src/components/assistant-ui/ansi-text.tsx +34 -0
  274. package/src/components/assistant-ui/clarify-tool.tsx +281 -0
  275. package/src/components/assistant-ui/directive-text.test.ts +39 -0
  276. package/src/components/assistant-ui/directive-text.tsx +389 -0
  277. package/src/components/assistant-ui/markdown-text.test.ts +204 -0
  278. package/src/components/assistant-ui/markdown-text.tsx +497 -0
  279. package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  280. package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  281. package/src/components/assistant-ui/streaming.test.tsx +739 -0
  282. package/src/components/assistant-ui/thread-list.tsx +307 -0
  283. package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
  284. package/src/components/assistant-ui/thread.tsx +1474 -0
  285. package/src/components/assistant-ui/todo-tool.tsx +109 -0
  286. package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
  287. package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
  288. package/src/components/assistant-ui/tool-approval.tsx +209 -0
  289. package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
  290. package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
  291. package/src/components/assistant-ui/tool-fallback.tsx +466 -0
  292. package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
  293. package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  294. package/src/components/assistant-ui/user-message-text.tsx +150 -0
  295. package/src/components/boot-failure-overlay.tsx +246 -0
  296. package/src/components/boot-failure-reauth.test.ts +100 -0
  297. package/src/components/boot-failure-reauth.ts +81 -0
  298. package/src/components/brand-mark.tsx +19 -0
  299. package/src/components/chat/activity-timer-text.tsx +24 -0
  300. package/src/components/chat/activity-timer.test.tsx +43 -0
  301. package/src/components/chat/activity-timer.ts +64 -0
  302. package/src/components/chat/code-card.tsx +78 -0
  303. package/src/components/chat/compact-markdown.tsx +113 -0
  304. package/src/components/chat/composer-dock.ts +31 -0
  305. package/src/components/chat/diff-lines.tsx +54 -0
  306. package/src/components/chat/disclosure-row.tsx +63 -0
  307. package/src/components/chat/generated-image-context.tsx +19 -0
  308. package/src/components/chat/generated-image-result.tsx +174 -0
  309. package/src/components/chat/image-generation-placeholder.tsx +279 -0
  310. package/src/components/chat/intro-copy.jsonl +75 -0
  311. package/src/components/chat/intro.tsx +182 -0
  312. package/src/components/chat/preview-attachment.tsx +125 -0
  313. package/src/components/chat/shiki-highlighter.tsx +107 -0
  314. package/src/components/chat/status-row.tsx +70 -0
  315. package/src/components/chat/status-section.tsx +42 -0
  316. package/src/components/chat/terminal-output.tsx +50 -0
  317. package/src/components/chat/zoomable-image.tsx +177 -0
  318. package/src/components/desktop-install-overlay.tsx +595 -0
  319. package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
  320. package/src/components/desktop-onboarding-overlay.tsx +1286 -0
  321. package/src/components/error-boundary.tsx +77 -0
  322. package/src/components/gateway-connecting-overlay.test.tsx +143 -0
  323. package/src/components/gateway-connecting-overlay.tsx +183 -0
  324. package/src/components/haptics-provider.tsx +19 -0
  325. package/src/components/language-switcher.test.tsx +53 -0
  326. package/src/components/language-switcher.tsx +175 -0
  327. package/src/components/model-picker.tsx +340 -0
  328. package/src/components/model-visibility-dialog.tsx +155 -0
  329. package/src/components/notifications.tsx +196 -0
  330. package/src/components/page-loader.tsx +34 -0
  331. package/src/components/pane-shell/context.ts +14 -0
  332. package/src/components/pane-shell/index.ts +4 -0
  333. package/src/components/pane-shell/pane-shell.test.tsx +333 -0
  334. package/src/components/pane-shell/pane-shell.tsx +330 -0
  335. package/src/components/prompt-overlays.tsx +234 -0
  336. package/src/components/session-picker.tsx +108 -0
  337. package/src/components/status-dot.tsx +26 -0
  338. package/src/components/ui/action-status.tsx +25 -0
  339. package/src/components/ui/alert.tsx +53 -0
  340. package/src/components/ui/badge.tsx +35 -0
  341. package/src/components/ui/braille-spinner.tsx +61 -0
  342. package/src/components/ui/button.tsx +81 -0
  343. package/src/components/ui/checkbox.tsx +27 -0
  344. package/src/components/ui/codicon.tsx +20 -0
  345. package/src/components/ui/command.tsx +111 -0
  346. package/src/components/ui/confirm-dialog.tsx +109 -0
  347. package/src/components/ui/context-menu.tsx +141 -0
  348. package/src/components/ui/control.ts +25 -0
  349. package/src/components/ui/copy-button.test.tsx +36 -0
  350. package/src/components/ui/copy-button.tsx +229 -0
  351. package/src/components/ui/dialog.tsx +152 -0
  352. package/src/components/ui/disclosure-caret.tsx +20 -0
  353. package/src/components/ui/dropdown-menu.tsx +291 -0
  354. package/src/components/ui/error-state.tsx +50 -0
  355. package/src/components/ui/fade-text.tsx +110 -0
  356. package/src/components/ui/glyph-spinner.tsx +63 -0
  357. package/src/components/ui/input.tsx +22 -0
  358. package/src/components/ui/kbd.tsx +37 -0
  359. package/src/components/ui/loader.tsx +558 -0
  360. package/src/components/ui/log-view.tsx +17 -0
  361. package/src/components/ui/pagination.tsx +114 -0
  362. package/src/components/ui/popover.tsx +44 -0
  363. package/src/components/ui/scroll-area.tsx +43 -0
  364. package/src/components/ui/search-field.tsx +80 -0
  365. package/src/components/ui/segmented-control.tsx +51 -0
  366. package/src/components/ui/select.tsx +92 -0
  367. package/src/components/ui/separator.tsx +26 -0
  368. package/src/components/ui/sheet.tsx +116 -0
  369. package/src/components/ui/sidebar.tsx +674 -0
  370. package/src/components/ui/skeleton.tsx +7 -0
  371. package/src/components/ui/switch.tsx +49 -0
  372. package/src/components/ui/tabs.tsx +36 -0
  373. package/src/components/ui/text-tab.tsx +43 -0
  374. package/src/components/ui/textarea.tsx +11 -0
  375. package/src/components/ui/tool-icon.tsx +65 -0
  376. package/src/components/ui/tooltip.tsx +69 -0
  377. package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/src/global.d.ts +457 -0
  381. package/src/hooks/use-image-download.ts +85 -0
  382. package/src/hooks/use-media-query.ts +24 -0
  383. package/src/hooks/use-mobile.ts +3 -0
  384. package/src/hooks/use-resize-observer.ts +38 -0
  385. package/src/hooks/use-worktree-info.ts +68 -0
  386. package/src/i18n/catalog.ts +12 -0
  387. package/src/i18n/context.test.tsx +232 -0
  388. package/src/i18n/context.tsx +183 -0
  389. package/src/i18n/define-locale.ts +41 -0
  390. package/src/i18n/en.ts +1779 -0
  391. package/src/i18n/index.ts +20 -0
  392. package/src/i18n/ja.ts +1890 -0
  393. package/src/i18n/languages.test.ts +43 -0
  394. package/src/i18n/languages.ts +86 -0
  395. package/src/i18n/runtime.test.ts +75 -0
  396. package/src/i18n/runtime.ts +53 -0
  397. package/src/i18n/types.ts +1452 -0
  398. package/src/i18n/zh-hant.ts +1849 -0
  399. package/src/i18n/zh.ts +1923 -0
  400. package/src/lib/ansi.test.ts +123 -0
  401. package/src/lib/ansi.ts +175 -0
  402. package/src/lib/chat-messages.test.ts +708 -0
  403. package/src/lib/chat-messages.ts +885 -0
  404. package/src/lib/chat-runtime.test.ts +18 -0
  405. package/src/lib/chat-runtime.ts +335 -0
  406. package/src/lib/clipboard.ts +28 -0
  407. package/src/lib/commit-changelog.test.ts +114 -0
  408. package/src/lib/commit-changelog.ts +177 -0
  409. package/src/lib/completion-sound.ts +519 -0
  410. package/src/lib/desktop-fs.test.ts +116 -0
  411. package/src/lib/desktop-fs.ts +113 -0
  412. package/src/lib/desktop-slash-commands.test.ts +126 -0
  413. package/src/lib/desktop-slash-commands.ts +286 -0
  414. package/src/lib/embedded-images.test.ts +35 -0
  415. package/src/lib/embedded-images.ts +60 -0
  416. package/src/lib/external-link.test.tsx +168 -0
  417. package/src/lib/external-link.tsx +303 -0
  418. package/src/lib/gateway-events.test.ts +27 -0
  419. package/src/lib/gateway-events.ts +49 -0
  420. package/src/lib/gateway-ws-url.test.ts +78 -0
  421. package/src/lib/gateway-ws-url.ts +91 -0
  422. package/src/lib/generated-images.test.ts +97 -0
  423. package/src/lib/generated-images.ts +116 -0
  424. package/src/lib/haptics.ts +129 -0
  425. package/src/lib/icons.ts +203 -0
  426. package/src/lib/incremental-external-store-runtime.ts +188 -0
  427. package/src/lib/katex-memo.ts +260 -0
  428. package/src/lib/keybinds/actions.ts +125 -0
  429. package/src/lib/keybinds/combo.test.ts +86 -0
  430. package/src/lib/keybinds/combo.ts +169 -0
  431. package/src/lib/local-preview.ts +126 -0
  432. package/src/lib/markdown-code.test.ts +23 -0
  433. package/src/lib/markdown-code.ts +195 -0
  434. package/src/lib/markdown-preprocess.ts +386 -0
  435. package/src/lib/media.remote.test.ts +58 -0
  436. package/src/lib/media.ts +111 -0
  437. package/src/lib/model-status-label.test.ts +31 -0
  438. package/src/lib/model-status-label.ts +103 -0
  439. package/src/lib/mutable-ref.ts +6 -0
  440. package/src/lib/preview-targets.test.ts +27 -0
  441. package/src/lib/preview-targets.ts +63 -0
  442. package/src/lib/profile-color.ts +58 -0
  443. package/src/lib/provider-setup-errors.test.ts +26 -0
  444. package/src/lib/provider-setup-errors.ts +12 -0
  445. package/src/lib/query-client.ts +13 -0
  446. package/src/lib/remend-tail.test.ts +105 -0
  447. package/src/lib/remend-tail.ts +108 -0
  448. package/src/lib/runtime-readiness.test.ts +65 -0
  449. package/src/lib/runtime-readiness.ts +147 -0
  450. package/src/lib/session-export.ts +57 -0
  451. package/src/lib/session-search.test.ts +58 -0
  452. package/src/lib/session-search.ts +19 -0
  453. package/src/lib/session-source.ts +62 -0
  454. package/src/lib/speech-text.ts +35 -0
  455. package/src/lib/statusbar.ts +91 -0
  456. package/src/lib/storage.test.ts +25 -0
  457. package/src/lib/storage.ts +107 -0
  458. package/src/lib/todos.test.ts +35 -0
  459. package/src/lib/todos.ts +51 -0
  460. package/src/lib/tool-result-summary.test.ts +106 -0
  461. package/src/lib/tool-result-summary.ts +467 -0
  462. package/src/lib/update-copy.test.ts +38 -0
  463. package/src/lib/update-copy.ts +44 -0
  464. package/src/lib/use-enter-animation.ts +100 -0
  465. package/src/lib/utils.ts +6 -0
  466. package/src/lib/voice-playback.ts +128 -0
  467. package/src/lib/yolo-session.ts +26 -0
  468. package/src/main.tsx +43 -0
  469. package/src/nastech.test.ts +49 -0
  470. package/src/nastech.ts +718 -0
  471. package/src/store/activity.ts +100 -0
  472. package/src/store/boot.ts +91 -0
  473. package/src/store/clarify.test.ts +81 -0
  474. package/src/store/clarify.ts +69 -0
  475. package/src/store/command-palette.ts +20 -0
  476. package/src/store/compaction.test.ts +53 -0
  477. package/src/store/compaction.ts +38 -0
  478. package/src/store/completion-sound.ts +32 -0
  479. package/src/store/composer-input-history.test.ts +147 -0
  480. package/src/store/composer-input-history.ts +158 -0
  481. package/src/store/composer-queue.test.ts +148 -0
  482. package/src/store/composer-queue.ts +239 -0
  483. package/src/store/composer-status.test.ts +99 -0
  484. package/src/store/composer-status.ts +277 -0
  485. package/src/store/composer.test.ts +106 -0
  486. package/src/store/composer.ts +184 -0
  487. package/src/store/cron.ts +19 -0
  488. package/src/store/gateway.ts +290 -0
  489. package/src/store/haptics.ts +17 -0
  490. package/src/store/keybinds.ts +139 -0
  491. package/src/store/layout.ts +176 -0
  492. package/src/store/model-presets.test.ts +51 -0
  493. package/src/store/model-presets.ts +86 -0
  494. package/src/store/model-visibility.test.ts +37 -0
  495. package/src/store/model-visibility.ts +108 -0
  496. package/src/store/native-notifications.test.ts +192 -0
  497. package/src/store/native-notifications.ts +203 -0
  498. package/src/store/notifications.ts +165 -0
  499. package/src/store/onboarding.test.ts +372 -0
  500. package/src/store/onboarding.ts +866 -0
  501. package/src/store/panes.test.ts +146 -0
  502. package/src/store/panes.ts +145 -0
  503. package/src/store/preview.test.ts +135 -0
  504. package/src/store/preview.ts +466 -0
  505. package/src/store/profile.test.ts +89 -0
  506. package/src/store/profile.ts +365 -0
  507. package/src/store/prompts.test.ts +121 -0
  508. package/src/store/prompts.ts +115 -0
  509. package/src/store/session-switcher.test.ts +115 -0
  510. package/src/store/session-switcher.ts +128 -0
  511. package/src/store/session-sync.ts +25 -0
  512. package/src/store/session.test.ts +131 -0
  513. package/src/store/session.ts +255 -0
  514. package/src/store/subagents.test.ts +111 -0
  515. package/src/store/subagents.ts +260 -0
  516. package/src/store/thread-scroll.ts +46 -0
  517. package/src/store/todos.test.ts +47 -0
  518. package/src/store/todos.ts +64 -0
  519. package/src/store/tool-diffs.ts +23 -0
  520. package/src/store/tool-dismiss.ts +45 -0
  521. package/src/store/tool-view.ts +91 -0
  522. package/src/store/translucency.ts +38 -0
  523. package/src/store/updates.test.ts +77 -0
  524. package/src/store/updates.ts +315 -0
  525. package/src/store/voice-playback.ts +24 -0
  526. package/src/store/windows.test.ts +143 -0
  527. package/src/store/windows.ts +77 -0
  528. package/src/styles.css +1235 -0
  529. package/src/themes/color.ts +142 -0
  530. package/src/themes/context.tsx +339 -0
  531. package/src/themes/index.ts +3 -0
  532. package/src/themes/install.test.ts +119 -0
  533. package/src/themes/install.ts +95 -0
  534. package/src/themes/presets.test.ts +33 -0
  535. package/src/themes/presets.ts +293 -0
  536. package/src/themes/profile-theme.test.ts +41 -0
  537. package/src/themes/types.ts +66 -0
  538. package/src/themes/use-skin-command.ts +60 -0
  539. package/src/themes/user-themes.test.ts +63 -0
  540. package/src/themes/user-themes.ts +122 -0
  541. package/src/themes/vscode.test.ts +171 -0
  542. package/src/themes/vscode.ts +343 -0
  543. package/src/types/nastech.ts +646 -0
  544. package/src/vite-env.d.ts +1 -0
  545. package/tsconfig.json +25 -0
  546. 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
+ })