relay-companion 0.1.25 → 0.1.26
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/overlay/host-select.cjs +15 -6
- package/overlay/main.cjs +38 -5
- package/package.json +1 -1
- package/src/claude-session-writer.js +8 -4
- package/src/host-paths.js +29 -18
package/overlay/host-select.cjs
CHANGED
|
@@ -23,8 +23,12 @@ function isRelayBundle(bundle) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Cold-start choice when NEITHER agent is running: pick by what's installed.
|
|
26
|
-
//
|
|
27
|
-
|
|
26
|
+
// macOS preserves the original preference (Codex, then Claude). Windows is
|
|
27
|
+
// Claude-only for now: the Codex materializer still depends on macOS Codex
|
|
28
|
+
// Desktop paths/app-server behavior, while Claude's claude:// resume handler is
|
|
29
|
+
// registered by Claude Desktop on Windows.
|
|
30
|
+
function defaultClickHost({ codexInstalled = false, claudeInstalled = false, platform = process.platform } = {}) {
|
|
31
|
+
if (platform === "win32") return "claude";
|
|
28
32
|
if (codexInstalled) return "codex";
|
|
29
33
|
if (claudeInstalled) return "claude";
|
|
30
34
|
return "codex";
|
|
@@ -50,17 +54,22 @@ function chooseClickHost({
|
|
|
50
54
|
codexRunning = false,
|
|
51
55
|
codexInstalled = false,
|
|
52
56
|
claudeInstalled = false,
|
|
57
|
+
platform = process.platform,
|
|
53
58
|
} = {}) {
|
|
59
|
+
const codexSupported = platform !== "win32";
|
|
60
|
+
const effectiveCodexRunning = codexSupported && codexRunning;
|
|
61
|
+
const effectiveCodexInstalled = codexSupported && codexInstalled;
|
|
62
|
+
|
|
54
63
|
if (frontHost) return frontHost;
|
|
55
64
|
|
|
56
65
|
const capturedHostIsFresh = lastHost && now - lastHostSeenAt < 2500;
|
|
57
66
|
if ((!bundle || isRelayBundle(bundle)) && capturedHostIsFresh) return lastHost;
|
|
58
67
|
|
|
59
|
-
if (claudeRunning && !
|
|
60
|
-
if (
|
|
61
|
-
if (claudeRunning &&
|
|
68
|
+
if (claudeRunning && !effectiveCodexRunning) return "claude";
|
|
69
|
+
if (effectiveCodexRunning && !claudeRunning) return "codex";
|
|
70
|
+
if (claudeRunning && effectiveCodexRunning && lastHost) return lastHost;
|
|
62
71
|
|
|
63
|
-
return defaultClickHost({ codexInstalled, claudeInstalled });
|
|
72
|
+
return defaultClickHost({ codexInstalled: effectiveCodexInstalled, claudeInstalled, platform });
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
module.exports = { hostFromBundle, isRelayBundle, defaultClickHost, chooseClickHost };
|
package/overlay/main.cjs
CHANGED
|
@@ -24,10 +24,10 @@ const { app, BrowserWindow, Menu, Tray, ipcMain, nativeImage, shell, screen } =
|
|
|
24
24
|
const fs = require("node:fs");
|
|
25
25
|
const os = require("node:os");
|
|
26
26
|
const path = require("node:path");
|
|
27
|
-
const { execFile, spawn, pathToFileURL } = (() => {
|
|
27
|
+
const { execFile, execFileSync, spawn, pathToFileURL } = (() => {
|
|
28
28
|
const cp = require("node:child_process");
|
|
29
29
|
const url = require("node:url");
|
|
30
|
-
return { execFile: cp.execFile, spawn: cp.spawn, pathToFileURL: url.pathToFileURL };
|
|
30
|
+
return { execFile: cp.execFile, execFileSync: cp.execFileSync, spawn: cp.spawn, pathToFileURL: url.pathToFileURL };
|
|
31
31
|
})();
|
|
32
32
|
const { hostFromBundle, chooseClickHost } = require("./host-select.cjs");
|
|
33
33
|
|
|
@@ -531,6 +531,11 @@ function rememberForegroundHost(host) {
|
|
|
531
531
|
function appInstalled(appName) {
|
|
532
532
|
const name = String(appName || "").replace(/[^A-Za-z0-9 ._-]/g, "");
|
|
533
533
|
if (!name) return false;
|
|
534
|
+
if (process.platform === "win32" && /^claude$/i.test(name)) {
|
|
535
|
+
if (windowsUriSchemeRegistered("claude")) return true;
|
|
536
|
+
const appDataClaude = process.env.APPDATA && path.join(process.env.APPDATA, "Claude");
|
|
537
|
+
if (appDataClaude && fs.existsSync(appDataClaude)) return true;
|
|
538
|
+
}
|
|
534
539
|
let candidates = [];
|
|
535
540
|
if (process.platform === "darwin") {
|
|
536
541
|
candidates = [
|
|
@@ -548,6 +553,23 @@ function appInstalled(appName) {
|
|
|
548
553
|
return candidates.some((candidate) => fs.existsSync(candidate));
|
|
549
554
|
}
|
|
550
555
|
|
|
556
|
+
function windowsUriSchemeRegistered(scheme) {
|
|
557
|
+
if (process.platform !== "win32") return false;
|
|
558
|
+
const clean = String(scheme || "").replace(/[^A-Za-z0-9+.-]/g, "");
|
|
559
|
+
if (!clean) return false;
|
|
560
|
+
for (const hive of ["HKCU\\Software\\Classes", "HKCR"]) {
|
|
561
|
+
try {
|
|
562
|
+
const out = execFileSync("reg.exe", ["query", `${hive}\\${clean}\\shell\\open\\command`, "/ve"], {
|
|
563
|
+
encoding: "utf8",
|
|
564
|
+
timeout: 1000,
|
|
565
|
+
windowsHide: true,
|
|
566
|
+
});
|
|
567
|
+
if (String(out || "").trim()) return true;
|
|
568
|
+
} catch {}
|
|
569
|
+
}
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
|
|
551
573
|
// Resolve which host a click opens into from the current frontmost bundle, folding
|
|
552
574
|
// in the polled running-process state (claudeRunning/codexRunning) so a host sitting
|
|
553
575
|
// on another macOS Space still wins over cold-starting the other. Shared by every
|
|
@@ -565,6 +587,7 @@ function resolveClickHost(bundle) {
|
|
|
565
587
|
codexRunning,
|
|
566
588
|
codexInstalled: appInstalled("Codex"),
|
|
567
589
|
claudeInstalled: appInstalled("Claude"),
|
|
590
|
+
platform: process.platform,
|
|
568
591
|
});
|
|
569
592
|
}
|
|
570
593
|
|
|
@@ -610,11 +633,20 @@ function claudeLogSawResume(sessionId, fromOffset) {
|
|
|
610
633
|
// local_<uuid>.json within ~1s of actually focusing it — and keeps doing so
|
|
611
634
|
// even when main.log has gone dead (observed live: log frozen for over an hour
|
|
612
635
|
// while resumes worked, which made a log-only check fire false fallbacks).
|
|
613
|
-
|
|
636
|
+
function claudeDesktopBaseDir() {
|
|
637
|
+
if (process.platform === "win32") {
|
|
638
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude");
|
|
639
|
+
}
|
|
640
|
+
return path.join(os.homedir(), "Library", "Application Support", "Claude");
|
|
641
|
+
}
|
|
642
|
+
function claudeDesktopSessionsDir() {
|
|
643
|
+
return process.env.CLAUDE_DESKTOP_SESSIONS_DIR || path.join(claudeDesktopBaseDir(), "claude-code-sessions");
|
|
644
|
+
}
|
|
614
645
|
function claudeSessionMetaPath(sessionId) {
|
|
646
|
+
const sessionsDir = claudeDesktopSessionsDir();
|
|
615
647
|
try {
|
|
616
|
-
for (const org of fs.readdirSync(
|
|
617
|
-
const orgDir = path.join(
|
|
648
|
+
for (const org of fs.readdirSync(sessionsDir)) {
|
|
649
|
+
const orgDir = path.join(sessionsDir, org);
|
|
618
650
|
let orgStat;
|
|
619
651
|
try {
|
|
620
652
|
orgStat = fs.statSync(orgDir);
|
|
@@ -640,6 +672,7 @@ function claudeLastFocusedAt(metaPath) {
|
|
|
640
672
|
function openClaudeDeepLinkVerified(url, fallbackUrl, label) {
|
|
641
673
|
const fire = () =>
|
|
642
674
|
shell.openExternal(url).catch((error) => console.error("[overlay] openExternal failed:", error && error.message));
|
|
675
|
+
if (process.platform !== "darwin") return fire();
|
|
643
676
|
const sessionId = claudeSessionIdFromUrl(url);
|
|
644
677
|
if (!sessionId) return fire();
|
|
645
678
|
const metaPath = claudeSessionMetaPath(sessionId);
|
package/package.json
CHANGED
|
@@ -268,16 +268,20 @@ export function importClaudeDesktopSession({ sessionId, activate = process.env.R
|
|
|
268
268
|
if (process.env.RELAY_IMPORT_CLAUDE_DESKTOP === "0") {
|
|
269
269
|
return { attempted: false, reason: "disabled", deepLink };
|
|
270
270
|
}
|
|
271
|
-
if (process.platform !== "darwin") {
|
|
271
|
+
if (process.platform !== "darwin" && process.platform !== "win32") {
|
|
272
272
|
return { attempted: false, reason: "unsupported-platform", deepLink };
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
const
|
|
276
|
-
const
|
|
275
|
+
const command = process.platform === "win32" ? "cmd.exe" : "open";
|
|
276
|
+
const args =
|
|
277
|
+
process.platform === "win32"
|
|
278
|
+
? ["/c", "start", "", deepLink]
|
|
279
|
+
: activate ? [deepLink] : ["-g", deepLink];
|
|
280
|
+
const result = spawnSync(command, args, { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
277
281
|
return {
|
|
278
282
|
attempted: true,
|
|
279
283
|
deepLink,
|
|
280
|
-
command
|
|
284
|
+
command,
|
|
281
285
|
args,
|
|
282
286
|
status: result.status,
|
|
283
287
|
signal: result.signal,
|
package/src/host-paths.js
CHANGED
|
@@ -12,20 +12,29 @@ export const DEFAULT_CODEX_HOME = path.join(os.homedir(), ".codex");
|
|
|
12
12
|
export const DEFAULT_CODEX_GLOBAL_STATE = path.join(DEFAULT_CODEX_HOME, ".codex-global-state.json");
|
|
13
13
|
export const DEFAULT_CODEX_STATE_DB = path.join(DEFAULT_CODEX_HOME, "state_5.sqlite");
|
|
14
14
|
export const DEFAULT_CLAUDE_HOME = path.join(os.homedir(), ".claude");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
);
|
|
15
|
+
|
|
16
|
+
function defaultWindowsAppData({ env = process.env, homedir = os.homedir() } = {}) {
|
|
17
|
+
return env.APPDATA || path.win32.join(homedir, "AppData", "Roaming");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function defaultClaudeDesktopBaseDir({ platform = process.platform, env = process.env, homedir = os.homedir() } = {}) {
|
|
21
|
+
if (platform === "win32") return path.win32.join(defaultWindowsAppData({ env, homedir }), "Claude");
|
|
22
|
+
if (platform === "darwin") return path.join(homedir, "Library", "Application Support", "Claude");
|
|
23
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), "Claude");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function defaultClaudeDesktopConfigPath(options = {}) {
|
|
27
|
+
const pathLib = options.platform === "win32" ? path.win32 : path;
|
|
28
|
+
return pathLib.join(defaultClaudeDesktopBaseDir(options), "claude_desktop_config.json");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function defaultClaudeDesktopSessionsDir(options = {}) {
|
|
32
|
+
const pathLib = options.platform === "win32" ? path.win32 : path;
|
|
33
|
+
return pathLib.join(defaultClaudeDesktopBaseDir(options), "claude-code-sessions");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_CLAUDE_DESKTOP_CONFIG = defaultClaudeDesktopConfigPath();
|
|
37
|
+
export const DEFAULT_CLAUDE_DESKTOP_SESSIONS = defaultClaudeDesktopSessionsDir();
|
|
29
38
|
|
|
30
39
|
// Durable companion store dir. The cloud companion stages packets + state under
|
|
31
40
|
// RELAY_HOME/RELAY_COMPANION_HOME (see notifications.companionHome()); the engine
|
|
@@ -58,10 +67,12 @@ export function claudeProjectsDir() {
|
|
|
58
67
|
return process.env.CLAUDE_PROJECTS_DIR || path.join(claudeHome(), "projects");
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
export function claudeDesktopConfigPath() {
|
|
62
|
-
|
|
70
|
+
export function claudeDesktopConfigPath(options = {}) {
|
|
71
|
+
const env = options.env || process.env;
|
|
72
|
+
return env.CLAUDE_DESKTOP_CONFIG || defaultClaudeDesktopConfigPath({ env, ...options });
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
export function claudeDesktopSessionsDir() {
|
|
66
|
-
|
|
75
|
+
export function claudeDesktopSessionsDir(options = {}) {
|
|
76
|
+
const env = options.env || process.env;
|
|
77
|
+
return env.CLAUDE_DESKTOP_SESSIONS_DIR || defaultClaudeDesktopSessionsDir({ env, ...options });
|
|
67
78
|
}
|