Skip to content

feat(cursor): support cursor-agent binary fallback#41

Open
erenaslandev wants to merge 114 commits intoyigitkonur:mainfrom
erenaslandev:feat/binary-fallback
Open

feat(cursor): support cursor-agent binary fallback#41
erenaslandev wants to merge 114 commits intoyigitkonur:mainfrom
erenaslandev:feat/binary-fallback

Conversation

@erenaslandev
Copy link
Copy Markdown

@erenaslandev erenaslandev commented Apr 14, 2026


Open with Devin

Review all of them with eye of John Carmack-like simplicity with elegeance approach and apply the one only if required

Greptile Summary

Adds cursor-agent as the preferred binary for Cursor AI with agent as a legacy fallback. The ToolAdapter interface gains an optional binaryFallbacks field, and resume.ts gains resolveToolBinaryName which tries candidates in order and returns the first available one. The fallback logic is clean, well-tested, and fits the registry-driven architecture correctly.

Confidence Score: 5/5

  • Safe to merge — the only finding is a cosmetic display mismatch when the legacy fallback binary is in use.
  • All remaining findings are P2. Runtime binary resolution is correct; nativeResume, crossToolResume, and getAvailableTools all use the new resolver. Tests cover the primary, fallback, and null cases. The resumeCommandDisplay string mismatch is a minor UX issue that does not affect actual command execution.
  • src/parsers/registry.ts — resumeCommandDisplay line 766

Important Files Changed

Filename Overview
src/utils/resume.ts Adds getToolBinaryCandidates, resolveToolBinaryName (exported, injectable), and requireToolBinaryName; integrates them into nativeResume, crossToolResume, and getAvailableTools cleanly. Logic is correct and properly uses ToolNotAvailableError.
src/parsers/registry.ts Adds optional binaryFallbacks field to ToolAdapter interface; changes cursor binaryName from 'agent' to 'cursor-agent' with 'agent' as fallback. resumeCommandDisplay hardcodes 'cursor-agent' regardless of which binary is resolved at runtime.
src/tests/resume-binary.test.ts New test file covering candidate ordering, primary preference, fallback, and null-return cases for resolveToolBinaryName. Full coverage of the new logic paths.
src/tests/forward-flags.test.ts Updates existing test to assert binaryName is now 'cursor-agent' and binaryFallbacks is ['agent']. Correct and minimal.

Sequence Diagram

sequenceDiagram
    participant C as CLI (resume/crossToolResume)
    participant R as requireToolBinaryName()
    participant RR as resolveToolBinaryName()
    participant GC as getToolBinaryCandidates()
    participant IA as isBinaryAvailable()

    C->>R: requireToolBinaryName('cursor')
    R->>RR: resolveToolBinaryName('cursor')
    RR->>GC: getToolBinaryCandidates('cursor')
    GC-->>RR: ['cursor-agent', 'agent']
    RR->>IA: isBinaryAvailable('cursor-agent')
    alt cursor-agent found
        IA-->>RR: true
        RR-->>R: 'cursor-agent'
    else cursor-agent missing
        IA-->>RR: false
        RR->>IA: isBinaryAvailable('agent')
        alt agent found
            IA-->>RR: true
            RR-->>R: 'agent'
        else neither found
            IA-->>RR: false
            RR-->>R: null
            R-->>C: throws ToolNotAvailableError
        end
    end
    R-->>C: resolved binaryName
    C->>C: runCommand(binaryName, args, cwd)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/parsers/registry.ts
Line: 766

Comment:
**`resumeCommandDisplay` out of sync with resolved binary**

This function always returns `cursor-agent --resume ${s.id}`, but at runtime `requireToolBinaryName` may resolve to the `agent` fallback instead. If a user only has the legacy `agent` binary installed, the TUI/help display shows a command they can't actually run. The runtime behavior is correct; the display is not.

```suggestion
  resumeCommandDisplay: (s) => `cursor-agent --resume ${s.id} (or: agent --resume ${s.id})`,
```

Or better: accept that the display reflects the preferred binary and add a note in parentheses, or make `getResumeCommand` in `resume.ts` accept the already-resolved `binaryName` as an argument so it can display what was actually used.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(cursor): support cursor-agent binar..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

yigitkonur and others added 30 commits February 19, 2026 08:01
Updated video source link and added badges.
Add full session parsing for Factory AI's Droid CLI (factory.ai).
Reads JSONL sessions from ~/.factory/sessions/ with companion
.settings.json for model/token metadata.

Parses Create/Read/Edit/Execute/Bash/LS tool calls, MCP tools,
thinking blocks, and todo_state events. Supports native resume
via `droid -s <id>` and cross-tool handoff in both directions.

All 7 integration points wired: types, parser, barrel export,
session index, resume commands, markdown labels, and CLI UI.
Add createDroidFixture() with realistic JSONL + settings.json data.
Add Droid-specific parser tests (message extraction, tool_use filtering,
session_start/todo_state skipping). Update all test files to include
droid in Record<SessionSource, ...> maps. Conversion paths: 5×4=20 → 6×5=30.
- Bump version 2.6.7 → 2.7.0
- Add postinstall message announcing Droid support
- Update README: add Droid to feature list, extraction table,
  storage table, quick-resume examples
- Create CHANGELOG.md with 2.7.0 release notes
- Update CLAUDE.md to reflect 6 supported tools
Add Cursor IDE as the 7th supported platform. Parses agent-transcript
JSONL files from ~/.cursor/projects/*/agent-transcripts/, extracts
conversation history, tool usage (Anthropic-style tool_use/tool_result
blocks), and thinking/reasoning highlights.

- New parser: src/parsers/cursor.ts
- Strips <user_query> tags from Cursor's wrapped user messages
- Derives working directory from project slug path
- Full integration: types, index, resume, CLI quick-command, colors
- Test fixtures and 42 cross-tool conversion paths (7×6) all passing
…ror-message

fix: show clear error when only one CLI tool is installed
Cursor replaces both `/` and `.` with `-` in project slugs, making
path reconstruction ambiguous. The previous greedy left-to-right
approach failed for names like `dzcm.test` (resolved as `dzcm/test`).

New approach uses recursive backtracking: at each dash position, tries
treating it as `/`, `.`, or literal `-`, and checks fs.existsSync only
on the final complete path. This correctly resolves all three cases:
- dzcm-test → dzcm.test
- readybyte-test → readybyte.test
- laravel-contentai → laravel/contentai
feat: add Cursor AI support with smart slug-to-path resolution
Replace 40+ manual wiring points across 8 files with a centralized
ToolAdapter registry. Adding a new CLI tool now requires only 3 files
instead of 8.

Key changes:
- New src/parsers/registry.ts with ToolAdapter interface and 7 registrations
- New src/utils/parser-helpers.ts with shared utilities (cleanSummary,
  extractRepoFromCwd, homeDir) extracted from duplicated parser code
- Rewired index.ts, resume.ts, cli.ts, markdown.ts to use registry lookups
  instead of explicit switch/import per tool
- Promise.allSettled replaces Promise.all in buildIndex (one broken parser
  no longer crashes the CLI)
- Fixed missing 'cursor' in two hardcoded allTools arrays
- SQLite close() moved to finally blocks in opencode.ts
- TOCTOU race fix in indexNeedsRebuild()
- Corrupted cache line protection in loadIndex()
- Removed dead SessionParser and ResumeOptions interfaces
- Removed unused 'tool' role from ConversationMessage
- Standardized message window to slice(-20) across parsers
- New beta-publish.yml: runs tests then publishes to npm with --tag beta
  on every push to develop
- CI now also runs tests on develop branch pushes
- Version bumped to 2.8.0-beta.0
The workflow now checks the current beta version on npm and
automatically bumps the prerelease number before publishing.
No manual version edits needed — just push to develop.
The 't' and 'i' letters were narrower than the other 4-char-wide
letters, causing a visible gap on the bottom row. Now all 9 letters
use centered double-width stems (▀██▀ for t, ▄▄ dot for i) at
exactly 4 characters wide with uniform spacing.
Replace flat white/cyan banner with per-letter hex gradient from soft
indigo (#9b8ec9) through sky blue to bright cyan, with the 's' brand
mark in bold mint (#00ffc8). Also dropped the half-block dot on 'i'
in favor of a clean thick bar matching 't' width.
BREAKING CHANGES:
- SessionSource type is now derived from TOOL_NAMES const array
- SOURCE_LABELS proxy removed (use getSourceLabels() instead)
- package.json main changed from dist/cli.js to dist/index.js

New features:
- Public library API via `import { getAllSessions } from 'continues'`
- Zod runtime validation for all JSON.parse calls (zero as-any in src/)
- Typed SQLite interface for OpenCode parser
- Biome linting/formatting with zero errors

Architecture:
- CLI split into src/commands/ and src/display/ (was 776-line monolith)
- Shared parser infrastructure (fs-helpers, jsonl, content, tool-extraction)
- Structured error hierarchy (ContinuesError base + 6 subclasses)
- Logger with configurable levels (--verbose/--debug/CONTINUES_DEBUG)
- Registry completeness assertion at module load
- ContentBlock discriminated union shared across all parsers

Cleanup:
- Removed dead constants.ts (never imported)
- Removed superseded test files (conversions.test.ts, parsers.test.ts)
- Removed unused function parameter (buildReferencePrompt filePath)
- Added .gitignore entries for local config files
- Net -1003 lines (4749 added, 3038 removed, + new modules)
The auto-increment script was always incrementing from the npm beta
base version (2.8.0-beta.3 → 2.8.0-beta.4), ignoring that
package.json had been bumped to 3.0.0-beta.0. Now compares the
base versions and uses package.json as-is when they differ.
Accept v3 develop-side values (version 3.0.0-beta.0, library exports,
Cursor in description) over the 2.7.5 release bump on main.
…warding (yigitkonur#9)

feat!: land v3 refactor on main + registry-driven cross-tool flag forwarding
Redesign the handoff markdown pipeline so each tool category gets
optimized extraction (capture what matters) and display (show what
matters) instead of flat one-liner summaries.

Pipeline changes:
- Add StructuredToolSample discriminated union (11 categories) with
  per-type data shapes (ShellSampleData, WriteSampleData, etc.)
- Refactor SummaryCollector to options-object API with per-category
  sample limits and error tracking
- Rewrite extractAnthropicToolData to produce structured samples:
  shell commands get exitCode + stdoutTail, writes/edits get unified
  diffs, grep/glob get match/file counts, MCP gets truncated params
- Add minimal diff utility (formatNewFileDiff, formatEditDiff,
  extractStdoutTail) with no external dependencies

Rendering changes:
- Category-aware markdown renderer with per-type templates:
  shell → blockquote with exit code + stdout in console blocks
  write/edit → fenced diff blocks with +/- line stats
  read → bullet list with optional line ranges
  grep/glob → bullet list with match/file counts
- Inline mode display caps (shell: 5, write/edit: 3, read: 15, etc.)
- Fixed category ordering: Shell→Write→Edit→Read→Grep→Glob→…

Parser updates:
- Codex: structured shell/patch/search/task data
- Gemini: capture fileDiff and diffStats from resultDisplay
- Copilot: extract tool data from toolRequests (was empty)
- Droid/Cursor/Claude: get rich data for free via shared extraction

Tests: 258 passing (20 new tests for classifyToolName, diff utils,
structured data extraction, and category-aware rendering)
Add full changelog entries covering:
- v3.1.0: smart context display with visual before/after examples
  showing exactly how the handoff markdown output changed
- v3.0.0: adapter registry, library exports, cursor support,
  typed schemas, CLI modularization, 7 parser rewrites
- v2.7.0 and v2.6.7 carried forward from previous changelog
yigitkonur and others added 24 commits March 2, 2026 13:19
Commit c527292 migrated qwen-code types to schemas.ts but never added
the types there, breaking the build with 20+ TS2304 errors. This restores
the inline type definitions (QwenPart, QwenContent, QwenToolCallResult,
QwenFileDiff, QwenTodoResult, QwenUsageMetadata, QwenSystemPayload,
QwenChatRecord) and reverts the QwenChatRecordSchema.safeParse() calls
back to JSON.parse() as QwenChatRecord since the schema never existed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ensive timestamps, dedup, tree path (yigitkonur#32)

- replace unsafe `as QwenChatRecord` casts with `QwenChatRecordSchema.safeParse()`
- add zod schemas for all qwen code types in schemas.ts
- remove duplicate local interfaces (restored by 1f2d89e, now properly in schemas)
- defensive timestamp parsing with mtime fallback for invalid dates
- deduplicate tool_result vs functionCall entries via parentUuid tracking
- reconstruct main conversation path from uuid/parentUuid tree
- rename antigravity fixture session.json → session.jsonl for accuracy

all 694 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-flags-more-clis

feat: expand handoff auto-approval defaults to more CLIs
…-presets-tab-cycle

improve handoff parsing, chaining, and banner quality
Ensure Cursor session slugs with Windows drive prefixes resolve to valid drive-letter paths instead of /D/... fallbacks, preventing ENOENT chdir failures when resuming. Adds regression tests for drive-path resolution and fallback behavior.

Made-with: Cursor
…ession

The fallback path in cwdFromSlug() produced Windows-style `D:/...` paths
on Unix for slugs starting with a single letter (e.g. `D-Workspace-...`).
Gate the drive-letter fallback behind `IS_WINDOWS` so Unix correctly
returns `/D/Workspace/...`.

Also fix the test: make the Windows drive-letter fallback assertion
Windows-only (itWindows) and add a Unix counterpart asserting the
correct `/D/Workspace/project/alpha` result.
…-slug

fix: resolve Windows Cursor cwd slug path conversion
greptile-apps[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Open in Devin Review

Comment thread src/parsers/registry.ts Outdated
Comment thread src/utils/resume.ts
Comment on lines 318 to 322
const checks = await Promise.allSettled(
ALL_TOOLS.map(async (name) => ({
name,
ok: await isBinaryAvailable(adapters[name].binaryName),
ok: (await resolveToolBinaryName(name)) !== null,
})),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: getAvailableTools now checks multiple binaries per tool sequentially within parallel checks

The getAvailableTools function at src/utils/resume.ts:317-330 previously called isBinaryAvailable(adapters[name].binaryName) once per tool. Now it calls resolveToolBinaryName(name) which iterates through candidates sequentially (primary + fallbacks). For cursor, this means two which/where spawns instead of one. However, since all tools are wrapped in Promise.allSettled via ALL_TOOLS.map(async ...), cursor's two sequential checks don't block other tools' checks. The performance impact is negligible — at most one extra which call for cursor, running concurrently with all other tool checks.

(Refers to lines 317-322)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/utils/resume.ts
Comment on lines +306 to +312
async function requireToolBinaryName(tool: SessionSource): Promise<string> {
const binaryName = await resolveToolBinaryName(tool);
if (binaryName) return binaryName;

const adapter = adapters[tool];
throw new ToolNotAvailableError(adapter?.label ?? tool);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: requireToolBinaryName correctly uses typed ToolNotAvailableError

The new requireToolBinaryName function at src/utils/resume.ts:306-312 correctly uses ToolNotAvailableError from src/errors.ts rather than a bare throw new Error(), consistent with the REVIEW.md rule about using typed errors on user-facing paths. This is called from both nativeResume (line 123) and crossToolResume (line 169). Note that the pre-existing throw new Error(...) on lines 122 and 168 (for unknown adapter) remain as bare errors — these are not part of this PR's diff but are worth noting as a pre-existing inconsistency with the project's error conventions.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/utils/resume.ts
Comment on lines 120 to +124
const cwd = session.cwd || process.cwd();
const adapter = adapters[session.source];
if (!adapter) throw new Error(`Unknown session source: ${session.source}`);
await runCommand(adapter.binaryName, adapter.nativeResumeArgs(session), cwd);
const binaryName = await requireToolBinaryName(session.source);
await runCommand(binaryName, adapter.nativeResumeArgs(session), cwd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Binary resolution adds a latency cost to every resume operation

Before this PR, nativeResume and crossToolResume directly used adapter.binaryName without checking availability. Now they call requireToolBinaryName which spawns which/where subprocess(es) to verify the binary exists before running it. This adds ~50-200ms of latency to every resume operation. For most tools (no fallbacks), it's one which check. For cursor, it could be two. This is a behavioral change — previously the tool would just attempt to spawn and fail with an ENOENT error if unavailable; now it fails early with a friendlier ToolNotAvailableError. The trade-off (slightly slower but better UX) seems intentional.

(Refers to lines 119-124)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread src/parsers/registry.ts
nativeResumeArgs: (s) => ['--resume', s.id],
crossToolArgs: (prompt) => [prompt],
resumeCommandDisplay: (s) => `agent --resume ${s.id}`,
resumeCommandDisplay: (s) => `cursor-agent --resume ${s.id} (or: agent --resume ${s.id})`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Cursor resumeCommandDisplay always shows both binaries regardless of which is installed

The resumeCommandDisplay at src/parsers/registry.ts:766 always emits cursor-agent --resume ... (or: agent --resume ...) as a static string. Unlike the actual spawn path which dynamically resolves which binary is available via resolveToolBinaryName, the display path doesn't check availability — it's purely informational. This is acceptable for a help/display string, but could be confusing to users who only have one binary installed. The display function is synchronous (resumeCommandDisplay returns string, not Promise<string>), so making it dynamic would require changing the interface signature.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants