@dyyz1993/create-agent 2.1.0 → 2.1.2
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 +5 -4
- package/src/__tests__/cli.test.ts +2 -0
- package/src/__tests__/copy.test.ts +110 -122
- package/src/__tests__/create-args.test.ts +113 -0
- package/src/__tests__/templates.test.ts +29 -3
- package/src/__tests__/update.test.ts +129 -0
- package/src/cli.ts +30 -32
- package/src/commands/create.ts +83 -24
- package/src/commands/update.ts +174 -0
- package/src/commands/workspace.ts +26 -6
- package/src/lib/copy.ts +233 -93
- package/src/lib/templates.ts +12 -0
- package/templates/agent/.husky/commit-msg +4 -0
- package/templates/agent/.husky/pre-commit +11 -0
- package/templates/agent/.husky/pre-push +6 -0
- package/templates/agent/.prettierignore +6 -0
- package/templates/agent/.prettierrc +9 -0
- package/templates/agent/CHANGELOG.md +15 -0
- package/templates/agent/commitlint.config.js +8 -0
- package/templates/agent/package.json +19 -4
- package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +53 -39
- package/templates/agent/src/mainview/App.tsx +14 -12
- package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +194 -190
- package/templates/agent/src/mainview/components/bash/BashPanel.tsx +210 -189
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +45 -20
- package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +41 -21
- package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +181 -114
- package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +80 -80
- package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +61 -58
- package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +43 -41
- package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +69 -50
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +31 -18
- package/templates/agent/src/mainview/components/git/GitPanel.tsx +731 -490
- package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +157 -139
- package/templates/agent/src/mainview/components/layout/AppLayout.tsx +210 -161
- package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +130 -122
- package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +119 -109
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +123 -117
- package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +64 -44
- package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +38 -38
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +40 -38
- package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +72 -72
- package/templates/agent/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/agent/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/agent/src/mainview/stores/use-app-store.ts +96 -86
- package/templates/agent/src/mainview/stores/use-explorer-store.ts +292 -275
- package/templates/agent/src/mainview/types/index.ts +23 -22
- package/templates/agent/src/mainview/utils/file-icon.tsx +17 -21
- package/templates/agent/src/shared/handlers/chat.ts +53 -53
- package/templates/agent/src/shared/handlers/debug.ts +13 -3
- package/templates/browser-agent/.env.example +19 -0
- package/templates/browser-agent/.husky/commit-msg +4 -0
- package/templates/browser-agent/.husky/pre-commit +11 -0
- package/templates/browser-agent/.husky/pre-push +6 -0
- package/templates/browser-agent/.prettierignore +6 -0
- package/templates/browser-agent/.prettierrc +9 -0
- package/templates/browser-agent/AGENTS.md +396 -0
- package/templates/browser-agent/CHANGELOG.md +15 -0
- package/templates/browser-agent/LICENSE +21 -0
- package/templates/browser-agent/README.md +103 -0
- package/templates/browser-agent/commitlint.config.js +8 -0
- package/templates/browser-agent/electrobun.config.ts +27 -0
- package/templates/browser-agent/electron/main.js +46 -0
- package/templates/browser-agent/electron/preload.js +5 -0
- package/templates/browser-agent/electron-builder.json +40 -0
- package/templates/browser-agent/eslint.config.mjs +79 -0
- package/templates/browser-agent/llms.txt +24 -0
- package/templates/browser-agent/package.json +140 -0
- package/templates/browser-agent/postcss.config.js +6 -0
- package/templates/browser-agent/scripts/dev.ts +138 -0
- package/templates/browser-agent/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/browser-agent/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/browser-agent/src/__tests__/server-config.test.ts +59 -0
- package/templates/browser-agent/src/bun/index.ts +130 -0
- package/templates/browser-agent/src/bun/three.d.ts +1 -0
- package/templates/browser-agent/src/gateway/http-routes.ts +1 -0
- package/templates/browser-agent/src/gateway/ipc-transport.ts +68 -0
- package/templates/browser-agent/src/gateway/sse-transport.ts +221 -0
- package/templates/browser-agent/src/mainview/App.tsx +45 -0
- package/templates/browser-agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
- package/templates/browser-agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
- package/templates/browser-agent/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
- package/templates/browser-agent/src/mainview/__tests__/setup.ts +40 -0
- package/templates/browser-agent/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
- package/templates/browser-agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
- package/templates/browser-agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/browser-agent/src/mainview/components/assets/AssetsPanel.tsx +139 -0
- package/templates/browser-agent/src/mainview/components/chat/ChatPanel.tsx +219 -0
- package/templates/browser-agent/src/mainview/components/chat/CommandBar.tsx +184 -0
- package/templates/browser-agent/src/mainview/components/chat/MessageBubble.tsx +249 -0
- package/templates/browser-agent/src/mainview/components/chat/ToolPicker.tsx +115 -0
- package/templates/browser-agent/src/mainview/components/common/ErrorBoundary.tsx +1 -0
- package/templates/browser-agent/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/browser-agent/src/mainview/components/common/ThemeToggle.tsx +45 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
- package/templates/browser-agent/src/mainview/components/dev/NetworkPanel.tsx +155 -0
- package/templates/browser-agent/src/mainview/components/layout/AppLayout.tsx +311 -0
- package/templates/browser-agent/src/mainview/components/onboarding/SetupWizard.tsx +310 -0
- package/templates/browser-agent/src/mainview/components/process/ProcessPanel.tsx +329 -0
- package/templates/browser-agent/src/mainview/components/sidebar/SessionSidebar.tsx +122 -0
- package/templates/browser-agent/src/mainview/components/sidebar/SkillSidebar.tsx +115 -0
- package/templates/browser-agent/src/mainview/components/topbar/TopBar.tsx +350 -0
- package/templates/browser-agent/src/mainview/global.d.ts +13 -0
- package/templates/browser-agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +151 -0
- package/templates/browser-agent/src/mainview/hooks/use-agent-chat.ts +157 -0
- package/templates/browser-agent/src/mainview/hooks/use-breakpoint.ts +68 -0
- package/templates/browser-agent/src/mainview/hooks/use-rpc-init.ts +71 -0
- package/templates/browser-agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/browser-agent/src/mainview/index.css +71 -0
- package/templates/browser-agent/src/mainview/index.html +12 -0
- package/templates/browser-agent/src/mainview/lib/api-client.ts +397 -0
- package/templates/browser-agent/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/browser-agent/src/mainview/lib/i18n/locales/en.json +130 -0
- package/templates/browser-agent/src/mainview/lib/i18n/locales/zh.json +128 -0
- package/templates/browser-agent/src/mainview/lib/network-bus.ts +92 -0
- package/templates/browser-agent/src/mainview/lib/rpc-cache.ts +94 -0
- package/templates/browser-agent/src/mainview/main.tsx +18 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/browser-agent/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/browser-agent/src/mainview/stores/use-asset-store.ts +65 -0
- package/templates/browser-agent/src/mainview/stores/use-chat-store.ts +200 -0
- package/templates/browser-agent/src/mainview/stores/use-connection-store.ts +173 -0
- package/templates/browser-agent/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/browser-agent/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/browser-agent/src/mainview/stores/use-network-panel-store.ts +18 -0
- package/templates/browser-agent/src/mainview/stores/use-record-store.ts +147 -0
- package/templates/browser-agent/src/mainview/stores/use-session-store.ts +151 -0
- package/templates/browser-agent/src/mainview/stores/use-sidebar-store.ts +161 -0
- package/templates/browser-agent/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/browser-agent/src/mainview/stores/use-view-store.ts +20 -0
- package/templates/browser-agent/src/mainview/types/index.ts +6 -0
- package/templates/browser-agent/src/server-config.ts +45 -0
- package/templates/browser-agent/src/server.ts +78 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
- package/templates/browser-agent/src/shared/handlers/browser.ts +596 -0
- package/templates/browser-agent/src/shared/handlers/chat.ts +213 -0
- package/templates/browser-agent/src/shared/handlers/file.ts +99 -0
- package/templates/browser-agent/src/shared/handlers/index.ts +7 -0
- package/templates/browser-agent/src/shared/handlers/session.ts +167 -0
- package/templates/browser-agent/src/shared/handlers/system.ts +27 -0
- package/templates/browser-agent/src/shared/handlers/timer.ts +34 -0
- package/templates/browser-agent/src/shared/http-routes.ts +437 -0
- package/templates/browser-agent/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/browser-agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
- package/templates/browser-agent/src/shared/lib/__tests__/web-server.test.ts +203 -0
- package/templates/browser-agent/src/shared/lib/agent.ts +384 -0
- package/templates/browser-agent/src/shared/lib/cdp.ts +448 -0
- package/templates/browser-agent/src/shared/lib/generate.ts +111 -0
- package/templates/browser-agent/src/shared/lib/logger.ts +152 -0
- package/templates/browser-agent/src/shared/lib/mock-stream.ts +147 -0
- package/templates/browser-agent/src/shared/lib/path-security.ts +38 -0
- package/templates/browser-agent/src/shared/lib/port-registry.ts +103 -0
- package/templates/browser-agent/src/shared/lib/web-server.ts +127 -0
- package/templates/browser-agent/src/shared/lib/xhs-extract.ts +71 -0
- package/templates/browser-agent/src/shared/modules/browser.ts +86 -0
- package/templates/browser-agent/src/shared/modules/chat.ts +20 -0
- package/templates/browser-agent/src/shared/modules/file.ts +40 -0
- package/templates/browser-agent/src/shared/modules/session.ts +55 -0
- package/templates/browser-agent/src/shared/modules/system.ts +8 -0
- package/templates/browser-agent/src/shared/modules/timer.ts +11 -0
- package/templates/browser-agent/src/shared/register-all-handlers.ts +36 -0
- package/templates/browser-agent/src/shared/rpc-schema.ts +34 -0
- package/templates/browser-agent/tailwind.config.js +15 -0
- package/templates/browser-agent/tsconfig.ipc.json +14 -0
- package/templates/browser-agent/tsconfig.json +36 -0
- package/templates/browser-agent/vite.config.ts +3 -0
- package/templates/browser-agent/vitest.config.ts +3 -0
- package/templates/chat/.husky/commit-msg +4 -0
- package/templates/chat/.husky/pre-commit +11 -0
- package/templates/chat/.husky/pre-push +6 -0
- package/templates/chat/CHANGELOG.md +15 -0
- package/templates/chat/commitlint.config.js +8 -0
- package/templates/chat/package.json +19 -4
- package/templates/chat/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
- package/templates/chat/src/mainview/__tests__/setup.ts +32 -29
- package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +80 -55
- package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +40 -20
- package/templates/chat/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/chat/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/chat/src/shared/handlers/chat.ts +17 -19
- package/templates/chat/src/shared/handlers/debug.ts +12 -2
- package/templates/cowork/.env.example +8 -0
- package/templates/cowork/.prettierignore +6 -0
- package/templates/cowork/.prettierrc +9 -0
- package/templates/cowork/AGENTS.md +236 -0
- package/templates/cowork/CHANGELOG.md +15 -0
- package/templates/cowork/LICENSE +21 -0
- package/templates/cowork/README.md +103 -0
- package/templates/cowork/commitlint.config.js +8 -0
- package/templates/cowork/docs/CODE-VIEW-PLAN.md +637 -0
- package/templates/cowork/docs/CODE-VIEW-V2.md +196 -0
- package/templates/cowork/docs/CODE-VIEW-V4.md +150 -0
- package/templates/cowork/electrobun.config.ts +27 -0
- package/templates/cowork/eslint.config.mjs +79 -0
- package/templates/cowork/llms.txt +24 -0
- package/templates/cowork/package.json +87 -0
- package/templates/cowork/postcss.config.js +6 -0
- package/templates/cowork/scripts/dev.ts +138 -0
- package/templates/cowork/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/cowork/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/cowork/src/__tests__/server-config.test.ts +59 -0
- package/templates/cowork/src/bun/index.ts +130 -0
- package/templates/cowork/src/bun/three.d.ts +1 -0
- package/templates/cowork/src/gateway/http-routes.ts +1 -0
- package/templates/cowork/src/gateway/ipc-transport.ts +68 -0
- package/templates/cowork/src/gateway/sse-transport.ts +231 -0
- package/templates/cowork/src/mainview/App.tsx +45 -0
- package/templates/cowork/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
- package/templates/cowork/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
- package/templates/cowork/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
- package/templates/cowork/src/mainview/__tests__/setup.ts +40 -0
- package/templates/cowork/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
- package/templates/cowork/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
- package/templates/cowork/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/cowork/src/mainview/components/chat/TaskChat.tsx +311 -0
- package/templates/cowork/src/mainview/components/chat/__tests__/localhost-links.test.ts +76 -0
- package/templates/cowork/src/mainview/components/common/ErrorBoundary.tsx +1 -0
- package/templates/cowork/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/cowork/src/mainview/components/common/ThemeToggle.tsx +45 -0
- package/templates/cowork/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
- package/templates/cowork/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
- package/templates/cowork/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
- package/templates/cowork/src/mainview/components/dev/NetworkPanel.tsx +155 -0
- package/templates/cowork/src/mainview/components/layout/AppLayout.tsx +216 -0
- package/templates/cowork/src/mainview/components/right/ArtifactsPanel.tsx +54 -0
- package/templates/cowork/src/mainview/components/right/ContextPanel.tsx +133 -0
- package/templates/cowork/src/mainview/components/right/ElementPicker.tsx +206 -0
- package/templates/cowork/src/mainview/components/right/PreviewBlock.tsx +155 -0
- package/templates/cowork/src/mainview/components/right/ProgressPanel.tsx +85 -0
- package/templates/cowork/src/mainview/components/sidebar/TaskSidebar.tsx +211 -0
- package/templates/cowork/src/mainview/components/topbar/TopBar.tsx +86 -0
- package/templates/cowork/src/mainview/global.d.ts +13 -0
- package/templates/cowork/src/mainview/hooks/__tests__/use-breakpoint.test.ts +123 -0
- package/templates/cowork/src/mainview/hooks/use-breakpoint.ts +53 -0
- package/templates/cowork/src/mainview/hooks/use-rpc-init.ts +26 -0
- package/templates/cowork/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/cowork/src/mainview/index.css +89 -0
- package/templates/cowork/src/mainview/index.html +12 -0
- package/templates/cowork/src/mainview/lib/api-client.ts +404 -0
- package/templates/cowork/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/cowork/src/mainview/lib/i18n/locales/en.json +130 -0
- package/templates/cowork/src/mainview/lib/i18n/locales/zh.json +128 -0
- package/templates/cowork/src/mainview/lib/network-bus.ts +92 -0
- package/templates/cowork/src/mainview/lib/rpc-cache.ts +94 -0
- package/templates/cowork/src/mainview/main.tsx +18 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-preview-store.test.ts +193 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-sidebar-store.test.ts +81 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/cowork/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/cowork/src/mainview/stores/use-chat-store.ts +198 -0
- package/templates/cowork/src/mainview/stores/use-connection-store.ts +168 -0
- package/templates/cowork/src/mainview/stores/use-context-store.ts +68 -0
- package/templates/cowork/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/cowork/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/cowork/src/mainview/stores/use-network-panel-store.ts +18 -0
- package/templates/cowork/src/mainview/stores/use-output-store.ts +47 -0
- package/templates/cowork/src/mainview/stores/use-preview-store.ts +97 -0
- package/templates/cowork/src/mainview/stores/use-sidebar-store.ts +272 -0
- package/templates/cowork/src/mainview/stores/use-task-store.ts +86 -0
- package/templates/cowork/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/cowork/src/mainview/stores/use-view-store.ts +29 -0
- package/templates/cowork/src/mainview/types/index.ts +6 -0
- package/templates/cowork/src/server-config.ts +44 -0
- package/templates/cowork/src/server.ts +78 -0
- package/templates/cowork/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/cowork/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
- package/templates/cowork/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
- package/templates/cowork/src/shared/handlers/__tests__/preview-handler.test.ts +172 -0
- package/templates/cowork/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
- package/templates/cowork/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
- package/templates/cowork/src/shared/handlers/chat.ts +213 -0
- package/templates/cowork/src/shared/handlers/context.ts +72 -0
- package/templates/cowork/src/shared/handlers/file.ts +99 -0
- package/templates/cowork/src/shared/handlers/index.ts +9 -0
- package/templates/cowork/src/shared/handlers/output.ts +59 -0
- package/templates/cowork/src/shared/handlers/preview.ts +81 -0
- package/templates/cowork/src/shared/handlers/system.ts +27 -0
- package/templates/cowork/src/shared/handlers/task.ts +101 -0
- package/templates/cowork/src/shared/handlers/timer.ts +34 -0
- package/templates/cowork/src/shared/http-routes.ts +444 -0
- package/templates/cowork/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/cowork/src/shared/lib/__tests__/path-security.test.ts +77 -0
- package/templates/cowork/src/shared/lib/__tests__/web-server.test.ts +203 -0
- package/templates/cowork/src/shared/lib/logger.ts +152 -0
- package/templates/cowork/src/shared/lib/path-security.ts +38 -0
- package/templates/cowork/src/shared/lib/port-registry.ts +103 -0
- package/templates/cowork/src/shared/lib/web-server.ts +127 -0
- package/templates/cowork/src/shared/modules/chat.ts +20 -0
- package/templates/cowork/src/shared/modules/context.ts +32 -0
- package/templates/cowork/src/shared/modules/file.ts +40 -0
- package/templates/cowork/src/shared/modules/output.ts +20 -0
- package/templates/cowork/src/shared/modules/preview.ts +44 -0
- package/templates/cowork/src/shared/modules/system.ts +8 -0
- package/templates/cowork/src/shared/modules/task.ts +31 -0
- package/templates/cowork/src/shared/modules/timer.ts +11 -0
- package/templates/cowork/src/shared/register-all-handlers.ts +36 -0
- package/templates/cowork/src/shared/rpc-schema.ts +38 -0
- package/templates/cowork/tailwind.config.js +15 -0
- package/templates/cowork/test-upload.txt +0 -0
- package/templates/cowork/tsconfig.ipc.json +14 -0
- package/templates/cowork/tsconfig.json +36 -0
- package/templates/cowork/vite.config.ts +3 -0
- package/templates/cowork/vitest.config.ts +3 -0
- package/templates/general/.husky/commit-msg +4 -0
- package/templates/general/.husky/pre-commit +11 -0
- package/templates/general/.husky/pre-push +6 -0
- package/templates/general/CHANGELOG.md +15 -0
- package/templates/general/commitlint.config.js +8 -0
- package/templates/general/package.json +19 -4
- package/templates/general/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
- package/templates/general/src/mainview/__tests__/setup.ts +32 -29
- package/templates/general/src/mainview/components/chat/ChatPanel.tsx +80 -55
- package/templates/general/src/mainview/components/common/ThemeToggle.tsx +40 -20
- package/templates/general/src/mainview/components/debug/DebugPanel.tsx +170 -156
- package/templates/general/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +169 -0
- package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +42 -42
- package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +72 -67
- package/templates/general/src/mainview/components/feed/FeedPanel.tsx +7 -5
- package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +64 -45
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +24 -7
- package/templates/general/src/mainview/components/git/GitPanel.tsx +700 -473
- package/templates/general/src/mainview/components/layout/AppLayout.tsx +123 -114
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +15 -19
- package/templates/general/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/general/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/general/src/mainview/stores/__tests__/use-explorer-store.test.ts +259 -0
- package/templates/general/src/mainview/stores/__tests__/use-feed-store.test.ts +160 -0
- package/templates/general/src/mainview/stores/__tests__/use-git-store.test.ts +313 -0
- package/templates/general/src/mainview/stores/__tests__/use-notification-store.test.ts +115 -0
- package/templates/general/src/mainview/stores/__tests__/use-sidebar-store.test.ts +120 -0
- package/templates/general/src/mainview/stores/use-explorer-store.ts +277 -260
- package/templates/general/src/mainview/types/index.ts +21 -20
- package/templates/general/src/shared/handlers/chat.ts +1 -1
- package/templates/general/src/shared/handlers/debug.ts +12 -2
- package/templates/shared/components/ErrorBoundary.tsx +0 -1
- package/templates/shared/vite-base.config.ts +8 -7
- /package/templates/{shared → browser-agent}/test-upload.txt +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("../../lib/logger", () => ({
|
|
4
|
+
createLogger: () => ({
|
|
5
|
+
info: vi.fn(),
|
|
6
|
+
error: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
debug: vi.fn(),
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("Chat Handler", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetModules();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("generateReply logic", () => {
|
|
18
|
+
it("should handle greeting patterns", () => {
|
|
19
|
+
const greetingPattern = /^(hi|hello|hey|howdy|hola|yo|sup)\b/i;
|
|
20
|
+
expect(greetingPattern.test("hello")).toBe(true);
|
|
21
|
+
expect(greetingPattern.test("hi there")).toBe(true);
|
|
22
|
+
expect(greetingPattern.test("what is this")).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should match time/date queries", () => {
|
|
26
|
+
const timePattern = /what('?s| is) the (time|date|day)|current (time|date)|what time|today'?s date/i;
|
|
27
|
+
expect(timePattern.test("what's the time")).toBe(true);
|
|
28
|
+
expect(timePattern.test("what is the date")).toBe(true);
|
|
29
|
+
expect(timePattern.test("current time")).toBe(true);
|
|
30
|
+
expect(timePattern.test("hello world")).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should match math expressions", () => {
|
|
34
|
+
const mathPattern = /(?:what(?:'s| is)\s+)?(\d+(?:\.\d+)?)\s*([+\-*/x×÷^])\s*(\d+(?:\.\d+)?)/;
|
|
35
|
+
expect(mathPattern.test("12 * 8")).toBe(true);
|
|
36
|
+
expect(mathPattern.test("what is 100 / 4")).toBe(true);
|
|
37
|
+
expect(mathPattern.test("3 + 5")).toBe(true);
|
|
38
|
+
expect(mathPattern.test("hello")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should compute math correctly", () => {
|
|
42
|
+
const compute = (a: number, op: string, b: number): number => {
|
|
43
|
+
switch (op) {
|
|
44
|
+
case "+": return a + b;
|
|
45
|
+
case "-": return a - b;
|
|
46
|
+
case "*": return a * b;
|
|
47
|
+
case "/": return b !== 0 ? a / b : NaN;
|
|
48
|
+
default: return NaN;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(compute(3, "+", 5)).toBe(8);
|
|
53
|
+
expect(compute(10, "-", 4)).toBe(6);
|
|
54
|
+
expect(compute(6, "*", 7)).toBe(42);
|
|
55
|
+
expect(compute(20, "/", 4)).toBe(5);
|
|
56
|
+
expect(compute(1, "/", 0)).toBeNaN();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("register", () => {
|
|
61
|
+
it("should register chat.list and chat.send methods", async () => {
|
|
62
|
+
const registeredMethods: string[] = [];
|
|
63
|
+
const mockServer = {
|
|
64
|
+
register: vi.fn((method: string) => {
|
|
65
|
+
registeredMethods.push(method);
|
|
66
|
+
}),
|
|
67
|
+
emitEvent: vi.fn(),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const { register } = await import("../chat");
|
|
71
|
+
register(mockServer as any, { platform: "web" });
|
|
72
|
+
|
|
73
|
+
expect(registeredMethods).toContain("chat.list");
|
|
74
|
+
expect(registeredMethods).toContain("chat.send");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("../../lib/logger", () => ({
|
|
4
|
+
createLogger: () => ({
|
|
5
|
+
info: vi.fn(),
|
|
6
|
+
error: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
debug: vi.fn(),
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("File Handler", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetModules();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("path-security integration", () => {
|
|
18
|
+
it("validatePath should reject null bytes", async () => {
|
|
19
|
+
const { validatePath } = await import("../../lib/path-security");
|
|
20
|
+
expect(() => validatePath("/some/path\0/evil")).toThrow(
|
|
21
|
+
"Access denied: path contains null bytes",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("validatePath should reject paths outside allowed roots", async () => {
|
|
26
|
+
const { validatePath, setAllowedRoots } = await import("../../lib/path-security");
|
|
27
|
+
setAllowedRoots(["/safe/dir"]);
|
|
28
|
+
expect(() => validatePath("/etc/passwd")).toThrow(
|
|
29
|
+
"Access denied",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("validatePath should accept paths within allowed roots", async () => {
|
|
34
|
+
const { validatePath, setAllowedRoots } = await import("../../lib/path-security");
|
|
35
|
+
setAllowedRoots(["/tmp"]);
|
|
36
|
+
const result = validatePath("/tmp/test.txt");
|
|
37
|
+
expect(result).toContain("/tmp");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("isRpcPathAllowed should return false for null bytes", async () => {
|
|
41
|
+
const { isRpcPathAllowed } = await import("../../lib/path-security");
|
|
42
|
+
expect(isRpcPathAllowed("/some\0path")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("register", () => {
|
|
47
|
+
it("should register all file.* methods", async () => {
|
|
48
|
+
const registeredMethods: string[] = [];
|
|
49
|
+
const mockServer = {
|
|
50
|
+
register: vi.fn((method: string) => {
|
|
51
|
+
registeredMethods.push(method);
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
vi.doMock("fs/promises", async (importOriginal) => {
|
|
56
|
+
const actual = await importOriginal() as Record<string, unknown>;
|
|
57
|
+
return {
|
|
58
|
+
...actual,
|
|
59
|
+
readdir: vi.fn().mockResolvedValue([]),
|
|
60
|
+
stat: vi.fn().mockResolvedValue({ isDirectory: () => true, size: 0 }),
|
|
61
|
+
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
62
|
+
readFile: vi.fn().mockResolvedValue(Buffer.from("")),
|
|
63
|
+
mkdir: vi.fn().mockResolvedValue(undefined),
|
|
64
|
+
rename: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
rm: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
cp: vi.fn().mockResolvedValue(undefined),
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
vi.doMock("fs", async (importOriginal) => {
|
|
71
|
+
const actual = await importOriginal() as Record<string, unknown>;
|
|
72
|
+
return {
|
|
73
|
+
...actual,
|
|
74
|
+
existsSync: vi.fn().mockReturnValue(true),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const { setAllowedRoots } = await import("../../lib/path-security");
|
|
79
|
+
setAllowedRoots(["/tmp"]);
|
|
80
|
+
|
|
81
|
+
const { register } = await import("../file");
|
|
82
|
+
register(mockServer as any, { platform: "web" });
|
|
83
|
+
|
|
84
|
+
const expectedMethods = [
|
|
85
|
+
"file.findProjectRoot",
|
|
86
|
+
"file.listDir",
|
|
87
|
+
"file.createFile",
|
|
88
|
+
"file.createDir",
|
|
89
|
+
"file.rename",
|
|
90
|
+
"file.delete",
|
|
91
|
+
"file.copy",
|
|
92
|
+
"file.readFile",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const method of expectedMethods) {
|
|
96
|
+
expect(registeredMethods).toContain(method);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { RPCServer } from '@dyyz1993/rpc-core';
|
|
3
|
+
|
|
4
|
+
describe('Preview Handler', () => {
|
|
5
|
+
let registeredHandlers: Record<string, (params: unknown) => Promise<unknown>>;
|
|
6
|
+
let emittedEvents: { event: string; payload: unknown }[];
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
vi.resetModules();
|
|
10
|
+
registeredHandlers = {};
|
|
11
|
+
emittedEvents = [];
|
|
12
|
+
|
|
13
|
+
const mockServer = {
|
|
14
|
+
register: vi.fn((method: string, handler: Function) => {
|
|
15
|
+
registeredHandlers[method] = handler as (params: unknown) => Promise<unknown>;
|
|
16
|
+
}),
|
|
17
|
+
emitEvent: vi.fn((event: string, payload: unknown) => {
|
|
18
|
+
emittedEvents.push({ event, payload });
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const { register } = await import('../preview');
|
|
23
|
+
register(mockServer as unknown as RPCServer, { platform: 'web' });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should register all preview methods', () => {
|
|
27
|
+
expect(registeredHandlers['preview.open']).toBeDefined();
|
|
28
|
+
expect(registeredHandlers['preview.navigate']).toBeDefined();
|
|
29
|
+
expect(registeredHandlers['preview.close']).toBeDefined();
|
|
30
|
+
expect(registeredHandlers['preview.list']).toBeDefined();
|
|
31
|
+
expect(registeredHandlers['preview.history']).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('preview.open should return a tab with useIframe=true on web platform', async () => {
|
|
35
|
+
const result = (await registeredHandlers['preview.open']({
|
|
36
|
+
url: 'http://localhost:5173',
|
|
37
|
+
})) as { tab: { id: string; url: string; state: string }; useIframe: boolean };
|
|
38
|
+
|
|
39
|
+
expect(result.tab).toBeDefined();
|
|
40
|
+
expect(result.tab.url).toBe('http://localhost:5173');
|
|
41
|
+
expect(result.tab.state).toBe('ready');
|
|
42
|
+
expect(result.useIframe).toBe(true);
|
|
43
|
+
expect(result.tab.id).toMatch(/^preview-/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('preview.open should emit navState event', async () => {
|
|
47
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
48
|
+
|
|
49
|
+
expect(emittedEvents.length).toBeGreaterThan(0);
|
|
50
|
+
expect(emittedEvents[0].event).toBe('preview.navState');
|
|
51
|
+
const payload = emittedEvents[0].payload as { url: string; state: string };
|
|
52
|
+
expect(payload.url).toBe('http://localhost:5173');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('preview.open should accept custom tabId', async () => {
|
|
56
|
+
const result = (await registeredHandlers['preview.open']({
|
|
57
|
+
url: 'http://localhost:3000',
|
|
58
|
+
tabId: 'custom-tab-1',
|
|
59
|
+
})) as { tab: { id: string } };
|
|
60
|
+
|
|
61
|
+
expect(result.tab.id).toBe('custom-tab-1');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('preview.open should record URL in history', async () => {
|
|
65
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
66
|
+
const historyResult = (await registeredHandlers['preview.history']({})) as {
|
|
67
|
+
urls: string[];
|
|
68
|
+
};
|
|
69
|
+
expect(historyResult.urls).toContain('http://localhost:5173');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('preview.navigate should emit loading then ready', async () => {
|
|
73
|
+
const openResult = (await registeredHandlers['preview.open']({
|
|
74
|
+
url: 'http://localhost:5173',
|
|
75
|
+
})) as { tab: { id: string } };
|
|
76
|
+
const tabId = openResult.tab.id;
|
|
77
|
+
|
|
78
|
+
emittedEvents.length = 0;
|
|
79
|
+
await registeredHandlers['preview.navigate']({ tabId, action: 'reload' });
|
|
80
|
+
|
|
81
|
+
// Should emit loading immediately
|
|
82
|
+
expect(emittedEvents[0].payload).toMatchObject({ state: 'loading' });
|
|
83
|
+
|
|
84
|
+
// Wait for setTimeout to fire ready state
|
|
85
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
86
|
+
const readyEvent = emittedEvents.find(
|
|
87
|
+
(e) => (e.payload as { state: string }).state === 'ready',
|
|
88
|
+
);
|
|
89
|
+
expect(readyEvent).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('preview.navigate should throw for unknown tabId', async () => {
|
|
93
|
+
await expect(
|
|
94
|
+
registeredHandlers['preview.navigate']({ tabId: 'nonexistent', action: 'reload' }),
|
|
95
|
+
).rejects.toThrow('Tab not found');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('preview.close should remove the tab', async () => {
|
|
99
|
+
const openResult = (await registeredHandlers['preview.open']({
|
|
100
|
+
url: 'http://localhost:5173',
|
|
101
|
+
})) as { tab: { id: string } };
|
|
102
|
+
const tabId = openResult.tab.id;
|
|
103
|
+
|
|
104
|
+
const closeResult = (await registeredHandlers['preview.close']({ tabId })) as {
|
|
105
|
+
ok: boolean;
|
|
106
|
+
};
|
|
107
|
+
expect(closeResult.ok).toBe(true);
|
|
108
|
+
|
|
109
|
+
const listResult = (await registeredHandlers['preview.list']({})) as {
|
|
110
|
+
tabs: { id: string }[];
|
|
111
|
+
};
|
|
112
|
+
expect(listResult.tabs.find((t) => t.id === tabId)).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('preview.list should return all open tabs', async () => {
|
|
116
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
117
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:3000' });
|
|
118
|
+
|
|
119
|
+
const result = (await registeredHandlers['preview.list']({})) as {
|
|
120
|
+
tabs: { id: string; url: string }[];
|
|
121
|
+
};
|
|
122
|
+
expect(result.tabs).toHaveLength(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('preview.history should not exceed 20 entries', async () => {
|
|
126
|
+
for (let i = 0; i < 25; i++) {
|
|
127
|
+
await registeredHandlers['preview.open']({ url: `http://localhost:${3000 + i}` });
|
|
128
|
+
}
|
|
129
|
+
const result = (await registeredHandlers['preview.history']({})) as {
|
|
130
|
+
urls: string[];
|
|
131
|
+
};
|
|
132
|
+
expect(result.urls.length).toBeLessThanOrEqual(20);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('preview.history should deduplicate URLs', async () => {
|
|
136
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
137
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
138
|
+
await registeredHandlers['preview.open']({ url: 'http://localhost:5173' });
|
|
139
|
+
|
|
140
|
+
const result = (await registeredHandlers['preview.history']({})) as {
|
|
141
|
+
urls: string[];
|
|
142
|
+
};
|
|
143
|
+
expect(result.urls.filter((u) => u === 'http://localhost:5173')).toHaveLength(1);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Preview Handler (Desktop Platform)', () => {
|
|
148
|
+
let registeredHandlers: Record<string, (params: unknown) => Promise<unknown>>;
|
|
149
|
+
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
vi.resetModules();
|
|
152
|
+
registeredHandlers = {};
|
|
153
|
+
|
|
154
|
+
const mockServer = {
|
|
155
|
+
register: vi.fn((method: string, handler: Function) => {
|
|
156
|
+
registeredHandlers[method] = handler as (params: unknown) => Promise<unknown>;
|
|
157
|
+
}),
|
|
158
|
+
emitEvent: vi.fn(),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const { register } = await import('../preview');
|
|
162
|
+
register(mockServer as unknown as RPCServer, { platform: 'desktop' });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('preview.open should return useIframe=false on desktop platform', async () => {
|
|
166
|
+
const result = (await registeredHandlers['preview.open']({
|
|
167
|
+
url: 'http://localhost:5173',
|
|
168
|
+
})) as { useIframe: boolean };
|
|
169
|
+
|
|
170
|
+
expect(result.useIframe).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
3
|
+
|
|
4
|
+
describe("System Handler", () => {
|
|
5
|
+
let mockServer: { register: ReturnType<typeof vi.fn> };
|
|
6
|
+
let registeredHandlers: Record<string, Function>;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
vi.resetModules();
|
|
10
|
+
registeredHandlers = {};
|
|
11
|
+
mockServer = {
|
|
12
|
+
register: vi.fn((method: string, handler: Function) => {
|
|
13
|
+
registeredHandlers[method] = handler;
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
const { register } = await import("../system");
|
|
17
|
+
register(mockServer as unknown as RPCServer, { platform: "web" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should register system methods", () => {
|
|
21
|
+
expect(registeredHandlers["system.ping"]).toBeDefined();
|
|
22
|
+
expect(registeredHandlers["system.hello"]).toBeDefined();
|
|
23
|
+
expect(registeredHandlers["system.echo"]).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("system.ping should return pong with timestamp and platform", async () => {
|
|
27
|
+
const result = await registeredHandlers["system.ping"]({});
|
|
28
|
+
expect(result).toHaveProperty("pong", true);
|
|
29
|
+
expect(result).toHaveProperty("timestamp");
|
|
30
|
+
expect(result).toHaveProperty("platform", "web");
|
|
31
|
+
expect(typeof result.timestamp).toBe("number");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("system.hello should return greeting with provided name", async () => {
|
|
35
|
+
const result = await registeredHandlers["system.hello"]({ name: "World" });
|
|
36
|
+
expect(result.message).toContain("World");
|
|
37
|
+
expect(result.message).toBe("Hello World!");
|
|
38
|
+
expect(result).toHaveProperty("timestamp");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("system.hello should default to 'World'", async () => {
|
|
42
|
+
const result = await registeredHandlers["system.hello"]({});
|
|
43
|
+
expect(result.message).toBe("Hello World!");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("system.echo should return input params", async () => {
|
|
47
|
+
const result = await registeredHandlers["system.echo"]({ data: "test", num: 42 });
|
|
48
|
+
expect(result).toEqual({ data: "test", num: 42 });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("system.echo should return empty object for empty params", async () => {
|
|
52
|
+
const result = await registeredHandlers["system.echo"]({});
|
|
53
|
+
expect(result).toEqual({});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import type { RPCServer } from "@dyyz1993/rpc-core";
|
|
3
|
+
|
|
4
|
+
describe("Timer Handler", () => {
|
|
5
|
+
let registeredHandlers: Record<string, Function>;
|
|
6
|
+
let mockServer: {
|
|
7
|
+
register: ReturnType<typeof vi.fn>;
|
|
8
|
+
emitEvent: ReturnType<typeof vi.fn>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
vi.useFakeTimers();
|
|
13
|
+
vi.resetModules();
|
|
14
|
+
registeredHandlers = {};
|
|
15
|
+
mockServer = {
|
|
16
|
+
register: vi.fn((method: string, handler: Function) => {
|
|
17
|
+
registeredHandlers[method] = handler;
|
|
18
|
+
}),
|
|
19
|
+
emitEvent: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
const { register } = await import("../timer");
|
|
22
|
+
register(mockServer as unknown as RPCServer, { platform: "web" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should register timer.start and timer.stop", () => {
|
|
30
|
+
expect(registeredHandlers["timer.start"]).toBeDefined();
|
|
31
|
+
expect(registeredHandlers["timer.stop"]).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("timer.start should return started: true", async () => {
|
|
35
|
+
const result = await registeredHandlers["timer.start"]({});
|
|
36
|
+
expect(result).toEqual({ started: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("timer.start should return alreadyRunning: true if called twice", async () => {
|
|
40
|
+
await registeredHandlers["timer.start"]({});
|
|
41
|
+
const result = await registeredHandlers["timer.start"]({});
|
|
42
|
+
expect(result).toEqual({ alreadyRunning: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("timer.start should emit timer.tick events on interval", async () => {
|
|
46
|
+
await registeredHandlers["timer.start"]({});
|
|
47
|
+
vi.advanceTimersByTime(3500);
|
|
48
|
+
expect(mockServer.emitEvent).toHaveBeenCalledTimes(3);
|
|
49
|
+
expect(mockServer.emitEvent).toHaveBeenCalledWith(
|
|
50
|
+
"timer.tick",
|
|
51
|
+
expect.objectContaining({ count: expect.any(Number), timestamp: expect.any(Number) }),
|
|
52
|
+
{ channel: "default" },
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("timer.stop should return stopped: true", async () => {
|
|
57
|
+
await registeredHandlers["timer.start"]({});
|
|
58
|
+
const result = await registeredHandlers["timer.stop"]({});
|
|
59
|
+
expect(result).toEqual({ stopped: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("timer.stop should stop emitting tick events", async () => {
|
|
63
|
+
await registeredHandlers["timer.start"]({});
|
|
64
|
+
vi.advanceTimersByTime(1500);
|
|
65
|
+
const ticksBefore = mockServer.emitEvent.mock.calls.length;
|
|
66
|
+
|
|
67
|
+
await registeredHandlers["timer.stop"]({});
|
|
68
|
+
vi.advanceTimersByTime(3000);
|
|
69
|
+
|
|
70
|
+
expect(mockServer.emitEvent.mock.calls.length).toBe(ticksBefore);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("timer.stop without start should still return stopped: true", async () => {
|
|
74
|
+
const result = await registeredHandlers["timer.stop"]({});
|
|
75
|
+
expect(result).toEqual({ stopped: true });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
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
|
+
type Platform = 'desktop' | 'web';
|
|
13
|
+
|
|
14
|
+
export function getStoragePathFor(platform: Platform): string {
|
|
15
|
+
const dir = join(homedir(), '.pi-agent');
|
|
16
|
+
if (platform === 'desktop') {
|
|
17
|
+
return join(dir, 'chat-history-desktop.json');
|
|
18
|
+
}
|
|
19
|
+
const sessionId = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
|
|
20
|
+
return join(dir, `chat-history-web-${sessionId}.json`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ChatMessage = { id: string; role: 'user' | 'assistant'; content: string; timestamp: number };
|
|
24
|
+
|
|
25
|
+
function createMessageStore(filePath: string) {
|
|
26
|
+
async function load(): Promise<ChatMessage[]> {
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(filePath)) {
|
|
29
|
+
log.info(`No history file at ${filePath}`);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
33
|
+
const msgs = JSON.parse(raw) as ChatMessage[];
|
|
34
|
+
log.info(`Loaded ${msgs.length} messages from ${filePath}`);
|
|
35
|
+
return msgs;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log.error('Failed to load history', { error: err });
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function save(messages: ChatMessage[]): Promise<void> {
|
|
43
|
+
const dir = dirname(filePath);
|
|
44
|
+
try {
|
|
45
|
+
if (!existsSync(dir)) {
|
|
46
|
+
await mkdir(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
await writeFile(filePath, JSON.stringify(messages, null, 2), 'utf-8');
|
|
49
|
+
log.info(`Saved ${messages.length} messages to ${filePath}`);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
log.error('Failed to save history', { error: err });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { load, save };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type RegisterFn = <K extends keyof RPCMethods & string>(
|
|
59
|
+
method: K,
|
|
60
|
+
handler: (params: MethodParams<RPCMethods, K>) => Promise<MethodResult<RPCMethods, K>>,
|
|
61
|
+
) => void;
|
|
62
|
+
|
|
63
|
+
export function generateReply(input: string): string {
|
|
64
|
+
const lower = input.toLowerCase().trim();
|
|
65
|
+
|
|
66
|
+
if (/^(hi|hello|hey|howdy|hola|yo|sup)\b/i.test(lower)) {
|
|
67
|
+
const greetings = [
|
|
68
|
+
'Hey there! How can I help you today?',
|
|
69
|
+
'Hello! Great to see you. What would you like to know?',
|
|
70
|
+
"Hi! I'm your desktop assistant. Ask me anything!",
|
|
71
|
+
];
|
|
72
|
+
return greetings[Math.floor(Math.random() * greetings.length)]!;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
/what('?s| is) the (time|date|day)|current (time|date)|what time|today'?s date/i.test(lower)
|
|
77
|
+
) {
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const date = now.toLocaleDateString('en-US', {
|
|
80
|
+
weekday: 'long',
|
|
81
|
+
year: 'numeric',
|
|
82
|
+
month: 'long',
|
|
83
|
+
day: 'numeric',
|
|
84
|
+
});
|
|
85
|
+
const time = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
86
|
+
return `It's currently **${time}** on **${date}**.`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const mathMatch = lower.match(
|
|
90
|
+
/(?:what(?:'s| is)\s+)?(\d+(?:\.\d+)?)\s*([+\-*/x×÷^])\s*(\d+(?:\.\d+)?)/,
|
|
91
|
+
);
|
|
92
|
+
if (mathMatch) {
|
|
93
|
+
const a = parseFloat(mathMatch[1]!);
|
|
94
|
+
const op = mathMatch[2]!;
|
|
95
|
+
const b = parseFloat(mathMatch[3]!);
|
|
96
|
+
let result: number;
|
|
97
|
+
switch (op) {
|
|
98
|
+
case '+':
|
|
99
|
+
result = a + b;
|
|
100
|
+
break;
|
|
101
|
+
case '-':
|
|
102
|
+
result = a - b;
|
|
103
|
+
break;
|
|
104
|
+
case '*':
|
|
105
|
+
case 'x':
|
|
106
|
+
case '×':
|
|
107
|
+
result = a * b;
|
|
108
|
+
break;
|
|
109
|
+
case '/':
|
|
110
|
+
case '÷':
|
|
111
|
+
result = b !== 0 ? a / b : NaN;
|
|
112
|
+
break;
|
|
113
|
+
case '^':
|
|
114
|
+
result = Math.pow(a, b);
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
result = NaN;
|
|
118
|
+
}
|
|
119
|
+
if (isNaN(result)) {
|
|
120
|
+
return "Hmm, I couldn't calculate that. Did you try dividing by zero?";
|
|
121
|
+
}
|
|
122
|
+
const niceResult = Number.isInteger(result)
|
|
123
|
+
? result.toString()
|
|
124
|
+
: result.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
|
|
125
|
+
return `That would be **${niceResult}**! Need help with anything else?`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (/file|folder|directory|browse|explorer|open file|read file/i.test(lower)) {
|
|
129
|
+
return (
|
|
130
|
+
'To browse and manage files, use the **File Explorer** panel on the left sidebar. ' +
|
|
131
|
+
'You can navigate directories, preview files, and open them for editing. ' +
|
|
132
|
+
'Try typing a file path or asking me about a specific directory!'
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (/git|commit|branch|push|pull|merge|status|diff|log/i.test(lower)) {
|
|
137
|
+
return (
|
|
138
|
+
'The **Git Panel** gives you full source control right inside the app. ' +
|
|
139
|
+
'You can view changes, stage files, commit, switch branches, push/pull, and see the commit log. ' +
|
|
140
|
+
'Look for the source control icon in the sidebar to get started.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (/^(help|commands|what can you|what do you|capabilities|features)/i.test(lower)) {
|
|
145
|
+
return (
|
|
146
|
+
"Here's what I can help with:\n\n" +
|
|
147
|
+
"- **Greetings** - Say hi and I'll say hi back!\n" +
|
|
148
|
+
'- **Time & Date** - Ask "what time is it?" or "what\'s today\'s date?"\n' +
|
|
149
|
+
'- **Math** - Give me an expression like "12 * 8" or "what is 100 / 4"\n' +
|
|
150
|
+
"- **Files** - Ask about file browsing and I'll explain the File Explorer\n" +
|
|
151
|
+
"- **Git** - Ask about version control and I'll walk you through it\n" +
|
|
152
|
+
'- **Help** - Show this message anytime!\n\n' +
|
|
153
|
+
'This is a demo assistant - try different things to see what sticks!'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const defaults = [
|
|
158
|
+
"That's an interesting question! I'm a demo assistant, so my knowledge is limited - but try asking about **time**, **math**, **files**, or **git**.",
|
|
159
|
+
"I wish I could help with that! For now I can answer questions about time, do simple math, and explain the app's features. Type **help** to see what I can do.",
|
|
160
|
+
"Hmm, I'm not sure about that one. But I *can* do math, tell you the time, and explain features. Give it a shot!",
|
|
161
|
+
];
|
|
162
|
+
return defaults[Math.floor(Math.random() * defaults.length)]!;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function register(server: RPCServer, options: HandlerOptions): void {
|
|
166
|
+
const storagePath = getStoragePathFor(options.platform);
|
|
167
|
+
const store = createMessageStore(storagePath);
|
|
168
|
+
|
|
169
|
+
const r: RegisterFn = (method, handler) => {
|
|
170
|
+
server.register(method, handler as (params: unknown) => Promise<unknown>);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
r('chat.list', async (params) => {
|
|
174
|
+
const all = await store.load();
|
|
175
|
+
const limit = params.limit ?? 50;
|
|
176
|
+
const messages = all.slice(-limit);
|
|
177
|
+
return {
|
|
178
|
+
messages,
|
|
179
|
+
hasMore: all.length > limit,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
r('chat.send', async (params) => {
|
|
184
|
+
const all = await store.load();
|
|
185
|
+
|
|
186
|
+
const userMsg: ChatMessage = {
|
|
187
|
+
id: `msg-${Date.now()}-user`,
|
|
188
|
+
role: 'user',
|
|
189
|
+
content: params.content,
|
|
190
|
+
timestamp: Date.now(),
|
|
191
|
+
};
|
|
192
|
+
all.push(userMsg);
|
|
193
|
+
|
|
194
|
+
server.emitEvent('chat.message', userMsg, { role: userMsg.role });
|
|
195
|
+
|
|
196
|
+
const thinkDelay = 200 + Math.floor(Math.random() * 300);
|
|
197
|
+
await new Promise((r) => setTimeout(r, thinkDelay));
|
|
198
|
+
|
|
199
|
+
const reply: ChatMessage = {
|
|
200
|
+
id: `msg-${Date.now()}-assistant`,
|
|
201
|
+
role: 'assistant',
|
|
202
|
+
content: generateReply(params.content),
|
|
203
|
+
timestamp: Date.now(),
|
|
204
|
+
};
|
|
205
|
+
all.push(reply);
|
|
206
|
+
|
|
207
|
+
server.emitEvent('chat.message', reply, { role: reply.role });
|
|
208
|
+
|
|
209
|
+
await store.save(all);
|
|
210
|
+
|
|
211
|
+
return { ok: true };
|
|
212
|
+
});
|
|
213
|
+
}
|