mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 04:17:54 +00:00
feat(cursor): split legacy bridge from cliproxy provider
This commit is contained in:
+32
-18
@@ -1,17 +1,20 @@
|
|||||||
# Cursor IDE Integration
|
# 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
|
## What It Provides
|
||||||
|
|
||||||
- OpenAI-compatible local endpoint powered by Cursor credentials.
|
- OpenAI-compatible local endpoint powered by Cursor credentials.
|
||||||
- Anthropic-compatible local endpoint at `/v1/messages` for Claude-native clients.
|
- Anthropic-compatible local endpoint at `/v1/messages` for Claude-native clients.
|
||||||
- Cursor model list and chat completions via the local CCS daemon.
|
- 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
|
## 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:
|
The current workflow is:
|
||||||
1. import Cursor credentials from local SQLite or manual input
|
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
|
### 1) Enable integration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ccs cursor enable
|
ccs legacy cursor enable
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2) Import credentials
|
### 2) Import credentials
|
||||||
@@ -40,25 +43,25 @@ ccs cursor enable
|
|||||||
Auto-detect from Cursor local SQLite state:
|
Auto-detect from Cursor local SQLite state:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ccs cursor auth
|
ccs legacy cursor auth
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual fallback:
|
Manual fallback:
|
||||||
|
|
||||||
```bash
|
```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
|
### 3) Start daemon
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ccs cursor start
|
ccs legacy cursor start
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4) Run a live probe
|
### 4) Run a live probe
|
||||||
|
|
||||||
```bash
|
```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.
|
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
|
### 5) Run Cursor-backed Claude
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ccs cursor "explain this repo"
|
ccs legacy cursor "explain this repo"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6) Verify status
|
### 6) Verify status
|
||||||
|
|
||||||
```bash
|
```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:
|
The admin namespace remains available for setup and inspection:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ccs cursor help
|
ccs legacy cursor help
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7) Stop daemon
|
### 7) Stop daemon
|
||||||
|
|
||||||
```bash
|
```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
|
## Runtime Defaults
|
||||||
@@ -96,7 +110,7 @@ ccs cursor stop
|
|||||||
- Model list resolution: authenticated live fetch when available, with cached/default fallback.
|
- 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.
|
- 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`.
|
- 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.
|
These values are managed in unified config and can be updated from CLI or dashboard.
|
||||||
|
|
||||||
@@ -108,7 +122,7 @@ Open dashboard:
|
|||||||
ccs config
|
ccs config
|
||||||
```
|
```
|
||||||
|
|
||||||
Then navigate to `Cursor IDE` in the sidebar.
|
Then navigate to `Cursor IDE (Legacy)` in the `Deprecated` section.
|
||||||
|
|
||||||
Available controls:
|
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`
|
### `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.
|
- `status` proves local config/auth/daemon readiness only.
|
||||||
- `probe` proves the live runtime path.
|
- `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
|
### Daemon fails to start
|
||||||
|
|
||||||
- Check if port `20129` is in use.
|
- 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 { getProfileLookupCandidates, isLegacyProfileAlias } from '../utils/profile-compat';
|
||||||
import type { CLIProxyProvider } from '../cliproxy/types';
|
import type { CLIProxyProvider } from '../cliproxy/types';
|
||||||
import { CLIPROXY_PROVIDER_IDS, isCLIProxyProvider } from '../cliproxy/provider-capabilities';
|
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 { normalizeCopilotModelId } from '../copilot/copilot-model-normalizer';
|
||||||
import type { TargetType } from '../targets/target-adapter';
|
import type { TargetType } from '../targets/target-adapter';
|
||||||
import type { ProfileType } from '../types/profile';
|
import type { ProfileType } from '../types/profile';
|
||||||
@@ -296,20 +297,17 @@ class ProfileDetector {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 0.25: Check Cursor profile - local Cursor daemon runtime.
|
// Priority 0.25: Check explicit legacy Cursor bridge profile.
|
||||||
// This keeps bare `ccs cursor` and `ccs cursor <subcommand>` bound to the
|
if (profileName === LEGACY_CURSOR_PROFILE_NAME) {
|
||||||
// existing runtime/admin surface even though CLIProxy also exposes a
|
|
||||||
// distinct provider named "cursor".
|
|
||||||
if (profileName === 'cursor') {
|
|
||||||
const cursorConfig = getCursorConfig();
|
const cursorConfig = getCursorConfig();
|
||||||
|
|
||||||
if (!cursorConfig?.enabled) {
|
if (!cursorConfig?.enabled) {
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
'Cursor profile is not enabled.\n\n' +
|
'Legacy Cursor profile is not enabled.\n\n' +
|
||||||
'To enable Cursor integration:\n' +
|
'To enable Cursor integration:\n' +
|
||||||
' 1. Run: ccs cursor enable\n' +
|
' 1. Run: ccs legacy cursor enable\n' +
|
||||||
' 2. Import auth: ccs cursor auth\n' +
|
' 2. Import auth: ccs legacy cursor auth\n' +
|
||||||
' 3. Start daemon: ccs cursor start\n\n' +
|
' 3. Start daemon: ccs legacy cursor start\n\n' +
|
||||||
'Or manually edit ~/.ccs/config.yaml:\n' +
|
'Or manually edit ~/.ccs/config.yaml:\n' +
|
||||||
' cursor:\n' +
|
' cursor:\n' +
|
||||||
' enabled: true'
|
' enabled: true'
|
||||||
@@ -322,7 +320,7 @@ class ProfileDetector {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'cursor',
|
type: 'cursor',
|
||||||
name: 'cursor',
|
name: LEGACY_CURSOR_PROFILE_NAME,
|
||||||
cursorConfig,
|
cursorConfig,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-17
@@ -65,7 +65,7 @@ import {
|
|||||||
resolveOfficialChannelsLaunchPlan,
|
resolveOfficialChannelsLaunchPlan,
|
||||||
} from './channels/official-channels-runtime';
|
} from './channels/official-channels-runtime';
|
||||||
import { getOfficialChannelReadiness } from './channels/official-channels-store';
|
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 { isCLIProxyProvider } from './cliproxy/provider-capabilities';
|
||||||
|
|
||||||
// Import centralized error handling
|
// 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(
|
function resolveRuntimeReasoningFlags(
|
||||||
args: string[],
|
args: string[],
|
||||||
envThinkingValue: string | undefined
|
envThinkingValue: string | undefined
|
||||||
@@ -343,7 +363,7 @@ async function main(): Promise<void> {
|
|||||||
registerTarget(new CodexAdapter());
|
registerTarget(new CodexAdapter());
|
||||||
const cliLogger = createLogger('cli');
|
const cliLogger = createLogger('cli');
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
let args = process.argv.slice(2);
|
||||||
const isCompletionCommand = args[0] === '__complete';
|
const isCompletionCommand = args[0] === '__complete';
|
||||||
|
|
||||||
// Initialize UI colors early to ensure consistent colored output
|
// Initialize UI colors early to ensure consistent colored output
|
||||||
@@ -419,6 +439,8 @@ async function main(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = normalizeLegacyCursorArgs(args);
|
||||||
|
|
||||||
cliLogger.info('command.start', 'CLI invocation started', {
|
cliLogger.info('command.start', 'CLI invocation started', {
|
||||||
command: args[0] || 'default',
|
command: args[0] || 'default',
|
||||||
argCount: args.length,
|
argCount: args.length,
|
||||||
@@ -489,7 +511,6 @@ async function main(): Promise<void> {
|
|||||||
if (
|
if (
|
||||||
typeof firstArg === 'string' &&
|
typeof firstArg === 'string' &&
|
||||||
isCLIProxyProvider(firstArg) &&
|
isCLIProxyProvider(firstArg) &&
|
||||||
firstArg !== 'cursor' &&
|
|
||||||
args.length > 1 &&
|
args.length > 1 &&
|
||||||
(args.includes('--help') || args.includes('-h'))
|
(args.includes('--help') || args.includes('-h'))
|
||||||
) {
|
) {
|
||||||
@@ -511,9 +532,8 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: cursor command (Cursor local proxy integration)
|
// Special case: explicit legacy Cursor bridge namespace.
|
||||||
// Route known admin subcommands to the command handler, keep all other args as profile passthrough.
|
if (firstArg === LEGACY_CURSOR_PROFILE_NAME && args.length > 1) {
|
||||||
if (firstArg === 'cursor' && args.length > 1) {
|
|
||||||
const { handleCursorCommand } = await import('./commands/cursor-command');
|
const { handleCursorCommand } = await import('./commands/cursor-command');
|
||||||
const cursorToken = args[1];
|
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
|
// First-time install: offer setup wizard for interactive users
|
||||||
// Check independently of recovery status (user may have empty config.yaml)
|
// Check independently of recovery status (user may have empty config.yaml)
|
||||||
// Skip if headless, CI, or non-TTY environment
|
// Skip if headless, CI, or non-TTY environment
|
||||||
@@ -551,17 +584,8 @@ async function main(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
// Detect profile (strip --target flags before profile detection)
|
// Detect profile (strip --target flags before profile detection)
|
||||||
const cleanArgs = stripTargetFlag(args);
|
const cleanArgs = stripTargetFlag(args);
|
||||||
const useCursorCliproxyShortcut = shouldUseCursorCliproxyShortcut(cleanArgs);
|
const { profile, remainingArgs } = detectProfile(cleanArgs);
|
||||||
const { profile, remainingArgs } = useCursorCliproxyShortcut
|
const profileInfo: ProfileDetectionResult = detector.detectProfileType(profile);
|
||||||
? { profile: 'cursor', remainingArgs: cleanArgs.slice(1) }
|
|
||||||
: detectProfile(cleanArgs);
|
|
||||||
const profileInfo: ProfileDetectionResult = useCursorCliproxyShortcut
|
|
||||||
? {
|
|
||||||
type: 'cliproxy',
|
|
||||||
name: 'cursor',
|
|
||||||
provider: 'cursor',
|
|
||||||
}
|
|
||||||
: detector.detectProfileType(profile);
|
|
||||||
let resolvedTarget: ReturnType<typeof resolveTargetType>;
|
let resolvedTarget: ReturnType<typeof resolveTargetType>;
|
||||||
try {
|
try {
|
||||||
resolvedTarget = resolveTargetType(
|
resolvedTarget = resolveTargetType(
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
normalizeProtocol,
|
normalizeProtocol,
|
||||||
CLIPROXY_DEFAULT_PORT,
|
CLIPROXY_DEFAULT_PORT,
|
||||||
} from './port-manager';
|
} from './port-manager';
|
||||||
import { getProviderSettingsPath } from './path-resolver';
|
import { migrateLegacyProviderSettingsIfNeeded } from './path-resolver';
|
||||||
import {
|
import {
|
||||||
canonicalizeModelIdForProvider,
|
canonicalizeModelIdForProvider,
|
||||||
MODEL_ENV_VAR_KEYS,
|
MODEL_ENV_VAR_KEYS,
|
||||||
@@ -465,7 +465,7 @@ export function getEffectiveEnvVars(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Priority 2: Default provider settings file
|
// Priority 2: Default provider settings file
|
||||||
const settingsPath = getProviderSettingsPath(provider);
|
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
|
|
||||||
// Check for user override file
|
// Check for user override file
|
||||||
if (fs.existsSync(settingsPath)) {
|
if (fs.existsSync(settingsPath)) {
|
||||||
@@ -505,7 +505,7 @@ export function getEffectiveEnvVars(
|
|||||||
* Called during installation/first run
|
* Called during installation/first run
|
||||||
*/
|
*/
|
||||||
export function ensureProviderSettings(provider: CLIProxyProvider): void {
|
export function ensureProviderSettings(provider: CLIProxyProvider): void {
|
||||||
const settingsPath = getProviderSettingsPath(provider);
|
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
const defaultEnv = getClaudeEnvVars(provider);
|
const defaultEnv = getClaudeEnvVars(provider);
|
||||||
|
|
||||||
const writeSettings = (settings: Record<string, unknown>): void => {
|
const writeSettings = (settings: Record<string, unknown>): void => {
|
||||||
@@ -663,7 +663,7 @@ export function getRemoteEnvVars(
|
|||||||
|
|
||||||
// Priority 2: Default provider settings file (~/.ccs/{provider}.settings.json)
|
// Priority 2: Default provider settings file (~/.ccs/{provider}.settings.json)
|
||||||
if (Object.keys(userEnvVars).length === 0) {
|
if (Object.keys(userEnvVars).length === 0) {
|
||||||
const settingsPath = getProviderSettingsPath(provider);
|
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
if (fs.existsSync(settingsPath)) {
|
if (fs.existsSync(settingsPath)) {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(settingsPath, 'utf-8');
|
const content = fs.readFileSync(settingsPath, 'utf-8');
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ export function getCliproxyDir(): string {
|
|||||||
return path.join(getCcsDir(), 'cliproxy');
|
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.
|
* Get CLIProxy writable directory for logs and runtime files.
|
||||||
* This directory is set as WRITABLE_PATH env var when spawning CLIProxy.
|
* 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
|
* Example: ~/.ccs/gemini.settings.json
|
||||||
*/
|
*/
|
||||||
export function getProviderSettingsPath(provider: CLIProxyProvider): string {
|
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`);
|
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 * as os from 'os';
|
||||||
import { InteractivePrompt } from '../utils/prompt';
|
import { InteractivePrompt } from '../utils/prompt';
|
||||||
import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog';
|
import { getProviderCatalog, supportsModelConfig, ModelEntry } from './model-catalog';
|
||||||
import { getProviderSettingsPath, getClaudeEnvVars } from './config-generator';
|
import { getClaudeEnvVars, migrateLegacyProviderSettingsIfNeeded } from './config-generator';
|
||||||
import { CLIProxyProvider } from './types';
|
import { CLIProxyProvider } from './types';
|
||||||
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
|
import { initUI, color, bold, dim, ok, info, header } from '../utils/ui';
|
||||||
import { getCcsDir } from '../utils/config-manager';
|
import { getCcsDir } from '../utils/config-manager';
|
||||||
@@ -31,7 +31,7 @@ function canonicalizeModelForProvider(provider: CLIProxyProvider, model: string)
|
|||||||
* Check if provider has user settings configured
|
* Check if provider has user settings configured
|
||||||
*/
|
*/
|
||||||
export function hasUserSettings(provider: CLIProxyProvider): boolean {
|
export function hasUserSettings(provider: CLIProxyProvider): boolean {
|
||||||
const settingsPath = getProviderSettingsPath(provider);
|
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
return fs.existsSync(settingsPath);
|
return fs.existsSync(settingsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export function getCurrentModel(
|
|||||||
): string | undefined {
|
): string | undefined {
|
||||||
const settingsPath = customSettingsPath
|
const settingsPath = customSettingsPath
|
||||||
? customSettingsPath.replace(/^~/, os.homedir())
|
? customSettingsPath.replace(/^~/, os.homedir())
|
||||||
: getProviderSettingsPath(provider);
|
: migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
if (!fs.existsSync(settingsPath)) return undefined;
|
if (!fs.existsSync(settingsPath)) return undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -116,7 +116,7 @@ export async function configureProviderModel(
|
|||||||
// Use custom settings path for CLIProxy variants, otherwise use default provider path
|
// Use custom settings path for CLIProxy variants, otherwise use default provider path
|
||||||
const settingsPath = customSettingsPath
|
const settingsPath = customSettingsPath
|
||||||
? customSettingsPath.replace(/^~/, os.homedir())
|
? customSettingsPath.replace(/^~/, os.homedir())
|
||||||
: getProviderSettingsPath(provider);
|
: migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
|
|
||||||
// Skip if already configured with a model (unless --config flag).
|
// Skip if already configured with a model (unless --config flag).
|
||||||
// A settings file can exist without model env keys (e.g., hook-only writes).
|
// 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();
|
await initUI();
|
||||||
|
|
||||||
const currentModel = getCurrentModel(provider);
|
const currentModel = getCurrentModel(provider);
|
||||||
const settingsPath = getProviderSettingsPath(provider);
|
const settingsPath = migrateLegacyProviderSettingsIfNeeded(provider);
|
||||||
const normalizedCurrentModel = currentModel
|
const normalizedCurrentModel = currentModel
|
||||||
? canonicalizeModelForProvider(provider, currentModel)
|
? canonicalizeModelForProvider(provider, currentModel)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { COPILOT_SUBCOMMANDS } from '../copilot/constants';
|
import { COPILOT_SUBCOMMANDS } from '../copilot/constants';
|
||||||
import { CURSOR_SUBCOMMANDS } from '../cursor/constants';
|
|
||||||
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
|
import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities';
|
||||||
|
|
||||||
export type HelpTopicName = 'profiles' | 'providers' | 'kiro' | 'completion' | 'targets';
|
export type HelpTopicName = 'profiles' | 'providers' | 'kiro' | 'completion' | 'targets';
|
||||||
@@ -105,7 +104,7 @@ export const ROOT_COMMAND_CATALOG: readonly RootCommandEntry[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cursor',
|
name: 'cursor',
|
||||||
summary: 'Run or manage the Cursor bridge',
|
summary: 'Run Cursor via CLIProxy or manage Cursor provider auth',
|
||||||
group: 'runtime',
|
group: 'runtime',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
},
|
},
|
||||||
@@ -308,7 +307,14 @@ export const COMMAND_FLAG_SUGGESTIONS: Readonly<Record<string, readonly string[]
|
|||||||
update: ['--force', '--beta', '--dev', '--help', '-h'],
|
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 const COPILOT_COMPLETION_SUBCOMMANDS = [...COPILOT_SUBCOMMANDS, 'help'] as const;
|
||||||
|
|
||||||
export function getPublicRootCommands(): readonly RootCommandEntry[] {
|
export function getPublicRootCommands(): readonly RootCommandEntry[] {
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import type { CursorConfig } from '../config/unified-config-types';
|
|||||||
import { getCcsDirDisplay } from '../utils/config-manager';
|
import { getCcsDirDisplay } from '../utils/config-manager';
|
||||||
import { color } from '../utils/ui';
|
import { color } from '../utils/ui';
|
||||||
|
|
||||||
|
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
|
||||||
|
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
|
||||||
|
|
||||||
function printLines(lines: string[]): void {
|
function printLines(lines: string[]): void {
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
console.log(line);
|
console.log(line);
|
||||||
@@ -14,11 +17,11 @@ export function renderCursorHelp(): number {
|
|||||||
printLines([
|
printLines([
|
||||||
'Legacy Cursor Compatibility',
|
'Legacy Cursor Compatibility',
|
||||||
'',
|
'',
|
||||||
'Deprecated: prefer CLIProxy-backed Cursor auth and account management.',
|
'Deprecated: `ccs cursor` now belongs to the CLIProxy Cursor provider.',
|
||||||
'Supported auth path: ccs cursor --auth',
|
`Supported auth path: ${CLIPROXY_CURSOR_COMMAND} --auth`,
|
||||||
'Supported dashboard path: ccs config -> CLIProxy -> Cursor',
|
'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):',
|
'Subcommands (deprecated compatibility for the local reverse-engineered bridge):',
|
||||||
' auth Import Cursor IDE authentication token (deprecated)',
|
' auth Import Cursor IDE authentication token (deprecated)',
|
||||||
@@ -32,24 +35,24 @@ export function renderCursorHelp(): number {
|
|||||||
' help Show this help message',
|
' help Show this help message',
|
||||||
'',
|
'',
|
||||||
'Supported CLIProxy path:',
|
'Supported CLIProxy path:',
|
||||||
' ccs cursor --auth # Authenticate Cursor via CLIProxy',
|
` ${CLIPROXY_CURSOR_COMMAND} --auth # Authenticate Cursor via CLIProxy`,
|
||||||
' ccs cursor --accounts # Manage CLIProxy Cursor accounts',
|
` ${CLIPROXY_CURSOR_COMMAND} --accounts # Manage CLIProxy Cursor accounts`,
|
||||||
' ccs cursor --config # Open CLIProxy Cursor settings',
|
` ${CLIPROXY_CURSOR_COMMAND} --config # Open CLIProxy Cursor settings`,
|
||||||
'',
|
'',
|
||||||
'Legacy runtime entry (deprecated compatibility):',
|
'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:',
|
'Legacy auth options:',
|
||||||
' ccs cursor auth # Auto-detect from Cursor SQLite (deprecated)',
|
` ${LEGACY_CURSOR_COMMAND} auth # Auto-detect from Cursor SQLite (deprecated)`,
|
||||||
' ccs cursor auth --manual --token <t> --machine-id <id>',
|
` ${LEGACY_CURSOR_COMMAND} auth --manual --token <t> --machine-id <id>`,
|
||||||
'',
|
'',
|
||||||
'Legacy bridge quick start:',
|
'Legacy bridge quick start:',
|
||||||
' 1. ccs cursor enable # Deprecated compatibility: enable local bridge',
|
` 1. ${LEGACY_CURSOR_COMMAND} enable # Deprecated compatibility: enable local bridge`,
|
||||||
' 2. ccs cursor auth # Deprecated compatibility: import Cursor IDE token',
|
` 2. ${LEGACY_CURSOR_COMMAND} auth # Deprecated compatibility: import Cursor IDE token`,
|
||||||
' 3. ccs cursor start # Start local daemon',
|
` 3. ${LEGACY_CURSOR_COMMAND} start # Start local daemon`,
|
||||||
' 4. ccs cursor probe # Verify live runtime health',
|
` 4. ${LEGACY_CURSOR_COMMAND} probe # Verify live runtime health`,
|
||||||
' 5. ccs cursor "task" # Run Claude through the local bridge',
|
` 5. ${LEGACY_CURSOR_COMMAND} "task" # Run Claude through the local bridge`,
|
||||||
' 6. ccs cursor status # Inspect auth/daemon wiring',
|
` 6. ${LEGACY_CURSOR_COMMAND} status # Inspect auth/daemon wiring`,
|
||||||
'',
|
'',
|
||||||
'Web UI: ccs config -> Deprecated -> Cursor IDE',
|
'Web UI: ccs config -> Deprecated -> Cursor IDE',
|
||||||
'',
|
'',
|
||||||
@@ -109,11 +112,13 @@ export function renderCursorStatus(
|
|||||||
console.log('');
|
console.log('');
|
||||||
console.log('Client setup:');
|
console.log('Client setup:');
|
||||||
console.log(` Raw settings: ${dirDisplay}/cursor.settings.json`);
|
console.log(` Raw settings: ${dirDisplay}/cursor.settings.json`);
|
||||||
console.log(' Runtime entry: ccs cursor [claude args] (deprecated compatibility)');
|
console.log(
|
||||||
console.log(' Supported auth: ccs cursor --auth');
|
` Runtime entry: ${LEGACY_CURSOR_COMMAND} [claude args] (deprecated compatibility)`
|
||||||
console.log(' Live probe: ccs cursor probe');
|
);
|
||||||
console.log(' Status command: ccs cursor status');
|
console.log(` Supported auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||||
console.log(' Help command: ccs cursor help');
|
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) {
|
if (isReady) {
|
||||||
return;
|
return;
|
||||||
@@ -123,16 +128,16 @@ export function renderCursorStatus(
|
|||||||
|
|
||||||
console.log('Next steps:');
|
console.log('Next steps:');
|
||||||
if (!cursorConfig.enabled) {
|
if (!cursorConfig.enabled) {
|
||||||
console.log(' - Enable: ccs cursor enable');
|
console.log(` - Enable: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||||
}
|
}
|
||||||
if (!authStatus.authenticated || authStatus.expired) {
|
if (!authStatus.authenticated || authStatus.expired) {
|
||||||
console.log(' - Supported: ccs cursor --auth');
|
console.log(` - Supported: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||||
console.log(' - Legacy auth: ccs cursor auth');
|
console.log(` - Legacy auth: ${LEGACY_CURSOR_COMMAND} auth`);
|
||||||
}
|
}
|
||||||
if (!daemonStatus.running) {
|
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 {
|
export function renderCursorModels(models: CursorModel[], defaultModel: string): void {
|
||||||
|
|||||||
@@ -26,10 +26,13 @@ import {
|
|||||||
} from './cursor-command-display';
|
} from './cursor-command-display';
|
||||||
import { ok, fail, info } from '../utils/ui';
|
import { ok, fail, info } from '../utils/ui';
|
||||||
|
|
||||||
|
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
|
||||||
|
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
|
||||||
|
|
||||||
function printLegacyCursorDeprecationNotice(): void {
|
function printLegacyCursorDeprecationNotice(): void {
|
||||||
console.log(
|
console.log(
|
||||||
info(
|
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('');
|
console.log('');
|
||||||
@@ -135,7 +138,7 @@ async function handleAuth(args: string[]): Promise<number> {
|
|||||||
if (!accessToken || !machineId) {
|
if (!accessToken || !machineId) {
|
||||||
console.error(
|
console.error(
|
||||||
fail(
|
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;
|
return 1;
|
||||||
@@ -156,9 +159,9 @@ async function handleAuth(args: string[]): Promise<number> {
|
|||||||
console.log(ok('Cursor credentials imported (manual mode)'));
|
console.log(ok('Cursor credentials imported (manual mode)'));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Next steps:');
|
console.log('Next steps:');
|
||||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||||
console.log(' 1. Enable integration: ccs cursor enable');
|
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||||
console.log(' 2. Start daemon: ccs cursor start');
|
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +182,10 @@ async function handleAuth(args: string[]): Promise<number> {
|
|||||||
console.log(ok('Auto-detected Cursor credentials'));
|
console.log(ok('Auto-detected Cursor credentials'));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Next steps:');
|
console.log('Next steps:');
|
||||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||||
console.log(' 1. Enable integration: ccs cursor enable');
|
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
|
||||||
console.log(' 2. Start daemon: ccs cursor start');
|
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||||
console.log(' 3. Check status: ccs cursor status');
|
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +193,7 @@ async function handleAuth(args: string[]): Promise<number> {
|
|||||||
printAutoDetectFailure(autoResult);
|
printAutoDetectFailure(autoResult);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Manual fallback:');
|
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('');
|
console.log('');
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
@@ -230,17 +233,17 @@ async function handleStart(): Promise<number> {
|
|||||||
const cursorConfig = getCursorConfig();
|
const cursorConfig = getCursorConfig();
|
||||||
|
|
||||||
if (!cursorConfig.enabled) {
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authStatus = checkAuthStatus();
|
const authStatus = checkAuthStatus();
|
||||||
if (!authStatus.authenticated) {
|
if (!authStatus.authenticated) {
|
||||||
console.error(fail('Not authenticated. Run: ccs cursor auth'));
|
console.error(fail(`Not authenticated. Run: ${LEGACY_CURSOR_COMMAND} auth`));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (authStatus.expired) {
|
if (authStatus.expired) {
|
||||||
console.error(fail('Credentials expired. Run: ccs cursor auth'));
|
console.error(fail(`Credentials expired. Run: ${LEGACY_CURSOR_COMMAND} auth`));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,10 +297,10 @@ async function handleEnable(): Promise<number> {
|
|||||||
console.log(ok('Cursor integration enabled'));
|
console.log(ok('Cursor integration enabled'));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Next steps:');
|
console.log('Next steps:');
|
||||||
console.log(' 0. Preferred auth: ccs cursor --auth');
|
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
|
||||||
console.log(' 1. Authenticate: ccs cursor auth');
|
console.log(` 1. Authenticate: ${LEGACY_CURSOR_COMMAND} auth`);
|
||||||
console.log(' 2. Start daemon: ccs cursor start');
|
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
|
||||||
console.log(' 3. Check status: ccs cursor status');
|
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,7 @@ export async function handleHelpRoute(
|
|||||||
cliproxy: async () => (await import('./cliproxy/help-subcommand')).showHelp(),
|
cliproxy: async () => (await import('./cliproxy/help-subcommand')).showHelp(),
|
||||||
copilot: async () =>
|
copilot: async () =>
|
||||||
process.exit(await (await import('./copilot-command')).handleCopilotCommand(['--help'])),
|
process.exit(await (await import('./copilot-command')).handleCopilotCommand(['--help'])),
|
||||||
cursor: async () =>
|
cursor: async () => await showProviderShortcutHelp('cursor', writeLine),
|
||||||
process.exit(await (await import('./cursor-command')).handleCursorCommand(['--help'])),
|
|
||||||
docker: async () => (await import('./docker/help-subcommand')).showHelp(),
|
docker: async () => (await import('./docker/help-subcommand')).showHelp(),
|
||||||
migrate: async () => (await import('./migrate-command')).printMigrateHelp(),
|
migrate: async () => (await import('./migrate-command')).printMigrateHelp(),
|
||||||
setup: async () => (await import('./setup-command')).handleSetupCommand(['--help']),
|
setup: async () => (await import('./setup-command')).handleSetupCommand(['--help']),
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export const RESERVED_PROFILE_NAMES = [
|
|||||||
'copilot',
|
'copilot',
|
||||||
// Cursor IDE (Cursor proxy daemon)
|
// Cursor IDE (Cursor proxy daemon)
|
||||||
'cursor',
|
'cursor',
|
||||||
|
'legacy-cursor',
|
||||||
|
'legacy',
|
||||||
// CLI commands and special names
|
// CLI commands and special names
|
||||||
'default',
|
'default',
|
||||||
'config',
|
'config',
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export const LEGACY_CURSOR_PROFILE_NAME = 'legacy-cursor';
|
||||||
|
|
||||||
export const CURSOR_SUBCOMMANDS = [
|
export const CURSOR_SUBCOMMANDS = [
|
||||||
'auth',
|
'auth',
|
||||||
'status',
|
'status',
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export function startCursorDaemonServer(options: DaemonRuntimeOptions): http.Ser
|
|||||||
|
|
||||||
const authStatus = checkAuthStatus();
|
const authStatus = checkAuthStatus();
|
||||||
if (!authStatus.authenticated || !authStatus.credentials) {
|
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) {
|
if (isAnthropicRoute) {
|
||||||
await pipeWebResponseToNode(
|
await pipeWebResponseToNode(
|
||||||
createAnthropicErrorResponse(401, 'authentication_error', message),
|
createAnthropicErrorResponse(401, 'authentication_error', message),
|
||||||
@@ -266,7 +266,7 @@ export function startCursorDaemonServer(options: DaemonRuntimeOptions): http.Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authStatus.expired) {
|
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) {
|
if (isAnthropicRoute) {
|
||||||
await pipeWebResponseToNode(
|
await pipeWebResponseToNode(
|
||||||
createAnthropicErrorResponse(401, 'authentication_error', message),
|
createAnthropicErrorResponse(401, 'authentication_error', message),
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export async function executeCursorProfile(
|
|||||||
if (!config.enabled) {
|
if (!config.enabled) {
|
||||||
console.error(fail('Cursor integration is not enabled.'));
|
console.error(fail('Cursor integration is not enabled.'));
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error('Enable it first: ccs cursor enable');
|
console.error('Enable it first: ccs legacy cursor enable');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,13 +105,13 @@ export async function executeCursorProfile(
|
|||||||
if (!authStatus.authenticated) {
|
if (!authStatus.authenticated) {
|
||||||
console.error(fail('Cursor credentials not found.'));
|
console.error(fail('Cursor credentials not found.'));
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error('Authenticate first: ccs cursor auth');
|
console.error('Authenticate first: ccs legacy cursor auth');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (authStatus.expired) {
|
if (authStatus.expired) {
|
||||||
console.error(fail('Cursor credentials have expired.'));
|
console.error(fail('Cursor credentials have expired.'));
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error('Refresh them with: ccs cursor auth');
|
console.error('Refresh them with: ccs legacy cursor auth');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export async function executeCursorProfile(
|
|||||||
console.error(fail('Cursor daemon is not running.'));
|
console.error(fail('Cursor daemon is not running.'));
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error('Start the daemon:');
|
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.');
|
console.error('Or enable auto_start in the Cursor config section.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
|||||||
stage: 'auth',
|
stage: 'auth',
|
||||||
status: 401,
|
status: 401,
|
||||||
duration_ms: Date.now() - startedAt,
|
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',
|
error_type: 'authentication_error',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
|||||||
stage: 'auth',
|
stage: 'auth',
|
||||||
status: 401,
|
status: 401,
|
||||||
duration_ms: Date.now() - startedAt,
|
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',
|
error_type: 'authentication_error',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ export async function probeCursorRuntime(config: CursorConfig): Promise<CursorPr
|
|||||||
status: 503,
|
status: 503,
|
||||||
duration_ms: Date.now() - startedAt,
|
duration_ms: Date.now() - startedAt,
|
||||||
message:
|
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',
|
error_type: 'daemon_not_running',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ function resolveBackend(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profileType === 'cursor' || profileName === 'cursor') {
|
if (profileType === 'cursor') {
|
||||||
return {
|
return {
|
||||||
backendId: null,
|
backendId: null,
|
||||||
backendDisplayName: null,
|
backendDisplayName: null,
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ apiRoutes.use('/copilot', copilotRoutes);
|
|||||||
|
|
||||||
// ==================== Cursor ====================
|
// ==================== Cursor ====================
|
||||||
apiRoutes.use('/cursor', cursorRoutes);
|
apiRoutes.use('/cursor', cursorRoutes);
|
||||||
|
apiRoutes.use('/legacy/cursor', cursorRoutes);
|
||||||
|
|
||||||
// ==================== Droid ====================
|
// ==================== Droid ====================
|
||||||
apiRoutes.use('/droid', droidRoutes);
|
apiRoutes.use('/droid', droidRoutes);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middlewar
|
|||||||
import type { Settings } from '../../types/config';
|
import type { Settings } from '../../types/config';
|
||||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||||
import { mapExternalProviderName } from '../../cliproxy/provider-capabilities';
|
import { mapExternalProviderName } from '../../cliproxy/provider-capabilities';
|
||||||
|
import { migrateLegacyProviderSettingsIfNeeded } from '../../cliproxy/config/path-resolver';
|
||||||
import { expandPath } from '../../utils/helpers';
|
import { expandPath } from '../../utils/helpers';
|
||||||
import {
|
import {
|
||||||
canonicalizeModelIdForProvider,
|
canonicalizeModelIdForProvider,
|
||||||
@@ -101,6 +102,17 @@ function resolveSettingsPath(profileOrVariant: string): string {
|
|||||||
const ccsDir = getCcsDir();
|
const ccsDir = getCcsDir();
|
||||||
const resolvedCcsDir = path.resolve(ccsDir);
|
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
|
// Check if this is a variant
|
||||||
const variants = listVariants();
|
const variants = listVariants();
|
||||||
const variant = variants[profileOrVariant];
|
const variant = variants[profileOrVariant];
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe('cursor daemon lifecycle smoke', () => {
|
|||||||
};
|
};
|
||||||
expect(anthropicBody.type).toBe('error');
|
expect(anthropicBody.type).toBe('error');
|
||||||
expect(anthropicBody.error?.type).toBe('authentication_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();
|
const stopResult = await stopDaemon();
|
||||||
expect(stopResult.success).toBe(true);
|
expect(stopResult.success).toBe(true);
|
||||||
|
|||||||
@@ -87,17 +87,22 @@ describe('npm CLI', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('routes cursor probe through the cursor command handler', function() {
|
it('routes cursor probe through the cursor command handler', function() {
|
||||||
|
let output = '';
|
||||||
try {
|
try {
|
||||||
execSync(`bun "${srcCcsPath}" cursor probe`, {
|
output = execSync(`bun "${srcCcsPath}" cursor probe`, {
|
||||||
|
encoding: 'utf8',
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
timeout: 3000,
|
timeout: 3000,
|
||||||
env: { ...process.env, CCS_HOME: testCcsHome }
|
env: { ...process.env, CCS_HOME: testCcsHome }
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const output = e.stderr?.toString() || e.stdout?.toString() || '';
|
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');
|
|
||||||
}
|
}
|
||||||
|
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() {
|
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 isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
|
||||||
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -213,9 +220,9 @@ describe('ProfileDetector', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = detector.detectProfileType('cursor');
|
const result = detector.detectProfileType('legacy-cursor');
|
||||||
expect(result.type).toBe('cursor');
|
expect(result.type).toBe('cursor');
|
||||||
expect(result.name).toBe('cursor');
|
expect(result.name).toBe('legacy-cursor');
|
||||||
expect(result.cursorConfig?.auto_start).toBe(true);
|
expect(result.cursorConfig?.auto_start).toBe(true);
|
||||||
} finally {
|
} finally {
|
||||||
isUnifiedModeSpy.mockRestore();
|
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;
|
const originalCcsHome = process.env.CCS_HOME;
|
||||||
process.env.CCS_HOME = tempDir;
|
process.env.CCS_HOME = tempDir;
|
||||||
const ccsDir = path.join(tempDir, '.ccs');
|
const ccsDir = path.join(tempDir, '.ccs');
|
||||||
@@ -235,7 +242,7 @@ describe('ProfileDetector', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const localDetector = new ProfileDetector();
|
const localDetector = new ProfileDetector();
|
||||||
const result = localDetector.detectProfileType('cursor');
|
const result = localDetector.detectProfileType('legacy-cursor');
|
||||||
expect(result.type).toBe('cursor');
|
expect(result.type).toBe('cursor');
|
||||||
expect(result.cursorConfig).toEqual({
|
expect(result.cursorConfig).toEqual({
|
||||||
enabled: true,
|
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 isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
|
||||||
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
const getCursorConfigSpy = spyOn(unifiedConfigLoader, 'getCursorConfig').mockReturnValue({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -264,7 +271,9 @@ describe('ProfileDetector', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(() => detector.detectProfileType('cursor')).toThrow(/Cursor profile is not enabled/);
|
expect(() => detector.detectProfileType('legacy-cursor')).toThrow(
|
||||||
|
/Legacy Cursor profile is not enabled/
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isUnifiedModeSpy.mockRestore();
|
isUnifiedModeSpy.mockRestore();
|
||||||
getCursorConfigSpy.mockRestore();
|
getCursorConfigSpy.mockRestore();
|
||||||
|
|||||||
@@ -140,6 +140,15 @@ describe('completion backend', () => {
|
|||||||
expect(suggestionValues(['cliproxy'])).toEqual(expect.arrayContaining(['remove', '--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', () => {
|
test('filters suggestions by the current token prefix', () => {
|
||||||
const values = suggestionValues([], 'do');
|
const values = suggestionValues([], 'do');
|
||||||
expect(values).toEqual(expect.arrayContaining(['docker', 'doctor']));
|
expect(values).toEqual(expect.arrayContaining(['docker', 'doctor']));
|
||||||
|
|||||||
@@ -322,7 +322,9 @@ describe('handleCursorCommand', () => {
|
|||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(errors).toHaveLength(0);
|
expect(errors).toHaveLength(0);
|
||||||
expect(logs.some((line) => line.includes('Legacy Cursor Compatibility'))).toBe(true);
|
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 {
|
} finally {
|
||||||
console.log = originalLog;
|
console.log = originalLog;
|
||||||
console.error = originalError;
|
console.error = originalError;
|
||||||
@@ -423,7 +425,9 @@ describe('renderCursorStatus', () => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
expect(logs.some((line) => line.includes('Next steps:'))).toBe(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 {
|
} finally {
|
||||||
console.log = originalLog;
|
console.log = originalLog;
|
||||||
}
|
}
|
||||||
@@ -483,11 +487,15 @@ describe('renderCursorHelp', () => {
|
|||||||
const exitCode = renderCursorHelp();
|
const exitCode = renderCursorHelp();
|
||||||
|
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
|
expect(logs.some((line) => line.includes('Usage: ccs legacy cursor <subcommand>'))).toBe(
|
||||||
expect(logs.some((line) => line.includes('Legacy Cursor Compatibility'))).toBe(true);
|
|
||||||
expect(logs.some((line) => line.includes('Deprecated: prefer CLIProxy-backed Cursor auth'))).toBe(
|
|
||||||
true
|
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(
|
expect(logs.some((line) => line.includes('probe Run a live authenticated runtime probe'))).toBe(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -495,7 +503,7 @@ describe('renderCursorHelp', () => {
|
|||||||
logs.some((line) => line.includes('ccs cursor --auth'))
|
logs.some((line) => line.includes('ccs cursor --auth'))
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
logs.some((line) => line.includes('ccs cursor [claude args]'))
|
logs.some((line) => line.includes('ccs legacy cursor [claude args]'))
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
} finally {
|
} finally {
|
||||||
console.log = originalLog;
|
console.log = originalLog;
|
||||||
|
|||||||
@@ -82,6 +82,21 @@ describe('image-analysis-backend-resolver', () => {
|
|||||||
expect(status.resolutionSource).toBe('profile-backend');
|
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', () => {
|
it('uses the fallback backend for an unmapped third-party settings profile', () => {
|
||||||
const status = resolveImageAnalysisStatus(
|
const status = resolveImageAnalysisStatus(
|
||||||
{
|
{
|
||||||
|
|||||||
+5
-1
@@ -1,5 +1,5 @@
|
|||||||
import { lazy, Suspense } from 'react';
|
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 { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
import { queryClient } from '@/lib/query-client';
|
import { queryClient } from '@/lib/query-client';
|
||||||
@@ -129,6 +129,10 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/cursor"
|
path="/cursor"
|
||||||
|
element={<Navigate to="/cliproxy?provider=cursor" replace />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/legacy/cursor"
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<PageLoader />}>
|
<Suspense fallback={<PageLoader />}>
|
||||||
<CursorPage />
|
<CursorPage />
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ interface ProviderEditorHeaderProps {
|
|||||||
onSave: () => void;
|
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({
|
export function ProviderEditorHeader({
|
||||||
displayName,
|
displayName,
|
||||||
logoProvider,
|
logoProvider,
|
||||||
@@ -63,7 +74,7 @@ export function ProviderEditorHeader({
|
|||||||
)}
|
)}
|
||||||
{!isRemoteMode && data?.path && (
|
{!isRemoteMode && data?.path && (
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{data.path.replace(/^.*[\\/]/, '')}
|
{formatSettingsPathBadgeLabel(data.path)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
isAnthropicModelEnvKey,
|
isAnthropicModelEnvKey,
|
||||||
} from '@/lib/extended-context-utils';
|
} from '@/lib/extended-context-utils';
|
||||||
import { supportsExtendedContext } from '@/lib/model-catalogs';
|
import { supportsExtendedContext } from '@/lib/model-catalogs';
|
||||||
|
import { isValidProvider } from '@/lib/provider-config';
|
||||||
|
|
||||||
/** Required env vars for CLIProxy providers (informational only - runtime fills defaults) */
|
/** Required env vars for CLIProxy providers (informational only - runtime fills defaults) */
|
||||||
const REQUIRED_ENV_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'] as const;
|
const REQUIRED_ENV_KEYS = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN'] as const;
|
||||||
@@ -39,12 +40,18 @@ export function useProviderEditor(
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await fetch(`/api/settings/${provider}/raw`);
|
const res = await fetch(`/api/settings/${provider}/raw`);
|
||||||
if (!res.ok) {
|
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 empty settings for unconfigured providers
|
||||||
return {
|
return {
|
||||||
profile: provider,
|
profile: provider,
|
||||||
settings: { env: {} },
|
settings: { env: {} },
|
||||||
mtime: Date.now(),
|
mtime: Date.now(),
|
||||||
path: `~/.ccs/profiles/${provider}/settings.json`,
|
path: fallbackPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
@@ -126,7 +126,11 @@ function buildNavGroups(t: (key: string) => string): SidebarGroupDef[] {
|
|||||||
{
|
{
|
||||||
title: t('nav.deprecated'),
|
title: t('nav.deprecated'),
|
||||||
items: [
|
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;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LEGACY_CURSOR_API_BASE = '/legacy/cursor';
|
||||||
|
|
||||||
export interface CursorProbeResult {
|
export interface CursorProbeResult {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
stage: 'config' | 'auth' | 'daemon' | 'runtime';
|
stage: 'config' | 'auth' | 'daemon' | 'runtime';
|
||||||
@@ -87,25 +89,25 @@ function getProbeErrorMessage(value: unknown): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCursorStatus(): Promise<CursorStatus> {
|
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');
|
if (!res.ok) throw new Error('Failed to fetch cursor status');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCursorConfig(): Promise<CursorConfig> {
|
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');
|
if (!res.ok) throw new Error('Failed to fetch cursor config');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCursorModels(): Promise<CursorModelsResponse> {
|
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');
|
if (!res.ok) throw new Error('Failed to fetch cursor models');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
|
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');
|
if (!res.ok) throw new Error('Failed to fetch cursor raw settings');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
@@ -113,7 +115,7 @@ async function fetchCursorRawSettings(): Promise<CursorRawSettings> {
|
|||||||
async function updateCursorConfig(
|
async function updateCursorConfig(
|
||||||
updates: Partial<CursorConfig>
|
updates: Partial<CursorConfig>
|
||||||
): Promise<{ success: boolean; cursor: 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',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(updates),
|
body: JSON.stringify(updates),
|
||||||
@@ -126,7 +128,7 @@ async function saveCursorRawSettings(data: {
|
|||||||
settings: CursorRawSettings['settings'];
|
settings: CursorRawSettings['settings'];
|
||||||
expectedMtime?: number;
|
expectedMtime?: number;
|
||||||
}): Promise<{ success: boolean; mtime: 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',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -137,7 +139,9 @@ async function saveCursorRawSettings(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function autoDetectCursorAuth(): Promise<CursorAuthResult> {
|
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) {
|
if (!res.ok) {
|
||||||
const error = await res.json().catch(() => ({ error: 'Auto-detect failed' }));
|
const error = await res.json().catch(() => ({ error: 'Auto-detect failed' }));
|
||||||
throw new Error(error.error || 'Auto-detect failed');
|
throw new Error(error.error || 'Auto-detect failed');
|
||||||
@@ -149,7 +153,7 @@ async function importCursorAuthManual(data: {
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
machineId: string;
|
machineId: string;
|
||||||
}): Promise<CursorAuthResult> {
|
}): Promise<CursorAuthResult> {
|
||||||
const res = await fetch(withApiBase('/cursor/auth/import'), {
|
const res = await fetch(withApiBase(`${LEGACY_CURSOR_API_BASE}/auth/import`), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -162,19 +166,23 @@ async function importCursorAuthManual(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startCursorDaemon(): Promise<{ success: boolean; pid?: number; error?: string }> {
|
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');
|
if (!res.ok) throw new Error('Failed to start cursor daemon');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopCursorDaemon(): Promise<{ success: boolean; error?: string }> {
|
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');
|
if (!res.ok) throw new Error('Failed to stop cursor daemon');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function probeCursorRuntime(): Promise<CursorProbeResult> {
|
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);
|
const payload = await res.json().catch(() => null);
|
||||||
|
|
||||||
if (isCursorProbeResult(payload)) {
|
if (isCursorProbeResult(payload)) {
|
||||||
|
|||||||
@@ -1283,7 +1283,7 @@ export function CursorPage() {
|
|||||||
<span className="font-medium text-muted-foreground">
|
<span className="font-medium text-muted-foreground">
|
||||||
{t('cursorPage.provider')}
|
{t('cursorPage.provider')}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-mono">Cursor IDE</span>
|
<span className="font-mono">Cursor IDE (Legacy)</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm items-center">
|
<div className="grid grid-cols-[100px_1fr] gap-2 text-sm items-center">
|
||||||
<span className="font-medium text-muted-foreground">
|
<span className="font-medium text-muted-foreground">
|
||||||
@@ -1295,7 +1295,7 @@ export function CursorPage() {
|
|||||||
</div>
|
</div>
|
||||||
{/* TODO i18n: missing key for model mapping env var info paragraph */}
|
{/* TODO i18n: missing key for model mapping env var info paragraph */}
|
||||||
<p className="text-xs text-muted-foreground">
|
<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_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, and
|
||||||
`ANTHROPIC_DEFAULT_HAIKU_MODEL` in `cursor.settings.json`.
|
`ANTHROPIC_DEFAULT_HAIKU_MODEL` in `cursor.settings.json`.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('useCursor', () => {
|
|||||||
const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
|
const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
const url = String(input);
|
const url = String(input);
|
||||||
|
|
||||||
if (url.endsWith('/api/cursor/status')) {
|
if (url.endsWith('/api/legacy/cursor/status')) {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
createJsonResponse({
|
createJsonResponse({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -48,7 +48,7 @@ describe('useCursor', () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.endsWith('/api/cursor/settings')) {
|
if (url.endsWith('/api/legacy/cursor/settings')) {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
createJsonResponse({
|
createJsonResponse({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -60,7 +60,7 @@ describe('useCursor', () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.endsWith('/api/cursor/models')) {
|
if (url.endsWith('/api/legacy/cursor/models')) {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
createJsonResponse({
|
createJsonResponse({
|
||||||
models: [{ id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex', provider: 'openai' }],
|
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(
|
return Promise.resolve(
|
||||||
createJsonResponse({
|
createJsonResponse({
|
||||||
settings: {},
|
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));
|
return Promise.resolve(createJsonResponse(probeFailure, 503));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,14 +102,16 @@ describe('useCursor', () => {
|
|||||||
await waitFor(() => expect(result.current.probeResult).toMatchObject(probeFailure));
|
await waitFor(() => expect(result.current.probeResult).toMatchObject(probeFailure));
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(
|
expect(
|
||||||
fetchMock.mock.calls.filter(([input]) => String(input).endsWith('/api/cursor/status'))
|
fetchMock.mock.calls.filter(([input]) =>
|
||||||
.length
|
String(input).endsWith('/api/legacy/cursor/status')
|
||||||
|
).length
|
||||||
).toBeGreaterThanOrEqual(2)
|
).toBeGreaterThanOrEqual(2)
|
||||||
);
|
);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(
|
expect(
|
||||||
fetchMock.mock.calls.filter(([input]) => String(input).endsWith('/api/cursor/models'))
|
fetchMock.mock.calls.filter(([input]) =>
|
||||||
.length
|
String(input).endsWith('/api/legacy/cursor/models')
|
||||||
|
).length
|
||||||
).toBeGreaterThanOrEqual(2)
|
).toBeGreaterThanOrEqual(2)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user