clideck 1.31.12 → 1.31.13
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/README.md +2 -4
- package/assets/clideck-ask.png +0 -0
- package/handlers.js +34 -4
- package/package.json +1 -1
- package/plugin-loader.js +21 -1
- package/plugins/voice-input/index.js +28 -18
- package/public/js/app.js +38 -22
package/README.md
CHANGED
|
@@ -51,14 +51,12 @@ clideck --port 4001
|
|
|
51
51
|
|
|
52
52
|
**Session resume** - close the lid, reopen tomorrow, pick up where things left off. each agent's session ID is captured automatically.
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**Ask another session** - from inside any CliDeck session, an agent can consult another session and get the answer back as command output:
|
|
55
55
|
|
|
56
56
|
<p align="center">
|
|
57
|
-
<img src="assets/
|
|
57
|
+
<img src="assets/clideck-ask.png" width="720" alt="One agent asking another session and getting findings back">
|
|
58
58
|
</p>
|
|
59
59
|
|
|
60
|
-
**Ask another session** - from inside any CliDeck session, an agent can consult another session and get the answer back as command output:
|
|
61
|
-
|
|
62
60
|
```bash
|
|
63
61
|
clideck agents
|
|
64
62
|
clideck ask --session "Reviewer" --message "Review this output and return findings." --timeout 10m
|
|
Binary file
|
package/handlers.js
CHANGED
|
@@ -100,9 +100,9 @@ function configRootFor(preset, cmd) {
|
|
|
100
100
|
return os.homedir();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function checkRemoteUpdate(ws) {
|
|
103
|
+
function checkRemoteUpdate(ws, force = false) {
|
|
104
104
|
const now = Date.now();
|
|
105
|
-
if (remoteUpdateCache && now - remoteUpdateCheckedAt < REMOTE_UPDATE_INTERVAL) {
|
|
105
|
+
if (!force && remoteUpdateCache && now - remoteUpdateCheckedAt < REMOTE_UPDATE_INTERVAL) {
|
|
106
106
|
ws.send(JSON.stringify({ type: 'remote.update', checked: true, ...remoteUpdateCache }));
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
@@ -573,7 +573,7 @@ function onConnection(ws) {
|
|
|
573
573
|
try { ws.send(JSON.stringify({ type: 'remote.status', installed: true, ...JSON.parse(stdout) })); }
|
|
574
574
|
catch { ws.send(JSON.stringify({ type: 'remote.status', installed: true })); }
|
|
575
575
|
});
|
|
576
|
-
checkRemoteUpdate(ws);
|
|
576
|
+
checkRemoteUpdate(ws, !!msg.forceUpdate);
|
|
577
577
|
break;
|
|
578
578
|
}
|
|
579
579
|
|
|
@@ -602,7 +602,25 @@ function onConnection(ws) {
|
|
|
602
602
|
break;
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
+
case 'remote.voice.transcribe': {
|
|
606
|
+
const requestId = String(msg.requestId || '');
|
|
607
|
+
const replyError = (error) => ws.send(JSON.stringify({ type: 'remote.voice.error', requestId, error }));
|
|
608
|
+
if (!plugins.hasCapability('voice-input', 'transcribeAudio')) {
|
|
609
|
+
replyError('Install the Voice Input plugin in CliDeck first.');
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
if (typeof msg.audio !== 'string' || !msg.audio) {
|
|
613
|
+
replyError('No audio received.');
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
plugins.invoke('voice-input', 'transcribeAudio', { audio: msg.audio })
|
|
617
|
+
.then(result => ws.send(JSON.stringify({ type: 'remote.voice.result', requestId, ...result })))
|
|
618
|
+
.catch(e => replyError(e.message || 'Voice transcription failed.'));
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
|
|
605
622
|
case 'remote.install': {
|
|
623
|
+
const update = !!msg.update;
|
|
606
624
|
const proc = require('child_process').spawn('npm', ['install', '-g', 'clideck-remote'], {
|
|
607
625
|
shell: true, stdio: ['ignore', 'pipe', 'pipe'],
|
|
608
626
|
});
|
|
@@ -610,7 +628,19 @@ function onConnection(ws) {
|
|
|
610
628
|
proc.stderr.on('data', d => ws.send(JSON.stringify({ type: 'remote.install.progress', text: d.toString() })));
|
|
611
629
|
proc.on('close', code => {
|
|
612
630
|
remoteUpdateCache = null;
|
|
613
|
-
|
|
631
|
+
if (code !== 0 || !update) {
|
|
632
|
+
ws.send(JSON.stringify({ type: 'remote.install.done', success: code === 0, update }));
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
require('child_process').execFile('clideck-remote', ['restart', '--json'], { timeout: 10000, shell: process.platform === 'win32', env: remoteCliEnv() }, (err, stdout) => {
|
|
636
|
+
if (err) {
|
|
637
|
+
ws.send(JSON.stringify({ type: 'remote.install.done', success: false, update, error: err.message }));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
let restart = null;
|
|
641
|
+
try { restart = JSON.parse(stdout); } catch {}
|
|
642
|
+
ws.send(JSON.stringify({ type: 'remote.install.done', success: true, update, restart }));
|
|
643
|
+
});
|
|
614
644
|
});
|
|
615
645
|
break;
|
|
616
646
|
}
|
package/package.json
CHANGED
package/plugin-loader.js
CHANGED
|
@@ -67,6 +67,7 @@ let createSessionFn = null;
|
|
|
67
67
|
let closeSessionFn = null;
|
|
68
68
|
const settingsChangeHandlers = new Map(); // pluginId → [fn]
|
|
69
69
|
const sessionPills = new Map(); // pillId → { pluginId, id, title, projectId, working, statusText, icon, logs[] }
|
|
70
|
+
const backendHandlers = new Map(); // pluginId.name → fn
|
|
70
71
|
|
|
71
72
|
function removeHooks(pluginId) {
|
|
72
73
|
for (const arr of [inputHooks, outputHooks, statusHooks, transcriptHooks, menuHooks, configHooks]) {
|
|
@@ -77,6 +78,9 @@ function removeHooks(pluginId) {
|
|
|
77
78
|
for (const key of frontendHandlers.keys()) {
|
|
78
79
|
if (key.startsWith(`plugin.${pluginId}.`)) frontendHandlers.delete(key);
|
|
79
80
|
}
|
|
81
|
+
for (const key of backendHandlers.keys()) {
|
|
82
|
+
if (key.startsWith(`${pluginId}.`)) backendHandlers.delete(key);
|
|
83
|
+
}
|
|
80
84
|
settingsChangeHandlers.delete(pluginId);
|
|
81
85
|
for (const [id, pill] of sessionPills) {
|
|
82
86
|
if (pill.pluginId === pluginId) {
|
|
@@ -202,6 +206,11 @@ function buildApi(pluginId, pluginDir, state) {
|
|
|
202
206
|
onFrontendMessage(event, fn) {
|
|
203
207
|
frontendHandlers.set(`plugin.${pluginId}.${event}`, fn);
|
|
204
208
|
},
|
|
209
|
+
expose(name, fn) {
|
|
210
|
+
if (typeof name === 'string' && name && typeof fn === 'function') {
|
|
211
|
+
backendHandlers.set(`${pluginId}.${name}`, fn);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
205
214
|
|
|
206
215
|
getSession(id) {
|
|
207
216
|
const s = sessionsFn?.()?.get(id);
|
|
@@ -423,6 +432,17 @@ function handleMessage(msg) {
|
|
|
423
432
|
return true;
|
|
424
433
|
}
|
|
425
434
|
|
|
435
|
+
function hasCapability(pluginId, name) {
|
|
436
|
+
return backendHandlers.has(`${pluginId}.${name}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function invoke(pluginId, name, data = {}) {
|
|
440
|
+
const key = `${pluginId}.${name}`;
|
|
441
|
+
const fn = backendHandlers.get(key);
|
|
442
|
+
if (!fn) throw new Error(`Plugin capability not available: ${key}`);
|
|
443
|
+
return await fn(data);
|
|
444
|
+
}
|
|
445
|
+
|
|
426
446
|
function getInfo() {
|
|
427
447
|
const cfg = getConfigFn?.();
|
|
428
448
|
const installed = [...plugins.values()].map(p => ({
|
|
@@ -561,6 +581,6 @@ module.exports = {
|
|
|
561
581
|
PLUGINS_DIR, BUNDLED_IDS,
|
|
562
582
|
init, shutdown,
|
|
563
583
|
transformInput, notifyOutput, notifyStatus, notifyTranscript, notifyMenu, notifyConfig, clearStatus, isWorking, shouldAutoApproveMenu,
|
|
564
|
-
handleMessage, updateSetting, getInfo, resolveFile, installPlugin, removePlugin,
|
|
584
|
+
handleMessage, hasCapability, invoke, updateSetting, getInfo, resolveFile, installPlugin, removePlugin,
|
|
565
585
|
getPills, getPillLogs,
|
|
566
586
|
};
|
|
@@ -244,28 +244,38 @@ module.exports = {
|
|
|
244
244
|
return { text: data.text || '', language: data.language || 'unknown', avg_logprob: null };
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// --- Transcription API ---
|
|
248
|
+
|
|
249
|
+
async function transcribeAudio(audio) {
|
|
250
|
+
if (!api.getSetting('enabled')) {
|
|
251
|
+
throw new Error('Enable the Voice Input plugin in CliDeck first.');
|
|
252
|
+
}
|
|
253
|
+
const backend = api.getSetting('backend');
|
|
254
|
+
let result;
|
|
255
|
+
if (backend === 'local') {
|
|
256
|
+
if (!worker) await startLocal();
|
|
257
|
+
if (!worker) throw new Error('Local model not running. Enable plugin with local backend to start.');
|
|
258
|
+
result = await workerCmd('transcribe', {
|
|
259
|
+
audio,
|
|
260
|
+
lang: api.getSetting('language') || 'auto',
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
result = await transcribeOpenAI(audio);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const text = processText(result.text || '');
|
|
267
|
+
if (!text) return { text: '', skipped: true };
|
|
268
|
+
return { text, language: result.language, inferenceTime: result.inference_time };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
api.expose('transcribeAudio', ({ audio }) => transcribeAudio(audio));
|
|
272
|
+
|
|
247
273
|
// --- Message handlers ---
|
|
248
274
|
|
|
249
275
|
api.onFrontendMessage('transcribe', async (msg) => {
|
|
250
|
-
const backend = api.getSetting('backend');
|
|
251
276
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!worker) { api.sendToFrontend('error', { error: 'Local model not running. Enable plugin with local backend to start.' }); return; }
|
|
255
|
-
result = await workerCmd('transcribe', {
|
|
256
|
-
audio: msg.audio,
|
|
257
|
-
lang: api.getSetting('language') || 'auto',
|
|
258
|
-
});
|
|
259
|
-
} else {
|
|
260
|
-
result = await transcribeOpenAI(msg.audio);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const text = processText(result.text || '');
|
|
264
|
-
if (!text) {
|
|
265
|
-
api.sendToFrontend('result', { text: '', skipped: true, sessionId: msg.sessionId });
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
api.sendToFrontend('result', { text, language: result.language, inferenceTime: result.inference_time, sessionId: msg.sessionId });
|
|
277
|
+
const result = await transcribeAudio(msg.audio);
|
|
278
|
+
api.sendToFrontend('result', { ...result, sessionId: msg.sessionId });
|
|
269
279
|
} catch (e) {
|
|
270
280
|
api.log(`transcribe: ${e.message}`);
|
|
271
281
|
api.sendToFrontend('error', { error: e.message });
|
package/public/js/app.js
CHANGED
|
@@ -45,7 +45,7 @@ function connect() {
|
|
|
45
45
|
reconnectReplaySkip = new Set(state.terms.keys());
|
|
46
46
|
setServerConnectionState(true);
|
|
47
47
|
flushQueuedSends();
|
|
48
|
-
send({ type: 'remote.status' });
|
|
48
|
+
send({ type: 'remote.status', forceUpdate: true });
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
state.ws.onmessage = ({ data }) => {
|
|
@@ -364,10 +364,13 @@ function connect() {
|
|
|
364
364
|
appendInstallLog(msg.text);
|
|
365
365
|
break;
|
|
366
366
|
case 'remote.install.done':
|
|
367
|
-
handleInstallDone(msg
|
|
367
|
+
handleInstallDone(msg);
|
|
368
368
|
break;
|
|
369
369
|
case 'remote.update':
|
|
370
370
|
remoteUpdateInfo = msg?.available ? msg : null;
|
|
371
|
+
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
372
|
+
startRemoteInstall({ update: true, auto: true });
|
|
373
|
+
}
|
|
371
374
|
if (remotePreflight?.pending) {
|
|
372
375
|
remotePreflight.updateSeen = true;
|
|
373
376
|
finishRemotePreflight();
|
|
@@ -1161,6 +1164,7 @@ let remoteStatsTimer = null;
|
|
|
1161
1164
|
let remoteUpdateInfo = null;
|
|
1162
1165
|
let remotePreflight = null;
|
|
1163
1166
|
let remoteLastStatus = null;
|
|
1167
|
+
let remoteInstallMode = null;
|
|
1164
1168
|
|
|
1165
1169
|
function startRemotePoll() {
|
|
1166
1170
|
stopRemotePoll();
|
|
@@ -1192,15 +1196,6 @@ function showRemoteIntro(opts = {}) {
|
|
|
1192
1196
|
setRemotePane('intro');
|
|
1193
1197
|
}
|
|
1194
1198
|
|
|
1195
|
-
function showRemoteUpdateRequired() {
|
|
1196
|
-
showRemoteIntro({
|
|
1197
|
-
title: 'Update Required',
|
|
1198
|
-
text: `Version ${remoteUpdateInfo.latest} is available. Update CliDeck Remote to continue with mobile pairing on this machine.`,
|
|
1199
|
-
foot: `Installed: <code class="text-slate-500">${esc(remoteUpdateInfo.installed)}</code> · Latest: <code class="text-slate-500">${esc(remoteUpdateInfo.latest)}</code>`,
|
|
1200
|
-
button: 'Update to Continue',
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
1199
|
function finishRemotePreflight() {
|
|
1205
1200
|
if (!remotePreflight?.pending || !remotePreflight.statusSeen || !remotePreflight.updateSeen) return;
|
|
1206
1201
|
remotePreflight = null;
|
|
@@ -1209,7 +1204,7 @@ function finishRemotePreflight() {
|
|
|
1209
1204
|
return;
|
|
1210
1205
|
}
|
|
1211
1206
|
if (remoteUpdateInfo?.available) {
|
|
1212
|
-
|
|
1207
|
+
startRemoteInstall({ update: true, auto: true });
|
|
1213
1208
|
return;
|
|
1214
1209
|
}
|
|
1215
1210
|
if (remoteState === 'idle') {
|
|
@@ -1368,8 +1363,9 @@ function handleRemoteStatus(msg) {
|
|
|
1368
1363
|
stopRemotePoll();
|
|
1369
1364
|
if (wasPaired) { stopRemoteStats(); setRemoteLock(false); }
|
|
1370
1365
|
}
|
|
1371
|
-
if (remoteUpdateInfo?.available &&
|
|
1372
|
-
|
|
1366
|
+
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
1367
|
+
startRemoteInstall({ update: true, auto: true });
|
|
1368
|
+
return;
|
|
1373
1369
|
}
|
|
1374
1370
|
updateRemoteButton();
|
|
1375
1371
|
if (remotePreflight?.pending) {
|
|
@@ -1387,8 +1383,8 @@ function handleRemotePaired(msg) {
|
|
|
1387
1383
|
else qrImg.classList.add('hidden');
|
|
1388
1384
|
updateRemoteButton();
|
|
1389
1385
|
startRemotePoll();
|
|
1390
|
-
if (remoteUpdateInfo?.available &&
|
|
1391
|
-
|
|
1386
|
+
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
1387
|
+
startRemoteInstall({ update: true, auto: true });
|
|
1392
1388
|
return;
|
|
1393
1389
|
}
|
|
1394
1390
|
if (remotePreflight?.pending) {
|
|
@@ -1422,17 +1418,39 @@ function appendInstallLog(text) {
|
|
|
1422
1418
|
log.scrollTop = log.scrollHeight;
|
|
1423
1419
|
}
|
|
1424
1420
|
|
|
1425
|
-
function
|
|
1421
|
+
function startRemoteInstall(opts = {}) {
|
|
1422
|
+
remoteInstallMode = { update: !!opts.update, auto: !!opts.auto };
|
|
1423
|
+
const log = document.getElementById('remote-install-log');
|
|
1424
|
+
log.textContent = '';
|
|
1425
|
+
if (remoteInstallMode.update) {
|
|
1426
|
+
appendInstallLog(`Updating clideck-remote to ${remoteUpdateInfo?.latest || 'latest'}...\n`);
|
|
1427
|
+
}
|
|
1428
|
+
setRemotePane('installing');
|
|
1429
|
+
if (!remoteModalOpen) openRemoteModal();
|
|
1430
|
+
send({ type: 'remote.install', update: remoteInstallMode.update });
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function handleInstallDone(msg) {
|
|
1434
|
+
const success = !!msg?.success;
|
|
1435
|
+
const wasUpdate = !!msg?.update || !!remoteInstallMode?.update;
|
|
1436
|
+
remoteInstallMode = null;
|
|
1426
1437
|
if (success) {
|
|
1427
1438
|
remoteInstalled = true;
|
|
1428
1439
|
remoteUpdateInfo = null;
|
|
1440
|
+
if (wasUpdate) {
|
|
1441
|
+
remoteState = 'connecting';
|
|
1442
|
+
setRemotePane('connecting');
|
|
1443
|
+
send({ type: 'remote.status', forceUpdate: true });
|
|
1444
|
+
startRemotePoll();
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1429
1447
|
// Installed — go straight to pairing
|
|
1430
1448
|
remoteState = 'connecting';
|
|
1431
1449
|
setRemotePane('connecting');
|
|
1432
1450
|
send({ type: 'remote.pair' });
|
|
1433
1451
|
} else {
|
|
1434
1452
|
const log = document.getElementById('remote-install-log');
|
|
1435
|
-
log.textContent +=
|
|
1453
|
+
log.textContent += `\n— ${msg?.error || 'Install failed'}. Check permissions or run manually:\n npm install -g clideck-remote\n`;
|
|
1436
1454
|
log.scrollTop = log.scrollHeight;
|
|
1437
1455
|
}
|
|
1438
1456
|
}
|
|
@@ -1450,14 +1468,12 @@ btnRemote.addEventListener('click', () => {
|
|
|
1450
1468
|
remotePreflight = { pending: true, statusSeen: false, updateSeen: false };
|
|
1451
1469
|
setRemotePane('connecting');
|
|
1452
1470
|
openRemoteModal();
|
|
1453
|
-
send({ type: 'remote.status' });
|
|
1471
|
+
send({ type: 'remote.status', forceUpdate: true });
|
|
1454
1472
|
});
|
|
1455
1473
|
|
|
1456
1474
|
// Install button
|
|
1457
1475
|
document.getElementById('remote-add').addEventListener('click', () => {
|
|
1458
|
-
|
|
1459
|
-
setRemotePane('installing');
|
|
1460
|
-
send({ type: 'remote.install' });
|
|
1476
|
+
startRemoteInstall({ update: !!(remoteInstalled && remoteUpdateInfo?.available) });
|
|
1461
1477
|
});
|
|
1462
1478
|
|
|
1463
1479
|
// Close / disconnect
|