clideck 1.31.7 → 1.31.9
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/package.json +1 -1
- package/server.js +11 -0
- package/session-ask.js +7 -2
- package/single-instance.js +51 -0
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -49,6 +49,15 @@ function checkSelfUpdate() {
|
|
|
49
49
|
|
|
50
50
|
checkSelfUpdate().then(() => {
|
|
51
51
|
|
|
52
|
+
const { acquireServerLock, removeLockIfOwned } = require('./single-instance');
|
|
53
|
+
const serverLock = acquireServerLock();
|
|
54
|
+
if (!serverLock.ok) {
|
|
55
|
+
const url = serverLock.lock?.url || `http://127.0.0.1:${serverLock.lock?.port || PORT}`;
|
|
56
|
+
const hint = terminalLink(url);
|
|
57
|
+
console.log(`CliDeck is already running at ${hint}`);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
const { onConnection } = require('./handlers');
|
|
53
62
|
const sessions = require('./sessions');
|
|
54
63
|
|
|
@@ -301,10 +310,12 @@ function onShutdown() {
|
|
|
301
310
|
plugins.shutdown();
|
|
302
311
|
activity.stop();
|
|
303
312
|
sessions.shutdown(getConfig());
|
|
313
|
+
removeLockIfOwned();
|
|
304
314
|
process.exit(0);
|
|
305
315
|
}
|
|
306
316
|
process.on('SIGINT', onShutdown);
|
|
307
317
|
process.on('SIGTERM', onShutdown);
|
|
318
|
+
process.on('exit', removeLockIfOwned);
|
|
308
319
|
|
|
309
320
|
server.listen(PORT, HOST, () => {
|
|
310
321
|
const v = require('./package.json').version;
|
package/session-ask.js
CHANGED
|
@@ -3,6 +3,8 @@ const transcript = require('./transcript');
|
|
|
3
3
|
const MAX_BODY = 2 * 1024 * 1024;
|
|
4
4
|
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
5
5
|
const MAX_TIMEOUT_MS = 60 * 60 * 1000;
|
|
6
|
+
const BRACKETED_PASTE_START = '\x1b[200~';
|
|
7
|
+
const BRACKETED_PASTE_END = '\x1b[201~';
|
|
6
8
|
|
|
7
9
|
function sendJson(res, status, payload) {
|
|
8
10
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
@@ -99,7 +101,8 @@ function submitAskInput(sessionsApi, targetId, message) {
|
|
|
99
101
|
const sessions = sessionsApi.getSessions();
|
|
100
102
|
const timers = [];
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
const payload = `\n\n${message}`;
|
|
105
|
+
sessionsApi.input({ id: targetId, data: `${BRACKETED_PASTE_START}${payload}${BRACKETED_PASTE_END}` });
|
|
103
106
|
const delay = askSubmitDelay(message);
|
|
104
107
|
timers.push(setTimeout(() => sessionsApi.input({ id: targetId, data: '\r' }), delay));
|
|
105
108
|
timers.push(setTimeout(() => {
|
|
@@ -171,7 +174,9 @@ async function askSession(payload, sessionsApi) {
|
|
|
171
174
|
if (!caller) throw jsonError('Caller session is not active', 404);
|
|
172
175
|
|
|
173
176
|
const [targetId, target] = findTarget(sessions, callerId, caller, payload.target);
|
|
174
|
-
if (target.working)
|
|
177
|
+
if (target.working) {
|
|
178
|
+
throw jsonError(`Target session "${target.name}" is busy. CliDeck ask only sends to idle sessions. Try again later, choose another idle session, or ask the user how to proceed.`, 409);
|
|
179
|
+
}
|
|
175
180
|
|
|
176
181
|
const message = String(payload.message || '').trim();
|
|
177
182
|
if (!message) throw jsonError('Message is required');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { existsSync, readFileSync, writeFileSync, unlinkSync } = require('fs');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { DATA_DIR } = require('./paths');
|
|
4
|
+
const { PORT, HOST, localUrl } = require('./runtime');
|
|
5
|
+
|
|
6
|
+
const LOCK_PATH = join(DATA_DIR, 'server.lock');
|
|
7
|
+
|
|
8
|
+
function isPidAlive(pid) {
|
|
9
|
+
const n = Number(pid);
|
|
10
|
+
if (!Number.isInteger(n) || n <= 0) return false;
|
|
11
|
+
try {
|
|
12
|
+
process.kill(n, 0);
|
|
13
|
+
return true;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return e.code === 'EPERM';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readLock() {
|
|
20
|
+
if (!existsSync(LOCK_PATH)) return null;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(readFileSync(LOCK_PATH, 'utf8'));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function removeLockIfOwned() {
|
|
29
|
+
const lock = readLock();
|
|
30
|
+
if (!lock || lock.pid !== process.pid) return;
|
|
31
|
+
try { unlinkSync(LOCK_PATH); } catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function acquireServerLock() {
|
|
35
|
+
const existing = readLock();
|
|
36
|
+
if (existing && existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
37
|
+
return { ok: false, lock: existing };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lock = {
|
|
41
|
+
pid: process.pid,
|
|
42
|
+
host: HOST,
|
|
43
|
+
port: PORT,
|
|
44
|
+
url: localUrl(),
|
|
45
|
+
startedAt: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
writeFileSync(LOCK_PATH, JSON.stringify(lock, null, 2) + '\n');
|
|
48
|
+
return { ok: true, lock };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { acquireServerLock, removeLockIfOwned, isPidAlive, LOCK_PATH };
|