feat(cursor): support cursor-agent binary fallback#41
feat(cursor): support cursor-agent binary fallback#41erenaslandev wants to merge 114 commits intoyigitkonur:mainfrom
Conversation
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 Factory Droid CLI support
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
feat: add Cursor AI support
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
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
| const checks = await Promise.allSettled( | ||
| ALL_TOOLS.map(async (name) => ({ | ||
| name, | ||
| ok: await isBinaryAvailable(adapters[name].binaryName), | ||
| ok: (await resolveToolBinaryName(name)) !== null, | ||
| })), |
There was a problem hiding this comment.
📝 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)
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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); | ||
| } |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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); |
There was a problem hiding this comment.
📝 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)
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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})`, |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
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-agentas the preferred binary for Cursor AI withagentas a legacy fallback. TheToolAdapterinterface gains an optionalbinaryFallbacksfield, andresume.tsgainsresolveToolBinaryNamewhich 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
Important Files Changed
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)Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(cursor): support cursor-agent binar..." | Re-trigger Greptile
(2/5) Greptile learns from your feedback when you react with thumbs up/down!