From 8bc7ae5fc82e6e218fe33d327851b7a6b43b3727 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 31 Aug 2026 16:36:23 -0500 Subject: [PATCH 1/2] feat: add Cursor Agent CLI as first-class native agent --- .../happy-app/sources/app/(app)/new/index.tsx | 2 ++ .../sources/app/(app)/settings/agents.tsx | 1 + .../sources/assets/images/icon-cursor.png | Bin 0 -> 4761 bytes .../sources/assets/images/icon-cursor@2x.png | Bin 0 -> 10077 bytes .../sources/assets/images/icon-cursor@3x.png | Bin 0 -> 16625 bytes .../sources/components/AgentInput.tsx | 2 +- .../happy-app/sources/components/Avatar.tsx | 5 ++- .../components/modelModeOptions.test.ts | 13 +++++++ .../sources/components/modelModeOptions.ts | 32 +++++++++++++++++ .../sources/sync/agentDefaults.test.ts | 2 ++ .../happy-app/sources/sync/agentDefaults.ts | 6 ++-- packages/happy-app/sources/sync/ops.ts | 4 +-- .../happy-app/sources/sync/persistence.ts | 4 +-- .../sources/utils/avatarHarness.test.ts | 3 +- .../happy-app/sources/utils/avatarHarness.ts | 3 +- .../sources/utils/harnessCatalog.test.ts | 15 ++++---- .../happy-app/sources/utils/harnessCatalog.ts | 2 ++ packages/happy-cli/README.md | 1 + .../src/agent/acp/acpAgentConfig.test.ts | 8 ++++- .../happy-cli/src/agent/acp/acpAgentConfig.ts | 1 + packages/happy-cli/src/agent/acp/runAcp.ts | 5 ++- .../src/agent/adapters/MessageAdapter.ts | 1 + .../src/agent/adapters/MobileMessageFormat.ts | 2 +- .../happy-cli/src/agent/core/AgentBackend.ts | 2 +- packages/happy-cli/src/api/apiSession.ts | 4 +-- packages/happy-cli/src/daemon/run.ts | 15 ++++++-- packages/happy-cli/src/index.ts | 34 ++++++++++++++++++ .../modules/common/registerCommonHandlers.ts | 2 +- .../src/utils/createSessionMetadata.ts | 2 +- .../happy-cli/src/utils/detectCLI.test.ts | 11 ++++++ packages/happy-cli/src/utils/detectCLI.ts | 7 ++-- 31 files changed, 161 insertions(+), 28 deletions(-) create mode 100644 packages/happy-app/sources/assets/images/icon-cursor.png create mode 100644 packages/happy-app/sources/assets/images/icon-cursor@2x.png create mode 100644 packages/happy-app/sources/assets/images/icon-cursor@3x.png diff --git a/packages/happy-app/sources/app/(app)/new/index.tsx b/packages/happy-app/sources/app/(app)/new/index.tsx index 9eec5d1d4e..d26e54d3f2 100644 --- a/packages/happy-app/sources/app/(app)/new/index.tsx +++ b/packages/happy-app/sources/app/(app)/new/index.tsx @@ -105,6 +105,7 @@ const agentIcons = { rig: require('@/assets/images/logo-black.png'), claude: require('@/assets/images/icon-claude.png'), codex: require('@/assets/images/icon-gpt.png'), + cursor: require('@/assets/images/icon-cursor.png'), openclaw: require('@/assets/images/icon-openclaw.png'), gemini: require('@/assets/images/icon-gemini.png'), agy: require('@/assets/images/icon-agy.png'), @@ -116,6 +117,7 @@ type AgentKey = NewSessionAgentType; const ALL_AGENTS: { key: AgentKey; label: string }[] = [ { key: 'claude', label: 'claude code' }, { key: 'codex', label: 'codex' }, + { key: 'cursor', label: 'cursor' }, { key: 'agy', label: 'antigravity' }, { key: 'rig', label: 'happy' }, ]; diff --git a/packages/happy-app/sources/app/(app)/settings/agents.tsx b/packages/happy-app/sources/app/(app)/settings/agents.tsx index fe7db3521d..77e53e5356 100644 --- a/packages/happy-app/sources/app/(app)/settings/agents.tsx +++ b/packages/happy-app/sources/app/(app)/settings/agents.tsx @@ -44,6 +44,7 @@ type FieldConfig = { const agentLabels: Record = { claude: getHarnessName('claude'), codex: getHarnessName('codex'), + cursor: getHarnessName('cursor'), gemini: getHarnessName('gemini'), openclaw: getHarnessName('openclaw'), agy: getHarnessName('agy'), diff --git a/packages/happy-app/sources/assets/images/icon-cursor.png b/packages/happy-app/sources/assets/images/icon-cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..ef51d1ea69b81aa6934d6673ec1c75b3d18bd4a4 GIT binary patch literal 4761 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 literal 0 HcmV?d00001 diff --git a/packages/happy-app/sources/assets/images/icon-cursor@2x.png b/packages/happy-app/sources/assets/images/icon-cursor@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f62d6dc7ae4db2676353374ec005c75b47f11be8 GIT binary patch literal 10077 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 M_03`Lb={u)57CyiApigX literal 0 HcmV?d00001 diff --git a/packages/happy-app/sources/assets/images/icon-cursor@3x.png b/packages/happy-app/sources/assets/images/icon-cursor@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e4261dec4ad541783873581c8d977629bbcd588c GIT binary patch literal 16625 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 literal 0 HcmV?d00001 diff --git a/packages/happy-app/sources/components/AgentInput.tsx b/packages/happy-app/sources/components/AgentInput.tsx index 70d94d69a0..40471d2818 100644 --- a/packages/happy-app/sources/components/AgentInput.tsx +++ b/packages/happy-app/sources/components/AgentInput.tsx @@ -109,7 +109,7 @@ interface AgentInputProps { /** Plan quota windows from agent state, for the week stat and its popup. */ sessionStatusUsageLimits?: UsageLimitsLike | null; onFileViewerPress?: () => void; - agentType?: 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy'; + agentType?: 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy' | 'cursor'; onAgentClick?: () => void; machineName?: string | null; onMachineClick?: () => void; diff --git a/packages/happy-app/sources/components/Avatar.tsx b/packages/happy-app/sources/components/Avatar.tsx index 03081d9ba8..aec8277a65 100644 --- a/packages/happy-app/sources/components/Avatar.tsx +++ b/packages/happy-app/sources/components/Avatar.tsx @@ -29,6 +29,7 @@ const harnessIcons: Record = { claude: require('@/assets/images/icon-claude.png'), codex: require('@/assets/images/icon-gpt.png'), agy: require('@/assets/images/icon-agy.png'), + cursor: require('@/assets/images/icon-cursor.png'), rig: require('@/assets/images/logo-black.png'), }; @@ -45,7 +46,9 @@ function harnessBadgeSizes(size: number, harness: AvatarHarnessIcon) { ? Math.round(size * 0.3) : harness === 'claude' ? Math.round(size * 0.34) - : Math.round(size * 0.42); + : harness === 'cursor' + ? Math.round(size * 0.32) + : Math.round(size * 0.42); return { circleSize, iconSize }; } diff --git a/packages/happy-app/sources/components/modelModeOptions.test.ts b/packages/happy-app/sources/components/modelModeOptions.test.ts index 9bc59007cc..15a46da99a 100644 --- a/packages/happy-app/sources/components/modelModeOptions.test.ts +++ b/packages/happy-app/sources/components/modelModeOptions.test.ts @@ -11,6 +11,8 @@ import { getCodexPermissionModes, getClaudeModelModes, getClaudePermissionModes, + getCursorModelModes, + getCursorPermissionModes, getGeminiPermissionModes, getDefaultEffortKey, getDefaultModelKey, @@ -251,6 +253,17 @@ describe('modelModeOptions', () => { expect(keys).not.toContain('sonnet'); }); + it('returns the curated cursor models and default model key', () => { + const models = getAvailableModels('cursor', null, translate); + expect(models).toEqual(getCursorModelModes()); + const keys = models.map((m) => m.key); + expect(keys).toContain('default[]'); + expect(getDefaultModelKey('cursor')).toBe('default[]'); + expect(getDefaultPermissionModeKey('cursor')).toBe('agent'); + expect(getCursorPermissionModes(translate).map((m) => m.key)).toContain('agent'); + expect(getCursorPermissionModes(translate).map((m) => m.key)).toContain('plan'); + }); + it('resolves the first matching preferred key', () => { const options = [ { key: 'a', name: 'A' }, diff --git a/packages/happy-app/sources/components/modelModeOptions.ts b/packages/happy-app/sources/components/modelModeOptions.ts index 959a017743..ea96c28325 100644 --- a/packages/happy-app/sources/components/modelModeOptions.ts +++ b/packages/happy-app/sources/components/modelModeOptions.ts @@ -287,15 +287,44 @@ export function getHardcodedPermissionModes(flavor: AgentFlavor, translate: Tran if (flavor === 'agy') { return getAgyPermissionModes(translate); } + if (flavor === 'cursor') { + return getCursorPermissionModes(translate); + } return getClaudePermissionModes(translate); } +export function getCursorPermissionModes(translate: Translate): PermissionMode[] { + return [ + { key: 'agent', name: 'Agent', description: translate('agentInput.permissionMode.default') }, + { key: 'plan', name: 'Plan', description: translate('agentInput.permissionMode.plan') }, + { key: 'ask', name: 'Ask', description: 'Q&A mode - no edits or command execution' }, + { key: 'bypassPermissions', name: 'Yolo', description: translate('agentInput.permissionMode.bypassPermissions') }, + { key: 'default', name: 'Default', description: translate('agentInput.permissionMode.default') }, + ]; +} + export function getOpenClawModelModes(): ModelMode[] { return [ { key: 'default', name: 'Default model', description: null }, ]; } +export function getCursorModelModes(): ModelMode[] { + return [ + { key: 'default[]', name: 'Auto', description: 'Cursor Router (Default)' }, + { key: 'grok-4.6[effort=high,fast=true]', name: 'Grok 4.6', description: 'xAI' }, + { key: 'composer-2.5[fast=true]', name: 'Composer 2.5', description: 'Cursor' }, + { key: 'claude-opus-5[thinking=true,context=300k,effort=high,fast=false]', name: 'Claude Opus 5', description: 'Anthropic' }, + { key: 'claude-sonnet-5[thinking=true,context=300k,effort=high]', name: 'Claude Sonnet 5', description: 'Anthropic' }, + { key: 'gpt-5.6-sol[context=272k,reasoning=medium,fast=false]', name: 'GPT-5.6 Sol', description: 'OpenAI' }, + { key: 'gpt-5.5[context=272k,reasoning=medium,fast=false]', name: 'GPT-5.5', description: 'OpenAI' }, + { key: 'gemini-3.7-flash[effort=high]', name: 'Gemini 3.7 Flash', description: 'Google' }, + { key: 'gemini-3.1-pro[]', name: 'Gemini 3.1 Pro', description: 'Google' }, + { key: 'claude-fable-5[thinking=true,context=300k,effort=high]', name: 'Claude Fable 5', description: 'Anthropic' }, + { key: 'grok-4.5[effort=high,fast=true]', name: 'Grok 4.5', description: 'xAI' }, + ]; +} + // Keys are the exact display names `agy --model` accepts (as printed by `agy models`). export function getAgyModelModes(): ModelMode[] { return [ @@ -326,6 +355,9 @@ export function getHardcodedModelModes(flavor: AgentFlavor, _translate: Translat if (flavor === 'agy') { return getAgyModelModes(); } + if (flavor === 'cursor') { + return getCursorModelModes(); + } return getClaudeModelModes(); } diff --git a/packages/happy-app/sources/sync/agentDefaults.test.ts b/packages/happy-app/sources/sync/agentDefaults.test.ts index cf6cd6b290..4bbf966756 100644 --- a/packages/happy-app/sources/sync/agentDefaults.test.ts +++ b/packages/happy-app/sources/sync/agentDefaults.test.ts @@ -48,5 +48,7 @@ describe('agent defaults', () => { expect(resolveAgentDefaultConfig({}, 'gemini', '1.0.0').permissionMode).toBe('default'); expect(resolveAgentDefaultConfig({}, 'openclaw', '1.0.0').permissionMode).toBe('default'); expect(resolveAgentDefaultConfig({}, 'agy', '1.0.0').permissionMode).toBe('default'); + expect(resolveAgentDefaultConfig({}, 'cursor', '1.0.0').permissionMode).toBe('agent'); + expect(resolveAgentDefaultConfig({}, 'cursor', '1.0.0').modelMode).toBe('default[]'); }); }); \ No newline at end of file diff --git a/packages/happy-app/sources/sync/agentDefaults.ts b/packages/happy-app/sources/sync/agentDefaults.ts index b610599a98..280143456c 100644 --- a/packages/happy-app/sources/sync/agentDefaults.ts +++ b/packages/happy-app/sources/sync/agentDefaults.ts @@ -1,7 +1,7 @@ import * as z from 'zod'; import { compareVersionsWithPrerelease, isWellFormedVersion } from '@/utils/versionUtils'; -export const agentKeys = ['claude', 'codex', 'gemini', 'openclaw', 'agy'] as const; +export const agentKeys = ['claude', 'codex', 'gemini', 'openclaw', 'agy', 'cursor'] as const; export type AgentKey = typeof agentKeys[number]; export const AgentDefaultOverrideSchema = z.object({ @@ -16,6 +16,7 @@ export const AgentDefaultOverridesSchema = z.object({ gemini: AgentDefaultOverrideSchema.optional(), openclaw: AgentDefaultOverrideSchema.optional(), agy: AgentDefaultOverrideSchema.optional(), + cursor: AgentDefaultOverrideSchema.optional(), }).passthrough().default({}); export type AgentDefaultOverride = z.infer; @@ -37,6 +38,7 @@ const codeAgentDefaults: Record = { gemini: { permissionMode: 'default', modelMode: 'gemini-2.5-pro', effortLevel: null }, openclaw: { permissionMode: 'default', modelMode: 'default', effortLevel: null }, agy: { permissionMode: 'default', modelMode: 'Gemini 3.1 Pro (High)', effortLevel: null }, + cursor: { permissionMode: 'agent', modelMode: 'default[]', effortLevel: null }, }; // `auto` first shipped in happy-cli 1.2.1-beta.2, for Claude and Codex alike. @@ -60,7 +62,7 @@ function resolveCodeDefaultPermissionMode( } export function normalizeAgentKey(flavor: string | null | undefined): AgentKey { - if (flavor === 'codex' || flavor === 'gemini' || flavor === 'openclaw' || flavor === 'agy') { + if (flavor === 'codex' || flavor === 'gemini' || flavor === 'openclaw' || flavor === 'agy' || flavor === 'cursor') { return flavor; } return 'claude'; diff --git a/packages/happy-app/sources/sync/ops.ts b/packages/happy-app/sources/sync/ops.ts index f206da6d03..21f45eae26 100644 --- a/packages/happy-app/sources/sync/ops.ts +++ b/packages/happy-app/sources/sync/ops.ts @@ -172,7 +172,7 @@ export interface SpawnSessionOptions { directory: string; approvedNewDirectoryCreation?: boolean; token?: string; - agent?: 'codex' | 'claude' | 'gemini' | 'openclaw' | 'agy' | 'rig'; + agent?: 'codex' | 'claude' | 'gemini' | 'openclaw' | 'agy' | 'cursor' | 'rig'; permissionMode?: string; modelMode?: string; effortLevel?: string; @@ -274,7 +274,7 @@ export async function machineSpawnNewSession(options: SpawnSessionOptions): Prom directory: string approvedNewDirectoryCreation?: boolean, token?: string, - agent?: 'codex' | 'claude' | 'gemini' | 'openclaw' | 'agy' | 'rig', + agent?: 'codex' | 'claude' | 'gemini' | 'openclaw' | 'agy' | 'cursor' | 'rig', permissionMode?: string, modelMode?: string, effortLevel?: string, diff --git a/packages/happy-app/sources/sync/persistence.ts b/packages/happy-app/sources/sync/persistence.ts index ca7f33d1a7..6c4ad8a1a0 100644 --- a/packages/happy-app/sources/sync/persistence.ts +++ b/packages/happy-app/sources/sync/persistence.ts @@ -12,7 +12,7 @@ const VOICE_SOFT_PAYWALL_SHOWN_KEY = 'voice-soft-paywall-shown'; const VOICE_ONBOARDING_PROMPT_LOAD_COUNT_KEY = 'voice-onboarding-prompt-load-count'; const VOICE_MESSAGE_COUNT_KEY = 'voice-message-count'; -export type NewSessionAgentType = 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy' | 'rig'; +export type NewSessionAgentType = 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy' | 'cursor' | 'rig'; export type NewSessionSessionType = 'simple' | 'worktree'; export interface NewSessionDraft { @@ -162,7 +162,7 @@ export function loadNewSessionDraft(): NewSessionDraft | null { const input = typeof parsed.input === 'string' ? parsed.input : ''; const selectedMachineId = typeof parsed.selectedMachineId === 'string' ? parsed.selectedMachineId : null; const selectedPath = typeof parsed.selectedPath === 'string' ? parsed.selectedPath : null; - const agentType: NewSessionAgentType = parsed.agentType === 'codex' || parsed.agentType === 'gemini' || parsed.agentType === 'openclaw' || parsed.agentType === 'agy' || parsed.agentType === 'rig' + const agentType: NewSessionAgentType = parsed.agentType === 'codex' || parsed.agentType === 'gemini' || parsed.agentType === 'openclaw' || parsed.agentType === 'agy' || parsed.agentType === 'cursor' || parsed.agentType === 'rig' ? parsed.agentType : 'claude'; const permissionMode: PermissionModeKey | null = typeof parsed.permissionMode === 'string' diff --git a/packages/happy-app/sources/utils/avatarHarness.test.ts b/packages/happy-app/sources/utils/avatarHarness.test.ts index ce5ecb3936..51476ded30 100644 --- a/packages/happy-app/sources/utils/avatarHarness.test.ts +++ b/packages/happy-app/sources/utils/avatarHarness.test.ts @@ -2,9 +2,10 @@ import { describe, expect, it } from 'vitest'; import { resolveAvatarHarness } from './avatarHarness'; describe('resolveAvatarHarness', () => { - it('keeps the existing Claude, Codex, and Antigravity mappings', () => { + it('keeps the existing Claude, Codex, Cursor, and Antigravity mappings', () => { expect(resolveAvatarHarness('claude')).toBe('claude'); expect(resolveAvatarHarness('codex')).toBe('codex'); + expect(resolveAvatarHarness('cursor')).toBe('cursor'); expect(resolveAvatarHarness('agy')).toBe('agy'); }); diff --git a/packages/happy-app/sources/utils/avatarHarness.ts b/packages/happy-app/sources/utils/avatarHarness.ts index 4dbd58480d..b1317f894d 100644 --- a/packages/happy-app/sources/utils/avatarHarness.ts +++ b/packages/happy-app/sources/utils/avatarHarness.ts @@ -6,12 +6,13 @@ * metadata. Only the active harnesses have badges; retired and unknown * flavors stay badge-free. */ -export type AvatarHarnessIcon = 'claude' | 'codex' | 'agy' | 'rig'; +export type AvatarHarnessIcon = 'claude' | 'codex' | 'agy' | 'cursor' | 'rig'; const ACTIVE_HARNESS_ICONS: ReadonlySet = new Set([ 'claude', 'codex', 'agy', + 'cursor', ]); export function resolveAvatarHarness( diff --git a/packages/happy-app/sources/utils/harnessCatalog.test.ts b/packages/happy-app/sources/utils/harnessCatalog.test.ts index 586c3298af..e4cbbf4094 100644 --- a/packages/happy-app/sources/utils/harnessCatalog.test.ts +++ b/packages/happy-app/sources/utils/harnessCatalog.test.ts @@ -3,8 +3,9 @@ import { describe, expect, it } from 'vitest'; import { HARNESS_NAMES, isRetiredHarness, listAvailableHarnesses } from './harnessCatalog'; describe('harness catalog', () => { - it('names Happy and Antigravity by product, not by CLI id', () => { + it('names Happy, Cursor, and Antigravity by product, not by CLI id', () => { expect(HARNESS_NAMES.rig).toBe('Happy'); + expect(HARNESS_NAMES.cursor).toBe('Cursor'); expect(HARNESS_NAMES.agy).toBe('Antigravity'); }); @@ -12,21 +13,23 @@ describe('harness catalog', () => { expect(isRetiredHarness('gemini')).toBe(true); expect(isRetiredHarness('openclaw')).toBe(true); expect(isRetiredHarness('claude')).toBe(false); + expect(isRetiredHarness('cursor')).toBe(false); // Antigravity is what Gemini's own error message redirects people to. expect(isRetiredHarness('agy')).toBe(false); }); it('lists only installed harnesses, in pick order', () => { const harnesses = listAvailableHarnesses({ - availability: { claude: true, codex: true, agy: true }, + availability: { claude: true, codex: true, cursor: true, agy: true }, happyAgentAvailable: true, selected: 'claude', }); - expect(harnesses.map((harness) => harness.key)).toEqual(['claude', 'codex', 'agy', 'rig']); + expect(harnesses.map((harness) => harness.key)).toEqual(['claude', 'codex', 'cursor', 'agy', 'rig']); expect(harnesses.map((harness) => harness.name)).toEqual([ 'Claude Code', 'Codex', + 'Cursor', 'Antigravity', 'Happy', ]); @@ -90,7 +93,7 @@ describe('harness catalog', () => { availability: null, happyAgentAvailable: false, selected: 'agy', - }).map((harness) => harness.key)).toEqual(['claude', 'codex']); + }).map((harness) => harness.key)).toEqual(['claude', 'codex', 'cursor']); }); it('falls back to the whole catalog when a machine reports no capabilities', () => { @@ -98,12 +101,12 @@ describe('harness catalog', () => { availability: null, happyAgentAvailable: false, selected: null, - }).map((harness) => harness.key)).toEqual(['claude', 'codex']); + }).map((harness) => harness.key)).toEqual(['claude', 'codex', 'cursor']); expect(listAvailableHarnesses({ availability: {}, happyAgentAvailable: false, selected: null, - }).map((harness) => harness.key)).toEqual(['claude', 'codex', 'rig']); + }).map((harness) => harness.key)).toEqual(['claude', 'codex', 'cursor', 'rig']); }); }); diff --git a/packages/happy-app/sources/utils/harnessCatalog.ts b/packages/happy-app/sources/utils/harnessCatalog.ts index 18f4d21025..7492149c84 100644 --- a/packages/happy-app/sources/utils/harnessCatalog.ts +++ b/packages/happy-app/sources/utils/harnessCatalog.ts @@ -8,6 +8,7 @@ import type { NewSessionAgentType } from '@/sync/persistence'; export const HARNESS_NAMES: Record = { claude: 'Claude Code', codex: 'Codex', + cursor: 'Cursor', rig: 'Happy', agy: 'Antigravity', gemini: 'Gemini', @@ -32,6 +33,7 @@ export const RETIRED_HARNESSES: ReadonlySet = new Set([ export const HARNESS_ORDER: readonly NewSessionAgentType[] = [ 'claude', 'codex', + 'cursor', 'agy', 'rig', ]; diff --git a/packages/happy-cli/README.md b/packages/happy-cli/README.md index a511f8f374..f4956e16cc 100644 --- a/packages/happy-cli/README.md +++ b/packages/happy-cli/README.md @@ -32,6 +32,7 @@ This will: ``` happy codex +happy cursor # Cursor Agent CLI (via ACP) happy agy # Antigravity CLI (Gemini's successor) happy gemini # deprecated — use `happy agy` happy openclaw diff --git a/packages/happy-cli/src/agent/acp/acpAgentConfig.test.ts b/packages/happy-cli/src/agent/acp/acpAgentConfig.test.ts index ef296627a2..5f32c67103 100644 --- a/packages/happy-cli/src/agent/acp/acpAgentConfig.test.ts +++ b/packages/happy-cli/src/agent/acp/acpAgentConfig.test.ts @@ -2,9 +2,10 @@ import { describe, expect, it } from 'vitest'; import { KNOWN_ACP_AGENTS, resolveAcpAgentConfig } from './acpAgentConfig'; describe('KNOWN_ACP_AGENTS', () => { - it('defines built-in Gemini and OpenCode command mappings', () => { + it('defines built-in Gemini, Cursor, and OpenCode command mappings', () => { expect(KNOWN_ACP_AGENTS).toEqual({ gemini: { command: 'gemini', args: ['--experimental-acp'] }, + cursor: { command: 'agent', args: ['acp'] }, opencode: { command: 'opencode', args: ['acp'] }, }); }); @@ -17,6 +18,11 @@ describe('resolveAcpAgentConfig', () => { command: 'gemini', args: ['--experimental-acp'], }); + expect(resolveAcpAgentConfig(['cursor'])).toEqual({ + agentName: 'cursor', + command: 'agent', + args: ['acp'], + }); }); it('appends extra CLI args for known agent aliases', () => { diff --git a/packages/happy-cli/src/agent/acp/acpAgentConfig.ts b/packages/happy-cli/src/agent/acp/acpAgentConfig.ts index 1c3ee2cbc0..9d9475aaef 100644 --- a/packages/happy-cli/src/agent/acp/acpAgentConfig.ts +++ b/packages/happy-cli/src/agent/acp/acpAgentConfig.ts @@ -5,6 +5,7 @@ export type AcpAgentConfig = { export const KNOWN_ACP_AGENTS: Record = { gemini: { command: 'gemini', args: ['--experimental-acp'] }, + cursor: { command: 'agent', args: ['acp'] }, opencode: { command: 'opencode', args: ['acp'] }, }; diff --git a/packages/happy-cli/src/agent/acp/runAcp.ts b/packages/happy-cli/src/agent/acp/runAcp.ts index 9ba0b0c928..ac147e73f1 100644 --- a/packages/happy-cli/src/agent/acp/runAcp.ts +++ b/packages/happy-cli/src/agent/acp/runAcp.ts @@ -436,13 +436,16 @@ type PendingTurn = { timeout: NodeJS.Timeout; }; -function resolveSessionFlavor(agentName: string): 'gemini' | 'opencode' | 'acp' { +function resolveSessionFlavor(agentName: string): 'gemini' | 'opencode' | 'cursor' | 'acp' { if (agentName === 'gemini') { return 'gemini'; } if (agentName === 'opencode') { return 'opencode'; } + if (agentName === 'cursor') { + return 'cursor'; + } return 'acp'; } diff --git a/packages/happy-cli/src/agent/adapters/MessageAdapter.ts b/packages/happy-cli/src/agent/adapters/MessageAdapter.ts index 17928c2e86..fa5f2f4571 100644 --- a/packages/happy-cli/src/agent/adapters/MessageAdapter.ts +++ b/packages/happy-cli/src/agent/adapters/MessageAdapter.ts @@ -280,5 +280,6 @@ export const adapters = { gemini: new MessageAdapter({ agentType: 'gemini' }), codex: new MessageAdapter({ agentType: 'codex' }), claude: new MessageAdapter({ agentType: 'claude' }), + cursor: new MessageAdapter({ agentType: 'cursor' }), opencode: new MessageAdapter({ agentType: 'opencode' }), } as const; diff --git a/packages/happy-cli/src/agent/adapters/MobileMessageFormat.ts b/packages/happy-cli/src/agent/adapters/MobileMessageFormat.ts index b24ee04d4d..68b6f86d0a 100644 --- a/packages/happy-cli/src/agent/adapters/MobileMessageFormat.ts +++ b/packages/happy-cli/src/agent/adapters/MobileMessageFormat.ts @@ -11,7 +11,7 @@ /** * Supported agent types for the mobile app */ -export type MobileAgentType = 'gemini' | 'codex' | 'claude' | 'opencode'; +export type MobileAgentType = 'gemini' | 'codex' | 'claude' | 'cursor' | 'opencode'; /** * Message roles for the mobile app diff --git a/packages/happy-cli/src/agent/core/AgentBackend.ts b/packages/happy-cli/src/agent/core/AgentBackend.ts index cec9b73f44..4799ee5e58 100644 --- a/packages/happy-cli/src/agent/core/AgentBackend.ts +++ b/packages/happy-cli/src/agent/core/AgentBackend.ts @@ -48,7 +48,7 @@ export interface McpServerConfig { export type AgentTransport = 'native-claude' | 'mcp-codex' | 'acp'; /** Agent identifier */ -export type AgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'agy' | 'claude-acp' | 'codex-acp'; +export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'agy' | 'claude-acp' | 'codex-acp'; /** * Configuration for creating an agent backend diff --git a/packages/happy-cli/src/api/apiSession.ts b/packages/happy-cli/src/api/apiSession.ts index d5394bc35d..26b6bb9a66 100644 --- a/packages/happy-cli/src/api/apiSession.ts +++ b/packages/happy-cli/src/api/apiSession.ts @@ -47,7 +47,7 @@ export type ACPMessageData = // Usage/metrics | { type: 'token_count';[key: string]: unknown }; -export type ACPProvider = 'gemini' | 'codex' | 'claude' | 'opencode'; +export type ACPProvider = 'gemini' | 'codex' | 'claude' | 'cursor' | 'opencode'; type V3SessionMessage = { id: string; @@ -823,7 +823,7 @@ export class ApiSessionClient extends EventEmitter { * @param provider - The agent provider sending the message (e.g., 'gemini', 'codex', 'claude') * @param body - The message payload (type: 'message' | 'reasoning' | 'tool-call' | 'tool-result') */ - sendAgentMessage(provider: 'gemini' | 'codex' | 'claude' | 'opencode' | 'openclaw', body: ACPMessageData) { + sendAgentMessage(provider: 'gemini' | 'codex' | 'claude' | 'cursor' | 'opencode' | 'openclaw', body: ACPMessageData) { let content = { role: 'agent', content: { diff --git a/packages/happy-cli/src/daemon/run.ts b/packages/happy-cli/src/daemon/run.ts index 92f167bd73..1192fdaefa 100644 --- a/packages/happy-cli/src/daemon/run.ts +++ b/packages/happy-cli/src/daemon/run.ts @@ -421,8 +421,16 @@ export async function startDaemon(): Promise { // Construct command for the CLI const cliPath = join(projectPath(), 'dist', 'index.mjs'); - // Determine agent command - support claude, codex, gemini, openclaw, and agy - const agent = options.agent === 'gemini' ? 'gemini' : (options.agent === 'codex' ? 'codex' : (options.agent === 'openclaw' ? 'openclaw' : (options.agent === 'agy' ? 'agy' : 'claude'))); + // Determine agent command - support claude, codex, gemini, openclaw, agy, and cursor + const agent = options.agent === 'gemini' + ? 'gemini' + : (options.agent === 'codex' + ? 'codex' + : (options.agent === 'openclaw' + ? 'openclaw' + : (options.agent === 'agy' + ? 'agy' + : (options.agent === 'cursor' ? 'cursor' : 'claude')))); const resumeId = agent === 'claude' ? options.resumeClaudeSessionId : (agent === 'codex' ? options.resumeCodexThreadId : undefined); @@ -536,6 +544,9 @@ export async function startDaemon(): Promise { case 'agy': agentCommand = 'agy'; break; + case 'cursor': + agentCommand = 'cursor'; + break; default: return { type: 'error', diff --git a/packages/happy-cli/src/index.ts b/packages/happy-cli/src/index.ts index 114a0de18f..b80a1df974 100644 --- a/packages/happy-cli/src/index.ts +++ b/packages/happy-cli/src/index.ts @@ -473,6 +473,39 @@ Conversation history is preserved on the server, but in-flight tool calls are in process.exit(1) } return; + } else if (subcommand === 'cursor') { + try { + const { runAcp } = await import('@/agent/acp/runAcp'); + + let startedBy: 'daemon' | 'terminal' | undefined = undefined; + let verbose = false; + for (let i = 1; i < args.length; i++) { + if (args[i] === '--started-by') { + startedBy = args[++i] as 'daemon' | 'terminal'; + } else if (args[i] === '--verbose') { + verbose = true; + } + } + + const { credentials } = await authAndSetupMachineIfNeeded(); + await ensureDaemonRunning(); + + await runAcp({ + credentials, + agentName: 'cursor', + command: 'agent', + args: ['acp'], + startedBy, + verbose, + }); + } catch (error) { + console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error'); + if (process.env.DEBUG) { + console.error(error); + } + process.exit(1); + } + return; } else if (subcommand === 'logout') { // Keep for backward compatibility - redirect to auth logout console.log(chalk.yellow('Note: "happy logout" is deprecated. Use "happy auth logout" instead.\n')); @@ -718,6 +751,7 @@ ${chalk.bold('Usage:')} happy codex Start Codex mode happy gemini Start Gemini mode (ACP) [deprecated — use agy] happy agy Start agy (Antigravity CLI) mode + happy cursor Start Cursor Agent CLI mode happy acp Start a generic ACP-compatible agent happy connect Connect AI vendor API keys happy sandbox Configure and manage OS-level sandboxing diff --git a/packages/happy-cli/src/modules/common/registerCommonHandlers.ts b/packages/happy-cli/src/modules/common/registerCommonHandlers.ts index 1921408d79..7826d2cc0d 100644 --- a/packages/happy-cli/src/modules/common/registerCommonHandlers.ts +++ b/packages/happy-cli/src/modules/common/registerCommonHandlers.ts @@ -120,7 +120,7 @@ export interface SpawnSessionOptions { directory: string; sessionId?: string; approvedNewDirectoryCreation?: boolean; - agent?: 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy'; + agent?: 'claude' | 'codex' | 'gemini' | 'openclaw' | 'agy' | 'cursor'; permissionMode?: string; modelMode?: string; effortLevel?: string; diff --git a/packages/happy-cli/src/utils/createSessionMetadata.ts b/packages/happy-cli/src/utils/createSessionMetadata.ts index aa6fb9e01b..f5db82005b 100644 --- a/packages/happy-cli/src/utils/createSessionMetadata.ts +++ b/packages/happy-cli/src/utils/createSessionMetadata.ts @@ -20,7 +20,7 @@ import packageJson from '../../package.json'; /** * Backend flavor identifier for session metadata. */ -export type BackendFlavor = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'agy' | 'acp'; +export type BackendFlavor = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'agy' | 'acp'; /** * Options for creating session metadata. diff --git a/packages/happy-cli/src/utils/detectCLI.test.ts b/packages/happy-cli/src/utils/detectCLI.test.ts index e7199ec28c..d65c68c2d2 100644 --- a/packages/happy-cli/src/utils/detectCLI.test.ts +++ b/packages/happy-cli/src/utils/detectCLI.test.ts @@ -41,4 +41,15 @@ describe('CLI availability detection', () => { expect(detectCLIAvailability().agy).toBe(true); }); + + it('detects Cursor CLI when agent binary is in PATH', () => { + expect(detectCLIAvailability().cursor).toBe(false); + + mockedExecSync.mockImplementation((cmd: string) => { + if (cmd.includes('agent')) return Buffer.from('/usr/local/bin/agent'); + throw new Error('not installed'); + }); + + expect(detectCLIAvailability().cursor).toBe(true); + }); }); \ No newline at end of file diff --git a/packages/happy-cli/src/utils/detectCLI.ts b/packages/happy-cli/src/utils/detectCLI.ts index 8c5ee7bacf..62a75b7826 100644 --- a/packages/happy-cli/src/utils/detectCLI.ts +++ b/packages/happy-cli/src/utils/detectCLI.ts @@ -8,6 +8,7 @@ export interface CLIAvailability { claude: boolean; codex: boolean; gemini: boolean; + cursor: boolean; openclaw: boolean; agy: boolean; detectedAt: number; @@ -39,6 +40,7 @@ function detectPosix(): CLIAvailability { const claude = commandExists('claude'); const codex = commandExists('codex'); const gemini = commandExists('gemini'); + const cursor = commandExists('agent') || commandExists('cursor'); const agy = findAgyBin() !== undefined; // OpenClaw: check command, config file, or env var @@ -47,7 +49,7 @@ function detectPosix(): CLIAvailability { const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL; const openclaw = openclawCommand || openclawConfig || openclawEnv; - return { claude, codex, gemini, openclaw, agy, detectedAt: Date.now() }; + return { claude, codex, gemini, cursor, openclaw, agy, detectedAt: Date.now() }; } function detectWindows(): CLIAvailability { @@ -63,6 +65,7 @@ function detectWindows(): CLIAvailability { const claude = checkCommand('claude'); const codex = checkCommand('codex'); const gemini = checkCommand('gemini'); + const cursor = checkCommand('agent') || checkCommand('cursor'); const agy = findAgyBin() !== undefined; // OpenClaw: check command, config file, or env var @@ -71,5 +74,5 @@ function detectWindows(): CLIAvailability { const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL; const openclaw = openclawCommand || openclawConfig || openclawEnv; - return { claude, codex, gemini, openclaw, agy, detectedAt: Date.now() }; + return { claude, codex, gemini, cursor, openclaw, agy, detectedAt: Date.now() }; } From 03c7b4202533085df56d57cfa9b9fe8cf857cf8e Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 31 Aug 2026 16:58:30 -0500 Subject: [PATCH 2/2] feat: add cursor to cliAvailability schemas and machine capability sync --- packages/happy-app/sources/sync/storageTypes.ts | 1 + packages/happy-cli/src/api/apiMachine.ts | 3 ++- packages/happy-cli/src/api/types.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/happy-app/sources/sync/storageTypes.ts b/packages/happy-app/sources/sync/storageTypes.ts index e820ef218a..3e8bd70a70 100644 --- a/packages/happy-app/sources/sync/storageTypes.ts +++ b/packages/happy-app/sources/sync/storageTypes.ts @@ -453,6 +453,7 @@ export const MachineMetadataSchema = z.object({ gemini: z.boolean(), openclaw: z.boolean(), agy: z.boolean().optional(), // optional: older CLIs don't report agy + cursor: z.boolean().optional(), rig: z.boolean().optional(), // Rig runs its own Happy-connected daemon detectedAt: z.number(), }).optional(), diff --git a/packages/happy-cli/src/api/apiMachine.ts b/packages/happy-cli/src/api/apiMachine.ts index 91af7861bc..c4d13ff83f 100644 --- a/packages/happy-cli/src/api/apiMachine.ts +++ b/packages/happy-cli/src/api/apiMachine.ts @@ -531,7 +531,8 @@ export class ApiMachineClient { || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw - || prev.agy !== newAvailability.agy; + || prev.agy !== newAvailability.agy + || prev.cursor !== newAvailability.cursor; const resumeSupportChanged = !prevResume || prevResume.rpcAvailable !== newResumeSupport.rpcAvailable || prevResume.happyAgentAuthenticated !== newResumeSupport.happyAgentAuthenticated; diff --git a/packages/happy-cli/src/api/types.ts b/packages/happy-cli/src/api/types.ts index d1ae6169ff..f990c5a9ec 100644 --- a/packages/happy-cli/src/api/types.ts +++ b/packages/happy-cli/src/api/types.ts @@ -148,6 +148,7 @@ export const MachineMetadataSchema = z.object({ // Optional so metadata written by a CLI predating agy detection still // matches this shape. detectCLIAvailability always reports it. agy: z.boolean().optional(), + cursor: z.boolean().optional(), detectedAt: z.number(), }).optional(), resumeSupport: z.object({