mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
feat(cursor): split legacy bridge from cliproxy provider
This commit is contained in:
+32
-18
@@ -1,17 +1,20 @@
|
||||
# Cursor IDE Integration
|
||||
|
||||
This guide covers the current CCS-owned Cursor runtime, including auth import, local daemon lifecycle, live probe checks, and dashboard controls.
|
||||
This guide covers the deprecated CCS-owned Cursor IDE bridge, including auth import, local daemon lifecycle, live probe checks, and dashboard controls.
|
||||
|
||||
`ccs cursor` now belongs to the CLIProxy-backed Cursor provider path.
|
||||
Use `ccs legacy cursor` for the deprecated local bridge documented here.
|
||||
|
||||
## What It Provides
|
||||
|
||||
- OpenAI-compatible local endpoint powered by Cursor credentials.
|
||||
- Anthropic-compatible local endpoint at `/v1/messages` for Claude-native clients.
|
||||
- Cursor model list and chat completions via the local CCS daemon.
|
||||
- Dedicated dashboard page: `ccs config` -> `Cursor IDE`.
|
||||
- Dedicated dashboard page: `ccs config` -> `Deprecated` -> `Cursor IDE (Legacy)`.
|
||||
|
||||
## What This Runtime Actually Does
|
||||
|
||||
`ccs cursor` does not launch Cursor IDE itself.
|
||||
`ccs legacy cursor` does not launch Cursor IDE itself.
|
||||
|
||||
The current workflow is:
|
||||
1. import Cursor credentials from local SQLite or manual input
|
||||
@@ -32,7 +35,7 @@ Treat this as a CCS-managed Cursor bridge, not a generic CLIProxy-backed provide
|
||||
### 1) Enable integration
|
||||
|
||||
```bash
|
||||
ccs cursor enable
|
||||
ccs legacy cursor enable
|
||||
```
|
||||
|
||||
### 2) Import credentials
|
||||
@@ -40,25 +43,25 @@ ccs cursor enable
|
||||
Auto-detect from Cursor local SQLite state:
|
||||
|
||||
```bash
|
||||
ccs cursor auth
|
||||
ccs legacy cursor auth
|
||||
```
|
||||
|
||||
Manual fallback:
|
||||
|
||||
```bash
|
||||
ccs cursor auth --manual --token <token> --machine-id <machine-id>
|
||||
ccs legacy cursor auth --manual --token <token> --machine-id <machine-id>
|
||||
```
|
||||
|
||||
### 3) Start daemon
|
||||
|
||||
```bash
|
||||
ccs cursor start
|
||||
ccs legacy cursor start
|
||||
```
|
||||
|
||||
### 4) Run a live probe
|
||||
|
||||
```bash
|
||||
ccs cursor probe
|
||||
ccs legacy cursor probe
|
||||
```
|
||||
|
||||
Use this to verify that the current build can complete one real authenticated request through the local daemon.
|
||||
@@ -66,26 +69,37 @@ Use this to verify that the current build can complete one real authenticated re
|
||||
### 5) Run Cursor-backed Claude
|
||||
|
||||
```bash
|
||||
ccs cursor "explain this repo"
|
||||
ccs legacy cursor "explain this repo"
|
||||
```
|
||||
|
||||
### 6) Verify status
|
||||
|
||||
```bash
|
||||
ccs cursor status
|
||||
ccs legacy cursor status
|
||||
```
|
||||
|
||||
Use `ccs cursor` with bare or normal Claude args to run through the local Cursor proxy.
|
||||
Use `ccs legacy cursor` with bare or normal Claude args to run through the local Cursor proxy.
|
||||
The admin namespace remains available for setup and inspection:
|
||||
|
||||
```bash
|
||||
ccs cursor help
|
||||
ccs legacy cursor help
|
||||
```
|
||||
|
||||
### 7) Stop daemon
|
||||
|
||||
```bash
|
||||
ccs cursor stop
|
||||
ccs legacy cursor stop
|
||||
```
|
||||
|
||||
## Supported Cursor Provider Path
|
||||
|
||||
For the supported CLIProxy-backed Cursor provider, use:
|
||||
|
||||
```bash
|
||||
ccs cursor --auth
|
||||
ccs cursor --accounts
|
||||
ccs cursor --config
|
||||
ccs cursor "task"
|
||||
```
|
||||
|
||||
## Runtime Defaults
|
||||
@@ -96,7 +110,7 @@ ccs cursor stop
|
||||
- Model list resolution: authenticated live fetch when available, with cached/default fallback.
|
||||
- Request model validation: if a requested model is not present in the available Cursor model catalog, daemon falls back to the resolved default model.
|
||||
- Daemon API surface: `POST /v1/chat/completions`, `POST /v1/messages`, and `GET /v1/models`.
|
||||
- Live verification: `ccs cursor probe` or `POST /api/cursor/probe`
|
||||
- Live verification: `ccs legacy cursor probe` or `POST /api/cursor/probe`
|
||||
|
||||
These values are managed in unified config and can be updated from CLI or dashboard.
|
||||
|
||||
@@ -108,7 +122,7 @@ Open dashboard:
|
||||
ccs config
|
||||
```
|
||||
|
||||
Then navigate to `Cursor IDE` in the sidebar.
|
||||
Then navigate to `Cursor IDE (Legacy)` in the `Deprecated` section.
|
||||
|
||||
Available controls:
|
||||
|
||||
@@ -131,9 +145,9 @@ When raw settings include a local `ANTHROPIC_BASE_URL` port override, CCS synchr
|
||||
|
||||
### `Not authenticated` or `expired` in `ccs cursor status`
|
||||
|
||||
- Re-run `ccs cursor auth` (or manual auth command).
|
||||
- Re-run `ccs legacy cursor auth` (or manual auth command).
|
||||
|
||||
### `ccs cursor probe` fails even though status is green
|
||||
### `ccs legacy cursor probe` fails even though status is green
|
||||
|
||||
- `status` proves local config/auth/daemon readiness only.
|
||||
- `probe` proves the live runtime path.
|
||||
@@ -148,4 +162,4 @@ When raw settings include a local `ANTHROPIC_BASE_URL` port override, CCS synchr
|
||||
### Daemon fails to start
|
||||
|
||||
- Check if port `20129` is in use.
|
||||
- Change port in dashboard config tab, then retry `ccs cursor start`.
|
||||
- Change port in dashboard config tab, then retry `ccs legacy cursor start`.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
phase: 1
|
||||
title: "CLI Routing & Namespacing"
|
||||
status: complete
|
||||
effort: "6h"
|
||||
---
|
||||
|
||||
# Phase 1: CLI Routing & Namespacing
|
||||
|
||||
## Context Links
|
||||
|
||||
- `plan.md`
|
||||
- `src/ccs.ts`
|
||||
- `src/auth/profile-detector.ts`
|
||||
- `src/cursor/constants.ts`
|
||||
- `src/commands/root-command-router.ts`
|
||||
- `src/commands/command-catalog.ts`
|
||||
- `src/commands/help-command.ts`
|
||||
- `src/commands/cursor-command.ts`
|
||||
- `src/commands/cursor-command-display.ts`
|
||||
- `src/types/profile.ts`
|
||||
- `src/config/reserved-names.ts`
|
||||
|
||||
## Overview
|
||||
|
||||
- Priority: P1
|
||||
- Owner scope: CLI entry, help, profile detection, command naming
|
||||
- Goal: make `cursor` provider-first and move the deprecated bridge under `legacy cursor`
|
||||
|
||||
## Key Insights
|
||||
|
||||
- The current collision is structural, not cosmetic. `ccs cursor` means "legacy bridge" in `src/ccs.ts` and `src/auth/profile-detector.ts`, but `cursor` is also listed as a built-in CLIProxy provider.
|
||||
- `shouldUseCursorCliproxyShortcut()` is only a heuristic escape hatch. It does not fix bare `ccs cursor`, quoted prompts, or help routing.
|
||||
- Help is currently inconsistent: provider help exists generically, but `cursor` is excluded and routed to bridge help instead.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Reserve `cursor` for CLIProxy runtime and CLIProxy admin flags.
|
||||
- Introduce explicit legacy syntax: `ccs legacy cursor ...`.
|
||||
- Keep a release-N alias for old legacy admin subcommands only.
|
||||
- Rename internal bridge-only profile typing from ambiguous `cursor` to explicit `legacy-cursor`.
|
||||
- Keep file ownership isolated to CLI/router/help files in this phase.
|
||||
|
||||
## Data Flow
|
||||
|
||||
- Provider path:
|
||||
`argv -> root command resolution -> provider shortcut/help path -> ProfileDetector(type=cliproxy, provider=cursor) -> CLIProxy runtime`
|
||||
- Legacy path:
|
||||
`argv -> legacy root command -> legacy cursor subrouter -> ProfileDetector(type=legacy-cursor) or direct handler -> local bridge runtime`
|
||||
- Deprecated alias path, release N only:
|
||||
`argv=ccs cursor auth|status|... -> alias shim -> warning -> dispatch to legacy cursor handler`
|
||||
|
||||
## Architecture
|
||||
|
||||
- Add a new root command namespace: `ccs legacy`.
|
||||
- Add nested routing under `legacy` with `cursor` as the first migrated leaf. Do not overload `cursor` itself any longer.
|
||||
- Remove provider exceptions for `cursor` from the generic provider help/routing logic. `ccs cursor --help` should now use provider shortcut help.
|
||||
- Convert bridge-only type checks from `profileInfo.type === 'cursor'` to `profileInfo.type === 'legacy-cursor'`.
|
||||
- Keep `ccs cursor help` only as a release-N compatibility shim that prints:
|
||||
- `Use "ccs cursor --help" for CLIProxy Cursor`
|
||||
- `Use "ccs legacy cursor help" for the deprecated bridge`
|
||||
|
||||
## Related Code Files
|
||||
|
||||
- Modify:
|
||||
- `src/ccs.ts`
|
||||
- `src/auth/profile-detector.ts`
|
||||
- `src/cursor/constants.ts`
|
||||
- `src/commands/root-command-router.ts`
|
||||
- `src/commands/command-catalog.ts`
|
||||
- `src/commands/help-command.ts`
|
||||
- `src/commands/cursor-command.ts`
|
||||
- `src/commands/cursor-command-display.ts`
|
||||
- `src/types/profile.ts`
|
||||
- `src/config/reserved-names.ts`
|
||||
- `src/shared/claude-extension-setup.ts`
|
||||
- `src/targets/target-runtime-compatibility.ts`
|
||||
- Create:
|
||||
- `src/commands/legacy-command.ts` or `src/commands/legacy/index.ts`
|
||||
- `src/commands/legacy/cursor-command.ts` if the team wants physical separation immediately
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Add the `legacy` root command route and its help surface.
|
||||
2. Flip `src/ccs.ts` so `cursor` goes through normal CLIProxy provider routing; remove the special-case that gives the bridge ownership of the name.
|
||||
3. Replace the `shouldUseCursorCliproxyShortcut()` hack with provider-first dispatch plus a compatibility alias table for the old legacy subcommands.
|
||||
4. Update `ProfileDetector` priority order so `cursor` resolves as `cliproxy`, while `legacy cursor` resolves as `legacy-cursor`.
|
||||
5. Rename bridge-only help text, summaries, and status text to say "legacy Cursor bridge" explicitly.
|
||||
6. Audit all `profileType === 'cursor'` checks and convert only the bridge-specific ones to `legacy-cursor`.
|
||||
|
||||
## Todo List
|
||||
|
||||
- [x] Add `legacy cursor` routing
|
||||
- [x] Make `ccs cursor` provider-first for bare, prompt, and `--help` usage
|
||||
- [x] Add deprecated alias forwarding for old admin subcommands
|
||||
- [x] Rename internal bridge profile path to `legacy-cursor`
|
||||
- [x] Update provider help, completion, and command catalog summaries
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- `ccs cursor "task"` resolves to CLIProxy Cursor.
|
||||
- `ccs legacy cursor "task"` resolves to the old bridge.
|
||||
- `ccs cursor --help` shows provider shortcut help.
|
||||
- `ccs cursor auth` still works in release N, but prints an exact replacement warning.
|
||||
- No CLI path depends on `shouldUseCursorCliproxyShortcut()` to disambiguate runtime meaning.
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- High likelihood / high impact: users with scripts calling `ccs cursor "task"` will hit the provider path immediately.
|
||||
Mitigation: call this out in release notes, keep admin aliases, add explicit warning when legacy files/config are detected and the user invokes `ccs cursor` with no flags.
|
||||
- Medium likelihood / medium impact: bridge-only type renames may break target compatibility checks or extension setup.
|
||||
Mitigation: grep audit every `profileType === 'cursor'` branch before tests.
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
- Re-enable the old `cursor` special-case in `src/ccs.ts` and `ProfileDetector`.
|
||||
- Keep the new `legacy` namespace in place even if dormant; it is additive and safe to leave.
|
||||
- Do not roll back migrated files in this phase; routing rollback alone is enough.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- No auth material moves in this phase.
|
||||
- Preserve existing `CCS_HOME`-aware path resolution. Do not introduce `os.homedir()` shortcuts while adding the new namespace.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Phase 2 depends on the new command contract from this phase.
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
phase: 2
|
||||
title: "Storage & API Boundaries"
|
||||
status: partial
|
||||
effort: "6h"
|
||||
---
|
||||
|
||||
# Phase 2: Storage & API Boundaries
|
||||
|
||||
## Context Links
|
||||
|
||||
- `plan.md`
|
||||
- `src/config/unified-config-types.ts`
|
||||
- `src/config/unified-config-loader.ts`
|
||||
- `src/cursor/cursor-auth.ts`
|
||||
- `src/cursor/cursor-daemon-pid.ts`
|
||||
- `src/cliproxy/config/path-resolver.ts`
|
||||
- `src/cliproxy/config/env-builder.ts`
|
||||
- `src/web-server/routes/index.ts`
|
||||
- `src/web-server/routes/cursor-routes.ts`
|
||||
- `src/web-server/routes/cursor-settings-routes.ts`
|
||||
- `src/web-server/routes/cliproxy-stats-routes.ts`
|
||||
- `src/api/services/profile-lifecycle-service.ts`
|
||||
|
||||
## Overview
|
||||
|
||||
- Priority: P1
|
||||
- Owner scope: config schema, path resolution, backend APIs, migration readers
|
||||
- Goal: make legacy bridge storage explicit and guarantee CLIProxy Cursor never writes the legacy raw settings file
|
||||
|
||||
## Key Insights
|
||||
|
||||
- Top-level `config.cursor` is bridge-only configuration today and must move.
|
||||
- The legacy bridge owns `~/.ccs/cursor.settings.json`, `~/.ccs/cursor/credentials.json`, and `~/.ccs/cursor/daemon.pid`.
|
||||
- CLIProxy provider settings currently resolve through generic provider settings helpers and can still collide with the legacy file for provider `cursor`.
|
||||
- `~/.ccs/cursor.settings.json` is historically documented as legacy-owned, so it is unsafe to auto-import it into provider storage by default.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Canonical legacy config key: `legacy.cursor`
|
||||
- Canonical legacy files:
|
||||
- `~/.ccs/legacy/cursor.settings.json`
|
||||
- `~/.ccs/legacy/cursor/credentials.json`
|
||||
- `~/.ccs/legacy/cursor/daemon.pid`
|
||||
- Canonical provider file for CLIProxy Cursor only:
|
||||
- `~/.ccs/cliproxy/cursor.settings.json`
|
||||
- Canonical legacy API namespace:
|
||||
- `/api/legacy/cursor/*`
|
||||
- Compatibility reads:
|
||||
- read old `config.cursor`
|
||||
- read old `~/.ccs/cursor.settings.json`
|
||||
- read old `~/.ccs/cursor/*`
|
||||
- Compatibility writes:
|
||||
- write only the new `legacy.*` and `cliproxy/*` paths
|
||||
|
||||
## Data Flow
|
||||
|
||||
- Legacy config:
|
||||
`load config -> prefer legacy.cursor -> fallback config.cursor -> normalize -> write legacy.cursor only`
|
||||
- Legacy raw settings:
|
||||
`load /api/legacy/cursor/settings/raw -> prefer ~/.ccs/legacy/cursor.settings.json -> fallback ~/.ccs/cursor.settings.json -> write new legacy path`
|
||||
- Provider settings:
|
||||
`CLIProxy env builder/stats updater -> read ~/.ccs/cliproxy/cursor.settings.json -> if absent use defaults -> never read/write ~/.ccs/cursor.settings.json`
|
||||
|
||||
## Architecture
|
||||
|
||||
- Add a `legacy` section to unified config types and loader. Keep old `cursor` as read-only migration input during the compatibility window.
|
||||
- Move legacy bridge filesystem helpers under a `legacy/cursor` path prefix.
|
||||
- Split API routing:
|
||||
- new canonical mount: `/api/legacy/cursor`
|
||||
- release-N alias: `/api/cursor` -> same handlers + deprecation header
|
||||
- Special-case CLIProxy provider settings for `cursor` only in the provider path resolver. Do not expand this migration to every provider in this issue.
|
||||
- Treat existing `~/.ccs/cursor.settings.json` as legacy-owned. Do not auto-copy it into provider storage unless a future explicit provider migration is added.
|
||||
|
||||
## Related Code Files
|
||||
|
||||
- Modify:
|
||||
- `src/config/unified-config-types.ts`
|
||||
- `src/config/unified-config-loader.ts`
|
||||
- `src/cursor/cursor-auth.ts`
|
||||
- `src/cursor/cursor-daemon-pid.ts`
|
||||
- `src/cliproxy/config/path-resolver.ts`
|
||||
- `src/cliproxy/config/env-builder.ts`
|
||||
- `src/web-server/routes/index.ts`
|
||||
- `src/web-server/routes/cursor-routes.ts`
|
||||
- `src/web-server/routes/cursor-settings-routes.ts`
|
||||
- `src/web-server/routes/cliproxy-stats-routes.ts`
|
||||
- `src/api/services/profile-lifecycle-service.ts`
|
||||
- Create:
|
||||
- `src/web-server/routes/legacy-cursor-routes.ts`
|
||||
- `src/web-server/routes/legacy-cursor-settings-routes.ts`
|
||||
- `src/config/migrations/cursor-legacy-migration.ts` if migration logic should stay out of the loader
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Extend config types and loader to support `legacy.cursor`, with `legacy.cursor` taking precedence over old `cursor`.
|
||||
2. Update legacy bridge credential and pid helpers to use `~/.ccs/legacy/cursor/`.
|
||||
3. Update the raw settings route to use `~/.ccs/legacy/cursor.settings.json` as canonical and old root path as read fallback only.
|
||||
4. Move legacy API mounts to `/api/legacy/cursor/*` and keep `/api/cursor/*` as a warned alias for release N.
|
||||
5. Change CLIProxy Cursor provider settings resolution to `~/.ccs/cliproxy/cursor.settings.json`.
|
||||
6. Update orphan detection and cleanup logic so old `cursor.settings.json` is treated as a migration target, not a permanent provider-owned file.
|
||||
|
||||
## Todo List
|
||||
|
||||
- [ ] Add `legacy.cursor` config schema and loader precedence
|
||||
- [ ] Move bridge credentials/pid/raw settings under `~/.ccs/legacy/`
|
||||
- [x] Add canonical `/api/legacy/cursor/*` routes
|
||||
- [x] Keep release-N `/api/cursor/*` alias
|
||||
- [x] Isolate CLIProxy Cursor settings away from `~/.ccs/cursor.settings.json`
|
||||
- [ ] Update cleanup/orphan handling
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Saving legacy bridge settings writes only to `legacy.cursor` and `~/.ccs/legacy/*`.
|
||||
- CLIProxy Cursor model/env updates write only to `~/.ccs/cliproxy/cursor.settings.json`.
|
||||
- Existing legacy users can still read old config/files during the compatibility window.
|
||||
- No backend route that serves the provider path references `~/.ccs/cursor.settings.json`.
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- High likelihood / high impact: old `~/.ccs/cursor.settings.json` contents are ambiguous between bridge and provider expectations.
|
||||
Mitigation: treat the file as legacy-owned and do not auto-import it into provider storage.
|
||||
- Medium likelihood / medium impact: route aliasing may mask which API is canonical.
|
||||
Mitigation: add explicit response headers or payload flags marking `/api/cursor/*` as deprecated.
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
- Keep read fallback from old paths even if the canonical write path changes back.
|
||||
- If the new legacy API namespace causes regressions, remount `/api/cursor/*` as canonical temporarily and keep the new namespace dormant.
|
||||
- Do not delete old files during release N; cleanup stays opt-in until release N+2.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Preserve `0600` for migrated credentials and `0700` for directories.
|
||||
- Use atomic temp-file writes exactly as current routes do.
|
||||
- Never copy provider tokens into the legacy namespace or legacy tokens into provider storage automatically.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Phase 3 depends on the canonical API and path names from this phase.
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
---
|
||||
phase: 3
|
||||
title: "Dashboard & Deprecation UX"
|
||||
status: partial
|
||||
effort: "4h"
|
||||
---
|
||||
|
||||
# Phase 3: Dashboard & Deprecation UX
|
||||
|
||||
## Context Links
|
||||
|
||||
- `plan.md`
|
||||
- `ui/src/App.tsx`
|
||||
- `ui/src/components/layout/app-sidebar.tsx`
|
||||
- `ui/src/pages/cursor.tsx`
|
||||
- `ui/src/hooks/use-cursor.ts`
|
||||
- `ui/src/lib/i18n.ts`
|
||||
- `src/web-server/routes/index.ts`
|
||||
- `src/commands/cursor-command-display.ts`
|
||||
|
||||
## Overview
|
||||
|
||||
- Priority: P1
|
||||
- Owner scope: dashboard route ownership, labels, user-facing deprecation messaging
|
||||
- Goal: align dashboard semantics with CLI semantics so `/cursor` means provider and legacy UI is clearly marked and isolated
|
||||
|
||||
## Key Insights
|
||||
|
||||
- The current dashboard already admits the bridge is deprecated, but the route `/cursor` still belongs to it.
|
||||
- The page includes direct navigation to CLIProxy Cursor, which means the UX already wants a split; the route layer just has not caught up.
|
||||
- Keeping `/cursor` for legacy while CLI uses `cursor` for provider would create the same ambiguity in a different surface.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `/cursor` must become the provider-owned dashboard surface.
|
||||
- The legacy bridge page must move to `/legacy/cursor`.
|
||||
- Legacy bridge API hooks must move to `/api/legacy/cursor/*`.
|
||||
- The deprecated UX must contain exact replacements, not generic warnings.
|
||||
- Sidebar grouping must reflect support level:
|
||||
- provider view under provider/cliproxy navigation
|
||||
- legacy bridge under deprecated navigation
|
||||
|
||||
## Data Flow
|
||||
|
||||
- Provider dashboard:
|
||||
`browser /cursor -> provider view or redirect wrapper -> /cliproxy?provider=cursor -> existing CLIProxy provider APIs`
|
||||
- Legacy dashboard:
|
||||
`browser /legacy/cursor -> legacy bridge page -> useLegacyCursor hook -> /api/legacy/cursor/*`
|
||||
- Compatibility API path, release N only:
|
||||
`old UI/tests -> /api/cursor/* -> alias handler -> same legacy payload + deprecation signal`
|
||||
|
||||
## Architecture
|
||||
|
||||
- Keep provider UI DRY by making `/cursor` a thin redirect or preselected wrapper around the existing CLIProxy provider page instead of building a second Cursor-provider page.
|
||||
- Move the current `ui/src/pages/cursor.tsx` implementation to a new `legacy-cursor` page and rename its hook to `useLegacyCursor`.
|
||||
- Change nav labels from generic "Cursor IDE" to explicit "Cursor Bridge (Legacy)" in the deprecated section.
|
||||
- Update CLI and dashboard warnings to show both paths side-by-side:
|
||||
- `ccs cursor --auth` / `/cursor`
|
||||
- `ccs legacy cursor auth` / `/legacy/cursor`
|
||||
|
||||
## Related Code Files
|
||||
|
||||
- Modify:
|
||||
- `ui/src/App.tsx`
|
||||
- `ui/src/components/layout/app-sidebar.tsx`
|
||||
- `ui/src/lib/i18n.ts`
|
||||
- `src/commands/cursor-command-display.ts`
|
||||
- Move or rename:
|
||||
- `ui/src/pages/cursor.tsx` -> `ui/src/pages/legacy-cursor.tsx`
|
||||
- `ui/src/hooks/use-cursor.ts` -> `ui/src/hooks/use-legacy-cursor.ts`
|
||||
- Create:
|
||||
- `ui/src/pages/cursor-provider-redirect.tsx` if a wrapper is preferred over direct router config
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Move the legacy page and hook to `legacy-*` names and update all imports.
|
||||
2. Reassign `/cursor` to the provider path and add `/legacy/cursor` for the bridge page.
|
||||
3. Update sidebar grouping and labels so the provider path is no longer listed under Deprecated.
|
||||
4. Replace vague deprecated copy with concrete migration copy:
|
||||
- old command
|
||||
- new command
|
||||
- old route
|
||||
- new route
|
||||
5. Keep the legacy page banner persistent until release N+2, not dismissible per session.
|
||||
|
||||
## Todo List
|
||||
|
||||
- [ ] Move legacy page/hook module names to `legacy-*`
|
||||
- [x] Reassign `/cursor` and add `/legacy/cursor`
|
||||
- [x] Update deprecated nav group and labels
|
||||
- [x] Rewrite key banners, button copy, and path labels with exact replacements
|
||||
- [x] Keep provider and legacy links visible from both surfaces during release N
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Opening `/cursor` lands on the CLIProxy Cursor provider surface.
|
||||
- Opening `/legacy/cursor` lands on the bridge page with a persistent deprecation banner.
|
||||
- No dashboard component serving the provider route uses the legacy API hook.
|
||||
- Every warning banner shows the exact before/after command and route.
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- Medium likelihood / medium impact: users with bookmarked `/cursor` expect the legacy page.
|
||||
Mitigation: provider page shows a top-level "Looking for the old bridge?" callout linking to `/legacy/cursor`.
|
||||
- Low likelihood / medium impact: UI rename churn breaks lazy imports or tests.
|
||||
Mitigation: do route and hook rename in one phase and leave compatibility API alias in place until tests pass.
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
- Point `/cursor` back to the legacy page if the provider redirect breaks.
|
||||
- Keep `/legacy/cursor` additive; it does not block rollback.
|
||||
- Do not remove the deprecation banner on rollback; it still communicates future intent.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- No auth secrets should be exposed in UI copy or route params.
|
||||
- Keep manual auth dialogs scoped to the legacy page only. Provider auth remains in CLIProxy flows.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Phase 4 owns test rewrites, docs updates, and release gating for these UI changes.
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
phase: 4
|
||||
title: "Tests Docs & Rollout"
|
||||
status: complete
|
||||
effort: "4h"
|
||||
---
|
||||
|
||||
# Phase 4: Tests Docs & Rollout
|
||||
|
||||
## Context Links
|
||||
|
||||
- `plan.md`
|
||||
- `docs/cursor-integration.md`
|
||||
- `README.md`
|
||||
- `docs/system-architecture/provider-flows.md`
|
||||
- `docs/system-architecture/index.md`
|
||||
- `tests/unit/cursor/cursor-shortcut-routing.test.ts`
|
||||
- `tests/unit/web-server/cursor-settings-routes.test.ts`
|
||||
- `tests/unit/web-server/cursor-routes.test.ts`
|
||||
- `ui/tests/unit/hooks/use-cursor.test.tsx`
|
||||
- `ui/tests/unit/ui/pages/cursor-page.test.tsx`
|
||||
|
||||
## Overview
|
||||
|
||||
- Priority: P1
|
||||
- Owner scope: compatibility rollout, validation, docs/help updates, release notes
|
||||
- Goal: ship the namespace split without surprising existing bridge users or leaving docs/help inconsistent
|
||||
|
||||
## Key Insights
|
||||
|
||||
- This change has one intentional breaking behavior: positional `ccs cursor` stops being the legacy bridge.
|
||||
- Everything else can use a compatibility window: admin subcommands, API aliases, old config reads, old file-path reads.
|
||||
- Tests must lock both meanings so the ambiguity does not regress later.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Document exact before/after commands and routes.
|
||||
- Add a concrete migration path for three user groups:
|
||||
- legacy bridge users
|
||||
- CLIProxy Cursor users
|
||||
- dashboard bookmark users
|
||||
- Define removal windows for aliases and old path fallbacks.
|
||||
- Run repo quality gates after implementation:
|
||||
- root: `bun run format && bun run lint:fix && bun run validate && bun run validate:ci-parity`
|
||||
- UI: `cd ui && bun run format && bun run lint:fix && bun run validate`
|
||||
|
||||
## Test Matrix
|
||||
|
||||
- Unit:
|
||||
- provider-first cursor routing
|
||||
- legacy alias forwarding
|
||||
- `legacy.cursor` loader precedence
|
||||
- path resolvers for legacy vs provider files
|
||||
- deprecation help text snapshots
|
||||
- Integration:
|
||||
- `ccs cursor "task"` -> provider
|
||||
- `ccs legacy cursor "task"` -> bridge
|
||||
- `/api/legacy/cursor/*` canonical behavior
|
||||
- `/api/cursor/*` alias behavior during release N
|
||||
- UI:
|
||||
- `/cursor` route ownership
|
||||
- `/legacy/cursor` banner and actions
|
||||
- hook path changes and raw settings save targets
|
||||
- Manual release validation:
|
||||
- migrate old config/files in a temp `CCS_HOME`
|
||||
- verify provider path never writes `~/.ccs/cursor.settings.json`
|
||||
|
||||
## User Migration Plan
|
||||
|
||||
1. Legacy bridge users:
|
||||
- replace `ccs cursor ...` with `ccs legacy cursor ...`
|
||||
- run `ccs legacy cursor status`
|
||||
- update scripts and dashboard bookmarks to `/legacy/cursor`
|
||||
2. CLIProxy Cursor users:
|
||||
- keep using `ccs cursor ...`
|
||||
- if provider-specific settings are needed, re-save them under the new provider-owned path instead of relying on `~/.ccs/cursor.settings.json`
|
||||
3. Mixed/unclear state:
|
||||
- `ccs migrate` should move `config.cursor` and legacy files into the new legacy namespace
|
||||
- do not auto-copy the old raw settings file into provider storage
|
||||
|
||||
## Deprecation UX Plan
|
||||
|
||||
- CLI warning text, release N:
|
||||
- `ccs cursor auth` is deprecated. Use `ccs legacy cursor auth` for the old bridge or `ccs cursor --auth` for CLIProxy Cursor.
|
||||
- Dashboard banner:
|
||||
- visible on `/legacy/cursor`
|
||||
- provider route links back to legacy route with "Looking for the old bridge?"
|
||||
- Docs banner:
|
||||
- top callout in `docs/cursor-integration.md` pointing users to CLIProxy Cursor as the supported path
|
||||
|
||||
## Related Code Files
|
||||
|
||||
- Modify tests:
|
||||
- `tests/unit/cursor/cursor-shortcut-routing.test.ts`
|
||||
- `tests/unit/web-server/cursor-settings-routes.test.ts`
|
||||
- `tests/unit/web-server/cursor-routes.test.ts`
|
||||
- `ui/tests/unit/hooks/use-cursor.test.tsx`
|
||||
- `ui/tests/unit/ui/pages/cursor-page.test.tsx`
|
||||
- Modify docs:
|
||||
- `docs/cursor-integration.md`
|
||||
- `README.md` if root command examples mention Cursor
|
||||
- `docs/system-architecture/provider-flows.md`
|
||||
- `docs/system-architecture/index.md`
|
||||
- CLI help snapshots or generated references if present
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Rewrite tests around the new command contract and route ownership before removing aliases in later releases.
|
||||
2. Update docs/help text in the same PR as code changes so the new syntax ships atomically.
|
||||
3. Add migration notes to changelog/release notes with a bold callout that `ccs cursor "task"` now means CLIProxy Cursor.
|
||||
4. Keep a removal checklist for release N+1 and N+2 in the plan or roadmap so the compatibility window does not become permanent.
|
||||
|
||||
## Todo List
|
||||
|
||||
- [x] Update unit, integration, and selected UI tests
|
||||
- [x] Update docs and CLI help text
|
||||
- [x] Add migration note and deprecation wording
|
||||
- [x] Run root and UI quality gates
|
||||
- [x] Record alias-removal follow-up for N+1 and old-path-removal follow-up for N+2
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Test suite covers both provider and legacy cursor paths explicitly.
|
||||
- Docs and help text match the shipped command contract exactly.
|
||||
- Release notes include the migration table and deprecation window.
|
||||
- Quality gates pass in both root and `ui/`.
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- High likelihood / medium impact: docs or tests lag behind the command flip and users keep invoking the wrong surface.
|
||||
Mitigation: block merge until help text, docs, and tests all match the new contract.
|
||||
- Medium likelihood / medium impact: compatibility shims never get removed.
|
||||
Mitigation: create follow-up issues or roadmap entries for N+1 and N+2 removal work before merge.
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
- If rollout messaging is incomplete, revert the command flip before removing aliases.
|
||||
- If only docs/help are wrong, fix docs first and keep aliases until corrected.
|
||||
- Old-path readers stay in place through N+1, so rollback does not strand migrated users.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use temp `CCS_HOME` in tests and manual verification. Never touch the real `~/.ccs`.
|
||||
- Sanitize any migration logs or warnings so they mention paths, not token contents.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Implementation is complete when all four phases land together; do not ship phase 1 without phases 2-4.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: "Separate legacy Cursor bridge from CLIProxy Cursor provider"
|
||||
description: "Reserve `cursor` for the CLIProxy provider, move the reverse-engineered bridge under `legacy`, and split storage/UI with a staged migration."
|
||||
status: in_progress
|
||||
priority: P1
|
||||
effort: 2d
|
||||
branch: kai/feat/1016-missing-provider-integration
|
||||
tags: [cursor, cliproxy, migration, dashboard, deprecation]
|
||||
created: 2026-04-15
|
||||
blockedBy: []
|
||||
blocks: []
|
||||
---
|
||||
|
||||
# Separate legacy Cursor bridge from CLIProxy Cursor provider
|
||||
|
||||
## Goal
|
||||
|
||||
Make `cursor` mean one thing everywhere: the CLIProxy-backed provider. Move the deprecated local bridge to `legacy`, stop provider writes to `~/.ccs/cursor.settings.json`, and ship a low-risk migration window.
|
||||
|
||||
## Current Collision Points
|
||||
|
||||
- `src/ccs.ts` hardcodes `cursor` as a legacy command/profile, then reclaims only `--auth|--logout|--config|--accounts` for CLIProxy.
|
||||
- `src/auth/profile-detector.ts` resolves `cursor` to the legacy runtime before CLIProxy provider detection.
|
||||
- `src/commands/command-catalog.ts` and `src/commands/help-command.ts` advertise `cursor` as both bridge and provider.
|
||||
- `src/config/unified-config-types.ts` + `src/config/unified-config-loader.ts` store bridge config under top-level `cursor`.
|
||||
- `src/cliproxy/config/path-resolver.ts`, `src/cliproxy/config/env-builder.ts`, and `src/web-server/routes/cliproxy-stats-routes.ts` still use provider settings paths that collide with the legacy raw file.
|
||||
- `src/web-server/routes/cursor-*.ts`, `ui/src/pages/cursor.tsx`, `ui/src/hooks/use-cursor.ts`, `ui/src/App.tsx`, and `ui/src/components/layout/app-sidebar.tsx` dedicate `/cursor` and `/api/cursor/*` to the legacy bridge.
|
||||
- `docs/cursor-integration.md` documents `ccs cursor` as the bridge even though CLIProxy already exposes a `cursor` provider shortcut.
|
||||
|
||||
## Command Contract
|
||||
|
||||
Before:
|
||||
```text
|
||||
ccs cursor -> legacy bridge runtime
|
||||
ccs cursor "task" -> legacy bridge runtime
|
||||
ccs cursor auth|status|... -> legacy bridge admin
|
||||
ccs cursor --auth|--config -> CLIProxy Cursor shortcut
|
||||
```
|
||||
|
||||
After release N:
|
||||
```text
|
||||
ccs cursor -> CLIProxy Cursor runtime
|
||||
ccs cursor "task" -> CLIProxy Cursor runtime
|
||||
ccs cursor --auth|--config -> CLIProxy Cursor admin
|
||||
ccs legacy cursor -> legacy bridge runtime
|
||||
ccs legacy cursor "task" -> legacy bridge runtime
|
||||
ccs legacy cursor auth|... -> legacy bridge admin
|
||||
```
|
||||
|
||||
Compatibility window, release N only:
|
||||
- `ccs cursor auth|status|probe|models|start|stop|enable|disable|help` forwards to `ccs legacy cursor ...` with a deprecation warning.
|
||||
- Bare and positional `ccs cursor` switch immediately to the provider path; no silent legacy fallback.
|
||||
|
||||
## Phase Plan
|
||||
|
||||
| Phase | Scope | Output |
|
||||
| --- | --- | --- |
|
||||
| 1 | [CLI Routing & Namespacing](./phase-01-cli-routing-namespacing.md) | Provider-first `cursor`, explicit `legacy cursor`, updated help/catalog/type names |
|
||||
| 2 | [Storage & API Boundaries](./phase-02-storage-api-boundaries.md) | `legacy.cursor` config, split file paths, `/api/legacy/cursor/*`, provider path isolation |
|
||||
| 3 | [Dashboard & Deprecation UX](./phase-03-dashboard-deprecation-ux.md) | `/cursor` -> provider view, `/legacy/cursor` -> bridge view, clear migration UX |
|
||||
| 4 | [Tests Docs & Rollout](./phase-04-tests-docs-rollout.md) | Compatibility plan, migration steps, test matrix, docs updates, rollback gates |
|
||||
|
||||
## Rollout Sequence
|
||||
|
||||
1. Release N: add new legacy namespace, flip `ccs cursor` to provider, keep old admin subcommands and `/api/cursor/*` as warned aliases, and split provider settings away from `~/.ccs/cursor.settings.json`.
|
||||
2. Release N+1: move the remaining legacy backend/config namespaces fully under `legacy.cursor`, keep old file-path fallback and `/api/cursor/*` alias for one more release.
|
||||
3. Release N+2: remove old `config.cursor` and root-level `~/.ccs/cursor*` fallback reads, delete stale alias docs/help, and let cleanup/migrate remove leftovers.
|
||||
|
||||
## Current Implementation Status
|
||||
|
||||
- Completed in this branch:
|
||||
- `ccs cursor` is provider-first for runtime and `--help`
|
||||
- `ccs legacy cursor` works as the explicit legacy bridge namespace
|
||||
- old legacy admin subcommands under `ccs cursor ...` forward with deprecation warnings
|
||||
- CLIProxy Cursor settings no longer collide with `~/.ccs/cursor.settings.json`
|
||||
- `/cursor` redirects to the provider surface while `/legacy/cursor` serves the deprecated bridge page
|
||||
- `/api/legacy/cursor/*` is mounted and the legacy page uses that namespace
|
||||
- docs, completion, and core regression tests were updated
|
||||
- Intentionally deferred follow-up:
|
||||
- move top-level `config.cursor` to `legacy.cursor`
|
||||
- move legacy credentials/pid/raw settings fully under `~/.ccs/legacy/cursor/*`
|
||||
- rename `use-cursor` and `CursorPage` modules to explicit `legacy-*`
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- `cursor` is provider-owned in CLI help, routing, dashboard nav, and docs.
|
||||
- Legacy bridge is reachable only through `legacy cursor` and `legacy.cursor` storage.
|
||||
- CLIProxy Cursor never reads or writes `~/.ccs/cursor.settings.json`.
|
||||
- Existing legacy users have an explicit migration path, warning UX, and rollback-safe compatibility window.
|
||||
|
||||
## Docs Impact
|
||||
|
||||
Major. CLI reference, Cursor docs, dashboard tour, provider docs, and migration notes all change in the same release.
|
||||
@@ -26,6 +26,7 @@ import { getCcsDir } from '../utils/config-manager';
|
||||
import { getProfileLookupCandidates, isLegacyProfileAlias } from '../utils/profile-compat';
|
||||
import type { CLIProxyProvider } from '../cliproxy/types';
|
||||
import { CLIPROXY_PROVIDER_IDS, isCLIProxyProvider } from '../cliproxy/provider-capabilities';
|
||||
import { LEGACY_CURSOR_PROFILE_NAME } from '../cursor/constants';
|
||||
import { normalizeCopilotModelId } from '../copilot/copilot-model-normalizer';
|
||||
import type { TargetType } from '../targets/target-adapter';
|
||||
import type { ProfileType } from '../types/profile';
|
||||
@@ -296,20 +297,17 @@ class ProfileDetector {
|
||||
};
|
||||
}
|
||||
|
||||
// Priority 0.25: Check Cursor profile - local Cursor daemon runtime.
|
||||
// This keeps bare `ccs cursor` and `ccs cursor <subcommand>` bound to the
|
||||
// existing runtime/admin surface even though CLIProxy also exposes a
|
||||
// distinct provider named "cursor".
|
||||
if (profileName === 'cursor') {
|
||||
// Priority 0.25: Check explicit legacy Cursor bridge profile.
|
||||
if (profileName === LEGACY_CURSOR_PROFILE_NAME) {
|
||||
const cursorConfig = getCursorConfig();
|
||||
|
||||
if (!cursorConfig?.enabled) {
|
||||
const error = new Error(
|
||||
'Cursor profile is not enabled.\n\n' +
|
||||
'Legacy Cursor profile is not enabled.\n\n' +
|
||||
'To enable Cursor integration:\n' +
|
||||
' 1. Run: ccs cursor enable\n' +
|
||||
' 2. Import auth: ccs cursor auth\n' +
|
||||
' 3. Start daemon: ccs cursor start\n\n' +
|
||||
' 1. Run: ccs legacy cursor enable\n' +
|
||||
' 2. Import auth: ccs legacy cursor auth\n' +
|
||||
' 3. Start daemon: ccs legacy cursor start\n\n' +
|
||||
'Or manually edit ~/.ccs/config.yaml:\n' +
|
||||
' cursor:\n' +
|
||||
' enabled: true'
|
||||
@@ -322,7 +320,7 @@ class ProfileDetector {
|
||||
|
||||
return {
|
||||
type: 'cursor',
|
||||
name: 'cursor',
|
||||
name: LEGACY_CURSOR_PROFILE_NAME,
|
||||
cursorConfig,
|
||||
};
|
||||
}
|
||||
|
||||
+41
-17
@@ -65,7 +65,7 @@ import {
|
||||
resolveOfficialChannelsLaunchPlan,
|
||||
} from './channels/official-channels-runtime';
|
||||
import { getOfficialChannelReadiness } from './channels/official-channels-store';
|
||||
import { isCursorSubcommandToken, shouldUseCursorCliproxyShortcut } from './cursor/constants';
|
||||
import { isCursorSubcommandToken, LEGACY_CURSOR_PROFILE_NAME } from './cursor/constants';
|
||||
import { isCLIProxyProvider } from './cliproxy/provider-capabilities';
|
||||
|
||||
// Import centralized error handling
|
||||
@@ -149,6 +149,26 @@ function detectProfile(args: string[]): DetectedProfile {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLegacyCursorArgs(args: string[]): string[] {
|
||||
if (args[0] === 'legacy' && args[1] === 'cursor') {
|
||||
return [LEGACY_CURSOR_PROFILE_NAME, ...args.slice(2)];
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function printCursorLegacySubcommandDeprecation(subcommand: string): void {
|
||||
console.error(
|
||||
info(`\`ccs cursor ${subcommand}\` is deprecated for the legacy Cursor IDE bridge.`)
|
||||
);
|
||||
console.error(
|
||||
info(
|
||||
`Use \`ccs legacy cursor ${subcommand}\` for the old bridge, or \`ccs cursor --auth|--accounts|--config\` for the CLIProxy provider.`
|
||||
)
|
||||
);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
function resolveRuntimeReasoningFlags(
|
||||
args: string[],
|
||||
envThinkingValue: string | undefined
|
||||
@@ -343,7 +363,7 @@ async function main(): Promise<void> {
|
||||
registerTarget(new CodexAdapter());
|
||||
const cliLogger = createLogger('cli');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let args = process.argv.slice(2);
|
||||
const isCompletionCommand = args[0] === '__complete';
|
||||
|
||||
// Initialize UI colors early to ensure consistent colored output
|
||||
@@ -419,6 +439,8 @@ async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
args = normalizeLegacyCursorArgs(args);
|
||||
|
||||
cliLogger.info('command.start', 'CLI invocation started', {
|
||||
command: args[0] || 'default',
|
||||
argCount: args.length,
|
||||
@@ -489,7 +511,6 @@ async function main(): Promise<void> {
|
||||
if (
|
||||
typeof firstArg === 'string' &&
|
||||
isCLIProxyProvider(firstArg) &&
|
||||
firstArg !== 'cursor' &&
|
||||
args.length > 1 &&
|
||||
(args.includes('--help') || args.includes('-h'))
|
||||
) {
|
||||
@@ -511,9 +532,8 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: cursor command (Cursor local proxy integration)
|
||||
// Route known admin subcommands to the command handler, keep all other args as profile passthrough.
|
||||
if (firstArg === 'cursor' && args.length > 1) {
|
||||
// Special case: explicit legacy Cursor bridge namespace.
|
||||
if (firstArg === LEGACY_CURSOR_PROFILE_NAME && args.length > 1) {
|
||||
const { handleCursorCommand } = await import('./commands/cursor-command');
|
||||
const cursorToken = args[1];
|
||||
|
||||
@@ -523,6 +543,19 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Compatibility shim: old `ccs cursor <subcommand>` still forwards to the legacy bridge
|
||||
// for one migration window, but bare/positional `ccs cursor` now belongs to CLIProxy.
|
||||
if (firstArg === 'cursor' && args.length > 1) {
|
||||
const { handleCursorCommand } = await import('./commands/cursor-command');
|
||||
const cursorToken = args[1];
|
||||
|
||||
if (isCursorSubcommandToken(cursorToken) && cursorToken !== '--help' && cursorToken !== '-h') {
|
||||
printCursorLegacySubcommandDeprecation(cursorToken);
|
||||
const exitCode = await handleCursorCommand(args.slice(1));
|
||||
process.exit(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
// First-time install: offer setup wizard for interactive users
|
||||
// Check independently of recovery status (user may have empty config.yaml)
|
||||
// Skip if headless, CI, or non-TTY environment
|
||||
@@ -551,17 +584,8 @@ async function main(): Promise<void> {
|
||||
try {
|
||||
// Detect profile (strip --target flags before profile detection)
|
||||
const cleanArgs = stripTargetFlag(args);
|
||||
const useCursorCliproxyShortcut = shouldUseCursorCliproxyShortcut(cleanArgs);
|
||||
const { profile, remainingArgs } = useCursorCliproxyShortcut
|
||||
? { profile: 'cursor', remainingArgs: cleanArgs.slice(1) }
|
||||
: detectProfile(cleanArgs);
|
||||
const profileInfo: ProfileDetectionResult = useCursorCliproxyShortcut
|
||||
? {
|
||||
type: 'cliproxy',
|
||||
name: 'cursor',
|
||||
provider: 'cursor',
|
||||
}
|
||||
: detector.detectProfileType(profile);
|
||||
const { profile, remainingArgs } = detectProfile(cleanArgs);
|
||||
const profileInfo: ProfileDetectionResult = detector.detectProfileType(profile);
|
||||
let resolvedTarget: ReturnType<typeof resolveTargetType>;
|
||||
try {
|
||||
resolvedTarget = resolveTargetType(
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
normalizeProtocol,
|
||||
CLIPROXY_DEFAULT_PORT,
|
||||
} from './port-manager';
|
||||
import { getProviderSettingsPath } from './path-resolver';
|
||||
import { migrateLegacyProviderSettingsIfNeeded } from './path-resolver';
|
||||
import {
|
||||
canonicalizeModelIdForProvider,
|
||||
MODEL_ENV_VAR_KEYS,
|
||||
@@ -465,7 +465,7 @@ export function getEffectiveEnvVars(
|
||||
}
|
||||
|
||||
// Priority 2: Default provider settings file
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
|
||||
// Check for user override file
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
@@ -505,7 +505,7 @@ export function getEffectiveEnvVars(
|
||||
* Called during installation/first run
|
||||
*/
|
||||
export function ensureProviderSettings(provider: CLIProxyProvider): void {
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
const defaultEnv = getClaudeEnvVars(provider);
|
||||
|
||||
const writeSettings = (settings: Record<string, unknown>): void => {
|
||||
@@ -663,7 +663,7 @@ export function getRemoteEnvVars(
|
||||
|
||||
// Priority 2: Default provider settings file (~/.ccs/{provider}.settings.json)
|
||||
if (Object.keys(userEnvVars).length === 0) {
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(settingsPath, 'utf-8');
|
||||
|
||||
@@ -16,6 +16,13 @@ export function getCliproxyDir(): string {
|
||||
return path.join(getCcsDir(), 'cliproxy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CLIProxy provider settings directory.
|
||||
*/
|
||||
export function getCliproxyProvidersDir(): string {
|
||||
return path.join(getCliproxyDir(), 'providers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CLIProxy writable directory for logs and runtime files.
|
||||
* This directory is set as WRITABLE_PATH env var when spawning CLIProxy.
|
||||
@@ -75,5 +82,40 @@ export function getBinDir(): string {
|
||||
* Example: ~/.ccs/gemini.settings.json
|
||||
*/
|
||||
export function getProviderSettingsPath(provider: CLIProxyProvider): string {
|
||||
if (provider === 'cursor') {
|
||||
return path.join(getCliproxyProvidersDir(), `${provider}.settings.json`);
|
||||
}
|
||||
|
||||
return getLegacyProviderSettingsPath(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CLIProxy provider settings path in the dedicated cliproxy/providers namespace.
|
||||
* Used only for providers that must not collide with legacy top-level settings files.
|
||||
*/
|
||||
export function getDedicatedProviderSettingsPath(provider: CLIProxyProvider): string {
|
||||
return path.join(getCliproxyProvidersDir(), `${provider}.settings.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get legacy provider settings file path in ~/.ccs root.
|
||||
* This is kept for compatibility reads/migration of older provider settings.
|
||||
*/
|
||||
export function getLegacyProviderSettingsPath(provider: CLIProxyProvider): string {
|
||||
return path.join(getCcsDir(), `${provider}.settings.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective provider settings path.
|
||||
*
|
||||
* Cursor uses a dedicated cliproxy/providers namespace so it does not collide
|
||||
* with the deprecated Cursor IDE bridge raw settings file.
|
||||
*/
|
||||
export function migrateLegacyProviderSettingsIfNeeded(provider: CLIProxyProvider): string {
|
||||
if (provider !== 'cursor') {
|
||||
return getProviderSettingsPath(provider);
|
||||
}
|
||||
|
||||
const targetPath = getDedicatedProviderSettingsPath(provider);
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { InteractivePrompt } from '../utils/prompt';
|
||||
import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog';
|
||||
import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
|
||||
import { getClaudeEnvVars, migrateLegacyProviderSettingsIfNeeded } from './config-generator';
|
||||
import { CLIProxyProvider } from './types';
|
||||
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
@@ -31,7 +31,7 @@ function canonicalizeModelForProvider(provider: CLIProxyProvider, model: string)
|
||||
* Check if provider has user settings configured
|
||||
*/
|
||||
export function hasUserSettings(provider: CLIProxyProvider): boolean {
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
return fs.existsSync(settingsPath);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function getCurrentModel(
|
||||
): string | undefined {
|
||||
const settingsPath = customSettingsPath
|
||||
? customSettingsPath.replace(/^~/, os.homedir())
|
||||
: getProviderSettingsPath(provider);
|
||||
: migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
if (!fs.existsSync(settingsPath)) return undefined;
|
||||
|
||||
try {
|
||||
@@ -116,7 +116,7 @@ export async function configureProviderModel(
|
||||
// Use custom settings path for CLIProxy variants, otherwise use default provider path
|
||||
const settingsPath = customSettingsPath
|
||||
? customSettingsPath.replace(/^~/, os.homedir())
|
||||
: getProviderSettingsPath(provider);
|
||||
: migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
|
||||
// Skip if already configured with a model (unless --config flag).
|
||||
// A settings file can exist without model env keys (e.g., hook-only writes).
|
||||
@@ -249,7 +249,7 @@ export async function showCurrentConfig(provider: CLIProxyProvider): Promise<voi
|
||||
await initUI();
|
||||
|
||||
const currentModel = getCurrentModel(provider);
|
||||
const settingsPath = getProviderSettingsPath(provider);
|
||||
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||
const normalizedCurrentModel = currentModel
|
||||
? canonicalizeModelForProvider(provider, currentModel)
|
||||
: undefined;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { COPILOT_SUBCOMMANDS } from '../copilot/constants';
|
||||
import { CURSOR_SUBCOMMANDS } from '../cursor/constants';
|
||||
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
|
||||
|
||||
export type HelpTopicName = 'profiles' | 'providers' | 'kiro' | 'completion' | 'targets';
|
||||
@@ -105,7 +104,7 @@ export const ROOT_COMMAND_CATALOG: readonly RootCommandEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'cursor',
|
||||
summary: 'Run or manage the Cursor bridge',
|
||||
summary: 'Run Cursor via CLIProxy or manage Cursor provider auth',
|
||||
group: 'runtime',
|
||||
visibility: 'public',
|
||||
},
|
||||
@@ -308,7 +307,14 @@ export const COMMAND_FLAG_SUGGESTIONS: Readonly<Record<string, readonly string[]
|
||||
update: ['--force', '--beta', '--dev', '--help', '-h'],
|
||||
};
|
||||
|
||||
export const CURSOR_COMPLETION_SUBCOMMANDS = [...CURSOR_SUBCOMMANDS] as const;
|
||||
export const CURSOR_COMPLETION_SUBCOMMANDS = [
|
||||
'--auth',
|
||||
'--accounts',
|
||||
'--config',
|
||||
'--logout',
|
||||
'--help',
|
||||
'-h',
|
||||
] as const;
|
||||
export const COPILOT_COMPLETION_SUBCOMMANDS = [...COPILOT_SUBCOMMANDS, 'help'] as const;
|
||||
|
||||
export function getPublicRootCommands(): readonly RootCommandEntry[] {
|
||||
|
||||
@@ -4,6 +4,9 @@ import type { CursorConfig } from '../config/unified-config-types';
|
||||
import { getCcsDirDisplay } from '../utils/config-manager';
|
||||
import { color } from '../utils/ui';
|
||||
|
||||
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
|
||||
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
|
||||
|
||||
function printLines(lines: string[]): void {
|
||||
for (const line of lines) {
|
||||
console.log(line);
|
||||
@@ -14,11 +17,11 @@ export function renderCursorHelp(): number {
|
||||
printLines([
|
||||
'Legacy Cursor Compatibility',
|
||||
'',
|
||||
'Deprecated: prefer CLIProxy-backed Cursor auth and account management.',
|
||||
'Supported auth path: ccs cursor --auth',
|
||||
'Deprecated: `ccs cursor` now belongs to the CLIProxy Cursor provider.',
|
||||
`Supported auth path: ${CLIPROXY_CURSOR_COMMAND} --auth`,
|
||||
'Supported dashboard path: ccs config -> CLIProxy -> Cursor',
|
||||
'',
|
||||
'Usage: ccs cursor <subcommand>',
|
||||
`Usage: ${LEGACY_CURSOR_COMMAND} <subcommand>`,
|
||||
'',
|
||||
'Subcommands (deprecated compatibility for the local reverse-engineered bridge):',
|
||||
' auth Import Cursor IDE authentication token (deprecated)',
|
||||
@@ -32,24 +35,24 @@ export function renderCursorHelp(): number {
|
||||
' help Show this help message',
|
||||
'',
|
||||
'Supported CLIProxy path:',
|
||||
' ccs cursor --auth # Authenticate Cursor via CLIProxy',
|
||||
' ccs cursor --accounts # Manage CLIProxy Cursor accounts',
|
||||
' ccs cursor --config # Open CLIProxy Cursor settings',
|
||||
` ${CLIPROXY_CURSOR_COMMAND} --auth # Authenticate Cursor via CLIProxy`,
|
||||
` ${CLIPROXY_CURSOR_COMMAND} --accounts # Manage CLIProxy Cursor accounts`,
|
||||
` ${CLIPROXY_CURSOR_COMMAND} --config # Open CLIProxy Cursor settings`,
|
||||
'',
|
||||
'Legacy runtime entry (deprecated compatibility):',
|
||||
' ccs cursor [claude args] # Run Claude via the local Cursor bridge',
|
||||
` ${LEGACY_CURSOR_COMMAND} [claude args] # Run Claude via the local Cursor bridge`,
|
||||
'',
|
||||
'Legacy auth options:',
|
||||
' ccs cursor auth # Auto-detect from Cursor SQLite (deprecated)',
|
||||
' ccs cursor auth --manual --token <t> --machine-id <id>',
|
||||
` ${LEGACY_CURSOR_COMMAND} auth # Auto-detect from Cursor SQLite (deprecated)`,
|
||||
` ${LEGACY_CURSOR_COMMAND} auth --manual --token <t> --machine-id <id>`,
|
||||
'',
|
||||
'Legacy bridge quick start:',
|
||||
' 1. ccs cursor enable # Deprecated compatibility: enable local bridge',
|
||||
' 2. ccs cursor auth # Deprecated compatibility: import Cursor IDE token',
|
||||
' 3. ccs cursor start # Start local daemon',
|
||||
' 4. ccs cursor probe # Verify live runtime health',
|
||||
' 5. ccs cursor "task" # Run Claude through the local bridge',
|
||||
' 6. ccs cursor status # Inspect auth/daemon wiring',
|
||||
` 1. ${LEGACY_CURSOR_COMMAND} enable # Deprecated compatibility: enable local bridge`,
|
||||
` 2. ${LEGACY_CURSOR_COMMAND} auth # Deprecated compatibility: import Cursor IDE token`,
|
||||
` 3. ${LEGACY_CURSOR_COMMAND} start # Start local daemon`,
|
||||
` 4. ${LEGACY_CURSOR_COMMAND} probe # Verify live runtime health`,
|
||||
` 5. ${LEGACY_CURSOR_COMMAND} "task" # Run Claude through the local bridge`,
|
||||
` 6. ${LEGACY_CURSOR_COMMAND} status # Inspect auth/daemon wiring`,
|
||||
'',
|
||||
'Web UI: ccs config -> Deprecated -> Cursor IDE',
|
||||
'',
|
||||
@@ -109,11 +112,13 @@ export function renderCursorStatus(
|
||||
console.log('');
|
||||
console.log('Client setup:');
|
||||
console.log(` Raw settings: ${dirDisplay}/cursor.settings.json`);
|
||||
console.log(' Runtime entry: ccs cursor [claude args] (deprecated compatibility)');
|
||||
console.log(' Supported auth: ccs cursor --auth');
|
||||
console.log(' Live probe: ccs cursor probe');
|
||||
console.log(' Status command: ccs cursor status');
|
||||
console.log(' Help command: ccs cursor help');
|
||||
console.log(
|
||||
` Runtime entry: ${LEGACY_CURSOR_COMMAND} [claude args] (deprecated compatibility)`
|
||||
);
|
||||
console.log(` Supported auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||
console.log(` Live probe: ${LEGACY_CURSOR_COMMAND} probe`);
|
||||
console.log(` Status command: ${LEGACY_CURSOR_COMMAND} status`);
|
||||
console.log(` Help command: ${LEGACY_CURSOR_COMMAND} help`);
|
||||
|
||||
if (isReady) {
|
||||
return;
|
||||
@@ -123,16 +128,16 @@ export function renderCursorStatus(
|
||||
|
||||
console.log('Next steps:');
|
||||
if (!cursorConfig.enabled) {
|
||||
console.log(' - Enable: ccs cursor enable');
|
||||
console.log(` - Enable: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||
}
|
||||
if (!authStatus.authenticated || authStatus.expired) {
|
||||
console.log(' - Supported: ccs cursor --auth');
|
||||
console.log(' - Legacy auth: ccs cursor auth');
|
||||
console.log(` - Supported: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||
console.log(` - Legacy auth: ${LEGACY_CURSOR_COMMAND} auth`);
|
||||
}
|
||||
if (!daemonStatus.running) {
|
||||
console.log(' - Start: ccs cursor start');
|
||||
console.log(` - Start: ${LEGACY_CURSOR_COMMAND} start`);
|
||||
}
|
||||
console.log(' - Help: ccs cursor help');
|
||||
console.log(` - Help: ${LEGACY_CURSOR_COMMAND} help`);
|
||||
}
|
||||
|
||||
export function renderCursorModels(models: CursorModel[], defaultModel: string): void {
|
||||
|
||||
@@ -26,10 +26,13 @@ import {
|
||||
} from './cursor-command-display';
|
||||
import { ok, fail, info } from '../utils/ui';
|
||||
|
||||
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
|
||||
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
|
||||
|
||||
function printLegacyCursorDeprecationNotice(): void {
|
||||
console.log(
|
||||
info(
|
||||
'Deprecated compatibility path. Prefer CLIProxy-backed Cursor auth via `ccs cursor --auth` or the CLIProxy dashboard.'
|
||||
`Deprecated compatibility path. \`${CLIPROXY_CURSOR_COMMAND}\` now belongs to the CLIProxy Cursor provider; use \`${LEGACY_CURSOR_COMMAND}\` for the old bridge.`
|
||||
)
|
||||
);
|
||||
console.log('');
|
||||
@@ -135,7 +138,7 @@ async function handleAuth(args: string[]): Promise<number> {
|
||||
if (!accessToken || !machineId) {
|
||||
console.error(
|
||||
fail(
|
||||
'Manual auth requires both token and machine ID.\n\nExample:\n ccs cursor auth --manual --token <token> --machine-id <machine-id>'
|
||||
`Manual auth requires both token and machine ID.\n\nExample:\n ${LEGACY_CURSOR_COMMAND} auth --manual --token <token> --machine-id <machine-id>`
|
||||
)
|
||||
);
|
||||
return 1;
|
||||
@@ -156,9 +159,9 @@ async function handleAuth(args: string[]): Promise<number> {
|
||||
console.log(ok('Cursor credentials imported (manual mode)'));
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
||||
console.log(' 1. Enable integration: ccs cursor enable');
|
||||
console.log(' 2. Start daemon: ccs cursor start');
|
||||
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -179,10 +182,10 @@ async function handleAuth(args: string[]): Promise<number> {
|
||||
console.log(ok('Auto-detected Cursor credentials'));
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
||||
console.log(' 1. Enable integration: ccs cursor enable');
|
||||
console.log(' 2. Start daemon: ccs cursor start');
|
||||
console.log(' 3. Check status: ccs cursor status');
|
||||
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -190,7 +193,7 @@ async function handleAuth(args: string[]): Promise<number> {
|
||||
printAutoDetectFailure(autoResult);
|
||||
console.log('');
|
||||
console.log('Manual fallback:');
|
||||
console.log(' ccs cursor auth --manual --token <token> --machine-id <machine-id>');
|
||||
console.log(` ${LEGACY_CURSOR_COMMAND} auth --manual --token <token> --machine-id <machine-id>`);
|
||||
console.log('');
|
||||
|
||||
return 1;
|
||||
@@ -230,17 +233,17 @@ async function handleStart(): Promise<number> {
|
||||
const cursorConfig = getCursorConfig();
|
||||
|
||||
if (!cursorConfig.enabled) {
|
||||
console.error(fail('Cursor integration is disabled. Run: ccs cursor enable'));
|
||||
console.error(fail(`Cursor integration is disabled. Run: ${LEGACY_CURSOR_COMMAND} enable`));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const authStatus = checkAuthStatus();
|
||||
if (!authStatus.authenticated) {
|
||||
console.error(fail('Not authenticated. Run: ccs cursor auth'));
|
||||
console.error(fail(`Not authenticated. Run: ${LEGACY_CURSOR_COMMAND} auth`));
|
||||
return 1;
|
||||
}
|
||||
if (authStatus.expired) {
|
||||
console.error(fail('Credentials expired. Run: ccs cursor auth'));
|
||||
console.error(fail(`Credentials expired. Run: ${LEGACY_CURSOR_COMMAND} auth`));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -294,10 +297,10 @@ async function handleEnable(): Promise<number> {
|
||||
console.log(ok('Cursor integration enabled'));
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
||||
console.log(' 1. Authenticate: ccs cursor auth');
|
||||
console.log(' 2. Start daemon: ccs cursor start');
|
||||
console.log(' 3. Check status: ccs cursor status');
|
||||
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||
console.log(` 1. Authenticate: ${LEGACY_CURSOR_COMMAND} auth`);
|
||||
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -344,8 +344,7 @@ export async function handleHelpRoute(
|
||||
cliproxy: async () => (await import('./cliproxy/help-subcommand')).showHelp(),
|
||||
copilot: async () =>
|
||||
process.exit(await (await import('./copilot-command')).handleCopilotCommand(['--help'])),
|
||||
cursor: async () =>
|
||||
process.exit(await (await import('./cursor-command')).handleCursorCommand(['--help'])),
|
||||
cursor: async () => await showProviderShortcutHelp('cursor', writeLine),
|
||||
docker: async () => (await import('./docker/help-subcommand')).showHelp(),
|
||||
migrate: async () => (await import('./migrate-command')).printMigrateHelp(),
|
||||
setup: async () => (await import('./setup-command')).handleSetupCommand(['--help']),
|
||||
|
||||
@@ -20,6 +20,8 @@ export const RESERVED_PROFILE_NAMES = [
|
||||
'copilot',
|
||||
// Cursor IDE (Cursor proxy daemon)
|
||||
'cursor',
|
||||
'legacy-cursor',
|
||||
'legacy',
|
||||
// CLI commands and special names
|
||||
'default',
|
||||
'config',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const LEGACY_CURSOR_PROFILE_NAME = 'legacy-cursor';
|
||||
|
||||
export const CURSOR_SUBCOMMANDS = [
|
||||
'auth',
|
||||
'status',
|
||||
|
||||
@@ -248,7 +248,7 @@ export function startCursorDaemonServer(options: DaemonRuntimeOptions): http.Ser
|
||||
|
||||
const authStatus = checkAuthStatus();
|
||||
if (!authStatus.authenticated || !authStatus.credentials) {
|
||||
const message = 'Cursor credentials not found. Run `ccs cursor auth` first.';
|
||||
const message = 'Cursor credentials not found. Run `ccs legacy cursor auth` first.';
|
||||
if (isAnthropicRoute) {
|
||||
await pipeWebResponseToNode(
|
||||
createAnthropicErrorResponse(401, 'authentication_error', message),
|
||||
@@ -266,7 +266,7 @@ export function startCursorDaemonServer(options: DaemonRuntimeOptions): http.Ser
|
||||
}
|
||||
|
||||
if (authStatus.expired) {
|
||||
const message = 'Cursor credentials expired. Run `ccs cursor auth` again.';
|
||||
const message = 'Cursor credentials expired. Run `ccs legacy cursor auth` again.';
|
||||
if (isAnthropicRoute) {
|
||||
await pipeWebResponseToNode(
|
||||
createAnthropicErrorResponse(401, 'authentication_error', message),
|
||||
|
||||
@@ -97,7 +97,7 @@ export async function executeCursorProfile(
|
||||
if (!config.enabled) {
|
||||
console.error(fail('Cursor integration is not enabled.'));
|
||||
console.error('');
|
||||
console.error('Enable it first: ccs cursor enable');
|
||||
console.error('Enable it first: ccs legacy cursor enable');
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -105,13 +105,13 @@ export async function executeCursorProfile(
|
||||
if (!authStatus.authenticated) {
|
||||
console.error(fail('Cursor credentials not found.'));
|
||||
console.error('');
|
||||
console.error('Authenticate first: ccs cursor auth');
|
||||
console.error('Authenticate first: ccs legacy cursor auth');
|
||||
return 1;
|
||||
}
|
||||
if (authStatus.expired) {
|
||||
console.error(fail('Cursor credentials have expired.'));
|
||||
console.error('');
|
||||
console.error('Refresh them with: ccs cursor auth');
|
||||
console.error('Refresh them with: ccs legacy cursor auth');
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function executeCursorProfile(
|
||||
console.error(fail('Cursor daemon is not running.'));
|
||||
console.error('');
|
||||
console.error('Start the daemon:');
|
||||
console.error(' ccs cursor start');
|
||||
console.error(' ccs legacy cursor start');
|
||||
console.error('Or enable auto_start in the Cursor config section.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
||||
stage: 'auth',
|
||||
status: 401,
|
||||
duration_ms: Date.now() - startedAt,
|
||||
message: 'Cursor credentials not found. Run `ccs cursor auth` first.',
|
||||
message: 'Cursor credentials not found. Run `ccs legacy cursor auth` first.',
|
||||
error_type: 'authentication_error',
|
||||
};
|
||||
}
|
||||
@@ -131,7 +131,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
||||
stage: 'auth',
|
||||
status: 401,
|
||||
duration_ms: Date.now() - startedAt,
|
||||
message: 'Cursor credentials expired. Run `ccs cursor auth` again.',
|
||||
message: 'Cursor credentials expired. Run `ccs legacy cursor auth` again.',
|
||||
error_type: 'authentication_error',
|
||||
};
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
||||
status: 503,
|
||||
duration_ms: Date.now() - startedAt,
|
||||
message:
|
||||
'Cursor daemon is not running. Start it with `ccs cursor start` or enable auto_start.',
|
||||
'Cursor daemon is not running. Start it with `ccs legacy cursor start` or enable auto_start.',
|
||||
error_type: 'daemon_not_running',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ function resolveBackend(
|
||||
};
|
||||
}
|
||||
|
||||
if (profileType === 'cursor' || profileName === 'cursor') {
|
||||
if (profileType === 'cursor') {
|
||||
return {
|
||||
backendId: null,
|
||||
backendDisplayName: null,
|
||||
|
||||
@@ -104,6 +104,7 @@ apiRoutes.use('/copilot', copilotRoutes);
|
||||
|
||||
// ==================== Cursor ====================
|
||||
apiRoutes.use('/cursor', cursorRoutes);
|
||||
apiRoutes.use('/legacy/cursor', cursorRoutes);
|
||||
|
||||
// ==================== Droid ====================
|
||||
apiRoutes.use('/droid', droidRoutes);
|
||||
|
||||
@@ -30,6 +30,7 @@ import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middlewar
|
||||
import type { Settings } from '../../types/config';
|
||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||
import { mapExternalProviderName } from '../../cliproxy/provider-capabilities';
|
||||
import { migrateLegacyProviderSettingsIfNeeded } from '../../cliproxy/config/path-resolver';
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import {
|
||||
canonicalizeModelIdForProvider,
|
||||
@@ -101,6 +102,17 @@ function resolveSettingsPath(profileOrVariant: string): string {
|
||||
const ccsDir = getCcsDir();
|
||||
const resolvedCcsDir = path.resolve(ccsDir);
|
||||
|
||||
const directProvider = mapExternalProviderName(profileOrVariant);
|
||||
if (directProvider) {
|
||||
if (profileOrVariant !== directProvider) {
|
||||
return resolvePathWithin(
|
||||
resolvedCcsDir,
|
||||
path.join(resolvedCcsDir, `${profileOrVariant}.settings.json`)
|
||||
);
|
||||
}
|
||||
return path.resolve(migrateLegacyProviderSettingsIfNeeded(directProvider));
|
||||
}
|
||||
|
||||
// Check if this is a variant
|
||||
const variants = listVariants();
|
||||
const variant = variants[profileOrVariant];
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('cursor daemon lifecycle smoke', () => {
|
||||
};
|
||||
expect(anthropicBody.type).toBe('error');
|
||||
expect(anthropicBody.error?.type).toBe('authentication_error');
|
||||
expect(anthropicBody.error?.message).toContain('Run `ccs cursor auth` first');
|
||||
expect(anthropicBody.error?.message).toContain('Run `ccs legacy cursor auth` first');
|
||||
|
||||
const stopResult = await stopDaemon();
|
||||
expect(stopResult.success).toBe(true);
|
||||
|
||||
@@ -87,17 +87,22 @@ describe('npm CLI', () => {
|
||||
});
|
||||
|
||||
it('routes cursor probe through the cursor command handler', function() {
|
||||
let output = '';
|
||||
try {
|
||||
execSync(`bun "${srcCcsPath}" cursor probe`, {
|
||||
output = execSync(`bun "${srcCcsPath}" cursor probe`, {
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe',
|
||||
timeout: 3000,
|
||||
env: { ...process.env, CCS_HOME: testCcsHome }
|
||||
});
|
||||
} catch (e) {
|
||||
const output = e.stderr?.toString() || e.stdout?.toString() || '';
|
||||
assert(!output.includes("Profile 'cursor' not found"), 'Should not fall through to profile lookup');
|
||||
assert(output.includes('Cursor Live Probe'), 'Should render the cursor probe command output');
|
||||
output = e.stderr?.toString() || e.stdout?.toString() || '';
|
||||
}
|
||||
assert(!output.includes("Profile 'cursor' not found"), 'Should not fall through to profile lookup');
|
||||
assert(
|
||||
output.includes('Cursor Live Probe') || output.includes('legacy cursor probe'),
|
||||
'Should route through the legacy cursor compatibility handler'
|
||||
);
|
||||
});
|
||||
|
||||
it('routes gitlab --help to provider shortcut help instead of starting auth', function() {
|
||||
|
||||
@@ -202,7 +202,14 @@ describe('ProfileDetector', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should detect cursor as a first-class runtime profile when enabled', () => {
|
||||
it('should detect cursor as a CLIProxy provider shortcut', () => {
|
||||
const result = detector.detectProfileType('cursor');
|
||||
expect(result.type).toBe('cliproxy');
|
||||
expect(result.name).toBe('cursor');
|
||||
expect(result.provider).toBe('cursor');
|
||||
});
|
||||
|
||||
it('should detect legacy-cursor as a first-class runtime profile when enabled', () => {
|
||||
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
|
||||
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
||||
enabled: true,
|
||||
@@ -213,9 +220,9 @@ describe('ProfileDetector', () => {
|
||||
});
|
||||
|
||||
try {
|
||||
const result = detector.detectProfileType('cursor');
|
||||
const result = detector.detectProfileType('legacy-cursor');
|
||||
expect(result.type).toBe('cursor');
|
||||
expect(result.name).toBe('cursor');
|
||||
expect(result.name).toBe('legacy-cursor');
|
||||
expect(result.cursorConfig?.auto_start).toBe(true);
|
||||
} finally {
|
||||
isUnifiedModeSpy.mockRestore();
|
||||
@@ -223,7 +230,7 @@ describe('ProfileDetector', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should merge default cursor fields when enabled via partial unified config', () => {
|
||||
it('should merge default legacy cursor fields when enabled via partial unified config', () => {
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempDir;
|
||||
const ccsDir = path.join(tempDir, '.ccs');
|
||||
@@ -235,7 +242,7 @@ describe('ProfileDetector', () => {
|
||||
|
||||
try {
|
||||
const localDetector = new ProfileDetector();
|
||||
const result = localDetector.detectProfileType('cursor');
|
||||
const result = localDetector.detectProfileType('legacy-cursor');
|
||||
expect(result.type).toBe('cursor');
|
||||
expect(result.cursorConfig).toEqual({
|
||||
enabled: true,
|
||||
@@ -253,7 +260,7 @@ describe('ProfileDetector', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw a helpful error when cursor profile is disabled', () => {
|
||||
it('should throw a helpful error when legacy cursor profile is disabled', () => {
|
||||
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
|
||||
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
||||
enabled: false,
|
||||
@@ -264,7 +271,9 @@ describe('ProfileDetector', () => {
|
||||
});
|
||||
|
||||
try {
|
||||
expect(() => detector.detectProfileType('cursor')).toThrow(/Cursor profile is not enabled/);
|
||||
expect(() => detector.detectProfileType('legacy-cursor')).toThrow(
|
||||
/Legacy Cursor profile is not enabled/
|
||||
);
|
||||
} finally {
|
||||
isUnifiedModeSpy.mockRestore();
|
||||
getCursorConfigSpy.mockRestore();
|
||||
|
||||
@@ -140,6 +140,15 @@ describe('completion backend', () => {
|
||||
expect(suggestionValues(['cliproxy'])).toEqual(expect.arrayContaining(['remove', '--backend']));
|
||||
});
|
||||
|
||||
test('treats cursor as a provider shortcut in completion', () => {
|
||||
const values = suggestionValues(['cursor']);
|
||||
expect(values).toEqual(
|
||||
expect.arrayContaining(['--auth', '--accounts', '--config', '--logout'])
|
||||
);
|
||||
expect(values).not.toContain('probe');
|
||||
expect(values).not.toContain('start');
|
||||
});
|
||||
|
||||
test('filters suggestions by the current token prefix', () => {
|
||||
const values = suggestionValues([], 'do');
|
||||
expect(values).toEqual(expect.arrayContaining(['docker', 'doctor']));
|
||||
|
||||
@@ -322,7 +322,9 @@ describe('handleCursorCommand', () => {
|
||||
expect(exitCode).toBe(0);
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(logs.some((line) => line.includes('Legacy Cursor Compatibility'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes('Usage: ccs legacy cursor <subcommand>'))).toBe(
|
||||
true
|
||||
);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
@@ -423,7 +425,9 @@ describe('renderCursorStatus', () => {
|
||||
true
|
||||
);
|
||||
expect(logs.some((line) => line.includes('Next steps:'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes(' - Help: ccs cursor help'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes(' - Help: ccs legacy cursor help'))).toBe(
|
||||
true
|
||||
);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
}
|
||||
@@ -483,11 +487,15 @@ describe('renderCursorHelp', () => {
|
||||
const exitCode = renderCursorHelp();
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes('Legacy Cursor Compatibility'))).toBe(true);
|
||||
expect(logs.some((line) => line.includes('Deprecated: prefer CLIProxy-backed Cursor auth'))).toBe(
|
||||
expect(logs.some((line) => line.includes('Usage: ccs legacy cursor <subcommand>'))).toBe(
|
||||
true
|
||||
);
|
||||
expect(logs.some((line) => line.includes('Legacy Cursor Compatibility'))).toBe(true);
|
||||
expect(
|
||||
logs.some((line) =>
|
||||
line.includes('Deprecated: `ccs cursor` now belongs to the CLIProxy Cursor provider.')
|
||||
)
|
||||
).toBe(true);
|
||||
expect(logs.some((line) => line.includes('probe Run a live authenticated runtime probe'))).toBe(
|
||||
true
|
||||
);
|
||||
@@ -495,7 +503,7 @@ describe('renderCursorHelp', () => {
|
||||
logs.some((line) => line.includes('ccs cursor --auth'))
|
||||
).toBe(true);
|
||||
expect(
|
||||
logs.some((line) => line.includes('ccs cursor [claude args]'))
|
||||
logs.some((line) => line.includes('ccs legacy cursor [claude args]'))
|
||||
).toBe(true);
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
|
||||
@@ -82,6 +82,21 @@ describe('image-analysis-backend-resolver', () => {
|
||||
expect(status.resolutionSource).toBe('profile-backend');
|
||||
});
|
||||
|
||||
it('treats cliproxy cursor as a provider-backed profile rather than a legacy bridge alias', () => {
|
||||
const status = resolveImageAnalysisStatus(
|
||||
{
|
||||
profileName: 'cursor',
|
||||
profileType: 'cliproxy',
|
||||
cliproxyProvider: 'cursor',
|
||||
},
|
||||
DEFAULT_IMAGE_ANALYSIS_CONFIG
|
||||
);
|
||||
|
||||
expect(status.backendId).toBe('cursor');
|
||||
expect(status.resolutionSource).toBe('cliproxy-provider');
|
||||
expect(status.reason).toContain('no image-analysis model configured');
|
||||
});
|
||||
|
||||
it('uses the fallback backend for an unmapped third-party settings profile', () => {
|
||||
const status = resolveImageAnalysisStatus(
|
||||
{
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Toaster } from 'sonner';
|
||||
import { queryClient } from '@/lib/query-client';
|
||||
@@ -129,6 +129,10 @@ export default function App() {
|
||||
/>
|
||||
<Route
|
||||
path="/cursor"
|
||||
element={<Navigate to="/cliproxy?provider=cursor" replace />}
|
||||
/>
|
||||
<Route
|
||||
path="/legacy/cursor"
|
||||
element={
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<CursorPage />
|
||||
|
||||
@@ -25,6 +25,17 @@ interface ProviderEditorHeaderProps {
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
function formatSettingsPathBadgeLabel(pathValue: string): string {
|
||||
const normalized = pathValue.replace(/\\/g, '/');
|
||||
const cliproxySegment = '/cliproxy/providers/';
|
||||
const cliproxyIndex = normalized.lastIndexOf(cliproxySegment);
|
||||
if (cliproxyIndex !== -1) {
|
||||
return normalized.slice(cliproxyIndex + 1);
|
||||
}
|
||||
|
||||
return normalized.replace(/^.*\//, '');
|
||||
}
|
||||
|
||||
export function ProviderEditorHeader({
|
||||
displayName,
|
||||
logoProvider,
|
||||
@@ -63,7 +74,7 @@ export function ProviderEditorHeader({
|
||||
)}
|
||||
{!isRemoteMode && data?.path && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{data.path.replace(/^.*[\\/]/, '')}
|
||||
{formatSettingsPathBadgeLabel(data.path)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
isAnthropicModelEnvKey,
|
||||
} from '@/lib/extended-context-utils';
|
||||
import { supportsExtendedContext } from '@/lib/model-catalogs';
|
||||
import { isValidProvider } from '@/lib/provider-config';
|
||||
|
||||
/** Required env vars for CLIProxy providers (informational only - runtime fills defaults) */
|
||||
const REQUIRED_ENV_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'] as const;
|
||||
@@ -39,12 +40,18 @@ export function useProviderEditor(
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/settings/${provider}/raw`);
|
||||
if (!res.ok) {
|
||||
const fallbackPath =
|
||||
provider === 'cursor'
|
||||
? `~/.ccs/cliproxy/providers/${provider}.settings.json`
|
||||
: isValidProvider(provider)
|
||||
? `~/.ccs/${provider}.settings.json`
|
||||
: `~/.ccs/profiles/${provider}/settings.json`;
|
||||
// Return empty settings for unconfigured providers
|
||||
return {
|
||||
profile: provider,
|
||||
settings: { env: {} },
|
||||
mtime: Date.now(),
|
||||
path: `~/.ccs/profiles/${provider}/settings.json`,
|
||||
path: fallbackPath,
|
||||
};
|
||||
}
|
||||
return res.json();
|
||||
|
||||
@@ -126,7 +126,11 @@ function buildNavGroups(t: (key: string) => string): SidebarGroupDef[] {
|
||||
{
|
||||
title: t('nav.deprecated'),
|
||||
items: [
|
||||
{ path: '/cursor', iconSrc: '/assets/sidebar/cursor.svg', label: t('nav.cursorIde') },
|
||||
{
|
||||
path: '/legacy/cursor',
|
||||
iconSrc: '/assets/sidebar/cursor.svg',
|
||||
label: `${t('nav.cursorIde')} (Legacy)`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
+19
-11
@@ -57,6 +57,8 @@ interface CursorAuthResult {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const LEGACY_CURSOR_API_BASE = '/legacy/cursor';
|
||||
|
||||
export interface CursorProbeResult {
|
||||
ok: boolean;
|
||||
stage: 'config' | 'auth' | 'daemon' | 'runtime';
|
||||
@@ -87,25 +89,25 @@ function getProbeErrorMessage(value: unknown): string | null {
|
||||
}
|
||||
|
||||
async function fetchCursorStatus(): Promise<CursorStatus> {
|
||||
const res = await fetch(withApiBase('/cursor/status'));
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/status`));
|
||||
if (!res.ok) throw new Error('Failed to fetch cursor status');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function fetchCursorConfig(): Promise<CursorConfig> {
|
||||
const res = await fetch(withApiBase('/cursor/settings'));
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/settings`));
|
||||
if (!res.ok) throw new Error('Failed to fetch cursor config');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function fetchCursorModels(): Promise<CursorModelsResponse> {
|
||||
const res = await fetch(withApiBase('/cursor/models'));
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/models`));
|
||||
if (!res.ok) throw new Error('Failed to fetch cursor models');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
|
||||
const res = await fetch(withApiBase('/cursor/settings/raw'));
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/settings/raw`));
|
||||
if (!res.ok) throw new Error('Failed to fetch cursor raw settings');
|
||||
return res.json();
|
||||
}
|
||||
@@ -113,7 +115,7 @@ async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
|
||||
async function updateCursorConfig(
|
||||
updates: Partial<CursorConfig>
|
||||
): Promise<{ success: boolean; cursor: CursorConfig }> {
|
||||
const res = await fetch(withApiBase('/cursor/settings'), {
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/settings`), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates),
|
||||
@@ -126,7 +128,7 @@ async function saveCursorRawSettings(data: {
|
||||
settings: CursorRawSettings['settings'];
|
||||
expectedMtime?: number;
|
||||
}): Promise<{ success: boolean; mtime: number }> {
|
||||
const res = await fetch(withApiBase('/cursor/settings/raw'), {
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/settings/raw`), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
@@ -137,7 +139,9 @@ async function saveCursorRawSettings(data: {
|
||||
}
|
||||
|
||||
async function autoDetectCursorAuth(): Promise<CursorAuthResult> {
|
||||
const res = await fetch(withApiBase('/cursor/auth/auto-detect'), { method: 'POST' });
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/auth/auto-detect`), {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!res.ok) {
|
||||
const error = await res.json().catch(() => ({ error: 'Auto-detect failed' }));
|
||||
throw new Error(error.error || 'Auto-detect failed');
|
||||
@@ -149,7 +153,7 @@ async function importCursorAuthManual(data: {
|
||||
accessToken: string;
|
||||
machineId: string;
|
||||
}): Promise<CursorAuthResult> {
|
||||
const res = await fetch(withApiBase('/cursor/auth/import'), {
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/auth/import`), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
@@ -162,19 +166,23 @@ async function importCursorAuthManual(data: {
|
||||
}
|
||||
|
||||
async function startCursorDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
const res = await fetch(withApiBase('/cursor/daemon/start'), { method: 'POST' });
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/daemon/start`), {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to start cursor daemon');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function stopCursorDaemon(): Promise<{ success: boolean; error?: string }> {
|
||||
const res = await fetch(withApiBase('/cursor/daemon/stop'), { method: 'POST' });
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/daemon/stop`), {
|
||||
method: 'POST',
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to stop cursor daemon');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function probeCursorRuntime(): Promise<CursorProbeResult> {
|
||||
const res = await fetch(withApiBase('/cursor/probe'), { method: 'POST' });
|
||||
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/probe`), { method: 'POST' });
|
||||
const payload = await res.json().catch(() => null);
|
||||
|
||||
if (isCursorProbeResult(payload)) {
|
||||
|
||||
@@ -1283,7 +1283,7 @@ export function CursorPage() {
|
||||
<span className="font-medium text-muted-foreground">
|
||||
{t('cursorPage.provider')}
|
||||
</span>
|
||||
<span className="font-mono">Cursor IDE</span>
|
||||
<span className="font-mono">Cursor IDE (Legacy)</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm items-center">
|
||||
<span className="font-medium text-muted-foreground">
|
||||
@@ -1295,7 +1295,7 @@ export function CursorPage() {
|
||||
</div>
|
||||
{/* TODO i18n: missing key for model mapping env var info paragraph */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Model mapping writes `ANTHROPIC_MODEL`,
|
||||
Legacy bridge model mapping writes `ANTHROPIC_MODEL`,
|
||||
`ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, and
|
||||
`ANTHROPIC_DEFAULT_HAIKU_MODEL` in `cursor.settings.json`.
|
||||
</p>
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('useCursor', () => {
|
||||
const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.endsWith('/api/cursor/status')) {
|
||||
if (url.endsWith('/api/legacy/cursor/status')) {
|
||||
return Promise.resolve(
|
||||
createJsonResponse({
|
||||
enabled: true,
|
||||
@@ -48,7 +48,7 @@ describe('useCursor', () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (url.endsWith('/api/cursor/settings')) {
|
||||
if (url.endsWith('/api/legacy/cursor/settings')) {
|
||||
return Promise.resolve(
|
||||
createJsonResponse({
|
||||
enabled: true,
|
||||
@@ -60,7 +60,7 @@ describe('useCursor', () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (url.endsWith('/api/cursor/models')) {
|
||||
if (url.endsWith('/api/legacy/cursor/models')) {
|
||||
return Promise.resolve(
|
||||
createJsonResponse({
|
||||
models: [{ id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex', provider: 'openai' }],
|
||||
@@ -69,7 +69,7 @@ describe('useCursor', () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (url.endsWith('/api/cursor/settings/raw')) {
|
||||
if (url.endsWith('/api/legacy/cursor/settings/raw')) {
|
||||
return Promise.resolve(
|
||||
createJsonResponse({
|
||||
settings: {},
|
||||
@@ -80,7 +80,7 @@ describe('useCursor', () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (url.endsWith('/api/cursor/probe') && init?.method === 'POST') {
|
||||
if (url.endsWith('/api/legacy/cursor/probe') && init?.method === 'POST') {
|
||||
return Promise.resolve(createJsonResponse(probeFailure, 503));
|
||||
}
|
||||
|
||||
@@ -102,14 +102,16 @@ describe('useCursor', () => {
|
||||
await waitFor(() => expect(result.current.probeResult).toMatchObject(probeFailure));
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
fetchMock.mock.calls.filter(([input]) => String(input).endsWith('/api/cursor/status'))
|
||||
.length
|
||||
fetchMock.mock.calls.filter(([input]) =>
|
||||
String(input).endsWith('/api/legacy/cursor/status')
|
||||
).length
|
||||
).toBeGreaterThanOrEqual(2)
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
fetchMock.mock.calls.filter(([input]) => String(input).endsWith('/api/cursor/models'))
|
||||
.length
|
||||
fetchMock.mock.calls.filter(([input]) =>
|
||||
String(input).endsWith('/api/legacy/cursor/models')
|
||||
).length
|
||||
).toBeGreaterThanOrEqual(2)
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user