Files
obra__episodic-memory/cli/episodic-memory.js
Jesse Vincent 30916f8cad feat(harness): add Cursor (#113) and opencode (#117) support
Adds Cursor (live transcripts + opt-in state.vscdb backfill) and opencode (DB->JSONL export) as harnesses, following the existing Codex pattern. opencode summarization is fixed to route via transcript text instead of a doomed claude --resume, and hasConversationContent recognizes opencode_message lines. Harness union is 'claude'|'codex'|'cursor'|'opencode'. Also pins conversation timestamps to en-US/UTC in every renderer (#130) and restores valid YAML in the search agent frontmatter (#153).

Co-authored-by: vicnaum <vicnaum@users.noreply.github.com>

Co-authored-by: slandau3 <slandau3@users.noreply.github.com>

Co-authored-by: arimu1 <arimu1@users.noreply.github.com>

Claude-Session: https://claude.ai/code/session_0112vdwZphiWzfCYfaXMes4C
2026-09-08 18:47:47 +00:00

118 lines
3.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { spawn } from 'child_process';
import { realpathSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(realpathSync(__filename));
const command = process.argv[2];
const args = process.argv.slice(3);
function runScript(scriptPath, args) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [scriptPath, ...args], {
stdio: 'inherit'
});
child.on('exit', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
child.on('error', (err) => {
reject(new Error(`Failed to run command: ${err.message}`));
});
});
}
function showHelp() {
console.log(`episodic-memory - Manage and search Claude Code, Codex, and Cursor conversations
USAGE:
episodic-memory <command> [options]
COMMANDS:
sync Sync conversations from Claude Code, Codex, and Cursor and index them
index Index conversations for search
search Search indexed conversations
show Display a conversation in readable format
stats Show index statistics
doctor Diagnose Claude Code or Codex integration issues
import-cursor-history Export legacy Cursor conversations from state.vscdb for indexing
Run 'episodic-memory <command> --help' for command-specific help.
EXAMPLES:
# Index all conversations
episodic-memory index --cleanup
# Search for something
episodic-memory search "React Router auth"
# Display a conversation
episodic-memory show path/to/conversation.jsonl
# Generate HTML output
episodic-memory show --format html conversation.jsonl > output.html`);
}
async function main() {
try {
const distDir = join(__dirname, '../dist');
switch (command) {
case 'index':
await runScript(join(__dirname, 'index-conversations.js'), args);
break;
case 'search':
await runScript(join(distDir, 'search-cli.js'), args);
break;
case 'show':
await runScript(join(distDir, 'show-cli.js'), args);
break;
case 'stats':
await runScript(join(distDir, 'stats-cli.js'), args);
break;
case 'doctor':
await runScript(join(distDir, 'doctor-cli.js'), args);
break;
case 'sync':
await runScript(join(distDir, 'sync-cli.js'), args);
break;
case 'import-cursor-history':
await runScript(join(distDir, 'cursor-import-cli.js'), args);
break;
case '--help':
case '-h':
case undefined:
showHelp();
break;
default:
console.error(`Unknown command: ${command}`);
console.error('Try: episodic-memory --help');
process.exit(1);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
main().catch((error) => {
console.error(`Unexpected error: ${error.message}`);
process.exit(1);
});