@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,82 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
export type NotificationLevel = "info" | "warning" | "error";
|
|
4
|
+
|
|
5
|
+
export interface AppNotification {
|
|
6
|
+
id: string;
|
|
7
|
+
message: string;
|
|
8
|
+
level: NotificationLevel;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
read: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface NotificationState {
|
|
15
|
+
notifications: AppNotification[];
|
|
16
|
+
panelOpen: boolean;
|
|
17
|
+
|
|
18
|
+
push: (n: Omit<AppNotification, "id" | "timestamp" | "read">) => void;
|
|
19
|
+
markRead: (id: string) => void;
|
|
20
|
+
markAllRead: () => void;
|
|
21
|
+
dismiss: (id: string) => void;
|
|
22
|
+
clearAll: () => void;
|
|
23
|
+
togglePanel: () => void;
|
|
24
|
+
setPanelOpen: (open: boolean) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useNotificationStore = create<NotificationState>((set) => ({
|
|
28
|
+
notifications: [],
|
|
29
|
+
panelOpen: false,
|
|
30
|
+
|
|
31
|
+
push(n) {
|
|
32
|
+
const entry: AppNotification = {
|
|
33
|
+
...n,
|
|
34
|
+
id: `notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
read: false,
|
|
37
|
+
};
|
|
38
|
+
set((s) => ({
|
|
39
|
+
notifications: [entry, ...s.notifications],
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
if (n.level === "info") {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
set((s) => ({
|
|
45
|
+
notifications: s.notifications.filter((x) => x.id !== entry.id),
|
|
46
|
+
}));
|
|
47
|
+
}, 5000);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
markRead(id) {
|
|
52
|
+
set((s) => ({
|
|
53
|
+
notifications: s.notifications.map((n) =>
|
|
54
|
+
n.id === id ? { ...n, read: true } : n,
|
|
55
|
+
),
|
|
56
|
+
}));
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
markAllRead() {
|
|
60
|
+
set((s) => ({
|
|
61
|
+
notifications: s.notifications.map((n) => ({ ...n, read: true })),
|
|
62
|
+
}));
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
dismiss(id) {
|
|
66
|
+
set((s) => ({
|
|
67
|
+
notifications: s.notifications.filter((n) => n.id !== id),
|
|
68
|
+
}));
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
clearAll() {
|
|
72
|
+
set({ notifications: [] });
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
togglePanel() {
|
|
76
|
+
set((s) => ({ panelOpen: !s.panelOpen }));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
setPanelOpen(open) {
|
|
80
|
+
set({ panelOpen: open });
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
export type SidebarPanelId = never;
|
|
4
|
+
export type Breakpoint = "mobile" | "tablet" | "desktop";
|
|
5
|
+
|
|
6
|
+
const PINNED_KEY = "sidebar-pinned";
|
|
7
|
+
const WIDTH_KEY = "sidebar-width";
|
|
8
|
+
export const SIDEBAR_MIN_WIDTH = 180;
|
|
9
|
+
export const SIDEBAR_MAX_WIDTH = 480;
|
|
10
|
+
const DEFAULT_WIDTH = 240;
|
|
11
|
+
|
|
12
|
+
function getBreakpoint(width: number): Breakpoint {
|
|
13
|
+
if (width < 768) return "mobile";
|
|
14
|
+
if (width < 1024) return "tablet";
|
|
15
|
+
return "desktop";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SidebarState {
|
|
19
|
+
activePanel: SidebarPanelId | null;
|
|
20
|
+
isPinned: boolean;
|
|
21
|
+
drawerOpen: boolean;
|
|
22
|
+
sidebarWidth: number;
|
|
23
|
+
breakpoint: Breakpoint;
|
|
24
|
+
togglePanel: (id: SidebarPanelId) => void;
|
|
25
|
+
setActivePanel: (id: SidebarPanelId | null) => void;
|
|
26
|
+
setPinned: (pinned: boolean) => void;
|
|
27
|
+
setDrawerOpen: (open: boolean) => void;
|
|
28
|
+
setSidebarWidth: (width: number) => void;
|
|
29
|
+
_setBreakpoint: (bp: Breakpoint) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readPinned(): boolean {
|
|
33
|
+
try {
|
|
34
|
+
const stored = localStorage.getItem(PINNED_KEY);
|
|
35
|
+
if (stored !== null) return stored !== "false";
|
|
36
|
+
return window.innerWidth >= 1024;
|
|
37
|
+
} catch {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readWidth(): number {
|
|
43
|
+
try {
|
|
44
|
+
const v = Number(localStorage.getItem(WIDTH_KEY));
|
|
45
|
+
if (Number.isNaN(v) || v < SIDEBAR_MIN_WIDTH || v > SIDEBAR_MAX_WIDTH) return DEFAULT_WIDTH;
|
|
46
|
+
return v;
|
|
47
|
+
} catch {
|
|
48
|
+
return DEFAULT_WIDTH;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const initialWidth = typeof window !== "undefined" ? window.innerWidth : 1024;
|
|
53
|
+
|
|
54
|
+
export const useSidebarStore = create<SidebarState>((set, get) => ({
|
|
55
|
+
activePanel: null,
|
|
56
|
+
isPinned: readPinned(),
|
|
57
|
+
drawerOpen: false,
|
|
58
|
+
sidebarWidth: readWidth(),
|
|
59
|
+
breakpoint: getBreakpoint(initialWidth),
|
|
60
|
+
|
|
61
|
+
togglePanel: (id) => {
|
|
62
|
+
const { activePanel, isPinned } = get();
|
|
63
|
+
if (activePanel === id) {
|
|
64
|
+
set({ activePanel: null, drawerOpen: false });
|
|
65
|
+
} else {
|
|
66
|
+
set({ activePanel: id, drawerOpen: !isPinned });
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
setActivePanel: (id) => set({ activePanel: id }),
|
|
71
|
+
|
|
72
|
+
setPinned: (pinned) => {
|
|
73
|
+
try {
|
|
74
|
+
localStorage.setItem(PINNED_KEY, String(pinned));
|
|
75
|
+
} catch { /* ignore */ }
|
|
76
|
+
set({ isPinned: pinned, drawerOpen: false });
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
setDrawerOpen: (open) => {
|
|
80
|
+
if (!open) {
|
|
81
|
+
const { isPinned } = get();
|
|
82
|
+
if (!isPinned) set({ drawerOpen: false, activePanel: null });
|
|
83
|
+
else set({ drawerOpen: false });
|
|
84
|
+
} else {
|
|
85
|
+
set({ drawerOpen: true });
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
setSidebarWidth: (width) => {
|
|
90
|
+
const clamped = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, width));
|
|
91
|
+
try {
|
|
92
|
+
localStorage.setItem(WIDTH_KEY, String(clamped));
|
|
93
|
+
} catch { /* ignore */ }
|
|
94
|
+
set({ sidebarWidth: clamped });
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
_setBreakpoint: (bp) => set({ breakpoint: bp }),
|
|
98
|
+
}));
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
export type Theme = "light" | "dark";
|
|
4
|
+
|
|
5
|
+
interface ThemeState {
|
|
6
|
+
theme: Theme;
|
|
7
|
+
setTheme: (theme: Theme) => void;
|
|
8
|
+
toggleTheme: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getInitialTheme(): Theme {
|
|
12
|
+
if (typeof window === "undefined") return "dark";
|
|
13
|
+
const stored = localStorage.getItem("theme");
|
|
14
|
+
if (stored === "light" || stored === "dark") return stored;
|
|
15
|
+
return "dark";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function applyTheme(theme: Theme): void {
|
|
19
|
+
if (typeof document === "undefined") return;
|
|
20
|
+
if (theme === "dark") {
|
|
21
|
+
document.documentElement.classList.add("dark");
|
|
22
|
+
} else {
|
|
23
|
+
document.documentElement.classList.remove("dark");
|
|
24
|
+
}
|
|
25
|
+
localStorage.setItem("theme", theme);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useThemeStore = create<ThemeState>((set) => {
|
|
29
|
+
const initial = getInitialTheme();
|
|
30
|
+
applyTheme(initial);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
theme: initial,
|
|
34
|
+
setTheme: (theme) => {
|
|
35
|
+
applyTheme(theme);
|
|
36
|
+
set({ theme });
|
|
37
|
+
},
|
|
38
|
+
toggleTheme: () => {
|
|
39
|
+
set((state) => {
|
|
40
|
+
const next = state.theme === "dark" ? "light" : "dark";
|
|
41
|
+
applyTheme(next);
|
|
42
|
+
return { theme: next };
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MAX_PREVIEW_SIZE = 500 * 1024; // 500KB
|
|
@@ -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,71 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generateReply } from "../handlers/chat";
|
|
3
|
+
|
|
4
|
+
describe("generateReply", () => {
|
|
5
|
+
it("should respond to greetings", () => {
|
|
6
|
+
const reply = generateReply("hello");
|
|
7
|
+
expect(reply).toMatch(/hey|hello|hi/i);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should respond to hi", () => {
|
|
11
|
+
const reply = generateReply("hi there");
|
|
12
|
+
expect(reply).toBeTruthy();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should respond to time queries", () => {
|
|
16
|
+
const reply = generateReply("what time is it?");
|
|
17
|
+
expect(reply).toContain("**");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should respond to date queries", () => {
|
|
21
|
+
const reply = generateReply("what is the date today?");
|
|
22
|
+
expect(reply).toContain("**");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should calculate simple math", () => {
|
|
26
|
+
const reply = generateReply("12 * 8");
|
|
27
|
+
expect(reply).toContain("96");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should calculate addition", () => {
|
|
31
|
+
const reply = generateReply("10 + 5");
|
|
32
|
+
expect(reply).toContain("15");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should calculate subtraction", () => {
|
|
36
|
+
const reply = generateReply("100 - 30");
|
|
37
|
+
expect(reply).toContain("70");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should handle division by zero", () => {
|
|
41
|
+
const reply = generateReply("10 / 0");
|
|
42
|
+
expect(reply).toMatch(/zero|couldn/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should respond to file-related queries", () => {
|
|
46
|
+
const reply = generateReply("show me files");
|
|
47
|
+
expect(reply).toMatch(/file|explorer/i);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should respond to git queries", () => {
|
|
51
|
+
const reply = generateReply("git status");
|
|
52
|
+
expect(reply).toMatch(/git/i);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should respond to help", () => {
|
|
56
|
+
const reply = generateReply("help");
|
|
57
|
+
expect(reply).toMatch(/time|math|help/i);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should return a default reply for unknown input", () => {
|
|
61
|
+
const reply = generateReply("xyzzy random unknown thing");
|
|
62
|
+
expect(reply).toBeTruthy();
|
|
63
|
+
expect(typeof reply).toBe("string");
|
|
64
|
+
expect(reply.length).toBeGreaterThan(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should respond to math with natural language", () => {
|
|
68
|
+
const reply = generateReply("what is 100 / 4");
|
|
69
|
+
expect(reply).toContain("25");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
3
|
+
|
|
4
|
+
type RegisteredHandler = (params: unknown) => Promise<unknown>;
|
|
5
|
+
|
|
6
|
+
function createMockServer() {
|
|
7
|
+
const handlers = new Map<string, RegisteredHandler>();
|
|
8
|
+
const server = {
|
|
9
|
+
register: vi.fn((method: string, handler: RegisteredHandler) => {
|
|
10
|
+
handlers.set(method, handler);
|
|
11
|
+
}),
|
|
12
|
+
emitEvent: vi.fn(),
|
|
13
|
+
} as unknown as RPCServer;
|
|
14
|
+
return { server, handlers };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("system handler", () => {
|
|
18
|
+
let handlers: Map<string, RegisteredHandler>;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
vi.resetModules();
|
|
22
|
+
const { server, handlers: h } = createMockServer();
|
|
23
|
+
handlers = h;
|
|
24
|
+
const { register } = await import("../handlers/system");
|
|
25
|
+
register(server, { platform: "web" });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should register system.ping", () => {
|
|
29
|
+
expect(handlers.has("system.ping")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should register system.hello", () => {
|
|
33
|
+
expect(handlers.has("system.hello")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should register system.echo", () => {
|
|
37
|
+
expect(handlers.has("system.echo")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("system.ping returns pong with timestamp and platform", async () => {
|
|
41
|
+
const result = await handlers.get("system.ping")!({});
|
|
42
|
+
expect(result).toEqual(
|
|
43
|
+
expect.objectContaining({
|
|
44
|
+
pong: true,
|
|
45
|
+
platform: "web",
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
expect((result as { timestamp: number }).timestamp).toBeTypeOf("number");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("system.hello returns greeting with default name", async () => {
|
|
52
|
+
const result = await handlers.get("system.hello")!({});
|
|
53
|
+
expect(result).toEqual(
|
|
54
|
+
expect.objectContaining({ message: "Hello World!" }),
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("system.hello returns greeting with custom name", async () => {
|
|
59
|
+
const result = await handlers.get("system.hello")!({ name: "Alice" });
|
|
60
|
+
expect(result).toEqual(
|
|
61
|
+
expect.objectContaining({ message: "Hello Alice!" }),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("system.echo returns params as-is", async () => {
|
|
66
|
+
const params = { foo: "bar", num: 42 };
|
|
67
|
+
const result = await handlers.get("system.echo")!(params);
|
|
68
|
+
expect(result).toEqual(params);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
2
|
+
import type { MethodParams, MethodResult } from "@dyyz1993/rpc-core";
|
|
3
|
+
import type { RPCMethods, HandlerOptions } from "../rpc-schema";
|
|
4
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { join, dirname } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { createLogger } from "../lib/logger";
|
|
9
|
+
|
|
10
|
+
const log = createLogger("chat");
|
|
11
|
+
|
|
12
|
+
function getStoragePath(): string {
|
|
13
|
+
const dir = join(homedir(), ".pi-agent");
|
|
14
|
+
return join(dir, "chat-history.json");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ChatMessage = { id: string; role: "user" | "assistant"; content: string; timestamp: number };
|
|
18
|
+
|
|
19
|
+
async function loadMessages(): Promise<ChatMessage[]> {
|
|
20
|
+
const filePath = getStoragePath();
|
|
21
|
+
try {
|
|
22
|
+
if (!existsSync(filePath)) {
|
|
23
|
+
log.info(`No history file at ${filePath}`);
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const raw = await readFile(filePath, "utf-8");
|
|
27
|
+
const msgs = JSON.parse(raw) as ChatMessage[];
|
|
28
|
+
log.info(`Loaded ${msgs.length} messages from ${filePath}`);
|
|
29
|
+
return msgs;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
log.error("Failed to load history", { error: err });
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function saveMessages(messages: ChatMessage[]): Promise<void> {
|
|
37
|
+
const filePath = getStoragePath();
|
|
38
|
+
const dir = dirname(filePath);
|
|
39
|
+
try {
|
|
40
|
+
if (!existsSync(dir)) {
|
|
41
|
+
await mkdir(dir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
await writeFile(filePath, JSON.stringify(messages, null, 2), "utf-8");
|
|
44
|
+
log.info(`Saved ${messages.length} messages to ${filePath}`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
log.error("Failed to save history", { error: err });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type RegisterFn = <K extends keyof RPCMethods & string>(
|
|
51
|
+
method: K,
|
|
52
|
+
handler: (params: MethodParams<RPCMethods, K>) => Promise<MethodResult<RPCMethods, K>>,
|
|
53
|
+
) => void;
|
|
54
|
+
|
|
55
|
+
export function generateReply(input: string): string {
|
|
56
|
+
const lower = input.toLowerCase().trim();
|
|
57
|
+
|
|
58
|
+
if (/^(hi|hello|hey|howdy|hola|yo|sup)\b/i.test(lower)) {
|
|
59
|
+
const greetings = [
|
|
60
|
+
"Hey there! How can I help you today?",
|
|
61
|
+
"Hello! Great to see you. What would you like to know?",
|
|
62
|
+
"Hi! I'm your desktop assistant. Ask me anything!",
|
|
63
|
+
];
|
|
64
|
+
return greetings[Math.floor(Math.random() * greetings.length)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (/what('?s| is) the (time|date|day)|current (time|date)|what time|today'?s date/i.test(lower)) {
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const date = now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
|
|
70
|
+
const time = now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
|
71
|
+
return `It's currently **${time}** on **${date}**.`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const mathMatch = lower.match(
|
|
75
|
+
/(?:what(?:'s| is)\s+)?(\d+(?:\.\d+)?)\s*([+\-*/x×÷^])\s*(\d+(?:\.\d+)?)/,
|
|
76
|
+
);
|
|
77
|
+
if (mathMatch) {
|
|
78
|
+
const a = parseFloat(mathMatch[1]);
|
|
79
|
+
const op = mathMatch[2];
|
|
80
|
+
const b = parseFloat(mathMatch[3]);
|
|
81
|
+
let result: number;
|
|
82
|
+
switch (op) {
|
|
83
|
+
case "+": result = a + b; break;
|
|
84
|
+
case "-": result = a - b; break;
|
|
85
|
+
case "*": case "x": case "×": result = a * b; break;
|
|
86
|
+
case "/": case "÷": result = b !== 0 ? a / b : NaN; break;
|
|
87
|
+
case "^": result = Math.pow(a, b); break;
|
|
88
|
+
default: result = NaN;
|
|
89
|
+
}
|
|
90
|
+
if (isNaN(result)) {
|
|
91
|
+
return "Hmm, I couldn't calculate that. Did you try dividing by zero?";
|
|
92
|
+
}
|
|
93
|
+
const niceResult = Number.isInteger(result) ? result.toString() : result.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
94
|
+
return `That would be **${niceResult}**! Need help with anything else?`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (/file|files|browse|explorer|directory|folder|open file/i.test(lower)) {
|
|
98
|
+
return (
|
|
99
|
+
"I can help you explore files! While I can't directly browse files in this demo, " +
|
|
100
|
+
"here's what you can do:\n\n" +
|
|
101
|
+
"- Use the **Explorer** panel to browse your project files\n" +
|
|
102
|
+
"- Click any file to preview its contents\n" +
|
|
103
|
+
"- Use the search feature to find specific files\n\n" +
|
|
104
|
+
"Try asking about **time** or **math** for a live demo!"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (/git|commit|branch|status|diff|push|pull/i.test(lower)) {
|
|
109
|
+
return (
|
|
110
|
+
"Git is a powerful version control system! Here's what I know:\n\n" +
|
|
111
|
+
"- **git status** - Check your current changes\n" +
|
|
112
|
+
"- **git branch** - See or switch branches\n" +
|
|
113
|
+
"- **git log** - View commit history\n" +
|
|
114
|
+
"- **git diff** - See what changed\n\n" +
|
|
115
|
+
"Check out the **Source Control** panel for a visual Git interface! " +
|
|
116
|
+
"Or try asking me about **time** or **math**."
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (/^(help|commands|what can you|what do you|capabilities|features)/i.test(lower)) {
|
|
121
|
+
return (
|
|
122
|
+
"Here's what I can help with:\n\n" +
|
|
123
|
+
"- **Greetings** - Say hi and I'll say hi back!\n" +
|
|
124
|
+
"- **Time & Date** - Ask \"what time is it?\" or \"what's today's date?\"\n" +
|
|
125
|
+
"- **Math** - Give me an expression like \"12 * 8\" or \"what is 100 / 4\"\n" +
|
|
126
|
+
"- **Files** - Ask about file browsing and the explorer\n" +
|
|
127
|
+
"- **Git** - Ask about version control commands\n" +
|
|
128
|
+
"- **Help** - Show this message anytime!\n\n" +
|
|
129
|
+
"This is a demo assistant - try different things to see what sticks!"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const defaults = [
|
|
134
|
+
"That's an interesting question! I'm a demo assistant, so my knowledge is limited - but try asking about **time**, **math**, **files**, or **git**.",
|
|
135
|
+
"I wish I could help with that! For now I can answer questions about time, math, files, and git. Type **help** to see what I can do.",
|
|
136
|
+
"Hmm, I'm not sure about that one. But I *can* do math, tell you the time, and talk about files and git. Give it a shot!",
|
|
137
|
+
];
|
|
138
|
+
return defaults[Math.floor(Math.random() * defaults.length)];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function register(server: RPCServer, _options: HandlerOptions): void {
|
|
142
|
+
const r: RegisterFn = (method, handler) => {
|
|
143
|
+
server.register(method, handler as (params: unknown) => Promise<unknown>);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
r("chat.list", async (params) => {
|
|
147
|
+
const all = await loadMessages();
|
|
148
|
+
const limit = params.limit ?? 50;
|
|
149
|
+
const messages = all.slice(-limit);
|
|
150
|
+
return {
|
|
151
|
+
messages,
|
|
152
|
+
hasMore: all.length > limit,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
r("chat.send", async (params) => {
|
|
157
|
+
const all = await loadMessages();
|
|
158
|
+
|
|
159
|
+
const userMsg: ChatMessage = {
|
|
160
|
+
id: `msg-${Date.now()}-user`,
|
|
161
|
+
role: "user",
|
|
162
|
+
content: params.content,
|
|
163
|
+
timestamp: Date.now(),
|
|
164
|
+
};
|
|
165
|
+
all.push(userMsg);
|
|
166
|
+
|
|
167
|
+
server.emitEvent("chat.message", userMsg, { role: userMsg.role });
|
|
168
|
+
|
|
169
|
+
const thinkDelay = 200 + Math.floor(Math.random() * 300);
|
|
170
|
+
await new Promise((r) => setTimeout(r, thinkDelay));
|
|
171
|
+
|
|
172
|
+
const reply: ChatMessage = {
|
|
173
|
+
id: `msg-${Date.now()}-assistant`,
|
|
174
|
+
role: "assistant",
|
|
175
|
+
content: generateReply(params.content),
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
};
|
|
178
|
+
all.push(reply);
|
|
179
|
+
|
|
180
|
+
server.emitEvent("chat.message", reply, { role: reply.role });
|
|
181
|
+
|
|
182
|
+
await saveMessages(all);
|
|
183
|
+
|
|
184
|
+
return { ok: true };
|
|
185
|
+
});
|
|
186
|
+
}
|