feat(cursor): add support for Cursor Agent CLI integration (#236)

* feat(cursor): add support for Cursor Agent CLI integration

- Introduced new command `hapi cursor` to start Cursor Agent sessions.
- Added functionality for resuming sessions and managing permission modes.
- Updated documentation to include Cursor Agent usage and installation instructions.
- Enhanced existing codebase to accommodate Cursor as a recognized agent flavor.
- Implemented local and remote session handling for Cursor Agent.

This update expands HAPI's capabilities by integrating support for the Cursor Agent, allowing users to leverage its features alongside existing agents.

* Remove TODO.md file as it is no longer needed following the integration of Cursor Agent CLI support. This cleanup helps streamline project documentation and reflects the completion of the associated tasks.

* feat(cursor): implement remote mode and fix --hapi-starting-mode

- Consume --hapi-starting-mode in cursor command (do not forward to agent)
- Implement cursorRemoteLauncher: spawn agent -p with stream-json, --trust
- Add cursorEventConverter for NDJSON parsing (system/assistant/tool_call/result)
- Multi-turn via --resume session_id
- Update docs: cursor supports both local and remote modes

Made-with: Cursor

* fix: type error

* fix(cursor): address PR review - model UI, sessionId metadata, duplicate flags

- HappyComposer: use isClaudeFlavor for model mode (cursor has no model modes)
- cursorLocalLauncher: call onSessionFound for resume so cursorSessionId in metadata
- cursorCommand: do not forward parsed flags to cursorArgs (avoid duplicates)

Made-with: Cursor
This commit is contained in:
Mao Mr
2026-03-03 10:02:47 +08:00
committed by GitHub
parent 72a23fc761
commit c9be2894ac
35 changed files with 1097 additions and 30 deletions
+11 -2
View File
@@ -10,11 +10,15 @@ export type GeminiPermissionMode = typeof GEMINI_PERMISSION_MODES[number]
export const OPENCODE_PERMISSION_MODES = ['default', 'yolo'] as const
export type OpencodePermissionMode = typeof OPENCODE_PERMISSION_MODES[number]
export const CURSOR_PERMISSION_MODES = ['default', 'plan', 'ask', 'yolo'] as const
export type CursorPermissionMode = typeof CURSOR_PERMISSION_MODES[number]
export const PERMISSION_MODES = [
'default',
'acceptEdits',
'bypassPermissions',
'plan',
'ask',
'read-only',
'safe-yolo',
'yolo'
@@ -24,12 +28,13 @@ export type PermissionMode = typeof PERMISSION_MODES[number]
export const MODEL_MODES = ['default', 'sonnet', 'opus'] as const
export type ModelMode = typeof MODEL_MODES[number]
export type AgentFlavor = 'claude' | 'codex' | 'gemini' | 'opencode'
export type AgentFlavor = 'claude' | 'codex' | 'gemini' | 'opencode' | 'cursor'
export const PERMISSION_MODE_LABELS: Record<PermissionMode, string> = {
default: 'Default',
acceptEdits: 'Accept Edits',
plan: 'Plan Mode',
ask: 'Ask Mode',
bypassPermissions: 'Yolo',
'read-only': 'Read Only',
'safe-yolo': 'Safe Yolo',
@@ -42,6 +47,7 @@ export const PERMISSION_MODE_TONES: Record<PermissionMode, PermissionModeTone> =
default: 'neutral',
acceptEdits: 'warning',
plan: 'info',
ask: 'info',
bypassPermissions: 'danger',
'read-only': 'warning',
'safe-yolo': 'warning',
@@ -78,6 +84,9 @@ export function getPermissionModesForFlavor(flavor?: string | null): readonly Pe
if (flavor === 'opencode') {
return OPENCODE_PERMISSION_MODES
}
if (flavor === 'cursor') {
return CURSOR_PERMISSION_MODES
}
return CLAUDE_PERMISSION_MODES
}
@@ -94,7 +103,7 @@ export function isPermissionModeAllowedForFlavor(mode: PermissionMode, flavor?:
}
export function getModelModesForFlavor(flavor?: string | null): readonly ModelMode[] {
if (flavor === 'codex' || flavor === 'gemini' || flavor === 'opencode') {
if (flavor === 'codex' || flavor === 'gemini' || flavor === 'opencode' || flavor === 'cursor') {
return []
}
return MODEL_MODES
+1
View File
@@ -31,6 +31,7 @@ export const MetadataSchema = z.object({
codexSessionId: z.string().optional(),
geminiSessionId: z.string().optional(),
opencodeSessionId: z.string().optional(),
cursorSessionId: z.string().optional(),
tools: z.array(z.string()).optional(),
slashCommands: z.array(z.string()).optional(),
homeDir: z.string().optional(),
+1
View File
@@ -17,6 +17,7 @@ export type {
AgentFlavor,
ClaudePermissionMode,
CodexPermissionMode,
CursorPermissionMode,
GeminiPermissionMode,
OpencodePermissionMode,
ModelMode,