@gakim-digital/dexter-bridge 0.11.17 → 0.11.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/package.json +1 -1
- package/src/agent.js +112 -31
- package/src/cli.js +42 -13
- package/src/config.js +64 -3
- package/src/framerAgentTools.js +171 -76
- package/src/framerBehaviorContract.js +15 -1
- package/src/googleFontsFallback.js +196 -0
- package/src/protocol.js +90 -68
- package/src/providers/codexAppServer.js +46 -1
- package/src/providers/cursorAcp.js +456 -0
- package/src/providers/index.js +12 -6
- package/src/runtimeProfiles.js +40 -6
package/README.md
CHANGED
|
@@ -166,11 +166,18 @@ for support and automation:
|
|
|
166
166
|
npx @gakim-digital/dexter-bridge pair 123456 --api http://localhost:3800/iwm-api/0.0.1
|
|
167
167
|
npx @gakim-digital/dexter-bridge start --agent claude-code
|
|
168
168
|
npx @gakim-digital/dexter-bridge start --agent codex
|
|
169
|
+
npx @gakim-digital/dexter-bridge start --agent cursor
|
|
169
170
|
npx @gakim-digital/dexter-bridge status
|
|
170
171
|
npx @gakim-digital/dexter-bridge doctor
|
|
171
172
|
npx @gakim-digital/dexter-bridge logout
|
|
172
173
|
```
|
|
173
174
|
|
|
175
|
+
Cursor needs the Cursor CLI signed in on the same computer
|
|
176
|
+
(`curl https://cursor.com/install -fsS | bash`, then `cursor-agent login`).
|
|
177
|
+
The bridge runs `cursor-agent acp` with Cursor's shell, file, and web tools
|
|
178
|
+
denied, so Cursor can use only Dexter's Framer tools. Set
|
|
179
|
+
`DEXTER_BRIDGE_CURSOR_BIN` if `cursor-agent` is installed elsewhere.
|
|
180
|
+
|
|
174
181
|
On Windows, the bridge checks the native Claude Code install at
|
|
175
182
|
`%USERPROFILE%\.local\bin\claude.exe` in addition to `PATH`. Before pairing,
|
|
176
183
|
verify the same PowerShell window can run:
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
} from './config.js';
|
|
28
29
|
import {
|
|
29
30
|
assertOutcomeResponseContract,
|
|
31
|
+
buildFramerOutcomePrompt,
|
|
30
32
|
buildOutcomePrompt,
|
|
31
33
|
buildModelTurnDeltaPrompt,
|
|
32
34
|
buildModelTurnFallbackPrompt,
|
|
@@ -172,6 +174,15 @@ export const AGENT_DEFINITIONS = {
|
|
|
172
174
|
model: 'codex:gpt-5.5',
|
|
173
175
|
statusStage: 'codex_start',
|
|
174
176
|
},
|
|
177
|
+
cursor: {
|
|
178
|
+
id: 'cursor',
|
|
179
|
+
label: 'Cursor',
|
|
180
|
+
commandEnv: 'DEXTER_BRIDGE_CURSOR_BIN',
|
|
181
|
+
fallbackCommand: 'cursor-agent',
|
|
182
|
+
promptMode: 'stdin',
|
|
183
|
+
model: 'cursor:grok-4.7',
|
|
184
|
+
statusStage: 'cursor_start',
|
|
185
|
+
},
|
|
175
186
|
};
|
|
176
187
|
|
|
177
188
|
export const AGENT_AUTHENTICATION_REQUIRED_CODE = 'DEXTER_AGENT_AUTHENTICATION_REQUIRED';
|
|
@@ -328,6 +339,23 @@ function claudeModelEngineSystemPrompt(product) {
|
|
|
328
339
|
].join(' ');
|
|
329
340
|
}
|
|
330
341
|
|
|
342
|
+
// Content-addressed, so identical instructions reuse one file and stay
|
|
343
|
+
// byte-identical for Claude Code's prompt cache.
|
|
344
|
+
function writeClaudeSystemPromptFile(content) {
|
|
345
|
+
const directory = path.join(os.tmpdir(), 'instawebai-outcome-prompts');
|
|
346
|
+
const file = path.join(
|
|
347
|
+
directory,
|
|
348
|
+
`${crypto.createHash('sha256').update(content).digest('hex').slice(0, 32)}.md`,
|
|
349
|
+
);
|
|
350
|
+
if (!fs.existsSync(file)) {
|
|
351
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
352
|
+
const partial = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
353
|
+
fs.writeFileSync(partial, content, { mode: 0o600 });
|
|
354
|
+
fs.renameSync(partial, file);
|
|
355
|
+
}
|
|
356
|
+
return file;
|
|
357
|
+
}
|
|
358
|
+
|
|
331
359
|
function claudeOutcomeSystemPrompt(product) {
|
|
332
360
|
const name = normalizeBridgeProduct(product).name;
|
|
333
361
|
if (normalizeBridgeProduct(product).id === 'dexter') {
|
|
@@ -337,7 +365,6 @@ function claudeOutcomeSystemPrompt(product) {
|
|
|
337
365
|
'Inspect the project, perform the requested work, verify the result, and return the structured outcome.',
|
|
338
366
|
'Do not access local credentials, unrelated files, other projects, account settings, or billing.',
|
|
339
367
|
'Do not publish unless the assignment explicitly authorizes publishing.',
|
|
340
|
-
'Call progress_update before the first inspection or edit and at meaningful phase changes. Use one or two natural first-person sentences that name the specific page area, component, or behavior and the concrete change you are about to make or are making now; optionally say what you will check next. Avoid generic updates such as "working on the next step", "thinking", "loading", or "making changes".',
|
|
341
368
|
'If framer_write is rejected by the safety boundary, do not retry JavaScript variants. Switch to framer_apply_changes, inspect the relevant state, or report the concrete blocker.',
|
|
342
369
|
`Return only the structured outcome result requested by ${name}.`,
|
|
343
370
|
].join(' ');
|
|
@@ -510,13 +537,18 @@ function argsWithClaudeModelEngine(args, definition, options = {}, env = process
|
|
|
510
537
|
let isolated = argsWithoutFlagValue(args, '--system-prompt');
|
|
511
538
|
isolated = argsWithoutFlagValue(isolated, '--append-system-prompt');
|
|
512
539
|
isolated = argsWithoutFlagValue(isolated, '--agent');
|
|
540
|
+
isolated = argsWithoutFlagValue(isolated, '--system-prompt-file');
|
|
513
541
|
isolated = argsWithoutFlagValue(isolated, '--effort');
|
|
514
542
|
return [
|
|
515
543
|
...isolated,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
544
|
+
...(options.harnessMode && options.systemPromptFile
|
|
545
|
+
? ['--system-prompt-file', options.systemPromptFile]
|
|
546
|
+
: [
|
|
547
|
+
'--system-prompt',
|
|
548
|
+
options.harnessMode
|
|
549
|
+
? claudeOutcomeSystemPrompt(options.product)
|
|
550
|
+
: claudeModelEngineSystemPrompt(options.product),
|
|
551
|
+
]),
|
|
520
552
|
'--effort',
|
|
521
553
|
claudeEffortForStep(options.step, env),
|
|
522
554
|
];
|
|
@@ -864,7 +896,15 @@ export function processEnvWithCliPath(baseEnv = process.env, platform = process.
|
|
|
864
896
|
baseEnv.APPDATA ? pathApi.join(baseEnv.APPDATA, 'npm') : '',
|
|
865
897
|
baseEnv.LOCALAPPDATA ? pathApi.join(baseEnv.LOCALAPPDATA, 'Programs') : '',
|
|
866
898
|
]
|
|
867
|
-
: [
|
|
899
|
+
: [
|
|
900
|
+
homeDirectory ? pathApi.join(homeDirectory, '.local', 'bin') : '',
|
|
901
|
+
'/opt/homebrew/bin',
|
|
902
|
+
'/usr/local/bin',
|
|
903
|
+
'/usr/bin',
|
|
904
|
+
'/bin',
|
|
905
|
+
'/usr/sbin',
|
|
906
|
+
'/sbin',
|
|
907
|
+
];
|
|
868
908
|
const shellEnv = platform === 'win32' ? {} : loginShellEnv();
|
|
869
909
|
const existingPath = String(baseEnv.PATH || '');
|
|
870
910
|
const mergedPath = mergePathEntries([shellEnv.PATH, existingPath, ...fallbackPath], platform);
|
|
@@ -1393,6 +1433,7 @@ function definitionForRuntimeProfile(profile, fallbackAgent) {
|
|
|
1393
1433
|
if (!profile) return definitionForAgent(fallbackAgent);
|
|
1394
1434
|
if (profile.driver === 'claude-code-cli') return AGENT_DEFINITIONS['claude-code'];
|
|
1395
1435
|
if (profile.driver === 'codex-app-server') return AGENT_DEFINITIONS.codex;
|
|
1436
|
+
if (profile.driver === 'cursor-acp') return AGENT_DEFINITIONS.cursor;
|
|
1396
1437
|
return {
|
|
1397
1438
|
id: profile.id || profile.driver,
|
|
1398
1439
|
label: profile.label || profile.driver || 'Local runtime',
|
|
@@ -2314,7 +2355,15 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2314
2355
|
1_000,
|
|
2315
2356
|
),
|
|
2316
2357
|
);
|
|
2317
|
-
const
|
|
2358
|
+
const framerAgentOutcome =
|
|
2359
|
+
run?.outcome?.kind === 'framer-project'
|
|
2360
|
+
|| run?.outcome?.context?.executionMode === 'framer-agent';
|
|
2361
|
+
// Every Dexter message starts a fresh provider session. The assignment
|
|
2362
|
+
// already carries the recent conversation, and resuming would make every
|
|
2363
|
+
// turn of this run re-read the whole previous run as well.
|
|
2364
|
+
const sessionKey = framerAgentOutcome
|
|
2365
|
+
? run?.runId || companionSessionKey(run)
|
|
2366
|
+
: companionSessionKey(run);
|
|
2318
2367
|
const infrastructureAbortController = new AbortController();
|
|
2319
2368
|
const controlMonitorAbort = new AbortController();
|
|
2320
2369
|
let finished = false;
|
|
@@ -2462,9 +2511,15 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2462
2511
|
Boolean(event.message || event.name) ||
|
|
2463
2512
|
event.kind === 'turn_started';
|
|
2464
2513
|
if (!meaningful) return;
|
|
2514
|
+
// Framer runs narrate in plain text rather than through a progress tool
|
|
2515
|
+
// (a tool call costs a whole turn), so that text is the user's progress.
|
|
2516
|
+
const narration =
|
|
2517
|
+
framerAgentOutcome
|
|
2518
|
+
&& event.kind === 'output_delta'
|
|
2519
|
+
&& String(event.activity || '').startsWith('assistant:');
|
|
2465
2520
|
const userFacingUpdate =
|
|
2466
|
-
event.kind === 'status_update'
|
|
2467
|
-
|
|
2521
|
+
(event.kind === 'status_update' && event.source === 'harness')
|
|
2522
|
+
|| narration;
|
|
2468
2523
|
void postActivity({
|
|
2469
2524
|
stage: 'outcome',
|
|
2470
2525
|
outcomeId: run?.outcome?.outcomeId,
|
|
@@ -2474,9 +2529,10 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2474
2529
|
provider: definition.id,
|
|
2475
2530
|
tool: event.name || undefined,
|
|
2476
2531
|
toolPhase: event.phase || undefined,
|
|
2477
|
-
activity:
|
|
2478
|
-
event.activity
|
|
2479
|
-
|
|
2532
|
+
activity: narration
|
|
2533
|
+
? event.activity.replace(/^assistant:/, 'assistant-progress:')
|
|
2534
|
+
: event.activity ||
|
|
2535
|
+
(userFacingUpdate ? `assistant-progress:${Date.now()}` : undefined),
|
|
2480
2536
|
paths: event.paths || undefined,
|
|
2481
2537
|
tokenUsage: event.tokenUsage || undefined,
|
|
2482
2538
|
occurredAt: event.occurredAt || new Date().toISOString(),
|
|
@@ -2522,18 +2578,16 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2522
2578
|
}
|
|
2523
2579
|
};
|
|
2524
2580
|
const controlMonitor = monitorControl();
|
|
2525
|
-
const rememberedSession =
|
|
2526
|
-
|
|
2527
|
-
(
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
run?.outcome?.kind === 'framer-project'
|
|
2536
|
-
|| run?.outcome?.context?.executionMode === 'framer-agent';
|
|
2581
|
+
const rememberedSession = framerAgentOutcome
|
|
2582
|
+
? null
|
|
2583
|
+
: rememberedCompanionSession(sessionKey) ||
|
|
2584
|
+
(run?.outcome?.session?.providerSessionId
|
|
2585
|
+
? {
|
|
2586
|
+
providerSessionId: run.outcome.session.providerSessionId,
|
|
2587
|
+
agent: definition.id,
|
|
2588
|
+
updatedAt: Date.now(),
|
|
2589
|
+
}
|
|
2590
|
+
: null);
|
|
2537
2591
|
const materialized = materializeOutcomeWorkspace(run?.outcome, {
|
|
2538
2592
|
sessionId: run?.outcome?.context?.harnessSessionId || sessionKey,
|
|
2539
2593
|
});
|
|
@@ -2677,11 +2731,34 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2677
2731
|
cancellation,
|
|
2678
2732
|
])
|
|
2679
2733
|
: null;
|
|
2680
|
-
|
|
2734
|
+
const promptContext = {
|
|
2681
2735
|
doctrine: run?.doctrine,
|
|
2682
2736
|
doctrineVersion: run?.doctrineVersion,
|
|
2683
2737
|
framerGuidance: framerGuidance?.content,
|
|
2684
|
-
|
|
2738
|
+
projectInventory: framerGuidance?.inventory,
|
|
2739
|
+
};
|
|
2740
|
+
// Claude Code gets the run-independent instructions as its system prompt,
|
|
2741
|
+
// which it caches across sessions: later runs and follow-up messages read
|
|
2742
|
+
// them from the cache instead of writing them again.
|
|
2743
|
+
let systemPromptFile = null;
|
|
2744
|
+
let systemPromptBytes = 0;
|
|
2745
|
+
if (
|
|
2746
|
+
framerAgentOutcome
|
|
2747
|
+
&& definition.id === 'claude-code'
|
|
2748
|
+
&& !options.providerAdapter?.runOutcome
|
|
2749
|
+
) {
|
|
2750
|
+
const { instructions, task } = buildFramerOutcomePrompt(
|
|
2751
|
+
executionOutcome,
|
|
2752
|
+
product,
|
|
2753
|
+
promptContext,
|
|
2754
|
+
);
|
|
2755
|
+
const systemPrompt = `${claudeOutcomeSystemPrompt(product)}\n\n${instructions}`;
|
|
2756
|
+
systemPromptFile = writeClaudeSystemPromptFile(systemPrompt);
|
|
2757
|
+
systemPromptBytes = Buffer.byteLength(systemPrompt, 'utf8');
|
|
2758
|
+
prompt = task;
|
|
2759
|
+
} else {
|
|
2760
|
+
prompt = buildOutcomePrompt(executionOutcome, product, promptContext);
|
|
2761
|
+
}
|
|
2685
2762
|
const unresolvedTargets = preflight?.unresolvedTargets || [];
|
|
2686
2763
|
if (unresolvedTargets.length > 0) {
|
|
2687
2764
|
prompt = [
|
|
@@ -2696,7 +2773,7 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2696
2773
|
harnessTools.configurePromptContext?.({
|
|
2697
2774
|
doctrineLoaded: Boolean(run?.doctrine),
|
|
2698
2775
|
doctrineVersion: run?.doctrineVersion,
|
|
2699
|
-
promptBytes: Buffer.byteLength(prompt, 'utf8'),
|
|
2776
|
+
promptBytes: systemPromptBytes + Buffer.byteLength(prompt, 'utf8'),
|
|
2700
2777
|
});
|
|
2701
2778
|
const gateway =
|
|
2702
2779
|
definition.id === 'codex' ? null : await harnessTools.startGateway();
|
|
@@ -2798,6 +2875,7 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2798
2875
|
maxDurationMs: outcomeMaxDurationMs,
|
|
2799
2876
|
cwd: materialized.directory,
|
|
2800
2877
|
harnessMode: true,
|
|
2878
|
+
systemPromptFile,
|
|
2801
2879
|
framerHarness: framerAgentOutcome ? executionOutcome : false,
|
|
2802
2880
|
harnessToolNames: harnessTools.toolNames,
|
|
2803
2881
|
nativeSkillsEnabled: Boolean(materializedNativeSkills),
|
|
@@ -2821,6 +2899,7 @@ async function executeOutcomeRun(run, send, agent, options = {}) {
|
|
|
2821
2899
|
maxDurationMs: outcomeMaxDurationMs,
|
|
2822
2900
|
cwd: materialized.directory,
|
|
2823
2901
|
harnessMode: true,
|
|
2902
|
+
systemPromptFile,
|
|
2824
2903
|
framerHarness: framerAgentOutcome ? executionOutcome : false,
|
|
2825
2904
|
harnessToolNames: harnessTools.toolNames,
|
|
2826
2905
|
nativeSkillsEnabled: Boolean(materializedNativeSkills),
|
|
@@ -3083,7 +3162,8 @@ export async function executeRun(
|
|
|
3083
3162
|
});
|
|
3084
3163
|
const runtimeProfile = run?.runtimeProfile || suppliedRuntimeProfile || null;
|
|
3085
3164
|
const normalizedAgent = normalizeAgentName(
|
|
3086
|
-
run?.companion?.agent
|
|
3165
|
+
run?.companion?.agent
|
|
3166
|
+
|| ({ 'claude-code-cli': 'claude-code', 'cursor-acp': 'cursor' }[runtimeProfile?.driver] || agent),
|
|
3087
3167
|
);
|
|
3088
3168
|
const runModel = runtimeProfile?.model?.id || run?.companion?.model?.id || run?.model || selectedModel;
|
|
3089
3169
|
const usageAccumulator = createCompanionUsageAccumulator(normalizedAgent);
|
|
@@ -3274,10 +3354,10 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3274
3354
|
modelCatalogRefreshedAt,
|
|
3275
3355
|
};
|
|
3276
3356
|
}
|
|
3277
|
-
if (normalizedAgent === 'codex') {
|
|
3357
|
+
if (normalizedAgent === 'codex' || normalizedAgent === 'cursor') {
|
|
3278
3358
|
const adapter =
|
|
3279
3359
|
options.providerAdapter
|
|
3280
|
-
|| createLocalAgentAdapter(
|
|
3360
|
+
|| createLocalAgentAdapter(normalizedAgent, {
|
|
3281
3361
|
env: options.env,
|
|
3282
3362
|
});
|
|
3283
3363
|
try {
|
|
@@ -3297,7 +3377,7 @@ export async function checkAgentAvailability(agent, model, options = {}) {
|
|
|
3297
3377
|
code: detection.installed === false ? 'DEXTER_AGENT_NOT_FOUND' : AGENT_AUTHENTICATION_REQUIRED_CODE,
|
|
3298
3378
|
error:
|
|
3299
3379
|
detection.error
|
|
3300
|
-
||
|
|
3380
|
+
|| `${definition.label} is not signed in. Sign in on this computer, then run the bridge command again.`,
|
|
3301
3381
|
modelCatalogSource: 'fallback',
|
|
3302
3382
|
modelCatalogRefreshedAt,
|
|
3303
3383
|
};
|
|
@@ -3377,6 +3457,7 @@ export async function checkAllAgents(options = {}) {
|
|
|
3377
3457
|
const agents = await Promise.all([
|
|
3378
3458
|
checkAgentAvailability('claude-code', undefined, options),
|
|
3379
3459
|
checkAgentAvailability('codex', undefined, options),
|
|
3460
|
+
checkAgentAvailability('cursor', undefined, options),
|
|
3380
3461
|
]);
|
|
3381
3462
|
return {
|
|
3382
3463
|
agents,
|
package/src/cli.js
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
resolveCompanionModelName,
|
|
17
17
|
saveConfigPatch,
|
|
18
18
|
} from './config.js';
|
|
19
|
-
import { checkAgentAvailability, checkAllAgents, executeRun } from './agent.js';
|
|
19
|
+
import { checkAgentAvailability, checkAllAgents, executeRun, processEnvWithCliPath } from './agent.js';
|
|
20
|
+
import { setUpCursorCli } from './providers/cursorAcp.js';
|
|
20
21
|
import {
|
|
21
22
|
claimPairing,
|
|
22
23
|
heartbeat,
|
|
@@ -55,11 +56,12 @@ function usage() {
|
|
|
55
56
|
'Environment:',
|
|
56
57
|
' DEXTER_API_URL API base URL, e.g. https://api.example.com/iwm-api/0.0.1',
|
|
57
58
|
' DEXTER_BRIDGE_RUNTIME codex, claude-code, or opencode',
|
|
58
|
-
' DEXTER_BRIDGE_AGENT claude-code, codex, opencode, or dry-run',
|
|
59
|
+
' DEXTER_BRIDGE_AGENT claude-code, codex, cursor, opencode, or dry-run',
|
|
59
60
|
' DEXTER_BRIDGE_MODEL Companion model, e.g. claude-code:sonnet or codex:gpt-5.5',
|
|
60
61
|
' DEXTER_BRIDGE_CLAUDE_BIN Claude CLI binary name/path',
|
|
61
62
|
' DEXTER_BRIDGE_CLAUDE_ARGS Claude CLI args, default: -p',
|
|
62
63
|
' DEXTER_BRIDGE_CODEX_BIN Codex CLI binary name/path',
|
|
64
|
+
' DEXTER_BRIDGE_CURSOR_BIN Cursor CLI binary name/path, default: cursor-agent',
|
|
63
65
|
' DEXTER_BRIDGE_CODEX_MAX_THREADS Maximum retained App Server threads, default: 32',
|
|
64
66
|
' DEXTER_BRIDGE_LOG_DIR Log directory, default: ~/.dexter-bridge/logs',
|
|
65
67
|
].join('\n');
|
|
@@ -134,6 +136,28 @@ function requireDeviceToken(config) {
|
|
|
134
136
|
return config.deviceToken;
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
async function confirmPrompt(question) {
|
|
140
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
141
|
+
try {
|
|
142
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
143
|
+
return answer === '' || answer === 'y' || answer === 'yes';
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
} finally {
|
|
147
|
+
rl.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// One `connect --agent cursor` command installs and signs in to the Cursor CLI
|
|
152
|
+
// when needed, so the Cursor setup has no separate steps.
|
|
153
|
+
async function prepareRequestedAgent(flags, config) {
|
|
154
|
+
if (normalizeAgentName(flags.agent || config.agent) !== 'cursor') return;
|
|
155
|
+
await setUpCursorCli({
|
|
156
|
+
env: processEnvWithCliPath(agentEnvironment(config)),
|
|
157
|
+
confirm: process.stdin.isTTY ? confirmPrompt : null,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
137
161
|
async function promptForPairingCode() {
|
|
138
162
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
139
163
|
try {
|
|
@@ -309,6 +333,9 @@ function agentEnvironment(config = {}, baseEnv = process.env) {
|
|
|
309
333
|
if (!env.DEXTER_BRIDGE_CODEX_BIN && commands.codex) {
|
|
310
334
|
env.DEXTER_BRIDGE_CODEX_BIN = commands.codex;
|
|
311
335
|
}
|
|
336
|
+
if (!env.DEXTER_BRIDGE_CURSOR_BIN && commands.cursor) {
|
|
337
|
+
env.DEXTER_BRIDGE_CURSOR_BIN = commands.cursor;
|
|
338
|
+
}
|
|
312
339
|
return env;
|
|
313
340
|
}
|
|
314
341
|
|
|
@@ -316,11 +343,11 @@ function modelCatalogStatus(runtimeProfiles = []) {
|
|
|
316
343
|
return Object.fromEntries(
|
|
317
344
|
runtimeProfiles.flatMap((profile) => {
|
|
318
345
|
const agent =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
346
|
+
{
|
|
347
|
+
'codex-app-server': 'codex',
|
|
348
|
+
'claude-code-cli': 'claude-code',
|
|
349
|
+
'cursor-acp': 'cursor',
|
|
350
|
+
}[profile.driver] || null;
|
|
324
351
|
return agent ? [[agent, profile.modelCatalog]] : [];
|
|
325
352
|
}),
|
|
326
353
|
);
|
|
@@ -466,7 +493,8 @@ function requireAvailableAgent(availability, agent, platform = process.platform)
|
|
|
466
493
|
const check = selectedAgentCheck(availability, agent);
|
|
467
494
|
if (check?.ok) return check;
|
|
468
495
|
|
|
469
|
-
const label =
|
|
496
|
+
const label = { 'claude-code': 'Claude Code', codex: 'Codex', cursor: 'Cursor' }[agent]
|
|
497
|
+
|| 'The selected agent';
|
|
470
498
|
let message = check?.error || `${label} is not installed or could not be started.`;
|
|
471
499
|
if (agent === 'claude-code' && check?.status === 'authentication_required') {
|
|
472
500
|
message = check.error || 'Claude Code is not signed in. Run `claude auth login`, then try again.';
|
|
@@ -778,11 +806,11 @@ async function startCommand({ apiBaseUrl, config, flags, configDir }) {
|
|
|
778
806
|
const runAgent = normalizeAgentName(
|
|
779
807
|
poll.run?.companion?.agent
|
|
780
808
|
|| (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
:
|
|
784
|
-
|
|
785
|
-
|
|
809
|
+
{
|
|
810
|
+
'claude-code-cli': 'claude-code',
|
|
811
|
+
'cursor-acp': 'cursor',
|
|
812
|
+
opencode: 'opencode',
|
|
813
|
+
}[runtimeProfile?.driver] || 'codex'
|
|
786
814
|
),
|
|
787
815
|
);
|
|
788
816
|
await executeClaimedRun({
|
|
@@ -865,6 +893,7 @@ export async function runCli(argv) {
|
|
|
865
893
|
// The one-command production path: claim the pairing code (when given or
|
|
866
894
|
// not yet paired), save config, and go straight into the polling loop.
|
|
867
895
|
case 'connect':
|
|
896
|
+
await prepareRequestedAgent(parsed.flags, config);
|
|
868
897
|
if (parsed.args[0]) {
|
|
869
898
|
await pairCommand({ apiBaseUrl, args: parsed.args, flags: parsed.flags, configDir });
|
|
870
899
|
}
|
package/src/config.js
CHANGED
|
@@ -31,6 +31,7 @@ export const BRIDGE_BUILD_FINGERPRINT = crypto
|
|
|
31
31
|
'./providers/directByok.js',
|
|
32
32
|
'./providers/openCode.js',
|
|
33
33
|
'./providers/acp.js',
|
|
34
|
+
'./providers/cursorAcp.js',
|
|
34
35
|
].map((relativePath) => {
|
|
35
36
|
const url = new URL(relativePath, import.meta.url);
|
|
36
37
|
return `${relativePath}\u0000${fs.readFileSync(url, 'utf8')}`;
|
|
@@ -52,6 +53,7 @@ export const BRIDGE_CAPABILITIES = [
|
|
|
52
53
|
'native-skills-v1',
|
|
53
54
|
'model-progress-v1',
|
|
54
55
|
'model-catalog-refresh-v1',
|
|
56
|
+
'cursor-acp-v1',
|
|
55
57
|
];
|
|
56
58
|
export const BRIDGE_PRODUCTS = {
|
|
57
59
|
dexter: {
|
|
@@ -69,7 +71,7 @@ export const BRIDGE_PRODUCTS = {
|
|
|
69
71
|
// user explicitly connects the locally installed CLI and chooses one of its
|
|
70
72
|
// reported models.
|
|
71
73
|
export const DEFAULT_BRIDGE_AGENT = 'codex';
|
|
72
|
-
export const BRIDGE_AGENTS = ['claude-code', 'codex', 'opencode', 'dry-run'];
|
|
74
|
+
export const BRIDGE_AGENTS = ['claude-code', 'codex', 'cursor', 'opencode', 'dry-run'];
|
|
73
75
|
export const COMPANION_MODEL_DEFINITIONS = [
|
|
74
76
|
{
|
|
75
77
|
id: 'claude-code:fable',
|
|
@@ -162,6 +164,63 @@ export const COMPANION_MODEL_DEFINITIONS = [
|
|
|
162
164
|
costTier: '$',
|
|
163
165
|
description: 'Low-latency Codex model for quick coding iterations.',
|
|
164
166
|
},
|
|
167
|
+
// Cursor lists every model it offers, whatever the user's plan; Dexter
|
|
168
|
+
// shows frontier models plus Composer. Paid Cursor plans include Grok and
|
|
169
|
+
// Composer; Claude, GPT and Gemini are billed from Cursor credits.
|
|
170
|
+
{
|
|
171
|
+
id: 'cursor:grok-4.7',
|
|
172
|
+
agent: 'cursor',
|
|
173
|
+
provider: 'cursor',
|
|
174
|
+
displayName: 'Grok 4.7',
|
|
175
|
+
invocationName: 'grok-4.7',
|
|
176
|
+
costTier: '$$$',
|
|
177
|
+
description: 'Grok 4.7, included in paid Cursor plans.',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'cursor:composer-2.5',
|
|
181
|
+
agent: 'cursor',
|
|
182
|
+
provider: 'cursor',
|
|
183
|
+
displayName: 'Composer 2.5',
|
|
184
|
+
invocationName: 'composer-2.5',
|
|
185
|
+
costTier: '$$',
|
|
186
|
+
description: "Cursor's fast model, included in paid Cursor plans.",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'cursor:claude-opus-5-5',
|
|
190
|
+
agent: 'cursor',
|
|
191
|
+
provider: 'cursor',
|
|
192
|
+
displayName: 'Claude Opus 5.5',
|
|
193
|
+
invocationName: 'claude-opus-5-5',
|
|
194
|
+
costTier: '$$$',
|
|
195
|
+
description: 'Claude Opus 5.5 via your Cursor plan.',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'cursor:claude-fable-5-1',
|
|
199
|
+
agent: 'cursor',
|
|
200
|
+
provider: 'cursor',
|
|
201
|
+
displayName: 'Claude Fable 5.1',
|
|
202
|
+
invocationName: 'claude-fable-5-1',
|
|
203
|
+
costTier: '$$$',
|
|
204
|
+
description: 'Claude Fable 5.1 via your Cursor plan.',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'cursor:gpt-5.6-sol',
|
|
208
|
+
agent: 'cursor',
|
|
209
|
+
provider: 'cursor',
|
|
210
|
+
displayName: 'GPT-5.6 Sol',
|
|
211
|
+
invocationName: 'gpt-5.6-sol',
|
|
212
|
+
costTier: '$$$',
|
|
213
|
+
description: 'GPT-5.6 Sol via your Cursor plan.',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'cursor:gemini-4-argon',
|
|
217
|
+
agent: 'cursor',
|
|
218
|
+
provider: 'cursor',
|
|
219
|
+
displayName: 'Gemini 4 Argon',
|
|
220
|
+
invocationName: 'gemini-4-argon',
|
|
221
|
+
costTier: '$$$',
|
|
222
|
+
description: 'Gemini 4 Argon via your Cursor plan.',
|
|
223
|
+
},
|
|
165
224
|
{
|
|
166
225
|
id: 'dry-run:default',
|
|
167
226
|
agent: 'dry-run',
|
|
@@ -176,6 +235,7 @@ export const COMPANION_MODEL_DEFINITIONS = [
|
|
|
176
235
|
const DEFAULT_MODEL_BY_AGENT = {
|
|
177
236
|
'claude-code': 'claude-code:sonnet',
|
|
178
237
|
codex: 'codex:gpt-5.5',
|
|
238
|
+
cursor: 'cursor:grok-4.7',
|
|
179
239
|
'dry-run': 'dry-run:default',
|
|
180
240
|
};
|
|
181
241
|
|
|
@@ -237,6 +297,7 @@ export function normalizeAgentName(value, fallback = DEFAULT_BRIDGE_AGENT) {
|
|
|
237
297
|
const raw = String(value || '').trim().toLowerCase();
|
|
238
298
|
if (raw === 'claude' || raw === 'claude_code' || raw === 'claude-code') return 'claude-code';
|
|
239
299
|
if (raw === 'openai-codex' || raw === 'openai_codex' || raw === 'codex') return 'codex';
|
|
300
|
+
if (raw === 'cursor' || raw === 'cursor-agent' || raw === 'cursor_agent') return 'cursor';
|
|
240
301
|
if (raw === 'open-code' || raw === 'open_code' || raw === 'opencode') return 'opencode';
|
|
241
302
|
if (raw === 'dryrun' || raw === 'dry_run' || raw === 'dry-run') return 'dry-run';
|
|
242
303
|
return BRIDGE_AGENTS.includes(fallback) ? fallback : DEFAULT_BRIDGE_AGENT;
|
|
@@ -269,11 +330,11 @@ export function companionModelDefinition(value, agent = DEFAULT_BRIDGE_AGENT) {
|
|
|
269
330
|
return {
|
|
270
331
|
id: `${normalizedAgent}:${invocationName}`,
|
|
271
332
|
agent: normalizedAgent,
|
|
272
|
-
provider: normalizedAgent === 'codex' ? 'openai' : normalizedAgent === 'claude-code' ? 'anthropic' : 'companion',
|
|
333
|
+
provider: normalizedAgent === 'codex' ? 'openai' : normalizedAgent === 'claude-code' ? 'anthropic' : normalizedAgent === 'cursor' ? 'cursor' : 'companion',
|
|
273
334
|
displayName: invocationName,
|
|
274
335
|
invocationName,
|
|
275
336
|
costTier: '$$',
|
|
276
|
-
description: `${
|
|
337
|
+
description: `${{ codex: 'Codex', cursor: 'Cursor' }[normalizedAgent] || 'Claude Code'} model reported by the connected bridge.`,
|
|
277
338
|
};
|
|
278
339
|
}
|
|
279
340
|
}
|