feat(connectd): companion cwd (writable session dir for the WhatsApp bridge)

companion.cwd (created if missing; ~/$HOME expanded) sets the sidecar's working directory so the
WhatsApp bridge writes its session store to a writable path (~/.neuron/whatsapp), never the read-only
app bundle. Typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tim Lingo
2026-06-27 14:56:38 -05:00
parent edf276c51e
commit 995b9a8548
2 changed files with 12 additions and 1 deletions
+8 -1
View File
@@ -5,6 +5,8 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
import { createHash } from "node:crypto";
import { join, dirname } from "node:path";
import { spawn, type ChildProcess } from "node:child_process";
import { mkdirSync } from "node:fs";
import { homedir } from "node:os";
import type { ConnectorsConfig, ServerConfig } from "./config.js";
import { saveConfig } from "./config.js";
import { KeychainOAuthProvider, takePendingAuthUrl } from "./oauth.js";
@@ -190,10 +192,15 @@ export class ConnectorBridge {
};
if (await ping()) return; // already running (this session or a prior one)
if (this.companions.has(id)) return; // we already started it
let cwd: string | undefined;
if (comp.cwd) {
cwd = comp.cwd.replace(/^~(?=\/|$)/, homedir()).replace(/^\$HOME(?=\/|$)/, homedir());
try { mkdirSync(cwd, { recursive: true }); } catch { /* best effort — spawn will surface a real failure */ }
}
const child = spawn(
resolveRuntimeCommand(comp.command),
(comp.args ?? []).map(resolveBundledArg),
{ env: { ...process.env, ...(comp.env ?? {}) } as Record<string, string>, stdio: "ignore" },
{ env: { ...process.env, ...(comp.env ?? {}) } as Record<string, string>, stdio: "ignore", cwd },
);
this.companions.set(id, child);
child.on("exit", () => this.companions.delete(id));
+4
View File
@@ -20,6 +20,10 @@ export interface ServerConfig {
args?: string[];
env?: Record<string, string>;
healthUrl?: string;
// Working directory for the sidecar (created if missing). The WhatsApp bridge writes its session
// store relative to cwd, so this must be writable (e.g. ~/.neuron/whatsapp) — never the read-only
// app bundle. "~" / "$HOME" at the start are expanded to the user's home.
cwd?: string;
};
// http (Phase 3)
url?: string;