@cmdctrl/cursor-ide 0.1.0

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.
Files changed (62) hide show
  1. package/dist/adapter/cdp-client.d.ts +66 -0
  2. package/dist/adapter/cdp-client.d.ts.map +1 -0
  3. package/dist/adapter/cdp-client.js +304 -0
  4. package/dist/adapter/cdp-client.js.map +1 -0
  5. package/dist/adapter/cursor-db.d.ts +114 -0
  6. package/dist/adapter/cursor-db.d.ts.map +1 -0
  7. package/dist/adapter/cursor-db.js +438 -0
  8. package/dist/adapter/cursor-db.js.map +1 -0
  9. package/dist/client/messages.d.ts +98 -0
  10. package/dist/client/messages.d.ts.map +1 -0
  11. package/dist/client/messages.js +6 -0
  12. package/dist/client/messages.js.map +1 -0
  13. package/dist/client/websocket.d.ts +103 -0
  14. package/dist/client/websocket.d.ts.map +1 -0
  15. package/dist/client/websocket.js +428 -0
  16. package/dist/client/websocket.js.map +1 -0
  17. package/dist/commands/register.d.ts +10 -0
  18. package/dist/commands/register.d.ts.map +1 -0
  19. package/dist/commands/register.js +175 -0
  20. package/dist/commands/register.js.map +1 -0
  21. package/dist/commands/start.d.ts +9 -0
  22. package/dist/commands/start.d.ts.map +1 -0
  23. package/dist/commands/start.js +86 -0
  24. package/dist/commands/start.js.map +1 -0
  25. package/dist/commands/status.d.ts +5 -0
  26. package/dist/commands/status.d.ts.map +1 -0
  27. package/dist/commands/status.js +75 -0
  28. package/dist/commands/status.js.map +1 -0
  29. package/dist/commands/stop.d.ts +5 -0
  30. package/dist/commands/stop.d.ts.map +1 -0
  31. package/dist/commands/stop.js +59 -0
  32. package/dist/commands/stop.js.map +1 -0
  33. package/dist/config/config.d.ts +68 -0
  34. package/dist/config/config.d.ts.map +1 -0
  35. package/dist/config/config.js +189 -0
  36. package/dist/config/config.js.map +1 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +34 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/session-discovery.d.ts +22 -0
  42. package/dist/session-discovery.d.ts.map +1 -0
  43. package/dist/session-discovery.js +90 -0
  44. package/dist/session-discovery.js.map +1 -0
  45. package/dist/session-watcher.d.ts +62 -0
  46. package/dist/session-watcher.d.ts.map +1 -0
  47. package/dist/session-watcher.js +210 -0
  48. package/dist/session-watcher.js.map +1 -0
  49. package/package.json +40 -0
  50. package/src/adapter/cdp-client.ts +296 -0
  51. package/src/adapter/cursor-db.ts +486 -0
  52. package/src/client/messages.ts +138 -0
  53. package/src/client/websocket.ts +486 -0
  54. package/src/commands/register.ts +201 -0
  55. package/src/commands/start.ts +106 -0
  56. package/src/commands/status.ts +83 -0
  57. package/src/commands/stop.ts +58 -0
  58. package/src/config/config.ts +167 -0
  59. package/src/index.ts +39 -0
  60. package/src/session-discovery.ts +115 -0
  61. package/src/session-watcher.ts +253 -0
  62. package/tsconfig.json +19 -0
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.discoverSessions = discoverSessions;
4
+ exports.getSessionDetails = getSessionDetails;
5
+ const cursor_db_1 = require("./adapter/cursor-db");
6
+ /**
7
+ * Discover Cursor IDE sessions from the SQLite database
8
+ * Returns sessions in a format compatible with CmdCtrl's external session API
9
+ */
10
+ function discoverSessions(excludeSessionIds = new Set()) {
11
+ const cursorDb = (0, cursor_db_1.getCursorDB)();
12
+ const composers = cursorDb.getComposers();
13
+ const sessions = [];
14
+ const now = Date.now();
15
+ const ACTIVE_THRESHOLD = 30 * 1000; // 30 seconds
16
+ for (const composer of composers) {
17
+ // Skip excluded sessions (managed by CmdCtrl)
18
+ if (excludeSessionIds.has(composer.composerId)) {
19
+ continue;
20
+ }
21
+ // Use composer metadata directly for discovery (avoid expensive per-session queries)
22
+ // The detailed info (message count, last message preview) can be fetched on-demand
23
+ const lastActivity = new Date(composer.lastUpdatedAt).toISOString();
24
+ const isActive = (now - composer.lastUpdatedAt) < ACTIVE_THRESHOLD;
25
+ // Generate a slug from the composer name
26
+ const slug = generateSlug(composer.name);
27
+ // Use project path if available, otherwise empty (frontend can show device name)
28
+ const project = composer.projectPath || '';
29
+ sessions.push({
30
+ session_id: composer.composerId,
31
+ slug,
32
+ title: composer.name || 'Untitled',
33
+ project,
34
+ project_name: project ? project.split('/').pop() || 'cursor' : 'cursor',
35
+ file_path: 'state.vscdb', // Reference to SQLite
36
+ last_message: '', // Fetched on-demand when viewing session
37
+ last_activity: lastActivity,
38
+ is_active: isActive,
39
+ message_count: 0, // Fetched on-demand when viewing session
40
+ });
41
+ }
42
+ // Sort by last activity (newest first)
43
+ return sessions.sort((a, b) => new Date(b.last_activity).getTime() - new Date(a.last_activity).getTime());
44
+ }
45
+ /**
46
+ * Generate a URL-friendly slug from a title
47
+ */
48
+ function generateSlug(title) {
49
+ return title
50
+ .toLowerCase()
51
+ .replace(/[^a-z0-9\s-]/g, '')
52
+ .replace(/\s+/g, '-')
53
+ .replace(/-+/g, '-')
54
+ .substring(0, 50);
55
+ }
56
+ /**
57
+ * Get details for a specific session
58
+ */
59
+ function getSessionDetails(sessionId) {
60
+ const cursorDb = (0, cursor_db_1.getCursorDB)();
61
+ const composers = cursorDb.getComposers();
62
+ const composer = composers.find(c => c.composerId === sessionId);
63
+ if (!composer) {
64
+ return null;
65
+ }
66
+ const messageCount = cursorDb.getBubbleCount(sessionId);
67
+ const latestBubble = cursorDb.getLatestBubble(sessionId);
68
+ const lastMessage = latestBubble?.text?.substring(0, 100) || '';
69
+ const lastActivity = latestBubble?.createdAt || new Date(composer.lastUpdatedAt).toISOString();
70
+ const now = Date.now();
71
+ const ACTIVE_THRESHOLD = 30 * 1000;
72
+ const lastUpdateTime = latestBubble
73
+ ? new Date(latestBubble.createdAt).getTime()
74
+ : composer.lastUpdatedAt;
75
+ const isActive = (now - lastUpdateTime) < ACTIVE_THRESHOLD;
76
+ const project = composer.projectPath || '';
77
+ return {
78
+ session_id: composer.composerId,
79
+ slug: generateSlug(composer.name),
80
+ title: composer.name || 'Untitled',
81
+ project,
82
+ project_name: project ? project.split('/').pop() || 'cursor' : 'cursor',
83
+ file_path: 'state.vscdb',
84
+ last_message: lastMessage,
85
+ last_activity: lastActivity,
86
+ is_active: isActive,
87
+ message_count: messageCount,
88
+ };
89
+ }
90
+ //# sourceMappingURL=session-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-discovery.js","sourceRoot":"","sources":["../src/session-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Session watcher for monitoring Cursor session activity
3
+ *
4
+ * Simple polling-based watcher that checks the SQLite database at regular intervals.
5
+ * More reliable than fs.watch across different file systems.
6
+ */
7
+ export interface SessionActivityEvent {
8
+ session_id: string;
9
+ file_path: string;
10
+ last_message: string;
11
+ message_count: number;
12
+ is_completion: boolean;
13
+ user_message_uuid?: string;
14
+ }
15
+ export type SessionActivityCallback = (event: SessionActivityEvent) => void;
16
+ /**
17
+ * Watch Cursor's SQLite database for changes using polling.
18
+ * More reliable than fs.watch on macOS.
19
+ */
20
+ export declare class SessionWatcher {
21
+ private watchedSessions;
22
+ private pollTimer;
23
+ private callback;
24
+ /**
25
+ * Start watching the database (starts the polling loop when first session is added)
26
+ */
27
+ start(callback: SessionActivityCallback): void;
28
+ /**
29
+ * Stop watching
30
+ */
31
+ stop(): void;
32
+ /**
33
+ * Add a session to watch for changes
34
+ */
35
+ watchSession(sessionId: string): void;
36
+ /**
37
+ * Remove a session from watch list
38
+ */
39
+ unwatchSession(sessionId: string): void;
40
+ /**
41
+ * Get list of watched session IDs
42
+ */
43
+ getWatchedSessions(): string[];
44
+ /**
45
+ * Start the polling loop
46
+ */
47
+ private startPolling;
48
+ /**
49
+ * Poll all watched sessions for changes
50
+ */
51
+ private pollAllSessions;
52
+ /**
53
+ * Check a single session for changes
54
+ */
55
+ private checkSession;
56
+ /**
57
+ * Force a check of all watched sessions (clears cooldowns)
58
+ */
59
+ forceCheck(): void;
60
+ }
61
+ export declare function getSessionWatcher(): SessionWatcher;
62
+ //# sourceMappingURL=session-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-watcher.d.ts","sourceRoot":"","sources":["../src/session-watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAe5E;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,QAAQ,CAAwC;IAExD;;OAEG;IACH,KAAK,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAK9C;;OAEG;IACH,IAAI,IAAI,IAAI;IAUZ;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAsBrC;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAYvC;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAiGpB;;OAEG;IACH,UAAU,IAAI,IAAI;CAMnB;AAKD,wBAAgB,iBAAiB,IAAI,cAAc,CAKlD"}
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * Session watcher for monitoring Cursor session activity
4
+ *
5
+ * Simple polling-based watcher that checks the SQLite database at regular intervals.
6
+ * More reliable than fs.watch across different file systems.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SessionWatcher = void 0;
10
+ exports.getSessionWatcher = getSessionWatcher;
11
+ const config_1 = require("./config/config");
12
+ const cursor_db_1 = require("./adapter/cursor-db");
13
+ // Polling interval for checking SQLite database
14
+ const POLL_INTERVAL_MS = 500;
15
+ // Minimum time between notifications for the same session (5 seconds)
16
+ const NOTIFY_COOLDOWN_MS = 5000;
17
+ /**
18
+ * Watch Cursor's SQLite database for changes using polling.
19
+ * More reliable than fs.watch on macOS.
20
+ */
21
+ class SessionWatcher {
22
+ watchedSessions = new Map();
23
+ pollTimer = null;
24
+ callback = null;
25
+ /**
26
+ * Start watching the database (starts the polling loop when first session is added)
27
+ */
28
+ start(callback) {
29
+ this.callback = callback;
30
+ console.log('[SessionWatcher] Started watching database');
31
+ }
32
+ /**
33
+ * Stop watching
34
+ */
35
+ stop() {
36
+ if (this.pollTimer) {
37
+ clearInterval(this.pollTimer);
38
+ this.pollTimer = null;
39
+ }
40
+ this.watchedSessions.clear();
41
+ this.callback = null;
42
+ console.log('[SessionWatcher] Stopped watching');
43
+ }
44
+ /**
45
+ * Add a session to watch for changes
46
+ */
47
+ watchSession(sessionId) {
48
+ if (this.watchedSessions.has(sessionId)) {
49
+ console.log(`[SessionWatcher] Already watching session ${sessionId}`);
50
+ return;
51
+ }
52
+ const cursorDb = (0, cursor_db_1.getCursorDB)();
53
+ const count = cursorDb.getBubbleCount(sessionId);
54
+ this.watchedSessions.set(sessionId, {
55
+ sessionId,
56
+ lastMessageCount: count,
57
+ });
58
+ console.log(`[SessionWatcher] Now watching session ${sessionId} (${count} messages)`);
59
+ // Start polling if not already running
60
+ if (!this.pollTimer) {
61
+ this.startPolling();
62
+ }
63
+ }
64
+ /**
65
+ * Remove a session from watch list
66
+ */
67
+ unwatchSession(sessionId) {
68
+ if (this.watchedSessions.delete(sessionId)) {
69
+ console.log(`[SessionWatcher] Stopped watching session ${sessionId}`);
70
+ }
71
+ // Stop polling if no sessions left
72
+ if (this.watchedSessions.size === 0 && this.pollTimer) {
73
+ clearInterval(this.pollTimer);
74
+ this.pollTimer = null;
75
+ }
76
+ }
77
+ /**
78
+ * Get list of watched session IDs
79
+ */
80
+ getWatchedSessions() {
81
+ return Array.from(this.watchedSessions.keys());
82
+ }
83
+ /**
84
+ * Start the polling loop
85
+ */
86
+ startPolling() {
87
+ this.pollTimer = setInterval(() => {
88
+ this.pollAllSessions();
89
+ }, POLL_INTERVAL_MS);
90
+ }
91
+ /**
92
+ * Poll all watched sessions for changes
93
+ */
94
+ pollAllSessions() {
95
+ if (!this.callback || this.watchedSessions.size === 0)
96
+ return;
97
+ const cursorDb = (0, cursor_db_1.getCursorDB)();
98
+ for (const [sessionId, session] of this.watchedSessions) {
99
+ this.checkSession(cursorDb, session);
100
+ }
101
+ }
102
+ /**
103
+ * Check a single session for changes
104
+ */
105
+ checkSession(cursorDb, session) {
106
+ const currentCount = cursorDb.getBubbleCount(session.sessionId);
107
+ // Check if we're waiting for content on a pending AGENT bubble
108
+ if (session.pendingAgentBubbleId) {
109
+ const latestBubble = cursorDb.getLatestBubble(session.sessionId);
110
+ if (latestBubble && latestBubble.bubbleId === session.pendingAgentBubbleId) {
111
+ const hasContent = !!latestBubble.text?.trim();
112
+ if (hasContent) {
113
+ // Content arrived! Send completion notification
114
+ let userMessageUuid;
115
+ const bubbles = cursorDb.getBubbles(session.sessionId);
116
+ for (let i = bubbles.length - 1; i >= 0; i--) {
117
+ if (bubbles[i].type === 1) {
118
+ userMessageUuid = bubbles[i].bubbleId;
119
+ break;
120
+ }
121
+ }
122
+ const event = {
123
+ session_id: session.sessionId,
124
+ file_path: config_1.CURSOR_GLOBAL_STORAGE,
125
+ last_message: latestBubble.text?.substring(0, 100) || '',
126
+ message_count: currentCount,
127
+ is_completion: true,
128
+ user_message_uuid: userMessageUuid,
129
+ };
130
+ console.log(`[SessionWatcher] Pending AGENT bubble now has content: ${session.sessionId} (bubble: ${latestBubble.bubbleId.substring(0, 8)})`);
131
+ session.lastNotifyTime = Date.now();
132
+ session.pendingAgentBubbleId = undefined;
133
+ this.callback(event);
134
+ return;
135
+ }
136
+ }
137
+ else {
138
+ // Different bubble or bubble gone - clear pending
139
+ session.pendingAgentBubbleId = undefined;
140
+ }
141
+ }
142
+ // Only notify if there are new messages
143
+ if (currentCount > session.lastMessageCount) {
144
+ // Get latest bubble for details
145
+ const latestBubble = cursorDb.getLatestBubble(session.sessionId);
146
+ if (!latestBubble) {
147
+ session.lastMessageCount = currentCount;
148
+ return;
149
+ }
150
+ // Determine if this is a completion (assistant message with non-empty content)
151
+ // For Cursor: type 1 = user, type 2 = assistant
152
+ // Cursor creates empty assistant bubbles first, then fills them in
153
+ const hasContent = !!latestBubble.text?.trim();
154
+ const isCompletion = latestBubble.type === 2 && hasContent;
155
+ const isUserMessage = latestBubble.type === 1;
156
+ // If empty AGENT bubble, track it for later but still update count
157
+ if (latestBubble.type === 2 && !hasContent) {
158
+ console.log(`[SessionWatcher] Empty AGENT bubble detected, tracking for content: ${session.sessionId} (bubble: ${latestBubble.bubbleId.substring(0, 8)})`);
159
+ session.pendingAgentBubbleId = latestBubble.bubbleId;
160
+ session.lastMessageCount = currentCount;
161
+ return;
162
+ }
163
+ const now = Date.now();
164
+ const timeSinceLastNotify = session.lastNotifyTime ? now - session.lastNotifyTime : Infinity;
165
+ // Always notify for completions (assistant responses), cooldown only for user messages
166
+ if (isCompletion || (isUserMessage && timeSinceLastNotify >= NOTIFY_COOLDOWN_MS)) {
167
+ // Find the last USER message's bubble ID for positioning verbose output
168
+ let userMessageUuid;
169
+ const bubbles = cursorDb.getBubbles(session.sessionId);
170
+ for (let i = bubbles.length - 1; i >= 0; i--) {
171
+ if (bubbles[i].type === 1) { // type 1 = user
172
+ userMessageUuid = bubbles[i].bubbleId;
173
+ break;
174
+ }
175
+ }
176
+ const event = {
177
+ session_id: session.sessionId,
178
+ file_path: config_1.CURSOR_GLOBAL_STORAGE,
179
+ last_message: latestBubble.text?.substring(0, 100) || '',
180
+ message_count: currentCount,
181
+ is_completion: isCompletion,
182
+ user_message_uuid: userMessageUuid,
183
+ };
184
+ console.log(`[SessionWatcher] Sending activity for session ${session.sessionId} (completion: ${isCompletion}, userUuid: ${userMessageUuid}, msg: "${event.last_message.substring(0, 30)}...")`);
185
+ session.lastNotifyTime = now;
186
+ this.callback(event);
187
+ }
188
+ session.lastMessageCount = currentCount;
189
+ }
190
+ }
191
+ /**
192
+ * Force a check of all watched sessions (clears cooldowns)
193
+ */
194
+ forceCheck() {
195
+ for (const session of this.watchedSessions.values()) {
196
+ session.lastNotifyTime = undefined;
197
+ }
198
+ this.pollAllSessions();
199
+ }
200
+ }
201
+ exports.SessionWatcher = SessionWatcher;
202
+ // Singleton instance
203
+ let sessionWatcherInstance = null;
204
+ function getSessionWatcher() {
205
+ if (!sessionWatcherInstance) {
206
+ sessionWatcherInstance = new SessionWatcher();
207
+ }
208
+ return sessionWatcherInstance;
209
+ }
210
+ //# sourceMappingURL=session-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-watcher.js","sourceRoot":"","sources":["../src/session-watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@cmdctrl/cursor-ide",
3
+ "version": "0.1.0",
4
+ "description": "CmdCtrl daemon for Cursor IDE - connects Cursor sessions to the CmdCtrl orchestration server",
5
+ "main": "./dist/index.js",
6
+ "bin": {
7
+ "cmdctrl-cursor-ide": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "start": "node dist/index.js",
13
+ "lint": "eslint src --ext .ts",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "keywords": [
17
+ "cmdctrl",
18
+ "daemon",
19
+ "cursor",
20
+ "ai",
21
+ "orchestration"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "commander": "^12.1.0",
27
+ "ws": "^8.18.0",
28
+ "better-sqlite3": "^11.7.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/better-sqlite3": "^7.6.12",
32
+ "@types/node": "^22.10.2",
33
+ "@types/ws": "^8.5.13",
34
+ "typescript": "^5.7.2",
35
+ "ts-node": "^10.9.2"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }