From db73cf5a39bbef0a0edc25a3164c0188fcf9b95c Mon Sep 17 00:00:00 2001 From: FuDie0915 Date: Tue, 10 Nov 2026 01:24:28 +0800 Subject: [PATCH] chore: import Local-Cursor v1.1.8 source snapshot Includes the current working directory changes. Historical commits and release binaries are excluded to limit Gitea storage. Source base: d7515973fbfd6dd2001fa156c483455b8081c7cb --- .dockerignore | 9 + .gitignore | 31 + LICENSE | 22 + README.md | 173 + Taskfile.yml | 566 + build/Taskfile.yml | 153 + build/appicon.png | Bin 0 -> 76264 bytes build/config.yml | 43 + build/darwin/Info.dev.plist | 37 + build/darwin/Info.plist | 32 + build/darwin/Taskfile.yml | 273 + build/darwin/icons.icns | Bin 0 -> 213427 bytes build/dmg-extras/提示损坏?点我.command | 13 + build/docker/Dockerfile.cross | 26 + build/docker/build-linux.sh | 81 + .../wailsapp/wails/v3/internal/eventcreate.js | 9 + .../wailsapp/wails/v3/internal/eventdata.d.ts | 2 + build/linux/Taskfile.yml | 192 + build/linux/desktop | 13 + build/linux/nfpm/nfpm.yaml | 67 + build/tray.png | Bin 0 -> 27174 bytes build/windows/Taskfile.yml | 169 + build/windows/icon.ico | Bin 0 -> 29941 bytes build/windows/info.json | 17 + build/windows/msix/app_manifest.xml | 55 + build/windows/msix/template.xml | 54 + .../nsis/MicrosoftEdgeWebview2Setup.exe | Bin 0 -> 1793816 bytes build/windows/nsis/project.nsi | 120 + build/windows/nsis/wails_tools.nsh | 263 + build/windows/wails.exe.manifest | 22 + cmd/ext-tool/extractor.go | 1805 + cmd/ext-tool/main.go | 119 + cursor-tab-server/.dockerignore | 2 + cursor-tab-server/Dockerfile | 24 + cursor-tab-server/README.md | 13 + cursor-tab-server/config.yaml | 1 + cursor-tab-server/go.mod | 5 + cursor-tab-server/go.sum | 4 + cursor-tab-server/main.go | 289 + docs/architecture.md | 44 + docs/build-environment.md | 41 + frontend/.yarnrc | 2 + frontend/index.html | 12 + frontend/jsconfig.json | 10 + frontend/package.json | 38 + frontend/plugins/static-i18n-plugin.js | 704 + frontend/postcss.config.cjs | 6 + frontend/public/Inter-Medium.ttf | Bin 0 -> 315132 bytes frontend/public/javascript.svg | 1 + frontend/public/style.css | 281 + frontend/public/wails.png | Bin 0 -> 9057 bytes frontend/scripts/run-vite-build.mjs | 27 + frontend/src/App.vue | 34 + frontend/src/assets/Group 11.png | Bin 0 -> 16298 bytes frontend/src/assets/logo.png | Bin 0 -> 8364 bytes frontend/src/components/HomeMetricsCard.vue | 417 + frontend/src/components/LocaleSelect.vue | 32 + .../src/components/ModelAdapterTestCard.vue | 154 + .../components/charts/CacheHitRateChart.vue | 128 + frontend/src/components/ui/Button.vue | 30 + frontend/src/components/ui/Card.vue | 6 + frontend/src/components/ui/Input.vue | 80 + frontend/src/components/ui/InputModal.vue | 97 + .../src/components/ui/MessageProvider.vue | 83 + frontend/src/components/ui/Modal.vue | 82 + frontend/src/components/ui/Select.vue | 347 + frontend/src/components/ui/Switch.vue | 65 + frontend/src/components/ui/Tooltip.vue | 156 + frontend/src/composables/useInputModal.js | 36 + frontend/src/composables/useMessage.js | 109 + frontend/src/composables/useModal.js | 37 + frontend/src/i18n/config.js | 10 + frontend/src/i18n/generated/catalog.json | 2412 ++ frontend/src/i18n/locales/en-US.json | 176 + frontend/src/i18n/locales/ja-JP.json | 176 + frontend/src/i18n/locales/zh-CN.json | 176 + frontend/src/i18n/runtime.js | 185 + frontend/src/layouts/MainLayout.vue | 156 + frontend/src/main.js | 40 + frontend/src/router/index.js | 33 + frontend/src/services/clientApi.js | 185 + frontend/src/state/appState.js | 1350 + frontend/src/style/fonts/HFKos-R.ttf | Bin 0 -> 30264 bytes frontend/src/style/fonts/PingFang-Medium.ttf | Bin 0 -> 10968356 bytes frontend/src/style/global.css | 188 + frontend/src/style/tailwind.css | 26 + frontend/src/utils/isWindows.js | 4 + frontend/src/utils/numberFormat.js | 39 + frontend/src/views/Home.vue | 315 + frontend/src/views/ModelConfig.vue | 484 + frontend/src/views/ModelEditor.vue | 548 + frontend/src/views/Skills.vue | 249 + frontend/tailwind.config.cjs | 77 + frontend/vite.config.js | 37 + frontend/yarn.lock | 1758 + go.mod | 75 + go.sum | 300 + internal/app/doc.go | 2 + internal/app/quit_dialog.go | 25 + internal/app/runner.go | 271 + internal/app/shortcut_other.go | 6 + internal/app/shortcut_windows.go | 48 + internal/appdata/migrate.go | 83 + internal/appdata/paths.go | 68 + internal/backend/README.md | 149 + internal/backend/agent/bridge/exec/bridge.go | 3054 ++ internal/backend/agent/bridge/exec/doc.go | 2 + .../agent/bridge/interaction/bridge.go | 929 + .../backend/agent/bridge/interaction/doc.go | 2 + internal/backend/agent/core/create_plan.go | 237 + internal/backend/agent/core/doc.go | 2 + internal/backend/agent/core/mcp.go | 110 + internal/backend/agent/core/tool_args.go | 370 + internal/backend/agent/core/types.go | 399 + internal/backend/agent/model/anthropic.go | 1676 + .../backend/agent/model/anthropic_test.go | 48 + internal/backend/agent/model/artifacts.go | 205 + internal/backend/agent/model/content_parts.go | 213 + .../agent/model/create_plan_progress.go | 97 + internal/backend/agent/model/doc.go | 2 + internal/backend/agent/model/http_error.go | 41 + internal/backend/agent/model/openai.go | 2178 ++ .../backend/agent/model/openai_url_test.go | 32 + .../backend/agent/model/provider_limits.go | 58 + .../agent/model/provider_limits_test.go | 70 + .../backend/agent/model/request_override.go | 121 + internal/backend/agent/model/retry.go | 49 + internal/backend/agent/model/router.go | 419 + internal/backend/agent/model/stream_idle.go | 133 + internal/backend/agent/model/tool_call_id.go | 126 + internal/backend/agent/model/types.go | 243 + internal/backend/agent/model/user_agent.go | 8 + .../backend/agent/prompt/content_parts.go | 15 + internal/backend/agent/prompt/doc.go | 2 + internal/backend/agent/prompt/engine.go | 1109 + internal/backend/agent/prompt/replay.go | 518 + internal/backend/agent/protocol/doc.go | 2 + internal/backend/agent/protocol/inbound.go | 224 + internal/backend/agent/step/doc.go | 2 + internal/backend/agent/step/recorder.go | 237 + internal/backend/forwarder/actor.go | 1154 + internal/backend/forwarder/ai_handler.go | 356 + internal/backend/forwarder/append_seq.go | 149 + internal/backend/forwarder/artifacts.go | 310 + .../backend/forwarder/await_shell_tool.go | 954 + internal/backend/forwarder/broker.go | 442 + .../backend/forwarder/checkpoint_memory.go | 147 + .../forwarder/codebase_index_handler.go | 200 + .../backend/forwarder/codebase_index_store.go | 606 + internal/backend/forwarder/commit_message.go | 306 + .../backend/forwarder/commit_message_log.go | 23 + internal/backend/forwarder/compaction.go | 1912 + internal/backend/forwarder/compiler.go | 256 + .../backend/forwarder/create_plan_sanitize.go | 36 + internal/backend/forwarder/debug_recorder.go | 394 + .../backend/forwarder/docs_index_handler.go | 210 + .../backend/forwarder/docs_index_store.go | 241 + .../backend/forwarder/edit_result_helpers.go | 491 + internal/backend/forwarder/events.go | 612 + internal/backend/forwarder/file_store.go | 1055 + .../backend/forwarder/history_maintenance.go | 172 + .../backend/forwarder/interaction_tools.go | 533 + internal/backend/forwarder/interrupt.go | 42 + internal/backend/forwarder/json_helpers.go | 72 + internal/backend/forwarder/legacy_stream.go | 73 + internal/backend/forwarder/module.go | 34 + internal/backend/forwarder/patch_edit_tool.go | 745 + internal/backend/forwarder/path_resolution.go | 296 + .../backend/forwarder/path_resolution_test.go | 37 + .../backend/forwarder/post_write_reconcile.go | 98 + .../backend/forwarder/process_exists_unix.go | 18 + .../forwarder/process_exists_windows.go | 18 + internal/backend/forwarder/projector.go | 1558 + internal/backend/forwarder/prompt_context.go | 130 + internal/backend/forwarder/prompt_guard.go | 323 + internal/backend/forwarder/provider.go | 62 + .../backend/forwarder/reasoning_metadata.go | 16 + internal/backend/forwarder/reminders.go | 458 + .../forwarder/repository_index_handler.go | 358 + internal/backend/forwarder/request_context.go | 395 + internal/backend/forwarder/rewind.go | 299 + internal/backend/forwarder/runtime_summary.go | 298 + internal/backend/forwarder/service.go | 3573 ++ internal/backend/forwarder/shell_recovery.go | 238 + internal/backend/forwarder/state_loaders.go | 37 + internal/backend/forwarder/state_tools.go | 871 + internal/backend/forwarder/token_estimator.go | 205 + internal/backend/forwarder/token_usage.go | 459 + internal/backend/forwarder/tool_catalog.go | 296 + .../forwarder/tool_error_completion.go | 111 + .../tool_result_replay_truncation.go | 394 + internal/backend/forwarder/types.go | 510 + .../backend/forwarder/upload_docs_handler.go | 233 + internal/backend/forwarder/usage_store.go | 374 + .../forwarder/usage_store_idempotency_test.go | 56 + internal/backend/forwarder/user_rules.go | 607 + internal/backend/forwarder/write_tool.go | 455 + internal/backend/host.go | 965 + .../backend/server/config/legacy_runtime.go | 46 + internal/backend/server/config/manager.go | 256 + internal/backend/server/config/resolver.go | 90 + .../backend/server/config/roundtrip_test.go | 58 + internal/backend/server/config/store.go | 183 + internal/backend/server/config/types.go | 371 + internal/backend/server/context.go | 72 + internal/backend/server/doc.go | 2 + internal/backend/server/errors.go | 5 + internal/backend/server/local.go | 38 + internal/backend/server/middleware.go | 97 + internal/backend/server/policy.go | 19 + internal/backend/server/response_writer.go | 99 + internal/backend/server/route.go | 191 + internal/backend/server/upstream/action.go | 222 + internal/backend/server/upstream/client.go | 438 + internal/backend/server/upstream/mocks.go | 950 + .../backend/server/upstream/mocks_test.go | 29 + internal/backend/server/upstream/types.go | 148 + internal/backend/server/url.go | 25 + internal/bridge/cli_terminal.go | 251 + internal/bridge/doc.go | 2 + internal/bridge/logging.go | 24 + internal/bridge/metrics.go | 65 + internal/bridge/proxy.go | 200 + internal/bridge/skill.go | 69 + internal/bridge/window.go | 345 + internal/buildinfo/buildinfo.go | 24 + internal/certs/ca.crt | 25 + internal/certs/ca.go | 258 + internal/certs/ca.key | 27 + internal/certs/doc.go | 2 + internal/client/config.go | 88 + internal/client/cursor.go | 111 + internal/client/doc.go | 2 + internal/client/license.go | 145 + internal/client/lifecycle.go | 292 + internal/client/model_adapter_benchmark.go | 771 + internal/client/models_fetch.go | 136 + internal/client/models_fetch_test.go | 76 + internal/client/observer.go | 21 + internal/client/service.go | 167 + internal/cursor/cli.go | 417 + internal/cursor/darwin_cert.go | 98 + internal/cursor/device.go | 49 + internal/cursor/doc.go | 2 + internal/cursor/localize.go | 389 + internal/cursor/localize_detect_test.go | 17 + internal/cursor/localize_error_test.go | 70 + internal/cursor/localize_other.go | 16 + internal/cursor/localize_scan_windows.go | 96 + internal/cursor/localize_windows.go | 221 + internal/cursor/localize_windows_test.go | 39 + internal/cursor/process_other.go | 11 + internal/cursor/process_windows.go | 18 + internal/cursor/projects.go | 343 + internal/cursor/projects_test.go | 189 + internal/cursor/settings.go | 439 + internal/cursor/state_db.go | 221 + internal/cursor/windows_cert.go | 162 + internal/cursor/windows_cert_stub.go | 10 + internal/historymetrics/report.go | 36 + internal/historymetrics/usage_json.go | 57 + internal/logger/logger.go | 323 + internal/mitm/doc.go | 2 + internal/mitm/local_relay_bridge.go | 112 + internal/mitm/service.go | 858 + internal/modelchannel/identity.go | 83 + internal/modelchannel/resolve.go | 66 + internal/netproxy/netproxy.go | 470 + internal/netproxy/system_darwin.go | 103 + internal/netproxy/system_other.go | 7 + internal/netproxy/system_windows.go | 106 + internal/prompt/agent/prompt.md | 259 + internal/prompt/agent/tools.json | 687 + internal/prompt/ask/prompt.md | 275 + internal/prompt/ask/tools.json | 640 + internal/prompt/commit/prompt.md | 6 + internal/prompt/common_prefix.md | 96 + internal/prompt/compaction/prompt.md | 4 + internal/prompt/debug/prompt.md | 328 + .../debug/system_reminder_continuing.txt | 10 + .../prompt/debug/system_reminder_initial.txt | 116 + internal/prompt/debug/tools.json | 640 + internal/prompt/doc.go | 2 + internal/prompt/embed.go | 193 + internal/prompt/multitask/prompt.md | 65 + internal/prompt/multitask/tools.json | 687 + internal/prompt/plan/prompt.md | 262 + internal/prompt/plan/system_reminder.txt | 38 + internal/prompt/plan/tools.json | 609 + internal/prompt/render.go | 14 + internal/prompt/subagent/prompt.md | 25 + internal/prompt/subagent/tools.json | 148 + internal/runtime/defaults.go | 10 + internal/runtime/doc.go | 2 + internal/runtime/local_runtime.go | 509 + .../skills/builtin/fd-engineering/SKILL.md | 60 + .../fd-engineering/commands/FD-commit.md | 13 + .../fd-engineering/commands/FD-devops.md | 13 + .../fd-engineering/commands/FD-execute.md | 13 + .../fd-engineering/commands/FD-grill.md | 13 + .../fd-engineering/commands/FD-plan.md | 13 + .../fd-engineering/commands/FD-research.md | 13 + .../fd-engineering/commands/FD-review.md | 13 + .../fd-engineering/commands/FD-uiux.md | 13 + .../fd-engineering/references/commit.md | 21 + .../fd-engineering/references/devops.md | 47 + .../fd-engineering/references/execute.md | 49 + .../fd-engineering/references/grill.md | 54 + .../builtin/fd-engineering/references/plan.md | 33 + .../fd-engineering/references/research.md | 31 + .../fd-engineering/references/review.md | 32 + .../fd-engineering/references/task-record.md | 58 + .../builtin/fd-engineering/references/todo.md | 58 + .../builtin/fd-engineering/references/uiux.md | 112 + .../fd-engineering/references/workflow.md | 48 + internal/skills/skills.go | 263 + internal/skills/skills_test.go | 154 + internal/winexec/winexec_other.go | 16 + internal/winexec/winexec_windows.go | 26 + main.go | 29 + proto/agent_v1.proto | 5671 +++ proto/aiserver_v1.proto | 30405 ++++++++++++++++ .../cursor-always-local/.gitignore | 2 + .../cursor-always-local/media/main.css | 0 .../cursor-always-local/package.json | 1 + .../cursor-always-local/package.nls.json | 1 + .../schemas/environment.schema.json | 1 + .../cursor-always-local/scripts/run_tests.sh | 15 + .../scripts/tsc-some-files.sh | 29 + proto/extract_extensions_proto.sh | 71 + proto/from_extensions/agent_v1.proto | 5671 +++ proto/from_extensions/aiserver_v1.proto | 30405 ++++++++++++++++ proto/from_extensions/anyrun_v1.proto | 953 + proto/from_extensions/internapi_v1.proto | 41 + release-notes.md | 7 + scripts/historymetrics/main.go | 70 + scripts/release/main.go | 249 + 337 files changed, 147502 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Taskfile.yml create mode 100644 build/Taskfile.yml create mode 100644 build/appicon.png create mode 100644 build/config.yml create mode 100644 build/darwin/Info.dev.plist create mode 100644 build/darwin/Info.plist create mode 100644 build/darwin/Taskfile.yml create mode 100644 build/darwin/icons.icns create mode 100644 build/dmg-extras/提示损坏?点我.command create mode 100644 build/docker/Dockerfile.cross create mode 100644 build/docker/build-linux.sh create mode 100644 build/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js create mode 100644 build/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts create mode 100644 build/linux/Taskfile.yml create mode 100644 build/linux/desktop create mode 100644 build/linux/nfpm/nfpm.yaml create mode 100644 build/tray.png create mode 100644 build/windows/Taskfile.yml create mode 100644 build/windows/icon.ico create mode 100644 build/windows/info.json create mode 100644 build/windows/msix/app_manifest.xml create mode 100644 build/windows/msix/template.xml create mode 100644 build/windows/nsis/MicrosoftEdgeWebview2Setup.exe create mode 100644 build/windows/nsis/project.nsi create mode 100644 build/windows/nsis/wails_tools.nsh create mode 100644 build/windows/wails.exe.manifest create mode 100644 cmd/ext-tool/extractor.go create mode 100644 cmd/ext-tool/main.go create mode 100644 cursor-tab-server/.dockerignore create mode 100644 cursor-tab-server/Dockerfile create mode 100644 cursor-tab-server/README.md create mode 100644 cursor-tab-server/config.yaml create mode 100644 cursor-tab-server/go.mod create mode 100644 cursor-tab-server/go.sum create mode 100644 cursor-tab-server/main.go create mode 100644 docs/architecture.md create mode 100644 docs/build-environment.md create mode 100644 frontend/.yarnrc create mode 100644 frontend/index.html create mode 100644 frontend/jsconfig.json create mode 100644 frontend/package.json create mode 100644 frontend/plugins/static-i18n-plugin.js create mode 100644 frontend/postcss.config.cjs create mode 100644 frontend/public/Inter-Medium.ttf create mode 100644 frontend/public/javascript.svg create mode 100644 frontend/public/style.css create mode 100644 frontend/public/wails.png create mode 100644 frontend/scripts/run-vite-build.mjs create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/assets/Group 11.png create mode 100644 frontend/src/assets/logo.png create mode 100644 frontend/src/components/HomeMetricsCard.vue create mode 100644 frontend/src/components/LocaleSelect.vue create mode 100644 frontend/src/components/ModelAdapterTestCard.vue create mode 100644 frontend/src/components/charts/CacheHitRateChart.vue create mode 100644 frontend/src/components/ui/Button.vue create mode 100644 frontend/src/components/ui/Card.vue create mode 100644 frontend/src/components/ui/Input.vue create mode 100644 frontend/src/components/ui/InputModal.vue create mode 100644 frontend/src/components/ui/MessageProvider.vue create mode 100644 frontend/src/components/ui/Modal.vue create mode 100644 frontend/src/components/ui/Select.vue create mode 100644 frontend/src/components/ui/Switch.vue create mode 100644 frontend/src/components/ui/Tooltip.vue create mode 100644 frontend/src/composables/useInputModal.js create mode 100644 frontend/src/composables/useMessage.js create mode 100644 frontend/src/composables/useModal.js create mode 100644 frontend/src/i18n/config.js create mode 100644 frontend/src/i18n/generated/catalog.json create mode 100644 frontend/src/i18n/locales/en-US.json create mode 100644 frontend/src/i18n/locales/ja-JP.json create mode 100644 frontend/src/i18n/locales/zh-CN.json create mode 100644 frontend/src/i18n/runtime.js create mode 100644 frontend/src/layouts/MainLayout.vue create mode 100644 frontend/src/main.js create mode 100644 frontend/src/router/index.js create mode 100644 frontend/src/services/clientApi.js create mode 100644 frontend/src/state/appState.js create mode 100644 frontend/src/style/fonts/HFKos-R.ttf create mode 100644 frontend/src/style/fonts/PingFang-Medium.ttf create mode 100644 frontend/src/style/global.css create mode 100644 frontend/src/style/tailwind.css create mode 100644 frontend/src/utils/isWindows.js create mode 100644 frontend/src/utils/numberFormat.js create mode 100644 frontend/src/views/Home.vue create mode 100644 frontend/src/views/ModelConfig.vue create mode 100644 frontend/src/views/ModelEditor.vue create mode 100644 frontend/src/views/Skills.vue create mode 100644 frontend/tailwind.config.cjs create mode 100644 frontend/vite.config.js create mode 100644 frontend/yarn.lock create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/app/doc.go create mode 100644 internal/app/quit_dialog.go create mode 100644 internal/app/runner.go create mode 100644 internal/app/shortcut_other.go create mode 100644 internal/app/shortcut_windows.go create mode 100644 internal/appdata/migrate.go create mode 100644 internal/appdata/paths.go create mode 100644 internal/backend/README.md create mode 100644 internal/backend/agent/bridge/exec/bridge.go create mode 100644 internal/backend/agent/bridge/exec/doc.go create mode 100644 internal/backend/agent/bridge/interaction/bridge.go create mode 100644 internal/backend/agent/bridge/interaction/doc.go create mode 100644 internal/backend/agent/core/create_plan.go create mode 100644 internal/backend/agent/core/doc.go create mode 100644 internal/backend/agent/core/mcp.go create mode 100644 internal/backend/agent/core/tool_args.go create mode 100644 internal/backend/agent/core/types.go create mode 100644 internal/backend/agent/model/anthropic.go create mode 100644 internal/backend/agent/model/anthropic_test.go create mode 100644 internal/backend/agent/model/artifacts.go create mode 100644 internal/backend/agent/model/content_parts.go create mode 100644 internal/backend/agent/model/create_plan_progress.go create mode 100644 internal/backend/agent/model/doc.go create mode 100644 internal/backend/agent/model/http_error.go create mode 100644 internal/backend/agent/model/openai.go create mode 100644 internal/backend/agent/model/openai_url_test.go create mode 100644 internal/backend/agent/model/provider_limits.go create mode 100644 internal/backend/agent/model/provider_limits_test.go create mode 100644 internal/backend/agent/model/request_override.go create mode 100644 internal/backend/agent/model/retry.go create mode 100644 internal/backend/agent/model/router.go create mode 100644 internal/backend/agent/model/stream_idle.go create mode 100644 internal/backend/agent/model/tool_call_id.go create mode 100644 internal/backend/agent/model/types.go create mode 100644 internal/backend/agent/model/user_agent.go create mode 100644 internal/backend/agent/prompt/content_parts.go create mode 100644 internal/backend/agent/prompt/doc.go create mode 100644 internal/backend/agent/prompt/engine.go create mode 100644 internal/backend/agent/prompt/replay.go create mode 100644 internal/backend/agent/protocol/doc.go create mode 100644 internal/backend/agent/protocol/inbound.go create mode 100644 internal/backend/agent/step/doc.go create mode 100644 internal/backend/agent/step/recorder.go create mode 100644 internal/backend/forwarder/actor.go create mode 100644 internal/backend/forwarder/ai_handler.go create mode 100644 internal/backend/forwarder/append_seq.go create mode 100644 internal/backend/forwarder/artifacts.go create mode 100644 internal/backend/forwarder/await_shell_tool.go create mode 100644 internal/backend/forwarder/broker.go create mode 100644 internal/backend/forwarder/checkpoint_memory.go create mode 100644 internal/backend/forwarder/codebase_index_handler.go create mode 100644 internal/backend/forwarder/codebase_index_store.go create mode 100644 internal/backend/forwarder/commit_message.go create mode 100644 internal/backend/forwarder/commit_message_log.go create mode 100644 internal/backend/forwarder/compaction.go create mode 100644 internal/backend/forwarder/compiler.go create mode 100644 internal/backend/forwarder/create_plan_sanitize.go create mode 100644 internal/backend/forwarder/debug_recorder.go create mode 100644 internal/backend/forwarder/docs_index_handler.go create mode 100644 internal/backend/forwarder/docs_index_store.go create mode 100644 internal/backend/forwarder/edit_result_helpers.go create mode 100644 internal/backend/forwarder/events.go create mode 100644 internal/backend/forwarder/file_store.go create mode 100644 internal/backend/forwarder/history_maintenance.go create mode 100644 internal/backend/forwarder/interaction_tools.go create mode 100644 internal/backend/forwarder/interrupt.go create mode 100644 internal/backend/forwarder/json_helpers.go create mode 100644 internal/backend/forwarder/legacy_stream.go create mode 100644 internal/backend/forwarder/module.go create mode 100644 internal/backend/forwarder/patch_edit_tool.go create mode 100644 internal/backend/forwarder/path_resolution.go create mode 100644 internal/backend/forwarder/path_resolution_test.go create mode 100644 internal/backend/forwarder/post_write_reconcile.go create mode 100644 internal/backend/forwarder/process_exists_unix.go create mode 100644 internal/backend/forwarder/process_exists_windows.go create mode 100644 internal/backend/forwarder/projector.go create mode 100644 internal/backend/forwarder/prompt_context.go create mode 100644 internal/backend/forwarder/prompt_guard.go create mode 100644 internal/backend/forwarder/provider.go create mode 100644 internal/backend/forwarder/reasoning_metadata.go create mode 100644 internal/backend/forwarder/reminders.go create mode 100644 internal/backend/forwarder/repository_index_handler.go create mode 100644 internal/backend/forwarder/request_context.go create mode 100644 internal/backend/forwarder/rewind.go create mode 100644 internal/backend/forwarder/runtime_summary.go create mode 100644 internal/backend/forwarder/service.go create mode 100644 internal/backend/forwarder/shell_recovery.go create mode 100644 internal/backend/forwarder/state_loaders.go create mode 100644 internal/backend/forwarder/state_tools.go create mode 100644 internal/backend/forwarder/token_estimator.go create mode 100644 internal/backend/forwarder/token_usage.go create mode 100644 internal/backend/forwarder/tool_catalog.go create mode 100644 internal/backend/forwarder/tool_error_completion.go create mode 100644 internal/backend/forwarder/tool_result_replay_truncation.go create mode 100644 internal/backend/forwarder/types.go create mode 100644 internal/backend/forwarder/upload_docs_handler.go create mode 100644 internal/backend/forwarder/usage_store.go create mode 100644 internal/backend/forwarder/usage_store_idempotency_test.go create mode 100644 internal/backend/forwarder/user_rules.go create mode 100644 internal/backend/forwarder/write_tool.go create mode 100644 internal/backend/host.go create mode 100644 internal/backend/server/config/legacy_runtime.go create mode 100644 internal/backend/server/config/manager.go create mode 100644 internal/backend/server/config/resolver.go create mode 100644 internal/backend/server/config/roundtrip_test.go create mode 100644 internal/backend/server/config/store.go create mode 100644 internal/backend/server/config/types.go create mode 100644 internal/backend/server/context.go create mode 100644 internal/backend/server/doc.go create mode 100644 internal/backend/server/errors.go create mode 100644 internal/backend/server/local.go create mode 100644 internal/backend/server/middleware.go create mode 100644 internal/backend/server/policy.go create mode 100644 internal/backend/server/response_writer.go create mode 100644 internal/backend/server/route.go create mode 100644 internal/backend/server/upstream/action.go create mode 100644 internal/backend/server/upstream/client.go create mode 100644 internal/backend/server/upstream/mocks.go create mode 100644 internal/backend/server/upstream/mocks_test.go create mode 100644 internal/backend/server/upstream/types.go create mode 100644 internal/backend/server/url.go create mode 100644 internal/bridge/cli_terminal.go create mode 100644 internal/bridge/doc.go create mode 100644 internal/bridge/logging.go create mode 100644 internal/bridge/metrics.go create mode 100644 internal/bridge/proxy.go create mode 100644 internal/bridge/skill.go create mode 100644 internal/bridge/window.go create mode 100644 internal/buildinfo/buildinfo.go create mode 100644 internal/certs/ca.crt create mode 100644 internal/certs/ca.go create mode 100644 internal/certs/ca.key create mode 100644 internal/certs/doc.go create mode 100644 internal/client/config.go create mode 100644 internal/client/cursor.go create mode 100644 internal/client/doc.go create mode 100644 internal/client/license.go create mode 100644 internal/client/lifecycle.go create mode 100644 internal/client/model_adapter_benchmark.go create mode 100644 internal/client/models_fetch.go create mode 100644 internal/client/models_fetch_test.go create mode 100644 internal/client/observer.go create mode 100644 internal/client/service.go create mode 100644 internal/cursor/cli.go create mode 100644 internal/cursor/darwin_cert.go create mode 100644 internal/cursor/device.go create mode 100644 internal/cursor/doc.go create mode 100644 internal/cursor/localize.go create mode 100644 internal/cursor/localize_detect_test.go create mode 100644 internal/cursor/localize_error_test.go create mode 100644 internal/cursor/localize_other.go create mode 100644 internal/cursor/localize_scan_windows.go create mode 100644 internal/cursor/localize_windows.go create mode 100644 internal/cursor/localize_windows_test.go create mode 100644 internal/cursor/process_other.go create mode 100644 internal/cursor/process_windows.go create mode 100644 internal/cursor/projects.go create mode 100644 internal/cursor/projects_test.go create mode 100644 internal/cursor/settings.go create mode 100644 internal/cursor/state_db.go create mode 100644 internal/cursor/windows_cert.go create mode 100644 internal/cursor/windows_cert_stub.go create mode 100644 internal/historymetrics/report.go create mode 100644 internal/historymetrics/usage_json.go create mode 100644 internal/logger/logger.go create mode 100644 internal/mitm/doc.go create mode 100644 internal/mitm/local_relay_bridge.go create mode 100644 internal/mitm/service.go create mode 100644 internal/modelchannel/identity.go create mode 100644 internal/modelchannel/resolve.go create mode 100644 internal/netproxy/netproxy.go create mode 100644 internal/netproxy/system_darwin.go create mode 100644 internal/netproxy/system_other.go create mode 100644 internal/netproxy/system_windows.go create mode 100644 internal/prompt/agent/prompt.md create mode 100644 internal/prompt/agent/tools.json create mode 100644 internal/prompt/ask/prompt.md create mode 100644 internal/prompt/ask/tools.json create mode 100644 internal/prompt/commit/prompt.md create mode 100644 internal/prompt/common_prefix.md create mode 100644 internal/prompt/compaction/prompt.md create mode 100644 internal/prompt/debug/prompt.md create mode 100644 internal/prompt/debug/system_reminder_continuing.txt create mode 100644 internal/prompt/debug/system_reminder_initial.txt create mode 100644 internal/prompt/debug/tools.json create mode 100644 internal/prompt/doc.go create mode 100644 internal/prompt/embed.go create mode 100644 internal/prompt/multitask/prompt.md create mode 100644 internal/prompt/multitask/tools.json create mode 100644 internal/prompt/plan/prompt.md create mode 100644 internal/prompt/plan/system_reminder.txt create mode 100644 internal/prompt/plan/tools.json create mode 100644 internal/prompt/render.go create mode 100644 internal/prompt/subagent/prompt.md create mode 100644 internal/prompt/subagent/tools.json create mode 100644 internal/runtime/defaults.go create mode 100644 internal/runtime/doc.go create mode 100644 internal/runtime/local_runtime.go create mode 100644 internal/skills/builtin/fd-engineering/SKILL.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-commit.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-devops.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-execute.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-grill.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-plan.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-research.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-review.md create mode 100644 internal/skills/builtin/fd-engineering/commands/FD-uiux.md create mode 100644 internal/skills/builtin/fd-engineering/references/commit.md create mode 100644 internal/skills/builtin/fd-engineering/references/devops.md create mode 100644 internal/skills/builtin/fd-engineering/references/execute.md create mode 100644 internal/skills/builtin/fd-engineering/references/grill.md create mode 100644 internal/skills/builtin/fd-engineering/references/plan.md create mode 100644 internal/skills/builtin/fd-engineering/references/research.md create mode 100644 internal/skills/builtin/fd-engineering/references/review.md create mode 100644 internal/skills/builtin/fd-engineering/references/task-record.md create mode 100644 internal/skills/builtin/fd-engineering/references/todo.md create mode 100644 internal/skills/builtin/fd-engineering/references/uiux.md create mode 100644 internal/skills/builtin/fd-engineering/references/workflow.md create mode 100644 internal/skills/skills.go create mode 100644 internal/skills/skills_test.go create mode 100644 internal/winexec/winexec_other.go create mode 100644 internal/winexec/winexec_windows.go create mode 100644 main.go create mode 100644 proto/agent_v1.proto create mode 100644 proto/aiserver_v1.proto create mode 100644 proto/extensions-cursor-app/cursor-always-local/.gitignore create mode 100644 proto/extensions-cursor-app/cursor-always-local/media/main.css create mode 100644 proto/extensions-cursor-app/cursor-always-local/package.json create mode 100644 proto/extensions-cursor-app/cursor-always-local/package.nls.json create mode 100644 proto/extensions-cursor-app/cursor-always-local/schemas/environment.schema.json create mode 100644 proto/extensions-cursor-app/cursor-always-local/scripts/run_tests.sh create mode 100644 proto/extensions-cursor-app/cursor-always-local/scripts/tsc-some-files.sh create mode 100644 proto/extract_extensions_proto.sh create mode 100644 proto/from_extensions/agent_v1.proto create mode 100644 proto/from_extensions/aiserver_v1.proto create mode 100644 proto/from_extensions/anyrun_v1.proto create mode 100644 proto/from_extensions/internapi_v1.proto create mode 100644 release-notes.md create mode 100644 scripts/historymetrics/main.go create mode 100644 scripts/release/main.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ce9d47a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +* +!go.mod +!go.sum +!cursor-tab-server/ +!cursor-tab-server/** +!gen/ +!gen/** +!internal/ +!internal/** diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caddafa --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +claude-server.tar +dist +.task +bin +logs/ +gen/ +*.syso +logs.zip +frontend/bindings +.cursor/ +frontend/dist +bin/release +dist +node_modules +cursor-server.tar +server-node/cursor.tar +server-go/cursor.tar +server-go/log/ +.cursor-local-assistant +.cursor-local-assistant-v2 +.cursor-app-formatted/ +ads-server-linux-amd64.tar +cmd/ads-server/*.db +cmd/ads-server/*.db-* +cmd/ads-server/*.sqlite +cmd/ads-server/*.sqlite-* +cmd/ads-server/data/ +cmd/ads-server/ads-server +cmd/ads-server/ads-server-linux-amd64.tar +cursor-tab-server/cursor-tab-server-linux-amd64.tar +build/build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94043db --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 leookun +Copyright (c) 2026 FuDie0915 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..754eb21 --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# Local-Cursor + +本地运行的 Cursor IDE 代理工具。通过 MITM 代理劫持 Cursor 的 AI 请求,转发到自定义模型 API(OpenAI / Anthropic 兼容),支持本地助手模式与直连上游模式;同时适配 CLI 版 cursor-agent,并提供 Skill 分发、一键汉化、使用量统计等增强功能。 + +> 基于 [cursor-byok](https://github.com/leookun/cursor-byok.git) 衍生,感谢原作者。本项目仅供学习与个人使用,请支持正版 Cursor。 + +## 功能 + +| 功能 | 说明 | +|------|------| +| 请求劫持与转发 | MITM 代理劫持 Cursor AI 请求,转发到自定义 API | +| 多模型渠道 | 支持同时配置多个 OpenAI / Anthropic 兼容渠道 | +| 模型批量拉取 | 通过 `/v1/models` 端点拉取可用模型列表,勾选后批量添加渠道 | +| Skill 管理 | 内置 Skill(含斜杠命令)分发到任意打开过的 Cursor 项目,开关即装/卸 | +| Cursor 状态面板 | 自动检测 Cursor 安装目录与运行状态,5 秒心跳刷新,支持手动指定路径 | +| 一键汉化 | 自动检测安装目录,运行社区汉化包(自动适配 fnm/nvm 等 Node 环境) | +| Tab 补全 | 可配置上游 Tab 补全服务地址(可选 Docker 部署) | +| 模型测速 | 内置模型渠道延迟/吞吐量基准测试 | +| 会话统计 | 使用量持久化与聚合统计,支持定时刷新与累计/本次运行两种口径 | +| CA 证书管理 | 自动注入 CA 证书到系统信任链 | +| Cursor 设置注入 | 自动写入代理、HTTP/2、证书配置,并写入 `update.mode: none` 禁用自动更新 | +| CLI 代理适配 | 自动检测 CLI 版 cursor-agent,生成 wrapper + preload,重定向 TCP 连接 | +| 快速启动 | 运行时自动注册开始菜单快捷方式,Flow Launcher / PowerToys Run 可直接检索 | +| 网络代理感知 | 自动检测系统/环境变量代理并适配出站连接 | + +## 架构 + +``` +┌──────────────────────────────────────────────────┐ +│ Cursor IDE │ +│ (settings.json 注入 http.proxy) │ +└────────────┬─────────────────────────────────────┘ + │ HTTPS CONNECT + ▼ +┌──────────────────────┐ 白名单域名(.cursor.sh) +│ MITM Proxy Server │ ────────────────► ┌──────────────────┐ +│ (127.0.0.1:28080) │ │ Backend Server │ +│ │ ◄──────────────── │ (127.0.0.1:28090)│ +│ 非白名单域名直连回源 │ 转发+路由 └────────┬─────────┘ +└──────────────────────┘ │ + ┌────────┴────────┐ + │ Forwarder │ + │ BidiAppend/SSE │ + │ Prompt 编译 │ + │ Provider 调用 │ + └────────┬────────┘ + │ + ┌────────┴────────┐ + │ Custom API │ + │ (OpenAI/Anthropic)│ + └─────────────────┘ + +┌──────────────────────────────────────────────────┐ +│ CLI 版 cursor-agent │ +│ (agent.cmd wrapper + tls-bypass.cjs preload) │ +└────────────┬─────────────────────────────────────┘ + │ TCP 重定向 (patch net.connect/tls.connect) + ▼ +┌──────────────────────┐ +│ CLI HTTPS Listener │ +│ (127.0.0.1:39092) │ ← HTTPS+HTTP/2,跳过 MITM +│ 复用 Backend 路由 │ +└──────────────────────┘ +``` + +**IDE 模式**:Cursor 通过注入的 `http.proxy` 将请求发往本地 MITM 代理(`:28080`),白名单域名解密后转发到本地后端(`:28090`),按 `routing.mode` 选择本地处理或上游转发。 + +**CLI 模式**:CLI 的 connect-node 使用 HTTP/2,无法被 goproxy MITM,因此通过 Node preload 在 TCP 层把 `api2/api3.cursor.sh` 重定向到本地 HTTPS+HTTP/2 监听器(`:39092`),复用同一后端路由。 + +## 默认端口 + +| 服务 | 地址 | 说明 | +|------|------|------| +| Backend Server | `127.0.0.1:28090` | 嵌入式 HTTP 后端 | +| MITM Proxy | `127.0.0.1:28080` | Cursor 流量劫持代理 | +| CLI HTTPS | `127.0.0.1:39092` | CLI 专用 HTTPS+HTTP/2 监听器 | +| Tab 补全 | `18041`(可选) | Docker 部署的 Tab 补全服务 | + +## 安装与使用 + +1. 从 [Releases](../../releases) 下载对应平台分发包并安装/解压。 +2. 启动后点击「开启服务」,程序自动完成 CA 证书注入、Cursor settings.json 注入与 CLI wrapper 生成。 +3. 在「模型配置」中添加渠道,或使用「拉取模型」从 API 端点批量导入。 +4. 配置完成后正常使用 Cursor / cursor-agent 即可,流量将走你配置的模型。 + +程序运行时会自动注册开始菜单快捷方式,Flow Launcher、PowerToys Run 等启动器可直接检索 `Local-Cursor`。 + +## 配置 + +首次启动后自动生成 `~/.cursor-local-assistant-v2/config.yaml`,关键字段: + +```yaml +providerStreamIdleTimeout: 240 +backendListenAddr: "127.0.0.1:28090" +proxyListenAddr: "127.0.0.1:28080" +cliHttpsListenAddr: "127.0.0.1:39092" +modelAdapters: + - displayName: "My Model" + type: "openai" # openai | anthropic + baseURL: "https://api.example.com" + apiKey: "sk-xxx" + modelID: "gpt-4" + reasoningEffort: "medium" # low | medium | high | xhigh + openAIEndpoint: "/v1/responses" # /v1/responses | /v1/chat/completions + contextWindowTokens: 500000 # 留空使用默认 500K + maxCompletionTokens: 278000 # 留空使用默认 278K +routing: + mode: "local" # local | upstream +homeMetrics: + includeCacheWriteInHitRate: false + refreshIntervalSeconds: 60 # 统计定时刷新间隔(秒,最小 5) + persistentMode: true # 累计统计 +tabServerBaseURL: "" +tabServerToken: "" +``` + +## 构建与开发 + +### 前置依赖 + +- Go 1.25+ +- Node.js 20+(含 yarn,可用 corepack) +- [Wails v3 CLI](https://wails.io/):`go install github.com/wailsapp/wails/v3/cmd/wails3@latest` +- protoc + protoc-gen-go + protoc-gen-connect-go(生成 `gen/`,运行 `task sync:proto` 或手动执行 `build/Taskfile.yml` 中的 generate:proto) + +### 常用命令 + +```bash +# 前端依赖 +corepack yarn install --cwd frontend + +# 生成 Wails 前端绑定(修改 service 后需要) +wails3 generate bindings -clean -d frontend/bindings + +# 构建当前平台分发包 +task build + +# 开发模式 +task dev +``` + +## 数据目录 + +所有数据存储在 `~/.cursor-local-assistant-v2/`: + +``` +~/.cursor-local-assistant-v2/ +├── config.yaml # 用户配置 +├── data/ +│ ├── ca.crt # 注入 CA 证书 +│ ├── codebase-index/ # 代码库索引 +│ └── docs-index/ # 文档索引 +├── history/ +│ ├── usage.json # 使用量聚合统计 +│ └── / # 会话状态 +├── logs/ # 运行日志 +├── cli/ # CLI wrapper 与 preload(自动生成) +└── rules/ # 用户规则 +``` + +## 免责声明 + +本项目通过本地代理方式修改 Cursor 的请求路由,仅供学习研究。请购买正版 Cursor 订阅,因使用本项目产生的任何问题与原作者及本项目维护者无关。 + +## License + +[MIT](LICENSE) + +## 致谢 + +- [cursor-byok](https://github.com/leookun/cursor-byok.git) — 原项目 +- [Wails v3](https://wails.io/) — 桌面框架 +- [@polang233/cursor-language-pack](https://www.npmjs.com/package/@polang233/cursor-language-pack) — Cursor 汉化包 \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..434848c --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,566 @@ +version: "3" + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "Local-Cursor" + BIN_DIR: "bin" + APP_VERSION: + sh: 'go run ./scripts/release version -config ./build/config.yml' + RELEASE_REPO: "" + RELEASE_BASE_NAME: "Local-Cursor" + RELEASE_SOURCE_PATH: "release-notes.md" + RELEASE_DIR: '{{.BIN_DIR}}/release/{{.APP_VERSION}}' + WINDOWS_RELEASE_DIR: '{{.BIN_DIR}}/release/v{{.APP_VERSION}}' + RELEASE_NOTES_PATH: '{{.RELEASE_DIR}}/.release-notes.md' + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + SCAN: '{{.SCAN | default "false"}}' + CURRENT_WINDOWS_ARCH: '{{if eq ARCH "386"}}386{{else}}amd64{{end}}' + CURRENT_WINDOWS_NAME: '{{if eq .CURRENT_WINDOWS_ARCH "386"}}windows-32{{else}}windows-64{{end}}' + CURRENT_DARWIN_NAME: '{{if eq ARCH "arm64"}}macos-arm64{{else}}macos-intel{{end}}' + CURRENT_LINUX_ARCH: "amd64" + CURRENT_LINUX_NAME: "linux-amd64" + +tasks: + build: + summary: 构建当前系统分发包 + preconditions: + - sh: '[ "{{OS}}" = "darwin" ] || [ "{{OS}}" = "windows" ] || [ "{{OS}}" = "linux" ]' + msg: "仅支持在 macOS、Windows 或 Linux 上执行 task build" + cmds: + - task: clean:dist + - task: build:current + + run: + summary: 运行当前系统版本 + cmds: + - task: "{{OS}}:run" + + build:all: + summary: 构建全部 macOS/Windows 分发包(Linux 需在原生 Linux 主机单独构建) + preconditions: + - sh: '[ "{{OS}}" = "darwin" ]' + msg: "task build:all 仅支持在 macOS 上执行;Linux 需在原生 Linux 主机构建。" + cmds: + - task: clean:dist + - task: build:darwin:arm64 + - task: build:darwin:amd64 + - task: build:windows:386 + - task: build:windows:amd64 + + build:current: + internal: true + cmds: + - task: '{{if eq OS "darwin"}}build:darwin:current{{else if eq OS "linux"}}build:linux:current{{else}}build:windows:current{{end}}' + + build:darwin:current: + internal: true + cmds: + - task: darwin:package:dmg + vars: + ARCH: '{{ARCH}}' + BINARY_NAME: '{{.CURRENT_DARWIN_NAME}}' + OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_DARWIN_NAME}}' + APP_BUNDLE: '{{.CURRENT_DARWIN_NAME}}.app' + DMG_NAME: '{{.CURRENT_DARWIN_NAME}}.dmg' + SCAN: '{{.SCAN}}' + + build:darwin:arm64: + internal: false + cmds: + - task: darwin:package:dmg + vars: + ARCH: arm64 + BINARY_NAME: macos-arm64 + OUTPUT: '{{.BIN_DIR}}/macos-arm64' + APP_BUNDLE: macos-arm64.app + DMG_NAME: macos-arm64.dmg + SCAN: '{{.SCAN}}' + + build:darwin:amd64: + internal: true + cmds: + - task: darwin:package:dmg + vars: + ARCH: amd64 + BINARY_NAME: macos-intel + OUTPUT: '{{.BIN_DIR}}/macos-intel' + APP_BUNDLE: macos-intel.app + DMG_NAME: macos-intel.dmg + SCAN: '{{.SCAN}}' + + build:windows:current: + internal: true + cmds: + - task: windows:create:zip + vars: + ARCH: '{{.CURRENT_WINDOWS_ARCH}}' + OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_WINDOWS_NAME}}.exe' + ZIP_NAME: '{{.CURRENT_WINDOWS_NAME}}.zip' + SCAN: '{{.SCAN}}' + + build:windows:386: + internal: true + cmds: + - task: windows:create:zip + vars: + ARCH: 386 + OUTPUT: '{{.BIN_DIR}}/windows-32.exe' + ZIP_NAME: windows-32.zip + SCAN: '{{.SCAN}}' + + build:windows:amd64: + cmds: + - task: windows:create:zip + vars: + ARCH: amd64 + OUTPUT: '{{.BIN_DIR}}/windows-64.exe' + ZIP_NAME: windows-64.zip + SCAN: '{{.SCAN}}' + + build:windows:installer: + summary: 构建 Windows NSIS 安装包(含卸载程序) + cmds: + - task: clean:dist + - task: windows:create:nsis:installer + vars: + ARCH: amd64 + SCAN: '{{.SCAN}}' + + build:linux:current: + internal: true + cmds: + - task: linux:package:archive + vars: + ARCH: '{{.CURRENT_LINUX_ARCH}}' + BINARY_NAME: '{{.CURRENT_LINUX_NAME}}' + OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_LINUX_NAME}}' + ARCHIVE_BINARY_NAME: '{{.APP_NAME}}' + ARCHIVE_PATH: '{{.BIN_DIR}}/{{.CURRENT_LINUX_NAME}}.tar.gz' + SCAN: '{{.SCAN}}' + + build:linux:amd64: + cmds: + - task: linux:package:archive + vars: + ARCH: amd64 + BINARY_NAME: linux-amd64 + OUTPUT: '{{.BIN_DIR}}/linux-amd64' + ARCHIVE_BINARY_NAME: '{{.APP_NAME}}' + ARCHIVE_PATH: '{{.BIN_DIR}}/linux-amd64.tar.gz' + SCAN: '{{.SCAN}}' + + cursor-tab-server:docker:amd64: + summary: 构建 cursor-tab-server 的 linux/amd64 Docker 镜像并保存到 cursor-tab-server 目录 + vars: + IMAGE_NAME: '{{.IMAGE_NAME | default "cursor-tab-server:linux-amd64"}}' + IMAGE_TAR: '{{.IMAGE_TAR | default "cursor-tab-server/cursor-tab-server-linux-amd64.tar"}}' + CONTEXT_DIR: cursor-tab-server + DOCKERFILE: cursor-tab-server/Dockerfile + preconditions: + - sh: docker info >/dev/null 2>&1 + msg: "未检测到可用的 Docker daemon,请先启动 Docker。" + - sh: test -f "{{.DOCKERFILE}}" + msg: "未找到 cursor-tab-server/Dockerfile。" + - sh: test -f "{{.CONTEXT_DIR}}/go.mod" + msg: "未找到 cursor-tab-server/go.mod。" + cmds: + - mkdir -p "{{.CONTEXT_DIR}}" + - docker build --platform linux/amd64 -t "{{.IMAGE_NAME}}" -f "{{.DOCKERFILE}}" "{{.CONTEXT_DIR}}" + - docker save "{{.IMAGE_NAME}}" -o "{{.IMAGE_TAR}}" + - ls -lh "{{.IMAGE_TAR}}" + + clean:dist: + internal: true + cmds: + - mkdir -p "{{.BIN_DIR}}" + - rm -f "{{.BIN_DIR}}/macos-arm64.dmg" "{{.BIN_DIR}}/macos-intel.dmg" "{{.BIN_DIR}}/windows-32.zip" "{{.BIN_DIR}}/windows-64.zip" "{{.BIN_DIR}}/linux-amd64.tar.gz" + - rm -f "{{.BIN_DIR}}/macos-arm64" "{{.BIN_DIR}}/macos-intel" "{{.BIN_DIR}}/windows-32.exe" "{{.BIN_DIR}}/windows-64.exe" "{{.BIN_DIR}}/linux-amd64" + - rm -f "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.exe" + - rm -rf "{{.BIN_DIR}}/macos-arm64.app" "{{.BIN_DIR}}/macos-intel.app" "{{.BIN_DIR}}/{{.APP_NAME}}.app" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app" + + dev: + summary: 启动开发模式(前台,终端会保持占用) + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + + setup:docker: + summary: 构建 Linux 交叉编译 Docker 镜像 + cmds: + - task: common:setup:docker + + release:clean: + summary: 清理当前版本的发布目录 + cmds: + - rm -rf "{{.RELEASE_DIR}}" + - mkdir -p "{{.RELEASE_DIR}}" + + release:ensure:dir: + internal: true + cmds: + - mkdir -p "{{.RELEASE_DIR}}" + + release:notes: + summary: 从 release-notes.md 生成当前版本的发布说明文件(README.md 仅用于公开仓库同步) + internal: true + preconditions: + - sh: test -f "{{.RELEASE_SOURCE_PATH}}" + msg: "未找到 release-notes.md,请先创建发布日志文件。" + cmds: + - go run ./scripts/release notes -config ./build/config.yml -out "{{.RELEASE_NOTES_PATH}}" -source "{{.RELEASE_SOURCE_PATH}}" + + release:build:macos:arm64: + cmds: + - task: darwin:package:archive + vars: + ARCH: arm64 + BINARY_NAME: cursor-release-macos-arm64 + OUTPUT: '{{.BIN_DIR}}/cursor-release-macos-arm64' + APP_BUNDLE: '{{.APP_NAME}}.app' + ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-arm64.tar.gz' + SCAN: '{{.SCAN}}' + + release:build:macos:amd64: + internal: true + cmds: + - task: darwin:package:archive + vars: + ARCH: amd64 + BINARY_NAME: cursor-release-macos-amd64 + OUTPUT: '{{.BIN_DIR}}/cursor-release-macos-amd64' + APP_BUNDLE: '{{.APP_NAME}}.app' + ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-amd64.tar.gz' + SCAN: '{{.SCAN}}' + + release:build:windows:amd64: + cmds: + - task: windows:create:zip + vars: + ARCH: amd64 + OUTPUT: '{{.BIN_DIR}}/{{.RELEASE_BASE_NAME}}-windows-amd64.exe' + ZIP_NAME: 'release/{{.APP_VERSION}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-windows-amd64.zip' + SCAN: '{{.SCAN}}' + + release:build:linux:amd64: + cmds: + - task: linux:package:archive + vars: + ARCH: amd64 + BINARY_NAME: cursor-release-linux-amd64 + OUTPUT: '{{.BIN_DIR}}/cursor-release-linux-amd64' + ARCHIVE_BINARY_NAME: '{{.APP_NAME}}' + ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-linux-amd64.tar.gz' + SCAN: '{{.SCAN}}' + + release:manifest: + summary: 生成 update.json + internal: true + cmds: + - go run ./scripts/release manifest -config ./build/config.yml -assets-dir "{{.RELEASE_DIR}}" -out "{{.RELEASE_DIR}}/update.json" -repo "{{.RELEASE_REPO}}" -base-name "{{.RELEASE_BASE_NAME}}" -notes "{{.RELEASE_NOTES_PATH}}" + + release:prepare: + summary: 在当前主机上生成对应的发布资产 + preconditions: + - sh: '[ "{{OS}}" = "darwin" ] || [ "{{OS}}" = "linux" ] || [ "{{OS}}" = "windows" ]' + msg: "release:prepare 仅支持在 macOS、Linux 或 Windows 上执行。" + - sh: wails3 version >/dev/null 2>&1 + msg: "未检测到 wails3,请先安装 Wails v3 CLI。" + cmds: + - task: '{{if eq OS "darwin"}}release:prepare:darwin{{else if eq OS "linux"}}release:prepare:linux{{else}}release:prepare:windows{{end}}' + + release:prepare:windows: + summary: 在 Windows 上生成约定结构的发布资产 + preconditions: + - sh: '[ "{{OS}}" = "windows" ]' + msg: "release:prepare:windows 仅支持在 Windows 上执行。" + - sh: wails3 version >/dev/null 2>&1 + msg: "未检测到 wails3,请先安装 Wails v3 CLI。" + cmds: + - mkdir -p "{{.WINDOWS_RELEASE_DIR}}" + - task: common:update:build-assets + - task: windows:create:zip + vars: + ARCH: amd64 + OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor.exe' + ZIP_NAME: 'release/v{{.APP_VERSION}}/local-cursor-v{{.APP_VERSION}}-windows-x64-portable.zip' + ARCHIVE_BINARY_NAME: windows-64.exe + KEEP_OUTPUT: "true" + SCAN: '{{.SCAN}}' + - task: windows:create:nsis:installer + vars: + ARCH: amd64 + OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor.exe' + INSTALLER_OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor-amd64-installer.exe' + SCAN: '{{.SCAN}}' + - powershell -NoProfile -Command "Compress-Archive -LiteralPath '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor-amd64-installer.exe' -DestinationPath '{{.WINDOWS_RELEASE_DIR}}/local-cursor-v{{.APP_VERSION}}-installer.zip' -Force -ErrorAction Stop" + + release:prepare:darwin: + summary: 在 macOS 上生成 macOS/Windows/Linux 发布资产 + preconditions: + - sh: '[ "{{OS}}" = "darwin" ]' + msg: "release:prepare:darwin 仅支持在 macOS 上执行。" + - sh: wails3 version >/dev/null 2>&1 + msg: "未检测到 wails3,请先安装 Wails v3 CLI。" + cmds: + - task: release:clean + - task: common:update:build-assets + - task: release:notes + - task: release:build:macos:arm64 + - task: release:build:macos:amd64 + - task: release:build:windows:amd64 + - task: release:build:linux:amd64 + + release:prepare:linux: + summary: 在当前主机上生成 Linux 发布资产 + preconditions: + - sh: '[ "{{OS}}" = "linux" ] || [ "{{OS}}" = "darwin" ]' + msg: "release:prepare:linux 当前仅支持在 Linux 或 macOS 上执行。" + - sh: wails3 version >/dev/null 2>&1 + msg: "未检测到 wails3,请先安装 Wails v3 CLI。" + cmds: + - task: release:ensure:dir + - task: common:update:build-assets + - task: release:notes + - task: release:build:linux:amd64 + + release:verify:assets: + summary: 验证当前版本发布资产是否齐全 + preconditions: + - sh: test -f "{{.RELEASE_NOTES_PATH}}" + msg: "未找到发布说明,请先执行对应的 release:prepare 任务。" + - sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-arm64.tar.gz" + msg: "缺少 macOS arm64 发布资产。" + - sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-amd64.tar.gz" + msg: "缺少 macOS amd64 发布资产。" + - sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-windows-amd64.zip" + msg: "缺少 Windows amd64 发布资产。" + - sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-linux-amd64.tar.gz" + msg: "缺少 Linux amd64 发布资产。" + cmds: + - echo "release assets verified" + + release:sync:readme: + summary: 同步 README 到公开发布仓库(发布日志来源固定为 release-notes.md) + internal: true + env: + GH_PAGER: cat + GIT_PAGER: cat + PAGER: cat + preconditions: + - sh: gh --version >/dev/null 2>&1 + msg: "未检测到 gh,请先安装 GitHub CLI。" + - sh: gh auth token >/dev/null 2>&1 + msg: "gh 未登录,请先执行 gh auth login。" + cmds: + - | + python3 - <<'PY' + import base64 + import json + import pathlib + import subprocess + import sys + import time + + RETRYABLE_TOKENS = ("EOF", "timeout", "TLS", "temporarily unavailable", "connection reset", "connection refused") + + def run_gh(cmd, *, input_text=None, allow_404=False, retries=4): + last = None + for attempt in range(1, retries + 1): + completed = subprocess.run( + cmd, + input=input_text, + text=True, + capture_output=True, + ) + if completed.returncode == 0: + return completed + stderr = (completed.stderr or "").strip() + stdout = (completed.stdout or "").strip() + combined = f"{stderr}\n{stdout}".strip() + if allow_404 and "404" in combined: + return completed + last = completed + if not any(token.lower() in combined.lower() for token in RETRYABLE_TOKENS): + return completed + if attempt < retries: + time.sleep(min(2 ** (attempt - 1), 5)) + return last + + repo = "{{.RELEASE_REPO}}" + readme_path = pathlib.Path("README.md") + content = readme_path.read_bytes() + encoded = base64.b64encode(content).decode("ascii") + + get_cmd = [ + "gh", "api", + f"repos/{repo}/contents/README.md", + ] + result = run_gh(get_cmd, allow_404=True) + + payload = { + "message": "docs: sync README from source repo", + "content": encoded, + "branch": "main", + } + + if result.returncode == 0: + existing = json.loads(result.stdout) + existing_content = base64.b64decode(existing["content"]) + if existing_content == content: + sys.exit(0) + payload["sha"] = existing["sha"] + + put_cmd = [ + "gh", "api", + f"repos/{repo}/contents/README.md", + "--method", "PUT", + "--input", "-", + ] + completed = run_gh(put_cmd, input_text=json.dumps(payload)) + if completed.returncode != 0 and completed.stderr: + sys.stderr.write(completed.stderr) + sys.exit(completed.returncode) + PY + + release:github: + summary: 发布当前版本到 GitHub Releases + env: + GH_PAGER: cat + GIT_PAGER: cat + PAGER: cat + preconditions: + - sh: gh --version >/dev/null 2>&1 + msg: "未检测到 gh,请先安装 GitHub CLI。" + - sh: gh auth token >/dev/null 2>&1 + msg: "gh 未登录,请先执行 gh auth login。" + cmds: + - task: release:verify:assets + - task: release:manifest + - task: release:sync:readme + - | + python3 - <<'PY' + import mimetypes + import json + import pathlib + import subprocess + import sys + import time + + RETRYABLE_TOKENS = ("EOF", "timeout", "TLS", "temporarily unavailable", "connection reset", "connection refused") + + def run_gh(cmd, *, input_text=None, allow_404=False, retries=4): + last = None + for attempt in range(1, retries + 1): + completed = subprocess.run( + cmd, + input=input_text, + text=True, + capture_output=True, + ) + if completed.returncode == 0: + return completed + stderr = (completed.stderr or "").strip() + stdout = (completed.stdout or "").strip() + combined = f"{stderr}\n{stdout}".strip() + if allow_404 and "404" in combined: + return completed + last = completed + if not any(token.lower() in combined.lower() for token in RETRYABLE_TOKENS): + return completed + if attempt < retries: + time.sleep(min(2 ** (attempt - 1), 5)) + return last + + repo = "{{.RELEASE_REPO}}" + version = "{{.APP_VERSION}}" + tag = f"v{version}" + notes = pathlib.Path("{{.RELEASE_NOTES_PATH}}").read_text(encoding="utf-8") + assets = sorted([ + pathlib.Path("{{.RELEASE_DIR}}/update.json"), + *pathlib.Path("{{.RELEASE_DIR}}").glob("*.tar.gz"), + *pathlib.Path("{{.RELEASE_DIR}}").glob("*.zip"), + ]) + + get_cmd = [ + "gh", "api", + f"repos/{repo}/releases/tags/{tag}", + ] + result = run_gh(get_cmd, allow_404=True) + + payload = { + "tag_name": tag, + "target_commitish": "main", + "name": tag, + "body": notes, + "draft": False, + "prerelease": False, + } + + if result.returncode == 0: + release = json.loads(result.stdout) + release_id = release["id"] + edit_cmd = [ + "gh", "api", + f"repos/{repo}/releases/{release_id}", + "--method", "PATCH", + "--input", "-", + ] + completed = run_gh(edit_cmd, input_text=json.dumps(payload)) + if completed.returncode != 0: + if completed.stderr: + sys.stderr.write(completed.stderr) + sys.exit(completed.returncode) + refreshed = run_gh(get_cmd) + if refreshed.returncode != 0: + if refreshed.stderr: + sys.stderr.write(refreshed.stderr) + sys.exit(refreshed.returncode) + release = json.loads(refreshed.stdout) + else: + create_cmd = [ + "gh", "api", + f"repos/{repo}/releases", + "--method", "POST", + "--input", "-", + ] + completed = run_gh(create_cmd, input_text=json.dumps(payload)) + if completed.returncode != 0: + sys.stderr.write(completed.stderr) + sys.exit(completed.returncode) + release = json.loads(completed.stdout) + + upload_url = release["upload_url"].split("{", 1)[0] + existing = {asset["name"]: asset["id"] for asset in release.get("assets", [])} + + for asset in assets: + name = asset.name + if name in existing: + delete_cmd = [ + "gh", "api", + f"repos/{repo}/releases/assets/{existing[name]}", + "--method", "DELETE", + ] + deleted = run_gh(delete_cmd) + if deleted.returncode != 0: + if deleted.stderr: + sys.stderr.write(deleted.stderr) + sys.exit(deleted.returncode) + + content_type = mimetypes.guess_type(name)[0] or "application/octet-stream" + upload_cmd = [ + "gh", "api", + f"{upload_url}?name={name}", + "--method", "POST", + "--header", f"Content-Type: {content_type}", + "--input", str(asset), + ] + uploaded = run_gh(upload_cmd) + if uploaded.returncode != 0: + if uploaded.stderr: + sys.stderr.write(uploaded.stderr) + sys.exit(uploaded.returncode) + PY diff --git a/build/Taskfile.yml b/build/Taskfile.yml new file mode 100644 index 0000000..e88106d --- /dev/null +++ b/build/Taskfile.yml @@ -0,0 +1,153 @@ +version: "3" + +vars: + ROOT_DIR: + sh: 'cd "{{.TASKFILE_DIR}}/.." && pwd' + BUILD_DIR: + sh: 'cd "{{.TASKFILE_DIR}}" && pwd' + +tasks: + extract:proto: + summary: 从 Cursor 扩展快照提取 proto + dir: '{{.ROOT_DIR}}' + preconditions: + - sh: 'test -z "{{.PROTO_INPUT}}" || test -f "{{.PROTO_INPUT}}"' + msg: "PROTO_INPUT 指向的 Cursor 扩展 bundle 不存在。" + - sh: 'test -n "{{.PROTO_INPUT}}" || test -f ./proto/extensions-cursor-app/cursor-always-local/dist/main.js || test -f /Applications/Cursor.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js' + msg: "未找到 Cursor 扩展 bundle;请传入 PROTO_INPUT=/path/to/cursor-always-local/dist/main.js。" + cmds: + - chmod +x ./proto/extract_extensions_proto.sh + - '{{if .PROTO_INPUT}}./proto/extract_extensions_proto.sh "{{.PROTO_INPUT}}"{{else}}./proto/extract_extensions_proto.sh{{end}}' + + sync:proto: + summary: 从 Cursor 扩展快照同步 proto 并重新生成 Go 代码 + dir: '{{.ROOT_DIR}}' + deps: + - task: extract:proto + cmds: + - cp ./proto/from_extensions/agent_v1.proto ./proto/agent_v1.proto + - cp ./proto/from_extensions/aiserver_v1.proto ./proto/aiserver_v1.proto + - perl -0pi -e 's|option go_package = "react-admin/cursor-server/gen/agent/v1;agentv1";|option go_package = "cursor/gen/agentv1;agentv1";|' ./proto/agent_v1.proto + - perl -0pi -e 's|option go_package = "react-admin/cursor-server/gen/aiserver/v1;aiserverv1";|option go_package = "cursor/gen/aiserverv1;aiserverv1";|' ./proto/aiserver_v1.proto + - rm -rf ./gen/agentv1 ./gen/aiserverv1 + - task: generate:proto + + generate:proto: + summary: 生成 proto Go/Connect 代码 + dir: '{{.ROOT_DIR}}' + sources: + - proto/*.proto + generates: + - gen/agentv1/*.go + - gen/aiserverv1/*.go + - gen/aiserverv1/aiserverv1connect/*.go + preconditions: + - sh: protoc --version + msg: "未检测到 protoc,请先安装 Protocol Buffers 编译器。" + - sh: protoc-gen-go --version >/dev/null 2>&1 || which protoc-gen-go + msg: "未检测到 protoc-gen-go,请先安装。" + - sh: protoc-gen-connect-go --version >/dev/null 2>&1 || which protoc-gen-connect-go + msg: "未检测到 protoc-gen-connect-go,请先安装。" + cmds: + - rm -rf ./gen/agentv1 ./gen/aiserverv1 + - protoc -I ./proto --go_out=. --go_opt=module=cursor --connect-go_out=. --connect-go_opt=module=cursor ./proto/agent_v1.proto ./proto/aiserver_v1.proto + - gofmt -w $(find ./gen -name '*.go' -type f) + + go:mod:tidy: + summary: 整理 Go 依赖 + internal: true + dir: '{{.ROOT_DIR}}' + cmds: + - go mod tidy + + install:frontend:deps: + summary: 安装前端依赖(Yarn) + dir: '{{.ROOT_DIR}}/frontend' + sources: + - package.json + - yarn.lock + generates: + - node_modules + preconditions: + - sh: yarn --version + msg: "未检测到 Yarn,请先安装 Yarn。" + cmds: + - yarn install --frozen-lockfile + + generate:bindings: + label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) + summary: 生成前端绑定 + dir: '{{.ROOT_DIR}}' + deps: + - task: generate:proto + - task: go:mod:tidy + sources: + - "**/*.[jt]s" + - exclude: frontend/**/* + - frontend/bindings/**/* + - "**/*.go" + - go.mod + - go.sum + generates: + - frontend/bindings/**/* + cmds: + - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true + + build:frontend: + label: build:frontend (DEV={{.DEV}} SCAN={{.SCAN}}) + summary: 构建前端资源 + dir: '{{.ROOT_DIR}}/frontend' + sources: + - "**/*" + generates: + - dist/**/* + deps: + - task: install:frontend:deps + - task: generate:bindings + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + cmds: + - yarn run {{.BUILD_COMMAND}}{{if eq .SCAN "true"}} --scan{{end}} + env: + PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}' + vars: + BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}' + SCAN: '{{.SCAN | default "false"}}' + + dev:frontend: + summary: 启动前端开发服务 + dir: '{{.ROOT_DIR}}/frontend' + deps: + - task: install:frontend:deps + cmds: + - yarn run dev --port {{.VITE_PORT}} --strictPort + + generate:icons: + summary: 仅使用 appicon.png 生成 Windows 与 macOS 图标 + dir: '{{.BUILD_DIR}}' + sources: + - "appicon.png" + generates: + - "darwin/icons.icns" + - "windows/icon.ico" + cmds: + - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico + + update:build-assets: + summary: 根据配置更新构建资产 + dir: '{{.BUILD_DIR}}' + cmds: + - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . + - rm -rf ios + + setup:docker: + summary: 构建 Linux 交叉编译 Docker 镜像 + dir: '{{.ROOT_DIR}}' + preconditions: + - sh: docker info >/dev/null 2>&1 + msg: "未检测到可用的 Docker daemon,请先启动 Docker。" + - sh: test -f ./build/docker/Dockerfile.cross + msg: "未找到 build/docker/Dockerfile.cross。" + cmds: + - docker build --platform linux/amd64{{if .BASE_IMAGE}} --build-arg BASE_IMAGE="{{.BASE_IMAGE}}"{{end}}{{if .GO_TARBALL_URL}} --build-arg GO_TARBALL_URL="{{.GO_TARBALL_URL}}"{{end}} -t cursor-linux-amd64-cross -f ./build/docker/Dockerfile.cross ./build/docker diff --git a/build/appicon.png b/build/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..457f9a9f4ea513ffcf12a557cf2a0bfcf0701902 GIT binary patch literal 76264 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 z

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