Skip to content

Commit ed8ef10

Browse files
vakovalskiiclaude
andcommitted
v6.3.3: Merge PR #14 & #19, fix Windows errors, close issues
Merged PRs: - PR #14 (@tenishevnikita): Fix project key encoding — use /[^a-zA-Z0-9-]/g to match Claude Code's encoding (fixes paths with underscores). Closes #16. - PR #19 (@dimstunt): cmux proper Resume (new-workspace) and Focus (CMUX_WORKSPACE_ID env var) support. Windows fixes (#15): - Skip ps/grep process scanning on win32 (no ps aux on Windows) - Add stdio:pipe to lsof calls to suppress error output - Prevents mojibake error spam in PowerShell Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cd70c43 commit ed8ef10

3 files changed

Lines changed: 53 additions & 19 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codedash-app",
3-
"version": "6.3.2",
3+
"version": "6.3.3",
44
"description": "Dashboard + CLI for Claude Code, Codex & OpenCode sessions. View, search, resume, convert, handoff between agents.",
55
"bin": {
66
"codedash": "./bin/cli.js"

src/data.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ function decodeCursorProjectFolderKey(proj) {
283283
for (var j = 0; j < dirs.length; j++) {
284284
var d = dirs[j];
285285
// Cursor encodes both / and . as -, so compare against encoded dir name
286-
var encoded = d.replace(/[\/\.]/g, '-');
286+
var encoded = d.replace(/[^a-zA-Z0-9-]/g, '-');
287287
if (enc === encoded || (enc.startsWith(encoded) && (enc.length === encoded.length || enc[encoded.length] === '-'))) {
288288
matched = d;
289289
break;
@@ -709,7 +709,7 @@ function loadSessions() {
709709
// Enrich Claude sessions with detail file info
710710
for (const [sid, s] of Object.entries(sessions)) {
711711
if (s.tool !== 'claude') continue;
712-
const projectKey = s.project.replace(/[\/\.]/g, '-');
712+
const projectKey = s.project.replace(/[^a-zA-Z0-9-]/g, '-');
713713
const sessionFile = path.join(PROJECTS_DIR, projectKey, `${sid}.jsonl`);
714714
if (fs.existsSync(sessionFile)) {
715715
s.has_detail = true;
@@ -866,7 +866,7 @@ function deleteSession(sessionId, project) {
866866
const deleted = [];
867867

868868
// 1. Remove session JSONL file from project dir
869-
const projectKey = project.replace(/[\/\.]/g, '-');
869+
const projectKey = project.replace(/[^a-zA-Z0-9-]/g, '-');
870870
const sessionFile = path.join(PROJECTS_DIR, projectKey, `${sessionId}.jsonl`);
871871
if (fs.existsSync(sessionFile)) {
872872
fs.unlinkSync(sessionFile);
@@ -935,7 +935,7 @@ function getGitCommits(projectDir, fromTs, toTs) {
935935
}
936936

937937
function exportSessionMarkdown(sessionId, project) {
938-
const projectKey = project.replace(/[\/\.]/g, '-');
938+
const projectKey = project.replace(/[^a-zA-Z0-9-]/g, '-');
939939
const sessionFile = path.join(PROJECTS_DIR, projectKey, `${sessionId}.jsonl`);
940940

941941
if (!fs.existsSync(sessionFile)) {
@@ -971,7 +971,7 @@ function exportSessionMarkdown(sessionId, project) {
971971
function findSessionFile(sessionId, project) {
972972
// Try Claude projects dir
973973
if (project) {
974-
const projectKey = project.replace(/[\/\.]/g, '-');
974+
const projectKey = project.replace(/[^a-zA-Z0-9-]/g, '-');
975975
const claudeFile = path.join(PROJECTS_DIR, projectKey, `${sessionId}.jsonl`);
976976
if (fs.existsSync(claudeFile)) return { file: claudeFile, format: 'claude' };
977977
}
@@ -1468,10 +1468,13 @@ function getActiveSessions() {
14681468
{ pattern: 'cursor-agent', tool: 'cursor', match: /cursor-agent/ },
14691469
];
14701470

1471+
// Skip process scanning on Windows (no ps/grep)
1472+
if (process.platform === 'win32') return active;
1473+
14711474
try {
14721475
const psOut = execSync(
14731476
'ps aux 2>/dev/null | grep -E "claude|codex|opencode|kiro-cli|cursor-agent" | grep -v grep || true',
1474-
{ encoding: 'utf8', timeout: 3000 }
1477+
{ encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }
14751478
);
14761479

14771480
for (const line of psOut.split('\n').filter(Boolean)) {
@@ -1513,7 +1516,7 @@ function getActiveSessions() {
15131516
// Try to get cwd from lsof if not from PID file
15141517
if (!cwd) {
15151518
try {
1516-
const lsofOut = execSync(`lsof -d cwd -p ${pid} -Fn 2>/dev/null`, { encoding: 'utf8', timeout: 2000 });
1519+
const lsofOut = execSync(`lsof -d cwd -p ${pid} -Fn 2>/dev/null`, { encoding: 'utf8', timeout: 2000, stdio: ['pipe', 'pipe', 'pipe'] });
15171520
const match = lsofOut.match(/\nn(\/[^\n]+)/);
15181521
if (match) cwd = match[1];
15191522
} catch {}

src/terminals.js

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
const fs = require('fs');
44
const { execSync, exec } = require('child_process');
55

6+
// Run cmux CLI command via osascript — needed because codedash runs as a detached server
7+
// and cmux rejects direct socket connections from processes not inside a cmux terminal
8+
function cmuxExec(args) {
9+
const escaped = args.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
10+
return execSync(`osascript -e 'do shell script "cmux ${escaped}"'`, { encoding: 'utf8', timeout: 5000 }).trim();
11+
}
12+
613
// ── Detect available terminals ──────────────────────────────
714

815
function detectTerminals() {
@@ -116,10 +123,22 @@ function openInTerminal(sessionId, tool, flags, projectDir, terminalId) {
116123
case 'alacritty':
117124
exec(`alacritty -e bash -c '${fullCmd}; exec bash'`);
118125
break;
119-
case 'cmux':
120-
// cmux — just activate it, user manages sessions inside
121-
execSync(`osascript -e 'tell application "cmux" to activate'`);
126+
case 'cmux': {
127+
// cmux — open new workspace with resume command, then switch to it
128+
try {
129+
const cwdArg = projectDir ? ` --cwd ${JSON.stringify(projectDir)}` : '';
130+
const cmdArg = ` --command ${JSON.stringify(cmd)}`;
131+
const out = cmuxExec(`new-workspace${cwdArg}${cmdArg}`);
132+
const wsMatch = out.match(/workspace:\d+/);
133+
if (wsMatch) {
134+
cmuxExec(`select-workspace --workspace ${wsMatch[0]}`);
135+
}
136+
execSync(`osascript -e 'tell application "cmux" to activate'`, { stdio: 'pipe', timeout: 2000 });
137+
} catch {
138+
execSync(`osascript -e 'tell application "cmux" to activate'`);
139+
}
122140
break;
141+
}
123142
case 'iterm2':
124143
default: {
125144
const script = `
@@ -174,6 +193,24 @@ function openInTerminal(sessionId, tool, flags, projectDir, terminalId) {
174193
}
175194
}
176195

196+
// ── Focus cmux workspace by PID → env var ───────────────────
197+
198+
function focusCmuxWorkspace(pid) {
199+
if (pid) {
200+
try {
201+
const psEnv = execSync(`ps eww -p ${pid} 2>/dev/null`, { encoding: 'utf8', timeout: 2000 });
202+
const wsMatch = psEnv.match(/CMUX_WORKSPACE_ID=([0-9A-F-]{36})/i);
203+
if (wsMatch) {
204+
cmuxExec(`select-workspace --workspace ${wsMatch[1]}`);
205+
execSync(`osascript -e 'tell application "cmux" to activate'`, { stdio: 'pipe', timeout: 2000 });
206+
return { ok: true, terminal: 'cmux' };
207+
}
208+
} catch {}
209+
}
210+
execSync(`osascript -e 'tell application "cmux" to activate'`, { stdio: 'pipe', timeout: 2000 });
211+
return { ok: true, terminal: 'cmux' };
212+
}
213+
177214
// ── Focus existing terminal by PID ──────────────────────────
178215

179216
function focusTerminalByPid(pid) {
@@ -209,15 +246,9 @@ function focusTerminalByPid(pid) {
209246

210247
termLog('FOCUS', `detected terminal from parent chain: ${detectedTerminal || '(none)'}`);
211248

212-
// cmux: activate + flash the surface
249+
// cmux: select workspace by PID's CMUX_WORKSPACE_ID env var
213250
if (detectedTerminal === 'cmux') {
214-
try {
215-
execSync(`osascript -e 'tell application "cmux" to activate'`, { stdio: 'pipe', timeout: 2000 });
216-
try {
217-
execSync(`cmux trigger-flash --surface ${ttyOut.replace('tty','')} 2>/dev/null`, { stdio: 'pipe', timeout: 2000 });
218-
} catch {}
219-
return { ok: true, terminal: 'cmux' };
220-
} catch {}
251+
return focusCmuxWorkspace(pid);
221252
}
222253

223254
// iTerm2: activate and select the right tab/window by tty

0 commit comments

Comments
 (0)