@dyyz1993/create-agent 1.9.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/package.json +25 -0
- package/src/__tests__/cli.test.ts +85 -0
- package/src/__tests__/copy.test.ts +168 -0
- package/src/__tests__/templates.test.ts +87 -0
- package/src/cli.ts +65 -0
- package/src/commands/create.ts +49 -0
- package/src/commands/list.ts +12 -0
- package/src/commands/status.ts +85 -0
- package/src/commands/workspace.ts +129 -0
- package/src/lib/copy.ts +285 -0
- package/src/lib/templates.ts +57 -0
- package/src/lib/types.ts +6 -0
- package/templates/agent/.env.example +7 -0
- package/templates/agent/LICENSE +21 -0
- package/templates/agent/README.md +87 -0
- package/templates/agent/bun.lock +1279 -0
- package/templates/agent/electrobun.config.ts +27 -0
- package/templates/agent/eslint.config.mjs +63 -0
- package/templates/agent/llms.txt +24 -0
- package/templates/agent/package-lock.json +3670 -0
- package/templates/agent/package.json +59 -0
- package/templates/agent/postcss.config.js +6 -0
- package/templates/agent/scripts/dev.ts +138 -0
- package/templates/agent/src/__tests__/server-config.test.ts +59 -0
- package/templates/agent/src/bun/index.ts +73 -0
- package/templates/agent/src/gateway/__tests__/ws-handler.test.ts +48 -0
- package/templates/agent/src/gateway/http-routes.ts +1 -0
- package/templates/agent/src/gateway/ipc-transport.ts +68 -0
- package/templates/agent/src/gateway/ws-handler.ts +86 -0
- package/templates/agent/src/mainview/App.tsx +41 -0
- package/templates/agent/src/mainview/__tests__/components/ChatPanel.test.tsx +114 -0
- package/templates/agent/src/mainview/__tests__/components/ExplorerSidebar.test.tsx +141 -0
- package/templates/agent/src/mainview/__tests__/components/MessageBubble.test.tsx +72 -0
- package/templates/agent/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
- package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +217 -0
- package/templates/agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
- package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
- package/templates/agent/src/mainview/__tests__/setup.ts +37 -0
- package/templates/agent/src/mainview/__tests__/stores/use-chat-store.test.ts +83 -0
- package/templates/agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +85 -0
- package/templates/agent/src/mainview/components/activity-bar/ActivityBar.tsx +36 -0
- package/templates/agent/src/mainview/components/activity-bar/MobileTabBar.tsx +36 -0
- package/templates/agent/src/mainview/components/activity-bar/__tests__/ActivityBar.test.tsx +44 -0
- package/templates/agent/src/mainview/components/activity-bar/__tests__/MobileTabBar.test.tsx +44 -0
- package/templates/agent/src/mainview/components/bash/BashPanel.tsx +206 -0
- package/templates/agent/src/mainview/components/bash/__tests__/BashPanel.test.tsx +103 -0
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +102 -0
- package/templates/agent/src/mainview/components/chat/MessageBubble.tsx +86 -0
- package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +58 -0
- package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +14 -0
- package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +24 -0
- package/templates/agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
- package/templates/agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
- package/templates/agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
- package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +120 -0
- package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +102 -0
- package/templates/agent/src/mainview/components/diff/DiffViewerPanel.tsx +106 -0
- package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +73 -0
- package/templates/agent/src/mainview/components/explorer/ConfirmDialog.tsx +53 -0
- package/templates/agent/src/mainview/components/explorer/ContextMenu.tsx +82 -0
- package/templates/agent/src/mainview/components/explorer/ExplorerSidebar.tsx +221 -0
- package/templates/agent/src/mainview/components/explorer/InlineInput.tsx +55 -0
- package/templates/agent/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
- package/templates/agent/src/mainview/components/explorer/__tests__/ConfirmDialog.test.tsx +37 -0
- package/templates/agent/src/mainview/components/explorer/__tests__/ContextMenu.test.tsx +49 -0
- package/templates/agent/src/mainview/components/explorer/__tests__/InlineInput.test.tsx +48 -0
- package/templates/agent/src/mainview/components/explorer/__tests__/TreeNodeItem.test.tsx +146 -0
- package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +239 -0
- package/templates/agent/src/mainview/components/feed/__tests__/FeedPanel.test.tsx +108 -0
- package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +56 -0
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
- package/templates/agent/src/mainview/components/file-preview/__tests__/FilePreviewOverlay.test.tsx +96 -0
- package/templates/agent/src/mainview/components/file-preview/__tests__/VirtualizedCodeView.test.tsx +58 -0
- package/templates/agent/src/mainview/components/git/GitBranchSelector.tsx +98 -0
- package/templates/agent/src/mainview/components/git/GitCommitInput.tsx +60 -0
- package/templates/agent/src/mainview/components/git/GitPanel.tsx +528 -0
- package/templates/agent/src/mainview/components/git/__tests__/GitBranchSelector.test.tsx +76 -0
- package/templates/agent/src/mainview/components/git/__tests__/GitCommitInput.test.tsx +91 -0
- package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +174 -0
- package/templates/agent/src/mainview/components/layout/AppLayout.tsx +170 -0
- package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +163 -0
- package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +117 -0
- package/templates/agent/src/mainview/components/rules/__tests__/RulesPanel.test.tsx +91 -0
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +404 -0
- package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +50 -0
- package/templates/agent/src/mainview/components/sidebar/PinButton.tsx +20 -0
- package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +48 -0
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +141 -0
- package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +91 -0
- package/templates/agent/src/mainview/global.d.ts +13 -0
- package/templates/agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +147 -0
- package/templates/agent/src/mainview/hooks/use-breakpoint.ts +34 -0
- package/templates/agent/src/mainview/hooks/use-input-history.ts +84 -0
- package/templates/agent/src/mainview/hooks/use-rpc-init.ts +61 -0
- package/templates/agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/agent/src/mainview/index.css +61 -0
- package/templates/agent/src/mainview/index.html +12 -0
- package/templates/agent/src/mainview/lib/api-client.ts +192 -0
- package/templates/agent/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/agent/src/mainview/lib/i18n/locales/en.json +157 -0
- package/templates/agent/src/mainview/lib/i18n/locales/zh.json +157 -0
- package/templates/agent/src/mainview/lib/rpc-cache.ts +94 -0
- package/templates/agent/src/mainview/main.tsx +24 -0
- package/templates/agent/src/mainview/stores/__tests__/use-app-store.test.ts +86 -0
- package/templates/agent/src/mainview/stores/__tests__/use-bash-store.test.ts +99 -0
- package/templates/agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
- package/templates/agent/src/mainview/stores/__tests__/use-explorer-store.test.ts +130 -0
- package/templates/agent/src/mainview/stores/__tests__/use-feed-store.test.ts +132 -0
- package/templates/agent/src/mainview/stores/__tests__/use-git-store.test.ts +161 -0
- package/templates/agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
- package/templates/agent/src/mainview/stores/__tests__/use-notification-store.test.ts +102 -0
- package/templates/agent/src/mainview/stores/__tests__/use-rules-store.test.ts +75 -0
- package/templates/agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/agent/src/mainview/stores/__tests__/use-todo-store.test.ts +75 -0
- package/templates/agent/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/agent/src/mainview/stores/use-app-store.ts +99 -0
- package/templates/agent/src/mainview/stores/use-bash-store.ts +93 -0
- package/templates/agent/src/mainview/stores/use-chat-store.ts +37 -0
- package/templates/agent/src/mainview/stores/use-connection-store.ts +42 -0
- package/templates/agent/src/mainview/stores/use-explorer-store.ts +320 -0
- package/templates/agent/src/mainview/stores/use-feed-store.ts +129 -0
- package/templates/agent/src/mainview/stores/use-git-store.ts +291 -0
- package/templates/agent/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/agent/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/agent/src/mainview/stores/use-notification-store.ts +82 -0
- package/templates/agent/src/mainview/stores/use-rules-store.ts +54 -0
- package/templates/agent/src/mainview/stores/use-sidebar-store.ts +99 -0
- package/templates/agent/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/agent/src/mainview/stores/use-todo-store.ts +54 -0
- package/templates/agent/src/mainview/types/index.ts +33 -0
- package/templates/agent/src/mainview/utils/constants.ts +1 -0
- package/templates/agent/src/mainview/utils/drop-handler.ts +150 -0
- package/templates/agent/src/mainview/utils/file-icon.tsx +24 -0
- package/templates/agent/src/mainview/utils/file-utils.ts +35 -0
- package/templates/agent/src/server-config.ts +43 -0
- package/templates/agent/src/server.ts +89 -0
- package/templates/agent/src/shared/handlers/__tests__/bash-handler.test.ts +135 -0
- package/templates/agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
- package/templates/agent/src/shared/handlers/__tests__/feed-handler.test.ts +108 -0
- package/templates/agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
- package/templates/agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
- package/templates/agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
- package/templates/agent/src/shared/handlers/bash.ts +89 -0
- package/templates/agent/src/shared/handlers/chat.ts +179 -0
- package/templates/agent/src/shared/handlers/feed.ts +53 -0
- package/templates/agent/src/shared/handlers/file.ts +99 -0
- package/templates/agent/src/shared/handlers/git.ts +263 -0
- package/templates/agent/src/shared/handlers/index.ts +10 -0
- package/templates/agent/src/shared/handlers/rules.ts +45 -0
- package/templates/agent/src/shared/handlers/system.ts +27 -0
- package/templates/agent/src/shared/handlers/timer.ts +34 -0
- package/templates/agent/src/shared/handlers/todo.ts +45 -0
- package/templates/agent/src/shared/lib/__tests__/bash-security.test.ts +125 -0
- package/templates/agent/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
- package/templates/agent/src/shared/lib/bash-security.ts +64 -0
- package/templates/agent/src/shared/lib/logger.ts +130 -0
- package/templates/agent/src/shared/lib/path-security.ts +38 -0
- package/templates/agent/src/shared/lib/port-registry.ts +103 -0
- package/templates/agent/src/shared/modules/bash.ts +19 -0
- package/templates/agent/src/shared/modules/chat.ts +20 -0
- package/templates/agent/src/shared/modules/feed.ts +30 -0
- package/templates/agent/src/shared/modules/file.ts +40 -0
- package/templates/agent/src/shared/modules/git.ts +81 -0
- package/templates/agent/src/shared/modules/rules.ts +25 -0
- package/templates/agent/src/shared/modules/system.ts +8 -0
- package/templates/agent/src/shared/modules/timer.ts +11 -0
- package/templates/agent/src/shared/modules/todo.ts +27 -0
- package/templates/agent/src/shared/register-all-handlers.ts +36 -0
- package/templates/agent/src/shared/rpc-schema.ts +35 -0
- package/templates/agent/tailwind.config.js +15 -0
- package/templates/agent/tsconfig.json +26 -0
- package/templates/agent/vite.config.ts +3 -0
- package/templates/agent/vitest.config.ts +3 -0
- package/templates/chat/.env.example +6 -0
- package/templates/chat/LICENSE +21 -0
- package/templates/chat/README.md +56 -0
- package/templates/chat/bun.lock +1203 -0
- package/templates/chat/electrobun.config.ts +27 -0
- package/templates/chat/eslint.config.mjs +56 -0
- package/templates/chat/llms.txt +24 -0
- package/templates/chat/package-lock.json +3670 -0
- package/templates/chat/package.json +58 -0
- package/templates/chat/postcss.config.js +6 -0
- package/templates/chat/scripts/dev.ts +138 -0
- package/templates/chat/src/__tests__/server-config.test.ts +59 -0
- package/templates/chat/src/bun/index.ts +72 -0
- package/templates/chat/src/gateway/__tests__/ws-handler.test.ts +48 -0
- package/templates/chat/src/gateway/http-routes.ts +1 -0
- package/templates/chat/src/gateway/ipc-transport.ts +68 -0
- package/templates/chat/src/gateway/ws-handler.ts +86 -0
- package/templates/chat/src/mainview/App.tsx +108 -0
- package/templates/chat/src/mainview/__tests__/components/ChatPanel.test.tsx +76 -0
- package/templates/chat/src/mainview/__tests__/components/MessageBubble.test.tsx +48 -0
- package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
- package/templates/chat/src/mainview/__tests__/setup-verify.test.ts +8 -0
- package/templates/chat/src/mainview/__tests__/setup.ts +37 -0
- package/templates/chat/src/mainview/__tests__/stores/use-app-store.test.ts +46 -0
- package/templates/chat/src/mainview/__tests__/stores/use-chat-store.test.ts +104 -0
- package/templates/chat/src/mainview/__tests__/stores/use-connection-store.test.ts +35 -0
- package/templates/chat/src/mainview/__tests__/stores/use-log-store.test.ts +45 -0
- package/templates/chat/src/mainview/__tests__/stores/use-notification-store.test.ts +102 -0
- package/templates/chat/src/mainview/__tests__/stores/use-sidebar-store.test.ts +89 -0
- package/templates/chat/src/mainview/components/activity-bar/ActivityBar.tsx +26 -0
- package/templates/chat/src/mainview/components/activity-bar/MobileTabBar.tsx +20 -0
- package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +66 -0
- package/templates/chat/src/mainview/components/chat/MessageBubble.tsx +85 -0
- package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +24 -0
- package/templates/chat/src/mainview/components/sidebar/PinButton.tsx +20 -0
- package/templates/chat/src/mainview/global.d.ts +13 -0
- package/templates/chat/src/mainview/hooks/use-breakpoint.ts +34 -0
- package/templates/chat/src/mainview/hooks/use-input-history.ts +84 -0
- package/templates/chat/src/mainview/index.css +61 -0
- package/templates/chat/src/mainview/index.html +12 -0
- package/templates/chat/src/mainview/lib/api-client.ts +175 -0
- package/templates/chat/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/chat/src/mainview/lib/i18n/locales/en.json +157 -0
- package/templates/chat/src/mainview/lib/i18n/locales/zh.json +157 -0
- package/templates/chat/src/mainview/main.tsx +20 -0
- package/templates/chat/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/chat/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/chat/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/chat/src/mainview/stores/use-app-store.ts +50 -0
- package/templates/chat/src/mainview/stores/use-chat-store.ts +37 -0
- package/templates/chat/src/mainview/stores/use-connection-store.ts +42 -0
- package/templates/chat/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/chat/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/chat/src/mainview/stores/use-notification-store.ts +82 -0
- package/templates/chat/src/mainview/stores/use-sidebar-store.ts +98 -0
- package/templates/chat/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/chat/src/mainview/types/index.ts +8 -0
- package/templates/chat/src/mainview/utils/constants.ts +1 -0
- package/templates/chat/src/server-config.ts +42 -0
- package/templates/chat/src/server.ts +89 -0
- package/templates/chat/src/shared/__tests__/chat-handler.test.ts +71 -0
- package/templates/chat/src/shared/__tests__/system-handler.test.ts +70 -0
- package/templates/chat/src/shared/handlers/chat.ts +186 -0
- package/templates/chat/src/shared/handlers/index.ts +2 -0
- package/templates/chat/src/shared/handlers/system.ts +27 -0
- package/templates/chat/src/shared/lib/logger.ts +130 -0
- package/templates/chat/src/shared/lib/path-security.ts +38 -0
- package/templates/chat/src/shared/lib/port-registry.ts +103 -0
- package/templates/chat/src/shared/modules/chat.ts +20 -0
- package/templates/chat/src/shared/modules/system.ts +8 -0
- package/templates/chat/src/shared/register-all-handlers.ts +36 -0
- package/templates/chat/src/shared/rpc-schema.ts +11 -0
- package/templates/chat/tailwind.config.js +15 -0
- package/templates/chat/tsconfig.json +26 -0
- package/templates/chat/vite.config.ts +3 -0
- package/templates/chat/vitest.config.ts +3 -0
- package/templates/general/.env.example +7 -0
- package/templates/general/LICENSE +21 -0
- package/templates/general/README.md +113 -0
- package/templates/general/bun.lock +1266 -0
- package/templates/general/electrobun.config.ts +27 -0
- package/templates/general/eslint.config.mjs +56 -0
- package/templates/general/llms.txt +24 -0
- package/templates/general/package-lock.json +3670 -0
- package/templates/general/package.json +59 -0
- package/templates/general/postcss.config.js +6 -0
- package/templates/general/scripts/dev.ts +138 -0
- package/templates/general/src/__tests__/server-config.test.ts +59 -0
- package/templates/general/src/bun/index.ts +72 -0
- package/templates/general/src/gateway/http-routes.ts +1 -0
- package/templates/general/src/gateway/ipc-transport.ts +68 -0
- package/templates/general/src/gateway/ws-handler.ts +86 -0
- package/templates/general/src/mainview/App.tsx +40 -0
- package/templates/general/src/mainview/__tests__/components/ChatPanel.test.tsx +82 -0
- package/templates/general/src/mainview/__tests__/components/FeedPanel.test.tsx +109 -0
- package/templates/general/src/mainview/__tests__/components/GitPanel.test.tsx +151 -0
- package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
- package/templates/general/src/mainview/__tests__/setup-verify.test.ts +8 -0
- package/templates/general/src/mainview/__tests__/setup.ts +37 -0
- package/templates/general/src/mainview/components/activity-bar/ActivityBar.tsx +40 -0
- package/templates/general/src/mainview/components/activity-bar/MobileTabBar.tsx +29 -0
- package/templates/general/src/mainview/components/chat/ChatPanel.tsx +66 -0
- package/templates/general/src/mainview/components/chat/MessageBubble.tsx +85 -0
- package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/general/src/mainview/components/common/ThemeToggle.tsx +24 -0
- package/templates/general/src/mainview/components/debug/DebugPanel.tsx +176 -0
- package/templates/general/src/mainview/components/diff/DiffViewerPanel.tsx +108 -0
- package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +52 -0
- package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +82 -0
- package/templates/general/src/mainview/components/explorer/ExplorerSidebar.tsx +225 -0
- package/templates/general/src/mainview/components/explorer/InlineInput.tsx +55 -0
- package/templates/general/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
- package/templates/general/src/mainview/components/feed/FeedPanel.tsx +248 -0
- package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +55 -0
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
- package/templates/general/src/mainview/components/git/GitBranchSelector.tsx +93 -0
- package/templates/general/src/mainview/components/git/GitCommitInput.tsx +60 -0
- package/templates/general/src/mainview/components/git/GitPanel.tsx +533 -0
- package/templates/general/src/mainview/components/layout/AppLayout.tsx +146 -0
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +405 -0
- package/templates/general/src/mainview/components/sidebar/PinButton.tsx +20 -0
- package/templates/general/src/mainview/global.d.ts +13 -0
- package/templates/general/src/mainview/hooks/use-breakpoint.ts +34 -0
- package/templates/general/src/mainview/hooks/use-input-history.ts +84 -0
- package/templates/general/src/mainview/hooks/use-rpc-init.ts +61 -0
- package/templates/general/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/general/src/mainview/index.css +61 -0
- package/templates/general/src/mainview/index.html +12 -0
- package/templates/general/src/mainview/lib/api-client.ts +175 -0
- package/templates/general/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/general/src/mainview/lib/i18n/locales/en.json +157 -0
- package/templates/general/src/mainview/lib/i18n/locales/zh.json +157 -0
- package/templates/general/src/mainview/main.tsx +20 -0
- package/templates/general/src/mainview/stores/__tests__/use-chat-store.test.ts +104 -0
- package/templates/general/src/mainview/stores/__tests__/use-connection-store.test.ts +35 -0
- package/templates/general/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/general/src/mainview/stores/__tests__/use-log-store.test.ts +45 -0
- package/templates/general/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/general/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/general/src/mainview/stores/use-app-store.ts +104 -0
- package/templates/general/src/mainview/stores/use-chat-store.ts +37 -0
- package/templates/general/src/mainview/stores/use-connection-store.ts +42 -0
- package/templates/general/src/mainview/stores/use-explorer-store.ts +315 -0
- package/templates/general/src/mainview/stores/use-feed-store.ts +129 -0
- package/templates/general/src/mainview/stores/use-git-store.ts +284 -0
- package/templates/general/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/general/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/general/src/mainview/stores/use-notification-store.ts +82 -0
- package/templates/general/src/mainview/stores/use-sidebar-store.ts +99 -0
- package/templates/general/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/general/src/mainview/types/index.ts +33 -0
- package/templates/general/src/mainview/utils/constants.ts +1 -0
- package/templates/general/src/mainview/utils/drop-handler.ts +150 -0
- package/templates/general/src/mainview/utils/file-icon.tsx +24 -0
- package/templates/general/src/mainview/utils/file-utils.ts +35 -0
- package/templates/general/src/server-config.ts +42 -0
- package/templates/general/src/server.ts +89 -0
- package/templates/general/src/shared/handlers/__tests__/chat.test.ts +183 -0
- package/templates/general/src/shared/handlers/__tests__/system.test.ts +94 -0
- package/templates/general/src/shared/handlers/__tests__/timer.test.ts +113 -0
- package/templates/general/src/shared/handlers/chat.ts +179 -0
- package/templates/general/src/shared/handlers/feed.ts +53 -0
- package/templates/general/src/shared/handlers/file.ts +99 -0
- package/templates/general/src/shared/handlers/git.ts +263 -0
- package/templates/general/src/shared/handlers/index.ts +7 -0
- package/templates/general/src/shared/handlers/system.ts +27 -0
- package/templates/general/src/shared/handlers/timer.ts +34 -0
- package/templates/general/src/shared/lib/logger.ts +130 -0
- package/templates/general/src/shared/lib/path-security.ts +38 -0
- package/templates/general/src/shared/lib/port-registry.ts +103 -0
- package/templates/general/src/shared/modules/chat.ts +20 -0
- package/templates/general/src/shared/modules/feed.ts +30 -0
- package/templates/general/src/shared/modules/file.ts +40 -0
- package/templates/general/src/shared/modules/git.ts +81 -0
- package/templates/general/src/shared/modules/system.ts +8 -0
- package/templates/general/src/shared/modules/timer.ts +11 -0
- package/templates/general/src/shared/register-all-handlers.ts +36 -0
- package/templates/general/src/shared/rpc-schema.ts +31 -0
- package/templates/general/tailwind.config.js +15 -0
- package/templates/general/tsconfig.json +26 -0
- package/templates/general/vite.config.ts +3 -0
- package/templates/general/vitest.config.ts +3 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { apiClient } from "../lib/api-client";
|
|
2
|
+
|
|
3
|
+
export interface DropEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
relativePath: string;
|
|
6
|
+
file?: File;
|
|
7
|
+
isDirectory: boolean;
|
|
8
|
+
children?: DropEntry[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 递归读取 webkitGetAsEntry,返回扁平文件列表 + 目录结构
|
|
13
|
+
*/
|
|
14
|
+
function readEntry(
|
|
15
|
+
entry: FileSystemEntry,
|
|
16
|
+
path: string,
|
|
17
|
+
): Promise<DropEntry> {
|
|
18
|
+
if (entry.isFile) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
(entry as FileSystemFileEntry).file((file) => {
|
|
21
|
+
resolve({ name: entry.name, relativePath: path, file, isDirectory: false });
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Directory
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const reader = (entry as FileSystemDirectoryEntry).createReader();
|
|
28
|
+
const children: DropEntry[] = [];
|
|
29
|
+
|
|
30
|
+
const readBatch = () => {
|
|
31
|
+
reader.readEntries(async (entries) => {
|
|
32
|
+
if (entries.length === 0) {
|
|
33
|
+
resolve({ name: entry.name, relativePath: path, isDirectory: true, children });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const e of entries) {
|
|
37
|
+
children.push(await readEntry(e, `${path}/${e.name}`));
|
|
38
|
+
}
|
|
39
|
+
readBatch(); // readEntries may not return all entries in one call
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
readBatch();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 从 DataTransfer 递归读取所有文件和目录
|
|
48
|
+
*/
|
|
49
|
+
export async function readDropItems(dataTransfer: DataTransfer): Promise<DropEntry[]> {
|
|
50
|
+
const items = dataTransfer.items;
|
|
51
|
+
if (!items) return [];
|
|
52
|
+
|
|
53
|
+
const entries: DropEntry[] = [];
|
|
54
|
+
const tasks: Promise<void>[] = [];
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < items.length; i++) {
|
|
57
|
+
const item = items[i];
|
|
58
|
+
// Try webkitGetAsEntry (Chrome, Edge, Safari)
|
|
59
|
+
const entry = item.webkitGetAsEntry?.();
|
|
60
|
+
if (entry) {
|
|
61
|
+
tasks.push(
|
|
62
|
+
readEntry(entry, entry.name).then<void>((e) => { entries.push(e); }),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await Promise.all(tasks);
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Web 端:递归上传 entries 到目标目录
|
|
73
|
+
*/
|
|
74
|
+
export async function uploadEntriesWeb(entries: DropEntry[], destDir: string): Promise<number> {
|
|
75
|
+
let count = 0;
|
|
76
|
+
|
|
77
|
+
async function process(entry: DropEntry, currentDir: string): Promise<void> {
|
|
78
|
+
if (entry.isDirectory) {
|
|
79
|
+
// Create directory
|
|
80
|
+
const dirPath = `${currentDir}/${entry.name}`;
|
|
81
|
+
await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
|
|
82
|
+
count++;
|
|
83
|
+
if (entry.children) {
|
|
84
|
+
for (const child of entry.children) {
|
|
85
|
+
await process(child, dirPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else if (entry.file) {
|
|
89
|
+
// Upload file via HTTP
|
|
90
|
+
const filePath = `${currentDir}/${entry.name}`;
|
|
91
|
+
const arrayBuffer = await entry.file.arrayBuffer();
|
|
92
|
+
const baseUrl = apiClient.getBaseUrl();
|
|
93
|
+
const token = apiClient.getAuthToken();
|
|
94
|
+
const res = await fetch(
|
|
95
|
+
`${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
|
|
96
|
+
{ method: "POST", body: arrayBuffer },
|
|
97
|
+
);
|
|
98
|
+
if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
|
|
99
|
+
count++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
await process(entry, destDir);
|
|
105
|
+
}
|
|
106
|
+
return count;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 桌面端:通过 RPC file.copy 直接复制
|
|
111
|
+
*/
|
|
112
|
+
export async function importFilesDesktop(entries: DropEntry[], destDir: string): Promise<number> {
|
|
113
|
+
let count = 0;
|
|
114
|
+
|
|
115
|
+
async function process(entry: DropEntry, currentDir: string): Promise<void> {
|
|
116
|
+
if (entry.isDirectory) {
|
|
117
|
+
const dirPath = `${currentDir}/${entry.name}`;
|
|
118
|
+
await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
|
|
119
|
+
count++;
|
|
120
|
+
if (entry.children) {
|
|
121
|
+
for (const child of entry.children) {
|
|
122
|
+
await process(child, dirPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (entry.file) {
|
|
126
|
+
// Desktop: File object has .path property (Electron/Electrobun)
|
|
127
|
+
const srcPath = (entry.file as File & { path?: string }).path;
|
|
128
|
+
if (srcPath) {
|
|
129
|
+
await apiClient.call("file.copy", { srcPath, destDir: currentDir });
|
|
130
|
+
} else {
|
|
131
|
+
// Fallback: no path available, upload via HTTP
|
|
132
|
+
const filePath = `${currentDir}/${entry.name}`;
|
|
133
|
+
const arrayBuffer = await entry.file.arrayBuffer();
|
|
134
|
+
const baseUrl = apiClient.getBaseUrl();
|
|
135
|
+
const token = apiClient.getAuthToken();
|
|
136
|
+
const res = await fetch(
|
|
137
|
+
`${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
|
|
138
|
+
{ method: "POST", body: arrayBuffer },
|
|
139
|
+
);
|
|
140
|
+
if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
|
|
141
|
+
}
|
|
142
|
+
count++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
await process(entry, destDir);
|
|
148
|
+
}
|
|
149
|
+
return count;
|
|
150
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FolderOpen,
|
|
3
|
+
Folder,
|
|
4
|
+
FileText,
|
|
5
|
+
FileCode,
|
|
6
|
+
Image,
|
|
7
|
+
FileArchive,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import type { TreeNode } from "../types";
|
|
10
|
+
|
|
11
|
+
export function getFileIcon(node: TreeNode) {
|
|
12
|
+
if (node.type === "directory") {
|
|
13
|
+
return node.expanded ? (
|
|
14
|
+
<FolderOpen className="w-4 h-4 text-yellow-400 shrink-0" />
|
|
15
|
+
) : (
|
|
16
|
+
<Folder className="w-4 h-4 text-yellow-400 shrink-0" />
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const ext = node.name.split(".").pop()?.toLowerCase() || "";
|
|
20
|
+
if (["ts", "tsx", "js", "jsx"].includes(ext)) return <FileCode className="w-4 h-4 text-blue-400 shrink-0" />;
|
|
21
|
+
if (["png", "jpg", "jpeg", "gif", "svg", "webp"].includes(ext)) return <Image className="w-4 h-4 text-green-400 shrink-0" />;
|
|
22
|
+
if (["zip", "gz", "tar", "rar"].includes(ext)) return <FileArchive className="w-4 h-4 text-orange-400 shrink-0" />;
|
|
23
|
+
return <FileText className="w-4 h-4 text-gray-400 shrink-0" />;
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function getLanguage(filename: string): string {
|
|
2
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
3
|
+
const map: Record<string, string> = {
|
|
4
|
+
ts: "typescript", tsx: "tsx", js: "javascript", jsx: "jsx",
|
|
5
|
+
mjs: "javascript", cjs: "javascript", mts: "typescript", cts: "typescript",
|
|
6
|
+
json: "json", html: "markup", css: "css", md: "markdown",
|
|
7
|
+
py: "python", rs: "rust", go: "go", sh: "bash", bash: "bash",
|
|
8
|
+
yml: "yaml", yaml: "yaml", toml: "toml", xml: "markup",
|
|
9
|
+
sql: "sql", graphql: "graphql",
|
|
10
|
+
};
|
|
11
|
+
return map[ext] || "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isTextFile(filename: string): boolean {
|
|
15
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
16
|
+
const textExts = new Set([
|
|
17
|
+
"ts", "tsx", "js", "jsx", "json", "html", "css", "scss", "less",
|
|
18
|
+
"md", "txt", "py", "rs", "go", "sh", "bash", "yml", "yaml", "toml",
|
|
19
|
+
"xml", "sql", "graphql", "env", "gitignore", "prettierrc", "eslintrc",
|
|
20
|
+
"lock", "log", "conf", "cfg", "ini", "csv", "tsv",
|
|
21
|
+
"mjs", "cjs", "mts", "cts", "map",
|
|
22
|
+
]);
|
|
23
|
+
return textExts.has(ext);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isImageFile(filename: string): boolean {
|
|
27
|
+
const ext = filename.split(".").pop()?.toLowerCase() || "";
|
|
28
|
+
return ["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"].includes(ext);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatSize(bytes: number): string {
|
|
32
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
33
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
34
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web server configuration — single source of truth.
|
|
3
|
+
* Values are read from environment variables with sensible defaults.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function parseEnvInt(
|
|
7
|
+
key: string,
|
|
8
|
+
value: string | undefined,
|
|
9
|
+
defaultValue: number,
|
|
10
|
+
min: number,
|
|
11
|
+
max: number,
|
|
12
|
+
): number {
|
|
13
|
+
if (value === undefined || value === "") return defaultValue;
|
|
14
|
+
const parsed = parseInt(value, 10);
|
|
15
|
+
if (isNaN(parsed) || parsed < min || parsed > max) {
|
|
16
|
+
console.warn(
|
|
17
|
+
`[config] Invalid ${key}: "${value}", using default: ${defaultValue}`,
|
|
18
|
+
);
|
|
19
|
+
return defaultValue;
|
|
20
|
+
}
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const config = {
|
|
25
|
+
port: parseEnvInt("PORT", process.env.PORT, 3100, 1024, 65535),
|
|
26
|
+
authToken: process.env.AUTH_TOKEN || "pi-agent-template-token",
|
|
27
|
+
maxUploadSize: parseEnvInt(
|
|
28
|
+
"MAX_UPLOAD_SIZE",
|
|
29
|
+
process.env.MAX_UPLOAD_SIZE,
|
|
30
|
+
50 * 1024 * 1024,
|
|
31
|
+
0,
|
|
32
|
+
1024 * 1024 * 1024,
|
|
33
|
+
),
|
|
34
|
+
logDir: process.env.LOG_DIR || "logs",
|
|
35
|
+
corsOrigin: process.env.CORS_ORIGIN || "http://localhost:5173",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
if (process.env.NODE_ENV === "production" && !process.env.AUTH_TOKEN) {
|
|
39
|
+
console.warn(
|
|
40
|
+
"[security] WARNING: Using default AUTH_TOKEN in production. Set AUTH_TOKEN environment variable.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web server entry point — HTTP file endpoints + WebSocket RPC gateway.
|
|
3
|
+
* Port auto-negotiation: tries config.port first, increments on EADDRINUSE.
|
|
4
|
+
* Writes actual port to .server-port for dev orchestration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createServer } from "http";
|
|
8
|
+
import { writeFileSync, unlinkSync, existsSync } from "fs";
|
|
9
|
+
import { join, resolve, basename } from "path";
|
|
10
|
+
import { config } from "./server-config";
|
|
11
|
+
import { createHttpHandler } from "./gateway/http-routes";
|
|
12
|
+
import { createWsHandler } from "./gateway/ws-handler";
|
|
13
|
+
import { createLogger, configureLogDir } from "./shared/lib/logger";
|
|
14
|
+
import { registerPort, unregisterPort, formatRegistryForOutput } from "./shared/lib/port-registry";
|
|
15
|
+
import { discoverMethodNames } from "./shared/register-all-handlers";
|
|
16
|
+
|
|
17
|
+
configureLogDir(config.logDir);
|
|
18
|
+
const log = createLogger("server");
|
|
19
|
+
|
|
20
|
+
const PORT_FILE = join(import.meta.dir, "..", ".server-port");
|
|
21
|
+
const PROJECT_ROOT = resolve(import.meta.dir, "..");
|
|
22
|
+
const PROJECT_NAME = basename(PROJECT_ROOT);
|
|
23
|
+
|
|
24
|
+
function cleanupPortFile() {
|
|
25
|
+
try { if (existsSync(PORT_FILE)) unlinkSync(PORT_FILE); } catch {}
|
|
26
|
+
unregisterPort(PROJECT_ROOT);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writePortFile(port: number) {
|
|
30
|
+
writeFileSync(PORT_FILE, String(port), "utf-8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
process.on("exit", cleanupPortFile);
|
|
34
|
+
process.on("SIGINT", () => { cleanupPortFile(); process.exit(0); });
|
|
35
|
+
process.on("SIGTERM", () => { cleanupPortFile(); process.exit(0); });
|
|
36
|
+
|
|
37
|
+
const httpServer = createServer();
|
|
38
|
+
const wss = createWsHandler(httpServer, { config });
|
|
39
|
+
|
|
40
|
+
httpServer.on("request", createHttpHandler({
|
|
41
|
+
config,
|
|
42
|
+
getWebSocketClientCount: () => wss.clients.size,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
function checkPort(port: number): Promise<number> {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const testServer = createServer();
|
|
48
|
+
testServer.once("error", (err: NodeJS.ErrnoException) => {
|
|
49
|
+
testServer.close();
|
|
50
|
+
reject(err);
|
|
51
|
+
});
|
|
52
|
+
testServer.once("listening", () => {
|
|
53
|
+
testServer.close();
|
|
54
|
+
resolve(port);
|
|
55
|
+
});
|
|
56
|
+
testServer.listen(port);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function findAvailablePort(startPort: number, maxRetries: number = 10): Promise<number> {
|
|
61
|
+
for (let port = startPort; port < startPort + maxRetries; port++) {
|
|
62
|
+
try {
|
|
63
|
+
await checkPort(port);
|
|
64
|
+
return port;
|
|
65
|
+
} catch {
|
|
66
|
+
log.info(`Port ${port} in use, trying ${port + 1}...`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`No available port found after ${maxRetries} retries`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function start() {
|
|
73
|
+
const port = await findAvailablePort(config.port);
|
|
74
|
+
httpServer.listen(port, () => {
|
|
75
|
+
writePortFile(port);
|
|
76
|
+
registerPort(PROJECT_ROOT, port, PROJECT_NAME);
|
|
77
|
+
log.info(`HTTP + WebSocket server running on http://localhost:${port}`);
|
|
78
|
+
log.info(`WebSocket: ws://localhost:${port}/ws (auth required)`);
|
|
79
|
+
log.info(`Available RPC methods: ${discoverMethodNames().join(", ")}`);
|
|
80
|
+
log.info("File endpoints: GET /file/{path}, GET /info/{path}");
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.log("\n" + formatRegistryForOutput() + "\n");
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
start().catch((err) => {
|
|
87
|
+
log.error("Server failed to start", { error: err.message });
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
3
|
+
import { register } from "../chat";
|
|
4
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
|
|
9
|
+
type Handler = (params: unknown) => Promise<unknown>;
|
|
10
|
+
type EmittedEvent = { event: string; payload: unknown; filter: unknown };
|
|
11
|
+
|
|
12
|
+
const STORAGE_PATH = join(homedir(), ".pi-agent", "chat-history.json");
|
|
13
|
+
|
|
14
|
+
function createMockServer() {
|
|
15
|
+
const handlers = new Map<string, Handler>();
|
|
16
|
+
const events: EmittedEvent[] = [];
|
|
17
|
+
return {
|
|
18
|
+
register: (method: string, handler: Handler) => {
|
|
19
|
+
handlers.set(method, handler);
|
|
20
|
+
},
|
|
21
|
+
emitEvent: (event: string, payload: unknown, filter?: unknown) => {
|
|
22
|
+
events.push({ event, payload, filter });
|
|
23
|
+
},
|
|
24
|
+
get: (method: string) => handlers.get(method),
|
|
25
|
+
has: (method: string) => handlers.has(method),
|
|
26
|
+
getEvents: () => events,
|
|
27
|
+
clearEvents: () => { events.length = 0; },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function clearHistory(): Promise<void> {
|
|
32
|
+
try {
|
|
33
|
+
if (existsSync(STORAGE_PATH)) {
|
|
34
|
+
const { unlink } = await import("fs/promises");
|
|
35
|
+
await unlink(STORAGE_PATH);
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function writeHistory(messages: Record<string, unknown>[]): Promise<void> {
|
|
41
|
+
const dir = dirname(STORAGE_PATH);
|
|
42
|
+
if (!existsSync(dir)) {
|
|
43
|
+
await mkdir(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
await writeFile(STORAGE_PATH, JSON.stringify(messages, null, 2), "utf-8");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("chat handler", () => {
|
|
49
|
+
let server: ReturnType<typeof createMockServer>;
|
|
50
|
+
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
server = createMockServer();
|
|
53
|
+
register(server as unknown as RPCServer, { platform: "desktop" });
|
|
54
|
+
await clearHistory();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("registers chat.list", () => {
|
|
58
|
+
expect(server.has("chat.list")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("registers chat.send", () => {
|
|
62
|
+
expect(server.has("chat.send")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("chat.list returns empty when no history", async () => {
|
|
66
|
+
const result = await server.get("chat.list")!({}) as Record<string, unknown>;
|
|
67
|
+
expect(result.messages).toEqual([]);
|
|
68
|
+
expect(result.hasMore).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("chat.list with existing history returns messages", async () => {
|
|
72
|
+
const msgs = [
|
|
73
|
+
{ id: "1", role: "user", content: "hi", timestamp: 1000 },
|
|
74
|
+
{ id: "2", role: "assistant", content: "hello", timestamp: 2000 },
|
|
75
|
+
];
|
|
76
|
+
await writeHistory(msgs);
|
|
77
|
+
|
|
78
|
+
const result = await server.get("chat.list")!({}) as Record<string, unknown>;
|
|
79
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
80
|
+
expect(messages.length).toBe(2);
|
|
81
|
+
expect(result.hasMore).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("chat.list respects limit param", async () => {
|
|
85
|
+
const msgs = Array.from({ length: 100 }, (_, i) => ({
|
|
86
|
+
id: `msg-${i}`,
|
|
87
|
+
role: i % 2 === 0 ? "user" as const : "assistant" as const,
|
|
88
|
+
content: `message ${i}`,
|
|
89
|
+
timestamp: i * 1000,
|
|
90
|
+
}));
|
|
91
|
+
await writeHistory(msgs);
|
|
92
|
+
|
|
93
|
+
const result = await server.get("chat.list")!({ limit: 10 }) as Record<string, unknown>;
|
|
94
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
95
|
+
expect(messages.length).toBe(10);
|
|
96
|
+
expect(result.hasMore).toBe(true);
|
|
97
|
+
expect(messages[0].id).toBe("msg-90");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("chat.list default limit is 50", async () => {
|
|
101
|
+
const msgs = Array.from({ length: 60 }, (_, i) => ({
|
|
102
|
+
id: `msg-${i}`,
|
|
103
|
+
role: "user" as const,
|
|
104
|
+
content: `message ${i}`,
|
|
105
|
+
timestamp: i * 1000,
|
|
106
|
+
}));
|
|
107
|
+
await writeHistory(msgs);
|
|
108
|
+
|
|
109
|
+
const result = await server.get("chat.list")!({}) as Record<string, unknown>;
|
|
110
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
111
|
+
expect(messages.length).toBe(50);
|
|
112
|
+
expect(result.hasMore).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("chat.send returns { ok: true }", async () => {
|
|
116
|
+
const result = await server.get("chat.send")!({ content: "hello" }) as Record<string, unknown>;
|
|
117
|
+
expect(result.ok).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("chat.send emits chat.message events for user and assistant", async () => {
|
|
121
|
+
await server.get("chat.send")!({ content: "hi" });
|
|
122
|
+
|
|
123
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
124
|
+
expect(events.length).toBe(2);
|
|
125
|
+
|
|
126
|
+
const userEvent = events[0].payload as Record<string, unknown>;
|
|
127
|
+
expect(userEvent.role).toBe("user");
|
|
128
|
+
expect(userEvent.content).toBe("hi");
|
|
129
|
+
|
|
130
|
+
const assistantEvent = events[1].payload as Record<string, unknown>;
|
|
131
|
+
expect(assistantEvent.role).toBe("assistant");
|
|
132
|
+
expect(assistantEvent.content).toBeTruthy();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("chat.send stores messages in history", async () => {
|
|
136
|
+
await server.get("chat.send")!({ content: "hello world" });
|
|
137
|
+
|
|
138
|
+
const result = await server.get("chat.list")!({}) as Record<string, unknown>;
|
|
139
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
140
|
+
expect(messages.length).toBe(2);
|
|
141
|
+
expect(messages[0].role).toBe("user");
|
|
142
|
+
expect(messages[0].content).toBe("hello world");
|
|
143
|
+
expect(messages[1].role).toBe("assistant");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("chat.send message has correct shape", async () => {
|
|
147
|
+
await server.get("chat.send")!({ content: "test" });
|
|
148
|
+
|
|
149
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
150
|
+
const userMsg = events[0].payload as Record<string, unknown>;
|
|
151
|
+
expect(userMsg).toHaveProperty("id");
|
|
152
|
+
expect(userMsg).toHaveProperty("role");
|
|
153
|
+
expect(userMsg).toHaveProperty("content");
|
|
154
|
+
expect(userMsg).toHaveProperty("timestamp");
|
|
155
|
+
expect(typeof userMsg.id).toBe("string");
|
|
156
|
+
expect(typeof userMsg.timestamp).toBe("number");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("chat.message events include role filter", async () => {
|
|
160
|
+
await server.get("chat.send")!({ content: "yo" });
|
|
161
|
+
|
|
162
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
163
|
+
expect(events[0].filter).toEqual({ role: "user" });
|
|
164
|
+
expect(events[1].filter).toEqual({ role: "assistant" });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("chat.send with greeting gets greeting reply", async () => {
|
|
168
|
+
await server.get("chat.send")!({ content: "hello" });
|
|
169
|
+
|
|
170
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
171
|
+
const reply = events[1].payload as Record<string, unknown>;
|
|
172
|
+
expect(reply.content).toBeTruthy();
|
|
173
|
+
expect(reply.role).toBe("assistant");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("chat.send with math expression gets calculation reply", async () => {
|
|
177
|
+
await server.get("chat.send")!({ content: "12 * 8" });
|
|
178
|
+
|
|
179
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
180
|
+
const reply = events[1].payload as Record<string, unknown>;
|
|
181
|
+
expect(reply.content).toContain("96");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
3
|
+
import { register } from "../system";
|
|
4
|
+
|
|
5
|
+
type Handler = (params: unknown) => Promise<unknown>;
|
|
6
|
+
|
|
7
|
+
function createMockServer() {
|
|
8
|
+
const handlers = new Map<string, Handler>();
|
|
9
|
+
return {
|
|
10
|
+
register: (method: string, handler: Handler) => {
|
|
11
|
+
handlers.set(method, handler);
|
|
12
|
+
},
|
|
13
|
+
emitEvent: () => {},
|
|
14
|
+
get: (method: string) => handlers.get(method),
|
|
15
|
+
has: (method: string) => handlers.has(method),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("system handler", () => {
|
|
20
|
+
const server = createMockServer();
|
|
21
|
+
register(server as unknown as RPCServer, { platform: "desktop" });
|
|
22
|
+
|
|
23
|
+
test("registers system.ping", () => {
|
|
24
|
+
expect(server.has("system.ping")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("registers system.hello", () => {
|
|
28
|
+
expect(server.has("system.hello")).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("registers system.echo", () => {
|
|
32
|
+
expect(server.has("system.echo")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("system.ping returns pong with timestamp and platform", async () => {
|
|
36
|
+
const before = Date.now();
|
|
37
|
+
const result = await server.get("system.ping")!({}) as Record<string, unknown>;
|
|
38
|
+
const after = Date.now();
|
|
39
|
+
|
|
40
|
+
expect(result.pong).toBe(true);
|
|
41
|
+
expect(result.platform).toBe("desktop");
|
|
42
|
+
expect(result.timestamp).toBeGreaterThanOrEqual(before);
|
|
43
|
+
expect(result.timestamp).toBeLessThanOrEqual(after);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("system.ping respects platform option", async () => {
|
|
47
|
+
const webServer = createMockServer();
|
|
48
|
+
register(webServer as unknown as RPCServer, { platform: "web" });
|
|
49
|
+
const result = await webServer.get("system.ping")!({}) as Record<string, unknown>;
|
|
50
|
+
expect(result.platform).toBe("web");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("system.hello with name returns personalized greeting", async () => {
|
|
54
|
+
const result = await server.get("system.hello")!({ name: "Alice" }) as Record<string, unknown>;
|
|
55
|
+
expect(result.message).toBe("Hello Alice!");
|
|
56
|
+
expect(result.timestamp).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("system.hello without name defaults to World", async () => {
|
|
60
|
+
const result = await server.get("system.hello")!({}) as Record<string, unknown>;
|
|
61
|
+
expect(result.message).toBe("Hello World!");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("system.hello with undefined name defaults to World", async () => {
|
|
65
|
+
const result = await server.get("system.hello")!({ name: undefined }) as Record<string, unknown>;
|
|
66
|
+
expect(result.message).toBe("Hello World!");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("system.hello with empty string name defaults to World", async () => {
|
|
70
|
+
const result = await server.get("system.hello")!({ name: "" }) as Record<string, unknown>;
|
|
71
|
+
expect(result.message).toBe("Hello World!");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("system.echo returns the input as-is", async () => {
|
|
75
|
+
const data = { foo: "bar", num: 42, nested: { a: [1, 2, 3] } };
|
|
76
|
+
const result = await server.get("system.echo")!(data);
|
|
77
|
+
expect(result).toEqual(data);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("system.echo with null returns null", async () => {
|
|
81
|
+
const result = await server.get("system.echo")!(null);
|
|
82
|
+
expect(result).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("system.echo with primitive returns primitive", async () => {
|
|
86
|
+
const result = await server.get("system.echo")!(42);
|
|
87
|
+
expect(result).toBe(42);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("system.echo with empty object returns empty object", async () => {
|
|
91
|
+
const result = await server.get("system.echo")!({});
|
|
92
|
+
expect(result).toEqual({});
|
|
93
|
+
});
|
|
94
|
+
});
|