clideck 1.31.3 → 1.31.5
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 +1 -1
- package/codex-hooks.js +7 -7
- package/config.js +3 -43
- package/handlers.js +71 -40
- package/package.json +1 -1
- package/plugin-loader.js +2 -3
- package/public/js/app.js +27 -10
- package/public/js/creator.js +44 -23
- package/public/js/hotkeys.js +12 -0
- package/public/js/settings.js +113 -24
- package/public/js/terminals.js +46 -0
- package/public/tailwind.css +1 -1
- package/sessions.js +35 -58
- package/transcript.js +5 -1
- package/public/js/roles.js +0 -112
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ the main problem with using multiple agents is not starting them. it is managing
|
|
|
26
26
|
|
|
27
27
|
Terminal multiplexers are great at panes. clideck is about conversations.
|
|
28
28
|
|
|
29
|
-
A pane grid is flat. agent work usually is not. projects,
|
|
29
|
+
A pane grid is flat. agent work usually is not. projects, previews, timestamps, notifications, resume, and sometimes a bit of routing between specialists all fit more naturally into a chat app layout. it also maps naturally to mobile, so the same mental model works on desktop and phone.
|
|
30
30
|
|
|
31
31
|
## Quick start
|
|
32
32
|
|
package/codex-hooks.js
CHANGED
|
@@ -18,7 +18,7 @@ function readHooksFile(path) {
|
|
|
18
18
|
if (!existsSync(path)) return { hooks: {} };
|
|
19
19
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
20
20
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
21
|
-
throw new Error('Invalid
|
|
21
|
+
throw new Error('Invalid Codex hooks.json shape');
|
|
22
22
|
}
|
|
23
23
|
if (!parsed.hooks || typeof parsed.hooks !== 'object' || Array.isArray(parsed.hooks)) parsed.hooks = {};
|
|
24
24
|
return parsed;
|
|
@@ -33,8 +33,8 @@ function stripClideckHooks(groups) {
|
|
|
33
33
|
.filter(group => group.hooks.length);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function installCodexHooks(
|
|
37
|
-
const hooksPath = join(
|
|
36
|
+
function installCodexHooks(codexHome, nodePath, helperPath, port) {
|
|
37
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
38
38
|
const doc = readHooksFile(hooksPath);
|
|
39
39
|
|
|
40
40
|
for (const [event, route] of Object.entries(EVENTS)) {
|
|
@@ -53,8 +53,8 @@ function installCodexHooks(home, nodePath, helperPath, port) {
|
|
|
53
53
|
writeFileSync(hooksPath, JSON.stringify(doc, null, 2) + '\n');
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function removeCodexHooks(
|
|
57
|
-
const hooksPath = join(
|
|
56
|
+
function removeCodexHooks(codexHome) {
|
|
57
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
58
58
|
if (!existsSync(hooksPath)) return;
|
|
59
59
|
const doc = readHooksFile(hooksPath);
|
|
60
60
|
|
|
@@ -73,9 +73,9 @@ function removeCodexHooks(home) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function codexHooksHealthy(
|
|
76
|
+
function codexHooksHealthy(codexHome, helperPath, port) {
|
|
77
77
|
try {
|
|
78
|
-
const doc = readHooksFile(join(
|
|
78
|
+
const doc = readHooksFile(join(codexHome, 'hooks.json'));
|
|
79
79
|
for (const [event, route] of Object.entries(EVENTS)) {
|
|
80
80
|
const groups = Array.isArray(doc.hooks?.[event]) ? doc.hooks[event] : [];
|
|
81
81
|
const expected = commandFor(process.execPath.replace(/\\/g, '/'), helperPath, port, route);
|
package/config.js
CHANGED
|
@@ -32,52 +32,13 @@ list your fidings please.`,
|
|
|
32
32
|
},
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
-
const STARTER_ROLES = [
|
|
36
|
-
{
|
|
37
|
-
id: 'starter-role-programmer',
|
|
38
|
-
name: 'Programmer',
|
|
39
|
-
instructions: `You are the main programmer of this project.
|
|
40
|
-
Do you not apply workarounds or bandaids, prefer pure solutions.
|
|
41
|
-
NEVER use plan tool/mode, start to build immediatly and ask questions along the way if any.
|
|
42
|
-
Check if any external findings are valid before applying changes, the reviewer doesnt always updated with the full scope.
|
|
43
|
-
When you done with changes, list concisely what you did.
|
|
44
|
-
|
|
45
|
-
Learn the project quickly if exist. Go over the structure, code and functionality.
|
|
46
|
-
Let me know when you are ready.`,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 'starter-role-reviewer',
|
|
50
|
-
name: 'Reviewer',
|
|
51
|
-
instructions: `You are the code reviewer in this project.
|
|
52
|
-
Your task is check the coder output and list critical / logical design flow issues, ugly workarounds or functionalty you just dont understand why its there. Do not waste time and list insignificunt findings.
|
|
53
|
-
|
|
54
|
-
If you didnt find anything, response with no findings.
|
|
55
|
-
|
|
56
|
-
You never write code!.
|
|
57
|
-
|
|
58
|
-
Quickly learn the project if exist. Go over the structure, code and functionality and let me know when you are ready.`,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 'starter-role-product-manager',
|
|
62
|
-
name: 'Product manager',
|
|
63
|
-
instructions: `You are the product manager of this project, you should understand why we do what we do and what is the best way to do it. You dont care about technical limitations or directions, the only thing matter to you is the user UI/UX and how this agents team will ship a top notch, professional deliveries.
|
|
64
|
-
Do not allow the team to round angles and skip small stuff that will basly impact the user.
|
|
65
|
-
|
|
66
|
-
You never write code!
|
|
67
|
-
You dont use your plan tool/mode - instead you are planning immediatly as you go.
|
|
68
|
-
|
|
69
|
-
Go over the project if exist and understand from the code, documentations and readme what is it and why we do it.
|
|
70
|
-
|
|
71
|
-
Let me know when you are ready`,
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
|
|
75
35
|
const DEFAULTS = {
|
|
76
36
|
defaultPath: join(os.homedir(), 'Documents'),
|
|
77
37
|
commands: [
|
|
78
38
|
{
|
|
79
39
|
id: '1', label: 'Shell', icon: 'terminal', command: defaultShell, enabled: true,
|
|
80
40
|
defaultPath: '', isAgent: false, canResume: false, resumeCommand: null, sessionIdPattern: null,
|
|
41
|
+
env: {},
|
|
81
42
|
},
|
|
82
43
|
],
|
|
83
44
|
confirmClose: true,
|
|
@@ -88,7 +49,6 @@ const DEFAULTS = {
|
|
|
88
49
|
defaultTheme: 'catppuccin-mocha',
|
|
89
50
|
defaultShell,
|
|
90
51
|
prompts: [],
|
|
91
|
-
roles: [],
|
|
92
52
|
projects: [],
|
|
93
53
|
};
|
|
94
54
|
|
|
@@ -155,6 +115,7 @@ function migrate(cfg) {
|
|
|
155
115
|
if (cmd.resumeCommand === undefined) cmd.resumeCommand = preset?.resumeCommand || null;
|
|
156
116
|
if (cmd.sessionIdPattern === undefined) cmd.sessionIdPattern = preset?.sessionIdPattern || null;
|
|
157
117
|
if (cmd.outputMarker === undefined) cmd.outputMarker = preset?.outputMarker || null;
|
|
118
|
+
if (!cmd.env || typeof cmd.env !== 'object' || Array.isArray(cmd.env)) cmd.env = {};
|
|
158
119
|
// Claude Code telemetry is built-in, always on
|
|
159
120
|
if (preset?.telemetryEnabled === true) cmd.telemetryEnabled = true;
|
|
160
121
|
else if (preset?.presetId === 'claude-code') cmd.telemetryEnabled = true;
|
|
@@ -181,11 +142,11 @@ function migrate(cfg) {
|
|
|
181
142
|
isAgent: preset.isAgent, canResume: preset.canResume,
|
|
182
143
|
resumeCommand: preset.resumeCommand, sessionIdPattern: preset.sessionIdPattern,
|
|
183
144
|
outputMarker: preset.outputMarker || null,
|
|
145
|
+
env: {},
|
|
184
146
|
});
|
|
185
147
|
}
|
|
186
148
|
}
|
|
187
149
|
if (!cfg.projects) cfg.projects = [];
|
|
188
|
-
if (!cfg.roles) cfg.roles = [];
|
|
189
150
|
return cfg;
|
|
190
151
|
}
|
|
191
152
|
|
|
@@ -194,7 +155,6 @@ function load() {
|
|
|
194
155
|
return {
|
|
195
156
|
...deepCopy(DEFAULTS),
|
|
196
157
|
prompts: deepCopy(STARTER_PROMPTS),
|
|
197
|
-
roles: deepCopy(STARTER_ROLES),
|
|
198
158
|
};
|
|
199
159
|
}
|
|
200
160
|
try {
|
package/handlers.js
CHANGED
|
@@ -19,7 +19,8 @@ function clientPresets() {
|
|
|
19
19
|
}
|
|
20
20
|
function filterClientCommands(commands) {
|
|
21
21
|
const allowedPresetIds = new Set(clientPresets().map(p => p.presetId));
|
|
22
|
-
|
|
22
|
+
const knownPresetIds = new Set(presets.map(p => p.presetId));
|
|
23
|
+
return (commands || []).filter(cmd => !cmd.presetId || allowedPresetIds.has(cmd.presetId) || !knownPresetIds.has(cmd.presetId));
|
|
23
24
|
}
|
|
24
25
|
const transcript = require('./transcript');
|
|
25
26
|
const plugins = require('./plugin-loader');
|
|
@@ -70,6 +71,35 @@ function getInstalledVersion(bin) {
|
|
|
70
71
|
return '';
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
function presetForCommand(cmd) {
|
|
75
|
+
if (cmd?.presetId) {
|
|
76
|
+
const preset = presets.find(p => p.presetId === cmd.presetId);
|
|
77
|
+
if (preset) return preset;
|
|
78
|
+
}
|
|
79
|
+
const bin = binName(cmd?.command);
|
|
80
|
+
return presets.find(p => binName(p.command) === bin);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function commandEnv(cmd) {
|
|
84
|
+
return cmd?.env && typeof cmd.env === 'object' && !Array.isArray(cmd.env) ? cmd.env : {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function expandHomePath(value) {
|
|
88
|
+
const text = String(value || '').trim();
|
|
89
|
+
if (!text) return '';
|
|
90
|
+
if (text === '~') return os.homedir();
|
|
91
|
+
if (text.startsWith('~/') || text.startsWith('~\\')) return join(os.homedir(), text.slice(2));
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function configRootFor(preset, cmd) {
|
|
96
|
+
const env = commandEnv(cmd);
|
|
97
|
+
if (preset?.presetId === 'claude-code') return expandHomePath(env.CLAUDE_CONFIG_DIR) || join(os.homedir(), '.claude');
|
|
98
|
+
if (preset?.presetId === 'codex') return expandHomePath(env.CODEX_HOME) || join(os.homedir(), '.codex');
|
|
99
|
+
if (preset?.presetId === 'gemini-cli') return join(expandHomePath(env.GEMINI_CLI_HOME) || os.homedir(), '.gemini');
|
|
100
|
+
return os.homedir();
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
function checkRemoteUpdate(ws) {
|
|
74
104
|
const now = Date.now();
|
|
75
105
|
if (remoteUpdateCache && now - remoteUpdateCheckedAt < REMOTE_UPDATE_INTERVAL) {
|
|
@@ -154,11 +184,11 @@ function codexHooksFeatureEnabled(content) {
|
|
|
154
184
|
return false;
|
|
155
185
|
}
|
|
156
186
|
|
|
157
|
-
function codexConfigLooksHealthy(content, port) {
|
|
187
|
+
function codexConfigLooksHealthy(content, port, codexHome) {
|
|
158
188
|
if (!content.includes('[otel]') || !content.includes(`localhost:${port}`)) return false;
|
|
159
189
|
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
160
190
|
if (!codexHooksFeatureEnabled(content)) return false;
|
|
161
|
-
if (!codexHooksHealthy(
|
|
191
|
+
if (!codexHooksHealthy(codexHome, codexHookPath, port)) return false;
|
|
162
192
|
const notifyLine = content.match(/^\s*notify\s*=\s*\[(.+)\]\s*$/m)?.[1] || '';
|
|
163
193
|
if (!notifyLine.includes('notify-helper')) return false;
|
|
164
194
|
const quoted = [...notifyLine.matchAll(/"([^"]+)"/g)].map(m => m[1]);
|
|
@@ -167,7 +197,6 @@ function codexConfigLooksHealthy(content, port) {
|
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
function detectTelemetryConfig(c) {
|
|
170
|
-
const home = os.homedir();
|
|
171
200
|
const port = String(PORT);
|
|
172
201
|
let changed = false;
|
|
173
202
|
const attemptedRepairs = new Set();
|
|
@@ -175,14 +204,13 @@ function detectTelemetryConfig(c) {
|
|
|
175
204
|
for (let pass = 0; pass < 2; pass++) {
|
|
176
205
|
let repairedAny = false;
|
|
177
206
|
for (const cmd of c.commands || []) {
|
|
178
|
-
const
|
|
179
|
-
const preset = presets.find(p => binName(p.command) === bin);
|
|
207
|
+
const preset = presetForCommand(cmd);
|
|
180
208
|
if (!preset) continue;
|
|
181
209
|
let detected = false;
|
|
182
210
|
let reason = '';
|
|
183
211
|
if (preset.presetId === 'claude-code') {
|
|
184
212
|
try {
|
|
185
|
-
const s = JSON.parse(readFileSync(join(
|
|
213
|
+
const s = JSON.parse(readFileSync(join(configRootFor(preset, cmd), 'settings.json'), 'utf8'));
|
|
186
214
|
const hooks = s.hooks || {};
|
|
187
215
|
detected = hasExistingHook(hooks.UserPromptSubmit, 'claude-hook.js', 'start')
|
|
188
216
|
&& hasExistingHook(hooks.Stop, 'claude-hook.js', 'stop')
|
|
@@ -193,13 +221,14 @@ function detectTelemetryConfig(c) {
|
|
|
193
221
|
} catch {}
|
|
194
222
|
} else if (preset.presetId === 'codex') {
|
|
195
223
|
try {
|
|
196
|
-
const
|
|
197
|
-
|
|
224
|
+
const codexHome = configRootFor(preset, cmd);
|
|
225
|
+
const content = readFileSync(join(codexHome, 'config.toml'), 'utf8');
|
|
226
|
+
detected = codexConfigLooksHealthy(content, port, codexHome);
|
|
198
227
|
if (!detected) reason = 'Needs re-patch';
|
|
199
228
|
} catch {}
|
|
200
229
|
} else if (preset.presetId === 'gemini-cli') {
|
|
201
230
|
try {
|
|
202
|
-
const s = JSON.parse(readFileSync(join(
|
|
231
|
+
const s = JSON.parse(readFileSync(join(configRootFor(preset, cmd), 'settings.json'), 'utf8'));
|
|
203
232
|
const hooks = s.hooks || {};
|
|
204
233
|
detected = hasExistingHook(hooks.BeforeAgent, 'gemini-hook.js', 'start')
|
|
205
234
|
&& hasExistingHook(hooks.AfterAgent, 'gemini-hook.js', 'stop')
|
|
@@ -214,9 +243,9 @@ function detectTelemetryConfig(c) {
|
|
|
214
243
|
if (preset.available && preset.minVersion && !preset.versionOk) {
|
|
215
244
|
detected = false;
|
|
216
245
|
reason = `Update required (${preset.minVersion}+)`;
|
|
217
|
-
} else if (!detected && cmd.telemetryEnabled && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(preset.presetId)) {
|
|
218
|
-
attemptedRepairs.add(preset.presetId);
|
|
219
|
-
const repaired = applyTelemetryConfig(preset);
|
|
246
|
+
} else if (!detected && cmd.telemetryEnabled && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(cmd.id || preset.presetId)) {
|
|
247
|
+
attemptedRepairs.add(cmd.id || preset.presetId);
|
|
248
|
+
const repaired = applyTelemetryConfig(preset, cmd);
|
|
220
249
|
if (repaired.success) {
|
|
221
250
|
repairedAny = true;
|
|
222
251
|
continue;
|
|
@@ -360,11 +389,12 @@ function onConnection(ws) {
|
|
|
360
389
|
}
|
|
361
390
|
|
|
362
391
|
case 'telemetry.autosetup': {
|
|
363
|
-
const
|
|
392
|
+
const targetCmd = msg.commandId ? cfg.commands.find(c => c.id === msg.commandId) : null;
|
|
393
|
+
const preset = targetCmd ? presetForCommand(targetCmd) : presets.find(p => p.presetId === msg.presetId);
|
|
364
394
|
if (!preset?.telemetryAutoSetup) break;
|
|
365
|
-
const result = applyTelemetryConfig(preset);
|
|
395
|
+
const result = applyTelemetryConfig(preset, targetCmd);
|
|
366
396
|
for (const cmd of cfg.commands) {
|
|
367
|
-
if (
|
|
397
|
+
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
368
398
|
cmd.telemetryEnabled = result.success;
|
|
369
399
|
cmd.telemetryStatus = result.success ? { ok: true } : { ok: false, error: result.message };
|
|
370
400
|
// Enable the agent when setup succeeds, disable if it fails
|
|
@@ -375,7 +405,8 @@ function onConnection(ws) {
|
|
|
375
405
|
sessions.broadcast({ type: 'config', config: configForClient() });
|
|
376
406
|
ws.send(JSON.stringify({
|
|
377
407
|
type: 'telemetry.autosetup.result',
|
|
378
|
-
presetId:
|
|
408
|
+
presetId: preset.presetId,
|
|
409
|
+
commandId: msg.commandId || null,
|
|
379
410
|
success: result.success,
|
|
380
411
|
output: result.message,
|
|
381
412
|
}));
|
|
@@ -383,18 +414,19 @@ function onConnection(ws) {
|
|
|
383
414
|
}
|
|
384
415
|
|
|
385
416
|
case 'telemetry.configure': {
|
|
386
|
-
const
|
|
417
|
+
const targetCmd = msg.commandId ? cfg.commands.find(c => c.id === msg.commandId) : null;
|
|
418
|
+
const preset = targetCmd ? presetForCommand(targetCmd) : presets.find(p => p.presetId === msg.presetId);
|
|
387
419
|
if (!preset) break;
|
|
388
420
|
const enable = !!msg.enable;
|
|
389
421
|
let result;
|
|
390
422
|
if (enable) {
|
|
391
|
-
result = applyTelemetryConfig(preset);
|
|
423
|
+
result = applyTelemetryConfig(preset, targetCmd);
|
|
392
424
|
} else {
|
|
393
|
-
result = removeTelemetryConfig(preset);
|
|
425
|
+
result = removeTelemetryConfig(preset, targetCmd);
|
|
394
426
|
}
|
|
395
427
|
// Update all matching commands in config
|
|
396
428
|
for (const cmd of cfg.commands) {
|
|
397
|
-
if (
|
|
429
|
+
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
398
430
|
cmd.telemetryEnabled = enable && result.success;
|
|
399
431
|
cmd.telemetryStatus = enable
|
|
400
432
|
? (result.success ? { ok: true } : { ok: false, error: result.message })
|
|
@@ -576,13 +608,12 @@ function onConnection(ws) {
|
|
|
576
608
|
}
|
|
577
609
|
|
|
578
610
|
// Deterministic telemetry config writers per agent — no AI, no YOLO
|
|
579
|
-
function applyTelemetryConfig(preset) {
|
|
611
|
+
function applyTelemetryConfig(preset, cmd = null) {
|
|
580
612
|
const port = String(PORT);
|
|
581
|
-
const home = os.homedir();
|
|
582
613
|
|
|
583
614
|
try {
|
|
584
615
|
if (preset.presetId === 'claude-code') {
|
|
585
|
-
const configPath = join(
|
|
616
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
586
617
|
let settings = {};
|
|
587
618
|
if (existsSync(configPath)) {
|
|
588
619
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -608,11 +639,12 @@ function applyTelemetryConfig(preset) {
|
|
|
608
639
|
settings.hooks = hooks;
|
|
609
640
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
610
641
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
611
|
-
return { success: true, message:
|
|
642
|
+
return { success: true, message: `Added hooks to ${configPath} — Claude will ask for one-time approval` };
|
|
612
643
|
}
|
|
613
644
|
|
|
614
645
|
if (preset.presetId === 'codex') {
|
|
615
|
-
const
|
|
646
|
+
const codexHome = configRootFor(preset, cmd);
|
|
647
|
+
const configPath = join(codexHome, 'config.toml');
|
|
616
648
|
let content = '';
|
|
617
649
|
if (existsSync(configPath)) content = readFileSync(configPath, 'utf8');
|
|
618
650
|
const hasOtel = content.includes('[otel]');
|
|
@@ -620,7 +652,7 @@ function applyTelemetryConfig(preset) {
|
|
|
620
652
|
const hasNotify = /^\s*notify\s*=.*notify-helper/m.test(content);
|
|
621
653
|
const hasWrongOtel = content.includes(`endpoint = "http://localhost:${port}/v1/logs"`);
|
|
622
654
|
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
623
|
-
const hasHooks = codexHooksFeatureEnabled(content) && codexHooksHealthy(
|
|
655
|
+
const hasHooks = codexHooksFeatureEnabled(content) && codexHooksHealthy(codexHome, codexHookPath, port);
|
|
624
656
|
if (hasOtel && hasCurrentOtel && hasNotify && !hasWrongOtel && hasHooks) {
|
|
625
657
|
return { success: true, message: 'Already configured' };
|
|
626
658
|
}
|
|
@@ -630,12 +662,12 @@ function applyTelemetryConfig(preset) {
|
|
|
630
662
|
if (!valid.ok) return { success: false, message: valid.error };
|
|
631
663
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
632
664
|
writeFileSync(configPath, nextContent);
|
|
633
|
-
installCodexHooks(
|
|
665
|
+
installCodexHooks(codexHome, process.execPath.replace(/\\/g, '/'), codexHookPath, port);
|
|
634
666
|
return { success: true, message: 'Configured. If Codex shows "2 hooks need review", open /hooks and approve the CliDeck hooks once.' };
|
|
635
667
|
}
|
|
636
668
|
|
|
637
669
|
if (preset.presetId === 'gemini-cli') {
|
|
638
|
-
const configPath = join(
|
|
670
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
639
671
|
let settings = {};
|
|
640
672
|
if (existsSync(configPath)) {
|
|
641
673
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -665,7 +697,7 @@ function applyTelemetryConfig(preset) {
|
|
|
665
697
|
if (settings.telemetry?.target === 'local' && /localhost:\d+/.test(String(settings.telemetry?.otlpEndpoint || ''))) delete settings.telemetry;
|
|
666
698
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
667
699
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
668
|
-
return { success: true, message:
|
|
700
|
+
return { success: true, message: `Added CliDeck hooks to ${configPath}` };
|
|
669
701
|
}
|
|
670
702
|
|
|
671
703
|
if (preset.presetId === 'opencode') {
|
|
@@ -684,12 +716,10 @@ function applyTelemetryConfig(preset) {
|
|
|
684
716
|
}
|
|
685
717
|
}
|
|
686
718
|
|
|
687
|
-
function removeTelemetryConfig(preset) {
|
|
688
|
-
const home = os.homedir();
|
|
689
|
-
|
|
719
|
+
function removeTelemetryConfig(preset, cmd = null) {
|
|
690
720
|
try {
|
|
691
721
|
if (preset.presetId === 'claude-code') {
|
|
692
|
-
const configPath = join(
|
|
722
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
693
723
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
694
724
|
let settings = {};
|
|
695
725
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -702,23 +732,24 @@ function removeTelemetryConfig(preset) {
|
|
|
702
732
|
}
|
|
703
733
|
if (!Object.keys(settings.hooks).length) delete settings.hooks;
|
|
704
734
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
705
|
-
return { success: true, message:
|
|
735
|
+
return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
|
|
706
736
|
}
|
|
707
737
|
|
|
708
738
|
if (preset.presetId === 'codex') {
|
|
709
|
-
const
|
|
739
|
+
const codexHome = configRootFor(preset, cmd);
|
|
740
|
+
const configPath = join(codexHome, 'config.toml');
|
|
710
741
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
711
742
|
let content = readFileSync(configPath, 'utf8');
|
|
712
743
|
content = content.replace(/\n?\[otel\][^\[]*/, '');
|
|
713
744
|
content = content.replace(/\n?notify\s*=\s*\[.*?notify-helper.*?\]\s*/g, '');
|
|
714
745
|
content = content.replace(/\n?codex_hooks\s*=\s*(true|false)\s*/g, '\n');
|
|
715
746
|
writeFileSync(configPath, content.trimEnd() + '\n');
|
|
716
|
-
removeCodexHooks(
|
|
717
|
-
return { success: true, message:
|
|
747
|
+
removeCodexHooks(codexHome);
|
|
748
|
+
return { success: true, message: `Removed otel + CliDeck hooks from ${configPath}` };
|
|
718
749
|
}
|
|
719
750
|
|
|
720
751
|
if (preset.presetId === 'gemini-cli') {
|
|
721
|
-
const configPath = join(
|
|
752
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
722
753
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
723
754
|
let settings = {};
|
|
724
755
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -731,7 +762,7 @@ function removeTelemetryConfig(preset) {
|
|
|
731
762
|
if (settings.hooks && !Object.keys(settings.hooks).length) delete settings.hooks;
|
|
732
763
|
if (settings.telemetry?.target === 'local' && /localhost:\d+/.test(String(settings.telemetry?.otlpEndpoint || ''))) delete settings.telemetry;
|
|
733
764
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
734
|
-
return { success: true, message:
|
|
765
|
+
return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
|
|
735
766
|
}
|
|
736
767
|
|
|
737
768
|
if (preset.presetId === 'opencode') {
|
package/package.json
CHANGED
package/plugin-loader.js
CHANGED
|
@@ -207,13 +207,13 @@ function buildApi(pluginId, pluginDir, state) {
|
|
|
207
207
|
const s = sessionsFn?.()?.get(id);
|
|
208
208
|
if (!s) return null;
|
|
209
209
|
const state = sessionStatus.get(id) || '';
|
|
210
|
-
return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId,
|
|
210
|
+
return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: state.startsWith('1:') };
|
|
211
211
|
},
|
|
212
212
|
getSessions() {
|
|
213
213
|
const sessions = sessionsFn?.();
|
|
214
214
|
if (!sessions) return [];
|
|
215
215
|
return [...sessions].map(([id, s]) => ({
|
|
216
|
-
id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId,
|
|
216
|
+
id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: (sessionStatus.get(id) || '').startsWith('1:'),
|
|
217
217
|
}));
|
|
218
218
|
},
|
|
219
219
|
|
|
@@ -232,7 +232,6 @@ function buildApi(pluginId, pluginDir, state) {
|
|
|
232
232
|
inputToSession(id, data) { inputFn?.({ id, data }); },
|
|
233
233
|
setAutoApproveMenu(id, enabled) { enabled ? autoApproveMenus.add(id) : autoApproveMenus.delete(id); },
|
|
234
234
|
|
|
235
|
-
getRoles() { return JSON.parse(JSON.stringify(getConfigFn?.()?.roles || [])); },
|
|
236
235
|
getProjects() { return JSON.parse(JSON.stringify(getConfigFn?.()?.projects || [])); },
|
|
237
236
|
getTranscript(id, n, order) { return transcript.getTurns(id, n || 20, order || 'end'); },
|
|
238
237
|
detectMenu(lines, presetId) { return transcript.detectMenu(lines, presetId); },
|
package/public/js/app.js
CHANGED
|
@@ -16,6 +16,19 @@ import { renderPrompts } from './prompts.js';
|
|
|
16
16
|
const shownAgentHealthToasts = new Set();
|
|
17
17
|
let reconnectReplaySkip = null;
|
|
18
18
|
|
|
19
|
+
function normalizeTerminalHistoryText(text) {
|
|
20
|
+
return `${text || ''}\n`.replace(/\r?\n/g, '\r\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function presetForCommand(cmd) {
|
|
24
|
+
if (cmd?.presetId) {
|
|
25
|
+
const byId = state.presets.find(p => p.presetId === cmd.presetId);
|
|
26
|
+
if (byId) return byId;
|
|
27
|
+
}
|
|
28
|
+
const bin = binName(cmd?.command);
|
|
29
|
+
return state.presets.find(p => binName(p.command) === bin);
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
function connect() {
|
|
20
33
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
21
34
|
state.ws = new WebSocket(`${wsProtocol}//${location.host}`);
|
|
@@ -67,7 +80,9 @@ function connect() {
|
|
|
67
80
|
}
|
|
68
81
|
msg.list.forEach(s => addTerminal(s.id, s.name, s.themeId, s.commandId, s.projectId, s.muted, s.lastPreview, s.presetId));
|
|
69
82
|
if (!state.active || !state.terms.has(state.active)) {
|
|
70
|
-
|
|
83
|
+
const savedActive = localStorage.getItem('clideck.activeSessionId');
|
|
84
|
+
const nextId = savedActive && liveIds.has(savedActive) ? savedActive : msg.list[0]?.id;
|
|
85
|
+
if (nextId) select(nextId);
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
break;
|
|
@@ -110,7 +125,8 @@ function connect() {
|
|
|
110
125
|
case 'session.history': {
|
|
111
126
|
const entry = state.terms.get(msg.id);
|
|
112
127
|
if (msg.replay && reconnectReplaySkip?.has(msg.id) && entry) break;
|
|
113
|
-
|
|
128
|
+
const historyText = normalizeTerminalHistoryText(msg.text);
|
|
129
|
+
if (entry && !entry.queue(historyText)) entry.term.write(historyText);
|
|
114
130
|
updatePreview(msg.id);
|
|
115
131
|
break;
|
|
116
132
|
}
|
|
@@ -209,7 +225,7 @@ function connect() {
|
|
|
209
225
|
break;
|
|
210
226
|
}
|
|
211
227
|
case 'telemetry.autosetup.result': {
|
|
212
|
-
const toast = document.querySelector(`[data-setup-preset="${msg.presetId}"]`);
|
|
228
|
+
const toast = document.querySelector(msg.commandId ? `[data-command-id="${msg.commandId}"]` : `[data-setup-preset="${msg.presetId}"]`);
|
|
213
229
|
if (!toast) break;
|
|
214
230
|
const actionsEl = toast.querySelector('.setup-actions');
|
|
215
231
|
if (msg.success) {
|
|
@@ -240,7 +256,7 @@ function connect() {
|
|
|
240
256
|
toast.remove();
|
|
241
257
|
};
|
|
242
258
|
} else {
|
|
243
|
-
shownSetup.delete(msg.presetId);
|
|
259
|
+
shownSetup.delete(msg.commandId || msg.presetId);
|
|
244
260
|
const btn = toast.querySelector('.auto-setup-btn');
|
|
245
261
|
btn.textContent = 'Failed — configure manually';
|
|
246
262
|
btn.className = 'auto-setup-btn flex-1 px-3 py-2 text-xs font-medium bg-red-600/20 text-red-400 border border-red-500/30 rounded-lg cursor-default';
|
|
@@ -469,11 +485,12 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
469
485
|
if (!cmd) return;
|
|
470
486
|
// Skip if telemetry is already configured via settings
|
|
471
487
|
if (cmd.telemetryEnabled && cmd.telemetryStatus?.ok) return;
|
|
472
|
-
const
|
|
473
|
-
|
|
488
|
+
const preset = presetForCommand(cmd);
|
|
489
|
+
if (!preset) return;
|
|
474
490
|
const setupRaw = preset.telemetrySetup || preset.pluginSetup;
|
|
475
|
-
|
|
476
|
-
shownSetup.
|
|
491
|
+
const setupKey = commandId || preset.presetId;
|
|
492
|
+
if (!setupRaw || shownSetup.has(setupKey)) return;
|
|
493
|
+
shownSetup.add(setupKey);
|
|
477
494
|
|
|
478
495
|
const port = location.port || '4000';
|
|
479
496
|
const setupText = setupRaw.replace(/\{\{port\}\}/g, port);
|
|
@@ -512,7 +529,7 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
512
529
|
</div>`;
|
|
513
530
|
|
|
514
531
|
toast.querySelectorAll('.dismiss-btn').forEach(b => b.onclick = () => {
|
|
515
|
-
shownSetup.delete(
|
|
532
|
+
shownSetup.delete(setupKey);
|
|
516
533
|
toast.remove();
|
|
517
534
|
});
|
|
518
535
|
|
|
@@ -522,7 +539,7 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
522
539
|
autoBtn.disabled = true;
|
|
523
540
|
autoBtn.innerHTML = `<svg class="w-3.5 h-3.5 inline animate-spin -mt-px mr-1.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2a10 10 0 0 1 10 10"/></svg>Configuring…`;
|
|
524
541
|
autoBtn.className = 'auto-setup-btn flex-1 px-3 py-2 text-xs font-medium bg-slate-700 text-slate-300 rounded-lg cursor-wait';
|
|
525
|
-
send({ type: 'telemetry.autosetup', presetId: preset.presetId });
|
|
542
|
+
send({ type: 'telemetry.autosetup', presetId: preset.presetId, commandId });
|
|
526
543
|
};
|
|
527
544
|
}
|
|
528
545
|
|