@cmdctrl/cursor-cli 0.2.0 → 0.2.2

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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Cursor CLI Session Watcher
3
+ *
4
+ * Polls cursor-agent JSONL transcript files for new messages and emits events.
5
+ * Used with the SDK's onWatchSession / onUnwatchSession hooks.
6
+ */
7
+ export interface CursorSessionEvent {
8
+ type: 'USER_MESSAGE' | 'AGENT_RESPONSE';
9
+ sessionId: string;
10
+ uuid: string;
11
+ content: string;
12
+ }
13
+ export interface CursorCompletionEvent {
14
+ sessionId: string;
15
+ filePath: string;
16
+ lastMessage: string;
17
+ messageCount: number;
18
+ }
19
+ type EventCallback = (event: CursorSessionEvent) => void;
20
+ type CompletionCallback = (event: CursorCompletionEvent) => void;
21
+ export declare class CursorSessionWatcher {
22
+ private watchedSessions;
23
+ private completionTimers;
24
+ private pollTimer;
25
+ private onEvent;
26
+ private onCompletion;
27
+ constructor(onEvent: EventCallback, onCompletion?: CompletionCallback);
28
+ watchSession(sessionId: string, filePath: string): void;
29
+ unwatchSession(sessionId: string): void;
30
+ unwatchAll(): void;
31
+ get watchCount(): number;
32
+ private startPolling;
33
+ private checkSession;
34
+ private startCompletionTimer;
35
+ private cancelCompletionTimer;
36
+ }
37
+ export {};
38
+ //# 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;AAQH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AACzD,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAWjE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAA4B;gBAEpC,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,kBAAkB;IAKrE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA8BvD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWvC,UAAU,IAAI,IAAI;IAUlB,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,qBAAqB;CAO9B"}
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ /**
3
+ * Cursor CLI Session Watcher
4
+ *
5
+ * Polls cursor-agent JSONL transcript files for new messages and emits events.
6
+ * Used with the SDK's onWatchSession / onUnwatchSession hooks.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.CursorSessionWatcher = void 0;
43
+ const fs = __importStar(require("fs"));
44
+ const session_discovery_1 = require("./session-discovery");
45
+ const POLL_INTERVAL_MS = 500;
46
+ const COMPLETION_DELAY_MS = 5000;
47
+ class CursorSessionWatcher {
48
+ watchedSessions = new Map();
49
+ completionTimers = new Map();
50
+ pollTimer = null;
51
+ onEvent;
52
+ onCompletion;
53
+ constructor(onEvent, onCompletion) {
54
+ this.onEvent = onEvent;
55
+ this.onCompletion = onCompletion || null;
56
+ }
57
+ watchSession(sessionId, filePath) {
58
+ if (this.watchedSessions.has(sessionId))
59
+ return;
60
+ if (!fs.existsSync(filePath)) {
61
+ console.warn(`[CursorWatcher] File not found: ${filePath}`);
62
+ return;
63
+ }
64
+ try {
65
+ const stat = fs.statSync(filePath);
66
+ const messages = (0, session_discovery_1.parseTranscriptFile)(filePath);
67
+ const lastAgent = [...messages].reverse().find(m => m.role === 'agent');
68
+ this.watchedSessions.set(sessionId, {
69
+ sessionId,
70
+ filePath,
71
+ lastSize: stat.size,
72
+ processedCount: messages.length,
73
+ messageCount: messages.length,
74
+ lastMessage: lastAgent?.content.slice(0, 200) || '',
75
+ });
76
+ console.log(`[CursorWatcher] Started watching session ${sessionId} (${messages.length} existing messages)`);
77
+ if (!this.pollTimer)
78
+ this.startPolling();
79
+ }
80
+ catch (err) {
81
+ console.error(`[CursorWatcher] Failed to watch ${filePath}:`, err);
82
+ }
83
+ }
84
+ unwatchSession(sessionId) {
85
+ this.cancelCompletionTimer(sessionId);
86
+ if (this.watchedSessions.delete(sessionId)) {
87
+ console.log(`[CursorWatcher] Stopped watching session ${sessionId}`);
88
+ }
89
+ if (this.watchedSessions.size === 0 && this.pollTimer) {
90
+ clearInterval(this.pollTimer);
91
+ this.pollTimer = null;
92
+ }
93
+ }
94
+ unwatchAll() {
95
+ for (const timer of this.completionTimers.values())
96
+ clearTimeout(timer);
97
+ this.completionTimers.clear();
98
+ this.watchedSessions.clear();
99
+ if (this.pollTimer) {
100
+ clearInterval(this.pollTimer);
101
+ this.pollTimer = null;
102
+ }
103
+ }
104
+ get watchCount() {
105
+ return this.watchedSessions.size;
106
+ }
107
+ startPolling() {
108
+ this.pollTimer = setInterval(() => {
109
+ for (const session of this.watchedSessions.values()) {
110
+ this.checkSession(session);
111
+ }
112
+ }, POLL_INTERVAL_MS);
113
+ }
114
+ checkSession(session) {
115
+ try {
116
+ if (!fs.existsSync(session.filePath)) {
117
+ this.unwatchSession(session.sessionId);
118
+ return;
119
+ }
120
+ const stat = fs.statSync(session.filePath);
121
+ if (stat.size === session.lastSize)
122
+ return;
123
+ session.lastSize = stat.size;
124
+ const allMessages = (0, session_discovery_1.parseTranscriptFile)(session.filePath);
125
+ const newMessages = allMessages.slice(session.processedCount);
126
+ if (newMessages.length === 0)
127
+ return;
128
+ let sawAgent = false;
129
+ for (const msg of newMessages) {
130
+ const uuid = (0, session_discovery_1.stableUuid)(session.sessionId + ':' + msg.id);
131
+ this.onEvent({
132
+ type: msg.role === 'user' ? 'USER_MESSAGE' : 'AGENT_RESPONSE',
133
+ sessionId: session.sessionId,
134
+ uuid,
135
+ content: msg.content,
136
+ });
137
+ if (msg.role === 'agent') {
138
+ sawAgent = true;
139
+ session.lastMessage = msg.content.slice(0, 200);
140
+ }
141
+ session.messageCount++;
142
+ }
143
+ session.processedCount = allMessages.length;
144
+ if (sawAgent)
145
+ this.startCompletionTimer(session);
146
+ }
147
+ catch (err) {
148
+ console.error(`[CursorWatcher] Error checking session ${session.sessionId}:`, err);
149
+ }
150
+ }
151
+ startCompletionTimer(session) {
152
+ this.cancelCompletionTimer(session.sessionId);
153
+ if (!this.onCompletion)
154
+ return;
155
+ const timer = setTimeout(() => {
156
+ this.completionTimers.delete(session.sessionId);
157
+ this.onCompletion?.({
158
+ sessionId: session.sessionId,
159
+ filePath: session.filePath,
160
+ lastMessage: session.lastMessage,
161
+ messageCount: session.messageCount,
162
+ });
163
+ }, COMPLETION_DELAY_MS);
164
+ this.completionTimers.set(session.sessionId, timer);
165
+ }
166
+ cancelCompletionTimer(sessionId) {
167
+ const timer = this.completionTimers.get(sessionId);
168
+ if (timer) {
169
+ clearTimeout(timer);
170
+ this.completionTimers.delete(sessionId);
171
+ }
172
+ }
173
+ }
174
+ exports.CursorSessionWatcher = CursorSessionWatcher;
175
+ //# 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmdctrl/cursor-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "CmdCtrl daemon for Cursor CLI - connects your workstation to the CmdCtrl orchestration server",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
@@ -2,7 +2,8 @@ import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { DaemonClient, ConfigManager } from '@cmdctrl/daemon-sdk';
4
4
  import { CursorAdapter } from '../adapter/cursor-cli';
5
- import { MessageStore } from '../message-store';
5
+ import { discoverSessions, readSessionMessages } from '../session-discovery';
6
+ import { CursorSessionWatcher } from '../session-watcher';
6
7
 
7
8
  const configManager = new ConfigManager('cursor-cli');
8
9
 
@@ -39,9 +40,33 @@ export async function start(): Promise<void> {
39
40
 
40
41
  configManager.writePidFile(process.pid);
41
42
 
42
- const messageStore = new MessageStore();
43
- const pendingInstructions = new Map<string, string>();
44
- const taskSessionMap = new Map<string, string>();
43
+ // Managed session IDs (started via task_start) – excluded from native discovery
44
+ const managedSessionIds = new Set<string>();
45
+
46
+ const sessionWatcher = new CursorSessionWatcher(
47
+ (event) => {
48
+ // Only send activity for user messages – agent responses are the final
49
+ // answer already shown via the transcript; sending them here duplicates.
50
+ if (event.type !== 'USER_MESSAGE') return;
51
+ client.sendSessionActivity(
52
+ event.sessionId,
53
+ '',
54
+ event.content,
55
+ 1,
56
+ false,
57
+ new Date().toISOString()
58
+ );
59
+ },
60
+ (completion) => {
61
+ client.sendSessionActivity(
62
+ completion.sessionId,
63
+ completion.filePath,
64
+ completion.lastMessage,
65
+ completion.messageCount,
66
+ true
67
+ );
68
+ }
69
+ );
45
70
 
46
71
  // Event callback wired into the DaemonClient below
47
72
  let sendEvent: (taskId: string, eventType: string, data: Record<string, unknown>) => void;
@@ -49,21 +74,8 @@ export async function start(): Promise<void> {
49
74
  const adapter = new CursorAdapter((taskId, eventType, data) => {
50
75
  const sessionId = data.session_id as string | undefined;
51
76
 
52
- // Store initial user message when session starts
53
77
  if (eventType === 'SESSION_STARTED' && sessionId) {
54
- const instruction = pendingInstructions.get(taskId);
55
- if (instruction) {
56
- messageStore.storeMessage(sessionId, 'USER', instruction);
57
- pendingInstructions.delete(taskId);
58
- }
59
- }
60
-
61
- // Store agent response on completion
62
- if (eventType === 'TASK_COMPLETE' && data.result) {
63
- const sid = (data.session_id as string) || taskSessionMap.get(taskId);
64
- if (sid) {
65
- messageStore.storeMessage(sid, 'AGENT', data.result as string);
66
- }
78
+ managedSessionIds.add(sessionId);
67
79
  }
68
80
 
69
81
  sendEvent(taskId, eventType, data);
@@ -77,10 +89,13 @@ export async function start(): Promise<void> {
77
89
  version: daemonVersion,
78
90
  });
79
91
 
80
- // Wire up sendEvent to the client's internal method
92
+ client.setSessionsProvider(() => discoverSessions(managedSessionIds));
93
+
81
94
  sendEvent = (taskId, eventType, data) => {
82
- // Use the client to send events the SDK handles this via task handles,
83
- // but since the adapter uses a callback pattern, we send raw events
95
+ // cursor-agent writes all content to transcript files suppress OUTPUT events
96
+ // and strip result from TASK_COMPLETE to avoid duplicating transcript content.
97
+ if (eventType === 'OUTPUT') return;
98
+ if (eventType === 'TASK_COMPLETE') data = { ...data, result: '' };
84
99
  (client as any).send({
85
100
  type: 'event',
86
101
  task_id: taskId,
@@ -89,8 +104,15 @@ export async function start(): Promise<void> {
89
104
  });
90
105
  };
91
106
 
107
+ client.onWatchSession((sessionId, filePath) => {
108
+ sessionWatcher.watchSession(sessionId, filePath);
109
+ });
110
+
111
+ client.onUnwatchSession((sessionId) => {
112
+ sessionWatcher.unwatchSession(sessionId);
113
+ });
114
+
92
115
  client.onTaskStart(async (task) => {
93
- pendingInstructions.set(task.taskId, task.instruction);
94
116
  try {
95
117
  await adapter.startTask(task.taskId, task.instruction, task.projectPath);
96
118
  } catch (err: unknown) {
@@ -99,8 +121,6 @@ export async function start(): Promise<void> {
99
121
  });
100
122
 
101
123
  client.onTaskResume(async (task) => {
102
- messageStore.storeMessage(task.sessionId, 'USER', task.message);
103
- taskSessionMap.set(task.taskId, task.sessionId);
104
124
  try {
105
125
  await adapter.resumeTask(task.taskId, task.sessionId, task.message, task.projectPath);
106
126
  } catch (err: unknown) {
@@ -112,19 +132,9 @@ export async function start(): Promise<void> {
112
132
  await adapter.cancelTask(taskId);
113
133
  });
114
134
 
135
+ // cursor-agent always writes to transcript files – use them as the single source of truth
115
136
  client.onGetMessages((req) => {
116
- const result = messageStore.getMessages(
117
- req.sessionId,
118
- req.limit,
119
- req.beforeUuid,
120
- req.afterUuid
121
- );
122
- return {
123
- messages: result.messages,
124
- hasMore: result.hasMore,
125
- oldestUuid: result.oldestUuid,
126
- newestUuid: result.newestUuid,
127
- };
137
+ return readSessionMessages(req.sessionId, req.limit, req.beforeUuid, req.afterUuid);
128
138
  });
129
139
 
130
140
  client.onVersionStatus((msg) => {
@@ -135,6 +145,7 @@ export async function start(): Promise<void> {
135
145
 
136
146
  const shutdown = async () => {
137
147
  console.log('\nShutting down...');
148
+ sessionWatcher.unwatchAll();
138
149
  await adapter.stopAll();
139
150
  await client.disconnect();
140
151
  configManager.deletePidFile();
@@ -1,7 +1,12 @@
1
- import { execSync } from 'child_process';
1
+ import { execSync, spawn } from 'child_process';
2
2
  import { readFileSync } from 'fs';
3
3
  import { join } from 'path';
4
+ import { ConfigManager } from '@cmdctrl/daemon-sdk';
5
+ import { stop } from './stop';
4
6
 
7
+ const configManager = new ConfigManager('cursor-cli');
8
+
9
+ // Get the current version from package.json
5
10
  function getCurrentVersion(): string {
6
11
  try {
7
12
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
@@ -16,6 +21,7 @@ function getCurrentVersion(): string {
16
21
  }
17
22
  }
18
23
 
24
+ // Fetch the latest version from npm registry
19
25
  async function getLatestVersion(packageName: string): Promise<string | null> {
20
26
  try {
21
27
  const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
@@ -30,6 +36,7 @@ async function getLatestVersion(packageName: string): Promise<string | null> {
30
36
  export async function update(): Promise<void> {
31
37
  const packageName = '@cmdctrl/cursor-cli';
32
38
  const currentVersion = getCurrentVersion();
39
+ const wasRunning = configManager.isDaemonRunning();
33
40
 
34
41
  console.log(`Current version: ${currentVersion}`);
35
42
  console.log(`Checking for updates...`);
@@ -46,6 +53,14 @@ export async function update(): Promise<void> {
46
53
  return;
47
54
  }
48
55
 
56
+ // Stop daemon before updating so the old process doesn't hold stale code
57
+ if (wasRunning) {
58
+ console.log('Stopping daemon before update...');
59
+ stop();
60
+ // Wait for stop to complete (uses setTimeout internally)
61
+ await new Promise((resolve) => setTimeout(resolve, 3000));
62
+ }
63
+
49
64
  console.log(`Updating ${packageName}: v${currentVersion} → v${latestVersion}`);
50
65
 
51
66
  try {
@@ -58,6 +73,7 @@ export async function update(): Promise<void> {
58
73
  process.exit(1);
59
74
  }
60
75
 
76
+ // Verify the update
61
77
  try {
62
78
  const result = execSync(`cmdctrl-cursor-cli --version`, { encoding: 'utf-8' }).trim();
63
79
  console.log(`\nUpdated successfully to v${result}`);
@@ -65,6 +81,14 @@ export async function update(): Promise<void> {
65
81
  console.log(`\nUpdate installed. Run 'cmdctrl-cursor-cli --version' to verify.`);
66
82
  }
67
83
 
68
- console.log('\nIf the daemon is running, restart it:');
69
- console.log(' cmdctrl-cursor-cli stop && cmdctrl-cursor-cli start');
84
+ // Restart daemon if it was running before update
85
+ if (wasRunning) {
86
+ console.log('Restarting daemon...');
87
+ const child = spawn('cmdctrl-cursor-cli', ['start'], {
88
+ detached: true,
89
+ stdio: 'ignore',
90
+ });
91
+ child.unref();
92
+ console.log('Daemon restarted.');
93
+ }
70
94
  }