@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.
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +30 -30
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +24 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/session-discovery.d.ts +62 -0
- package/dist/session-discovery.d.ts.map +1 -0
- package/dist/session-discovery.js +324 -0
- package/dist/session-discovery.js.map +1 -0
- package/dist/session-watcher.d.ts +38 -0
- package/dist/session-watcher.d.ts.map +1 -0
- package/dist/session-watcher.js +175 -0
- package/dist/session-watcher.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/start.ts +47 -36
- package/src/commands/update.ts +27 -3
- package/src/session-discovery.ts +328 -0
- package/src/session-watcher.ts +182 -0
|
@@ -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
package/src/commands/start.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
client.setSessionsProvider(() => discoverSessions(managedSessionIds));
|
|
93
|
+
|
|
81
94
|
sendEvent = (taskId, eventType, data) => {
|
|
82
|
-
//
|
|
83
|
-
//
|
|
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
|
-
|
|
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();
|
package/src/commands/update.ts
CHANGED
|
@@ -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
|
-
|
|
69
|
-
|
|
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
|
}
|