@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.
- package/dist/adapter/cdp-client.d.ts +66 -0
- package/dist/adapter/cdp-client.d.ts.map +1 -0
- package/dist/adapter/cdp-client.js +304 -0
- package/dist/adapter/cdp-client.js.map +1 -0
- package/dist/adapter/cursor-db.d.ts +114 -0
- package/dist/adapter/cursor-db.d.ts.map +1 -0
- package/dist/adapter/cursor-db.js +438 -0
- package/dist/adapter/cursor-db.js.map +1 -0
- package/dist/client/messages.d.ts +98 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +6 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/websocket.d.ts +103 -0
- package/dist/client/websocket.d.ts.map +1 -0
- package/dist/client/websocket.js +428 -0
- package/dist/client/websocket.js.map +1 -0
- package/dist/commands/register.d.ts +10 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +175 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/commands/start.d.ts +9 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +86 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +5 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/config/config.d.ts +68 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +189 -0
- package/dist/config/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/session-discovery.d.ts +22 -0
- package/dist/session-discovery.d.ts.map +1 -0
- package/dist/session-discovery.js +90 -0
- package/dist/session-discovery.js.map +1 -0
- package/dist/session-watcher.d.ts +62 -0
- package/dist/session-watcher.d.ts.map +1 -0
- package/dist/session-watcher.js +210 -0
- package/dist/session-watcher.js.map +1 -0
- package/package.json +40 -0
- package/src/adapter/cdp-client.ts +296 -0
- package/src/adapter/cursor-db.ts +486 -0
- package/src/client/messages.ts +138 -0
- package/src/client/websocket.ts +486 -0
- package/src/commands/register.ts +201 -0
- package/src/commands/start.ts +106 -0
- package/src/commands/status.ts +83 -0
- package/src/commands/stop.ts +58 -0
- package/src/config/config.ts +167 -0
- package/src/index.ts +39 -0
- package/src/session-discovery.ts +115 -0
- package/src/session-watcher.ts +253 -0
- 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
|
+
}
|